/** * Modified version of [Douglas Crockford's JSON.js][dc] that doesn't * mess with the Object prototype. * * [dc]: http://www.json.org/js.html * * @singleton */ Ext.JSON = (new(function() { var me = this, encodingFunction, decodingFunction, useNative = null, useHasOwn = !! {}.hasOwnProperty, isNative = function() { if (useNative === null) { useNative = Ext.USE_NATIVE_JSON && window.JSON && JSON.toString() == '[object JSON]'; } return useNative; }, pad = function(n) { return n < 10 ? "0" + n : n; }, doDecode = function(json) { return eval("(" + json + ')'); }, doEncode = function(o, newline) { // http://jsperf.com/is-undefined if (o === null || o === undefined) { return "null"; } else if (Ext.isDate(o)) { return Ext.JSON.encodeDate(o); } else if (Ext.isString(o)) { return Ext.JSON.encodeString(o); } else if (typeof o == "number") { //don't use isNumber here, since finite checks happen inside isNumber return isFinite(o) ? String(o) : "null"; } else if (Ext.isBoolean(o)) { return String(o); } // Allow custom zerialization by adding a toJSON method to any object type. // Date/String have a toJSON in some environments, so check these first. else if (o.toJSON) { return o.toJSON(); } else if (Ext.isArray(o)) { return encodeArray(o, newline); } else if (Ext.isObject(o)) { return encodeObject(o, newline); } else if (typeof o === "function") { return "null"; } return 'undefined'; }, m = { "\b": '\\b', "\t": '\\t', "\n": '\\n', "\f": '\\f', "\r": '\\r', '"': '\\"', "\\": '\\\\', '\x0b': '\\u000b' //ie doesn't handle \v }, charToReplace = /[\\\"\x00-\x1f\x7f-\uffff]/g, encodeString = function(s) { return '"' + s.replace(charToReplace, function(a) { var c = m[a]; return typeof c === 'string' ? c : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); }) + '"'; }, // encodeArrayPretty = function(o, newline) { var len = o.length, cnewline = newline + ' ', sep = ',' + cnewline, a = ["[", cnewline], // Note newline in case there are no members i; for (i = 0; i < len; i += 1) { a.push(Ext.JSON.encodeValue(o[i], cnewline), sep); } // Overwrite trailing comma (or empty string) a[a.length - 1] = newline + ']'; return a.join(''); }, encodeObjectPretty = function(o, newline) { var cnewline = newline + ' ', sep = ',' + cnewline, a = ["{", cnewline], // Note newline in case there are no members i; for (i in o) { if (!useHasOwn || o.hasOwnProperty(i)) { a.push(Ext.JSON.encodeValue(i) + ': ' + Ext.JSON.encodeValue(o[i], cnewline), sep); } } // Overwrite trailing comma (or empty string) a[a.length - 1] = newline + '}'; return a.join(''); }, // encodeArray = function(o, newline) { // if (newline) { return encodeArrayPretty(o, newline); } // var a = ["[", ""], // Note empty string in case there are no serializable members. len = o.length, i; for (i = 0; i < len; i += 1) { a.push(Ext.JSON.encodeValue(o[i]), ','); } // Overwrite trailing comma (or empty string) a[a.length - 1] = ']'; return a.join(""); }, encodeObject = function(o, newline) { // if (newline) { return encodeObjectPretty(o, newline); } // var a = ["{", ""], // Note empty string in case there are no serializable members. i; for (i in o) { if (!useHasOwn || o.hasOwnProperty(i)) { a.push(Ext.JSON.encodeValue(i), ":", Ext.JSON.encodeValue(o[i]), ','); } } // Overwrite trailing comma (or empty string) a[a.length - 1] = '}'; return a.join(""); }; /** * Encodes a String. This returns the actual string which is inserted into the JSON string as the literal * expression. **The returned value includes enclosing double quotation marks.** * * To override this: * * Ext.JSON.encodeString = function(s) { * return 'Foo' + s; * }; * * @param {String} s The String to encode * @return {String} The string literal to use in a JSON string. * @method */ me.encodeString = encodeString; /** * The function which {@link #encode} uses to encode all javascript values to their JSON representations * when {@link Ext#USE_NATIVE_JSON} is `false`. * * This is made public so that it can be replaced with a custom implementation. * * @param {Object} o Any javascript value to be converted to its JSON representation * @return {String} The JSON representation of the passed value. * @method */ me.encodeValue = doEncode; /** * Encodes a Date. This returns the actual string which is inserted into the JSON string as the literal * expression. **The returned value includes enclosing double quotation marks.** * * The default return format is `"yyyy-mm-ddThh:mm:ss"`. * * To override this: * * Ext.JSON.encodeDate = function(d) { * return Ext.Date.format(d, '"Y-m-d"'); * }; * * @param {Date} d The Date to encode * @return {String} The string literal to use in a JSON string. */ me.encodeDate = function(o) { return '"' + o.getFullYear() + "-" + pad(o.getMonth() + 1) + "-" + pad(o.getDate()) + "T" + pad(o.getHours()) + ":" + pad(o.getMinutes()) + ":" + pad(o.getSeconds()) + '"'; }; /** * Encodes an Object, Array or other value. * * If the environment's native JSON encoding is not being used ({@link Ext#USE_NATIVE_JSON} is not set, * or the environment does not support it), then ExtJS's encoding will be used. This allows the developer * to add a `toJSON` method to their classes which need serializing to return a valid JSON representation * of the object. * * @param {Object} o The variable to encode * @return {String} The JSON string */ me.encode = function(o) { if (!encodingFunction) { // setup encoding function on first access encodingFunction = isNative() ? JSON.stringify : me.encodeValue; } return encodingFunction(o); }; /** * Decodes (parses) a JSON string to an object. If the JSON is invalid, this function throws * a SyntaxError unless the safe option is set. * * @param {String} json The JSON string * @param {Boolean} [safe=false] True to return null, false to throw an exception if the JSON is invalid. * @return {Object} The resulting object */ me.decode = function(json, safe) { if (!decodingFunction) { // setup decoding function on first access decodingFunction = isNative() ? JSON.parse : doDecode; } try { return decodingFunction(json); } catch (e) { if (safe === true) { return null; } Ext.Error.raise({ sourceClass: "Ext.JSON", sourceMethod: "decode", msg: "You're trying to decode an invalid JSON String: " + json }); } }; })()); /** * Shorthand for {@link Ext.JSON#encode} * @member Ext * @method encode * @inheritdoc Ext.JSON#encode */ Ext.encode = Ext.JSON.encode; /** * Shorthand for {@link Ext.JSON#decode} * @member Ext * @method decode * @inheritdoc Ext.JSON#decode */ Ext.decode = Ext.JSON.decode; /** * @class Ext * * The Ext namespace (global object) encapsulates all classes, singletons, and * utility methods provided by Sencha's libraries. * * Most user interface Components are at a lower level of nesting in the namespace, * but many common utility functions are provided as direct properties of the Ext namespace. * * Also many frequently used methods from other classes are provided as shortcuts * within the Ext namespace. For example {@link Ext#getCmp Ext.getCmp} aliases * {@link Ext.ComponentManager#get Ext.ComponentManager.get}. * * Many applications are initiated with {@link Ext#onReady Ext.onReady} which is * called once the DOM is ready. This ensures all scripts have been loaded, * preventing dependency issues. For example: * * Ext.onReady(function(){ * new Ext.Component({ * renderTo: document.body, * html: 'DOM ready!' * }); * }); * * For more information about how to use the Ext classes, see: * * - The Learning Center * - The FAQ * - The forums * * @singleton */ Ext.apply(Ext, { userAgent: navigator.userAgent.toLowerCase(), cache: {}, idSeed: 1000, windowId: 'ext-window', documentId: 'ext-document', /** * True when the document is fully initialized and ready for action */ isReady: false, /** * True to automatically uncache orphaned Ext.Elements periodically */ enableGarbageCollector: true, /** * True to automatically purge event listeners during garbageCollection. */ enableListenerCollection: true, addCacheEntry: function(id, el, dom) { dom = dom || el.dom; // if (!dom) { // Without the DOM node we can't GC the entry Ext.Error.raise('Cannot add an entry to the element cache without the DOM node'); } // var key = id || (el && el.id) || dom.id, entry = Ext.cache[key] || (Ext.cache[key] = { data: {}, events: {}, dom: dom, // Skip garbage collection for special elements (window, document, iframes) skipGarbageCollection: !!(dom.getElementById || dom.navigator) }); if (el) { el.$cache = entry; // Inject the back link from the cache in case the cache entry // had already been created by Ext.fly. Ext.fly creates a cache entry with no el link. entry.el = el; } return entry; }, updateCacheEntry: function(cacheItem, dom){ cacheItem.dom = dom; if (cacheItem.el) { cacheItem.el.dom = dom; } return cacheItem; }, /** * Generates unique ids. If the element already has an id, it is unchanged * @param {HTMLElement/Ext.Element} [el] The element to generate an id for * @param {String} prefix (optional) Id prefix (defaults "ext-gen") * @return {String} The generated Id. */ id: function(el, prefix) { var me = this, sandboxPrefix = ''; el = Ext.getDom(el, true) || {}; if (el === document) { el.id = me.documentId; } else if (el === window) { el.id = me.windowId; } if (!el.id) { if (me.isSandboxed) { sandboxPrefix = Ext.sandboxName.toLowerCase() + '-'; } el.id = sandboxPrefix + (prefix || "ext-gen") + (++Ext.idSeed); } return el.id; }, escapeId: (function(){ var validIdRe = /^[a-zA-Z_][a-zA-Z0-9_\-]*$/i, escapeRx = /([\W]{1})/g, leadingNumRx = /^(\d)/g, escapeFn = function(match, capture){ return "\\" + capture; }, numEscapeFn = function(match, capture){ return '\\00' + capture.charCodeAt(0).toString(16) + ' '; }; return function(id) { return validIdRe.test(id) ? id // replace the number portion last to keep the trailing ' ' // from being escaped : id.replace(escapeRx, escapeFn) .replace(leadingNumRx, numEscapeFn); }; }()), /** * Returns the current document body as an {@link Ext.Element}. * @return Ext.Element The document body */ getBody: (function() { var body; return function() { return body || (body = Ext.get(document.body)); }; }()), /** * Returns the current document head as an {@link Ext.Element}. * @return Ext.Element The document head * @method */ getHead: (function() { var head; return function() { return head || (head = Ext.get(document.getElementsByTagName("head")[0])); }; }()), /** * Returns the current HTML document object as an {@link Ext.Element}. * @return Ext.Element The document */ getDoc: (function() { var doc; return function() { return doc || (doc = Ext.get(document)); }; }()), /** * This is shorthand reference to {@link Ext.ComponentManager#get}. * Looks up an existing {@link Ext.Component Component} by {@link Ext.Component#id id} * * @param {String} id The component {@link Ext.Component#id id} * @return Ext.Component The Component, `undefined` if not found, or `null` if a * Class was found. */ getCmp: function(id) { return Ext.ComponentManager.get(id); }, /** * Returns the current orientation of the mobile device * @return {String} Either 'portrait' or 'landscape' */ getOrientation: function() { return window.innerHeight > window.innerWidth ? 'portrait' : 'landscape'; }, /** * Attempts to destroy any objects passed to it by removing all event listeners, removing them from the * DOM (if applicable) and calling their destroy functions (if available). This method is primarily * intended for arguments of type {@link Ext.Element} and {@link Ext.Component}, but any subclass of * {@link Ext.util.Observable} can be passed in. Any number of elements and/or components can be * passed into this function in a single call as separate arguments. * * @param {Ext.Element/Ext.Component/Ext.Element[]/Ext.Component[]...} args * An {@link Ext.Element}, {@link Ext.Component}, or an Array of either of these to destroy */ destroy: function() { var ln = arguments.length, i, arg; for (i = 0; i < ln; i++) { arg = arguments[i]; if (arg) { if (Ext.isArray(arg)) { this.destroy.apply(this, arg); } else if (Ext.isFunction(arg.destroy)) { arg.destroy(); } else if (arg.dom) { arg.remove(); } } } }, /** * Execute a callback function in a particular scope. If no function is passed the call is ignored. * * For example, these lines are equivalent: * * Ext.callback(myFunc, this, [arg1, arg2]); * Ext.isFunction(myFunc) && myFunc.apply(this, [arg1, arg2]); * * @param {Function} callback The callback to execute * @param {Object} [scope] The scope to execute in * @param {Array} [args] The arguments to pass to the function * @param {Number} [delay] Pass a number to delay the call by a number of milliseconds. */ callback: function(callback, scope, args, delay){ if(Ext.isFunction(callback)){ args = args || []; scope = scope || window; if (delay) { Ext.defer(callback, delay, scope, args); } else { callback.apply(scope, args); } } }, /** * Alias for {@link Ext.String#htmlEncode}. * @inheritdoc Ext.String#htmlEncode * @ignore */ htmlEncode : function(value) { return Ext.String.htmlEncode(value); }, /** * Alias for {@link Ext.String#htmlDecode}. * @inheritdoc Ext.String#htmlDecode * @ignore */ htmlDecode : function(value) { return Ext.String.htmlDecode(value); }, /** * Alias for {@link Ext.String#urlAppend}. * @inheritdoc Ext.String#urlAppend * @ignore */ urlAppend : function(url, s) { return Ext.String.urlAppend(url, s); } }); Ext.ns = Ext.namespace; // for old browsers window.undefined = window.undefined; /** * @class Ext */ (function(){ /* FF 3.6 - Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.17) Gecko/20110420 Firefox/3.6.17 FF 4.0.1 - Mozilla/5.0 (Windows NT 5.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1 FF 5.0 - Mozilla/5.0 (Windows NT 6.1; WOW64; rv:5.0) Gecko/20100101 Firefox/5.0 IE6 - Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1;) IE7 - Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; SV1;) IE8 - Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0) IE9 - Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E) Chrome 11 - Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.60 Safari/534.24 Safari 5 - Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5 Safari/533.21.1 Opera 11.11 - Opera/9.80 (Windows NT 6.1; U; en) Presto/2.8.131 Version/11.11 */ var check = function(regex){ return regex.test(Ext.userAgent); }, isStrict = document.compatMode == "CSS1Compat", version = function (is, regex) { var m; return (is && (m = regex.exec(Ext.userAgent))) ? parseFloat(m[1]) : 0; }, docMode = document.documentMode, isOpera = check(/opera/), isOpera10_5 = isOpera && check(/version\/10\.5/), isChrome = check(/\bchrome\b/), isWebKit = check(/webkit/), isSafari = !isChrome && check(/safari/), isSafari2 = isSafari && check(/applewebkit\/4/), // unique to Safari 2 isSafari3 = isSafari && check(/version\/3/), isSafari4 = isSafari && check(/version\/4/), isSafari5_0 = isSafari && check(/version\/5\.0/), isSafari5 = isSafari && check(/version\/5/), isIE = !isOpera && check(/msie/), isIE7 = isIE && ((check(/msie 7/) && docMode != 8 && docMode != 9) || docMode == 7), isIE8 = isIE && ((check(/msie 8/) && docMode != 7 && docMode != 9) || docMode == 8), isIE9 = isIE && ((check(/msie 9/) && docMode != 7 && docMode != 8) || docMode == 9), isIE6 = isIE && check(/msie 6/), isGecko = !isWebKit && check(/gecko/), isGecko3 = isGecko && check(/rv:1\.9/), isGecko4 = isGecko && check(/rv:2\.0/), isGecko5 = isGecko && check(/rv:5\./), isGecko10 = isGecko && check(/rv:10\./), isFF3_0 = isGecko3 && check(/rv:1\.9\.0/), isFF3_5 = isGecko3 && check(/rv:1\.9\.1/), isFF3_6 = isGecko3 && check(/rv:1\.9\.2/), isWindows = check(/windows|win32/), isMac = check(/macintosh|mac os x/), isLinux = check(/linux/), scrollbarSize = null, chromeVersion = version(true, /\bchrome\/(\d+\.\d+)/), firefoxVersion = version(true, /\bfirefox\/(\d+\.\d+)/), ieVersion = version(isIE, /msie (\d+\.\d+)/), operaVersion = version(isOpera, /version\/(\d+\.\d+)/), safariVersion = version(isSafari, /version\/(\d+\.\d+)/), webKitVersion = version(isWebKit, /webkit\/(\d+\.\d+)/), isSecure = /^https/i.test(window.location.protocol), nullLog; // remove css image flicker try { document.execCommand("BackgroundImageCache", false, true); } catch(e) {} // var primitiveRe = /string|number|boolean/; function dumpObject (object) { var member, type, value, name, members = []; // Cannot use Ext.encode since it can recurse endlessly (if we're lucky) // ...and the data could be prettier! for (name in object) { if (object.hasOwnProperty(name)) { value = object[name]; type = typeof value; if (type == "function") { continue; } if (type == 'undefined') { member = type; } else if (value === null || primitiveRe.test(type) || Ext.isDate(value)) { member = Ext.encode(value); } else if (Ext.isArray(value)) { member = '[ ]'; } else if (Ext.isObject(value)) { member = '{ }'; } else { member = type; } members.push(Ext.encode(name) + ': ' + member); } } if (members.length) { return ' \nData: {\n ' + members.join(',\n ') + '\n}'; } return ''; } function log (message) { var options, dump, con = Ext.global.console, level = 'log', indent = log.indent || 0, stack, out, max; log.indent = indent; if (typeof message != 'string') { options = message; message = options.msg || ''; level = options.level || level; dump = options.dump; stack = options.stack; if (options.indent) { ++log.indent; } else if (options.outdent) { log.indent = indent = Math.max(indent - 1, 0); } if (dump && !(con && con.dir)) { message += dumpObject(dump); dump = null; } } if (arguments.length > 1) { message += Array.prototype.slice.call(arguments, 1).join(''); } message = indent ? Ext.String.repeat(' ', log.indentSize * indent) + message : message; // w/o console, all messages are equal, so munge the level into the message: if (level != 'log') { message = '[' + level.charAt(0).toUpperCase() + '] ' + message; } // Not obvious, but 'console' comes and goes when Firebug is turned on/off, so // an early test may fail either direction if Firebug is toggled. // if (con) { // if (Firebug-like console) if (con[level]) { con[level](message); } else { con.log(message); } if (dump) { con.dir(dump); } if (stack && con.trace) { // Firebug's console.error() includes a trace already... if (!con.firebug || level != 'error') { con.trace(); } } } else { if (Ext.isOpera) { opera.postError(message); } else { out = log.out; max = log.max; if (out.length >= max) { // this formula allows out.max to change (via debugger), where the // more obvious "max/4" would not quite be the same Ext.Array.erase(out, 0, out.length - 3 * Math.floor(max / 4)); // keep newest 75% } out.push(message); } } // Mostly informational, but the Ext.Error notifier uses them: ++log.count; ++log.counters[level]; } function logx (level, args) { if (typeof args[0] == 'string') { args.unshift({}); } args[0].level = level; log.apply(this, args); } log.error = function () { logx('error', Array.prototype.slice.call(arguments)); }; log.info = function () { logx('info', Array.prototype.slice.call(arguments)); }; log.warn = function () { logx('warn', Array.prototype.slice.call(arguments)); }; log.count = 0; log.counters = { error: 0, warn: 0, info: 0, log: 0 }; log.indentSize = 2; log.out = []; log.max = 750; log.show = function () { window.open('','extlog').document.write([ ''].join('')); }; // nullLog = function () {}; nullLog.info = nullLog.warn = nullLog.error = Ext.emptyFn; Ext.setVersion('extjs', '4.1.1'); Ext.apply(Ext, { /** * @property {String} SSL_SECURE_URL * URL to a blank file used by Ext when in secure mode for iframe src and onReady src * to prevent the IE insecure content warning (`'about:blank'`, except for IE * in secure mode, which is `'javascript:""'`). */ SSL_SECURE_URL : isSecure && isIE ? 'javascript:\'\'' : 'about:blank', /** * @property {Boolean} enableFx * True if the {@link Ext.fx.Anim} Class is available. */ /** * @property {Boolean} scopeResetCSS * True to scope the reset CSS to be just applied to Ext components. Note that this * wraps root containers with an additional element. Also remember that when you turn * on this option, you have to use ext-all-scoped (unless you use the bootstrap.js to * load your javascript, in which case it will be handled for you). */ scopeResetCSS : Ext.buildSettings.scopeResetCSS, /** * @property {String} resetCls * The css class used to wrap Ext components when the {@link #scopeResetCSS} option * is used. */ resetCls: Ext.buildSettings.baseCSSPrefix + 'reset', /** * @property {Boolean} enableNestedListenerRemoval * **Experimental.** True to cascade listener removal to child elements when an element * is removed. Currently not optimized for performance. */ enableNestedListenerRemoval : false, /** * @property {Boolean} USE_NATIVE_JSON * Indicates whether to use native browser parsing for JSON methods. * This option is ignored if the browser does not support native JSON methods. * * **Note:** Native JSON methods will not work with objects that have functions. * Also, property names must be quoted, otherwise the data will not parse. */ USE_NATIVE_JSON : false, /** * Returns the dom node for the passed String (id), dom node, or Ext.Element. * Optional 'strict' flag is needed for IE since it can return 'name' and * 'id' elements by using getElementById. * * Here are some examples: * * // gets dom node based on id * var elDom = Ext.getDom('elId'); * // gets dom node based on the dom node * var elDom1 = Ext.getDom(elDom); * * // If we don't know if we are working with an * // Ext.Element or a dom node use Ext.getDom * function(el){ * var dom = Ext.getDom(el); * // do something with the dom node * } * * **Note:** the dom node to be found actually needs to exist (be rendered, etc) * when this method is called to be successful. * * @param {String/HTMLElement/Ext.Element} el * @return HTMLElement */ getDom : function(el, strict) { if (!el || !document) { return null; } if (el.dom) { return el.dom; } else { if (typeof el == 'string') { var e = Ext.getElementById(el); // IE returns elements with the 'name' and 'id' attribute. // we do a strict check to return the element with only the id attribute if (e && isIE && strict) { if (el == e.getAttribute('id')) { return e; } else { return null; } } return e; } else { return el; } } }, /** * Removes a DOM node from the document. * * Removes this element from the document, removes all DOM event listeners, and * deletes the cache reference. All DOM event listeners are removed from this element. * If {@link Ext#enableNestedListenerRemoval Ext.enableNestedListenerRemoval} is * `true`, then DOM event listeners are also removed from all child nodes. * The body node will be ignored if passed in. * * @param {HTMLElement} node The node to remove * @method */ removeNode : isIE6 || isIE7 || isIE8 ? (function() { var d; return function(n){ if(n && n.tagName.toUpperCase() != 'BODY'){ (Ext.enableNestedListenerRemoval) ? Ext.EventManager.purgeElement(n) : Ext.EventManager.removeAll(n); var cache = Ext.cache, id = n.id; if (cache[id]) { delete cache[id].dom; delete cache[id]; } if (isIE8 && n.parentNode) { n.parentNode.removeChild(n); } d = d || document.createElement('div'); d.appendChild(n); d.innerHTML = ''; } }; }()) : function(n) { if (n && n.parentNode && n.tagName.toUpperCase() != 'BODY') { (Ext.enableNestedListenerRemoval) ? Ext.EventManager.purgeElement(n) : Ext.EventManager.removeAll(n); var cache = Ext.cache, id = n.id; if (cache[id]) { delete cache[id].dom; delete cache[id]; } n.parentNode.removeChild(n); } }, isStrict: isStrict, isIEQuirks: isIE && !isStrict, /** * True if the detected browser is Opera. * @type Boolean */ isOpera : isOpera, /** * True if the detected browser is Opera 10.5x. * @type Boolean */ isOpera10_5 : isOpera10_5, /** * True if the detected browser uses WebKit. * @type Boolean */ isWebKit : isWebKit, /** * True if the detected browser is Chrome. * @type Boolean */ isChrome : isChrome, /** * True if the detected browser is Safari. * @type Boolean */ isSafari : isSafari, /** * True if the detected browser is Safari 3.x. * @type Boolean */ isSafari3 : isSafari3, /** * True if the detected browser is Safari 4.x. * @type Boolean */ isSafari4 : isSafari4, /** * True if the detected browser is Safari 5.x. * @type Boolean */ isSafari5 : isSafari5, /** * True if the detected browser is Safari 5.0.x. * @type Boolean */ isSafari5_0 : isSafari5_0, /** * True if the detected browser is Safari 2.x. * @type Boolean */ isSafari2 : isSafari2, /** * True if the detected browser is Internet Explorer. * @type Boolean */ isIE : isIE, /** * True if the detected browser is Internet Explorer 6.x. * @type Boolean */ isIE6 : isIE6, /** * True if the detected browser is Internet Explorer 7.x. * @type Boolean */ isIE7 : isIE7, /** * True if the detected browser is Internet Explorer 8.x. * @type Boolean */ isIE8 : isIE8, /** * True if the detected browser is Internet Explorer 9.x. * @type Boolean */ isIE9 : isIE9, /** * True if the detected browser uses the Gecko layout engine (e.g. Mozilla, Firefox). * @type Boolean */ isGecko : isGecko, /** * True if the detected browser uses a Gecko 1.9+ layout engine (e.g. Firefox 3.x). * @type Boolean */ isGecko3 : isGecko3, /** * True if the detected browser uses a Gecko 2.0+ layout engine (e.g. Firefox 4.x). * @type Boolean */ isGecko4 : isGecko4, /** * True if the detected browser uses a Gecko 5.0+ layout engine (e.g. Firefox 5.x). * @type Boolean */ isGecko5 : isGecko5, /** * True if the detected browser uses a Gecko 5.0+ layout engine (e.g. Firefox 5.x). * @type Boolean */ isGecko10 : isGecko10, /** * True if the detected browser uses FireFox 3.0 * @type Boolean */ isFF3_0 : isFF3_0, /** * True if the detected browser uses FireFox 3.5 * @type Boolean */ isFF3_5 : isFF3_5, /** * True if the detected browser uses FireFox 3.6 * @type Boolean */ isFF3_6 : isFF3_6, /** * True if the detected browser uses FireFox 4 * @type Boolean */ isFF4 : 4 <= firefoxVersion && firefoxVersion < 5, /** * True if the detected browser uses FireFox 5 * @type Boolean */ isFF5 : 5 <= firefoxVersion && firefoxVersion < 6, /** * True if the detected browser uses FireFox 10 * @type Boolean */ isFF10 : 10 <= firefoxVersion && firefoxVersion < 11, /** * True if the detected platform is Linux. * @type Boolean */ isLinux : isLinux, /** * True if the detected platform is Windows. * @type Boolean */ isWindows : isWindows, /** * True if the detected platform is Mac OS. * @type Boolean */ isMac : isMac, /** * The current version of Chrome (0 if the browser is not Chrome). * @type Number */ chromeVersion: chromeVersion, /** * The current version of Firefox (0 if the browser is not Firefox). * @type Number */ firefoxVersion: firefoxVersion, /** * The current version of IE (0 if the browser is not IE). This does not account * for the documentMode of the current page, which is factored into {@link #isIE7}, * {@link #isIE8} and {@link #isIE9}. Thus this is not always true: * * Ext.isIE8 == (Ext.ieVersion == 8) * * @type Number */ ieVersion: ieVersion, /** * The current version of Opera (0 if the browser is not Opera). * @type Number */ operaVersion: operaVersion, /** * The current version of Safari (0 if the browser is not Safari). * @type Number */ safariVersion: safariVersion, /** * The current version of WebKit (0 if the browser does not use WebKit). * @type Number */ webKitVersion: webKitVersion, /** * True if the page is running over SSL * @type Boolean */ isSecure: isSecure, /** * URL to a 1x1 transparent gif image used by Ext to create inline icons with * CSS background images. In older versions of IE, this defaults to * "http://sencha.com/s.gif" and you should change this to a URL on your server. * For other browsers it uses an inline data URL. * @type String */ BLANK_IMAGE_URL : (isIE6 || isIE7) ? '/' + '/www.sencha.com/s.gif' : 'data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==', /** * Utility method for returning a default value if the passed value is empty. * * The value is deemed to be empty if it is: * * - null * - undefined * - an empty array * - a zero length string (Unless the `allowBlank` parameter is `true`) * * @param {Object} value The value to test * @param {Object} defaultValue The value to return if the original value is empty * @param {Boolean} [allowBlank=false] true to allow zero length strings to qualify as non-empty. * @return {Object} value, if non-empty, else defaultValue * @deprecated 4.0.0 Use {@link Ext#valueFrom} instead */ value : function(v, defaultValue, allowBlank){ return Ext.isEmpty(v, allowBlank) ? defaultValue : v; }, /** * Escapes the passed string for use in a regular expression. * @param {String} str * @return {String} * @deprecated 4.0.0 Use {@link Ext.String#escapeRegex} instead */ escapeRe : function(s) { return s.replace(/([-.*+?\^${}()|\[\]\/\\])/g, "\\$1"); }, /** * Applies event listeners to elements by selectors when the document is ready. * The event name is specified with an `@` suffix. * * Ext.addBehaviors({ * // add a listener for click on all anchors in element with id foo * '#foo a@click' : function(e, t){ * // do something * }, * * // add the same listener to multiple selectors (separated by comma BEFORE the @) * '#foo a, #bar span.some-class@mouseover' : function(){ * // do something * } * }); * * @param {Object} obj The list of behaviors to apply */ addBehaviors : function(o){ if(!Ext.isReady){ Ext.onReady(function(){ Ext.addBehaviors(o); }); } else { var cache = {}, // simple cache for applying multiple behaviors to same selector does query multiple times parts, b, s; for (b in o) { if ((parts = b.split('@'))[1]) { // for Object prototype breakers s = parts[0]; if(!cache[s]){ cache[s] = Ext.select(s); } cache[s].on(parts[1], o[b]); } } cache = null; } }, /** * Returns the size of the browser scrollbars. This can differ depending on * operating system settings, such as the theme or font size. * @param {Boolean} [force] true to force a recalculation of the value. * @return {Object} An object containing scrollbar sizes. * @return.width {Number} The width of the vertical scrollbar. * @return.height {Number} The height of the horizontal scrollbar. */ getScrollbarSize: function (force) { if (!Ext.isReady) { return {}; } if (force || !scrollbarSize) { var db = document.body, div = document.createElement('div'); div.style.width = div.style.height = '100px'; div.style.overflow = 'scroll'; div.style.position = 'absolute'; db.appendChild(div); // now we can measure the div... // at least in iE9 the div is not 100px - the scrollbar size is removed! scrollbarSize = { width: div.offsetWidth - div.clientWidth, height: div.offsetHeight - div.clientHeight }; db.removeChild(div); } return scrollbarSize; }, /** * Utility method for getting the width of the browser's vertical scrollbar. This * can differ depending on operating system settings, such as the theme or font size. * * This method is deprected in favor of {@link #getScrollbarSize}. * * @param {Boolean} [force] true to force a recalculation of the value. * @return {Number} The width of a vertical scrollbar. * @deprecated */ getScrollBarWidth: function(force){ var size = Ext.getScrollbarSize(force); return size.width + 2; // legacy fudge factor }, /** * Copies a set of named properties fom the source object to the destination object. * * Example: * * ImageComponent = Ext.extend(Ext.Component, { * initComponent: function() { * this.autoEl = { tag: 'img' }; * MyComponent.superclass.initComponent.apply(this, arguments); * this.initialBox = Ext.copyTo({}, this.initialConfig, 'x,y,width,height'); * } * }); * * Important note: To borrow class prototype methods, use {@link Ext.Base#borrow} instead. * * @param {Object} dest The destination object. * @param {Object} source The source object. * @param {String/String[]} names Either an Array of property names, or a comma-delimited list * of property names to copy. * @param {Boolean} [usePrototypeKeys] Defaults to false. Pass true to copy keys off of the * prototype as well as the instance. * @return {Object} The modified object. */ copyTo : function(dest, source, names, usePrototypeKeys){ if(typeof names == 'string'){ names = names.split(/[,;\s]/); } var n, nLen = names.length, name; for(n = 0; n < nLen; n++) { name = names[n]; if(usePrototypeKeys || source.hasOwnProperty(name)){ dest[name] = source[name]; } } return dest; }, /** * Attempts to destroy and then remove a set of named properties of the passed object. * @param {Object} o The object (most likely a Component) who's properties you wish to destroy. * @param {String...} args One or more names of the properties to destroy and remove from the object. */ destroyMembers : function(o){ for (var i = 1, a = arguments, len = a.length; i < len; i++) { Ext.destroy(o[a[i]]); delete o[a[i]]; } }, /** * Logs a message. If a console is present it will be used. On Opera, the method * "opera.postError" is called. In other cases, the message is logged to an array * "Ext.log.out". An attached debugger can watch this array and view the log. The * log buffer is limited to a maximum of "Ext.log.max" entries (defaults to 250). * The `Ext.log.out` array can also be written to a popup window by entering the * following in the URL bar (a "bookmarklet"): * * javascript:void(Ext.log.show()); * * If additional parameters are passed, they are joined and appended to the message. * A technique for tracing entry and exit of a function is this: * * function foo () { * Ext.log({ indent: 1 }, '>> foo'); * * // log statements in here or methods called from here will be indented * // by one step * * Ext.log({ outdent: 1 }, '<< foo'); * } * * This method does nothing in a release build. * * @param {String/Object} [options] The message to log or an options object with any * of the following properties: * * - `msg`: The message to log (required). * - `level`: One of: "error", "warn", "info" or "log" (the default is "log"). * - `dump`: An object to dump to the log as part of the message. * - `stack`: True to include a stack trace in the log. * - `indent`: Cause subsequent log statements to be indented one step. * - `outdent`: Cause this and following statements to be one step less indented. * * @param {String...} [message] The message to log (required unless specified in * options object). * * @method */ log : // log || // nullLog, /** * Partitions the set into two sets: a true set and a false set. * * Example 1: * * Ext.partition([true, false, true, true, false]); * // returns [[true, true, true], [false, false]] * * Example 2: * * Ext.partition( * Ext.query("p"), * function(val){ * return val.className == "class1" * } * ); * // true are those paragraph elements with a className of "class1", * // false set are those that do not have that className. * * @param {Array/NodeList} arr The array to partition * @param {Function} truth (optional) a function to determine truth. * If this is omitted the element itself must be able to be evaluated for its truthfulness. * @return {Array} [array of truish values, array of falsy values] * @deprecated 4.0.0 Will be removed in the next major version */ partition : function(arr, truth){ var ret = [[],[]], a, v, aLen = arr.length; for (a = 0; a < aLen; a++) { v = arr[a]; ret[ (truth && truth(v, a, arr)) || (!truth && v) ? 0 : 1].push(v); } return ret; }, /** * Invokes a method on each item in an Array. * * Example: * * Ext.invoke(Ext.query("p"), "getAttribute", "id"); * // [el1.getAttribute("id"), el2.getAttribute("id"), ..., elN.getAttribute("id")] * * @param {Array/NodeList} arr The Array of items to invoke the method on. * @param {String} methodName The method name to invoke. * @param {Object...} args Arguments to send into the method invocation. * @return {Array} The results of invoking the method on each item in the array. * @deprecated 4.0.0 Will be removed in the next major version */ invoke : function(arr, methodName){ var ret = [], args = Array.prototype.slice.call(arguments, 2), a, v, aLen = arr.length; for (a = 0; a < aLen; a++) { v = arr[a]; if (v && typeof v[methodName] == 'function') { ret.push(v[methodName].apply(v, args)); } else { ret.push(undefined); } } return ret; }, /** * Zips N sets together. * * Example 1: * * Ext.zip([1,2,3],[4,5,6]); // [[1,4],[2,5],[3,6]] * * Example 2: * * Ext.zip( * [ "+", "-", "+"], * [ 12, 10, 22], * [ 43, 15, 96], * function(a, b, c){ * return "$" + a + "" + b + "." + c * } * ); // ["$+12.43", "$-10.15", "$+22.96"] * * @param {Array/NodeList...} arr This argument may be repeated. Array(s) * to contribute values. * @param {Function} zipper (optional) The last item in the argument list. * This will drive how the items are zipped together. * @return {Array} The zipped set. * @deprecated 4.0.0 Will be removed in the next major version */ zip : function(){ var parts = Ext.partition(arguments, function( val ){ return typeof val != 'function'; }), arrs = parts[0], fn = parts[1][0], len = Ext.max(Ext.pluck(arrs, "length")), ret = [], i, j, aLen; for (i = 0; i < len; i++) { ret[i] = []; if(fn){ ret[i] = fn.apply(fn, Ext.pluck(arrs, i)); }else{ for (j = 0, aLen = arrs.length; j < aLen; j++){ ret[i].push( arrs[j][i] ); } } } return ret; }, /** * Turns an array into a sentence, joined by a specified connector - e.g.: * * Ext.toSentence(['Adama', 'Tigh', 'Roslin']); //'Adama, Tigh and Roslin' * Ext.toSentence(['Adama', 'Tigh', 'Roslin'], 'or'); //'Adama, Tigh or Roslin' * * @param {String[]} items The array to create a sentence from * @param {String} connector The string to use to connect the last two words. * Usually 'and' or 'or' - defaults to 'and'. * @return {String} The sentence string * @deprecated 4.0.0 Will be removed in the next major version */ toSentence: function(items, connector) { var length = items.length, head, tail; if (length <= 1) { return items[0]; } else { head = items.slice(0, length - 1); tail = items[length - 1]; return Ext.util.Format.format("{0} {1} {2}", head.join(", "), connector || 'and', tail); } }, /** * @property {Boolean} useShims * By default, Ext intelligently decides whether floating elements should be shimmed. * If you are using flash, you may want to set this to true. */ useShims: isIE6 }); }()); /** * Loads Ext.app.Application class and starts it up with given configuration after the page is ready. * * See Ext.app.Application for details. * * @param {Object} config */ Ext.application = function(config) { Ext.require('Ext.app.Application'); Ext.onReady(function() { new Ext.app.Application(config); }); }; /** * @class Ext.util.Format * * This class is a centralized place for formatting functions. It includes * functions to format various different types of data, such as text, dates and numeric values. * * ## Localization * * This class contains several options for localization. These can be set once the library has loaded, * all calls to the functions from that point will use the locale settings that were specified. * * Options include: * * - thousandSeparator * - decimalSeparator * - currenyPrecision * - currencySign * - currencyAtEnd * * This class also uses the default date format defined here: {@link Ext.Date#defaultFormat}. * * ## Using with renderers * * There are two helper functions that return a new function that can be used in conjunction with * grid renderers: * * columns: [{ * dataIndex: 'date', * renderer: Ext.util.Format.dateRenderer('Y-m-d') * }, { * dataIndex: 'time', * renderer: Ext.util.Format.numberRenderer('0.000') * }] * * Functions that only take a single argument can also be passed directly: * * columns: [{ * dataIndex: 'cost', * renderer: Ext.util.Format.usMoney * }, { * dataIndex: 'productCode', * renderer: Ext.util.Format.uppercase * }] * * ## Using with XTemplates * * XTemplates can also directly use Ext.util.Format functions: * * new Ext.XTemplate([ * 'Date: {startDate:date("Y-m-d")}', * 'Cost: {cost:usMoney}' * ]); * * @singleton */ (function() { Ext.ns('Ext.util'); Ext.util.Format = {}; var UtilFormat = Ext.util.Format, stripTagsRE = /<\/?[^>]+>/gi, stripScriptsRe = /(?:)((\n|\r|.)*?)(?:<\/script>)/ig, nl2brRe = /\r?\n/g, // A RegExp to remove from a number format string, all characters except digits and '.' formatCleanRe = /[^\d\.]/g, // A RegExp to remove from a number format string, all characters except digits and the local decimal separator. // Created on first use. The local decimal separator character must be initialized for this to be created. I18NFormatCleanRe; Ext.apply(UtilFormat, { // /** * @property {String} thousandSeparator * The character that the {@link #number} function uses as a thousand separator. * * This may be overridden in a locale file. */ thousandSeparator: ',', // // /** * @property {String} decimalSeparator * The character that the {@link #number} function uses as a decimal point. * * This may be overridden in a locale file. */ decimalSeparator: '.', // // /** * @property {Number} currencyPrecision * The number of decimal places that the {@link #currency} function displays. * * This may be overridden in a locale file. */ currencyPrecision: 2, // // /** * @property {String} currencySign * The currency sign that the {@link #currency} function displays. * * This may be overridden in a locale file. */ currencySign: '$', // // /** * @property {Boolean} currencyAtEnd * This may be set to true to make the {@link #currency} function * append the currency sign to the formatted value. * * This may be overridden in a locale file. */ currencyAtEnd: false, // /** * Checks a reference and converts it to empty string if it is undefined. * @param {Object} value Reference to check * @return {Object} Empty string if converted, otherwise the original value */ undef : function(value) { return value !== undefined ? value : ""; }, /** * Checks a reference and converts it to the default value if it's empty. * @param {Object} value Reference to check * @param {String} [defaultValue=""] The value to insert of it's undefined. * @return {String} */ defaultValue : function(value, defaultValue) { return value !== undefined && value !== '' ? value : defaultValue; }, /** * Returns a substring from within an original string. * @param {String} value The original text * @param {Number} start The start index of the substring * @param {Number} length The length of the substring * @return {String} The substring * @method */ substr : 'ab'.substr(-1) != 'b' ? function (value, start, length) { var str = String(value); return (start < 0) ? str.substr(Math.max(str.length + start, 0), length) : str.substr(start, length); } : function(value, start, length) { return String(value).substr(start, length); }, /** * Converts a string to all lower case letters. * @param {String} value The text to convert * @return {String} The converted text */ lowercase : function(value) { return String(value).toLowerCase(); }, /** * Converts a string to all upper case letters. * @param {String} value The text to convert * @return {String} The converted text */ uppercase : function(value) { return String(value).toUpperCase(); }, /** * Format a number as US currency. * @param {Number/String} value The numeric value to format * @return {String} The formatted currency string */ usMoney : function(v) { return UtilFormat.currency(v, '$', 2); }, /** * Format a number as a currency. * @param {Number/String} value The numeric value to format * @param {String} [sign] The currency sign to use (defaults to {@link #currencySign}) * @param {Number} [decimals] The number of decimals to use for the currency * (defaults to {@link #currencyPrecision}) * @param {Boolean} [end] True if the currency sign should be at the end of the string * (defaults to {@link #currencyAtEnd}) * @return {String} The formatted currency string */ currency: function(v, currencySign, decimals, end) { var negativeSign = '', format = ",0", i = 0; v = v - 0; if (v < 0) { v = -v; negativeSign = '-'; } decimals = Ext.isDefined(decimals) ? decimals : UtilFormat.currencyPrecision; format += format + (decimals > 0 ? '.' : ''); for (; i < decimals; i++) { format += '0'; } v = UtilFormat.number(v, format); if ((end || UtilFormat.currencyAtEnd) === true) { return Ext.String.format("{0}{1}{2}", negativeSign, v, currencySign || UtilFormat.currencySign); } else { return Ext.String.format("{0}{1}{2}", negativeSign, currencySign || UtilFormat.currencySign, v); } }, /** * Formats the passed date using the specified format pattern. * @param {String/Date} value The value to format. If a string is passed, it is converted to a Date * by the Javascript's built-in Date#parse method. * @param {String} [format] Any valid date format string. Defaults to {@link Ext.Date#defaultFormat}. * @return {String} The formatted date string. */ date: function(v, format) { if (!v) { return ""; } if (!Ext.isDate(v)) { v = new Date(Date.parse(v)); } return Ext.Date.dateFormat(v, format || Ext.Date.defaultFormat); }, /** * Returns a date rendering function that can be reused to apply a date format multiple times efficiently. * @param {String} format Any valid date format string. Defaults to {@link Ext.Date#defaultFormat}. * @return {Function} The date formatting function */ dateRenderer : function(format) { return function(v) { return UtilFormat.date(v, format); }; }, /** * Strips all HTML tags. * @param {Object} value The text from which to strip tags * @return {String} The stripped text */ stripTags : function(v) { return !v ? v : String(v).replace(stripTagsRE, ""); }, /** * Strips all script tags. * @param {Object} value The text from which to strip script tags * @return {String} The stripped text */ stripScripts : function(v) { return !v ? v : String(v).replace(stripScriptsRe, ""); }, /** * Simple format for a file size (xxx bytes, xxx KB, xxx MB). * @param {Number/String} size The numeric value to format * @return {String} The formatted file size */ fileSize : function(size) { if (size < 1024) { return size + " bytes"; } else if (size < 1048576) { return (Math.round(((size*10) / 1024))/10) + " KB"; } else { return (Math.round(((size*10) / 1048576))/10) + " MB"; } }, /** * It does simple math for use in a template, for example: * * var tpl = new Ext.Template('{value} * 10 = {value:math("* 10")}'); * * @return {Function} A function that operates on the passed value. * @method */ math : (function(){ var fns = {}; return function(v, a){ if (!fns[a]) { fns[a] = Ext.functionFactory('v', 'return v ' + a + ';'); } return fns[a](v); }; }()), /** * Rounds the passed number to the required decimal precision. * @param {Number/String} value The numeric value to round. * @param {Number} precision The number of decimal places to which to round the first parameter's value. * @return {Number} The rounded value. */ round : function(value, precision) { var result = Number(value); if (typeof precision == 'number') { precision = Math.pow(10, precision); result = Math.round(value * precision) / precision; } return result; }, /** * Formats the passed number according to the passed format string. * * The number of digits after the decimal separator character specifies the number of * decimal places in the resulting string. The *local-specific* decimal character is * used in the result. * * The *presence* of a thousand separator character in the format string specifies that * the *locale-specific* thousand separator (if any) is inserted separating thousand groups. * * By default, "," is expected as the thousand separator, and "." is expected as the decimal separator. * * ## New to Ext JS 4 * * Locale-specific characters are always used in the formatted output when inserting * thousand and decimal separators. * * The format string must specify separator characters according to US/UK conventions ("," as the * thousand separator, and "." as the decimal separator) * * To allow specification of format strings according to local conventions for separator characters, add * the string `/i` to the end of the format string. * * examples (123456.789): * * - `0` - (123456) show only digits, no precision * - `0.00` - (123456.78) show only digits, 2 precision * - `0.0000` - (123456.7890) show only digits, 4 precision * - `0,000` - (123,456) show comma and digits, no precision * - `0,000.00` - (123,456.78) show comma and digits, 2 precision * - `0,0.00` - (123,456.78) shortcut method, show comma and digits, 2 precision * * To allow specification of the formatting string using UK/US grouping characters (,) and * decimal (.) for international numbers, add /i to the end. For example: 0.000,00/i * * @param {Number} v The number to format. * @param {String} format The way you would like to format this text. * @return {String} The formatted number. */ number : function(v, formatString) { if (!formatString) { return v; } v = Ext.Number.from(v, NaN); if (isNaN(v)) { return ''; } var comma = UtilFormat.thousandSeparator, dec = UtilFormat.decimalSeparator, i18n = false, neg = v < 0, hasComma, psplit, fnum, cnum, parr, j, m, n, i; v = Math.abs(v); // The "/i" suffix allows caller to use a locale-specific formatting string. // Clean the format string by removing all but numerals and the decimal separator. // Then split the format string into pre and post decimal segments according to *what* the // decimal separator is. If they are specifying "/i", they are using the local convention in the format string. if (formatString.substr(formatString.length - 2) == '/i') { if (!I18NFormatCleanRe) { I18NFormatCleanRe = new RegExp('[^\\d\\' + UtilFormat.decimalSeparator + ']','g'); } formatString = formatString.substr(0, formatString.length - 2); i18n = true; hasComma = formatString.indexOf(comma) != -1; psplit = formatString.replace(I18NFormatCleanRe, '').split(dec); } else { hasComma = formatString.indexOf(',') != -1; psplit = formatString.replace(formatCleanRe, '').split('.'); } if (psplit.length > 2) { // Ext.Error.raise({ sourceClass: "Ext.util.Format", sourceMethod: "number", value: v, formatString: formatString, msg: "Invalid number format, should have no more than 1 decimal" }); // } else if (psplit.length > 1) { v = Ext.Number.toFixed(v, psplit[1].length); } else { v = Ext.Number.toFixed(v, 0); } fnum = v.toString(); psplit = fnum.split('.'); if (hasComma) { cnum = psplit[0]; parr = []; j = cnum.length; m = Math.floor(j / 3); n = cnum.length % 3 || 3; for (i = 0; i < j; i += n) { if (i !== 0) { n = 3; } parr[parr.length] = cnum.substr(i, n); m -= 1; } fnum = parr.join(comma); if (psplit[1]) { fnum += dec + psplit[1]; } } else { if (psplit[1]) { fnum = psplit[0] + dec + psplit[1]; } } if (neg) { /* * Edge case. If we have a very small negative number it will get rounded to 0, * however the initial check at the top will still report as negative. Replace * everything but 1-9 and check if the string is empty to determine a 0 value. */ neg = fnum.replace(/[^1-9]/g, '') !== ''; } return (neg ? '-' : '') + formatString.replace(/[\d,?\.?]+/, fnum); }, /** * Returns a number rendering function that can be reused to apply a number format multiple * times efficiently. * * @param {String} format Any valid number format string for {@link #number} * @return {Function} The number formatting function */ numberRenderer : function(format) { return function(v) { return UtilFormat.number(v, format); }; }, /** * Selectively do a plural form of a word based on a numeric value. For example, in a template, * `{commentCount:plural("Comment")}` would result in `"1 Comment"` if commentCount was 1 or * would be `"x Comments"` if the value is 0 or greater than 1. * * @param {Number} value The value to compare against * @param {String} singular The singular form of the word * @param {String} [plural] The plural form of the word (defaults to the singular with an "s") */ plural : function(v, s, p) { return v +' ' + (v == 1 ? s : (p ? p : s+'s')); }, /** * Converts newline characters to the HTML tag `
` * * @param {String} The string value to format. * @return {String} The string with embedded `
` tags in place of newlines. */ nl2br : function(v) { return Ext.isEmpty(v) ? '' : v.replace(nl2brRe, '
'); }, /** * Alias for {@link Ext.String#capitalize}. * @method * @inheritdoc Ext.String#capitalize */ capitalize: Ext.String.capitalize, /** * Alias for {@link Ext.String#ellipsis}. * @method * @inheritdoc Ext.String#ellipsis */ ellipsis: Ext.String.ellipsis, /** * Alias for {@link Ext.String#format}. * @method * @inheritdoc Ext.String#format */ format: Ext.String.format, /** * Alias for {@link Ext.String#htmlDecode}. * @method * @inheritdoc Ext.String#htmlDecode */ htmlDecode: Ext.String.htmlDecode, /** * Alias for {@link Ext.String#htmlEncode}. * @method * @inheritdoc Ext.String#htmlEncode */ htmlEncode: Ext.String.htmlEncode, /** * Alias for {@link Ext.String#leftPad}. * @method * @inheritdoc Ext.String#leftPad */ leftPad: Ext.String.leftPad, /** * Alias for {@link Ext.String#trim}. * @method * @inheritdoc Ext.String#trim */ trim : Ext.String.trim, /** * Parses a number or string representing margin sizes into an object. * Supports CSS-style margin declarations (e.g. 10, "10", "10 10", "10 10 10" and * "10 10 10 10" are all valid options and would return the same result). * * @param {Number/String} v The encoded margins * @return {Object} An object with margin sizes for top, right, bottom and left */ parseBox : function(box) { box = Ext.isEmpty(box) ? '' : box; if (Ext.isNumber(box)) { box = box.toString(); } var parts = box.split(' '), ln = parts.length; if (ln == 1) { parts[1] = parts[2] = parts[3] = parts[0]; } else if (ln == 2) { parts[2] = parts[0]; parts[3] = parts[1]; } else if (ln == 3) { parts[3] = parts[1]; } return { top :parseInt(parts[0], 10) || 0, right :parseInt(parts[1], 10) || 0, bottom:parseInt(parts[2], 10) || 0, left :parseInt(parts[3], 10) || 0 }; }, /** * Escapes the passed string for use in a regular expression. * @param {String} str * @return {String} */ escapeRegex : function(s) { return s.replace(/([\-.*+?\^${}()|\[\]\/\\])/g, "\\$1"); } }); }()); /** * Provides the ability to execute one or more arbitrary tasks in a asynchronous manner. * Generally, you can use the singleton {@link Ext.TaskManager} instead, but if needed, * you can create separate instances of TaskRunner. Any number of separate tasks can be * started at any time and will run independently of each other. * * Example usage: * * // Start a simple clock task that updates a div once per second * var updateClock = function () { * Ext.fly('clock').update(new Date().format('g:i:s A')); * } * * var runner = new Ext.util.TaskRunner(); * var task = runner.start({ * run: updateClock, * interval: 1000 * } * * The equivalent using TaskManager: * * var task = Ext.TaskManager.start({ * run: updateClock, * interval: 1000 * }); * * To end a running task: * * Ext.TaskManager.stop(task); * * If a task needs to be started and stopped repeated over time, you can create a * {@link Ext.util.TaskRunner.Task Task} instance. * * var task = runner.newTask({ * run: function () { * // useful code * }, * interval: 1000 * }); * * task.start(); * * // ... * * task.stop(); * * // ... * * task.start(); * * A re-usable, one-shot task can be managed similar to the above: * * var task = runner.newTask({ * run: function () { * // useful code to run once * }, * repeat: 1 * }); * * task.start(); * * // ... * * task.start(); * * See the {@link #start} method for details about how to configure a task object. * * Also see {@link Ext.util.DelayedTask}. * * @constructor * @param {Number/Object} [interval=10] The minimum precision in milliseconds supported by this * TaskRunner instance. Alternatively, a config object to apply to the new instance. */ Ext.define('Ext.util.TaskRunner', { /** * @cfg interval * The timer resolution. */ interval: 10, /** * @property timerId * The id of the current timer. * @private */ timerId: null, constructor: function (interval) { var me = this; if (typeof interval == 'number') { me.interval = interval; } else if (interval) { Ext.apply(me, interval); } me.tasks = []; me.timerFn = Ext.Function.bind(me.onTick, me); }, /** * Creates a new {@link Ext.util.TaskRunner.Task Task} instance. These instances can * be easily started and stopped. * @param {Object} config The config object. For details on the supported properties, * see {@link #start}. */ newTask: function (config) { var task = new Ext.util.TaskRunner.Task(config); task.manager = this; return task; }, /** * Starts a new task. * * Before each invocation, Ext injects the property `taskRunCount` into the task object * so that calculations based on the repeat count can be performed. * * The returned task will contain a `destroy` method that can be used to destroy the * task and cancel further calls. This is equivalent to the {@link #stop} method. * * @param {Object} task A config object that supports the following properties: * @param {Function} task.run The function to execute each time the task is invoked. The * function will be called at each interval and passed the `args` argument if specified, * and the current invocation count if not. * * If a particular scope (`this` reference) is required, be sure to specify it using * the `scope` argument. * * @param {Function} task.onError The function to execute in case of unhandled * error on task.run. * * @param {Boolean} task.run.return `false` from this function to terminate the task. * * @param {Number} task.interval The frequency in milliseconds with which the task * should be invoked. * * @param {Object[]} task.args An array of arguments to be passed to the function * specified by `run`. If not specified, the current invocation count is passed. * * @param {Object} task.scope The scope (`this` reference) in which to execute the * `run` function. Defaults to the task config object. * * @param {Number} task.duration The length of time in milliseconds to invoke the task * before stopping automatically (defaults to indefinite). * * @param {Number} task.repeat The number of times to invoke the task before stopping * automatically (defaults to indefinite). * @return {Object} The task */ start: function(task) { var me = this, now = new Date().getTime(); if (!task.pending) { me.tasks.push(task); task.pending = true; // don't allow the task to be added to me.tasks again } task.stopped = false; // might have been previously stopped... task.taskStartTime = now; task.taskRunTime = task.fireOnStart !== false ? 0 : task.taskStartTime; task.taskRunCount = 0; if (!me.firing) { if (task.fireOnStart !== false) { me.startTimer(0, now); } else { me.startTimer(task.interval, now); } } return task; }, /** * Stops an existing running task. * @param {Object} task The task to stop * @return {Object} The task */ stop: function(task) { // NOTE: we don't attempt to remove the task from me.tasks at this point because // this could be called from inside a task which would then corrupt the state of // the loop in onTick if (!task.stopped) { task.stopped = true; if (task.onStop) { task.onStop.call(task.scope || task, task); } } return task; }, /** * Stops all tasks that are currently running. */ stopAll: function() { // onTick will take care of cleaning up the mess after this point... Ext.each(this.tasks, this.stop, this); }, //------------------------------------------------------------------------- firing: false, nextExpires: 1e99, // private onTick: function () { var me = this, tasks = me.tasks, now = new Date().getTime(), nextExpires = 1e99, len = tasks.length, expires, newTasks, i, task, rt, remove; me.timerId = null; me.firing = true; // ensure we don't startTimer during this loop... // tasks.length can be > len if start is called during a task.run call... so we // first check len to avoid tasks.length reference but eventually we need to also // check tasks.length. we avoid repeating use of tasks.length by setting len at // that time (to help the next loop) for (i = 0; i < len || i < (len = tasks.length); ++i) { task = tasks[i]; if (!(remove = task.stopped)) { expires = task.taskRunTime + task.interval; if (expires <= now) { rt = 1; // otherwise we have a stale "rt" try { rt = task.run.apply(task.scope || task, task.args || [++task.taskRunCount]); } catch (taskError) { try { if (task.onError) { rt = task.onError.call(task.scope || task, task, taskError); } } catch (ignore) { } } task.taskRunTime = now; if (rt === false || task.taskRunCount === task.repeat) { me.stop(task); remove = true; } else { remove = task.stopped; // in case stop was called by run expires = now + task.interval; } } if (!remove && task.duration && task.duration <= (now - task.taskStartTime)) { me.stop(task); remove = true; } } if (remove) { task.pending = false; // allow the task to be added to me.tasks again // once we detect that a task needs to be removed, we copy the tasks that // will carry forward into newTasks... this way we avoid O(N*N) to remove // each task from the tasks array (and ripple the array down) and also the // potentially wasted effort of making a new tasks[] even if all tasks are // going into the next wave. if (!newTasks) { newTasks = tasks.slice(0, i); // we don't set me.tasks here because callbacks can also start tasks, // which get added to me.tasks... so we will visit them in this loop // and account for their expirations in nextExpires... } } else { if (newTasks) { newTasks.push(task); // we've cloned the tasks[], so keep this one... } if (nextExpires > expires) { nextExpires = expires; // track the nearest expiration time } } } if (newTasks) { // only now can we copy the newTasks to me.tasks since no user callbacks can // take place me.tasks = newTasks; } me.firing = false; // we're done, so allow startTimer afterwards if (me.tasks.length) { // we create a new Date here because all the callbacks could have taken a long // time... we want to base the next timeout on the current time (after the // callback storm): me.startTimer(nextExpires - now, new Date().getTime()); } }, // private startTimer: function (timeout, now) { var me = this, expires = now + timeout, timerId = me.timerId; // Check to see if this request is enough in advance of the current timer. If so, // we reschedule the timer based on this new expiration. if (timerId && me.nextExpires - expires > me.interval) { clearTimeout(timerId); timerId = null; } if (!timerId) { if (timeout < me.interval) { timeout = me.interval; } me.timerId = setTimeout(me.timerFn, timeout); me.nextExpires = expires; } } }, function () { var me = this, proto = me.prototype; /** * Destroys this instance, stopping all tasks that are currently running. * @method destroy */ proto.destroy = proto.stopAll; /** * @class Ext.TaskManager * @extends Ext.util.TaskRunner * @singleton * * A static {@link Ext.util.TaskRunner} instance that can be used to start and stop * arbitrary tasks. See {@link Ext.util.TaskRunner} for supported methods and task * config properties. * * // Start a simple clock task that updates a div once per second * var task = { * run: function(){ * Ext.fly('clock').update(new Date().format('g:i:s A')); * }, * interval: 1000 //1 second * } * * Ext.TaskManager.start(task); * * See the {@link #start} method for details about how to configure a task object. */ Ext.util.TaskManager = Ext.TaskManager = new me(); /** * Instances of this class are created by {@link Ext.util.TaskRunner#newTask} method. * * For details on config properties, see {@link Ext.util.TaskRunner#start}. * @class Ext.util.TaskRunner.Task */ me.Task = new Ext.Class({ isTask: true, /** * This flag is set to `true` by {@link #stop}. * @private */ stopped: true, // this avoids the odd combination of !stopped && !pending /** * Override default behavior */ fireOnStart: false, constructor: function (config) { Ext.apply(this, config); }, /** * Restarts this task, clearing it duration, expiration and run count. * @param {Number} [interval] Optionally reset this task's interval. */ restart: function (interval) { if (interval !== undefined) { this.interval = interval; } this.manager.start(this); }, /** * Starts this task if it is not already started. * @param {Number} [interval] Optionally reset this task's interval. */ start: function (interval) { if (this.stopped) { this.restart(interval); } }, /** * Stops this task. */ stop: function () { this.manager.stop(this); } }); proto = me.Task.prototype; /** * Destroys this instance, stopping this task's execution. * @method destroy */ proto.destroy = proto.stop; }); /** * @class Ext.perf.Accumulator * @private */ Ext.define('Ext.perf.Accumulator', (function () { var currentFrame = null, khrome = Ext.global['chrome'], formatTpl, // lazy init on first request for timestamp (avoids infobar in IE until needed) // Also avoids kicking off Chrome's microsecond timer until first needed getTimestamp = function () { getTimestamp = function () { return new Date().getTime(); }; var interval, toolbox; // If Chrome is started with the --enable-benchmarking switch if (Ext.isChrome && khrome && khrome.Interval) { interval = new khrome.Interval(); interval.start(); getTimestamp = function () { return interval.microseconds() / 1000; }; } else if (window.ActiveXObject) { try { // the above technique is not very accurate for small intervals... toolbox = new ActiveXObject('SenchaToolbox.Toolbox'); Ext.senchaToolbox = toolbox; // export for other uses getTimestamp = function () { return toolbox.milliseconds; }; } catch (e) { // ignore } } else if (Date.now) { getTimestamp = Date.now; } Ext.perf.getTimestamp = Ext.perf.Accumulator.getTimestamp = getTimestamp; return getTimestamp(); }; function adjustSet (set, time) { set.sum += time; set.min = Math.min(set.min, time); set.max = Math.max(set.max, time); } function leaveFrame (time) { var totalTime = time ? time : (getTimestamp() - this.time), // do this first me = this, // me = frame accum = me.accum; ++accum.count; if (! --accum.depth) { adjustSet(accum.total, totalTime); } adjustSet(accum.pure, totalTime - me.childTime); currentFrame = me.parent; if (currentFrame) { ++currentFrame.accum.childCount; currentFrame.childTime += totalTime; } } function makeSet () { return { min: Number.MAX_VALUE, max: 0, sum: 0 }; } function makeTap (me, fn) { return function () { var frame = me.enter(), ret = fn.apply(this, arguments); frame.leave(); return ret; }; } function round (x) { return Math.round(x * 100) / 100; } function setToJSON (count, childCount, calibration, set) { var data = { avg: 0, min: set.min, max: set.max, sum: 0 }; if (count) { calibration = calibration || 0; data.sum = set.sum - childCount * calibration; data.avg = data.sum / count; // min and max cannot be easily corrected since we don't know the number of // child calls for them. } return data; } return { constructor: function (name) { var me = this; me.count = me.childCount = me.depth = me.maxDepth = 0; me.pure = makeSet(); me.total = makeSet(); me.name = name; }, statics: { getTimestamp: getTimestamp }, format: function (calibration) { if (!formatTpl) { formatTpl = new Ext.XTemplate([ '{name} - {count} call(s)', '', '', ' ({childCount} children)', '', '', ' ({depth} deep)', '', '', ', {type}: {[this.time(values.sum)]} msec (', //'min={[this.time(values.min)]}, ', 'avg={[this.time(values.sum / parent.count)]}', //', max={[this.time(values.max)]}', ')', '', '' ].join(''), { time: function (t) { return Math.round(t * 100) / 100; } }); } var data = this.getData(calibration); data.name = this.name; data.pure.type = 'Pure'; data.total.type = 'Total'; data.times = [data.pure, data.total]; return formatTpl.apply(data); }, getData: function (calibration) { var me = this; return { count: me.count, childCount: me.childCount, depth: me.maxDepth, pure: setToJSON(me.count, me.childCount, calibration, me.pure), total: setToJSON(me.count, me.childCount, calibration, me.total) }; }, enter: function () { var me = this, frame = { accum: me, leave: leaveFrame, childTime: 0, parent: currentFrame }; ++me.depth; if (me.maxDepth < me.depth) { me.maxDepth = me.depth; } currentFrame = frame; frame.time = getTimestamp(); // do this last return frame; }, monitor: function (fn, scope, args) { var frame = this.enter(); if (args) { fn.apply(scope, args); } else { fn.call(scope); } frame.leave(); }, report: function () { Ext.log(this.format()); }, tap: function (className, methodName) { var me = this, methods = typeof methodName == 'string' ? [methodName] : methodName, klass, statik, i, parts, length, name, src, tapFunc; tapFunc = function(){ if (typeof className == 'string') { klass = Ext.global; parts = className.split('.'); for (i = 0, length = parts.length; i < length; ++i) { klass = klass[parts[i]]; } } else { klass = className; } for (i = 0, length = methods.length; i < length; ++i) { name = methods[i]; statik = name.charAt(0) == '!'; if (statik) { name = name.substring(1); } else { statik = !(name in klass.prototype); } src = statik ? klass : klass.prototype; src[name] = makeTap(me, src[name]); } }; Ext.ClassManager.onCreated(tapFunc, me, className); return me; } }; }()), function () { Ext.perf.getTimestamp = this.getTimestamp; }); /** * @class Ext.perf.Monitor * @singleton * @private */ Ext.define('Ext.perf.Monitor', { singleton: true, alternateClassName: 'Ext.Perf', requires: [ 'Ext.perf.Accumulator' ], constructor: function () { this.accumulators = []; this.accumulatorsByName = {}; }, calibrate: function () { var accum = new Ext.perf.Accumulator('$'), total = accum.total, getTimestamp = Ext.perf.Accumulator.getTimestamp, count = 0, frame, endTime, startTime; startTime = getTimestamp(); do { frame = accum.enter(); frame.leave(); ++count; } while (total.sum < 100); endTime = getTimestamp(); return (endTime - startTime) / count; }, get: function (name) { var me = this, accum = me.accumulatorsByName[name]; if (!accum) { me.accumulatorsByName[name] = accum = new Ext.perf.Accumulator(name); me.accumulators.push(accum); } return accum; }, enter: function (name) { return this.get(name).enter(); }, monitor: function (name, fn, scope) { this.get(name).monitor(fn, scope); }, report: function () { var me = this, accumulators = me.accumulators, calibration = me.calibrate(); accumulators.sort(function (a, b) { return (a.name < b.name) ? -1 : ((b.name < a.name) ? 1 : 0); }); me.updateGC(); Ext.log('Calibration: ' + Math.round(calibration * 100) / 100 + ' msec/sample'); Ext.each(accumulators, function (accum) { Ext.log(accum.format(calibration)); }); }, getData: function (all) { var ret = {}, accumulators = this.accumulators; Ext.each(accumulators, function (accum) { if (all || accum.count) { ret[accum.name] = accum.getData(); } }); return ret; }, reset: function(){ Ext.each(this.accumulators, function(accum){ var me = accum; me.count = me.childCount = me.depth = me.maxDepth = 0; me.pure = { min: Number.MAX_VALUE, max: 0, sum: 0 }; me.total = { min: Number.MAX_VALUE, max: 0, sum: 0 }; }); }, updateGC: function () { var accumGC = this.accumulatorsByName.GC, toolbox = Ext.senchaToolbox, bucket; if (accumGC) { accumGC.count = toolbox.garbageCollectionCounter || 0; if (accumGC.count) { bucket = accumGC.pure; accumGC.total.sum = bucket.sum = toolbox.garbageCollectionMilliseconds; bucket.min = bucket.max = bucket.sum / accumGC.count; bucket = accumGC.total; bucket.min = bucket.max = bucket.sum / accumGC.count; } } }, watchGC: function () { Ext.perf.getTimestamp(); // initializes SenchaToolbox (if available) var toolbox = Ext.senchaToolbox; if (toolbox) { this.get("GC"); toolbox.watchGarbageCollector(false); // no logging, just totals } }, setup: function (config) { if (!config) { config = { /*insertHtml: { 'Ext.dom.Helper': 'insertHtml' },*/ /*xtplCompile: { 'Ext.XTemplateCompiler': 'compile' },*/ // doInsert: { // 'Ext.Template': 'doInsert' // }, // applyOut: { // 'Ext.XTemplate': 'applyOut' // }, render: { 'Ext.AbstractComponent': 'render' }, // fnishRender: { // 'Ext.AbstractComponent': 'finishRender' // }, // renderSelectors: { // 'Ext.AbstractComponent': 'applyRenderSelectors' // }, // compAddCls: { // 'Ext.AbstractComponent': 'addCls' // }, // compRemoveCls: { // 'Ext.AbstractComponent': 'removeCls' // }, // getStyle: { // 'Ext.core.Element': 'getStyle' // }, // setStyle: { // 'Ext.core.Element': 'setStyle' // }, // addCls: { // 'Ext.core.Element': 'addCls' // }, // removeCls: { // 'Ext.core.Element': 'removeCls' // }, // measure: { // 'Ext.layout.component.Component': 'measureAutoDimensions' // }, // moveItem: { // 'Ext.layout.Layout': 'moveItem' // }, // layoutFlush: { // 'Ext.layout.Context': 'flush' // }, layout: { 'Ext.layout.Context': 'run' } }; } this.currentConfig = config; var key, prop, accum, className, methods; for (key in config) { if (config.hasOwnProperty(key)) { prop = config[key]; accum = Ext.Perf.get(key); for (className in prop) { if (prop.hasOwnProperty(className)) { methods = prop[className]; accum.tap(className, methods); } } } } this.watchGC(); } }); /** * @class Ext.is * * Determines information about the current platform the application is running on. * * @singleton */ Ext.is = { init : function(navigator) { var platforms = this.platforms, ln = platforms.length, i, platform; navigator = navigator || window.navigator; for (i = 0; i < ln; i++) { platform = platforms[i]; this[platform.identity] = platform.regex.test(navigator[platform.property]); } /** * @property Desktop True if the browser is running on a desktop machine * @type {Boolean} */ this.Desktop = this.Mac || this.Windows || (this.Linux && !this.Android); /** * @property Tablet True if the browser is running on a tablet (iPad) */ this.Tablet = this.iPad; /** * @property Phone True if the browser is running on a phone. * @type {Boolean} */ this.Phone = !this.Desktop && !this.Tablet; /** * @property iOS True if the browser is running on iOS * @type {Boolean} */ this.iOS = this.iPhone || this.iPad || this.iPod; /** * @property Standalone Detects when application has been saved to homescreen. * @type {Boolean} */ this.Standalone = !!window.navigator.standalone; }, /** * @property iPhone True when the browser is running on a iPhone * @type {Boolean} */ platforms: [{ property: 'platform', regex: /iPhone/i, identity: 'iPhone' }, /** * @property iPod True when the browser is running on a iPod * @type {Boolean} */ { property: 'platform', regex: /iPod/i, identity: 'iPod' }, /** * @property iPad True when the browser is running on a iPad * @type {Boolean} */ { property: 'userAgent', regex: /iPad/i, identity: 'iPad' }, /** * @property Blackberry True when the browser is running on a Blackberry * @type {Boolean} */ { property: 'userAgent', regex: /Blackberry/i, identity: 'Blackberry' }, /** * @property Android True when the browser is running on an Android device * @type {Boolean} */ { property: 'userAgent', regex: /Android/i, identity: 'Android' }, /** * @property Mac True when the browser is running on a Mac * @type {Boolean} */ { property: 'platform', regex: /Mac/i, identity: 'Mac' }, /** * @property Windows True when the browser is running on Windows * @type {Boolean} */ { property: 'platform', regex: /Win/i, identity: 'Windows' }, /** * @property Linux True when the browser is running on Linux * @type {Boolean} */ { property: 'platform', regex: /Linux/i, identity: 'Linux' }] }; Ext.is.init(); /** * @class Ext.supports * * Determines information about features are supported in the current environment * * @singleton */ (function(){ // this is a local copy of certain logic from (Abstract)Element.getStyle // to break a dependancy between the supports mechanism and Element // use this instead of element references to check for styling info var getStyle = function(element, styleName){ var view = element.ownerDocument.defaultView, style = (view ? view.getComputedStyle(element, null) : element.currentStyle) || element.style; return style[styleName]; }; Ext.supports = { /** * Runs feature detection routines and sets the various flags. This is called when * the scripts loads (very early) and again at {@link Ext#onReady}. Some detections * are flagged as `early` and run immediately. Others that require the document body * will not run until ready. * * Each test is run only once, so calling this method from an onReady function is safe * and ensures that all flags have been set. * @markdown * @private */ init : function() { var me = this, doc = document, tests = me.tests, n = tests.length, div = n && Ext.isReady && doc.createElement('div'), test, notRun = []; if (div) { div.innerHTML = [ '
', '
', '
', '
', '
', '
', '
', '
' ].join(''); doc.body.appendChild(div); } while (n--) { test = tests[n]; if (div || test.early) { me[test.identity] = test.fn.call(me, doc, div); } else { notRun.push(test); } } if (div) { doc.body.removeChild(div); } me.tests = notRun; }, /** * @property PointerEvents True if document environment supports the CSS3 pointer-events style. * @type {Boolean} */ PointerEvents: 'pointerEvents' in document.documentElement.style, /** * @property CSS3BoxShadow True if document environment supports the CSS3 box-shadow style. * @type {Boolean} */ CSS3BoxShadow: 'boxShadow' in document.documentElement.style || 'WebkitBoxShadow' in document.documentElement.style || 'MozBoxShadow' in document.documentElement.style, /** * @property ClassList True if document environment supports the HTML5 classList API. * @type {Boolean} */ ClassList: !!document.documentElement.classList, /** * @property OrientationChange True if the device supports orientation change * @type {Boolean} */ OrientationChange: ((typeof window.orientation != 'undefined') && ('onorientationchange' in window)), /** * @property DeviceMotion True if the device supports device motion (acceleration and rotation rate) * @type {Boolean} */ DeviceMotion: ('ondevicemotion' in window), /** * @property Touch True if the device supports touch * @type {Boolean} */ // is.Desktop is needed due to the bug in Chrome 5.0.375, Safari 3.1.2 // and Safari 4.0 (they all have 'ontouchstart' in the window object). Touch: ('ontouchstart' in window) && (!Ext.is.Desktop), /** * @property TimeoutActualLateness True if the browser passes the "actualLateness" parameter to * setTimeout. See: https://developer.mozilla.org/en/DOM/window.setTimeout * @type {Boolean} */ TimeoutActualLateness: (function(){ setTimeout(function(){ Ext.supports.TimeoutActualLateness = arguments.length !== 0; }, 0); }()), tests: [ /** * @property Transitions True if the device supports CSS3 Transitions * @type {Boolean} */ { identity: 'Transitions', fn: function(doc, div) { var prefix = [ 'webkit', 'Moz', 'o', 'ms', 'khtml' ], TE = 'TransitionEnd', transitionEndName = [ prefix[0] + TE, 'transitionend', //Moz bucks the prefixing convention prefix[2] + TE, prefix[3] + TE, prefix[4] + TE ], ln = prefix.length, i = 0, out = false; for (; i < ln; i++) { if (getStyle(div, prefix[i] + "TransitionProperty")) { Ext.supports.CSS3Prefix = prefix[i]; Ext.supports.CSS3TransitionEnd = transitionEndName[i]; out = true; break; } } return out; } }, /** * @property RightMargin True if the device supports right margin. * See https://bugs.webkit.org/show_bug.cgi?id=13343 for why this is needed. * @type {Boolean} */ { identity: 'RightMargin', fn: function(doc, div) { var view = doc.defaultView; return !(view && view.getComputedStyle(div.firstChild.firstChild, null).marginRight != '0px'); } }, /** * @property DisplayChangeInputSelectionBug True if INPUT elements lose their * selection when their display style is changed. Essentially, if a text input * has focus and its display style is changed, the I-beam disappears. * * This bug is encountered due to the work around in place for the {@link #RightMargin} * bug. This has been observed in Safari 4.0.4 and older, and appears to be fixed * in Safari 5. It's not clear if Safari 4.1 has the bug, but it has the same WebKit * version number as Safari 5 (according to http://unixpapa.com/js/gecko.html). */ { identity: 'DisplayChangeInputSelectionBug', early: true, fn: function() { var webKitVersion = Ext.webKitVersion; // WebKit but older than Safari 5 or Chrome 6: return 0 < webKitVersion && webKitVersion < 533; } }, /** * @property DisplayChangeTextAreaSelectionBug True if TEXTAREA elements lose their * selection when their display style is changed. Essentially, if a text area has * focus and its display style is changed, the I-beam disappears. * * This bug is encountered due to the work around in place for the {@link #RightMargin} * bug. This has been observed in Chrome 10 and Safari 5 and older, and appears to * be fixed in Chrome 11. */ { identity: 'DisplayChangeTextAreaSelectionBug', early: true, fn: function() { var webKitVersion = Ext.webKitVersion; /* Has bug w/textarea: (Chrome) Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.127 Safari/534.16 (Safari) Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; en-us) AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5 Safari/533.21.1 No bug: (Chrome) Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_7) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.57 Safari/534.24 */ return 0 < webKitVersion && webKitVersion < 534.24; } }, /** * @property TransparentColor True if the device supports transparent color * @type {Boolean} */ { identity: 'TransparentColor', fn: function(doc, div, view) { view = doc.defaultView; return !(view && view.getComputedStyle(div.lastChild, null).backgroundColor != 'transparent'); } }, /** * @property ComputedStyle True if the browser supports document.defaultView.getComputedStyle() * @type {Boolean} */ { identity: 'ComputedStyle', fn: function(doc, div, view) { view = doc.defaultView; return view && view.getComputedStyle; } }, /** * @property Svg True if the device supports SVG * @type {Boolean} */ { identity: 'Svg', fn: function(doc) { return !!doc.createElementNS && !!doc.createElementNS( "http:/" + "/www.w3.org/2000/svg", "svg").createSVGRect; } }, /** * @property Canvas True if the device supports Canvas * @type {Boolean} */ { identity: 'Canvas', fn: function(doc) { return !!doc.createElement('canvas').getContext; } }, /** * @property Vml True if the device supports VML * @type {Boolean} */ { identity: 'Vml', fn: function(doc) { var d = doc.createElement("div"); d.innerHTML = ""; return (d.childNodes.length == 2); } }, /** * @property Float True if the device supports CSS float * @type {Boolean} */ { identity: 'Float', fn: function(doc, div) { return !!div.lastChild.style.cssFloat; } }, /** * @property AudioTag True if the device supports the HTML5 audio tag * @type {Boolean} */ { identity: 'AudioTag', fn: function(doc) { return !!doc.createElement('audio').canPlayType; } }, /** * @property History True if the device supports HTML5 history * @type {Boolean} */ { identity: 'History', fn: function() { var history = window.history; return !!(history && history.pushState); } }, /** * @property CSS3DTransform True if the device supports CSS3DTransform * @type {Boolean} */ { identity: 'CSS3DTransform', fn: function() { return (typeof WebKitCSSMatrix != 'undefined' && new WebKitCSSMatrix().hasOwnProperty('m41')); } }, /** * @property CSS3LinearGradient True if the device supports CSS3 linear gradients * @type {Boolean} */ { identity: 'CSS3LinearGradient', fn: function(doc, div) { var property = 'background-image:', webkit = '-webkit-gradient(linear, left top, right bottom, from(black), to(white))', w3c = 'linear-gradient(left top, black, white)', moz = '-moz-' + w3c, opera = '-o-' + w3c, options = [property + webkit, property + w3c, property + moz, property + opera]; div.style.cssText = options.join(';'); return ("" + div.style.backgroundImage).indexOf('gradient') !== -1; } }, /** * @property CSS3BorderRadius True if the device supports CSS3 border radius * @type {Boolean} */ { identity: 'CSS3BorderRadius', fn: function(doc, div) { var domPrefixes = ['borderRadius', 'BorderRadius', 'MozBorderRadius', 'WebkitBorderRadius', 'OBorderRadius', 'KhtmlBorderRadius'], pass = false, i; for (i = 0; i < domPrefixes.length; i++) { if (document.body.style[domPrefixes[i]] !== undefined) { return true; } } return pass; } }, /** * @property GeoLocation True if the device supports GeoLocation * @type {Boolean} */ { identity: 'GeoLocation', fn: function() { return (typeof navigator != 'undefined' && typeof navigator.geolocation != 'undefined') || (typeof google != 'undefined' && typeof google.gears != 'undefined'); } }, /** * @property MouseEnterLeave True if the browser supports mouseenter and mouseleave events * @type {Boolean} */ { identity: 'MouseEnterLeave', fn: function(doc, div){ return ('onmouseenter' in div && 'onmouseleave' in div); } }, /** * @property MouseWheel True if the browser supports the mousewheel event * @type {Boolean} */ { identity: 'MouseWheel', fn: function(doc, div) { return ('onmousewheel' in div); } }, /** * @property Opacity True if the browser supports normal css opacity * @type {Boolean} */ { identity: 'Opacity', fn: function(doc, div){ // Not a strict equal comparison in case opacity can be converted to a number. if (Ext.isIE6 || Ext.isIE7 || Ext.isIE8) { return false; } div.firstChild.style.cssText = 'opacity:0.73'; return div.firstChild.style.opacity == '0.73'; } }, /** * @property Placeholder True if the browser supports the HTML5 placeholder attribute on inputs * @type {Boolean} */ { identity: 'Placeholder', fn: function(doc) { return 'placeholder' in doc.createElement('input'); } }, /** * @property Direct2DBug True if when asking for an element's dimension via offsetWidth or offsetHeight, * getBoundingClientRect, etc. the browser returns the subpixel width rounded to the nearest pixel. * @type {Boolean} */ { identity: 'Direct2DBug', fn: function() { return Ext.isString(document.body.style.msTransformOrigin); } }, /** * @property BoundingClientRect True if the browser supports the getBoundingClientRect method on elements * @type {Boolean} */ { identity: 'BoundingClientRect', fn: function(doc, div) { return Ext.isFunction(div.getBoundingClientRect); } }, { identity: 'IncludePaddingInWidthCalculation', fn: function(doc, div){ return div.childNodes[1].firstChild.offsetWidth == 210; } }, { identity: 'IncludePaddingInHeightCalculation', fn: function(doc, div){ return div.childNodes[1].firstChild.offsetHeight == 210; } }, /** * @property ArraySort True if the Array sort native method isn't bugged. * @type {Boolean} */ { identity: 'ArraySort', fn: function() { var a = [1,2,3,4,5].sort(function(){ return 0; }); return a[0] === 1 && a[1] === 2 && a[2] === 3 && a[3] === 4 && a[4] === 5; } }, /** * @property Range True if browser support document.createRange native method. * @type {Boolean} */ { identity: 'Range', fn: function() { return !!document.createRange; } }, /** * @property CreateContextualFragment True if browser support CreateContextualFragment range native methods. * @type {Boolean} */ { identity: 'CreateContextualFragment', fn: function() { var range = Ext.supports.Range ? document.createRange() : false; return range && !!range.createContextualFragment; } }, /** * @property WindowOnError True if browser supports window.onerror. * @type {Boolean} */ { identity: 'WindowOnError', fn: function () { // sadly, we cannot feature detect this... return Ext.isIE || Ext.isGecko || Ext.webKitVersion >= 534.16; // Chrome 10+ } }, /** * @property TextAreaMaxLength True if the browser supports maxlength on textareas. * @type {Boolean} */ { identity: 'TextAreaMaxLength', fn: function(){ var el = document.createElement('textarea'); return ('maxlength' in el); } }, /** * @property GetPositionPercentage True if the browser will return the left/top/right/bottom * position as a percentage when explicitly set as a percentage value. * @type {Boolean} */ // Related bug: https://bugzilla.mozilla.org/show_bug.cgi?id=707691#c7 { identity: 'GetPositionPercentage', fn: function(doc, div){ return getStyle(div.childNodes[2], 'left') == '10%'; } } ] }; }()); Ext.supports.init(); // run the "early" detections now