123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202 |
- 'use strict';
- var Collection = require('./collection');
- function hasOwnProperty(e, property) {
- return Object.prototype.hasOwnProperty.call(e, property.name || property);
- }
- function defineCollectionProperty(ref, property, target) {
- var collection = Collection.extend(target[property.name] || [], ref, property, target);
- Object.defineProperty(target, property.name, {
- enumerable: property.enumerable,
- value: collection
- });
- if (collection.length) {
- collection.forEach(function(o) {
- ref.set(o, property.inverse, target);
- });
- }
- }
- function defineProperty(ref, property, target) {
- var inverseProperty = property.inverse;
- var _value = target[property.name];
- Object.defineProperty(target, property.name, {
- configurable: property.configurable,
- enumerable: property.enumerable,
- get: function() {
- return _value;
- },
- set: function(value) {
- // return if we already performed all changes
- if (value === _value) {
- return;
- }
- var old = _value;
- // temporary set null
- _value = null;
- if (old) {
- ref.unset(old, inverseProperty, target);
- }
- // set new value
- _value = value;
- // set inverse value
- ref.set(_value, inverseProperty, target);
- }
- });
- }
- /**
- * Creates a new references object defining two inversly related
- * attribute descriptors a and b.
- *
- * <p>
- * When bound to an object using {@link Refs#bind} the references
- * get activated and ensure that add and remove operations are applied
- * reversely, too.
- * </p>
- *
- * <p>
- * For attributes represented as collections {@link Refs} provides the
- * {@link RefsCollection#add}, {@link RefsCollection#remove} and {@link RefsCollection#contains} extensions
- * that must be used to properly hook into the inverse change mechanism.
- * </p>
- *
- * @class Refs
- *
- * @classdesc A bi-directional reference between two attributes.
- *
- * @param {Refs.AttributeDescriptor} a property descriptor
- * @param {Refs.AttributeDescriptor} b property descriptor
- *
- * @example
- *
- * var refs = Refs({ name: 'wheels', collection: true, enumerable: true }, { name: 'car' });
- *
- * var car = { name: 'toyota' };
- * var wheels = [{ pos: 'front-left' }, { pos: 'front-right' }];
- *
- * refs.bind(car, 'wheels');
- *
- * car.wheels // []
- * car.wheels.add(wheels[0]);
- * car.wheels.add(wheels[1]);
- *
- * car.wheels // [{ pos: 'front-left' }, { pos: 'front-right' }]
- *
- * wheels[0].car // { name: 'toyota' };
- * car.wheels.remove(wheels[0]);
- *
- * wheels[0].car // undefined
- */
- function Refs(a, b) {
- if (!(this instanceof Refs)) {
- return new Refs(a, b);
- }
- // link
- a.inverse = b;
- b.inverse = a;
- this.props = {};
- this.props[a.name] = a;
- this.props[b.name] = b;
- }
- /**
- * Binds one side of a bi-directional reference to a
- * target object.
- *
- * @memberOf Refs
- *
- * @param {Object} target
- * @param {String} property
- */
- Refs.prototype.bind = function(target, property) {
- if (typeof property === 'string') {
- if (!this.props[property]) {
- throw new Error('no property <' + property + '> in ref');
- }
- property = this.props[property];
- }
- if (property.collection) {
- defineCollectionProperty(this, property, target);
- } else {
- defineProperty(this, property, target);
- }
- };
- Refs.prototype.ensureRefsCollection = function(target, property) {
- var collection = target[property.name];
- if (!Collection.isExtended(collection)) {
- defineCollectionProperty(this, property, target);
- }
- return collection;
- };
- Refs.prototype.ensureBound = function(target, property) {
- if (!hasOwnProperty(target, property)) {
- this.bind(target, property);
- }
- };
- Refs.prototype.unset = function(target, property, value) {
- if (target) {
- this.ensureBound(target, property);
- if (property.collection) {
- this.ensureRefsCollection(target, property).remove(value);
- } else {
- target[property.name] = undefined;
- }
- }
- };
- Refs.prototype.set = function(target, property, value) {
- if (target) {
- this.ensureBound(target, property);
- if (property.collection) {
- this.ensureRefsCollection(target, property).add(value);
- } else {
- target[property.name] = value;
- }
- }
- };
- module.exports = Refs;
- /**
- * An attribute descriptor to be used specify an attribute in a {@link Refs} instance
- *
- * @typedef {Object} Refs.AttributeDescriptor
- * @property {String} name
- * @property {boolean} [collection=false]
- * @property {boolean} [enumerable=false]
- */
|