mirror of
https://github.com/status-im/consul.git
synced 2025-01-17 17:22:17 +00:00
26341f0b05
This sounds a bit 'backwards' as the end goal here is to add an improved UX to partitions, not namespaces. The reason for doing it this way is that Namespaces already has a type of 'improved UX' CRUD in that it has one to many relationship in the form when saving your namespaces (the end goal for partitions). In moving Namespaces to use the same approach as partitions we: - Ensure the new approach works with one-to-many forms. - Test the new approach without writing a single test (we already have a bunch of tests for namespaces which are now testing the approach used by both namespaces and partitions) Additionally: - Fixes issue with missing default nspace in the nspace selector - In doing when checking to see that things where consistent between the two, I found a few little minor problems with the Admin Partition CRUD so fixed those up here also. - Removed the old style Nspace notifications
175 lines
6.7 KiB
JavaScript
175 lines
6.7 KiB
JavaScript
import { get, set } from '@ember/object';
|
|
import { Changeset as createChangeset } from 'ember-changeset';
|
|
import Changeset from 'consul-ui/utils/form/changeset';
|
|
import lookupValidator from 'ember-changeset-validations';
|
|
|
|
// Keep these here for now so forms are easy to make
|
|
// TODO: Probably move this to utils/form/parse-element-name
|
|
import parseElementName from 'consul-ui/utils/get-form-name-property';
|
|
export const defaultChangeset = function(data, validators) {
|
|
return createChangeset(data, lookupValidator(validators), validators, { changeset: Changeset });
|
|
};
|
|
/**
|
|
* Form builder/Form factory
|
|
* Deals with handling (generally change) events and updating data in response to the change
|
|
* in a typical data down event up manner
|
|
* validations are included currently using ember-changeset-validations
|
|
*
|
|
* @param {string} name - The name of the form, generally this is the name of your model
|
|
* Generally (until view building is complete) you should name your form elements as `name="modelName[property]"`
|
|
* or pass this name through using you action and create an Event-like object instead
|
|
* You can also just not set a name and use `name="property"`, but if you want to use combinations
|
|
* if multiple forms at least form children should use names
|
|
*
|
|
* @param {object} config - Form configuration object. Just a plain object to configure the form should be a hash
|
|
* with property names relating to the form data. Each property is the configuration for that model/data property
|
|
* currently the only supported property of these configuration objects is `type` which currently allows you to
|
|
* set a property as 'array-like'
|
|
*/
|
|
export default function(changeset = defaultChangeset, getFormNameProperty = parseElementName) {
|
|
return function(name = '', obj = {}) {
|
|
const _children = {};
|
|
let _validators = null;
|
|
// TODO make this into a class to reuse prototype
|
|
const form = {
|
|
data: null,
|
|
name: name,
|
|
getName: function() {
|
|
return this.name;
|
|
},
|
|
setData: function(data) {
|
|
// Array check temporarily for when we get an empty array from repo.status
|
|
if (_validators && !Array.isArray(data)) {
|
|
data = changeset(data, _validators);
|
|
}
|
|
set(this, 'data', data);
|
|
return this;
|
|
},
|
|
getData: function() {
|
|
return this.data;
|
|
},
|
|
add: function(child) {
|
|
_children[child.getName()] = child;
|
|
return this;
|
|
},
|
|
handleEvent: function(e, targetName) {
|
|
const target = e.target;
|
|
// currently we only use targetName in {{form-component}} for handling deeply
|
|
// nested forms, once {{form-component}} handles deeply nested forms targetName can go
|
|
const parts = getFormNameProperty(targetName || target.name);
|
|
// split the form element name from `name[prop]`
|
|
const name = parts[0];
|
|
const prop = parts[1];
|
|
//
|
|
let config = obj;
|
|
// if the name (usually the name of the model) isn't this form, look at its children
|
|
if (name !== this.getName()) {
|
|
if (this.has(name)) {
|
|
// is its a child form then use the child form
|
|
return this.form(name).handleEvent(e);
|
|
}
|
|
// should probably throw here, unless we support having a name
|
|
// even if you are referring to this form
|
|
config = config[name];
|
|
}
|
|
const data = this.getData();
|
|
// ember-data/changeset dance
|
|
// TODO: This works for ember-data RecordSets and Changesets but not for plain js Objects
|
|
// see settings
|
|
const json = typeof data.toJSON === 'function' ? data.toJSON() : get(data, 'data').toJSON();
|
|
// if the form doesn't include a property then throw so it can be
|
|
// caught outside, therefore the user can deal with things that aren't in the data
|
|
// TODO: possibly need to add support for deeper properties using `get` here
|
|
// for example `client.blocking` instead of just `blocking`
|
|
if (!Object.keys(json).includes(prop)) {
|
|
const error = new Error(`${prop} property doesn't exist`);
|
|
error.target = target;
|
|
throw error;
|
|
}
|
|
// deal with the change of property
|
|
let currentValue = get(data, prop);
|
|
// if the value is an array-like or config says its an array
|
|
if (
|
|
Array.isArray(currentValue) ||
|
|
(typeof config[prop] !== 'undefined' &&
|
|
typeof config[prop].type === 'string' &&
|
|
config[prop].type.toLowerCase() === 'array')
|
|
) {
|
|
// array specific set
|
|
if (currentValue == null) {
|
|
currentValue = [];
|
|
}
|
|
const method = target.checked ? 'pushObject' : 'removeObject';
|
|
currentValue[method](target.value);
|
|
set(data, prop, currentValue);
|
|
} else {
|
|
// deal with booleans
|
|
// but only booleans that aren't checkboxes/radios with values
|
|
if (
|
|
typeof target.checked !== 'undefined' &&
|
|
(target.value.toLowerCase() === 'on' || target.value.toLowerCase() === 'off')
|
|
) {
|
|
set(data, prop, target.checked);
|
|
} else {
|
|
// text and non-boolean checkboxes/radios
|
|
set(data, prop, target.value);
|
|
}
|
|
}
|
|
// validate everything
|
|
return this.validate();
|
|
},
|
|
reset: function() {
|
|
const data = this.getData();
|
|
if (typeof data.rollbackAttributes === 'function') {
|
|
this.getData().rollbackAttributes();
|
|
}
|
|
return this;
|
|
},
|
|
clear: function(cb = {}) {
|
|
if (typeof cb === 'function') {
|
|
return (this.clearer = cb);
|
|
} else {
|
|
return this.setData(this.clearer(cb)).getData();
|
|
}
|
|
},
|
|
submit: function(cb = {}) {
|
|
if (typeof cb === 'function') {
|
|
return (this.submitter = cb);
|
|
} else {
|
|
this.submitter(this.getData());
|
|
}
|
|
},
|
|
setValidators: function(validators) {
|
|
_validators = validators;
|
|
return this;
|
|
},
|
|
validate: function() {
|
|
const data = this.getData();
|
|
// just pass along to the Changeset for now
|
|
if (typeof data.validate === 'function') {
|
|
data.validate();
|
|
}
|
|
return this;
|
|
},
|
|
addError: function(name, message) {
|
|
const data = this.getData();
|
|
if (typeof data.addError === 'function') {
|
|
data.addError(...arguments);
|
|
}
|
|
},
|
|
form: function(name) {
|
|
if (name == null) {
|
|
return this;
|
|
}
|
|
return _children[name];
|
|
},
|
|
has: function(name) {
|
|
return typeof _children[name] !== 'undefined';
|
|
},
|
|
};
|
|
form.submit = form.submit.bind(form);
|
|
form.reset = form.reset.bind(form);
|
|
return form;
|
|
};
|
|
}
|