UNPKG

ember-changeset

Version:
297 lines (277 loc) 7.65 kB
import { assert } from '@ember/debug'; import { dependentKeyCompat } from '@ember/object/compat'; import { BufferedChangeset } from 'validated-changeset'; export { Changeset as ValidatedChangeset } from './validated-changeset.js'; import ArrayProxy from '@ember/array/proxy'; import ObjectProxy from '@ember/object/proxy'; import { get, set, notifyPropertyChange } from '@ember/object'; import mergeDeep from './utils/merge-deep.js'; import isObject from './utils/is-object.js'; import { tracked } from '@glimmer/tracking'; import { macroCondition, dependencySatisfies, importSync } from '@embroider/macros'; import { g, i, n } from 'decorator-transforms/runtime'; export { default as changesetGet } from './helpers/changeset-get.js'; export { default as changesetSet } from './helpers/changeset-set.js'; const CHANGES = '_changes'; const PREVIOUS_CONTENT = '_previousContent'; const CONTENT = '_content'; const defaultValidatorFn = () => true; function buildOldValues(content, changes, getDeep) { const obj = Object.create(null); for (let change of changes) { obj[change.key] = getDeep(content, change.key); } return obj; } function isProxy(o) { return !!(o && (o instanceof ObjectProxy || o instanceof ArrayProxy)); } function maybeUnwrapProxy(o) { return isProxy(o) ? maybeUnwrapProxy(get(o, 'content')) : o; } let Model; if (macroCondition(dependencySatisfies('ember-data', '*'))) { Model = importSync('@ember-data/model').default; } class EmberChangeset extends BufferedChangeset { static { g(this.prototype, "_changes", [tracked]); } #_changes = (i(this, "_changes"), void 0); static { g(this.prototype, "_errors", [tracked]); } #_errors = (i(this, "_errors"), void 0); static { g(this.prototype, "_content", [tracked]); } #_content = (i(this, "_content"), void 0); isObject = isObject; maybeUnwrapProxy = maybeUnwrapProxy; // DO NOT override setDeep. Ember.set does not work wth empty hash and nested // key Ember.set({}, 'user.name', 'foo'); // override base class // DO NOT override setDeep. Ember.set does not work with Ember.set({}, 'user.name', 'foo'); getDeep = get; mergeDeep = mergeDeep; safeGet(obj, key) { if (Model && obj.relationshipFor?.(key)?.meta?.kind == 'belongsTo') { return obj.belongsTo(key).value(); } return get(obj, key); } safeSet(obj, key, value) { return set(obj, key, value); } /** * @property isValid * @type {Array} */ get isValid() { return super.isValid; } /** * @property isInvalid * @type {Boolean} */ static { n(this.prototype, "isValid", [dependentKeyCompat]); } get isInvalid() { return super.isInvalid; } /** * @property isPristine * @type {Boolean} */ static { n(this.prototype, "isInvalid", [dependentKeyCompat]); } get isPristine() { return super.isPristine; } /** * @property isDirty * @type {Boolean} */ static { n(this.prototype, "isPristine", [dependentKeyCompat]); } get isDirty() { return super.isDirty; } static { n(this.prototype, "isDirty", [dependentKeyCompat]); } get pendingData() { let content = this[CONTENT]; let changes = this[CHANGES]; let pendingChanges = this.mergeDeep(Object.create(Object.getPrototypeOf(content)), content, { safeGet: get, safeSet: set }); return this.mergeDeep(pendingChanges, changes, { safeGet: get, safeSet: set }); } /** * Manually add an error to the changeset. If there is an existing * error or change for `key`, it will be overwritten. * * @method addError */ addError(key, error) { super.addError(key, error); notifyPropertyChange(this, key); // Return passed-in `error`. return error; } /** * Manually remove an error from the changeset. * * @method removeError */ removeError(key) { super.removeError(key); notifyPropertyChange(this, key); return this; } /** * Manually clears the errors from the changeset * * @method removeError */ removeErrors() { super.removeErrors(); return this; } /** * Manually push multiple errors to the changeset as an array. * * @method pushErrors */ pushErrors(key, ...newErrors) { const { value, validation } = super.pushErrors(key, ...newErrors); notifyPropertyChange(this, key); return { value, validation }; } /** * Sets property or error on the changeset. * Returns value or error */ _setProperty({ key, value, oldValue }) { super._setProperty({ key, value, oldValue }); notifyPropertyChange(this, key); } /** * Notifies virtual properties set on the changeset of a change. * You can specify which keys are notified by passing in an array. * * @private * @param {Array} keys * @return {Void} */ _notifyVirtualProperties(keys) { keys = super._notifyVirtualProperties(keys); (keys || []).forEach(key => notifyPropertyChange(this, key)); return; } /** * Deletes a key off an object and notifies observers. */ _deleteKey(objName, key = '') { const result = super._deleteKey(objName, key); notifyPropertyChange(this, key); return result; } /** * Executes the changeset if in a valid state. * * @method execute */ execute() { let oldContent; if (this.isValid && this.isDirty) { let content = this[CONTENT]; let changes = this[CHANGES]; // keep old values in case of error and we want to rollback oldContent = buildOldValues(content, this.changes, this.getDeep); // we want mutation on original object // @tracked this[CONTENT] = this.mergeDeep(content, changes, { safeGet: get, safeSet: set }); } this[PREVIOUS_CONTENT] = oldContent; return this; } } /** * Creates new changesets. */ function changeset(obj, validateFn = defaultValidatorFn, validationMap = {}, options = {}) { assert('Underlying object for changeset is missing', Boolean(obj)); assert('Array is not a valid type to pass as the first argument to `changeset`', !Array.isArray(obj)); if (options.changeset) { return new options.changeset(obj, validateFn, validationMap, options); } const c = new EmberChangeset(obj, validateFn, validationMap, options); return c; } /** * Creates new changesets. * @function Changeset */ function Changeset(obj, validateFn = defaultValidatorFn, validationMap = {}, options = {}) { const c = changeset(obj, validateFn, validationMap, options); return new Proxy(c, { get(targetBuffer, key /*, receiver*/) { const res = targetBuffer.get(key.toString()); return res; }, set(targetBuffer, key, value /*, receiver*/) { targetBuffer.set(key.toString(), value); return true; } }); } class ChangesetKlass { /** * Changeset factory * TODO: deprecate in favor of factory function * * @class ChangesetKlass * @constructor */ constructor(obj, validateFn = defaultValidatorFn, validationMap = {}, options = {}) { const c = changeset(obj, validateFn, validationMap, options); return new Proxy(c, { get(targetBuffer, key /*, receiver*/) { const res = targetBuffer.get(key.toString()); return res; }, set(targetBuffer, key, value /*, receiver*/) { targetBuffer.set(key.toString(), value); return true; } }); } } export { Changeset, EmberChangeset, buildOldValues, changeset, ChangesetKlass as default }; //# sourceMappingURL=index.js.map