8063 lines
298 KiB
JavaScript
8063 lines
298 KiB
JavaScript
/*
|
|
This file is part of Ext JS 3.4
|
|
|
|
Copyright (c) 2011-2013 Sencha Inc
|
|
|
|
Contact: http://www.sencha.com/contact
|
|
|
|
GNU General Public License Usage
|
|
This file may be used under the terms of the GNU General Public License version 3.0 as
|
|
published by the Free Software Foundation and appearing in the file LICENSE included in the
|
|
packaging of this file.
|
|
|
|
Please review the following information to ensure the GNU General Public License version 3.0
|
|
requirements will be met: http://www.gnu.org/copyleft/gpl.html.
|
|
|
|
If you are unsure which license is appropriate for your use, please contact the sales department
|
|
at http://www.sencha.com/contact.
|
|
|
|
Build date: 2013-04-03 15:07:25
|
|
*/
|
|
/**
|
|
* @class Ext
|
|
*/
|
|
|
|
Ext.ns("Ext.grid", "Ext.list", "Ext.dd", "Ext.tree", "Ext.form", "Ext.menu",
|
|
"Ext.state", "Ext.layout.boxOverflow", "Ext.app", "Ext.ux", "Ext.chart", "Ext.direct", "Ext.slider");
|
|
/**
|
|
* Namespace alloted for extensions to the framework.
|
|
* @property ux
|
|
* @type Object
|
|
*/
|
|
|
|
Ext.apply(Ext, function(){
|
|
var E = Ext,
|
|
idSeed = 0,
|
|
scrollWidth = null;
|
|
|
|
return {
|
|
/**
|
|
* A reusable empty function
|
|
* @property
|
|
* @type Function
|
|
*/
|
|
emptyFn : function(){},
|
|
|
|
/**
|
|
* 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://extjs.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 : Ext.isIE6 || Ext.isIE7 || Ext.isAir ?
|
|
'http:/' + '/www.extjs.com/s.gif' :
|
|
'data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==',
|
|
|
|
extendX : function(supr, fn){
|
|
return Ext.extend(supr, fn(supr.prototype));
|
|
},
|
|
|
|
/**
|
|
* Returns the current HTML document object as an {@link Ext.Element}.
|
|
* @return Ext.Element The document
|
|
*/
|
|
getDoc : function(){
|
|
return Ext.get(document);
|
|
},
|
|
|
|
/**
|
|
* Utility method for validating that a value is numeric, returning the specified default value if it is not.
|
|
* @param {Mixed} value Should be a number, but any type will be handled appropriately
|
|
* @param {Number} defaultValue The value to return if the original value is non-numeric
|
|
* @return {Number} Value, if numeric, else defaultValue
|
|
*/
|
|
num : function(v, defaultValue){
|
|
v = Number(Ext.isEmpty(v) || Ext.isArray(v) || typeof v == 'boolean' || (typeof v == 'string' && v.trim().length == 0) ? NaN : v);
|
|
return isNaN(v) ? defaultValue : v;
|
|
},
|
|
|
|
/**
|
|
* <p>Utility method for returning a default value if the passed value is empty.</p>
|
|
* <p>The value is deemed to be empty if it is<div class="mdetail-params"><ul>
|
|
* <li>null</li>
|
|
* <li>undefined</li>
|
|
* <li>an empty array</li>
|
|
* <li>a zero length string (Unless the <tt>allowBlank</tt> parameter is <tt>true</tt>)</li>
|
|
* </ul></div>
|
|
* @param {Mixed} value The value to test
|
|
* @param {Mixed} defaultValue The value to return if the original value is empty
|
|
* @param {Boolean} allowBlank (optional) true to allow zero length strings to qualify as non-empty (defaults to false)
|
|
* @return {Mixed} value, if non-empty, else defaultValue
|
|
*/
|
|
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}
|
|
*/
|
|
escapeRe : function(s) {
|
|
return s.replace(/([-.*+?^${}()|[\]\/\\])/g, "\\$1");
|
|
},
|
|
|
|
sequence : function(o, name, fn, scope){
|
|
o[name] = o[name].createSequence(fn, scope);
|
|
},
|
|
|
|
/**
|
|
* Applies event listeners to elements by selectors when the document is ready.
|
|
* The event name is specified with an <tt>@</tt> suffix.
|
|
* <pre><code>
|
|
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
|
|
}
|
|
});
|
|
* </code></pre>
|
|
* @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;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Utility method for getting the width of the browser scrollbar. This can differ depending on
|
|
* operating system settings, such as the theme or font size.
|
|
* @param {Boolean} force (optional) true to force a recalculation of the value.
|
|
* @return {Number} The width of the scrollbar.
|
|
*/
|
|
getScrollBarWidth: function(force){
|
|
if(!Ext.isReady){
|
|
return 0;
|
|
}
|
|
|
|
if(force === true || scrollWidth === null){
|
|
// Append our div, do our calculation and then remove it
|
|
var div = Ext.getBody().createChild('<div class="x-hide-offsets" style="width:100px;height:50px;overflow:hidden;"><div style="height:200px;"></div></div>'),
|
|
child = div.child('div', true);
|
|
var w1 = child.offsetWidth;
|
|
div.setStyle('overflow', (Ext.isWebKit || Ext.isGecko) ? 'auto' : 'scroll');
|
|
var w2 = child.offsetWidth;
|
|
div.remove();
|
|
// Need to add 2 to ensure we leave enough space
|
|
scrollWidth = w1 - w2 + 2;
|
|
}
|
|
return scrollWidth;
|
|
},
|
|
|
|
|
|
// deprecated
|
|
combine : function(){
|
|
var as = arguments, l = as.length, r = [];
|
|
for(var i = 0; i < l; i++){
|
|
var a = as[i];
|
|
if(Ext.isArray(a)){
|
|
r = r.concat(a);
|
|
}else if(a.length !== undefined && !a.substr){
|
|
r = r.concat(Array.prototype.slice.call(a, 0));
|
|
}else{
|
|
r.push(a);
|
|
}
|
|
}
|
|
return r;
|
|
},
|
|
|
|
/**
|
|
* Copies a set of named properties fom the source object to the destination object.
|
|
* <p>example:<pre><code>
|
|
ImageComponent = Ext.extend(Ext.BoxComponent, {
|
|
initComponent: function() {
|
|
this.autoEl = { tag: 'img' };
|
|
MyComponent.superclass.initComponent.apply(this, arguments);
|
|
this.initialBox = Ext.copyTo({}, this.initialConfig, 'x,y,width,height');
|
|
}
|
|
});
|
|
* </code></pre>
|
|
* @param {Object} dest The destination object.
|
|
* @param {Object} source The source object.
|
|
* @param {Array/String} names Either an Array of property names, or a comma-delimited list
|
|
* of property names to copy.
|
|
* @return {Object} The modified object.
|
|
*/
|
|
copyTo : function(dest, source, names){
|
|
if(typeof names == 'string'){
|
|
names = names.split(/[,;\s]/);
|
|
}
|
|
Ext.each(names, function(name){
|
|
if(source.hasOwnProperty(name)){
|
|
dest[name] = source[name];
|
|
}
|
|
}, this);
|
|
return dest;
|
|
},
|
|
|
|
/**
|
|
* 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 {Mixed...} args An {@link Ext.Element}, {@link Ext.Component}, or an Array of either of these to destroy
|
|
*/
|
|
destroy : function(){
|
|
Ext.each(arguments, function(arg){
|
|
if(arg){
|
|
if(Ext.isArray(arg)){
|
|
this.destroy.apply(this, arg);
|
|
}else if(typeof arg.destroy == 'function'){
|
|
arg.destroy();
|
|
}else if(arg.dom){
|
|
arg.remove();
|
|
}
|
|
}
|
|
}, this);
|
|
},
|
|
|
|
/**
|
|
* 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 {Mixed} arg1 The name of the property to destroy and remove from the object.
|
|
* @param {Mixed} etc... More property names to destroy and remove.
|
|
*/
|
|
destroyMembers : function(o, arg1, arg2, etc){
|
|
for(var i = 1, a = arguments, len = a.length; i < len; i++) {
|
|
Ext.destroy(o[a[i]]);
|
|
delete o[a[i]];
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Creates a copy of the passed Array with falsy values removed.
|
|
* @param {Array/NodeList} arr The Array from which to remove falsy values.
|
|
* @return {Array} The new, compressed Array.
|
|
*/
|
|
clean : function(arr){
|
|
var ret = [];
|
|
Ext.each(arr, function(v){
|
|
if(!!v){
|
|
ret.push(v);
|
|
}
|
|
});
|
|
return ret;
|
|
},
|
|
|
|
/**
|
|
* Creates a copy of the passed Array, filtered to contain only unique values.
|
|
* @param {Array} arr The Array to filter
|
|
* @return {Array} The new Array containing unique values.
|
|
*/
|
|
unique : function(arr){
|
|
var ret = [],
|
|
collect = {};
|
|
|
|
Ext.each(arr, function(v) {
|
|
if(!collect[v]){
|
|
ret.push(v);
|
|
}
|
|
collect[v] = true;
|
|
});
|
|
return ret;
|
|
},
|
|
|
|
/**
|
|
* Recursively flattens into 1-d Array. Injects Arrays inline.
|
|
* @param {Array} arr The array to flatten
|
|
* @return {Array} The new, flattened array.
|
|
*/
|
|
flatten : function(arr){
|
|
var worker = [];
|
|
function rFlatten(a) {
|
|
Ext.each(a, function(v) {
|
|
if(Ext.isArray(v)){
|
|
rFlatten(v);
|
|
}else{
|
|
worker.push(v);
|
|
}
|
|
});
|
|
return worker;
|
|
}
|
|
return rFlatten(arr);
|
|
},
|
|
|
|
/**
|
|
* Returns the minimum value in the Array.
|
|
* @param {Array|NodeList} arr The Array from which to select the minimum value.
|
|
* @param {Function} comp (optional) a function to perform the comparision which determines minimization.
|
|
* If omitted the "<" operator will be used. Note: gt = 1; eq = 0; lt = -1
|
|
* @return {Object} The minimum value in the Array.
|
|
*/
|
|
min : function(arr, comp){
|
|
var ret = arr[0];
|
|
comp = comp || function(a,b){ return a < b ? -1 : 1; };
|
|
Ext.each(arr, function(v) {
|
|
ret = comp(ret, v) == -1 ? ret : v;
|
|
});
|
|
return ret;
|
|
},
|
|
|
|
/**
|
|
* Returns the maximum value in the Array
|
|
* @param {Array|NodeList} arr The Array from which to select the maximum value.
|
|
* @param {Function} comp (optional) a function to perform the comparision which determines maximization.
|
|
* If omitted the ">" operator will be used. Note: gt = 1; eq = 0; lt = -1
|
|
* @return {Object} The maximum value in the Array.
|
|
*/
|
|
max : function(arr, comp){
|
|
var ret = arr[0];
|
|
comp = comp || function(a,b){ return a > b ? 1 : -1; };
|
|
Ext.each(arr, function(v) {
|
|
ret = comp(ret, v) == 1 ? ret : v;
|
|
});
|
|
return ret;
|
|
},
|
|
|
|
/**
|
|
* Calculates the mean of the Array
|
|
* @param {Array} arr The Array to calculate the mean value of.
|
|
* @return {Number} The mean.
|
|
*/
|
|
mean : function(arr){
|
|
return arr.length > 0 ? Ext.sum(arr) / arr.length : undefined;
|
|
},
|
|
|
|
/**
|
|
* Calculates the sum of the Array
|
|
* @param {Array} arr The Array to calculate the sum value of.
|
|
* @return {Number} The sum.
|
|
*/
|
|
sum : function(arr){
|
|
var ret = 0;
|
|
Ext.each(arr, function(v) {
|
|
ret += v;
|
|
});
|
|
return ret;
|
|
},
|
|
|
|
/**
|
|
* Partitions the set into two sets: a true set and a false set.
|
|
* Example:
|
|
* Example2:
|
|
* <pre><code>
|
|
// Example 1:
|
|
Ext.partition([true, false, true, true, false]); // [[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.
|
|
* </code></pre>
|
|
* @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} [true<Array>,false<Array>]
|
|
*/
|
|
partition : function(arr, truth){
|
|
var ret = [[],[]];
|
|
Ext.each(arr, function(v, i, a) {
|
|
ret[ (truth && truth(v, i, a)) || (!truth && v) ? 0 : 1].push(v);
|
|
});
|
|
return ret;
|
|
},
|
|
|
|
/**
|
|
* Invokes a method on each item in an Array.
|
|
* <pre><code>
|
|
// Example:
|
|
Ext.invoke(Ext.query("p"), "getAttribute", "id");
|
|
// [el1.getAttribute("id"), el2.getAttribute("id"), ..., elN.getAttribute("id")]
|
|
* </code></pre>
|
|
* @param {Array|NodeList} arr The Array of items to invoke the method on.
|
|
* @param {String} methodName The method name to invoke.
|
|
* @param {...*} args Arguments to send into the method invocation.
|
|
* @return {Array} The results of invoking the method on each item in the array.
|
|
*/
|
|
invoke : function(arr, methodName){
|
|
var ret = [],
|
|
args = Array.prototype.slice.call(arguments, 2);
|
|
Ext.each(arr, function(v,i) {
|
|
if (v && typeof v[methodName] == 'function') {
|
|
ret.push(v[methodName].apply(v, args));
|
|
} else {
|
|
ret.push(undefined);
|
|
}
|
|
});
|
|
return ret;
|
|
},
|
|
|
|
/**
|
|
* Plucks the value of a property from each item in the Array
|
|
* <pre><code>
|
|
// Example:
|
|
Ext.pluck(Ext.query("p"), "className"); // [el1.className, el2.className, ..., elN.className]
|
|
* </code></pre>
|
|
* @param {Array|NodeList} arr The Array of items to pluck the value from.
|
|
* @param {String} prop The property name to pluck from each element.
|
|
* @return {Array} The value from each item in the Array.
|
|
*/
|
|
pluck : function(arr, prop){
|
|
var ret = [];
|
|
Ext.each(arr, function(v) {
|
|
ret.push( v[prop] );
|
|
});
|
|
return ret;
|
|
},
|
|
|
|
/**
|
|
* <p>Zips N sets together.</p>
|
|
* <pre><code>
|
|
// 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"]
|
|
* </code></pre>
|
|
* @param {Arrays|NodeLists} 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.
|
|
*/
|
|
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 = [];
|
|
|
|
for (var i = 0; i < len; i++) {
|
|
ret[i] = [];
|
|
if(fn){
|
|
ret[i] = fn.apply(fn, Ext.pluck(arrs, i));
|
|
}else{
|
|
for (var j = 0, aLen = arrs.length; j < aLen; j++){
|
|
ret[i].push( arrs[j][i] );
|
|
}
|
|
}
|
|
}
|
|
return ret;
|
|
},
|
|
|
|
/**
|
|
* This is shorthand reference to {@link Ext.ComponentMgr#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, <tt>undefined</tt> if not found, or <tt>null</tt> if a
|
|
* Class was found.
|
|
*/
|
|
getCmp : function(id){
|
|
return Ext.ComponentMgr.get(id);
|
|
},
|
|
|
|
/**
|
|
* By default, Ext intelligently decides whether floating elements should be shimmed. If you are using flash,
|
|
* you may want to set this to true.
|
|
* @type Boolean
|
|
*/
|
|
useShims: E.isIE6 || (E.isMac && E.isGecko2),
|
|
|
|
// inpired by a similar function in mootools library
|
|
/**
|
|
* Returns the type of object that is passed in. If the object passed in is null or undefined it
|
|
* return false otherwise it returns one of the following values:<div class="mdetail-params"><ul>
|
|
* <li><b>string</b>: If the object passed is a string</li>
|
|
* <li><b>number</b>: If the object passed is a number</li>
|
|
* <li><b>boolean</b>: If the object passed is a boolean value</li>
|
|
* <li><b>date</b>: If the object passed is a Date object</li>
|
|
* <li><b>function</b>: If the object passed is a function reference</li>
|
|
* <li><b>object</b>: If the object passed is an object</li>
|
|
* <li><b>array</b>: If the object passed is an array</li>
|
|
* <li><b>regexp</b>: If the object passed is a regular expression</li>
|
|
* <li><b>element</b>: If the object passed is a DOM Element</li>
|
|
* <li><b>nodelist</b>: If the object passed is a DOM NodeList</li>
|
|
* <li><b>textnode</b>: If the object passed is a DOM text node and contains something other than whitespace</li>
|
|
* <li><b>whitespace</b>: If the object passed is a DOM text node and contains only whitespace</li>
|
|
* </ul></div>
|
|
* @param {Mixed} object
|
|
* @return {String}
|
|
*/
|
|
type : function(o){
|
|
if(o === undefined || o === null){
|
|
return false;
|
|
}
|
|
if(o.htmlElement){
|
|
return 'element';
|
|
}
|
|
var t = typeof o;
|
|
if(t == 'object' && o.nodeName) {
|
|
switch(o.nodeType) {
|
|
case 1: return 'element';
|
|
case 3: return (/\S/).test(o.nodeValue) ? 'textnode' : 'whitespace';
|
|
}
|
|
}
|
|
if(t == 'object' || t == 'function') {
|
|
switch(o.constructor) {
|
|
case Array: return 'array';
|
|
case RegExp: return 'regexp';
|
|
case Date: return 'date';
|
|
}
|
|
if(typeof o.length == 'number' && typeof o.item == 'function') {
|
|
return 'nodelist';
|
|
}
|
|
}
|
|
return t;
|
|
},
|
|
|
|
intercept : function(o, name, fn, scope){
|
|
o[name] = o[name].createInterceptor(fn, scope);
|
|
},
|
|
|
|
// internal
|
|
callback : function(cb, scope, args, delay){
|
|
if(typeof cb == 'function'){
|
|
if(delay){
|
|
cb.defer(delay, scope, args || []);
|
|
}else{
|
|
cb.apply(scope, args || []);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}());
|
|
|
|
/**
|
|
* @class Function
|
|
* These functions are available on every Function object (any JavaScript function).
|
|
*/
|
|
Ext.apply(Function.prototype, {
|
|
/**
|
|
* Create a combined function call sequence of the original function + the passed function.
|
|
* The resulting function returns the results of the original function.
|
|
* The passed fcn is called with the parameters of the original function. Example usage:
|
|
* <pre><code>
|
|
var sayHi = function(name){
|
|
alert('Hi, ' + name);
|
|
}
|
|
|
|
sayHi('Fred'); // alerts "Hi, Fred"
|
|
|
|
var sayGoodbye = sayHi.createSequence(function(name){
|
|
alert('Bye, ' + name);
|
|
});
|
|
|
|
sayGoodbye('Fred'); // both alerts show
|
|
</code></pre>
|
|
* @param {Function} fcn The function to sequence
|
|
* @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the passed function is executed.
|
|
* <b>If omitted, defaults to the scope in which the original function is called or the browser window.</b>
|
|
* @return {Function} The new function
|
|
*/
|
|
createSequence : function(fcn, scope){
|
|
var method = this;
|
|
return (typeof fcn != 'function') ?
|
|
this :
|
|
function(){
|
|
var retval = method.apply(this || window, arguments);
|
|
fcn.apply(scope || this || window, arguments);
|
|
return retval;
|
|
};
|
|
}
|
|
});
|
|
|
|
|
|
/**
|
|
* @class String
|
|
* These functions are available as static methods on the JavaScript String object.
|
|
*/
|
|
Ext.applyIf(String, {
|
|
|
|
/**
|
|
* Escapes the passed string for ' and \
|
|
* @param {String} string The string to escape
|
|
* @return {String} The escaped string
|
|
* @static
|
|
*/
|
|
escape : function(string) {
|
|
return string.replace(/('|\\)/g, "\\$1");
|
|
},
|
|
|
|
/**
|
|
* Pads the left side of a string with a specified character. This is especially useful
|
|
* for normalizing number and date strings. Example usage:
|
|
* <pre><code>
|
|
var s = String.leftPad('123', 5, '0');
|
|
// s now contains the string: '00123'
|
|
* </code></pre>
|
|
* @param {String} string The original string
|
|
* @param {Number} size The total length of the output string
|
|
* @param {String} char (optional) The character with which to pad the original string (defaults to empty string " ")
|
|
* @return {String} The padded string
|
|
* @static
|
|
*/
|
|
leftPad : function (val, size, ch) {
|
|
var result = String(val);
|
|
if(!ch) {
|
|
ch = " ";
|
|
}
|
|
while (result.length < size) {
|
|
result = ch + result;
|
|
}
|
|
return result;
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Utility function that allows you to easily switch a string between two alternating values. The passed value
|
|
* is compared to the current string, and if they are equal, the other value that was passed in is returned. If
|
|
* they are already different, the first value passed in is returned. Note that this method returns the new value
|
|
* but does not change the current string.
|
|
* <pre><code>
|
|
// alternate sort directions
|
|
sort = sort.toggle('ASC', 'DESC');
|
|
|
|
// instead of conditional logic:
|
|
sort = (sort == 'ASC' ? 'DESC' : 'ASC');
|
|
</code></pre>
|
|
* @param {String} value The value to compare to the current string
|
|
* @param {String} other The new value to use if the string already equals the first value passed in
|
|
* @return {String} The new value
|
|
*/
|
|
String.prototype.toggle = function(value, other){
|
|
return this == value ? other : value;
|
|
};
|
|
|
|
/**
|
|
* Trims whitespace from either end of a string, leaving spaces within the string intact. Example:
|
|
* <pre><code>
|
|
var s = ' foo bar ';
|
|
alert('-' + s + '-'); //alerts "- foo bar -"
|
|
alert('-' + s.trim() + '-'); //alerts "-foo bar-"
|
|
</code></pre>
|
|
* @return {String} The trimmed string
|
|
*/
|
|
String.prototype.trim = function(){
|
|
var re = /^\s+|\s+$/g;
|
|
return function(){ return this.replace(re, ""); };
|
|
}();
|
|
|
|
// here to prevent dependency on Date.js
|
|
/**
|
|
Returns the number of milliseconds between this date and date
|
|
@param {Date} date (optional) Defaults to now
|
|
@return {Number} The diff in milliseconds
|
|
@member Date getElapsed
|
|
*/
|
|
Date.prototype.getElapsed = function(date) {
|
|
return Math.abs((date || new Date()).getTime()-this.getTime());
|
|
};
|
|
|
|
|
|
/**
|
|
* @class Number
|
|
*/
|
|
Ext.applyIf(Number.prototype, {
|
|
/**
|
|
* Checks whether or not the current number is within a desired range. If the number is already within the
|
|
* range it is returned, otherwise the min or max value is returned depending on which side of the range is
|
|
* exceeded. Note that this method returns the constrained value but does not change the current number.
|
|
* @param {Number} min The minimum number in the range
|
|
* @param {Number} max The maximum number in the range
|
|
* @return {Number} The constrained value if outside the range, otherwise the current value
|
|
*/
|
|
constrain : function(min, max){
|
|
return Math.min(Math.max(this, min), max);
|
|
}
|
|
});
|
|
Ext.lib.Dom.getRegion = function(el) {
|
|
return Ext.lib.Region.getRegion(el);
|
|
}; Ext.lib.Region = function(t, r, b, l) {
|
|
var me = this;
|
|
me.top = t;
|
|
me[1] = t;
|
|
me.right = r;
|
|
me.bottom = b;
|
|
me.left = l;
|
|
me[0] = l;
|
|
};
|
|
|
|
Ext.lib.Region.prototype = {
|
|
contains : function(region) {
|
|
var me = this;
|
|
return ( region.left >= me.left &&
|
|
region.right <= me.right &&
|
|
region.top >= me.top &&
|
|
region.bottom <= me.bottom );
|
|
|
|
},
|
|
|
|
getArea : function() {
|
|
var me = this;
|
|
return ( (me.bottom - me.top) * (me.right - me.left) );
|
|
},
|
|
|
|
intersect : function(region) {
|
|
var me = this,
|
|
t = Math.max(me.top, region.top),
|
|
r = Math.min(me.right, region.right),
|
|
b = Math.min(me.bottom, region.bottom),
|
|
l = Math.max(me.left, region.left);
|
|
|
|
if (b >= t && r >= l) {
|
|
return new Ext.lib.Region(t, r, b, l);
|
|
}
|
|
},
|
|
|
|
union : function(region) {
|
|
var me = this,
|
|
t = Math.min(me.top, region.top),
|
|
r = Math.max(me.right, region.right),
|
|
b = Math.max(me.bottom, region.bottom),
|
|
l = Math.min(me.left, region.left);
|
|
|
|
return new Ext.lib.Region(t, r, b, l);
|
|
},
|
|
|
|
constrainTo : function(r) {
|
|
var me = this;
|
|
me.top = me.top.constrain(r.top, r.bottom);
|
|
me.bottom = me.bottom.constrain(r.top, r.bottom);
|
|
me.left = me.left.constrain(r.left, r.right);
|
|
me.right = me.right.constrain(r.left, r.right);
|
|
return me;
|
|
},
|
|
|
|
adjust : function(t, l, b, r) {
|
|
var me = this;
|
|
me.top += t;
|
|
me.left += l;
|
|
me.right += r;
|
|
me.bottom += b;
|
|
return me;
|
|
}
|
|
};
|
|
|
|
Ext.lib.Region.getRegion = function(el) {
|
|
var p = Ext.lib.Dom.getXY(el),
|
|
t = p[1],
|
|
r = p[0] + el.offsetWidth,
|
|
b = p[1] + el.offsetHeight,
|
|
l = p[0];
|
|
|
|
return new Ext.lib.Region(t, r, b, l);
|
|
}; Ext.lib.Point = function(x, y) {
|
|
if (Ext.isArray(x)) {
|
|
y = x[1];
|
|
x = x[0];
|
|
}
|
|
var me = this;
|
|
me.x = me.right = me.left = me[0] = x;
|
|
me.y = me.top = me.bottom = me[1] = y;
|
|
};
|
|
|
|
Ext.lib.Point.prototype = new Ext.lib.Region();
|
|
/**
|
|
* @class Ext.DomHelper
|
|
*/
|
|
Ext.apply(Ext.DomHelper,
|
|
function(){
|
|
var pub,
|
|
afterbegin = 'afterbegin',
|
|
afterend = 'afterend',
|
|
beforebegin = 'beforebegin',
|
|
beforeend = 'beforeend',
|
|
confRe = /tag|children|cn|html$/i;
|
|
|
|
// private
|
|
function doInsert(el, o, returnElement, pos, sibling, append){
|
|
el = Ext.getDom(el);
|
|
var newNode;
|
|
if (pub.useDom) {
|
|
newNode = createDom(o, null);
|
|
if (append) {
|
|
el.appendChild(newNode);
|
|
} else {
|
|
(sibling == 'firstChild' ? el : el.parentNode).insertBefore(newNode, el[sibling] || el);
|
|
}
|
|
} else {
|
|
newNode = Ext.DomHelper.insertHtml(pos, el, Ext.DomHelper.createHtml(o));
|
|
}
|
|
return returnElement ? Ext.get(newNode, true) : newNode;
|
|
}
|
|
|
|
// build as dom
|
|
/** @ignore */
|
|
function createDom(o, parentNode){
|
|
var el,
|
|
doc = document,
|
|
useSet,
|
|
attr,
|
|
val,
|
|
cn;
|
|
|
|
if (Ext.isArray(o)) { // Allow Arrays of siblings to be inserted
|
|
el = doc.createDocumentFragment(); // in one shot using a DocumentFragment
|
|
for (var i = 0, l = o.length; i < l; i++) {
|
|
createDom(o[i], el);
|
|
}
|
|
} else if (typeof o == 'string') { // Allow a string as a child spec.
|
|
el = doc.createTextNode(o);
|
|
} else {
|
|
el = doc.createElement( o.tag || 'div' );
|
|
useSet = !!el.setAttribute; // In IE some elements don't have setAttribute
|
|
for (var attr in o) {
|
|
if(!confRe.test(attr)){
|
|
val = o[attr];
|
|
if(attr == 'cls'){
|
|
el.className = val;
|
|
}else{
|
|
if(useSet){
|
|
el.setAttribute(attr, val);
|
|
}else{
|
|
el[attr] = val;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Ext.DomHelper.applyStyles(el, o.style);
|
|
|
|
if ((cn = o.children || o.cn)) {
|
|
createDom(cn, el);
|
|
} else if (o.html) {
|
|
el.innerHTML = o.html;
|
|
}
|
|
}
|
|
if(parentNode){
|
|
parentNode.appendChild(el);
|
|
}
|
|
return el;
|
|
}
|
|
|
|
pub = {
|
|
/**
|
|
* Creates a new Ext.Template from the DOM object spec.
|
|
* @param {Object} o The DOM object spec (and children)
|
|
* @return {Ext.Template} The new template
|
|
*/
|
|
createTemplate : function(o){
|
|
var html = Ext.DomHelper.createHtml(o);
|
|
return new Ext.Template(html);
|
|
},
|
|
|
|
/** True to force the use of DOM instead of html fragments @type Boolean */
|
|
useDom : false,
|
|
|
|
/**
|
|
* Creates new DOM element(s) and inserts them before el.
|
|
* @param {Mixed} el The context element
|
|
* @param {Object/String} o The DOM object spec (and children) or raw HTML blob
|
|
* @param {Boolean} returnElement (optional) true to return a Ext.Element
|
|
* @return {HTMLElement/Ext.Element} The new node
|
|
* @hide (repeat)
|
|
*/
|
|
insertBefore : function(el, o, returnElement){
|
|
return doInsert(el, o, returnElement, beforebegin);
|
|
},
|
|
|
|
/**
|
|
* Creates new DOM element(s) and inserts them after el.
|
|
* @param {Mixed} el The context element
|
|
* @param {Object} o The DOM object spec (and children)
|
|
* @param {Boolean} returnElement (optional) true to return a Ext.Element
|
|
* @return {HTMLElement/Ext.Element} The new node
|
|
* @hide (repeat)
|
|
*/
|
|
insertAfter : function(el, o, returnElement){
|
|
return doInsert(el, o, returnElement, afterend, 'nextSibling');
|
|
},
|
|
|
|
/**
|
|
* Creates new DOM element(s) and inserts them as the first child of el.
|
|
* @param {Mixed} el The context element
|
|
* @param {Object/String} o The DOM object spec (and children) or raw HTML blob
|
|
* @param {Boolean} returnElement (optional) true to return a Ext.Element
|
|
* @return {HTMLElement/Ext.Element} The new node
|
|
* @hide (repeat)
|
|
*/
|
|
insertFirst : function(el, o, returnElement){
|
|
return doInsert(el, o, returnElement, afterbegin, 'firstChild');
|
|
},
|
|
|
|
/**
|
|
* Creates new DOM element(s) and appends them to el.
|
|
* @param {Mixed} el The context element
|
|
* @param {Object/String} o The DOM object spec (and children) or raw HTML blob
|
|
* @param {Boolean} returnElement (optional) true to return a Ext.Element
|
|
* @return {HTMLElement/Ext.Element} The new node
|
|
* @hide (repeat)
|
|
*/
|
|
append: function(el, o, returnElement){
|
|
return doInsert(el, o, returnElement, beforeend, '', true);
|
|
},
|
|
|
|
/**
|
|
* Creates new DOM element(s) without inserting them to the document.
|
|
* @param {Object/String} o The DOM object spec (and children) or raw HTML blob
|
|
* @return {HTMLElement} The new uninserted node
|
|
*/
|
|
createDom: createDom
|
|
};
|
|
return pub;
|
|
}());
|
|
/**
|
|
* @class Ext.Template
|
|
*/
|
|
Ext.apply(Ext.Template.prototype, {
|
|
/**
|
|
* @cfg {Boolean} disableFormats Specify <tt>true</tt> to disable format
|
|
* functions in the template. If the template does not contain
|
|
* {@link Ext.util.Format format functions}, setting <code>disableFormats</code>
|
|
* to true will reduce <code>{@link #apply}</code> time. Defaults to <tt>false</tt>.
|
|
* <pre><code>
|
|
var t = new Ext.Template(
|
|
'<div name="{id}">',
|
|
'<span class="{cls}">{name} {value}</span>',
|
|
'</div>',
|
|
{
|
|
compiled: true, // {@link #compile} immediately
|
|
disableFormats: true // reduce <code>{@link #apply}</code> time since no formatting
|
|
}
|
|
);
|
|
* </code></pre>
|
|
* For a list of available format functions, see {@link Ext.util.Format}.
|
|
*/
|
|
disableFormats : false,
|
|
/**
|
|
* See <code>{@link #disableFormats}</code>.
|
|
* @type Boolean
|
|
* @property disableFormats
|
|
*/
|
|
|
|
/**
|
|
* The regular expression used to match template variables
|
|
* @type RegExp
|
|
* @property
|
|
* @hide repeat doc
|
|
*/
|
|
re : /\{([\w\-]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?\}/g,
|
|
argsRe : /^\s*['"](.*)["']\s*$/,
|
|
compileARe : /\\/g,
|
|
compileBRe : /(\r\n|\n)/g,
|
|
compileCRe : /'/g,
|
|
|
|
/**
|
|
* Returns an HTML fragment of this template with the specified values applied.
|
|
* @param {Object/Array} values The template values. Can be an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'})
|
|
* @return {String} The HTML fragment
|
|
* @hide repeat doc
|
|
*/
|
|
applyTemplate : function(values){
|
|
var me = this,
|
|
useF = me.disableFormats !== true,
|
|
fm = Ext.util.Format,
|
|
tpl = me;
|
|
|
|
if(me.compiled){
|
|
return me.compiled(values);
|
|
}
|
|
function fn(m, name, format, args){
|
|
if (format && useF) {
|
|
if (format.substr(0, 5) == "this.") {
|
|
return tpl.call(format.substr(5), values[name], values);
|
|
} else {
|
|
if (args) {
|
|
// quoted values are required for strings in compiled templates,
|
|
// but for non compiled we need to strip them
|
|
// quoted reversed for jsmin
|
|
var re = me.argsRe;
|
|
args = args.split(',');
|
|
for(var i = 0, len = args.length; i < len; i++){
|
|
args[i] = args[i].replace(re, "$1");
|
|
}
|
|
args = [values[name]].concat(args);
|
|
} else {
|
|
args = [values[name]];
|
|
}
|
|
return fm[format].apply(fm, args);
|
|
}
|
|
} else {
|
|
return values[name] !== undefined ? values[name] : "";
|
|
}
|
|
}
|
|
return me.html.replace(me.re, fn);
|
|
},
|
|
|
|
/**
|
|
* Compiles the template into an internal function, eliminating the RegEx overhead.
|
|
* @return {Ext.Template} this
|
|
* @hide repeat doc
|
|
*/
|
|
compile : function(){
|
|
var me = this,
|
|
fm = Ext.util.Format,
|
|
useF = me.disableFormats !== true,
|
|
sep = Ext.isGecko ? "+" : ",",
|
|
body;
|
|
|
|
function fn(m, name, format, args){
|
|
if(format && useF){
|
|
args = args ? ',' + args : "";
|
|
if(format.substr(0, 5) != "this."){
|
|
format = "fm." + format + '(';
|
|
}else{
|
|
format = 'this.call("'+ format.substr(5) + '", ';
|
|
args = ", values";
|
|
}
|
|
}else{
|
|
args= ''; format = "(values['" + name + "'] == undefined ? '' : ";
|
|
}
|
|
return "'"+ sep + format + "values['" + name + "']" + args + ")"+sep+"'";
|
|
}
|
|
|
|
// branched to use + in gecko and [].join() in others
|
|
if(Ext.isGecko){
|
|
body = "this.compiled = function(values){ return '" +
|
|
me.html.replace(me.compileARe, '\\\\').replace(me.compileBRe, '\\n').replace(me.compileCRe, "\\'").replace(me.re, fn) +
|
|
"';};";
|
|
}else{
|
|
body = ["this.compiled = function(values){ return ['"];
|
|
body.push(me.html.replace(me.compileARe, '\\\\').replace(me.compileBRe, '\\n').replace(me.compileCRe, "\\'").replace(me.re, fn));
|
|
body.push("'].join('');};");
|
|
body = body.join('');
|
|
}
|
|
eval(body);
|
|
return me;
|
|
},
|
|
|
|
// private function used to call members
|
|
call : function(fnName, value, allValues){
|
|
return this[fnName](value, allValues);
|
|
}
|
|
});
|
|
Ext.Template.prototype.apply = Ext.Template.prototype.applyTemplate;
|
|
/**
|
|
* @class Ext.util.Functions
|
|
* @singleton
|
|
*/
|
|
Ext.util.Functions = {
|
|
/**
|
|
* Creates an interceptor function. The passed function is called before the original one. If it returns false,
|
|
* the original one is not called. The resulting function returns the results of the original function.
|
|
* The passed function is called with the parameters of the original function. Example usage:
|
|
* <pre><code>
|
|
var sayHi = function(name){
|
|
alert('Hi, ' + name);
|
|
}
|
|
|
|
sayHi('Fred'); // alerts "Hi, Fred"
|
|
|
|
// create a new function that validates input without
|
|
// directly modifying the original function:
|
|
var sayHiToFriend = Ext.createInterceptor(sayHi, function(name){
|
|
return name == 'Brian';
|
|
});
|
|
|
|
sayHiToFriend('Fred'); // no alert
|
|
sayHiToFriend('Brian'); // alerts "Hi, Brian"
|
|
</code></pre>
|
|
* @param {Function} origFn The original function.
|
|
* @param {Function} newFn The function to call before the original
|
|
* @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the passed function is executed.
|
|
* <b>If omitted, defaults to the scope in which the original function is called or the browser window.</b>
|
|
* @return {Function} The new function
|
|
*/
|
|
createInterceptor: function(origFn, newFn, scope) {
|
|
var method = origFn;
|
|
if (!Ext.isFunction(newFn)) {
|
|
return origFn;
|
|
}
|
|
else {
|
|
return function() {
|
|
var me = this,
|
|
args = arguments;
|
|
newFn.target = me;
|
|
newFn.method = origFn;
|
|
return (newFn.apply(scope || me || window, args) !== false) ?
|
|
origFn.apply(me || window, args) :
|
|
null;
|
|
};
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Creates a delegate (callback) that sets the scope to obj.
|
|
* Call directly on any function. Example: <code>Ext.createDelegate(this.myFunction, this, [arg1, arg2])</code>
|
|
* Will create a function that is automatically scoped to obj so that the <tt>this</tt> variable inside the
|
|
* callback points to obj. Example usage:
|
|
* <pre><code>
|
|
var sayHi = function(name){
|
|
// Note this use of "this.text" here. This function expects to
|
|
// execute within a scope that contains a text property. In this
|
|
// example, the "this" variable is pointing to the btn object that
|
|
// was passed in createDelegate below.
|
|
alert('Hi, ' + name + '. You clicked the "' + this.text + '" button.');
|
|
}
|
|
|
|
var btn = new Ext.Button({
|
|
text: 'Say Hi',
|
|
renderTo: Ext.getBody()
|
|
});
|
|
|
|
// This callback will execute in the scope of the
|
|
// button instance. Clicking the button alerts
|
|
// "Hi, Fred. You clicked the "Say Hi" button."
|
|
btn.on('click', Ext.createDelegate(sayHi, btn, ['Fred']));
|
|
</code></pre>
|
|
* @param {Function} fn The function to delegate.
|
|
* @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the function is executed.
|
|
* <b>If omitted, defaults to the browser window.</b>
|
|
* @param {Array} args (optional) Overrides arguments for the call. (Defaults to the arguments passed by the caller)
|
|
* @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
|
|
* if a number the args are inserted at the specified position
|
|
* @return {Function} The new function
|
|
*/
|
|
createDelegate: function(fn, obj, args, appendArgs) {
|
|
if (!Ext.isFunction(fn)) {
|
|
return fn;
|
|
}
|
|
return function() {
|
|
var callArgs = args || arguments;
|
|
if (appendArgs === true) {
|
|
callArgs = Array.prototype.slice.call(arguments, 0);
|
|
callArgs = callArgs.concat(args);
|
|
}
|
|
else if (Ext.isNumber(appendArgs)) {
|
|
callArgs = Array.prototype.slice.call(arguments, 0);
|
|
// copy arguments first
|
|
var applyArgs = [appendArgs, 0].concat(args);
|
|
// create method call params
|
|
Array.prototype.splice.apply(callArgs, applyArgs);
|
|
// splice them in
|
|
}
|
|
return fn.apply(obj || window, callArgs);
|
|
};
|
|
},
|
|
|
|
/**
|
|
* Calls this function after the number of millseconds specified, optionally in a specific scope. Example usage:
|
|
* <pre><code>
|
|
var sayHi = function(name){
|
|
alert('Hi, ' + name);
|
|
}
|
|
|
|
// executes immediately:
|
|
sayHi('Fred');
|
|
|
|
// executes after 2 seconds:
|
|
Ext.defer(sayHi, 2000, this, ['Fred']);
|
|
|
|
// this syntax is sometimes useful for deferring
|
|
// execution of an anonymous function:
|
|
Ext.defer(function(){
|
|
alert('Anonymous');
|
|
}, 100);
|
|
</code></pre>
|
|
* @param {Function} fn The function to defer.
|
|
* @param {Number} millis The number of milliseconds for the setTimeout call (if less than or equal to 0 the function is executed immediately)
|
|
* @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the function is executed.
|
|
* <b>If omitted, defaults to the browser window.</b>
|
|
* @param {Array} args (optional) Overrides arguments for the call. (Defaults to the arguments passed by the caller)
|
|
* @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
|
|
* if a number the args are inserted at the specified position
|
|
* @return {Number} The timeout id that can be used with clearTimeout
|
|
*/
|
|
defer: function(fn, millis, obj, args, appendArgs) {
|
|
fn = Ext.util.Functions.createDelegate(fn, obj, args, appendArgs);
|
|
if (millis > 0) {
|
|
return setTimeout(fn, millis);
|
|
}
|
|
fn();
|
|
return 0;
|
|
},
|
|
|
|
|
|
/**
|
|
* Create a combined function call sequence of the original function + the passed function.
|
|
* The resulting function returns the results of the original function.
|
|
* The passed fcn is called with the parameters of the original function. Example usage:
|
|
*
|
|
|
|
var sayHi = function(name){
|
|
alert('Hi, ' + name);
|
|
}
|
|
|
|
sayHi('Fred'); // alerts "Hi, Fred"
|
|
|
|
var sayGoodbye = Ext.createSequence(sayHi, function(name){
|
|
alert('Bye, ' + name);
|
|
});
|
|
|
|
sayGoodbye('Fred'); // both alerts show
|
|
|
|
* @param {Function} origFn The original function.
|
|
* @param {Function} newFn The function to sequence
|
|
* @param {Object} scope (optional) The scope (this reference) in which the passed function is executed.
|
|
* If omitted, defaults to the scope in which the original function is called or the browser window.
|
|
* @return {Function} The new function
|
|
*/
|
|
createSequence: function(origFn, newFn, scope) {
|
|
if (!Ext.isFunction(newFn)) {
|
|
return origFn;
|
|
}
|
|
else {
|
|
return function() {
|
|
var retval = origFn.apply(this || window, arguments);
|
|
newFn.apply(scope || this || window, arguments);
|
|
return retval;
|
|
};
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Shorthand for {@link Ext.util.Functions#defer}
|
|
* @param {Function} fn The function to defer.
|
|
* @param {Number} millis The number of milliseconds for the setTimeout call (if less than or equal to 0 the function is executed immediately)
|
|
* @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the function is executed.
|
|
* <b>If omitted, defaults to the browser window.</b>
|
|
* @param {Array} args (optional) Overrides arguments for the call. (Defaults to the arguments passed by the caller)
|
|
* @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
|
|
* if a number the args are inserted at the specified position
|
|
* @return {Number} The timeout id that can be used with clearTimeout
|
|
* @member Ext
|
|
* @method defer
|
|
*/
|
|
|
|
Ext.defer = Ext.util.Functions.defer;
|
|
|
|
/**
|
|
* Shorthand for {@link Ext.util.Functions#createInterceptor}
|
|
* @param {Function} origFn The original function.
|
|
* @param {Function} newFn The function to call before the original
|
|
* @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the passed function is executed.
|
|
* <b>If omitted, defaults to the scope in which the original function is called or the browser window.</b>
|
|
* @return {Function} The new function
|
|
* @member Ext
|
|
* @method createInterceptor
|
|
*/
|
|
|
|
Ext.createInterceptor = Ext.util.Functions.createInterceptor;
|
|
|
|
/**
|
|
* Shorthand for {@link Ext.util.Functions#createSequence}
|
|
* @param {Function} origFn The original function.
|
|
* @param {Function} newFn The function to sequence
|
|
* @param {Object} scope (optional) The scope (this reference) in which the passed function is executed.
|
|
* If omitted, defaults to the scope in which the original function is called or the browser window.
|
|
* @return {Function} The new function
|
|
* @member Ext
|
|
* @method createSequence
|
|
*/
|
|
|
|
Ext.createSequence = Ext.util.Functions.createSequence;
|
|
|
|
/**
|
|
* Shorthand for {@link Ext.util.Functions#createDelegate}
|
|
* @param {Function} fn The function to delegate.
|
|
* @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the function is executed.
|
|
* <b>If omitted, defaults to the browser window.</b>
|
|
* @param {Array} args (optional) Overrides arguments for the call. (Defaults to the arguments passed by the caller)
|
|
* @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
|
|
* if a number the args are inserted at the specified position
|
|
* @return {Function} The new function
|
|
* @member Ext
|
|
* @method createDelegate
|
|
*/
|
|
Ext.createDelegate = Ext.util.Functions.createDelegate;
|
|
/**
|
|
* @class Ext.util.Observable
|
|
*/
|
|
Ext.apply(Ext.util.Observable.prototype, function(){
|
|
// this is considered experimental (along with beforeMethod, afterMethod, removeMethodListener?)
|
|
// allows for easier interceptor and sequences, including cancelling and overwriting the return value of the call
|
|
// private
|
|
function getMethodEvent(method){
|
|
var e = (this.methodEvents = this.methodEvents ||
|
|
{})[method], returnValue, v, cancel, obj = this;
|
|
|
|
if (!e) {
|
|
this.methodEvents[method] = e = {};
|
|
e.originalFn = this[method];
|
|
e.methodName = method;
|
|
e.before = [];
|
|
e.after = [];
|
|
|
|
var makeCall = function(fn, scope, args){
|
|
if((v = fn.apply(scope || obj, args)) !== undefined){
|
|
if (typeof v == 'object') {
|
|
if(v.returnValue !== undefined){
|
|
returnValue = v.returnValue;
|
|
}else{
|
|
returnValue = v;
|
|
}
|
|
cancel = !!v.cancel;
|
|
}
|
|
else
|
|
if (v === false) {
|
|
cancel = true;
|
|
}
|
|
else {
|
|
returnValue = v;
|
|
}
|
|
}
|
|
};
|
|
|
|
this[method] = function(){
|
|
var args = Array.prototype.slice.call(arguments, 0),
|
|
b;
|
|
returnValue = v = undefined;
|
|
cancel = false;
|
|
|
|
for(var i = 0, len = e.before.length; i < len; i++){
|
|
b = e.before[i];
|
|
makeCall(b.fn, b.scope, args);
|
|
if (cancel) {
|
|
return returnValue;
|
|
}
|
|
}
|
|
|
|
if((v = e.originalFn.apply(obj, args)) !== undefined){
|
|
returnValue = v;
|
|
}
|
|
|
|
for(var i = 0, len = e.after.length; i < len; i++){
|
|
b = e.after[i];
|
|
makeCall(b.fn, b.scope, args);
|
|
if (cancel) {
|
|
return returnValue;
|
|
}
|
|
}
|
|
return returnValue;
|
|
};
|
|
}
|
|
return e;
|
|
}
|
|
|
|
return {
|
|
// these are considered experimental
|
|
// allows for easier interceptor and sequences, including cancelling and overwriting the return value of the call
|
|
// adds an 'interceptor' called before the original method
|
|
beforeMethod : function(method, fn, scope){
|
|
getMethodEvent.call(this, method).before.push({
|
|
fn: fn,
|
|
scope: scope
|
|
});
|
|
},
|
|
|
|
// adds a 'sequence' called after the original method
|
|
afterMethod : function(method, fn, scope){
|
|
getMethodEvent.call(this, method).after.push({
|
|
fn: fn,
|
|
scope: scope
|
|
});
|
|
},
|
|
|
|
removeMethodListener: function(method, fn, scope){
|
|
var e = this.getMethodEvent(method);
|
|
for(var i = 0, len = e.before.length; i < len; i++){
|
|
if(e.before[i].fn == fn && e.before[i].scope == scope){
|
|
e.before.splice(i, 1);
|
|
return;
|
|
}
|
|
}
|
|
for(var i = 0, len = e.after.length; i < len; i++){
|
|
if(e.after[i].fn == fn && e.after[i].scope == scope){
|
|
e.after.splice(i, 1);
|
|
return;
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Relays selected events from the specified Observable as if the events were fired by <tt><b>this</b></tt>.
|
|
* @param {Object} o The Observable whose events this object is to relay.
|
|
* @param {Array} events Array of event names to relay.
|
|
*/
|
|
relayEvents : function(o, events){
|
|
var me = this;
|
|
function createHandler(ename){
|
|
return function(){
|
|
return me.fireEvent.apply(me, [ename].concat(Array.prototype.slice.call(arguments, 0)));
|
|
};
|
|
}
|
|
for(var i = 0, len = events.length; i < len; i++){
|
|
var ename = events[i];
|
|
me.events[ename] = me.events[ename] || true;
|
|
o.on(ename, createHandler(ename), me);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* <p>Enables events fired by this Observable to bubble up an owner hierarchy by calling
|
|
* <code>this.getBubbleTarget()</code> if present. There is no implementation in the Observable base class.</p>
|
|
* <p>This is commonly used by Ext.Components to bubble events to owner Containers. See {@link Ext.Component.getBubbleTarget}. The default
|
|
* implementation in Ext.Component returns the Component's immediate owner. But if a known target is required, this can be overridden to
|
|
* access the required target more quickly.</p>
|
|
* <p>Example:</p><pre><code>
|
|
Ext.override(Ext.form.Field, {
|
|
// Add functionality to Field's initComponent to enable the change event to bubble
|
|
initComponent : Ext.form.Field.prototype.initComponent.createSequence(function() {
|
|
this.enableBubble('change');
|
|
}),
|
|
|
|
// We know that we want Field's events to bubble directly to the FormPanel.
|
|
getBubbleTarget : function() {
|
|
if (!this.formPanel) {
|
|
this.formPanel = this.findParentByType('form');
|
|
}
|
|
return this.formPanel;
|
|
}
|
|
});
|
|
|
|
var myForm = new Ext.formPanel({
|
|
title: 'User Details',
|
|
items: [{
|
|
...
|
|
}],
|
|
listeners: {
|
|
change: function() {
|
|
// Title goes red if form has been modified.
|
|
myForm.header.setStyle('color', 'red');
|
|
}
|
|
}
|
|
});
|
|
</code></pre>
|
|
* @param {String/Array} events The event name to bubble, or an Array of event names.
|
|
*/
|
|
enableBubble : function(events){
|
|
var me = this;
|
|
if(!Ext.isEmpty(events)){
|
|
events = Ext.isArray(events) ? events : Array.prototype.slice.call(arguments, 0);
|
|
for(var i = 0, len = events.length; i < len; i++){
|
|
var ename = events[i];
|
|
ename = ename.toLowerCase();
|
|
var ce = me.events[ename] || true;
|
|
if (typeof ce == 'boolean') {
|
|
ce = new Ext.util.Event(me, ename);
|
|
me.events[ename] = ce;
|
|
}
|
|
ce.bubble = true;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}());
|
|
|
|
|
|
/**
|
|
* Starts capture on the specified Observable. All events will be passed
|
|
* to the supplied function with the event name + standard signature of the event
|
|
* <b>before</b> the event is fired. If the supplied function returns false,
|
|
* the event will not fire.
|
|
* @param {Observable} o The Observable to capture events from.
|
|
* @param {Function} fn The function to call when an event is fired.
|
|
* @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the Observable firing the event.
|
|
* @static
|
|
*/
|
|
Ext.util.Observable.capture = function(o, fn, scope){
|
|
o.fireEvent = o.fireEvent.createInterceptor(fn, scope);
|
|
};
|
|
|
|
|
|
/**
|
|
* Sets observability on the passed class constructor.<p>
|
|
* <p>This makes any event fired on any instance of the passed class also fire a single event through
|
|
* the <i>class</i> allowing for central handling of events on many instances at once.</p>
|
|
* <p>Usage:</p><pre><code>
|
|
Ext.util.Observable.observeClass(Ext.data.Connection);
|
|
Ext.data.Connection.on('beforerequest', function(con, options) {
|
|
console.log('Ajax request made to ' + options.url);
|
|
});</code></pre>
|
|
* @param {Function} c The class constructor to make observable.
|
|
* @param {Object} listeners An object containing a series of listeners to add. See {@link #addListener}.
|
|
* @static
|
|
*/
|
|
Ext.util.Observable.observeClass = function(c, listeners){
|
|
if(c){
|
|
if(!c.fireEvent){
|
|
Ext.apply(c, new Ext.util.Observable());
|
|
Ext.util.Observable.capture(c.prototype, c.fireEvent, c);
|
|
}
|
|
if(typeof listeners == 'object'){
|
|
c.on(listeners);
|
|
}
|
|
return c;
|
|
}
|
|
};
|
|
/**
|
|
* @class Ext.EventManager
|
|
*/
|
|
Ext.apply(Ext.EventManager, function(){
|
|
var resizeEvent,
|
|
resizeTask,
|
|
textEvent,
|
|
textSize,
|
|
D = Ext.lib.Dom,
|
|
propRe = /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate)$/,
|
|
unload = Ext.EventManager._unload,
|
|
curWidth = 0,
|
|
curHeight = 0,
|
|
// note 1: IE fires ONLY the keydown event on specialkey autorepeat
|
|
// note 2: Safari < 3.1, Gecko (Mac/Linux) & Opera fire only the keypress event on specialkey autorepeat
|
|
// (research done by @Jan Wolter at http://unixpapa.com/js/key.html)
|
|
useKeydown = Ext.isWebKit ?
|
|
Ext.num(navigator.userAgent.match(/AppleWebKit\/(\d+)/)[1]) >= 525 :
|
|
!((Ext.isGecko && !Ext.isWindows) || Ext.isOpera);
|
|
|
|
return {
|
|
_unload: function(){
|
|
Ext.EventManager.un(window, "resize", this.fireWindowResize, this);
|
|
unload.call(Ext.EventManager);
|
|
},
|
|
|
|
// private
|
|
doResizeEvent: function(){
|
|
var h = D.getViewHeight(),
|
|
w = D.getViewWidth();
|
|
|
|
//whacky problem in IE where the resize event will fire even though the w/h are the same.
|
|
if(curHeight != h || curWidth != w){
|
|
resizeEvent.fire(curWidth = w, curHeight = h);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Adds a listener to be notified when the browser window is resized and provides resize event buffering (100 milliseconds),
|
|
* passes new viewport width and height to handlers.
|
|
* @param {Function} fn The handler function the window resize event invokes.
|
|
* @param {Object} scope The scope (<code>this</code> reference) in which the handler function executes. Defaults to the browser window.
|
|
* @param {boolean} options Options object as passed to {@link Ext.Element#addListener}
|
|
*/
|
|
onWindowResize : function(fn, scope, options){
|
|
if(!resizeEvent){
|
|
resizeEvent = new Ext.util.Event();
|
|
resizeTask = new Ext.util.DelayedTask(this.doResizeEvent);
|
|
Ext.EventManager.on(window, "resize", this.fireWindowResize, this);
|
|
}
|
|
resizeEvent.addListener(fn, scope, options);
|
|
},
|
|
|
|
// exposed only to allow manual firing
|
|
fireWindowResize : function(){
|
|
if(resizeEvent){
|
|
resizeTask.delay(100);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Adds a listener to be notified when the user changes the active text size. Handler gets called with 2 params, the old size and the new size.
|
|
* @param {Function} fn The function the event invokes.
|
|
* @param {Object} scope The scope (<code>this</code> reference) in which the handler function executes. Defaults to the browser window.
|
|
* @param {boolean} options Options object as passed to {@link Ext.Element#addListener}
|
|
*/
|
|
onTextResize : function(fn, scope, options){
|
|
if(!textEvent){
|
|
textEvent = new Ext.util.Event();
|
|
var textEl = new Ext.Element(document.createElement('div'));
|
|
textEl.dom.className = 'x-text-resize';
|
|
textEl.dom.innerHTML = 'X';
|
|
textEl.appendTo(document.body);
|
|
textSize = textEl.dom.offsetHeight;
|
|
setInterval(function(){
|
|
if(textEl.dom.offsetHeight != textSize){
|
|
textEvent.fire(textSize, textSize = textEl.dom.offsetHeight);
|
|
}
|
|
}, this.textResizeInterval);
|
|
}
|
|
textEvent.addListener(fn, scope, options);
|
|
},
|
|
|
|
/**
|
|
* Removes the passed window resize listener.
|
|
* @param {Function} fn The method the event invokes
|
|
* @param {Object} scope The scope of handler
|
|
*/
|
|
removeResizeListener : function(fn, scope){
|
|
if(resizeEvent){
|
|
resizeEvent.removeListener(fn, scope);
|
|
}
|
|
},
|
|
|
|
// private
|
|
fireResize : function(){
|
|
if(resizeEvent){
|
|
resizeEvent.fire(D.getViewWidth(), D.getViewHeight());
|
|
}
|
|
},
|
|
|
|
/**
|
|
* The frequency, in milliseconds, to check for text resize events (defaults to 50)
|
|
*/
|
|
textResizeInterval : 50,
|
|
|
|
/**
|
|
* Url used for onDocumentReady with using SSL (defaults to Ext.SSL_SECURE_URL)
|
|
*/
|
|
ieDeferSrc : false,
|
|
|
|
// protected, short accessor for useKeydown
|
|
getKeyEvent : function(){
|
|
return useKeydown ? 'keydown' : 'keypress';
|
|
},
|
|
|
|
// protected for use inside the framework
|
|
// detects whether we should use keydown or keypress based on the browser.
|
|
useKeydown: useKeydown
|
|
};
|
|
}());
|
|
|
|
Ext.EventManager.on = Ext.EventManager.addListener;
|
|
|
|
|
|
Ext.apply(Ext.EventObjectImpl.prototype, {
|
|
/** Key constant @type Number */
|
|
BACKSPACE: 8,
|
|
/** Key constant @type Number */
|
|
TAB: 9,
|
|
/** Key constant @type Number */
|
|
NUM_CENTER: 12,
|
|
/** Key constant @type Number */
|
|
ENTER: 13,
|
|
/** Key constant @type Number */
|
|
RETURN: 13,
|
|
/** Key constant @type Number */
|
|
SHIFT: 16,
|
|
/** Key constant @type Number */
|
|
CTRL: 17,
|
|
CONTROL : 17, // legacy
|
|
/** Key constant @type Number */
|
|
ALT: 18,
|
|
/** Key constant @type Number */
|
|
PAUSE: 19,
|
|
/** Key constant @type Number */
|
|
CAPS_LOCK: 20,
|
|
/** Key constant @type Number */
|
|
ESC: 27,
|
|
/** Key constant @type Number */
|
|
SPACE: 32,
|
|
/** Key constant @type Number */
|
|
PAGE_UP: 33,
|
|
PAGEUP : 33, // legacy
|
|
/** Key constant @type Number */
|
|
PAGE_DOWN: 34,
|
|
PAGEDOWN : 34, // legacy
|
|
/** Key constant @type Number */
|
|
END: 35,
|
|
/** Key constant @type Number */
|
|
HOME: 36,
|
|
/** Key constant @type Number */
|
|
LEFT: 37,
|
|
/** Key constant @type Number */
|
|
UP: 38,
|
|
/** Key constant @type Number */
|
|
RIGHT: 39,
|
|
/** Key constant @type Number */
|
|
DOWN: 40,
|
|
/** Key constant @type Number */
|
|
PRINT_SCREEN: 44,
|
|
/** Key constant @type Number */
|
|
INSERT: 45,
|
|
/** Key constant @type Number */
|
|
DELETE: 46,
|
|
/** Key constant @type Number */
|
|
ZERO: 48,
|
|
/** Key constant @type Number */
|
|
ONE: 49,
|
|
/** Key constant @type Number */
|
|
TWO: 50,
|
|
/** Key constant @type Number */
|
|
THREE: 51,
|
|
/** Key constant @type Number */
|
|
FOUR: 52,
|
|
/** Key constant @type Number */
|
|
FIVE: 53,
|
|
/** Key constant @type Number */
|
|
SIX: 54,
|
|
/** Key constant @type Number */
|
|
SEVEN: 55,
|
|
/** Key constant @type Number */
|
|
EIGHT: 56,
|
|
/** Key constant @type Number */
|
|
NINE: 57,
|
|
/** Key constant @type Number */
|
|
A: 65,
|
|
/** Key constant @type Number */
|
|
B: 66,
|
|
/** Key constant @type Number */
|
|
C: 67,
|
|
/** Key constant @type Number */
|
|
D: 68,
|
|
/** Key constant @type Number */
|
|
E: 69,
|
|
/** Key constant @type Number */
|
|
F: 70,
|
|
/** Key constant @type Number */
|
|
G: 71,
|
|
/** Key constant @type Number */
|
|
H: 72,
|
|
/** Key constant @type Number */
|
|
I: 73,
|
|
/** Key constant @type Number */
|
|
J: 74,
|
|
/** Key constant @type Number */
|
|
K: 75,
|
|
/** Key constant @type Number */
|
|
L: 76,
|
|
/** Key constant @type Number */
|
|
M: 77,
|
|
/** Key constant @type Number */
|
|
N: 78,
|
|
/** Key constant @type Number */
|
|
O: 79,
|
|
/** Key constant @type Number */
|
|
P: 80,
|
|
/** Key constant @type Number */
|
|
Q: 81,
|
|
/** Key constant @type Number */
|
|
R: 82,
|
|
/** Key constant @type Number */
|
|
S: 83,
|
|
/** Key constant @type Number */
|
|
T: 84,
|
|
/** Key constant @type Number */
|
|
U: 85,
|
|
/** Key constant @type Number */
|
|
V: 86,
|
|
/** Key constant @type Number */
|
|
W: 87,
|
|
/** Key constant @type Number */
|
|
X: 88,
|
|
/** Key constant @type Number */
|
|
Y: 89,
|
|
/** Key constant @type Number */
|
|
Z: 90,
|
|
/** Key constant @type Number */
|
|
CONTEXT_MENU: 93,
|
|
/** Key constant @type Number */
|
|
NUM_ZERO: 96,
|
|
/** Key constant @type Number */
|
|
NUM_ONE: 97,
|
|
/** Key constant @type Number */
|
|
NUM_TWO: 98,
|
|
/** Key constant @type Number */
|
|
NUM_THREE: 99,
|
|
/** Key constant @type Number */
|
|
NUM_FOUR: 100,
|
|
/** Key constant @type Number */
|
|
NUM_FIVE: 101,
|
|
/** Key constant @type Number */
|
|
NUM_SIX: 102,
|
|
/** Key constant @type Number */
|
|
NUM_SEVEN: 103,
|
|
/** Key constant @type Number */
|
|
NUM_EIGHT: 104,
|
|
/** Key constant @type Number */
|
|
NUM_NINE: 105,
|
|
/** Key constant @type Number */
|
|
NUM_MULTIPLY: 106,
|
|
/** Key constant @type Number */
|
|
NUM_PLUS: 107,
|
|
/** Key constant @type Number */
|
|
NUM_MINUS: 109,
|
|
/** Key constant @type Number */
|
|
NUM_PERIOD: 110,
|
|
/** Key constant @type Number */
|
|
NUM_DIVISION: 111,
|
|
/** Key constant @type Number */
|
|
F1: 112,
|
|
/** Key constant @type Number */
|
|
F2: 113,
|
|
/** Key constant @type Number */
|
|
F3: 114,
|
|
/** Key constant @type Number */
|
|
F4: 115,
|
|
/** Key constant @type Number */
|
|
F5: 116,
|
|
/** Key constant @type Number */
|
|
F6: 117,
|
|
/** Key constant @type Number */
|
|
F7: 118,
|
|
/** Key constant @type Number */
|
|
F8: 119,
|
|
/** Key constant @type Number */
|
|
F9: 120,
|
|
/** Key constant @type Number */
|
|
F10: 121,
|
|
/** Key constant @type Number */
|
|
F11: 122,
|
|
/** Key constant @type Number */
|
|
F12: 123,
|
|
|
|
/** @private */
|
|
isNavKeyPress : function(){
|
|
var me = this,
|
|
k = this.normalizeKey(me.keyCode);
|
|
return (k >= 33 && k <= 40) || // Page Up/Down, End, Home, Left, Up, Right, Down
|
|
k == me.RETURN ||
|
|
k == me.TAB ||
|
|
k == me.ESC;
|
|
},
|
|
|
|
isSpecialKey : function(){
|
|
var k = this.normalizeKey(this.keyCode);
|
|
return (this.type == 'keypress' && this.ctrlKey) ||
|
|
this.isNavKeyPress() ||
|
|
(k == this.BACKSPACE) || // Backspace
|
|
(k >= 16 && k <= 20) || // Shift, Ctrl, Alt, Pause, Caps Lock
|
|
(k >= 44 && k <= 46); // Print Screen, Insert, Delete
|
|
},
|
|
|
|
getPoint : function(){
|
|
return new Ext.lib.Point(this.xy[0], this.xy[1]);
|
|
},
|
|
|
|
/**
|
|
* Returns true if the control, meta, shift or alt key was pressed during this event.
|
|
* @return {Boolean}
|
|
*/
|
|
hasModifier : function(){
|
|
return ((this.ctrlKey || this.altKey) || this.shiftKey);
|
|
}
|
|
});/**
|
|
* @class Ext.Element
|
|
*/
|
|
Ext.Element.addMethods({
|
|
/**
|
|
* Stops the specified event(s) from bubbling and optionally prevents the default action
|
|
* @param {String/Array} eventName an event / array of events to stop from bubbling
|
|
* @param {Boolean} preventDefault (optional) true to prevent the default action too
|
|
* @return {Ext.Element} this
|
|
*/
|
|
swallowEvent : function(eventName, preventDefault) {
|
|
var me = this;
|
|
function fn(e) {
|
|
e.stopPropagation();
|
|
if (preventDefault) {
|
|
e.preventDefault();
|
|
}
|
|
}
|
|
|
|
if (Ext.isArray(eventName)) {
|
|
Ext.each(eventName, function(e) {
|
|
me.on(e, fn);
|
|
});
|
|
return me;
|
|
}
|
|
me.on(eventName, fn);
|
|
return me;
|
|
},
|
|
|
|
/**
|
|
* Create an event handler on this element such that when the event fires and is handled by this element,
|
|
* it will be relayed to another object (i.e., fired again as if it originated from that object instead).
|
|
* @param {String} eventName The type of event to relay
|
|
* @param {Object} object Any object that extends {@link Ext.util.Observable} that will provide the context
|
|
* for firing the relayed event
|
|
*/
|
|
relayEvent : function(eventName, observable) {
|
|
this.on(eventName, function(e) {
|
|
observable.fireEvent(eventName, e);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Removes worthless text nodes
|
|
* @param {Boolean} forceReclean (optional) By default the element
|
|
* keeps track if it has been cleaned already so
|
|
* you can call this over and over. However, if you update the element and
|
|
* need to force a reclean, you can pass true.
|
|
*/
|
|
clean : function(forceReclean) {
|
|
var me = this,
|
|
dom = me.dom,
|
|
n = dom.firstChild,
|
|
ni = -1;
|
|
|
|
if (Ext.Element.data(dom, 'isCleaned') && forceReclean !== true) {
|
|
return me;
|
|
}
|
|
|
|
while (n) {
|
|
var nx = n.nextSibling;
|
|
if (n.nodeType == 3 && !(/\S/.test(n.nodeValue))) {
|
|
dom.removeChild(n);
|
|
} else {
|
|
n.nodeIndex = ++ni;
|
|
}
|
|
n = nx;
|
|
}
|
|
|
|
Ext.Element.data(dom, 'isCleaned', true);
|
|
return me;
|
|
},
|
|
|
|
/**
|
|
* Direct access to the Updater {@link Ext.Updater#update} method. The method takes the same object
|
|
* parameter as {@link Ext.Updater#update}
|
|
* @return {Ext.Element} this
|
|
*/
|
|
load : function() {
|
|
var updateManager = this.getUpdater();
|
|
updateManager.update.apply(updateManager, arguments);
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Gets this element's {@link Ext.Updater Updater}
|
|
* @return {Ext.Updater} The Updater
|
|
*/
|
|
getUpdater : function() {
|
|
return this.updateManager || (this.updateManager = new Ext.Updater(this));
|
|
},
|
|
|
|
/**
|
|
* Update the innerHTML of this element, optionally searching for and processing scripts
|
|
* @param {String} html The new HTML
|
|
* @param {Boolean} loadScripts (optional) True to look for and process scripts (defaults to false)
|
|
* @param {Function} callback (optional) For async script loading you can be notified when the update completes
|
|
* @return {Ext.Element} this
|
|
*/
|
|
update : function(html, loadScripts, callback) {
|
|
if (!this.dom) {
|
|
return this;
|
|
}
|
|
html = html || "";
|
|
|
|
if (loadScripts !== true) {
|
|
this.dom.innerHTML = html;
|
|
if (typeof callback == 'function') {
|
|
callback();
|
|
}
|
|
return this;
|
|
}
|
|
|
|
var id = Ext.id(),
|
|
dom = this.dom;
|
|
|
|
html += '<span id="' + id + '"></span>';
|
|
|
|
Ext.lib.Event.onAvailable(id, function() {
|
|
var DOC = document,
|
|
hd = DOC.getElementsByTagName("head")[0],
|
|
re = /(?:<script([^>]*)?>)((\n|\r|.)*?)(?:<\/script>)/ig,
|
|
srcRe = /\ssrc=([\'\"])(.*?)\1/i,
|
|
typeRe = /\stype=([\'\"])(.*?)\1/i,
|
|
match,
|
|
attrs,
|
|
srcMatch,
|
|
typeMatch,
|
|
el,
|
|
s;
|
|
|
|
while ((match = re.exec(html))) {
|
|
attrs = match[1];
|
|
srcMatch = attrs ? attrs.match(srcRe) : false;
|
|
if (srcMatch && srcMatch[2]) {
|
|
s = DOC.createElement("script");
|
|
s.src = srcMatch[2];
|
|
typeMatch = attrs.match(typeRe);
|
|
if (typeMatch && typeMatch[2]) {
|
|
s.type = typeMatch[2];
|
|
}
|
|
hd.appendChild(s);
|
|
} else if (match[2] && match[2].length > 0) {
|
|
if (window.execScript) {
|
|
window.execScript(match[2]);
|
|
} else {
|
|
window.eval(match[2]);
|
|
}
|
|
}
|
|
}
|
|
|
|
el = DOC.getElementById(id);
|
|
if (el) {
|
|
Ext.removeNode(el);
|
|
}
|
|
|
|
if (typeof callback == 'function') {
|
|
callback();
|
|
}
|
|
});
|
|
dom.innerHTML = html.replace(/(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)/ig, "");
|
|
return this;
|
|
},
|
|
|
|
// inherit docs, overridden so we can add removeAnchor
|
|
removeAllListeners : function() {
|
|
this.removeAnchor();
|
|
Ext.EventManager.removeAll(this.dom);
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Creates a proxy element of this element
|
|
* @param {String/Object} config The class name of the proxy element or a DomHelper config object
|
|
* @param {String/HTMLElement} renderTo (optional) The element or element id to render the proxy to (defaults to document.body)
|
|
* @param {Boolean} matchBox (optional) True to align and size the proxy to this element now (defaults to false)
|
|
* @return {Ext.Element} The new proxy element
|
|
*/
|
|
createProxy : function(config, renderTo, matchBox) {
|
|
config = (typeof config == 'object') ? config : {tag : "div", cls: config};
|
|
|
|
var me = this,
|
|
proxy = renderTo ? Ext.DomHelper.append(renderTo, config, true) :
|
|
Ext.DomHelper.insertBefore(me.dom, config, true);
|
|
|
|
if (matchBox && me.setBox && me.getBox) { // check to make sure Element.position.js is loaded
|
|
proxy.setBox(me.getBox());
|
|
}
|
|
return proxy;
|
|
}
|
|
});
|
|
|
|
Ext.Element.prototype.getUpdateManager = Ext.Element.prototype.getUpdater;
|
|
/**
|
|
* @class Ext.Element
|
|
*/
|
|
Ext.Element.addMethods({
|
|
/**
|
|
* Gets the x,y coordinates specified by the anchor position on the element.
|
|
* @param {String} anchor (optional) The specified anchor position (defaults to "c"). See {@link #alignTo}
|
|
* for details on supported anchor positions.
|
|
* @param {Boolean} local (optional) True to get the local (element top/left-relative) anchor position instead
|
|
* of page coordinates
|
|
* @param {Object} size (optional) An object containing the size to use for calculating anchor position
|
|
* {width: (target width), height: (target height)} (defaults to the element's current size)
|
|
* @return {Array} [x, y] An array containing the element's x and y coordinates
|
|
*/
|
|
getAnchorXY : function(anchor, local, s){
|
|
//Passing a different size is useful for pre-calculating anchors,
|
|
//especially for anchored animations that change the el size.
|
|
anchor = (anchor || "tl").toLowerCase();
|
|
s = s || {};
|
|
|
|
var me = this,
|
|
vp = me.dom == document.body || me.dom == document,
|
|
w = s.width || vp ? Ext.lib.Dom.getViewWidth() : me.getWidth(),
|
|
h = s.height || vp ? Ext.lib.Dom.getViewHeight() : me.getHeight(),
|
|
xy,
|
|
r = Math.round,
|
|
o = me.getXY(),
|
|
scroll = me.getScroll(),
|
|
extraX = vp ? scroll.left : !local ? o[0] : 0,
|
|
extraY = vp ? scroll.top : !local ? o[1] : 0,
|
|
hash = {
|
|
c : [r(w * 0.5), r(h * 0.5)],
|
|
t : [r(w * 0.5), 0],
|
|
l : [0, r(h * 0.5)],
|
|
r : [w, r(h * 0.5)],
|
|
b : [r(w * 0.5), h],
|
|
tl : [0, 0],
|
|
bl : [0, h],
|
|
br : [w, h],
|
|
tr : [w, 0]
|
|
};
|
|
|
|
xy = hash[anchor];
|
|
return [xy[0] + extraX, xy[1] + extraY];
|
|
},
|
|
|
|
/**
|
|
* Anchors an element to another element and realigns it when the window is resized.
|
|
* @param {Mixed} element The element to align to.
|
|
* @param {String} position The position to align to.
|
|
* @param {Array} offsets (optional) Offset the positioning by [x, y]
|
|
* @param {Boolean/Object} animate (optional) True for the default animation or a standard Element animation config object
|
|
* @param {Boolean/Number} monitorScroll (optional) True to monitor body scroll and reposition. If this parameter
|
|
* is a number, it is used as the buffer delay (defaults to 50ms).
|
|
* @param {Function} callback (optional) The function to call after the animation finishes
|
|
* @return {Ext.Element} this
|
|
*/
|
|
anchorTo : function(el, alignment, offsets, animate, monitorScroll, callback){
|
|
var me = this,
|
|
dom = me.dom,
|
|
scroll = !Ext.isEmpty(monitorScroll),
|
|
action = function(){
|
|
Ext.fly(dom).alignTo(el, alignment, offsets, animate);
|
|
Ext.callback(callback, Ext.fly(dom));
|
|
},
|
|
anchor = this.getAnchor();
|
|
|
|
// previous listener anchor, remove it
|
|
this.removeAnchor();
|
|
Ext.apply(anchor, {
|
|
fn: action,
|
|
scroll: scroll
|
|
});
|
|
|
|
Ext.EventManager.onWindowResize(action, null);
|
|
|
|
if(scroll){
|
|
Ext.EventManager.on(window, 'scroll', action, null,
|
|
{buffer: !isNaN(monitorScroll) ? monitorScroll : 50});
|
|
}
|
|
action.call(me); // align immediately
|
|
return me;
|
|
},
|
|
|
|
/**
|
|
* Remove any anchor to this element. See {@link #anchorTo}.
|
|
* @return {Ext.Element} this
|
|
*/
|
|
removeAnchor : function(){
|
|
var me = this,
|
|
anchor = this.getAnchor();
|
|
|
|
if(anchor && anchor.fn){
|
|
Ext.EventManager.removeResizeListener(anchor.fn);
|
|
if(anchor.scroll){
|
|
Ext.EventManager.un(window, 'scroll', anchor.fn);
|
|
}
|
|
delete anchor.fn;
|
|
}
|
|
return me;
|
|
},
|
|
|
|
// private
|
|
getAnchor : function(){
|
|
var data = Ext.Element.data,
|
|
dom = this.dom;
|
|
if (!dom) {
|
|
return;
|
|
}
|
|
var anchor = data(dom, '_anchor');
|
|
|
|
if(!anchor){
|
|
anchor = data(dom, '_anchor', {});
|
|
}
|
|
return anchor;
|
|
},
|
|
|
|
/**
|
|
* Gets the x,y coordinates to align this element with another element. See {@link #alignTo} for more info on the
|
|
* supported position values.
|
|
* @param {Mixed} element The element to align to.
|
|
* @param {String} position (optional, defaults to "tl-bl?") The position to align to.
|
|
* @param {Array} offsets (optional) Offset the positioning by [x, y]
|
|
* @return {Array} [x, y]
|
|
*/
|
|
getAlignToXY : function(el, p, o){
|
|
el = Ext.get(el);
|
|
|
|
if(!el || !el.dom){
|
|
throw "Element.alignToXY with an element that doesn't exist";
|
|
}
|
|
|
|
o = o || [0,0];
|
|
p = (!p || p == "?" ? "tl-bl?" : (!(/-/).test(p) && p !== "" ? "tl-" + p : p || "tl-bl")).toLowerCase();
|
|
|
|
var me = this,
|
|
d = me.dom,
|
|
a1,
|
|
a2,
|
|
x,
|
|
y,
|
|
//constrain the aligned el to viewport if necessary
|
|
w,
|
|
h,
|
|
r,
|
|
dw = Ext.lib.Dom.getViewWidth() -10, // 10px of margin for ie
|
|
dh = Ext.lib.Dom.getViewHeight()-10, // 10px of margin for ie
|
|
p1y,
|
|
p1x,
|
|
p2y,
|
|
p2x,
|
|
swapY,
|
|
swapX,
|
|
doc = document,
|
|
docElement = doc.documentElement,
|
|
docBody = doc.body,
|
|
scrollX = (docElement.scrollLeft || docBody.scrollLeft || 0)+5,
|
|
scrollY = (docElement.scrollTop || docBody.scrollTop || 0)+5,
|
|
c = false, //constrain to viewport
|
|
p1 = "",
|
|
p2 = "",
|
|
m = p.match(/^([a-z]+)-([a-z]+)(\?)?$/);
|
|
|
|
if(!m){
|
|
throw "Element.alignTo with an invalid alignment " + p;
|
|
}
|
|
|
|
p1 = m[1];
|
|
p2 = m[2];
|
|
c = !!m[3];
|
|
|
|
//Subtract the aligned el's internal xy from the target's offset xy
|
|
//plus custom offset to get the aligned el's new offset xy
|
|
a1 = me.getAnchorXY(p1, true);
|
|
a2 = el.getAnchorXY(p2, false);
|
|
|
|
x = a2[0] - a1[0] + o[0];
|
|
y = a2[1] - a1[1] + o[1];
|
|
|
|
if(c){
|
|
w = me.getWidth();
|
|
h = me.getHeight();
|
|
r = el.getRegion();
|
|
//If we are at a viewport boundary and the aligned el is anchored on a target border that is
|
|
//perpendicular to the vp border, allow the aligned el to slide on that border,
|
|
//otherwise swap the aligned el to the opposite border of the target.
|
|
p1y = p1.charAt(0);
|
|
p1x = p1.charAt(p1.length-1);
|
|
p2y = p2.charAt(0);
|
|
p2x = p2.charAt(p2.length-1);
|
|
swapY = ((p1y=="t" && p2y=="b") || (p1y=="b" && p2y=="t"));
|
|
swapX = ((p1x=="r" && p2x=="l") || (p1x=="l" && p2x=="r"));
|
|
|
|
|
|
if (x + w > dw + scrollX) {
|
|
x = swapX ? r.left-w : dw+scrollX-w;
|
|
}
|
|
if (x < scrollX) {
|
|
x = swapX ? r.right : scrollX;
|
|
}
|
|
if (y + h > dh + scrollY) {
|
|
y = swapY ? r.top-h : dh+scrollY-h;
|
|
}
|
|
if (y < scrollY){
|
|
y = swapY ? r.bottom : scrollY;
|
|
}
|
|
}
|
|
return [x,y];
|
|
},
|
|
|
|
/**
|
|
* Aligns this element with another element relative to the specified anchor points. If the other element is the
|
|
* document it aligns it to the viewport.
|
|
* The position parameter is optional, and can be specified in any one of the following formats:
|
|
* <ul>
|
|
* <li><b>Blank</b>: Defaults to aligning the element's top-left corner to the target's bottom-left corner ("tl-bl").</li>
|
|
* <li><b>One anchor (deprecated)</b>: The passed anchor position is used as the target element's anchor point.
|
|
* The element being aligned will position its top-left corner (tl) to that point. <i>This method has been
|
|
* deprecated in favor of the newer two anchor syntax below</i>.</li>
|
|
* <li><b>Two anchors</b>: If two values from the table below are passed separated by a dash, the first value is used as the
|
|
* element's anchor point, and the second value is used as the target's anchor point.</li>
|
|
* </ul>
|
|
* In addition to the anchor points, the position parameter also supports the "?" character. If "?" is passed at the end of
|
|
* the position string, the element will attempt to align as specified, but the position will be adjusted to constrain to
|
|
* the viewport if necessary. Note that the element being aligned might be swapped to align to a different position than
|
|
* that specified in order to enforce the viewport constraints.
|
|
* Following are all of the supported anchor positions:
|
|
<pre>
|
|
Value Description
|
|
----- -----------------------------
|
|
tl The top left corner (default)
|
|
t The center of the top edge
|
|
tr The top right corner
|
|
l The center of the left edge
|
|
c In the center of the element
|
|
r The center of the right edge
|
|
bl The bottom left corner
|
|
b The center of the bottom edge
|
|
br The bottom right corner
|
|
</pre>
|
|
Example Usage:
|
|
<pre><code>
|
|
// align el to other-el using the default positioning ("tl-bl", non-constrained)
|
|
el.alignTo("other-el");
|
|
|
|
// align the top left corner of el with the top right corner of other-el (constrained to viewport)
|
|
el.alignTo("other-el", "tr?");
|
|
|
|
// align the bottom right corner of el with the center left edge of other-el
|
|
el.alignTo("other-el", "br-l?");
|
|
|
|
// align the center of el with the bottom left corner of other-el and
|
|
// adjust the x position by -6 pixels (and the y position by 0)
|
|
el.alignTo("other-el", "c-bl", [-6, 0]);
|
|
</code></pre>
|
|
* @param {Mixed} element The element to align to.
|
|
* @param {String} position (optional, defaults to "tl-bl?") The position to align to.
|
|
* @param {Array} offsets (optional) Offset the positioning by [x, y]
|
|
* @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
|
|
* @return {Ext.Element} this
|
|
*/
|
|
alignTo : function(element, position, offsets, animate){
|
|
var me = this;
|
|
return me.setXY(me.getAlignToXY(element, position, offsets),
|
|
me.preanim && !!animate ? me.preanim(arguments, 3) : false);
|
|
},
|
|
|
|
// private ==> used outside of core
|
|
adjustForConstraints : function(xy, parent, offsets){
|
|
return this.getConstrainToXY(parent || document, false, offsets, xy) || xy;
|
|
},
|
|
|
|
// private ==> used outside of core
|
|
getConstrainToXY : function(el, local, offsets, proposedXY){
|
|
var os = {top:0, left:0, bottom:0, right: 0};
|
|
|
|
return function(el, local, offsets, proposedXY){
|
|
el = Ext.get(el);
|
|
offsets = offsets ? Ext.applyIf(offsets, os) : os;
|
|
|
|
var vw, vh, vx = 0, vy = 0;
|
|
if(el.dom == document.body || el.dom == document){
|
|
vw =Ext.lib.Dom.getViewWidth();
|
|
vh = Ext.lib.Dom.getViewHeight();
|
|
}else{
|
|
vw = el.dom.clientWidth;
|
|
vh = el.dom.clientHeight;
|
|
if(!local){
|
|
var vxy = el.getXY();
|
|
vx = vxy[0];
|
|
vy = vxy[1];
|
|
}
|
|
}
|
|
|
|
var s = el.getScroll();
|
|
|
|
vx += offsets.left + s.left;
|
|
vy += offsets.top + s.top;
|
|
|
|
vw -= offsets.right;
|
|
vh -= offsets.bottom;
|
|
|
|
var vr = vx + vw,
|
|
vb = vy + vh,
|
|
xy = proposedXY || (!local ? this.getXY() : [this.getLeft(true), this.getTop(true)]),
|
|
x = xy[0], y = xy[1],
|
|
offset = this.getConstrainOffset(),
|
|
w = this.dom.offsetWidth + offset,
|
|
h = this.dom.offsetHeight + offset;
|
|
|
|
// only move it if it needs it
|
|
var moved = false;
|
|
|
|
// first validate right/bottom
|
|
if((x + w) > vr){
|
|
x = vr - w;
|
|
moved = true;
|
|
}
|
|
if((y + h) > vb){
|
|
y = vb - h;
|
|
moved = true;
|
|
}
|
|
// then make sure top/left isn't negative
|
|
if(x < vx){
|
|
x = vx;
|
|
moved = true;
|
|
}
|
|
if(y < vy){
|
|
y = vy;
|
|
moved = true;
|
|
}
|
|
return moved ? [x, y] : false;
|
|
};
|
|
}(),
|
|
|
|
|
|
|
|
// el = Ext.get(el);
|
|
// offsets = Ext.applyIf(offsets || {}, {top : 0, left : 0, bottom : 0, right : 0});
|
|
|
|
// var me = this,
|
|
// doc = document,
|
|
// s = el.getScroll(),
|
|
// vxy = el.getXY(),
|
|
// vx = offsets.left + s.left,
|
|
// vy = offsets.top + s.top,
|
|
// vw = -offsets.right,
|
|
// vh = -offsets.bottom,
|
|
// vr,
|
|
// vb,
|
|
// xy = proposedXY || (!local ? me.getXY() : [me.getLeft(true), me.getTop(true)]),
|
|
// x = xy[0],
|
|
// y = xy[1],
|
|
// w = me.dom.offsetWidth, h = me.dom.offsetHeight,
|
|
// moved = false; // only move it if it needs it
|
|
//
|
|
//
|
|
// if(el.dom == doc.body || el.dom == doc){
|
|
// vw += Ext.lib.Dom.getViewWidth();
|
|
// vh += Ext.lib.Dom.getViewHeight();
|
|
// }else{
|
|
// vw += el.dom.clientWidth;
|
|
// vh += el.dom.clientHeight;
|
|
// if(!local){
|
|
// vx += vxy[0];
|
|
// vy += vxy[1];
|
|
// }
|
|
// }
|
|
|
|
// // first validate right/bottom
|
|
// if(x + w > vx + vw){
|
|
// x = vx + vw - w;
|
|
// moved = true;
|
|
// }
|
|
// if(y + h > vy + vh){
|
|
// y = vy + vh - h;
|
|
// moved = true;
|
|
// }
|
|
// // then make sure top/left isn't negative
|
|
// if(x < vx){
|
|
// x = vx;
|
|
// moved = true;
|
|
// }
|
|
// if(y < vy){
|
|
// y = vy;
|
|
// moved = true;
|
|
// }
|
|
// return moved ? [x, y] : false;
|
|
// },
|
|
|
|
// private, used internally
|
|
getConstrainOffset : function(){
|
|
return 0;
|
|
},
|
|
|
|
/**
|
|
* Calculates the x, y to center this element on the screen
|
|
* @return {Array} The x, y values [x, y]
|
|
*/
|
|
getCenterXY : function(){
|
|
return this.getAlignToXY(document, 'c-c');
|
|
},
|
|
|
|
/**
|
|
* Centers the Element in either the viewport, or another Element.
|
|
* @param {Mixed} centerIn (optional) The element in which to center the element.
|
|
*/
|
|
center : function(centerIn){
|
|
return this.alignTo(centerIn || document, 'c-c');
|
|
}
|
|
});
|
|
/**
|
|
* @class Ext.Element
|
|
*/
|
|
Ext.Element.addMethods({
|
|
/**
|
|
* Creates a {@link Ext.CompositeElement} for child nodes based on the passed CSS selector (the selector should not contain an id).
|
|
* @param {String} selector The CSS selector
|
|
* @param {Boolean} unique (optional) True to create a unique Ext.Element for each child (defaults to false, which creates a single shared flyweight object)
|
|
* @return {CompositeElement/CompositeElementLite} The composite element
|
|
*/
|
|
select : function(selector, unique){
|
|
return Ext.Element.select(selector, unique, this.dom);
|
|
}
|
|
});/**
|
|
* @class Ext.Element
|
|
*/
|
|
Ext.apply(Ext.Element.prototype, function() {
|
|
var GETDOM = Ext.getDom,
|
|
GET = Ext.get,
|
|
DH = Ext.DomHelper;
|
|
|
|
return {
|
|
/**
|
|
* Inserts (or creates) the passed element (or DomHelper config) as a sibling of this element
|
|
* @param {Mixed/Object/Array} el The id, element to insert or a DomHelper config to create and insert *or* an array of any of those.
|
|
* @param {String} where (optional) 'before' or 'after' defaults to before
|
|
* @param {Boolean} returnDom (optional) True to return the raw DOM element instead of Ext.Element
|
|
* @return {Ext.Element} The inserted Element. If an array is passed, the last inserted element is returned.
|
|
*/
|
|
insertSibling: function(el, where, returnDom){
|
|
var me = this,
|
|
rt,
|
|
isAfter = (where || 'before').toLowerCase() == 'after',
|
|
insertEl;
|
|
|
|
if(Ext.isArray(el)){
|
|
insertEl = me;
|
|
Ext.each(el, function(e) {
|
|
rt = Ext.fly(insertEl, '_internal').insertSibling(e, where, returnDom);
|
|
if(isAfter){
|
|
insertEl = rt;
|
|
}
|
|
});
|
|
return rt;
|
|
}
|
|
|
|
el = el || {};
|
|
|
|
if(el.nodeType || el.dom){
|
|
rt = me.dom.parentNode.insertBefore(GETDOM(el), isAfter ? me.dom.nextSibling : me.dom);
|
|
if (!returnDom) {
|
|
rt = GET(rt);
|
|
}
|
|
}else{
|
|
if (isAfter && !me.dom.nextSibling) {
|
|
rt = DH.append(me.dom.parentNode, el, !returnDom);
|
|
} else {
|
|
rt = DH[isAfter ? 'insertAfter' : 'insertBefore'](me.dom, el, !returnDom);
|
|
}
|
|
}
|
|
return rt;
|
|
}
|
|
};
|
|
}());/**
|
|
* @class Ext.Element
|
|
*/
|
|
|
|
// special markup used throughout Ext when box wrapping elements
|
|
Ext.Element.boxMarkup = '<div class="{0}-tl"><div class="{0}-tr"><div class="{0}-tc"></div></div></div><div class="{0}-ml"><div class="{0}-mr"><div class="{0}-mc"></div></div></div><div class="{0}-bl"><div class="{0}-br"><div class="{0}-bc"></div></div></div>';
|
|
|
|
Ext.Element.addMethods(function(){
|
|
var INTERNAL = "_internal",
|
|
pxMatch = /(\d+\.?\d+)px/;
|
|
return {
|
|
/**
|
|
* More flexible version of {@link #setStyle} for setting style properties.
|
|
* @param {String/Object/Function} styles A style specification string, e.g. "width:100px", or object in the form {width:"100px"}, or
|
|
* a function which returns such a specification.
|
|
* @return {Ext.Element} this
|
|
*/
|
|
applyStyles : function(style){
|
|
Ext.DomHelper.applyStyles(this.dom, style);
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Returns an object with properties matching the styles requested.
|
|
* For example, el.getStyles('color', 'font-size', 'width') might return
|
|
* {'color': '#FFFFFF', 'font-size': '13px', 'width': '100px'}.
|
|
* @param {String} style1 A style name
|
|
* @param {String} style2 A style name
|
|
* @param {String} etc.
|
|
* @return {Object} The style object
|
|
*/
|
|
getStyles : function(){
|
|
var ret = {};
|
|
Ext.each(arguments, function(v) {
|
|
ret[v] = this.getStyle(v);
|
|
},
|
|
this);
|
|
return ret;
|
|
},
|
|
|
|
// private ==> used by ext full
|
|
setOverflow : function(v){
|
|
var dom = this.dom;
|
|
if(v=='auto' && Ext.isMac && Ext.isGecko2){ // work around stupid FF 2.0/Mac scroll bar bug
|
|
dom.style.overflow = 'hidden';
|
|
(function(){dom.style.overflow = 'auto';}).defer(1);
|
|
}else{
|
|
dom.style.overflow = v;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* <p>Wraps the specified element with a special 9 element markup/CSS block that renders by default as
|
|
* a gray container with a gradient background, rounded corners and a 4-way shadow.</p>
|
|
* <p>This special markup is used throughout Ext when box wrapping elements ({@link Ext.Button},
|
|
* {@link Ext.Panel} when <tt>{@link Ext.Panel#frame frame=true}</tt>, {@link Ext.Window}). The markup
|
|
* is of this form:</p>
|
|
* <pre><code>
|
|
Ext.Element.boxMarkup =
|
|
'<div class="{0}-tl"><div class="{0}-tr"><div class="{0}-tc"></div></div></div>
|
|
<div class="{0}-ml"><div class="{0}-mr"><div class="{0}-mc"></div></div></div>
|
|
<div class="{0}-bl"><div class="{0}-br"><div class="{0}-bc"></div></div></div>';
|
|
* </code></pre>
|
|
* <p>Example usage:</p>
|
|
* <pre><code>
|
|
// Basic box wrap
|
|
Ext.get("foo").boxWrap();
|
|
|
|
// You can also add a custom class and use CSS inheritance rules to customize the box look.
|
|
// 'x-box-blue' is a built-in alternative -- look at the related CSS definitions as an example
|
|
// for how to create a custom box wrap style.
|
|
Ext.get("foo").boxWrap().addClass("x-box-blue");
|
|
* </code></pre>
|
|
* @param {String} class (optional) A base CSS class to apply to the containing wrapper element
|
|
* (defaults to <tt>'x-box'</tt>). Note that there are a number of CSS rules that are dependent on
|
|
* this name to make the overall effect work, so if you supply an alternate base class, make sure you
|
|
* also supply all of the necessary rules.
|
|
* @return {Ext.Element} The outermost wrapping element of the created box structure.
|
|
*/
|
|
boxWrap : function(cls){
|
|
cls = cls || 'x-box';
|
|
var el = Ext.get(this.insertHtml("beforeBegin", "<div class='" + cls + "'>" + String.format(Ext.Element.boxMarkup, cls) + "</div>")); //String.format('<div class="{0}">'+Ext.Element.boxMarkup+'</div>', cls)));
|
|
Ext.DomQuery.selectNode('.' + cls + '-mc', el.dom).appendChild(this.dom);
|
|
return el;
|
|
},
|
|
|
|
/**
|
|
* Set the size of this Element. If animation is true, both width and height will be animated concurrently.
|
|
* @param {Mixed} width The new width. This may be one of:<div class="mdetail-params"><ul>
|
|
* <li>A Number specifying the new width in this Element's {@link #defaultUnit}s (by default, pixels).</li>
|
|
* <li>A String used to set the CSS width style. Animation may <b>not</b> be used.
|
|
* <li>A size object in the format <code>{width: widthValue, height: heightValue}</code>.</li>
|
|
* </ul></div>
|
|
* @param {Mixed} height The new height. This may be one of:<div class="mdetail-params"><ul>
|
|
* <li>A Number specifying the new height in this Element's {@link #defaultUnit}s (by default, pixels).</li>
|
|
* <li>A String used to set the CSS height style. Animation may <b>not</b> be used.</li>
|
|
* </ul></div>
|
|
* @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
|
|
* @return {Ext.Element} this
|
|
*/
|
|
setSize : function(width, height, animate){
|
|
var me = this;
|
|
if(typeof width == 'object'){ // in case of object from getSize()
|
|
height = width.height;
|
|
width = width.width;
|
|
}
|
|
width = me.adjustWidth(width);
|
|
height = me.adjustHeight(height);
|
|
if(!animate || !me.anim){
|
|
me.dom.style.width = me.addUnits(width);
|
|
me.dom.style.height = me.addUnits(height);
|
|
}else{
|
|
me.anim({width: {to: width}, height: {to: height}}, me.preanim(arguments, 2));
|
|
}
|
|
return me;
|
|
},
|
|
|
|
/**
|
|
* Returns either the offsetHeight or the height of this element based on CSS height adjusted by padding or borders
|
|
* when needed to simulate offsetHeight when offsets aren't available. This may not work on display:none elements
|
|
* if a height has not been set using CSS.
|
|
* @return {Number}
|
|
*/
|
|
getComputedHeight : function(){
|
|
var me = this,
|
|
h = Math.max(me.dom.offsetHeight, me.dom.clientHeight);
|
|
if(!h){
|
|
h = parseFloat(me.getStyle('height')) || 0;
|
|
if(!me.isBorderBox()){
|
|
h += me.getFrameWidth('tb');
|
|
}
|
|
}
|
|
return h;
|
|
},
|
|
|
|
/**
|
|
* Returns either the offsetWidth or the width of this element based on CSS width adjusted by padding or borders
|
|
* when needed to simulate offsetWidth when offsets aren't available. This may not work on display:none elements
|
|
* if a width has not been set using CSS.
|
|
* @return {Number}
|
|
*/
|
|
getComputedWidth : function(){
|
|
var w = Math.max(this.dom.offsetWidth, this.dom.clientWidth);
|
|
if(!w){
|
|
w = parseFloat(this.getStyle('width')) || 0;
|
|
if(!this.isBorderBox()){
|
|
w += this.getFrameWidth('lr');
|
|
}
|
|
}
|
|
return w;
|
|
},
|
|
|
|
/**
|
|
* Returns the sum width of the padding and borders for the passed "sides". See getBorderWidth()
|
|
for more information about the sides.
|
|
* @param {String} sides
|
|
* @return {Number}
|
|
*/
|
|
getFrameWidth : function(sides, onlyContentBox){
|
|
return onlyContentBox && this.isBorderBox() ? 0 : (this.getPadding(sides) + this.getBorderWidth(sides));
|
|
},
|
|
|
|
/**
|
|
* Sets up event handlers to add and remove a css class when the mouse is over this element
|
|
* @param {String} className
|
|
* @return {Ext.Element} this
|
|
*/
|
|
addClassOnOver : function(className){
|
|
this.hover(
|
|
function(){
|
|
Ext.fly(this, INTERNAL).addClass(className);
|
|
},
|
|
function(){
|
|
Ext.fly(this, INTERNAL).removeClass(className);
|
|
}
|
|
);
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Sets up event handlers to add and remove a css class when this element has the focus
|
|
* @param {String} className
|
|
* @return {Ext.Element} this
|
|
*/
|
|
addClassOnFocus : function(className){
|
|
this.on("focus", function(){
|
|
Ext.fly(this, INTERNAL).addClass(className);
|
|
}, this.dom);
|
|
this.on("blur", function(){
|
|
Ext.fly(this, INTERNAL).removeClass(className);
|
|
}, this.dom);
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Sets up event handlers to add and remove a css class when the mouse is down and then up on this element (a click effect)
|
|
* @param {String} className
|
|
* @return {Ext.Element} this
|
|
*/
|
|
addClassOnClick : function(className){
|
|
var dom = this.dom;
|
|
this.on("mousedown", function(){
|
|
Ext.fly(dom, INTERNAL).addClass(className);
|
|
var d = Ext.getDoc(),
|
|
fn = function(){
|
|
Ext.fly(dom, INTERNAL).removeClass(className);
|
|
d.removeListener("mouseup", fn);
|
|
};
|
|
d.on("mouseup", fn);
|
|
});
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* <p>Returns the dimensions of the element available to lay content out in.<p>
|
|
* <p>If the element (or any ancestor element) has CSS style <code>display : none</code>, the dimensions will be zero.</p>
|
|
* example:<pre><code>
|
|
var vpSize = Ext.getBody().getViewSize();
|
|
|
|
// all Windows created afterwards will have a default value of 90% height and 95% width
|
|
Ext.Window.override({
|
|
width: vpSize.width * 0.9,
|
|
height: vpSize.height * 0.95
|
|
});
|
|
// To handle window resizing you would have to hook onto onWindowResize.
|
|
* </code></pre>
|
|
*
|
|
* getViewSize utilizes clientHeight/clientWidth which excludes sizing of scrollbars.
|
|
* To obtain the size including scrollbars, use getStyleSize
|
|
*
|
|
* Sizing of the document body is handled at the adapter level which handles special cases for IE and strict modes, etc.
|
|
*/
|
|
|
|
getViewSize : function(){
|
|
var doc = document,
|
|
d = this.dom,
|
|
isDoc = (d == doc || d == doc.body);
|
|
|
|
// If the body, use Ext.lib.Dom
|
|
if (isDoc) {
|
|
var extdom = Ext.lib.Dom;
|
|
return {
|
|
width : extdom.getViewWidth(),
|
|
height : extdom.getViewHeight()
|
|
};
|
|
|
|
// Else use clientHeight/clientWidth
|
|
} else {
|
|
return {
|
|
width : d.clientWidth,
|
|
height : d.clientHeight
|
|
};
|
|
}
|
|
},
|
|
|
|
/**
|
|
* <p>Returns the dimensions of the element available to lay content out in.<p>
|
|
*
|
|
* getStyleSize utilizes prefers style sizing if present, otherwise it chooses the larger of offsetHeight/clientHeight and offsetWidth/clientWidth.
|
|
* To obtain the size excluding scrollbars, use getViewSize
|
|
*
|
|
* Sizing of the document body is handled at the adapter level which handles special cases for IE and strict modes, etc.
|
|
*/
|
|
|
|
getStyleSize : function(){
|
|
var me = this,
|
|
w, h,
|
|
doc = document,
|
|
d = this.dom,
|
|
isDoc = (d == doc || d == doc.body),
|
|
s = d.style;
|
|
|
|
// If the body, use Ext.lib.Dom
|
|
if (isDoc) {
|
|
var extdom = Ext.lib.Dom;
|
|
return {
|
|
width : extdom.getViewWidth(),
|
|
height : extdom.getViewHeight()
|
|
};
|
|
}
|
|
// Use Styles if they are set
|
|
if(s.width && s.width != 'auto'){
|
|
w = parseFloat(s.width);
|
|
if(me.isBorderBox()){
|
|
w -= me.getFrameWidth('lr');
|
|
}
|
|
}
|
|
// Use Styles if they are set
|
|
if(s.height && s.height != 'auto'){
|
|
h = parseFloat(s.height);
|
|
if(me.isBorderBox()){
|
|
h -= me.getFrameWidth('tb');
|
|
}
|
|
}
|
|
// Use getWidth/getHeight if style not set.
|
|
return {width: w || me.getWidth(true), height: h || me.getHeight(true)};
|
|
},
|
|
|
|
/**
|
|
* Returns the size of the element.
|
|
* @param {Boolean} contentSize (optional) true to get the width/size minus borders and padding
|
|
* @return {Object} An object containing the element's size {width: (element width), height: (element height)}
|
|
*/
|
|
getSize : function(contentSize){
|
|
return {width: this.getWidth(contentSize), height: this.getHeight(contentSize)};
|
|
},
|
|
|
|
/**
|
|
* Forces the browser to repaint this element
|
|
* @return {Ext.Element} this
|
|
*/
|
|
repaint : function(){
|
|
var dom = this.dom;
|
|
this.addClass("x-repaint");
|
|
setTimeout(function(){
|
|
Ext.fly(dom).removeClass("x-repaint");
|
|
}, 1);
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Disables text selection for this element (normalized across browsers)
|
|
* @return {Ext.Element} this
|
|
*/
|
|
unselectable : function(){
|
|
this.dom.unselectable = "on";
|
|
return this.swallowEvent("selectstart", true).
|
|
addClass("x-unselectable");
|
|
},
|
|
|
|
/**
|
|
* Returns an object with properties top, left, right and bottom representing the margins of this element unless sides is passed,
|
|
* then it returns the calculated width of the sides (see getPadding)
|
|
* @param {String} sides (optional) Any combination of l, r, t, b to get the sum of those sides
|
|
* @return {Object/Number}
|
|
*/
|
|
getMargins : function(side){
|
|
var me = this,
|
|
key,
|
|
hash = {t:"top", l:"left", r:"right", b: "bottom"},
|
|
o = {};
|
|
|
|
if (!side) {
|
|
for (key in me.margins){
|
|
o[hash[key]] = parseFloat(me.getStyle(me.margins[key])) || 0;
|
|
}
|
|
return o;
|
|
} else {
|
|
return me.addStyles.call(me, side, me.margins);
|
|
}
|
|
}
|
|
};
|
|
}());
|
|
/**
|
|
* @class Ext.Element
|
|
*/
|
|
Ext.Element.addMethods({
|
|
/**
|
|
* Sets the element's box. Use getBox() on another element to get a box obj. If animate is true then width, height, x and y will be animated concurrently.
|
|
* @param {Object} box The box to fill {x, y, width, height}
|
|
* @param {Boolean} adjust (optional) Whether to adjust for box-model issues automatically
|
|
* @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
|
|
* @return {Ext.Element} this
|
|
*/
|
|
setBox : function(box, adjust, animate){
|
|
var me = this,
|
|
w = box.width,
|
|
h = box.height;
|
|
if((adjust && !me.autoBoxAdjust) && !me.isBorderBox()){
|
|
w -= (me.getBorderWidth("lr") + me.getPadding("lr"));
|
|
h -= (me.getBorderWidth("tb") + me.getPadding("tb"));
|
|
}
|
|
me.setBounds(box.x, box.y, w, h, me.animTest.call(me, arguments, animate, 2));
|
|
return me;
|
|
},
|
|
|
|
/**
|
|
* Return an object defining the area of this Element which can be passed to {@link #setBox} to
|
|
* set another Element's size/location to match this element.
|
|
* @param {Boolean} contentBox (optional) If true a box for the content of the element is returned.
|
|
* @param {Boolean} local (optional) If true the element's left and top are returned instead of page x/y.
|
|
* @return {Object} box An object in the format<pre><code>
|
|
{
|
|
x: <Element's X position>,
|
|
y: <Element's Y position>,
|
|
width: <Element's width>,
|
|
height: <Element's height>,
|
|
bottom: <Element's lower bound>,
|
|
right: <Element's rightmost bound>
|
|
}
|
|
</code></pre>
|
|
* The returned object may also be addressed as an Array where index 0 contains the X position
|
|
* and index 1 contains the Y position. So the result may also be used for {@link #setXY}
|
|
*/
|
|
getBox : function(contentBox, local) {
|
|
var me = this,
|
|
xy,
|
|
left,
|
|
top,
|
|
getBorderWidth = me.getBorderWidth,
|
|
getPadding = me.getPadding,
|
|
l,
|
|
r,
|
|
t,
|
|
b;
|
|
if(!local){
|
|
xy = me.getXY();
|
|
}else{
|
|
left = parseInt(me.getStyle("left"), 10) || 0;
|
|
top = parseInt(me.getStyle("top"), 10) || 0;
|
|
xy = [left, top];
|
|
}
|
|
var el = me.dom, w = el.offsetWidth, h = el.offsetHeight, bx;
|
|
if(!contentBox){
|
|
bx = {x: xy[0], y: xy[1], 0: xy[0], 1: xy[1], width: w, height: h};
|
|
}else{
|
|
l = getBorderWidth.call(me, "l") + getPadding.call(me, "l");
|
|
r = getBorderWidth.call(me, "r") + getPadding.call(me, "r");
|
|
t = getBorderWidth.call(me, "t") + getPadding.call(me, "t");
|
|
b = getBorderWidth.call(me, "b") + getPadding.call(me, "b");
|
|
bx = {x: xy[0]+l, y: xy[1]+t, 0: xy[0]+l, 1: xy[1]+t, width: w-(l+r), height: h-(t+b)};
|
|
}
|
|
bx.right = bx.x + bx.width;
|
|
bx.bottom = bx.y + bx.height;
|
|
return bx;
|
|
},
|
|
|
|
/**
|
|
* Move this element relative to its current position.
|
|
* @param {String} direction Possible values are: "l" (or "left"), "r" (or "right"), "t" (or "top", or "up"), "b" (or "bottom", or "down").
|
|
* @param {Number} distance How far to move the element in pixels
|
|
* @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
|
|
* @return {Ext.Element} this
|
|
*/
|
|
move : function(direction, distance, animate){
|
|
var me = this,
|
|
xy = me.getXY(),
|
|
x = xy[0],
|
|
y = xy[1],
|
|
left = [x - distance, y],
|
|
right = [x + distance, y],
|
|
top = [x, y - distance],
|
|
bottom = [x, y + distance],
|
|
hash = {
|
|
l : left,
|
|
left : left,
|
|
r : right,
|
|
right : right,
|
|
t : top,
|
|
top : top,
|
|
up : top,
|
|
b : bottom,
|
|
bottom : bottom,
|
|
down : bottom
|
|
};
|
|
|
|
direction = direction.toLowerCase();
|
|
me.moveTo(hash[direction][0], hash[direction][1], me.animTest.call(me, arguments, animate, 2));
|
|
},
|
|
|
|
/**
|
|
* Quick set left and top adding default units
|
|
* @param {String} left The left CSS property value
|
|
* @param {String} top The top CSS property value
|
|
* @return {Ext.Element} this
|
|
*/
|
|
setLeftTop : function(left, top){
|
|
var me = this,
|
|
style = me.dom.style;
|
|
style.left = me.addUnits(left);
|
|
style.top = me.addUnits(top);
|
|
return me;
|
|
},
|
|
|
|
/**
|
|
* Returns the region of the given element.
|
|
* The element must be part of the DOM tree to have a region (display:none or elements not appended return false).
|
|
* @return {Region} A Ext.lib.Region containing "top, left, bottom, right" member data.
|
|
*/
|
|
getRegion : function(){
|
|
return Ext.lib.Dom.getRegion(this.dom);
|
|
},
|
|
|
|
/**
|
|
* Sets the element's position and size in one shot. If animation is true then width, height, x and y will be animated concurrently.
|
|
* @param {Number} x X value for new position (coordinates are page-based)
|
|
* @param {Number} y Y value for new position (coordinates are page-based)
|
|
* @param {Mixed} width The new width. This may be one of:<div class="mdetail-params"><ul>
|
|
* <li>A Number specifying the new width in this Element's {@link #defaultUnit}s (by default, pixels)</li>
|
|
* <li>A String used to set the CSS width style. Animation may <b>not</b> be used.
|
|
* </ul></div>
|
|
* @param {Mixed} height The new height. This may be one of:<div class="mdetail-params"><ul>
|
|
* <li>A Number specifying the new height in this Element's {@link #defaultUnit}s (by default, pixels)</li>
|
|
* <li>A String used to set the CSS height style. Animation may <b>not</b> be used.</li>
|
|
* </ul></div>
|
|
* @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
|
|
* @return {Ext.Element} this
|
|
*/
|
|
setBounds : function(x, y, width, height, animate){
|
|
var me = this;
|
|
if (!animate || !me.anim) {
|
|
me.setSize(width, height);
|
|
me.setLocation(x, y);
|
|
} else {
|
|
me.anim({points: {to: [x, y]},
|
|
width: {to: me.adjustWidth(width)},
|
|
height: {to: me.adjustHeight(height)}},
|
|
me.preanim(arguments, 4),
|
|
'motion');
|
|
}
|
|
return me;
|
|
},
|
|
|
|
/**
|
|
* Sets the element's position and size the specified region. If animation is true then width, height, x and y will be animated concurrently.
|
|
* @param {Ext.lib.Region} region The region to fill
|
|
* @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
|
|
* @return {Ext.Element} this
|
|
*/
|
|
setRegion : function(region, animate) {
|
|
return this.setBounds(region.left, region.top, region.right-region.left, region.bottom-region.top, this.animTest.call(this, arguments, animate, 1));
|
|
}
|
|
});/**
|
|
* @class Ext.Element
|
|
*/
|
|
Ext.Element.addMethods({
|
|
/**
|
|
* Scrolls this element the specified scroll point. It does NOT do bounds checking so if you scroll to a weird value it will try to do it. For auto bounds checking, use scroll().
|
|
* @param {String} side Either "left" for scrollLeft values or "top" for scrollTop values.
|
|
* @param {Number} value The new scroll value
|
|
* @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
|
|
* @return {Element} this
|
|
*/
|
|
scrollTo : function(side, value, animate) {
|
|
//check if we're scrolling top or left
|
|
var top = /top/i.test(side),
|
|
me = this,
|
|
dom = me.dom,
|
|
prop;
|
|
if (!animate || !me.anim) {
|
|
// just setting the value, so grab the direction
|
|
prop = 'scroll' + (top ? 'Top' : 'Left');
|
|
dom[prop] = value;
|
|
}
|
|
else {
|
|
// if scrolling top, we need to grab scrollLeft, if left, scrollTop
|
|
prop = 'scroll' + (top ? 'Left' : 'Top');
|
|
me.anim({scroll: {to: top ? [dom[prop], value] : [value, dom[prop]]}}, me.preanim(arguments, 2), 'scroll');
|
|
}
|
|
return me;
|
|
},
|
|
|
|
/**
|
|
* Scrolls this element into view within the passed container.
|
|
* @param {Mixed} container (optional) The container element to scroll (defaults to document.body). Should be a
|
|
* string (id), dom node, or Ext.Element.
|
|
* @param {Boolean} hscroll (optional) False to disable horizontal scroll (defaults to true)
|
|
* @return {Ext.Element} this
|
|
*/
|
|
scrollIntoView : function(container, hscroll) {
|
|
var c = Ext.getDom(container) || Ext.getBody().dom,
|
|
el = this.dom,
|
|
o = this.getOffsetsTo(c),
|
|
l = o[0] + c.scrollLeft,
|
|
t = o[1] + c.scrollTop,
|
|
b = t + el.offsetHeight,
|
|
r = l + el.offsetWidth,
|
|
ch = c.clientHeight,
|
|
ct = parseInt(c.scrollTop, 10),
|
|
cl = parseInt(c.scrollLeft, 10),
|
|
cb = ct + ch,
|
|
cr = cl + c.clientWidth;
|
|
|
|
if (el.offsetHeight > ch || t < ct) {
|
|
c.scrollTop = t;
|
|
}
|
|
else if (b > cb) {
|
|
c.scrollTop = b-ch;
|
|
}
|
|
// corrects IE, other browsers will ignore
|
|
c.scrollTop = c.scrollTop;
|
|
|
|
if (hscroll !== false) {
|
|
if (el.offsetWidth > c.clientWidth || l < cl) {
|
|
c.scrollLeft = l;
|
|
}
|
|
else if (r > cr) {
|
|
c.scrollLeft = r - c.clientWidth;
|
|
}
|
|
c.scrollLeft = c.scrollLeft;
|
|
}
|
|
return this;
|
|
},
|
|
|
|
// private
|
|
scrollChildIntoView : function(child, hscroll) {
|
|
Ext.fly(child, '_scrollChildIntoView').scrollIntoView(this, hscroll);
|
|
},
|
|
|
|
/**
|
|
* Scrolls this element the specified direction. Does bounds checking to make sure the scroll is
|
|
* within this element's scrollable range.
|
|
* @param {String} direction Possible values are: "l" (or "left"), "r" (or "right"), "t" (or "top", or "up"), "b" (or "bottom", or "down").
|
|
* @param {Number} distance How far to scroll the element in pixels
|
|
* @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
|
|
* @return {Boolean} Returns true if a scroll was triggered or false if the element
|
|
* was scrolled as far as it could go.
|
|
*/
|
|
scroll : function(direction, distance, animate) {
|
|
if (!this.isScrollable()) {
|
|
return false;
|
|
}
|
|
var el = this.dom,
|
|
l = el.scrollLeft, t = el.scrollTop,
|
|
w = el.scrollWidth, h = el.scrollHeight,
|
|
cw = el.clientWidth, ch = el.clientHeight,
|
|
scrolled = false, v,
|
|
hash = {
|
|
l: Math.min(l + distance, w-cw),
|
|
r: v = Math.max(l - distance, 0),
|
|
t: Math.max(t - distance, 0),
|
|
b: Math.min(t + distance, h-ch)
|
|
};
|
|
hash.d = hash.b;
|
|
hash.u = hash.t;
|
|
|
|
direction = direction.substr(0, 1);
|
|
if ((v = hash[direction]) > -1) {
|
|
scrolled = true;
|
|
this.scrollTo(direction == 'l' || direction == 'r' ? 'left' : 'top', v, this.preanim(arguments, 2));
|
|
}
|
|
return scrolled;
|
|
}
|
|
});/**
|
|
* @class Ext.Element
|
|
*/
|
|
Ext.Element.addMethods(
|
|
function() {
|
|
var VISIBILITY = "visibility",
|
|
DISPLAY = "display",
|
|
HIDDEN = "hidden",
|
|
NONE = "none",
|
|
XMASKED = "x-masked",
|
|
XMASKEDRELATIVE = "x-masked-relative",
|
|
data = Ext.Element.data;
|
|
|
|
return {
|
|
/**
|
|
* Checks whether the element is currently visible using both visibility and display properties.
|
|
* @param {Boolean} deep (optional) True to walk the dom and see if parent elements are hidden (defaults to false)
|
|
* @return {Boolean} True if the element is currently visible, else false
|
|
*/
|
|
isVisible : function(deep) {
|
|
var vis = !this.isStyle(VISIBILITY, HIDDEN) && !this.isStyle(DISPLAY, NONE),
|
|
p = this.dom.parentNode;
|
|
|
|
if (deep !== true || !vis) {
|
|
return vis;
|
|
}
|
|
|
|
while (p && !(/^body/i.test(p.tagName))) {
|
|
if (!Ext.fly(p, '_isVisible').isVisible()) {
|
|
return false;
|
|
}
|
|
p = p.parentNode;
|
|
}
|
|
return true;
|
|
},
|
|
|
|
/**
|
|
* Returns true if display is not "none"
|
|
* @return {Boolean}
|
|
*/
|
|
isDisplayed : function() {
|
|
return !this.isStyle(DISPLAY, NONE);
|
|
},
|
|
|
|
/**
|
|
* Convenience method for setVisibilityMode(Element.DISPLAY)
|
|
* @param {String} display (optional) What to set display to when visible
|
|
* @return {Ext.Element} this
|
|
*/
|
|
enableDisplayMode : function(display) {
|
|
this.setVisibilityMode(Ext.Element.DISPLAY);
|
|
|
|
if (!Ext.isEmpty(display)) {
|
|
data(this.dom, 'originalDisplay', display);
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Puts a mask over this element to disable user interaction. Requires core.css.
|
|
* This method can only be applied to elements which accept child nodes.
|
|
* @param {String} msg (optional) A message to display in the mask
|
|
* @param {String} msgCls (optional) A css class to apply to the msg element
|
|
* @return {Element} The mask element
|
|
*/
|
|
mask : function(msg, msgCls) {
|
|
var me = this,
|
|
dom = me.dom,
|
|
dh = Ext.DomHelper,
|
|
EXTELMASKMSG = "ext-el-mask-msg",
|
|
el,
|
|
mask;
|
|
|
|
if (!/^body/i.test(dom.tagName) && me.getStyle('position') == 'static') {
|
|
me.addClass(XMASKEDRELATIVE);
|
|
}
|
|
if (el = data(dom, 'maskMsg')) {
|
|
el.remove();
|
|
}
|
|
if (el = data(dom, 'mask')) {
|
|
el.remove();
|
|
}
|
|
|
|
mask = dh.append(dom, {cls : "ext-el-mask"}, true);
|
|
data(dom, 'mask', mask);
|
|
|
|
me.addClass(XMASKED);
|
|
mask.setDisplayed(true);
|
|
|
|
if (typeof msg == 'string') {
|
|
var mm = dh.append(dom, {cls : EXTELMASKMSG, cn:{tag:'div'}}, true);
|
|
data(dom, 'maskMsg', mm);
|
|
mm.dom.className = msgCls ? EXTELMASKMSG + " " + msgCls : EXTELMASKMSG;
|
|
mm.dom.firstChild.innerHTML = msg;
|
|
mm.setDisplayed(true);
|
|
mm.center(me);
|
|
}
|
|
|
|
// ie will not expand full height automatically
|
|
if (Ext.isIE && !(Ext.isIE7 && Ext.isStrict) && me.getStyle('height') == 'auto') {
|
|
mask.setSize(undefined, me.getHeight());
|
|
}
|
|
|
|
return mask;
|
|
},
|
|
|
|
/**
|
|
* Removes a previously applied mask.
|
|
*/
|
|
unmask : function() {
|
|
var me = this,
|
|
dom = me.dom,
|
|
mask = data(dom, 'mask'),
|
|
maskMsg = data(dom, 'maskMsg');
|
|
|
|
if (mask) {
|
|
if (maskMsg) {
|
|
maskMsg.remove();
|
|
data(dom, 'maskMsg', undefined);
|
|
}
|
|
|
|
mask.remove();
|
|
data(dom, 'mask', undefined);
|
|
me.removeClass([XMASKED, XMASKEDRELATIVE]);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Returns true if this element is masked
|
|
* @return {Boolean}
|
|
*/
|
|
isMasked : function() {
|
|
var m = data(this.dom, 'mask');
|
|
return m && m.isVisible();
|
|
},
|
|
|
|
/**
|
|
* Creates an iframe shim for this element to keep selects and other windowed objects from
|
|
* showing through.
|
|
* @return {Ext.Element} The new shim element
|
|
*/
|
|
createShim : function() {
|
|
var el = document.createElement('iframe'),
|
|
shim;
|
|
|
|
el.frameBorder = '0';
|
|
el.className = 'ext-shim';
|
|
el.src = Ext.SSL_SECURE_URL;
|
|
shim = Ext.get(this.dom.parentNode.insertBefore(el, this.dom));
|
|
shim.autoBoxAdjust = false;
|
|
return shim;
|
|
}
|
|
};
|
|
}()
|
|
);/**
|
|
* @class Ext.Element
|
|
*/
|
|
Ext.Element.addMethods({
|
|
/**
|
|
* Convenience method for constructing a KeyMap
|
|
* @param {Number/Array/Object/String} key Either a string with the keys to listen for, the numeric key code, array of key codes or an object with the following options:
|
|
* <code>{key: (number or array), shift: (true/false), ctrl: (true/false), alt: (true/false)}</code>
|
|
* @param {Function} fn The function to call
|
|
* @param {Object} scope (optional) The scope (<code>this</code> reference) in which the specified function is executed. Defaults to this Element.
|
|
* @return {Ext.KeyMap} The KeyMap created
|
|
*/
|
|
addKeyListener : function(key, fn, scope){
|
|
var config;
|
|
if(typeof key != 'object' || Ext.isArray(key)){
|
|
config = {
|
|
key: key,
|
|
fn: fn,
|
|
scope: scope
|
|
};
|
|
}else{
|
|
config = {
|
|
key : key.key,
|
|
shift : key.shift,
|
|
ctrl : key.ctrl,
|
|
alt : key.alt,
|
|
fn: fn,
|
|
scope: scope
|
|
};
|
|
}
|
|
return new Ext.KeyMap(this, config);
|
|
},
|
|
|
|
/**
|
|
* Creates a KeyMap for this element
|
|
* @param {Object} config The KeyMap config. See {@link Ext.KeyMap} for more details
|
|
* @return {Ext.KeyMap} The KeyMap created
|
|
*/
|
|
addKeyMap : function(config){
|
|
return new Ext.KeyMap(this, config);
|
|
}
|
|
});
|
|
|
|
//Import the newly-added Ext.Element functions into CompositeElementLite. We call this here because
|
|
//Element.keys.js is the last extra Ext.Element include in the ext-all.js build
|
|
Ext.CompositeElementLite.importElementMethods();/**
|
|
* @class Ext.CompositeElementLite
|
|
*/
|
|
Ext.apply(Ext.CompositeElementLite.prototype, {
|
|
addElements : function(els, root){
|
|
if(!els){
|
|
return this;
|
|
}
|
|
if(typeof els == "string"){
|
|
els = Ext.Element.selectorFunction(els, root);
|
|
}
|
|
var yels = this.elements;
|
|
Ext.each(els, function(e) {
|
|
yels.push(Ext.get(e));
|
|
});
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Returns the first Element
|
|
* @return {Ext.Element}
|
|
*/
|
|
first : function(){
|
|
return this.item(0);
|
|
},
|
|
|
|
/**
|
|
* Returns the last Element
|
|
* @return {Ext.Element}
|
|
*/
|
|
last : function(){
|
|
return this.item(this.getCount()-1);
|
|
},
|
|
|
|
/**
|
|
* Returns true if this composite contains the passed element
|
|
* @param el {Mixed} The id of an element, or an Ext.Element, or an HtmlElement to find within the composite collection.
|
|
* @return Boolean
|
|
*/
|
|
contains : function(el){
|
|
return this.indexOf(el) != -1;
|
|
},
|
|
|
|
/**
|
|
* Removes the specified element(s).
|
|
* @param {Mixed} el The id of an element, the Element itself, the index of the element in this composite
|
|
* or an array of any of those.
|
|
* @param {Boolean} removeDom (optional) True to also remove the element from the document
|
|
* @return {CompositeElement} this
|
|
*/
|
|
removeElement : function(keys, removeDom){
|
|
var me = this,
|
|
els = this.elements,
|
|
el;
|
|
Ext.each(keys, function(val){
|
|
if ((el = (els[val] || els[val = me.indexOf(val)]))) {
|
|
if(removeDom){
|
|
if(el.dom){
|
|
el.remove();
|
|
}else{
|
|
Ext.removeNode(el);
|
|
}
|
|
}
|
|
els.splice(val, 1);
|
|
}
|
|
});
|
|
return this;
|
|
}
|
|
});
|
|
/**
|
|
* @class Ext.CompositeElement
|
|
* @extends Ext.CompositeElementLite
|
|
* <p>This class encapsulates a <i>collection</i> of DOM elements, providing methods to filter
|
|
* members, or to perform collective actions upon the whole set.</p>
|
|
* <p>Although they are not listed, this class supports all of the methods of {@link Ext.Element} and
|
|
* {@link Ext.Fx}. The methods from these classes will be performed on all the elements in this collection.</p>
|
|
* <p>All methods return <i>this</i> and can be chained.</p>
|
|
* Usage:
|
|
<pre><code>
|
|
var els = Ext.select("#some-el div.some-class", true);
|
|
// or select directly from an existing element
|
|
var el = Ext.get('some-el');
|
|
el.select('div.some-class', true);
|
|
|
|
els.setWidth(100); // all elements become 100 width
|
|
els.hide(true); // all elements fade out and hide
|
|
// or
|
|
els.setWidth(100).hide(true);
|
|
</code></pre>
|
|
*/
|
|
Ext.CompositeElement = Ext.extend(Ext.CompositeElementLite, {
|
|
|
|
constructor : function(els, root){
|
|
this.elements = [];
|
|
this.add(els, root);
|
|
},
|
|
|
|
// private
|
|
getElement : function(el){
|
|
// In this case just return it, since we already have a reference to it
|
|
return el;
|
|
},
|
|
|
|
// private
|
|
transformElement : function(el){
|
|
return Ext.get(el);
|
|
}
|
|
|
|
/**
|
|
* Adds elements to this composite.
|
|
* @param {String/Array} els A string CSS selector, an array of elements or an element
|
|
* @return {CompositeElement} this
|
|
* @ignore
|
|
*/
|
|
|
|
/**
|
|
* Returns the Element object at the specified index
|
|
* @param {Number} index
|
|
* @return {Ext.Element}
|
|
* @ignore
|
|
*/
|
|
|
|
/**
|
|
* Iterates each <code>element</code> in this <code>composite</code>
|
|
* calling the supplied function using {@link Ext#each}.
|
|
* @param {Function} fn The function to be called with each
|
|
* <code>element</code>. If the supplied function returns <tt>false</tt>,
|
|
* iteration stops. This function is called with the following arguments:
|
|
* <div class="mdetail-params"><ul>
|
|
* <li><code>element</code> : <i>Ext.Element</i><div class="sub-desc">The element at the current <code>index</code>
|
|
* in the <code>composite</code></div></li>
|
|
* <li><code>composite</code> : <i>Object</i> <div class="sub-desc">This composite.</div></li>
|
|
* <li><code>index</code> : <i>Number</i> <div class="sub-desc">The current index within the <code>composite</code> </div></li>
|
|
* </ul></div>
|
|
* @param {Object} scope (optional) The scope (<code><this</code> reference) in which the specified function is executed.
|
|
* Defaults to the <code>element</code> at the current <code>index</code>
|
|
* within the composite.
|
|
* @return {CompositeElement} this
|
|
* @ignore
|
|
*/
|
|
});
|
|
|
|
/**
|
|
* Selects elements based on the passed CSS selector to enable {@link Ext.Element Element} methods
|
|
* to be applied to many related elements in one statement through the returned {@link Ext.CompositeElement CompositeElement} or
|
|
* {@link Ext.CompositeElementLite CompositeElementLite} object.
|
|
* @param {String/Array} selector The CSS selector or an array of elements
|
|
* @param {Boolean} unique (optional) true to create a unique Ext.Element for each element (defaults to a shared flyweight object)
|
|
* @param {HTMLElement/String} root (optional) The root element of the query or id of the root
|
|
* @return {CompositeElementLite/CompositeElement}
|
|
* @member Ext.Element
|
|
* @method select
|
|
* @static
|
|
*/
|
|
Ext.Element.select = function(selector, unique, root){
|
|
var els;
|
|
if(typeof selector == "string"){
|
|
els = Ext.Element.selectorFunction(selector, root);
|
|
}else if(selector.length !== undefined){
|
|
els = selector;
|
|
}else{
|
|
throw "Invalid selector";
|
|
}
|
|
|
|
return (unique === true) ? new Ext.CompositeElement(els) : new Ext.CompositeElementLite(els);
|
|
};
|
|
|
|
/**
|
|
* Selects elements based on the passed CSS selector to enable {@link Ext.Element Element} methods
|
|
* to be applied to many related elements in one statement through the returned {@link Ext.CompositeElement CompositeElement} or
|
|
* {@link Ext.CompositeElementLite CompositeElementLite} object.
|
|
* @param {String/Array} selector The CSS selector or an array of elements
|
|
* @param {Boolean} unique (optional) true to create a unique Ext.Element for each element (defaults to a shared flyweight object)
|
|
* @param {HTMLElement/String} root (optional) The root element of the query or id of the root
|
|
* @return {CompositeElementLite/CompositeElement}
|
|
* @member Ext
|
|
* @method select
|
|
*/
|
|
Ext.select = Ext.Element.select;/**
|
|
* @class Ext.Updater
|
|
* @extends Ext.util.Observable
|
|
* Provides AJAX-style update capabilities for Element objects. Updater can be used to {@link #update}
|
|
* an {@link Ext.Element} once, or you can use {@link #startAutoRefresh} to set up an auto-updating
|
|
* {@link Ext.Element Element} on a specific interval.<br><br>
|
|
* Usage:<br>
|
|
* <pre><code>
|
|
* var el = Ext.get("foo"); // Get Ext.Element object
|
|
* var mgr = el.getUpdater();
|
|
* mgr.update({
|
|
url: "http://myserver.com/index.php",
|
|
params: {
|
|
param1: "foo",
|
|
param2: "bar"
|
|
}
|
|
* });
|
|
* ...
|
|
* mgr.formUpdate("myFormId", "http://myserver.com/index.php");
|
|
* <br>
|
|
* // or directly (returns the same Updater instance)
|
|
* var mgr = new Ext.Updater("myElementId");
|
|
* mgr.startAutoRefresh(60, "http://myserver.com/index.php");
|
|
* mgr.on("update", myFcnNeedsToKnow);
|
|
* <br>
|
|
* // short handed call directly from the element object
|
|
* Ext.get("foo").load({
|
|
url: "bar.php",
|
|
scripts: true,
|
|
params: "param1=foo&param2=bar",
|
|
text: "Loading Foo..."
|
|
* });
|
|
* </code></pre>
|
|
* @constructor
|
|
* Create new Updater directly.
|
|
* @param {Mixed} el The element to update
|
|
* @param {Boolean} forceNew (optional) By default the constructor checks to see if the passed element already
|
|
* has an Updater and if it does it returns the same instance. This will skip that check (useful for extending this class).
|
|
*/
|
|
Ext.UpdateManager = Ext.Updater = Ext.extend(Ext.util.Observable,
|
|
function() {
|
|
var BEFOREUPDATE = "beforeupdate",
|
|
UPDATE = "update",
|
|
FAILURE = "failure";
|
|
|
|
// private
|
|
function processSuccess(response){
|
|
var me = this;
|
|
me.transaction = null;
|
|
if (response.argument.form && response.argument.reset) {
|
|
try { // put in try/catch since some older FF releases had problems with this
|
|
response.argument.form.reset();
|
|
} catch(e){}
|
|
}
|
|
if (me.loadScripts) {
|
|
me.renderer.render(me.el, response, me,
|
|
updateComplete.createDelegate(me, [response]));
|
|
} else {
|
|
me.renderer.render(me.el, response, me);
|
|
updateComplete.call(me, response);
|
|
}
|
|
}
|
|
|
|
// private
|
|
function updateComplete(response, type, success){
|
|
this.fireEvent(type || UPDATE, this.el, response);
|
|
if(Ext.isFunction(response.argument.callback)){
|
|
response.argument.callback.call(response.argument.scope, this.el, Ext.isEmpty(success) ? true : false, response, response.argument.options);
|
|
}
|
|
}
|
|
|
|
// private
|
|
function processFailure(response){
|
|
updateComplete.call(this, response, FAILURE, !!(this.transaction = null));
|
|
}
|
|
|
|
return {
|
|
constructor: function(el, forceNew){
|
|
var me = this;
|
|
el = Ext.get(el);
|
|
if(!forceNew && el.updateManager){
|
|
return el.updateManager;
|
|
}
|
|
/**
|
|
* The Element object
|
|
* @type Ext.Element
|
|
*/
|
|
me.el = el;
|
|
/**
|
|
* Cached url to use for refreshes. Overwritten every time update() is called unless "discardUrl" param is set to true.
|
|
* @type String
|
|
*/
|
|
me.defaultUrl = null;
|
|
|
|
me.addEvents(
|
|
/**
|
|
* @event beforeupdate
|
|
* Fired before an update is made, return false from your handler and the update is cancelled.
|
|
* @param {Ext.Element} el
|
|
* @param {String/Object/Function} url
|
|
* @param {String/Object} params
|
|
*/
|
|
BEFOREUPDATE,
|
|
/**
|
|
* @event update
|
|
* Fired after successful update is made.
|
|
* @param {Ext.Element} el
|
|
* @param {Object} oResponseObject The response Object
|
|
*/
|
|
UPDATE,
|
|
/**
|
|
* @event failure
|
|
* Fired on update failure.
|
|
* @param {Ext.Element} el
|
|
* @param {Object} oResponseObject The response Object
|
|
*/
|
|
FAILURE
|
|
);
|
|
|
|
Ext.apply(me, Ext.Updater.defaults);
|
|
/**
|
|
* Blank page URL to use with SSL file uploads (defaults to {@link Ext.Updater.defaults#sslBlankUrl}).
|
|
* @property sslBlankUrl
|
|
* @type String
|
|
*/
|
|
/**
|
|
* Whether to append unique parameter on get request to disable caching (defaults to {@link Ext.Updater.defaults#disableCaching}).
|
|
* @property disableCaching
|
|
* @type Boolean
|
|
*/
|
|
/**
|
|
* Text for loading indicator (defaults to {@link Ext.Updater.defaults#indicatorText}).
|
|
* @property indicatorText
|
|
* @type String
|
|
*/
|
|
/**
|
|
* Whether to show indicatorText when loading (defaults to {@link Ext.Updater.defaults#showLoadIndicator}).
|
|
* @property showLoadIndicator
|
|
* @type String
|
|
*/
|
|
/**
|
|
* Timeout for requests or form posts in seconds (defaults to {@link Ext.Updater.defaults#timeout}).
|
|
* @property timeout
|
|
* @type Number
|
|
*/
|
|
/**
|
|
* True to process scripts in the output (defaults to {@link Ext.Updater.defaults#loadScripts}).
|
|
* @property loadScripts
|
|
* @type Boolean
|
|
*/
|
|
|
|
/**
|
|
* Transaction object of the current executing transaction, or null if there is no active transaction.
|
|
*/
|
|
me.transaction = null;
|
|
/**
|
|
* Delegate for refresh() prebound to "this", use myUpdater.refreshDelegate.createCallback(arg1, arg2) to bind arguments
|
|
* @type Function
|
|
*/
|
|
me.refreshDelegate = me.refresh.createDelegate(me);
|
|
/**
|
|
* Delegate for update() prebound to "this", use myUpdater.updateDelegate.createCallback(arg1, arg2) to bind arguments
|
|
* @type Function
|
|
*/
|
|
me.updateDelegate = me.update.createDelegate(me);
|
|
/**
|
|
* Delegate for formUpdate() prebound to "this", use myUpdater.formUpdateDelegate.createCallback(arg1, arg2) to bind arguments
|
|
* @type Function
|
|
*/
|
|
me.formUpdateDelegate = (me.formUpdate || function(){}).createDelegate(me);
|
|
|
|
/**
|
|
* The renderer for this Updater (defaults to {@link Ext.Updater.BasicRenderer}).
|
|
*/
|
|
me.renderer = me.renderer || me.getDefaultRenderer();
|
|
|
|
Ext.Updater.superclass.constructor.call(me);
|
|
},
|
|
|
|
/**
|
|
* Sets the content renderer for this Updater. See {@link Ext.Updater.BasicRenderer#render} for more details.
|
|
* @param {Object} renderer The object implementing the render() method
|
|
*/
|
|
setRenderer : function(renderer){
|
|
this.renderer = renderer;
|
|
},
|
|
|
|
/**
|
|
* Returns the current content renderer for this Updater. See {@link Ext.Updater.BasicRenderer#render} for more details.
|
|
* @return {Object}
|
|
*/
|
|
getRenderer : function(){
|
|
return this.renderer;
|
|
},
|
|
|
|
/**
|
|
* This is an overrideable method which returns a reference to a default
|
|
* renderer class if none is specified when creating the Ext.Updater.
|
|
* Defaults to {@link Ext.Updater.BasicRenderer}
|
|
*/
|
|
getDefaultRenderer: function() {
|
|
return new Ext.Updater.BasicRenderer();
|
|
},
|
|
|
|
/**
|
|
* Sets the default URL used for updates.
|
|
* @param {String/Function} defaultUrl The url or a function to call to get the url
|
|
*/
|
|
setDefaultUrl : function(defaultUrl){
|
|
this.defaultUrl = defaultUrl;
|
|
},
|
|
|
|
/**
|
|
* Get the Element this Updater is bound to
|
|
* @return {Ext.Element} The element
|
|
*/
|
|
getEl : function(){
|
|
return this.el;
|
|
},
|
|
|
|
/**
|
|
* Performs an <b>asynchronous</b> request, updating this element with the response.
|
|
* If params are specified it uses POST, otherwise it uses GET.<br><br>
|
|
* <b>Note:</b> Due to the asynchronous nature of remote server requests, the Element
|
|
* will not have been fully updated when the function returns. To post-process the returned
|
|
* data, use the callback option, or an <b><code>update</code></b> event handler.
|
|
* @param {Object} options A config object containing any of the following options:<ul>
|
|
* <li>url : <b>String/Function</b><p class="sub-desc">The URL to request or a function which
|
|
* <i>returns</i> the URL (defaults to the value of {@link Ext.Ajax#url} if not specified).</p></li>
|
|
* <li>method : <b>String</b><p class="sub-desc">The HTTP method to
|
|
* use. Defaults to POST if the <code>params</code> argument is present, otherwise GET.</p></li>
|
|
* <li>params : <b>String/Object/Function</b><p class="sub-desc">The
|
|
* parameters to pass to the server (defaults to none). These may be specified as a url-encoded
|
|
* string, or as an object containing properties which represent parameters,
|
|
* or as a function, which returns such an object.</p></li>
|
|
* <li>scripts : <b>Boolean</b><p class="sub-desc">If <code>true</code>
|
|
* any <script> tags embedded in the response text will be extracted
|
|
* and executed (defaults to {@link Ext.Updater.defaults#loadScripts}). If this option is specified,
|
|
* the callback will be called <i>after</i> the execution of the scripts.</p></li>
|
|
* <li>callback : <b>Function</b><p class="sub-desc">A function to
|
|
* be called when the response from the server arrives. The following
|
|
* parameters are passed:<ul>
|
|
* <li><b>el</b> : Ext.Element<p class="sub-desc">The Element being updated.</p></li>
|
|
* <li><b>success</b> : Boolean<p class="sub-desc">True for success, false for failure.</p></li>
|
|
* <li><b>response</b> : XMLHttpRequest<p class="sub-desc">The XMLHttpRequest which processed the update.</p></li>
|
|
* <li><b>options</b> : Object<p class="sub-desc">The config object passed to the update call.</p></li></ul>
|
|
* </p></li>
|
|
* <li>scope : <b>Object</b><p class="sub-desc">The scope in which
|
|
* to execute the callback (The callback's <code>this</code> reference.) If the
|
|
* <code>params</code> argument is a function, this scope is used for that function also.</p></li>
|
|
* <li>discardUrl : <b>Boolean</b><p class="sub-desc">By default, the URL of this request becomes
|
|
* the default URL for this Updater object, and will be subsequently used in {@link #refresh}
|
|
* calls. To bypass this behavior, pass <code>discardUrl:true</code> (defaults to false).</p></li>
|
|
* <li>timeout : <b>Number</b><p class="sub-desc">The number of seconds to wait for a response before
|
|
* timing out (defaults to {@link Ext.Updater.defaults#timeout}).</p></li>
|
|
* <li>text : <b>String</b><p class="sub-desc">The text to use as the innerHTML of the
|
|
* {@link Ext.Updater.defaults#indicatorText} div (defaults to 'Loading...'). To replace the entire div, not
|
|
* just the text, override {@link Ext.Updater.defaults#indicatorText} directly.</p></li>
|
|
* <li>nocache : <b>Boolean</b><p class="sub-desc">Only needed for GET
|
|
* requests, this option causes an extra, auto-generated parameter to be appended to the request
|
|
* to defeat caching (defaults to {@link Ext.Updater.defaults#disableCaching}).</p></li></ul>
|
|
* <p>
|
|
* For example:
|
|
<pre><code>
|
|
um.update({
|
|
url: "your-url.php",
|
|
params: {param1: "foo", param2: "bar"}, // or a URL encoded string
|
|
callback: yourFunction,
|
|
scope: yourObject, //(optional scope)
|
|
discardUrl: true,
|
|
nocache: true,
|
|
text: "Loading...",
|
|
timeout: 60,
|
|
scripts: false // Save time by avoiding RegExp execution.
|
|
});
|
|
</code></pre>
|
|
*/
|
|
update : function(url, params, callback, discardUrl){
|
|
var me = this,
|
|
cfg,
|
|
callerScope;
|
|
|
|
if(me.fireEvent(BEFOREUPDATE, me.el, url, params) !== false){
|
|
if(Ext.isObject(url)){ // must be config object
|
|
cfg = url;
|
|
url = cfg.url;
|
|
params = params || cfg.params;
|
|
callback = callback || cfg.callback;
|
|
discardUrl = discardUrl || cfg.discardUrl;
|
|
callerScope = cfg.scope;
|
|
if(!Ext.isEmpty(cfg.nocache)){me.disableCaching = cfg.nocache;};
|
|
if(!Ext.isEmpty(cfg.text)){me.indicatorText = '<div class="loading-indicator">'+cfg.text+"</div>";};
|
|
if(!Ext.isEmpty(cfg.scripts)){me.loadScripts = cfg.scripts;};
|
|
if(!Ext.isEmpty(cfg.timeout)){me.timeout = cfg.timeout;};
|
|
}
|
|
me.showLoading();
|
|
|
|
if(!discardUrl){
|
|
me.defaultUrl = url;
|
|
}
|
|
if(Ext.isFunction(url)){
|
|
url = url.call(me);
|
|
}
|
|
|
|
var o = Ext.apply({}, {
|
|
url : url,
|
|
params: (Ext.isFunction(params) && callerScope) ? params.createDelegate(callerScope) : params,
|
|
success: processSuccess,
|
|
failure: processFailure,
|
|
scope: me,
|
|
callback: undefined,
|
|
timeout: (me.timeout*1000),
|
|
disableCaching: me.disableCaching,
|
|
argument: {
|
|
"options": cfg,
|
|
"url": url,
|
|
"form": null,
|
|
"callback": callback,
|
|
"scope": callerScope || window,
|
|
"params": params
|
|
}
|
|
}, cfg);
|
|
|
|
me.transaction = Ext.Ajax.request(o);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* <p>Performs an asynchronous form post, updating this element with the response. If the form has the attribute
|
|
* enctype="<a href="http://www.faqs.org/rfcs/rfc2388.html">multipart/form-data</a>", it assumes it's a file upload.
|
|
* Uses this.sslBlankUrl for SSL file uploads to prevent IE security warning.</p>
|
|
* <p>File uploads are not performed using normal "Ajax" techniques, that is they are <b>not</b>
|
|
* performed using XMLHttpRequests. Instead the form is submitted in the standard manner with the
|
|
* DOM <code><form></code> element temporarily modified to have its
|
|
* <a href="http://www.w3.org/TR/REC-html40/present/frames.html#adef-target">target</a> set to refer
|
|
* to a dynamically generated, hidden <code><iframe></code> which is inserted into the document
|
|
* but removed after the return data has been gathered.</p>
|
|
* <p>Be aware that file upload packets, sent with the content type <a href="http://www.faqs.org/rfcs/rfc2388.html">multipart/form-data</a>
|
|
* and some server technologies (notably JEE) may require some custom processing in order to
|
|
* retrieve parameter names and parameter values from the packet content.</p>
|
|
* @param {String/HTMLElement} form The form Id or form element
|
|
* @param {String} url (optional) The url to pass the form to. If omitted the action attribute on the form will be used.
|
|
* @param {Boolean} reset (optional) Whether to try to reset the form after the update
|
|
* @param {Function} callback (optional) Callback when transaction is complete. The following
|
|
* parameters are passed:<ul>
|
|
* <li><b>el</b> : Ext.Element<p class="sub-desc">The Element being updated.</p></li>
|
|
* <li><b>success</b> : Boolean<p class="sub-desc">True for success, false for failure.</p></li>
|
|
* <li><b>response</b> : XMLHttpRequest<p class="sub-desc">The XMLHttpRequest which processed the update.</p></li></ul>
|
|
*/
|
|
formUpdate : function(form, url, reset, callback){
|
|
var me = this;
|
|
if(me.fireEvent(BEFOREUPDATE, me.el, form, url) !== false){
|
|
if(Ext.isFunction(url)){
|
|
url = url.call(me);
|
|
}
|
|
form = Ext.getDom(form);
|
|
me.transaction = Ext.Ajax.request({
|
|
form: form,
|
|
url:url,
|
|
success: processSuccess,
|
|
failure: processFailure,
|
|
scope: me,
|
|
timeout: (me.timeout*1000),
|
|
argument: {
|
|
"url": url,
|
|
"form": form,
|
|
"callback": callback,
|
|
"reset": reset
|
|
}
|
|
});
|
|
me.showLoading.defer(1, me);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Set this element to auto refresh. Can be canceled by calling {@link #stopAutoRefresh}.
|
|
* @param {Number} interval How often to update (in seconds).
|
|
* @param {String/Object/Function} url (optional) The url for this request, a config object in the same format
|
|
* supported by {@link #load}, or a function to call to get the url (defaults to the last used url). Note that while
|
|
* the url used in a load call can be reused by this method, other load config options will not be reused and must be
|
|
* sepcified as part of a config object passed as this paramter if needed.
|
|
* @param {String/Object} params (optional) The parameters to pass as either a url encoded string
|
|
* "¶m1=1¶m2=2" or as an object {param1: 1, param2: 2}
|
|
* @param {Function} callback (optional) Callback when transaction is complete - called with signature (oElement, bSuccess)
|
|
* @param {Boolean} refreshNow (optional) Whether to execute the refresh now, or wait the interval
|
|
*/
|
|
startAutoRefresh : function(interval, url, params, callback, refreshNow){
|
|
var me = this;
|
|
if(refreshNow){
|
|
me.update(url || me.defaultUrl, params, callback, true);
|
|
}
|
|
if(me.autoRefreshProcId){
|
|
clearInterval(me.autoRefreshProcId);
|
|
}
|
|
me.autoRefreshProcId = setInterval(me.update.createDelegate(me, [url || me.defaultUrl, params, callback, true]), interval * 1000);
|
|
},
|
|
|
|
/**
|
|
* Stop auto refresh on this element.
|
|
*/
|
|
stopAutoRefresh : function(){
|
|
if(this.autoRefreshProcId){
|
|
clearInterval(this.autoRefreshProcId);
|
|
delete this.autoRefreshProcId;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Returns true if the Updater is currently set to auto refresh its content (see {@link #startAutoRefresh}), otherwise false.
|
|
*/
|
|
isAutoRefreshing : function(){
|
|
return !!this.autoRefreshProcId;
|
|
},
|
|
|
|
/**
|
|
* Display the element's "loading" state. By default, the element is updated with {@link #indicatorText}. This
|
|
* method may be overridden to perform a custom action while this Updater is actively updating its contents.
|
|
*/
|
|
showLoading : function(){
|
|
if(this.showLoadIndicator){
|
|
this.el.dom.innerHTML = this.indicatorText;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Aborts the currently executing transaction, if any.
|
|
*/
|
|
abort : function(){
|
|
if(this.transaction){
|
|
Ext.Ajax.abort(this.transaction);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Returns true if an update is in progress, otherwise false.
|
|
* @return {Boolean}
|
|
*/
|
|
isUpdating : function(){
|
|
return this.transaction ? Ext.Ajax.isLoading(this.transaction) : false;
|
|
},
|
|
|
|
/**
|
|
* Refresh the element with the last used url or defaultUrl. If there is no url, it returns immediately
|
|
* @param {Function} callback (optional) Callback when transaction is complete - called with signature (oElement, bSuccess)
|
|
*/
|
|
refresh : function(callback){
|
|
if(this.defaultUrl){
|
|
this.update(this.defaultUrl, null, callback, true);
|
|
}
|
|
}
|
|
};
|
|
}());
|
|
|
|
/**
|
|
* @class Ext.Updater.defaults
|
|
* The defaults collection enables customizing the default properties of Updater
|
|
*/
|
|
Ext.Updater.defaults = {
|
|
/**
|
|
* Timeout for requests or form posts in seconds (defaults to 30 seconds).
|
|
* @type Number
|
|
*/
|
|
timeout : 30,
|
|
/**
|
|
* True to append a unique parameter to GET requests to disable caching (defaults to false).
|
|
* @type Boolean
|
|
*/
|
|
disableCaching : false,
|
|
/**
|
|
* Whether or not to show {@link #indicatorText} during loading (defaults to true).
|
|
* @type Boolean
|
|
*/
|
|
showLoadIndicator : true,
|
|
/**
|
|
* Text for loading indicator (defaults to '<div class="loading-indicator">Loading...</div>').
|
|
* @type String
|
|
*/
|
|
indicatorText : '<div class="loading-indicator">Loading...</div>',
|
|
/**
|
|
* True to process scripts by default (defaults to false).
|
|
* @type Boolean
|
|
*/
|
|
loadScripts : false,
|
|
/**
|
|
* Blank page URL to use with SSL file uploads (defaults to {@link Ext#SSL_SECURE_URL} if set, or "javascript:false").
|
|
* @type String
|
|
*/
|
|
sslBlankUrl : Ext.SSL_SECURE_URL
|
|
};
|
|
|
|
|
|
/**
|
|
* Static convenience method. <b>This method is deprecated in favor of el.load({url:'foo.php', ...})</b>.
|
|
* Usage:
|
|
* <pre><code>Ext.Updater.updateElement("my-div", "stuff.php");</code></pre>
|
|
* @param {Mixed} el The element to update
|
|
* @param {String} url The url
|
|
* @param {String/Object} params (optional) Url encoded param string or an object of name/value pairs
|
|
* @param {Object} options (optional) A config object with any of the Updater properties you want to set - for
|
|
* example: {disableCaching:true, indicatorText: "Loading data..."}
|
|
* @static
|
|
* @deprecated
|
|
* @member Ext.Updater
|
|
*/
|
|
Ext.Updater.updateElement = function(el, url, params, options){
|
|
var um = Ext.get(el).getUpdater();
|
|
Ext.apply(um, options);
|
|
um.update(url, params, options ? options.callback : null);
|
|
};
|
|
|
|
/**
|
|
* @class Ext.Updater.BasicRenderer
|
|
* <p>This class is a base class implementing a simple render method which updates an element using results from an Ajax request.</p>
|
|
* <p>The BasicRenderer updates the element's innerHTML with the responseText. To perform a custom render (i.e. XML or JSON processing),
|
|
* create an object with a conforming {@link #render} method and pass it to setRenderer on the Updater.</p>
|
|
*/
|
|
Ext.Updater.BasicRenderer = function(){};
|
|
|
|
Ext.Updater.BasicRenderer.prototype = {
|
|
/**
|
|
* This method is called when an Ajax response is received, and an Element needs updating.
|
|
* @param {Ext.Element} el The element being rendered
|
|
* @param {Object} xhr The XMLHttpRequest object
|
|
* @param {Updater} updateManager The calling update manager
|
|
* @param {Function} callback A callback that will need to be called if loadScripts is true on the Updater
|
|
*/
|
|
render : function(el, response, updateManager, callback){
|
|
el.update(response.responseText, updateManager.loadScripts, callback);
|
|
}
|
|
};/**
|
|
* @class Date
|
|
*
|
|
* The date parsing and formatting syntax contains a subset of
|
|
* <a href="http://www.php.net/date">PHP's date() function</a>, and the formats that are
|
|
* supported will provide results equivalent to their PHP versions.
|
|
*
|
|
* The following is a list of all currently supported formats:
|
|
* <pre>
|
|
Format Description Example returned values
|
|
------ ----------------------------------------------------------------------- -----------------------
|
|
d Day of the month, 2 digits with leading zeros 01 to 31
|
|
D A short textual representation of the day of the week Mon to Sun
|
|
j Day of the month without leading zeros 1 to 31
|
|
l A full textual representation of the day of the week Sunday to Saturday
|
|
N ISO-8601 numeric representation of the day of the week 1 (for Monday) through 7 (for Sunday)
|
|
S English ordinal suffix for the day of the month, 2 characters st, nd, rd or th. Works well with j
|
|
w Numeric representation of the day of the week 0 (for Sunday) to 6 (for Saturday)
|
|
z The day of the year (starting from 0) 0 to 364 (365 in leap years)
|
|
W ISO-8601 week number of year, weeks starting on Monday 01 to 53
|
|
F A full textual representation of a month, such as January or March January to December
|
|
m Numeric representation of a month, with leading zeros 01 to 12
|
|
M A short textual representation of a month Jan to Dec
|
|
n Numeric representation of a month, without leading zeros 1 to 12
|
|
t Number of days in the given month 28 to 31
|
|
L Whether it's a leap year 1 if it is a leap year, 0 otherwise.
|
|
o ISO-8601 year number (identical to (Y), but if the ISO week number (W) Examples: 1998 or 2004
|
|
belongs to the previous or next year, that year is used instead)
|
|
Y A full numeric representation of a year, 4 digits Examples: 1999 or 2003
|
|
y A two digit representation of a year Examples: 99 or 03
|
|
a Lowercase Ante meridiem and Post meridiem am or pm
|
|
A Uppercase Ante meridiem and Post meridiem AM or PM
|
|
g 12-hour format of an hour without leading zeros 1 to 12
|
|
G 24-hour format of an hour without leading zeros 0 to 23
|
|
h 12-hour format of an hour with leading zeros 01 to 12
|
|
H 24-hour format of an hour with leading zeros 00 to 23
|
|
i Minutes, with leading zeros 00 to 59
|
|
s Seconds, with leading zeros 00 to 59
|
|
u Decimal fraction of a second Examples:
|
|
(minimum 1 digit, arbitrary number of digits allowed) 001 (i.e. 0.001s) or
|
|
100 (i.e. 0.100s) or
|
|
999 (i.e. 0.999s) or
|
|
999876543210 (i.e. 0.999876543210s)
|
|
O Difference to Greenwich time (GMT) in hours and minutes Example: +1030
|
|
P Difference to Greenwich time (GMT) with colon between hours and minutes Example: -08:00
|
|
T Timezone abbreviation of the machine running the code Examples: EST, MDT, PDT ...
|
|
Z Timezone offset in seconds (negative if west of UTC, positive if east) -43200 to 50400
|
|
c ISO 8601 date
|
|
Notes: Examples:
|
|
1) If unspecified, the month / day defaults to the current month / day, 1991 or
|
|
the time defaults to midnight, while the timezone defaults to the 1992-10 or
|
|
browser's timezone. If a time is specified, it must include both hours 1993-09-20 or
|
|
and minutes. The "T" delimiter, seconds, milliseconds and timezone 1994-08-19T16:20+01:00 or
|
|
are optional. 1995-07-18T17:21:28-02:00 or
|
|
2) The decimal fraction of a second, if specified, must contain at 1996-06-17T18:22:29.98765+03:00 or
|
|
least 1 digit (there is no limit to the maximum number 1997-05-16T19:23:30,12345-0400 or
|
|
of digits allowed), and may be delimited by either a '.' or a ',' 1998-04-15T20:24:31.2468Z or
|
|
Refer to the examples on the right for the various levels of 1999-03-14T20:24:32Z or
|
|
date-time granularity which are supported, or see 2000-02-13T21:25:33
|
|
http://www.w3.org/TR/NOTE-datetime for more info. 2001-01-12 22:26:34
|
|
U Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT) 1193432466 or -2138434463
|
|
M$ Microsoft AJAX serialized dates \/Date(1238606590509)\/ (i.e. UTC milliseconds since epoch) or
|
|
\/Date(1238606590509+0800)\/
|
|
</pre>
|
|
*
|
|
* Example usage (note that you must escape format specifiers with '\\' to render them as character literals):
|
|
* <pre><code>
|
|
// Sample date:
|
|
// 'Wed Jan 10 2007 15:05:01 GMT-0600 (Central Standard Time)'
|
|
|
|
var dt = new Date('1/10/2007 03:05:01 PM GMT-0600');
|
|
document.write(dt.format('Y-m-d')); // 2007-01-10
|
|
document.write(dt.format('F j, Y, g:i a')); // January 10, 2007, 3:05 pm
|
|
document.write(dt.format('l, \\t\\he jS \\of F Y h:i:s A')); // Wednesday, the 10th of January 2007 03:05:01 PM
|
|
</code></pre>
|
|
*
|
|
* Here are some standard date/time patterns that you might find helpful. They
|
|
* are not part of the source of Date.js, but to use them you can simply copy this
|
|
* block of code into any script that is included after Date.js and they will also become
|
|
* globally available on the Date object. Feel free to add or remove patterns as needed in your code.
|
|
* <pre><code>
|
|
Date.patterns = {
|
|
ISO8601Long:"Y-m-d H:i:s",
|
|
ISO8601Short:"Y-m-d",
|
|
ShortDate: "n/j/Y",
|
|
LongDate: "l, F d, Y",
|
|
FullDateTime: "l, F d, Y g:i:s A",
|
|
MonthDay: "F d",
|
|
ShortTime: "g:i A",
|
|
LongTime: "g:i:s A",
|
|
SortableDateTime: "Y-m-d\\TH:i:s",
|
|
UniversalSortableDateTime: "Y-m-d H:i:sO",
|
|
YearMonth: "F, Y"
|
|
};
|
|
</code></pre>
|
|
*
|
|
* Example usage:
|
|
* <pre><code>
|
|
var dt = new Date();
|
|
document.write(dt.format(Date.patterns.ShortDate));
|
|
</code></pre>
|
|
* <p>Developer-written, custom formats may be used by supplying both a formatting and a parsing function
|
|
* which perform to specialized requirements. The functions are stored in {@link #parseFunctions} and {@link #formatFunctions}.</p>
|
|
*/
|
|
|
|
/*
|
|
* Most of the date-formatting functions below are the excellent work of Baron Schwartz.
|
|
* (see http://www.xaprb.com/blog/2005/12/12/javascript-closures-for-runtime-efficiency/)
|
|
* They generate precompiled functions from format patterns instead of parsing and
|
|
* processing each pattern every time a date is formatted. These functions are available
|
|
* on every Date object.
|
|
*/
|
|
|
|
(function() {
|
|
|
|
/**
|
|
* Global flag which determines if strict date parsing should be used.
|
|
* Strict date parsing will not roll-over invalid dates, which is the
|
|
* default behaviour of javascript Date objects.
|
|
* (see {@link #parseDate} for more information)
|
|
* Defaults to <tt>false</tt>.
|
|
* @static
|
|
* @type Boolean
|
|
*/
|
|
Date.useStrict = false;
|
|
|
|
|
|
// create private copy of Ext's String.format() method
|
|
// - to remove unnecessary dependency
|
|
// - to resolve namespace conflict with M$-Ajax's implementation
|
|
function xf(format) {
|
|
var args = Array.prototype.slice.call(arguments, 1);
|
|
return format.replace(/\{(\d+)\}/g, function(m, i) {
|
|
return args[i];
|
|
});
|
|
}
|
|
|
|
|
|
// private
|
|
Date.formatCodeToRegex = function(character, currentGroup) {
|
|
// Note: currentGroup - position in regex result array (see notes for Date.parseCodes below)
|
|
var p = Date.parseCodes[character];
|
|
|
|
if (p) {
|
|
p = typeof p == 'function'? p() : p;
|
|
Date.parseCodes[character] = p; // reassign function result to prevent repeated execution
|
|
}
|
|
|
|
return p ? Ext.applyIf({
|
|
c: p.c ? xf(p.c, currentGroup || "{0}") : p.c
|
|
}, p) : {
|
|
g:0,
|
|
c:null,
|
|
s:Ext.escapeRe(character) // treat unrecognised characters as literals
|
|
};
|
|
};
|
|
|
|
// private shorthand for Date.formatCodeToRegex since we'll be using it fairly often
|
|
var $f = Date.formatCodeToRegex;
|
|
|
|
Ext.apply(Date, {
|
|
/**
|
|
* <p>An object hash in which each property is a date parsing function. The property name is the
|
|
* format string which that function parses.</p>
|
|
* <p>This object is automatically populated with date parsing functions as
|
|
* date formats are requested for Ext standard formatting strings.</p>
|
|
* <p>Custom parsing functions may be inserted into this object, keyed by a name which from then on
|
|
* may be used as a format string to {@link #parseDate}.<p>
|
|
* <p>Example:</p><pre><code>
|
|
Date.parseFunctions['x-date-format'] = myDateParser;
|
|
</code></pre>
|
|
* <p>A parsing function should return a Date object, and is passed the following parameters:<div class="mdetail-params"><ul>
|
|
* <li><code>date</code> : String<div class="sub-desc">The date string to parse.</div></li>
|
|
* <li><code>strict</code> : Boolean<div class="sub-desc">True to validate date strings while parsing
|
|
* (i.e. prevent javascript Date "rollover") (The default must be false).
|
|
* Invalid date strings should return null when parsed.</div></li>
|
|
* </ul></div></p>
|
|
* <p>To enable Dates to also be <i>formatted</i> according to that format, a corresponding
|
|
* formatting function must be placed into the {@link #formatFunctions} property.
|
|
* @property parseFunctions
|
|
* @static
|
|
* @type Object
|
|
*/
|
|
parseFunctions: {
|
|
"M$": function(input, strict) {
|
|
// note: the timezone offset is ignored since the M$ Ajax server sends
|
|
// a UTC milliseconds-since-Unix-epoch value (negative values are allowed)
|
|
var re = new RegExp('\\/Date\\(([-+])?(\\d+)(?:[+-]\\d{4})?\\)\\/');
|
|
var r = (input || '').match(re);
|
|
return r? new Date(((r[1] || '') + r[2]) * 1) : null;
|
|
}
|
|
},
|
|
parseRegexes: [],
|
|
|
|
/**
|
|
* <p>An object hash in which each property is a date formatting function. The property name is the
|
|
* format string which corresponds to the produced formatted date string.</p>
|
|
* <p>This object is automatically populated with date formatting functions as
|
|
* date formats are requested for Ext standard formatting strings.</p>
|
|
* <p>Custom formatting functions may be inserted into this object, keyed by a name which from then on
|
|
* may be used as a format string to {@link #format}. Example:</p><pre><code>
|
|
Date.formatFunctions['x-date-format'] = myDateFormatter;
|
|
</code></pre>
|
|
* <p>A formatting function should return a string representation of the passed Date object, and is passed the following parameters:<div class="mdetail-params"><ul>
|
|
* <li><code>date</code> : Date<div class="sub-desc">The Date to format.</div></li>
|
|
* </ul></div></p>
|
|
* <p>To enable date strings to also be <i>parsed</i> according to that format, a corresponding
|
|
* parsing function must be placed into the {@link #parseFunctions} property.
|
|
* @property formatFunctions
|
|
* @static
|
|
* @type Object
|
|
*/
|
|
formatFunctions: {
|
|
"M$": function() {
|
|
// UTC milliseconds since Unix epoch (M$-AJAX serialized date format (MRSF))
|
|
return '\\/Date(' + this.getTime() + ')\\/';
|
|
}
|
|
},
|
|
|
|
y2kYear : 50,
|
|
|
|
/**
|
|
* Date interval constant
|
|
* @static
|
|
* @type String
|
|
*/
|
|
MILLI : "ms",
|
|
|
|
/**
|
|
* Date interval constant
|
|
* @static
|
|
* @type String
|
|
*/
|
|
SECOND : "s",
|
|
|
|
/**
|
|
* Date interval constant
|
|
* @static
|
|
* @type String
|
|
*/
|
|
MINUTE : "mi",
|
|
|
|
/** Date interval constant
|
|
* @static
|
|
* @type String
|
|
*/
|
|
HOUR : "h",
|
|
|
|
/**
|
|
* Date interval constant
|
|
* @static
|
|
* @type String
|
|
*/
|
|
DAY : "d",
|
|
|
|
/**
|
|
* Date interval constant
|
|
* @static
|
|
* @type String
|
|
*/
|
|
MONTH : "mo",
|
|
|
|
/**
|
|
* Date interval constant
|
|
* @static
|
|
* @type String
|
|
*/
|
|
YEAR : "y",
|
|
|
|
/**
|
|
* <p>An object hash containing default date values used during date parsing.</p>
|
|
* <p>The following properties are available:<div class="mdetail-params"><ul>
|
|
* <li><code>y</code> : Number<div class="sub-desc">The default year value. (defaults to undefined)</div></li>
|
|
* <li><code>m</code> : Number<div class="sub-desc">The default 1-based month value. (defaults to undefined)</div></li>
|
|
* <li><code>d</code> : Number<div class="sub-desc">The default day value. (defaults to undefined)</div></li>
|
|
* <li><code>h</code> : Number<div class="sub-desc">The default hour value. (defaults to undefined)</div></li>
|
|
* <li><code>i</code> : Number<div class="sub-desc">The default minute value. (defaults to undefined)</div></li>
|
|
* <li><code>s</code> : Number<div class="sub-desc">The default second value. (defaults to undefined)</div></li>
|
|
* <li><code>ms</code> : Number<div class="sub-desc">The default millisecond value. (defaults to undefined)</div></li>
|
|
* </ul></div></p>
|
|
* <p>Override these properties to customize the default date values used by the {@link #parseDate} method.</p>
|
|
* <p><b>Note: In countries which experience Daylight Saving Time (i.e. DST), the <tt>h</tt>, <tt>i</tt>, <tt>s</tt>
|
|
* and <tt>ms</tt> properties may coincide with the exact time in which DST takes effect.
|
|
* It is the responsiblity of the developer to account for this.</b></p>
|
|
* Example Usage:
|
|
* <pre><code>
|
|
// set default day value to the first day of the month
|
|
Date.defaults.d = 1;
|
|
|
|
// parse a February date string containing only year and month values.
|
|
// setting the default day value to 1 prevents weird date rollover issues
|
|
// when attempting to parse the following date string on, for example, March 31st 2009.
|
|
Date.parseDate('2009-02', 'Y-m'); // returns a Date object representing February 1st 2009
|
|
</code></pre>
|
|
* @property defaults
|
|
* @static
|
|
* @type Object
|
|
*/
|
|
defaults: {},
|
|
|
|
/**
|
|
* An array of textual day names.
|
|
* Override these values for international dates.
|
|
* Example:
|
|
* <pre><code>
|
|
Date.dayNames = [
|
|
'SundayInYourLang',
|
|
'MondayInYourLang',
|
|
...
|
|
];
|
|
</code></pre>
|
|
* @type Array
|
|
* @static
|
|
*/
|
|
dayNames : [
|
|
"Sunday",
|
|
"Monday",
|
|
"Tuesday",
|
|
"Wednesday",
|
|
"Thursday",
|
|
"Friday",
|
|
"Saturday"
|
|
],
|
|
|
|
/**
|
|
* An array of textual month names.
|
|
* Override these values for international dates.
|
|
* Example:
|
|
* <pre><code>
|
|
Date.monthNames = [
|
|
'JanInYourLang',
|
|
'FebInYourLang',
|
|
...
|
|
];
|
|
</code></pre>
|
|
* @type Array
|
|
* @static
|
|
*/
|
|
monthNames : [
|
|
"January",
|
|
"February",
|
|
"March",
|
|
"April",
|
|
"May",
|
|
"June",
|
|
"July",
|
|
"August",
|
|
"September",
|
|
"October",
|
|
"November",
|
|
"December"
|
|
],
|
|
|
|
/**
|
|
* An object hash of zero-based javascript month numbers (with short month names as keys. note: keys are case-sensitive).
|
|
* Override these values for international dates.
|
|
* Example:
|
|
* <pre><code>
|
|
Date.monthNumbers = {
|
|
'ShortJanNameInYourLang':0,
|
|
'ShortFebNameInYourLang':1,
|
|
...
|
|
};
|
|
</code></pre>
|
|
* @type Object
|
|
* @static
|
|
*/
|
|
monthNumbers : {
|
|
Jan:0,
|
|
Feb:1,
|
|
Mar:2,
|
|
Apr:3,
|
|
May:4,
|
|
Jun:5,
|
|
Jul:6,
|
|
Aug:7,
|
|
Sep:8,
|
|
Oct:9,
|
|
Nov:10,
|
|
Dec:11
|
|
},
|
|
|
|
/**
|
|
* Get the short month name for the given month number.
|
|
* Override this function for international dates.
|
|
* @param {Number} month A zero-based javascript month number.
|
|
* @return {String} The short month name.
|
|
* @static
|
|
*/
|
|
getShortMonthName : function(month) {
|
|
return Date.monthNames[month].substring(0, 3);
|
|
},
|
|
|
|
/**
|
|
* Get the short day name for the given day number.
|
|
* Override this function for international dates.
|
|
* @param {Number} day A zero-based javascript day number.
|
|
* @return {String} The short day name.
|
|
* @static
|
|
*/
|
|
getShortDayName : function(day) {
|
|
return Date.dayNames[day].substring(0, 3);
|
|
},
|
|
|
|
/**
|
|
* Get the zero-based javascript month number for the given short/full month name.
|
|
* Override this function for international dates.
|
|
* @param {String} name The short/full month name.
|
|
* @return {Number} The zero-based javascript month number.
|
|
* @static
|
|
*/
|
|
getMonthNumber : function(name) {
|
|
// handle camel casing for english month names (since the keys for the Date.monthNumbers hash are case sensitive)
|
|
return Date.monthNumbers[name.substring(0, 1).toUpperCase() + name.substring(1, 3).toLowerCase()];
|
|
},
|
|
|
|
/**
|
|
* Checks if the specified format contains hour information
|
|
* @param {Object} format The format to check
|
|
* @return {Boolean} True if the format contains hour information
|
|
* @static
|
|
*/
|
|
formatContainsHourInfo : (function(){
|
|
var stripEscapeRe = /(\\.)/g,
|
|
hourInfoRe = /([gGhHisucUOPZ]|M\$)/;
|
|
return function(format){
|
|
return hourInfoRe.test(format.replace(stripEscapeRe, ''));
|
|
};
|
|
})(),
|
|
|
|
/**
|
|
* The base format-code to formatting-function hashmap used by the {@link #format} method.
|
|
* Formatting functions are strings (or functions which return strings) which
|
|
* will return the appropriate value when evaluated in the context of the Date object
|
|
* from which the {@link #format} method is called.
|
|
* Add to / override these mappings for custom date formatting.
|
|
* Note: Date.format() treats characters as literals if an appropriate mapping cannot be found.
|
|
* Example:
|
|
* <pre><code>
|
|
Date.formatCodes.x = "String.leftPad(this.getDate(), 2, '0')";
|
|
(new Date()).format("X"); // returns the current day of the month
|
|
</code></pre>
|
|
* @type Object
|
|
* @static
|
|
*/
|
|
formatCodes : {
|
|
d: "String.leftPad(this.getDate(), 2, '0')",
|
|
D: "Date.getShortDayName(this.getDay())", // get localised short day name
|
|
j: "this.getDate()",
|
|
l: "Date.dayNames[this.getDay()]",
|
|
N: "(this.getDay() ? this.getDay() : 7)",
|
|
S: "this.getSuffix()",
|
|
w: "this.getDay()",
|
|
z: "this.getDayOfYear()",
|
|
W: "String.leftPad(this.getWeekOfYear(), 2, '0')",
|
|
F: "Date.monthNames[this.getMonth()]",
|
|
m: "String.leftPad(this.getMonth() + 1, 2, '0')",
|
|
M: "Date.getShortMonthName(this.getMonth())", // get localised short month name
|
|
n: "(this.getMonth() + 1)",
|
|
t: "this.getDaysInMonth()",
|
|
L: "(this.isLeapYear() ? 1 : 0)",
|
|
o: "(this.getFullYear() + (this.getWeekOfYear() == 1 && this.getMonth() > 0 ? +1 : (this.getWeekOfYear() >= 52 && this.getMonth() < 11 ? -1 : 0)))",
|
|
Y: "String.leftPad(this.getFullYear(), 4, '0')",
|
|
y: "('' + this.getFullYear()).substring(2, 4)",
|
|
a: "(this.getHours() < 12 ? 'am' : 'pm')",
|
|
A: "(this.getHours() < 12 ? 'AM' : 'PM')",
|
|
g: "((this.getHours() % 12) ? this.getHours() % 12 : 12)",
|
|
G: "this.getHours()",
|
|
h: "String.leftPad((this.getHours() % 12) ? this.getHours() % 12 : 12, 2, '0')",
|
|
H: "String.leftPad(this.getHours(), 2, '0')",
|
|
i: "String.leftPad(this.getMinutes(), 2, '0')",
|
|
s: "String.leftPad(this.getSeconds(), 2, '0')",
|
|
u: "String.leftPad(this.getMilliseconds(), 3, '0')",
|
|
O: "this.getGMTOffset()",
|
|
P: "this.getGMTOffset(true)",
|
|
T: "this.getTimezone()",
|
|
Z: "(this.getTimezoneOffset() * -60)",
|
|
|
|
c: function() { // ISO-8601 -- GMT format
|
|
for (var c = "Y-m-dTH:i:sP", code = [], i = 0, l = c.length; i < l; ++i) {
|
|
var e = c.charAt(i);
|
|
code.push(e == "T" ? "'T'" : Date.getFormatCode(e)); // treat T as a character literal
|
|
}
|
|
return code.join(" + ");
|
|
},
|
|
/*
|
|
c: function() { // ISO-8601 -- UTC format
|
|
return [
|
|
"this.getUTCFullYear()", "'-'",
|
|
"String.leftPad(this.getUTCMonth() + 1, 2, '0')", "'-'",
|
|
"String.leftPad(this.getUTCDate(), 2, '0')",
|
|
"'T'",
|
|
"String.leftPad(this.getUTCHours(), 2, '0')", "':'",
|
|
"String.leftPad(this.getUTCMinutes(), 2, '0')", "':'",
|
|
"String.leftPad(this.getUTCSeconds(), 2, '0')",
|
|
"'Z'"
|
|
].join(" + ");
|
|
},
|
|
*/
|
|
|
|
U: "Math.round(this.getTime() / 1000)"
|
|
},
|
|
|
|
/**
|
|
* Checks if the passed Date parameters will cause a javascript Date "rollover".
|
|
* @param {Number} year 4-digit year
|
|
* @param {Number} month 1-based month-of-year
|
|
* @param {Number} day Day of month
|
|
* @param {Number} hour (optional) Hour
|
|
* @param {Number} minute (optional) Minute
|
|
* @param {Number} second (optional) Second
|
|
* @param {Number} millisecond (optional) Millisecond
|
|
* @return {Boolean} true if the passed parameters do not cause a Date "rollover", false otherwise.
|
|
* @static
|
|
*/
|
|
isValid : function(y, m, d, h, i, s, ms) {
|
|
// setup defaults
|
|
h = h || 0;
|
|
i = i || 0;
|
|
s = s || 0;
|
|
ms = ms || 0;
|
|
|
|
// Special handling for year < 100
|
|
var dt = new Date(y < 100 ? 100 : y, m - 1, d, h, i, s, ms).add(Date.YEAR, y < 100 ? y - 100 : 0);
|
|
|
|
return y == dt.getFullYear() &&
|
|
m == dt.getMonth() + 1 &&
|
|
d == dt.getDate() &&
|
|
h == dt.getHours() &&
|
|
i == dt.getMinutes() &&
|
|
s == dt.getSeconds() &&
|
|
ms == dt.getMilliseconds();
|
|
},
|
|
|
|
/**
|
|
* Parses the passed string using the specified date format.
|
|
* Note that this function expects normal calendar dates, meaning that months are 1-based (i.e. 1 = January).
|
|
* The {@link #defaults} hash will be used for any date value (i.e. year, month, day, hour, minute, second or millisecond)
|
|
* which cannot be found in the passed string. If a corresponding default date value has not been specified in the {@link #defaults} hash,
|
|
* the current date's year, month, day or DST-adjusted zero-hour time value will be used instead.
|
|
* Keep in mind that the input date string must precisely match the specified format string
|
|
* in order for the parse operation to be successful (failed parse operations return a null value).
|
|
* <p>Example:</p><pre><code>
|
|
//dt = Fri May 25 2007 (current date)
|
|
var dt = new Date();
|
|
|
|
//dt = Thu May 25 2006 (today's month/day in 2006)
|
|
dt = Date.parseDate("2006", "Y");
|
|
|
|
//dt = Sun Jan 15 2006 (all date parts specified)
|
|
dt = Date.parseDate("2006-01-15", "Y-m-d");
|
|
|
|
//dt = Sun Jan 15 2006 15:20:01
|
|
dt = Date.parseDate("2006-01-15 3:20:01 PM", "Y-m-d g:i:s A");
|
|
|
|
// attempt to parse Sun Feb 29 2006 03:20:01 in strict mode
|
|
dt = Date.parseDate("2006-02-29 03:20:01", "Y-m-d H:i:s", true); // returns null
|
|
</code></pre>
|
|
* @param {String} input The raw date string.
|
|
* @param {String} format The expected date string format.
|
|
* @param {Boolean} strict (optional) True to validate date strings while parsing (i.e. prevents javascript Date "rollover")
|
|
(defaults to false). Invalid date strings will return null when parsed.
|
|
* @return {Date} The parsed Date.
|
|
* @static
|
|
*/
|
|
parseDate : function(input, format, strict) {
|
|
var p = Date.parseFunctions;
|
|
if (p[format] == null) {
|
|
Date.createParser(format);
|
|
}
|
|
return p[format](input, Ext.isDefined(strict) ? strict : Date.useStrict);
|
|
},
|
|
|
|
// private
|
|
getFormatCode : function(character) {
|
|
var f = Date.formatCodes[character];
|
|
|
|
if (f) {
|
|
f = typeof f == 'function'? f() : f;
|
|
Date.formatCodes[character] = f; // reassign function result to prevent repeated execution
|
|
}
|
|
|
|
// note: unknown characters are treated as literals
|
|
return f || ("'" + String.escape(character) + "'");
|
|
},
|
|
|
|
// private
|
|
createFormat : function(format) {
|
|
var code = [],
|
|
special = false,
|
|
ch = '';
|
|
|
|
for (var i = 0; i < format.length; ++i) {
|
|
ch = format.charAt(i);
|
|
if (!special && ch == "\\") {
|
|
special = true;
|
|
} else if (special) {
|
|
special = false;
|
|
code.push("'" + String.escape(ch) + "'");
|
|
} else {
|
|
code.push(Date.getFormatCode(ch));
|
|
}
|
|
}
|
|
Date.formatFunctions[format] = new Function("return " + code.join('+'));
|
|
},
|
|
|
|
// private
|
|
createParser : function() {
|
|
var code = [
|
|
"var dt, y, m, d, h, i, s, ms, o, z, zz, u, v,",
|
|
"def = Date.defaults,",
|
|
"results = String(input).match(Date.parseRegexes[{0}]);", // either null, or an array of matched strings
|
|
|
|
"if(results){",
|
|
"{1}",
|
|
|
|
"if(u != null){", // i.e. unix time is defined
|
|
"v = new Date(u * 1000);", // give top priority to UNIX time
|
|
"}else{",
|
|
// create Date object representing midnight of the current day;
|
|
// this will provide us with our date defaults
|
|
// (note: clearTime() handles Daylight Saving Time automatically)
|
|
"dt = (new Date()).clearTime();",
|
|
|
|
// date calculations (note: these calculations create a dependency on Ext.num())
|
|
"y = Ext.num(y, Ext.num(def.y, dt.getFullYear()));",
|
|
"m = Ext.num(m, Ext.num(def.m - 1, dt.getMonth()));",
|
|
"d = Ext.num(d, Ext.num(def.d, dt.getDate()));",
|
|
|
|
// time calculations (note: these calculations create a dependency on Ext.num())
|
|
"h = Ext.num(h, Ext.num(def.h, dt.getHours()));",
|
|
"i = Ext.num(i, Ext.num(def.i, dt.getMinutes()));",
|
|
"s = Ext.num(s, Ext.num(def.s, dt.getSeconds()));",
|
|
"ms = Ext.num(ms, Ext.num(def.ms, dt.getMilliseconds()));",
|
|
|
|
"if(z >= 0 && y >= 0){",
|
|
// both the year and zero-based day of year are defined and >= 0.
|
|
// these 2 values alone provide sufficient info to create a full date object
|
|
|
|
// create Date object representing January 1st for the given year
|
|
// handle years < 100 appropriately
|
|
"v = new Date(y < 100 ? 100 : y, 0, 1, h, i, s, ms).add(Date.YEAR, y < 100 ? y - 100 : 0);",
|
|
|
|
// then add day of year, checking for Date "rollover" if necessary
|
|
"v = !strict? v : (strict === true && (z <= 364 || (v.isLeapYear() && z <= 365))? v.add(Date.DAY, z) : null);",
|
|
"}else if(strict === true && !Date.isValid(y, m + 1, d, h, i, s, ms)){", // check for Date "rollover"
|
|
"v = null;", // invalid date, so return null
|
|
"}else{",
|
|
// plain old Date object
|
|
// handle years < 100 properly
|
|
"v = new Date(y < 100 ? 100 : y, m, d, h, i, s, ms).add(Date.YEAR, y < 100 ? y - 100 : 0);",
|
|
"}",
|
|
"}",
|
|
"}",
|
|
|
|
"if(v){",
|
|
// favour UTC offset over GMT offset
|
|
"if(zz != null){",
|
|
// reset to UTC, then add offset
|
|
"v = v.add(Date.SECOND, -v.getTimezoneOffset() * 60 - zz);",
|
|
"}else if(o){",
|
|
// reset to GMT, then add offset
|
|
"v = v.add(Date.MINUTE, -v.getTimezoneOffset() + (sn == '+'? -1 : 1) * (hr * 60 + mn));",
|
|
"}",
|
|
"}",
|
|
|
|
"return v;"
|
|
].join('\n');
|
|
|
|
return function(format) {
|
|
var regexNum = Date.parseRegexes.length,
|
|
currentGroup = 1,
|
|
calc = [],
|
|
regex = [],
|
|
special = false,
|
|
ch = "",
|
|
i = 0,
|
|
obj,
|
|
last;
|
|
|
|
for (; i < format.length; ++i) {
|
|
ch = format.charAt(i);
|
|
if (!special && ch == "\\") {
|
|
special = true;
|
|
} else if (special) {
|
|
special = false;
|
|
regex.push(String.escape(ch));
|
|
} else {
|
|
obj = $f(ch, currentGroup);
|
|
currentGroup += obj.g;
|
|
regex.push(obj.s);
|
|
if (obj.g && obj.c) {
|
|
if (obj.calcLast) {
|
|
last = obj.c;
|
|
} else {
|
|
calc.push(obj.c);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (last) {
|
|
calc.push(last);
|
|
}
|
|
|
|
Date.parseRegexes[regexNum] = new RegExp("^" + regex.join('') + "$", 'i');
|
|
Date.parseFunctions[format] = new Function("input", "strict", xf(code, regexNum, calc.join('')));
|
|
};
|
|
}(),
|
|
|
|
// private
|
|
parseCodes : {
|
|
/*
|
|
* Notes:
|
|
* g = {Number} calculation group (0 or 1. only group 1 contributes to date calculations.)
|
|
* c = {String} calculation method (required for group 1. null for group 0. {0} = currentGroup - position in regex result array)
|
|
* s = {String} regex pattern. all matches are stored in results[], and are accessible by the calculation mapped to 'c'
|
|
*/
|
|
d: {
|
|
g:1,
|
|
c:"d = parseInt(results[{0}], 10);\n",
|
|
s:"(\\d{2})" // day of month with leading zeroes (01 - 31)
|
|
},
|
|
j: {
|
|
g:1,
|
|
c:"d = parseInt(results[{0}], 10);\n",
|
|
s:"(\\d{1,2})" // day of month without leading zeroes (1 - 31)
|
|
},
|
|
D: function() {
|
|
for (var a = [], i = 0; i < 7; a.push(Date.getShortDayName(i)), ++i); // get localised short day names
|
|
return {
|
|
g:0,
|
|
c:null,
|
|
s:"(?:" + a.join("|") +")"
|
|
};
|
|
},
|
|
l: function() {
|
|
return {
|
|
g:0,
|
|
c:null,
|
|
s:"(?:" + Date.dayNames.join("|") + ")"
|
|
};
|
|
},
|
|
N: {
|
|
g:0,
|
|
c:null,
|
|
s:"[1-7]" // ISO-8601 day number (1 (monday) - 7 (sunday))
|
|
},
|
|
S: {
|
|
g:0,
|
|
c:null,
|
|
s:"(?:st|nd|rd|th)"
|
|
},
|
|
w: {
|
|
g:0,
|
|
c:null,
|
|
s:"[0-6]" // javascript day number (0 (sunday) - 6 (saturday))
|
|
},
|
|
z: {
|
|
g:1,
|
|
c:"z = parseInt(results[{0}], 10);\n",
|
|
s:"(\\d{1,3})" // day of the year (0 - 364 (365 in leap years))
|
|
},
|
|
W: {
|
|
g:0,
|
|
c:null,
|
|
s:"(?:\\d{2})" // ISO-8601 week number (with leading zero)
|
|
},
|
|
F: function() {
|
|
return {
|
|
g:1,
|
|
c:"m = parseInt(Date.getMonthNumber(results[{0}]), 10);\n", // get localised month number
|
|
s:"(" + Date.monthNames.join("|") + ")"
|
|
};
|
|
},
|
|
M: function() {
|
|
for (var a = [], i = 0; i < 12; a.push(Date.getShortMonthName(i)), ++i); // get localised short month names
|
|
return Ext.applyIf({
|
|
s:"(" + a.join("|") + ")"
|
|
}, $f("F"));
|
|
},
|
|
m: {
|
|
g:1,
|
|
c:"m = parseInt(results[{0}], 10) - 1;\n",
|
|
s:"(\\d{2})" // month number with leading zeros (01 - 12)
|
|
},
|
|
n: {
|
|
g:1,
|
|
c:"m = parseInt(results[{0}], 10) - 1;\n",
|
|
s:"(\\d{1,2})" // month number without leading zeros (1 - 12)
|
|
},
|
|
t: {
|
|
g:0,
|
|
c:null,
|
|
s:"(?:\\d{2})" // no. of days in the month (28 - 31)
|
|
},
|
|
L: {
|
|
g:0,
|
|
c:null,
|
|
s:"(?:1|0)"
|
|
},
|
|
o: function() {
|
|
return $f("Y");
|
|
},
|
|
Y: {
|
|
g:1,
|
|
c:"y = parseInt(results[{0}], 10);\n",
|
|
s:"(\\d{4})" // 4-digit year
|
|
},
|
|
y: {
|
|
g:1,
|
|
c:"var ty = parseInt(results[{0}], 10);\n"
|
|
+ "y = ty > Date.y2kYear ? 1900 + ty : 2000 + ty;\n", // 2-digit year
|
|
s:"(\\d{1,2})"
|
|
},
|
|
/**
|
|
* In the am/pm parsing routines, we allow both upper and lower case
|
|
* even though it doesn't exactly match the spec. It gives much more flexibility
|
|
* in being able to specify case insensitive regexes.
|
|
*/
|
|
a: function(){
|
|
return $f("A");
|
|
},
|
|
A: {
|
|
// We need to calculate the hour before we apply AM/PM when parsing
|
|
calcLast: true,
|
|
g:1,
|
|
c:"if (/(am)/i.test(results[{0}])) {\n"
|
|
+ "if (!h || h == 12) { h = 0; }\n"
|
|
+ "} else { if (!h || h < 12) { h = (h || 0) + 12; }}",
|
|
s:"(AM|PM|am|pm)"
|
|
},
|
|
g: function() {
|
|
return $f("G");
|
|
},
|
|
G: {
|
|
g:1,
|
|
c:"h = parseInt(results[{0}], 10);\n",
|
|
s:"(\\d{1,2})" // 24-hr format of an hour without leading zeroes (0 - 23)
|
|
},
|
|
h: function() {
|
|
return $f("H");
|
|
},
|
|
H: {
|
|
g:1,
|
|
c:"h = parseInt(results[{0}], 10);\n",
|
|
s:"(\\d{2})" // 24-hr format of an hour with leading zeroes (00 - 23)
|
|
},
|
|
i: {
|
|
g:1,
|
|
c:"i = parseInt(results[{0}], 10);\n",
|
|
s:"(\\d{2})" // minutes with leading zeros (00 - 59)
|
|
},
|
|
s: {
|
|
g:1,
|
|
c:"s = parseInt(results[{0}], 10);\n",
|
|
s:"(\\d{2})" // seconds with leading zeros (00 - 59)
|
|
},
|
|
u: {
|
|
g:1,
|
|
c:"ms = results[{0}]; ms = parseInt(ms, 10)/Math.pow(10, ms.length - 3);\n",
|
|
s:"(\\d+)" // decimal fraction of a second (minimum = 1 digit, maximum = unlimited)
|
|
},
|
|
O: {
|
|
g:1,
|
|
c:[
|
|
"o = results[{0}];",
|
|
"var sn = o.substring(0,1),", // get + / - sign
|
|
"hr = o.substring(1,3)*1 + Math.floor(o.substring(3,5) / 60),", // get hours (performs minutes-to-hour conversion also, just in case)
|
|
"mn = o.substring(3,5) % 60;", // get minutes
|
|
"o = ((-12 <= (hr*60 + mn)/60) && ((hr*60 + mn)/60 <= 14))? (sn + String.leftPad(hr, 2, '0') + String.leftPad(mn, 2, '0')) : null;\n" // -12hrs <= GMT offset <= 14hrs
|
|
].join("\n"),
|
|
s: "([+\-]\\d{4})" // GMT offset in hrs and mins
|
|
},
|
|
P: {
|
|
g:1,
|
|
c:[
|
|
"o = results[{0}];",
|
|
"var sn = o.substring(0,1),", // get + / - sign
|
|
"hr = o.substring(1,3)*1 + Math.floor(o.substring(4,6) / 60),", // get hours (performs minutes-to-hour conversion also, just in case)
|
|
"mn = o.substring(4,6) % 60;", // get minutes
|
|
"o = ((-12 <= (hr*60 + mn)/60) && ((hr*60 + mn)/60 <= 14))? (sn + String.leftPad(hr, 2, '0') + String.leftPad(mn, 2, '0')) : null;\n" // -12hrs <= GMT offset <= 14hrs
|
|
].join("\n"),
|
|
s: "([+\-]\\d{2}:\\d{2})" // GMT offset in hrs and mins (with colon separator)
|
|
},
|
|
T: {
|
|
g:0,
|
|
c:null,
|
|
s:"[A-Z]{1,4}" // timezone abbrev. may be between 1 - 4 chars
|
|
},
|
|
Z: {
|
|
g:1,
|
|
c:"zz = results[{0}] * 1;\n" // -43200 <= UTC offset <= 50400
|
|
+ "zz = (-43200 <= zz && zz <= 50400)? zz : null;\n",
|
|
s:"([+\-]?\\d{1,5})" // leading '+' sign is optional for UTC offset
|
|
},
|
|
c: function() {
|
|
var calc = [],
|
|
arr = [
|
|
$f("Y", 1), // year
|
|
$f("m", 2), // month
|
|
$f("d", 3), // day
|
|
$f("h", 4), // hour
|
|
$f("i", 5), // minute
|
|
$f("s", 6), // second
|
|
{c:"ms = results[7] || '0'; ms = parseInt(ms, 10)/Math.pow(10, ms.length - 3);\n"}, // decimal fraction of a second (minimum = 1 digit, maximum = unlimited)
|
|
{c:[ // allow either "Z" (i.e. UTC) or "-0530" or "+08:00" (i.e. UTC offset) timezone delimiters. assumes local timezone if no timezone is specified
|
|
"if(results[8]) {", // timezone specified
|
|
"if(results[8] == 'Z'){",
|
|
"zz = 0;", // UTC
|
|
"}else if (results[8].indexOf(':') > -1){",
|
|
$f("P", 8).c, // timezone offset with colon separator
|
|
"}else{",
|
|
$f("O", 8).c, // timezone offset without colon separator
|
|
"}",
|
|
"}"
|
|
].join('\n')}
|
|
];
|
|
|
|
for (var i = 0, l = arr.length; i < l; ++i) {
|
|
calc.push(arr[i].c);
|
|
}
|
|
|
|
return {
|
|
g:1,
|
|
c:calc.join(""),
|
|
s:[
|
|
arr[0].s, // year (required)
|
|
"(?:", "-", arr[1].s, // month (optional)
|
|
"(?:", "-", arr[2].s, // day (optional)
|
|
"(?:",
|
|
"(?:T| )?", // time delimiter -- either a "T" or a single blank space
|
|
arr[3].s, ":", arr[4].s, // hour AND minute, delimited by a single colon (optional). MUST be preceded by either a "T" or a single blank space
|
|
"(?::", arr[5].s, ")?", // seconds (optional)
|
|
"(?:(?:\\.|,)(\\d+))?", // decimal fraction of a second (e.g. ",12345" or ".98765") (optional)
|
|
"(Z|(?:[-+]\\d{2}(?::)?\\d{2}))?", // "Z" (UTC) or "-0530" (UTC offset without colon delimiter) or "+08:00" (UTC offset with colon delimiter) (optional)
|
|
")?",
|
|
")?",
|
|
")?"
|
|
].join("")
|
|
};
|
|
},
|
|
U: {
|
|
g:1,
|
|
c:"u = parseInt(results[{0}], 10);\n",
|
|
s:"(-?\\d+)" // leading minus sign indicates seconds before UNIX epoch
|
|
}
|
|
}
|
|
});
|
|
|
|
}());
|
|
|
|
Ext.apply(Date.prototype, {
|
|
// private
|
|
dateFormat : function(format) {
|
|
if (Date.formatFunctions[format] == null) {
|
|
Date.createFormat(format);
|
|
}
|
|
return Date.formatFunctions[format].call(this);
|
|
},
|
|
|
|
/**
|
|
* Get the timezone abbreviation of the current date (equivalent to the format specifier 'T').
|
|
*
|
|
* Note: The date string returned by the javascript Date object's toString() method varies
|
|
* between browsers (e.g. FF vs IE) and system region settings (e.g. IE in Asia vs IE in America).
|
|
* For a given date string e.g. "Thu Oct 25 2007 22:55:35 GMT+0800 (Malay Peninsula Standard Time)",
|
|
* getTimezone() first tries to get the timezone abbreviation from between a pair of parentheses
|
|
* (which may or may not be present), failing which it proceeds to get the timezone abbreviation
|
|
* from the GMT offset portion of the date string.
|
|
* @return {String} The abbreviated timezone name (e.g. 'CST', 'PDT', 'EDT', 'MPST' ...).
|
|
*/
|
|
getTimezone : function() {
|
|
// the following list shows the differences between date strings from different browsers on a WinXP SP2 machine from an Asian locale:
|
|
//
|
|
// Opera : "Thu, 25 Oct 2007 22:53:45 GMT+0800" -- shortest (weirdest) date string of the lot
|
|
// Safari : "Thu Oct 25 2007 22:55:35 GMT+0800 (Malay Peninsula Standard Time)" -- value in parentheses always gives the correct timezone (same as FF)
|
|
// FF : "Thu Oct 25 2007 22:55:35 GMT+0800 (Malay Peninsula Standard Time)" -- value in parentheses always gives the correct timezone
|
|
// IE : "Thu Oct 25 22:54:35 UTC+0800 2007" -- (Asian system setting) look for 3-4 letter timezone abbrev
|
|
// IE : "Thu Oct 25 17:06:37 PDT 2007" -- (American system setting) look for 3-4 letter timezone abbrev
|
|
//
|
|
// this crazy regex attempts to guess the correct timezone abbreviation despite these differences.
|
|
// step 1: (?:\((.*)\) -- find timezone in parentheses
|
|
// step 2: ([A-Z]{1,4})(?:[\-+][0-9]{4})?(?: -?\d+)?) -- if nothing was found in step 1, find timezone from timezone offset portion of date string
|
|
// step 3: remove all non uppercase characters found in step 1 and 2
|
|
return this.toString().replace(/^.* (?:\((.*)\)|([A-Z]{1,4})(?:[\-+][0-9]{4})?(?: -?\d+)?)$/, "$1$2").replace(/[^A-Z]/g, "");
|
|
},
|
|
|
|
/**
|
|
* Get the offset from GMT of the current date (equivalent to the format specifier 'O').
|
|
* @param {Boolean} colon (optional) true to separate the hours and minutes with a colon (defaults to false).
|
|
* @return {String} The 4-character offset string prefixed with + or - (e.g. '-0600').
|
|
*/
|
|
getGMTOffset : function(colon) {
|
|
return (this.getTimezoneOffset() > 0 ? "-" : "+")
|
|
+ String.leftPad(Math.floor(Math.abs(this.getTimezoneOffset()) / 60), 2, "0")
|
|
+ (colon ? ":" : "")
|
|
+ String.leftPad(Math.abs(this.getTimezoneOffset() % 60), 2, "0");
|
|
},
|
|
|
|
/**
|
|
* Get the numeric day number of the year, adjusted for leap year.
|
|
* @return {Number} 0 to 364 (365 in leap years).
|
|
*/
|
|
getDayOfYear: function() {
|
|
var num = 0,
|
|
d = this.clone(),
|
|
m = this.getMonth(),
|
|
i;
|
|
|
|
for (i = 0, d.setDate(1), d.setMonth(0); i < m; d.setMonth(++i)) {
|
|
num += d.getDaysInMonth();
|
|
}
|
|
return num + this.getDate() - 1;
|
|
},
|
|
|
|
/**
|
|
* Get the numeric ISO-8601 week number of the year.
|
|
* (equivalent to the format specifier 'W', but without a leading zero).
|
|
* @return {Number} 1 to 53
|
|
*/
|
|
getWeekOfYear : function() {
|
|
// adapted from http://www.merlyn.demon.co.uk/weekcalc.htm
|
|
var ms1d = 864e5, // milliseconds in a day
|
|
ms7d = 7 * ms1d; // milliseconds in a week
|
|
|
|
return function() { // return a closure so constants get calculated only once
|
|
var DC3 = Date.UTC(this.getFullYear(), this.getMonth(), this.getDate() + 3) / ms1d, // an Absolute Day Number
|
|
AWN = Math.floor(DC3 / 7), // an Absolute Week Number
|
|
Wyr = new Date(AWN * ms7d).getUTCFullYear();
|
|
|
|
return AWN - Math.floor(Date.UTC(Wyr, 0, 7) / ms7d) + 1;
|
|
};
|
|
}(),
|
|
|
|
/**
|
|
* Checks if the current date falls within a leap year.
|
|
* @return {Boolean} True if the current date falls within a leap year, false otherwise.
|
|
*/
|
|
isLeapYear : function() {
|
|
var year = this.getFullYear();
|
|
return !!((year & 3) == 0 && (year % 100 || (year % 400 == 0 && year)));
|
|
},
|
|
|
|
/**
|
|
* Get the first day of the current month, adjusted for leap year. The returned value
|
|
* is the numeric day index within the week (0-6) which can be used in conjunction with
|
|
* the {@link #monthNames} array to retrieve the textual day name.
|
|
* Example:
|
|
* <pre><code>
|
|
var dt = new Date('1/10/2007');
|
|
document.write(Date.dayNames[dt.getFirstDayOfMonth()]); //output: 'Monday'
|
|
</code></pre>
|
|
* @return {Number} The day number (0-6).
|
|
*/
|
|
getFirstDayOfMonth : function() {
|
|
var day = (this.getDay() - (this.getDate() - 1)) % 7;
|
|
return (day < 0) ? (day + 7) : day;
|
|
},
|
|
|
|
/**
|
|
* Get the last day of the current month, adjusted for leap year. The returned value
|
|
* is the numeric day index within the week (0-6) which can be used in conjunction with
|
|
* the {@link #monthNames} array to retrieve the textual day name.
|
|
* Example:
|
|
* <pre><code>
|
|
var dt = new Date('1/10/2007');
|
|
document.write(Date.dayNames[dt.getLastDayOfMonth()]); //output: 'Wednesday'
|
|
</code></pre>
|
|
* @return {Number} The day number (0-6).
|
|
*/
|
|
getLastDayOfMonth : function() {
|
|
return this.getLastDateOfMonth().getDay();
|
|
},
|
|
|
|
|
|
/**
|
|
* Get the date of the first day of the month in which this date resides.
|
|
* @return {Date}
|
|
*/
|
|
getFirstDateOfMonth : function() {
|
|
return new Date(this.getFullYear(), this.getMonth(), 1);
|
|
},
|
|
|
|
/**
|
|
* Get the date of the last day of the month in which this date resides.
|
|
* @return {Date}
|
|
*/
|
|
getLastDateOfMonth : function() {
|
|
return new Date(this.getFullYear(), this.getMonth(), this.getDaysInMonth());
|
|
},
|
|
|
|
/**
|
|
* Get the number of days in the current month, adjusted for leap year.
|
|
* @return {Number} The number of days in the month.
|
|
*/
|
|
getDaysInMonth: function() {
|
|
var daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
|
|
return function() { // return a closure for efficiency
|
|
var m = this.getMonth();
|
|
|
|
return m == 1 && this.isLeapYear() ? 29 : daysInMonth[m];
|
|
};
|
|
}(),
|
|
|
|
/**
|
|
* Get the English ordinal suffix of the current day (equivalent to the format specifier 'S').
|
|
* @return {String} 'st, 'nd', 'rd' or 'th'.
|
|
*/
|
|
getSuffix : function() {
|
|
switch (this.getDate()) {
|
|
case 1:
|
|
case 21:
|
|
case 31:
|
|
return "st";
|
|
case 2:
|
|
case 22:
|
|
return "nd";
|
|
case 3:
|
|
case 23:
|
|
return "rd";
|
|
default:
|
|
return "th";
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Creates and returns a new Date instance with the exact same date value as the called instance.
|
|
* Dates are copied and passed by reference, so if a copied date variable is modified later, the original
|
|
* variable will also be changed. When the intention is to create a new variable that will not
|
|
* modify the original instance, you should create a clone.
|
|
*
|
|
* Example of correctly cloning a date:
|
|
* <pre><code>
|
|
//wrong way:
|
|
var orig = new Date('10/1/2006');
|
|
var copy = orig;
|
|
copy.setDate(5);
|
|
document.write(orig); //returns 'Thu Oct 05 2006'!
|
|
|
|
//correct way:
|
|
var orig = new Date('10/1/2006');
|
|
var copy = orig.clone();
|
|
copy.setDate(5);
|
|
document.write(orig); //returns 'Thu Oct 01 2006'
|
|
</code></pre>
|
|
* @return {Date} The new Date instance.
|
|
*/
|
|
clone : function() {
|
|
return new Date(this.getTime());
|
|
},
|
|
|
|
/**
|
|
* Checks if the current date is affected by Daylight Saving Time (DST).
|
|
* @return {Boolean} True if the current date is affected by DST.
|
|
*/
|
|
isDST : function() {
|
|
// adapted from http://extjs.com/forum/showthread.php?p=247172#post247172
|
|
// courtesy of @geoffrey.mcgill
|
|
return new Date(this.getFullYear(), 0, 1).getTimezoneOffset() != this.getTimezoneOffset();
|
|
},
|
|
|
|
/**
|
|
* Attempts to clear all time information from this Date by setting the time to midnight of the same day,
|
|
* automatically adjusting for Daylight Saving Time (DST) where applicable.
|
|
* (note: DST timezone information for the browser's host operating system is assumed to be up-to-date)
|
|
* @param {Boolean} clone true to create a clone of this date, clear the time and return it (defaults to false).
|
|
* @return {Date} this or the clone.
|
|
*/
|
|
clearTime : function(clone) {
|
|
if (clone) {
|
|
return this.clone().clearTime();
|
|
}
|
|
|
|
// get current date before clearing time
|
|
var d = this.getDate();
|
|
|
|
// clear time
|
|
this.setHours(0);
|
|
this.setMinutes(0);
|
|
this.setSeconds(0);
|
|
this.setMilliseconds(0);
|
|
|
|
if (this.getDate() != d) { // account for DST (i.e. day of month changed when setting hour = 0)
|
|
// note: DST adjustments are assumed to occur in multiples of 1 hour (this is almost always the case)
|
|
// refer to http://www.timeanddate.com/time/aboutdst.html for the (rare) exceptions to this rule
|
|
|
|
// increment hour until cloned date == current date
|
|
for (var hr = 1, c = this.add(Date.HOUR, hr); c.getDate() != d; hr++, c = this.add(Date.HOUR, hr));
|
|
|
|
this.setDate(d);
|
|
this.setHours(c.getHours());
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Provides a convenient method for performing basic date arithmetic. This method
|
|
* does not modify the Date instance being called - it creates and returns
|
|
* a new Date instance containing the resulting date value.
|
|
*
|
|
* Examples:
|
|
* <pre><code>
|
|
// Basic usage:
|
|
var dt = new Date('10/29/2006').add(Date.DAY, 5);
|
|
document.write(dt); //returns 'Fri Nov 03 2006 00:00:00'
|
|
|
|
// Negative values will be subtracted:
|
|
var dt2 = new Date('10/1/2006').add(Date.DAY, -5);
|
|
document.write(dt2); //returns 'Tue Sep 26 2006 00:00:00'
|
|
|
|
// You can even chain several calls together in one line:
|
|
var dt3 = new Date('10/1/2006').add(Date.DAY, 5).add(Date.HOUR, 8).add(Date.MINUTE, -30);
|
|
document.write(dt3); //returns 'Fri Oct 06 2006 07:30:00'
|
|
</code></pre>
|
|
*
|
|
* @param {String} interval A valid date interval enum value.
|
|
* @param {Number} value The amount to add to the current date.
|
|
* @return {Date} The new Date instance.
|
|
*/
|
|
add : function(interval, value) {
|
|
var d = this.clone();
|
|
if (!interval || value === 0) return d;
|
|
|
|
switch(interval.toLowerCase()) {
|
|
case Date.MILLI:
|
|
d.setMilliseconds(this.getMilliseconds() + value);
|
|
break;
|
|
case Date.SECOND:
|
|
d.setSeconds(this.getSeconds() + value);
|
|
break;
|
|
case Date.MINUTE:
|
|
d.setMinutes(this.getMinutes() + value);
|
|
break;
|
|
case Date.HOUR:
|
|
d.setHours(this.getHours() + value);
|
|
break;
|
|
case Date.DAY:
|
|
d.setDate(this.getDate() + value);
|
|
break;
|
|
case Date.MONTH:
|
|
var day = this.getDate();
|
|
if (day > 28) {
|
|
day = Math.min(day, this.getFirstDateOfMonth().add('mo', value).getLastDateOfMonth().getDate());
|
|
}
|
|
d.setDate(day);
|
|
d.setMonth(this.getMonth() + value);
|
|
break;
|
|
case Date.YEAR:
|
|
d.setFullYear(this.getFullYear() + value);
|
|
break;
|
|
}
|
|
return d;
|
|
},
|
|
|
|
/**
|
|
* Checks if this date falls on or between the given start and end dates.
|
|
* @param {Date} start Start date
|
|
* @param {Date} end End date
|
|
* @return {Boolean} true if this date falls on or between the given start and end dates.
|
|
*/
|
|
between : function(start, end) {
|
|
var t = this.getTime();
|
|
return start.getTime() <= t && t <= end.getTime();
|
|
}
|
|
});
|
|
|
|
|
|
/**
|
|
* Formats a date given the supplied format string.
|
|
* @param {String} format The format string.
|
|
* @return {String} The formatted date.
|
|
* @method format
|
|
*/
|
|
Date.prototype.format = Date.prototype.dateFormat;
|
|
|
|
|
|
// private
|
|
if (Ext.isSafari && (navigator.userAgent.match(/WebKit\/(\d+)/)[1] || NaN) < 420) {
|
|
Ext.apply(Date.prototype, {
|
|
_xMonth : Date.prototype.setMonth,
|
|
_xDate : Date.prototype.setDate,
|
|
|
|
// Bug in Safari 1.3, 2.0 (WebKit build < 420)
|
|
// Date.setMonth does not work consistently if iMonth is not 0-11
|
|
setMonth : function(num) {
|
|
if (num <= -1) {
|
|
var n = Math.ceil(-num),
|
|
back_year = Math.ceil(n / 12),
|
|
month = (n % 12) ? 12 - n % 12 : 0;
|
|
|
|
this.setFullYear(this.getFullYear() - back_year);
|
|
|
|
return this._xMonth(month);
|
|
} else {
|
|
return this._xMonth(num);
|
|
}
|
|
},
|
|
|
|
// Bug in setDate() method (resolved in WebKit build 419.3, so to be safe we target Webkit builds < 420)
|
|
// The parameter for Date.setDate() is converted to a signed byte integer in Safari
|
|
// http://brianary.blogspot.com/2006/03/safari-date-bug.html
|
|
setDate : function(d) {
|
|
// use setTime() to workaround setDate() bug
|
|
// subtract current day of month in milliseconds, then add desired day of month in milliseconds
|
|
return this.setTime(this.getTime() - (this.getDate() - d) * 864e5);
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
|
|
/* Some basic Date tests... (requires Firebug)
|
|
|
|
Date.parseDate('', 'c'); // call Date.parseDate() once to force computation of regex string so we can console.log() it
|
|
console.log('Insane Regex for "c" format: %o', Date.parseCodes.c.s); // view the insane regex for the "c" format specifier
|
|
|
|
// standard tests
|
|
console.group('Standard Date.parseDate() Tests');
|
|
console.log('Date.parseDate("2009-01-05T11:38:56", "c") = %o', Date.parseDate("2009-01-05T11:38:56", "c")); // assumes browser's timezone setting
|
|
console.log('Date.parseDate("2009-02-04T12:37:55.001000", "c") = %o', Date.parseDate("2009-02-04T12:37:55.001000", "c")); // assumes browser's timezone setting
|
|
console.log('Date.parseDate("2009-03-03T13:36:54,101000Z", "c") = %o', Date.parseDate("2009-03-03T13:36:54,101000Z", "c")); // UTC
|
|
console.log('Date.parseDate("2009-04-02T14:35:53.901000-0530", "c") = %o', Date.parseDate("2009-04-02T14:35:53.901000-0530", "c")); // GMT-0530
|
|
console.log('Date.parseDate("2009-05-01T15:34:52,9876000+08:00", "c") = %o', Date.parseDate("2009-05-01T15:34:52,987600+08:00", "c")); // GMT+08:00
|
|
console.groupEnd();
|
|
|
|
// ISO-8601 format as specified in http://www.w3.org/TR/NOTE-datetime
|
|
// -- accepts ALL 6 levels of date-time granularity
|
|
console.group('ISO-8601 Granularity Test (see http://www.w3.org/TR/NOTE-datetime)');
|
|
console.log('Date.parseDate("1997", "c") = %o', Date.parseDate("1997", "c")); // YYYY (e.g. 1997)
|
|
console.log('Date.parseDate("1997-07", "c") = %o', Date.parseDate("1997-07", "c")); // YYYY-MM (e.g. 1997-07)
|
|
console.log('Date.parseDate("1997-07-16", "c") = %o', Date.parseDate("1997-07-16", "c")); // YYYY-MM-DD (e.g. 1997-07-16)
|
|
console.log('Date.parseDate("1997-07-16T19:20+01:00", "c") = %o', Date.parseDate("1997-07-16T19:20+01:00", "c")); // YYYY-MM-DDThh:mmTZD (e.g. 1997-07-16T19:20+01:00)
|
|
console.log('Date.parseDate("1997-07-16T19:20:30+01:00", "c") = %o', Date.parseDate("1997-07-16T19:20:30+01:00", "c")); // YYYY-MM-DDThh:mm:ssTZD (e.g. 1997-07-16T19:20:30+01:00)
|
|
console.log('Date.parseDate("1997-07-16T19:20:30.45+01:00", "c") = %o', Date.parseDate("1997-07-16T19:20:30.45+01:00", "c")); // YYYY-MM-DDThh:mm:ss.sTZD (e.g. 1997-07-16T19:20:30.45+01:00)
|
|
console.log('Date.parseDate("1997-07-16 19:20:30.45+01:00", "c") = %o', Date.parseDate("1997-07-16 19:20:30.45+01:00", "c")); // YYYY-MM-DD hh:mm:ss.sTZD (e.g. 1997-07-16T19:20:30.45+01:00)
|
|
console.log('Date.parseDate("1997-13-16T19:20:30.45+01:00", "c", true)= %o', Date.parseDate("1997-13-16T19:20:30.45+01:00", "c", true)); // strict date parsing with invalid month value
|
|
console.groupEnd();
|
|
|
|
*/
|
|
/**
|
|
* @class Ext.util.MixedCollection
|
|
* @extends Ext.util.Observable
|
|
* A Collection class that maintains both numeric indexes and keys and exposes events.
|
|
* @constructor
|
|
* @param {Boolean} allowFunctions Specify <tt>true</tt> if the {@link #addAll}
|
|
* function should add function references to the collection. Defaults to
|
|
* <tt>false</tt>.
|
|
* @param {Function} keyFn A function that can accept an item of the type(s) stored in this MixedCollection
|
|
* and return the key value for that item. This is used when available to look up the key on items that
|
|
* were passed without an explicit key parameter to a MixedCollection method. Passing this parameter is
|
|
* equivalent to providing an implementation for the {@link #getKey} method.
|
|
*/
|
|
Ext.util.MixedCollection = function(allowFunctions, keyFn){
|
|
this.items = [];
|
|
this.map = {};
|
|
this.keys = [];
|
|
this.length = 0;
|
|
this.addEvents(
|
|
/**
|
|
* @event clear
|
|
* Fires when the collection is cleared.
|
|
*/
|
|
'clear',
|
|
/**
|
|
* @event add
|
|
* Fires when an item is added to the collection.
|
|
* @param {Number} index The index at which the item was added.
|
|
* @param {Object} o The item added.
|
|
* @param {String} key The key associated with the added item.
|
|
*/
|
|
'add',
|
|
/**
|
|
* @event replace
|
|
* Fires when an item is replaced in the collection.
|
|
* @param {String} key he key associated with the new added.
|
|
* @param {Object} old The item being replaced.
|
|
* @param {Object} new The new item.
|
|
*/
|
|
'replace',
|
|
/**
|
|
* @event remove
|
|
* Fires when an item is removed from the collection.
|
|
* @param {Object} o The item being removed.
|
|
* @param {String} key (optional) The key associated with the removed item.
|
|
*/
|
|
'remove',
|
|
'sort'
|
|
);
|
|
this.allowFunctions = allowFunctions === true;
|
|
if(keyFn){
|
|
this.getKey = keyFn;
|
|
}
|
|
Ext.util.MixedCollection.superclass.constructor.call(this);
|
|
};
|
|
|
|
Ext.extend(Ext.util.MixedCollection, Ext.util.Observable, {
|
|
|
|
/**
|
|
* @cfg {Boolean} allowFunctions Specify <tt>true</tt> if the {@link #addAll}
|
|
* function should add function references to the collection. Defaults to
|
|
* <tt>false</tt>.
|
|
*/
|
|
allowFunctions : false,
|
|
|
|
/**
|
|
* Adds an item to the collection. Fires the {@link #add} event when complete.
|
|
* @param {String} key <p>The key to associate with the item, or the new item.</p>
|
|
* <p>If a {@link #getKey} implementation was specified for this MixedCollection,
|
|
* or if the key of the stored items is in a property called <tt><b>id</b></tt>,
|
|
* the MixedCollection will be able to <i>derive</i> the key for the new item.
|
|
* In this case just pass the new item in this parameter.</p>
|
|
* @param {Object} o The item to add.
|
|
* @return {Object} The item added.
|
|
*/
|
|
add : function(key, o){
|
|
if(arguments.length == 1){
|
|
o = arguments[0];
|
|
key = this.getKey(o);
|
|
}
|
|
if(typeof key != 'undefined' && key !== null){
|
|
var old = this.map[key];
|
|
if(typeof old != 'undefined'){
|
|
return this.replace(key, o);
|
|
}
|
|
this.map[key] = o;
|
|
}
|
|
this.length++;
|
|
this.items.push(o);
|
|
this.keys.push(key);
|
|
this.fireEvent('add', this.length-1, o, key);
|
|
return o;
|
|
},
|
|
|
|
/**
|
|
* MixedCollection has a generic way to fetch keys if you implement getKey. The default implementation
|
|
* simply returns <b><code>item.id</code></b> but you can provide your own implementation
|
|
* to return a different value as in the following examples:<pre><code>
|
|
// normal way
|
|
var mc = new Ext.util.MixedCollection();
|
|
mc.add(someEl.dom.id, someEl);
|
|
mc.add(otherEl.dom.id, otherEl);
|
|
//and so on
|
|
|
|
// using getKey
|
|
var mc = new Ext.util.MixedCollection();
|
|
mc.getKey = function(el){
|
|
return el.dom.id;
|
|
};
|
|
mc.add(someEl);
|
|
mc.add(otherEl);
|
|
|
|
// or via the constructor
|
|
var mc = new Ext.util.MixedCollection(false, function(el){
|
|
return el.dom.id;
|
|
});
|
|
mc.add(someEl);
|
|
mc.add(otherEl);
|
|
* </code></pre>
|
|
* @param {Object} item The item for which to find the key.
|
|
* @return {Object} The key for the passed item.
|
|
*/
|
|
getKey : function(o){
|
|
return o.id;
|
|
},
|
|
|
|
/**
|
|
* Replaces an item in the collection. Fires the {@link #replace} event when complete.
|
|
* @param {String} key <p>The key associated with the item to replace, or the replacement item.</p>
|
|
* <p>If you supplied a {@link #getKey} implementation for this MixedCollection, or if the key
|
|
* of your stored items is in a property called <tt><b>id</b></tt>, then the MixedCollection
|
|
* will be able to <i>derive</i> the key of the replacement item. If you want to replace an item
|
|
* with one having the same key value, then just pass the replacement item in this parameter.</p>
|
|
* @param o {Object} o (optional) If the first parameter passed was a key, the item to associate
|
|
* with that key.
|
|
* @return {Object} The new item.
|
|
*/
|
|
replace : function(key, o){
|
|
if(arguments.length == 1){
|
|
o = arguments[0];
|
|
key = this.getKey(o);
|
|
}
|
|
var old = this.map[key];
|
|
if(typeof key == 'undefined' || key === null || typeof old == 'undefined'){
|
|
return this.add(key, o);
|
|
}
|
|
var index = this.indexOfKey(key);
|
|
this.items[index] = o;
|
|
this.map[key] = o;
|
|
this.fireEvent('replace', key, old, o);
|
|
return o;
|
|
},
|
|
|
|
/**
|
|
* Adds all elements of an Array or an Object to the collection.
|
|
* @param {Object/Array} objs An Object containing properties which will be added
|
|
* to the collection, or an Array of values, each of which are added to the collection.
|
|
* Functions references will be added to the collection if <code>{@link #allowFunctions}</code>
|
|
* has been set to <tt>true</tt>.
|
|
*/
|
|
addAll : function(objs){
|
|
if(arguments.length > 1 || Ext.isArray(objs)){
|
|
var args = arguments.length > 1 ? arguments : objs;
|
|
for(var i = 0, len = args.length; i < len; i++){
|
|
this.add(args[i]);
|
|
}
|
|
}else{
|
|
for(var key in objs){
|
|
if(this.allowFunctions || typeof objs[key] != 'function'){
|
|
this.add(key, objs[key]);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Executes the specified function once for every item in the collection, passing the following arguments:
|
|
* <div class="mdetail-params"><ul>
|
|
* <li><b>item</b> : Mixed<p class="sub-desc">The collection item</p></li>
|
|
* <li><b>index</b> : Number<p class="sub-desc">The item's index</p></li>
|
|
* <li><b>length</b> : Number<p class="sub-desc">The total number of items in the collection</p></li>
|
|
* </ul></div>
|
|
* The function should return a boolean value. Returning false from the function will stop the iteration.
|
|
* @param {Function} fn The function to execute for each item.
|
|
* @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current item in the iteration.
|
|
*/
|
|
each : function(fn, scope){
|
|
var items = [].concat(this.items); // each safe for removal
|
|
for(var i = 0, len = items.length; i < len; i++){
|
|
if(fn.call(scope || items[i], items[i], i, len) === false){
|
|
break;
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Executes the specified function once for every key in the collection, passing each
|
|
* key, and its associated item as the first two parameters.
|
|
* @param {Function} fn The function to execute for each item.
|
|
* @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the browser window.
|
|
*/
|
|
eachKey : function(fn, scope){
|
|
for(var i = 0, len = this.keys.length; i < len; i++){
|
|
fn.call(scope || window, this.keys[i], this.items[i], i, len);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Returns the first item in the collection which elicits a true return value from the
|
|
* passed selection function.
|
|
* @param {Function} fn The selection function to execute for each item.
|
|
* @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the browser window.
|
|
* @return {Object} The first item in the collection which returned true from the selection function.
|
|
*/
|
|
find : function(fn, scope){
|
|
for(var i = 0, len = this.items.length; i < len; i++){
|
|
if(fn.call(scope || window, this.items[i], this.keys[i])){
|
|
return this.items[i];
|
|
}
|
|
}
|
|
return null;
|
|
},
|
|
|
|
/**
|
|
* Inserts an item at the specified index in the collection. Fires the {@link #add} event when complete.
|
|
* @param {Number} index The index to insert the item at.
|
|
* @param {String} key The key to associate with the new item, or the item itself.
|
|
* @param {Object} o (optional) If the second parameter was a key, the new item.
|
|
* @return {Object} The item inserted.
|
|
*/
|
|
insert : function(index, key, o){
|
|
if(arguments.length == 2){
|
|
o = arguments[1];
|
|
key = this.getKey(o);
|
|
}
|
|
if(this.containsKey(key)){
|
|
this.suspendEvents();
|
|
this.removeKey(key);
|
|
this.resumeEvents();
|
|
}
|
|
if(index >= this.length){
|
|
return this.add(key, o);
|
|
}
|
|
this.length++;
|
|
this.items.splice(index, 0, o);
|
|
if(typeof key != 'undefined' && key !== null){
|
|
this.map[key] = o;
|
|
}
|
|
this.keys.splice(index, 0, key);
|
|
this.fireEvent('add', index, o, key);
|
|
return o;
|
|
},
|
|
|
|
/**
|
|
* Remove an item from the collection.
|
|
* @param {Object} o The item to remove.
|
|
* @return {Object} The item removed or false if no item was removed.
|
|
*/
|
|
remove : function(o){
|
|
return this.removeAt(this.indexOf(o));
|
|
},
|
|
|
|
/**
|
|
* Remove an item from a specified index in the collection. Fires the {@link #remove} event when complete.
|
|
* @param {Number} index The index within the collection of the item to remove.
|
|
* @return {Object} The item removed or false if no item was removed.
|
|
*/
|
|
removeAt : function(index){
|
|
if(index < this.length && index >= 0){
|
|
this.length--;
|
|
var o = this.items[index];
|
|
this.items.splice(index, 1);
|
|
var key = this.keys[index];
|
|
if(typeof key != 'undefined'){
|
|
delete this.map[key];
|
|
}
|
|
this.keys.splice(index, 1);
|
|
this.fireEvent('remove', o, key);
|
|
return o;
|
|
}
|
|
return false;
|
|
},
|
|
|
|
/**
|
|
* Removed an item associated with the passed key fom the collection.
|
|
* @param {String} key The key of the item to remove.
|
|
* @return {Object} The item removed or false if no item was removed.
|
|
*/
|
|
removeKey : function(key){
|
|
return this.removeAt(this.indexOfKey(key));
|
|
},
|
|
|
|
/**
|
|
* Returns the number of items in the collection.
|
|
* @return {Number} the number of items in the collection.
|
|
*/
|
|
getCount : function(){
|
|
return this.length;
|
|
},
|
|
|
|
/**
|
|
* Returns index within the collection of the passed Object.
|
|
* @param {Object} o The item to find the index of.
|
|
* @return {Number} index of the item. Returns -1 if not found.
|
|
*/
|
|
indexOf : function(o){
|
|
return this.items.indexOf(o);
|
|
},
|
|
|
|
/**
|
|
* Returns index within the collection of the passed key.
|
|
* @param {String} key The key to find the index of.
|
|
* @return {Number} index of the key.
|
|
*/
|
|
indexOfKey : function(key){
|
|
return this.keys.indexOf(key);
|
|
},
|
|
|
|
/**
|
|
* Returns the item associated with the passed key OR index.
|
|
* Key has priority over index. This is the equivalent
|
|
* of calling {@link #key} first, then if nothing matched calling {@link #itemAt}.
|
|
* @param {String/Number} key The key or index of the item.
|
|
* @return {Object} If the item is found, returns the item. If the item was not found, returns <tt>undefined</tt>.
|
|
* If an item was found, but is a Class, returns <tt>null</tt>.
|
|
*/
|
|
item : function(key){
|
|
var mk = this.map[key],
|
|
item = mk !== undefined ? mk : (typeof key == 'number') ? this.items[key] : undefined;
|
|
return typeof item != 'function' || this.allowFunctions ? item : null; // for prototype!
|
|
},
|
|
|
|
/**
|
|
* Returns the item at the specified index.
|
|
* @param {Number} index The index of the item.
|
|
* @return {Object} The item at the specified index.
|
|
*/
|
|
itemAt : function(index){
|
|
return this.items[index];
|
|
},
|
|
|
|
/**
|
|
* Returns the item associated with the passed key.
|
|
* @param {String/Number} key The key of the item.
|
|
* @return {Object} The item associated with the passed key.
|
|
*/
|
|
key : function(key){
|
|
return this.map[key];
|
|
},
|
|
|
|
/**
|
|
* Returns true if the collection contains the passed Object as an item.
|
|
* @param {Object} o The Object to look for in the collection.
|
|
* @return {Boolean} True if the collection contains the Object as an item.
|
|
*/
|
|
contains : function(o){
|
|
return this.indexOf(o) != -1;
|
|
},
|
|
|
|
/**
|
|
* Returns true if the collection contains the passed Object as a key.
|
|
* @param {String} key The key to look for in the collection.
|
|
* @return {Boolean} True if the collection contains the Object as a key.
|
|
*/
|
|
containsKey : function(key){
|
|
return typeof this.map[key] != 'undefined';
|
|
},
|
|
|
|
/**
|
|
* Removes all items from the collection. Fires the {@link #clear} event when complete.
|
|
*/
|
|
clear : function(){
|
|
this.length = 0;
|
|
this.items = [];
|
|
this.keys = [];
|
|
this.map = {};
|
|
this.fireEvent('clear');
|
|
},
|
|
|
|
/**
|
|
* Returns the first item in the collection.
|
|
* @return {Object} the first item in the collection..
|
|
*/
|
|
first : function(){
|
|
return this.items[0];
|
|
},
|
|
|
|
/**
|
|
* Returns the last item in the collection.
|
|
* @return {Object} the last item in the collection..
|
|
*/
|
|
last : function(){
|
|
return this.items[this.length-1];
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
* Performs the actual sorting based on a direction and a sorting function. Internally,
|
|
* this creates a temporary array of all items in the MixedCollection, sorts it and then writes
|
|
* the sorted array data back into this.items and this.keys
|
|
* @param {String} property Property to sort by ('key', 'value', or 'index')
|
|
* @param {String} dir (optional) Direction to sort 'ASC' or 'DESC'. Defaults to 'ASC'.
|
|
* @param {Function} fn (optional) Comparison function that defines the sort order.
|
|
* Defaults to sorting by numeric value.
|
|
*/
|
|
_sort : function(property, dir, fn){
|
|
var i, len,
|
|
dsc = String(dir).toUpperCase() == 'DESC' ? -1 : 1,
|
|
|
|
//this is a temporary array used to apply the sorting function
|
|
c = [],
|
|
keys = this.keys,
|
|
items = this.items;
|
|
|
|
//default to a simple sorter function if one is not provided
|
|
fn = fn || function(a, b) {
|
|
return a - b;
|
|
};
|
|
|
|
//copy all the items into a temporary array, which we will sort
|
|
for(i = 0, len = items.length; i < len; i++){
|
|
c[c.length] = {
|
|
key : keys[i],
|
|
value: items[i],
|
|
index: i
|
|
};
|
|
}
|
|
|
|
//sort the temporary array
|
|
c.sort(function(a, b){
|
|
var v = fn(a[property], b[property]) * dsc;
|
|
if(v === 0){
|
|
v = (a.index < b.index ? -1 : 1);
|
|
}
|
|
return v;
|
|
});
|
|
|
|
//copy the temporary array back into the main this.items and this.keys objects
|
|
for(i = 0, len = c.length; i < len; i++){
|
|
items[i] = c[i].value;
|
|
keys[i] = c[i].key;
|
|
}
|
|
|
|
this.fireEvent('sort', this);
|
|
},
|
|
|
|
/**
|
|
* Sorts this collection by <b>item</b> value with the passed comparison function.
|
|
* @param {String} direction (optional) 'ASC' or 'DESC'. Defaults to 'ASC'.
|
|
* @param {Function} fn (optional) Comparison function that defines the sort order.
|
|
* Defaults to sorting by numeric value.
|
|
*/
|
|
sort : function(dir, fn){
|
|
this._sort('value', dir, fn);
|
|
},
|
|
|
|
/**
|
|
* Reorders each of the items based on a mapping from old index to new index. Internally this
|
|
* just translates into a sort. The 'sort' event is fired whenever reordering has occured.
|
|
* @param {Object} mapping Mapping from old item index to new item index
|
|
*/
|
|
reorder: function(mapping) {
|
|
this.suspendEvents();
|
|
|
|
var items = this.items,
|
|
index = 0,
|
|
length = items.length,
|
|
order = [],
|
|
remaining = [],
|
|
oldIndex;
|
|
|
|
//object of {oldPosition: newPosition} reversed to {newPosition: oldPosition}
|
|
for (oldIndex in mapping) {
|
|
order[mapping[oldIndex]] = items[oldIndex];
|
|
}
|
|
|
|
for (index = 0; index < length; index++) {
|
|
if (mapping[index] == undefined) {
|
|
remaining.push(items[index]);
|
|
}
|
|
}
|
|
|
|
for (index = 0; index < length; index++) {
|
|
if (order[index] == undefined) {
|
|
order[index] = remaining.shift();
|
|
}
|
|
}
|
|
|
|
this.clear();
|
|
this.addAll(order);
|
|
|
|
this.resumeEvents();
|
|
this.fireEvent('sort', this);
|
|
},
|
|
|
|
/**
|
|
* Sorts this collection by <b>key</b>s.
|
|
* @param {String} direction (optional) 'ASC' or 'DESC'. Defaults to 'ASC'.
|
|
* @param {Function} fn (optional) Comparison function that defines the sort order.
|
|
* Defaults to sorting by case insensitive string.
|
|
*/
|
|
keySort : function(dir, fn){
|
|
this._sort('key', dir, fn || function(a, b){
|
|
var v1 = String(a).toUpperCase(), v2 = String(b).toUpperCase();
|
|
return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Returns a range of items in this collection
|
|
* @param {Number} startIndex (optional) The starting index. Defaults to 0.
|
|
* @param {Number} endIndex (optional) The ending index. Defaults to the last item.
|
|
* @return {Array} An array of items
|
|
*/
|
|
getRange : function(start, end){
|
|
var items = this.items;
|
|
if(items.length < 1){
|
|
return [];
|
|
}
|
|
start = start || 0;
|
|
end = Math.min(typeof end == 'undefined' ? this.length-1 : end, this.length-1);
|
|
var i, r = [];
|
|
if(start <= end){
|
|
for(i = start; i <= end; i++) {
|
|
r[r.length] = items[i];
|
|
}
|
|
}else{
|
|
for(i = start; i >= end; i--) {
|
|
r[r.length] = items[i];
|
|
}
|
|
}
|
|
return r;
|
|
},
|
|
|
|
/**
|
|
* Filter the <i>objects</i> in this collection by a specific property.
|
|
* Returns a new collection that has been filtered.
|
|
* @param {String} property A property on your objects
|
|
* @param {String/RegExp} value Either string that the property values
|
|
* should start with or a RegExp to test against the property
|
|
* @param {Boolean} anyMatch (optional) True to match any part of the string, not just the beginning
|
|
* @param {Boolean} caseSensitive (optional) True for case sensitive comparison (defaults to False).
|
|
* @return {MixedCollection} The new filtered collection
|
|
*/
|
|
filter : function(property, value, anyMatch, caseSensitive){
|
|
if(Ext.isEmpty(value, false)){
|
|
return this.clone();
|
|
}
|
|
value = this.createValueMatcher(value, anyMatch, caseSensitive);
|
|
return this.filterBy(function(o){
|
|
return o && value.test(o[property]);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Filter by a function. Returns a <i>new</i> collection that has been filtered.
|
|
* The passed function will be called with each object in the collection.
|
|
* If the function returns true, the value is included otherwise it is filtered.
|
|
* @param {Function} fn The function to be called, it will receive the args o (the object), k (the key)
|
|
* @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this MixedCollection.
|
|
* @return {MixedCollection} The new filtered collection
|
|
*/
|
|
filterBy : function(fn, scope){
|
|
var r = new Ext.util.MixedCollection();
|
|
r.getKey = this.getKey;
|
|
var k = this.keys, it = this.items;
|
|
for(var i = 0, len = it.length; i < len; i++){
|
|
if(fn.call(scope||this, it[i], k[i])){
|
|
r.add(k[i], it[i]);
|
|
}
|
|
}
|
|
return r;
|
|
},
|
|
|
|
/**
|
|
* Finds the index of the first matching object in this collection by a specific property/value.
|
|
* @param {String} property The name of a property on your objects.
|
|
* @param {String/RegExp} value A string that the property values
|
|
* should start with or a RegExp to test against the property.
|
|
* @param {Number} start (optional) The index to start searching at (defaults to 0).
|
|
* @param {Boolean} anyMatch (optional) True to match any part of the string, not just the beginning.
|
|
* @param {Boolean} caseSensitive (optional) True for case sensitive comparison.
|
|
* @return {Number} The matched index or -1
|
|
*/
|
|
findIndex : function(property, value, start, anyMatch, caseSensitive){
|
|
if(Ext.isEmpty(value, false)){
|
|
return -1;
|
|
}
|
|
value = this.createValueMatcher(value, anyMatch, caseSensitive);
|
|
return this.findIndexBy(function(o){
|
|
return o && value.test(o[property]);
|
|
}, null, start);
|
|
},
|
|
|
|
/**
|
|
* Find the index of the first matching object in this collection by a function.
|
|
* If the function returns <i>true</i> it is considered a match.
|
|
* @param {Function} fn The function to be called, it will receive the args o (the object), k (the key).
|
|
* @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this MixedCollection.
|
|
* @param {Number} start (optional) The index to start searching at (defaults to 0).
|
|
* @return {Number} The matched index or -1
|
|
*/
|
|
findIndexBy : function(fn, scope, start){
|
|
var k = this.keys, it = this.items;
|
|
for(var i = (start||0), len = it.length; i < len; i++){
|
|
if(fn.call(scope||this, it[i], k[i])){
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
},
|
|
|
|
/**
|
|
* Returns a regular expression based on the given value and matching options. This is used internally for finding and filtering,
|
|
* and by Ext.data.Store#filter
|
|
* @private
|
|
* @param {String} value The value to create the regex for. This is escaped using Ext.escapeRe
|
|
* @param {Boolean} anyMatch True to allow any match - no regex start/end line anchors will be added. Defaults to false
|
|
* @param {Boolean} caseSensitive True to make the regex case sensitive (adds 'i' switch to regex). Defaults to false.
|
|
* @param {Boolean} exactMatch True to force exact match (^ and $ characters added to the regex). Defaults to false. Ignored if anyMatch is true.
|
|
*/
|
|
createValueMatcher : function(value, anyMatch, caseSensitive, exactMatch) {
|
|
if (!value.exec) { // not a regex
|
|
var er = Ext.escapeRe;
|
|
value = String(value);
|
|
|
|
if (anyMatch === true) {
|
|
value = er(value);
|
|
} else {
|
|
value = '^' + er(value);
|
|
if (exactMatch === true) {
|
|
value += '$';
|
|
}
|
|
}
|
|
value = new RegExp(value, caseSensitive ? '' : 'i');
|
|
}
|
|
return value;
|
|
},
|
|
|
|
/**
|
|
* Creates a shallow copy of this collection
|
|
* @return {MixedCollection}
|
|
*/
|
|
clone : function(){
|
|
var r = new Ext.util.MixedCollection();
|
|
var k = this.keys, it = this.items;
|
|
for(var i = 0, len = it.length; i < len; i++){
|
|
r.add(k[i], it[i]);
|
|
}
|
|
r.getKey = this.getKey;
|
|
return r;
|
|
}
|
|
});
|
|
/**
|
|
* This method calls {@link #item item()}.
|
|
* Returns the item associated with the passed key OR index. Key has priority
|
|
* over index. This is the equivalent of calling {@link #key} first, then if
|
|
* nothing matched calling {@link #itemAt}.
|
|
* @param {String/Number} key The key or index of the item.
|
|
* @return {Object} If the item is found, returns the item. If the item was
|
|
* not found, returns <tt>undefined</tt>. If an item was found, but is a Class,
|
|
* returns <tt>null</tt>.
|
|
*/
|
|
Ext.util.MixedCollection.prototype.get = Ext.util.MixedCollection.prototype.item;
|
|
/**
|
|
* @class Ext.AbstractManager
|
|
* @extends Object
|
|
* Base Manager class - extended by ComponentMgr and PluginMgr
|
|
*/
|
|
Ext.AbstractManager = Ext.extend(Object, {
|
|
typeName: 'type',
|
|
|
|
constructor: function(config) {
|
|
Ext.apply(this, config || {});
|
|
|
|
/**
|
|
* Contains all of the items currently managed
|
|
* @property all
|
|
* @type Ext.util.MixedCollection
|
|
*/
|
|
this.all = new Ext.util.MixedCollection();
|
|
|
|
this.types = {};
|
|
},
|
|
|
|
/**
|
|
* Returns a component by {@link Ext.Component#id id}.
|
|
* For additional details see {@link Ext.util.MixedCollection#get}.
|
|
* @param {String} id The component {@link Ext.Component#id id}
|
|
* @return Ext.Component The Component, <code>undefined</code> if not found, or <code>null</code> if a
|
|
* Class was found.
|
|
*/
|
|
get : function(id){
|
|
return this.all.get(id);
|
|
},
|
|
|
|
/**
|
|
* Registers an item to be managed
|
|
* @param {Mixed} item The item to register
|
|
*/
|
|
register: function(item) {
|
|
this.all.add(item);
|
|
},
|
|
|
|
/**
|
|
* Unregisters a component by removing it from this manager
|
|
* @param {Mixed} item The item to unregister
|
|
*/
|
|
unregister: function(item) {
|
|
this.all.remove(item);
|
|
},
|
|
|
|
/**
|
|
* <p>Registers a new Component constructor, keyed by a new
|
|
* {@link Ext.Component#xtype}.</p>
|
|
* <p>Use this method (or its alias {@link Ext#reg Ext.reg}) to register new
|
|
* subclasses of {@link Ext.Component} so that lazy instantiation may be used when specifying
|
|
* child Components.
|
|
* see {@link Ext.Container#items}</p>
|
|
* @param {String} xtype The mnemonic string by which the Component class may be looked up.
|
|
* @param {Constructor} cls The new Component class.
|
|
*/
|
|
registerType : function(type, cls){
|
|
this.types[type] = cls;
|
|
cls[this.typeName] = type;
|
|
},
|
|
|
|
/**
|
|
* Checks if a Component type is registered.
|
|
* @param {Ext.Component} xtype The mnemonic string by which the Component class may be looked up
|
|
* @return {Boolean} Whether the type is registered.
|
|
*/
|
|
isRegistered : function(type){
|
|
return this.types[type] !== undefined;
|
|
},
|
|
|
|
/**
|
|
* Creates and returns an instance of whatever this manager manages, based on the supplied type and config object
|
|
* @param {Object} config The config object
|
|
* @param {String} defaultType If no type is discovered in the config object, we fall back to this type
|
|
* @return {Mixed} The instance of whatever this manager is managing
|
|
*/
|
|
create: function(config, defaultType) {
|
|
var type = config[this.typeName] || config.type || defaultType,
|
|
Constructor = this.types[type];
|
|
|
|
if (Constructor == undefined) {
|
|
throw new Error(String.format("The '{0}' type has not been registered with this manager", type));
|
|
}
|
|
|
|
return new Constructor(config);
|
|
},
|
|
|
|
/**
|
|
* Registers a function that will be called when a Component with the specified id is added to the manager. This will happen on instantiation.
|
|
* @param {String} id The component {@link Ext.Component#id id}
|
|
* @param {Function} fn The callback function
|
|
* @param {Object} scope The scope (<code>this</code> reference) in which the callback is executed. Defaults to the Component.
|
|
*/
|
|
onAvailable : function(id, fn, scope){
|
|
var all = this.all;
|
|
|
|
all.on("add", function(index, o){
|
|
if (o.id == id) {
|
|
fn.call(scope || o, o);
|
|
all.un("add", fn, scope);
|
|
}
|
|
});
|
|
}
|
|
});/**
|
|
* @class Ext.util.Format
|
|
* Reusable data formatting functions
|
|
* @singleton
|
|
*/
|
|
Ext.util.Format = function() {
|
|
var trimRe = /^\s+|\s+$/g,
|
|
stripTagsRE = /<\/?[^>]+>/gi,
|
|
stripScriptsRe = /(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)/ig,
|
|
nl2brRe = /\r?\n/g;
|
|
|
|
return {
|
|
/**
|
|
* Truncate a string and add an ellipsis ('...') to the end if it exceeds the specified length
|
|
* @param {String} value The string to truncate
|
|
* @param {Number} length The maximum length to allow before truncating
|
|
* @param {Boolean} word True to try to find a common work break
|
|
* @return {String} The converted text
|
|
*/
|
|
ellipsis : function(value, len, word) {
|
|
if (value && value.length > len) {
|
|
if (word) {
|
|
var vs = value.substr(0, len - 2),
|
|
index = Math.max(vs.lastIndexOf(' '), vs.lastIndexOf('.'), vs.lastIndexOf('!'), vs.lastIndexOf('?'));
|
|
if (index == -1 || index < (len - 15)) {
|
|
return value.substr(0, len - 3) + "...";
|
|
} else {
|
|
return vs.substr(0, index) + "...";
|
|
}
|
|
} else {
|
|
return value.substr(0, len - 3) + "...";
|
|
}
|
|
}
|
|
return value;
|
|
},
|
|
|
|
/**
|
|
* Checks a reference and converts it to empty string if it is undefined
|
|
* @param {Mixed} value Reference to check
|
|
* @return {Mixed} 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 {Mixed} value Reference to check
|
|
* @param {String} defaultValue The value to insert if it's undefined (defaults to "")
|
|
* @return {String}
|
|
*/
|
|
defaultValue : function(value, defaultValue) {
|
|
if (!defaultValue && defaultValue !== 0) {
|
|
defaultValue = '';
|
|
}
|
|
return value !== undefined && value !== '' ? value : defaultValue;
|
|
},
|
|
|
|
/**
|
|
* Convert certain characters (&, <, >, and ') to their HTML character equivalents for literal display in web pages.
|
|
* @param {String} value The string to encode
|
|
* @return {String} The encoded text
|
|
*/
|
|
htmlEncode : function(value) {
|
|
return !value ? value : String(value).replace(/&/g, "&").replace(/>/g, ">").replace(/</g, "<").replace(/"/g, """);
|
|
},
|
|
|
|
/**
|
|
* Convert certain characters (&, <, >, and ') from their HTML character equivalents.
|
|
* @param {String} value The string to decode
|
|
* @return {String} The decoded text
|
|
*/
|
|
htmlDecode : function(value) {
|
|
return !value ? value : String(value).replace(/>/g, ">").replace(/</g, "<").replace(/"/g, '"').replace(/&/g, "&");
|
|
},
|
|
|
|
/**
|
|
* Trims any whitespace from either side of a string
|
|
* @param {String} value The text to trim
|
|
* @return {String} The trimmed text
|
|
*/
|
|
trim : function(value) {
|
|
return String(value).replace(trimRe, "");
|
|
},
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
substr : 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();
|
|
},
|
|
|
|
/**
|
|
* Converts the first character only of a string to upper case
|
|
* @param {String} value The text to convert
|
|
* @return {String} The converted text
|
|
*/
|
|
capitalize : function(value) {
|
|
return !value ? value : value.charAt(0).toUpperCase() + value.substr(1).toLowerCase();
|
|
},
|
|
|
|
// private
|
|
call : function(value, fn) {
|
|
if (arguments.length > 2) {
|
|
var args = Array.prototype.slice.call(arguments, 2);
|
|
args.unshift(value);
|
|
return eval(fn).apply(window, args);
|
|
} else {
|
|
return eval(fn).call(window, value);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Format a number as US currency
|
|
* @param {Number/String} value The numeric value to format
|
|
* @return {String} The formatted currency string
|
|
*/
|
|
usMoney : function(v) {
|
|
v = (Math.round((v-0)*100))/100;
|
|
v = (v == Math.floor(v)) ? v + ".00" : ((v*10 == Math.floor(v*10)) ? v + "0" : v);
|
|
v = String(v);
|
|
var ps = v.split('.'),
|
|
whole = ps[0],
|
|
sub = ps[1] ? '.'+ ps[1] : '.00',
|
|
r = /(\d+)(\d{3})/;
|
|
while (r.test(whole)) {
|
|
whole = whole.replace(r, '$1' + ',' + '$2');
|
|
}
|
|
v = whole + sub;
|
|
if (v.charAt(0) == '-') {
|
|
return '-$' + v.substr(1);
|
|
}
|
|
return "$" + v;
|
|
},
|
|
|
|
/**
|
|
* Parse a value into a formatted date using the specified format pattern.
|
|
* @param {String/Date} value The value to format (Strings must conform to the format expected by the javascript Date object's <a href="http://www.w3schools.com/jsref/jsref_parse.asp">parse()</a> method)
|
|
* @param {String} format (optional) Any valid date format string (defaults to 'm/d/Y')
|
|
* @return {String} The formatted date string
|
|
*/
|
|
date : function(v, format) {
|
|
if (!v) {
|
|
return "";
|
|
}
|
|
if (!Ext.isDate(v)) {
|
|
v = new Date(Date.parse(v));
|
|
}
|
|
return v.dateFormat(format || "m/d/Y");
|
|
},
|
|
|
|
/**
|
|
* 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
|
|
* @return {Function} The date formatting function
|
|
*/
|
|
dateRenderer : function(format) {
|
|
return function(v) {
|
|
return Ext.util.Format.date(v, format);
|
|
};
|
|
},
|
|
|
|
/**
|
|
* Strips all HTML tags
|
|
* @param {Mixed} 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 {Mixed} 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:<pre><code>
|
|
* var tpl = new Ext.Template('{value} * 10 = {value:math("* 10")}');
|
|
* </code></pre>
|
|
* @return {Function} A function that operates on the passed value.
|
|
*/
|
|
math : function(){
|
|
var fns = {};
|
|
|
|
return function(v, a){
|
|
if (!fns[a]) {
|
|
fns[a] = new Function('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 number according to the format string.
|
|
* <div style="margin-left:40px">examples (123456.789):
|
|
* <div style="margin-left:10px">
|
|
* 0 - (123456) show only digits, no precision<br>
|
|
* 0.00 - (123456.78) show only digits, 2 precision<br>
|
|
* 0.0000 - (123456.7890) show only digits, 4 precision<br>
|
|
* 0,000 - (123,456) show comma and digits, no precision<br>
|
|
* 0,000.00 - (123,456.78) show comma and digits, 2 precision<br>
|
|
* 0,0.00 - (123,456.78) shortcut method, show comma and digits, 2 precision<br>
|
|
* To reverse the grouping (,) and decimal (.) for international numbers, add /i to the end.
|
|
* For example: 0.000,00/i
|
|
* </div></div>
|
|
* @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, format) {
|
|
if (!format) {
|
|
return v;
|
|
}
|
|
v = Ext.num(v, NaN);
|
|
if (isNaN(v)) {
|
|
return '';
|
|
}
|
|
var comma = ',',
|
|
dec = '.',
|
|
i18n = false,
|
|
neg = v < 0;
|
|
|
|
v = Math.abs(v);
|
|
if (format.substr(format.length - 2) == '/i') {
|
|
format = format.substr(0, format.length - 2);
|
|
i18n = true;
|
|
comma = '.';
|
|
dec = ',';
|
|
}
|
|
|
|
var hasComma = format.indexOf(comma) != -1,
|
|
psplit = (i18n ? format.replace(/[^\d\,]/g, '') : format.replace(/[^\d\.]/g, '')).split(dec);
|
|
|
|
if (1 < psplit.length) {
|
|
v = v.toFixed(psplit[1].length);
|
|
} else if(2 < psplit.length) {
|
|
throw ('NumberFormatException: invalid format, formats should have no more than 1 period: ' + format);
|
|
} else {
|
|
v = v.toFixed(0);
|
|
}
|
|
|
|
var fnum = v.toString();
|
|
|
|
psplit = fnum.split('.');
|
|
|
|
if (hasComma) {
|
|
var cnum = psplit[0],
|
|
parr = [],
|
|
j = cnum.length,
|
|
m = Math.floor(j / 3),
|
|
n = cnum.length % 3 || 3,
|
|
i;
|
|
|
|
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];
|
|
}
|
|
}
|
|
|
|
return (neg ? '-' : '') + format.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 Ext.util.Format.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 (optional) 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 <br/>
|
|
* @param {String} The string value to format.
|
|
* @return {String} The string with embedded <br/> tags in place of newlines.
|
|
*/
|
|
nl2br : function(v) {
|
|
return Ext.isEmpty(v) ? '' : v.replace(nl2brRe, '<br/>');
|
|
}
|
|
};
|
|
}();
|
|
/**
|
|
* @class Ext.XTemplate
|
|
* @extends Ext.Template
|
|
* <p>A template class that supports advanced functionality like:<div class="mdetail-params"><ul>
|
|
* <li>Autofilling arrays using templates and sub-templates</li>
|
|
* <li>Conditional processing with basic comparison operators</li>
|
|
* <li>Basic math function support</li>
|
|
* <li>Execute arbitrary inline code with special built-in template variables</li>
|
|
* <li>Custom member functions</li>
|
|
* <li>Many special tags and built-in operators that aren't defined as part of
|
|
* the API, but are supported in the templates that can be created</li>
|
|
* </ul></div></p>
|
|
* <p>XTemplate provides the templating mechanism built into:<div class="mdetail-params"><ul>
|
|
* <li>{@link Ext.DataView}</li>
|
|
* <li>{@link Ext.ListView}</li>
|
|
* <li>{@link Ext.form.ComboBox}</li>
|
|
* <li>{@link Ext.grid.TemplateColumn}</li>
|
|
* <li>{@link Ext.grid.GroupingView}</li>
|
|
* <li>{@link Ext.menu.Item}</li>
|
|
* <li>{@link Ext.layout.MenuLayout}</li>
|
|
* <li>{@link Ext.ColorPalette}</li>
|
|
* </ul></div></p>
|
|
*
|
|
* <p>For example usage {@link #XTemplate see the constructor}.</p>
|
|
*
|
|
* @constructor
|
|
* The {@link Ext.Template#Template Ext.Template constructor} describes
|
|
* the acceptable parameters to pass to the constructor. The following
|
|
* examples demonstrate all of the supported features.</p>
|
|
*
|
|
* <div class="mdetail-params"><ul>
|
|
*
|
|
* <li><b><u>Sample Data</u></b>
|
|
* <div class="sub-desc">
|
|
* <p>This is the data object used for reference in each code example:</p>
|
|
* <pre><code>
|
|
var data = {
|
|
name: 'Jack Slocum',
|
|
title: 'Lead Developer',
|
|
company: 'Ext JS, LLC',
|
|
email: 'jack@extjs.com',
|
|
address: '4 Red Bulls Drive',
|
|
city: 'Cleveland',
|
|
state: 'Ohio',
|
|
zip: '44102',
|
|
drinks: ['Red Bull', 'Coffee', 'Water'],
|
|
kids: [{
|
|
name: 'Sara Grace',
|
|
age:3
|
|
},{
|
|
name: 'Zachary',
|
|
age:2
|
|
},{
|
|
name: 'John James',
|
|
age:0
|
|
}]
|
|
};
|
|
* </code></pre>
|
|
* </div>
|
|
* </li>
|
|
*
|
|
*
|
|
* <li><b><u>Auto filling of arrays</u></b>
|
|
* <div class="sub-desc">
|
|
* <p>The <b><tt>tpl</tt></b> tag and the <b><tt>for</tt></b> operator are used
|
|
* to process the provided data object:
|
|
* <ul>
|
|
* <li>If the value specified in <tt>for</tt> is an array, it will auto-fill,
|
|
* repeating the template block inside the <tt>tpl</tt> tag for each item in the
|
|
* array.</li>
|
|
* <li>If <tt>for="."</tt> is specified, the data object provided is examined.</li>
|
|
* <li>While processing an array, the special variable <tt>{#}</tt>
|
|
* will provide the current array index + 1 (starts at 1, not 0).</li>
|
|
* </ul>
|
|
* </p>
|
|
* <pre><code>
|
|
<tpl <b>for</b>=".">...</tpl> // loop through array at root node
|
|
<tpl <b>for</b>="foo">...</tpl> // loop through array at foo node
|
|
<tpl <b>for</b>="foo.bar">...</tpl> // loop through array at foo.bar node
|
|
* </code></pre>
|
|
* Using the sample data above:
|
|
* <pre><code>
|
|
var tpl = new Ext.XTemplate(
|
|
'<p>Kids: ',
|
|
'<tpl <b>for</b>=".">', // process the data.kids node
|
|
'<p>{#}. {name}</p>', // use current array index to autonumber
|
|
'</tpl></p>'
|
|
);
|
|
tpl.overwrite(panel.body, data.kids); // pass the kids property of the data object
|
|
* </code></pre>
|
|
* <p>An example illustrating how the <b><tt>for</tt></b> property can be leveraged
|
|
* to access specified members of the provided data object to populate the template:</p>
|
|
* <pre><code>
|
|
var tpl = new Ext.XTemplate(
|
|
'<p>Name: {name}</p>',
|
|
'<p>Title: {title}</p>',
|
|
'<p>Company: {company}</p>',
|
|
'<p>Kids: ',
|
|
'<tpl <b>for="kids"</b>>', // interrogate the kids property within the data
|
|
'<p>{name}</p>',
|
|
'</tpl></p>'
|
|
);
|
|
tpl.overwrite(panel.body, data); // pass the root node of the data object
|
|
* </code></pre>
|
|
* <p>Flat arrays that contain values (and not objects) can be auto-rendered
|
|
* using the special <b><tt>{.}</tt></b> variable inside a loop. This variable
|
|
* will represent the value of the array at the current index:</p>
|
|
* <pre><code>
|
|
var tpl = new Ext.XTemplate(
|
|
'<p>{name}\'s favorite beverages:</p>',
|
|
'<tpl for="drinks">',
|
|
'<div> - {.}</div>',
|
|
'</tpl>'
|
|
);
|
|
tpl.overwrite(panel.body, data);
|
|
* </code></pre>
|
|
* <p>When processing a sub-template, for example while looping through a child array,
|
|
* you can access the parent object's members via the <b><tt>parent</tt></b> object:</p>
|
|
* <pre><code>
|
|
var tpl = new Ext.XTemplate(
|
|
'<p>Name: {name}</p>',
|
|
'<p>Kids: ',
|
|
'<tpl for="kids">',
|
|
'<tpl if="age > 1">',
|
|
'<p>{name}</p>',
|
|
'<p>Dad: {<b>parent</b>.name}</p>',
|
|
'</tpl>',
|
|
'</tpl></p>'
|
|
);
|
|
tpl.overwrite(panel.body, data);
|
|
* </code></pre>
|
|
* </div>
|
|
* </li>
|
|
*
|
|
*
|
|
* <li><b><u>Conditional processing with basic comparison operators</u></b>
|
|
* <div class="sub-desc">
|
|
* <p>The <b><tt>tpl</tt></b> tag and the <b><tt>if</tt></b> operator are used
|
|
* to provide conditional checks for deciding whether or not to render specific
|
|
* parts of the template. Notes:<div class="sub-desc"><ul>
|
|
* <li>Double quotes must be encoded if used within the conditional</li>
|
|
* <li>There is no <tt>else</tt> operator — if needed, two opposite
|
|
* <tt>if</tt> statements should be used.</li>
|
|
* </ul></div>
|
|
* <pre><code>
|
|
<tpl if="age > 1 && age < 10">Child</tpl>
|
|
<tpl if="age >= 10 && age < 18">Teenager</tpl>
|
|
<tpl <b>if</b>="this.isGirl(name)">...</tpl>
|
|
<tpl <b>if</b>="id==\'download\'">...</tpl>
|
|
<tpl <b>if</b>="needsIcon"><img src="{icon}" class="{iconCls}"/></tpl>
|
|
// no good:
|
|
<tpl if="name == "Jack"">Hello</tpl>
|
|
// encode " if it is part of the condition, e.g.
|
|
<tpl if="name == &quot;Jack&quot;">Hello</tpl>
|
|
* </code></pre>
|
|
* Using the sample data above:
|
|
* <pre><code>
|
|
var tpl = new Ext.XTemplate(
|
|
'<p>Name: {name}</p>',
|
|
'<p>Kids: ',
|
|
'<tpl for="kids">',
|
|
'<tpl if="age > 1">',
|
|
'<p>{name}</p>',
|
|
'</tpl>',
|
|
'</tpl></p>'
|
|
);
|
|
tpl.overwrite(panel.body, data);
|
|
* </code></pre>
|
|
* </div>
|
|
* </li>
|
|
*
|
|
*
|
|
* <li><b><u>Basic math support</u></b>
|
|
* <div class="sub-desc">
|
|
* <p>The following basic math operators may be applied directly on numeric
|
|
* data values:</p><pre>
|
|
* + - * /
|
|
* </pre>
|
|
* For example:
|
|
* <pre><code>
|
|
var tpl = new Ext.XTemplate(
|
|
'<p>Name: {name}</p>',
|
|
'<p>Kids: ',
|
|
'<tpl for="kids">',
|
|
'<tpl if="age &gt; 1">', // <-- Note that the > is encoded
|
|
'<p>{#}: {name}</p>', // <-- Auto-number each item
|
|
'<p>In 5 Years: {age+5}</p>', // <-- Basic math
|
|
'<p>Dad: {parent.name}</p>',
|
|
'</tpl>',
|
|
'</tpl></p>'
|
|
);
|
|
tpl.overwrite(panel.body, data);
|
|
</code></pre>
|
|
* </div>
|
|
* </li>
|
|
*
|
|
*
|
|
* <li><b><u>Execute arbitrary inline code with special built-in template variables</u></b>
|
|
* <div class="sub-desc">
|
|
* <p>Anything between <code>{[ ... ]}</code> is considered code to be executed
|
|
* in the scope of the template. There are some special variables available in that code:
|
|
* <ul>
|
|
* <li><b><tt>values</tt></b>: The values in the current scope. If you are using
|
|
* scope changing sub-templates, you can change what <tt>values</tt> is.</li>
|
|
* <li><b><tt>parent</tt></b>: The scope (values) of the ancestor template.</li>
|
|
* <li><b><tt>xindex</tt></b>: If you are in a looping template, the index of the
|
|
* loop you are in (1-based).</li>
|
|
* <li><b><tt>xcount</tt></b>: If you are in a looping template, the total length
|
|
* of the array you are looping.</li>
|
|
* <li><b><tt>fm</tt></b>: An alias for <tt>Ext.util.Format</tt>.</li>
|
|
* </ul>
|
|
* This example demonstrates basic row striping using an inline code block and the
|
|
* <tt>xindex</tt> variable:</p>
|
|
* <pre><code>
|
|
var tpl = new Ext.XTemplate(
|
|
'<p>Name: {name}</p>',
|
|
'<p>Company: {[values.company.toUpperCase() + ", " + values.title]}</p>',
|
|
'<p>Kids: ',
|
|
'<tpl for="kids">',
|
|
'<div class="{[xindex % 2 === 0 ? "even" : "odd"]}">',
|
|
'{name}',
|
|
'</div>',
|
|
'</tpl></p>'
|
|
);
|
|
tpl.overwrite(panel.body, data);
|
|
* </code></pre>
|
|
* </div>
|
|
* </li>
|
|
*
|
|
* <li><b><u>Template member functions</u></b>
|
|
* <div class="sub-desc">
|
|
* <p>One or more member functions can be specified in a configuration
|
|
* object passed into the XTemplate constructor for more complex processing:</p>
|
|
* <pre><code>
|
|
var tpl = new Ext.XTemplate(
|
|
'<p>Name: {name}</p>',
|
|
'<p>Kids: ',
|
|
'<tpl for="kids">',
|
|
'<tpl if="this.isGirl(name)">',
|
|
'<p>Girl: {name} - {age}</p>',
|
|
'</tpl>',
|
|
// use opposite if statement to simulate 'else' processing:
|
|
'<tpl if="this.isGirl(name) == false">',
|
|
'<p>Boy: {name} - {age}</p>',
|
|
'</tpl>',
|
|
'<tpl if="this.isBaby(age)">',
|
|
'<p>{name} is a baby!</p>',
|
|
'</tpl>',
|
|
'</tpl></p>',
|
|
{
|
|
// XTemplate configuration:
|
|
compiled: true,
|
|
disableFormats: true,
|
|
// member functions:
|
|
isGirl: function(name){
|
|
return name == 'Sara Grace';
|
|
},
|
|
isBaby: function(age){
|
|
return age < 1;
|
|
}
|
|
}
|
|
);
|
|
tpl.overwrite(panel.body, data);
|
|
* </code></pre>
|
|
* </div>
|
|
* </li>
|
|
*
|
|
* </ul></div>
|
|
*
|
|
* @param {Mixed} config
|
|
*/
|
|
Ext.XTemplate = function(){
|
|
Ext.XTemplate.superclass.constructor.apply(this, arguments);
|
|
|
|
var me = this,
|
|
s = me.html,
|
|
re = /<tpl\b[^>]*>((?:(?=([^<]+))\2|<(?!tpl\b[^>]*>))*?)<\/tpl>/,
|
|
nameRe = /^<tpl\b[^>]*?for="(.*?)"/,
|
|
ifRe = /^<tpl\b[^>]*?if="(.*?)"/,
|
|
execRe = /^<tpl\b[^>]*?exec="(.*?)"/,
|
|
m,
|
|
id = 0,
|
|
tpls = [],
|
|
VALUES = 'values',
|
|
PARENT = 'parent',
|
|
XINDEX = 'xindex',
|
|
XCOUNT = 'xcount',
|
|
RETURN = 'return ',
|
|
WITHVALUES = 'with(values){ ';
|
|
|
|
s = ['<tpl>', s, '</tpl>'].join('');
|
|
|
|
while((m = s.match(re))){
|
|
var m2 = m[0].match(nameRe),
|
|
m3 = m[0].match(ifRe),
|
|
m4 = m[0].match(execRe),
|
|
exp = null,
|
|
fn = null,
|
|
exec = null,
|
|
name = m2 && m2[1] ? m2[1] : '';
|
|
|
|
if (m3) {
|
|
exp = m3 && m3[1] ? m3[1] : null;
|
|
if(exp){
|
|
fn = new Function(VALUES, PARENT, XINDEX, XCOUNT, WITHVALUES + RETURN +(Ext.util.Format.htmlDecode(exp))+'; }');
|
|
}
|
|
}
|
|
if (m4) {
|
|
exp = m4 && m4[1] ? m4[1] : null;
|
|
if(exp){
|
|
exec = new Function(VALUES, PARENT, XINDEX, XCOUNT, WITHVALUES +(Ext.util.Format.htmlDecode(exp))+'; }');
|
|
}
|
|
}
|
|
if(name){
|
|
switch(name){
|
|
case '.': name = new Function(VALUES, PARENT, WITHVALUES + RETURN + VALUES + '; }'); break;
|
|
case '..': name = new Function(VALUES, PARENT, WITHVALUES + RETURN + PARENT + '; }'); break;
|
|
default: name = new Function(VALUES, PARENT, WITHVALUES + RETURN + name + '; }');
|
|
}
|
|
}
|
|
tpls.push({
|
|
id: id,
|
|
target: name,
|
|
exec: exec,
|
|
test: fn,
|
|
body: m[1]||''
|
|
});
|
|
s = s.replace(m[0], '{xtpl'+ id + '}');
|
|
++id;
|
|
}
|
|
for(var i = tpls.length-1; i >= 0; --i){
|
|
me.compileTpl(tpls[i]);
|
|
}
|
|
me.master = tpls[tpls.length-1];
|
|
me.tpls = tpls;
|
|
};
|
|
Ext.extend(Ext.XTemplate, Ext.Template, {
|
|
// private
|
|
re : /\{([\w\-\.\#]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?(\s?[\+\-\*\\]\s?[\d\.\+\-\*\\\(\)]+)?\}/g,
|
|
// private
|
|
codeRe : /\{\[((?:\\\]|.|\n)*?)\]\}/g,
|
|
|
|
// private
|
|
applySubTemplate : function(id, values, parent, xindex, xcount){
|
|
var me = this,
|
|
len,
|
|
t = me.tpls[id],
|
|
vs,
|
|
buf = [];
|
|
if ((t.test && !t.test.call(me, values, parent, xindex, xcount)) ||
|
|
(t.exec && t.exec.call(me, values, parent, xindex, xcount))) {
|
|
return '';
|
|
}
|
|
vs = t.target ? t.target.call(me, values, parent) : values;
|
|
len = vs.length;
|
|
parent = t.target ? values : parent;
|
|
if(t.target && Ext.isArray(vs)){
|
|
for(var i = 0, len = vs.length; i < len; i++){
|
|
buf[buf.length] = t.compiled.call(me, vs[i], parent, i+1, len);
|
|
}
|
|
return buf.join('');
|
|
}
|
|
return t.compiled.call(me, vs, parent, xindex, xcount);
|
|
},
|
|
|
|
// private
|
|
compileTpl : function(tpl){
|
|
var fm = Ext.util.Format,
|
|
useF = this.disableFormats !== true,
|
|
sep = Ext.isGecko ? "+" : ",",
|
|
body;
|
|
|
|
function fn(m, name, format, args, math){
|
|
if(name.substr(0, 4) == 'xtpl'){
|
|
return "'"+ sep +'this.applySubTemplate('+name.substr(4)+', values, parent, xindex, xcount)'+sep+"'";
|
|
}
|
|
var v;
|
|
if(name === '.'){
|
|
v = 'values';
|
|
}else if(name === '#'){
|
|
v = 'xindex';
|
|
}else if(name.indexOf('.') != -1){
|
|
v = name;
|
|
}else{
|
|
v = "values['" + name + "']";
|
|
}
|
|
if(math){
|
|
v = '(' + v + math + ')';
|
|
}
|
|
if (format && useF) {
|
|
args = args ? ',' + args : "";
|
|
if(format.substr(0, 5) != "this."){
|
|
format = "fm." + format + '(';
|
|
}else{
|
|
format = 'this.call("'+ format.substr(5) + '", ';
|
|
args = ", values";
|
|
}
|
|
} else {
|
|
args= ''; format = "("+v+" === undefined ? '' : ";
|
|
}
|
|
return "'"+ sep + format + v + args + ")"+sep+"'";
|
|
}
|
|
|
|
function codeFn(m, code){
|
|
// Single quotes get escaped when the template is compiled, however we want to undo this when running code.
|
|
return "'" + sep + '(' + code.replace(/\\'/g, "'") + ')' + sep + "'";
|
|
}
|
|
|
|
// branched to use + in gecko and [].join() in others
|
|
if(Ext.isGecko){
|
|
body = "tpl.compiled = function(values, parent, xindex, xcount){ return '" +
|
|
tpl.body.replace(/(\r\n|\n)/g, '\\n').replace(/'/g, "\\'").replace(this.re, fn).replace(this.codeRe, codeFn) +
|
|
"';};";
|
|
}else{
|
|
body = ["tpl.compiled = function(values, parent, xindex, xcount){ return ['"];
|
|
body.push(tpl.body.replace(/(\r\n|\n)/g, '\\n').replace(/'/g, "\\'").replace(this.re, fn).replace(this.codeRe, codeFn));
|
|
body.push("'].join('');};");
|
|
body = body.join('');
|
|
}
|
|
eval(body);
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Returns an HTML fragment of this template with the specified values applied.
|
|
* @param {Object} values The template values. Can be an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'})
|
|
* @return {String} The HTML fragment
|
|
*/
|
|
applyTemplate : function(values){
|
|
return this.master.compiled.call(this, values, {}, 1, 1);
|
|
},
|
|
|
|
/**
|
|
* Compile the template to a function for optimized performance. Recommended if the template will be used frequently.
|
|
* @return {Function} The compiled function
|
|
*/
|
|
compile : function(){return this;}
|
|
|
|
/**
|
|
* @property re
|
|
* @hide
|
|
*/
|
|
/**
|
|
* @property disableFormats
|
|
* @hide
|
|
*/
|
|
/**
|
|
* @method set
|
|
* @hide
|
|
*/
|
|
|
|
});
|
|
/**
|
|
* Alias for {@link #applyTemplate}
|
|
* Returns an HTML fragment of this template with the specified values applied.
|
|
* @param {Object/Array} values The template values. Can be an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'})
|
|
* @return {String} The HTML fragment
|
|
* @member Ext.XTemplate
|
|
* @method apply
|
|
*/
|
|
Ext.XTemplate.prototype.apply = Ext.XTemplate.prototype.applyTemplate;
|
|
|
|
/**
|
|
* Creates a template from the passed element's value (<i>display:none</i> textarea, preferred) or innerHTML.
|
|
* @param {String/HTMLElement} el A DOM element or its id
|
|
* @return {Ext.Template} The created template
|
|
* @static
|
|
*/
|
|
Ext.XTemplate.from = function(el){
|
|
el = Ext.getDom(el);
|
|
return new Ext.XTemplate(el.value || el.innerHTML);
|
|
};
|
|
/**
|
|
* @class Ext.util.CSS
|
|
* Utility class for manipulating CSS rules
|
|
* @singleton
|
|
*/
|
|
Ext.util.CSS = function(){
|
|
var rules = null;
|
|
var doc = document;
|
|
|
|
var camelRe = /(-[a-z])/gi;
|
|
var camelFn = function(m, a){ return a.charAt(1).toUpperCase(); };
|
|
|
|
return {
|
|
/**
|
|
* Creates a stylesheet from a text blob of rules.
|
|
* These rules will be wrapped in a STYLE tag and appended to the HEAD of the document.
|
|
* @param {String} cssText The text containing the css rules
|
|
* @param {String} id An id to add to the stylesheet for later removal
|
|
* @return {StyleSheet}
|
|
*/
|
|
createStyleSheet : function(cssText, id){
|
|
var ss;
|
|
var head = doc.getElementsByTagName("head")[0];
|
|
var rules = doc.createElement("style");
|
|
rules.setAttribute("type", "text/css");
|
|
if(id){
|
|
rules.setAttribute("id", id);
|
|
}
|
|
if(Ext.isIE){
|
|
head.appendChild(rules);
|
|
ss = rules.styleSheet;
|
|
ss.cssText = cssText;
|
|
}else{
|
|
try{
|
|
rules.appendChild(doc.createTextNode(cssText));
|
|
}catch(e){
|
|
rules.cssText = cssText;
|
|
}
|
|
head.appendChild(rules);
|
|
ss = rules.styleSheet ? rules.styleSheet : (rules.sheet || doc.styleSheets[doc.styleSheets.length-1]);
|
|
}
|
|
this.cacheStyleSheet(ss);
|
|
return ss;
|
|
},
|
|
|
|
/**
|
|
* Removes a style or link tag by id
|
|
* @param {String} id The id of the tag
|
|
*/
|
|
removeStyleSheet : function(id){
|
|
var existing = doc.getElementById(id);
|
|
if(existing){
|
|
existing.parentNode.removeChild(existing);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Dynamically swaps an existing stylesheet reference for a new one
|
|
* @param {String} id The id of an existing link tag to remove
|
|
* @param {String} url The href of the new stylesheet to include
|
|
*/
|
|
swapStyleSheet : function(id, url){
|
|
this.removeStyleSheet(id);
|
|
var ss = doc.createElement("link");
|
|
ss.setAttribute("rel", "stylesheet");
|
|
ss.setAttribute("type", "text/css");
|
|
ss.setAttribute("id", id);
|
|
ss.setAttribute("href", url);
|
|
doc.getElementsByTagName("head")[0].appendChild(ss);
|
|
},
|
|
|
|
/**
|
|
* Refresh the rule cache if you have dynamically added stylesheets
|
|
* @return {Object} An object (hash) of rules indexed by selector
|
|
*/
|
|
refreshCache : function(){
|
|
return this.getRules(true);
|
|
},
|
|
|
|
// private
|
|
cacheStyleSheet : function(ss){
|
|
if(!rules){
|
|
rules = {};
|
|
}
|
|
try{// try catch for cross domain access issue
|
|
var ssRules = ss.cssRules || ss.rules;
|
|
for(var j = ssRules.length-1; j >= 0; --j){
|
|
rules[ssRules[j].selectorText.toLowerCase()] = ssRules[j];
|
|
}
|
|
}catch(e){}
|
|
},
|
|
|
|
/**
|
|
* Gets all css rules for the document
|
|
* @param {Boolean} refreshCache true to refresh the internal cache
|
|
* @return {Object} An object (hash) of rules indexed by selector
|
|
*/
|
|
getRules : function(refreshCache){
|
|
if(rules === null || refreshCache){
|
|
rules = {};
|
|
var ds = doc.styleSheets;
|
|
for(var i =0, len = ds.length; i < len; i++){
|
|
try{
|
|
this.cacheStyleSheet(ds[i]);
|
|
}catch(e){}
|
|
}
|
|
}
|
|
return rules;
|
|
},
|
|
|
|
/**
|
|
* Gets an an individual CSS rule by selector(s)
|
|
* @param {String/Array} selector The CSS selector or an array of selectors to try. The first selector that is found is returned.
|
|
* @param {Boolean} refreshCache true to refresh the internal cache if you have recently updated any rules or added styles dynamically
|
|
* @return {CSSRule} The CSS rule or null if one is not found
|
|
*/
|
|
getRule : function(selector, refreshCache){
|
|
var rs = this.getRules(refreshCache);
|
|
if(!Ext.isArray(selector)){
|
|
return rs[selector.toLowerCase()];
|
|
}
|
|
for(var i = 0; i < selector.length; i++){
|
|
if(rs[selector[i]]){
|
|
return rs[selector[i].toLowerCase()];
|
|
}
|
|
}
|
|
return null;
|
|
},
|
|
|
|
|
|
/**
|
|
* Updates a rule property
|
|
* @param {String/Array} selector If it's an array it tries each selector until it finds one. Stops immediately once one is found.
|
|
* @param {String} property The css property
|
|
* @param {String} value The new value for the property
|
|
* @return {Boolean} true If a rule was found and updated
|
|
*/
|
|
updateRule : function(selector, property, value){
|
|
if(!Ext.isArray(selector)){
|
|
var rule = this.getRule(selector);
|
|
if(rule){
|
|
rule.style[property.replace(camelRe, camelFn)] = value;
|
|
return true;
|
|
}
|
|
}else{
|
|
for(var i = 0; i < selector.length; i++){
|
|
if(this.updateRule(selector[i], property, value)){
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
}();/**
|
|
@class Ext.util.ClickRepeater
|
|
@extends Ext.util.Observable
|
|
|
|
A wrapper class which can be applied to any element. Fires a "click" event while the
|
|
mouse is pressed. The interval between firings may be specified in the config but
|
|
defaults to 20 milliseconds.
|
|
|
|
Optionally, a CSS class may be applied to the element during the time it is pressed.
|
|
|
|
@cfg {Mixed} el The element to act as a button.
|
|
@cfg {Number} delay The initial delay before the repeating event begins firing.
|
|
Similar to an autorepeat key delay.
|
|
@cfg {Number} interval The interval between firings of the "click" event. Default 20 ms.
|
|
@cfg {String} pressClass A CSS class name to be applied to the element while pressed.
|
|
@cfg {Boolean} accelerate True if autorepeating should start slowly and accelerate.
|
|
"interval" and "delay" are ignored.
|
|
@cfg {Boolean} preventDefault True to prevent the default click event
|
|
@cfg {Boolean} stopDefault True to stop the default click event
|
|
|
|
@history
|
|
2007-02-02 jvs Original code contributed by Nige "Animal" White
|
|
2007-02-02 jvs Renamed to ClickRepeater
|
|
2007-02-03 jvs Modifications for FF Mac and Safari
|
|
|
|
@constructor
|
|
@param {Mixed} el The element to listen on
|
|
@param {Object} config
|
|
*/
|
|
Ext.util.ClickRepeater = Ext.extend(Ext.util.Observable, {
|
|
|
|
constructor : function(el, config){
|
|
this.el = Ext.get(el);
|
|
this.el.unselectable();
|
|
|
|
Ext.apply(this, config);
|
|
|
|
this.addEvents(
|
|
/**
|
|
* @event mousedown
|
|
* Fires when the mouse button is depressed.
|
|
* @param {Ext.util.ClickRepeater} this
|
|
* @param {Ext.EventObject} e
|
|
*/
|
|
"mousedown",
|
|
/**
|
|
* @event click
|
|
* Fires on a specified interval during the time the element is pressed.
|
|
* @param {Ext.util.ClickRepeater} this
|
|
* @param {Ext.EventObject} e
|
|
*/
|
|
"click",
|
|
/**
|
|
* @event mouseup
|
|
* Fires when the mouse key is released.
|
|
* @param {Ext.util.ClickRepeater} this
|
|
* @param {Ext.EventObject} e
|
|
*/
|
|
"mouseup"
|
|
);
|
|
|
|
if(!this.disabled){
|
|
this.disabled = true;
|
|
this.enable();
|
|
}
|
|
|
|
// allow inline handler
|
|
if(this.handler){
|
|
this.on("click", this.handler, this.scope || this);
|
|
}
|
|
|
|
Ext.util.ClickRepeater.superclass.constructor.call(this);
|
|
},
|
|
|
|
interval : 20,
|
|
delay: 250,
|
|
preventDefault : true,
|
|
stopDefault : false,
|
|
timer : 0,
|
|
|
|
/**
|
|
* Enables the repeater and allows events to fire.
|
|
*/
|
|
enable: function(){
|
|
if(this.disabled){
|
|
this.el.on('mousedown', this.handleMouseDown, this);
|
|
if (Ext.isIE){
|
|
this.el.on('dblclick', this.handleDblClick, this);
|
|
}
|
|
if(this.preventDefault || this.stopDefault){
|
|
this.el.on('click', this.eventOptions, this);
|
|
}
|
|
}
|
|
this.disabled = false;
|
|
},
|
|
|
|
/**
|
|
* Disables the repeater and stops events from firing.
|
|
*/
|
|
disable: function(/* private */ force){
|
|
if(force || !this.disabled){
|
|
clearTimeout(this.timer);
|
|
if(this.pressClass){
|
|
this.el.removeClass(this.pressClass);
|
|
}
|
|
Ext.getDoc().un('mouseup', this.handleMouseUp, this);
|
|
this.el.removeAllListeners();
|
|
}
|
|
this.disabled = true;
|
|
},
|
|
|
|
/**
|
|
* Convenience function for setting disabled/enabled by boolean.
|
|
* @param {Boolean} disabled
|
|
*/
|
|
setDisabled: function(disabled){
|
|
this[disabled ? 'disable' : 'enable']();
|
|
},
|
|
|
|
eventOptions: function(e){
|
|
if(this.preventDefault){
|
|
e.preventDefault();
|
|
}
|
|
if(this.stopDefault){
|
|
e.stopEvent();
|
|
}
|
|
},
|
|
|
|
// private
|
|
destroy : function() {
|
|
this.disable(true);
|
|
Ext.destroy(this.el);
|
|
this.purgeListeners();
|
|
},
|
|
|
|
handleDblClick : function(e){
|
|
clearTimeout(this.timer);
|
|
this.el.blur();
|
|
|
|
this.fireEvent("mousedown", this, e);
|
|
this.fireEvent("click", this, e);
|
|
},
|
|
|
|
// private
|
|
handleMouseDown : function(e){
|
|
clearTimeout(this.timer);
|
|
this.el.blur();
|
|
if(this.pressClass){
|
|
this.el.addClass(this.pressClass);
|
|
}
|
|
this.mousedownTime = new Date();
|
|
|
|
Ext.getDoc().on("mouseup", this.handleMouseUp, this);
|
|
this.el.on("mouseout", this.handleMouseOut, this);
|
|
|
|
this.fireEvent("mousedown", this, e);
|
|
this.fireEvent("click", this, e);
|
|
|
|
// Do not honor delay or interval if acceleration wanted.
|
|
if (this.accelerate) {
|
|
this.delay = 400;
|
|
}
|
|
this.timer = this.click.defer(this.delay || this.interval, this, [e]);
|
|
},
|
|
|
|
// private
|
|
click : function(e){
|
|
this.fireEvent("click", this, e);
|
|
this.timer = this.click.defer(this.accelerate ?
|
|
this.easeOutExpo(this.mousedownTime.getElapsed(),
|
|
400,
|
|
-390,
|
|
12000) :
|
|
this.interval, this, [e]);
|
|
},
|
|
|
|
easeOutExpo : function (t, b, c, d) {
|
|
return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
|
|
},
|
|
|
|
// private
|
|
handleMouseOut : function(){
|
|
clearTimeout(this.timer);
|
|
if(this.pressClass){
|
|
this.el.removeClass(this.pressClass);
|
|
}
|
|
this.el.on("mouseover", this.handleMouseReturn, this);
|
|
},
|
|
|
|
// private
|
|
handleMouseReturn : function(){
|
|
this.el.un("mouseover", this.handleMouseReturn, this);
|
|
if(this.pressClass){
|
|
this.el.addClass(this.pressClass);
|
|
}
|
|
this.click();
|
|
},
|
|
|
|
// private
|
|
handleMouseUp : function(e){
|
|
clearTimeout(this.timer);
|
|
this.el.un("mouseover", this.handleMouseReturn, this);
|
|
this.el.un("mouseout", this.handleMouseOut, this);
|
|
Ext.getDoc().un("mouseup", this.handleMouseUp, this);
|
|
this.el.removeClass(this.pressClass);
|
|
this.fireEvent("mouseup", this, e);
|
|
}
|
|
});/**
|
|
* @class Ext.KeyNav
|
|
* <p>Provides a convenient wrapper for normalized keyboard navigation. KeyNav allows you to bind
|
|
* navigation keys to function calls that will get called when the keys are pressed, providing an easy
|
|
* way to implement custom navigation schemes for any UI component.</p>
|
|
* <p>The following are all of the possible keys that can be implemented: enter, left, right, up, down, tab, esc,
|
|
* pageUp, pageDown, del, home, end. Usage:</p>
|
|
<pre><code>
|
|
var nav = new Ext.KeyNav("my-element", {
|
|
"left" : function(e){
|
|
this.moveLeft(e.ctrlKey);
|
|
},
|
|
"right" : function(e){
|
|
this.moveRight(e.ctrlKey);
|
|
},
|
|
"enter" : function(e){
|
|
this.save();
|
|
},
|
|
scope : this
|
|
});
|
|
</code></pre>
|
|
* @constructor
|
|
* @param {Mixed} el The element to bind to
|
|
* @param {Object} config The config
|
|
*/
|
|
Ext.KeyNav = function(el, config){
|
|
this.el = Ext.get(el);
|
|
Ext.apply(this, config);
|
|
if(!this.disabled){
|
|
this.disabled = true;
|
|
this.enable();
|
|
}
|
|
};
|
|
|
|
Ext.KeyNav.prototype = {
|
|
/**
|
|
* @cfg {Boolean} disabled
|
|
* True to disable this KeyNav instance (defaults to false)
|
|
*/
|
|
disabled : false,
|
|
/**
|
|
* @cfg {String} defaultEventAction
|
|
* The method to call on the {@link Ext.EventObject} after this KeyNav intercepts a key. Valid values are
|
|
* {@link Ext.EventObject#stopEvent}, {@link Ext.EventObject#preventDefault} and
|
|
* {@link Ext.EventObject#stopPropagation} (defaults to 'stopEvent')
|
|
*/
|
|
defaultEventAction: "stopEvent",
|
|
/**
|
|
* @cfg {Boolean} forceKeyDown
|
|
* Handle the keydown event instead of keypress (defaults to false). KeyNav automatically does this for IE since
|
|
* IE does not propagate special keys on keypress, but setting this to true will force other browsers to also
|
|
* handle keydown instead of keypress.
|
|
*/
|
|
forceKeyDown : false,
|
|
|
|
// private
|
|
relay : function(e){
|
|
var k = e.getKey(),
|
|
h = this.keyToHandler[k];
|
|
if(h && this[h]){
|
|
if(this.doRelay(e, this[h], h) !== true){
|
|
e[this.defaultEventAction]();
|
|
}
|
|
}
|
|
},
|
|
|
|
// private
|
|
doRelay : function(e, h, hname){
|
|
return h.call(this.scope || this, e, hname);
|
|
},
|
|
|
|
// possible handlers
|
|
enter : false,
|
|
left : false,
|
|
right : false,
|
|
up : false,
|
|
down : false,
|
|
tab : false,
|
|
esc : false,
|
|
pageUp : false,
|
|
pageDown : false,
|
|
del : false,
|
|
home : false,
|
|
end : false,
|
|
space : false,
|
|
|
|
// quick lookup hash
|
|
keyToHandler : {
|
|
37 : "left",
|
|
39 : "right",
|
|
38 : "up",
|
|
40 : "down",
|
|
33 : "pageUp",
|
|
34 : "pageDown",
|
|
46 : "del",
|
|
36 : "home",
|
|
35 : "end",
|
|
13 : "enter",
|
|
27 : "esc",
|
|
9 : "tab",
|
|
32 : "space"
|
|
},
|
|
|
|
stopKeyUp: function(e) {
|
|
var k = e.getKey();
|
|
|
|
if (k >= 37 && k <= 40) {
|
|
// *** bugfix - safari 2.x fires 2 keyup events on cursor keys
|
|
// *** (note: this bugfix sacrifices the "keyup" event originating from keyNav elements in Safari 2)
|
|
e.stopEvent();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Destroy this KeyNav (this is the same as calling disable).
|
|
*/
|
|
destroy: function(){
|
|
this.disable();
|
|
},
|
|
|
|
/**
|
|
* Enable this KeyNav
|
|
*/
|
|
enable: function() {
|
|
if (this.disabled) {
|
|
if (Ext.isSafari2) {
|
|
// call stopKeyUp() on "keyup" event
|
|
this.el.on('keyup', this.stopKeyUp, this);
|
|
}
|
|
|
|
this.el.on(this.isKeydown()? 'keydown' : 'keypress', this.relay, this);
|
|
this.disabled = false;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Disable this KeyNav
|
|
*/
|
|
disable: function() {
|
|
if (!this.disabled) {
|
|
if (Ext.isSafari2) {
|
|
// remove "keyup" event handler
|
|
this.el.un('keyup', this.stopKeyUp, this);
|
|
}
|
|
|
|
this.el.un(this.isKeydown()? 'keydown' : 'keypress', this.relay, this);
|
|
this.disabled = true;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Convenience function for setting disabled/enabled by boolean.
|
|
* @param {Boolean} disabled
|
|
*/
|
|
setDisabled : function(disabled){
|
|
this[disabled ? "disable" : "enable"]();
|
|
},
|
|
|
|
// private
|
|
isKeydown: function(){
|
|
return this.forceKeyDown || Ext.EventManager.useKeydown;
|
|
}
|
|
};
|
|
/**
|
|
* @class Ext.KeyMap
|
|
* Handles mapping keys to actions for an element. One key map can be used for multiple actions.
|
|
* The constructor accepts the same config object as defined by {@link #addBinding}.
|
|
* If you bind a callback function to a KeyMap, anytime the KeyMap handles an expected key
|
|
* combination it will call the function with this signature (if the match is a multi-key
|
|
* combination the callback will still be called only once): (String key, Ext.EventObject e)
|
|
* A KeyMap can also handle a string representation of keys.<br />
|
|
* Usage:
|
|
<pre><code>
|
|
// map one key by key code
|
|
var map = new Ext.KeyMap("my-element", {
|
|
key: 13, // or Ext.EventObject.ENTER
|
|
fn: myHandler,
|
|
scope: myObject
|
|
});
|
|
|
|
// map multiple keys to one action by string
|
|
var map = new Ext.KeyMap("my-element", {
|
|
key: "a\r\n\t",
|
|
fn: myHandler,
|
|
scope: myObject
|
|
});
|
|
|
|
// map multiple keys to multiple actions by strings and array of codes
|
|
var map = new Ext.KeyMap("my-element", [
|
|
{
|
|
key: [10,13],
|
|
fn: function(){ alert("Return was pressed"); }
|
|
}, {
|
|
key: "abc",
|
|
fn: function(){ alert('a, b or c was pressed'); }
|
|
}, {
|
|
key: "\t",
|
|
ctrl:true,
|
|
shift:true,
|
|
fn: function(){ alert('Control + shift + tab was pressed.'); }
|
|
}
|
|
]);
|
|
</code></pre>
|
|
* <b>Note: A KeyMap starts enabled</b>
|
|
* @constructor
|
|
* @param {Mixed} el The element to bind to
|
|
* @param {Object} config The config (see {@link #addBinding})
|
|
* @param {String} eventName (optional) The event to bind to (defaults to "keydown")
|
|
*/
|
|
Ext.KeyMap = function(el, config, eventName){
|
|
this.el = Ext.get(el);
|
|
this.eventName = eventName || "keydown";
|
|
this.bindings = [];
|
|
if(config){
|
|
this.addBinding(config);
|
|
}
|
|
this.enable();
|
|
};
|
|
|
|
Ext.KeyMap.prototype = {
|
|
/**
|
|
* True to stop the event from bubbling and prevent the default browser action if the
|
|
* key was handled by the KeyMap (defaults to false)
|
|
* @type Boolean
|
|
*/
|
|
stopEvent : false,
|
|
|
|
/**
|
|
* Add a new binding to this KeyMap. The following config object properties are supported:
|
|
* <pre>
|
|
Property Type Description
|
|
---------- --------------- ----------------------------------------------------------------------
|
|
key String/Array A single keycode or an array of keycodes to handle
|
|
shift Boolean True to handle key only when shift is pressed, False to handle the key only when shift is not pressed (defaults to undefined)
|
|
ctrl Boolean True to handle key only when ctrl is pressed, False to handle the key only when ctrl is not pressed (defaults to undefined)
|
|
alt Boolean True to handle key only when alt is pressed, False to handle the key only when alt is not pressed (defaults to undefined)
|
|
handler Function The function to call when KeyMap finds the expected key combination
|
|
fn Function Alias of handler (for backwards-compatibility)
|
|
scope Object The scope of the callback function
|
|
stopEvent Boolean True to stop the event from bubbling and prevent the default browser action if the key was handled by the KeyMap (defaults to false)
|
|
</pre>
|
|
*
|
|
* Usage:
|
|
* <pre><code>
|
|
// Create a KeyMap
|
|
var map = new Ext.KeyMap(document, {
|
|
key: Ext.EventObject.ENTER,
|
|
fn: handleKey,
|
|
scope: this
|
|
});
|
|
|
|
//Add a new binding to the existing KeyMap later
|
|
map.addBinding({
|
|
key: 'abc',
|
|
shift: true,
|
|
fn: handleKey,
|
|
scope: this
|
|
});
|
|
</code></pre>
|
|
* @param {Object/Array} config A single KeyMap config or an array of configs
|
|
*/
|
|
addBinding : function(config){
|
|
if(Ext.isArray(config)){
|
|
Ext.each(config, function(c){
|
|
this.addBinding(c);
|
|
}, this);
|
|
return;
|
|
}
|
|
var keyCode = config.key,
|
|
fn = config.fn || config.handler,
|
|
scope = config.scope;
|
|
|
|
if (config.stopEvent) {
|
|
this.stopEvent = config.stopEvent;
|
|
}
|
|
|
|
if(typeof keyCode == "string"){
|
|
var ks = [];
|
|
var keyString = keyCode.toUpperCase();
|
|
for(var j = 0, len = keyString.length; j < len; j++){
|
|
ks.push(keyString.charCodeAt(j));
|
|
}
|
|
keyCode = ks;
|
|
}
|
|
var keyArray = Ext.isArray(keyCode);
|
|
|
|
var handler = function(e){
|
|
if(this.checkModifiers(config, e)){
|
|
var k = e.getKey();
|
|
if(keyArray){
|
|
for(var i = 0, len = keyCode.length; i < len; i++){
|
|
if(keyCode[i] == k){
|
|
if(this.stopEvent){
|
|
e.stopEvent();
|
|
}
|
|
fn.call(scope || window, k, e);
|
|
return;
|
|
}
|
|
}
|
|
}else{
|
|
if(k == keyCode){
|
|
if(this.stopEvent){
|
|
e.stopEvent();
|
|
}
|
|
fn.call(scope || window, k, e);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
this.bindings.push(handler);
|
|
},
|
|
|
|
// private
|
|
checkModifiers: function(config, e){
|
|
var val, key, keys = ['shift', 'ctrl', 'alt'];
|
|
for (var i = 0, len = keys.length; i < len; ++i){
|
|
key = keys[i];
|
|
val = config[key];
|
|
if(!(val === undefined || (val === e[key + 'Key']))){
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
},
|
|
|
|
/**
|
|
* Shorthand for adding a single key listener
|
|
* @param {Number/Array/Object} key Either the numeric key code, array of key codes or an object with the
|
|
* following options:
|
|
* {key: (number or array), shift: (true/false), ctrl: (true/false), alt: (true/false)}
|
|
* @param {Function} fn The function to call
|
|
* @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the browser window.
|
|
*/
|
|
on : function(key, fn, scope){
|
|
var keyCode, shift, ctrl, alt;
|
|
if(typeof key == "object" && !Ext.isArray(key)){
|
|
keyCode = key.key;
|
|
shift = key.shift;
|
|
ctrl = key.ctrl;
|
|
alt = key.alt;
|
|
}else{
|
|
keyCode = key;
|
|
}
|
|
this.addBinding({
|
|
key: keyCode,
|
|
shift: shift,
|
|
ctrl: ctrl,
|
|
alt: alt,
|
|
fn: fn,
|
|
scope: scope
|
|
});
|
|
},
|
|
|
|
// private
|
|
handleKeyDown : function(e){
|
|
if(this.enabled){ //just in case
|
|
var b = this.bindings;
|
|
for(var i = 0, len = b.length; i < len; i++){
|
|
b[i].call(this, e);
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Returns true if this KeyMap is enabled
|
|
* @return {Boolean}
|
|
*/
|
|
isEnabled : function(){
|
|
return this.enabled;
|
|
},
|
|
|
|
/**
|
|
* Enables this KeyMap
|
|
*/
|
|
enable: function(){
|
|
if(!this.enabled){
|
|
this.el.on(this.eventName, this.handleKeyDown, this);
|
|
this.enabled = true;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Disable this KeyMap
|
|
*/
|
|
disable: function(){
|
|
if(this.enabled){
|
|
this.el.removeListener(this.eventName, this.handleKeyDown, this);
|
|
this.enabled = false;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Convenience function for setting disabled/enabled by boolean.
|
|
* @param {Boolean} disabled
|
|
*/
|
|
setDisabled : function(disabled){
|
|
this[disabled ? "disable" : "enable"]();
|
|
}
|
|
};/**
|
|
* @class Ext.util.TextMetrics
|
|
* Provides precise pixel measurements for blocks of text so that you can determine exactly how high and
|
|
* wide, in pixels, a given block of text will be. Note that when measuring text, it should be plain text and
|
|
* should not contain any HTML, otherwise it may not be measured correctly.
|
|
* @singleton
|
|
*/
|
|
Ext.util.TextMetrics = function(){
|
|
var shared;
|
|
return {
|
|
/**
|
|
* Measures the size of the specified text
|
|
* @param {String/HTMLElement} el The element, dom node or id from which to copy existing CSS styles
|
|
* that can affect the size of the rendered text
|
|
* @param {String} text The text to measure
|
|
* @param {Number} fixedWidth (optional) If the text will be multiline, you have to set a fixed width
|
|
* in order to accurately measure the text height
|
|
* @return {Object} An object containing the text's size {width: (width), height: (height)}
|
|
*/
|
|
measure : function(el, text, fixedWidth){
|
|
if(!shared){
|
|
shared = Ext.util.TextMetrics.Instance(el, fixedWidth);
|
|
}
|
|
shared.bind(el);
|
|
shared.setFixedWidth(fixedWidth || 'auto');
|
|
return shared.getSize(text);
|
|
},
|
|
|
|
/**
|
|
* Return a unique TextMetrics instance that can be bound directly to an element and reused. This reduces
|
|
* the overhead of multiple calls to initialize the style properties on each measurement.
|
|
* @param {String/HTMLElement} el The element, dom node or id that the instance will be bound to
|
|
* @param {Number} fixedWidth (optional) If the text will be multiline, you have to set a fixed width
|
|
* in order to accurately measure the text height
|
|
* @return {Ext.util.TextMetrics.Instance} instance The new instance
|
|
*/
|
|
createInstance : function(el, fixedWidth){
|
|
return Ext.util.TextMetrics.Instance(el, fixedWidth);
|
|
}
|
|
};
|
|
}();
|
|
|
|
Ext.util.TextMetrics.Instance = function(bindTo, fixedWidth){
|
|
var ml = new Ext.Element(document.createElement('div'));
|
|
document.body.appendChild(ml.dom);
|
|
ml.position('absolute');
|
|
ml.setLeftTop(-1000, -1000);
|
|
ml.hide();
|
|
|
|
if(fixedWidth){
|
|
ml.setWidth(fixedWidth);
|
|
}
|
|
|
|
var instance = {
|
|
/**
|
|
* <p><b>Only available on the instance returned from {@link #createInstance}, <u>not</u> on the singleton.</b></p>
|
|
* Returns the size of the specified text based on the internal element's style and width properties
|
|
* @param {String} text The text to measure
|
|
* @return {Object} An object containing the text's size {width: (width), height: (height)}
|
|
*/
|
|
getSize : function(text){
|
|
ml.update(text);
|
|
var s = ml.getSize();
|
|
ml.update('');
|
|
return s;
|
|
},
|
|
|
|
/**
|
|
* <p><b>Only available on the instance returned from {@link #createInstance}, <u>not</u> on the singleton.</b></p>
|
|
* Binds this TextMetrics instance to an element from which to copy existing CSS styles
|
|
* that can affect the size of the rendered text
|
|
* @param {String/HTMLElement} el The element, dom node or id
|
|
*/
|
|
bind : function(el){
|
|
ml.setStyle(
|
|
Ext.fly(el).getStyles('font-size','font-style', 'font-weight', 'font-family','line-height', 'text-transform', 'letter-spacing')
|
|
);
|
|
},
|
|
|
|
/**
|
|
* <p><b>Only available on the instance returned from {@link #createInstance}, <u>not</u> on the singleton.</b></p>
|
|
* Sets a fixed width on the internal measurement element. If the text will be multiline, you have
|
|
* to set a fixed width in order to accurately measure the text height.
|
|
* @param {Number} width The width to set on the element
|
|
*/
|
|
setFixedWidth : function(width){
|
|
ml.setWidth(width);
|
|
},
|
|
|
|
/**
|
|
* <p><b>Only available on the instance returned from {@link #createInstance}, <u>not</u> on the singleton.</b></p>
|
|
* Returns the measured width of the specified text
|
|
* @param {String} text The text to measure
|
|
* @return {Number} width The width in pixels
|
|
*/
|
|
getWidth : function(text){
|
|
ml.dom.style.width = 'auto';
|
|
return this.getSize(text).width;
|
|
},
|
|
|
|
/**
|
|
* <p><b>Only available on the instance returned from {@link #createInstance}, <u>not</u> on the singleton.</b></p>
|
|
* Returns the measured height of the specified text. For multiline text, be sure to call
|
|
* {@link #setFixedWidth} if necessary.
|
|
* @param {String} text The text to measure
|
|
* @return {Number} height The height in pixels
|
|
*/
|
|
getHeight : function(text){
|
|
return this.getSize(text).height;
|
|
}
|
|
};
|
|
|
|
instance.bind(bindTo);
|
|
|
|
return instance;
|
|
};
|
|
|
|
Ext.Element.addMethods({
|
|
/**
|
|
* Returns the width in pixels of the passed text, or the width of the text in this Element.
|
|
* @param {String} text The text to measure. Defaults to the innerHTML of the element.
|
|
* @param {Number} min (Optional) The minumum value to return.
|
|
* @param {Number} max (Optional) The maximum value to return.
|
|
* @return {Number} The text width in pixels.
|
|
* @member Ext.Element getTextWidth
|
|
*/
|
|
getTextWidth : function(text, min, max){
|
|
return (Ext.util.TextMetrics.measure(this.dom, Ext.value(text, this.dom.innerHTML, true)).width).constrain(min || 0, max || 1000000);
|
|
}
|
|
});
|
|
/**
|
|
* @class Ext.util.Cookies
|
|
* Utility class for managing and interacting with cookies.
|
|
* @singleton
|
|
*/
|
|
Ext.util.Cookies = {
|
|
/**
|
|
* Create a cookie with the specified name and value. Additional settings
|
|
* for the cookie may be optionally specified (for example: expiration,
|
|
* access restriction, SSL).
|
|
* @param {String} name The name of the cookie to set.
|
|
* @param {Mixed} value The value to set for the cookie.
|
|
* @param {Object} expires (Optional) Specify an expiration date the
|
|
* cookie is to persist until. Note that the specified Date object will
|
|
* be converted to Greenwich Mean Time (GMT).
|
|
* @param {String} path (Optional) Setting a path on the cookie restricts
|
|
* access to pages that match that path. Defaults to all pages (<tt>'/'</tt>).
|
|
* @param {String} domain (Optional) Setting a domain restricts access to
|
|
* pages on a given domain (typically used to allow cookie access across
|
|
* subdomains). For example, "extjs.com" will create a cookie that can be
|
|
* accessed from any subdomain of extjs.com, including www.extjs.com,
|
|
* support.extjs.com, etc.
|
|
* @param {Boolean} secure (Optional) Specify true to indicate that the cookie
|
|
* should only be accessible via SSL on a page using the HTTPS protocol.
|
|
* Defaults to <tt>false</tt>. Note that this will only work if the page
|
|
* calling this code uses the HTTPS protocol, otherwise the cookie will be
|
|
* created with default options.
|
|
*/
|
|
set : function(name, value){
|
|
var argv = arguments;
|
|
var argc = arguments.length;
|
|
var expires = (argc > 2) ? argv[2] : null;
|
|
var path = (argc > 3) ? argv[3] : '/';
|
|
var domain = (argc > 4) ? argv[4] : null;
|
|
var secure = (argc > 5) ? argv[5] : false;
|
|
document.cookie = name + "=" + escape(value) + ((expires === null) ? "" : ("; expires=" + expires.toGMTString())) + ((path === null) ? "" : ("; path=" + path)) + ((domain === null) ? "" : ("; domain=" + domain)) + ((secure === true) ? "; secure" : "");
|
|
},
|
|
|
|
/**
|
|
* Retrieves cookies that are accessible by the current page. If a cookie
|
|
* does not exist, <code>get()</code> returns <tt>null</tt>. The following
|
|
* example retrieves the cookie called "valid" and stores the String value
|
|
* in the variable <tt>validStatus</tt>.
|
|
* <pre><code>
|
|
* var validStatus = Ext.util.Cookies.get("valid");
|
|
* </code></pre>
|
|
* @param {String} name The name of the cookie to get
|
|
* @return {Mixed} Returns the cookie value for the specified name;
|
|
* null if the cookie name does not exist.
|
|
*/
|
|
get : function(name){
|
|
var arg = name + "=";
|
|
var alen = arg.length;
|
|
var clen = document.cookie.length;
|
|
var i = 0;
|
|
var j = 0;
|
|
while(i < clen){
|
|
j = i + alen;
|
|
if(document.cookie.substring(i, j) == arg){
|
|
return Ext.util.Cookies.getCookieVal(j);
|
|
}
|
|
i = document.cookie.indexOf(" ", i) + 1;
|
|
if(i === 0){
|
|
break;
|
|
}
|
|
}
|
|
return null;
|
|
},
|
|
|
|
/**
|
|
* Removes a cookie with the provided name from the browser
|
|
* if found by setting its expiration date to sometime in the past.
|
|
* @param {String} name The name of the cookie to remove
|
|
*/
|
|
clear : function(name){
|
|
if(Ext.util.Cookies.get(name)){
|
|
document.cookie = name + "=" + "; expires=Thu, 01-Jan-70 00:00:01 GMT";
|
|
}
|
|
},
|
|
/**
|
|
* @private
|
|
*/
|
|
getCookieVal : function(offset){
|
|
var endstr = document.cookie.indexOf(";", offset);
|
|
if(endstr == -1){
|
|
endstr = document.cookie.length;
|
|
}
|
|
return unescape(document.cookie.substring(offset, endstr));
|
|
}
|
|
};/**
|
|
* Framework-wide error-handler. Developers can override this method to provide
|
|
* custom exception-handling. Framework errors will often extend from the base
|
|
* Ext.Error class.
|
|
* @param {Object/Error} e The thrown exception object.
|
|
* @member Ext
|
|
*/
|
|
Ext.handleError = function(e) {
|
|
throw e;
|
|
};
|
|
|
|
/**
|
|
* @class Ext.Error
|
|
* @extends Error
|
|
* <p>A base error class. Future implementations are intended to provide more
|
|
* robust error handling throughout the framework (<b>in the debug build only</b>)
|
|
* to check for common errors and problems. The messages issued by this class
|
|
* will aid error checking. Error checks will be automatically removed in the
|
|
* production build so that performance is not negatively impacted.</p>
|
|
* <p>Some sample messages currently implemented:</p><pre>
|
|
"DataProxy attempted to execute an API-action but found an undefined
|
|
url / function. Please review your Proxy url/api-configuration."
|
|
* </pre><pre>
|
|
"Could not locate your "root" property in your server response.
|
|
Please review your JsonReader config to ensure the config-property
|
|
"root" matches the property your server-response. See the JsonReader
|
|
docs for additional assistance."
|
|
* </pre>
|
|
* <p>An example of the code used for generating error messages:</p><pre><code>
|
|
try {
|
|
generateError({
|
|
foo: 'bar'
|
|
});
|
|
}
|
|
catch (e) {
|
|
console.error(e);
|
|
}
|
|
function generateError(data) {
|
|
throw new Ext.Error('foo-error', data);
|
|
}
|
|
* </code></pre>
|
|
* @param {String} message
|
|
*/
|
|
Ext.Error = function(message) {
|
|
// Try to read the message from Ext.Error.lang
|
|
this.message = (this.lang[message]) ? this.lang[message] : message;
|
|
};
|
|
|
|
Ext.Error.prototype = new Error();
|
|
Ext.apply(Ext.Error.prototype, {
|
|
// protected. Extensions place their error-strings here.
|
|
lang: {},
|
|
|
|
name: 'Ext.Error',
|
|
/**
|
|
* getName
|
|
* @return {String}
|
|
*/
|
|
getName : function() {
|
|
return this.name;
|
|
},
|
|
/**
|
|
* getMessage
|
|
* @return {String}
|
|
*/
|
|
getMessage : function() {
|
|
return this.message;
|
|
},
|
|
/**
|
|
* toJson
|
|
* @return {String}
|
|
*/
|
|
toJson : function() {
|
|
return Ext.encode(this);
|
|
}
|
|
});
|