github.com/outbrain/consul@v1.4.5/ui-v2/app/utils/form/builder.js (about)

     1  import { get, set, computed } from '@ember/object';
     2  import Changeset from 'ember-changeset';
     3  import lookupValidator from 'ember-changeset-validations';
     4  
     5  // Keep these here for now so forms are easy to make
     6  // TODO: Probably move this to utils/form/parse-element-name
     7  import parseElementName from 'consul-ui/utils/get-form-name-property';
     8  const defaultChangeset = function(data, validators) {
     9    const changeset = new Changeset(data, lookupValidator(validators), validators);
    10    // TODO: Currently supporting ember-data nicely like this
    11    changeset.isSaving = computed('data.isSaving', function() {
    12      return get(this, 'data.isSaving');
    13    });
    14    return changeset;
    15  };
    16  /**
    17   * Form builder/Form factory (WIP)
    18   * Deals with handling (generally change) events and updating data in response to the change
    19   * in a typical data down event up manner
    20   * validations are included currently using ember-changeset-validations
    21   *
    22   * @param {string} name - The name of the form, generally this is the name of your model
    23   *   Generally (until view building is complete) you should name your form elements as `name="modelName[property]"`
    24   *   or pass this name through using you action and create an Event-like object instead
    25   *   You can also just not set a name and use `name="property"`, but if you want to use combinations
    26   *   if multiple forms at least form children should use names
    27   *
    28   * @param {object} config - Form configuration object. Just a plain object to configure the form should be a hash
    29   *   with property names relating to the form data. Each property is the configuration for that model/data property
    30   *   currently the only supported property of these configuration objects is `type` which currently allows you to
    31   *   set a property as 'array-like'
    32   */
    33  export default function(changeset = defaultChangeset, getFormNameProperty = parseElementName) {
    34    return function(name = '', obj = {}) {
    35      let _data;
    36      const _name = name;
    37      const _children = {};
    38      let _validators = null;
    39      // TODO make this into a class to reuse prototype
    40      return {
    41        getName: function() {
    42          return _name;
    43        },
    44        setData: function(data) {
    45          // Array check temporarily for when we get an empty array from repo.status
    46          if (_validators && !Array.isArray(data)) {
    47            _data = changeset(data, _validators);
    48          } else {
    49            _data = data;
    50          }
    51          return this;
    52        },
    53        getData: function() {
    54          return _data;
    55        },
    56        add: function(child) {
    57          _children[child.getName()] = child;
    58          return this;
    59        },
    60        handleEvent: function(e) {
    61          const target = e.target;
    62          const parts = getFormNameProperty(target.name);
    63          // split the form element name from `name[prop]`
    64          const name = parts[0];
    65          const prop = parts[1];
    66          //
    67          let config = obj;
    68          // if the name (usually the name of the model) isn't this form, look at its children
    69          if (name !== _name) {
    70            if (this.has(name)) {
    71              // is its a child form then use the child form
    72              return this.form(name).handleEvent(e);
    73            }
    74            // should probably throw here, unless we support having a name
    75            // even if you are referring to this form
    76            config = config[name];
    77          }
    78          const data = this.getData();
    79          // ember-data/changeset dance
    80          const json = typeof data.toJSON === 'function' ? data.toJSON() : get(data, 'data').toJSON();
    81          // if the form doesn't include a property then throw so it can be
    82          // caught outside, therefore the user can deal with things that aren't in the data
    83          if (!Object.keys(json).includes(prop)) {
    84            const error = new Error(`${prop} property doesn't exist`);
    85            error.target = target;
    86            throw error;
    87          }
    88          // deal with the change of property
    89          let currentValue = get(data, prop);
    90          // if the value is an array-like or config says its an array
    91          if (
    92            Array.isArray(currentValue) ||
    93            (typeof config[prop] !== 'undefined' &&
    94              typeof config[prop].type === 'string' &&
    95              config[prop].type.toLowerCase() === 'array')
    96          ) {
    97            // array specific set
    98            if (currentValue == null) {
    99              currentValue = [];
   100            }
   101            const method = target.checked ? 'pushObject' : 'removeObject';
   102            currentValue[method](target.value);
   103            set(data, prop, currentValue);
   104          } else {
   105            // deal with booleans
   106            // but only booleans that aren't checkboxes/radios with values
   107            if (
   108              typeof target.checked !== 'undefined' &&
   109              (target.value.toLowerCase() === 'on' || target.value.toLowerCase() === 'off')
   110            ) {
   111              set(data, prop, target.checked);
   112            } else {
   113              // text and non-boolean checkboxes/radios
   114              set(data, prop, target.value);
   115            }
   116          }
   117          // validate everything
   118          return this.validate();
   119        },
   120        reset: function() {
   121          const data = this.getData();
   122          if (typeof data.rollbackAttributes === 'function') {
   123            this.getData().rollbackAttributes();
   124          }
   125          return this;
   126        },
   127        setValidators: function(validators) {
   128          _validators = validators;
   129          return this;
   130        },
   131        validate: function() {
   132          const data = this.getData();
   133          // just pass along to the Changeset for now
   134          if (typeof data.validate === 'function') {
   135            data.validate();
   136          }
   137          return this;
   138        },
   139        addError: function(name, message) {
   140          const data = this.getData();
   141          if (typeof data.addError === 'function') {
   142            data.addError(...arguments);
   143          }
   144        },
   145        form: function(name) {
   146          if (name == null) {
   147            return this;
   148          }
   149          return _children[name];
   150        },
   151        has: function(name) {
   152          return typeof _children[name] !== 'undefined';
   153        },
   154      };
   155    };
   156  }