Skip to content

Commit f11fd2c

Browse files
authored
Merge pull request #24 from microsoft/safe-dropdown
Safe dropdown
2 parents 8f785d5 + d78e386 commit f11fd2c

4 files changed

Lines changed: 96 additions & 52 deletions

File tree

docs/dist/idocs.editor.umd.js

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1430,13 +1430,13 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
14301430
<label>
14311431
<span class="vega-bind-name">\${spec.label || spec.name}</span>
14321432
<select class="vega-bind-select" id="\${spec.name}" name="\${spec.name}" \${spec.multiple ? "multiple" : ""} size="\${spec.size || 1}">
1433-
\${getOptions(spec.multiple ?? false, spec.options ?? [], spec.value ?? (spec.multiple ? [] : ""))}
14341433
</select>
14351434
</label>
14361435
</div>
14371436
</form>\`;
14381437
container.innerHTML = html;
14391438
const element = container.querySelector("select");
1439+
setSelectOptions(element, spec.multiple ?? false, spec.options ?? [], spec.value ?? (spec.multiple ? [] : ""));
14401440
const dropdownInstance = { id: container.id, spec, element };
14411441
dropdownInstances.push(dropdownInstance);
14421442
} catch (e) {
@@ -1481,12 +1481,16 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
14811481
if (hasFieldName) {
14821482
const options = Array.from(uniqueOptions);
14831483
const existingSelection = spec.multiple ? Array.from(element.selectedOptions).map((option) => option.value) : element.value;
1484-
element.innerHTML = getOptions(spec.multiple ?? false, options, existingSelection);
1484+
setSelectOptions(element, spec.multiple ?? false, options, existingSelection);
14851485
if (!spec.multiple) {
14861486
element.value = ((_b = batch[spec.name]) == null ? void 0 : _b.value) || options[0];
14871487
}
14881488
} else {
1489-
element.innerHTML = \`<option value="">Field "\${dynamicOptions.fieldName}" not found</option>\`;
1489+
element.innerHTML = "";
1490+
const errorOption = document.createElement("option");
1491+
errorOption.value = "";
1492+
errorOption.textContent = \`Field "\${dynamicOptions.fieldName}" not found\`;
1493+
element.appendChild(errorOption);
14901494
element.value = "";
14911495
}
14921496
}
@@ -1528,8 +1532,9 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
15281532
return instances;
15291533
}
15301534
};
1531-
function getOptions(multiple, options, selected) {
1532-
if (!options) {
1535+
function setSelectOptions(selectElement, multiple, options, selected) {
1536+
selectElement.innerHTML = "";
1537+
if (!options || options.length === 0) {
15331538
if (multiple) {
15341539
if (Array.isArray(selected)) {
15351540
options = selected;
@@ -1544,18 +1549,22 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
15441549
}
15451550
}
15461551
}
1547-
if (!options) {
1548-
return "";
1552+
if (!options || options.length === 0) {
1553+
return;
15491554
}
1550-
return options.map((option) => {
1551-
let attr = "";
1555+
options.forEach((optionValue) => {
1556+
const optionElement = document.createElement("option");
1557+
optionElement.value = optionValue;
1558+
optionElement.textContent = optionValue;
1559+
let isSelected = false;
15521560
if (multiple) {
1553-
attr = (selected || []).includes(option) ? "selected" : "";
1561+
isSelected = (selected || []).includes(optionValue);
15541562
} else {
1555-
attr = selected === option ? "selected" : "";
1563+
isSelected = selected === optionValue;
15561564
}
1557-
return \`<option value="\${option}" \${attr}>\${option}</option>\`;
1558-
}).join("\\n");
1565+
optionElement.selected = isSelected;
1566+
selectElement.appendChild(optionElement);
1567+
});
15591568
}
15601569
/*!
15611570
* Copyright (c) Microsoft Corporation.

docs/dist/idocs.host.umd.js

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1430,13 +1430,13 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
14301430
<label>
14311431
<span class="vega-bind-name">\${spec.label || spec.name}</span>
14321432
<select class="vega-bind-select" id="\${spec.name}" name="\${spec.name}" \${spec.multiple ? "multiple" : ""} size="\${spec.size || 1}">
1433-
\${getOptions(spec.multiple ?? false, spec.options ?? [], spec.value ?? (spec.multiple ? [] : ""))}
14341433
</select>
14351434
</label>
14361435
</div>
14371436
</form>\`;
14381437
container.innerHTML = html;
14391438
const element = container.querySelector("select");
1439+
setSelectOptions(element, spec.multiple ?? false, spec.options ?? [], spec.value ?? (spec.multiple ? [] : ""));
14401440
const dropdownInstance = { id: container.id, spec, element };
14411441
dropdownInstances.push(dropdownInstance);
14421442
} catch (e) {
@@ -1481,12 +1481,16 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
14811481
if (hasFieldName) {
14821482
const options = Array.from(uniqueOptions);
14831483
const existingSelection = spec.multiple ? Array.from(element.selectedOptions).map((option) => option.value) : element.value;
1484-
element.innerHTML = getOptions(spec.multiple ?? false, options, existingSelection);
1484+
setSelectOptions(element, spec.multiple ?? false, options, existingSelection);
14851485
if (!spec.multiple) {
14861486
element.value = ((_b = batch[spec.name]) == null ? void 0 : _b.value) || options[0];
14871487
}
14881488
} else {
1489-
element.innerHTML = \`<option value="">Field "\${dynamicOptions.fieldName}" not found</option>\`;
1489+
element.innerHTML = "";
1490+
const errorOption = document.createElement("option");
1491+
errorOption.value = "";
1492+
errorOption.textContent = \`Field "\${dynamicOptions.fieldName}" not found\`;
1493+
element.appendChild(errorOption);
14901494
element.value = "";
14911495
}
14921496
}
@@ -1528,8 +1532,9 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
15281532
return instances;
15291533
}
15301534
};
1531-
function getOptions(multiple, options, selected) {
1532-
if (!options) {
1535+
function setSelectOptions(selectElement, multiple, options, selected) {
1536+
selectElement.innerHTML = "";
1537+
if (!options || options.length === 0) {
15331538
if (multiple) {
15341539
if (Array.isArray(selected)) {
15351540
options = selected;
@@ -1544,18 +1549,22 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
15441549
}
15451550
}
15461551
}
1547-
if (!options) {
1548-
return "";
1552+
if (!options || options.length === 0) {
1553+
return;
15491554
}
1550-
return options.map((option) => {
1551-
let attr = "";
1555+
options.forEach((optionValue) => {
1556+
const optionElement = document.createElement("option");
1557+
optionElement.value = optionValue;
1558+
optionElement.textContent = optionValue;
1559+
let isSelected = false;
15521560
if (multiple) {
1553-
attr = (selected || []).includes(option) ? "selected" : "";
1561+
isSelected = (selected || []).includes(optionValue);
15541562
} else {
1555-
attr = selected === option ? "selected" : "";
1563+
isSelected = selected === optionValue;
15561564
}
1557-
return \`<option value="\${option}" \${attr}>\${option}</option>\`;
1558-
}).join("\\n");
1565+
optionElement.selected = isSelected;
1566+
selectElement.appendChild(optionElement);
1567+
});
15591568
}
15601569
/*!
15611570
* Copyright (c) Microsoft Corporation.

docs/dist/idocs.sandbox.umd.js

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1030,13 +1030,13 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
10301030
<label>
10311031
<span class="vega-bind-name">\${spec.label || spec.name}</span>
10321032
<select class="vega-bind-select" id="\${spec.name}" name="\${spec.name}" \${spec.multiple ? "multiple" : ""} size="\${spec.size || 1}">
1033-
\${getOptions(spec.multiple ?? false, spec.options ?? [], spec.value ?? (spec.multiple ? [] : ""))}
10341033
</select>
10351034
</label>
10361035
</div>
10371036
</form>\`;
10381037
container.innerHTML = html;
10391038
const element = container.querySelector("select");
1039+
setSelectOptions(element, spec.multiple ?? false, spec.options ?? [], spec.value ?? (spec.multiple ? [] : ""));
10401040
const dropdownInstance = { id: container.id, spec, element };
10411041
dropdownInstances.push(dropdownInstance);
10421042
} catch (e) {
@@ -1081,12 +1081,16 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
10811081
if (hasFieldName) {
10821082
const options = Array.from(uniqueOptions);
10831083
const existingSelection = spec.multiple ? Array.from(element.selectedOptions).map((option) => option.value) : element.value;
1084-
element.innerHTML = getOptions(spec.multiple ?? false, options, existingSelection);
1084+
setSelectOptions(element, spec.multiple ?? false, options, existingSelection);
10851085
if (!spec.multiple) {
10861086
element.value = ((_b = batch[spec.name]) == null ? void 0 : _b.value) || options[0];
10871087
}
10881088
} else {
1089-
element.innerHTML = \`<option value="">Field "\${dynamicOptions.fieldName}" not found</option>\`;
1089+
element.innerHTML = "";
1090+
const errorOption = document.createElement("option");
1091+
errorOption.value = "";
1092+
errorOption.textContent = \`Field "\${dynamicOptions.fieldName}" not found\`;
1093+
element.appendChild(errorOption);
10901094
element.value = "";
10911095
}
10921096
}
@@ -1128,8 +1132,9 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
11281132
return instances;
11291133
}
11301134
};
1131-
function getOptions(multiple, options, selected) {
1132-
if (!options) {
1135+
function setSelectOptions(selectElement, multiple, options, selected) {
1136+
selectElement.innerHTML = "";
1137+
if (!options || options.length === 0) {
11331138
if (multiple) {
11341139
if (Array.isArray(selected)) {
11351140
options = selected;
@@ -1144,18 +1149,22 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
11441149
}
11451150
}
11461151
}
1147-
if (!options) {
1148-
return "";
1152+
if (!options || options.length === 0) {
1153+
return;
11491154
}
1150-
return options.map((option) => {
1151-
let attr = "";
1155+
options.forEach((optionValue) => {
1156+
const optionElement = document.createElement("option");
1157+
optionElement.value = optionValue;
1158+
optionElement.textContent = optionValue;
1159+
let isSelected = false;
11521160
if (multiple) {
1153-
attr = (selected || []).includes(option) ? "selected" : "";
1161+
isSelected = (selected || []).includes(optionValue);
11541162
} else {
1155-
attr = selected === option ? "selected" : "";
1163+
isSelected = selected === optionValue;
11561164
}
1157-
return \`<option value="\${option}" \${attr}>\${option}</option>\`;
1158-
}).join("\\n");
1165+
optionElement.selected = isSelected;
1166+
selectElement.appendChild(optionElement);
1167+
});
11591168
}
11601169
/*!
11611170
* Copyright (c) Microsoft Corporation.

packages/markdown/src/plugins/dropdown.ts

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,15 @@ export const dropdownPlugin: Plugin = {
4949
<label>
5050
<span class="vega-bind-name">${spec.label || spec.name}</span>
5151
<select class="vega-bind-select" id="${spec.name}" name="${spec.name}" ${spec.multiple ? 'multiple' : ''} size="${spec.size || 1}">
52-
${getOptions(spec.multiple ?? false, spec.options ?? [], spec.value ?? (spec.multiple ? [] : ""))}
5352
</select>
5453
</label>
5554
</div>
5655
</form>`;
5756
container.innerHTML = html;
5857
const element = container.querySelector('select') as HTMLSelectElement;
58+
59+
// Safely set the initial options
60+
setSelectOptions(element, spec.multiple ?? false, spec.options ?? [], spec.value ?? (spec.multiple ? [] : ""));
5961

6062
const dropdownInstance: DropdownInstance = { id: container.id, spec, element };
6163
dropdownInstances.push(dropdownInstance);
@@ -103,13 +105,17 @@ ${getOptions(spec.multiple ?? false, spec.options ?? [], spec.value ?? (spec.mul
103105
if (hasFieldName) {
104106
const options = Array.from(uniqueOptions);
105107
const existingSelection = spec.multiple ? Array.from(element.selectedOptions).map(option => option.value) : element.value;
106-
element.innerHTML = getOptions(spec.multiple ?? false, options, existingSelection);
108+
setSelectOptions(element, spec.multiple ?? false, options, existingSelection);
107109
if (!spec.multiple) {
108110
element.value = (batch[spec.name]?.value as string) || options[0];
109111
}
110112
} else {
111113
//if the field doesn't exist, set the select to the first option
112-
element.innerHTML = `<option value="">Field "${dynamicOptions.fieldName}" not found</option>`;
114+
element.innerHTML = '';
115+
const errorOption = document.createElement('option');
116+
errorOption.value = '';
117+
errorOption.textContent = `Field "${dynamicOptions.fieldName}" not found`;
118+
element.appendChild(errorOption);
113119
element.value = '';
114120
}
115121
}
@@ -155,8 +161,11 @@ ${getOptions(spec.multiple ?? false, spec.options ?? [], spec.value ?? (spec.mul
155161
},
156162
};
157163

158-
function getOptions(multiple: boolean, options: string[], selected: string | string[]) {
159-
if (!options) {
164+
function setSelectOptions(selectElement: HTMLSelectElement, multiple: boolean, options: string[], selected: string | string[]) {
165+
// Clear existing options
166+
selectElement.innerHTML = '';
167+
168+
if (!options || options.length === 0) {
160169
if (multiple) {
161170
if (Array.isArray(selected)) {
162171
options = selected as string[];
@@ -171,16 +180,24 @@ function getOptions(multiple: boolean, options: string[], selected: string | str
171180
}
172181
}
173182
}
174-
if (!options) {
175-
return '';
183+
184+
if (!options || options.length === 0) {
185+
return;
176186
}
177-
return options.map((option) => {
178-
let attr = '';
187+
188+
options.forEach((optionValue) => {
189+
const optionElement = document.createElement('option');
190+
optionElement.value = optionValue;
191+
optionElement.textContent = optionValue; // This safely escapes HTML
192+
193+
let isSelected = false;
179194
if (multiple) {
180-
attr = (selected as string[] || []).includes(option) ? 'selected' : '';
195+
isSelected = (selected as string[] || []).includes(optionValue);
181196
} else {
182-
attr = selected === option ? 'selected' : '';
197+
isSelected = selected === optionValue;
183198
}
184-
return `<option value="${option}" ${attr}>${option}</option>`;
185-
}).join('\n');
199+
optionElement.selected = isSelected;
200+
201+
selectElement.appendChild(optionElement);
202+
});
186203
}

0 commit comments

Comments
 (0)