import { Flags, ObjectProxy } from '@uino/base-thing';
import { MathUtils } from '../math/MathUtils';
import { Utils } from '../common/Utils'
import { Selector } from '../selector/Selector';
import { DynamicSelector } from '../selector/DynamicSelector';
import { BaseComponentGroup } from '../components/BaseComponentGroup';
import { EventType } from '../const';
const __ = {
private: Symbol('private'),
}
const Flag = {
Destroyed: 1 << 0,
Dirty: 1 << 1,
Queryable: 1 << 2,
Destroyable: 1 << 3,
}
let _orderId = 1;
let _dummySelector = new Selector();
let _eventListeners = [];
let _defaultParams = {};
// #region Private Functions
// #endregion
/**
* @class BaseObject
* The base object.
* @memberof THING
* @extends THING.BaseComponentGroup
* @public
*/
class BaseObject extends BaseComponentGroup {
/**
* @typedef BaseObjectInitialOptions
* @property {String} [name=''] The name.
* @property {String} [id=''] The id.
*/
/**
* The base object.
* @param {BaseObjectInitialOptions} param The initial parameters.
*/
constructor(param = {}) {
super();
this[__.private] = {};
let _private = this[__.private];
let app = Utils.getCurrentApp();
// Common
_private.app = app;
_private.userData = null;
_private.type = '';
// Temporary Data
_private.tempData = null;
// Update internal order ID for fast searching
_private.orderId = _orderId++;
// Relation
_private.parent = null;
_private.children = null;
// The proxy object
_private.proxyObject = null;
// Show important info what we can not wrap into private space, so bind to object
this._name = Utils.parseValue(param['name'], '');
this._id = Utils.parseValue(param['id'], '');
this._uuid = param['uuid'] || MathUtils.generateUUID();
// Setup flags
this._flags = new Flags();
this._flags.enable(Flag.Queryable, Utils.parseValue(param['queryable'], true));
this._flags.enable(Flag.Destroyable, Utils.parseValue(param['destroyable'], true));
// Setup type first, because we need type attribute
this.onSetupType(param);
// Setup
this.onBeforeSetup(param);
this.onSetupParent(param); // Set parent first to make sure parent order ID is valid
this.onSetupAsyncSelector(param); // Add object first to make sure object order ID is valid
this.onSetupUserData(param);
this.onAfterSetup(param);
// Add object into manager
app.objectManager.addObject(this);
}
// #region Private
_removeFromParent() {
let _private = this[__.private];
// Remove it from previous parent
if (_private.parent) {
let children = _private.parent.children;
let index = children.indexOf(this);
if (index !== -1) {
children.removeAt(index);
}
_private.parent = null;
}
}
_addEventListener(type, condition, callback, tag, priority, options, once = false) {
let info = Utils.parseEvent(type, condition, callback, tag, priority, options, this.onInitEventArgs);
if (this.onParseEvent(info)) {
return this.app.eventManager.addEventListener(info.type.toLowerCase(), this, info.condition, info.callback, info.tag, {
once,
priority: info.priority,
useCapture: info.useCapture,
enumerable: info.enumerable,
pausable: info.pausable,
includeSelf: info.includeSelf
});
}
return null;
}
_getEventListener(type, condition, tag) {
return this.app.eventManager.getEventListener(type.toLowerCase(), this, condition, tag);
}
_removeEventListener(type, condition, tag) {
this.app.eventManager.removeEventListener(type.toLowerCase(), this, condition, tag);
}
_getUserData() {
let _private = this[__.private];
let userData = _private.userData;
if (userData) {
return userData;
}
if (Selector.asyncSelector) {
_private.userData = new ObjectProxy({
onChange: (ev) => {
Selector.updateObjectAttribute(this, 'userData', ev.data);
}
});
}
else {
_private.userData = {};
}
return _private.userData;
}
_getTempData(callback) {
let _private = this[__.private];
if (!_private.tempData) {
_private.tempData = {
tickableCounter: 0,
resizableCounter: 0,
};
if (callback) {
callback(_private.tempData);
}
}
return _private.tempData;
}
_getParentObject(param) {
let parent = param['parent'];
if (parent === null) {
return null;
}
return parent || this.app.root;
}
_isExpression(condition) {
// It's string format
if (condition._startsWith('"') || condition._startsWith('\'')) {
return true;
}
// It's id format
if (condition._startsWith('#')) {
return true;
}
// It's type format
if (condition._startsWith('.')) {
return true;
}
// It's attribute name
if (condition._startsWith('[') && condition._endsWith(']')) {
return true;
}
// Has OR operator
if (condition.indexOf('||') !== -1 || condition.indexOf('|') !== -1) {
return true;
}
// Has AND operator
if (condition.indexOf('&&') !== -1 || condition.indexOf('&') !== -1) {
return true;
}
// It's tags format
if (condition._startsWith('tags')) {
if (condition[4] === ':' || condition[4] === '(') {
return true;
}
}
return false;
}
// #endregion
// #region Overrides
onAddTickableObject() {
let tempData = this._getTempData();
if (tempData['tickableCounter'] === 0) {
this.app.objectManager.addTickableObject(this);
}
tempData['tickableCounter']++;
}
onAddResizableObject() {
let tempData = this._getTempData();
if (tempData['resizableCounter'] === 0) {
this.app.objectManager.addResizableObject(this);
}
tempData['resizableCounter']++;
}
onRemoveTickableObject() {
let tempData = this._getTempData();
tempData['tickableCounter']--;
if (tempData['tickableCounter'] === 0) {
this.app.objectManager.removeTickableObject(this);
}
}
onRemoveResizableObject() {
let tempData = this._getTempData();
tempData['resizableCounter']--;
if (tempData['resizableCounter'] === 0) {
this.app.objectManager.removeResizableObject(this);
}
}
// #endregion
// #region Setup
onBeforeSetup(param) {
}
onAfterSetup(param) {
}
onSetupType(param) {
let _private = this[__.private];
let prototype = this.__proto__;
let className = Utils.getClassNameFromProto(prototype);
_private.type = className;
// Check whether we had processed yet
prototype.__inherits = prototype.__inherits || {};
if (prototype.__inherits[className]) {
return;
}
prototype.__inherits[className] = true;
// The types of each class to get inherition
let types = {};
Object.defineProperty(prototype, '__types', {
enumerable: false,
configurable: false,
get() {
return types;
},
});
// Get the class names
let classNames = this.app.global.getClassNamesFromObject(this);
classNames.forEach(name => {
types[name] = true;
let key = 'is' + name;
if (!this[key]) {
Object.defineProperty(prototype, key, {
enumerable: false,
configurable: false,
get() {
return types[name];
},
});
}
});
}
onSetupParent(param) {
let parent = this._getParentObject(param);
if (!parent) {
return;
}
parent.add(this, { attachMode: false });
}
onSetupAsyncSelector(param) {
// Add into async selector
Selector.addObject(this);
}
onSetupUserData(param) {
let fromUserData = param['userData'];
if (!fromUserData) {
return;
}
let toUserData = this._getUserData();
if (toUserData.isObjectProxy) {
toUserData.copy(fromUserData);
}
else {
Object.assign(toUserData, fromUserData);
}
}
// #endregion
// #region Overrides
/**
* When update.
* @param {Number} deltaTime The delta time in seconds.
* @private
*/
onUpdate(deltaTime) {
let tickableComponents = this.tickableComponents;
if (tickableComponents) {
for (let i = 0, l = tickableComponents.length; i < l; i++) {
let component = tickableComponents[i];
if (component.active === false) {
continue;
}
component.onUpdate(deltaTime);
}
}
}
/**
* When resize.
* @param {Number} width The width in pixel.
* @param {Number} height The height in pixel.
* @private
*/
onResize(width, height) {
let resizableComponents = this.resizableComponents;
if (resizableComponents) {
for (let i = 0, l = resizableComponents.length; i < l; i++) {
let component = resizableComponents[i];
component.onResize(width, height);
}
}
}
/**
* When refresh.
* @private
*/
onRefresh() {
let refreshableComponents = this.refreshableComponents;
if (refreshableComponents) {
for (let i = 0, l = refreshableComponents.length; i < l; i++) {
let component = refreshableComponents[i];
component.onRefresh();
}
}
this._flags.enable(Flag.Dirty, false);
}
/**
* When before destroy.
* @returns {Boolean} false indicates break the destroy operation.
* @private
*/
onBeforeDestroy() {
let flags = this._flags;
// Check whether can be destroyed
if (!flags.has(Flag.Destroyable)) {
return false;
}
// Check whether had destroyed
if (flags.has(Flag.Destroyed)) {
return false;
}
return true;
}
/**
* When destroy.
* @private
*/
onDestroy() {
let _private = this[__.private];
// Notify it's going to destroy it
this.trigger(EventType.Destroy);
// Notify parent to remove child object
let parent = _private.parent;
if (parent) {
parent.onBeforeRemoveChild(this);
this.onSetParent(null);
parent.onAfterRemoveChild(this);
}
// Remove all children
if (_private.children) {
let children = _private.children.slice(0);
children.forEach(child => {
child.destroy();
});
_private.children.clear();
}
// Remove it from object manager
_private.app.objectManager.removeObject(this);
// Remove it from async selector
Selector.removeObject(this);
// Remove all components
this.removeAllComponents(true);
// Clear all accessor info
this.clearAccessorInfo();
// Clear user data
let userData = _private.userData;
if (userData) {
if (userData.isObjectProxy) {
userData.dispose();
}
_private.userData = null;
}
// We must clear parent here, due to 'AfterDestroy' event need to check parent object
_private.parent = null;
// Remove all events of it
_private.app.eventManager.removeAllEventListeners(this);
// Make it destroyed, be careful:
// We must set the destroy flag after object manager remove it to prevent 'Destroy' event trigger failed
this._flags.enable(Flag.Destroyed, true);
}
/**
* When after destroy.
* @private
*/
onAfterDestroy() {
let _private = this[__.private];
// Remove it from previous parent
this._removeFromParent();
// Resume all paused events
_private.app.eventManager.resumeAllEvents(this);
_private.app = null;
}
onParseEvent(info) {
return true;
}
onBeforeAddChild(object) {
// Notify all components
let components = this.components;
components.forEach((component) => {
if (component.onBeforeAddChild) {
component.onBeforeAddChild(object);
}
})
this.trigger(EventType.BeforeAddChild, { child: object });
}
onAddChild(object) {
object.onSetParent(this);
}
onAfterAddChild(object) {
// Notify all components
let components = this.components;
components.forEach((component) => {
if (component.onAfterAddChild) {
component.onAfterAddChild(object);
}
})
this.trigger(EventType.AfterAddChild, { child: object });
}
onBeforeRemoveChild(object) {
// Notify all components
let components = this.components;
components.forEach((component) => {
if (component.onBeforeRemoveChild) {
component.onBeforeRemoveChild(object);
}
})
this.trigger(EventType.BeforeRemoveChild, { child: object });
}
onAfterRemoveChild(object) {
// Notify all components
let components = this.components;
components.forEach((component) => {
if (component.onAfterRemoveChild) {
component.onAfterRemoveChild(object);
}
})
this.trigger(EventType.AfterRemoveChild, { child: object });
}
// #endregion
// #region BaseComponentGroup Interfaces
onAddComponent(component, args) {
super.onAddComponent(component, args);
if (component.onUpdate || component.onRender) {
this.onAddTickableObject();
}
if (component.onResize) {
this.onAddResizableObject();
}
}
onRemoveComponent(component) {
super.onRemoveComponent(component);
if (component.onUpdate || component.onRender) {
this.onRemoveTickableObject();
}
if (component.onResize) {
this.onRemoveResizableObject();
}
}
onBeforeSetParent() {
}
// #endregion
// #region Common
/**
* Check whether has attribute.
* @param {String} name The attribute name, it can use like 'a/b/c' to access attribute.
* @returns {Boolean}
* @example
* let object = new THING.BaseObject();
* object.userData['power'] = 100;
* let ret = object.hasAttribute('userData/power');
* // @expect(ret == true)
* @public
*/
hasAttribute(name, separator = '/') {
return Utils.hasAttribute(this, name, separator);
}
/**
* Get attribute value.
* @param {String} name The attribute name, it can use like 'a/b/c' to access attribute.
* @returns {*}
* @example
* let object = new THING.BaseObject();
* object.userData['power'] = 100;
* let power = object.getAttribute('userData/power');
* // @expect(power == 100)
* @public
*/
getAttribute(key, separator = '/') {
return Utils.getAttribute(this, key, separator);
}
/**
* Set attribute value.
* @param {String} name The attribute name, it can use like 'a/b/c' to access attribute.
* @param {*} value The attribute value.
* @example
* let object = new THING.BaseObject();
* object.setAttribute('userData/power', 200);
* let power = object.getAttribute('userData/power');
* // @expect(power == 200)
* @public
*/
setAttribute(key, value, separator = '/') {
Utils.setAttribute(this, key, value, separator);
}
/**
* Set attribute values.
* @param {Object} attributes The attribute values.
* @param {Boolean} [overwrite=true] True indicates overwrite attribute.
* @example
* let object = new THING.BaseObject();
* object.setAttributes({
* "userData/name": 'Mr.Door',
* "userData/age": 18
* })
* let name = object.getAttribute('userData/name');
* // @expect(name == 'Mr.Door')
* let age = object.getAttribute('userData/age');
* // @expect(age == 18)
* @public
*/
setAttributes(attributes = {}, overwrite = true, separator = '/') {
for (let key in attributes) {
if (!overwrite && !Utils.isUndefined(this.getAttribute(key))) {
continue;
}
this.setAttribute(key, attributes[key], separator);
}
this.trigger(EventType.ChangeAttributes, { attributes });
}
/**
* Get application.
* @type {THING.App}
* @example
* let object = new THING.BaseObject();
* let app = object.app;
* let ret = app == THING.App.current;
* // @expect(ret == true)
* @public
*/
get app() {
return this[__.private].app;
}
/**
* Get type.
* @type {String}
* @example
* let object = new THING.BaseObject();
* let type = object.type;
* // @expect(type == 'BaseObject')
* @public
*/
get type() {
return this[__.private].type;
}
/**
* Get order ID.
* @type {Number}
* @private
*/
get orderId() {
return this[__.private].orderId;
}
/**
* Get/Set name.
* @type {String}
* @example
* let object = new THING.BaseObject();
* // @expect(object.name == '');
* object.name = 'car';
* // @expect(object.name == 'car');
* @public
*/
get name() {
return this._name;
}
set name(value) {
this._name = value;
Selector.updateObjectAttribute(this, 'name', value);
}
/**
* Get/Set id.
* @type {String}
* @example
* let object = new THING.BaseObject();
* object.id = 'DEVICE_007';
* // @expect(object.id == 'DEVICE_007')
* @public
*/
get id() {
return this._id;
}
set id(value) {
this._id = value;
Selector.updateObjectAttribute(this, 'id', value);
}
/**
* Get/Set uuid.
* @type {String}
* @example
* let object = new THING.BaseObject({uuid: 10000});
* // @expect(object.uuid == 10000)
* object.uuid = THING.Math.generateUUID();
* // @expect(object.id != 10000)
* @public
*/
get uuid() {
return this._uuid;
}
set uuid(value) {
this._uuid = value;
Selector.updateObjectAttribute(this, 'uuid', value);
}
/**
* Get/Set user data.
* @type {Object}
* @example
* let object = new THING.BaseObject();
* object.userData['Notebook'] = {
* name: 'FlyingCar',
* price: 100
* };
* let name = object.userData['Notebook'].name;
* // @expect(name == 'FlyingCar')
* let price = object.userData['Notebook'].price
* // @expect(price == 100)
* @public
*/
get userData() {
let userData = this._getUserData();
if (userData.isObjectProxy) {
return userData.dataProxy;
}
else {
return userData;
}
}
set userData(value) {
let userData = this._getUserData();
if (userData.isObjectProxy) {
userData.copy(value);
}
else {
Utils.copyObject(userData, value);
}
}
// #endregion
// #region State
/**
* Get queryable state.
* @returns {Boolean}
* @private
*/
getQueryable() {
return this._flags.has(Flag.Queryable);
}
/**
* Set queryable state.
* @param {Boolean} value The state value.
* @param {Boolean} recursive True indicates process it with all children.
* @private
*/
setQueryable(value, recursive) {
// Update queryable flag
this._flags.enable(Flag.Queryable, value);
// Sync background selector's flag
Selector.updateObjectAttribute(this, 'queryable', value);
// Change all children if needed
if (recursive) {
let _private = this[__.private];
if (_private.children) {
let children = _private.children;
for (let i = 0, l = children.length; i < l; i++) {
children[i].setQueryable(value, true);
}
}
}
}
/**
* Make object to call onRefresh() interface later.
* @private
*/
setDirty(force) {
let flags = this._flags;
if (flags.has(Flag.Dirty)) {
return;
}
flags.enable(Flag.Dirty, true);
if (force) {
this.onRefresh();
}
else {
this.app.objectManager.addDirtyObject(this);
}
}
/**
* Destroy.
* @returns {Boolean}
* @example
* let object = new THING.BaseObject();
* // @expect(object.destroyed == false);
* object.destroy();
* // @expect(object.destroyed == true)
* @public
*/
destroy() {
if (this.onBeforeDestroy() === false) {
return false;
}
this.onDestroy();
this.onAfterDestroy();
return true;
}
/**
* Get flags.
* @type {Object}
* @private
*/
get flags() {
return this._flags;
}
/**
* Get destroyed state.
* @type {Boolean}
* @private
*/
get destroyed() {
return this._flags.has(Flag.Destroyed);
}
/**
* Enable/Disable queryable.
* @type {Boolean}
* @example
* let object = new THING.BaseObject();
* object.name = 'Hidden';
* let ret = app.query('Hidden');
* // @expect(ret[0].name = 'Hidden')
* object.queryable = false;
* ret = app.query('Hidden');
* // @expect(ret.length = 0)
* @public
*/
get queryable() {
return this._flags.has(Flag.Queryable);
}
set queryable(value) {
this.setQueryable(value, true);
}
// #endregion
// #region Event
/**
* Check whether has specified event listener.
* @property {String} type The event type.
* @property {String} tag The event tag.
* @returns {Boolean}
* @private
*/
hasEvent(type, tag) {
_eventListeners.length = 0;
let events = this.app.eventManager.getEventListeners(type.toLowerCase(), this, _eventListeners);
for (let i = 0; i < events.length; i++) {
let event = events[i];
if (event.removed) {
continue;
}
if (tag !== event.tag) {
continue;
}
return true;
}
return false;
}
/**
* @typedef {Object} ObjectListenerInfo
* @property {String} type The event type.
* @property {String} condition The condition to select objects.
* @property {Function} callback The event callback.
* @property {String} tag The event tag.
* @property {Number} priority The event priority.
* @property {Boolean} once True indicates it's trigger only once event.
* @property {Boolean} paused True indicates it had paused.
*/
/**
* Get events with type.
* @param {String} type The event type, null indicates get all events.
* @returns {Array<ObjectListenerInfo>}
* @private
*/
getEvents(type) {
let eventManager = this.app.eventManager;
let events;
if (type) {
events = eventManager.getEventListeners(type.toLowerCase(), this);
}
else {
events = eventManager.getAllEventListeners(this);
}
let aliveEvents = [];
events.forEach(event => {
if (event.removed) {
return;
}
if (!event.enumerable) {
return;
}
aliveEvents.push(event);
});
return aliveEvents;
}
/**
* Get event.
* @param {String} type The event type.
* @param {String} condition The condition to select objects.
* @param {String} tag The event tag.
* @returns {ObjectListenerInfo}
* @private
*/
getEvent(type, condition, tag) {
type = type.toLowerCase();
let events = this.getEvents();
for (let i = 0; i < events.length; i++) {
let event = events[i];
if (event.type != type) {
continue;
}
if (event.condition != condition) {
continue;
}
if (event.tag != tag) {
continue;
}
return event;
}
return null;
}
/**
* @typedef {Object} ObjectEventOptions
* @property {Boolean} useCapture True indicates capture all same events from children.
*/
/**
* Register event.
* @param {String} type The event type.
* @param {String} condition The condition to select objects.
* @param {Function} callback The callback function.
* @param {String} tag The event tag.
* @param {Number} priority The priority value(default is 0, higher value will be processed first).
* @param {ObjectEventOptions} options The options.
* @example
* let object = new THING.BaseObject();
* let mark = 0;
* object.on('click', function(ev){
* mark = 1;
* }, 'MyClick');
* object.trigger('click');
* // @expect(mark == 1)
* let mark2 = 0;
* object.on('click', '.Box', function(ev){
* mark2 = 1;
* }, 'MyClick');
* // @expect(mark2 == 0)
* @public
*/
on(type, condition, callback, tag, priority, options) {
this._addEventListener(type, condition, callback, tag, priority, options, false);
}
/**
* Register event what just trigger once time.
* @param {String} type The event type.
* @param {String} condition? The condition to select objects.
* @param {Function} callback? The callback function.
* @param {String} tag? The event tag.
* @param {Number} priority? The priority value(default is 0, higher value will be processed first).
* @param {ObjectEventOptions} options? The options.
* @example
* let object = new THING.BaseObject();
* let markOnce = 0;
* object.once('testOnce', function(ev) {
* markOnce = 1;
* });
* object.trigger('testOnce');
* // @expect(markOnce == 1);
* markOnce = 0;
* object.trigger('testOnce');
* // @expect(markOnce == 0);
* @public
*/
once(type, condition, callback, tag, priority, options) {
this._addEventListener(type, condition, callback, tag, priority, options, true);
}
/**
* Unregister event.
* @param {String} type The event type.
* @param {String} condition? The condition to select objects.
* @param {String} tag The event tag.
* @example
* let object = new THING.BaseObject();
* let markOff = 0;
* object.on('testOff', function(ev) {
* markOff = 1;
* });
* object.trigger('testOff');
* // @expect(markOff == 1);
* markOff = 0;
* object.off('testOff');
* object.trigger('testOff');
* // @expect(markOff == 0);
* @public
*/
off(type, condition, tag) {
// Use condition as tag
if (Utils.isString(condition) && Utils.isNull(tag)) {
this._removeEventListener(type, null, condition);
}
// Use both condition and tag
else {
this._removeEventListener(type, condition, tag);
}
}
/**
* Pause event.
* @param {String} type The event type.
* @param {String} condition The condition to select objects.
* @param {String} tag The event tag.
* @example
* let object = new THING.BaseObject();
* let markPause = 0;
* object.on('testPause', function(ev) {
* markPause = 1;
* });
* object.trigger('testPause');
* // @expect(markPause == 1);
* markPause = 0;
* object.pauseEvent('testPause');
* object.trigger('testPause');
* // @expect(markPause == 0);
* @public
*/
pauseEvent(type, condition, tag) {
this.app.eventManager.pauseEvent(this, type, condition, tag);
}
/**
* Resume event.
* @param {String} type The event type.
* @param {String} condition The condition to select objects.
* @param {String} tag The event tag.
* @example
* let object = new THING.BaseObject();
* let markResume = 0;
* object.on('testResume', function(ev) {
* markResume = 1;
* });
* object.trigger('testResume');
* // @expect(markResume == 1);
* markResume = 0;
* object.pauseEvent('testResume');
* object.trigger('testResume');
* // @expect(markResume == 0);
* object.resumeEvent('testResume');
* object.trigger('testResume');
* // @expect(markResume == 1);
* @public
*/
resumeEvent(type, condition, tag) {
this.app.eventManager.resumeEvent(this, type, condition, tag);
}
/**
* Trigger event.
* @param {String} type The event type.
* @param {Object} ev The event info.
* @param {Object} options? The options.
* @param {String} options.tag The tag name.
* @returns {*}
* @example
* let object = new THING.BaseObject();
* let markTrigger = {};
* object.on('myEvent', function(ev) {
* markTrigger = ev;
* });
* object.trigger('myEvent', { result: true });
* let ret = markTrigger.result;
* // @expect(ret == true);
* @public
*/
trigger(type, ev, options) {
if (this.destroyed) {
return null;
}
return this.app.eventManager.dispatchEvent(type.toLowerCase(), this, ev, options);
}
/**
* Get proxy object.
* @returns {THING.BaseObject}
* @private
*/
getProxyObject() {
let _private = this[__.private];
return _private.proxyObject;
}
/**
* Set proxy object(it will catch all events of this object).
* @param {THING.BaseObject} object The object.
* @example
* let car = app.query('#car01')[0];
* object.setProxyObject(car); // The car will catch all events of object
* @private
*/
setProxyObject(object) {
let _private = this[__.private];
_private.proxyObject = object;
}
triggerError(error, callback) {
this.trigger(EventType.Error, error);
Utils.error(error);
callback && callback(error);
}
/**
* Get all events.
* @type {Array<ObjectListenerInfo>}
* @private
*/
get events() {
return this.getEvents();
}
// #endregion
// #region Relative
/**
* When set the parent.
* @param {THING.BaseObject} parent The parent.
* @private
*/
onSetParent(parent) {
let _private = this[__.private];
// Prevent for the same parent
if (parent == _private.parent) {
return;
}
// Attach to new parent
if (parent) {
parent.addChild(this);
}
// Remove it from previous parent
this._removeFromParent();
// Update current parent
_private.parent = parent;
}
/**
* Get the parent.
* @returns {THING.BaseObject}
* @private
*/
getParent() {
return this[__.private].parent;
}
/**
* Get the parnet by type.
* @param {String} type The type string.
* @returns {THING.BaseObject}
* @private
*/
getParentByType(type) {
let typeString = 'is' + type;
for (let parent = this.parent; parent; parent = parent.parent) {
if (parent[typeString]) {
return parent;
}
}
return null;
}
/**
* Get the parents.
* @param {Object} param The parameters.
* @param {THING.BaseObject} param.root The root to stop.
* @param {Boolean} param.includeSelf True indicates including self object.
* @returns {THING.Selector}
* @private
*/
getParents(param = _defaultParams) {
let _private = this[__.private];
let root = param['root'];
let includeSelf = param['includeSelf'];
let selector = new Selector();
if (includeSelf) {
selector.push(this);
}
let parent = _private.parent;
while (parent) {
if (parent == root) {
break;
}
selector.push(parent);
parent = parent.parent;
}
return selector;
}
/**
* Get the brothers except self.
* @returns {THING.Selector}
* @private
*/
getBrothers() {
let _private = this[__.private];
let parent = _private.parent;
if (parent) {
return parent.children.filter(object => {
if (object === this) {
return false;
}
return true;
});
}
return new Selector();
}
/**
* Get the path to specific object.
* @param {THING.BaseObject} object The object to check.
* @returns {THING.Selector}
* @private
*/
getPathTo(object) {
if (!object) {
return null;
}
if (this.isChildOf(object)) {
let path = this.getParents({ root: object });
path.push(object);
return path;
}
else if (object.isChildOf(this)) {
let path = object.getParents({ root: this }).reverse();
path.push(object);
return path;
}
else {
// Get all parents
let targetParents = object.parents.reverse();
let selfParents = this.parents.reverse();
// Check whether it has parents
if (targetParents.length && selfParents.length) {
// Check whether it come from the same root
if (targetParents[0] == selfParents[0]) {
// Find the link parent
let linkParent;
while (targetParents.length && selfParents.length) {
if (targetParents[0] != selfParents[0]) {
break;
}
linkParent = selfParents[0];
targetParents.splice(0, 1);
selfParents.splice(0, 1);
}
if (linkParent) {
return selfParents.reverse().concat([linkParent]).concat(targetParents).concat(object);
}
}
}
}
return null;
}
/**
* Check whether it's child.
* @param {THING.BaseObject} object The object to check.
* @returns {Boolean}
* @example
* let parent = new THING.BaseObject();
* let child = new THING.BaseObject({parent: parent});
* let ret = child.isChildOf(parent);
* // @expect(ret == true);
*/
isChildOf(object) {
if (!object) {
return false;
}
let parent = this.parent;
while (parent) {
if (parent == object) {
return true;
}
parent = parent.parent;
}
return false;
}
/**
* Check whether it's brother.
* @param {THING.BaseObject} object The object to check.
* @returns {Boolean}
* @example
* let parent = new THING.BaseObject();
* let child1 = new THING.BaseObject({parent: parent});
* let child2 = new THING.BaseObject({parent: parent});
* let ret = child1.isBrotherOf(child2);
* // @expect(ret == true);
*/
isBrotherOf(object) {
if (!object) {
return false;
}
if (this.parent != object.parent) {
return false;
}
return true;
}
addChild(object) {
let _private = this[__.private];
_private.children = _private.children || new Selector();
_private.children.push(object);
}
/**
* Add object as child.
* @param {THING.BaseObject} object The object what you want to add.
* @param {Object} options The options.
* @returns {Boolean}
* @example
* let parent = new THING.BaseObject();
* let child = new THING.BaseObject();
* parent.add(child);
* let ret = child.isChildOf(parent);
* // @expect(ret == true);
* @public
*/
add(object, options) {
if (!object) {
return false;
}
let parent = object.parent;
if (parent) {
// The final parent is passed in as an argument.
object.onBeforeSetParent(this);
parent.remove(object, options);
}
this.onBeforeAddChild(object);
this.onAddChild(object, options);
this.onAfterAddChild(object);
return true;
}
/**
* Remove child object.
* @param {THING.BaseObject} object The object what you want to remove.
* @returns {Boolean}
* @example
* let parent = new THING.BaseObject();
* let child = new THING.BaseObject();
* parent.add(child);
* let ret = child.isChildOf(parent);
* // @expect(ret == true);
* parent.remove(child);
* ret = child.isChildOf(parent);
* // @expect(ret == false);
* @public
*/
remove(object, options) {
if (!object) {
return false;
}
this.onBeforeRemoveChild(object);
object.onSetParent(null, null, options);
this.onAfterRemoveChild(object);
return true;
}
/**
* Traverse self and all children.
* @param {Function} callback The callback function.
* @example
* let parent = new THING.BaseObject();
* let child1 = new THING.BaseObject({parent: parent});
* let child2 = new THING.BaseObject({parent: parent});
* let mark = 0;
* parent.traverse((child) => {
* mark++;
* });
* // @expect(mark == 3)
* @public
*/
traverse(callback) {
callback(this);
if (this.children) {
let objects = this.children.objects;
for (let i = 0, l = objects.length; i < l; i++) {
objects[i].traverse(callback);
}
}
}
/**
* Traverse self and all children. (Support for exit at traverse runtime)
* @param {Function} callback The callback function. (Return false to exit)
* @example
* let parent = new THING.BaseObject();
* let child1 = new THING.BaseObject({parent: parent});
* let child2 = new THING.BaseObject({parent: parent});
* let mark = 0;
* parent.traverseBranch((child)=>{
* mark++;
* });
* // @expect(mark == 3)
* mark = 0;
* parent.traverseBranch((child)=>{
* mark++;
* if(child.children.length > 0){
* return false;
* }
* return true;
* });
* // @expect(mark == 1)
*/
traverseBranch(callback) {
let isBranch = Utils.parseBoolean(callback(this), true);
if (!isBranch) {
return false;
}
let _private = this[__.private];
let children = _private.children;
if (children) {
for (let i = 0, l = children.length; i < l; i++) {
isBranch = children[i].traverseBranch(callback);
if (!isBranch) {
return false;
}
}
}
return true;
}
/**
* Traverse all parents except self.
* @param {Function} callback The callback function.
* @deprecated Deprecated, call with the selector returned by the object's parent interface
* @private
*/
traverseParents(callback) {
console.warn('Plase use Selector.tarverse instead of tarverseParents');
for (let parent = this.parent; parent; parent = parent.parent) {
callback(parent);
}
}
/**
* Traverse all children except self.
* @param {Function} callback The callback function.
* @deprecated Deprecated, call with the selector returned by the object's children interface
* @private
*/
traverseChildren(callback) {
console.warn('Plase use Selector.tarverse instead of traverseChildren');
let _private = this[__.private];
if (_private.children) {
let children = _private.children;
for (let i = 0, l = children.length; i < l; i++) {
children[i].traverse(callback);
}
}
}
/**
* Find all children except self.
* @param {Function} callback The callback function.
* @returns {THING.BaseObject}
* @private
*/
findChildren(callback) {
let _private = this[__.private];
let children = _private.children;
if (children) {
for (let i = 0, l = children.length; i < l; i++) {
let child = children[i];
if (callback(child) === true) {
return child;
}
let result = child.findChildren(callback);
if (result) {
return result;
}
}
}
return null;
}
/**
* Check whether has any children.
* @returns {Boolean}
* @example
* let parent = new THING.BaseObject();
* let child = new THING.BaseObject({parent: parent});
* let ret = parent.hasChildren();
* // @expect(ret == true);
*/
hasChildren() {
let _private = this[__.private];
return !!_private.children;
}
addRelationship(value) {
this.relationship.addRelationship(value);
}
removeRelationship(value) {
this.relationship.removeRelationship(value);
}
/**
* Get/Set parent.
* @type {THING.BaseObject}
* @example
* let parent = new THING.BaseObject();
* let child = new THING.BaseObject({parent: parent});
* let ret = child.parent == parent;
* // @expect(ret == true);
* @public
*/
get parent() {
return this[__.private].parent;
}
set parent(value) {
if (value) {
value.add(this);
}
else if (this.parent) {
this.parent.remove(this);
}
}
/**
* Get/Set relationships.
* @type {Array<THING.Relationship>}
* @example
* let object = new THING.Object3D();
* let source = new THING.Object3D();
* let target = new THING.Object3D();
* let relationship = new THING.Relationship({
* type: 'control',
* source: source,
* target: target
* });
* object.addRelationship(relationship);
* let ret = object.relationships[0].type == 'control';
* // @expect(ret == true)
* @public
*/
get relationships() {
return this.relationship.relationships || [];
}
/**
* Get the parents.
* @type {THING.Selector}
* @example
* let box1 = new THING.Box();
* let box2 = new THING.Box({parent: box1});
* let parents = box2.parents;
* // @expect(parents.length == 2);
* @public
*/
get parents() {
return this.getParents();
}
/**
* Get the brothers.
* @type {THING.Selector}
* @example
* let box1 = new THING.Box();
* let box2 = new THING.Box({parent: box1});
* let box3 = new THING.Box({parent: box1});
* let brothers = box3.brothers;
* // @expect(brothers.length == 1);
* @public
*/
get brothers() {
return this.getBrothers();
}
/**
* Get children.
* @type {THING.Selector}
* @example
* let object = new THING.BaseObject();
* let child= new THING.BaseObject({parent: object});
* let children = object.children;
* let ret = children.length == 1;
* // @expect(ret == true)
* @public
*/
get children() {
let _private = this[__.private];
return _private.children || _dummySelector;
}
// #endregion
// #region Selector
/**
* Test whether fit condition or not.
* @param {String} condition The condition to check.
* @returns {Boolean}
* @private
*/
test(condition) {
return Selector.test(condition, this);
}
/**
* @typedef {Object} ObjectQueryOptions
* @property {Boolean} recursive True indicates query in recursive mode.
* @property {Boolean} includeSelf True indicates including self.
*/
/**
* Query children by condition.
* @param {String} condition The condition to select objects.
* @param {ObjectQueryOptions} options? The options.
* @returns {THING.Selector}
* @example
* let object = new THING.BaseObject();
* let child= new THING.BaseObject({parent: object});
* child.userData = {power: 1000};
* let children = object.children.query('[userData/power>100]');
* let ret = children.length == 1;
* // @expect(ret == true)
* @public
*/
query(condition, options = {}) {
let recursive = Utils.parseValue(options['recursive'], true);
let includeSelf = options['includeSelf'];
let selector = options['dynamic'] ? new DynamicSelector(this, condition, options) : new Selector();
if (this.isRootObject && recursive) {
selector.query(condition, this.app.objectManager.objects, selector);
}
else {
if (includeSelf) {
if (this.test(condition)) {
selector.push(this);
}
}
if (recursive) {
if (Utils.isString(condition)) {
// All
var objs = [];
if (condition == '*') {
this.children.traverse(child => {
if (child.queryable) {
objs.push(child)
}
});
}
// Expression
else if (this._isExpression(condition)) {
let expression = Selector.buildExpression(condition);
if (expression) {
this.children.traverse(child => {
if (Selector.testByExpression(expression, child) && child.queryable) {
objs.push(child);
}
});
}
}
// Compare name
else {
this.children.traverse(child => {
if (child.name == condition && child.queryable) {
objs.push(child);
}
});
}
selector.push(objs);
}
// Reg Expression
else if (Utils.isRegExp(condition)) {
if (this.children) {
var objs = [];
this.children.traverse(child => {
if (Selector.testByRegExpName(condition, child) && child.queryable) {
objs.push(child);
}
});
selector.push(objs);
}
}
// Function
else if (Utils.isFunction(condition)) {
if (this.children) {
var objs = [];
this.children.traverse(child => {
if (Selector.testByFunction(condition, child) && child.queryable) {
objs.push(child);
}
});
selector.push(objs);
}
}
}
else {
if (this.children) {
selector = this.children.query(condition);
}
}
}
return selector;
}
/**
* Query children by name.
* @param {String} condition The condition to select objects.
* @param {ObjectQueryOptions} options The options.
* @returns {THING.Selector}
* @example
* let object = new THING.BaseObject();
* let child= new THING.BaseObject({parent: object, name: 'liming'});
* let result = object.queryByName('liming');
* let ret = result[0].name == 'liming';
* // @expect(ret == true)
* @public
*/
queryByName(condition, options = {}) {
return this._queryByAttribute(this, options, (c) => { return c.name == condition });
}
/**
* Query children by reg exp.
* @param {String} condition The condition to select objects.
* @param {ObjectQueryOptions} options The options.
* @returns {THING.Selector}
* @example
* let object = new THING.BaseObject();
* let child1= new THING.BaseObject({parent: object, name: 'car1'});
* let child2= new THING.BaseObject({parent: object, name: 'car2'});
* let result = object.queryByRegExp(/car/);
* let ret = result.length == 2;
* //@expect(ret == true)
* @public
*/
queryByRegExp(condition, options = {}) {
return this.query(condition, options);
}
/**
* Query children by reg.
* @param {String} condition The condition to select objects.
* @param {ObjectQueryOptions} options The options.
* @returns {THING.Selector}
* @example
* let result = object.queryByReg(/Sphere/);
* console.log(result);
* @deprecated 2.7
* @private
*/
queryByReg(condition, options = {}) {
return this.queryByRegExp(condition, options);
}
/**
* Query children by tag.
* @param {String} condition The condition to select objects.
* @param {ObjectQueryOptions} options The options.
* @returns {THING.Selector}
* @example
* let object = new THING.BaseObject();
* let child1= new THING.Object3D({parent: object, name: 'car1'});
* child1.tags.add('testCar');
* let child2= new THING.BaseObject({parent: object, name: 'car2'});
* let result = object.queryByTags('testCar');
* let ret = result.length == 1;
* //@expect(ret == true)
* @public
*/
queryByTags(condition, options = {}) {
return this._queryByCondition('tags(' + condition + ')', options, 'tags');
}
/**
* Query children by uuid.
* @param {String} condition The condition to select objects.
* @param {ObjectQueryOptions} options The options.
* @returns {THING.Selector}
* @example
* let object = new THING.BaseObject();
* let child1= new THING.BaseObject({parent: object, uuid: '1000'});
* let child2= new THING.BaseObject({parent: object});
* let result = object.queryByUUID('1000');
* let ret = result[0].uuid == '1000';
* //@expect(ret == true)
* @public
*/
queryByUUID(condition, options = {}) {
return this._queryByAttribute(this, options, (c) => { return c.uuid == condition });
}
/**
* Query children by id.
* @param {String} condition The condition to select objects.
* @param {ObjectQueryOptions} options The options.
* @returns {THING.Selector}
* @example
* let object = new THING.BaseObject();
* let child1= new THING.BaseObject({parent: object});
* child1.id = '10000';
* let child2= new THING.BaseObject({parent: object});
* let result = object.queryById('10000');
* let ret = result[0].id == '10000';
* //@expect(ret == true)
* @public
*/
queryById(condition, options = {}) {
return this._queryByAttribute(this, options, (c) => { return c.id == condition });
}
/**
* Query children by type.
* @param {String} condition The condition to select objects.
* @param {ObjectQueryOptions} options The options.
* @returns {THING.Selector}
* @example
* let object = new THING.BaseObject();
* let child1= new THING.Box({parent: object, id: '10000'});
* let child2= new THING.BaseObject({parent: object});
* let result = object.queryByType('Box');
* let ret = result[0].id == '10000';
* //@expect(ret == true)
* @public
*/
queryByType(condition, options = {}) {
var key = "is" + condition;
return this._queryByAttribute(this, options, (c) => { return c[key] == true });
}
/**
* Query children by userData.
* @param {String} condition The condition to select objects.
* @param {ObjectQueryOptions} options The options.
* @returns {THING.Selector}
* @example
* let object = new THING.BaseObject();
* let child1= new THING.Box({parent: object});
* child1.userData['power'] = 100;
* let child2= new THING.BaseObject({parent: object});
* let result = object.queryByUserData('power=100');
* let ret = result[0].userData.power == 100;
* //@expect(ret == true)
* @public
*/
queryByUserData(condition, options = {}) {
return this._queryByCondition(condition, options, 'userData');
}
/**
* Query children by userData.
* @param {String} condition The condition to select objects.
* @param {ObjectQueryOptions} options The options.
* @returns {THING.Selector}
* @example
* let result = object.queryByData('test=1');
* console.log(result);
* @deprecated 2.7
* @private
*/
queryByData(condition, options = {}) {
return this.queryByUserData(condition, options);
}
_queryByAttribute(root, options, cb) {
let selector = options['dynamic'] ? new DynamicSelector(this, condition) : new Selector();
let recursive = Utils.parseValue(options['recursive'], true);
let includeSelf = options['includeSelf'];
// RootObject query from all objects
if (root.isRootObject && recursive) {
selector._queryByAttribute(this.app.objectManager.objects, cb, selector);
}
else {
let objects = [];
if (includeSelf) {
if (cb(root) && root.queryable) {
objects.push(root);
}
}
if (recursive) {
this.children.traverse(c => {
if (cb(c) && c.queryable) {
objects.push(c);
}
});
}
else {
objects = objects.concat(this.children.objects.filter((c) => { return cb(c) && c.queryable; }));
}
selector.push(objects);
}
return selector;
}
_queryByCondition(condition, options = {}, mode) {
let selector = options['dynamic'] ? new DynamicSelector(this, condition) : new Selector();
let recursive = Utils.parseValue(options['recursive'], true);
let includeSelf = options['includeSelf'];
var objects = [];
if (Utils.isString(condition)) {
let expression = Selector.buildExpression(condition, mode);
if (expression) {
// RootObject query from all objects
if (this.isRootObject && recursive) {
selector._queryByCondition(condition, this.app.objectManager.objects, selector, mode);
}
else {
if (includeSelf) {
if (Selector.testByExpression(expression, this) && this.queryable) {
objects.push(this);
}
}
if (recursive) {
this.children.traverse(child => {
if (Selector.testByExpression(expression, child) && child.queryable) {
objects.push(child);
}
});
}
else {
this.children.forEach((child) => {
if (Selector.testByExpression(expression, child) && child.queryable) {
objects.push(child);
}
})
}
}
}
}
selector.push(objects);
return selector;
}
/**
* Query children by condition in async mode.
* @param {String} condition The condition to select objects.
* @param {ObjectQueryOptions} options The options.
* @returns {Promise<any>}
* @private
*/
queryAsync(condition, options = {}) {
let recursive = Utils.parseValue(options['recursive'], true);
let includeSelf = options['includeSelf'];
return Selector.queryAsync(condition, this, recursive).then(selector => {
if (includeSelf) {
if (this.test(condition)) {
selector.push(this);
}
}
return selector;
});
}
/**
* Find children by condition.
* @param {String} condition The condition to select objects.
* @param {ObjectQueryOptions} options The options.
* @returns {THING.BaseObject}
* @private
*/
find(condition, options = {}) {
let recursive = Utils.parseValue(options['recursive'], true);
let includeSelf = options['includeSelf'];
if (includeSelf) {
if (this.test(condition)) {
return this;
}
}
let _private = this[__.private];
if (recursive) {
if (Utils.isString(condition)) {
// All
if (condition == '*') {
return this.children[0];
}
else {
let expression = Selector.buildExpression(condition);
if (expression) {
return this.findChildren(child => {
return Selector.testByExpression(expression, child);
});
}
}
}
// Reg Expression
else if (Utils.isRegExp(condition)) {
if (_private.children) {
return this.findChildren(child => {
return Selector.testByRegExpName(condition, child);
});
}
}
// Function
else if (Utils.isFunction(condition)) {
if (_private.children) {
return this.findChildren(child => {
return Selector.testByFunction(condition, child);
});
}
}
}
else {
if (_private.children) {
return _private.children.find(condition);
}
}
return null;
}
// #endregion
get isBaseObject() {
return true;
}
}
export { BaseObject }