kekule
Version:
Open source JavaScript toolkit for chemoinformatics
1,892 lines (1,766 loc) • 114 kB
JavaScript
/**
* @fileoverview
* This file simulates the class-based inheritance, most of the code is
* borrowed from Prototype lib. My own property system borrowed form WebShow framework
* is also added here.
*
* @author Partridge Jiang
*/
"use strict";
(function($jsRoot){
// ensure the $jsRoot refers to the global object in browser or node
if (typeof(self) === 'object')
$jsRoot = self;
else if (typeof(window) === 'object' && window.document)
$jsRoot = window;
else if (typeof(global) === 'object') // node env
$jsRoot = global;
/** @ignore */
function emptyFunction() {};
/** @ignore */
function __$A__(iterable) {
if (!iterable) return [];
if (iterable.toArray) return iterable.toArray();
var length = iterable.length || 0, results = new Array(length);
while (length--) results[length] = iterable[length];
return results;
}
/**
* A flag, whether stores all classes with CLASS_NAME field in Class._named_classes
* @ignore
*/
var __registerNamedClasses__ = true;
/** @class Class */
var Class = {
/**
* @description Create a Prototypejs style class, createCore(baseClass, newClassHash).
* @param {Class} baseClass Parent class.
* @param {Hash} newClassHash A hash contains methods of current class.
* @return {Class} The class created.
*/
createCore: function() {
var parent = null, properties = __$A__(arguments);
var exProps = (properties.length > 1)? properties[1]: properties[0];
var currClassName = (exProps && exProps.CLASS_NAME);
if (!properties[0])
{
if (properties.length > 1)
{
/*
var exProps = properties[1];
var currClassName = (exProps && exProps.CLASS_NAME);
*/
throw 'Can not create new class' + (currClassName? ' ' + currClassName: '') + ' , base class not found';
}
}
if (Object.isFunction(properties[0]))
parent = properties.shift();
function klass() {
this.initialize.apply(this, arguments);
}
Object.extend(klass, Class.Methods);
klass.superclass = parent;
klass.subclasses = [];
if (parent) {
var subclass = function() { };
subclass.prototype = parent.prototype;
klass.prototype = new subclass;
parent.subclasses.push(klass);
}
for (var i = 0; i < properties.length; i++)
klass.addMethods(properties[i]);
if (!klass.prototype.initialize)
klass.prototype.initialize = emptyFunction; //Prototype.emptyFunction;
klass.prototype.constructor = klass;
if (currClassName && __registerNamedClasses__) // register
Class._named_classes[currClassName] = klass;
return klass;
},
/**
* @description Create a new class, currently same as createCore, create(baseClass, newClassHash).
* @see Class#createCore.
* @param {Class} baseClass Parent class.
* @param {Hash} newClassHash A hash contains methods of current class.
* @return {Class} The class created.
*/
create: function()
{
var result = Class.createCore.apply(this, arguments);
// init properties
//if (result.prototype.initProperties)
{
// init properties field in prototype
/*
if (!result.prototype.properties)
result.prototype.properties = [];
*/
/*
* Use lasy creation, the property list will be created only the first instance of class is used
if (result.prototype.hasOwnProperty('initProperties')) // prevent call parent initProperties method
result.prototype.initProperties.apply(result.prototype);
*/
}
return result;
},
/**
* Finalize object.
* @param {ObjectEx} obj
*/
free: function(obj)
{
if (obj.finalize)
obj.finalize();
obj = null;
},
/**
* Find the class by a class name.
* @param {String} className
* @returns {Class} Found class or null.
*/
findClass: function(className, root)
{
var result = Class._named_classes[className];
if (!result)
{
var v = Object.getCascadeFieldValue(className, root);
result = (v && Object.isFunction(v) && (v.superclass || v.subclasses))? v: null;
}
return result;
},
/**
* Stores created classes with CLASS_NAME property
*/
_named_classes: {}
};
/** @ignore */
Class.Methods = {
/* @lends Class */
/*
* @description Add new methods to a existing class.
* @param {Hash} source A hash contains methods to add.
* @return {Class} resultClass Current class with methods added.
*/
/** @ignore */
// Note this method is modified by Partridge Jiang to support '$origin' param.
addMethods: function(source) {
if (!source)
return this;
var ancestor = this.superclass && this.superclass.prototype;
var properties = Object.keys(source);
if (!Object.keys({ toString: true }).length)
properties.push("toString", "valueOf");
var doNothing = function() {};
for (var i = 0, length = properties.length; i < length; i++) {
var property = properties[i];
var value = source[property];
var isFunction = Object.isFunction(value);
//if (ancestor && isFunction && value.argumentNames().first() == "$super")
//if (ancestor && isFunction && FunctionUtils.argumentNames(value).first() == "$super")
if (ancestor && isFunction && FunctionUtils.argumentNames(value)[0] === "$super")
{
var method = value;
/** @inner */
value = (function(m) {
return function() { return (ancestor[m] || doNothing).apply(this, arguments); };
})(property).wrap(method);
value.valueOf = method.valueOf.bind(method);
value.toString = method.toString.bind(method);
}
this.prototype[property] = value;
}
return this;
}
};
/** @ignore */
Object.extend = function(destination, source, ignoreUnsetValue, ignoreEmptyString)
{
if (!ignoreUnsetValue && !ignoreEmptyString) // normal situation, extract out for performance
{
for (var property in source)
{
destination[property] = source[property];
}
}
else
{
for (var property in source)
{
var value = source[property];
if (ignoreUnsetValue && ((value === undefined) || (value === null)))
continue;
if (ignoreEmptyString && (value === ''))
continue;
destination[property] = value;
}
}
return destination;
};
/** @ignore */
Object.extendEx = function(destination, source, options)
{
var ops = options || {};
for (var property in source)
{
var value = source[property];
if (ops.ignoreUnsetValue && ((value === undefined) || (value === null)))
continue;
if (ops.ignoreEmptyString && (value === ''))
continue;
var oldValue = destination[property];
if (options.preserveExisted && oldValue)
continue;
var oldProto = oldValue && oldValue.constructor && oldValue.constructor.prototype;
var newProto = value && value.constructor && value.constructor.prototype;
if (oldValue && typeof(oldValue) === 'object' && oldProto === newProto)
Object.extendEx(oldValue, value, options);
else
destination[property] = value;
}
return destination;
};
/** @ignore */
Object._extendSupportMethods = function(destination, methods)
{
return Object.extendEx(destination, methods, {ignoreUnsetValue: true, preserveExisted: true});
};
/** @ignore */
// e.g., obj.getCascadeFieldValue('level1.level2.name') will return obj.level1.level2.name.
Object.getCascadeFieldValue = function(fieldName, root)
{
var result;
var cascadeNames;
if (fieldName.length && fieldName.splice) // is an array
cascadeNames = fieldName;
else
cascadeNames = fieldName.split('.');
if (!root)
var root = $jsRoot || this;
for (var i = 0, l = cascadeNames.length; i < l; ++i)
{
result = root[cascadeNames[i]];
/*
if (!result && i === 0 && l > 1) // may be root part is defined in const or let, which won't be registered with global)
{
try { result = eval(cascadeNames[i]) } catch(e) { result = null; }
}
*/
if (!result)
{
break;
}
else
root = result;
}
return result;
};
/** @ignore */
Object.setCascadeFieldValue = function(fieldName, value, root, forceCreateEssentialObjs)
{
var cascadeNames;
if (fieldName.length && fieldName.splice) // is an array
cascadeNames = fieldName;
else
cascadeNames = fieldName.split('.');
var parent = root;
for (var i = 0, l = cascadeNames.length; i < l; ++i)
{
var name = cascadeNames[i];
if (i === l - 1) // success
{
parent[name] = value;
return value; //true;
}
else
{
var obj = parent[name];
if (!obj && forceCreateEssentialObjs) // create new obj
{
obj = {};
parent[name] = obj;
}
if (!obj)
return false;
else
parent = obj;
}
}
};
/**
* Create a "heir" object of proto.
* @ignore
*/
Object._inherit = function(proto)
{
if (proto === null)
proto = {};
//throw TypeError();
var t = typeof(proto);
if (!(DataType.isFunctionType(t) || DataType.isObjectType(t)))
throw TypeError();
function f() {};
f.prototype = proto;
return new f();
};
if (!Object.create)
{
Object.create = Object._inherit;
}
/** @ignore */
// Add by Partridge Jiang
// copy a set of values from src to dest
Object.copyValues = function(dest, src, propNames)
{
if (!propNames)
return Object.extend(dest, src);
else
{
for (var i = 0, l = propNames.length; i < l; ++i)
{
var prop = propNames[i];
var value = src[prop];
if (value !== undefined)
dest[prop] = value;
}
return dest;
}
};
// extend class methods
/** @ignore */
Object._extendSupportMethods(Object, {
keys: function(object) {
var keys = [];
for (var property in object)
keys.push(property);
return keys;
},
isFunction: function(object) {
return typeof object == "function";
},
isUndefined: function(object) {
return typeof object == "undefined";
}
});
if (!Object.getOwnPropertyNames)
{
Object.getOwnPropertyNames = function(obj)
{
var result = [];
for (var k in obj)
{
if (obj.hasOwnProperty(k))
result.push(k);
}
return result;
}
}
var FunctionUtils = {
argumentNames: function(f) {
var names = ((f.toString().match(/^[\s\(]*function[^(]*\(([^\)]*)\)/) || [])[1] || '').replace(/\s+/g, '').split(',');
return names.length == 1 && !names[0] ? [] : names;
}
/*
wrap: function(f, wrapper) {
var __method = f;
return function() {
return wrapper.apply(f, [__method.bind(f)].concat(__$A__(arguments)));
};
},
methodize: function(f) {
if (f._methodized) return f._methodized;
var __method = f;
return f._methodized = function() {
var a = Array.prototype.slice.call(arguments);
a.unshift(f);
return __method.apply(null, a);
};
},
bind: function(f) {
if (arguments.length < 2 && Object.isUndefined(arguments[0])) return f;
var __method = f, args = __$A__(arguments), object = args.shift();
return function() {
return __method.apply(object, args.concat(__$A__(arguments)));
};
},
delay: function(f) {
var __method = f, args = __$A__(arguments), timeout = args.shift();
return window.setTimeout(function() {
return __method.apply(__method, args);
}, timeout);
},
defer: function() {
var __method = f, args = __$A__(arguments), timeout = args.shift();
return window.setTimeout(function() {
return __method.apply(__method, args);
}, 10);
}
*/
};
/** @ignore */
Object._extendSupportMethods(Function.prototype, {
argumentNames: function() {
var names = this.toString().match(/^[\s\(]*function[^(]*\(([^\)]*)\)/)[1].replace(/\s+/g, '').split(',');
return names.length == 1 && !names[0] ? [] : names;
},
wrap: function(wrapper) {
var __method = this;
return function() {
return wrapper.apply(this, [__method.bind(this)].concat(__$A__(arguments)));
};
},
methodize: function() {
if (this._methodized) return this._methodized;
var __method = this;
return this._methodized = function() {
var a = Array.prototype.slice.call(arguments);
a.unshift(this);
return __method.apply(null, a);
};
},
bind: function() {
if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this;
var __method = this, args = __$A__(arguments), object = args.shift();
return function() {
return __method.apply(object, args.concat(__$A__(arguments)));
};
},
delay: function() {
var __method = this, args = __$A__(arguments), timeout = args.shift();
return $jsRoot.setTimeout(function() {
return __method.apply(__method, args);
}, timeout);
},
defer: function() {
var __method = this, args = __$A__(arguments), timeout = args.shift();
return $jsRoot.setTimeout(function() {
return __method.apply(__method, args);
}, 10);
}
});
/*
Object._extendSupportMethods(Function.prototype, {
argumentNames: function() {
return FunctionUtils.argumentNames(this);
},
wrap: function(wrapper) {
return FunctionUtils.wrap(this, wrapper);
},
methodize: function() {
return FunctionUtils.methodize(this);
},
bind: function() {
return FunctionUtils.bind(this);
},
delay: function() {
return FunctionUtils.delay(this);
},
defer: function() {
return FunctionUtils.defer(this);
}
});
*/
/** @ignore */
/*
Object._extendSupportMethods(Array.prototype, {
first: function() {
return this[0];
},
last: function() {
return this[this.length - 1];
},
clear: function() {
this.length = 0;
return this;
},
without: function() {
var values = __$A__(arguments);
return this.select(function(value) {
return !values.include(value);
});
},
removeAt: function(index)
{
this.splice(index, 1);
},
remove: function(item)
{
var index = this.indexOf(item);
if (index >= 0)
return this.removeAt(index);
},
forEach: function(func, scope)
{
var i, len;
for (i = 0, len = this.length; i < len; ++i) {
if (i in this) {
func.call(scope, this[i], i, this);
}
}
}
});
*/
if (!Array.prototype.indexOf)
{
/** @ignore */
Array.prototype.indexOf = function(item, i) {
i || (i = 0);
var length = this.length;
if (i < 0) i = length + i;
for (; i < length; i++)
if (this[i] === item) return i;
return -1;
};
}
if (!Array.prototype.lastIndexOf)
{
/** @ignore */
Array.prototype.lastIndexOf = function(item, i) {
i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1;
var n = this.slice(0, i).reverse().indexOf(item);
return (n < 0) ? n : i - n - 1;
};
}
// Production steps of ECMA-262, Edition 5, 15.4.4.19
// Reference: http://es5.github.io/#x15.4.4.19
if (!!Array.prototype.map) {
Array.prototype.map = function(callback/*, thisArg*/) {
var T, A, k;
if (this == null) {
throw new TypeError('this is null or not defined');
}
// 1. Let O be the result of calling ToObject passing the |this|
// value as the argument.
var O = Object(this);
// 2. Let lenValue be the result of calling the Get internal
// method of O with the argument "length".
// 3. Let len be ToUint32(lenValue).
var len = O.length >>> 0;
// 4. If IsCallable(callback) is false, throw a TypeError exception.
// See: http://es5.github.com/#x9.11
if (typeof callback !== 'function') {
throw new TypeError(callback + ' is not a function');
}
// 5. If thisArg was supplied, let T be thisArg; else let T be undefined.
if (arguments.length > 1) {
T = arguments[1];
}
// 6. Let A be a new array created as if by the expression new Array(len)
// where Array is the standard built-in constructor with that name and
// len is the value of len.
A = new Array(len);
// 7. Let k be 0
k = 0;
// 8. Repeat, while k < len
while (k < len) {
var kValue, mappedValue;
// a. Let Pk be ToString(k).
// This is implicit for LHS operands of the in operator
// b. Let kPresent be the result of calling the HasProperty internal
// method of O with argument Pk.
// This step can be combined with c
// c. If kPresent is true, then
if (k in O) {
// i. Let kValue be the result of calling the Get internal
// method of O with argument Pk.
kValue = O[k];
// ii. Let mappedValue be the result of calling the Call internal
// method of callback with T as the this value and argument
// list containing kValue, k, and O.
mappedValue = callback.call(T, kValue, k, O);
// iii. Call the DefineOwnProperty internal method of A with arguments
// Pk, Property Descriptor
// { Value: mappedValue,
// Writable: true,
// Enumerable: true,
// Configurable: true },
// and false.
// In browsers that support Object.defineProperty, use the following:
// Object.defineProperty(A, k, {
// value: mappedValue,
// writable: true,
// enumerable: true,
// configurable: true
// });
// For best browser support, use the following:
A[k] = mappedValue;
}
// d. Increase k by 1.
k++;
}
// 9. return A
return A;
};
}
/** @ignore */
Object._extendSupportMethods(String.prototype, {
gsub: function gsub(pattern, replacement) {
var result = '', source = this, match;
//replacement = this.gsub.prepareReplacement(replacement);
while (source.length > 0) {
if (match === source.match(pattern)) {
result += source.slice(0, match.index);
result += replacement;
source = source.slice(match.index + match[0].length);
} else {
result += source, source = '';
}
}
return result;
},
sub: function(pattern, replacement, count) {
//replacement = this.gsub.prepareReplacement(replacement);
count = Object.isUndefined(count) ? 1 : count;
return this.gsub(pattern, function(match) {
if (--count < 0) return match[0];
return replacement(match);
});
},
scan: function(pattern, iterator) {
this.gsub(pattern, iterator);
return String(this);
},
truncate: function(length, truncation) {
length = length || 30;
truncation = Object.isUndefined(truncation) ? '...' : truncation;
return this.length > length ?
this.slice(0, length - truncation.length) + truncation : String(this);
},
strip: function() {
return this.replace(/^\s+/, '').replace(/\s+$/, '');
},
stripTags: function() {
return this.replace(/<\/?[^>]+>/gi, '');
},
stripScripts: function() {
return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
},
extractScripts: function() {
var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
return (this.match(matchAll) || []).map(function(scriptTag) {
return (scriptTag.match(matchOne) || ['', ''])[1];
});
},
/*
evalScripts: function() {
return this.extractScripts().map(function(script) { return eval(script); });
},
*/
escapeHTML: function escapeHTML() {
/*
var self = arguments.callee;
self.text.data = this;
return self.div.innerHTML;
*/
return this.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/\n/g, '<br />');
},
unescapeHTML: function() {
/*
var div = new Element('div');
div.innerHTML = this.stripTags();
return div.childNodes[0] ? (div.childNodes.length > 1 ?
__$A__(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue; }) :
div.childNodes[0].nodeValue) : '';
*/
return this.replace(/\<br \/\>/g, '\n').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
},
toQueryParams: function(separator) {
var match = this.strip().match(/([^?#]*)(#.*)?$/);
if (!match) return { };
return match[1].split(separator || '&').inject({ }, function(hash, pair) {
if ((pair = pair.split('='))[0]) {
var key = decodeURIComponent(pair.shift());
var value = pair.length > 1 ? pair.join('=') : pair[0];
if (value != undefined) value = decodeURIComponent(value);
if (key in hash) {
if (!Object.isArray(hash[key])) hash[key] = [hash[key]];
hash[key].push(value);
}
else hash[key] = value;
}
return hash;
});
},
toArray: function() {
return this.split('');
},
succ: function() {
return this.slice(0, this.length - 1) +
String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
},
times: function(count) {
return count < 1 ? '' : new Array(count + 1).join(this);
},
camelize: function() {
var parts = this.split('-'), len = parts.length;
if (len == 1) return parts[0];
var camelized = this.charAt(0) == '-'
? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)
: parts[0];
for (var i = 1; i < len; i++)
camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);
return camelized;
},
capitalize: function() {
return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
},
capitalizeFirst: function() {
return this.charAt(0).toUpperCase() + this.substring(1);
},
underscore: function() {
//return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase();
return this.replace(/::/g, '/')
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
.replace(/([a-z\d])([A-Z])/g, '$1_$2')
.replace(/-/g, '_')
.toLowerCase();
},
dasherize: function() {
//return this.gsub(/_/,'-');
return this.replace(/::/g, '/')
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1-$2')
.replace(/([a-z\d])([A-Z])/g, '$1-$2')
.replace(/-/g, '-')
.toLowerCase();
},
inspect: function(useDoubleQuotes) {
var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) {
var character = String.specialChar[match[0]];
return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16);
});
if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"';
return "'" + escapedString.replace(/'/g, '\\\'') + "'";
},
toJSON: function() {
return this.inspect(true);
},
unfilterJSON: function(filter) {
return this.sub(filter || Prototype.JSONFilter, '#{1}');
},
isJSON: function() {
var str = this;
if (str.blank()) return false;
str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '');
return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str);
},
/*
evalJSON: function(sanitize) {
var json = this.unfilterJSON();
try {
if (!sanitize || json.isJSON()) return eval('(' + json + ')');
} catch (e) { }
throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
},
*/
include: function(pattern) {
return this.indexOf(pattern) > -1;
},
startsWith: function(pattern) {
return this.indexOf(pattern) === 0;
},
endsWith: function(pattern) {
var d = this.length - pattern.length;
return d >= 0 && this.lastIndexOf(pattern) === d;
},
empty: function() {
return this == '';
},
blank: function() {
return /^\s*$/.test(this);
},
interpolate: function(object, pattern) {
return new Template(this, pattern).evaluate(object);
}
});
// added by partridge
/** @ignore */
Object._extendSupportMethods(String.prototype, {
upperFirst: function()
{
return this.charAt(0).toUpperCase() + this.substring(1);
},
// 'The {0} is dead. Don\'t code {0}. Code {1} that is open source!'.format('ASP', 'PHP');
format: function() {
var formatted = this;
for (var i = 0; i < arguments.length; i++) {
var regexp = new RegExp('\\{'+i+'\\}', 'gi');
formatted = formatted.replace(regexp, arguments[i]);
}
return formatted;
},
trim: function()
{
return this.replace(/^\s*|\s*$/g, "");
},
ltrim: function()
{
return this.replace(/^\s+/,"");
},
rtrim: function()
{
return this.replace(/\s+$/,"");
},
trimLeft: function()
{
return this.ltrim();
},
trimRight: function()
{
return this.rtrim();
},
pad: function(length, padString, rightPad)
{
var p = padString || ' ';
var a = [];
for (var i = 0, l = length - this.length; i < l; ++i)
a.push(p);
var str = this;
if (rightPad)
return str + a.join('');
else
return a.join('') + str;
},
lpad: function(length, padString)
{
return this.pad(length, padString, false);
},
rpad: function(length, padString)
{
return this.pad(length, padString, true);
},
reverse: function()
{
var length = this.length;
var a = [];
for (var i = length - 1; i >= 0; --i)
{
a.push(this.charAt(i));
}
return a.join('');
},
toCharArray: function()
{
var a = [];
for (var i = 0, l = this.length; i < l; ++i)
{
a.push(this.charAt(i));
}
return a;
},
/*
* Turn camelized word like thisIsAWord to this-is-a-word.
*/
hyphenize: function(hyphen)
{
if (!hyphen)
hyphen = '-';
var len = this.length;
var a = [];
for (var i = 0; i < len; ++i)
{
var c = this.charAt(i);
if ((i !== 0) && (c >= 'A') && (c <= 'Z'))
{
a.push(hyphen + c.toLowerCase());
}
else
a.push(c);
}
return a.join('');
}
});
/**
* Some helper methods about string
* @class
*/
var StringUtils = {
/** @private */
STRUE: '$TRUE',
/** @private */
SFALSE: '$FALSE',
/** @private */
SUNDEFINED: '$UNDEFINED',
/** @private */
SNULL: '$NULL',
/** @private */
SNAN: '$NAN',
/** @private */
SPOSITIVE: '+',
/** @private */
SNEGATIVE: '-',
/** @private */
SDATEPREFIX: '@',
/**
* Check if a str is all consisted of digitals
* @param {String} str
* @returns {Bool}
* @private
*/
isAllDigitalChar: function(str)
{
for (var i = 0, l = str.length; i < l; ++i)
{
var c = str.charAt(i);
var isDigital = (c >= '0') && (c <= '9');
if ((!isDigital) && (c != '.'))
return false;
}
return true;
},
/**
* Check if str is in number format.
* @param {String} str
* @returns {Bool}
*/
isNumbericStr: function(str)
{
var a = Number(str);
return !isNaN(a);
},
/**
* Serialize a simple value and try to preserve value type info.
* @param {Variant} value
* @param {Array} unchangeTypes Name of types that need not to be special marked.
* @returns {String}
*/
serializeValue: function(value, unchangeTypes)
{
var utypes = unchangeTypes || [];
var vtype = DataType.getType(value);
if (utypes.indexOf(vtype) >= 0)
return value.toString();
if (value === null)
return StringUtils.SNULL;
else if (typeof(value) == 'undefined')
return StringUtils.SUNDEFINED;
else if (value != value) // NaN
return StringUtils.SNAN;
else
{
switch (vtype)
{
case 'boolean':
return value ? StringUtils.STRUE : StringUtils.SFALSE;
break;
case 'number': case DataType.INT:case DataType.FLOAT:
// add '+' or '-' symbol before a number value
var sign = (value >= 0)? StringUtils.SPOSITIVE: ''; //this.SNEGATIVE;
return sign + value;
break;
case DataType.DATE:
return StringUtils.SDATEPREFIX + value.toString();
default: // string
return value.toString();
}
}
},
/**
* Deserialize a simple value from string by value type info stored inside.
* @param {String} str
* @param {String} preferedType If provided, str will be converted to this type.
* @returns {Variant}
*/
deserializeValue: function(str, preferedType)
{
if (typeof(str) !== 'string')
return str;
switch(str)
{
case StringUtils.STRUE: return true; break;
case StringUtils.SFALSE: return false; break;
case StringUtils.SNULL: return null; break;
case StringUtils.SUNDEFINED: return undefined; break;
case StringUtils.SNAN: return NaN; break;
default:
{
if (preferedType)
{
if (!str && [DataType.NUMBER, DataType.INT, DataType.FLOAT].indexOf(preferedType) >= 0) // need to convert empty string to num, avoid NaN result
return undefined;
switch (preferedType)
{
case DataType.FLOAT:
return parseFloat(str); break;
case DataType.INT:
return parseInt(str); break;
case 'number':
return parseFloat(str); break;
case 'boolean':
return !!str; break;
default:
return str;
}
}
else // guess
{
var firstChar = str.charAt(0);
switch (firstChar)
{
case StringUtils.SPOSITIVE:
case StringUtils.SNEGATIVE: // may be number
{
var s = str.substring(1);
if (StringUtils.isNumbericStr(s)) // really number or number like 1e20
return parseFloat(str);
else
return str;
break;
}
case StringUtils.SDATEPREFIX: // may be date
{
var s = str.substr(1);
try
{
var d = new Date(s);
if (!isNaN(d.getTime()))
return d;
else
return str;
}
catch(e)
{
return str;
}
}
default:
return str;
}
}
}
}
}
};
// Added by partridge
/** @ignore */
Object.extend(Date.prototype, {
/** @ignore */
copyFrom: function(src)
{
this.setFullYear(src.getFullYear(), src.getMonth(), src.getDate());
this.setTime(src.getTime());
}
});
if (!Date.now) // polyfill some ancient browser
{
Date.now = function now() {
return new Date().getTime();
};
}
/** @ignore */
if (!Math.sqr)
{
Math.sqr = function(x)
{
return x * x;
};
}
/** @ignore */
if (!Math.sign)
Math.sign = function(x) {
return (x > 0)? 1:
(x < 0)? -1:
0;
};
/** @ignore */
if (!Math.log10)
Math.log10 = function(x) {
return Math.log(x) * Math.LOG10E;
};
// Add Node.XXXX support in IE
//if (!window.Node) var Node = { };
if (typeof($jsRoot.Node) == 'undefined') $jsRoot.Node = function(){};
if (!$jsRoot.Node.ELEMENT_NODE) {
// DOM level 2 ECMAScript Language Binding
Object.extend($jsRoot.Node, {
ELEMENT_NODE: 1,
ATTRIBUTE_NODE: 2,
TEXT_NODE: 3,
CDATA_SECTION_NODE: 4,
ENTITY_REFERENCE_NODE: 5,
ENTITY_NODE: 6,
PROCESSING_INSTRUCTION_NODE: 7,
COMMENT_NODE: 8,
DOCUMENT_NODE: 9,
DOCUMENT_TYPE_NODE: 10,
DOCUMENT_FRAGMENT_NODE: 11,
NOTATION_NODE: 12
});
}
/**
* Enumeration of property scope.
* @enum {Int}
*/
Class.PropertyScope = {
DEFAULT: 3,
PUBLISHED: 3,
PUBLIC: 2,
PRIVATE: 1
};
/** @class Class.PropList
* @description Property list, inside each prop is stored as a object:
* {
* name: property name,
* storeField: defaultly which field to store value,
* dataType:
* serializable: boolean,
* defaultValue: default value of property
* // added in 2014-05-02, for object inspector
* title: a string that displays in object inspector.
* description: a string that describes the property.
* category: string, category of property. Different property may have the same category.
* scope: private, public or published. Used to filter output properties in object inspector.
* enumSource: object. Some property marked as int or other type may be actually a enum,
* if this value is set, all owned fields of enumSource will be treated as possible enum values in object inspector.
* elementType: string. For array type property, this field set the type of array items.
* }
* @ignore
*/
Class.PropList = function()
{
this.props = [];
};
Class.PropList.prototype = {
//** @private */
//PROP_KEY_PREFIX: '__$prop__',
//** @private */
/*
getPropKeyPrefix: function(propName)
{
return this.PROP_KEY_PREFIX + propName;
},
*/
/**
* Add a property info object to the list.
* @param {String} propName
* @param {Hash} options A hash that may contains the following fields:
* {
* storeField: field name to store the property in object.
* dataType: data type of property, unused currently.
* serializable: whether this property can be serialized or unserialized.
* defaultValue: default value of property, can only be simple type (string, number) now.
* getter:
* setter:
* // added in 2014-05-02, for object inspector
* title: a string that displays in object inspector.
* description: a string that describes the property.
* category: string, category of property. Different property may have the same category.
* scope: private, public or published. Used to filter output properties in object inspector.
* enumSource: object. Some property marked as int or other type may be actually a enum,
* if this value is set, all owned fields of enumSource will be treated as possible enum values in object inspector.
* elementType: string. For array type property, this field set the type of array items.
* }
* @returns {Object} A property info object just added.
*/
addProperty: function(propName, options)
{
if (!options)
options = {};
var propInfo;
propInfo = this.getPropInfo(propName);
if (!propInfo)
{
propInfo = {};
this.props.push(propInfo);
}
propInfo = Object.extend(propInfo, options);
if (propInfo.serializable === undefined)
propInfo.serializable = true;
propInfo.name = propName;
/*
propInfo.storeField = options.storeField;
propInfo.dataType = options.dataType;
propInfo.serializable = options.serializable;
propInfo.defaultValue = options.defaultValue;
*/
/*
// to accelarate the speed of getting prop info, add a hash key here
this[this.getPropKeyPrefix(propName)] = propInfo;
*/
return propInfo;
},
/**
* Remove a property info object from list
* @param {String} propName property name to be removed.
*/
removeProperty: function(propName)
{
/*
var hashKey = this.getPropKeyPrefix(propName);
if (this[hashKey])
delete this[hashKey];
*/
var index = this.indexOf(propName);
if (index >= 0)
{
this.props.splice(index, 1);
}
//this.props.length = this.props.length - 1;
},
removePropAt: function(index)
{
if (index >= 0)
{
var prop = this.props[index];
if (prop)
{
/*
var hashKey = this.getPropKeyPrefix(prop.name);
if (this[hashKey])
delete this[hashKey];
*/
this.props.splice(index, 1);
}
}
},
/**
* Returns property count in this list.
* @returns {Int}
*/
getLength: function()
{
return this.props.length;
},
/**
* Get the index of a property in list.
* @param {String} propName Name of property to be found.
* @returns {Num} Index of property found. If nothing is found, returns -1.
*/
indexOf: function(propName)
{
for (var i = 0, l = this.props.length; i < l; ++i)
{
if (this.props[i].name === propName)
return i;
}
return -1;
},
/**
* Get property info object from the list at index.
* @param {Int} index index of property to be found.
* @returns {Object} Property info object found. If nothing is found, returns null.
*/
getPropInfoAt: function(index)
{
if (index >= 0)
return this.props[index];
else
return null;
},
/**
* Get property info object from the list.
* @param {String} propName Name of property to be found.
* @returns {Object} Property info object found. If nothing is found, returns null.
*/
getPropInfo: function(propName)
{
var result;
/*
var hashKey = this.getPropKeyPrefix(propName);
if (this[hashKey])
result = this[hashKey];
else
*/
{
var index = this.indexOf(propName);
var result = this.getPropInfoAt(index);
/*
if (!result)
;
else
{
console.dir(this);
throw('should not be here: ' + propName);
}
*/
//this[hashKey] = result; // add to hash key to accelerate next time searching
}
return result;
},
/**
* Check whether a property existed in the list.
* @param {String} propName Name of property to be checked.
* @return {Bool} true or false.
*/
hasProperty: function(propName)
{
/*
var hashKey = this.getPropKeyPrefix(propName);
return (!!this[hashKey]) || (this.indexOf(propName) >= 0);
*/
return (this.indexOf(propName) >= 0);
},
/**
* Clear all property info objects in the list.
*/
clear: function()
{
/*
var props = this.props;
for (var i = props.length; i >= 0; --i)
{
this.removePropAt(i);
}
*/
this.props.clear();
},
/**
* Clone the while propList
* @returns {Class.PropList}
*/
clone: function()
{
var result = new Class.PropList();
result.props = this.props.slice();
//result.props = [].concat(this.props);
return result;
},
/**
* Append the content of propList to current one.
* @param {Class.PropList} propList
*/
appendList: function(propList)
{
for (var i = 0, l = propList.props.length; i < l; ++i)
{
this.props.push(propList.props[i]);
}
}
};
Class.PropList.prototype.constructor = Class.PropList;
/**
* @class Class.EventHandlerList
* @description
* Event handler list, support for multi-receiver event system
* the list hold a handlerInfo array, each item has two fields:
* {
* handler: handler function,
* thisArg: this scope object, if null, use default this
* }
* @ignore
*/
Class.EventHandlerList = function()
{
this.handlers = [];
this._$flag_ = 'KekuleEventList';
};
Class.EventHandlerList.prototype = {
/**
* Add a handler to the list
* @param {Function} handler An event handler function.
* @param {Object} thisArg The handler should be bind to which scope when be invoked.
*/
add: function(handler, thisArg)
{
if (!thisArg)
thisArg = null;
this.handlers.push({
'thisArg': thisArg,
'handler': handler
});
},
/**
* Remove an event handler from the list.
* @param {Function} handler Handler function to be removed.
* @param {Object} thisArg If this param is null, all functions same as handler
* in the list will be removed regardless of whether their thisArg is setted.
*/
remove: function(handler, thisArg)
{
var indexes = this.indexesOf(handler, thisArg);
if (indexes.length > 0)
{
for (var i = indexes.length - 1; i >= 0; --i)
{
this.removeAt(indexes[i]);
}
}
},
/**
* Remove an event handler at a specified index.
* @param {Num} index
*/
removeAt: function(index)
{
for (var i = index, l = this.handlers.length; i < l; ++i)
this.handlers[i] = this.handlers[i + 1];
this.handlers.length = this.handlers.length - 1;
},
/**
* Clear all handlers in the list.
*/
clear: function()
{
this.handlers = [];
},
/**
* Get handler info object from the list.
* @param {Num} index
* @return {Object} Handler info object on index.
*/
getHandlerInfo: function(index)
{
return this.handlers[index];
},
/**
* Get the index of a handler specified with thisArg.
* @param {Function} handler
* @param {Object} thisArg
* @return {Num} Index of the handler. If nothing is found, returns -1.
*/
indexOf: function(handler, thisArg)
{
for (var i = 0, l = this.handlers.length; i < l; ++i)
{
if (this.handlers[i].handler == handler)
{
if ((thisArg !== undefined) && (this.handlers[i].thisArg === thisArg))
return i;
else if (thisArg === undefined)
return i;
}
}
return -1;
},
/**
* Seek out all indexes that match handler and thisArg.
* @param {Function} handler
* @param {Object} thisArg
* @return {Array} All found indexes. If nothing is found, an empty array will be returned.
*/
indexesOf: function(handler, thisArg)
{
var result = [];
for (var i = 0, l = this.handlers.length; i < l; ++i)
{
if (this.handlers[i].handler == handler)
{
if ((thisArg !== undefined) && (this.handlers[i].thisArg === thisArg))
result.push(i);
else if (thisArg === undefined)
result.push(i);
}
}
return result;
},
/**
* Get total count of registered handlers.
* @return {Num} Number of handlers.
*/
getLength: function()
{
return this.handlers.length;
}
};
Class.EventHandlerList.constructor = Class.EventHandlerList;
/**
* Includes constants and mthods about data types.
* @class
*/
var DataType = {
/** Unknown data type, same as {@link DataType.VARIANT}. */
UNKNOWN: null,
/** Variant data type, same as {@link DataType.UNKNOWN}. */
VARIANT: null,
/** Basic data type, including string, number and boolean. */
PRIMARY: 'primary',
/** type of JS const undefined */
UNDEFINED: 'undefined',
/** Boolean. */
BOOL: 'boolean',
/** Boolean. same as {@link DataType.BOOL} */
BOOLEAN: 'boolean',
/** Number. */
NUMBER: 'number',
/** Explicit integer number. */
INT: 'int',
/** Explicit integer number, same as {@link DataType.INT} */
INTEGER: 'int',
/** Explicit float number. */
FLOAT: 'float',
/** String */
STRING: 'string',
/** Array */
ARRAY: 'array',
/** Function */
FUNCTION: 'function',
/** Hash */
DATE: 'date',
HASH: 'object',
/** A normal JavaScript object. */
OBJECT: 'object',
/** Object extended from {@link ObjectEx} */
OBJECTEX: 'objectex',
/** A CLASS */
CLASS: 'class',
/**
* Returns whether a type name is string, number or boolean
* @param {String} typeName
* @returns {Bool}
*/
isSimpleType: function(typeName)
{
return (typeName == DataType.STRING) || (typeName == DataType.NUMBER)
|| (typeName == DataType.INT) || (typeName == DataType.FLOAT)
|| (typeName == DataType.BOOL) || (typeName == DataType.UNDEFINED)
|| (typeName == DataType.PRIMARY);
},
/**
* Returns whether a type name is object, array or objectex
* @param {String} typeName
* @returns {Bool}
*/
isComplexType: function(typeName)
{
return !(DataType.isSimpleType(typeName) || DataType.isFunctionType(typeName));
},
/**
* Returns whether a type name is function.
* @param {String} typeName
* @returns {Bool}
*/
isFunctionType: function(typeName)
{
return typeName == DataType.FUNCTION;
},
/**
* Returns whether a type name is object.
* NOTE: this function does not distinguish array.
* @param {String} typeName
* @returns {Bool}
*/
isObjectType: function(typeName)
{
return typeName == DataType.OBJECT;
},
/**
* Returns whether a type name is Date.
* @param {String} typeName
* @returns {Bool}
*/
isDateType: function(typeName)
{
return typeName == DataType.DATE;
},
/**
* Returns whether a type name is ObjectEx.
* @param {String} typeName
* @returns {Bool}
*/
isObjectExType: function(typeName)
{
var result = DataType.isComplexType(typeName) && (!DataType.isObjectType(typeName)) && (!DataType.isDateType(typeName));
if (result) // check if the class exists
{
var classObj = ClassEx.findClass(typeName);
result = classObj && ClassEx.isOrIsDescendantOf(classObj, ObjectEx);
}
return result;
},
/**
* Get value type and returns a data type string.
* @param {Variant} value
* @returns {String}
*/
getType: function(value)
{
var stype = typeof(value);
switch (stype)
{
// TODO: Some native classes such as RegExp are not checked yet
// basic types
case 'undefined': return DataType.UNDEFINED;
case 'function': return DataType.FUNCTION;
case 'boolean': return DataType.BOOL;
case 'string': return DataType.STRING;
case 'number':
{
if (Math.floor(value) == value)
return DataType.INT;
else
return DataType.FLOAT;
}
case 'object': // complex
{
if (this.isDateValue(value))
return DataType.DATE;
else if (DataType.isArrayValue(value))
return DataType.ARRAY;
else if (ClassEx.isClass(value))
return DataType.CLASS;
else if (DataType.isObjectExValue(value) && value.getClassName)
return value.getClassName();
else
return DataType.OBJECT;
}
default:
return stype;
}
},
/**
* Check if value is number, string or bool.
* @param {Variant} value
* @returns {Bool}
* @private
*/
isSimpleValue: function(value)
{
return DataType.isSimpleType(typeof(value));
},
/**
* Check if value is undefined
* @param {Variant} value
* @returns {Bool}
* @private
*/
isUndefinedValue: function(value)
{
return typeof(value) == 'undefined';
},
/**
* Check if value is null
* @param {Variant} value
* @returns {Bool}
* @private
*/
isNullValue: function(value)
{
return (value === null);
},
/**
* Check if value is a function.
* @param {Variant} value
* @returns {Bool}
* @private
*/
isFunctionValue: function(value)
{
return typeof(value) == 'function';
},
/**
* Check if an value is an non-array Object.
* @param {Variant} value
* @returns {Bool}
* @private
*/
isObjectValue: function(value)
{
if (value) // not null
return (typeof(value) == 'object') && (!DataType.isArrayValue(value)) && (!DataType.isDateValue(value));
else
return false;
},
/**
* Check if an value is an instance of Date.
* @param {Variant} value
* @returns {Bool}
* @private
*/
isDateValue: function(value)
{
if (value)
return ((typeof(value) == 'object') && (value.getFullYear !== undefined));
return false;
},
/**
* Check if an value is an Array
* @param {Variant} value
* @returns {Bool}
* @private
*/
isArrayValue: function(value)
{
if (value)
return ((typeof(value) == 'object') && (value.length !== undefined));
else
return false;
},
/**
* Check if an value is an instance of ObjectEx
* @param {Variant} value
* @returns {Bool}
* @private
*/
isObjectExValue: function(value)
{
return (value instanceof ObjectEx);
},
/**
* Create an instance of typeName
* @param {String} typeName
* @returns {Variant}
*/
createInstance: function(typeName)
{
switch (typeName)
{
case DataType.UNDEFINED: return undefined;
case DataType.DATE: return new Date();
case DataType.ARRAY: return new Array();
case DataType.OBJECT: return new Object();
//case DataType.FUNCTION: return new Function();
case DataType.FUNCTION: return function(){};
default: // maybe a ObjectEx descendant
{
var classInstance = ClassEx.findClass(typeName.capitalizeFirst()); //eval(typeName.capitalizeFirst());
return new classInstance();
}
}
}
};
// Wether has full support of Object/defineProperty method (IE8 has partial support and will return false)
var __definePropertyAvailable__ = Object.defineProperty &&
(function () { try { Object.defineProperty({}, 'x', {}); return true; } catch (e) { return false; } } ())
/**
* A pack of utility methods to modify a existing class.
* @class ClassEx
*/
var ClassEx = {
/**
* Checks if a object is a class object.
* @param {Object} classObj
*/
isClass: function(classObj)
{
if (!classObj)
return false;
return !!(classObj.superclass || classObj.subclasses);
},
/**
* Return class object from class name. If this class is not found, null will be returned.
* @param {String} className
* @returns {Class}
*/
findClass: function(className, root)
{
/*
var result;
var cascadeNames = className.split('.');
if (!root)
var root = $jsRoot;
for (var i = 0, l = cascadeNames.length; i < l; ++i)
{
result = root[cascadeNames[i]];
if (!result)
break;
else
root = result;
}
return result;
*/
//return Object.getCascadeFieldValue(className, root || $jsRoot);
return Class.findClass(className, root || $jsRoot);
},
/**
* Get class name of aClass, usually returns CLASS_NAME field of aClass
* @returns {String} Class name.
*/
getClassName: function(aClass)
{
if (aClass)
return aClass.prototype.CLASS_NAME;
else
return null;
},
/**
* Get last part of class name of this class.
* For example, 'Atom' will be returned by class 'Kekule.Atom'.
* @return {String} Last part of class name of cla