kekule
Version:
Open source JavaScript toolkit for chemoinformatics
1,908 lines (1,854 loc) • 165 kB
JavaScript
/**
* @fileoverview
* Some global symbols used in Kekule library.
* @author Partridge Jiang
*/
/*
* requires /lan/classes.js
* requires /utils/kekule.utils.js
* requires /localization
*/
"use strict";
(function(){
//var OT = Kekule.OBJDEF_TEXTS;
// modify defineProp method of ObjectEx, add auto title/description supports
if (Kekule.PROP_AUTO_TITLE)
{
var originMethod = ClassEx.getPrototype(ObjectEx).defineProp;
var OBJ_PROP_TITLE_TEXT_PREFIX = 'TITLE_';
var OBJ_PROP_DESCRIPTION_TEXT_PREFIX = 'DES_';
var newMethod = function(propName, options)
{
if (!options.title && !options.description) // all not set, auto decide
{
var className = this.getClassName();
if (className.startsWith('Kekule.')) // remove Kekule prefix
{
className = className.substr(7);
}
//var textObj = Object.getCascadeFieldValue(className, Kekule.OBJDEF_TEXTS);
//if (textObj)
{
//var titleField = Kekule.OBJDEF_TEXTS.TITLE_PREFIX + propName;
//var desField = Kekule.OBJDEF_TEXTS.DESCRIPTION_PREFIX + propName;
var titlePrefix = OBJ_PROP_TITLE_TEXT_PREFIX;
var destPrefix = OBJ_PROP_DESCRIPTION_TEXT_PREFIX;
var titleField = titlePrefix + propName;
var desField = destPrefix + propName;
var title = Kekule.Localization.findValue('OBJDEF_TEXTS.' + className + '.' + titleField);
var description = Kekule.Localization.findValue('OBJDEF_TEXTS.' + className + '.' + desField);
options.title = title; //textObj[titleField];
options.description = description; //textObj[desField];
}
}
return originMethod.apply(this, [propName, options]);
};
ClassEx.getPrototype(ObjectEx).defineProp = newMethod;
}
/**
* Enumeration of exception types.
* @class
*/
Kekule.ExceptionLevel = {
/** Fatal error, usually cause the stop of program execution. */
ERROR: -1,
/** Error but not fatal, the program can still run but need to display the error message to user. */
NOT_FATAL_ERROR: -10,
/** Serious exception, but the program can still run. */
WARNING: -2,
/** Minor exception, user usually need not to know about it. */
NOTE: -3,
/** Log message, just for debug use. */
LOG: -4,
/**
* Returns all excpetion level values.
* @returns {Array}
*/
getAllLevels: function()
{
var EL = Kekule.ExceptionLevel;
return [EL.ERROR, EL.NOT_FATAL_ERROR, EL.WARNING, EL.NOTE, EL.LOG];
},
/**
* Returns a string identity for exception level.
* @param {Int} level
* @returns {String}
*/
levelToString: function(level)
{
var EL = Kekule.ExceptionLevel;
return (level === EL.ERROR || level === EL.NOT_FATAL_ERROR)? 'error':
(level === EL.WARNING)? 'warning':
(level === EL.NOTE)? 'note':
'log';
}
};
/**
* Error level, alias of ExceptionLevel
*/
Kekule.ErrorLevel = Kekule.ExceptionLevel;
/**
* Throw an exception in ExceptionHandler, same as {@link Kekule.ExceptionHandler.throwException}
*/
Kekule.throwException = function(e, exceptionLevel)
{
if (Kekule.exceptionHandler)
{
return Kekule.exceptionHandler.throwException(e, exceptionLevel);
}
else
{
var EL = Kekule.ExceptionLevel;
if ((!exceptionLevel) || (exceptionLevel === EL.ERROR))
{
if (typeof(e) === 'string')
e = new Kekule.Exception(e);
throw e;
}
else
{
if (typeof(console) !== 'undefined')
{
if (typeof(e) !== 'string')
e = e.message;
var method = (exceptionLevel === EL.NOT_FATAL_ERROR)? console.error:
(exceptionLevel === EL.WARNING)? console.warn:
(exceptionLevel === EL.NOTE)? console.info:
console.log;
if (method)
method(e);
else
console.log(e);
}
}
}
};
/**
* Returns the message of exception object or string.
* @param {Variant} e
* @returns {String}
*/
Kekule.getExceptionMsg = function(e)
{
if (typeof(e) === 'string')
return e;
else if (typeof(e) === 'object')
return e.message;
else
return '';
};
/**
* Throw an exception in ExceptionHandler, same as {@link Kekule.ExceptionHandler.throwException}
* @function
*/
Kekule.raise = Kekule.throwException;
/**
* Throw an {@link Kekule.ExceptionLevel.Error} or {@link Kekule.ExceptionLevel.NOT_FATAL_ERROR} in ExceptionHandler.
*/
Kekule.error = function(e, notFatal)
{
/*
if (typeof(e) == 'string')
e = new Kekule.Error(e);
if (Kekule.exceptionHandler)
return Kekule.exceptionHandler.throwException(e);
else
throw e;
*/
var level = notFatal? Kekule.ExceptionLevel.NOT_FATAL_ERROR: Kekule.ExceptionLevel.ERROR;
return Kekule.raise(e, level);
};
/**
* Throw an {@link Kekule.ChemError} in ExceptionHandler.
*/
Kekule.chemError = function(e, notFatal)
{
if (typeof(e) == 'string')
e = new Kekule.ChemError(e);
/*
if (Kekule.exceptionHandler)
return Kekule.exceptionHandler.throwException(e);
else
throw e;
*/
return Kekule.error(e, notFatal);
};
/**
* Raise a warning message (but do not raise error).
* @param e
*/
Kekule.warn = function(e)
{
return Kekule.raise(e, Kekule.ExceptionLevel.WARNING);
};
/**
* Raise a informative hint message.
* @param e
*/
Kekule.notify = function(e)
{
return Kekule.raise(e, Kekule.ExceptionLevel.NOTE);
};
/**
* Raise a log message.
* @param e
*/
Kekule.log = function(e)
{
return Kekule.raise(e, Kekule.ExceptionLevel.LOG);
};
/**
* Check if localization resource is imported and available.
* If it returns true, l10n constants can be used.
* @returns {Bool}
*/
Kekule.hasLocalRes = function()
{
return !!Kekule.LOCAL_RES;
};
/**
* An root object to store default options for many operations (e.g., ring search, stereo perception).
* User can modify concrete options to change the default action of some functions.
* @object
*/
Kekule.globalOptions = {
add: function(optionName, valueOrHash)
{
var oldValue = Object.getCascadeFieldValue(optionName, Kekule.globalOptions);
if (oldValue && DataType.isObjectValue(oldValue) && DataType.isObjectValue(valueOrHash)) // value already exists and can be extended
{
Object.extend(oldValue, valueOrHash);
}
else
Object.setCascadeFieldValue(optionName, valueOrHash, Kekule.globalOptions, true);
},
get: function(optionName, defaultValue)
{
var result = Object.getCascadeFieldValue(optionName, Kekule.globalOptions);
if (typeof(result) === 'undefined')
result = defaultValue;
return result;
}
};
/**
* A class to implement str => variant mapping.
* @class
*/
Kekule.HashEx = Class.create(
/** @lends Kekule.HashEx# */
{
/** @private */
CLASS_NAME: 'Kekule.HashEx',
/** @private */
PROP_NAME_PREFIX: '__$key$__',
/** @constructs */
initialize: function()
{
this._map = {};
},
/**
* Free resources.
*/
finalize: function()
{
this._map = null;
},
/** @private */
keyToPropName: function(key)
{
return this.PROP_NAME_PREFIX + key; // add a prefix to avoid possible name conflicts with default object
},
/**
* Check whether a value has been associated to the key string.
* @param {String} key
* @returns {Bool}
*/
has: function(key)
{
return this._map.hasOwnProperty(this.keyToPropName(key));
},
/**
* Set the value for the key string.
* @param {String} key
* @param {Variant} value
*/
set: function(key, value)
{
this._map[this.keyToPropName(key)] = value;
return this;
},
/**
* Returns the value associated to the key string,
* @param {String} key
* @param {Variant} defaultValue Return this value if key does not exist.
* @returns {Variant}
*/
get: function(key, defaultValue)
{
var propName = this.keyToPropName(key);
if (this._map.hasOwnProperty(propName))
return this._map[propName];
else
return defaultValue;
},
/**
* Removes any value associated to the key string.
* @param {String} key
*/
remove: function(key)
{
var propName = this.keyToPropName(key);
if (this._map.hasOwnProperty(propName))
{
delete this._map[propName];
}
return this;
},
/**
* Clear all key and values in map.
*/
clear: function()
{
this._map = {};
}
});
/**
* A class to implement obj => obj mappings.
* @class
*
* @params {Bool} nonWeak Whether try to create this map as a weak one (do not disturb GC).
*/
Kekule.MapEx = Class.create(
/** @lends Kekule.MapEx# */
{
/** @private */
CLASS_NAME: 'Kekule.MapEx',
/** @constructs */
initialize: function(nonWeak, enableCache)
{
this.isWeak = !nonWeak;
if (!Kekule.MapEx._inited)
Kekule.MapEx._init();
// try use built in
var _implClass = nonWeak? Kekule.MapEx._implementationNonWeak: Kekule.MapEx._implementationWeak;
if (!_implClass) // fall back to JavaScript implementation
{
this._keys = [];
this._values = [];
}
else
this._implementation = new _implClass();
if (enableCache)
this._cache = {}; // cache enabled for performance
/*
if ((!Kekule.MapEx._implementation) || (!this.isWeak)) // use JavaScript implementation
if (!this._implementation)
{
this.keys = [];
this.values = [];
}
else // use built-in implementation
this._implementation = new Kekule.MapEx._implementation();
*/
},
/**
* Free resources.
*/
finalize: function(/*$super*/)
{
if (!this._implementation)
{
this._keys = null;
this._values = null;
this._cache = null;
}
},
/**
* Set the value for the key object.
* @param {Variant} key
* @param {Variant} value
*/
set: function(key, value)
{
if (this._implementation)
this._implementation.set(key, value);
else
{
var index = this._keys.indexOf(key);
if (index >= 0)
this._values[index] = value;
else
{
this._keys.push(key);
this._values.push(value);
}
}
if (this._cache && this._cache.key === key)
{
this._cache.value = value;
}
//console.log(key, value, this.get(key));
return this;
},
/**
* Returns the value associated to the key object,
* @param {Variant} key
* @param {Variant} defaultValue Return this value if key does not exist.
* @returns {Variant}
*/
get: function(key, defaultValue)
{
if (this._cache && this._cache.key === key) // has cache, return directly
return this._cache.value;
var result;
var found = false;
if (this._implementation)
{
if (this._implementation.has(key))
{
result = this._implementation.get(key);
found = true;
}
}
else
{
var index = this._keys.indexOf(key);
if (index >= 0)
{
result = this._values[index];
found = true;
}
}
if (found && this._cache) // set cache
{
this._cache.key = key;
this._cache.value = result;
}
return found? result: defaultValue;
},
/**
* Check whether a value has been associated to the key object.
* @param {Variant} key
* @returns {Bool}
*/
has: function(key)
{
if (this._implementation)
return this._implementation.has(key);
else
{
var index = this._keys.indexOf(key);
return index >= 0;
}
},
/**
* Removes any value associated to the key object.
* @param {Object} key
*/
remove: function(key)
{
if (this._cache && this._cache.key === key) // clear cache
this._cache = {};
if (this._implementation)
{
return this._implementation[!this._implementation || 'delete'](key); // avoid IE regard delete as a reserved word
}
else
{
var index = this._keys.indexOf(key);
if (index >= 0)
{
this._keys.splice(index, 1);
this._values.splice(index, 1);
}
}
return this;
},
/**
* Clear all key and values in map.
*/
clear: function()
{
if (this._cache) // clear cache
this._cache = {};
if (!this._implementation || this._debug)
{
this._keys = [];
this._values = [];
}
//else
if (this._implementation)
{
if (this.isWeak)
Kekule.error(Kekule.$L('ErrorMsg.CANNOT_CLEAR_WEAKMAP')/*Kekule.ErrorMsg.CANNOT_CLEAR_WEAKMAP*/);
else
this._implementation.clear();
}
return this;
},
/**
* Returns an array of all keys in map.
* @returns {Array}
*/
getKeys: function()
{
if (!this._implementation)
return [].concat(this._keys); // clone the array, avoid modification
else
{
if (this.isWeak)
{
// weak map, can not return keys
Kekule.error(Kekule.$L('ErrorMsg.CANNOT_GET_KEY_LIST_IN_WEAKMAP')/*Kekule.ErrorMsg.CANNOT_GET_KEY_LIST_IN_WEAKMAP*/);
return [];
}
else
{
var iter = this._implementation.keys();
var result;
if (Array.from)
result = Array.from(iter);
else if (iter.next)
{
result = [];
var nextResult = iter.next();
while (!nextResult.done)
{
result.push(nextResult.value);
nextResult = iter.next();
}
}
else
{
result = [];
for (var item in iter)
{
result.push(item);
}
}
return result;
}
}
},
/**
* Returns an array of all values in map.
* @returns {Array}
*/
getValues: function()
{
if (!this._implementation)
return this._values;
else
{
if (this.isWeak)
{
// weak map, can not return keys
Kekule.error(Kekule.$L('ErrorMsg.CANNOT_GET_VALUE_LIST_IN_WEAKMAP')/*Kekule.ErrorMsg.CANNOT_GET_VALUE_LIST_IN_WEAKMAP*/);
return [];
}
else
{
var iter = this._implementation.values();
var result;
if (Array.from)
result = Array.from(iter);
else if (iter.next)
{
result = [];
var nextResult = iter.next();
while (!nextResult.done)
{
result.push(nextResult.value);
nextResult = iter.next();
}
}
else
{
result = [];
for (var item in iter)
{
result.push(item);
}
}
return result;
}
}
},
/**
* Return count all map items.
* @returns {Int}
*/
getLength: function()
{
return this.getKeys().length;
}
});
/** @private */
Kekule.MapEx._init = function()
{
Kekule.MapEx._implementationWeak = null; // use JavaScript implementation
Kekule.MapEx._implementationNonWeak = null; // use JavaScript implementation
if (Kekule.$jsRoot.WeakMap) // Fx6 and above, use built-in WeakMap object
Kekule.MapEx._implementationWeak = Kekule.$jsRoot.WeakMap;
if (Kekule.$jsRoot.Map && Kekule.$jsRoot.Map.prototype.keys) // the implementation of Map in IE does not support keys() and values()
Kekule.MapEx._implementationNonWeak = Kekule.$jsRoot.Map;
Kekule.MapEx._inited = true;
};
Kekule.MapEx._inited = false;
/**
* A class to implement obj1, obj2 => obj mappings.
* @class
*
* @params {Bool} nonWeak Whether try to create this map as a weak one (do not disturb GC).
*/
Kekule.TwoTupleMapEx = Class.create(
/** @lends Kekule.TwoTupleMapEx# */
{
/** @private */
CLASS_NAME: 'Kekule.TwoTupleMapEx',
/** @constructs */
initialize: function(nonWeak, enableCache)
{
this.nonWeak = nonWeak;
this.map = new Kekule.MapEx(nonWeak, enableCache);
if (enableCache)
this._level1Cache = {}; // a cache for quickly find level 1 map
},
/**
* Free resources.
*/
finalize: function()
{
this._level1Cache = null;
this.map.finalize();
},
/** @private */
getSecondLevelMap: function(key1, allowCreate)
{
if (key1 && this._level1Cache && (key1 === this._level1Cache.key) && this._level1Cache.value)
{
return this._level1Cache.value;
}
else
{
var result = this.map.get(key1);
if ((!result) && allowCreate)
{
result = new Kekule.MapEx(this.nonWeak);
this.map.set(key1, result);
}
/*
// set to cache
this._level1Cache.key = key1;
this._level1Cache.value = result;
*/
return result;
}
},
/**
* Set the value for the key object.
* @param {Variant} key1
* @param {Variant} key2
* @param {Variant} value
*/
set: function(key1, key2, value)
{
var map = this.getSecondLevelMap(key1, true);
map.set(key2, value);
return this;
},
/**
* Returns the value associated to the key object,
* @param {Variant} key1
* @param {Variant} key2
* @param {Variant} defaultValue Return this value if key does not exist.
* @returns {Variant}
*/
get: function(key1, key2, defaultValue)
{
var map = this.getSecondLevelMap(key1, false);
if (map)
return map.get(key2, defaultValue);
else
return defaultValue;
},
/**
* Check whether a value has been associated to the key object.
* @param {Variant} key1
* @param {Variant} key2
* @returns {Bool}
*/
has: function(key1, key2)
{
var map = this.getSecondLevelMap(key1, false);
if (map)
return map.has(key2);
else
return false;
},
/**
* Removes any value associated to the key object.
* @param {Variant} key1
* @param {Variant} key2
*/
remove: function(key1, key2)
{
var map = this.getSecondLevelMap(key1, false);
if (map)
return map.remove(key2);
},
/**
* Clear all key and values in map.
*/
clear: function()
{
this.map.clear();
if (this._level1Cache)
this._level1Cache = {};
}
});
/**
* Enumeration of coordinate mode.
* @class
*/
Kekule.CoordMode = {
/** Unknown dimension, not useful in most cases. */
UNKNOWN: 0,
/** 2D coordinate */
COORD2D: 2,
/** 3D coordinate */
COORD3D: 3
};
/**
* Enumeration of the anchor point position of a chem object.
* @enum
*/
Kekule.ObjAnchorPosition = {
/** At the corner of object box (e.g., top left corner of 2D box). */
CORNER: 0,
/** At the center of object box. */
CENTER: 1,
DEFAULT: 0
};
/**
* Class to help to define some classes with similar interfaces.
* @class
* @@private
*/
Kekule.ClassDefineUtils = {
/// Methods to expand support for size2D and size3D property
/**
* @class
* @private
*/
CommonSizeMethods:
{
/**
* Get size of specified mode.
* @param {Int} coordMode Value from {@link Kekule.CoordMode}, mode of coordinate.
* @param {Bool} allowSizeBorrow If corresponding size of 2D/3D not found, whether
* size of another mode can be used instead.
* If true, 2D size can be expanded to 3D one as {x, y, 0} and 3D
* reduced to 2D one as {x, y}.
* @returns {Hash}
*/
getSizeOfMode: function(coordMode, allowSizeBorrow)
{
if (coordMode === Kekule.CoordMode.COORD3D)
return this.getSize3D(allowSizeBorrow);
else
return this.getSize2D(allowSizeBorrow);
},
/**
* Set size of specified mode.
* @param {Hash} value Value of size. {x, y} or {x, y, z}.
* @param {Int} coordMode Value from {@link Kekule.CoordMode}, mode of coordinate.
*/
setSizeOfMode: function(value, coordMode)
{
if (coordMode === Kekule.CoordMode.COORD3D)
return this.setSize3D(value);
else
return this.setSize2D(value);
},
/**
* Returns a min 2D box containing this object.
* @param {Int} coordMode Value from {@link Kekule.CoordMode}, mode of coordinate.
* @param {Bool} allowCoordBorrow
* @returns {Hash}
*/
getBoxOfMode: function(coordMode, allowCoordBorrow)
{
if (coordMode === Kekule.CoordMode.COORD3D)
return this.getBox3D(allowCoordBorrow);
else
return this.getBox2D(allowCoordBorrow);
}
},
/**
* @class
* @private
*/
Size2DMethods:
{
/*
* Get size2D property, if this property is null, then create new hash for it.
* Note this method will return a direct instance of size2D hash (instead of a
* clone as in getSize2D).
* @returns {Hash} {x, y}
*/
fetchSize2D: function()
{
var c = this.getPropStoreFieldValue('size2D');
if (!c)
{
c = {};
this.setPropStoreFieldValue('size2D', c);
}
return c;
},
/** @private */
notifySize2DChanged: function(newValue)
{
this.notifyPropSet('size2D', newValue);
},
/**
* Check if this object has a 2D size.
* @param {Bool} allowSizeBorrow
* @returns {Bool}
*/
hasSize2D: function(allowSizeBorrow)
{
var c = this.getPropStoreFieldValue('size2D'); //this.getCoord2D();
var result = (c) && ((typeof(c.x) != 'undefined') || (typeof(c.y) != 'undefined'));
if ((!result) && allowSizeBorrow)
result = this.hasSize3D? this.hasSize3D(): false;
return result;
},
/**
* Returns a min 2D box containing this object.
* @param {Bool} allowCoordBorrow
* @returns {Hash}
*/
getBox2D: function(allowCoordBorrow)
{
var coord1 = this.getCoord2D(allowCoordBorrow) || {'x': 0, 'y': 0};
var size = this.getSize2D(allowCoordBorrow);
if (/*coord1 &&*/ size && (size.x || size.y))
{
// usually coord point is on top-left corner, in object coord system, y is on the largest edge
// of object, so -size.y is used to get the bottom edge of box.
var delta = {'x': size.x, 'y': -size.y};
var coord2 = Kekule.CoordUtils.add(coord1, delta);
return {'x1': coord1.x, 'y1': coord1.y, 'x2': coord2.x, 'y2': coord2.y};
}
else
return {'x1': coord1.x, 'y1': coord1.y, 'x2': coord1.x, 'y2': coord1.y};
},
/**
* Get x (width) value of size2D property.
* @param {Bool} allowSizeBorrow
* @returns {Float} x
*/
get2DSizeX: function(allowSizeBorrow)
{
var c = this.getSize2D(allowSizeBorrow);
return c? c.x || 0: 0;
},
/**
* Change x (width) value of size2D property.
* @param {Float} x
*/
set2DSizeX: function(x)
{
var c = this.fetchSize2D();
if (c.x != x)
{
c.x = x;
this.notifySize2DChanged(c);
}
},
/**
* Get y (height) value of size2D property.
* @param {Bool} allowSizeBorrow
* @returns {Float} y
*/
get2DSizeY: function(allowSizeBorrow)
{
var c = this.getSize2D(allowSizeBorrow);
return c? c.y || 0: 0;
},
/**
* Change y (height) value of size2D property.
* @param {Float} y
*/
set2DSizeY: function(y)
{
var c = this.fetchSize2D();
if (c.y != y)
{
c.y = y;
this.notifySize2DChanged(c);
}
}
},
/**
* @class
* @private
*/
Size3DMethods:
{
/*
* Get size3D property, if this property is null, then create new hash for it.
* Note this method will return a direct instance of size3D hash (instead of a
* clone as in getSize3D).
* @returns {Hash} {x, y}
*/
fetchSize3D: function()
{
var c = this.getPropStoreFieldValue('size3D');
if (!c)
{
c = {};
this.setPropStoreFieldValue('size3D', c);
}
return c;
},
/** @private */
notifySize3DChanged: function(newValue)
{
this.notifyPropSet('size3D', newValue);
},
/**
* Check if this object has a 3D size.
* @param {Bool} allowSizeBorrow
* @returns {Bool}
*/
hasSize3D: function(allowSizeBorrow)
{
var c = this.getPropStoreFieldValue('size3D');
var result = (c) && ((typeof(c.x) !== 'undefined') || (typeof(c.y) !== 'undefined') || (typeof(c.z) !== 'undefined'));
if ((!result) && allowSizeBorrow)
result = this.hasSize2D? this.hasSize2D(): false;
return result;
},
/**
* Returns a min 3D box containing this object.
* @param {Bool} allowCoordBorrow
* @returns {Hash}
*/
getBox3D: function(allowCoordBorrow)
{
var coord1 = this.getCoord3D(allowCoordBorrow);
var size = this.getSize3D(allowCoordBorrow);
if (coord1 && size)
{
var coord2 = Kekule.CoordUtils.add(coord1, size);
return {'x1': coord1.x, 'y1': coord1.y, 'z1': coord1.z, 'x2': coord2.x, 'y2': coord2.y, 'z2': coord2.z};
}
},
/**
* Get x (width) value of size3D property.
* @param {Bool} allowSizeBorrow
* @returns {Float} x
*/
get3DSizeX: function(allowSizeBorrow)
{
var c = this.getSize3D(allowSizeBorrow);
return c? c.x || 0: 0;
},
/**
* Change x (width) value of size3D property.
* @param {Float} x
*/
set3DSizeX: function(x)
{
var c = this.fetchSize3D();
if (c.x != x)
{
c.x = x;
this.notifySize3DChanged(c);
}
},
/**
* Get y (height) value of size3D property.
* @param {Bool} allowSizeBorrow
* @returns {Float} y
*/
get3DSizeY: function(allowSizeBorrow)
{
var c = this.getSize3D(allowSizeBorrow);
return c? c.y || 0: 0;
},
/**
* Change y (height) value of size3D property.
* @param {Float} y
*/
set3DSizeY: function(y)
{
var c = this.fetchSize3D();
if (c.y != y)
{
c.y = y;
this.notifySize3DChanged(c);
}
},
/**
* Get z (depth) value of size3D property.
* @param {Bool} allowSizeBorrow
* @returns {Float} z
*/
get3DSizeZ: function(allowSizeBorrow)
{
var c = this.getSize3D(allowSizeBorrow);
return c? c.z || 0: 0;
},
/**
* Change y (height) value of size3D property.
* @param {Float} z
*/
set3DSizeZ: function(z)
{
var c = this.fetchSize3D();
if (c.z != z)
{
c.z = z;
this.notifySize3DChanged(c);
}
}
},
/**
* Define size2D/size3D related properties and methods to a class.
* @param {Object} aClass Should be class object.
* @param {Array} props Which properties should be defined. e.g. ['size2D', 'size3D'].
* If not set, all 2D and 3D properties will be defined.
*/
addStandardSizeSupport: function(aClass, props)
{
ClassEx.extend(aClass, Kekule.ClassDefineUtils.CommonSizeMethods);
if ((!props) || (props.indexOf('size2D') >= 0))
{
ClassEx.defineProp(aClass, 'size2D', {
'dataType': DataType.HASH,
// clone result object so that user can not modify x/y directly from getter
'getter': function(allowSizeBorrow, allowCreateNew)
{
var c = this.getPropStoreFieldValue('size2D');
if ((!c) && allowSizeBorrow)
{
if (this.hasSize3D())
{
c = this.getSize3D();
}
}
if ((!c) && allowCreateNew)
{
c = {'x': 0, 'y': 0};
this.setPropStoreFieldValue('size2D', c);
}
var result = c? {'x': c.x, 'y': c.y}: undefined;
if (result)
result = Kekule.CoordUtils.absValue(result); // size should always be positive value
return result;
},
// clone value from input
'setter': function(value)
{
var c = this.fetchSize2D();
if (value.x !== undefined)
c.x = value.x;
if (value.y !== undefined)
c.y = value.y;
this.notifySize2DChanged(c);
}
});
ClassEx.extend(aClass, Kekule.ClassDefineUtils.Size2DMethods);
}
if ((!props) || (props.indexOf('size3D') >= 0))
{
ClassEx.defineProp(aClass, 'size3D', {
'dataType': DataType.HASH,
// clone result object so that user can not modify x/y directly from getter
'getter': function(allowSizeBorrow, allowCreateNew)
{
var c = this.getPropStoreFieldValue('size3D');
if ((!c) && allowSizeBorrow)
{
if (this.hasSize2D())
{
c = this.getSize2D();
c.z = 0;
}
}
if ((!c) && allowCreateNew)
{
c = {'x': 0, 'y': 0, 'z': 0};
this.setPropStoreFieldValue('size3D', c);
}
var result = c? {'x': c.x, 'y': c.y, 'z': c.z}: undefined;
return result;
},
// clone value from input
'setter': function(value){
var c = this.fetchSize3D();
if (value.x !== undefined)
c.x = value.x;
if (value.y !== undefined)
c.y = value.y;
if (value.z !== undefined)
c.z = value.z;
this.notifySize3DChanged(c);
}
});
ClassEx.extend(aClass, Kekule.ClassDefineUtils.Size3DMethods);
}
},
/// Methods to expand support for Coord2D and Coord3D property
/**
* @class
* @private
*/
CommonCoordMethods:
/** @lends Kekule.ClassDefineUtils.CommonCoordMethods# */
{
/**
* Returns the anchor position of this object.
* @param {Int} coordMode
* @returns {Int} Value from {@link Kekule.ObjAnchorPosition}
*/
getObjAnchorPosition: function(coordMode)
{
var result = this.doGetObjAnchorPosition && this.doGetObjAnchorPosition(coordMode);
result = result || Kekule.ObjAnchorPosition.DEFAULT;
return result;
},
/**
* Returns the parent object that influences the abs coord of this object.
* @param {Int} coordMode
* @returns {Kekule.ChemObject}
*/
getCoordParent: function(coordMode)
{
return (this.getParent && this.getParent()) || null;
},
/**
* Get coordinate of specified mode.
* @param {Int} coordMode Value from {@link Kekule.CoordMode}, mode of coordinate.
* @param {Bool} allowCoordBorrow If corresponding coord of 2D/3D not found, whether
* coord of another mode can be used instead.
* If true, 2D coord can be expanded to 3D one as {x, y, 0} and 3D
* reducts to 2D one as {x, y}.
* @returns {Hash}
*/
getCoordOfMode: function(coordMode, allowCoordBorrow)
{
if (coordMode === Kekule.CoordMode.COORD3D)
return this.getCoord3D(allowCoordBorrow);
else
return this.getCoord2D(allowCoordBorrow);
},
/**
* Set coordinate of specified mode.
* @param {Hash} value Value of coordinate. {x, y} or {x, y, z}.
* @param {Int} coordMode Value from {@link Kekule.CoordMode}, mode of coordinate.
*/
setCoordOfMode: function(value, coordMode)
{
if (coordMode === Kekule.CoordMode.COORD3D)
return this.setCoord3D(value);
else
return this.setCoord2D(value);
},
/**
* Get absolute coordinate of specified mode.
* @param {Int} coordMode Value from {@link Kekule.CoordMode}, mode of coordinate.
* @param {Bool} allowCoordBorrow
* @returns {Hash}
*/
getAbsCoordOfMode: function(coordMode, allowCoordBorrow)
{
if (coordMode === Kekule.CoordMode.COORD3D)
return this.getAbsCoord3D(allowCoordBorrow);
else
return this.getAbsCoord2D(allowCoordBorrow);
},
/**
* Set absolute coordinate of specified mode.
* @param {Hash} value Value of coordinate. {x, y} or {x, y, z}.
* @param {Int} coordMode Value from {@link Kekule.CoordMode}, mode of coordinate.
*/
setAbsCoordOfMode: function(value, coordMode)
{
if (coordMode === Kekule.CoordMode.COORD3D)
return this.setAbsCoord3D(value);
else
return this.setAbsCoord2D(value);
},
/**
* Check if this node has a coordinate on coordMode.
* @param {Int} coordMode
* @param {Bool} allowCoordBorrow
* @returns {Bool}
*/
hasCoordOfMode: function(coordMode, allowCoordBorrow)
{
if (coordMode === Kekule.CoordMode.COORD3D)
return this.hasCoord3D(allowCoordBorrow);
else
return this.hasCoord2D(allowCoordBorrow);
},
/**
* Returns the indirect coord data storage object in coordMode.
* @param {Int} coordMode
* @returns {Hash}
*/
getIndirectCoordStorageOfMode: function(coordMode)
{
var s = this.getIndirectCoordStorage && this.getIndirectCoordStorage();
return s && s[coordMode];
},
/**
* Set the indirect coord data storage object in coordMode.
* @param {Int} coordMode
* @param {Hash} data
*/
setIndirectCoordStorageOfMode: function(coordMode, data)
{
var s = this.getIndirectCoordStorage && this.getIndirectCoordStorage();
if (s)
s[coordMode] = data;
return this;
},
/**
* Default methods of getting relative coord values.
* Desendants can override this method.
* @returns {Hash}
* @private
*/
calcIndirectCoordValue: function(coordMode, allowCoordBorrow)
{
var coordFields = ['x', 'y'];
if (coordMode === Kekule.CoordMode.COORD3D)
coordFields.push('z');
var ratios = this.getIndirectCoordStorage()[coordMode];
var refCoords = this.getIndirectCoordRefCoords(coordMode, allowCoordBorrow);
if (Kekule.ArrayUtils.isArray(refCoords))
refCoords = refCoords[0];
var refLengthes = this.getIndirectCoordRefLengths(coordMode, allowCoordBorrow);
if (ratios && refCoords && refLengthes)
{
var result = {};
var notUnset = Kekule.ObjUtils.notUnset;
for (var i = 0, l = coordFields.length; i < l; ++i)
{
var field = coordFields[i];
var ratio = ratios[field];
var refCoord = refCoords[field];
var refLength = refLengthes[field];
if (notUnset(ratio) && notUnset(refCoord) && notUnset(refLength))
{
var v;
if (refLength === 0) // ratio maybe infinite, avoid a NaN result
v = refCoord;
else
v = refCoord + refLength * ratio;
result[field] = v;
}
/*
else
{
return null;
}
*/
}
//console.log('calc', coordMode, refCoords, refLengthes, ratios, coordFields, result);
return result;
}
else
return null;
},
/**
* Default methods of getting relative coord ratios from coord value.
* Desendants can override this method.
* @returns {Hash}
* @private
*/
calcIndirectCoordStorage: function(coordMode, coordValue, oldCoordValue, allowCoordBorrow)
{
var coordFields = ['x', 'y'];
if (coordMode === Kekule.CoordMode.COORD3D)
coordFields.push('z');
var ratios = this.getIndirectCoordStorageOfMode(coordMode);
var refCoords = this.getIndirectCoordRefCoords(coordMode, allowCoordBorrow);
if (Kekule.ArrayUtils.isArray(refCoords))
refCoords = refCoords[0];
var refLengthes = this.getIndirectCoordRefLengths(coordMode, allowCoordBorrow);
if (ratios && refCoords && refLengthes)
{
var newRatios = {};
var notUnset = Kekule.ObjUtils.notUnset;
for (var i = 0, l = coordFields.length; i < l; ++i)
{
var field = coordFields[i];
var coord = coordValue[field];
var refCoord = refCoords[field];
var refLength = refLengthes[field];
if (notUnset(coord) && notUnset(refCoord) && notUnset(refLength))
{
var ratio;
if (refLength !== 0) // avoid divided by zero
ratio = (coord - refCoord) / refLength;
else
ratio = 1;
newRatios[field] = ratio;
}
else
{
return null;
}
}
return newRatios;
}
else
return null;
},
/**
* Default methods of updating the relative coord ratios.
* Desendants can override this method.
* @private
*/
saveIndirectCoordValue: function(coordMode, coordValue, oldCoordValue, allowCoordBorrow)
{
if (coordValue)
{
var newRatios = this.calcIndirectCoordStorage(coordMode, coordValue, oldCoordValue, allowCoordBorrow);
if (newRatios)
{
this.setIndirectCoordStorageOfMode(coordMode, newRatios);
return newRatios;
}
}
return null;
},
/**
* Method to return the reference lengths used by using relative mode coord.
* @returns {Variant} A numberic length or hash.
* @private
*/
getIndirectCoordRefLengths: function(coordMode, allowCoordBorrow)
{
return null;
},
/**
* Method to return the reference coords used by using relative mode coord.
* @returns {Array}
* @private
*/
getIndirectCoordRefCoords: function(coordMode, allowCoordBorrow)
{
return null;
}
},
/**
* @class
* @private
*/
Coord2DMethods:
/** @lends Kekule.ClassDefineUtils.Coord2DMethods# */
{
/*
* Get coord2D property, if this property is null, then create new hash for it.
* Note this method will return a direct instance of coord2D hash (instead of a
* clone as in getCoord2D).
* //@param {Bool} allowCoordBorrow
* @returns {Hash} {x, y}
*/
fetchCoord2D: function(/*allowCoordBorrow*/)
{
var c = this.getPropStoreFieldValue('coord2D');
if (!c)
{
c = {};
this.setPropStoreFieldValue('coord2D', c);
}
return c;
/*
console.log('fetch2D');
var result = this.getCoord2D(allowCoordBorrow, true); // allow create
console.log(result);
return result;
*/
},
/** @private */
notifyCoord2DChanged: function(newValue)
{
this.notifyPropSet('coord2D', newValue);
},
/**
* Check if this node has a 2D coordinate.
* @param {Bool} allowCoordBorrow
* @returns {Bool}
*/
hasCoord2D: function(allowCoordBorrow)
{
var c = this.getPropStoreFieldValue('coord2D'); //this.getCoord2D();
var result = (c) && ((typeof(c.x) != 'undefined') || (typeof(c.y) != 'undefined'));
if ((!result) && allowCoordBorrow)
result = this.hasCoord3D? this.hasCoord3D(): false;
return result;
},
/**
* Get x coordinate of coord2D property.
* @param {Bool} allowCoordBorrow
* @returns {Float} x
*/
get2DX: function(allowCoordBorrow)
{
var c = this.getCoord2D(allowCoordBorrow);
return c? c.x || 0: 0;
//return this.hasCoord2D()? this.getCoord2D().x || 0: 0;
},
/**
* Change x coordinate of coord2D property.
* @param {Float} x
*/
set2DX: function(x)
{
var c = this.fetchCoord2D();
if (c.x != x)
{
c.x = x;
this.notifyCoord2DChanged(c);
}
},
/**
* Get y coordinate of coord2D property.
* @param {Bool} allowCoordBorrow
* @returns {Float} y
*/
get2DY: function(allowCoordBorrow)
{
//return this.hasCoord2D()? this.getCoord2D().y || 0: 0;
var c = this.getCoord2D(allowCoordBorrow);
return c? c.y || 0: 0;
},
/**
* Change y coordinate of coord2D property.
* @param {Float} y
*/
set2DY: function(y)
{
var c = this.fetchCoord2D();
if (c.y != y)
{
c.y = y;
this.notifyCoord2DChanged(c);
}
}
},
/**
* @class
* @private
*/
Coord3DMethods:
/** @lends Kekule.ClassDefineUtils.Coord3DMethods# */
{
/*
* Get coord3D property, if this property is null, then create new hash for it.
* Note this method will return a direct instance of coord2D hash (instead of a
* clone as in getCoord3D).
* //@param {Bool} allowCoordBorrow
* @returns {Hash} {x, y, z}
*/
fetchCoord3D: function(/*allowCoordBorrow*/)
{
var c = this.getPropStoreFieldValue('coord3D');
if (!c)
{
c = {};
this.setPropStoreFieldValue('coord3D', c);
}
return c;
//return this.getCoord3D(allowCoordBorrow, true); // allow create
},
/** @private */
notifyCoord3DChanged: function(newValue)
{
this.notifyPropSet('coord3D', newValue);
},
/**
* Check if this node has a 3D coordinate.
* @param {Bool} allowCoordBorrow
* @returns {Bool}
*/
hasCoord3D: function(allowCoordBorrow)
{
var c = this.getPropStoreFieldValue('coord3D'); //this.getCoord3D();
var result = (c) && ((typeof(c.x) != 'undefined') || (typeof(c.y) != 'undefined') || (typeof(c.z) != 'undefined'));
if ((!result) && allowCoordBorrow)
result = this.hasCoord2D? this.hasCoord2D(): false;
return result;
},
/**
* Get x coordinate of coord3D property.
* @param {Bool} allowCoordBorrow
* @returns {Float} x
*/
get3DX: function(allowCoordBorrow)
{
//return this.hasCoord3D()? this.getCoord3D().x || 0: 0;
var c = this.getCoord3D(allowCoordBorrow);
return c? c.x || 0: 0;
},
/**
* Change x coordinate of coord3D property.
* @param {Float} x
*/
set3DX: function(x)
{
var c = this.fetchCoord3D();
if (c.x != x)
{
c.x = x;
this.notifyCoord3DChanged(c);
}
},
/**
* Get y coordinate of coord3D property.
* @param {Bool} allowCoordBorrow
* @returns {Float} y
*/
get3DY: function(allowCoordBorrow)
{
//return this.hasCoord3D()? this.getCoord3D().y || 0: 0;
var c = this.getCoord3D(allowCoordBorrow);
return c? c.y || 0: 0;
},
/**
* Change y coordinate of coord3D property.
* @param {Float} y
*/
set3DY: function(y)
{
var c = this.fetchCoord3D();
if (c.y != y)
{
c.y = y;
this.notifyCoord3DChanged(c);
}
},
/**
* Get z coordinate of coord3D property.
* @param {Bool} allowCoordBorrow
* @returns {Float} z
*/
get3DZ: function(allowCoordBorrow)
{
//return this.hasCoord3D()? this.getCoord3D().z || 0: 0;
var c = this.getCoord3D(allowCoordBorrow);
return c? c.z || 0: 0;
},
/**
* Change z coordinate of coord3D property.
* @param {Float} z
*/
set3DZ: function(z)
{
var c = this.fetchCoord3D();
if (c.z != z)
{
c.z = z;
this.notifyCoord3DChanged(c);
}
}
},
/**
* Define coord2D/coord3D related properties and methods to a class.
* @param {Object} aClass Should be class object.
* @param {Array} props Which properties should be defined. e.g. ['coord3D', 'absCoord3D'].
* If not set, all 2D and 3D properties will be defined.
*/
addStandardCoordSupport: function(aClass, props)
{
ClassEx.extend(aClass, Kekule.ClassDefineUtils.CommonCoordMethods);
if ((!props) || (props.indexOf('coord2D') >= 0))
{
ClassEx.defineProp(aClass, 'coord2D', {
'dataType': DataType.HASH,
// clone result object so that user can not modify x/y directly from getter
'getter': function(allowCoordBorrow, allowCreateNew)
{
if (this.getEnableIndirectCoord())
{
var rc = this.calcIndirectCoordValue(Kekule.CoordMode.COORD2D, allowCoordBorrow);
if (rc)
return rc;
}
var c = this.getPropStoreFieldValue('coord2D');
if ((!c) && allowCoordBorrow)
{
if (this.hasCoord3D())
{
c = this.getCoord3D();
}
}
if ((!c) && allowCreateNew)
{
c = {'x': 0, 'y': 0};
this.setPropStoreFieldValue('coord2D', c);
}
var result = c? {'x': c.x, 'y': c.y}: undefined;
return result;
/*
if (!this.hasCoord2D())
{
return undefined;
}
var c = this.getPropStoreFieldValue('coord2D');
if ((!c) && allowCreateNew)
this.setPropStoreFieldValue('coord2D', {});
var r = {};
r = Object.extend(r, c);
return r;
*/
},
// clone value from input
'setter': function(value)
{
var c = this.fetchCoord2D();
if (this.getEnableIndirectCoord())
{
//console.log('save relative', value);
this.saveIndirectCoordValue(Kekule.CoordMode.COORD2D, value, c);
}
if (value.x !== undefined)
c.x = value.x;
if (value.y !== undefined)
c.y = value.y;
this.notifyCoord2DChanged(c);
}
});
ClassEx.extend(aClass, Kekule.ClassDefineUtils.Coord2DMethods);
}
if ((!props) || (props.indexOf('coord3D') >= 0))
{
ClassEx.defineProp(aClass, 'coord3D', {
'dataType': DataType.HASH,
// clone result object so that user can not modify x/y directly from getter
'getter': function(allowCoordBorrow, allowCreateNew)
{
if (this.getEnableIndirectCoord())
{
var rc = this.calcIndirectCoordValue(Kekule.CoordMode.COORD2D, allowCoordBorrow);
if (rc)
return rc;
}
var c = this.getPropStoreFieldValue('coord3D');
if ((!c) && allowCoordBorrow)
{
if (this.hasCoord2D())
{
c = this.getCoord2D();
c.z = 0;
}
}
if ((!c) && allowCreateNew)
{
c = {'x': 0, 'y': 0, 'z': 0};
this.setPropStoreFieldValue('coord3D', c);
}
var result = c? {'x': c.x, 'y': c.y, 'z': c.z}: undefined;
return result;
/*
if (!this.hasCoord3D())
return undefined;
var c = this.getPropStoreFieldValue('coord3D');
if ((!c) && allowCreateNew)
this.setPropStoreFieldValue('coord2D', {});
var r = {};
r = Object.extend(r, c);
return r;
*/
},
// clone value from input
'setter': function(value){
var c = this.fetchCoord3D();
if (this.getEnableIndirectCoord())
this.saveIndirectCoordValue(Kekule.CoordMode.COORD3D, value, c);
if (value.x !== undefined)
c.x = value.x;
if (value.y !== undefined)
c.y = value.y;
if (value.z !== undefined)
c.z = value.z;
this.notifyCoord3DChanged(c);
}
});
ClassEx.extend(aClass, Kekule.ClassDefineUtils.Coord3DMethods);
}
if ((!props) || (props.indexOf('absCoord2D') >= 0))
{
ClassEx.defineProp(aClass, 'absCoord2D', {
'dataType': DataType.HASH,
'serializable': false,
'getter': function(allowCoordBorrow){
var c = this.getCoord2D(allowCoordBorrow) || {};
//var p = this.getParent? this.getParent(): null;
var p = this.getCoordParent(Kekule.CoordMode.COORD2D);
var result;
//if (p && p.hasCoord2D && p.hasCoord2D()) // has parent, consider parent coordinate to get absolute position
if (p && p.getAbsCoord2D)
{
return Kekule.CoordUtils.add(c, p.getAbsCoord2D(allowCoordBorrow) || {});
}
else
//return Object.extend({}, c);
return {'x': c.x, 'y': c.y};
},
'setter': function(value, allowCoordBorrow){
var c = value;
var p = this.getCoordParent(Kekule.CoordMode.COORD2D);
if (p && p.getAbsCoord2D) // has parent, consider parent coordinate to get absolute position
c = Kekule.CoordUtils.substract(value, p.getAbsCoord2D(allowCoordBorrow));
this.setCoord2D(c);
}
});
}
if ((!props) || (props.indexOf('absCoord3D') >= 0))
{
ClassEx.defineProp(aClass, 'absCoord3D', {
'dataType': DataType.HASH,
'serializable': false,
'getter': function(allowCoordBorrow){
var c = this.getCoord3D(allowCoordBorrow) || {};
//var p = this.getParent? this.getParent(): null;
var p = this.getCoordParent(Kekule.CoordMode.COORD3D);
//if (p && p.hasCoord3D && p.hasCoord3D()) // has parent, consider parent coordinate to get absolute position
if (p && p.getAbsCoord3D)
return Kekule.CoordUtils.add(c, p.getAbsCoord3D(allowCoordBorrow) || {});
else
//return Object.extend({}, c);
return {'x': c.x, 'y': c.y, 'z': c.z};
},
'setter': function(value, allowCoordBorrow){
var c = value;
var p = this.getCoordParent(Kekule.CoordMode.COORD2D);
if (p && p.getAbsCoord3D) // has parent, consider parent coordinate to get absolute position
c = Kekule.CoordUtils.substract(value, p.getAbsCoord3D(allowCoordBorrow));
this.setCoord3D(c);
}
});
}
if (!props || (props.indexOf('enableIndirectCoord') >= 0))
{
ClassEx.defineProp(aClass, 'enableIndirectCoord', {
'scope': Class.PropertyScope.PUBLIC,
'dataType': DataType.BOOL,
'serializable': true,
'setter': function(value)
{
if (this.getPropStoreFieldValue('enableIndirectCoord') != value)
{
if (value) // when enable relative coord, save the coord ratio first
{
var coord2D = this.getCoord2D();
var coord3D = this.getCoord3D();
if (coord2D)
this.saveIndirectCoordValue(Kekule.CoordMode.COORD2D, coord2D, coord2D, true);
if (coord3D)
this.saveIndirectCoordValue(Kekule.CoordMode.COORD3D, coord3D, coord3D, true);
}
this.setPropStoreFieldValue('enableIndirectCoord', value);
}
}
});
}
if (!props || (props.indexOf('indirectCoordStorage') >= 0))
{
ClassEx.defineProp(aClass, 'indirectCoordStorage', {
//'scope': Class.PropertyScope.PUBLIC,
'dataType': DataType.HASH,
'serializable': function(){ return !!this.getEnableIndirectCoord(); },
'setter': null,
'getter': function()
{
var result = this.getPropStoreFieldValue('indirectCoordStorage');
if (!result)
{
result = {};
result[Kekule.CoordMode.COORD2D] = {};
result[Kekule.CoordMode.COORD3D] = {};
this.setPropStoreFieldValue('indirectCoordStorage', result);
}
return result;
}
});
}
},
/// Methods to expand support for ExtraObjMap
/**
* @class
* @private
*/
ExtraObjMapMethods: {
/**
* Get extra mapped property of obj.
* @param {Object} obj
* @param {String} propName
* @returns {Variant} If propName is set, returns the prop value. Otherwise return a hash contains all extra properties.
* @private
*/
getExtraProp: function(obj, propName)
{
var info = this.getExtraObjMap().get(obj);
if (info)
return propName? info[propName]: info;
else
return undefined;
},
/**
* Set extra mapped property of obj.
* @param {Object} obj
* @param {String} propName
* @param {Variant} propValue
* @private
*/
setExtraProp: function(obj, propName, propValue)
{
var info = this.getExtraProp(obj);
if (info)
info[propName] = propValue;
else
{
var info = {};
info[propName] = propValue;
this.getExtraObjMap().set(obj, info);
}
},
/**
* Remove extra mapped property of obj.
* @param {Object} obj
* @param {String} propName If not set, all properties on obj will be removed.
* @private
*/
removeExtraProp: function(obj, propName)
{
if (propName)
{
var info = this.getExtraProp(obj);
info[propName] = undefined;
delete info[propName];
}
else
this.getExtraObjMap().remove(obj);
}
},
/**
* Define extraObjMap related properties and methods to a class.
* @param {Object} aClass Should be class object.
*/
addExtraObjMapSupport: function(aClass)
{
var mapName = 'extraObjMap';
ClassEx.defineProp(aClass, mapName, {
'dataType': 'Kekule.MapEx', 'serializable': false,