@mlightcad/common
Version:
The common package provides shared utilities and base classes that are used across the RealDWG-Web ecosystem. This package contains fundamental components for color management, event handling, logging, performance monitoring, and file loading operations.
175 lines • 6.76 kB
JavaScript
import { AcCmEventManager } from './AcCmEventManager';
import { clone, defaults, has, isEmpty, isEqual } from './AcCmLodashUtils';
/**
* This class is used to store attributes of one data model. It has the following benifits.
* - Get notification when value of one attributes is changed
* - Have one `changed` property to store all of changed values
* - Store all of states of one model in one key/value object and make it is easy to serialize/deserialize model and model changes
*
* Actually implementation of this class is based on class Model in Backbone.js. However, Model class in Backbone
* is too heavy. So we implement it again based on source code of class Model in Backbone.js. Morever, we want to
* keep our library with less external dependencies and don't want to introduce depenedency on Backbone.js.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
var AcCmObject = /** @class */ (function () {
/**
* Create one object to store attributes. For performance reason, values of attributes passed to constructor
* will not be cloned to `attributes` property. So it means that value of `attributes` property in this object
* is just a reference to arguments `attributes` passed to constructor.
* @param attributes Input attributes to store in this object
*/
function AcCmObject(attributes, defaultAttrs) {
this.events = {
attrChanged: new AcCmEventManager(),
modelChanged: new AcCmEventManager()
};
this._changing = false;
this._previousAttributes = {};
this._pending = false;
var attrs = attributes || {};
if (defaultAttrs) {
defaults(attrs, defaultAttrs);
}
this.attributes = attrs;
this.changed = {};
}
/**
* For strongly-typed access to attributes, use the `get` method only privately in public getter properties.
* @example
* get name(): string {
* return super.get("name")
* }
*/
AcCmObject.prototype.get = function (key) {
return this.attributes[key];
};
AcCmObject.prototype.set = function (key, val, options) {
if (key == null)
return this;
// Handle both `"key", value` and `{key: value}` -style arguments.
var attrs;
if (typeof key === 'object') {
attrs = key;
options = val;
}
else {
attrs = {};
attrs[key] = val;
}
options || (options = {});
// Extract attributes and options.
var unset = options.unset;
var silent = options.silent;
var changes = [];
var changing = this._changing;
this._changing = true;
if (!changing) {
this._previousAttributes = clone(this.attributes);
this.changed = {};
}
var current = this.attributes;
var changed = this.changed;
var prev = this._previousAttributes;
// For each `set` attribute, update or delete the current value.
for (var attr in attrs) {
val = attrs[attr];
if (!isEqual(current[attr], val))
changes.push(attr);
if (!isEqual(prev[attr], val)) {
changed[attr] = val;
}
else {
delete changed[attr];
}
unset ? delete current[attr] : (current[attr] = val);
}
// Trigger all relevant attribute changes.
if (!silent) {
// @ts-expect-error just keep backbone implementation as is
if (changes.length)
this._pending = options;
for (var i = 0; i < changes.length; i++) {
this.events.attrChanged.dispatch({
object: this,
attrName: changes[i],
attrValue: current[changes[i]],
options: options
});
}
}
// You might be wondering why there's a `while` loop here. Changes can
// be recursively nested within `"change"` events.
if (changing)
return this;
if (!silent) {
while (this._pending) {
// @ts-expect-error just keep backbone implementation as is
options = this._pending;
this._pending = false;
this.events.modelChanged.dispatch({
object: this,
options: options
});
}
}
this._pending = false;
this._changing = false;
return this;
};
AcCmObject.prototype.has = function (key) {
return this.get(key) != null;
};
/**
* Determine if the model has changed since the last `"change"` event.
* If you specify an attribute name, determine if that attribute has changed.
*/
AcCmObject.prototype.hasChanged = function (key) {
if (key == null)
return !isEmpty(this.changed);
return has(this.changed, key);
};
/**
* Return an object containing all the attributes that have changed. Useful for determining what parts
* of a view need to be updated and/or what attributes need to be persisted to the server.
*
* Unset attributes will be set to undefined. You can also pass an attributes object to diff against
* the model, determining if there *would be* a change.
*/
AcCmObject.prototype.changedAttributes = function (diff) {
if (!diff)
return this.hasChanged() ? clone(this.changed) : {};
var old = this._changing ? this._previousAttributes : this.attributes;
var changed = {};
for (var attr in diff) {
var val = diff[attr];
if (isEqual(old[attr], val))
continue;
changed[attr] = val;
}
return changed;
};
/**
* Get the previous value of an attribute, recorded at the time the last `"change"` event was fired.
*/
AcCmObject.prototype.previous = function (key) {
if (key == null || !this._previousAttributes)
return null;
return this._previousAttributes[key];
};
/**
* Get all of the attributes of the model at the time of the previous `"change"` event.
*/
AcCmObject.prototype.previousAttributes = function () {
return clone(this._previousAttributes);
};
/**
* Create a new model with identical attributes to this one.
*/
AcCmObject.prototype.clone = function () {
var attrs = clone(this.attributes);
return new AcCmObject(attrs);
};
return AcCmObject;
}());
export { AcCmObject };
//# sourceMappingURL=AcCmObject.js.map