@@ -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