UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

403 lines (366 loc) 10.7 kB
/* ************************************************************************ qooxdoo - the new era of web development http://qooxdoo.org Copyright: 2017 Zenesis Limited, http://www.zenesis.com License: MIT: https://opensource.org/licenses/MIT See the LICENSE file in the project's top-level directory for details. Authors: * John Spackman (john.spackman@zenesis.com, @johnspackman) ************************************************************************ */ /** * A mixin providing objects by ID and owners. * * The typical use of IDs is to override the `_createQxObjectImpl` method and create * new instances on demand; all code should access these instances by calling * `getQxObject`. */ qx.Mixin.define("qx.core.MObjectId", { /* * **************************************************************************** * PROPERTIES * **************************************************************************** */ properties: { /** The owning object */ qxOwner: { init: null, check: "qx.core.Object", nullable: true, apply: "_applyQxOwner" }, /** {String} The ID of the object. */ qxObjectId: { init: null, check(value) { return ( value === null || (typeof value == "string" && value.indexOf("/") < 0) ); }, nullable: true, apply: "_applyQxObjectId" } }, /* * **************************************************************************** * MEMBERS * **************************************************************************** */ statics: { handleObjects(clazz, instance, id) { const objectsDef = clazz.$$objects; const clazzObject = objectsDef?.[id]?.call(instance); if (clazzObject !== undefined) { return clazzObject; } for (const mixin of clazz.$$includes ?? []) { const mixinObject = qx.core.MObjectId.handleObjects( mixin, instance, id ); if (mixinObject !== undefined) { return mixinObject; } } return undefined; } }, members: { __ownedQxObjects: null, __changingQxOwner: false, /** * Apply owner */ _applyQxOwner(value, oldValue) { if (!this.__changingQxOwner) { throw new Error( "Please use API methods to change owner, not the property" ); } }, /** * Apply objectId */ _applyQxObjectId(value, oldValue) { if (!this.__changingQxOwner) { var owner = this.getQxOwner(); if (owner) { owner.__onOwnedObjectIdChange(this, value, oldValue); } this._cascadeQxObjectIdChanges(); } }, /** * Called when a child's objectId changes */ __onOwnedObjectIdChange(obj, newId, oldId) { delete this.__ownedQxObjects[oldId]; this.__ownedQxObjects[newId] = obj; }, /** * Reflect changes to IDs or owners */ _cascadeQxObjectIdChanges() { if (typeof this.getContentElement == "function") { var contentElement = this.getContentElement(); if (contentElement) { contentElement.updateObjectId(); } } if (this.__ownedQxObjects) { for (var name in this.__ownedQxObjects) { var obj = this.__ownedQxObjects[name]; if (obj instanceof qx.core.Object) { obj._cascadeQxObjectIdChanges(); } } } }, /** * Returns the object with the specified ID * * @param id * {String} ID of the object * @return {qx.core.Object?} the found object */ getQxObject(id) { if (this.__ownedQxObjects) { var obj = this.__ownedQxObjects[id]; if (obj !== undefined) { return obj; } } // Separate out the child control ID var controlId = null; var pos = id.indexOf("#"); if (pos > -1) { controlId = id.substring(pos + 1); id = id.substring(0, pos); } var result = undefined; // Handle paths if (id.indexOf("/") > -1) { var segments = id.split("/"); var target = this; var found = segments.every(segment => { if (!segment.length) { return true; } if (!target) { return false; } var tmp; if (segment === "..") { tmp = target.getQxOwner(); } else { tmp = target.getQxObject(segment); } if (tmp !== undefined) { target = tmp; return true; } }); if (found) { result = target; } } else { // No object, creating the object result = this._createQxObject(id); } if (result && controlId) { var childControl = result.getChildControl(controlId); return childControl; } if (!qx.core.Environment.get("qx.core.Object.allowUndefinedObjectId")) { if (result === undefined) { throw new Error( `Cannot find a QX Object in ${this.classname} [${this}] with id=${id}` ); } } return result; }, /** * Creates the object and adds it to a list; most classes are expected to * override `_createQxObjectImpl` NOT this method. * * @param id {String} ID of the object * @return {qx.core.Object?} the created object */ _createQxObject(id) { var result = this._createQxObjectImpl(id); if (result !== undefined) { this.addOwnedQxObject(result, id); } return result; }, /** * Creates the object, intended to be overridden. Null is a valid return * value and will be cached by `getQxObject`, however `undefined` is NOT a * valid value and so will not be cached meaning that `_createQxObjectImpl` * will be called multiple times until a valid value is returned. * * @param id {String} ID of the object * @return {qx.core.Object?} the created object */ _createQxObjectImpl(id) { return undefined; }, /** * Adds an object as owned by this object * * @param obj {qx.core.Object} the object to register * @param id {String?} the id to set when registering the object */ addOwnedQxObject(obj, id) { if (!this.__ownedQxObjects) { this.__ownedQxObjects = {}; } if (!(obj instanceof qx.core.Object)) { if (!id) { throw new Error( "Cannot register an object that has no ID, obj=" + obj ); } if (this.__ownedQxObjects[id]) { throw new Error( "Cannot register an object with ID '" + id + "' because that ID is already in use, this=" + this + ", obj=" + obj ); } this.__ownedQxObjects[id] = obj; return; } var thatOwner = obj.getQxOwner(); if (thatOwner === this) { return; } obj.__changingQxOwner = true; try { if (thatOwner) { thatOwner.__removeOwnedQxObjectImpl(obj); } if (id === undefined) { id = obj.getQxObjectId(); } if (!id) { throw new Error( "Cannot register an object that has no ID, obj=" + obj ); } if (this.__ownedQxObjects[id]) { throw new Error( "Cannot register an object with ID '" + id + "' because that ID is already in use, this=" + this + ", obj=" + obj ); } if (obj.getQxOwner() != null) { throw new Error( "Cannot register an object with ID '" + id + "' because it is already owned by another object this=" + this + ", obj=" + obj ); } obj.setQxOwner(this); obj.setQxObjectId(id); obj._cascadeQxObjectIdChanges(); } finally { obj.__changingQxOwner = false; } this.__ownedQxObjects[id] = obj; }, /** * Discards an object from the list of owned objects; note that this does * not dispose of the object, simply forgets it if it exists. * * @param args {String|Object} the ID of the object to discard, or the object itself */ removeOwnedQxObject(args) { if (!this.__ownedQxObjects) { throw new Error( "Cannot discard object because it is not owned by this, this=" + this + ", object=" + obj ); } var id; var obj; if (typeof args === "string") { if (args.indexOf("/") > -1) { throw new Error("Cannot discard owned objects based on a path"); } id = args; obj = this.__ownedQxObjects[id]; if (obj === undefined) { return; } } else { obj = args; if (!(obj instanceof qx.core.Object)) { throw new Error( "Cannot discard object by reference because it is not a Qooxdoo object, please remove it using the original ID; object=" + obj ); } id = obj.getQxObjectId(); if (this.__ownedQxObjects[id] !== obj) { throw new Error( "Cannot discard object because it is not owned by this, this=" + this + ", object=" + obj ); } } if (obj !== null) { if (!(obj instanceof qx.core.Object)) { this.__removeOwnedQxObjectImpl(obj); delete this.__ownedQxObjects[id]; } else { obj.__changingQxOwner = true; try { this.__removeOwnedQxObjectImpl(obj); obj._cascadeQxObjectIdChanges(); } finally { obj.__changingQxOwner = false; } } } }, /** * Removes an owned object * * @param obj {qx.core.Object} the object */ __removeOwnedQxObjectImpl(obj) { if (obj !== null) { var id = obj.getQxObjectId(); obj.setQxOwner(null); delete this.__ownedQxObjects[id]; } }, /** * Returns an array of objects that are owned by this object, or an empty * array if none exists. * * @return {Array} */ getOwnedQxObjects() { return this.__ownedQxObjects ? Object.values(this.__ownedQxObjects) : []; } } });