src/js/showTransformModal.js
function showTransformModal (node, container) {
  var value = node.getValue();

  var content = '<label class="pico-modal-contents">' +
      '<div class="pico-modal-header">' + translate('transform') + '</div>' +
      '<p>' +
      'Enter a <a href="http://jmespath.org" target="_blank">JMESPath</a> query to filter, sort, or transform the JSON data.<br/>' +
      'To learn JMESPath, go to <a href="http://jmespath.org/tutorial.html" target="_blank">the interactive tutorial</a>.' +
      '</p>' +
      '<div class="jsoneditor-jmespath-label">' + translate('transformWizardLabel') + ' </div>' +
      '<div id="wizard" class="jsoneditor-jmespath-block jsoneditor-jmespath-wizard">' +
      '  <table class="jsoneditor-jmespath-wizard-table">' +
      '    <tbody>' +
      '      <tr>' +
      '        <th>' + translate('transformWizardFilter') + '</th>' +
      '        <td class="jsoneditor-jmespath-filter">' +
      '          <div class="jsoneditor-inline jsoneditor-jmespath-filter-field" >' +
      '            <select id="filterField">' +
      '            </select>' +
      '          </div>' +
      '          <div class="jsoneditor-inline jsoneditor-jmespath-filter-relation" >' +
      '            <select id="filterRelation">' +
      '              <option value="==">==</option>' +
      '              <option value="!=">!=</option>' +
      '              <option value="<">&lt;</option>' +
      '              <option value="<=">&lt;=</option>' +
      '              <option value=">">&gt;</option>' +
      '              <option value=">=">&gt;=</option>' +
      '            </select>' +
      '          </div>' +
      '          <div class="jsoneditor-inline jsoneditor-jmespath-filter-value" >' +
      '            <input placeholder="value..." id="filterValue" />' +
      '          </div>' +
      '        </td>' +
      '      </tr>' +
      '      <tr>' +
      '        <th>' + translate('transformWizardSortBy') + '</th>' +
      '        <td class="jsoneditor-jmespath-filter">' +
      '          <div class="jsoneditor-inline jsoneditor-jmespath-sort-field">' +
      '            <select id="sortField">' +
      '            </select>' +
      '          </div>' +
      '          <div class="jsoneditor-inline jsoneditor-jmespath-sort-order" >' +
      '            <select id="sortOrder">' +
      '              <option value="asc">Ascending</option>' +
      '              <option value="desc">Descending</option>' +
      '            </select>' +
      '          </div>' +
      '        </td>' +
      '      </tr>' +
      '      <tr id="selectFieldsPart">' +
      '        <th>' + translate('transformWizardSelectFields') + '</th>' +
      '        <td class="jsoneditor-jmespath-filter">' +
      '          <select class="jsoneditor-jmespath-select-fields" id="selectFields" multiple></select>' +
      '        </td>' +
      '      </tr>' +
      '    </tbody>' +
      '  </table>' +
      '</div>' +
      '<div class="jsoneditor-jmespath-label">' + translate('transformQueryLabel') + ' </div>' +
      '<div class="jsoneditor-jmespath-block">' +
      '  <textarea id="query" ' +
      '            rows="4" ' +
      '            autocomplete="off" ' +
      '            autocorrect="off" ' +
      '            autocapitalize="off" ' +
      '            spellcheck="false"' +
      '            title="' + translate('transformQueryTitle') + '">[*]</textarea>' +
      '</div>' +
      '<div class="jsoneditor-jmespath-label">' + translate('transformPreviewLabel') + ' </div>' +
      '<div class="jsoneditor-jmespath-block">' +
      '  <textarea id="preview" ' +
      '      class="jsoneditor-transform-preview"' +
      '      readonly> </textarea>' +
      '</div>' +
      '<div class="jsoneditor-jmespath-block jsoneditor-modal-actions">' +
      '  <input type="submit" id="ok" value="' + translate('ok') + '" autofocus />' +
      '</div>' +
      '</div>';

  picoModal({
    parent: container,
    content: content,
    overlayClass: 'jsoneditor-modal-overlay',
    modalClass: 'jsoneditor-modal jsoneditor-modal-transform',
    focus: false
  })
      .afterCreate(function (modal) {
        var elem = modal.modalElem();

        var wizard = elem.querySelector('#wizard');
        var ok = elem.querySelector('#ok');
        var filterField = elem.querySelector('#filterField');
        var filterRelation = elem.querySelector('#filterRelation');
        var filterValue = elem.querySelector('#filterValue');
        var sortField = elem.querySelector('#sortField');
        var sortOrder = elem.querySelector('#sortOrder');
        var selectFields = elem.querySelector('#selectFields');
        var query = elem.querySelector('#query');
        var preview = elem.querySelector('#preview');

        if (!Array.isArray(value)) {
          wizard.style.fontStyle = 'italic';
          wizard.innerHTML = '(wizard not available for objects, only for arrays)'
        }

        var paths = node.getChildPaths();
        paths.forEach(function (path) {
          var formattedPath = preprocessPath(path);
          var filterOption = document.createElement('option');
          filterOption.text = formattedPath;
          filterOption.value = formattedPath;
          filterField.appendChild(filterOption);

          var sortOption = document.createElement('option');
          sortOption.text = formattedPath;
          sortOption.value = formattedPath;
          sortField.appendChild(sortOption);
        });

        var allPaths = node.getChildPaths(true).filter(function(path) {
          return path !== '.';
        });

        if (allPaths.length > 0) {
          allPaths.forEach(function (path) {
            var formattedPath = preprocessPath(path);
            var option = document.createElement('option');
            option.text = formattedPath;
            option.value = formattedPath;
            selectFields.appendChild(option);
          });
        }
        else {
          var selectFieldsPart = elem.querySelector('#selectFieldsPart');
          if (selectFieldsPart) {
            selectFieldsPart.style.display = 'none';
          }
        }

        var selectrFilterField = new Selectr(filterField, { defaultSelected: false, clearable: true, allowDeselect: true, placeholder: 'field...' });
        var selectrFilterRelation = new Selectr(filterRelation, { defaultSelected: false, clearable: true, allowDeselect: true, placeholder: 'compare...' });
        var selectrSortField = new Selectr(sortField, { defaultSelected: false, clearable: true, allowDeselect: true, placeholder: 'field...' });
        var selectrSortOrder = new Selectr(sortOrder, { defaultSelected: false, clearable: true, allowDeselect: true, placeholder: 'order...' });
        var selectrSelectFields = new Selectr(selectFields, {multiple: true, clearable: true, defaultSelected: false, placeholder: 'select fields...'});

        selectrFilterField.on('selectr.change', generateQueryFromWizard);
        selectrFilterRelation.on('selectr.change', generateQueryFromWizard);
        filterValue.oninput = generateQueryFromWizard;
        selectrSortField.on('selectr.change', generateQueryFromWizard);
        selectrSortOrder.on('selectr.change', generateQueryFromWizard);
        selectrSelectFields.on('selectr.change', generateQueryFromWizard);

        elem.querySelector('.pico-modal-contents').onclick = function (event) {
          // prevent the first clear button (in any select box) from getting
          // focus when clicking anywhere in the modal. Only allow clicking links.
          if (event.target.nodeName !== 'A') {
            event.preventDefault();
          }
        };

        query.value = Array.isArray(value) ? '[*]' : '@';

        function preprocessPath(path) {
          if (path[0] === '.') {
            return (path === '.')
                ? '@'
                : path.slice(1);
          }
          else {
            return path;
          }
        }

        function generateQueryFromWizard () {
          if (filterField.value && filterRelation.value && filterValue.value) {
            var field1 = filterField.value;
            var examplePath = field1 !== '@'
                ? ['0'].concat(util.parsePath('.' + field1))
                : ['0']
            var exampleValue = util.get(value, examplePath)
            // TODO: move _stringCast into a static util function
            var value1 = typeof exampleValue === 'string'
                ? filterValue.value
                : node._stringCast(filterValue.value);

            query.value = '[? ' +
                field1 + ' ' +
                filterRelation.value + ' ' +
                '`' + JSON.stringify(value1) + '`' +
                ']';
          }
          else {
            query.value = '[*]';
          }

          if (sortField.value && sortOrder.value) {
            var field2 = sortField.value;
            if (sortOrder.value === 'desc') {
              query.value += ' | reverse(sort_by(@, &' + field2 + '))';
            }
            else {
              query.value += ' | sort_by(@, &' + field2 + ')';
            }
          }

          if (selectFields.value) {
            var values = [];
            for (var i=0; i < selectFields.options.length; i++) {
              if (selectFields.options[i].selected) {
                var selectedValue = selectFields.options[i].value;
                values.push(selectedValue);
              }
            }

            if (query.value[query.value.length - 1] !== ']') {
              query.value += ' | [*]';
            }

            if (values.length === 1) {
              query.value += '.' + selectedValue;
            }
            else if (values.length > 1) {
              query.value += '.{' +
                  values.map(function (value) {
                    var parts = value.split('.');
                    var last = parts[parts.length - 1];
                    return last + ': ' + selectedValue;
                  }).join(', ') +
                  '}';
            }
            else { // values.length === 0
              // ignore
            }
          }

          debouncedUpdatePreview();
        }

        function updatePreview() {
          try {
            var transformed = jmespath.search(value, query.value);
            var lines =  JSON.stringify(transformed, null, 2).split('\n');

            if (lines.length > MAX_PREVIEW_LINES) {
              lines = lines.slice(0, MAX_PREVIEW_LINES).concat(['...'])
            }


            preview.className = 'jsoneditor-transform-preview';
            preview.value = lines.join('\n');
            ok.disabled = false;
          }
          catch (err) {
            preview.className = 'jsoneditor-transform-preview jsoneditor-error';
            preview.value = err.toString();
            ok.disabled = true;
          }
        }

        var debouncedUpdatePreview = debounce(updatePreview, 300);

        query.oninput = debouncedUpdatePreview;
        debouncedUpdatePreview();

        ok.onclick = function (event) {
          event.preventDefault();
          event.stopPropagation();

          modal.close();

          node.transform(query.value)
        };

        setTimeout(function () {
          query.select();
          query.focus();
          query.selectionStart = 3;
          query.selectionEnd = 3;
        });
      })
      .afterClose(function (modal) {
        modal.destroy();
      })
      .show();
}
function showTransformModal (node, container) {
  var value = node.getValue();

  var content = '<label class="pico-modal-contents">' +
      '<div class="pico-modal-header">' + translate('transform') + '</div>' +
      '<p>' +
      'Enter a <a href="http://jmespath.org" target="_blank">JMESPath</a> query to filter, sort, or transform the JSON data.<br/>' +
      'To learn JMESPath, go to <a href="http://jmespath.org/tutorial.html" target="_blank">the interactive tutorial</a>.' +
      '</p>' +
      '<div class="jsoneditor-jmespath-label">' + translate('transformWizardLabel') + ' </div>' +
      '<div id="wizard" class="jsoneditor-jmespath-block jsoneditor-jmespath-wizard">' +
      '  <table class="jsoneditor-jmespath-wizard-table">' +
      '    <tbody>' +
      '      <tr>' +
      '        <th>' + translate('transformWizardFilter') + '</th>' +
      '        <td class="jsoneditor-jmespath-filter">' +
      '          <div class="jsoneditor-inline jsoneditor-jmespath-filter-field" >' +
      '            <select id="filterField">' +
      '            </select>' +
      '          </div>' +
      '          <div class="jsoneditor-inline jsoneditor-jmespath-filter-relation" >' +
      '            <select id="filterRelation">' +
      '              <option value="==">==</option>' +
      '              <option value="!=">!=</option>' +
      '              <option value="<">&lt;</option>' +
      '              <option value="<=">&lt;=</option>' +
      '              <option value=">">&gt;</option>' +
      '              <option value=">=">&gt;=</option>' +
      '            </select>' +
      '          </div>' +
      '          <div class="jsoneditor-inline jsoneditor-jmespath-filter-value" >' +
      '            <input placeholder="value..." id="filterValue" />' +
      '          </div>' +
      '        </td>' +
      '      </tr>' +
      '      <tr>' +
      '        <th>' + translate('transformWizardSortBy') + '</th>' +
      '        <td class="jsoneditor-jmespath-filter">' +
      '          <div class="jsoneditor-inline jsoneditor-jmespath-sort-field">' +
      '            <select id="sortField">' +
      '            </select>' +
      '          </div>' +
      '          <div class="jsoneditor-inline jsoneditor-jmespath-sort-order" >' +
      '            <select id="sortOrder">' +
      '              <option value="asc">Ascending</option>' +
      '              <option value="desc">Descending</option>' +
      '            </select>' +
      '          </div>' +
      '        </td>' +
      '      </tr>' +
      '      <tr id="selectFieldsPart">' +
      '        <th>' + translate('transformWizardSelectFields') + '</th>' +
      '        <td class="jsoneditor-jmespath-filter">' +
      '          <select class="jsoneditor-jmespath-select-fields" id="selectFields" multiple></select>' +
      '        </td>' +
      '      </tr>' +
      '    </tbody>' +
      '  </table>' +
      '</div>' +
      '<div class="jsoneditor-jmespath-label">' + translate('transformQueryLabel') + ' </div>' +
      '<div class="jsoneditor-jmespath-block">' +
      '  <textarea id="query" ' +
      '            rows="4" ' +
      '            autocomplete="off" ' +
      '            autocorrect="off" ' +
      '            autocapitalize="off" ' +
      '            spellcheck="false"' +
      '            title="' + translate('transformQueryTitle') + '">[*]</textarea>' +
      '</div>' +
      '<div class="jsoneditor-jmespath-label">' + translate('transformPreviewLabel') + ' </div>' +
      '<div class="jsoneditor-jmespath-block">' +
      '  <textarea id="preview" ' +
      '      class="jsoneditor-transform-preview"' +
      '      readonly> </textarea>' +
      '</div>' +
      '<div class="jsoneditor-jmespath-block jsoneditor-modal-actions">' +
      '  <input type="submit" id="ok" value="' + translate('ok') + '" autofocus />' +
      '</div>' +
      '</div>';

  picoModal({
    parent: container,
    content: content,
    overlayClass: 'jsoneditor-modal-overlay',
    modalClass: 'jsoneditor-modal jsoneditor-modal-transform',
    focus: false
  })
      .afterCreate(function (modal) {
        var elem = modal.modalElem();

        var wizard = elem.querySelector('#wizard');
        var ok = elem.querySelector('#ok');
        var filterField = elem.querySelector('#filterField');
        var filterRelation = elem.querySelector('#filterRelation');
        var filterValue = elem.querySelector('#filterValue');
        var sortField = elem.querySelector('#sortField');
        var sortOrder = elem.querySelector('#sortOrder');
        var selectFields = elem.querySelector('#selectFields');
        var query = elem.querySelector('#query');
        var preview = elem.querySelector('#preview');

        if (!Array.isArray(value)) {
          wizard.style.fontStyle = 'italic';
          wizard.innerHTML = '(wizard not available for objects, only for arrays)'
        }

        var paths = node.getChildPaths();
        paths.forEach(function (path) {
          var formattedPath = preprocessPath(path);
          var filterOption = document.createElement('option');
          filterOption.text = formattedPath;
          filterOption.value = formattedPath;
          filterField.appendChild(filterOption);

          var sortOption = document.createElement('option');
          sortOption.text = formattedPath;
          sortOption.value = formattedPath;
          sortField.appendChild(sortOption);
        });

        var allPaths = node.getChildPaths(true).filter(function(path) {
          return path !== '.';
        });

        if (allPaths.length > 0) {
          allPaths.forEach(function (path) {
            var formattedPath = preprocessPath(path);
            var option = document.createElement('option');
            option.text = formattedPath;
            option.value = formattedPath;
            selectFields.appendChild(option);
          });
        }
        else {
          var selectFieldsPart = elem.querySelector('#selectFieldsPart');
          if (selectFieldsPart) {
            selectFieldsPart.style.display = 'none';
          }
        }

        var selectrFilterField = new Selectr(filterField, { defaultSelected: false, clearable: true, allowDeselect: true, placeholder: 'field...' });
        var selectrFilterRelation = new Selectr(filterRelation, { defaultSelected: false, clearable: true, allowDeselect: true, placeholder: 'compare...' });
        var selectrSortField = new Selectr(sortField, { defaultSelected: false, clearable: true, allowDeselect: true, placeholder: 'field...' });
        var selectrSortOrder = new Selectr(sortOrder, { defaultSelected: false, clearable: true, allowDeselect: true, placeholder: 'order...' });
        var selectrSelectFields = new Selectr(selectFields, {multiple: true, clearable: true, defaultSelected: false, placeholder: 'select fields...'});

        selectrFilterField.on('selectr.change', generateQueryFromWizard);
        selectrFilterRelation.on('selectr.change', generateQueryFromWizard);
        filterValue.oninput = generateQueryFromWizard;
        selectrSortField.on('selectr.change', generateQueryFromWizard);
        selectrSortOrder.on('selectr.change', generateQueryFromWizard);
        selectrSelectFields.on('selectr.change', generateQueryFromWizard);

        elem.querySelector('.pico-modal-contents').onclick = function (event) {
          // prevent the first clear button (in any select box) from getting
          // focus when clicking anywhere in the modal. Only allow clicking links.
          if (event.target.nodeName !== 'A') {
            event.preventDefault();
          }
        };

        query.value = Array.isArray(value) ? '[*]' : '@';

        function preprocessPath(path) {
          if (path[0] === '.') {
            return (path === '.')
                ? '@'
                : path.slice(1);
          }
          else {
            return path;
          }
        }

        function generateQueryFromWizard () {
          if (filterField.value && filterRelation.value && filterValue.value) {
            var field1 = filterField.value;
            var examplePath = field1 !== '@'
                ? ['0'].concat(util.parsePath('.' + field1))
                : ['0']
            var exampleValue = util.get(value, examplePath)
            // TODO: move _stringCast into a static util function
            var value1 = typeof exampleValue === 'string'
                ? filterValue.value
                : node._stringCast(filterValue.value);

            query.value = '[? ' +
                field1 + ' ' +
                filterRelation.value + ' ' +
                '`' + JSON.stringify(value1) + '`' +
                ']';
          }
          else {
            query.value = '[*]';
          }

          if (sortField.value && sortOrder.value) {
            var field2 = sortField.value;
            if (sortOrder.value === 'desc') {
              query.value += ' | reverse(sort_by(@, &' + field2 + '))';
            }
            else {
              query.value += ' | sort_by(@, &' + field2 + ')';
            }
          }

          if (selectFields.value) {
            var values = [];
            for (var i=0; i < selectFields.options.length; i++) {
              if (selectFields.options[i].selected) {
                var selectedValue = selectFields.options[i].value;
                values.push(selectedValue);
              }
            }

            if (query.value[query.value.length - 1] !== ']') {
              query.value += ' | [*]';
            }

            if (values.length === 1) {
              query.value += '.' + selectedValue;
            }
            else if (values.length > 1) {
              query.value += '.{' +
                  values.map(function (value) {
                    var parts = value.split('.');
                    var last = parts[parts.length - 1];
                    return last + ': ' + selectedValue;
                  }).join(', ') +
                  '}';
            }
            else { // values.length === 0
              // ignore
            }
          }

          debouncedUpdatePreview();
        }

        function updatePreview() {
          try {
            var transformed = jmespath.search(value, query.value);
            var lines =  JSON.stringify(transformed, null, 2).split('\n');

            if (lines.length > MAX_PREVIEW_LINES) {
              lines = lines.slice(0, MAX_PREVIEW_LINES).concat(['...'])
            }


            preview.className = 'jsoneditor-transform-preview';
            preview.value = lines.join('\n');
            ok.disabled = false;
          }
          catch (err) {
            preview.className = 'jsoneditor-transform-preview jsoneditor-error';
            preview.value = err.toString();
            ok.disabled = true;
          }
        }

        var debouncedUpdatePreview = debounce(updatePreview, 300);

        query.oninput = debouncedUpdatePreview;
        debouncedUpdatePreview();

        ok.onclick = function (event) {
          event.preventDefault();
          event.stopPropagation();

          modal.close();

          node.transform(query.value)
        };

        setTimeout(function () {
          query.select();
          query.focus();
          query.selectionStart = 3;
          query.selectionEnd = 3;
        });
      })
      .afterClose(function (modal) {
        modal.destroy();
      })
      .show();
}
Show advanced filter and transform modal using JMESPath
@param {Node} node the node to be transformed
@param {HTMLElement} container   The container where to center
the modal and create an overlay