jstd-web/node_modules/object-refs/lib/refs.js

202 lines
4.4 KiB
JavaScript
Raw Normal View History

2025-11-25 15:23:22 +08:00
'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]
*/