@qooxdoo/framework
Version:
The JS Framework for Coders
511 lines (444 loc) • 14.6 kB
JavaScript
/* ************************************************************************
qooxdoo - the new era of web development
http://qooxdoo.org
Copyright:
2004-2008 1&1 Internet AG, Germany, http://www.1und1.de
License:
MIT: https://opensource.org/licenses/MIT
See the LICENSE file in the project's top-level directory for details.
Authors:
* Sebastian Werner (wpbasti)
* Andreas Ecker (ecker)
* Fabian Jakobs (fjakobs)
************************************************************************ */
/**
* The qooxdoo root class. All other classes are direct or indirect subclasses of this one.
*
* This class contains methods for:
*
* * object management (creation and destruction)
* * interfaces for event system
* * generic setter/getter support
* * interfaces for logging console
* * user friendly OO interfaces like {@link #self} or {@link #base}
*
* @require(qx.core.ObjectRegistry)
*/
qx.Class.define("qx.core.Object", {
extend: Object,
include: qx.core.Environment.filter({
"module.databinding": qx.data.MBinding,
"module.logger": qx.core.MLogging,
"module.events": qx.core.MEvent,
"module.property": qx.core.MProperty,
"module.objectid": qx.core.MObjectId,
"qx.debug": qx.core.MAssert
}),
/*
*****************************************************************************
CONSTRUCTOR
*****************************************************************************
*/
/**
* Create a new instance
*/
construct() {},
/*
*****************************************************************************
STATICS
*****************************************************************************
*/
statics: {
/** Internal type */
$$type: "Object"
},
/*
*****************************************************************************
MEMBERS
*****************************************************************************
*/
members: {
__Property: qx.core.Environment.get("module.property")
? qx.core.Property
: null,
/*
---------------------------------------------------------------------------
BASICS
---------------------------------------------------------------------------
*/
/**
* Return unique hash code of object
*
* @return {String} unique hash code of the object
*/
toHashCode() {
if (!this.$$hash && !this.$$disposed) {
if (
!qx.core.Environment.get("qx.automaticMemoryManagement") ||
qx.Class.hasInterface(this.constructor, qx.core.IDisposable)
) {
qx.core.ObjectRegistry.register(this);
} else {
qx.core.ObjectRegistry.toHashCode(this);
}
}
return this.$$hash;
},
/**
* Returns a UUID for this object
*
* @return {String} a UUID
*/
toUuid() {
if (!this.$$uuid) {
this.$$uuid = qx.util.Uuid.createUuidV4();
}
return this.$$uuid;
},
/**
* Sets a UUID; normally set automatically, you would only set this manually
* if you have a very special reason to do so - for example, you are using UUIDs which are
* synchronized from a special source, eg remote server.
*
* This can only be called once, and only if it has not been automatically allocated. If
* you really do need to call this, call it as soon after construction as possible to avoid
* an exception.
*
* @param uuid {String} an ID which is unique across the whole application
*/
setExplicitUuid(uuid) {
if (Boolean(this.$$uuid)) {
throw new Error("Cannot change the UUID of an object once set");
}
this.$$uuid = uuid;
},
/**
* Returns a string representation of the qooxdoo object.
*
* @return {String} string representation of the object
*/
toString() {
return this.classname + "[" + this.toHashCode() + "]";
},
/**
* Call the same method of the super class.
*
* Either the compiler translate all calls to this.base
* into mypkg.MyBaseClass.prototype.myMethod.call(this, 123);
* this method is still needed for use in compile.js or playground
* which are not precompiled
*
* @param args {IArguments} the arguments variable of the calling method
* @param varargs {var?} variable number of arguments passed to the overwritten function
* @return {var} the return value of the method of the base class.
*/
base(args, varargs) {
var func = args.callee.base;
if (!func) {
func = this[args.callee.name].base;
}
if (qx.core.Environment.get("qx.debug")) {
if (!qx.Bootstrap.isFunctionOrAsyncFunction(func)) {
throw new Error(
"Cannot call super class. Method is not derived: " +
args.callee.displayName
);
}
}
if (arguments.length === 1) {
return func.call(this);
} else {
return func.apply(this, Array.prototype.slice.call(arguments, 1));
}
},
/**
* Returns the static class (to access static members of this class)
*
* @param args {arguments} the arguments variable of the calling method
* @return {var} the return value of the method of the base class.
*/
self(args) {
return args.callee.self;
},
/*
---------------------------------------------------------------------------
CLONE SUPPORT
---------------------------------------------------------------------------
*/
/**
*
* Returns a clone of this object. Copies over all user configured
* property values. Do not configure a parent nor apply the appearance
* styles directly.
*
* @return {qx.core.Object} The clone
*/
clone() {
if (!qx.core.Environment.get("module.property")) {
throw new Error("Cloning only possible with properties.");
}
var clazz = this.constructor;
var clone = new clazz();
var props = qx.Class.getProperties(clazz);
var user = this.__Property.$$store.user;
var setter = this.__Property.$$method.set;
var name;
// Iterate through properties
for (var i = 0, l = props.length; i < l; i++) {
name = props[i];
if (this.hasOwnProperty(user[name])) {
clone[setter[name]](this[user[name]]);
}
}
// Return clone
return clone;
},
/*
---------------------------------------------------------------------------
USER DATA
---------------------------------------------------------------------------
*/
/** @type {Map} stored user data */
__userData: null,
/**
* Store user defined data inside the object.
*
* @param key {String} the key
* @param value {Object} the value of the user data
*/
setUserData(key, value) {
if (!this.__userData) {
this.__userData = {};
}
this.__userData[key] = value;
},
/**
* Load user defined data from the object
*
* @param key {String} the key
* @return {Object} the user data
*/
getUserData(key) {
if (!this.__userData) {
return null;
}
var data = this.__userData[key];
return data === undefined ? null : data;
},
/**
* Clears all user defined data from the object.
*/
resetUserData() {
this.__userData = null;
},
/*
---------------------------------------------------------------------------
DISPOSER
---------------------------------------------------------------------------
*/
/**
* Returns true if the object is disposed.
*
* @return {Boolean} Whether the object has been disposed
*/
isDisposed() {
return this.$$disposed || false;
},
/**
* Returns true if the object is being disposed, ie this.dispose() has started but
* not finished
*
* @return {Boolean} Whether the object is being disposed
*/
isDisposing() {
return this.$$disposing || false;
},
/**
* Dispose this object
*
*/
dispose() {
// Check first
if (this.$$disposed) {
return;
}
// Mark as disposed (directly, not at end, to omit recursions)
this.$$disposed = true;
this.$$disposing = true;
this.$$instance = null;
this.$$allowconstruct = null;
// Debug output
if (qx.core.Environment.get("qx.debug")) {
if (qx.core.Environment.get("qx.debug.dispose.level") > 2) {
qx.Bootstrap.debug(
this,
"Disposing " + this.classname + "[" + this.toHashCode() + "]"
);
}
}
// Remove all listeners.
//
// This must be done early, since it calls
// qx.core.ObjectRegistry.toHashCode(target) which would add a
// hash code back in after code here has cleaned it up.
qx.event.Registration.removeAllListeners(this);
// Deconstructor support for classes
var clazz = this.constructor;
var mixins;
while (clazz.superclass) {
// Processing this class...
if (clazz.$$destructor) {
clazz.$$destructor.call(this);
}
// Destructor support for mixins
if (clazz.$$includes) {
mixins = clazz.$$flatIncludes;
for (var i = 0, l = mixins.length; i < l; i++) {
if (mixins[i].$$destructor) {
mixins[i].$$destructor.call(this);
}
}
}
// Jump up to next super class
clazz = clazz.superclass;
}
this.$$disposing = false;
// Additional checks
if (qx.core.Environment.get("qx.debug")) {
if (qx.core.Environment.get("qx.debug.dispose.level") > 0 && qx.Class.hasOwnInterface(this.constructor, qx.core.IDisposable)) {
var key, value;
for (key in this) {
value = this[key];
// Check for Objects but respect values attached to the prototype itself
if (
value !== null &&
typeof value === "object" &&
!qx.Bootstrap.isString(value)
) {
// Check prototype value
// undefined is the best, but null may be used as a placeholder for
// private variables (hint: checks in qx.Class.define). We accept both.
if (this.constructor.prototype[key] != null) {
continue;
}
if (qx.core.Environment.get("qx.debug.dispose.level") > 1) {
qx.Bootstrap.warn(
this,
"Missing destruct definition for '" +
key +
"' in " +
this.classname +
"[" +
this.toHashCode() +
"]: " +
value
);
delete this[key];
}
}
}
}
}
},
/*
---------------------------------------------------------------------------
DISPOSER UTILITIES
---------------------------------------------------------------------------
*/
/**
* Disconnects and disposes given objects from instance.
* Only works with qx.core.Object based objects e.g. Widgets.
*
* @param varargs {arguments} Names of fields (which store objects) to dispose
*/
_disposeObjects(varargs) {
qx.util.DisposeUtil.disposeObjects(this, arguments);
},
/**
* Disconnects and disposes given singleton objects from instance.
* Only works with qx.core.Object based objects e.g. Widgets.
*
* @param varargs {arguments} Names of fields (which store objects) to dispose
*/
_disposeSingletonObjects(varargs) {
qx.util.DisposeUtil.disposeObjects(this, arguments, true);
},
/**
* Disposes all members of the given array and deletes
* the field which refers to the array afterwards.
*
* @param field {String} Name of the field which refers to the array
*/
_disposeArray(field) {
qx.util.DisposeUtil.disposeArray(this, field);
},
/**
* Disposes all members of the given map and deletes
* the field which refers to the map afterwards.
*
* @param field {String} Name of the field which refers to the map
*/
_disposeMap(field) {
qx.util.DisposeUtil.disposeMap(this, field);
}
},
/*
*****************************************************************************
ENVIRONMENT SETTINGS
*****************************************************************************
*/
environment: {
"qx.debug.dispose.level": 0,
// Ideally this would be in the mixin, but mixins do not support environment blocks
// Also, this would be better as false, not true but that would not be BC
"qx.core.Object.allowUndefinedObjectId": true
},
/*
*****************************************************************************
DESTRUCTOR
*****************************************************************************
*/
destruct() {
if (qx.core.Environment.get("module.events")) {
if (!qx.core.ObjectRegistry.inShutDown) {
// Cleanup event listeners
qx.event.Registration.removeAllListeners(this);
} else {
// on shutdown, just clear the internal listener map
qx.event.Registration.deleteAllListeners(this);
}
}
// Cleanup object registry
qx.core.ObjectRegistry.unregister(this);
// Cleanup user data
this.__userData = null;
// only of properties are available
if (qx.core.Environment.get("module.property")) {
// Cleanup properties
var clazz = this.constructor;
var properties;
var store = this.__Property.$$store;
var storeUser = store.user;
var storeTheme = store.theme;
var storeInherit = store.inherit;
var storeUseinit = store.useinit;
var storeInit = store.init;
while (clazz) {
properties = clazz.$$properties;
if (properties) {
for (var name in properties) {
if (properties[name].dereference) {
this[storeUser[name]] =
this[storeTheme[name]] =
this[storeInherit[name]] =
this[storeUseinit[name]] =
this[storeInit[name]] =
undefined;
}
}
}
clazz = clazz.superclass;
}
}
}
});