sizzle / The GM_config Unit Test

// ==UserScript==
// @name          The GM_config Unit Test
// @namespace     sizzlemctwizzle
// @description   Attempts to test every aspect of GM_config.
// @license       MIT
// @version       2.3.0.3
// @grant         GM_getValue
// @grant         GM_setValue
// @grant         GM.getValue
// @grant         GM.setValue
// @require       https://openuserjs.org/src/libs/sizzle/GM_config.min.js
// @include       https://openuserjs.org/scripts/sizzle/The_GM_config_Unit_Test
// @homepageURL   https://github.com/sizzlemctwizzle/UserScripts/blob/master/gm_config_unit_test.user.js
// @updateURL     https://openuserjs.org/meta/sizzle/The_GM_config_Unit_Test.meta.js
// ==/UserScript==

// ==OpenUserJS==
// @author sizzle
// @collaborator Marti
// ==/OpenUserJS==

/* jshint esversion: 5 */
/* globals GM_config, GM_configStruct */
var fieldDefs = {
    'name': {
        'section': [GM_config.create('Personal Info About Yourself'), 'We need this info to do stuff'],
        'label': GM_config.create('Name'),
        'type': 'text',
        'default': 'Joe Simmons'
    },
    'birthday': {
        'label': 'Date of Birth',
        'type': 'date',
        'format': 'dd/mm/yyyy'
    },
    'age': {
        'label': 'Age',
        'type': 'unsigned int',
        'default': 19
    },
    'gender': {
        'options': ['Male', 'Female'],
        'label': 'Gender',
        'type': 'radio',
        'default': 'Male'
    },
    'income': {
        'labelPos': 'right',
        'label': 'Income',
        'type': 'float',
        'default': 50000.0
    },
    'status': {
        'label': 'Married',
        'labelPos': 'above',
        'type': 'checkbox',
        'default': false
    },
    'work': {
        'label': 'Job',
        'type': 'select',
        'labelPos': 'below',
        'options': ['Carpenter', 'Truck Driver', 'Porn Star'],
        'default': 'Truck Driver'
    },
    'bunchOtext': {
        'label': 'Bunch of Text',
        'type': 'textarea',
        'default': 'I actually did\'t realize we had this field until recently...'
    },
    'magic': {
        'label': 'Magic Button',
        'type': 'button',
        'click': function() {
            alert('Magic works!');
        }
    },
    'upperLeft': {
        'section': 'Check some boxes',
        'type': 'checkbox',
        'default': false
    },
    'upperLeftMiddle': {
        'type': 'checkbox',
        'default': false
    },
    'upperRightMiddle': {
        'type': 'checkbox',
        'default': false
    },
    'upperRight': {
        'type': 'checkbox',
        'default': false
    },
    'middleLeft': {
        'section': [],
        'type': 'checkbox',
        'default': false
    },
    'middleLeftMiddle': {
        'type': 'checkbox',
        'default': false
    },
    'middleRightMiddle': {
        'type': 'checkbox',
        'default': false
    },
    'middleRight': {
        'type': 'checkbox',
        'default': false
    },
    'bottomLeft': {
        'section': [],
        'type': 'checkbox',
        'default': false
    },
    'bottomLeftMiddle': {
        'type': 'checkbox',
        'default': false
    },
    'bottomRightMiddle': {
        'type': 'checkbox',
        'default': false
    },
    'bottomRight': {
        'type': 'checkbox',
        'default': false
    },
    'labelLess': {
        'section': GM_config.create('New Section'),
        'type': 'text',
        'default': 'This value is not saved.',
        'save': false
    },
    'alertTextField': {
        'label': 'Alert Text',
        'type': 'button',
        'click': function() {
            var value = GM_config.fields['labelLess'].toValue();
            if (value != null) {
              alert(value);
              GM_config.fields['labelLess'].value = 'Value changed.';
              GM_config.fields['labelLess'].reload();
            } else alert('null field has already been changed and forgotten.');
        }
    },
    'customCSS': {
        'label': 'Enter CSS',
        'type': 'text',
        'save': false,
        'default': ''
    },
    'validCSS': {
        'type': 'hidden',
        'default': '',
    }
};

GM_config.init(
{
  id: 'GM_config',
  title: 'Configurable Options Script',
  fields: fieldDefs,
  css: '#GM_config_section_1 .config_var, #GM_config_section_2 .config_var, #GM_config_section_3 .config_var { margin: 5% !important;display: inline !important; }',
  events:
  {
    open: function(doc) {
      doc.getElementById('GM_config_section_header_1').className = 'field_label';
      var customCSS = GM_config.fields['customCSS'].node;
      var validCSS = GM_config.fields['validCSS'].node;
      customCSS.value = validCSS.value;
      customCSS.addEventListener('change', function () {
        if(/\w+\s*\{\s*\w+\s*:\s*\w+[\s|\S]*\}/.test(customCSS.value))
          validCSS.value = customCSS.value;
      }, false);
    },
    save: function(values) {
      // All the values that aren't saved are passed to this function
      // for (i in values) alert(values[i]);
    }
  },
  'types':
  {
    'date': {
      'default': null,
        toNode: function(configId) {
          var field = this.settings,
              value = this.value,
              id = this.id,
              create = this.create,
              format = (field.format || 'mm/dd/yyyy').split('/'),
              slash = null,
              retNode = create('div', { className: 'config_var',
                id: configId + '_' + id + '_var',
                title: field.title || '' });

          // Save the format array to the field object so
          // it's easier to hack externally
          this.format = format;

          // Create the field lable
          retNode.appendChild(create('label', {
            innerHTML: field.label,
            id: configId + '_' + id + '_field_label',
            for: configId + '_field_' + id,
            className: 'field_label'
          }));

          // Create the inputs for each part of the date
          value = value ? value.split('/') : this['default'];
          for (var i = 0, len = format.length; i < len; ++i) {
            var props = {
              id: configId + '_field_' + id + '_' + format[i],
              type: 'text',
              size: format[i].length,
              value: value ? value[i] : '',
              onkeydown: function(e) {
                var input = e.target;
                if (input.value.length >= input.size)
                  input.value = input.value.substr(0, input.size - 1);
              }
            };

            // Jump to the next input once one is complete
            // This saves the user a little work
            if (i < format.length - 1) {
              slash = create(' / ');
              props.onkeyup = function(e) {
                var input = e.target,
                    inputs = input.parentNode.getElementsByTagName('input'),
                    num = 0;

                for (; num < inputs.length && input != inputs[num]; ++num);
                if (input.value.length >= input.size)
                  inputs[num + 1].focus();
              };
            } else slash = null;

            // Actually create and append the input element
            retNode.appendChild(create('input', props));
            if (slash) retNode.appendChild(slash);
          }

          return retNode;
        },
        toValue: function() {
          var rval = null;
          if (this.wrapper) {
            var inputs = this.wrapper.getElementsByTagName('input');
            rval = '';

            // Join the field values together seperated by slashes
            for (var i = 0, len = inputs.length; i < len; ++i) {
              // Don't save values that aren't numbers
              if (isNaN(Number(inputs[i].value))) {
                alert('Date is invalid');
                return null;
              }
              rval += inputs[i].value + (i < len - 1 ? '/' : '');
            }
          }

          // We are just returning a string to be saved
          // If you want to use this value you'll want a Date object
          return rval;
        },
        reset: function() {
          // Empty all the input fields
          if (this.wrapper) {
            var inputs = this.wrapper.getElementsByTagName('input');
            for (var i = 0, len = inputs.length; i < len; ++i)
              inputs[i].value = '';
          }
        }
    }
  }
});
(async () => {
  // Retrieve language setting
  var lang = await GM_config.getValue('lang', 'en');

  // Fields in different languages
  var langDefs = {
    'en': // Fields in English
    {
      'lang':
      {
        'label': 'Choose Language',
        'type': 'select',
        'options': ['en', 'de'],
        'save': false // This field's value will NOT be saved
      }
    },
    'de': // Fields in German
    {
      'lang':
      {
        'label': 'Sprache wählen',
        'type': 'select',
        'options': ['en', 'de'],
        'save': false // This field's value will NOT be saved
      }
    }
  };

  // Use field definitions for the stored language
  var fields = langDefs[lang];

  // The title for the settings panel in different languages
  var titles = {
    'en': 'Translations Dialog',
    'de': 'Übersetzungen Dialog'
  };
  var title = titles[lang];

  // Translations for the buttons and reset link
  var saveButton = {'en': 'Save', 'de': 'Speichern'};
  var closeButton = {'en': 'Close', 'de': 'Schließen'};
  var resetLink = {
    'en': 'Reset fields to default values',
    'de': 'Felder zurücksetzen auf Standardwerte'
  };

  var gmc_trans = new GM_configStruct(
  {
    'id': 'GM_config_trans', // The id used for this instance of GM_config
    'title': title,
    'fields': fields, // Fields object
    'events':
    {
      'init': function()
      {
        // You must manually set an unsaved value
        this.fields['lang'].value = lang;
      },
      'open': function (doc) {
        // translate the buttons
        var config = this;
        doc.getElementById(config.id + '_saveBtn').textContent = saveButton[lang];
        doc.getElementById(config.id + '_closeBtn').textContent = closeButton[lang];
        doc.getElementById(config.id + '_resetLink').textContent = resetLink[lang];
      },
      'save': function(values) { // All unsaved values are passed to save
        for (var i in values) {
          if (i == 'lang' && values[i] != lang) {
            var config = this;
            lang = values[i];

            // Use field definitions for the chosen language
            fields = langDefs[lang];
            config.fields['lang'].value = lang;

            // Use the title for the chose language
            title = titles[lang];

            // Re-initialize GM_config for the language change
            config.init({ 'id': config.id, title: title, 'fields': fields });

            // Refresh the config panel for the new language change
            config.close();
            config.open();

            // Save the chosen language for next time
            config.setValue('lang', lang);
          }
        }
      }
    }
  });

  GM_config.init(
  {
    id: 'GM_config',
    'events':
    {
      'init': function()
      {
        GM_config.open();
      }
    },
    fields:
    {
      'extra':
      {
        'label': 'Extra Field',
        'type': 'text',
        'default': 'This field was added with a second call to init()'
      },
      'openTrans':
      {
        'label': 'Open Translation Demo',
        'type': 'button',
        'click': function() {
          GM_config.close();
          gmc_trans.open();
        }
      }
    }
  });
})();