github.com/DerekStrickland/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 }