set option “selected” attribute from dynamic created option

后端 未结 17 1731
时光说笑
时光说笑 2020-11-30 20:01

I have a dynamically created select option using a javascript function. the select object is


<         


        
17条回答
  •  执念已碎
    2020-11-30 20:43

    Make option defaultSelected

    HTMLOptionElement.defaultSelected = true;     // JS
    $('selector').prop({defaultSelected: true});  // jQuery  
    

    HTMLOptionElement MDN

    If the SELECT element is already added to the document (statically or dynamically), to set an option to Attribute-selected and to make it survive a HTMLFormElement.reset() - defaultSelected is used:

    const EL_country = document.querySelector('#country');
    EL_country.value = 'ID';   // Set SELECT value to 'ID' ("Indonesia")
    EL_country.options[EL_country.selectedIndex].defaultSelected = true; // Add Attribute selected to Option Element
    
    document.forms[0].reset(); // "Indonesia" is still selected

    The above will also work if you build the options dynamically, and than (only afterwards) you want to set one option to be defaultSelected.

    const countries = {
      AF: 'Afghanistan',
      AL: 'Albania',
      HR: 'Croatia',
      ID: 'Indonesia',
      ZW: 'Zimbabwe',
    };
    
    const EL_country = document.querySelector('#country');
    
    // (Bad example. Ideally use .createDocumentFragment() and .appendChild() methods)
    EL_country.innerHTML = Object.keys(countries).reduce((str, key) => str += ``, ''); 
    
    EL_country.value = 'ID';
    EL_country.options[EL_country.selectedIndex].defaultSelected = true;
    
    document.forms[0].reset(); // "Indonesia" is still selected

    Make option defaultSelected while dynamically creating options

    To make an option selected while populating the SELECT Element, use the Option() constructor MDN

    var optionElementReference = new Option(text, value, defaultSelected, selected);

    const countries = {
      AF: 'Afghanistan',
      AL: 'Albania',
      HR: 'Croatia',
      ID: 'Indonesia',     // <<< make this one defaultSelected
      ZW: 'Zimbabwe',
    };
    
    const EL_country = document.querySelector('#country');
    const DF_options = document.createDocumentFragment();
    
    Object.keys(countries).forEach(key => {
      const isIndonesia = key === 'ID';  // Boolean
      DF_options.appendChild(new Option(countries[key], key, isIndonesia, isIndonesia))
    });
    
    EL_country.appendChild(DF_options);
    
    document.forms[0].reset(); // "Indonesia" is still selected

    In the demo above Document.createDocumentFragment is used to prevent rendering elements inside the DOM in a loop. Instead, the fragment (containing all the Options) is appended to the Select only once.


    SELECT.value vs. OPTION.setAttribute vs. OPTION.selected vs. OPTION.defaultSelected

    Although some (older) browsers interpret the OPTION's selected attribute as a "string" state, the WHATWG HTML Specifications html.spec.whatwg.org state that it should represent a Boolean selectedness

    The selectedness of an option element is a boolean state, initially false. Except where otherwise specified, when the element is created, its selectedness must be set to true if the element has a selected attribute.
    html.spec.whatwg.org - Option selectedness

    one can correctly deduce that just the name selected in is enough to set a truthy state.


    Comparison test of the different methods

    const EL_select = document.querySelector('#country');
    const TPL_options = `
      
      
      
      
      
    `;
    
    // https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver/MutationObserver
    const mutationCB = (mutationsList, observer) => {
      mutationsList.forEach(mu => {
        const EL = mu.target;
        if (mu.type === 'attributes') {
          return console.log(`* Attribute ${mu.attributeName} Mutation. ${EL.value}(${EL.text})`);
        }
      });
    };
    
    // (PREPARE SOME TEST FUNCTIONS)
    
    const testOptionsSelectedByProperty = () => {
      const test = 'OPTION with Property selected:';
      try {
        const EL = [...EL_select.options].find(opt => opt.selected);
        console.log(`${test} ${EL.value}(${EL.text}) PropSelectedValue: ${EL.selected}`);
      } catch (e) {
        console.log(`${test} NOT FOUND!`);
      }
    } 
    
    const testOptionsSelectedByAttribute = () => {
      const test = 'OPTION with Attribute selected:'
      try {
        const EL = [...EL_select.options].find(opt => opt.hasAttribute('selected'));
        console.log(`${test} ${EL.value}(${EL.text}) AttrSelectedValue: ${EL.getAttribute('selected')}`);
      } catch (e) {
        console.log(`${test} NOT FOUND!`);
      }
    } 
    
    const testSelect = () => {
      console.log(`SELECT value:${EL_select.value} selectedIndex:${EL_select.selectedIndex}`);
    }
    
    const formReset = () => {
      EL_select.value = '';
      EL_select.innerHTML = TPL_options;
      // Attach MutationObserver to every Option to track if Attribute will change
      [...EL_select.options].forEach(EL_option => {
        const observer = new MutationObserver(mutationCB);
        observer.observe(EL_option, {attributes: true});
      });
    }
    
    // -----------
    // LET'S TEST! 
    
    console.log('\n1. Set SELECT value');
    formReset();
    EL_select.value = 'AL'; // Constatation: MutationObserver did NOT triggered!!!!
    testOptionsSelectedByProperty();
    testOptionsSelectedByAttribute();
    testSelect();
    
    console.log('\n2. Set HTMLElement.setAttribute()');
    formReset();
    EL_select.options[2].setAttribute('selected', true); // MutationObserver triggers
    testOptionsSelectedByProperty();
    testOptionsSelectedByAttribute();
    testSelect();
    
    console.log('\n3. Set HTMLOptionElement.defaultSelected');
    formReset();
    EL_select.options[3].defaultSelected = true; // MutationObserver triggers
    testOptionsSelectedByProperty();
    testOptionsSelectedByAttribute();
    testSelect();
    
    console.log('\n4. Set SELECT value and HTMLOptionElement.defaultSelected');
    formReset();
    EL_select.value = 'ZW'
    EL_select.options[EL_select.selectedIndex].defaultSelected = true; // MutationObserver triggers
    testOptionsSelectedByProperty();
    testOptionsSelectedByAttribute();
    testSelect();
    
    /* END */
    console.log('\n*. Getting MutationObservers out from call-stack...');

    Although the test 2. using .setAttribute() seems at first the best solution since both the Element Property and Attribute are unison, it can lead to confusion, specially because .setAttribute expects two parameters:

    EL_select.options[1].setAttribute('selected', false);
    // 

    will actually make the option selected

    Should one use .removeAttribute() or perhaps .setAttribute('selected', ???) to another value? Or should one read the state by using .getAttribute('selected') or by using .hasAttribute('selected')?

    Instead test 3. (and 4.) using defaultSelected gives the expected results:

    • Attribute selected as a named Selectedness state.
    • Property selected on the Element Object, with a Boolean value.

提交回复
热议问题