UNPKG

kekule

Version:

Open source JavaScript toolkit for chemoinformatics

1,892 lines (1,766 loc) 114 kB
/** * @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,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').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(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/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