Building a UI to fetch list of features and edit them I am building a UI which fetches a list of features based on an ID and then the user can edit the list of features and save it. The script also build the UI based on the data structure currently hard coded in.

Was hoping you guys review it and let me know how I can improve it

    "use strict";

var activeFeatures = [
      //hardcoded structure
      {
        feature: 'feautreName',
        variants: {
          'featureVariant': 'percentage'
        }
      }
    ],
    //container there the ui will be build
    list = document.querySelector('#features');

// builds the vairants per variant
function injectVariants(variants) {
  var variantHtml = '';

  Object.keys(variants).forEach(function (variant) {
    var percentage = variants[variant] || 0;
    variantHtml += '&lt;li class="variant"&gt;&lt;span class="variant-name"&gt;' + variant + '&lt;/span&gt;&lt;span class="control-wrapper"&gt;&lt;input type="text" id="' + variant + '" class="percentage-active" value="' + percentage + '"&gt;&lt;/span&gt;&lt;/li&gt;'
  })

  return variantHtml;
}

// builds the list of features
function buildFeaturesList(featureList) {
  let featureList = featureList || activeFeatures
  list.innerHTML = '';

  featureList.forEach(item =&gt; {
    list.innerHTML += '&lt;li id="' + item.feature + '"&gt;&lt;span class="checkbox"  data-feature="' + item.feature + '" checked="false"&gt;&lt;/span&gt; &lt;span class="feature-name"&gt;' + item.feature + '&lt;/span&gt; &lt;ul data-feature="' + item.feature + '" class="variant-list"&gt;' + injectVariants(item.variants) + '&lt;/ul&gt;&lt;/li&gt;';
  })

}

// gets document based on Id and click checked if feature is checked
function getDoumentWithFeautres() {
  var r = new XMLHttpRequest(),
      documentId = document.getElementById("documentFeatures").value;

  if (documentId !== '') {
    r.open('GET', '/id/' + documentId, true);
    r.onreadystatechange = function () {
      if (r.readyState != 4 || r.status != 200) return;
      let currentFeatures = JSON.parse(r.responseText).features || [];

      buildFeaturesList(currentFeatures);

      currentFeatures.forEach(item =&gt; {
          document.getElementById(item.feature).querySelector('.checkbox').setAttribute('checked', true);
      });
    };
    r.send();
  }
}

// each feautre has a checkbox for user to select a feature
// this is for toggles
const toggleCheckBox = (target) =&gt; {
  if (target.getAttribute('checked') === "true") {
    target.setAttribute('checked', false);
  } else {
    target.setAttribute('checked', true);
  }
}

//build the payload before posting
function getActiveFeatures() {
  var listOfFeaturesElements = [].slice.call(document.querySelectorAll(".checkbox"));
  var payload = {};

  Payload.id = document.getElementById("documentFeatures").value;
  payload.features = [];

  listOfFeaturesElements.forEach(function (element) {
    var featureObject;

    if (element.getAttribute("checked") === 'true') {

      featureObject = (activeFeatures.filter(function (obj) {
          return obj.feature === element.getAttribute('data-feature')
      }));

      Object.keys(featureObject[0].variants).map(function (item) {
          featureObject[0].variants[item] = document.getElementById(item).value
      });

      payload.features.push(featureObject[0]);
    }
  });

  return payload;
}

// post updated features on save
function updateDocument(target) {
  var payload = getActiveFeatures(),
      r = new XMLHttpRequest();

  r.open("POST", "/journeys", true);
  r.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
  r.onreadystatechange = function () {
      if (r.readyState != 4 || r.status != 200) return;
  };
  r.send(JSON.stringify(payload));
}

document.body.addEventListener('click', function (e) {
  switch (e.target.classList[0]) {
    case 'search':
      getDoumentWithFeautres()
      break;
    case 'checkbox':
      if (journey) toggleCheckBox();
      break;
    case 'save':
      if (journey) updateDocument(e.target);
      break;
    default: break;
  }
});

buildFeaturesList();
Syntax and Compatibility

Most browsers will balk at the redefinition of a function parameter using the let keyword here:

// builds the list of features
function buildFeaturesList(featureList) {
    let featureList = featureList || activeFeatures



Chrome: Uncaught SyntaxError: Identifier 'featureList' has already been declared
Firefox: SyntaxError: redeclaration of formal parameter featureList
IE11: Let/Const redeclaration


This can be corrected by replacing let with var in this instance. Note that the primary difference between let and var is that let is block-scoped, while var is function-scoped. Since the block containing this variable declaration is a function anyway, you don't gain any advantage by using let.

IE11 also does not yet support arrow (=>) syntax for lambda functions, but if you don't have to support such users you can ignore that problem.

Avoid modifying innerHTML directly

Direct modification of an element's innerHTML property should be avoided whenever possible. It has performance drawbacks (forcing the browser to re-evaluate the DOM with each modification) and it can create memory leaks (when code retains references to elements that have been destroyed by overwriting their HTML).

Instead, consider these approaches:

Clearing innerHTML

To clear the innerHTML without creating memory leaks, instead of features.innerHTML = ''; you can use a loop to remove all child nodes:

while(features.childNodes.length &gt; 0){
    features.removeChild(features.childNodes[features.childNodes.length - 1]);
}


A for loop can be more efficient since you can avoid accessing the childNodes.length property multiple times.

for(let i = features.childNodes.length-1; i &gt;= 0; i--){
    features.removeChild(features.childNodes[i]);
}


Appending to innerHTML

To append to innerHTML without forcing the DOM to reload with every change, you can use element.insertAdjacentHTML().

For example, instead of list.innerHTML += stringValue use list.insertAdjacentHTML("beforeend",stringValue):

list.insertAdjacentHTML("beforeend",
    '&lt;li id="' + item.feature + '"&gt;&lt;span class="checkbox"  data-feature="' 
    + item.feature + '" checked="false"&gt;&lt;/span&gt; &lt;span class="feature-name"&gt;' 
    + item.feature + '&lt;/span&gt; &lt;ul data-feature="' + item.feature + '" class="variant-list"&gt;' 
    + injectVariants(item.variants) + '&lt;/ul&gt;&lt;/li&gt;'
);


Note that you might also want to consider element.appendChild() which lets you append any HTML element by its reference, such as the reference obtained when you create an element using document.createElement(). That approach is more versatile (you can retain references to the elements in code and attach event handlers to the elements programmatically) but will be slightly slower than element.insertAdjacentHTML().