Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Fix finding inputs with IDs containing spaces #3852

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
42 changes: 26 additions & 16 deletions inst/www/shared/shiny.js
Original file line number Diff line number Diff line change
Expand Up @@ -6698,6 +6698,15 @@
return val;
return val.replace(/([!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~])/g, "\\$1");
}
function escapedIdSelector(val) {
val = $escape(val);
if (val.indexOf(" ") === -1)
return "#" + val;
console.warn("[shiny] Invalid ID, should not include spaces.", {
id: val
});
return '[id="'.concat(val, '"]');
}
function mapValues(obj, f4) {
var newObj = {};
Object.keys(obj).forEach(function(key) {
Expand Down Expand Up @@ -23541,21 +23550,22 @@
});
addMessageHandler("inputMessages", /* @__PURE__ */ function() {
var _ref4 = _asyncToGenerator13(/* @__PURE__ */ _regeneratorRuntime13().mark(function _callee9(message) {
var i5, $obj, inputBinding, el, evt;
var i5, inputSelector, $obj, inputBinding, el, evt;
return _regeneratorRuntime13().wrap(function _callee9$(_context9) {
while (1)
switch (_context9.prev = _context9.next) {
case 0:
i5 = 0;
case 1:
if (!(i5 < message.length)) {
_context9.next = 23;
_context9.next = 24;
break;
}
$obj = (0, import_jquery38.default)(".shiny-bound-input#" + $escape(message[i5].id));
inputSelector = ".shiny-bound-input" + escapedIdSelector(message[i5].id);
$obj = (0, import_jquery38.default)(inputSelector);
inputBinding = $obj.data("shiny-input-binding");
if (!($obj.length > 0)) {
_context9.next = 20;
_context9.next = 21;
break;
}
if (!$obj.attr("aria-live"))
Expand All @@ -23566,32 +23576,32 @@
evt.binding = inputBinding;
(0, import_jquery38.default)(el).trigger(evt);
if (evt.isDefaultPrevented()) {
_context9.next = 20;
_context9.next = 21;
break;
}
_context9.prev = 12;
_context9.next = 15;
_context9.prev = 13;
_context9.next = 16;
return inputBinding.receiveMessage(el, evt.message);
case 15:
_context9.next = 20;
case 16:
_context9.next = 21;
break;
case 17:
_context9.prev = 17;
_context9.t0 = _context9["catch"](12);
case 18:
_context9.prev = 18;
_context9.t0 = _context9["catch"](13);
console.error("[shiny] Error in inputBinding.receiveMessage()", {
error: _context9.t0,
binding: inputBinding,
message: evt.message
});
case 20:
case 21:
i5++;
_context9.next = 1;
break;
case 23:
case 24:
case "end":
return _context9.stop();
}
}, _callee9, null, [[12, 17]]);
}, _callee9, null, [[13, 18]]);
}));
return function(_x11) {
return _ref4.apply(this, arguments);
Expand Down Expand Up @@ -23859,7 +23869,7 @@
}
});
function getTabset(id) {
var $tabset = (0, import_jquery38.default)("#" + $escape(id));
var $tabset = (0, import_jquery38.default)(escapedIdSelector(id));
if ($tabset.length === 0)
throw "There is no tabsetPanel (or navbarPage or navlistPanel) with id equal to '" + id + "'";
return $tabset;
Expand Down
4 changes: 2 additions & 2 deletions inst/www/shared/shiny.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion inst/www/shared/shiny.min.js

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions inst/www/shared/shiny.min.js.map

Large diffs are not rendered by default.

14 changes: 11 additions & 3 deletions srcts/src/shiny/shinyapp.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import $ from "jquery";
import { $escape, hasOwnProperty, randomId, scopeExprToFunc } from "../utils";
import {
$escape,
escapedIdSelector,
hasOwnProperty,
randomId,
scopeExprToFunc,
} from "../utils";
import {
getShinyCreateWebsocket,
getShinyOnCustomMessage,
Expand Down Expand Up @@ -733,7 +739,9 @@ class ShinyApp {
async (message: Array<{ id: string; message: unknown }>) => {
// inputMessages should be an array
for (let i = 0; i < message.length; i++) {
const $obj = $(".shiny-bound-input#" + $escape(message[i].id));
const inputSelector =
".shiny-bound-input" + escapedIdSelector(message[i].id);
const $obj = $(inputSelector);
const inputBinding: InputBinding = $obj.data("shiny-input-binding");

// Dispatch the message to the appropriate input object
Expand Down Expand Up @@ -960,7 +968,7 @@ class ShinyApp {
});

function getTabset(id: string) {
const $tabset = $("#" + $escape(id));
const $tabset = $(escapedIdSelector(id));

if ($tabset.length === 0)
throw (
Expand Down
10 changes: 10 additions & 0 deletions srcts/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,15 @@ function $escape(val: string | undefined): string | undefined {
return val.replace(/([!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~])/g, "\\$1");
}

// Returns a selector for an ID, preferring the #id syntax but falling back to
// [id="id"] if the ID contains spaces (with a warning).
function escapedIdSelector(val: string): string {
val = $escape(val);
if (val.indexOf(" ") === -1) return "#" + val;
console.warn("[shiny] Invalid ID, should not include spaces.", { id: val });
return `[id="${val}"]`;
}

// Maps a function over an object, preserving keys. Like the mapValues
// function from lodash.
function mapValues<T extends { [key: string]: any }, R>(
Expand Down Expand Up @@ -401,6 +410,7 @@ export {
asArray,
mergeSort,
$escape,
escapedIdSelector,
mapValues,
isnan,
_equal,
Expand Down
3 changes: 2 additions & 1 deletion srcts/types/src/utils/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ declare function asArray<T>(value: T | T[] | null | undefined): T[];
declare function mergeSort<Item>(list: Item[], sortfunc: (a: Item, b: Item) => boolean | number): Item[];
declare function $escape(val: undefined): undefined;
declare function $escape(val: string): string;
declare function escapedIdSelector(val: string): string;
declare function mapValues<T extends {
[key: string]: any;
}, R>(obj: T, f: (value: MapValuesUnion<T>, key: string, object: typeof obj) => R): MapWithResult<T, R>;
Expand All @@ -26,4 +27,4 @@ declare function updateLabel(labelTxt: string | undefined, labelNode: JQuery<HTM
declare function getComputedLinkColor(el: HTMLElement): string;
declare function isBS3(): boolean;
declare function toLowerCase<T extends string>(str: T): Lowercase<T>;
export { escapeHTML, randomId, strToBool, getStyle, padZeros, roundSignif, parseDate, formatDateUTC, makeResizeFilter, pixelRatio, scopeExprToFunc, asArray, mergeSort, $escape, mapValues, isnan, _equal, equal, compareVersion, updateLabel, getComputedLinkColor, hasOwnProperty, hasDefinedProperty, isBS3, toLowerCase, };
export { escapeHTML, randomId, strToBool, getStyle, padZeros, roundSignif, parseDate, formatDateUTC, makeResizeFilter, pixelRatio, scopeExprToFunc, asArray, mergeSort, $escape, escapedIdSelector, mapValues, isnan, _equal, equal, compareVersion, updateLabel, getComputedLinkColor, hasOwnProperty, hasDefinedProperty, isBS3, toLowerCase, };