/*!
- * Ext JS Library 3.2.1
- * Copyright(c) 2006-2010 Ext JS, Inc.
- * licensing@extjs.com
- * http://www.extjs.com/license
+ * Ext JS Library 3.3.1
+ * Copyright(c) 2006-2010 Sencha Inc.
+ * licensing@sencha.com
+ * http://www.sencha.com/license
*/
+(function(){
+
+var EXTUTIL = Ext.util,
+ EACH = Ext.each,
+ TRUE = true,
+ FALSE = false;
/**
- * @class Ext.DomHelper
- * <p>The DomHelper class provides a layer of abstraction from DOM and transparently supports creating
- * elements via DOM or using HTML fragments. It also has the ability to create HTML fragment templates
- * from your DOM building code.</p>
- *
- * <p><b><u>DomHelper element specification object</u></b></p>
- * <p>A specification object is used when creating elements. Attributes of this object
- * are assumed to be element attributes, except for 4 special attributes:
- * <div class="mdetail-params"><ul>
- * <li><b><tt>tag</tt></b> : <div class="sub-desc">The tag name of the element</div></li>
- * <li><b><tt>children</tt></b> : or <tt>cn</tt><div class="sub-desc">An array of the
- * same kind of element definition objects to be created and appended. These can be nested
- * as deep as you want.</div></li>
- * <li><b><tt>cls</tt></b> : <div class="sub-desc">The class attribute of the element.
- * This will end up being either the "class" attribute on a HTML fragment or className
- * for a DOM node, depending on whether DomHelper is using fragments or DOM.</div></li>
- * <li><b><tt>html</tt></b> : <div class="sub-desc">The innerHTML for the element</div></li>
- * </ul></div></p>
- *
- * <p><b><u>Insertion methods</u></b></p>
- * <p>Commonly used insertion methods:
- * <div class="mdetail-params"><ul>
- * <li><b><tt>{@link #append}</tt></b> : <div class="sub-desc"></div></li>
- * <li><b><tt>{@link #insertBefore}</tt></b> : <div class="sub-desc"></div></li>
- * <li><b><tt>{@link #insertAfter}</tt></b> : <div class="sub-desc"></div></li>
- * <li><b><tt>{@link #overwrite}</tt></b> : <div class="sub-desc"></div></li>
- * <li><b><tt>{@link #createTemplate}</tt></b> : <div class="sub-desc"></div></li>
- * <li><b><tt>{@link #insertHtml}</tt></b> : <div class="sub-desc"></div></li>
- * </ul></div></p>
- *
- * <p><b><u>Example</u></b></p>
- * <p>This is an example, where an unordered list with 3 children items is appended to an existing
- * element with id <tt>'my-div'</tt>:<br>
- <pre><code>
-var dh = Ext.DomHelper; // create shorthand alias
-// specification object
-var spec = {
- id: 'my-ul',
- tag: 'ul',
- cls: 'my-list',
- // append children after creating
- children: [ // may also specify 'cn' instead of 'children'
- {tag: 'li', id: 'item0', html: 'List Item 0'},
- {tag: 'li', id: 'item1', html: 'List Item 1'},
- {tag: 'li', id: 'item2', html: 'List Item 2'}
- ]
-};
-var list = dh.append(
- 'my-div', // the context element 'my-div' can either be the id or the actual node
- spec // the specification object
-);
- </code></pre></p>
- * <p>Element creation specification parameters in this class may also be passed as an Array of
- * specification objects. This can be used to insert multiple sibling nodes into an existing
- * container very efficiently. For example, to add more list items to the example above:<pre><code>
-dh.append('my-ul', [
- {tag: 'li', id: 'item3', html: 'List Item 3'},
- {tag: 'li', id: 'item4', html: 'List Item 4'}
-]);
- * </code></pre></p>
- *
- * <p><b><u>Templating</u></b></p>
- * <p>The real power is in the built-in templating. Instead of creating or appending any elements,
- * <tt>{@link #createTemplate}</tt> returns a Template object which can be used over and over to
- * insert new elements. Revisiting the example above, we could utilize templating this time:
+ * @class Ext.util.Observable
+ * Base class that provides a common interface for publishing events. Subclasses are expected to
+ * to have a property "events" with all the events defined, and, optionally, a property "listeners"
+ * with configured listeners defined.<br>
+ * For example:
* <pre><code>
-// create the node
-var list = dh.append('my-div', {tag: 'ul', cls: 'my-list'});
-// get template
-var tpl = dh.createTemplate({tag: 'li', id: 'item{0}', html: 'List Item {0}'});
-
-for(var i = 0; i < 5, i++){
- tpl.append(list, [i]); // use template to append to the actual node
-}
- * </code></pre></p>
- * <p>An example using a template:<pre><code>
-var html = '<a id="{0}" href="{1}" class="nav">{2}</a>';
+Employee = Ext.extend(Ext.util.Observable, {
+ constructor: function(config){
+ this.name = config.name;
+ this.addEvents({
+ "fired" : true,
+ "quit" : true
+ });
-var tpl = new Ext.DomHelper.createTemplate(html);
-tpl.append('blog-roll', ['link1', 'http://www.jackslocum.com/', "Jack's Site"]);
-tpl.append('blog-roll', ['link2', 'http://www.dustindiaz.com/', "Dustin's Site"]);
- * </code></pre></p>
- *
- * <p>The same example using named parameters:<pre><code>
-var html = '<a id="{id}" href="{url}" class="nav">{text}</a>';
+ // Copy configured listeners into *this* object so that the base class's
+ // constructor will add them.
+ this.listeners = config.listeners;
-var tpl = new Ext.DomHelper.createTemplate(html);
-tpl.append('blog-roll', {
- id: 'link1',
- url: 'http://www.jackslocum.com/',
- text: "Jack's Site"
+ // Call our superclass constructor to complete construction process.
+ Employee.superclass.constructor.call(this, config)
+ }
});
-tpl.append('blog-roll', {
- id: 'link2',
- url: 'http://www.dustindiaz.com/',
- text: "Dustin's Site"
+</code></pre>
+ * This could then be used like this:<pre><code>
+var newEmployee = new Employee({
+ name: employeeName,
+ listeners: {
+ quit: function() {
+ // By default, "this" will be the object that fired the event.
+ alert(this.name + " has quit!");
+ }
+ }
});
- * </code></pre></p>
- *
- * <p><b><u>Compiling Templates</u></b></p>
- * <p>Templates are applied using regular expressions. The performance is great, but if
- * you are adding a bunch of DOM elements using the same template, you can increase
- * performance even further by {@link Ext.Template#compile "compiling"} the template.
- * The way "{@link Ext.Template#compile compile()}" works is the template is parsed and
- * broken up at the different variable points and a dynamic function is created and eval'ed.
- * The generated function performs string concatenation of these parts and the passed
- * variables instead of using regular expressions.
- * <pre><code>
-var html = '<a id="{id}" href="{url}" class="nav">{text}</a>';
+</code></pre>
+ */
+EXTUTIL.Observable = function(){
+ /**
+ * @cfg {Object} listeners (optional) <p>A config object containing one or more event handlers to be added to this
+ * object during initialization. This should be a valid listeners config object as specified in the
+ * {@link #addListener} example for attaching multiple handlers at once.</p>
+ * <br><p><b><u>DOM events from ExtJs {@link Ext.Component Components}</u></b></p>
+ * <br><p>While <i>some</i> ExtJs Component classes export selected DOM events (e.g. "click", "mouseover" etc), this
+ * is usually only done when extra value can be added. For example the {@link Ext.DataView DataView}'s
+ * <b><code>{@link Ext.DataView#click click}</code></b> event passing the node clicked on. To access DOM
+ * events directly from a Component's HTMLElement, listeners must be added to the <i>{@link Ext.Component#getEl Element}</i> after the Component
+ * has been rendered. A plugin can simplify this step:<pre><code>
+// Plugin is configured with a listeners config object.
+// The Component is appended to the argument list of all handler functions.
+Ext.DomObserver = Ext.extend(Object, {
+ constructor: function(config) {
+ this.listeners = config.listeners ? config.listeners : config;
+ },
-var tpl = new Ext.DomHelper.createTemplate(html);
-tpl.compile();
+ // Component passes itself into plugin's init method
+ init: function(c) {
+ var p, l = this.listeners;
+ for (p in l) {
+ if (Ext.isFunction(l[p])) {
+ l[p] = this.createHandler(l[p], c);
+ } else {
+ l[p].fn = this.createHandler(l[p].fn, c);
+ }
+ }
-//... use template like normal
- * </code></pre></p>
- *
- * <p><b><u>Performance Boost</u></b></p>
- * <p>DomHelper will transparently create HTML fragments when it can. Using HTML fragments instead
- * of DOM can significantly boost performance.</p>
- * <p>Element creation specification parameters may also be strings. If {@link #useDom} is <tt>false</tt>,
- * then the string is used as innerHTML. If {@link #useDom} is <tt>true</tt>, a string specification
- * results in the creation of a text node. Usage:</p>
- * <pre><code>
-Ext.DomHelper.useDom = true; // force it to use DOM; reduces performance
- * </code></pre>
- * @singleton
- */
-Ext.DomHelper = function(){
- var tempTableEl = null,
- emptyTags = /^(?:br|frame|hr|img|input|link|meta|range|spacer|wbr|area|param|col)$/i,
- tableRe = /^table|tbody|tr|td$/i,
- confRe = /tag|children|cn|html$/i,
- tableElRe = /td|tr|tbody/i,
- cssRe = /([a-z0-9-]+)\s*:\s*([^;\s]+(?:\s*[^;\s]+)*);?/gi,
- endRe = /end/i,
- pub,
- // kill repeat to save bytes
- afterbegin = 'afterbegin',
- afterend = 'afterend',
- beforebegin = 'beforebegin',
- beforeend = 'beforeend',
- ts = '<table>',
- te = '</table>',
- tbs = ts+'<tbody>',
- tbe = '</tbody>'+te,
- trs = tbs + '<tr>',
- tre = '</tr>'+tbe;
+ // Add the listeners to the Element immediately following the render call
+ c.render = c.render.{@link Function#createSequence createSequence}(function() {
+ var e = c.getEl();
+ if (e) {
+ e.on(l);
+ }
+ });
+ },
- // private
- function doInsert(el, o, returnElement, pos, sibling, append){
- var newNode = pub.insertHtml(pos, Ext.getDom(el), createHtml(o));
- return returnElement ? Ext.get(newNode, true) : newNode;
+ createHandler: function(fn, c) {
+ return function(e) {
+ fn.call(this, e, c);
+ };
}
+});
- // build as innerHTML where available
- function createHtml(o){
- var b = '',
- attr,
- val,
- key,
- keyVal,
- cn;
+var combo = new Ext.form.ComboBox({
- if(typeof o == "string"){
- b = o;
- } else if (Ext.isArray(o)) {
- for (var i=0; i < o.length; i++) {
- if(o[i]) {
- b += createHtml(o[i]);
+ // Collapse combo when its element is clicked on
+ plugins: [ new Ext.DomObserver({
+ click: function(evt, comp) {
+ comp.collapse();
+ }
+ })],
+ store: myStore,
+ typeAhead: true,
+ mode: 'local',
+ triggerAction: 'all'
+});
+ * </code></pre></p>
+ */
+ var me = this, e = me.events;
+ if(me.listeners){
+ me.on(me.listeners);
+ delete me.listeners;
+ }
+ me.events = e || {};
+};
+
+EXTUTIL.Observable.prototype = {
+ // private
+ filterOptRe : /^(?:scope|delay|buffer|single)$/,
+
+ /**
+ * <p>Fires the specified event with the passed parameters (minus the event name).</p>
+ * <p>An event may be set to bubble up an Observable parent hierarchy (See {@link Ext.Component#getBubbleTarget})
+ * by calling {@link #enableBubble}.</p>
+ * @param {String} eventName The name of the event to fire.
+ * @param {Object...} args Variable number of parameters are passed to handlers.
+ * @return {Boolean} returns false if any of the handlers return false otherwise it returns true.
+ */
+ fireEvent : function(){
+ var a = Array.prototype.slice.call(arguments, 0),
+ ename = a[0].toLowerCase(),
+ me = this,
+ ret = TRUE,
+ ce = me.events[ename],
+ cc,
+ q,
+ c;
+ if (me.eventsSuspended === TRUE) {
+ if (q = me.eventQueue) {
+ q.push(a);
+ }
+ }
+ else if(typeof ce == 'object') {
+ if (ce.bubble){
+ if(ce.fire.apply(ce, a.slice(1)) === FALSE) {
+ return FALSE;
}
- };
- } else {
- b += '<' + (o.tag = o.tag || 'div');
- for (attr in o) {
- val = o[attr];
- if(!confRe.test(attr)){
- if (typeof val == "object") {
- b += ' ' + attr + '="';
- for (key in val) {
- b += key + ':' + val[key] + ';';
- };
- b += '"';
- }else{
- b += ' ' + ({cls : 'class', htmlFor : 'for'}[attr] || attr) + '="' + val + '"';
+ c = me.getBubbleTarget && me.getBubbleTarget();
+ if(c && c.enableBubble) {
+ cc = c.events[ename];
+ if(!cc || typeof cc != 'object' || !cc.bubble) {
+ c.enableBubble(ename);
}
+ return c.fireEvent.apply(c, a);
}
- };
- // Now either just close the tag or try to add children and close the tag.
- if (emptyTags.test(o.tag)) {
- b += '/>';
- } else {
- b += '>';
- if ((cn = o.children || o.cn)) {
- b += createHtml(cn);
- } else if(o.html){
- b += o.html;
- }
- b += '</' + o.tag + '>';
+ }
+ else {
+ a.shift();
+ ret = ce.fire.apply(ce, a);
}
}
- return b;
- }
+ return ret;
+ },
- function ieTable(depth, s, h, e){
- tempTableEl.innerHTML = [s, h, e].join('');
- var i = -1,
- el = tempTableEl,
- ns;
- while(++i < depth){
- el = el.firstChild;
- }
-// If the result is multiple siblings, then encapsulate them into one fragment.
- if(ns = el.nextSibling){
- var df = document.createDocumentFragment();
- while(el){
- ns = el.nextSibling;
- df.appendChild(el);
- el = ns;
+ /**
+ * Appends an event handler to this object.
+ * @param {String} eventName The name of the event to listen for.
+ * @param {Function} handler The method the event invokes.
+ * @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the handler function is executed.
+ * <b>If omitted, defaults to the object which fired the event.</b>
+ * @param {Object} options (optional) An object containing handler configuration.
+ * properties. This may contain any of the following properties:<ul>
+ * <li><b>scope</b> : Object<div class="sub-desc">The scope (<code><b>this</b></code> reference) in which the handler function is executed.
+ * <b>If omitted, defaults to the object which fired the event.</b></div></li>
+ * <li><b>delay</b> : Number<div class="sub-desc">The number of milliseconds to delay the invocation of the handler after the event fires.</div></li>
+ * <li><b>single</b> : Boolean<div class="sub-desc">True to add a handler to handle just the next firing of the event, and then remove itself.</div></li>
+ * <li><b>buffer</b> : Number<div class="sub-desc">Causes the handler to be scheduled to run in an {@link Ext.util.DelayedTask} delayed
+ * by the specified number of milliseconds. If the event fires again within that time, the original
+ * handler is <em>not</em> invoked, but the new handler is scheduled in its place.</div></li>
+ * <li><b>target</b> : Observable<div class="sub-desc">Only call the handler if the event was fired on the target Observable, <i>not</i>
+ * if the event was bubbled up from a child Observable.</div></li>
+ * </ul><br>
+ * <p>
+ * <b>Combining Options</b><br>
+ * Using the options argument, it is possible to combine different types of listeners:<br>
+ * <br>
+ * A delayed, one-time listener.
+ * <pre><code>
+myDataView.on('click', this.onClick, this, {
+single: true,
+delay: 100
+});</code></pre>
+ * <p>
+ * <b>Attaching multiple handlers in 1 call</b><br>
+ * The method also allows for a single argument to be passed which is a config object containing properties
+ * which specify multiple handlers.
+ * <p>
+ * <pre><code>
+myGridPanel.on({
+'click' : {
+ fn: this.onClick,
+ scope: this,
+ delay: 100
+},
+'mouseover' : {
+ fn: this.onMouseOver,
+ scope: this
+},
+'mouseout' : {
+ fn: this.onMouseOut,
+ scope: this
+}
+});</code></pre>
+ * <p>
+ * Or a shorthand syntax:<br>
+ * <pre><code>
+myGridPanel.on({
+'click' : this.onClick,
+'mouseover' : this.onMouseOver,
+'mouseout' : this.onMouseOut,
+ scope: this
+});</code></pre>
+ */
+ addListener : function(eventName, fn, scope, o){
+ var me = this,
+ e,
+ oe,
+ ce;
+
+ if (typeof eventName == 'object') {
+ o = eventName;
+ for (e in o) {
+ oe = o[e];
+ if (!me.filterOptRe.test(e)) {
+ me.addListener(e, oe.fn || oe, oe.scope || o.scope, oe.fn ? oe : o);
+ }
}
- el = df;
+ } else {
+ eventName = eventName.toLowerCase();
+ ce = me.events[eventName] || TRUE;
+ if (typeof ce == 'boolean') {
+ me.events[eventName] = ce = new EXTUTIL.Event(me, eventName);
+ }
+ ce.addListener(fn, scope, typeof o == 'object' ? o : {});
}
- return el;
- }
+ },
/**
- * @ignore
- * Nasty code for IE's broken table implementation
+ * Removes an event handler.
+ * @param {String} eventName The type of event the handler was associated with.
+ * @param {Function} handler The handler to remove. <b>This must be a reference to the function passed into the {@link #addListener} call.</b>
+ * @param {Object} scope (optional) The scope originally specified for the handler.
*/
- function insertIntoTable(tag, where, el, html) {
- var node,
- before;
-
- tempTableEl = tempTableEl || document.createElement('div');
-
- if(tag == 'td' && (where == afterbegin || where == beforeend) ||
- !tableElRe.test(tag) && (where == beforebegin || where == afterend)) {
- return;
+ removeListener : function(eventName, fn, scope){
+ var ce = this.events[eventName.toLowerCase()];
+ if (typeof ce == 'object') {
+ ce.removeListener(fn, scope);
}
- before = where == beforebegin ? el :
- where == afterend ? el.nextSibling :
- where == afterbegin ? el.firstChild : null;
+ },
- if (where == beforebegin || where == afterend) {
- el = el.parentNode;
+ /**
+ * Removes all listeners for this object
+ */
+ purgeListeners : function(){
+ var events = this.events,
+ evt,
+ key;
+ for(key in events){
+ evt = events[key];
+ if(typeof evt == 'object'){
+ evt.clearListeners();
+ }
}
+ },
- if (tag == 'td' || (tag == 'tr' && (where == beforeend || where == afterbegin))) {
- node = ieTable(4, trs, html, tre);
- } else if ((tag == 'tbody' && (where == beforeend || where == afterbegin)) ||
- (tag == 'tr' && (where == beforebegin || where == afterend))) {
- node = ieTable(3, tbs, html, tbe);
+ /**
+ * Adds the specified events to the list of events which this Observable may fire.
+ * @param {Object|String} o Either an object with event names as properties with a value of <code>true</code>
+ * or the first event name string if multiple event names are being passed as separate parameters.
+ * @param {string} Optional. Event name if multiple event names are being passed as separate parameters.
+ * Usage:<pre><code>
+this.addEvents('storeloaded', 'storecleared');
+</code></pre>
+ */
+ addEvents : function(o){
+ var me = this;
+ me.events = me.events || {};
+ if (typeof o == 'string') {
+ var a = arguments,
+ i = a.length;
+ while(i--) {
+ me.events[a[i]] = me.events[a[i]] || TRUE;
+ }
} else {
- node = ieTable(2, ts, html, te);
+ Ext.applyIf(me.events, o);
}
- el.insertBefore(node, before);
- return node;
- }
-
+ },
- pub = {
- /**
- * Returns the markup for the passed Element(s) config.
- * @param {Object} o The DOM object spec (and children)
- * @return {String}
- */
- markup : function(o){
- return createHtml(o);
- },
+ /**
+ * Checks to see if this object has any listeners for a specified event
+ * @param {String} eventName The name of the event to check for
+ * @return {Boolean} True if the event is being listened for, else false
+ */
+ hasListener : function(eventName){
+ var e = this.events[eventName.toLowerCase()];
+ return typeof e == 'object' && e.listeners.length > 0;
+ },
- /**
- * Applies a style specification to an element.
- * @param {String/HTMLElement} el The element to apply styles to
- * @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.
+ /**
+ * Suspend the firing of all events. (see {@link #resumeEvents})
+ * @param {Boolean} queueSuspended Pass as true to queue up suspended events to be fired
+ * after the {@link #resumeEvents} call instead of discarding all suspended events;
+ */
+ suspendEvents : function(queueSuspended){
+ this.eventsSuspended = TRUE;
+ if(queueSuspended && !this.eventQueue){
+ this.eventQueue = [];
+ }
+ },
+
+ /**
+ * Resume firing events. (see {@link #suspendEvents})
+ * If events were suspended using the <tt><b>queueSuspended</b></tt> parameter, then all
+ * events fired during event suspension will be sent to any listeners now.
+ */
+ resumeEvents : function(){
+ var me = this,
+ queued = me.eventQueue || [];
+ me.eventsSuspended = FALSE;
+ delete me.eventQueue;
+ EACH(queued, function(e) {
+ me.fireEvent.apply(me, e);
+ });
+ }
+};
+
+var OBSERVABLE = EXTUTIL.Observable.prototype;
+/**
+ * Appends an event handler to this object (shorthand for {@link #addListener}.)
+ * @param {String} eventName The type of event to listen for
+ * @param {Function} handler The method the event invokes
+ * @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the handler function is executed.
+ * <b>If omitted, defaults to the object which fired the event.</b>
+ * @param {Object} options (optional) An object containing handler configuration.
+ * @method
+ */
+OBSERVABLE.on = OBSERVABLE.addListener;
+/**
+ * Removes an event handler (shorthand for {@link #removeListener}.)
+ * @param {String} eventName The type of event the handler was associated with.
+ * @param {Function} handler The handler to remove. <b>This must be a reference to the function passed into the {@link #addListener} call.</b>
+ * @param {Object} scope (optional) The scope originally specified for the handler.
+ * @method
+ */
+OBSERVABLE.un = OBSERVABLE.removeListener;
+
+/**
+ * Removes <b>all</b> added captures from the Observable.
+ * @param {Observable} o The Observable to release
+ * @static
+ */
+EXTUTIL.Observable.releaseCapture = function(o){
+ o.fireEvent = OBSERVABLE.fireEvent;
+};
+
+function createTargeted(h, o, scope){
+ return function(){
+ if(o.target == arguments[0]){
+ h.apply(scope, Array.prototype.slice.call(arguments, 0));
+ }
+ };
+};
+
+function createBuffered(h, o, l, scope){
+ l.task = new EXTUTIL.DelayedTask();
+ return function(){
+ l.task.delay(o.buffer, h, scope, Array.prototype.slice.call(arguments, 0));
+ };
+};
+
+function createSingle(h, e, fn, scope){
+ return function(){
+ e.removeListener(fn, scope);
+ return h.apply(scope, arguments);
+ };
+};
+
+function createDelayed(h, o, l, scope){
+ return function(){
+ var task = new EXTUTIL.DelayedTask(),
+ args = Array.prototype.slice.call(arguments, 0);
+ if(!l.tasks) {
+ l.tasks = [];
+ }
+ l.tasks.push(task);
+ task.delay(o.delay || 10, function(){
+ l.tasks.remove(task);
+ h.apply(scope, args);
+ }, scope);
+ };
+};
+
+EXTUTIL.Event = function(obj, name){
+ this.name = name;
+ this.obj = obj;
+ this.listeners = [];
+};
+
+EXTUTIL.Event.prototype = {
+ addListener : function(fn, scope, options){
+ var me = this,
+ l;
+ scope = scope || me.obj;
+ if(!me.isListening(fn, scope)){
+ l = me.createListener(fn, scope, options);
+ if(me.firing){ // if we are currently firing this event, don't disturb the listener loop
+ me.listeners = me.listeners.slice(0);
+ }
+ me.listeners.push(l);
+ }
+ },
+
+ createListener: function(fn, scope, o){
+ o = o || {};
+ scope = scope || this.obj;
+ var l = {
+ fn: fn,
+ scope: scope,
+ options: o
+ }, h = fn;
+ if(o.target){
+ h = createTargeted(h, o, scope);
+ }
+ if(o.delay){
+ h = createDelayed(h, o, l, scope);
+ }
+ if(o.single){
+ h = createSingle(h, this, fn, scope);
+ }
+ if(o.buffer){
+ h = createBuffered(h, o, l, scope);
+ }
+ l.fireFn = h;
+ return l;
+ },
+
+ findListener : function(fn, scope){
+ var list = this.listeners,
+ i = list.length,
+ l;
+
+ scope = scope || this.obj;
+ while(i--){
+ l = list[i];
+ if(l){
+ if(l.fn == fn && l.scope == scope){
+ return i;
+ }
+ }
+ }
+ return -1;
+ },
+
+ isListening : function(fn, scope){
+ return this.findListener(fn, scope) != -1;
+ },
+
+ removeListener : function(fn, scope){
+ var index,
+ l,
+ k,
+ me = this,
+ ret = FALSE;
+ if((index = me.findListener(fn, scope)) != -1){
+ if (me.firing) {
+ me.listeners = me.listeners.slice(0);
+ }
+ l = me.listeners[index];
+ if(l.task) {
+ l.task.cancel();
+ delete l.task;
+ }
+ k = l.tasks && l.tasks.length;
+ if(k) {
+ while(k--) {
+ l.tasks[k].cancel();
+ }
+ delete l.tasks;
+ }
+ me.listeners.splice(index, 1);
+ ret = TRUE;
+ }
+ return ret;
+ },
+
+ // Iterate to stop any buffered/delayed events
+ clearListeners : function(){
+ var me = this,
+ l = me.listeners,
+ i = l.length;
+ while(i--) {
+ me.removeListener(l[i].fn, l[i].scope);
+ }
+ },
+
+ fire : function(){
+ var me = this,
+ listeners = me.listeners,
+ len = listeners.length,
+ i = 0,
+ l;
+
+ if(len > 0){
+ me.firing = TRUE;
+ var args = Array.prototype.slice.call(arguments, 0);
+ for (; i < len; i++) {
+ l = listeners[i];
+ if(l && l.fireFn.apply(l.scope || me.obj || window, args) === FALSE) {
+ return (me.firing = FALSE);
+ }
+ }
+ }
+ me.firing = FALSE;
+ return TRUE;
+ }
+
+};
+})();
+/**
+ * @class Ext.DomHelper
+ * <p>The DomHelper class provides a layer of abstraction from DOM and transparently supports creating
+ * elements via DOM or using HTML fragments. It also has the ability to create HTML fragment templates
+ * from your DOM building code.</p>
+ *
+ * <p><b><u>DomHelper element specification object</u></b></p>
+ * <p>A specification object is used when creating elements. Attributes of this object
+ * are assumed to be element attributes, except for 4 special attributes:
+ * <div class="mdetail-params"><ul>
+ * <li><b><tt>tag</tt></b> : <div class="sub-desc">The tag name of the element</div></li>
+ * <li><b><tt>children</tt></b> : or <tt>cn</tt><div class="sub-desc">An array of the
+ * same kind of element definition objects to be created and appended. These can be nested
+ * as deep as you want.</div></li>
+ * <li><b><tt>cls</tt></b> : <div class="sub-desc">The class attribute of the element.
+ * This will end up being either the "class" attribute on a HTML fragment or className
+ * for a DOM node, depending on whether DomHelper is using fragments or DOM.</div></li>
+ * <li><b><tt>html</tt></b> : <div class="sub-desc">The innerHTML for the element</div></li>
+ * </ul></div></p>
+ *
+ * <p><b><u>Insertion methods</u></b></p>
+ * <p>Commonly used insertion methods:
+ * <div class="mdetail-params"><ul>
+ * <li><b><tt>{@link #append}</tt></b> : <div class="sub-desc"></div></li>
+ * <li><b><tt>{@link #insertBefore}</tt></b> : <div class="sub-desc"></div></li>
+ * <li><b><tt>{@link #insertAfter}</tt></b> : <div class="sub-desc"></div></li>
+ * <li><b><tt>{@link #overwrite}</tt></b> : <div class="sub-desc"></div></li>
+ * <li><b><tt>{@link #createTemplate}</tt></b> : <div class="sub-desc"></div></li>
+ * <li><b><tt>{@link #insertHtml}</tt></b> : <div class="sub-desc"></div></li>
+ * </ul></div></p>
+ *
+ * <p><b><u>Example</u></b></p>
+ * <p>This is an example, where an unordered list with 3 children items is appended to an existing
+ * element with id <tt>'my-div'</tt>:<br>
+ <pre><code>
+var dh = Ext.DomHelper; // create shorthand alias
+// specification object
+var spec = {
+ id: 'my-ul',
+ tag: 'ul',
+ cls: 'my-list',
+ // append children after creating
+ children: [ // may also specify 'cn' instead of 'children'
+ {tag: 'li', id: 'item0', html: 'List Item 0'},
+ {tag: 'li', id: 'item1', html: 'List Item 1'},
+ {tag: 'li', id: 'item2', html: 'List Item 2'}
+ ]
+};
+var list = dh.append(
+ 'my-div', // the context element 'my-div' can either be the id or the actual node
+ spec // the specification object
+);
+ </code></pre></p>
+ * <p>Element creation specification parameters in this class may also be passed as an Array of
+ * specification objects. This can be used to insert multiple sibling nodes into an existing
+ * container very efficiently. For example, to add more list items to the example above:<pre><code>
+dh.append('my-ul', [
+ {tag: 'li', id: 'item3', html: 'List Item 3'},
+ {tag: 'li', id: 'item4', html: 'List Item 4'}
+]);
+ * </code></pre></p>
+ *
+ * <p><b><u>Templating</u></b></p>
+ * <p>The real power is in the built-in templating. Instead of creating or appending any elements,
+ * <tt>{@link #createTemplate}</tt> returns a Template object which can be used over and over to
+ * insert new elements. Revisiting the example above, we could utilize templating this time:
+ * <pre><code>
+// create the node
+var list = dh.append('my-div', {tag: 'ul', cls: 'my-list'});
+// get template
+var tpl = dh.createTemplate({tag: 'li', id: 'item{0}', html: 'List Item {0}'});
+
+for(var i = 0; i < 5, i++){
+ tpl.append(list, [i]); // use template to append to the actual node
+}
+ * </code></pre></p>
+ * <p>An example using a template:<pre><code>
+var html = '<a id="{0}" href="{1}" class="nav">{2}</a>';
+
+var tpl = new Ext.DomHelper.createTemplate(html);
+tpl.append('blog-roll', ['link1', 'http://www.jackslocum.com/', "Jack's Site"]);
+tpl.append('blog-roll', ['link2', 'http://www.dustindiaz.com/', "Dustin's Site"]);
+ * </code></pre></p>
+ *
+ * <p>The same example using named parameters:<pre><code>
+var html = '<a id="{id}" href="{url}" class="nav">{text}</a>';
+
+var tpl = new Ext.DomHelper.createTemplate(html);
+tpl.append('blog-roll', {
+ id: 'link1',
+ url: 'http://www.jackslocum.com/',
+ text: "Jack's Site"
+});
+tpl.append('blog-roll', {
+ id: 'link2',
+ url: 'http://www.dustindiaz.com/',
+ text: "Dustin's Site"
+});
+ * </code></pre></p>
+ *
+ * <p><b><u>Compiling Templates</u></b></p>
+ * <p>Templates are applied using regular expressions. The performance is great, but if
+ * you are adding a bunch of DOM elements using the same template, you can increase
+ * performance even further by {@link Ext.Template#compile "compiling"} the template.
+ * The way "{@link Ext.Template#compile compile()}" works is the template is parsed and
+ * broken up at the different variable points and a dynamic function is created and eval'ed.
+ * The generated function performs string concatenation of these parts and the passed
+ * variables instead of using regular expressions.
+ * <pre><code>
+var html = '<a id="{id}" href="{url}" class="nav">{text}</a>';
+
+var tpl = new Ext.DomHelper.createTemplate(html);
+tpl.compile();
+
+//... use template like normal
+ * </code></pre></p>
+ *
+ * <p><b><u>Performance Boost</u></b></p>
+ * <p>DomHelper will transparently create HTML fragments when it can. Using HTML fragments instead
+ * of DOM can significantly boost performance.</p>
+ * <p>Element creation specification parameters may also be strings. If {@link #useDom} is <tt>false</tt>,
+ * then the string is used as innerHTML. If {@link #useDom} is <tt>true</tt>, a string specification
+ * results in the creation of a text node. Usage:</p>
+ * <pre><code>
+Ext.DomHelper.useDom = true; // force it to use DOM; reduces performance
+ * </code></pre>
+ * @singleton
+ */
+Ext.DomHelper = function(){
+ var tempTableEl = null,
+ emptyTags = /^(?:br|frame|hr|img|input|link|meta|range|spacer|wbr|area|param|col)$/i,
+ tableRe = /^table|tbody|tr|td$/i,
+ confRe = /tag|children|cn|html$/i,
+ tableElRe = /td|tr|tbody/i,
+ cssRe = /([a-z0-9-]+)\s*:\s*([^;\s]+(?:\s*[^;\s]+)*);?/gi,
+ endRe = /end/i,
+ pub,
+ // kill repeat to save bytes
+ afterbegin = 'afterbegin',
+ afterend = 'afterend',
+ beforebegin = 'beforebegin',
+ beforeend = 'beforeend',
+ ts = '<table>',
+ te = '</table>',
+ tbs = ts+'<tbody>',
+ tbe = '</tbody>'+te,
+ trs = tbs + '<tr>',
+ tre = '</tr>'+tbe;
+
+ // private
+ function doInsert(el, o, returnElement, pos, sibling, append){
+ var newNode = pub.insertHtml(pos, Ext.getDom(el), createHtml(o));
+ return returnElement ? Ext.get(newNode, true) : newNode;
+ }
+
+ // build as innerHTML where available
+ function createHtml(o){
+ var b = '',
+ attr,
+ val,
+ key,
+ cn;
+
+ if(typeof o == "string"){
+ b = o;
+ } else if (Ext.isArray(o)) {
+ for (var i=0; i < o.length; i++) {
+ if(o[i]) {
+ b += createHtml(o[i]);
+ }
+ };
+ } else {
+ b += '<' + (o.tag = o.tag || 'div');
+ for (attr in o) {
+ val = o[attr];
+ if(!confRe.test(attr)){
+ if (typeof val == "object") {
+ b += ' ' + attr + '="';
+ for (key in val) {
+ b += key + ':' + val[key] + ';';
+ };
+ b += '"';
+ }else{
+ b += ' ' + ({cls : 'class', htmlFor : 'for'}[attr] || attr) + '="' + val + '"';
+ }
+ }
+ };
+ // Now either just close the tag or try to add children and close the tag.
+ if (emptyTags.test(o.tag)) {
+ b += '/>';
+ } else {
+ b += '>';
+ if ((cn = o.children || o.cn)) {
+ b += createHtml(cn);
+ } else if(o.html){
+ b += o.html;
+ }
+ b += '</' + o.tag + '>';
+ }
+ }
+ return b;
+ }
+
+ function ieTable(depth, s, h, e){
+ tempTableEl.innerHTML = [s, h, e].join('');
+ var i = -1,
+ el = tempTableEl,
+ ns;
+ while(++i < depth){
+ el = el.firstChild;
+ }
+// If the result is multiple siblings, then encapsulate them into one fragment.
+ if(ns = el.nextSibling){
+ var df = document.createDocumentFragment();
+ while(el){
+ ns = el.nextSibling;
+ df.appendChild(el);
+ el = ns;
+ }
+ el = df;
+ }
+ return el;
+ }
+
+ /**
+ * @ignore
+ * Nasty code for IE's broken table implementation
+ */
+ function insertIntoTable(tag, where, el, html) {
+ var node,
+ before;
+
+ tempTableEl = tempTableEl || document.createElement('div');
+
+ if(tag == 'td' && (where == afterbegin || where == beforeend) ||
+ !tableElRe.test(tag) && (where == beforebegin || where == afterend)) {
+ return;
+ }
+ before = where == beforebegin ? el :
+ where == afterend ? el.nextSibling :
+ where == afterbegin ? el.firstChild : null;
+
+ if (where == beforebegin || where == afterend) {
+ el = el.parentNode;
+ }
+
+ if (tag == 'td' || (tag == 'tr' && (where == beforeend || where == afterbegin))) {
+ node = ieTable(4, trs, html, tre);
+ } else if ((tag == 'tbody' && (where == beforeend || where == afterbegin)) ||
+ (tag == 'tr' && (where == beforebegin || where == afterend))) {
+ node = ieTable(3, tbs, html, tbe);
+ } else {
+ node = ieTable(2, ts, html, te);
+ }
+ el.insertBefore(node, before);
+ return node;
+ }
+
+
+ pub = {
+ /**
+ * Returns the markup for the passed Element(s) config.
+ * @param {Object} o The DOM object spec (and children)
+ * @return {String}
+ */
+ markup : function(o){
+ return createHtml(o);
+ },
+
+ /**
+ * Applies a style specification to an element.
+ * @param {String/HTMLElement} el The element to apply styles to
+ * @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.
*/
applyStyles : function(el, styles){
- if(styles){
- var i = 0,
- len,
- style,
- matches;
+ if (styles) {
+ var matches;
el = Ext.fly(el);
- if(typeof styles == "function"){
+ if (typeof styles == "function") {
styles = styles.call();
}
- if(typeof styles == "string"){
- while((matches = cssRe.exec(styles))){
+ if (typeof styles == "string") {
+ /**
+ * Since we're using the g flag on the regex, we need to set the lastIndex.
+ * This automatically happens on some implementations, but not others, see:
+ * http://stackoverflow.com/questions/2645273/javascript-regular-expression-literal-persists-between-function-calls
+ * http://blog.stevenlevithan.com/archives/fixing-javascript-regexp
+ */
+ cssRe.lastIndex = 0;
+ while ((matches = cssRe.exec(styles))) {
el.setStyle(matches[1], matches[2]);
}
- }else if (typeof styles == "object"){
+ } else if (typeof styles == "object") {
el.setStyle(styles);
}
}
};
return pub;
}();
-/**
- * @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
* <p>Represents an HTML fragment template. Templates may be {@link #compile precompiled}
el = Ext.getDom(el);
return new Ext.Template(el.value || el.innerHTML, config || '');
};
-/**
- * @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;
/*
* This is code is also distributed under MIT license for use
* with jQuery and prototype JavaScript libraries.
<li> <b>E[foo$=bar]</b> has an attribute "foo" that ends with "bar"</li>
<li> <b>E[foo*=bar]</b> has an attribute "foo" that contains the substring "bar"</li>
<li> <b>E[foo%=2]</b> has an attribute "foo" that is evenly divisible by 2</li>
- <li> <b>E[foo!=bar]</b> has an attribute "foo" that does not equal "bar"</li>
+ <li> <b>E[foo!=bar]</b> attribute "foo" does not equal "bar"</li>
</ul>
<h4>Pseudo Classes:</h4>
<ul class="list">
ri = -1,
useGetStyle = custom == "{",
fn = Ext.DomQuery.operators[op],
- a,
- innerHTML;
+ a,
+ xml,
+ hasXml;
+
for(var i = 0, ci; ci = cs[i]; i++){
// skip non-element nodes.
if(ci.nodeType != 1){
continue;
}
+ // only need to do this for the first node
+ if(!hasXml){
+ xml = Ext.DomQuery.isXml(ci);
+ hasXml = true;
+ }
- innerHTML = ci.innerHTML;
// we only need to change the property names if we're dealing with html nodes, not XML
- if(innerHTML !== null && innerHTML !== undefined){
+ if(!xml){
if(useGetStyle){
a = Ext.DomQuery.getStyle(ci, attr);
} else if (attr == "class" || attr == "className"){
compile : function(path, type){
type = type || "select";
- // setup fn preamble
+ // setup fn preamble
var fn = ["var f = function(root){\n var mode; ++batch; var n = root || document;\n"],
- mode,
- lastPath,
+ mode,
+ lastPath,
matchers = Ext.DomQuery.matchers,
matchersLn = matchers.length,
modeMatch,
id = null;
}
};
-};(function(){
+};/**
+ * @class Ext.Element
+ * <p>Encapsulates a DOM element, adding simple DOM manipulation facilities, normalizing for browser differences.</p>
+ * <p>All instances of this class inherit the methods of {@link Ext.Fx} making visual effects easily available to all DOM elements.</p>
+ * <p>Note that the events documented in this class are not Ext events, they encapsulate browser events. To
+ * access the underlying browser event, see {@link Ext.EventObject#browserEvent}. Some older
+ * browsers may not support the full range of events. Which events are supported is beyond the control of ExtJs.</p>
+ * Usage:<br>
+<pre><code>
+// by id
+var el = Ext.get("my-div");
-var EXTUTIL = Ext.util,
- EACH = Ext.each,
- TRUE = true,
- FALSE = false;
-/**
- * @class Ext.util.Observable
- * Base class that provides a common interface for publishing events. Subclasses are expected to
- * to have a property "events" with all the events defined, and, optionally, a property "listeners"
- * with configured listeners defined.<br>
- * For example:
+// by DOM element reference
+var el = Ext.get(myDivElement);
+</code></pre>
+ * <b>Animations</b><br />
+ * <p>When an element is manipulated, by default there is no animation.</p>
* <pre><code>
-Employee = Ext.extend(Ext.util.Observable, {
- constructor: function(config){
- this.name = config.name;
- this.addEvents({
- "fired" : true,
- "quit" : true
- });
-
- // Copy configured listeners into *this* object so that the base class's
- // constructor will add them.
- this.listeners = config.listeners;
+var el = Ext.get("my-div");
- // Call our superclass constructor to complete construction process.
- Employee.superclass.constructor.call(this, config)
- }
-});
-</code></pre>
- * This could then be used like this:<pre><code>
-var newEmployee = new Employee({
- name: employeeName,
- listeners: {
- quit: function() {
- // By default, "this" will be the object that fired the event.
- alert(this.name + " has quit!");
- }
- }
-});
-</code></pre>
+// no animation
+el.setWidth(100);
+ * </code></pre>
+ * <p>Many of the functions for manipulating an element have an optional "animate" parameter. This
+ * parameter can be specified as boolean (<tt>true</tt>) for default animation effects.</p>
+ * <pre><code>
+// default animation
+el.setWidth(100, true);
+ * </code></pre>
+ *
+ * <p>To configure the effects, an object literal with animation options to use as the Element animation
+ * configuration object can also be specified. Note that the supported Element animation configuration
+ * options are a subset of the {@link Ext.Fx} animation options specific to Fx effects. The supported
+ * Element animation configuration options are:</p>
+<pre>
+Option Default Description
+--------- -------- ---------------------------------------------
+{@link Ext.Fx#duration duration} .35 The duration of the animation in seconds
+{@link Ext.Fx#easing easing} easeOut The easing method
+{@link Ext.Fx#callback callback} none A function to execute when the anim completes
+{@link Ext.Fx#scope scope} this The scope (this) of the callback function
+</pre>
+ *
+ * <pre><code>
+// Element animation options object
+var opt = {
+ {@link Ext.Fx#duration duration}: 1,
+ {@link Ext.Fx#easing easing}: 'elasticIn',
+ {@link Ext.Fx#callback callback}: this.foo,
+ {@link Ext.Fx#scope scope}: this
+};
+// animation with some options set
+el.setWidth(100, opt);
+ * </code></pre>
+ * <p>The Element animation object being used for the animation will be set on the options
+ * object as "anim", which allows you to stop or manipulate the animation. Here is an example:</p>
+ * <pre><code>
+// using the "anim" property to get the Anim object
+if(opt.anim.isAnimated()){
+ opt.anim.stop();
+}
+ * </code></pre>
+ * <p>Also see the <tt>{@link #animate}</tt> method for another animation technique.</p>
+ * <p><b> Composite (Collections of) Elements</b></p>
+ * <p>For working with collections of Elements, see {@link Ext.CompositeElement}</p>
+ * @constructor Create a new Element directly.
+ * @param {String/HTMLElement} element
+ * @param {Boolean} forceNew (optional) By default the constructor checks to see if there is already an instance of this element in the cache and if there is it returns the same instance. This will skip that check (useful for extending this class).
*/
-EXTUTIL.Observable = function(){
- /**
- * @cfg {Object} listeners (optional) <p>A config object containing one or more event handlers to be added to this
- * object during initialization. This should be a valid listeners config object as specified in the
- * {@link #addListener} example for attaching multiple handlers at once.</p>
- * <br><p><b><u>DOM events from ExtJs {@link Ext.Component Components}</u></b></p>
- * <br><p>While <i>some</i> ExtJs Component classes export selected DOM events (e.g. "click", "mouseover" etc), this
- * is usually only done when extra value can be added. For example the {@link Ext.DataView DataView}'s
- * <b><code>{@link Ext.DataView#click click}</code></b> event passing the node clicked on. To access DOM
- * events directly from a Component's HTMLElement, listeners must be added to the <i>{@link Ext.Component#getEl Element}</i> after the Component
- * has been rendered. A plugin can simplify this step:<pre><code>
-// Plugin is configured with a listeners config object.
-// The Component is appended to the argument list of all handler functions.
-Ext.DomObserver = Ext.extend(Object, {
- constructor: function(config) {
- this.listeners = config.listeners ? config.listeners : config;
- },
+(function(){
+var DOC = document;
- // Component passes itself into plugin's init method
- init: function(c) {
- var p, l = this.listeners;
- for (p in l) {
- if (Ext.isFunction(l[p])) {
- l[p] = this.createHandler(l[p], c);
- } else {
- l[p].fn = this.createHandler(l[p].fn, c);
- }
- }
+Ext.Element = function(element, forceNew){
+ var dom = typeof element == "string" ?
+ DOC.getElementById(element) : element,
+ id;
- // Add the listeners to the Element immediately following the render call
- c.render = c.render.{@link Function#createSequence createSequence}(function() {
- var e = c.getEl();
- if (e) {
- e.on(l);
- }
- });
- },
+ if(!dom) return null;
- createHandler: function(fn, c) {
- return function(e) {
- fn.call(this, e, c);
- };
+ id = dom.id;
+
+ if(!forceNew && id && Ext.elCache[id]){ // element object already exists
+ return Ext.elCache[id].el;
}
-});
-var combo = new Ext.form.ComboBox({
+ /**
+ * The DOM element
+ * @type HTMLElement
+ */
+ this.dom = dom;
- // Collapse combo when its element is clicked on
- plugins: [ new Ext.DomObserver({
- click: function(evt, comp) {
- comp.collapse();
- }
- })],
- store: myStore,
- typeAhead: true,
- mode: 'local',
- triggerAction: 'all'
-});
- * </code></pre></p>
+ /**
+ * The DOM element ID
+ * @type String
*/
- var me = this, e = me.events;
- if(me.listeners){
- me.on(me.listeners);
- delete me.listeners;
- }
- me.events = e || {};
+ this.id = id || Ext.id(dom);
};
-EXTUTIL.Observable.prototype = {
- // private
- filterOptRe : /^(?:scope|delay|buffer|single)$/,
+var DH = Ext.DomHelper,
+ El = Ext.Element,
+ EC = Ext.elCache;
+El.prototype = {
/**
- * <p>Fires the specified event with the passed parameters (minus the event name).</p>
- * <p>An event may be set to bubble up an Observable parent hierarchy (See {@link Ext.Component#getBubbleTarget})
- * by calling {@link #enableBubble}.</p>
- * @param {String} eventName The name of the event to fire.
- * @param {Object...} args Variable number of parameters are passed to handlers.
- * @return {Boolean} returns false if any of the handlers return false otherwise it returns true.
+ * Sets the passed attributes as attributes of this element (a style attribute can be a string, object or function)
+ * @param {Object} o The object with the attributes
+ * @param {Boolean} useSet (optional) false to override the default setAttribute to use expandos.
+ * @return {Ext.Element} this
*/
- fireEvent : function(){
- var a = Array.prototype.slice.call(arguments, 0),
- ename = a[0].toLowerCase(),
- me = this,
- ret = TRUE,
- ce = me.events[ename],
- cc,
- q,
- c;
- if (me.eventsSuspended === TRUE) {
- if (q = me.eventQueue) {
- q.push(a);
- }
- }
- else if(typeof ce == 'object') {
- if (ce.bubble){
- if(ce.fire.apply(ce, a.slice(1)) === FALSE) {
- return FALSE;
- }
- c = me.getBubbleTarget && me.getBubbleTarget();
- if(c && c.enableBubble) {
- cc = c.events[ename];
- if(!cc || typeof cc != 'object' || !cc.bubble) {
- c.enableBubble(ename);
- }
- return c.fireEvent.apply(c, a);
+ set : function(o, useSet){
+ var el = this.dom,
+ attr,
+ val,
+ useSet = (useSet !== false) && !!el.setAttribute;
+
+ for (attr in o) {
+ if (o.hasOwnProperty(attr)) {
+ val = o[attr];
+ if (attr == 'style') {
+ DH.applyStyles(el, val);
+ } else if (attr == 'cls') {
+ el.className = val;
+ } else if (useSet) {
+ el.setAttribute(attr, val);
+ } else {
+ el[attr] = val;
}
}
- else {
- a.shift();
- ret = ce.fire.apply(ce, a);
- }
}
- return ret;
+ return this;
},
+// Mouse events
/**
- * Appends an event handler to this object.
- * @param {String} eventName The name of the event to listen for.
- * @param {Function} handler The method the event invokes.
- * @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the handler function is executed.
- * <b>If omitted, defaults to the object which fired the event.</b>
- * @param {Object} options (optional) An object containing handler configuration.
- * properties. This may contain any of the following properties:<ul>
- * <li><b>scope</b> : Object<div class="sub-desc">The scope (<code><b>this</b></code> reference) in which the handler function is executed.
- * <b>If omitted, defaults to the object which fired the event.</b></div></li>
- * <li><b>delay</b> : Number<div class="sub-desc">The number of milliseconds to delay the invocation of the handler after the event fires.</div></li>
- * <li><b>single</b> : Boolean<div class="sub-desc">True to add a handler to handle just the next firing of the event, and then remove itself.</div></li>
- * <li><b>buffer</b> : Number<div class="sub-desc">Causes the handler to be scheduled to run in an {@link Ext.util.DelayedTask} delayed
- * by the specified number of milliseconds. If the event fires again within that time, the original
- * handler is <em>not</em> invoked, but the new handler is scheduled in its place.</div></li>
- * <li><b>target</b> : Observable<div class="sub-desc">Only call the handler if the event was fired on the target Observable, <i>not</i>
- * if the event was bubbled up from a child Observable.</div></li>
- * </ul><br>
- * <p>
- * <b>Combining Options</b><br>
- * Using the options argument, it is possible to combine different types of listeners:<br>
- * <br>
- * A delayed, one-time listener.
- * <pre><code>
-myDataView.on('click', this.onClick, this, {
-single: true,
-delay: 100
-});</code></pre>
- * <p>
- * <b>Attaching multiple handlers in 1 call</b><br>
- * The method also allows for a single argument to be passed which is a config object containing properties
- * which specify multiple handlers.
- * <p>
- * <pre><code>
-myGridPanel.on({
-'click' : {
- fn: this.onClick,
- scope: this,
- delay: 100
-},
-'mouseover' : {
- fn: this.onMouseOver,
- scope: this
-},
-'mouseout' : {
- fn: this.onMouseOut,
- scope: this
-}
-});</code></pre>
- * <p>
- * Or a shorthand syntax:<br>
- * <pre><code>
-myGridPanel.on({
-'click' : this.onClick,
-'mouseover' : this.onMouseOver,
-'mouseout' : this.onMouseOut,
- scope: this
-});</code></pre>
+ * @event click
+ * Fires when a mouse click is detected within the element.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
*/
- addListener : function(eventName, fn, scope, o){
- var me = this,
- e,
- oe,
- isF,
- ce;
- if (typeof eventName == 'object') {
- o = eventName;
- for (e in o){
- oe = o[e];
- if (!me.filterOptRe.test(e)) {
- me.addListener(e, oe.fn || oe, oe.scope || o.scope, oe.fn ? oe : o);
- }
- }
- } else {
- eventName = eventName.toLowerCase();
- ce = me.events[eventName] || TRUE;
- if (typeof ce == 'boolean') {
- me.events[eventName] = ce = new EXTUTIL.Event(me, eventName);
- }
- ce.addListener(fn, scope, typeof o == 'object' ? o : {});
- }
- },
-
/**
- * Removes an event handler.
- * @param {String} eventName The type of event the handler was associated with.
- * @param {Function} handler The handler to remove. <b>This must be a reference to the function passed into the {@link #addListener} call.</b>
- * @param {Object} scope (optional) The scope originally specified for the handler.
+ * @event contextmenu
+ * Fires when a right click is detected within the element.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
*/
- removeListener : function(eventName, fn, scope){
- var ce = this.events[eventName.toLowerCase()];
- if (typeof ce == 'object') {
- ce.removeListener(fn, scope);
- }
- },
-
/**
- * Removes all listeners for this object
+ * @event dblclick
+ * Fires when a mouse double click is detected within the element.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
*/
- purgeListeners : function(){
- var events = this.events,
- evt,
- key;
- for(key in events){
- evt = events[key];
- if(typeof evt == 'object'){
- evt.clearListeners();
- }
- }
- },
-
/**
- * Adds the specified events to the list of events which this Observable may fire.
- * @param {Object|String} o Either an object with event names as properties with a value of <code>true</code>
- * or the first event name string if multiple event names are being passed as separate parameters.
- * @param {string} Optional. Event name if multiple event names are being passed as separate parameters.
- * Usage:<pre><code>
-this.addEvents('storeloaded', 'storecleared');
-</code></pre>
+ * @event mousedown
+ * Fires when a mousedown is detected within the element.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
*/
- addEvents : function(o){
- var me = this;
- me.events = me.events || {};
- if (typeof o == 'string') {
- var a = arguments,
- i = a.length;
- while(i--) {
- me.events[a[i]] = me.events[a[i]] || TRUE;
- }
- } else {
- Ext.applyIf(me.events, o);
- }
- },
-
/**
- * Checks to see if this object has any listeners for a specified event
- * @param {String} eventName The name of the event to check for
- * @return {Boolean} True if the event is being listened for, else false
+ * @event mouseup
+ * Fires when a mouseup is detected within the element.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
*/
- hasListener : function(eventName){
- var e = this.events[eventName.toLowerCase()];
- return typeof e == 'object' && e.listeners.length > 0;
- },
-
/**
- * Suspend the firing of all events. (see {@link #resumeEvents})
- * @param {Boolean} queueSuspended Pass as true to queue up suspended events to be fired
- * after the {@link #resumeEvents} call instead of discarding all suspended events;
+ * @event mouseover
+ * Fires when a mouseover is detected within the element.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
*/
- suspendEvents : function(queueSuspended){
- this.eventsSuspended = TRUE;
- if(queueSuspended && !this.eventQueue){
- this.eventQueue = [];
- }
- },
-
/**
- * Resume firing events. (see {@link #suspendEvents})
- * If events were suspended using the <tt><b>queueSuspended</b></tt> parameter, then all
- * events fired during event suspension will be sent to any listeners now.
+ * @event mousemove
+ * Fires when a mousemove is detected with the element.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event mouseout
+ * Fires when a mouseout is detected with the element.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event mouseenter
+ * Fires when the mouse enters the element.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event mouseleave
+ * Fires when the mouse leaves the element.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
*/
- resumeEvents : function(){
- var me = this,
- queued = me.eventQueue || [];
- me.eventsSuspended = FALSE;
- delete me.eventQueue;
- EACH(queued, function(e) {
- me.fireEvent.apply(me, e);
- });
- }
-};
-var OBSERVABLE = EXTUTIL.Observable.prototype;
-/**
- * Appends an event handler to this object (shorthand for {@link #addListener}.)
- * @param {String} eventName The type of event to listen for
- * @param {Function} handler The method the event invokes
- * @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the handler function is executed.
- * <b>If omitted, defaults to the object which fired the event.</b>
- * @param {Object} options (optional) An object containing handler configuration.
- * @method
- */
-OBSERVABLE.on = OBSERVABLE.addListener;
-/**
- * Removes an event handler (shorthand for {@link #removeListener}.)
- * @param {String} eventName The type of event the handler was associated with.
- * @param {Function} handler The handler to remove. <b>This must be a reference to the function passed into the {@link #addListener} call.</b>
- * @param {Object} scope (optional) The scope originally specified for the handler.
- * @method
- */
-OBSERVABLE.un = OBSERVABLE.removeListener;
+// Keyboard events
+ /**
+ * @event keypress
+ * Fires when a keypress is detected within the element.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event keydown
+ * Fires when a keydown is detected within the element.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event keyup
+ * Fires when a keyup is detected within the element.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
-/**
- * Removes <b>all</b> added captures from the Observable.
- * @param {Observable} o The Observable to release
- * @static
- */
-EXTUTIL.Observable.releaseCapture = function(o){
- o.fireEvent = OBSERVABLE.fireEvent;
-};
-function createTargeted(h, o, scope){
- return function(){
- if(o.target == arguments[0]){
- h.apply(scope, Array.prototype.slice.call(arguments, 0));
- }
- };
-};
+// HTML frame/object events
+ /**
+ * @event load
+ * Fires when the user agent finishes loading all content within the element. Only supported by window, frames, objects and images.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event unload
+ * Fires when the user agent removes all content from a window or frame. For elements, it fires when the target element or any of its content has been removed.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event abort
+ * Fires when an object/image is stopped from loading before completely loaded.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event error
+ * Fires when an object/image/frame cannot be loaded properly.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event resize
+ * Fires when a document view is resized.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event scroll
+ * Fires when a document view is scrolled.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
-function createBuffered(h, o, l, scope){
- l.task = new EXTUTIL.DelayedTask();
- return function(){
- l.task.delay(o.buffer, h, scope, Array.prototype.slice.call(arguments, 0));
- };
-};
+// Form events
+ /**
+ * @event select
+ * Fires when a user selects some text in a text field, including input and textarea.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event change
+ * Fires when a control loses the input focus and its value has been modified since gaining focus.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event submit
+ * Fires when a form is submitted.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event reset
+ * Fires when a form is reset.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event focus
+ * Fires when an element receives focus either via the pointing device or by tab navigation.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event blur
+ * Fires when an element loses focus either via the pointing device or by tabbing navigation.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
-function createSingle(h, e, fn, scope){
- return function(){
- e.removeListener(fn, scope);
- return h.apply(scope, arguments);
- };
-};
+// User Interface events
+ /**
+ * @event DOMFocusIn
+ * Where supported. Similar to HTML focus event, but can be applied to any focusable element.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event DOMFocusOut
+ * Where supported. Similar to HTML blur event, but can be applied to any focusable element.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event DOMActivate
+ * Where supported. Fires when an element is activated, for instance, through a mouse click or a keypress.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
-function createDelayed(h, o, l, scope){
- return function(){
- var task = new EXTUTIL.DelayedTask();
- if(!l.tasks) {
- l.tasks = [];
- }
- l.tasks.push(task);
- task.delay(o.delay || 10, h, scope, Array.prototype.slice.call(arguments, 0));
- };
-};
+// DOM Mutation events
+ /**
+ * @event DOMSubtreeModified
+ * Where supported. Fires when the subtree is modified.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event DOMNodeInserted
+ * Where supported. Fires when a node has been added as a child of another node.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event DOMNodeRemoved
+ * Where supported. Fires when a descendant node of the element is removed.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event DOMNodeRemovedFromDocument
+ * Where supported. Fires when a node is being removed from a document.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event DOMNodeInsertedIntoDocument
+ * Where supported. Fires when a node is being inserted into a document.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event DOMAttrModified
+ * Where supported. Fires when an attribute has been modified.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event DOMCharacterDataModified
+ * Where supported. Fires when the character data has been modified.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
-EXTUTIL.Event = function(obj, name){
- this.name = name;
- this.obj = obj;
- this.listeners = [];
-};
+ /**
+ * The default unit to append to CSS values where a unit isn't provided (defaults to px).
+ * @type String
+ */
+ defaultUnit : "px",
-EXTUTIL.Event.prototype = {
- addListener : function(fn, scope, options){
+ /**
+ * Returns true if this element matches the passed simple selector (e.g. div.some-class or span:first-child)
+ * @param {String} selector The simple selector to test
+ * @return {Boolean} True if this element matches the selector, else false
+ */
+ is : function(simpleSelector){
+ return Ext.DomQuery.is(this.dom, simpleSelector);
+ },
+
+ /**
+ * Tries to focus the element. Any exceptions are caught and ignored.
+ * @param {Number} defer (optional) Milliseconds to defer the focus
+ * @return {Ext.Element} this
+ */
+ focus : function(defer, /* private */ dom) {
var me = this,
- l;
- scope = scope || me.obj;
- if(!me.isListening(fn, scope)){
- l = me.createListener(fn, scope, options);
- if(me.firing){ // if we are currently firing this event, don't disturb the listener loop
- me.listeners = me.listeners.slice(0);
+ dom = dom || me.dom;
+ try{
+ if(Number(defer)){
+ me.focus.defer(defer, null, [null, dom]);
+ }else{
+ dom.focus();
}
- me.listeners.push(l);
- }
+ }catch(e){}
+ return me;
},
- createListener: function(fn, scope, o){
- o = o || {}, scope = scope || this.obj;
- var l = {
- fn: fn,
- scope: scope,
- options: o
- }, h = fn;
- if(o.target){
- h = createTargeted(h, o, scope);
- }
- if(o.delay){
- h = createDelayed(h, o, l, scope);
- }
- if(o.single){
- h = createSingle(h, this, fn, scope);
- }
- if(o.buffer){
- h = createBuffered(h, o, l, scope);
- }
- l.fireFn = h;
- return l;
+ /**
+ * Tries to blur the element. Any exceptions are caught and ignored.
+ * @return {Ext.Element} this
+ */
+ blur : function() {
+ try{
+ this.dom.blur();
+ }catch(e){}
+ return this;
},
- findListener : function(fn, scope){
- var list = this.listeners,
- i = list.length,
- l;
+ /**
+ * Returns the value of the "value" attribute
+ * @param {Boolean} asNumber true to parse the value as a number
+ * @return {String/Number}
+ */
+ getValue : function(asNumber){
+ var val = this.dom.value;
+ return asNumber ? parseInt(val, 10) : val;
+ },
- scope = scope || this.obj;
- while(i--){
- l = list[i];
- if(l){
- if(l.fn == fn && l.scope == scope){
- return i;
- }
- }
- }
- return -1;
+ /**
+ * Appends an event handler to this element. The shorthand version {@link #on} is equivalent.
+ * @param {String} eventName The name of event to handle.
+ * @param {Function} fn The handler function the event invokes. This function is passed
+ * the following parameters:<ul>
+ * <li><b>evt</b> : EventObject<div class="sub-desc">The {@link Ext.EventObject EventObject} describing the event.</div></li>
+ * <li><b>el</b> : HtmlElement<div class="sub-desc">The DOM element which was the target of the event.
+ * Note that this may be filtered by using the <tt>delegate</tt> option.</div></li>
+ * <li><b>o</b> : Object<div class="sub-desc">The options object from the addListener call.</div></li>
+ * </ul>
+ * @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the handler function is executed.
+ * <b>If omitted, defaults to this Element.</b>.
+ * @param {Object} options (optional) An object containing handler configuration properties.
+ * This may contain any of the following properties:<ul>
+ * <li><b>scope</b> Object : <div class="sub-desc">The scope (<code><b>this</b></code> reference) in which the handler function is executed.
+ * <b>If omitted, defaults to this Element.</b></div></li>
+ * <li><b>delegate</b> String: <div class="sub-desc">A simple selector to filter the target or look for a descendant of the target. See below for additional details.</div></li>
+ * <li><b>stopEvent</b> Boolean: <div class="sub-desc">True to stop the event. That is stop propagation, and prevent the default action.</div></li>
+ * <li><b>preventDefault</b> Boolean: <div class="sub-desc">True to prevent the default action</div></li>
+ * <li><b>stopPropagation</b> Boolean: <div class="sub-desc">True to prevent event propagation</div></li>
+ * <li><b>normalized</b> Boolean: <div class="sub-desc">False to pass a browser event to the handler function instead of an Ext.EventObject</div></li>
+ * <li><b>target</b> Ext.Element: <div class="sub-desc">Only call the handler if the event was fired on the target Element, <i>not</i> if the event was bubbled up from a child node.</div></li>
+ * <li><b>delay</b> Number: <div class="sub-desc">The number of milliseconds to delay the invocation of the handler after the event fires.</div></li>
+ * <li><b>single</b> Boolean: <div class="sub-desc">True to add a handler to handle just the next firing of the event, and then remove itself.</div></li>
+ * <li><b>buffer</b> Number: <div class="sub-desc">Causes the handler to be scheduled to run in an {@link Ext.util.DelayedTask} delayed
+ * by the specified number of milliseconds. If the event fires again within that time, the original
+ * handler is <em>not</em> invoked, but the new handler is scheduled in its place.</div></li>
+ * </ul><br>
+ * <p>
+ * <b>Combining Options</b><br>
+ * In the following examples, the shorthand form {@link #on} is used rather than the more verbose
+ * addListener. The two are equivalent. Using the options argument, it is possible to combine different
+ * types of listeners:<br>
+ * <br>
+ * A delayed, one-time listener that auto stops the event and adds a custom argument (forumId) to the
+ * options object. The options object is available as the third parameter in the handler function.<div style="margin: 5px 20px 20px;">
+ * Code:<pre><code>
+el.on('click', this.onClick, this, {
+ single: true,
+ delay: 100,
+ stopEvent : true,
+ forumId: 4
+});</code></pre></p>
+ * <p>
+ * <b>Attaching multiple handlers in 1 call</b><br>
+ * The method also allows for a single argument to be passed which is a config object containing properties
+ * which specify multiple handlers.</p>
+ * <p>
+ * Code:<pre><code>
+el.on({
+ 'click' : {
+ fn: this.onClick,
+ scope: this,
+ delay: 100
+ },
+ 'mouseover' : {
+ fn: this.onMouseOver,
+ scope: this
+ },
+ 'mouseout' : {
+ fn: this.onMouseOut,
+ scope: this
+ }
+});</code></pre>
+ * <p>
+ * Or a shorthand syntax:<br>
+ * Code:<pre><code></p>
+el.on({
+ 'click' : this.onClick,
+ 'mouseover' : this.onMouseOver,
+ 'mouseout' : this.onMouseOut,
+ scope: this
+});
+ * </code></pre></p>
+ * <p><b>delegate</b></p>
+ * <p>This is a configuration option that you can pass along when registering a handler for
+ * an event to assist with event delegation. Event delegation is a technique that is used to
+ * reduce memory consumption and prevent exposure to memory-leaks. By registering an event
+ * for a container element as opposed to each element within a container. By setting this
+ * configuration option to a simple selector, the target element will be filtered to look for
+ * a descendant of the target.
+ * For example:<pre><code>
+// using this markup:
+<div id='elId'>
+ <p id='p1'>paragraph one</p>
+ <p id='p2' class='clickable'>paragraph two</p>
+ <p id='p3'>paragraph three</p>
+</div>
+// utilize event delegation to registering just one handler on the container element:
+el = Ext.get('elId');
+el.on(
+ 'click',
+ function(e,t) {
+ // handle click
+ console.info(t.id); // 'p2'
+ },
+ this,
+ {
+ // filter the target element to be a descendant with the class 'clickable'
+ delegate: '.clickable'
+ }
+);
+ * </code></pre></p>
+ * @return {Ext.Element} this
+ */
+ addListener : function(eventName, fn, scope, options){
+ Ext.EventManager.on(this.dom, eventName, fn, scope || this, options);
+ return this;
},
- isListening : function(fn, scope){
- return this.findListener(fn, scope) != -1;
+ /**
+ * Removes an event handler from this element. The shorthand version {@link #un} is equivalent.
+ * <b>Note</b>: if a <i>scope</i> was explicitly specified when {@link #addListener adding} the
+ * listener, the same scope must be specified here.
+ * Example:
+ * <pre><code>
+el.removeListener('click', this.handlerFn);
+// or
+el.un('click', this.handlerFn);
+</code></pre>
+ * @param {String} eventName The name of the event from which to remove the handler.
+ * @param {Function} fn The handler function to remove. <b>This must be a reference to the function passed into the {@link #addListener} call.</b>
+ * @param {Object} scope If a scope (<b><code>this</code></b> reference) was specified when the listener was added,
+ * then this must refer to the same object.
+ * @return {Ext.Element} this
+ */
+ removeListener : function(eventName, fn, scope){
+ Ext.EventManager.removeListener(this.dom, eventName, fn, scope || this);
+ return this;
},
- removeListener : function(fn, scope){
- var index,
- l,
- k,
- me = this,
- ret = FALSE;
- if((index = me.findListener(fn, scope)) != -1){
- if (me.firing) {
- me.listeners = me.listeners.slice(0);
- }
- l = me.listeners[index];
- if(l.task) {
- l.task.cancel();
- delete l.task;
- }
- k = l.tasks && l.tasks.length;
- if(k) {
- while(k--) {
- l.tasks[k].cancel();
- }
- delete l.tasks;
- }
- me.listeners.splice(index, 1);
- ret = TRUE;
- }
- return ret;
+ /**
+ * Removes all previous added listeners from this element
+ * @return {Ext.Element} this
+ */
+ removeAllListeners : function(){
+ Ext.EventManager.removeAll(this.dom);
+ return this;
},
- // Iterate to stop any buffered/delayed events
- clearListeners : function(){
- var me = this,
- l = me.listeners,
- i = l.length;
- while(i--) {
- me.removeListener(l[i].fn, l[i].scope);
+ /**
+ * Recursively removes all previous added listeners from this element and its children
+ * @return {Ext.Element} this
+ */
+ purgeAllListeners : function() {
+ Ext.EventManager.purgeElement(this, true);
+ return this;
+ },
+ /**
+ * @private Test if size has a unit, otherwise appends the default
+ */
+ addUnits : function(size){
+ if(size === "" || size == "auto" || size === undefined){
+ size = size || '';
+ } else if(!isNaN(size) || !unitPattern.test(size)){
+ size = size + (this.defaultUnit || 'px');
}
+ return size;
},
- fire : function(){
+ /**
+ * <p>Updates the <a href="http://developer.mozilla.org/en/DOM/element.innerHTML">innerHTML</a> of this Element
+ * from a specified URL. Note that this is subject to the <a href="http://en.wikipedia.org/wiki/Same_origin_policy">Same Origin Policy</a></p>
+ * <p>Updating innerHTML of an element will <b>not</b> execute embedded <tt><script></tt> elements. This is a browser restriction.</p>
+ * @param {Mixed} options. Either a sring containing the URL from which to load the HTML, or an {@link Ext.Ajax#request} options object specifying
+ * exactly how to request the HTML.
+ * @return {Ext.Element} this
+ */
+ load : function(url, params, cb){
+ Ext.Ajax.request(Ext.apply({
+ params: params,
+ url: url.url || url,
+ callback: cb,
+ el: this.dom,
+ indicatorText: url.indicatorText || ''
+ }, Ext.isObject(url) ? url : {}));
+ return this;
+ },
+
+ /**
+ * Tests various css rules/browsers to determine if this element uses a border box
+ * @return {Boolean}
+ */
+ isBorderBox : function(){
+ return Ext.isBorderBox || Ext.isForcedBorderBox || noBoxAdjust[(this.dom.tagName || "").toLowerCase()];
+ },
+
+ /**
+ * <p>Removes this element's dom reference. Note that event and cache removal is handled at {@link Ext#removeNode}</p>
+ */
+ remove : function(){
var me = this,
- listeners = me.listeners,
- len = listeners.length,
- i = 0,
- l;
+ dom = me.dom;
- if(len > 0){
- me.firing = TRUE;
- var args = Array.prototype.slice.call(arguments, 0);
- for (; i < len; i++) {
- l = listeners[i];
- if(l && l.fireFn.apply(l.scope || me.obj || window, args) === FALSE) {
- return (me.firing = FALSE);
- }
- }
+ if (dom) {
+ delete me.dom;
+ Ext.removeNode(dom);
}
- me.firing = FALSE;
- return TRUE;
- }
+ },
-};
-})();
-/**
- * @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;
- }
- }
- };
+ /**
+ * Sets up event handlers to call the passed functions when the mouse is moved into and out of the Element.
+ * @param {Function} overFn The function to call when the mouse enters the Element.
+ * @param {Function} outFn The function to call when the mouse leaves the Element.
+ * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the functions are executed. Defaults to the Element's DOM element.
+ * @param {Object} options (optional) Options for the listener. See {@link Ext.util.Observable#addListener the <tt>options</tt> parameter}.
+ * @return {Ext.Element} this
+ */
+ hover : function(overFn, outFn, scope, options){
+ var me = this;
+ me.on('mouseenter', overFn, scope || me.dom, options);
+ me.on('mouseleave', outFn, scope || me.dom, options);
+ return me;
+ },
- this[method] = function(){
- var args = Array.prototype.slice.call(arguments, 0),
- b;
- returnValue = v = undefined;
- cancel = false;
+ /**
+ * Returns true if this element is an ancestor of the passed element
+ * @param {HTMLElement/String} el The element to check
+ * @return {Boolean} True if this element is an ancestor of el, else false
+ */
+ contains : function(el){
+ return !el ? false : Ext.lib.Dom.isAncestor(this.dom, el.dom ? el.dom : el);
+ },
- 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;
- }
- }
+ /**
+ * Returns the value of a namespaced attribute from the element's underlying DOM node.
+ * @param {String} namespace The namespace in which to look for the attribute
+ * @param {String} name The attribute name
+ * @return {String} The attribute value
+ * @deprecated
+ */
+ getAttributeNS : function(ns, name){
+ return this.getAttribute(name, ns);
+ },
- if((v = e.originalFn.apply(obj, args)) !== undefined){
- returnValue = v;
- }
+ /**
+ * Returns the value of an attribute from the element's underlying DOM node.
+ * @param {String} name The attribute name
+ * @param {String} namespace (optional) The namespace in which to look for the attribute
+ * @return {String} The attribute value
+ */
+ getAttribute : Ext.isIE ? function(name, ns){
+ var d = this.dom,
+ type = typeof d[ns + ":" + name];
- 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;
- };
+ if(['undefined', 'unknown'].indexOf(type) == -1){
+ return d[ns + ":" + name];
}
- 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');
- }),
+ return d[name];
+ } : function(name, ns){
+ var d = this.dom;
+ return d.getAttributeNS(ns, name) || d.getAttribute(ns + ":" + name) || d.getAttribute(name) || d[name];
+ },
- // We know that we want Field's events to bubble directly to the FormPanel.
- getBubbleTarget : function() {
- if (!this.formPanel) {
- this.formPanel = this.findParentByType('form');
+ /**
+ * Update the innerHTML of this element
+ * @param {String} html The new HTML
+ * @return {Ext.Element} this
+ */
+ update : function(html) {
+ if (this.dom) {
+ this.dom.innerHTML = html;
}
- return this.formPanel;
+ return this;
}
-});
+};
-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;
- }
- }
- }
- };
-}());
+var ep = El.prototype;
+El.addMethods = function(o){
+ Ext.apply(ep, o);
+};
/**
- * 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
+ * Appends an event handler (shorthand for {@link #addListener}).
+ * @param {String} eventName The name of event to handle.
+ * @param {Function} fn The handler function the event invokes.
+ * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the handler function is executed.
+ * @param {Object} options (optional) An object containing standard {@link #addListener} options
+ * @member Ext.Element
+ * @method on
*/
-Ext.util.Observable.capture = function(o, fn, scope){
- o.fireEvent = o.fireEvent.createInterceptor(fn, scope);
-};
-
+ep.on = ep.addListener;
/**
- * 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
+ * Removes an event handler from this element (see {@link #removeListener} for additional notes).
+ * @param {String} eventName The name of the event from which to remove the handler.
+ * @param {Function} fn The handler function to remove. <b>This must be a reference to the function passed into the {@link #addListener} call.</b>
+ * @param {Object} scope If a scope (<b><code>this</code></b> reference) was specified when the listener was added,
+ * then this must refer to the same object.
+ * @return {Ext.Element} this
+ * @member Ext.Element
+ * @method un
*/
-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;
- }
-};
+ep.un = ep.removeListener;
+
/**
- * @class Ext.EventManager
- * Registers event handlers that want to receive a normalized EventObject instead of the standard browser event and provides
- * several useful events directly.
- * See {@link Ext.EventObject} for more details on normalized event objects.
- * @singleton
+ * true to automatically adjust width and height settings for box-model issues (default to true)
*/
+ep.autoBoxAdjust = true;
-Ext.EventManager = function(){
- var docReadyEvent,
- docReadyProcId,
- docReadyState = false,
- DETECT_NATIVE = Ext.isGecko || Ext.isWebKit || Ext.isSafari,
- E = Ext.lib.Event,
- D = Ext.lib.Dom,
- DOC = document,
- WINDOW = window,
- DOMCONTENTLOADED = "DOMContentLoaded",
- COMPLETE = 'complete',
- propRe = /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate)$/,
- /*
- * This cache is used to hold special js objects, the document and window, that don't have an id. We need to keep
- * a reference to them so we can look them up at a later point.
- */
- specialElCache = [];
-
- function getId(el){
- var id = false,
- i = 0,
- len = specialElCache.length,
- id = false,
- skip = false,
- o;
- if(el){
- if(el.getElementById || el.navigator){
- // look up the id
- for(; i < len; ++i){
- o = specialElCache[i];
- if(o.el === el){
- id = o.id;
- break;
- }
- }
- if(!id){
- // for browsers that support it, ensure that give the el the same id
- id = Ext.id(el);
- specialElCache.push({
- id: id,
- el: el
- });
- skip = true;
- }
- }else{
- id = Ext.id(el);
- }
- if(!Ext.elCache[id]){
- Ext.Element.addToCache(new Ext.Element(el), id);
- if(skip){
- Ext.elCache[id].skipGC = true;
- }
- }
- }
- return id;
- };
-
- /// There is some jquery work around stuff here that isn't needed in Ext Core.
- function addListener(el, ename, fn, task, wrap, scope){
- el = Ext.getDom(el);
- var id = getId(el),
- es = Ext.elCache[id].events,
- wfn;
-
- wfn = E.on(el, ename, wrap);
- es[ename] = es[ename] || [];
-
- /* 0 = Original Function,
- 1 = Event Manager Wrapped Function,
- 2 = Scope,
- 3 = Adapter Wrapped Function,
- 4 = Buffered Task
- */
- es[ename].push([fn, wrap, scope, wfn, task]);
+// private
+var unitPattern = /\d+(px|em|%|en|ex|pt|in|cm|mm|pc)$/i,
+ docEl;
- // this is a workaround for jQuery and should somehow be removed from Ext Core in the future
- // without breaking ExtJS.
+/**
+ * @private
+ */
- // workaround for jQuery
- if(el.addEventListener && ename == "mousewheel"){
- var args = ["DOMMouseScroll", wrap, false];
- el.addEventListener.apply(el, args);
- Ext.EventManager.addListener(WINDOW, 'unload', function(){
- el.removeEventListener.apply(el, args);
- });
+/**
+ * Retrieves Ext.Element objects.
+ * <p><b>This method does not retrieve {@link Ext.Component Component}s.</b> This method
+ * retrieves Ext.Element objects which encapsulate DOM elements. To retrieve a Component by
+ * its ID, use {@link Ext.ComponentMgr#get}.</p>
+ * <p>Uses simple caching to consistently return the same object. Automatically fixes if an
+ * object was recreated with the same id via AJAX or DOM.</p>
+ * @param {Mixed} el The id of the node, a DOM Node or an existing Element.
+ * @return {Element} The Element object (or null if no matching element was found)
+ * @static
+ * @member Ext.Element
+ * @method get
+ */
+El.get = function(el){
+ var ex,
+ elm,
+ id;
+ if(!el){ return null; }
+ if (typeof el == "string") { // element id
+ if (!(elm = DOC.getElementById(el))) {
+ return null;
}
-
- // fix stopped mousedowns on the document
- if(el == DOC && ename == "mousedown"){
- Ext.EventManager.stoppedMouseDownEvent.addListener(wrap);
+ if (EC[el] && EC[el].el) {
+ ex = EC[el].el;
+ ex.dom = elm;
+ } else {
+ ex = El.addToCache(new El(elm));
}
- };
-
- function doScrollChk(){
- /* Notes:
- 'doScroll' will NOT work in a IFRAME/FRAMESET.
- The method succeeds but, a DOM query done immediately after -- FAILS.
- */
- if(window != top){
- return false;
+ return ex;
+ } else if (el.tagName) { // dom element
+ if(!(id = el.id)){
+ id = Ext.id(el);
}
-
- try{
- DOC.documentElement.doScroll('left');
- }catch(e){
- return false;
+ if (EC[id] && EC[id].el) {
+ ex = EC[id].el;
+ ex.dom = el;
+ } else {
+ ex = El.addToCache(new El(el));
}
+ return ex;
+ } else if (el instanceof El) {
+ if(el != docEl){
+ // refresh dom element in case no longer valid,
+ // catch case where it hasn't been appended
- fireDocReady();
- return true;
- }
- /**
- * @return {Boolean} True if the document is in a 'complete' state (or was determined to
- * be true by other means). If false, the state is evaluated again until canceled.
- */
- function checkReadyState(e){
-
- if(Ext.isIE && doScrollChk()){
- return true;
+ // If an el instance is passed, don't pass to getElementById without some kind of id
+ if (Ext.isIE && (el.id == undefined || el.id == '')) {
+ el.dom = el.dom;
+ } else {
+ el.dom = DOC.getElementById(el.id) || el.dom;
+ }
}
- if(DOC.readyState == COMPLETE){
- fireDocReady();
- return true;
+ return el;
+ } else if(el.isComposite) {
+ return el;
+ } else if(Ext.isArray(el)) {
+ return El.select(el);
+ } else if(el == DOC) {
+ // create a bogus element object representing the document object
+ if(!docEl){
+ var f = function(){};
+ f.prototype = El.prototype;
+ docEl = new f();
+ docEl.dom = DOC;
}
- docReadyState || (docReadyProcId = setTimeout(arguments.callee, 2));
- return false;
+ return docEl;
}
+ return null;
+};
- var styles;
- function checkStyleSheets(e){
- styles || (styles = Ext.query('style, link[rel=stylesheet]'));
- if(styles.length == DOC.styleSheets.length){
- fireDocReady();
- return true;
- }
- docReadyState || (docReadyProcId = setTimeout(arguments.callee, 2));
- return false;
- }
+El.addToCache = function(el, id){
+ id = id || el.id;
+ EC[id] = {
+ el: el,
+ data: {},
+ events: {}
+ };
+ return el;
+};
- function OperaDOMContentLoaded(e){
- DOC.removeEventListener(DOMCONTENTLOADED, arguments.callee, false);
- checkStyleSheets();
+// private method for getting and setting element data
+El.data = function(el, key, value){
+ el = El.get(el);
+ if (!el) {
+ return null;
}
+ var c = EC[el.id].data;
+ if(arguments.length == 2){
+ return c[key];
+ }else{
+ return (c[key] = value);
+ }
+};
- function fireDocReady(e){
- if(!docReadyState){
- docReadyState = true; //only attempt listener removal once
-
- if(docReadyProcId){
- clearTimeout(docReadyProcId);
- }
- if(DETECT_NATIVE) {
- DOC.removeEventListener(DOMCONTENTLOADED, fireDocReady, false);
- }
- if(Ext.isIE && checkReadyState.bindIE){ //was this was actually set ??
- DOC.detachEvent('onreadystatechange', checkReadyState);
- }
- E.un(WINDOW, "load", arguments.callee);
- }
- if(docReadyEvent && !Ext.isReady){
- Ext.isReady = true;
- docReadyEvent.fire();
- docReadyEvent.listeners = [];
- }
-
- };
-
- function initDocReady(){
- docReadyEvent || (docReadyEvent = new Ext.util.Event());
- if (DETECT_NATIVE) {
- DOC.addEventListener(DOMCONTENTLOADED, fireDocReady, false);
- }
- /*
- * Handle additional (exceptional) detection strategies here
- */
- if (Ext.isIE){
- //Use readystatechange as a backup AND primary detection mechanism for a FRAME/IFRAME
- //See if page is already loaded
- if(!checkReadyState()){
- checkReadyState.bindIE = true;
- DOC.attachEvent('onreadystatechange', checkReadyState);
- }
-
- }else if(Ext.isOpera ){
- /* Notes:
- Opera needs special treatment needed here because CSS rules are NOT QUITE
- available after DOMContentLoaded is raised.
- */
-
- //See if page is already loaded and all styleSheets are in place
- (DOC.readyState == COMPLETE && checkStyleSheets()) ||
- DOC.addEventListener(DOMCONTENTLOADED, OperaDOMContentLoaded, false);
-
- }else if (Ext.isWebKit){
- //Fallback for older Webkits without DOMCONTENTLOADED support
- checkReadyState();
- }
- // no matter what, make sure it fires on load
- E.on(WINDOW, "load", fireDocReady);
- };
-
- function createTargeted(h, o){
- return function(){
- var args = Ext.toArray(arguments);
- if(o.target == Ext.EventObject.setEvent(args[0]).target){
- h.apply(this, args);
- }
- };
- };
-
- function createBuffered(h, o, task){
- return function(e){
- // create new event object impl so new events don't wipe out properties
- task.delay(o.buffer, h, null, [new Ext.EventObjectImpl(e)]);
- };
- };
-
- function createSingle(h, el, ename, fn, scope){
- return function(e){
- Ext.EventManager.removeListener(el, ename, fn, scope);
- h(e);
- };
- };
-
- function createDelayed(h, o, fn){
- return function(e){
- var task = new Ext.util.DelayedTask(h);
- if(!fn.tasks) {
- fn.tasks = [];
- }
- fn.tasks.push(task);
- task.delay(o.delay || 10, h, null, [new Ext.EventObjectImpl(e)]);
- };
- };
-
- function listen(element, ename, opt, fn, scope){
- var o = (!opt || typeof opt == "boolean") ? {} : opt,
- el = Ext.getDom(element), task;
-
- fn = fn || o.fn;
- scope = scope || o.scope;
+// private
+// Garbage collection - uncache elements/purge listeners on orphaned elements
+// so we don't hold a reference and cause the browser to retain them
+function garbageCollect(){
+ if(!Ext.enableGarbageCollector){
+ clearInterval(El.collectorThreadId);
+ } else {
+ var eid,
+ el,
+ d,
+ o;
- if(!el){
- throw "Error listening for \"" + ename + '\". Element "' + element + '" doesn\'t exist.';
- }
- function h(e){
- // prevent errors while unload occurring
- if(!Ext){// !window[xname]){ ==> can't we do this?
- return;
+ for(eid in EC){
+ o = EC[eid];
+ if(o.skipGC){
+ continue;
}
- e = Ext.EventObject.setEvent(e);
- var t;
- if (o.delegate) {
- if(!(t = e.getTarget(o.delegate, el))){
- return;
+ el = o.el;
+ d = el.dom;
+ // -------------------------------------------------------
+ // Determining what is garbage:
+ // -------------------------------------------------------
+ // !d
+ // dom node is null, definitely garbage
+ // -------------------------------------------------------
+ // !d.parentNode
+ // no parentNode == direct orphan, definitely garbage
+ // -------------------------------------------------------
+ // !d.offsetParent && !document.getElementById(eid)
+ // display none elements have no offsetParent so we will
+ // also try to look it up by it's id. However, check
+ // offsetParent first so we don't do unneeded lookups.
+ // This enables collection of elements that are not orphans
+ // directly, but somewhere up the line they have an orphan
+ // parent.
+ // -------------------------------------------------------
+ if(!d || !d.parentNode || (!d.offsetParent && !DOC.getElementById(eid))){
+ if(Ext.enableListenerCollection){
+ Ext.EventManager.removeAll(d);
}
- } else {
- t = e.target;
- }
- if (o.stopEvent) {
- e.stopEvent();
- }
- if (o.preventDefault) {
- e.preventDefault();
- }
- if (o.stopPropagation) {
- e.stopPropagation();
- }
- if (o.normalized) {
- e = e.browserEvent;
+ delete EC[eid];
}
-
- fn.call(scope || el, e, t, o);
- };
- if(o.target){
- h = createTargeted(h, o);
- }
- if(o.delay){
- h = createDelayed(h, o, fn);
- }
- if(o.single){
- h = createSingle(h, el, ename, fn, scope);
- }
- if(o.buffer){
- task = new Ext.util.DelayedTask(h);
- h = createBuffered(h, o, task);
}
-
- addListener(el, ename, fn, task, h, scope);
- return h;
- };
-
- var pub = {
- /**
- * Appends an event handler to an element. The shorthand version {@link #on} is equivalent. Typically you will
- * use {@link Ext.Element#addListener} directly on an Element in favor of calling this version.
- * @param {String/HTMLElement} el The html element or id to assign the event handler to.
- * @param {String} eventName The name of the event to listen for.
- * @param {Function} handler The handler function the event invokes. This function is passed
- * the following parameters:<ul>
- * <li>evt : EventObject<div class="sub-desc">The {@link Ext.EventObject EventObject} describing the event.</div></li>
- * <li>t : Element<div class="sub-desc">The {@link Ext.Element Element} which was the target of the event.
- * Note that this may be filtered by using the <tt>delegate</tt> option.</div></li>
- * <li>o : Object<div class="sub-desc">The options object from the addListener call.</div></li>
- * </ul>
- * @param {Object} scope (optional) The scope (<b><code>this</code></b> reference) in which the handler function is executed. <b>Defaults to the Element</b>.
- * @param {Object} options (optional) An object containing handler configuration properties.
- * This may contain any of the following properties:<ul>
- * <li>scope : Object<div class="sub-desc">The scope (<b><code>this</code></b> reference) in which the handler function is executed. <b>Defaults to the Element</b>.</div></li>
- * <li>delegate : String<div class="sub-desc">A simple selector to filter the target or look for a descendant of the target</div></li>
- * <li>stopEvent : Boolean<div class="sub-desc">True to stop the event. That is stop propagation, and prevent the default action.</div></li>
- * <li>preventDefault : Boolean<div class="sub-desc">True to prevent the default action</div></li>
- * <li>stopPropagation : Boolean<div class="sub-desc">True to prevent event propagation</div></li>
- * <li>normalized : Boolean<div class="sub-desc">False to pass a browser event to the handler function instead of an Ext.EventObject</div></li>
- * <li>delay : Number<div class="sub-desc">The number of milliseconds to delay the invocation of the handler after te event fires.</div></li>
- * <li>single : Boolean<div class="sub-desc">True to add a handler to handle just the next firing of the event, and then remove itself.</div></li>
- * <li>buffer : Number<div class="sub-desc">Causes the handler to be scheduled to run in an {@link Ext.util.DelayedTask} delayed
- * by the specified number of milliseconds. If the event fires again within that time, the original
- * handler is <em>not</em> invoked, but the new handler is scheduled in its place.</div></li>
- * <li>target : Element<div class="sub-desc">Only call the handler if the event was fired on the target Element, <i>not</i> if the event was bubbled up from a child node.</div></li>
- * </ul><br>
- * <p>See {@link Ext.Element#addListener} for examples of how to use these options.</p>
- */
- addListener : function(element, eventName, fn, scope, options){
- if(typeof eventName == 'object'){
- var o = eventName, e, val;
- for(e in o){
- val = o[e];
- if(!propRe.test(e)){
- if(Ext.isFunction(val)){
- // shared options
- listen(element, e, o, val, o.scope);
- }else{
- // individual options
- listen(element, e, val);
- }
- }
- }
- } else {
- listen(element, eventName, options, fn, scope);
+ // Cleanup IE Object leaks
+ if (Ext.isIE) {
+ var t = {};
+ for (eid in EC) {
+ t[eid] = EC[eid];
}
- },
-
- /**
- * Removes an event handler from an element. The shorthand version {@link #un} is equivalent. Typically
- * you will use {@link Ext.Element#removeListener} directly on an Element in favor of calling this version.
- * @param {String/HTMLElement} el The id or html element from which to remove the listener.
- * @param {String} eventName The name of the event.
- * @param {Function} fn The handler function to remove. <b>This must be a reference to the function passed into the {@link #addListener} call.</b>
- * @param {Object} scope If a scope (<b><code>this</code></b> reference) was specified when the listener was added,
- * then this must refer to the same object.
- */
- removeListener : function(el, eventName, fn, scope){
- el = Ext.getDom(el);
- var id = getId(el),
- f = el && (Ext.elCache[id].events)[eventName] || [],
- wrap, i, l, k, len, fnc;
-
- for (i = 0, len = f.length; i < len; i++) {
+ EC = Ext.elCache = t;
+ }
+ }
+}
+El.collectorThreadId = setInterval(garbageCollect, 30000);
- /* 0 = Original Function,
- 1 = Event Manager Wrapped Function,
- 2 = Scope,
- 3 = Adapter Wrapped Function,
- 4 = Buffered Task
- */
- if (Ext.isArray(fnc = f[i]) && fnc[0] == fn && (!scope || fnc[2] == scope)) {
- if(fnc[4]) {
- fnc[4].cancel();
- }
- k = fn.tasks && fn.tasks.length;
- if(k) {
- while(k--) {
- fn.tasks[k].cancel();
- }
- delete fn.tasks;
- }
- wrap = fnc[1];
- E.un(el, eventName, E.extAdapter ? fnc[3] : wrap);
+var flyFn = function(){};
+flyFn.prototype = El.prototype;
- // jQuery workaround that should be removed from Ext Core
- if(wrap && el.addEventListener && eventName == "mousewheel"){
- el.removeEventListener("DOMMouseScroll", wrap, false);
- }
+// dom is optional
+El.Flyweight = function(dom){
+ this.dom = dom;
+};
- // fix stopped mousedowns on the document
- if(wrap && el == DOC && eventName == "mousedown"){
- Ext.EventManager.stoppedMouseDownEvent.removeListener(wrap);
- }
+El.Flyweight.prototype = new flyFn();
+El.Flyweight.prototype.isFlyweight = true;
+El._flyweights = {};
- f.splice(i, 1);
- if (f.length === 0) {
- delete Ext.elCache[id].events[eventName];
- }
- for (k in Ext.elCache[id].events) {
- return false;
- }
- Ext.elCache[id].events = {};
- return false;
- }
- }
- },
+/**
+ * <p>Gets the globally shared flyweight Element, with the passed node as the active element. Do not store a reference to this element -
+ * the dom node can be overwritten by other code. Shorthand of {@link Ext.Element#fly}</p>
+ * <p>Use this to make one-time references to DOM elements which are not going to be accessed again either by
+ * application code, or by Ext's classes. If accessing an element which will be processed regularly, then {@link Ext#get}
+ * will be more appropriate to take advantage of the caching provided by the Ext.Element class.</p>
+ * @param {String/HTMLElement} el The dom node or id
+ * @param {String} named (optional) Allows for creation of named reusable flyweights to prevent conflicts
+ * (e.g. internally Ext uses "_global")
+ * @return {Element} The shared Element object (or null if no matching element was found)
+ * @member Ext.Element
+ * @method fly
+ */
+El.fly = function(el, named){
+ var ret = null;
+ named = named || '_global';
- /**
- * Removes all event handers from an element. Typically you will use {@link Ext.Element#removeAllListeners}
- * directly on an Element in favor of calling this version.
- * @param {String/HTMLElement} el The id or html element from which to remove all event handlers.
- */
- removeAll : function(el){
- el = Ext.getDom(el);
- var id = getId(el),
- ec = Ext.elCache[id] || {},
- es = ec.events || {},
- f, i, len, ename, fn, k, wrap;
+ if (el = Ext.getDom(el)) {
+ (El._flyweights[named] = El._flyweights[named] || new El.Flyweight()).dom = el;
+ ret = El._flyweights[named];
+ }
+ return ret;
+};
- for(ename in es){
- if(es.hasOwnProperty(ename)){
- f = es[ename];
- /* 0 = Original Function,
- 1 = Event Manager Wrapped Function,
- 2 = Scope,
- 3 = Adapter Wrapped Function,
- 4 = Buffered Task
- */
- for (i = 0, len = f.length; i < len; i++) {
- fn = f[i];
- if(fn[4]) {
- fn[4].cancel();
- }
- if(fn[0].tasks && (k = fn[0].tasks.length)) {
- while(k--) {
- fn[0].tasks[k].cancel();
- }
- delete fn.tasks;
- }
- wrap = fn[1];
- E.un(el, ename, E.extAdapter ? fn[3] : wrap);
+/**
+ * Retrieves Ext.Element objects.
+ * <p><b>This method does not retrieve {@link Ext.Component Component}s.</b> This method
+ * retrieves Ext.Element objects which encapsulate DOM elements. To retrieve a Component by
+ * its ID, use {@link Ext.ComponentMgr#get}.</p>
+ * <p>Uses simple caching to consistently return the same object. Automatically fixes if an
+ * object was recreated with the same id via AJAX or DOM.</p>
+ * Shorthand of {@link Ext.Element#get}
+ * @param {Mixed} el The id of the node, a DOM Node or an existing Element.
+ * @return {Element} The Element object (or null if no matching element was found)
+ * @member Ext
+ * @method get
+ */
+Ext.get = El.get;
- // jQuery workaround that should be removed from Ext Core
- if(el.addEventListener && wrap && ename == "mousewheel"){
- el.removeEventListener("DOMMouseScroll", wrap, false);
- }
+/**
+ * <p>Gets the globally shared flyweight Element, with the passed node as the active element. Do not store a reference to this element -
+ * the dom node can be overwritten by other code. Shorthand of {@link Ext.Element#fly}</p>
+ * <p>Use this to make one-time references to DOM elements which are not going to be accessed again either by
+ * application code, or by Ext's classes. If accessing an element which will be processed regularly, then {@link Ext#get}
+ * will be more appropriate to take advantage of the caching provided by the Ext.Element class.</p>
+ * @param {String/HTMLElement} el The dom node or id
+ * @param {String} named (optional) Allows for creation of named reusable flyweights to prevent conflicts
+ * (e.g. internally Ext uses "_global")
+ * @return {Element} The shared Element object (or null if no matching element was found)
+ * @member Ext
+ * @method fly
+ */
+Ext.fly = El.fly;
- // fix stopped mousedowns on the document
- if(wrap && el == DOC && ename == "mousedown"){
- Ext.EventManager.stoppedMouseDownEvent.removeListener(wrap);
- }
- }
- }
- }
- if (Ext.elCache[id]) {
- Ext.elCache[id].events = {};
- }
- },
+// speedy lookup for elements never to box adjust
+var noBoxAdjust = Ext.isStrict ? {
+ select:1
+} : {
+ input:1, select:1, textarea:1
+};
+if(Ext.isIE || Ext.isGecko){
+ noBoxAdjust['button'] = 1;
+}
- getListeners : function(el, eventName) {
- el = Ext.getDom(el);
- var id = getId(el),
- ec = Ext.elCache[id] || {},
- es = ec.events || {},
- results = [];
- if (es && es[eventName]) {
- return es[eventName];
- } else {
+})();
+/**
+ * @class Ext.Element
+ */
+Ext.Element.addMethods(function(){
+ var PARENTNODE = 'parentNode',
+ NEXTSIBLING = 'nextSibling',
+ PREVIOUSSIBLING = 'previousSibling',
+ DQ = Ext.DomQuery,
+ GET = Ext.get;
+
+ return {
+ /**
+ * Looks at this node and then at parent nodes for a match of the passed simple selector (e.g. div.some-class or span:first-child)
+ * @param {String} selector The simple selector to test
+ * @param {Number/Mixed} maxDepth (optional) The max depth to search as a number or element (defaults to 50 || document.body)
+ * @param {Boolean} returnEl (optional) True to return a Ext.Element object instead of DOM node
+ * @return {HTMLElement} The matching DOM node (or null if no match was found)
+ */
+ findParent : function(simpleSelector, maxDepth, returnEl){
+ var p = this.dom,
+ b = document.body,
+ depth = 0,
+ stopEl;
+ if(Ext.isGecko && Object.prototype.toString.call(p) == '[object XULElement]') {
return null;
}
- },
-
- purgeElement : function(el, recurse, eventName) {
- el = Ext.getDom(el);
- var id = getId(el),
- ec = Ext.elCache[id] || {},
- es = ec.events || {},
- i, f, len;
- if (eventName) {
- if (es && es.hasOwnProperty(eventName)) {
- f = es[eventName];
- for (i = 0, len = f.length; i < len; i++) {
- Ext.EventManager.removeListener(el, eventName, f[i][0]);
- }
- }
- } else {
- Ext.EventManager.removeAll(el);
- }
- if (recurse && el && el.childNodes) {
- for (i = 0, len = el.childNodes.length; i < len; i++) {
- Ext.EventManager.purgeElement(el.childNodes[i], recurse, eventName);
- }
- }
- },
-
- _unload : function() {
- var el;
- for (el in Ext.elCache) {
- Ext.EventManager.removeAll(el);
- }
- delete Ext.elCache;
- delete Ext.Element._flyweights;
-
- // Abort any outstanding Ajax requests
- var c,
- conn,
- tid,
- ajax = Ext.lib.Ajax;
- (typeof ajax.conn == 'object') ? conn = ajax.conn : conn = {};
- for (tid in conn) {
- c = conn[tid];
- if (c) {
- ajax.abort({conn: c, tId: tid});
- }
- }
- },
- /**
- * Adds a listener to be notified when the document is ready (before onload and before images are loaded). Can be
- * accessed shorthanded as Ext.onReady().
- * @param {Function} fn The method the event invokes.
- * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the handler function executes. Defaults to the browser window.
- * @param {boolean} options (optional) Options object as passed to {@link Ext.Element#addListener}. It is recommended that the options
- * <code>{single: true}</code> be used so that the handler is removed on first invocation.
- */
- onDocumentReady : function(fn, scope, options){
- if(Ext.isReady){ // if it already fired or document.body is present
- docReadyEvent || (docReadyEvent = new Ext.util.Event());
- docReadyEvent.addListener(fn, scope, options);
- docReadyEvent.fire();
- docReadyEvent.listeners = [];
- }else{
- if(!docReadyEvent){
- initDocReady();
- }
- options = options || {};
- options.delay = options.delay || 1;
- docReadyEvent.addListener(fn, scope, options);
- }
- },
-
- /**
- * Forces a document ready state transition for the framework. Used when Ext is loaded
- * into a DOM structure AFTER initial page load (Google API or other dynamic load scenario.
- * Any pending 'onDocumentReady' handlers will be fired (if not already handled).
- */
- fireDocReady : fireDocReady
+ maxDepth = maxDepth || 50;
+ if (isNaN(maxDepth)) {
+ stopEl = Ext.getDom(maxDepth);
+ maxDepth = Number.MAX_VALUE;
+ }
+ while(p && p.nodeType == 1 && depth < maxDepth && p != b && p != stopEl){
+ if(DQ.is(p, simpleSelector)){
+ return returnEl ? GET(p) : p;
+ }
+ depth++;
+ p = p.parentNode;
+ }
+ return null;
+ },
+
+ /**
+ * Looks at parent nodes for a match of the passed simple selector (e.g. div.some-class or span:first-child)
+ * @param {String} selector The simple selector to test
+ * @param {Number/Mixed} maxDepth (optional) The max depth to
+ search as a number or element (defaults to 10 || document.body)
+ * @param {Boolean} returnEl (optional) True to return a Ext.Element object instead of DOM node
+ * @return {HTMLElement} The matching DOM node (or null if no match was found)
+ */
+ findParentNode : function(simpleSelector, maxDepth, returnEl){
+ var p = Ext.fly(this.dom.parentNode, '_internal');
+ return p ? p.findParent(simpleSelector, maxDepth, returnEl) : null;
+ },
+
+ /**
+ * Walks up the dom looking for a parent node that matches the passed simple selector (e.g. div.some-class or span:first-child).
+ * This is a shortcut for findParentNode() that always returns an Ext.Element.
+ * @param {String} selector The simple selector to test
+ * @param {Number/Mixed} maxDepth (optional) The max depth to
+ search as a number or element (defaults to 10 || document.body)
+ * @return {Ext.Element} The matching DOM node (or null if no match was found)
+ */
+ up : function(simpleSelector, maxDepth){
+ return this.findParentNode(simpleSelector, maxDepth, true);
+ },
+
+ /**
+ * 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
+ * @return {CompositeElement/CompositeElementLite} The composite element
+ */
+ select : function(selector){
+ return Ext.Element.select(selector, this.dom);
+ },
+
+ /**
+ * Selects child nodes based on the passed CSS selector (the selector should not contain an id).
+ * @param {String} selector The CSS selector
+ * @return {Array} An array of the matched nodes
+ */
+ query : function(selector){
+ return DQ.select(selector, this.dom);
+ },
+
+ /**
+ * Selects a single child at any depth below this element based on the passed CSS selector (the selector should not contain an id).
+ * @param {String} selector The CSS selector
+ * @param {Boolean} returnDom (optional) True to return the DOM node instead of Ext.Element (defaults to false)
+ * @return {HTMLElement/Ext.Element} The child Ext.Element (or DOM node if returnDom = true)
+ */
+ child : function(selector, returnDom){
+ var n = DQ.selectNode(selector, this.dom);
+ return returnDom ? n : GET(n);
+ },
+
+ /**
+ * Selects a single *direct* child based on the passed CSS selector (the selector should not contain an id).
+ * @param {String} selector The CSS selector
+ * @param {Boolean} returnDom (optional) True to return the DOM node instead of Ext.Element (defaults to false)
+ * @return {HTMLElement/Ext.Element} The child Ext.Element (or DOM node if returnDom = true)
+ */
+ down : function(selector, returnDom){
+ var n = DQ.selectNode(" > " + selector, this.dom);
+ return returnDom ? n : GET(n);
+ },
+
+ /**
+ * Gets the parent node for this element, optionally chaining up trying to match a selector
+ * @param {String} selector (optional) Find a parent node that matches the passed simple selector
+ * @param {Boolean} returnDom (optional) True to return a raw dom node instead of an Ext.Element
+ * @return {Ext.Element/HTMLElement} The parent node or null
+ */
+ parent : function(selector, returnDom){
+ return this.matchNode(PARENTNODE, PARENTNODE, selector, returnDom);
+ },
+
+ /**
+ * Gets the next sibling, skipping text nodes
+ * @param {String} selector (optional) Find the next sibling that matches the passed simple selector
+ * @param {Boolean} returnDom (optional) True to return a raw dom node instead of an Ext.Element
+ * @return {Ext.Element/HTMLElement} The next sibling or null
+ */
+ next : function(selector, returnDom){
+ return this.matchNode(NEXTSIBLING, NEXTSIBLING, selector, returnDom);
+ },
+
+ /**
+ * Gets the previous sibling, skipping text nodes
+ * @param {String} selector (optional) Find the previous sibling that matches the passed simple selector
+ * @param {Boolean} returnDom (optional) True to return a raw dom node instead of an Ext.Element
+ * @return {Ext.Element/HTMLElement} The previous sibling or null
+ */
+ prev : function(selector, returnDom){
+ return this.matchNode(PREVIOUSSIBLING, PREVIOUSSIBLING, selector, returnDom);
+ },
+
+
+ /**
+ * Gets the first child, skipping text nodes
+ * @param {String} selector (optional) Find the next sibling that matches the passed simple selector
+ * @param {Boolean} returnDom (optional) True to return a raw dom node instead of an Ext.Element
+ * @return {Ext.Element/HTMLElement} The first child or null
+ */
+ first : function(selector, returnDom){
+ return this.matchNode(NEXTSIBLING, 'firstChild', selector, returnDom);
+ },
+
+ /**
+ * Gets the last child, skipping text nodes
+ * @param {String} selector (optional) Find the previous sibling that matches the passed simple selector
+ * @param {Boolean} returnDom (optional) True to return a raw dom node instead of an Ext.Element
+ * @return {Ext.Element/HTMLElement} The last child or null
+ */
+ last : function(selector, returnDom){
+ return this.matchNode(PREVIOUSSIBLING, 'lastChild', selector, returnDom);
+ },
+
+ matchNode : function(dir, start, selector, returnDom){
+ var n = this.dom[start];
+ while(n){
+ if(n.nodeType == 1 && (!selector || DQ.is(n, selector))){
+ return !returnDom ? GET(n) : n;
+ }
+ n = n[dir];
+ }
+ return null;
+ }
};
- /**
- * Appends an event handler to an element. Shorthand for {@link #addListener}.
- * @param {String/HTMLElement} el The html element or id to assign the event handler to
- * @param {String} eventName The name of the event to listen for.
- * @param {Function} handler The handler function the event invokes.
- * @param {Object} scope (optional) (<code>this</code> reference) in which the handler function executes. <b>Defaults to the Element</b>.
- * @param {Object} options (optional) An object containing standard {@link #addListener} options
- * @member Ext.EventManager
- * @method on
- */
- pub.on = pub.addListener;
- /**
- * Removes an event handler from an element. Shorthand for {@link #removeListener}.
- * @param {String/HTMLElement} el The id or html element from which to remove the listener.
- * @param {String} eventName The name of the event.
- * @param {Function} fn The handler function to remove. <b>This must be a reference to the function passed into the {@link #on} call.</b>
- * @param {Object} scope If a scope (<b><code>this</code></b> reference) was specified when the listener was added,
- * then this must refer to the same object.
- * @member Ext.EventManager
- * @method un
- */
- pub.un = pub.removeListener;
-
- pub.stoppedMouseDownEvent = new Ext.util.Event();
- return pub;
-}();
-/**
- * Adds a listener to be notified when the document is ready (before onload and before images are loaded). Shorthand of {@link Ext.EventManager#onDocumentReady}.
- * @param {Function} fn The method the event invokes.
- * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the handler function executes. Defaults to the browser window.
- * @param {boolean} options (optional) Options object as passed to {@link Ext.Element#addListener}. It is recommended that the options
- * <code>{single: true}</code> be used so that the handler is removed on first invocation.
- * @member Ext
- * @method onReady
+}());/**
+ * @class Ext.Element
*/
-Ext.onReady = Ext.EventManager.onDocumentReady;
-
-
-//Initialize doc classes
-(function(){
-
- var initExtCss = function(){
- // find the body element
- var bd = document.body || document.getElementsByTagName('body')[0];
- if(!bd){ return false; }
- var cls = [' ',
- Ext.isIE ? "ext-ie " + (Ext.isIE6 ? 'ext-ie6' : (Ext.isIE7 ? 'ext-ie7' : 'ext-ie8'))
- : Ext.isGecko ? "ext-gecko " + (Ext.isGecko2 ? 'ext-gecko2' : 'ext-gecko3')
- : Ext.isOpera ? "ext-opera"
- : Ext.isWebKit ? "ext-webkit" : ""];
-
- if(Ext.isSafari){
- cls.push("ext-safari " + (Ext.isSafari2 ? 'ext-safari2' : (Ext.isSafari3 ? 'ext-safari3' : 'ext-safari4')));
- }else if(Ext.isChrome){
- cls.push("ext-chrome");
- }
+Ext.Element.addMethods(
+function() {
+ var GETDOM = Ext.getDom,
+ GET = Ext.get,
+ DH = Ext.DomHelper;
+
+ return {
+ /**
+ * Appends the passed element(s) to this element
+ * @param {String/HTMLElement/Array/Element/CompositeElement} el
+ * @return {Ext.Element} this
+ */
+ appendChild: function(el){
+ return GET(el).appendTo(this);
+ },
+
+ /**
+ * Appends this element to the passed element
+ * @param {Mixed} el The new parent element
+ * @return {Ext.Element} this
+ */
+ appendTo: function(el){
+ GETDOM(el).appendChild(this.dom);
+ return this;
+ },
+
+ /**
+ * Inserts this element before the passed element in the DOM
+ * @param {Mixed} el The element before which this element will be inserted
+ * @return {Ext.Element} this
+ */
+ insertBefore: function(el){
+ (el = GETDOM(el)).parentNode.insertBefore(this.dom, el);
+ return this;
+ },
+
+ /**
+ * Inserts this element after the passed element in the DOM
+ * @param {Mixed} el The element to insert after
+ * @return {Ext.Element} this
+ */
+ insertAfter: function(el){
+ (el = GETDOM(el)).parentNode.insertBefore(this.dom, el.nextSibling);
+ return this;
+ },
+
+ /**
+ * Inserts (or creates) an element (or DomHelper config) as the first child of this element
+ * @param {Mixed/Object} el The id or element to insert or a DomHelper config to create and insert
+ * @return {Ext.Element} The new child
+ */
+ insertFirst: function(el, returnDom){
+ el = el || {};
+ if(el.nodeType || el.dom || typeof el == 'string'){ // element
+ el = GETDOM(el);
+ this.dom.insertBefore(el, this.dom.firstChild);
+ return !returnDom ? GET(el) : el;
+ }else{ // dh config
+ return this.createChild(el, this.dom.firstChild, returnDom);
+ }
+ },
+
+ /**
+ * Replaces the passed element with this element
+ * @param {Mixed} el The element to replace
+ * @return {Ext.Element} this
+ */
+ replace: function(el){
+ el = GET(el);
+ this.insertBefore(el);
+ el.remove();
+ return this;
+ },
+
+ /**
+ * Replaces this element with the passed element
+ * @param {Mixed/Object} el The new element or a DomHelper config of an element to create
+ * @return {Ext.Element} this
+ */
+ replaceWith: function(el){
+ var me = this;
+
+ if(el.nodeType || el.dom || typeof el == 'string'){
+ el = GETDOM(el);
+ me.dom.parentNode.insertBefore(el, me.dom);
+ }else{
+ el = DH.insertBefore(me.dom, el);
+ }
+
+ delete Ext.elCache[me.id];
+ Ext.removeNode(me.dom);
+ me.id = Ext.id(me.dom = el);
+ Ext.Element.addToCache(me.isFlyweight ? new Ext.Element(me.dom) : me);
+ return me;
+ },
+
+ /**
+ * Creates the passed DomHelper config and appends it to this element or optionally inserts it before the passed child element.
+ * @param {Object} config DomHelper element config object. If no tag is specified (e.g., {tag:'input'}) then a div will be
+ * automatically generated with the specified attributes.
+ * @param {HTMLElement} insertBefore (optional) a child element of this element
+ * @param {Boolean} returnDom (optional) true to return the dom node instead of creating an Element
+ * @return {Ext.Element} The new child element
+ */
+ createChild: function(config, insertBefore, returnDom){
+ config = config || {tag:'div'};
+ return insertBefore ?
+ DH.insertBefore(insertBefore, config, returnDom !== true) :
+ DH[!this.dom.firstChild ? 'overwrite' : 'append'](this.dom, config, returnDom !== true);
+ },
+
+ /**
+ * Creates and wraps this element with another element
+ * @param {Object} config (optional) DomHelper element config object for the wrapper element or null for an empty div
+ * @param {Boolean} returnDom (optional) True to return the raw DOM element instead of Ext.Element
+ * @return {HTMLElement/Element} The newly created wrapper element
+ */
+ wrap: function(config, returnDom){
+ var newEl = DH.insertBefore(this.dom, config || {tag: "div"}, !returnDom);
+ newEl.dom ? newEl.dom.appendChild(this.dom) : newEl.appendChild(this.dom);
+ return newEl;
+ },
+
+ /**
+ * Inserts an html fragment into this element
+ * @param {String} where Where to insert the html in relation to this element - beforeBegin, afterBegin, beforeEnd, afterEnd.
+ * @param {String} html The HTML fragment
+ * @param {Boolean} returnEl (optional) True to return an Ext.Element (defaults to false)
+ * @return {HTMLElement/Ext.Element} The inserted node (or nearest related if more than 1 inserted)
+ */
+ insertHtml : function(where, html, returnEl){
+ var el = DH.insertHtml(where, this.dom, html);
+ return returnEl ? Ext.get(el) : el;
+ }
+ };
+}());/**
+ * @class Ext.Element
+ */
+Ext.Element.addMethods(function(){
+ // local style camelizing for speed
+ var supports = Ext.supports,
+ propCache = {},
+ camelRe = /(-[a-z])/gi,
+ view = document.defaultView,
+ opacityRe = /alpha\(opacity=(.*)\)/i,
+ trimRe = /^\s+|\s+$/g,
+ EL = Ext.Element,
+ spacesRe = /\s+/,
+ wordsRe = /\w/g,
+ PADDING = "padding",
+ MARGIN = "margin",
+ BORDER = "border",
+ LEFT = "-left",
+ RIGHT = "-right",
+ TOP = "-top",
+ BOTTOM = "-bottom",
+ WIDTH = "-width",
+ MATH = Math,
+ HIDDEN = 'hidden',
+ ISCLIPPED = 'isClipped',
+ OVERFLOW = 'overflow',
+ OVERFLOWX = 'overflow-x',
+ OVERFLOWY = 'overflow-y',
+ ORIGINALCLIP = 'originalClip',
+ // special markup used throughout Ext when box wrapping elements
+ borders = {l: BORDER + LEFT + WIDTH, r: BORDER + RIGHT + WIDTH, t: BORDER + TOP + WIDTH, b: BORDER + BOTTOM + WIDTH},
+ paddings = {l: PADDING + LEFT, r: PADDING + RIGHT, t: PADDING + TOP, b: PADDING + BOTTOM},
+ margins = {l: MARGIN + LEFT, r: MARGIN + RIGHT, t: MARGIN + TOP, b: MARGIN + BOTTOM},
+ data = Ext.Element.data;
- if(Ext.isMac){
- cls.push("ext-mac");
- }
- if(Ext.isLinux){
- cls.push("ext-linux");
- }
- if(Ext.isStrict || Ext.isBorderBox){ // add to the parent to allow for selectors like ".ext-strict .ext-ie"
- var p = bd.parentNode;
- if(p){
- p.className += Ext.isStrict ? ' ext-strict' : ' ext-border-box';
- }
- }
- bd.className += cls.join(' ');
- return true;
+ // private
+ function camelFn(m, a) {
+ return a.charAt(1).toUpperCase();
}
- if(!initExtCss()){
- Ext.onReady(initExtCss);
+ function chkCache(prop) {
+ return propCache[prop] || (propCache[prop] = prop == 'float' ? (supports.cssFloat ? 'cssFloat' : 'styleFloat') : prop.replace(camelRe, camelFn));
}
-})();
-
-/**
- * @class Ext.EventObject
- * Just as {@link Ext.Element} wraps around a native DOM node, Ext.EventObject
- * wraps the browser's native event-object normalizing cross-browser differences,
- * such as which mouse button is clicked, keys pressed, mechanisms to stop
- * event-propagation along with a method to prevent default actions from taking place.
- * <p>For example:</p>
- * <pre><code>
-function handleClick(e, t){ // e is not a standard event object, it is a Ext.EventObject
- e.preventDefault();
- var target = e.getTarget(); // same as t (the target HTMLElement)
- ...
-}
-var myDiv = {@link Ext#get Ext.get}("myDiv"); // get reference to an {@link Ext.Element}
-myDiv.on( // 'on' is shorthand for addListener
- "click", // perform an action on click of myDiv
- handleClick // reference to the action handler
-);
-// other methods to do the same:
-Ext.EventManager.on("myDiv", 'click', handleClick);
-Ext.EventManager.addListener("myDiv", 'click', handleClick);
- </code></pre>
- * @singleton
- */
-Ext.EventObject = function(){
- var E = Ext.lib.Event,
- // safari keypress events for special keys return bad keycodes
- safariKeys = {
- 3 : 13, // enter
- 63234 : 37, // left
- 63235 : 39, // right
- 63232 : 38, // up
- 63233 : 40, // down
- 63276 : 33, // page up
- 63277 : 34, // page down
- 63272 : 46, // delete
- 63273 : 36, // home
- 63275 : 35 // end
+ return {
+ // private ==> used by Fx
+ adjustWidth : function(width) {
+ var me = this;
+ var isNum = (typeof width == "number");
+ if(isNum && me.autoBoxAdjust && !me.isBorderBox()){
+ width -= (me.getBorderWidth("lr") + me.getPadding("lr"));
+ }
+ return (isNum && width < 0) ? 0 : width;
},
- // normalize button clicks
- btnMap = Ext.isIE ? {1:0,4:1,2:2} :
- (Ext.isWebKit ? {1:0,2:1,3:2} : {0:0,1:1,2:2});
-
- Ext.EventObjectImpl = function(e){
- if(e){
- this.setEvent(e.browserEvent || e);
- }
- };
- Ext.EventObjectImpl.prototype = {
- /** @private */
- setEvent : function(e){
+ // private ==> used by Fx
+ adjustHeight : function(height) {
var me = this;
- if(e == me || (e && e.browserEvent)){ // already wrapped
- return e;
- }
- me.browserEvent = e;
- if(e){
- // normalize buttons
- me.button = e.button ? btnMap[e.button] : (e.which ? e.which - 1 : -1);
- if(e.type == 'click' && me.button == -1){
- me.button = 0;
- }
- me.type = e.type;
- me.shiftKey = e.shiftKey;
- // mac metaKey behaves like ctrlKey
- me.ctrlKey = e.ctrlKey || e.metaKey || false;
- me.altKey = e.altKey;
- // in getKey these will be normalized for the mac
- me.keyCode = e.keyCode;
- me.charCode = e.charCode;
- // cache the target for the delayed and or buffered events
- me.target = E.getTarget(e);
- // same for XY
- me.xy = E.getXY(e);
- }else{
- me.button = -1;
- me.shiftKey = false;
- me.ctrlKey = false;
- me.altKey = false;
- me.keyCode = 0;
- me.charCode = 0;
- me.target = null;
- me.xy = [0, 0];
+ var isNum = (typeof height == "number");
+ if(isNum && me.autoBoxAdjust && !me.isBorderBox()){
+ height -= (me.getBorderWidth("tb") + me.getPadding("tb"));
}
- return me;
+ return (isNum && height < 0) ? 0 : height;
},
+
/**
- * Stop the event (preventDefault and stopPropagation)
+ * Adds one or more CSS classes to the element. Duplicate classes are automatically filtered out.
+ * @param {String/Array} className The CSS class to add, or an array of classes
+ * @return {Ext.Element} this
*/
- stopEvent : function(){
- var me = this;
- if(me.browserEvent){
- if(me.browserEvent.type == 'mousedown'){
- Ext.EventManager.stoppedMouseDownEvent.fire(me);
+ addClass : function(className){
+ var me = this,
+ i,
+ len,
+ v,
+ cls = [];
+ // Separate case is for speed
+ if (!Ext.isArray(className)) {
+ if (typeof className == 'string' && !this.hasClass(className)) {
+ me.dom.className += " " + className;
+ }
+ }
+ else {
+ for (i = 0, len = className.length; i < len; i++) {
+ v = className[i];
+ if (typeof v == 'string' && (' ' + me.dom.className + ' ').indexOf(' ' + v + ' ') == -1) {
+ cls.push(v);
+ }
+ }
+ if (cls.length) {
+ me.dom.className += " " + cls.join(" ");
}
- E.stopEvent(me.browserEvent);
}
+ return me;
},
/**
- * Prevents the browsers default handling of the event.
+ * Removes one or more CSS classes from the element.
+ * @param {String/Array} className The CSS class to remove, or an array of classes
+ * @return {Ext.Element} this
*/
- preventDefault : function(){
- if(this.browserEvent){
- E.preventDefault(this.browserEvent);
+ removeClass : function(className){
+ var me = this,
+ i,
+ idx,
+ len,
+ cls,
+ elClasses;
+ if (!Ext.isArray(className)){
+ className = [className];
+ }
+ if (me.dom && me.dom.className) {
+ elClasses = me.dom.className.replace(trimRe, '').split(spacesRe);
+ for (i = 0, len = className.length; i < len; i++) {
+ cls = className[i];
+ if (typeof cls == 'string') {
+ cls = cls.replace(trimRe, '');
+ idx = elClasses.indexOf(cls);
+ if (idx != -1) {
+ elClasses.splice(idx, 1);
+ }
+ }
+ }
+ me.dom.className = elClasses.join(" ");
}
+ return me;
},
/**
- * Cancels bubbling of the event.
+ * Adds one or more CSS classes to this element and removes the same class(es) from all siblings.
+ * @param {String/Array} className The CSS class to add, or an array of classes
+ * @return {Ext.Element} this
*/
- stopPropagation : function(){
- var me = this;
- if(me.browserEvent){
- if(me.browserEvent.type == 'mousedown'){
- Ext.EventManager.stoppedMouseDownEvent.fire(me);
+ radioClass : function(className){
+ var cn = this.dom.parentNode.childNodes,
+ v,
+ i,
+ len;
+ className = Ext.isArray(className) ? className : [className];
+ for (i = 0, len = cn.length; i < len; i++) {
+ v = cn[i];
+ if (v && v.nodeType == 1) {
+ Ext.fly(v, '_internal').removeClass(className);
}
- E.stopPropagation(me.browserEvent);
- }
+ };
+ return this.addClass(className);
},
/**
- * Gets the character code for the event.
- * @return {Number}
+ * Toggles the specified CSS class on this element (removes it if it already exists, otherwise adds it).
+ * @param {String} className The CSS class to toggle
+ * @return {Ext.Element} this
*/
- getCharCode : function(){
- return this.charCode || this.keyCode;
+ toggleClass : function(className){
+ return this.hasClass(className) ? this.removeClass(className) : this.addClass(className);
},
/**
- * Returns a normalized keyCode for the event.
- * @return {Number} The key code
+ * Checks if the specified CSS class exists on this element's DOM node.
+ * @param {String} className The CSS class to check for
+ * @return {Boolean} True if the class exists, else false
*/
- getKey : function(){
- return this.normalizeKey(this.keyCode || this.charCode)
- },
-
- // private
- normalizeKey: function(k){
- return Ext.isSafari ? (safariKeys[k] || k) : k;
+ hasClass : function(className){
+ return className && (' '+this.dom.className+' ').indexOf(' '+className+' ') != -1;
},
/**
- * Gets the x coordinate of the event.
- * @return {Number}
+ * Replaces a CSS class on the element with another. If the old name does not exist, the new name will simply be added.
+ * @param {String} oldClassName The CSS class to replace
+ * @param {String} newClassName The replacement CSS class
+ * @return {Ext.Element} this
*/
- getPageX : function(){
- return this.xy[0];
+ replaceClass : function(oldClassName, newClassName){
+ return this.removeClass(oldClassName).addClass(newClassName);
},
- /**
- * Gets the y coordinate of the event.
- * @return {Number}
- */
- getPageY : function(){
- return this.xy[1];
+ isStyle : function(style, val) {
+ return this.getStyle(style) == val;
},
/**
- * Gets the page coordinates of the event.
- * @return {Array} The xy values like [x, y]
+ * Normalizes currentStyle and computedStyle.
+ * @param {String} property The style property whose value is returned.
+ * @return {String} The current value of the style property for this element.
*/
- getXY : function(){
- return this.xy;
- },
+ getStyle : function(){
+ return view && view.getComputedStyle ?
+ function(prop){
+ var el = this.dom,
+ v,
+ cs,
+ out,
+ display;
+
+ if(el == document){
+ return null;
+ }
+ prop = chkCache(prop);
+ out = (v = el.style[prop]) ? v :
+ (cs = view.getComputedStyle(el, "")) ? cs[prop] : null;
+
+ // Ignore cases when the margin is correctly reported as 0, the bug only shows
+ // numbers larger.
+ if(prop == 'marginRight' && out != '0px' && !supports.correctRightMargin){
+ display = el.style.display;
+ el.style.display = 'inline-block';
+ out = view.getComputedStyle(el, '').marginRight;
+ el.style.display = display;
+ }
+
+ if(prop == 'backgroundColor' && out == 'rgba(0, 0, 0, 0)' && !supports.correctTransparentColor){
+ out = 'transparent';
+ }
+ return out;
+ } :
+ function(prop){
+ var el = this.dom,
+ m,
+ cs;
+
+ if(el == document) return null;
+ if (prop == 'opacity') {
+ if (el.style.filter.match) {
+ if(m = el.style.filter.match(opacityRe)){
+ var fv = parseFloat(m[1]);
+ if(!isNaN(fv)){
+ return fv ? fv / 100 : 0;
+ }
+ }
+ }
+ return 1;
+ }
+ prop = chkCache(prop);
+ return el.style[prop] || ((cs = el.currentStyle) ? cs[prop] : null);
+ };
+ }(),
/**
- * Gets the target for the event.
- * @param {String} selector (optional) A simple selector to filter the target or look for an ancestor of the target
- * @param {Number/Mixed} maxDepth (optional) The max depth to
- search as a number or element (defaults to 10 || document.body)
- * @param {Boolean} returnEl (optional) True to return a Ext.Element object instead of DOM node
- * @return {HTMLelement}
+ * Return the CSS color for the specified CSS attribute. rgb, 3 digit (like #fff) and valid values
+ * are convert to standard 6 digit hex color.
+ * @param {String} attr The css attribute
+ * @param {String} defaultValue The default value to use when a valid color isn't found
+ * @param {String} prefix (optional) defaults to #. Use an empty string when working with
+ * color anims.
*/
- getTarget : function(selector, maxDepth, returnEl){
- return selector ? Ext.fly(this.target).findParent(selector, maxDepth, returnEl) : (returnEl ? Ext.get(this.target) : this.target);
+ getColor : function(attr, defaultValue, prefix){
+ var v = this.getStyle(attr),
+ color = (typeof prefix != 'undefined') ? prefix : '#',
+ h;
+
+ if(!v || (/transparent|inherit/.test(v))) {
+ return defaultValue;
+ }
+ if(/^r/.test(v)){
+ Ext.each(v.slice(4, v.length -1).split(','), function(s){
+ h = parseInt(s, 10);
+ color += (h < 16 ? '0' : '') + h.toString(16);
+ });
+ }else{
+ v = v.replace('#', '');
+ color += v.length == 3 ? v.replace(/^(\w)(\w)(\w)$/, '$1$1$2$2$3$3') : v;
+ }
+ return(color.length > 5 ? color.toLowerCase() : defaultValue);
},
/**
- * Gets the related target.
- * @return {HTMLElement}
+ * Wrapper for setting style properties, also takes single object parameter of multiple styles.
+ * @param {String/Object} property The style property to be set, or an object of multiple styles.
+ * @param {String} value (optional) The value to apply to the given property, or null if an object was passed.
+ * @return {Ext.Element} this
*/
- getRelatedTarget : function(){
- return this.browserEvent ? E.getRelatedTarget(this.browserEvent) : null;
+ setStyle : function(prop, value){
+ var tmp, style;
+
+ if (typeof prop != 'object') {
+ tmp = {};
+ tmp[prop] = value;
+ prop = tmp;
+ }
+ for (style in prop) {
+ value = prop[style];
+ style == 'opacity' ?
+ this.setOpacity(value) :
+ this.dom.style[chkCache(style)] = value;
+ }
+ return this;
},
/**
- * Normalizes mouse wheel delta across browsers
- * @return {Number} The delta
+ * Set the opacity of the element
+ * @param {Float} opacity The new opacity. 0 = transparent, .5 = 50% visibile, 1 = fully visible, etc
+ * @param {Boolean/Object} animate (optional) a standard Element animation config object or <tt>true</tt> for
+ * the default animation (<tt>{duration: .35, easing: 'easeIn'}</tt>)
+ * @return {Ext.Element} this
*/
- getWheelDelta : function(){
- var e = this.browserEvent;
- var delta = 0;
- if(e.wheelDelta){ /* IE/Opera. */
- delta = e.wheelDelta/120;
- }else if(e.detail){ /* Mozilla case. */
- delta = -e.detail/3;
+ setOpacity : function(opacity, animate){
+ var me = this,
+ s = me.dom.style;
+
+ if(!animate || !me.anim){
+ if(Ext.isIE){
+ var opac = opacity < 1 ? 'alpha(opacity=' + opacity * 100 + ')' : '',
+ val = s.filter.replace(opacityRe, '').replace(trimRe, '');
+
+ s.zoom = 1;
+ s.filter = val + (val.length > 0 ? ' ' : '') + opac;
+ }else{
+ s.opacity = opacity;
+ }
+ }else{
+ me.anim({opacity: {to: opacity}}, me.preanim(arguments, 1), null, .35, 'easeIn');
}
- return delta;
+ return me;
},
/**
- * Returns true if the target of this event is a child of el. Unless the allowEl parameter is set, it will return false if if the target is el.
- * Example usage:<pre><code>
- // Handle click on any child of an element
- Ext.getBody().on('click', function(e){
- if(e.within('some-el')){
- alert('Clicked on a child of some-el!');
- }
- });
-
- // Handle click directly on an element, ignoring clicks on child nodes
- Ext.getBody().on('click', function(e,t){
- if((t.id == 'some-el') && !e.within(t, true)){
- alert('Clicked directly on some-el!');
- }
- });
- </code></pre>
- * @param {Mixed} el The id, DOM element or Ext.Element to check
- * @param {Boolean} related (optional) true to test if the related target is within el instead of the target
- * @param {Boolean} allowEl {optional} true to also check if the passed element is the target or related target
- * @return {Boolean}
+ * Clears any opacity settings from this element. Required in some cases for IE.
+ * @return {Ext.Element} this
*/
- within : function(el, related, allowEl){
- if(el){
- var t = this[related ? "getRelatedTarget" : "getTarget"]();
- return t && ((allowEl ? (t == Ext.getDom(el)) : false) || Ext.fly(el).contains(t));
+ clearOpacity : function(){
+ var style = this.dom.style;
+ if(Ext.isIE){
+ if(!Ext.isEmpty(style.filter)){
+ style.filter = style.filter.replace(opacityRe, '').replace(trimRe, '');
+ }
+ }else{
+ style.opacity = style['-moz-opacity'] = style['-khtml-opacity'] = '';
}
- return false;
- }
- };
+ return this;
+ },
- return new Ext.EventObjectImpl();
-}();
-/**
-* @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)$/,
- 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);
+ /**
+ * Returns the offset height of the element
+ * @param {Boolean} contentHeight (optional) true to get the height minus borders and padding
+ * @return {Number} The element's height
+ */
+ getHeight : function(contentHeight){
+ var me = this,
+ dom = me.dom,
+ hidden = Ext.isIE && me.isStyle('display', 'none'),
+ h = MATH.max(dom.offsetHeight, hidden ? 0 : dom.clientHeight) || 0;
- return {
- // private
- doResizeEvent: function(){
- var h = D.getViewHeight(),
- w = D.getViewWidth();
+ h = !contentHeight ? h : h - me.getBorderWidth("tb") - me.getPadding("tb");
+ return h < 0 ? 0 : h;
+ },
- //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);
- }
- },
+ /**
+ * Returns the offset width of the element
+ * @param {Boolean} contentWidth (optional) true to get the width minus borders and padding
+ * @return {Number} The element's width
+ */
+ getWidth : function(contentWidth){
+ var me = this,
+ dom = me.dom,
+ hidden = Ext.isIE && me.isStyle('display', 'none'),
+ w = MATH.max(dom.offsetWidth, hidden ? 0 : dom.clientWidth) || 0;
+ w = !contentWidth ? w : w - me.getBorderWidth("lr") - me.getPadding("lr");
+ return w < 0 ? 0 : w;
+ },
- /**
- * 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);
- },
+ /**
+ * Set the width of this Element.
+ * @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 {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
+ * @return {Ext.Element} this
+ */
+ setWidth : function(width, animate){
+ var me = this;
+ width = me.adjustWidth(width);
+ !animate || !me.anim ?
+ me.dom.style.width = me.addUnits(width) :
+ me.anim({width : {to : width}}, me.preanim(arguments, 1));
+ return me;
+ },
- // exposed only to allow manual firing
- fireWindowResize : function(){
- if(resizeEvent){
- resizeTask.delay(100);
- }
- },
+ /**
+ * Set the height of this Element.
+ * <pre><code>
+// change the height to 200px and animate with default configuration
+Ext.fly('elementId').setHeight(200, true);
- /**
- * 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);
- },
+// change the height to 150px and animate with a custom configuration
+Ext.fly('elId').setHeight(150, {
+ duration : .5, // animation will have a duration of .5 seconds
+ // will change the content to "finished"
+ callback: function(){ this.{@link #update}("finished"); }
+});
+ * </code></pre>
+ * @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
+ */
+ setHeight : function(height, animate){
+ var me = this;
+ height = me.adjustHeight(height);
+ !animate || !me.anim ?
+ me.dom.style.height = me.addUnits(height) :
+ me.anim({height : {to : height}}, me.preanim(arguments, 1));
+ return me;
+ },
- /**
- * 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);
- }
- },
+ /**
+ * Gets the width of the border(s) for the specified side(s)
+ * @param {String} side Can be t, l, r, b or any combination of those to add multiple values. For example,
+ * passing <tt>'lr'</tt> would get the border <b><u>l</u></b>eft width + the border <b><u>r</u></b>ight width.
+ * @return {Number} The width of the sides passed added together
+ */
+ getBorderWidth : function(side){
+ return this.addStyles(side, borders);
+ },
- // private
- fireResize : function(){
- if(resizeEvent){
- resizeEvent.fire(D.getViewWidth(), D.getViewHeight());
- }
- },
+ /**
+ * Gets the width of the padding(s) for the specified side(s)
+ * @param {String} side Can be t, l, r, b or any combination of those to add multiple values. For example,
+ * passing <tt>'lr'</tt> would get the padding <b><u>l</u></b>eft + the padding <b><u>r</u></b>ight.
+ * @return {Number} The padding of the sides passed added together
+ */
+ getPadding : function(side){
+ return this.addStyles(side, paddings);
+ },
/**
- * The frequency, in milliseconds, to check for text resize events (defaults to 50)
- */
- textResizeInterval : 50,
+ * Store the current overflow setting and clip overflow on the element - use <tt>{@link #unclip}</tt> to remove
+ * @return {Ext.Element} this
+ */
+ clip : function(){
+ var me = this,
+ dom = me.dom;
- /**
- * Url used for onDocumentReady with using SSL (defaults to Ext.SSL_SECURE_URL)
- */
- ieDeferSrc : false,
+ if(!data(dom, ISCLIPPED)){
+ data(dom, ISCLIPPED, true);
+ data(dom, ORIGINALCLIP, {
+ o: me.getStyle(OVERFLOW),
+ x: me.getStyle(OVERFLOWX),
+ y: me.getStyle(OVERFLOWY)
+ });
+ me.setStyle(OVERFLOW, HIDDEN);
+ me.setStyle(OVERFLOWX, HIDDEN);
+ me.setStyle(OVERFLOWY, HIDDEN);
+ }
+ return me;
+ },
- // protected for use inside the framework
- // detects whether we should use keydown or keypress based on the browser.
- useKeydown: useKeydown
- };
-}());
+ /**
+ * Return clipping (overflow) to original clipping before <tt>{@link #clip}</tt> was called
+ * @return {Ext.Element} this
+ */
+ unclip : function(){
+ var me = this,
+ dom = me.dom;
-Ext.EventManager.on = Ext.EventManager.addListener;
+ if(data(dom, ISCLIPPED)){
+ data(dom, ISCLIPPED, false);
+ var o = data(dom, ORIGINALCLIP);
+ if(o.o){
+ me.setStyle(OVERFLOW, o.o);
+ }
+ if(o.x){
+ me.setStyle(OVERFLOWX, o.x);
+ }
+ if(o.y){
+ me.setStyle(OVERFLOWY, o.y);
+ }
+ }
+ return me;
+ },
+ // private
+ addStyles : function(sides, styles){
+ var ttlSize = 0,
+ sidesArr = sides.match(wordsRe),
+ side,
+ size,
+ i,
+ len = sidesArr.length;
+ for (i = 0; i < len; i++) {
+ side = sidesArr[i];
+ size = side && parseInt(this.getStyle(styles[side]), 10);
+ if (size) {
+ ttlSize += MATH.abs(size);
+ }
+ }
+ return ttlSize;
+ },
-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);
- }
-});/**
+ margins : margins
+ };
+}()
+);
+/**
* @class Ext.Element
- * <p>Encapsulates a DOM element, adding simple DOM manipulation facilities, normalizing for browser differences.</p>
- * <p>All instances of this class inherit the methods of {@link Ext.Fx} making visual effects easily available to all DOM elements.</p>
- * <p>Note that the events documented in this class are not Ext events, they encapsulate browser events. To
- * access the underlying browser event, see {@link Ext.EventObject#browserEvent}. Some older
- * browsers may not support the full range of events. Which events are supported is beyond the control of ExtJs.</p>
- * Usage:<br>
-<pre><code>
-// by id
-var el = Ext.get("my-div");
-
-// by DOM element reference
-var el = Ext.get(myDivElement);
-</code></pre>
- * <b>Animations</b><br />
- * <p>When an element is manipulated, by default there is no animation.</p>
- * <pre><code>
-var el = Ext.get("my-div");
-
-// no animation
-el.setWidth(100);
- * </code></pre>
- * <p>Many of the functions for manipulating an element have an optional "animate" parameter. This
- * parameter can be specified as boolean (<tt>true</tt>) for default animation effects.</p>
- * <pre><code>
-// default animation
-el.setWidth(100, true);
- * </code></pre>
- *
- * <p>To configure the effects, an object literal with animation options to use as the Element animation
- * configuration object can also be specified. Note that the supported Element animation configuration
- * options are a subset of the {@link Ext.Fx} animation options specific to Fx effects. The supported
- * Element animation configuration options are:</p>
-<pre>
-Option Default Description
---------- -------- ---------------------------------------------
-{@link Ext.Fx#duration duration} .35 The duration of the animation in seconds
-{@link Ext.Fx#easing easing} easeOut The easing method
-{@link Ext.Fx#callback callback} none A function to execute when the anim completes
-{@link Ext.Fx#scope scope} this The scope (this) of the callback function
-</pre>
- *
- * <pre><code>
-// Element animation options object
-var opt = {
- {@link Ext.Fx#duration duration}: 1,
- {@link Ext.Fx#easing easing}: 'elasticIn',
- {@link Ext.Fx#callback callback}: this.foo,
- {@link Ext.Fx#scope scope}: this
-};
-// animation with some options set
-el.setWidth(100, opt);
- * </code></pre>
- * <p>The Element animation object being used for the animation will be set on the options
- * object as "anim", which allows you to stop or manipulate the animation. Here is an example:</p>
- * <pre><code>
-// using the "anim" property to get the Anim object
-if(opt.anim.isAnimated()){
- opt.anim.stop();
-}
- * </code></pre>
- * <p>Also see the <tt>{@link #animate}</tt> method for another animation technique.</p>
- * <p><b> Composite (Collections of) Elements</b></p>
- * <p>For working with collections of Elements, see {@link Ext.CompositeElement}</p>
- * @constructor Create a new Element directly.
- * @param {String/HTMLElement} element
- * @param {Boolean} forceNew (optional) By default the constructor checks to see if there is already an instance of this element in the cache and if there is it returns the same instance. This will skip that check (useful for extending this class).
*/
(function(){
-var DOC = document;
+var D = Ext.lib.Dom,
+ LEFT = "left",
+ RIGHT = "right",
+ TOP = "top",
+ BOTTOM = "bottom",
+ POSITION = "position",
+ STATIC = "static",
+ RELATIVE = "relative",
+ AUTO = "auto",
+ ZINDEX = "z-index";
-Ext.Element = function(element, forceNew){
- var dom = typeof element == "string" ?
- DOC.getElementById(element) : element,
- id;
+Ext.Element.addMethods({
+ /**
+ * Gets the current X position of the element based on page coordinates. Element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
+ * @return {Number} The X position of the element
+ */
+ getX : function(){
+ return D.getX(this.dom);
+ },
- if(!dom) return null;
+ /**
+ * Gets the current Y position of the element based on page coordinates. Element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
+ * @return {Number} The Y position of the element
+ */
+ getY : function(){
+ return D.getY(this.dom);
+ },
- id = dom.id;
+ /**
+ * Gets the current position of the element based on page coordinates. Element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
+ * @return {Array} The XY position of the element
+ */
+ getXY : function(){
+ return D.getXY(this.dom);
+ },
- if(!forceNew && id && Ext.elCache[id]){ // element object already exists
- return Ext.elCache[id].el;
- }
+ /**
+ * Returns the offsets of this element from the passed element. Both element must be part of the DOM tree and not have display:none to have page coordinates.
+ * @param {Mixed} element The element to get the offsets from.
+ * @return {Array} The XY page offsets (e.g. [100, -200])
+ */
+ getOffsetsTo : function(el){
+ var o = this.getXY(),
+ e = Ext.fly(el, '_internal').getXY();
+ return [o[0]-e[0],o[1]-e[1]];
+ },
/**
- * The DOM element
- * @type HTMLElement
+ * Sets the X position of the element based on page coordinates. Element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
+ * @param {Number} The X position of the element
+ * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
+ * @return {Ext.Element} this
*/
- this.dom = dom;
+ setX : function(x, animate){
+ return this.setXY([x, this.getY()], this.animTest(arguments, animate, 1));
+ },
/**
- * The DOM element ID
- * @type String
+ * Sets the Y position of the element based on page coordinates. Element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
+ * @param {Number} The Y position of the element
+ * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
+ * @return {Ext.Element} this
*/
- this.id = id || Ext.id(dom);
-};
-
-var D = Ext.lib.Dom,
- DH = Ext.DomHelper,
- E = Ext.lib.Event,
- A = Ext.lib.Anim,
- El = Ext.Element,
- EC = Ext.elCache;
+ setY : function(y, animate){
+ return this.setXY([this.getX(), y], this.animTest(arguments, animate, 1));
+ },
-El.prototype = {
/**
- * Sets the passed attributes as attributes of this element (a style attribute can be a string, object or function)
- * @param {Object} o The object with the attributes
- * @param {Boolean} useSet (optional) false to override the default setAttribute to use expandos.
+ * Sets the element's left position directly using CSS style (instead of {@link #setX}).
+ * @param {String} left The left CSS property value
* @return {Ext.Element} this
*/
- set : function(o, useSet){
- var el = this.dom,
- attr,
- val,
- useSet = (useSet !== false) && !!el.setAttribute;
-
- for(attr in o){
- if (o.hasOwnProperty(attr)) {
- val = o[attr];
- if (attr == 'style') {
- DH.applyStyles(el, val);
- } else if (attr == 'cls') {
- el.className = val;
- } else if (useSet) {
- el.setAttribute(attr, val);
- } else {
- el[attr] = val;
- }
- }
- }
+ setLeft : function(left){
+ this.setStyle(LEFT, this.addUnits(left));
return this;
},
-// Mouse events
/**
- * @event click
- * Fires when a mouse click is detected within the element.
- * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
- * @param {HtmlElement} t The target of the event.
- * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ * Sets the element's top position directly using CSS style (instead of {@link #setY}).
+ * @param {String} top The top CSS property value
+ * @return {Ext.Element} this
*/
+ setTop : function(top){
+ this.setStyle(TOP, this.addUnits(top));
+ return this;
+ },
+
/**
- * @event contextmenu
- * Fires when a right click is detected within the element.
- * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
- * @param {HtmlElement} t The target of the event.
- * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ * Sets the element's CSS right style.
+ * @param {String} right The right CSS property value
+ * @return {Ext.Element} this
*/
+ setRight : function(right){
+ this.setStyle(RIGHT, this.addUnits(right));
+ return this;
+ },
+
/**
- * @event dblclick
- * Fires when a mouse double click is detected within the element.
- * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
- * @param {HtmlElement} t The target of the event.
- * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ * Sets the element's CSS bottom style.
+ * @param {String} bottom The bottom CSS property value
+ * @return {Ext.Element} this
*/
+ setBottom : function(bottom){
+ this.setStyle(BOTTOM, this.addUnits(bottom));
+ return this;
+ },
+
/**
- * @event mousedown
- * Fires when a mousedown is detected within the element.
- * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
- * @param {HtmlElement} t The target of the event.
- * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ * Sets the position of the element in page coordinates, regardless of how the element is positioned.
+ * The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
+ * @param {Array} pos Contains X & Y [x, y] values for new position (coordinates are page-based)
+ * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
+ * @return {Ext.Element} this
*/
+ setXY : function(pos, animate){
+ var me = this;
+ if(!animate || !me.anim){
+ D.setXY(me.dom, pos);
+ }else{
+ me.anim({points: {to: pos}}, me.preanim(arguments, 1), 'motion');
+ }
+ return me;
+ },
+
/**
- * @event mouseup
- * Fires when a mouseup is detected within the element.
- * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
- * @param {HtmlElement} t The target of the event.
- * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ * Sets the position of the element in page coordinates, regardless of how the element is positioned.
+ * The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
+ * @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 {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
+ * @return {Ext.Element} this
*/
+ setLocation : function(x, y, animate){
+ return this.setXY([x, y], this.animTest(arguments, animate, 2));
+ },
+
/**
- * @event mouseover
- * Fires when a mouseover is detected within the element.
- * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
- * @param {HtmlElement} t The target of the event.
- * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ * Sets the position of the element in page coordinates, regardless of how the element is positioned.
+ * The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
+ * @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 {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
+ * @return {Ext.Element} this
*/
+ moveTo : function(x, y, animate){
+ return this.setXY([x, y], this.animTest(arguments, animate, 2));
+ },
+
/**
- * @event mousemove
- * Fires when a mousemove is detected with the element.
- * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
- * @param {HtmlElement} t The target of the event.
- * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ * Gets the left X coordinate
+ * @param {Boolean} local True to get the local css position instead of page coordinate
+ * @return {Number}
*/
+ getLeft : function(local){
+ return !local ? this.getX() : parseInt(this.getStyle(LEFT), 10) || 0;
+ },
+
/**
- * @event mouseout
- * Fires when a mouseout is detected with the element.
- * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
- * @param {HtmlElement} t The target of the event.
- * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ * Gets the right X coordinate of the element (element X position + element width)
+ * @param {Boolean} local True to get the local css position instead of page coordinate
+ * @return {Number}
*/
+ getRight : function(local){
+ var me = this;
+ return !local ? me.getX() + me.getWidth() : (me.getLeft(true) + me.getWidth()) || 0;
+ },
+
/**
- * @event mouseenter
- * Fires when the mouse enters the element.
- * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
- * @param {HtmlElement} t The target of the event.
- * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ * Gets the top Y coordinate
+ * @param {Boolean} local True to get the local css position instead of page coordinate
+ * @return {Number}
*/
+ getTop : function(local) {
+ return !local ? this.getY() : parseInt(this.getStyle(TOP), 10) || 0;
+ },
+
/**
- * @event mouseleave
- * Fires when the mouse leaves the element.
- * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
- * @param {HtmlElement} t The target of the event.
- * @param {Object} o The options configuration passed to the {@link #addListener} call.
- */
-
-// Keyboard events
- /**
- * @event keypress
- * Fires when a keypress is detected within the element.
- * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
- * @param {HtmlElement} t The target of the event.
- * @param {Object} o The options configuration passed to the {@link #addListener} call.
- */
- /**
- * @event keydown
- * Fires when a keydown is detected within the element.
- * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
- * @param {HtmlElement} t The target of the event.
- * @param {Object} o The options configuration passed to the {@link #addListener} call.
- */
- /**
- * @event keyup
- * Fires when a keyup is detected within the element.
- * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
- * @param {HtmlElement} t The target of the event.
- * @param {Object} o The options configuration passed to the {@link #addListener} call.
- */
-
-
-// HTML frame/object events
- /**
- * @event load
- * Fires when the user agent finishes loading all content within the element. Only supported by window, frames, objects and images.
- * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
- * @param {HtmlElement} t The target of the event.
- * @param {Object} o The options configuration passed to the {@link #addListener} call.
- */
- /**
- * @event unload
- * Fires when the user agent removes all content from a window or frame. For elements, it fires when the target element or any of its content has been removed.
- * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
- * @param {HtmlElement} t The target of the event.
- * @param {Object} o The options configuration passed to the {@link #addListener} call.
- */
- /**
- * @event abort
- * Fires when an object/image is stopped from loading before completely loaded.
- * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
- * @param {HtmlElement} t The target of the event.
- * @param {Object} o The options configuration passed to the {@link #addListener} call.
- */
- /**
- * @event error
- * Fires when an object/image/frame cannot be loaded properly.
- * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
- * @param {HtmlElement} t The target of the event.
- * @param {Object} o The options configuration passed to the {@link #addListener} call.
- */
- /**
- * @event resize
- * Fires when a document view is resized.
- * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
- * @param {HtmlElement} t The target of the event.
- * @param {Object} o The options configuration passed to the {@link #addListener} call.
- */
- /**
- * @event scroll
- * Fires when a document view is scrolled.
- * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
- * @param {HtmlElement} t The target of the event.
- * @param {Object} o The options configuration passed to the {@link #addListener} call.
- */
-
-// Form events
- /**
- * @event select
- * Fires when a user selects some text in a text field, including input and textarea.
- * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
- * @param {HtmlElement} t The target of the event.
- * @param {Object} o The options configuration passed to the {@link #addListener} call.
- */
- /**
- * @event change
- * Fires when a control loses the input focus and its value has been modified since gaining focus.
- * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
- * @param {HtmlElement} t The target of the event.
- * @param {Object} o The options configuration passed to the {@link #addListener} call.
- */
- /**
- * @event submit
- * Fires when a form is submitted.
- * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
- * @param {HtmlElement} t The target of the event.
- * @param {Object} o The options configuration passed to the {@link #addListener} call.
- */
- /**
- * @event reset
- * Fires when a form is reset.
- * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
- * @param {HtmlElement} t The target of the event.
- * @param {Object} o The options configuration passed to the {@link #addListener} call.
- */
- /**
- * @event focus
- * Fires when an element receives focus either via the pointing device or by tab navigation.
- * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
- * @param {HtmlElement} t The target of the event.
- * @param {Object} o The options configuration passed to the {@link #addListener} call.
- */
- /**
- * @event blur
- * Fires when an element loses focus either via the pointing device or by tabbing navigation.
- * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
- * @param {HtmlElement} t The target of the event.
- * @param {Object} o The options configuration passed to the {@link #addListener} call.
- */
-
-// User Interface events
- /**
- * @event DOMFocusIn
- * Where supported. Similar to HTML focus event, but can be applied to any focusable element.
- * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
- * @param {HtmlElement} t The target of the event.
- * @param {Object} o The options configuration passed to the {@link #addListener} call.
- */
- /**
- * @event DOMFocusOut
- * Where supported. Similar to HTML blur event, but can be applied to any focusable element.
- * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
- * @param {HtmlElement} t The target of the event.
- * @param {Object} o The options configuration passed to the {@link #addListener} call.
- */
- /**
- * @event DOMActivate
- * Where supported. Fires when an element is activated, for instance, through a mouse click or a keypress.
- * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
- * @param {HtmlElement} t The target of the event.
- * @param {Object} o The options configuration passed to the {@link #addListener} call.
- */
-
-// DOM Mutation events
- /**
- * @event DOMSubtreeModified
- * Where supported. Fires when the subtree is modified.
- * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
- * @param {HtmlElement} t The target of the event.
- * @param {Object} o The options configuration passed to the {@link #addListener} call.
- */
- /**
- * @event DOMNodeInserted
- * Where supported. Fires when a node has been added as a child of another node.
- * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
- * @param {HtmlElement} t The target of the event.
- * @param {Object} o The options configuration passed to the {@link #addListener} call.
- */
- /**
- * @event DOMNodeRemoved
- * Where supported. Fires when a descendant node of the element is removed.
- * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
- * @param {HtmlElement} t The target of the event.
- * @param {Object} o The options configuration passed to the {@link #addListener} call.
- */
- /**
- * @event DOMNodeRemovedFromDocument
- * Where supported. Fires when a node is being removed from a document.
- * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
- * @param {HtmlElement} t The target of the event.
- * @param {Object} o The options configuration passed to the {@link #addListener} call.
- */
- /**
- * @event DOMNodeInsertedIntoDocument
- * Where supported. Fires when a node is being inserted into a document.
- * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
- * @param {HtmlElement} t The target of the event.
- * @param {Object} o The options configuration passed to the {@link #addListener} call.
- */
- /**
- * @event DOMAttrModified
- * Where supported. Fires when an attribute has been modified.
- * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
- * @param {HtmlElement} t The target of the event.
- * @param {Object} o The options configuration passed to the {@link #addListener} call.
- */
- /**
- * @event DOMCharacterDataModified
- * Where supported. Fires when the character data has been modified.
- * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
- * @param {HtmlElement} t The target of the event.
- * @param {Object} o The options configuration passed to the {@link #addListener} call.
- */
-
- /**
- * The default unit to append to CSS values where a unit isn't provided (defaults to px).
- * @type String
- */
- defaultUnit : "px",
-
- /**
- * Returns true if this element matches the passed simple selector (e.g. div.some-class or span:first-child)
- * @param {String} selector The simple selector to test
- * @return {Boolean} True if this element matches the selector, else false
+ * Gets the bottom Y coordinate of the element (element Y position + element height)
+ * @param {Boolean} local True to get the local css position instead of page coordinate
+ * @return {Number}
*/
- is : function(simpleSelector){
- return Ext.DomQuery.is(this.dom, simpleSelector);
+ getBottom : function(local){
+ var me = this;
+ return !local ? me.getY() + me.getHeight() : (me.getTop(true) + me.getHeight()) || 0;
},
/**
- * Tries to focus the element. Any exceptions are caught and ignored.
- * @param {Number} defer (optional) Milliseconds to defer the focus
- * @return {Ext.Element} this
- */
- focus : function(defer, /* private */ dom) {
- var me = this,
- dom = dom || me.dom;
- try{
- if(Number(defer)){
- me.focus.defer(defer, null, [null, dom]);
- }else{
- dom.focus();
- }
- }catch(e){}
- return me;
+ * Initializes positioning on this element. If a desired position is not passed, it will make the
+ * the element positioned relative IF it is not already positioned.
+ * @param {String} pos (optional) Positioning to use "relative", "absolute" or "fixed"
+ * @param {Number} zIndex (optional) The zIndex to apply
+ * @param {Number} x (optional) Set the page X position
+ * @param {Number} y (optional) Set the page Y position
+ */
+ position : function(pos, zIndex, x, y){
+ var me = this;
+
+ if(!pos && me.isStyle(POSITION, STATIC)){
+ me.setStyle(POSITION, RELATIVE);
+ } else if(pos) {
+ me.setStyle(POSITION, pos);
+ }
+ if(zIndex){
+ me.setStyle(ZINDEX, zIndex);
+ }
+ if(x || y) me.setXY([x || false, y || false]);
},
/**
- * Tries to blur the element. Any exceptions are caught and ignored.
- * @return {Ext.Element} this
+ * Clear positioning back to the default when the document was loaded
+ * @param {String} value (optional) The value to use for the left,right,top,bottom, defaults to '' (empty string). You could use 'auto'.
+ * @return {Ext.Element} this
*/
- blur : function() {
- try{
- this.dom.blur();
- }catch(e){}
+ clearPositioning : function(value){
+ value = value || '';
+ this.setStyle({
+ left : value,
+ right : value,
+ top : value,
+ bottom : value,
+ "z-index" : "",
+ position : STATIC
+ });
return this;
},
/**
- * Returns the value of the "value" attribute
- * @param {Boolean} asNumber true to parse the value as a number
- * @return {String/Number}
- */
- getValue : function(asNumber){
- var val = this.dom.value;
- return asNumber ? parseInt(val, 10) : val;
- },
-
- /**
- * Appends an event handler to this element. The shorthand version {@link #on} is equivalent.
- * @param {String} eventName The name of event to handle.
- * @param {Function} fn The handler function the event invokes. This function is passed
- * the following parameters:<ul>
- * <li><b>evt</b> : EventObject<div class="sub-desc">The {@link Ext.EventObject EventObject} describing the event.</div></li>
- * <li><b>el</b> : HtmlElement<div class="sub-desc">The DOM element which was the target of the event.
- * Note that this may be filtered by using the <tt>delegate</tt> option.</div></li>
- * <li><b>o</b> : Object<div class="sub-desc">The options object from the addListener call.</div></li>
- * </ul>
- * @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the handler function is executed.
- * <b>If omitted, defaults to this Element.</b>.
- * @param {Object} options (optional) An object containing handler configuration properties.
- * This may contain any of the following properties:<ul>
- * <li><b>scope</b> Object : <div class="sub-desc">The scope (<code><b>this</b></code> reference) in which the handler function is executed.
- * <b>If omitted, defaults to this Element.</b></div></li>
- * <li><b>delegate</b> String: <div class="sub-desc">A simple selector to filter the target or look for a descendant of the target. See below for additional details.</div></li>
- * <li><b>stopEvent</b> Boolean: <div class="sub-desc">True to stop the event. That is stop propagation, and prevent the default action.</div></li>
- * <li><b>preventDefault</b> Boolean: <div class="sub-desc">True to prevent the default action</div></li>
- * <li><b>stopPropagation</b> Boolean: <div class="sub-desc">True to prevent event propagation</div></li>
- * <li><b>normalized</b> Boolean: <div class="sub-desc">False to pass a browser event to the handler function instead of an Ext.EventObject</div></li>
- * <li><b>target</b> Ext.Element: <div class="sub-desc">Only call the handler if the event was fired on the target Element, <i>not</i> if the event was bubbled up from a child node.</div></li>
- * <li><b>delay</b> Number: <div class="sub-desc">The number of milliseconds to delay the invocation of the handler after the event fires.</div></li>
- * <li><b>single</b> Boolean: <div class="sub-desc">True to add a handler to handle just the next firing of the event, and then remove itself.</div></li>
- * <li><b>buffer</b> Number: <div class="sub-desc">Causes the handler to be scheduled to run in an {@link Ext.util.DelayedTask} delayed
- * by the specified number of milliseconds. If the event fires again within that time, the original
- * handler is <em>not</em> invoked, but the new handler is scheduled in its place.</div></li>
- * </ul><br>
- * <p>
- * <b>Combining Options</b><br>
- * In the following examples, the shorthand form {@link #on} is used rather than the more verbose
- * addListener. The two are equivalent. Using the options argument, it is possible to combine different
- * types of listeners:<br>
- * <br>
- * A delayed, one-time listener that auto stops the event and adds a custom argument (forumId) to the
- * options object. The options object is available as the third parameter in the handler function.<div style="margin: 5px 20px 20px;">
- * Code:<pre><code>
-el.on('click', this.onClick, this, {
- single: true,
- delay: 100,
- stopEvent : true,
- forumId: 4
-});</code></pre></p>
- * <p>
- * <b>Attaching multiple handlers in 1 call</b><br>
- * The method also allows for a single argument to be passed which is a config object containing properties
- * which specify multiple handlers.</p>
- * <p>
- * Code:<pre><code>
-el.on({
- 'click' : {
- fn: this.onClick,
- scope: this,
- delay: 100
- },
- 'mouseover' : {
- fn: this.onMouseOver,
- scope: this
- },
- 'mouseout' : {
- fn: this.onMouseOut,
- scope: this
- }
-});</code></pre>
- * <p>
- * Or a shorthand syntax:<br>
- * Code:<pre><code></p>
-el.on({
- 'click' : this.onClick,
- 'mouseover' : this.onMouseOver,
- 'mouseout' : this.onMouseOut,
- scope: this
-});
- * </code></pre></p>
- * <p><b>delegate</b></p>
- * <p>This is a configuration option that you can pass along when registering a handler for
- * an event to assist with event delegation. Event delegation is a technique that is used to
- * reduce memory consumption and prevent exposure to memory-leaks. By registering an event
- * for a container element as opposed to each element within a container. By setting this
- * configuration option to a simple selector, the target element will be filtered to look for
- * a descendant of the target.
- * For example:<pre><code>
-// using this markup:
-<div id='elId'>
- <p id='p1'>paragraph one</p>
- <p id='p2' class='clickable'>paragraph two</p>
- <p id='p3'>paragraph three</p>
-</div>
-// utilize event delegation to registering just one handler on the container element:
-el = Ext.get('elId');
-el.on(
- 'click',
- function(e,t) {
- // handle click
- console.info(t.id); // 'p2'
- },
- this,
- {
- // filter the target element to be a descendant with the class 'clickable'
- delegate: '.clickable'
- }
-);
- * </code></pre></p>
- * @return {Ext.Element} this
- */
- addListener : function(eventName, fn, scope, options){
- Ext.EventManager.on(this.dom, eventName, fn, scope || this, options);
- return this;
+ * Gets an object with all CSS positioning properties. Useful along with setPostioning to get
+ * snapshot before performing an update and then restoring the element.
+ * @return {Object}
+ */
+ getPositioning : function(){
+ var l = this.getStyle(LEFT);
+ var t = this.getStyle(TOP);
+ return {
+ "position" : this.getStyle(POSITION),
+ "left" : l,
+ "right" : l ? "" : this.getStyle(RIGHT),
+ "top" : t,
+ "bottom" : t ? "" : this.getStyle(BOTTOM),
+ "z-index" : this.getStyle(ZINDEX)
+ };
},
-
+
/**
- * Removes an event handler from this element. The shorthand version {@link #un} is equivalent.
- * <b>Note</b>: if a <i>scope</i> was explicitly specified when {@link #addListener adding} the
- * listener, the same scope must be specified here.
- * Example:
- * <pre><code>
-el.removeListener('click', this.handlerFn);
-// or
-el.un('click', this.handlerFn);
-</code></pre>
- * @param {String} eventName The name of the event from which to remove the handler.
- * @param {Function} fn The handler function to remove. <b>This must be a reference to the function passed into the {@link #addListener} call.</b>
- * @param {Object} scope If a scope (<b><code>this</code></b> reference) was specified when the listener was added,
- * then this must refer to the same object.
- * @return {Ext.Element} this
+ * Set positioning with an object returned by getPositioning().
+ * @param {Object} posCfg
+ * @return {Ext.Element} this
*/
- removeListener : function(eventName, fn, scope){
- Ext.EventManager.removeListener(this.dom, eventName, fn, scope || this);
- return this;
- },
-
+ setPositioning : function(pc){
+ var me = this,
+ style = me.dom.style;
+
+ me.setStyle(pc);
+
+ if(pc.right == AUTO){
+ style.right = "";
+ }
+ if(pc.bottom == AUTO){
+ style.bottom = "";
+ }
+
+ return me;
+ },
+
/**
- * Removes all previous added listeners from this element
- * @return {Ext.Element} this
+ * Translates the passed page coordinates into left/top css values for this element
+ * @param {Number/Array} x The page x or an array containing [x, y]
+ * @param {Number} y (optional) The page y, required if x is not an array
+ * @return {Object} An object with left and top properties. e.g. {left: (value), top: (value)}
*/
- removeAllListeners : function(){
- Ext.EventManager.removeAll(this.dom);
- return this;
- },
+ translatePoints : function(x, y){
+ y = isNaN(x[1]) ? y : x[1];
+ x = isNaN(x[0]) ? x : x[0];
+ var me = this,
+ relative = me.isStyle(POSITION, RELATIVE),
+ o = me.getXY(),
+ l = parseInt(me.getStyle(LEFT), 10),
+ t = parseInt(me.getStyle(TOP), 10);
+
+ l = !isNaN(l) ? l : (relative ? 0 : me.dom.offsetLeft);
+ t = !isNaN(t) ? t : (relative ? 0 : me.dom.offsetTop);
- /**
- * Recursively removes all previous added listeners from this element and its children
- * @return {Ext.Element} this
- */
- purgeAllListeners : function() {
- Ext.EventManager.purgeElement(this, true);
- return this;
+ return {left: (x - o[0] + l), top: (y - o[1] + t)};
},
+
+ animTest : function(args, animate, i) {
+ return !!animate && this.preanim ? this.preanim(args, i) : false;
+ }
+});
+})();/**
+ * @class Ext.Element
+ */
+Ext.Element.addMethods({
/**
- * @private Test if size has a unit, otherwise appends the default
+ * Returns true if this element is scrollable.
+ * @return {Boolean}
*/
- addUnits : function(size){
- if(size === "" || size == "auto" || size === undefined){
- size = size || '';
- } else if(!isNaN(size) || !unitPattern.test(size)){
- size = size + (this.defaultUnit || 'px');
- }
- return size;
+ isScrollable : function(){
+ var dom = this.dom;
+ return dom.scrollHeight > dom.clientHeight || dom.scrollWidth > dom.clientWidth;
},
/**
- * <p>Updates the <a href="http://developer.mozilla.org/en/DOM/element.innerHTML">innerHTML</a> of this Element
- * from a specified URL. Note that this is subject to the <a href="http://en.wikipedia.org/wiki/Same_origin_policy">Same Origin Policy</a></p>
- * <p>Updating innerHTML of an element will <b>not</b> execute embedded <tt><script></tt> elements. This is a browser restriction.</p>
- * @param {Mixed} options. Either a sring containing the URL from which to load the HTML, or an {@link Ext.Ajax#request} options object specifying
- * exactly how to request the HTML.
- * @return {Ext.Element} this
+ * 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.
+ * @return {Element} this
*/
- load : function(url, params, cb){
- Ext.Ajax.request(Ext.apply({
- params: params,
- url: url.url || url,
- callback: cb,
- el: this.dom,
- indicatorText: url.indicatorText || ''
- }, Ext.isObject(url) ? url : {}));
+ scrollTo : function(side, value){
+ this.dom["scroll" + (/top/i.test(side) ? "Top" : "Left")] = value;
return this;
},
/**
- * Tests various css rules/browsers to determine if this element uses a border box
- * @return {Boolean}
+ * Returns the current scroll position of the element.
+ * @return {Object} An object containing the scroll position in the format {left: (scrollLeft), top: (scrollTop)}
*/
- isBorderBox : function(){
- return noBoxAdjust[(this.dom.tagName || "").toLowerCase()] || Ext.isBorderBox;
- },
-
- /**
- * <p>Removes this element's dom reference. Note that event and cache removal is handled at {@link Ext#removeNode}</p>
- */
- remove : function(){
- var me = this,
- dom = me.dom;
-
- if (dom) {
- delete me.dom;
- Ext.removeNode(dom);
- }
- },
-
- /**
- * Sets up event handlers to call the passed functions when the mouse is moved into and out of the Element.
- * @param {Function} overFn The function to call when the mouse enters the Element.
- * @param {Function} outFn The function to call when the mouse leaves the Element.
- * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the functions are executed. Defaults to the Element's DOM element.
- * @param {Object} options (optional) Options for the listener. See {@link Ext.util.Observable#addListener the <tt>options</tt> parameter}.
- * @return {Ext.Element} this
- */
- hover : function(overFn, outFn, scope, options){
- var me = this;
- me.on('mouseenter', overFn, scope || me.dom, options);
- me.on('mouseleave', outFn, scope || me.dom, options);
- return me;
- },
-
- /**
- * Returns true if this element is an ancestor of the passed element
- * @param {HTMLElement/String} el The element to check
- * @return {Boolean} True if this element is an ancestor of el, else false
- */
- contains : function(el){
- return !el ? false : Ext.lib.Dom.isAncestor(this.dom, el.dom ? el.dom : el);
- },
-
- /**
- * Returns the value of a namespaced attribute from the element's underlying DOM node.
- * @param {String} namespace The namespace in which to look for the attribute
- * @param {String} name The attribute name
- * @return {String} The attribute value
- * @deprecated
- */
- getAttributeNS : function(ns, name){
- return this.getAttribute(name, ns);
- },
-
- /**
- * Returns the value of an attribute from the element's underlying DOM node.
- * @param {String} name The attribute name
- * @param {String} namespace (optional) The namespace in which to look for the attribute
- * @return {String} The attribute value
- */
- getAttribute : Ext.isIE ? function(name, ns){
- var d = this.dom,
- type = typeof d[ns + ":" + name];
-
- if(['undefined', 'unknown'].indexOf(type) == -1){
- return d[ns + ":" + name];
- }
- return d[name];
- } : function(name, ns){
- var d = this.dom;
- return d.getAttributeNS(ns, name) || d.getAttribute(ns + ":" + name) || d.getAttribute(name) || d[name];
- },
+ getScroll : function(){
+ var d = this.dom,
+ doc = document,
+ body = doc.body,
+ docElement = doc.documentElement,
+ l,
+ t,
+ ret;
- /**
- * Update the innerHTML of this element
- * @param {String} html The new HTML
- * @return {Ext.Element} this
- */
- update : function(html) {
- if (this.dom) {
- this.dom.innerHTML = html;
+ if(d == doc || d == body){
+ if(Ext.isIE && Ext.isStrict){
+ l = docElement.scrollLeft;
+ t = docElement.scrollTop;
+ }else{
+ l = window.pageXOffset;
+ t = window.pageYOffset;
+ }
+ ret = {left: l || (body ? body.scrollLeft : 0), top: t || (body ? body.scrollTop : 0)};
+ }else{
+ ret = {left: d.scrollLeft, top: d.scrollTop};
}
- return this;
+ return ret;
}
-};
-
-var ep = El.prototype;
-
-El.addMethods = function(o){
- Ext.apply(ep, o);
-};
-
+});/**
+ * @class Ext.Element
+ */
/**
- * Appends an event handler (shorthand for {@link #addListener}).
- * @param {String} eventName The name of event to handle.
- * @param {Function} fn The handler function the event invokes.
- * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the handler function is executed.
- * @param {Object} options (optional) An object containing standard {@link #addListener} options
- * @member Ext.Element
- * @method on
+ * Visibility mode constant for use with {@link #setVisibilityMode}. Use visibility to hide element
+ * @static
+ * @type Number
*/
-ep.on = ep.addListener;
-
+Ext.Element.VISIBILITY = 1;
/**
- * Removes an event handler from this element (see {@link #removeListener} for additional notes).
- * @param {String} eventName The name of the event from which to remove the handler.
- * @param {Function} fn The handler function to remove. <b>This must be a reference to the function passed into the {@link #addListener} call.</b>
- * @param {Object} scope If a scope (<b><code>this</code></b> reference) was specified when the listener was added,
- * then this must refer to the same object.
- * @return {Ext.Element} this
- * @member Ext.Element
- * @method un
+ * Visibility mode constant for use with {@link #setVisibilityMode}. Use display to hide element
+ * @static
+ * @type Number
*/
-ep.un = ep.removeListener;
+Ext.Element.DISPLAY = 2;
/**
- * true to automatically adjust width and height settings for box-model issues (default to true)
+ * Visibility mode constant for use with {@link #setVisibilityMode}. Use offsets (x and y positioning offscreen)
+ * to hide element.
+ * @static
+ * @type Number
*/
-ep.autoBoxAdjust = true;
+Ext.Element.OFFSETS = 3;
-// private
-var unitPattern = /\d+(px|em|%|en|ex|pt|in|cm|mm|pc)$/i,
- docEl;
-/**
- * @private
- */
+Ext.Element.ASCLASS = 4;
/**
- * Retrieves Ext.Element objects.
- * <p><b>This method does not retrieve {@link Ext.Component Component}s.</b> This method
- * retrieves Ext.Element objects which encapsulate DOM elements. To retrieve a Component by
- * its ID, use {@link Ext.ComponentMgr#get}.</p>
- * <p>Uses simple caching to consistently return the same object. Automatically fixes if an
- * object was recreated with the same id via AJAX or DOM.</p>
- * @param {Mixed} el The id of the node, a DOM Node or an existing Element.
- * @return {Element} The Element object (or null if no matching element was found)
+ * Defaults to 'x-hide-nosize'
* @static
- * @member Ext.Element
- * @method get
+ * @type String
*/
-El.get = function(el){
- var ex,
- elm,
- id;
- if(!el){ return null; }
- if (typeof el == "string") { // element id
- if (!(elm = DOC.getElementById(el))) {
- return null;
- }
- if (EC[el] && EC[el].el) {
- ex = EC[el].el;
- ex.dom = elm;
- } else {
- ex = El.addToCache(new El(elm));
- }
- return ex;
- } else if (el.tagName) { // dom element
- if(!(id = el.id)){
- id = Ext.id(el);
- }
- if (EC[id] && EC[id].el) {
- ex = EC[id].el;
- ex.dom = el;
- } else {
- ex = El.addToCache(new El(el));
- }
- return ex;
- } else if (el instanceof El) {
- if(el != docEl){
- // refresh dom element in case no longer valid,
- // catch case where it hasn't been appended
+Ext.Element.visibilityCls = 'x-hide-nosize';
- // If an el instance is passed, don't pass to getElementById without some kind of id
- if (Ext.isIE && (el.id == undefined || el.id == '')) {
- el.dom = el.dom;
- } else {
- el.dom = DOC.getElementById(el.id) || el.dom;
+Ext.Element.addMethods(function(){
+ var El = Ext.Element,
+ OPACITY = "opacity",
+ VISIBILITY = "visibility",
+ DISPLAY = "display",
+ HIDDEN = "hidden",
+ OFFSETS = "offsets",
+ ASCLASS = "asclass",
+ NONE = "none",
+ NOSIZE = 'nosize',
+ ORIGINALDISPLAY = 'originalDisplay',
+ VISMODE = 'visibilityMode',
+ ISVISIBLE = 'isVisible',
+ data = El.data,
+ getDisplay = function(dom){
+ var d = data(dom, ORIGINALDISPLAY);
+ if(d === undefined){
+ data(dom, ORIGINALDISPLAY, d = '');
}
+ return d;
+ },
+ getVisMode = function(dom){
+ var m = data(dom, VISMODE);
+ if(m === undefined){
+ data(dom, VISMODE, m = 1);
+ }
+ return m;
+ };
+
+ return {
+ /**
+ * The element's default display mode (defaults to "")
+ * @type String
+ */
+ originalDisplay : "",
+ visibilityMode : 1,
+
+ /**
+ * Sets the element's visibility mode. When setVisible() is called it
+ * will use this to determine whether to set the visibility or the display property.
+ * @param {Number} visMode Ext.Element.VISIBILITY or Ext.Element.DISPLAY
+ * @return {Ext.Element} this
+ */
+ setVisibilityMode : function(visMode){
+ data(this.dom, VISMODE, visMode);
+ return this;
+ },
+
+ /**
+ * Perform custom animation on this element.
+ * <div><ul class="mdetail-params">
+ * <li><u>Animation Properties</u></li>
+ *
+ * <p>The Animation Control Object enables gradual transitions for any member of an
+ * element's style object that takes a numeric value including but not limited to
+ * these properties:</p><div><ul class="mdetail-params">
+ * <li><tt>bottom, top, left, right</tt></li>
+ * <li><tt>height, width</tt></li>
+ * <li><tt>margin, padding</tt></li>
+ * <li><tt>borderWidth</tt></li>
+ * <li><tt>opacity</tt></li>
+ * <li><tt>fontSize</tt></li>
+ * <li><tt>lineHeight</tt></li>
+ * </ul></div>
+ *
+ *
+ * <li><u>Animation Property Attributes</u></li>
+ *
+ * <p>Each Animation Property is a config object with optional properties:</p>
+ * <div><ul class="mdetail-params">
+ * <li><tt>by</tt>* : relative change - start at current value, change by this value</li>
+ * <li><tt>from</tt> : ignore current value, start from this value</li>
+ * <li><tt>to</tt>* : start at current value, go to this value</li>
+ * <li><tt>unit</tt> : any allowable unit specification</li>
+ * <p>* do not specify both <tt>to</tt> and <tt>by</tt> for an animation property</p>
+ * </ul></div>
+ *
+ * <li><u>Animation Types</u></li>
+ *
+ * <p>The supported animation types:</p><div><ul class="mdetail-params">
+ * <li><tt>'run'</tt> : Default
+ * <pre><code>
+var el = Ext.get('complexEl');
+el.animate(
+ // animation control object
+ {
+ borderWidth: {to: 3, from: 0},
+ opacity: {to: .3, from: 1},
+ height: {to: 50, from: el.getHeight()},
+ width: {to: 300, from: el.getWidth()},
+ top : {by: - 100, unit: 'px'},
+ },
+ 0.35, // animation duration
+ null, // callback
+ 'easeOut', // easing method
+ 'run' // animation type ('run','color','motion','scroll')
+);
+ * </code></pre>
+ * </li>
+ * <li><tt>'color'</tt>
+ * <p>Animates transition of background, text, or border colors.</p>
+ * <pre><code>
+el.animate(
+ // animation control object
+ {
+ color: { to: '#06e' },
+ backgroundColor: { to: '#e06' }
+ },
+ 0.35, // animation duration
+ null, // callback
+ 'easeOut', // easing method
+ 'color' // animation type ('run','color','motion','scroll')
+);
+ * </code></pre>
+ * </li>
+ *
+ * <li><tt>'motion'</tt>
+ * <p>Animates the motion of an element to/from specific points using optional bezier
+ * way points during transit.</p>
+ * <pre><code>
+el.animate(
+ // animation control object
+ {
+ borderWidth: {to: 3, from: 0},
+ opacity: {to: .3, from: 1},
+ height: {to: 50, from: el.getHeight()},
+ width: {to: 300, from: el.getWidth()},
+ top : {by: - 100, unit: 'px'},
+ points: {
+ to: [50, 100], // go to this point
+ control: [ // optional bezier way points
+ [ 600, 800],
+ [-100, 200]
+ ]
}
- return el;
- } else if(el.isComposite) {
- return el;
- } else if(Ext.isArray(el)) {
- return El.select(el);
- } else if(el == DOC) {
- // create a bogus element object representing the document object
- if(!docEl){
- var f = function(){};
- f.prototype = El.prototype;
- docEl = new f();
- docEl.dom = DOC;
- }
- return docEl;
- }
- return null;
-};
+ },
+ 3000, // animation duration (milliseconds!)
+ null, // callback
+ 'easeOut', // easing method
+ 'motion' // animation type ('run','color','motion','scroll')
+);
+ * </code></pre>
+ * </li>
+ * <li><tt>'scroll'</tt>
+ * <p>Animate horizontal or vertical scrolling of an overflowing page element.</p>
+ * <pre><code>
+el.animate(
+ // animation control object
+ {
+ scroll: {to: [400, 300]}
+ },
+ 0.35, // animation duration
+ null, // callback
+ 'easeOut', // easing method
+ 'scroll' // animation type ('run','color','motion','scroll')
+);
+ * </code></pre>
+ * </li>
+ * </ul></div>
+ *
+ * </ul></div>
+ *
+ * @param {Object} args The animation control args
+ * @param {Float} duration (optional) How long the animation lasts in seconds (defaults to <tt>.35</tt>)
+ * @param {Function} onComplete (optional) Function to call when animation completes
+ * @param {String} easing (optional) {@link Ext.Fx#easing} method to use (defaults to <tt>'easeOut'</tt>)
+ * @param {String} animType (optional) <tt>'run'</tt> is the default. Can also be <tt>'color'</tt>,
+ * <tt>'motion'</tt>, or <tt>'scroll'</tt>
+ * @return {Ext.Element} this
+ */
+ animate : function(args, duration, onComplete, easing, animType){
+ this.anim(args, {duration: duration, callback: onComplete, easing: easing}, animType);
+ return this;
+ },
-El.addToCache = function(el, id){
- id = id || el.id;
- EC[id] = {
- el: el,
- data: {},
- events: {}
- };
- return el;
-};
+ /*
+ * @private Internal animation call
+ */
+ anim : function(args, opt, animType, defaultDur, defaultEase, cb){
+ animType = animType || 'run';
+ opt = opt || {};
+ var me = this,
+ anim = Ext.lib.Anim[animType](
+ me.dom,
+ args,
+ (opt.duration || defaultDur) || .35,
+ (opt.easing || defaultEase) || 'easeOut',
+ function(){
+ if(cb) cb.call(me);
+ if(opt.callback) opt.callback.call(opt.scope || me, me, opt);
+ },
+ me
+ );
+ opt.anim = anim;
+ return anim;
+ },
-// private method for getting and setting element data
-El.data = function(el, key, value){
- el = El.get(el);
- if (!el) {
- return null;
- }
- var c = EC[el.id].data;
- if(arguments.length == 2){
- return c[key];
- }else{
- return (c[key] = value);
- }
-};
+ // private legacy anim prep
+ preanim : function(a, i){
+ return !a[i] ? false : (typeof a[i] == 'object' ? a[i]: {duration: a[i+1], callback: a[i+2], easing: a[i+3]});
+ },
-// private
-// Garbage collection - uncache elements/purge listeners on orphaned elements
-// so we don't hold a reference and cause the browser to retain them
-function garbageCollect(){
- if(!Ext.enableGarbageCollector){
- clearInterval(El.collectorThreadId);
- } else {
- var eid,
- el,
- d,
- o;
+ /**
+ * Checks whether the element is currently visible using both visibility and display properties.
+ * @return {Boolean} True if the element is currently visible, else false
+ */
+ isVisible : function() {
+ var me = this,
+ dom = me.dom,
+ visible = data(dom, ISVISIBLE);
- for(eid in EC){
- o = EC[eid];
- if(o.skipGC){
- continue;
+ if(typeof visible == 'boolean'){ //return the cached value if registered
+ return visible;
}
- el = o.el;
- d = el.dom;
- // -------------------------------------------------------
- // Determining what is garbage:
- // -------------------------------------------------------
- // !d
- // dom node is null, definitely garbage
- // -------------------------------------------------------
- // !d.parentNode
- // no parentNode == direct orphan, definitely garbage
- // -------------------------------------------------------
- // !d.offsetParent && !document.getElementById(eid)
- // display none elements have no offsetParent so we will
- // also try to look it up by it's id. However, check
- // offsetParent first so we don't do unneeded lookups.
- // This enables collection of elements that are not orphans
- // directly, but somewhere up the line they have an orphan
- // parent.
- // -------------------------------------------------------
- if(!d || !d.parentNode || (!d.offsetParent && !DOC.getElementById(eid))){
- if(Ext.enableListenerCollection){
- Ext.EventManager.removeAll(d);
+ //Determine the current state based on display states
+ visible = !me.isStyle(VISIBILITY, HIDDEN) &&
+ !me.isStyle(DISPLAY, NONE) &&
+ !((getVisMode(dom) == El.ASCLASS) && me.hasClass(me.visibilityCls || El.visibilityCls));
+
+ data(dom, ISVISIBLE, visible);
+ return visible;
+ },
+
+ /**
+ * Sets the visibility of the element (see details). If the visibilityMode is set to Element.DISPLAY, it will use
+ * the display property to hide the element, otherwise it uses visibility. The default is to hide and show using the visibility property.
+ * @param {Boolean} visible Whether the element is visible
+ * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
+ * @return {Ext.Element} this
+ */
+ setVisible : function(visible, animate){
+ var me = this, isDisplay, isVisibility, isOffsets, isNosize,
+ dom = me.dom,
+ visMode = getVisMode(dom);
+
+
+ // hideMode string override
+ if (typeof animate == 'string'){
+ switch (animate) {
+ case DISPLAY:
+ visMode = El.DISPLAY;
+ break;
+ case VISIBILITY:
+ visMode = El.VISIBILITY;
+ break;
+ case OFFSETS:
+ visMode = El.OFFSETS;
+ break;
+ case NOSIZE:
+ case ASCLASS:
+ visMode = El.ASCLASS;
+ break;
}
- delete EC[eid];
- }
- }
- // Cleanup IE Object leaks
- if (Ext.isIE) {
- var t = {};
- for (eid in EC) {
- t[eid] = EC[eid];
+ me.setVisibilityMode(visMode);
+ animate = false;
}
- EC = Ext.elCache = t;
- }
- }
-}
-El.collectorThreadId = setInterval(garbageCollect, 30000);
-var flyFn = function(){};
-flyFn.prototype = El.prototype;
+ if (!animate || !me.anim) {
+ if(visMode == El.ASCLASS ){
-// dom is optional
-El.Flyweight = function(dom){
- this.dom = dom;
-};
+ me[visible?'removeClass':'addClass'](me.visibilityCls || El.visibilityCls);
-El.Flyweight.prototype = new flyFn();
-El.Flyweight.prototype.isFlyweight = true;
-El._flyweights = {};
+ } else if (visMode == El.DISPLAY){
-/**
- * <p>Gets the globally shared flyweight Element, with the passed node as the active element. Do not store a reference to this element -
- * the dom node can be overwritten by other code. Shorthand of {@link Ext.Element#fly}</p>
- * <p>Use this to make one-time references to DOM elements which are not going to be accessed again either by
- * application code, or by Ext's classes. If accessing an element which will be processed regularly, then {@link Ext#get}
- * will be more appropriate to take advantage of the caching provided by the Ext.Element class.</p>
- * @param {String/HTMLElement} el The dom node or id
- * @param {String} named (optional) Allows for creation of named reusable flyweights to prevent conflicts
- * (e.g. internally Ext uses "_global")
- * @return {Element} The shared Element object (or null if no matching element was found)
- * @member Ext.Element
- * @method fly
- */
-El.fly = function(el, named){
- var ret = null;
- named = named || '_global';
-
- if (el = Ext.getDom(el)) {
- (El._flyweights[named] = El._flyweights[named] || new El.Flyweight()).dom = el;
- ret = El._flyweights[named];
- }
- return ret;
-};
+ return me.setDisplayed(visible);
-/**
- * Retrieves Ext.Element objects.
- * <p><b>This method does not retrieve {@link Ext.Component Component}s.</b> This method
- * retrieves Ext.Element objects which encapsulate DOM elements. To retrieve a Component by
- * its ID, use {@link Ext.ComponentMgr#get}.</p>
- * <p>Uses simple caching to consistently return the same object. Automatically fixes if an
- * object was recreated with the same id via AJAX or DOM.</p>
- * Shorthand of {@link Ext.Element#get}
- * @param {Mixed} el The id of the node, a DOM Node or an existing Element.
- * @return {Element} The Element object (or null if no matching element was found)
- * @member Ext
- * @method get
- */
-Ext.get = El.get;
-
-/**
- * <p>Gets the globally shared flyweight Element, with the passed node as the active element. Do not store a reference to this element -
- * the dom node can be overwritten by other code. Shorthand of {@link Ext.Element#fly}</p>
- * <p>Use this to make one-time references to DOM elements which are not going to be accessed again either by
- * application code, or by Ext's classes. If accessing an element which will be processed regularly, then {@link Ext#get}
- * will be more appropriate to take advantage of the caching provided by the Ext.Element class.</p>
- * @param {String/HTMLElement} el The dom node or id
- * @param {String} named (optional) Allows for creation of named reusable flyweights to prevent conflicts
- * (e.g. internally Ext uses "_global")
- * @return {Element} The shared Element object (or null if no matching element was found)
- * @member Ext
- * @method fly
- */
-Ext.fly = El.fly;
+ } else if (visMode == El.OFFSETS){
-// speedy lookup for elements never to box adjust
-var noBoxAdjust = Ext.isStrict ? {
- select:1
-} : {
- input:1, select:1, textarea:1
-};
-if(Ext.isIE || Ext.isGecko){
- noBoxAdjust['button'] = 1;
-}
+ if (!visible){
+ me.hideModeStyles = {
+ position: me.getStyle('position'),
+ top: me.getStyle('top'),
+ left: me.getStyle('left')
+ };
+ me.applyStyles({position: 'absolute', top: '-10000px', left: '-10000px'});
+ } else {
+ me.applyStyles(me.hideModeStyles || {position: '', top: '', left: ''});
+ delete me.hideModeStyles;
+ }
-})();
-/**
- * @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();
+ }else{
+ me.fixDisplay();
+ dom.style.visibility = visible ? "visible" : HIDDEN;
+ }
+ }else{
+ // closure for composites
+ if(visible){
+ me.setOpacity(.01);
+ me.setVisible(true);
+ }
+ me.anim({opacity: { to: (visible?1:0) }},
+ me.preanim(arguments, 1),
+ null,
+ .35,
+ 'easeIn',
+ function(){
+ visible || me.setVisible(false).setOpacity(1);
+ });
}
- }
- if(Ext.isArray(eventName)){
- Ext.each(eventName, function(e) {
- me.on(e, fn);
- });
+ data(dom, ISVISIBLE, visible); //set logical visibility state
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;
+ /**
+ * @private
+ * Determine if the Element has a relevant height and width available based
+ * upon current logical visibility state
+ */
+ hasMetrics : function(){
+ var dom = this.dom;
+ return this.isVisible() || (getVisMode(dom) == El.VISIBILITY);
+ },
- if(Ext.Element.data(dom, 'isCleaned') && forceReclean !== true){
+ /**
+ * Toggles the element's visibility or display, depending on visibility mode.
+ * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
+ * @return {Ext.Element} this
+ */
+ toggle : function(animate){
+ var me = this;
+ me.setVisible(!me.isVisible(), me.preanim(arguments, 0));
return me;
- }
+ },
- while(n){
- var nx = n.nextSibling;
- if(n.nodeType == 3 && !/\S/.test(n.nodeValue)){
- dom.removeChild(n);
- }else{
- n.nodeIndex = ++ni;
+ /**
+ * Sets the CSS display property. Uses originalDisplay if the specified value is a boolean true.
+ * @param {Mixed} value Boolean value to display the element using its default display, or a string to set the display directly.
+ * @return {Ext.Element} this
+ */
+ setDisplayed : function(value) {
+ if(typeof value == "boolean"){
+ value = value ? getDisplay(this.dom) : NONE;
}
- 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 um = this.getUpdater();
- um.update.apply(um, arguments);
- return this;
- },
+ this.setStyle(DISPLAY, value);
+ 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));
- },
+ // private
+ fixDisplay : function(){
+ var me = this;
+ if(me.isStyle(DISPLAY, NONE)){
+ me.setStyle(VISIBILITY, HIDDEN);
+ me.setStyle(DISPLAY, getDisplay(this.dom)); // first try reverting to default
+ if(me.isStyle(DISPLAY, NONE)){ // if that fails, default to block
+ me.setStyle(DISPLAY, "block");
+ }
+ }
+ },
- /**
- * 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) {
+ /**
+ * Hide this element - Uses display mode to determine whether to use "display" or "visibility". See {@link #setVisible}.
+ * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
+ * @return {Ext.Element} this
+ */
+ hide : function(animate){
+ // hideMode override
+ if (typeof animate == 'string'){
+ this.setVisible(false, animate);
+ return this;
+ }
+ this.setVisible(false, this.preanim(arguments, 0));
return this;
- }
- html = html || "";
+ },
- if(loadScripts !== true){
- this.dom.innerHTML = html;
- if(typeof callback == 'function'){
- callback();
+ /**
+ * Show this element - Uses display mode to determine whether to use "display" or "visibility". See {@link #setVisible}.
+ * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
+ * @return {Ext.Element} this
+ */
+ show : function(animate){
+ // hideMode override
+ if (typeof animate == 'string'){
+ this.setVisible(true, animate);
+ return this;
}
+ this.setVisible(true, this.preanim(arguments, 0));
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();
+ };
+}());(function(){
+ // contants
+ var NULL = null,
+ UNDEFINED = undefined,
+ TRUE = true,
+ FALSE = false,
+ SETX = "setX",
+ SETY = "setY",
+ SETXY = "setXY",
+ LEFT = "left",
+ BOTTOM = "bottom",
+ TOP = "top",
+ RIGHT = "right",
+ HEIGHT = "height",
+ WIDTH = "width",
+ POINTS = "points",
+ HIDDEN = "hidden",
+ ABSOLUTE = "absolute",
+ VISIBLE = "visible",
+ MOTION = "motion",
+ POSITION = "position",
+ EASEOUT = "easeOut",
+ /*
+ * Use a light flyweight here since we are using so many callbacks and are always assured a DOM element
+ */
+ flyEl = new Ext.Element.Flyweight(),
+ queues = {},
+ getObject = function(o){
+ return o || {};
+ },
+ fly = function(dom){
+ flyEl.dom = dom;
+ flyEl.id = Ext.id(dom);
+ return flyEl;
+ },
+ /*
+ * Queueing now stored outside of the element due to closure issues
+ */
+ getQueue = function(id){
+ if(!queues[id]){
+ queues[id] = [];
}
- });
- dom.innerHTML = html.replace(/(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)/ig, "");
- return this;
- },
+ return queues[id];
+ },
+ setQueue = function(id, value){
+ queues[id] = value;
+ };
+
+//Notifies Element that fx methods are available
+Ext.enableFx = TRUE;
- // inherit docs, overridden so we can add removeAnchor
- removeAllListeners : function(){
- this.removeAnchor();
- Ext.EventManager.removeAll(this.dom);
- return this;
+/**
+ * @class Ext.Fx
+ * <p>A class to provide basic animation and visual effects support. <b>Note:</b> This class is automatically applied
+ * to the {@link Ext.Element} interface when included, so all effects calls should be performed via {@link Ext.Element}.
+ * Conversely, since the effects are not actually defined in {@link Ext.Element}, Ext.Fx <b>must</b> be
+ * {@link Ext#enableFx included} in order for the Element effects to work.</p><br/>
+ *
+ * <p><b><u>Method Chaining</u></b></p>
+ * <p>It is important to note that although the Fx methods and many non-Fx Element methods support "method chaining" in that
+ * they return the Element object itself as the method return value, it is not always possible to mix the two in a single
+ * method chain. The Fx methods use an internal effects queue so that each effect can be properly timed and sequenced.
+ * Non-Fx methods, on the other hand, have no such internal queueing and will always execute immediately. For this reason,
+ * while it may be possible to mix certain Fx and non-Fx method calls in a single chain, it may not always provide the
+ * expected results and should be done with care. Also see <tt>{@link #callback}</tt>.</p><br/>
+ *
+ * <p><b><u>Anchor Options for Motion Effects</u></b></p>
+ * <p>Motion effects support 8-way anchoring, meaning that you can choose one of 8 different anchor points on the Element
+ * that will serve as either the start or end point of the animation. Following are all of the supported anchor positions:</p>
+<pre>
+Value Description
+----- -----------------------------
+tl The top left corner
+t The center of the top edge
+tr The top right corner
+l The center of the left edge
+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>
+ * <b>Note</b>: some Fx methods accept specific custom config parameters. The options shown in the Config Options
+ * section below are common options that can be passed to any Fx method unless otherwise noted.</b>
+ *
+ * @cfg {Function} callback A function called when the effect is finished. Note that effects are queued internally by the
+ * Fx class, so a callback is not required to specify another effect -- effects can simply be chained together
+ * and called in sequence (see note for <b><u>Method Chaining</u></b> above), for example:<pre><code>
+ * el.slideIn().highlight();
+ * </code></pre>
+ * The callback is intended for any additional code that should run once a particular effect has completed. The Element
+ * being operated upon is passed as the first parameter.
+ *
+ * @cfg {Object} scope The scope (<code>this</code> reference) in which the <tt>{@link #callback}</tt> function is executed. Defaults to the browser window.
+ *
+ * @cfg {String} easing A valid Ext.lib.Easing value for the effect:</p><div class="mdetail-params"><ul>
+ * <li><b><tt>backBoth</tt></b></li>
+ * <li><b><tt>backIn</tt></b></li>
+ * <li><b><tt>backOut</tt></b></li>
+ * <li><b><tt>bounceBoth</tt></b></li>
+ * <li><b><tt>bounceIn</tt></b></li>
+ * <li><b><tt>bounceOut</tt></b></li>
+ * <li><b><tt>easeBoth</tt></b></li>
+ * <li><b><tt>easeBothStrong</tt></b></li>
+ * <li><b><tt>easeIn</tt></b></li>
+ * <li><b><tt>easeInStrong</tt></b></li>
+ * <li><b><tt>easeNone</tt></b></li>
+ * <li><b><tt>easeOut</tt></b></li>
+ * <li><b><tt>easeOutStrong</tt></b></li>
+ * <li><b><tt>elasticBoth</tt></b></li>
+ * <li><b><tt>elasticIn</tt></b></li>
+ * <li><b><tt>elasticOut</tt></b></li>
+ * </ul></div>
+ *
+ * @cfg {String} afterCls A css class to apply after the effect
+ * @cfg {Number} duration The length of time (in seconds) that the effect should last
+ *
+ * @cfg {Number} endOpacity Only applicable for {@link #fadeIn} or {@link #fadeOut}, a number between
+ * <tt>0</tt> and <tt>1</tt> inclusive to configure the ending opacity value.
+ *
+ * @cfg {Boolean} remove Whether the Element should be removed from the DOM and destroyed after the effect finishes
+ * @cfg {Boolean} useDisplay Whether to use the <i>display</i> CSS property instead of <i>visibility</i> when hiding Elements (only applies to
+ * effects that end with the element being visually hidden, ignored otherwise)
+ * @cfg {String/Object/Function} afterStyle A style specification string, e.g. <tt>"width:100px"</tt>, or an object
+ * in the form <tt>{width:"100px"}</tt>, or a function which returns such a specification that will be applied to the
+ * Element after the effect finishes.
+ * @cfg {Boolean} block Whether the effect should block other effects from queueing while it runs
+ * @cfg {Boolean} concurrent Whether to allow subsequently-queued effects to run at the same time as the current effect, or to ensure that they run in sequence
+ * @cfg {Boolean} stopFx Whether preceding effects should be stopped and removed before running current effect (only applies to non blocking effects)
+ */
+Ext.Fx = {
+
+ // private - calls the function taking arguments from the argHash based on the key. Returns the return value of the function.
+ // this is useful for replacing switch statements (for example).
+ switchStatements : function(key, fn, argHash){
+ return fn.apply(this, argHash[key]);
},
-
+
/**
- * 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};
+ * Slides the element into view. An anchor point can be optionally passed to set the point of
+ * origin for the slide effect. This function automatically handles wrapping the element with
+ * a fixed-size container if needed. See the Fx class overview for valid anchor point options.
+ * Usage:
+ *<pre><code>
+// default: slide the element in from the top
+el.slideIn();
- var me = this,
- proxy = renderTo ? Ext.DomHelper.append(renderTo, config, true) :
- Ext.DomHelper.insertBefore(me.dom, config, true);
+// custom: slide the element in from the right with a 2-second duration
+el.slideIn('r', { duration: 2 });
- if(matchBox && me.setBox && me.getBox){ // check to make sure Element.position.js is loaded
- proxy.setBox(me.getBox());
- }
- return proxy;
- }
+// common config options shown with default values
+el.slideIn('t', {
+ easing: 'easeOut',
+ duration: .5
});
-
-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 The function to call after the animation finishes
- * @return {Ext.Element} this
+</code></pre>
+ * @param {String} anchor (optional) One of the valid Fx anchor positions (defaults to top: 't')
+ * @param {Object} options (optional) Object literal with any of the Fx config options
+ * @return {Ext.Element} The Element
*/
- anchorTo : function(el, alignment, offsets, animate, monitorScroll, callback){
- var me = this,
+ slideIn : function(anchor, o){
+ o = getObject(o);
+ 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();
+ st = dom.style,
+ xy,
+ r,
+ b,
+ wrap,
+ after,
+ st,
+ args,
+ pt,
+ bw,
+ bh;
- // previous listener anchor, remove it
- this.removeAnchor();
- Ext.apply(anchor, {
- fn: action,
- scroll: scroll
- });
+ anchor = anchor || "t";
- Ext.EventManager.onWindowResize(action, null);
-
- if(scroll){
- Ext.EventManager.on(window, 'scroll', action, null,
- {buffer: !isNaN(monitorScroll) ? monitorScroll : 50});
- }
- action.call(me); // align immediately
+ me.queueFx(o, function(){
+ xy = fly(dom).getXY();
+ // fix display to visibility
+ fly(dom).fixDisplay();
+
+ // restore values after effect
+ r = fly(dom).getFxRestore();
+ b = {x: xy[0], y: xy[1], 0: xy[0], 1: xy[1], width: dom.offsetWidth, height: dom.offsetHeight};
+ b.right = b.x + b.width;
+ b.bottom = b.y + b.height;
+
+ // fixed size for slide
+ fly(dom).setWidth(b.width).setHeight(b.height);
+
+ // wrap if needed
+ wrap = fly(dom).fxWrap(r.pos, o, HIDDEN);
+
+ st.visibility = VISIBLE;
+ st.position = ABSOLUTE;
+
+ // clear out temp styles after slide and unwrap
+ function after(){
+ fly(dom).fxUnwrap(wrap, r.pos, o);
+ st.width = r.width;
+ st.height = r.height;
+ fly(dom).afterFx(o);
+ }
+
+ // time to calculate the positions
+ pt = {to: [b.x, b.y]};
+ bw = {to: b.width};
+ bh = {to: b.height};
+
+ function argCalc(wrap, style, ww, wh, sXY, sXYval, s1, s2, w, h, p){
+ var ret = {};
+ fly(wrap).setWidth(ww).setHeight(wh);
+ if(fly(wrap)[sXY]){
+ fly(wrap)[sXY](sXYval);
+ }
+ style[s1] = style[s2] = "0";
+ if(w){
+ ret.width = w;
+ }
+ if(h){
+ ret.height = h;
+ }
+ if(p){
+ ret.points = p;
+ }
+ return ret;
+ };
+
+ args = fly(dom).switchStatements(anchor.toLowerCase(), argCalc, {
+ t : [wrap, st, b.width, 0, NULL, NULL, LEFT, BOTTOM, NULL, bh, NULL],
+ l : [wrap, st, 0, b.height, NULL, NULL, RIGHT, TOP, bw, NULL, NULL],
+ r : [wrap, st, b.width, b.height, SETX, b.right, LEFT, TOP, NULL, NULL, pt],
+ b : [wrap, st, b.width, b.height, SETY, b.bottom, LEFT, TOP, NULL, bh, pt],
+ tl : [wrap, st, 0, 0, NULL, NULL, RIGHT, BOTTOM, bw, bh, pt],
+ bl : [wrap, st, 0, 0, SETY, b.y + b.height, RIGHT, TOP, bw, bh, pt],
+ br : [wrap, st, 0, 0, SETXY, [b.right, b.bottom], LEFT, TOP, bw, bh, pt],
+ tr : [wrap, st, 0, 0, SETX, b.x + b.width, LEFT, BOTTOM, bw, bh, pt]
+ });
+
+ st.visibility = VISIBLE;
+ fly(wrap).show();
+
+ arguments.callee.anim = fly(wrap).fxanim(args,
+ o,
+ MOTION,
+ .5,
+ EASEOUT,
+ after);
+ });
return me;
},
/**
- * Remove any anchor to this element. See {@link #anchorTo}.
- * @return {Ext.Element} this
+ * Slides the element out of view. An anchor point can be optionally passed to set the end point
+ * for the slide effect. When the effect is completed, the element will be hidden (visibility =
+ * 'hidden') but block elements will still take up space in the document. The element must be removed
+ * from the DOM using the 'remove' config option if desired. This function automatically handles
+ * wrapping the element with a fixed-size container if needed. See the Fx class overview for valid anchor point options.
+ * Usage:
+ *<pre><code>
+// default: slide the element out to the top
+el.slideOut();
+
+// custom: slide the element out to the right with a 2-second duration
+el.slideOut('r', { duration: 2 });
+
+// common config options shown with default values
+el.slideOut('t', {
+ easing: 'easeOut',
+ duration: .5,
+ remove: false,
+ useDisplay: false
+});
+</code></pre>
+ * @param {String} anchor (optional) One of the valid Fx anchor positions (defaults to top: 't')
+ * @param {Object} options (optional) Object literal with any of the Fx config options
+ * @return {Ext.Element} The Element
*/
- removeAnchor : function(){
+ slideOut : function(anchor, o){
+ o = getObject(o);
var me = this,
- anchor = this.getAnchor();
+ dom = me.dom,
+ st = dom.style,
+ xy = me.getXY(),
+ wrap,
+ r,
+ b,
+ a,
+ zero = {to: 0};
+
+ anchor = anchor || "t";
+
+ me.queueFx(o, function(){
- 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');
+ // restore values after effect
+ r = fly(dom).getFxRestore();
+ b = {x: xy[0], y: xy[1], 0: xy[0], 1: xy[1], width: dom.offsetWidth, height: dom.offsetHeight};
+ b.right = b.x + b.width;
+ b.bottom = b.y + b.height;
+
+ // fixed size for slide
+ fly(dom).setWidth(b.width).setHeight(b.height);
+
+ // wrap if needed
+ wrap = fly(dom).fxWrap(r.pos, o, VISIBLE);
+
+ st.visibility = VISIBLE;
+ st.position = ABSOLUTE;
+ fly(wrap).setWidth(b.width).setHeight(b.height);
+
+ function after(){
+ o.useDisplay ? fly(dom).setDisplayed(FALSE) : fly(dom).hide();
+ fly(dom).fxUnwrap(wrap, r.pos, o);
+ st.width = r.width;
+ st.height = r.height;
+ fly(dom).afterFx(o);
+ }
- if(!anchor){
- anchor = data(dom, '_anchor', {});
- }
- return anchor;
+ function argCalc(style, s1, s2, p1, v1, p2, v2, p3, v3){
+ var ret = {};
+
+ style[s1] = style[s2] = "0";
+ ret[p1] = v1;
+ if(p2){
+ ret[p2] = v2;
+ }
+ if(p3){
+ ret[p3] = v3;
+ }
+
+ return ret;
+ };
+
+ a = fly(dom).switchStatements(anchor.toLowerCase(), argCalc, {
+ t : [st, LEFT, BOTTOM, HEIGHT, zero],
+ l : [st, RIGHT, TOP, WIDTH, zero],
+ r : [st, LEFT, TOP, WIDTH, zero, POINTS, {to : [b.right, b.y]}],
+ b : [st, LEFT, TOP, HEIGHT, zero, POINTS, {to : [b.x, b.bottom]}],
+ tl : [st, RIGHT, BOTTOM, WIDTH, zero, HEIGHT, zero],
+ bl : [st, RIGHT, TOP, WIDTH, zero, HEIGHT, zero, POINTS, {to : [b.x, b.bottom]}],
+ br : [st, LEFT, TOP, WIDTH, zero, HEIGHT, zero, POINTS, {to : [b.x + b.width, b.bottom]}],
+ tr : [st, LEFT, BOTTOM, WIDTH, zero, HEIGHT, zero, POINTS, {to : [b.right, b.y]}]
+ });
+
+ arguments.callee.anim = fly(wrap).fxanim(a,
+ o,
+ MOTION,
+ .5,
+ EASEOUT,
+ after);
+ });
+ return me;
},
/**
- * 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]
+ * Fades the element out while slowly expanding it in all directions. When the effect is completed, the
+ * element will be hidden (visibility = 'hidden') but block elements will still take up space in the document.
+ * The element must be removed from the DOM using the 'remove' config option if desired.
+ * Usage:
+ *<pre><code>
+// default
+el.puff();
+
+// common config options shown with default values
+el.puff({
+ easing: 'easeOut',
+ duration: .5,
+ remove: false,
+ useDisplay: false
+});
+</code></pre>
+ * @param {Object} options (optional) Object literal with any of the Fx config options
+ * @return {Ext.Element} The Element
*/
- 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();
-
+ puff : function(o){
+ o = getObject(o);
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);
+ dom = me.dom,
+ st = dom.style,
+ width,
+ height,
+ r;
- x = a2[0] - a1[0] + o[0];
- y = a2[1] - a1[1] + o[1];
+ me.queueFx(o, function(){
+ width = fly(dom).getWidth();
+ height = fly(dom).getHeight();
+ fly(dom).clearOpacity();
+ fly(dom).show();
- 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"));
-
+ // restore values after effect
+ r = fly(dom).getFxRestore();
+
+ function after(){
+ o.useDisplay ? fly(dom).setDisplayed(FALSE) : fly(dom).hide();
+ fly(dom).clearOpacity();
+ fly(dom).setPositioning(r.pos);
+ st.width = r.width;
+ st.height = r.height;
+ st.fontSize = '';
+ fly(dom).afterFx(o);
+ }
- 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];
+ arguments.callee.anim = fly(dom).fxanim({
+ width : {to : fly(dom).adjustWidth(width * 2)},
+ height : {to : fly(dom).adjustHeight(height * 2)},
+ points : {by : [-width * .5, -height * .5]},
+ opacity : {to : 0},
+ fontSize: {to : 200, unit: "%"}
+ },
+ o,
+ MOTION,
+ .5,
+ EASEOUT,
+ after);
+ });
+ return me;
},
/**
- * 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?");
+ * Blinks the element as if it was clicked and then collapses on its center (similar to switching off a television).
+ * When the effect is completed, the element will be hidden (visibility = 'hidden') but block elements will still
+ * take up space in the document. The element must be removed from the DOM using the 'remove' config option if desired.
+ * Usage:
+ *<pre><code>
+// default
+el.switchOff();
-// 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]);
+// all config options shown with default values
+el.switchOff({
+ easing: 'easeIn',
+ duration: .3,
+ remove: false,
+ useDisplay: false
+});
</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
+ * @param {Object} options (optional) Object literal with any of the Fx config options
+ * @return {Ext.Element} The Element
*/
- 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;
- },
+ switchOff : function(o){
+ o = getObject(o);
+ var me = this,
+ dom = me.dom,
+ st = dom.style,
+ r;
- // private ==> used outside of core
- getConstrainToXY : function(el, local, offsets, proposedXY){
- var os = {top:0, left:0, bottom:0, right: 0};
+ me.queueFx(o, function(){
+ fly(dom).clearOpacity();
+ fly(dom).clip();
- return function(el, local, offsets, proposedXY){
- el = Ext.get(el);
- offsets = offsets ? Ext.applyIf(offsets, os) : os;
+ // restore values after effect
+ r = fly(dom).getFxRestore();
+
+ function after(){
+ o.useDisplay ? fly(dom).setDisplayed(FALSE) : fly(dom).hide();
+ fly(dom).clearOpacity();
+ fly(dom).setPositioning(r.pos);
+ st.width = r.width;
+ st.height = r.height;
+ fly(dom).afterFx(o);
+ };
- 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];
- }
- }
+ fly(dom).fxanim({opacity : {to : 0.3}},
+ NULL,
+ NULL,
+ .1,
+ NULL,
+ function(){
+ fly(dom).clearOpacity();
+ (function(){
+ fly(dom).fxanim({
+ height : {to : 1},
+ points : {by : [0, fly(dom).getHeight() * .5]}
+ },
+ o,
+ MOTION,
+ 0.3,
+ 'easeIn',
+ after);
+ }).defer(100);
+ });
+ });
+ return me;
+ },
- var s = el.getScroll();
+ /**
+ * Highlights the Element by setting a color (applies to the background-color by default, but can be
+ * changed using the "attr" config option) and then fading back to the original color. If no original
+ * color is available, you should provide the "endColor" config option which will be cleared after the animation.
+ * Usage:
+<pre><code>
+// default: highlight background to yellow
+el.highlight();
- vx += offsets.left + s.left;
- vy += offsets.top + s.top;
+// custom: highlight foreground text to blue for 2 seconds
+el.highlight("0000ff", { attr: 'color', duration: 2 });
- vw -= offsets.right;
- vh -= offsets.bottom;
+// common config options shown with default values
+el.highlight("ffff9c", {
+ attr: "background-color", //can be any valid CSS property (attribute) that supports a color value
+ endColor: (current color) or "ffffff",
+ easing: 'easeIn',
+ duration: 1
+});
+</code></pre>
+ * @param {String} color (optional) The highlight color. Should be a 6 char hex color without the leading # (defaults to yellow: 'ffff9c')
+ * @param {Object} options (optional) Object literal with any of the Fx config options
+ * @return {Ext.Element} The Element
+ */
+ highlight : function(color, o){
+ o = getObject(o);
+ var me = this,
+ dom = me.dom,
+ attr = o.attr || "backgroundColor",
+ a = {},
+ restore;
- var vr = vx+vw;
- var vb = vy+vh;
+ me.queueFx(o, function(){
+ fly(dom).clearOpacity();
+ fly(dom).show();
- var xy = proposedXY || (!local ? this.getXY() : [this.getLeft(true), this.getTop(true)]);
- var x = xy[0], y = xy[1];
- var w = this.dom.offsetWidth, h = this.dom.offsetHeight;
+ function after(){
+ dom.style[attr] = restore;
+ fly(dom).afterFx(o);
+ }
+ restore = dom.style[attr];
+ a[attr] = {from: color || "ffff9c", to: o.endColor || fly(dom).getColor(attr) || "ffffff"};
+ arguments.callee.anim = fly(dom).fxanim(a,
+ o,
+ 'color',
+ 1,
+ 'easeIn',
+ after);
+ });
+ return me;
+ },
+
+ /**
+ * Shows a ripple of exploding, attenuating borders to draw attention to an Element.
+ * Usage:
+<pre><code>
+// default: a single light blue ripple
+el.frame();
+
+// custom: 3 red ripples lasting 3 seconds total
+el.frame("ff0000", 3, { duration: 3 });
+
+// common config options shown with default values
+el.frame("C3DAF9", 1, {
+ duration: 1 //duration of each individual ripple.
+ // Note: Easing is not configurable and will be ignored if included
+});
+</code></pre>
+ * @param {String} color (optional) The color of the border. Should be a 6 char hex color without the leading # (defaults to light blue: 'C3DAF9').
+ * @param {Number} count (optional) The number of ripples to display (defaults to 1)
+ * @param {Object} options (optional) Object literal with any of the Fx config options
+ * @return {Ext.Element} The Element
+ */
+ frame : function(color, count, o){
+ o = getObject(o);
+ var me = this,
+ dom = me.dom,
+ proxy,
+ active;
+
+ me.queueFx(o, function(){
+ color = color || '#C3DAF9';
+ if(color.length == 6){
+ color = '#' + color;
+ }
+ count = count || 1;
+ fly(dom).show();
+
+ var xy = fly(dom).getXY(),
+ b = {x: xy[0], y: xy[1], 0: xy[0], 1: xy[1], width: dom.offsetWidth, height: dom.offsetHeight},
+ queue = function(){
+ proxy = fly(document.body || document.documentElement).createChild({
+ style:{
+ position : ABSOLUTE,
+ 'z-index': 35000, // yee haw
+ border : '0px solid ' + color
+ }
+ });
+ return proxy.queueFx({}, animFn);
+ };
+
+
+ arguments.callee.anim = {
+ isAnimated: true,
+ stop: function() {
+ count = 0;
+ proxy.stopFx();
+ }
+ };
+
+ function animFn(){
+ var scale = Ext.isBorderBox ? 2 : 1;
+ active = proxy.anim({
+ top : {from : b.y, to : b.y - 20},
+ left : {from : b.x, to : b.x - 20},
+ borderWidth : {from : 0, to : 10},
+ opacity : {from : 1, to : 0},
+ height : {from : b.height, to : b.height + 20 * scale},
+ width : {from : b.width, to : b.width + 20 * scale}
+ },{
+ duration: o.duration || 1,
+ callback: function() {
+ proxy.remove();
+ --count > 0 ? queue() : fly(dom).afterFx(o);
+ }
+ });
+ arguments.callee.anim = {
+ isAnimated: true,
+ stop: function(){
+ active.stop();
+ }
+ };
+ };
+ queue();
+ });
+ return me;
+ },
+
+ /**
+ * Creates a pause before any subsequent queued effects begin. If there are
+ * no effects queued after the pause it will have no effect.
+ * Usage:
+<pre><code>
+el.pause(1);
+</code></pre>
+ * @param {Number} seconds The length of time to pause (in seconds)
+ * @return {Ext.Element} The Element
+ */
+ pause : function(seconds){
+ var dom = this.dom,
+ t;
+
+ this.queueFx({}, function(){
+ t = setTimeout(function(){
+ fly(dom).afterFx({});
+ }, seconds * 1000);
+ arguments.callee.anim = {
+ isAnimated: true,
+ stop: function(){
+ clearTimeout(t);
+ fly(dom).afterFx({});
+ }
+ };
+ });
+ return this;
+ },
+
+ /**
+ * Fade an element in (from transparent to opaque). The ending opacity can be specified
+ * using the <tt>{@link #endOpacity}</tt> config option.
+ * Usage:
+<pre><code>
+// default: fade in from opacity 0 to 100%
+el.fadeIn();
+
+// custom: fade in from opacity 0 to 75% over 2 seconds
+el.fadeIn({ endOpacity: .75, duration: 2});
+
+// common config options shown with default values
+el.fadeIn({
+ endOpacity: 1, //can be any value between 0 and 1 (e.g. .5)
+ easing: 'easeOut',
+ duration: .5
+});
+</code></pre>
+ * @param {Object} options (optional) Object literal with any of the Fx config options
+ * @return {Ext.Element} The Element
+ */
+ fadeIn : function(o){
+ o = getObject(o);
+ var me = this,
+ dom = me.dom,
+ to = o.endOpacity || 1;
+
+ me.queueFx(o, function(){
+ fly(dom).setOpacity(0);
+ fly(dom).fixDisplay();
+ dom.style.visibility = VISIBLE;
+ arguments.callee.anim = fly(dom).fxanim({opacity:{to:to}},
+ o, NULL, .5, EASEOUT, function(){
+ if(to == 1){
+ fly(dom).clearOpacity();
+ }
+ fly(dom).afterFx(o);
+ });
+ });
+ return me;
+ },
+
+ /**
+ * Fade an element out (from opaque to transparent). The ending opacity can be specified
+ * using the <tt>{@link #endOpacity}</tt> config option. Note that IE may require
+ * <tt>{@link #useDisplay}:true</tt> in order to redisplay correctly.
+ * Usage:
+<pre><code>
+// default: fade out from the element's current opacity to 0
+el.fadeOut();
+
+// custom: fade out from the element's current opacity to 25% over 2 seconds
+el.fadeOut({ endOpacity: .25, duration: 2});
+
+// common config options shown with default values
+el.fadeOut({
+ endOpacity: 0, //can be any value between 0 and 1 (e.g. .5)
+ easing: 'easeOut',
+ duration: .5,
+ remove: false,
+ useDisplay: false
+});
+</code></pre>
+ * @param {Object} options (optional) Object literal with any of the Fx config options
+ * @return {Ext.Element} The Element
+ */
+ fadeOut : function(o){
+ o = getObject(o);
+ var me = this,
+ dom = me.dom,
+ style = dom.style,
+ to = o.endOpacity || 0;
+
+ me.queueFx(o, function(){
+ arguments.callee.anim = fly(dom).fxanim({
+ opacity : {to : to}},
+ o,
+ NULL,
+ .5,
+ EASEOUT,
+ function(){
+ if(to == 0){
+ Ext.Element.data(dom, 'visibilityMode') == Ext.Element.DISPLAY || o.useDisplay ?
+ style.display = "none" :
+ style.visibility = HIDDEN;
+
+ fly(dom).clearOpacity();
+ }
+ fly(dom).afterFx(o);
+ });
+ });
+ return me;
+ },
+
+ /**
+ * Animates the transition of an element's dimensions from a starting height/width
+ * to an ending height/width. This method is a convenience implementation of {@link shift}.
+ * Usage:
+<pre><code>
+// change height and width to 100x100 pixels
+el.scale(100, 100);
+
+// common config options shown with default values. The height and width will default to
+// the element's existing values if passed as null.
+el.scale(
+ [element's width],
+ [element's height], {
+ easing: 'easeOut',
+ duration: .35
+ }
+);
+</code></pre>
+ * @param {Number} width The new width (pass undefined to keep the original width)
+ * @param {Number} height The new height (pass undefined to keep the original height)
+ * @param {Object} options (optional) Object literal with any of the Fx config options
+ * @return {Ext.Element} The Element
+ */
+ scale : function(w, h, o){
+ this.shift(Ext.apply({}, o, {
+ width: w,
+ height: h
+ }));
+ return this;
+ },
+
+ /**
+ * Animates the transition of any combination of an element's dimensions, xy position and/or opacity.
+ * Any of these properties not specified in the config object will not be changed. This effect
+ * requires that at least one new dimension, position or opacity setting must be passed in on
+ * the config object in order for the function to have any effect.
+ * Usage:
+<pre><code>
+// slide the element horizontally to x position 200 while changing the height and opacity
+el.shift({ x: 200, height: 50, opacity: .8 });
+
+// common config options shown with default values.
+el.shift({
+ width: [element's width],
+ height: [element's height],
+ x: [element's x position],
+ y: [element's y position],
+ opacity: [element's opacity],
+ easing: 'easeOut',
+ duration: .35
+});
+</code></pre>
+ * @param {Object} options Object literal with any of the Fx config options
+ * @return {Ext.Element} The Element
+ */
+ shift : function(o){
+ o = getObject(o);
+ var dom = this.dom,
+ a = {};
+
+ this.queueFx(o, function(){
+ for (var prop in o) {
+ if (o[prop] != UNDEFINED) {
+ a[prop] = {to : o[prop]};
+ }
+ }
+
+ a.width ? a.width.to = fly(dom).adjustWidth(o.width) : a;
+ a.height ? a.height.to = fly(dom).adjustWidth(o.height) : a;
+
+ if (a.x || a.y || a.xy) {
+ a.points = a.xy ||
+ {to : [ a.x ? a.x.to : fly(dom).getX(),
+ a.y ? a.y.to : fly(dom).getY()]};
+ }
+
+ arguments.callee.anim = fly(dom).fxanim(a,
+ o,
+ MOTION,
+ .35,
+ EASEOUT,
+ function(){
+ fly(dom).afterFx(o);
+ });
+ });
+ return this;
+ },
+
+ /**
+ * Slides the element while fading it out of view. An anchor point can be optionally passed to set the
+ * ending point of the effect.
+ * Usage:
+ *<pre><code>
+// default: slide the element downward while fading out
+el.ghost();
+
+// custom: slide the element out to the right with a 2-second duration
+el.ghost('r', { duration: 2 });
+
+// common config options shown with default values
+el.ghost('b', {
+ easing: 'easeOut',
+ duration: .5,
+ remove: false,
+ useDisplay: false
+});
+</code></pre>
+ * @param {String} anchor (optional) One of the valid Fx anchor positions (defaults to bottom: 'b')
+ * @param {Object} options (optional) Object literal with any of the Fx config options
+ * @return {Ext.Element} The Element
+ */
+ ghost : function(anchor, o){
+ o = getObject(o);
+ var me = this,
+ dom = me.dom,
+ st = dom.style,
+ a = {opacity: {to: 0}, points: {}},
+ pt = a.points,
+ r,
+ w,
+ h;
+
+ anchor = anchor || "b";
+
+ me.queueFx(o, function(){
+ // restore values after effect
+ r = fly(dom).getFxRestore();
+ w = fly(dom).getWidth();
+ h = fly(dom).getHeight();
+
+ function after(){
+ o.useDisplay ? fly(dom).setDisplayed(FALSE) : fly(dom).hide();
+ fly(dom).clearOpacity();
+ fly(dom).setPositioning(r.pos);
+ st.width = r.width;
+ st.height = r.height;
+ fly(dom).afterFx(o);
+ }
+
+ pt.by = fly(dom).switchStatements(anchor.toLowerCase(), function(v1,v2){ return [v1, v2];}, {
+ t : [0, -h],
+ l : [-w, 0],
+ r : [w, 0],
+ b : [0, h],
+ tl : [-w, -h],
+ bl : [-w, h],
+ br : [w, h],
+ tr : [w, -h]
+ });
+
+ arguments.callee.anim = fly(dom).fxanim(a,
+ o,
+ MOTION,
+ .5,
+ EASEOUT, after);
+ });
+ return me;
+ },
+
+ /**
+ * Ensures that all effects queued after syncFx is called on the element are
+ * run concurrently. This is the opposite of {@link #sequenceFx}.
+ * @return {Ext.Element} The Element
+ */
+ syncFx : function(){
+ var me = this;
+ me.fxDefaults = Ext.apply(me.fxDefaults || {}, {
+ block : FALSE,
+ concurrent : TRUE,
+ stopFx : FALSE
+ });
+ return me;
+ },
+
+ /**
+ * Ensures that all effects queued after sequenceFx is called on the element are
+ * run in sequence. This is the opposite of {@link #syncFx}.
+ * @return {Ext.Element} The Element
+ */
+ sequenceFx : function(){
+ var me = this;
+ me.fxDefaults = Ext.apply(me.fxDefaults || {}, {
+ block : FALSE,
+ concurrent : FALSE,
+ stopFx : FALSE
+ });
+ return me;
+ },
+
+ /* @private */
+ nextFx : function(){
+ var ef = getQueue(this.dom.id)[0];
+ if(ef){
+ ef.call(this);
+ }
+ },
+
+ /**
+ * Returns true if the element has any effects actively running or queued, else returns false.
+ * @return {Boolean} True if element has active effects, else false
+ */
+ hasActiveFx : function(){
+ return getQueue(this.dom.id)[0];
+ },
+
+ /**
+ * Stops any running effects and clears the element's internal effects queue if it contains
+ * any additional effects that haven't started yet.
+ * @return {Ext.Element} The Element
+ */
+ stopFx : function(finish){
+ var me = this,
+ id = me.dom.id;
+ if(me.hasActiveFx()){
+ var cur = getQueue(id)[0];
+ if(cur && cur.anim){
+ if(cur.anim.isAnimated){
+ setQueue(id, [cur]); //clear
+ cur.anim.stop(finish !== undefined ? finish : TRUE);
+ }else{
+ setQueue(id, []);
+ }
+ }
+ }
+ return me;
+ },
+
+ /* @private */
+ beforeFx : function(o){
+ if(this.hasActiveFx() && !o.concurrent){
+ if(o.stopFx){
+ this.stopFx();
+ return TRUE;
+ }
+ return FALSE;
+ }
+ return TRUE;
+ },
+
+ /**
+ * Returns true if the element is currently blocking so that no other effect can be queued
+ * until this effect is finished, else returns false if blocking is not set. This is commonly
+ * used to ensure that an effect initiated by a user action runs to completion prior to the
+ * same effect being restarted (e.g., firing only one effect even if the user clicks several times).
+ * @return {Boolean} True if blocking, else false
+ */
+ hasFxBlock : function(){
+ var q = getQueue(this.dom.id);
+ return q && q[0] && q[0].block;
+ },
+
+ /* @private */
+ queueFx : function(o, fn){
+ var me = fly(this.dom);
+ if(!me.hasFxBlock()){
+ Ext.applyIf(o, me.fxDefaults);
+ if(!o.concurrent){
+ var run = me.beforeFx(o);
+ fn.block = o.block;
+ getQueue(me.dom.id).push(fn);
+ if(run){
+ me.nextFx();
+ }
+ }else{
+ fn.call(me);
+ }
+ }
+ return me;
+ },
+
+ /* @private */
+ fxWrap : function(pos, o, vis){
+ var dom = this.dom,
+ wrap,
+ wrapXY;
+ if(!o.wrap || !(wrap = Ext.getDom(o.wrap))){
+ if(o.fixPosition){
+ wrapXY = fly(dom).getXY();
+ }
+ var div = document.createElement("div");
+ div.style.visibility = vis;
+ wrap = dom.parentNode.insertBefore(div, dom);
+ fly(wrap).setPositioning(pos);
+ if(fly(wrap).isStyle(POSITION, "static")){
+ fly(wrap).position("relative");
+ }
+ fly(dom).clearPositioning('auto');
+ fly(wrap).clip();
+ wrap.appendChild(dom);
+ if(wrapXY){
+ fly(wrap).setXY(wrapXY);
+ }
+ }
+ return wrap;
+ },
+
+ /* @private */
+ fxUnwrap : function(wrap, pos, o){
+ var dom = this.dom;
+ fly(dom).clearPositioning();
+ fly(dom).setPositioning(pos);
+ if(!o.wrap){
+ var pn = fly(wrap).dom.parentNode;
+ pn.insertBefore(dom, wrap);
+ fly(wrap).remove();
+ }
+ },
+
+ /* @private */
+ getFxRestore : function(){
+ var st = this.dom.style;
+ return {pos: this.getPositioning(), width: st.width, height : st.height};
+ },
+
+ /* @private */
+ afterFx : function(o){
+ var dom = this.dom,
+ id = dom.id;
+ if(o.afterStyle){
+ fly(dom).setStyle(o.afterStyle);
+ }
+ if(o.afterCls){
+ fly(dom).addClass(o.afterCls);
+ }
+ if(o.remove == TRUE){
+ fly(dom).remove();
+ }
+ if(o.callback){
+ o.callback.call(o.scope, fly(dom));
+ }
+ if(!o.concurrent){
+ getQueue(id).shift();
+ fly(dom).nextFx();
+ }
+ },
+
+ /* @private */
+ fxanim : function(args, opt, animType, defaultDur, defaultEase, cb){
+ animType = animType || 'run';
+ opt = opt || {};
+ var anim = Ext.lib.Anim[animType](
+ this.dom,
+ args,
+ (opt.duration || defaultDur) || .35,
+ (opt.easing || defaultEase) || EASEOUT,
+ cb,
+ this
+ );
+ opt.anim = anim;
+ return anim;
+ }
+};
+
+// backwards compat
+Ext.Fx.resize = Ext.Fx.scale;
+
+//When included, Ext.Fx is automatically applied to Element so that all basic
+//effects are available directly via the Element API
+Ext.Element.addMethods(Ext.Fx);
+})();
+/**
+ * @class 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>
+ * Example:<pre><code>
+var els = Ext.select("#some-el div.some-class");
+// or select directly from an existing element
+var el = Ext.get('some-el');
+el.select('div.some-class');
+
+els.setWidth(100); // all elements become 100 width
+els.hide(true); // all elements fade out and hide
+// or
+els.setWidth(100).hide(true);
+</code>
+ */
+Ext.CompositeElementLite = function(els, root){
+ /**
+ * <p>The Array of DOM elements which this CompositeElement encapsulates. Read-only.</p>
+ * <p>This will not <i>usually</i> be accessed in developers' code, but developers wishing
+ * to augment the capabilities of the CompositeElementLite class may use it when adding
+ * methods to the class.</p>
+ * <p>For example to add the <code>nextAll</code> method to the class to <b>add</b> all
+ * following siblings of selected elements, the code would be</p><code><pre>
+Ext.override(Ext.CompositeElementLite, {
+ nextAll: function() {
+ var els = this.elements, i, l = els.length, n, r = [], ri = -1;
+
+// Loop through all elements in this Composite, accumulating
+// an Array of all siblings.
+ for (i = 0; i < l; i++) {
+ for (n = els[i].nextSibling; n; n = n.nextSibling) {
+ r[++ri] = n;
+ }
+ }
+
+// Add all found siblings to this Composite
+ return this.add(r);
+ }
+});</pre></code>
+ * @type Array
+ * @property elements
+ */
+ this.elements = [];
+ this.add(els, root);
+ this.el = new Ext.Element.Flyweight();
+};
+
+Ext.CompositeElementLite.prototype = {
+ isComposite: true,
+
+ // private
+ getElement : function(el){
+ // Set the shared flyweight dom property to the current element
+ var e = this.el;
+ e.dom = el;
+ e.id = el.id;
+ return e;
+ },
+
+ // private
+ transformElement : function(el){
+ return Ext.getDom(el);
+ },
+
+ /**
+ * Returns the number of elements in this Composite.
+ * @return Number
+ */
+ getCount : function(){
+ return this.elements.length;
+ },
+ /**
+ * Adds elements to this Composite object.
+ * @param {Mixed} els Either an Array of DOM elements to add, or another Composite object who's elements should be added.
+ * @return {CompositeElement} This Composite object.
+ */
+ add : function(els, root){
+ var me = this,
+ elements = me.elements;
+ if(!els){
+ return this;
+ }
+ if(typeof els == "string"){
+ els = Ext.Element.selectorFunction(els, root);
+ }else if(els.isComposite){
+ els = els.elements;
+ }else if(!Ext.isIterable(els)){
+ els = [els];
+ }
+
+ for(var i = 0, len = els.length; i < len; ++i){
+ elements.push(me.transformElement(els[i]));
+ }
+ return me;
+ },
+
+ invoke : function(fn, args){
+ var me = this,
+ els = me.elements,
+ len = els.length,
+ e,
+ i;
+
+ for(i = 0; i < len; i++) {
+ e = els[i];
+ if(e){
+ Ext.Element.prototype[fn].apply(me.getElement(e), args);
+ }
+ }
+ return me;
+ },
+ /**
+ * Returns a flyweight Element of the dom element object at the specified index
+ * @param {Number} index
+ * @return {Ext.Element}
+ */
+ item : function(index){
+ var me = this,
+ el = me.elements[index],
+ out = null;
+
+ if(el){
+ out = me.getElement(el);
+ }
+ return out;
+ },
+
+ // fixes scope with flyweight
+ addListener : function(eventName, handler, scope, opt){
+ var els = this.elements,
+ len = els.length,
+ i, e;
+
+ for(i = 0; i<len; i++) {
+ e = els[i];
+ if(e) {
+ Ext.EventManager.on(e, eventName, handler, scope || e, opt);
+ }
+ }
+ return this;
+ },
+ /**
+ * <p>Calls the passed function for each element in this composite.</p>
+ * @param {Function} fn The function to call. The function is passed the following parameters:<ul>
+ * <li><b>el</b> : Element<div class="sub-desc">The current Element in the iteration.
+ * <b>This is the flyweight (shared) Ext.Element instance, so if you require a
+ * a reference to the dom node, use el.dom.</b></div></li>
+ * <li><b>c</b> : Composite<div class="sub-desc">This Composite object.</div></li>
+ * <li><b>idx</b> : Number<div class="sub-desc">The zero-based index in the iteration.</div></li>
+ * </ul>
+ * @param {Object} scope (optional) The scope (<i>this</i> reference) in which the function is executed. (defaults to the Element)
+ * @return {CompositeElement} this
+ */
+ each : function(fn, scope){
+ var me = this,
+ els = me.elements,
+ len = els.length,
+ i, e;
+
+ for(i = 0; i<len; i++) {
+ e = els[i];
+ if(e){
+ e = this.getElement(e);
+ if(fn.call(scope || e, e, me, i) === false){
+ break;
+ }
+ }
+ }
+ return me;
+ },
+
+ /**
+ * Clears this Composite and adds the elements passed.
+ * @param {Mixed} els Either an array of DOM elements, or another Composite from which to fill this Composite.
+ * @return {CompositeElement} this
+ */
+ fill : function(els){
+ var me = this;
+ me.elements = [];
+ me.add(els);
+ return me;
+ },
+
+ /**
+ * Filters this composite to only elements that match the passed selector.
+ * @param {String/Function} selector A string CSS selector or a comparison function.
+ * The comparison function will be called with the following arguments:<ul>
+ * <li><code>el</code> : Ext.Element<div class="sub-desc">The current DOM element.</div></li>
+ * <li><code>index</code> : Number<div class="sub-desc">The current index within the collection.</div></li>
+ * </ul>
+ * @return {CompositeElement} this
+ */
+ filter : function(selector){
+ var els = [],
+ me = this,
+ fn = Ext.isFunction(selector) ? selector
+ : function(el){
+ return el.is(selector);
+ };
+
+ me.each(function(el, self, i) {
+ if (fn(el, i) !== false) {
+ els[els.length] = me.transformElement(el);
+ }
+ });
+
+ me.elements = els;
+ return me;
+ },
+
+ /**
+ * Find the index of the passed element within the composite collection.
+ * @param el {Mixed} The id of an element, or an Ext.Element, or an HtmlElement to find within the composite collection.
+ * @return Number The index of the passed Ext.Element in the composite collection, or -1 if not found.
+ */
+ indexOf : function(el){
+ return this.elements.indexOf(this.transformElement(el));
+ },
+
+ /**
+ * Replaces the specified element with the passed element.
+ * @param {Mixed} el The id of an element, the Element itself, the index of the element in this composite
+ * to replace.
+ * @param {Mixed} replacement The id of an element or the Element itself.
+ * @param {Boolean} domReplace (Optional) True to remove and replace the element in the document too.
+ * @return {CompositeElement} this
+ */
+ replaceElement : function(el, replacement, domReplace){
+ var index = !isNaN(el) ? el : this.indexOf(el),
+ d;
+ if(index > -1){
+ replacement = Ext.getDom(replacement);
+ if(domReplace){
+ d = this.elements[index];
+ d.parentNode.insertBefore(replacement, d);
+ Ext.removeNode(d);
+ }
+ this.elements.splice(index, 1, replacement);
+ }
+ return this;
+ },
+
+ /**
+ * Removes all elements.
+ */
+ clear : function(){
+ this.elements = [];
+ }
+};
+
+Ext.CompositeElementLite.prototype.on = Ext.CompositeElementLite.prototype.addListener;
+
+/**
+ * @private
+ * Copies all of the functions from Ext.Element's prototype onto CompositeElementLite's prototype.
+ * This is called twice - once immediately below, and once again after additional Ext.Element
+ * are added in Ext JS
+ */
+Ext.CompositeElementLite.importElementMethods = function() {
+ var fnName,
+ ElProto = Ext.Element.prototype,
+ CelProto = Ext.CompositeElementLite.prototype;
+
+ for (fnName in ElProto) {
+ if (typeof ElProto[fnName] == 'function'){
+ (function(fnName) {
+ CelProto[fnName] = CelProto[fnName] || function() {
+ return this.invoke(fnName, arguments);
+ };
+ }).call(CelProto, fnName);
+
+ }
+ }
+};
+
+Ext.CompositeElementLite.importElementMethods();
+
+if(Ext.DomQuery){
+ Ext.Element.selectorFunction = Ext.DomQuery.select;
+}
+
+/**
+ * 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 {HTMLElement/String} root (optional) The root element of the query or id of the root
+ * @return {CompositeElementLite/CompositeElement}
+ * @member Ext.Element
+ * @method select
+ */
+Ext.Element.select = function(selector, 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 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 {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;
+(function(){
+ var BEFOREREQUEST = "beforerequest",
+ REQUESTCOMPLETE = "requestcomplete",
+ REQUESTEXCEPTION = "requestexception",
+ UNDEFINED = undefined,
+ LOAD = 'load',
+ POST = 'POST',
+ GET = 'GET',
+ WINDOW = window;
+
+ /**
+ * @class Ext.data.Connection
+ * @extends Ext.util.Observable
+ * <p>The class encapsulates a connection to the page's originating domain, allowing requests to be made
+ * either to a configured URL, or to a URL specified at request time.</p>
+ * <p>Requests made by this class are asynchronous, and will return immediately. No data from
+ * the server will be available to the statement immediately following the {@link #request} call.
+ * To process returned data, use a
+ * <a href="#request-option-success" ext:member="request-option-success" ext:cls="Ext.data.Connection">success callback</a>
+ * in the request options object,
+ * or an {@link #requestcomplete event listener}.</p>
+ * <p><h3>File Uploads</h3><a href="#request-option-isUpload" ext:member="request-option-isUpload" ext:cls="Ext.data.Connection">File uploads</a> 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 <tt><form></tt> 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 <tt><iframe></tt> which is inserted into the document
+ * but removed after the return data has been gathered.</p>
+ * <p>The server response is parsed by the browser to create the document for the IFRAME. If the
+ * server is using JSON to send the return object, then the
+ * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17">Content-Type</a> header
+ * must be set to "text/html" in order to tell the browser to insert the text unchanged into the document body.</p>
+ * <p>Characters which are significant to an HTML parser must be sent as HTML entities, so encode
+ * "<" as "&lt;", "&" as "&amp;" etc.</p>
+ * <p>The response text is retrieved from the document, and a fake XMLHttpRequest object
+ * is created containing a <tt>responseText</tt> property in order to conform to the
+ * requirements of event handlers and callbacks.</p>
+ * <p>Be aware that file upload packets are sent with the content type <a href="http://www.faqs.org/rfcs/rfc2388.html">multipart/form</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>
+ * <p>Also note that it's not possible to check the response code of the hidden iframe, so the success handler will ALWAYS fire.</p>
+ * @constructor
+ * @param {Object} config a configuration object.
+ */
+ Ext.data.Connection = function(config){
+ Ext.apply(this, config);
+ this.addEvents(
+ /**
+ * @event beforerequest
+ * Fires before a network request is made to retrieve a data object.
+ * @param {Connection} conn This Connection object.
+ * @param {Object} options The options config object passed to the {@link #request} method.
+ */
+ BEFOREREQUEST,
+ /**
+ * @event requestcomplete
+ * Fires if the request was successfully completed.
+ * @param {Connection} conn This Connection object.
+ * @param {Object} response The XHR object containing the response data.
+ * See <a href="http://www.w3.org/TR/XMLHttpRequest/">The XMLHttpRequest Object</a>
+ * for details.
+ * @param {Object} options The options config object passed to the {@link #request} method.
+ */
+ REQUESTCOMPLETE,
+ /**
+ * @event requestexception
+ * Fires if an error HTTP status was returned from the server.
+ * See <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html">HTTP Status Code Definitions</a>
+ * for details of HTTP status codes.
+ * @param {Connection} conn This Connection object.
+ * @param {Object} response The XHR object containing the response data.
+ * See <a href="http://www.w3.org/TR/XMLHttpRequest/">The XMLHttpRequest Object</a>
+ * for details.
+ * @param {Object} options The options config object passed to the {@link #request} method.
+ */
+ REQUESTEXCEPTION
+ );
+ Ext.data.Connection.superclass.constructor.call(this);
+ };
+
+ Ext.extend(Ext.data.Connection, Ext.util.Observable, {
+ /**
+ * @cfg {String} url (Optional) <p>The default URL to be used for requests to the server. Defaults to undefined.</p>
+ * <p>The <code>url</code> config may be a function which <i>returns</i> the URL to use for the Ajax request. The scope
+ * (<code><b>this</b></code> reference) of the function is the <code>scope</code> option passed to the {@link #request} method.</p>
+ */
+ /**
+ * @cfg {Object} extraParams (Optional) An object containing properties which are used as
+ * extra parameters to each request made by this object. (defaults to undefined)
+ */
+ /**
+ * @cfg {Object} defaultHeaders (Optional) An object containing request headers which are added
+ * to each request made by this object. (defaults to undefined)
+ */
+ /**
+ * @cfg {String} method (Optional) The default HTTP method to be used for requests.
+ * (defaults to undefined; if not set, but {@link #request} params are present, POST will be used;
+ * otherwise, GET will be used.)
+ */
+ /**
+ * @cfg {Number} timeout (Optional) The timeout in milliseconds to be used for requests. (defaults to 30000)
+ */
+ timeout : 30000,
+ /**
+ * @cfg {Boolean} autoAbort (Optional) Whether this request should abort any pending requests. (defaults to false)
+ * @type Boolean
+ */
+ autoAbort:false,
+
+ /**
+ * @cfg {Boolean} disableCaching (Optional) True to add a unique cache-buster param to GET requests. (defaults to true)
+ * @type Boolean
+ */
+ disableCaching: true,
+
+ /**
+ * @cfg {String} disableCachingParam (Optional) Change the parameter which is sent went disabling caching
+ * through a cache buster. Defaults to '_dc'
+ * @type String
+ */
+ disableCachingParam: '_dc',
+
+ /**
+ * <p>Sends an HTTP request to a remote server.</p>
+ * <p><b>Important:</b> Ajax server requests are asynchronous, and this call will
+ * return before the response has been received. Process any returned data
+ * in a callback function.</p>
+ * <pre><code>
+Ext.Ajax.request({
+ url: 'ajax_demo/sample.json',
+ success: function(response, opts) {
+ var obj = Ext.decode(response.responseText);
+ console.dir(obj);
+ },
+ failure: function(response, opts) {
+ console.log('server-side failure with status code ' + response.status);
+ }
+});
+ * </code></pre>
+ * <p>To execute a callback function in the correct scope, use the <tt>scope</tt> option.</p>
+ * @param {Object} options An object which may contain the following properties:<ul>
+ * <li><b>url</b> : String/Function (Optional)<div class="sub-desc">The URL to
+ * which to send the request, or a function to call which returns a URL string. The scope of the
+ * function is specified by the <tt>scope</tt> option. Defaults to the configured
+ * <tt>{@link #url}</tt>.</div></li>
+ * <li><b>params</b> : Object/String/Function (Optional)<div class="sub-desc">
+ * An object containing properties which are used as parameters to the
+ * request, a url encoded string or a function to call to get either. The scope of the function
+ * is specified by the <tt>scope</tt> option.</div></li>
+ * <li><b>method</b> : String (Optional)<div class="sub-desc">The HTTP method to use
+ * for the request. Defaults to the configured method, or if no method was configured,
+ * "GET" if no parameters are being sent, and "POST" if parameters are being sent. Note that
+ * the method name is case-sensitive and should be all caps.</div></li>
+ * <li><b>callback</b> : Function (Optional)<div class="sub-desc">The
+ * function to be called upon receipt of the HTTP response. The callback is
+ * called regardless of success or failure and is passed the following
+ * parameters:<ul>
+ * <li><b>options</b> : Object<div class="sub-desc">The parameter to the request call.</div></li>
+ * <li><b>success</b> : Boolean<div class="sub-desc">True if the request succeeded.</div></li>
+ * <li><b>response</b> : Object<div class="sub-desc">The XMLHttpRequest object containing the response data.
+ * See <a href="http://www.w3.org/TR/XMLHttpRequest/">http://www.w3.org/TR/XMLHttpRequest/</a> for details about
+ * accessing elements of the response.</div></li>
+ * </ul></div></li>
+ * <li><a id="request-option-success"></a><b>success</b> : Function (Optional)<div class="sub-desc">The function
+ * to be called upon success of the request. The callback is passed the following
+ * parameters:<ul>
+ * <li><b>response</b> : Object<div class="sub-desc">The XMLHttpRequest object containing the response data.</div></li>
+ * <li><b>options</b> : Object<div class="sub-desc">The parameter to the request call.</div></li>
+ * </ul></div></li>
+ * <li><b>failure</b> : Function (Optional)<div class="sub-desc">The function
+ * to be called upon failure of the request. The callback is passed the
+ * following parameters:<ul>
+ * <li><b>response</b> : Object<div class="sub-desc">The XMLHttpRequest object containing the response data.</div></li>
+ * <li><b>options</b> : Object<div class="sub-desc">The parameter to the request call.</div></li>
+ * </ul></div></li>
+ * <li><b>scope</b> : Object (Optional)<div class="sub-desc">The scope in
+ * which to execute the callbacks: The "this" object for the callback function. If the <tt>url</tt>, or <tt>params</tt> options were
+ * specified as functions from which to draw values, then this also serves as the scope for those function calls.
+ * Defaults to the browser window.</div></li>
+ * <li><b>timeout</b> : Number (Optional)<div class="sub-desc">The timeout in milliseconds to be used for this request. Defaults to 30 seconds.</div></li>
+ * <li><b>form</b> : Element/HTMLElement/String (Optional)<div class="sub-desc">The <tt><form></tt>
+ * Element or the id of the <tt><form></tt> to pull parameters from.</div></li>
+ * <li><a id="request-option-isUpload"></a><b>isUpload</b> : Boolean (Optional)<div class="sub-desc"><b>Only meaningful when used
+ * with the <tt>form</tt> option</b>.
+ * <p>True if the form object is a file upload (will be set automatically if the form was
+ * configured with <b><tt>enctype</tt></b> "multipart/form-data").</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 <tt><form></tt> 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 <tt><iframe></tt> which is inserted into the document
+ * but removed after the return data has been gathered.</p>
+ * <p>The server response is parsed by the browser to create the document for the IFRAME. If the
+ * server is using JSON to send the return object, then the
+ * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17">Content-Type</a> header
+ * must be set to "text/html" in order to tell the browser to insert the text unchanged into the document body.</p>
+ * <p>The response text is retrieved from the document, and a fake XMLHttpRequest object
+ * is created containing a <tt>responseText</tt> property in order to conform to the
+ * requirements of event handlers and callbacks.</p>
+ * <p>Be aware that file upload packets are sent with the content type <a href="http://www.faqs.org/rfcs/rfc2388.html">multipart/form</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>
+ * </div></li>
+ * <li><b>headers</b> : Object (Optional)<div class="sub-desc">Request
+ * headers to set for the request.</div></li>
+ * <li><b>xmlData</b> : Object (Optional)<div class="sub-desc">XML document
+ * to use for the post. Note: This will be used instead of params for the post
+ * data. Any params will be appended to the URL.</div></li>
+ * <li><b>jsonData</b> : Object/String (Optional)<div class="sub-desc">JSON
+ * data to use as the post. Note: This will be used instead of params for the post
+ * data. Any params will be appended to the URL.</div></li>
+ * <li><b>disableCaching</b> : Boolean (Optional)<div class="sub-desc">True
+ * to add a unique cache-buster param to GET requests.</div></li>
+ * </ul></p>
+ * <p>The options object may also contain any other property which might be needed to perform
+ * postprocessing in a callback because it is passed to callback functions.</p>
+ * @return {Number} transactionId The id of the server transaction. This may be used
+ * to cancel the request.
+ */
+ request : function(o){
+ var me = this;
+ if(me.fireEvent(BEFOREREQUEST, me, o)){
+ if (o.el) {
+ if(!Ext.isEmpty(o.indicatorText)){
+ me.indicatorText = '<div class="loading-indicator">'+o.indicatorText+"</div>";
+ }
+ if(me.indicatorText) {
+ Ext.getDom(o.el).innerHTML = me.indicatorText;
+ }
+ o.success = (Ext.isFunction(o.success) ? o.success : function(){}).createInterceptor(function(response) {
+ Ext.getDom(o.el).innerHTML = response.responseText;
+ });
+ }
- // only move it if it needs it
- var moved = false;
+ var p = o.params,
+ url = o.url || me.url,
+ method,
+ cb = {success: me.handleResponse,
+ failure: me.handleFailure,
+ scope: me,
+ argument: {options: o},
+ timeout : Ext.num(o.timeout, me.timeout)
+ },
+ form,
+ serForm;
+
+
+ if (Ext.isFunction(p)) {
+ p = p.call(o.scope||WINDOW, o);
+ }
+
+ p = Ext.urlEncode(me.extraParams, Ext.isObject(p) ? Ext.urlEncode(p) : p);
+
+ if (Ext.isFunction(url)) {
+ url = url.call(o.scope || WINDOW, o);
+ }
+
+ if((form = Ext.getDom(o.form))){
+ url = url || form.action;
+ if(o.isUpload || (/multipart\/form-data/i.test(form.getAttribute("enctype")))) {
+ return me.doFormUpload.call(me, o, p, url);
+ }
+ serForm = Ext.lib.Ajax.serializeForm(form);
+ p = p ? (p + '&' + serForm) : serForm;
+ }
+
+ method = o.method || me.method || ((p || o.xmlData || o.jsonData) ? POST : GET);
+
+ if(method === GET && (me.disableCaching && o.disableCaching !== false) || o.disableCaching === true){
+ var dcp = o.disableCachingParam || me.disableCachingParam;
+ url = Ext.urlAppend(url, dcp + '=' + (new Date().getTime()));
+ }
+
+ o.headers = Ext.apply(o.headers || {}, me.defaultHeaders || {});
+
+ if(o.autoAbort === true || me.autoAbort) {
+ me.abort();
+ }
+
+ if((method == GET || o.xmlData || o.jsonData) && p){
+ url = Ext.urlAppend(url, p);
+ p = '';
+ }
+ return (me.transId = Ext.lib.Ajax.request(method, url, cb, p, o));
+ }else{
+ return o.callback ? o.callback.apply(o.scope, [o,UNDEFINED,UNDEFINED]) : null;
+ }
+ },
+
+ /**
+ * Determine whether this object has a request outstanding.
+ * @param {Number} transactionId (Optional) defaults to the last transaction
+ * @return {Boolean} True if there is an outstanding request.
+ */
+ isLoading : function(transId){
+ return transId ? Ext.lib.Ajax.isCallInProgress(transId) : !! this.transId;
+ },
+
+ /**
+ * Aborts any outstanding request.
+ * @param {Number} transactionId (Optional) defaults to the last transaction
+ */
+ abort : function(transId){
+ if(transId || this.isLoading()){
+ Ext.lib.Ajax.abort(transId || this.transId);
+ }
+ },
+
+ // private
+ handleResponse : function(response){
+ this.transId = false;
+ var options = response.argument.options;
+ response.argument = options ? options.argument : null;
+ this.fireEvent(REQUESTCOMPLETE, this, response, options);
+ if(options.success){
+ options.success.call(options.scope, response, options);
+ }
+ if(options.callback){
+ options.callback.call(options.scope, options, true, response);
+ }
+ },
+
+ // private
+ handleFailure : function(response, e){
+ this.transId = false;
+ var options = response.argument.options;
+ response.argument = options ? options.argument : null;
+ this.fireEvent(REQUESTEXCEPTION, this, response, options, e);
+ if(options.failure){
+ options.failure.call(options.scope, response, options);
+ }
+ if(options.callback){
+ options.callback.call(options.scope, options, false, response);
+ }
+ },
+
+ // private
+ doFormUpload : function(o, ps, url){
+ var id = Ext.id(),
+ doc = document,
+ frame = doc.createElement('iframe'),
+ form = Ext.getDom(o.form),
+ hiddens = [],
+ hd,
+ encoding = 'multipart/form-data',
+ buf = {
+ target: form.target,
+ method: form.method,
+ encoding: form.encoding,
+ enctype: form.enctype,
+ action: form.action
+ };
+
+ /*
+ * Originally this behaviour was modified for Opera 10 to apply the secure URL after
+ * the frame had been added to the document. It seems this has since been corrected in
+ * Opera so the behaviour has been reverted, the URL will be set before being added.
+ */
+ Ext.fly(frame).set({
+ id: id,
+ name: id,
+ cls: 'x-hidden',
+ src: Ext.SSL_SECURE_URL
+ });
+
+ doc.body.appendChild(frame);
+
+ // This is required so that IE doesn't pop the response up in a new window.
+ if(Ext.isIE){
+ document.frames[id].name = id;
+ }
+
+
+ Ext.fly(form).set({
+ target: id,
+ method: POST,
+ enctype: encoding,
+ encoding: encoding,
+ action: url || buf.action
+ });
+
+ // add dynamic params
+ Ext.iterate(Ext.urlDecode(ps, false), function(k, v){
+ hd = doc.createElement('input');
+ Ext.fly(hd).set({
+ type: 'hidden',
+ value: v,
+ name: k
+ });
+ form.appendChild(hd);
+ hiddens.push(hd);
+ });
+
+ function cb(){
+ var me = this,
+ // bogus response object
+ r = {responseText : '',
+ responseXML : null,
+ argument : o.argument},
+ doc,
+ firstChild;
+
+ try{
+ doc = frame.contentWindow.document || frame.contentDocument || WINDOW.frames[id].document;
+ if(doc){
+ if(doc.body){
+ if(/textarea/i.test((firstChild = doc.body.firstChild || {}).tagName)){ // json response wrapped in textarea
+ r.responseText = firstChild.value;
+ }else{
+ r.responseText = doc.body.innerHTML;
+ }
+ }
+ //in IE the document may still have a body even if returns XML.
+ r.responseXML = doc.XMLDocument || doc;
+ }
+ }
+ catch(e) {}
+
+ Ext.EventManager.removeListener(frame, LOAD, cb, me);
+
+ me.fireEvent(REQUESTCOMPLETE, me, r, o);
+
+ function runCallback(fn, scope, args){
+ if(Ext.isFunction(fn)){
+ fn.apply(scope, args);
+ }
+ }
+
+ runCallback(o.success, o.scope, [r, o]);
+ runCallback(o.callback, o.scope, [o, true, r]);
+
+ if(!me.debugUploads){
+ setTimeout(function(){Ext.removeNode(frame);}, 100);
+ }
+ }
+
+ Ext.EventManager.on(frame, LOAD, cb, this);
+ form.submit();
+
+ Ext.fly(form).set(buf);
+ Ext.each(hiddens, function(h) {
+ Ext.removeNode(h);
+ });
+ }
+ });
+})();
+
+/**
+ * @class Ext.Ajax
+ * @extends Ext.data.Connection
+ * <p>The global Ajax request class that provides a simple way to make Ajax requests
+ * with maximum flexibility.</p>
+ * <p>Since Ext.Ajax is a singleton, you can set common properties/events for it once
+ * and override them at the request function level only if necessary.</p>
+ * <p>Common <b>Properties</b> you may want to set are:<div class="mdetail-params"><ul>
+ * <li><b><tt>{@link #method}</tt></b><p class="sub-desc"></p></li>
+ * <li><b><tt>{@link #extraParams}</tt></b><p class="sub-desc"></p></li>
+ * <li><b><tt>{@link #url}</tt></b><p class="sub-desc"></p></li>
+ * </ul></div>
+ * <pre><code>
+// Default headers to pass in every request
+Ext.Ajax.defaultHeaders = {
+ 'Powered-By': 'Ext'
+};
+ * </code></pre>
+ * </p>
+ * <p>Common <b>Events</b> you may want to set are:<div class="mdetail-params"><ul>
+ * <li><b><tt>{@link Ext.data.Connection#beforerequest beforerequest}</tt></b><p class="sub-desc"></p></li>
+ * <li><b><tt>{@link Ext.data.Connection#requestcomplete requestcomplete}</tt></b><p class="sub-desc"></p></li>
+ * <li><b><tt>{@link Ext.data.Connection#requestexception requestexception}</tt></b><p class="sub-desc"></p></li>
+ * </ul></div>
+ * <pre><code>
+// Example: show a spinner during all Ajax requests
+Ext.Ajax.on('beforerequest', this.showSpinner, this);
+Ext.Ajax.on('requestcomplete', this.hideSpinner, this);
+Ext.Ajax.on('requestexception', this.hideSpinner, this);
+ * </code></pre>
+ * </p>
+ * <p>An example request:</p>
+ * <pre><code>
+// Basic request
+Ext.Ajax.{@link Ext.data.Connection#request request}({
+ url: 'foo.php',
+ success: someFn,
+ failure: otherFn,
+ headers: {
+ 'my-header': 'foo'
+ },
+ params: { foo: 'bar' }
+});
+
+// Simple ajax form submission
+Ext.Ajax.{@link Ext.data.Connection#request request}({
+ form: 'some-form',
+ params: 'foo=bar'
+});
+ * </code></pre>
+ * </p>
+ * @singleton
+ */
+Ext.Ajax = new Ext.data.Connection({
+ /**
+ * @cfg {String} url @hide
+ */
+ /**
+ * @cfg {Object} extraParams @hide
+ */
+ /**
+ * @cfg {Object} defaultHeaders @hide
+ */
+ /**
+ * @cfg {String} method (Optional) @hide
+ */
+ /**
+ * @cfg {Number} timeout (Optional) @hide
+ */
+ /**
+ * @cfg {Boolean} autoAbort (Optional) @hide
+ */
+
+ /**
+ * @cfg {Boolean} disableCaching (Optional) @hide
+ */
+
+ /**
+ * @property disableCaching
+ * True to add a unique cache-buster param to GET requests. (defaults to true)
+ * @type Boolean
+ */
+ /**
+ * @property url
+ * The default URL to be used for requests to the server. (defaults to undefined)
+ * If the server receives all requests through one URL, setting this once is easier than
+ * entering it on every request.
+ * @type String
+ */
+ /**
+ * @property extraParams
+ * An object containing properties which are used as extra parameters to each request made
+ * by this object (defaults to undefined). Session information and other data that you need
+ * to pass with each request are commonly put here.
+ * @type Object
+ */
+ /**
+ * @property defaultHeaders
+ * An object containing request headers which are added to each request made by this object
+ * (defaults to undefined).
+ * @type Object
+ */
+ /**
+ * @property method
+ * The default HTTP method to be used for requests. Note that this is case-sensitive and
+ * should be all caps (defaults to undefined; if not set but params are present will use
+ * <tt>"POST"</tt>, otherwise will use <tt>"GET"</tt>.)
+ * @type String
+ */
+ /**
+ * @property timeout
+ * The timeout in milliseconds to be used for requests. (defaults to 30000)
+ * @type Number
+ */
+
+ /**
+ * @property autoAbort
+ * Whether a new request should abort any pending requests. (defaults to false)
+ * @type Boolean
+ */
+ autoAbort : false,
+
+ /**
+ * Serialize the passed form into a url encoded string
+ * @param {String/HTMLElement} form
+ * @return {String}
+ */
+ serializeForm : function(form){
+ return Ext.lib.Ajax.serializeForm(form);
+ }
+});
+/**
+ * @class Ext.util.JSON
+ * Modified version of Douglas Crockford"s json.js that doesn"t
+ * mess with the Object prototype
+ * http://www.json.org/js.html
+ * @singleton
+ */
+Ext.util.JSON = new (function(){
+ var useHasOwn = !!{}.hasOwnProperty,
+ isNative = function() {
+ var useNative = null;
+
+ return function() {
+ if (useNative === null) {
+ useNative = Ext.USE_NATIVE_JSON && window.JSON && JSON.toString() == '[object JSON]';
+ }
+
+ return useNative;
+ };
+ }(),
+ pad = function(n) {
+ return n < 10 ? "0" + n : n;
+ },
+ doDecode = function(json){
+ return eval("(" + json + ")");
+ },
+ doEncode = function(o){
+ if(!Ext.isDefined(o) || o === null){
+ return "null";
+ }else if(Ext.isArray(o)){
+ return encodeArray(o);
+ }else if(Ext.isDate(o)){
+ return Ext.util.JSON.encodeDate(o);
+ }else if(Ext.isString(o)){
+ return encodeString(o);
+ }else if(typeof o == "number"){
+ //don't use isNumber here, since finite checks happen inside isNumber
+ return isFinite(o) ? String(o) : "null";
+ }else if(Ext.isBoolean(o)){
+ return String(o);
+ }else {
+ var a = ["{"], b, i, v;
+ for (i in o) {
+ // don't encode DOM objects
+ if(!o.getElementsByTagName){
+ if(!useHasOwn || o.hasOwnProperty(i)) {
+ v = o[i];
+ switch (typeof v) {
+ case "undefined":
+ case "function":
+ case "unknown":
+ break;
+ default:
+ if(b){
+ a.push(',');
+ }
+ a.push(doEncode(i), ":",
+ v === null ? "null" : doEncode(v));
+ b = true;
+ }
+ }
+ }
+ }
+ a.push("}");
+ return a.join("");
+ }
+ },
+ m = {
+ "\b": '\\b',
+ "\t": '\\t',
+ "\n": '\\n',
+ "\f": '\\f',
+ "\r": '\\r',
+ '"' : '\\"',
+ "\\": '\\\\'
+ },
+ encodeString = function(s){
+ if (/["\\\x00-\x1f]/.test(s)) {
+ return '"' + s.replace(/([\x00-\x1f\\"])/g, function(a, b) {
+ var c = m[b];
+ if(c){
+ return c;
+ }
+ c = b.charCodeAt();
+ return "\\u00" +
+ Math.floor(c / 16).toString(16) +
+ (c % 16).toString(16);
+ }) + '"';
+ }
+ return '"' + s + '"';
+ },
+ encodeArray = function(o){
+ var a = ["["], b, i, l = o.length, v;
+ for (i = 0; i < l; i += 1) {
+ v = o[i];
+ switch (typeof v) {
+ case "undefined":
+ case "function":
+ case "unknown":
+ break;
+ default:
+ if (b) {
+ a.push(',');
+ }
+ a.push(v === null ? "null" : Ext.util.JSON.encode(v));
+ b = true;
+ }
+ }
+ a.push("]");
+ return a.join("");
+ };
+
+ /**
+ * <p>Encodes a Date. This returns the actual string which is inserted into the JSON string as the literal expression.
+ * <b>The returned value includes enclosing double quotation marks.</b></p>
+ * <p>The default return format is "yyyy-mm-ddThh:mm:ss".</p>
+ * <p>To override this:</p><pre><code>
+Ext.util.JSON.encodeDate = function(d) {
+ return d.format('"Y-m-d"');
+};
+</code></pre>
+ * @param {Date} d The Date to encode
+ * @return {String} The string literal to use in a JSON string.
+ */
+ this.encodeDate = function(o){
+ return '"' + o.getFullYear() + "-" +
+ pad(o.getMonth() + 1) + "-" +
+ pad(o.getDate()) + "T" +
+ pad(o.getHours()) + ":" +
+ pad(o.getMinutes()) + ":" +
+ pad(o.getSeconds()) + '"';
+ };
- // 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;
+ /**
+ * Encodes an Object, Array or other value
+ * @param {Mixed} o The variable to encode
+ * @return {String} The JSON string
+ */
+ this.encode = function() {
+ var ec;
+ return function(o) {
+ if (!ec) {
+ // setup encoding function on first access
+ ec = isNative() ? JSON.stringify : doEncode;
}
- return moved ? [x, y] : false;
+ return ec(o);
};
- }(),
-
-
-
-// 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;
-// },
-
/**
- * 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');
- },
+ * Decodes (parses) a JSON string to an object. If the JSON is invalid, this function throws a SyntaxError unless the safe option is set.
+ * @param {String} json The JSON string
+ * @return {Object} The resulting object
+ */
+ this.decode = function() {
+ var dc;
+ return function(json) {
+ if (!dc) {
+ // setup decoding function on first access
+ dc = isNative() ? JSON.parse : doDecode;
+ }
+ return dc(json);
+ };
+ }();
- /**
- * 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(function(){
- var PARENTNODE = 'parentNode',
- NEXTSIBLING = 'nextSibling',
- PREVIOUSSIBLING = 'previousSibling',
- DQ = Ext.DomQuery,
- GET = Ext.get;
-
- return {
- /**
- * Looks at this node and then at parent nodes for a match of the passed simple selector (e.g. div.some-class or span:first-child)
- * @param {String} selector The simple selector to test
- * @param {Number/Mixed} maxDepth (optional) The max depth to search as a number or element (defaults to 50 || document.body)
- * @param {Boolean} returnEl (optional) True to return a Ext.Element object instead of DOM node
- * @return {HTMLElement} The matching DOM node (or null if no match was found)
- */
- findParent : function(simpleSelector, maxDepth, returnEl){
- var p = this.dom,
- b = document.body,
- depth = 0,
- stopEl;
- if(Ext.isGecko && Object.prototype.toString.call(p) == '[object XULElement]') {
- return null;
- }
- maxDepth = maxDepth || 50;
- if (isNaN(maxDepth)) {
- stopEl = Ext.getDom(maxDepth);
- maxDepth = Number.MAX_VALUE;
- }
- while(p && p.nodeType == 1 && depth < maxDepth && p != b && p != stopEl){
- if(DQ.is(p, simpleSelector)){
- return returnEl ? GET(p) : p;
- }
- depth++;
- p = p.parentNode;
- }
- return null;
- },
-
- /**
- * Looks at parent nodes for a match of the passed simple selector (e.g. div.some-class or span:first-child)
- * @param {String} selector The simple selector to test
- * @param {Number/Mixed} maxDepth (optional) The max depth to
- search as a number or element (defaults to 10 || document.body)
- * @param {Boolean} returnEl (optional) True to return a Ext.Element object instead of DOM node
- * @return {HTMLElement} The matching DOM node (or null if no match was found)
- */
- findParentNode : function(simpleSelector, maxDepth, returnEl){
- var p = Ext.fly(this.dom.parentNode, '_internal');
- return p ? p.findParent(simpleSelector, maxDepth, returnEl) : null;
- },
-
- /**
- * Walks up the dom looking for a parent node that matches the passed simple selector (e.g. div.some-class or span:first-child).
- * This is a shortcut for findParentNode() that always returns an Ext.Element.
- * @param {String} selector The simple selector to test
- * @param {Number/Mixed} maxDepth (optional) The max depth to
- search as a number or element (defaults to 10 || document.body)
- * @return {Ext.Element} The matching DOM node (or null if no match was found)
- */
- up : function(simpleSelector, maxDepth){
- return this.findParentNode(simpleSelector, maxDepth, true);
- },
-
- /**
- * 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
- * @return {CompositeElement/CompositeElementLite} The composite element
- */
- select : function(selector){
- return Ext.Element.select(selector, this.dom);
- },
-
- /**
- * Selects child nodes based on the passed CSS selector (the selector should not contain an id).
- * @param {String} selector The CSS selector
- * @return {Array} An array of the matched nodes
- */
- query : function(selector){
- return DQ.select(selector, this.dom);
- },
-
- /**
- * Selects a single child at any depth below this element based on the passed CSS selector (the selector should not contain an id).
- * @param {String} selector The CSS selector
- * @param {Boolean} returnDom (optional) True to return the DOM node instead of Ext.Element (defaults to false)
- * @return {HTMLElement/Ext.Element} The child Ext.Element (or DOM node if returnDom = true)
- */
- child : function(selector, returnDom){
- var n = DQ.selectNode(selector, this.dom);
- return returnDom ? n : GET(n);
- },
-
- /**
- * Selects a single *direct* child based on the passed CSS selector (the selector should not contain an id).
- * @param {String} selector The CSS selector
- * @param {Boolean} returnDom (optional) True to return the DOM node instead of Ext.Element (defaults to false)
- * @return {HTMLElement/Ext.Element} The child Ext.Element (or DOM node if returnDom = true)
- */
- down : function(selector, returnDom){
- var n = DQ.selectNode(" > " + selector, this.dom);
- return returnDom ? n : GET(n);
- },
-
- /**
- * Gets the parent node for this element, optionally chaining up trying to match a selector
- * @param {String} selector (optional) Find a parent node that matches the passed simple selector
- * @param {Boolean} returnDom (optional) True to return a raw dom node instead of an Ext.Element
- * @return {Ext.Element/HTMLElement} The parent node or null
- */
- parent : function(selector, returnDom){
- return this.matchNode(PARENTNODE, PARENTNODE, selector, returnDom);
- },
-
- /**
- * Gets the next sibling, skipping text nodes
- * @param {String} selector (optional) Find the next sibling that matches the passed simple selector
- * @param {Boolean} returnDom (optional) True to return a raw dom node instead of an Ext.Element
- * @return {Ext.Element/HTMLElement} The next sibling or null
- */
- next : function(selector, returnDom){
- return this.matchNode(NEXTSIBLING, NEXTSIBLING, selector, returnDom);
- },
-
- /**
- * Gets the previous sibling, skipping text nodes
- * @param {String} selector (optional) Find the previous sibling that matches the passed simple selector
- * @param {Boolean} returnDom (optional) True to return a raw dom node instead of an Ext.Element
- * @return {Ext.Element/HTMLElement} The previous sibling or null
- */
- prev : function(selector, returnDom){
- return this.matchNode(PREVIOUSSIBLING, PREVIOUSSIBLING, selector, returnDom);
- },
-
-
- /**
- * Gets the first child, skipping text nodes
- * @param {String} selector (optional) Find the next sibling that matches the passed simple selector
- * @param {Boolean} returnDom (optional) True to return a raw dom node instead of an Ext.Element
- * @return {Ext.Element/HTMLElement} The first child or null
- */
- first : function(selector, returnDom){
- return this.matchNode(NEXTSIBLING, 'firstChild', selector, returnDom);
- },
-
- /**
- * Gets the last child, skipping text nodes
- * @param {String} selector (optional) Find the previous sibling that matches the passed simple selector
- * @param {Boolean} returnDom (optional) True to return a raw dom node instead of an Ext.Element
- * @return {Ext.Element/HTMLElement} The last child or null
- */
- last : function(selector, returnDom){
- return this.matchNode(PREVIOUSSIBLING, 'lastChild', selector, returnDom);
- },
-
- matchNode : function(dir, start, selector, returnDom){
- var n = this.dom[start];
- while(n){
- if(n.nodeType == 1 && (!selector || DQ.is(n, selector))){
- return !returnDom ? GET(n) : n;
- }
- n = n[dir];
- }
- return null;
- }
- }
-}());/**
- * @class Ext.Element
+ * Shorthand for {@link Ext.util.JSON#encode}
+ * @param {Mixed} o The variable to encode
+ * @return {String} The JSON string
+ * @member Ext
+ * @method encode
*/
-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.encode = Ext.util.JSON.encode;
+/**
+ * Shorthand for {@link Ext.util.JSON#decode}
+ * @param {String} json The JSON string
+ * @param {Boolean} safe (optional) Whether to return null or throw an exception if the JSON is invalid.
+ * @return {Object} The resulting object
+ * @member Ext
+ * @method decode
*/
-Ext.Element.addMethods(
-function() {
- var GETDOM = Ext.getDom,
- GET = Ext.get,
- DH = Ext.DomHelper;
-
- return {
- /**
- * Appends the passed element(s) to this element
- * @param {String/HTMLElement/Array/Element/CompositeElement} el
- * @return {Ext.Element} this
- */
- appendChild: function(el){
- return GET(el).appendTo(this);
- },
-
- /**
- * Appends this element to the passed element
- * @param {Mixed} el The new parent element
- * @return {Ext.Element} this
- */
- appendTo: function(el){
- GETDOM(el).appendChild(this.dom);
- return this;
- },
-
- /**
- * Inserts this element before the passed element in the DOM
- * @param {Mixed} el The element before which this element will be inserted
- * @return {Ext.Element} this
- */
- insertBefore: function(el){
- (el = GETDOM(el)).parentNode.insertBefore(this.dom, el);
- return this;
- },
-
- /**
- * Inserts this element after the passed element in the DOM
- * @param {Mixed} el The element to insert after
- * @return {Ext.Element} this
- */
- insertAfter: function(el){
- (el = GETDOM(el)).parentNode.insertBefore(this.dom, el.nextSibling);
- return this;
- },
-
- /**
- * Inserts (or creates) an element (or DomHelper config) as the first child of this element
- * @param {Mixed/Object} el The id or element to insert or a DomHelper config to create and insert
- * @return {Ext.Element} The new child
- */
- insertFirst: function(el, returnDom){
- el = el || {};
- if(el.nodeType || el.dom || typeof el == 'string'){ // element
- el = GETDOM(el);
- this.dom.insertBefore(el, this.dom.firstChild);
- return !returnDom ? GET(el) : el;
- }else{ // dh config
- return this.createChild(el, this.dom.firstChild, returnDom);
- }
- },
-
- /**
- * Replaces the passed element with this element
- * @param {Mixed} el The element to replace
- * @return {Ext.Element} this
- */
- replace: function(el){
- el = GET(el);
- this.insertBefore(el);
- el.remove();
- return this;
- },
-
- /**
- * Replaces this element with the passed element
- * @param {Mixed/Object} el The new element or a DomHelper config of an element to create
- * @return {Ext.Element} this
- */
- replaceWith: function(el){
- var me = this;
-
- if(el.nodeType || el.dom || typeof el == 'string'){
- el = GETDOM(el);
- me.dom.parentNode.insertBefore(el, me.dom);
- }else{
- el = DH.insertBefore(me.dom, el);
- }
-
- delete Ext.elCache[me.id];
- Ext.removeNode(me.dom);
- me.id = Ext.id(me.dom = el);
- Ext.Element.addToCache(me.isFlyweight ? new Ext.Element(me.dom) : me);
- return me;
- },
-
- /**
- * Creates the passed DomHelper config and appends it to this element or optionally inserts it before the passed child element.
- * @param {Object} config DomHelper element config object. If no tag is specified (e.g., {tag:'input'}) then a div will be
- * automatically generated with the specified attributes.
- * @param {HTMLElement} insertBefore (optional) a child element of this element
- * @param {Boolean} returnDom (optional) true to return the dom node instead of creating an Element
- * @return {Ext.Element} The new child element
- */
- createChild: function(config, insertBefore, returnDom){
- config = config || {tag:'div'};
- return insertBefore ?
- DH.insertBefore(insertBefore, config, returnDom !== true) :
- DH[!this.dom.firstChild ? 'overwrite' : 'append'](this.dom, config, returnDom !== true);
- },
-
- /**
- * Creates and wraps this element with another element
- * @param {Object} config (optional) DomHelper element config object for the wrapper element or null for an empty div
- * @param {Boolean} returnDom (optional) True to return the raw DOM element instead of Ext.Element
- * @return {HTMLElement/Element} The newly created wrapper element
- */
- wrap: function(config, returnDom){
- var newEl = DH.insertBefore(this.dom, config || {tag: "div"}, !returnDom);
- newEl.dom ? newEl.dom.appendChild(this.dom) : newEl.appendChild(this.dom);
- return newEl;
- },
-
- /**
- * Inserts an html fragment into this element
- * @param {String} where Where to insert the html in relation to this element - beforeBegin, afterBegin, beforeEnd, afterEnd.
- * @param {String} html The HTML fragment
- * @param {Boolean} returnEl (optional) True to return an Ext.Element (defaults to false)
- * @return {HTMLElement/Ext.Element} The inserted node (or nearest related if more than 1 inserted)
- */
- insertHtml : function(where, html, returnEl){
- var el = DH.insertHtml(where, this.dom, html);
- return returnEl ? Ext.get(el) : el;
- }
- }
-}());/**
- * @class Ext.Element
+Ext.decode = Ext.util.JSON.decode;
+/**
+ * @class Ext.EventManager
+ * Registers event handlers that want to receive a normalized EventObject instead of the standard browser event and provides
+ * several useful events directly.
+ * See {@link Ext.EventObject} for more details on normalized event objects.
+ * @singleton
*/
-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;
+Ext.EventManager = function(){
+ var docReadyEvent,
+ docReadyProcId,
+ docReadyState = false,
+ DETECT_NATIVE = Ext.isGecko || Ext.isWebKit || Ext.isSafari,
+ E = Ext.lib.Event,
+ D = Ext.lib.Dom,
+ DOC = document,
+ WINDOW = window,
+ DOMCONTENTLOADED = "DOMContentLoaded",
+ COMPLETE = 'complete',
+ propRe = /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate)$/,
+ /*
+ * This cache is used to hold special js objects, the document and window, that don't have an id. We need to keep
+ * a reference to them so we can look them up at a later point.
+ */
+ specialElCache = [];
+
+ function getId(el){
+ var id = false,
+ i = 0,
+ len = specialElCache.length,
+ skip = false,
+ o;
+
+ if (el) {
+ if (el.getElementById || el.navigator) {
+ // look up the id
+ for(; i < len; ++i){
+ o = specialElCache[i];
+ if(o.el === el){
+ id = o.id;
+ break;
}
- });
- 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);
+ }
+ if(!id){
+ // for browsers that support it, ensure that give the el the same id
+ id = Ext.id(el);
+ specialElCache.push({
+ id: id,
+ el: el
+ });
+ skip = true;
}
}else{
- if (isAfter && !me.dom.nextSibling) {
- rt = DH.append(me.dom.parentNode, el, !returnDom);
- } else {
- rt = DH[isAfter ? 'insertAfter' : 'insertBefore'](me.dom, el, !returnDom);
+ id = Ext.id(el);
+ }
+ if(!Ext.elCache[id]){
+ Ext.Element.addToCache(new Ext.Element(el), id);
+ if(skip){
+ Ext.elCache[id].skipGC = true;
}
}
- return rt;
- }
- };
-}());/**
- * @class Ext.Element
- */
-Ext.Element.addMethods(function(){
- // local style camelizing for speed
- var propCache = {},
- camelRe = /(-[a-z])/gi,
- classReCache = {},
- view = document.defaultView,
- propFloat = Ext.isIE ? 'styleFloat' : 'cssFloat',
- opacityRe = /alpha\(opacity=(.*)\)/i,
- trimRe = /^\s+|\s+$/g,
- spacesRe = /\s+/,
- wordsRe = /\w/g,
- EL = Ext.Element,
- PADDING = "padding",
- MARGIN = "margin",
- BORDER = "border",
- LEFT = "-left",
- RIGHT = "-right",
- TOP = "-top",
- BOTTOM = "-bottom",
- WIDTH = "-width",
- MATH = Math,
- HIDDEN = 'hidden',
- ISCLIPPED = 'isClipped',
- OVERFLOW = 'overflow',
- OVERFLOWX = 'overflow-x',
- OVERFLOWY = 'overflow-y',
- ORIGINALCLIP = 'originalClip',
- // special markup used throughout Ext when box wrapping elements
- borders = {l: BORDER + LEFT + WIDTH, r: BORDER + RIGHT + WIDTH, t: BORDER + TOP + WIDTH, b: BORDER + BOTTOM + WIDTH},
- paddings = {l: PADDING + LEFT, r: PADDING + RIGHT, t: PADDING + TOP, b: PADDING + BOTTOM},
- margins = {l: MARGIN + LEFT, r: MARGIN + RIGHT, t: MARGIN + TOP, b: MARGIN + BOTTOM},
- data = Ext.Element.data;
+ }
+ return id;
+ }
+ /// There is some jquery work around stuff here that isn't needed in Ext Core.
+ function addListener(el, ename, fn, task, wrap, scope){
+ el = Ext.getDom(el);
+ var id = getId(el),
+ es = Ext.elCache[id].events,
+ wfn;
- // private
- function camelFn(m, a) {
- return a.charAt(1).toUpperCase();
+ wfn = E.on(el, ename, wrap);
+ es[ename] = es[ename] || [];
+
+ /* 0 = Original Function,
+ 1 = Event Manager Wrapped Function,
+ 2 = Scope,
+ 3 = Adapter Wrapped Function,
+ 4 = Buffered Task
+ */
+ es[ename].push([fn, wrap, scope, wfn, task]);
+
+ // this is a workaround for jQuery and should somehow be removed from Ext Core in the future
+ // without breaking ExtJS.
+
+ // workaround for jQuery
+ if(el.addEventListener && ename == "mousewheel"){
+ var args = ["DOMMouseScroll", wrap, false];
+ el.addEventListener.apply(el, args);
+ Ext.EventManager.addListener(WINDOW, 'unload', function(){
+ el.removeEventListener.apply(el, args);
+ });
+ }
+
+ // fix stopped mousedowns on the document
+ if(el == DOC && ename == "mousedown"){
+ Ext.EventManager.stoppedMouseDownEvent.addListener(wrap);
+ }
}
- function chkCache(prop) {
- return propCache[prop] || (propCache[prop] = prop == 'float' ? propFloat : prop.replace(camelRe, camelFn));
+ function doScrollChk(){
+ /* Notes:
+ 'doScroll' will NOT work in a IFRAME/FRAMESET.
+ The method succeeds but, a DOM query done immediately after -- FAILS.
+ */
+ if(window != top){
+ return false;
+ }
+
+ try{
+ DOC.documentElement.doScroll('left');
+ }catch(e){
+ return false;
+ }
+
+ fireDocReady();
+ return true;
+ }
+ /**
+ * @return {Boolean} True if the document is in a 'complete' state (or was determined to
+ * be true by other means). If false, the state is evaluated again until canceled.
+ */
+ function checkReadyState(e){
+
+ if(Ext.isIE && doScrollChk()){
+ return true;
+ }
+ if(DOC.readyState == COMPLETE){
+ fireDocReady();
+ return true;
+ }
+ docReadyState || (docReadyProcId = setTimeout(arguments.callee, 2));
+ return false;
}
- return {
- // private ==> used by Fx
- adjustWidth : function(width) {
- var me = this;
- var isNum = (typeof width == "number");
- if(isNum && me.autoBoxAdjust && !me.isBorderBox()){
- width -= (me.getBorderWidth("lr") + me.getPadding("lr"));
- }
- return (isNum && width < 0) ? 0 : width;
- },
+ var styles;
+ function checkStyleSheets(e){
+ styles || (styles = Ext.query('style, link[rel=stylesheet]'));
+ if(styles.length == DOC.styleSheets.length){
+ fireDocReady();
+ return true;
+ }
+ docReadyState || (docReadyProcId = setTimeout(arguments.callee, 2));
+ return false;
+ }
- // private ==> used by Fx
- adjustHeight : function(height) {
- var me = this;
- var isNum = (typeof height == "number");
- if(isNum && me.autoBoxAdjust && !me.isBorderBox()){
- height -= (me.getBorderWidth("tb") + me.getPadding("tb"));
+ function OperaDOMContentLoaded(e){
+ DOC.removeEventListener(DOMCONTENTLOADED, arguments.callee, false);
+ checkStyleSheets();
+ }
+
+ function fireDocReady(e){
+ if(!docReadyState){
+ docReadyState = true; //only attempt listener removal once
+
+ if(docReadyProcId){
+ clearTimeout(docReadyProcId);
}
- return (isNum && height < 0) ? 0 : height;
- },
+ if(DETECT_NATIVE) {
+ DOC.removeEventListener(DOMCONTENTLOADED, fireDocReady, false);
+ }
+ if(Ext.isIE && checkReadyState.bindIE){ //was this was actually set ??
+ DOC.detachEvent('onreadystatechange', checkReadyState);
+ }
+ E.un(WINDOW, "load", arguments.callee);
+ }
+ if(docReadyEvent && !Ext.isReady){
+ Ext.isReady = true;
+ docReadyEvent.fire();
+ docReadyEvent.listeners = [];
+ }
+ }
- /**
- * Adds one or more CSS classes to the element. Duplicate classes are automatically filtered out.
- * @param {String/Array} className The CSS class to add, or an array of classes
- * @return {Ext.Element} this
+ function initDocReady(){
+ docReadyEvent || (docReadyEvent = new Ext.util.Event());
+ if (DETECT_NATIVE) {
+ DOC.addEventListener(DOMCONTENTLOADED, fireDocReady, false);
+ }
+ /*
+ * Handle additional (exceptional) detection strategies here
*/
- addClass : function(className){
- var me = this,
- i,
- len,
- v,
- cls = [];
- // Separate case is for speed
- if (!Ext.isArray(className)) {
- if (typeof className == 'string' && !this.hasClass(className)) {
- me.dom.className += " " + className;
- }
+ if (Ext.isIE){
+ //Use readystatechange as a backup AND primary detection mechanism for a FRAME/IFRAME
+ //See if page is already loaded
+ if(!checkReadyState()){
+ checkReadyState.bindIE = true;
+ DOC.attachEvent('onreadystatechange', checkReadyState);
}
- else {
- for (i = 0, len = className.length; i < len; i++) {
- v = className[i];
- if (typeof v == 'string' && (' ' + me.dom.className + ' ').indexOf(' ' + v + ' ') == -1) {
- cls.push(v);
- }
- }
- if (cls.length) {
- me.dom.className += " " + cls.join(" ");
- }
+
+ }else if(Ext.isOpera ){
+ /* Notes:
+ Opera needs special treatment needed here because CSS rules are NOT QUITE
+ available after DOMContentLoaded is raised.
+ */
+
+ //See if page is already loaded and all styleSheets are in place
+ (DOC.readyState == COMPLETE && checkStyleSheets()) ||
+ DOC.addEventListener(DOMCONTENTLOADED, OperaDOMContentLoaded, false);
+
+ }else if (Ext.isWebKit){
+ //Fallback for older Webkits without DOMCONTENTLOADED support
+ checkReadyState();
+ }
+ // no matter what, make sure it fires on load
+ E.on(WINDOW, "load", fireDocReady);
+ }
+
+ function createTargeted(h, o){
+ return function(){
+ var args = Ext.toArray(arguments);
+ if(o.target == Ext.EventObject.setEvent(args[0]).target){
+ h.apply(this, args);
}
- return me;
- },
+ };
+ }
- /**
- * Removes one or more CSS classes from the element.
- * @param {String/Array} className The CSS class to remove, or an array of classes
- * @return {Ext.Element} this
- */
- removeClass : function(className){
- var me = this,
- i,
- idx,
- len,
- cls,
- elClasses;
- if (!Ext.isArray(className)){
- className = [className];
+ function createBuffered(h, o, task){
+ return function(e){
+ // create new event object impl so new events don't wipe out properties
+ task.delay(o.buffer, h, null, [new Ext.EventObjectImpl(e)]);
+ };
+ }
+
+ function createSingle(h, el, ename, fn, scope){
+ return function(e){
+ Ext.EventManager.removeListener(el, ename, fn, scope);
+ h(e);
+ };
+ }
+
+ function createDelayed(h, o, fn){
+ return function(e){
+ var task = new Ext.util.DelayedTask(h);
+ if(!fn.tasks) {
+ fn.tasks = [];
}
- if (me.dom && me.dom.className) {
- elClasses = me.dom.className.replace(trimRe, '').split(spacesRe);
- for (i = 0, len = className.length; i < len; i++) {
- cls = className[i];
- if (typeof cls == 'string') {
- cls = cls.replace(trimRe, '');
- idx = elClasses.indexOf(cls);
- if (idx != -1) {
- elClasses.splice(idx, 1);
- }
- }
+ fn.tasks.push(task);
+ task.delay(o.delay || 10, h, null, [new Ext.EventObjectImpl(e)]);
+ };
+ }
+
+ function listen(element, ename, opt, fn, scope){
+ var o = (!opt || typeof opt == "boolean") ? {} : opt,
+ el = Ext.getDom(element), task;
+
+ fn = fn || o.fn;
+ scope = scope || o.scope;
+
+ if(!el){
+ throw "Error listening for \"" + ename + '\". Element "' + element + '" doesn\'t exist.';
+ }
+ function h(e){
+ // prevent errors while unload occurring
+ if(!Ext){// !window[xname]){ ==> can't we do this?
+ return;
+ }
+ e = Ext.EventObject.setEvent(e);
+ var t;
+ if (o.delegate) {
+ if(!(t = e.getTarget(o.delegate, el))){
+ return;
}
- me.dom.className = elClasses.join(" ");
+ } else {
+ t = e.target;
+ }
+ if (o.stopEvent) {
+ e.stopEvent();
+ }
+ if (o.preventDefault) {
+ e.preventDefault();
+ }
+ if (o.stopPropagation) {
+ e.stopPropagation();
+ }
+ if (o.normalized === false) {
+ e = e.browserEvent;
}
- return me;
- },
- /**
- * Adds one or more CSS classes to this element and removes the same class(es) from all siblings.
- * @param {String/Array} className The CSS class to add, or an array of classes
- * @return {Ext.Element} this
- */
- radioClass : function(className){
- var cn = this.dom.parentNode.childNodes,
- v,
- i,
- len;
- className = Ext.isArray(className) ? className : [className];
- for (i = 0, len = cn.length; i < len; i++) {
- v = cn[i];
- if (v && v.nodeType == 1) {
- Ext.fly(v, '_internal').removeClass(className);
- }
- };
- return this.addClass(className);
- },
+ fn.call(scope || el, e, t, o);
+ }
+ if(o.target){
+ h = createTargeted(h, o);
+ }
+ if(o.delay){
+ h = createDelayed(h, o, fn);
+ }
+ if(o.single){
+ h = createSingle(h, el, ename, fn, scope);
+ }
+ if(o.buffer){
+ task = new Ext.util.DelayedTask(h);
+ h = createBuffered(h, o, task);
+ }
- /**
- * Toggles the specified CSS class on this element (removes it if it already exists, otherwise adds it).
- * @param {String} className The CSS class to toggle
- * @return {Ext.Element} this
- */
- toggleClass : function(className){
- return this.hasClass(className) ? this.removeClass(className) : this.addClass(className);
- },
+ addListener(el, ename, fn, task, h, scope);
+ return h;
+ }
+ var pub = {
/**
- * Checks if the specified CSS class exists on this element's DOM node.
- * @param {String} className The CSS class to check for
- * @return {Boolean} True if the class exists, else false
+ * Appends an event handler to an element. The shorthand version {@link #on} is equivalent. Typically you will
+ * use {@link Ext.Element#addListener} directly on an Element in favor of calling this version.
+ * @param {String/HTMLElement} el The html element or id to assign the event handler to.
+ * @param {String} eventName The name of the event to listen for.
+ * @param {Function} handler The handler function the event invokes. This function is passed
+ * the following parameters:<ul>
+ * <li>evt : EventObject<div class="sub-desc">The {@link Ext.EventObject EventObject} describing the event.</div></li>
+ * <li>t : Element<div class="sub-desc">The {@link Ext.Element Element} which was the target of the event.
+ * Note that this may be filtered by using the <tt>delegate</tt> option.</div></li>
+ * <li>o : Object<div class="sub-desc">The options object from the addListener call.</div></li>
+ * </ul>
+ * @param {Object} scope (optional) The scope (<b><code>this</code></b> reference) in which the handler function is executed. <b>Defaults to the Element</b>.
+ * @param {Object} options (optional) An object containing handler configuration properties.
+ * This may contain any of the following properties:<ul>
+ * <li>scope : Object<div class="sub-desc">The scope (<b><code>this</code></b> reference) in which the handler function is executed. <b>Defaults to the Element</b>.</div></li>
+ * <li>delegate : String<div class="sub-desc">A simple selector to filter the target or look for a descendant of the target</div></li>
+ * <li>stopEvent : Boolean<div class="sub-desc">True to stop the event. That is stop propagation, and prevent the default action.</div></li>
+ * <li>preventDefault : Boolean<div class="sub-desc">True to prevent the default action</div></li>
+ * <li>stopPropagation : Boolean<div class="sub-desc">True to prevent event propagation</div></li>
+ * <li>normalized : Boolean<div class="sub-desc">False to pass a browser event to the handler function instead of an Ext.EventObject</div></li>
+ * <li>delay : Number<div class="sub-desc">The number of milliseconds to delay the invocation of the handler after te event fires.</div></li>
+ * <li>single : Boolean<div class="sub-desc">True to add a handler to handle just the next firing of the event, and then remove itself.</div></li>
+ * <li>buffer : Number<div class="sub-desc">Causes the handler to be scheduled to run in an {@link Ext.util.DelayedTask} delayed
+ * by the specified number of milliseconds. If the event fires again within that time, the original
+ * handler is <em>not</em> invoked, but the new handler is scheduled in its place.</div></li>
+ * <li>target : Element<div class="sub-desc">Only call the handler if the event was fired on the target Element, <i>not</i> if the event was bubbled up from a child node.</div></li>
+ * </ul><br>
+ * <p>See {@link Ext.Element#addListener} for examples of how to use these options.</p>
*/
- hasClass : function(className){
- return className && (' '+this.dom.className+' ').indexOf(' '+className+' ') != -1;
+ addListener : function(element, eventName, fn, scope, options){
+ if(typeof eventName == 'object'){
+ var o = eventName, e, val;
+ for(e in o){
+ val = o[e];
+ if(!propRe.test(e)){
+ if(Ext.isFunction(val)){
+ // shared options
+ listen(element, e, o, val, o.scope);
+ }else{
+ // individual options
+ listen(element, e, val);
+ }
+ }
+ }
+ } else {
+ listen(element, eventName, options, fn, scope);
+ }
},
/**
- * Replaces a CSS class on the element with another. If the old name does not exist, the new name will simply be added.
- * @param {String} oldClassName The CSS class to replace
- * @param {String} newClassName The replacement CSS class
- * @return {Ext.Element} this
+ * Removes an event handler from an element. The shorthand version {@link #un} is equivalent. Typically
+ * you will use {@link Ext.Element#removeListener} directly on an Element in favor of calling this version.
+ * @param {String/HTMLElement} el The id or html element from which to remove the listener.
+ * @param {String} eventName The name of the event.
+ * @param {Function} fn The handler function to remove. <b>This must be a reference to the function passed into the {@link #addListener} call.</b>
+ * @param {Object} scope If a scope (<b><code>this</code></b> reference) was specified when the listener was added,
+ * then this must refer to the same object.
*/
- replaceClass : function(oldClassName, newClassName){
- return this.removeClass(oldClassName).addClass(newClassName);
- },
-
- isStyle : function(style, val) {
- return this.getStyle(style) == val;
- },
+ removeListener : function(el, eventName, fn, scope){
+ el = Ext.getDom(el);
+ var id = getId(el),
+ f = el && (Ext.elCache[id].events)[eventName] || [],
+ wrap, i, l, k, len, fnc;
- /**
- * Normalizes currentStyle and computedStyle.
- * @param {String} property The style property whose value is returned.
- * @return {String} The current value of the style property for this element.
- */
- getStyle : function(){
- return view && view.getComputedStyle ?
- function(prop){
- var el = this.dom,
- v,
- cs,
- out,
- display,
- wk = Ext.isWebKit,
- display;
+ for (i = 0, len = f.length; i < len; i++) {
- if(el == document){
- return null;
+ /* 0 = Original Function,
+ 1 = Event Manager Wrapped Function,
+ 2 = Scope,
+ 3 = Adapter Wrapped Function,
+ 4 = Buffered Task
+ */
+ if (Ext.isArray(fnc = f[i]) && fnc[0] == fn && (!scope || fnc[2] == scope)) {
+ if(fnc[4]) {
+ fnc[4].cancel();
}
- prop = chkCache(prop);
- // Fix bug caused by this: https://bugs.webkit.org/show_bug.cgi?id=13343
- if(wk && /marginRight/.test(prop)){
- display = this.getStyle('display');
- el.style.display = 'inline-block';
+ k = fn.tasks && fn.tasks.length;
+ if(k) {
+ while(k--) {
+ fn.tasks[k].cancel();
+ }
+ delete fn.tasks;
}
- out = (v = el.style[prop]) ? v :
- (cs = view.getComputedStyle(el, "")) ? cs[prop] : null;
+ wrap = fnc[1];
+ E.un(el, eventName, E.extAdapter ? fnc[3] : wrap);
- // Webkit returns rgb values for transparent.
- if(wk){
- if(out == 'rgba(0, 0, 0, 0)'){
- out = 'transparent';
- }else if(display){
- el.style.display = display;
- }
+ // jQuery workaround that should be removed from Ext Core
+ if(wrap && el.addEventListener && eventName == "mousewheel"){
+ el.removeEventListener("DOMMouseScroll", wrap, false);
+ }
+
+ // fix stopped mousedowns on the document
+ if(wrap && el == DOC && eventName == "mousedown"){
+ Ext.EventManager.stoppedMouseDownEvent.removeListener(wrap);
}
- return out;
- } :
- function(prop){
- var el = this.dom,
- m,
- cs;
- if(el == document) return null;
- if (prop == 'opacity') {
- if (el.style.filter.match) {
- if(m = el.style.filter.match(opacityRe)){
- var fv = parseFloat(m[1]);
- if(!isNaN(fv)){
- return fv ? fv / 100 : 0;
- }
- }
- }
- return 1;
+ f.splice(i, 1);
+ if (f.length === 0) {
+ delete Ext.elCache[id].events[eventName];
}
- prop = chkCache(prop);
- return el.style[prop] || ((cs = el.currentStyle) ? cs[prop] : null);
- };
- }(),
+ for (k in Ext.elCache[id].events) {
+ return false;
+ }
+ Ext.elCache[id].events = {};
+ return false;
+ }
+ }
+ },
/**
- * Return the CSS color for the specified CSS attribute. rgb, 3 digit (like #fff) and valid values
- * are convert to standard 6 digit hex color.
- * @param {String} attr The css attribute
- * @param {String} defaultValue The default value to use when a valid color isn't found
- * @param {String} prefix (optional) defaults to #. Use an empty string when working with
- * color anims.
+ * Removes all event handers from an element. Typically you will use {@link Ext.Element#removeAllListeners}
+ * directly on an Element in favor of calling this version.
+ * @param {String/HTMLElement} el The id or html element from which to remove all event handlers.
*/
- getColor : function(attr, defaultValue, prefix){
- var v = this.getStyle(attr),
- color = (typeof prefix != 'undefined') ? prefix : '#',
- h;
+ removeAll : function(el){
+ el = Ext.getDom(el);
+ var id = getId(el),
+ ec = Ext.elCache[id] || {},
+ es = ec.events || {},
+ f, i, len, ename, fn, k, wrap;
- if(!v || /transparent|inherit/.test(v)){
- return defaultValue;
+ for(ename in es){
+ if(es.hasOwnProperty(ename)){
+ f = es[ename];
+ /* 0 = Original Function,
+ 1 = Event Manager Wrapped Function,
+ 2 = Scope,
+ 3 = Adapter Wrapped Function,
+ 4 = Buffered Task
+ */
+ for (i = 0, len = f.length; i < len; i++) {
+ fn = f[i];
+ if(fn[4]) {
+ fn[4].cancel();
+ }
+ if(fn[0].tasks && (k = fn[0].tasks.length)) {
+ while(k--) {
+ fn[0].tasks[k].cancel();
+ }
+ delete fn.tasks;
+ }
+ wrap = fn[1];
+ E.un(el, ename, E.extAdapter ? fn[3] : wrap);
+
+ // jQuery workaround that should be removed from Ext Core
+ if(el.addEventListener && wrap && ename == "mousewheel"){
+ el.removeEventListener("DOMMouseScroll", wrap, false);
+ }
+
+ // fix stopped mousedowns on the document
+ if(wrap && el == DOC && ename == "mousedown"){
+ Ext.EventManager.stoppedMouseDownEvent.removeListener(wrap);
+ }
+ }
+ }
}
- if(/^r/.test(v)){
- Ext.each(v.slice(4, v.length -1).split(','), function(s){
- h = parseInt(s, 10);
- color += (h < 16 ? '0' : '') + h.toString(16);
- });
- }else{
- v = v.replace('#', '');
- color += v.length == 3 ? v.replace(/^(\w)(\w)(\w)$/, '$1$1$2$2$3$3') : v;
+ if (Ext.elCache[id]) {
+ Ext.elCache[id].events = {};
}
- return(color.length > 5 ? color.toLowerCase() : defaultValue);
},
- /**
- * Wrapper for setting style properties, also takes single object parameter of multiple styles.
- * @param {String/Object} property The style property to be set, or an object of multiple styles.
- * @param {String} value (optional) The value to apply to the given property, or null if an object was passed.
- * @return {Ext.Element} this
- */
- setStyle : function(prop, value){
- var tmp,
- style,
- camel;
- if (typeof prop != 'object') {
- tmp = {};
- tmp[prop] = value;
- prop = tmp;
- }
- for (style in prop) {
- value = prop[style];
- style == 'opacity' ?
- this.setOpacity(value) :
- this.dom.style[chkCache(style)] = value;
+ getListeners : function(el, eventName) {
+ el = Ext.getDom(el);
+ var id = getId(el),
+ ec = Ext.elCache[id] || {},
+ es = ec.events || {},
+ results = [];
+ if (es && es[eventName]) {
+ return es[eventName];
+ } else {
+ return null;
}
- return this;
},
- /**
- * Set the opacity of the element
- * @param {Float} opacity The new opacity. 0 = transparent, .5 = 50% visibile, 1 = fully visible, etc
- * @param {Boolean/Object} animate (optional) a standard Element animation config object or <tt>true</tt> for
- * the default animation (<tt>{duration: .35, easing: 'easeIn'}</tt>)
- * @return {Ext.Element} this
- */
- setOpacity : function(opacity, animate){
- var me = this,
- s = me.dom.style;
+ purgeElement : function(el, recurse, eventName) {
+ el = Ext.getDom(el);
+ var id = getId(el),
+ ec = Ext.elCache[id] || {},
+ es = ec.events || {},
+ i, f, len;
+ if (eventName) {
+ if (es && es.hasOwnProperty(eventName)) {
+ f = es[eventName];
+ for (i = 0, len = f.length; i < len; i++) {
+ Ext.EventManager.removeListener(el, eventName, f[i][0]);
+ }
+ }
+ } else {
+ Ext.EventManager.removeAll(el);
+ }
+ if (recurse && el && el.childNodes) {
+ for (i = 0, len = el.childNodes.length; i < len; i++) {
+ Ext.EventManager.purgeElement(el.childNodes[i], recurse, eventName);
+ }
+ }
+ },
- if(!animate || !me.anim){
- if(Ext.isIE){
- var opac = opacity < 1 ? 'alpha(opacity=' + opacity * 100 + ')' : '',
- val = s.filter.replace(opacityRe, '').replace(trimRe, '');
+ _unload : function() {
+ var el;
+ for (el in Ext.elCache) {
+ Ext.EventManager.removeAll(el);
+ }
+ delete Ext.elCache;
+ delete Ext.Element._flyweights;
- s.zoom = 1;
- s.filter = val + (val.length > 0 ? ' ' : '') + opac;
- }else{
- s.opacity = opacity;
+ // Abort any outstanding Ajax requests
+ var c,
+ conn,
+ tid,
+ ajax = Ext.lib.Ajax;
+ (typeof ajax.conn == 'object') ? conn = ajax.conn : conn = {};
+ for (tid in conn) {
+ c = conn[tid];
+ if (c) {
+ ajax.abort({conn: c, tId: tid});
}
- }else{
- me.anim({opacity: {to: opacity}}, me.preanim(arguments, 1), null, .35, 'easeIn');
}
- return me;
},
-
/**
- * Clears any opacity settings from this element. Required in some cases for IE.
- * @return {Ext.Element} this
+ * Adds a listener to be notified when the document is ready (before onload and before images are loaded). Can be
+ * accessed shorthanded as Ext.onReady().
+ * @param {Function} fn The method the event invokes.
+ * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the handler function executes. Defaults to the browser window.
+ * @param {boolean} options (optional) Options object as passed to {@link Ext.Element#addListener}. It is recommended that the options
+ * <code>{single: true}</code> be used so that the handler is removed on first invocation.
*/
- clearOpacity : function(){
- var style = this.dom.style;
- if(Ext.isIE){
- if(!Ext.isEmpty(style.filter)){
- style.filter = style.filter.replace(opacityRe, '').replace(trimRe, '');
+ onDocumentReady : function(fn, scope, options){
+ if (Ext.isReady) { // if it already fired or document.body is present
+ docReadyEvent || (docReadyEvent = new Ext.util.Event());
+ docReadyEvent.addListener(fn, scope, options);
+ docReadyEvent.fire();
+ docReadyEvent.listeners = [];
+ } else {
+ if (!docReadyEvent) {
+ initDocReady();
}
- }else{
- style.opacity = style['-moz-opacity'] = style['-khtml-opacity'] = '';
+ options = options || {};
+ options.delay = options.delay || 1;
+ docReadyEvent.addListener(fn, scope, options);
}
- return this;
},
/**
- * Returns the offset height of the element
- * @param {Boolean} contentHeight (optional) true to get the height minus borders and padding
- * @return {Number} The element's height
+ * Forces a document ready state transition for the framework. Used when Ext is loaded
+ * into a DOM structure AFTER initial page load (Google API or other dynamic load scenario.
+ * Any pending 'onDocumentReady' handlers will be fired (if not already handled).
*/
- getHeight : function(contentHeight){
- var me = this,
- dom = me.dom,
- hidden = Ext.isIE && me.isStyle('display', 'none'),
- h = MATH.max(dom.offsetHeight, hidden ? 0 : dom.clientHeight) || 0;
+ fireDocReady : fireDocReady
+ };
+ /**
+ * Appends an event handler to an element. Shorthand for {@link #addListener}.
+ * @param {String/HTMLElement} el The html element or id to assign the event handler to
+ * @param {String} eventName The name of the event to listen for.
+ * @param {Function} handler The handler function the event invokes.
+ * @param {Object} scope (optional) (<code>this</code> reference) in which the handler function executes. <b>Defaults to the Element</b>.
+ * @param {Object} options (optional) An object containing standard {@link #addListener} options
+ * @member Ext.EventManager
+ * @method on
+ */
+ pub.on = pub.addListener;
+ /**
+ * Removes an event handler from an element. Shorthand for {@link #removeListener}.
+ * @param {String/HTMLElement} el The id or html element from which to remove the listener.
+ * @param {String} eventName The name of the event.
+ * @param {Function} fn The handler function to remove. <b>This must be a reference to the function passed into the {@link #on} call.</b>
+ * @param {Object} scope If a scope (<b><code>this</code></b> reference) was specified when the listener was added,
+ * then this must refer to the same object.
+ * @member Ext.EventManager
+ * @method un
+ */
+ pub.un = pub.removeListener;
- h = !contentHeight ? h : h - me.getBorderWidth("tb") - me.getPadding("tb");
- return h < 0 ? 0 : h;
- },
+ pub.stoppedMouseDownEvent = new Ext.util.Event();
+ return pub;
+}();
+/**
+ * Adds a listener to be notified when the document is ready (before onload and before images are loaded). Shorthand of {@link Ext.EventManager#onDocumentReady}.
+ * @param {Function} fn The method the event invokes.
+ * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the handler function executes. Defaults to the browser window.
+ * @param {boolean} options (optional) Options object as passed to {@link Ext.Element#addListener}. It is recommended that the options
+ * <code>{single: true}</code> be used so that the handler is removed on first invocation.
+ * @member Ext
+ * @method onReady
+ */
+Ext.onReady = Ext.EventManager.onDocumentReady;
- /**
- * Returns the offset width of the element
- * @param {Boolean} contentWidth (optional) true to get the width minus borders and padding
- * @return {Number} The element's width
- */
- getWidth : function(contentWidth){
- var me = this,
- dom = me.dom,
- hidden = Ext.isIE && me.isStyle('display', 'none'),
- w = MATH.max(dom.offsetWidth, hidden ? 0 : dom.clientWidth) || 0;
- w = !contentWidth ? w : w - me.getBorderWidth("lr") - me.getPadding("lr");
- return w < 0 ? 0 : w;
- },
- /**
- * Set the width of this Element.
- * @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 {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
- * @return {Ext.Element} this
- */
- setWidth : function(width, animate){
- var me = this;
- width = me.adjustWidth(width);
- !animate || !me.anim ?
- me.dom.style.width = me.addUnits(width) :
- me.anim({width : {to : width}}, me.preanim(arguments, 1));
- return me;
- },
+//Initialize doc classes
+(function(){
+ var initExtCss = function() {
+ // find the body element
+ var bd = document.body || document.getElementsByTagName('body')[0];
+ if (!bd) {
+ return false;
+ }
+
+ var cls = [' ',
+ Ext.isIE ? "ext-ie " + (Ext.isIE6 ? 'ext-ie6' : (Ext.isIE7 ? 'ext-ie7' : 'ext-ie8'))
+ : Ext.isGecko ? "ext-gecko " + (Ext.isGecko2 ? 'ext-gecko2' : 'ext-gecko3')
+ : Ext.isOpera ? "ext-opera"
+ : Ext.isWebKit ? "ext-webkit" : ""];
- /**
- * Set the height of this Element.
- * <pre><code>
-// change the height to 200px and animate with default configuration
-Ext.fly('elementId').setHeight(200, true);
+ if (Ext.isSafari) {
+ cls.push("ext-safari " + (Ext.isSafari2 ? 'ext-safari2' : (Ext.isSafari3 ? 'ext-safari3' : 'ext-safari4')));
+ } else if(Ext.isChrome) {
+ cls.push("ext-chrome");
+ }
-// change the height to 150px and animate with a custom configuration
-Ext.fly('elId').setHeight(150, {
- duration : .5, // animation will have a duration of .5 seconds
- // will change the content to "finished"
- callback: function(){ this.{@link #update}("finished"); }
-});
- * </code></pre>
- * @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
- */
- setHeight : function(height, animate){
- var me = this;
- height = me.adjustHeight(height);
- !animate || !me.anim ?
- me.dom.style.height = me.addUnits(height) :
- me.anim({height : {to : height}}, me.preanim(arguments, 1));
- return me;
- },
+ if (Ext.isMac) {
+ cls.push("ext-mac");
+ }
+ if (Ext.isLinux) {
+ cls.push("ext-linux");
+ }
- /**
- * Gets the width of the border(s) for the specified side(s)
- * @param {String} side Can be t, l, r, b or any combination of those to add multiple values. For example,
- * passing <tt>'lr'</tt> would get the border <b><u>l</u></b>eft width + the border <b><u>r</u></b>ight width.
- * @return {Number} The width of the sides passed added together
- */
- getBorderWidth : function(side){
- return this.addStyles(side, borders);
- },
+ // add to the parent to allow for selectors like ".ext-strict .ext-ie"
+ if (Ext.isStrict || Ext.isBorderBox) {
+ var p = bd.parentNode;
+ if (p) {
+ Ext.fly(p, '_internal').addClass(((Ext.isStrict && Ext.isIE ) || (!Ext.enableForcedBoxModel && !Ext.isIE)) ? ' ext-strict' : ' ext-border-box');
+ }
+ }
+ // Forced border box model class applied to all elements. Bypassing javascript based box model adjustments
+ // in favor of css. This is for non-IE browsers.
+ if (Ext.enableForcedBoxModel && !Ext.isIE) {
+ Ext.isForcedBorderBox = true;
+ cls.push("ext-forced-border-box");
+ }
+
+ Ext.fly(bd, '_internal').addClass(cls);
+ return true;
+ };
+
+ if (!initExtCss()) {
+ Ext.onReady(initExtCss);
+ }
+})();
+/**
+ * Code used to detect certain browser feature/quirks/bugs at startup.
+ */
+(function(){
+ var supports = Ext.apply(Ext.supports, {
/**
- * Gets the width of the padding(s) for the specified side(s)
- * @param {String} side Can be t, l, r, b or any combination of those to add multiple values. For example,
- * passing <tt>'lr'</tt> would get the padding <b><u>l</u></b>eft + the padding <b><u>r</u></b>ight.
- * @return {Number} The padding of the sides passed added together
+ * In Webkit, there is an issue with getting the margin right property, see
+ * https://bugs.webkit.org/show_bug.cgi?id=13343
*/
- getPadding : function(side){
- return this.addStyles(side, paddings);
- },
-
+ correctRightMargin: true,
+
/**
- * Store the current overflow setting and clip overflow on the element - use <tt>{@link #unclip}</tt> to remove
- * @return {Ext.Element} this
+ * Webkit browsers return rgba(0, 0, 0) when a transparent color is used
*/
- clip : function(){
- var me = this,
- dom = me.dom;
-
- if(!data(dom, ISCLIPPED)){
- data(dom, ISCLIPPED, true);
- data(dom, ORIGINALCLIP, {
- o: me.getStyle(OVERFLOW),
- x: me.getStyle(OVERFLOWX),
- y: me.getStyle(OVERFLOWY)
- });
- me.setStyle(OVERFLOW, HIDDEN);
- me.setStyle(OVERFLOWX, HIDDEN);
- me.setStyle(OVERFLOWY, HIDDEN);
- }
- return me;
- },
-
+ correctTransparentColor: true,
+
/**
- * Return clipping (overflow) to original clipping before <tt>{@link #clip}</tt> was called
- * @return {Ext.Element} this
+ * IE uses styleFloat, not cssFloat for the float property.
*/
- unclip : function(){
- var me = this,
- dom = me.dom;
-
- if(data(dom, ISCLIPPED)){
- data(dom, ISCLIPPED, false);
- var o = data(dom, ORIGINALCLIP);
- if(o.o){
- me.setStyle(OVERFLOW, o.o);
- }
- if(o.x){
- me.setStyle(OVERFLOWX, o.x);
+ cssFloat: true
+ });
+
+ var supportTests = function(){
+ var div = document.createElement('div'),
+ doc = document,
+ view,
+ last;
+
+ div.innerHTML = '<div style="height:30px;width:50px;"><div style="height:20px;width:20px;"></div></div><div style="float:left;background-color:transparent;">';
+ doc.body.appendChild(div);
+ last = div.lastChild;
+
+ if((view = doc.defaultView)){
+ if(view.getComputedStyle(div.firstChild.firstChild, null).marginRight != '0px'){
+ supports.correctRightMargin = false;
}
- if(o.y){
- me.setStyle(OVERFLOWY, o.y);
+ if(view.getComputedStyle(last, null).backgroundColor != 'transparent'){
+ supports.correctTransparentColor = false;
}
}
- return me;
+ supports.cssFloat = !!last.style.cssFloat;
+ doc.body.removeChild(div);
+ };
+
+ if (Ext.isReady) {
+ supportTests();
+ } else {
+ Ext.onReady(supportTests);
+ }
+})();
+
+
+/**
+ * @class Ext.EventObject
+ * Just as {@link Ext.Element} wraps around a native DOM node, Ext.EventObject
+ * wraps the browser's native event-object normalizing cross-browser differences,
+ * such as which mouse button is clicked, keys pressed, mechanisms to stop
+ * event-propagation along with a method to prevent default actions from taking place.
+ * <p>For example:</p>
+ * <pre><code>
+function handleClick(e, t){ // e is not a standard event object, it is a Ext.EventObject
+ e.preventDefault();
+ var target = e.getTarget(); // same as t (the target HTMLElement)
+ ...
+}
+var myDiv = {@link Ext#get Ext.get}("myDiv"); // get reference to an {@link Ext.Element}
+myDiv.on( // 'on' is shorthand for addListener
+ "click", // perform an action on click of myDiv
+ handleClick // reference to the action handler
+);
+// other methods to do the same:
+Ext.EventManager.on("myDiv", 'click', handleClick);
+Ext.EventManager.addListener("myDiv", 'click', handleClick);
+ </code></pre>
+ * @singleton
+ */
+Ext.EventObject = function(){
+ var E = Ext.lib.Event,
+ clickRe = /(dbl)?click/,
+ // safari keypress events for special keys return bad keycodes
+ safariKeys = {
+ 3 : 13, // enter
+ 63234 : 37, // left
+ 63235 : 39, // right
+ 63232 : 38, // up
+ 63233 : 40, // down
+ 63276 : 33, // page up
+ 63277 : 34, // page down
+ 63272 : 46, // delete
+ 63273 : 36, // home
+ 63275 : 35 // end
},
+ // normalize button clicks
+ btnMap = Ext.isIE ? {1:0,4:1,2:2} : {0:0,1:1,2:2};
- // private
- addStyles : function(sides, styles){
- var ttlSize = 0,
- sidesArr = sides.match(wordsRe),
- side,
- size,
- i,
- len = sidesArr.length;
- for (i = 0; i < len; i++) {
- side = sidesArr[i];
- size = side && parseInt(this.getStyle(styles[side]), 10);
- if (size) {
- ttlSize += MATH.abs(size);
+ Ext.EventObjectImpl = function(e){
+ if(e){
+ this.setEvent(e.browserEvent || e);
+ }
+ };
+
+ Ext.EventObjectImpl.prototype = {
+ /** @private */
+ setEvent : function(e){
+ var me = this;
+ if(e == me || (e && e.browserEvent)){ // already wrapped
+ return e;
+ }
+ me.browserEvent = e;
+ if(e){
+ // normalize buttons
+ me.button = e.button ? btnMap[e.button] : (e.which ? e.which - 1 : -1);
+ if(clickRe.test(e.type) && me.button == -1){
+ me.button = 0;
}
+ me.type = e.type;
+ me.shiftKey = e.shiftKey;
+ // mac metaKey behaves like ctrlKey
+ me.ctrlKey = e.ctrlKey || e.metaKey || false;
+ me.altKey = e.altKey;
+ // in getKey these will be normalized for the mac
+ me.keyCode = e.keyCode;
+ me.charCode = e.charCode;
+ // cache the target for the delayed and or buffered events
+ me.target = E.getTarget(e);
+ // same for XY
+ me.xy = E.getXY(e);
+ }else{
+ me.button = -1;
+ me.shiftKey = false;
+ me.ctrlKey = false;
+ me.altKey = false;
+ me.keyCode = 0;
+ me.charCode = 0;
+ me.target = null;
+ me.xy = [0, 0];
}
- return ttlSize;
+ return me;
},
- margins : margins
- }
-}()
-);
-/**
- * @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
+ * Stop the event (preventDefault and stopPropagation)
*/
- applyStyles : function(style){
- Ext.DomHelper.applyStyles(this.dom, style);
- return this;
+ stopEvent : function(){
+ var me = this;
+ if(me.browserEvent){
+ if(me.browserEvent.type == 'mousedown'){
+ Ext.EventManager.stoppedMouseDownEvent.fire(me);
+ }
+ E.stopEvent(me.browserEvent);
+ }
},
/**
- * 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
+ * Prevents the browsers default handling of the event.
*/
- 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;
+ preventDefault : function(){
+ if(this.browserEvent){
+ E.preventDefault(this.browserEvent);
}
},
- /**
- * <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
+ * Cancels bubbling of the event.
*/
- setSize : function(width, height, animate){
+ stopPropagation : function(){
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));
+ if(me.browserEvent){
+ if(me.browserEvent.type == 'mousedown'){
+ Ext.EventManager.stoppedMouseDownEvent.fire(me);
+ }
+ E.stopPropagation(me.browserEvent);
}
- 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.
+ * Gets the character code for the event.
* @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;
+ getCharCode : function(){
+ return this.charCode || this.keyCode;
},
/**
- * 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.
+ * Returns a normalized keyCode for the event.
+ * @return {Number} The key code
+ */
+ getKey : function(){
+ return this.normalizeKey(this.keyCode || this.charCode);
+ },
+
+ // private
+ normalizeKey: function(k){
+ return Ext.isSafari ? (safariKeys[k] || k) : k;
+ },
+
+ /**
+ * Gets the x coordinate of the event.
* @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;
+ getPageX : function(){
+ return this.xy[0];
},
/**
- * Returns the sum width of the padding and borders for the passed "sides". See getBorderWidth()
- for more information about the sides.
- * @param {String} sides
+ * Gets the y coordinate of the event.
* @return {Number}
*/
- getFrameWidth : function(sides, onlyContentBox){
- return onlyContentBox && this.isBorderBox() ? 0 : (this.getPadding(sides) + this.getBorderWidth(sides));
+ getPageY : function(){
+ return this.xy[1];
},
/**
- * 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
+ * Gets the page coordinates of the event.
+ * @return {Array} The xy values like [x, y]
*/
- addClassOnOver : function(className){
- this.hover(
- function(){
- Ext.fly(this, INTERNAL).addClass(className);
- },
- function(){
- Ext.fly(this, INTERNAL).removeClass(className);
- }
- );
- return this;
+ getXY : function(){
+ return this.xy;
},
/**
- * Sets up event handlers to add and remove a css class when this element has the focus
- * @param {String} className
- * @return {Ext.Element} this
+ * Gets the target for the event.
+ * @param {String} selector (optional) A simple selector to filter the target or look for an ancestor of the target
+ * @param {Number/Mixed} maxDepth (optional) The max depth to
+ search as a number or element (defaults to 10 || document.body)
+ * @param {Boolean} returnEl (optional) True to return a Ext.Element object instead of DOM node
+ * @return {HTMLelement}
*/
- 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;
+ getTarget : function(selector, maxDepth, returnEl){
+ return selector ? Ext.fly(this.target).findParent(selector, maxDepth, returnEl) : (returnEl ? Ext.get(this.target) : this.target);
},
/**
- * 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
+ * Gets the related target.
+ * @return {HTMLElement}
*/
- 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;
+ getRelatedTarget : function(){
+ return this.browserEvent ? E.getRelatedTarget(this.browserEvent) : null;
},
/**
- * <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();
+ * Normalizes mouse wheel delta across browsers
+ * @return {Number} The delta
+ */
+ getWheelDelta : function(){
+ var e = this.browserEvent;
+ var delta = 0;
+ if(e.wheelDelta){ /* IE/Opera. */
+ delta = e.wheelDelta/120;
+ }else if(e.detail){ /* Mozilla case. */
+ delta = -e.detail/3;
+ }
+ return delta;
+ },
- // 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
+ /**
+ * Returns true if the target of this event is a child of el. Unless the allowEl parameter is set, it will return false if if the target is el.
+ * Example usage:<pre><code>
+ // Handle click on any child of an element
+ Ext.getBody().on('click', function(e){
+ if(e.within('some-el')){
+ alert('Clicked on a child of some-el!');
+ }
});
- // 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()
- };
+ // Handle click directly on an element, ignoring clicks on child nodes
+ Ext.getBody().on('click', function(e,t){
+ if((t.id == 'some-el') && !e.within(t, true)){
+ alert('Clicked directly on some-el!');
+ }
+ });
+ </code></pre>
+ * @param {Mixed} el The id, DOM element or Ext.Element to check
+ * @param {Boolean} related (optional) true to test if the related target is within el instead of the target
+ * @param {Boolean} allowEl {optional} true to also check if the passed element is the target or related target
+ * @return {Boolean}
+ */
+ within : function(el, related, allowEl){
+ if(el){
+ var t = this[related ? "getRelatedTarget" : "getTarget"]();
+ return t && ((allowEl ? (t == Ext.getDom(el)) : false) || Ext.fly(el).contains(t));
+ }
+ return false;
+ }
+ };
- // Else use clientHeight/clientWidth
+ return new Ext.EventObjectImpl();
+}();
+/**
+ * @class Ext.Loader
+ * @singleton
+ * Simple class to help load JavaScript files on demand
+ */
+Ext.Loader = Ext.apply({}, {
+ /**
+ * Loads a given set of .js files. Calls the callback function when all files have been loaded
+ * Set preserveOrder to true to ensure non-parallel loading of files if load order is important
+ * @param {Array} fileList Array of all files to load
+ * @param {Function} callback Callback to call after all files have been loaded
+ * @param {Object} scope The scope to call the callback in
+ * @param {Boolean} preserveOrder True to make files load in serial, one after the other (defaults to false)
+ */
+ load: function(fileList, callback, scope, preserveOrder) {
+ var scope = scope || this,
+ head = document.getElementsByTagName("head")[0],
+ fragment = document.createDocumentFragment(),
+ numFiles = fileList.length,
+ loadedFiles = 0,
+ me = this;
+
+ /**
+ * Loads a particular file from the fileList by index. This is used when preserving order
+ */
+ var loadFileIndex = function(index) {
+ head.appendChild(
+ me.buildScriptTag(fileList[index], onFileLoaded)
+ );
+ };
+
+ /**
+ * Callback function which is called after each file has been loaded. This calls the callback
+ * passed to load once the final file in the fileList has been loaded
+ */
+ var onFileLoaded = function() {
+ loadedFiles ++;
+
+ //if this was the last file, call the callback, otherwise load the next file
+ if (numFiles == loadedFiles && typeof callback == 'function') {
+ callback.call(scope);
} else {
- return {
- width : d.clientWidth,
- height : d.clientHeight
+ if (preserveOrder === true) {
+ loadFileIndex(loadedFiles);
}
}
- },
+ };
+
+ if (preserveOrder === true) {
+ loadFileIndex.call(this, 0);
+ } else {
+ //load each file (most browsers will do this in parallel)
+ Ext.each(fileList, function(file, index) {
+ fragment.appendChild(
+ this.buildScriptTag(file, onFileLoaded)
+ );
+ }, this);
+
+ head.appendChild(fragment);
+ }
+ },
+
+ /**
+ * @private
+ * Creates and returns a script tag, but does not place it into the document. If a callback function
+ * is passed, this is called when the script has been loaded
+ * @param {String} filename The name of the file to create a script tag for
+ * @param {Function} callback Optional callback, which is called when the script has been loaded
+ * @return {Element} The new script ta
+ */
+ buildScriptTag: function(filename, callback) {
+ var script = document.createElement('script');
+ script.type = "text/javascript";
+ script.src = filename;
+
+ //IE has a different way of handling <script> loads, so we need to check for it here
+ if (script.readyState) {
+ script.onreadystatechange = function() {
+ if (script.readyState == "loaded" || script.readyState == "complete") {
+ script.onreadystatechange = null;
+ callback();
+ }
+ };
+ } else {
+ script.onload = callback;
+ }
+
+ return script;
+ }
+});
+/**
+ * @class Ext
+ */
+
+Ext.ns("Ext.grid", "Ext.list", "Ext.dd", "Ext.tree", "Ext.form", "Ext.menu",
+ "Ext.state", "Ext.layout", "Ext.app", "Ext.ux", "Ext.chart", "Ext.direct");
+ /**
+ * Namespace alloted for extensions to the framework.
+ * @property ux
+ * @type Object
+ */
+Ext.apply(Ext, function(){
+ var E = Ext,
+ idSeed = 0,
+ scrollWidth = null;
+
+ return {
/**
- * <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.
+ * A reusable empty function
+ * @property
+ * @type Function
*/
+ emptyFn : function(){},
- getStyleSize : function(){
- var me = this,
- w, h,
- doc = document,
- d = this.dom,
- isDoc = (d == doc || d == doc.body),
- s = d.style;
+ /**
+ * 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==',
- // 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)};
+ extendX : function(supr, fn){
+ return Ext.extend(supr, fn(supr.prototype));
},
/**
- * 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)}
+ * Returns the current HTML document object as an {@link Ext.Element}.
+ * @return Ext.Element The document
*/
- getSize : function(contentSize){
- return {width: this.getWidth(contentSize), height: this.getHeight(contentSize)};
+ getDoc : function(){
+ return Ext.get(document);
},
/**
- * Forces the browser to repaint this element
- * @return {Ext.Element} this
+ * 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
*/
- repaint : function(){
- var dom = this.dom;
- this.addClass("x-repaint");
- setTimeout(function(){
- Ext.fly(dom).removeClass("x-repaint");
- }, 1);
- return this;
+ 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;
},
/**
- * Disables text selection for this element (normalized across browsers)
- * @return {Ext.Element} this
+ * <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
*/
- unselectable : function(){
- this.dom.unselectable = "on";
- return this.swallowEvent("selectstart", true).
- applyStyles("-moz-user-select:none;-khtml-user-select:none;").
- addClass("x-unselectable");
+ value : function(v, defaultValue, allowBlank){
+ return Ext.isEmpty(v, allowBlank) ? defaultValue : v;
},
/**
- * 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}
+ * Escapes the passed string for use in a regular expression
+ * @param {String} str
+ * @return {String}
*/
- getMargins : function(side){
- var me = this,
- key,
- hash = {t:"top", l:"left", r:"right", b: "bottom"},
- o = {};
+ escapeRe : function(s) {
+ return s.replace(/([-.*+?^${}()|[\]\/\\])/g, "\\$1");
+ },
- if (!side) {
- for (key in me.margins){
- o[hash[key]] = parseFloat(me.getStyle(me.margins[key])) || 0;
- }
- return o;
+ 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 {
- return me.addStyles.call(me, side, me.margins);
+ 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;
}
- }
- };
-}());
-/**
- * @class Ext.Element
- */
-(function(){
-var D = Ext.lib.Dom,
- LEFT = "left",
- RIGHT = "right",
- TOP = "top",
- BOTTOM = "bottom",
- POSITION = "position",
- STATIC = "static",
- RELATIVE = "relative",
- AUTO = "auto",
- ZINDEX = "z-index";
+ },
-Ext.Element.addMethods({
- /**
- * Gets the current X position of the element based on page coordinates. Element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
- * @return {Number} The X position of the element
- */
- getX : function(){
- return D.getX(this.dom);
- },
+ /**
+ * 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;
+ }
- /**
- * Gets the current Y position of the element based on page coordinates. Element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
- * @return {Number} The Y position of the element
- */
- getY : function(){
- return D.getY(this.dom);
- },
+ 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;
+ },
- /**
- * Gets the current position of the element based on page coordinates. Element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
- * @return {Array} The XY position of the element
- */
- getXY : function(){
- return D.getXY(this.dom);
- },
- /**
- * Returns the offsets of this element from the passed element. Both element must be part of the DOM tree and not have display:none to have page coordinates.
- * @param {Mixed} element The element to get the offsets from.
- * @return {Array} The XY page offsets (e.g. [100, -200])
- */
- getOffsetsTo : function(el){
- var o = this.getXY(),
- e = Ext.fly(el, '_internal').getXY();
- return [o[0]-e[0],o[1]-e[1]];
- },
+ // 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} arg1 An {@link Ext.Element}, {@link Ext.Component}, or an Array of either of these to destroy
+ * @param {Mixed} arg2 (optional)
+ * @param {Mixed} etc... (optional)
+ */
+ 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]];
+ }
+ },
- /**
- * Sets the X position of the element based on page coordinates. Element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
- * @param {Number} The X position of the element
- * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
- * @return {Ext.Element} this
- */
- setX : function(x, animate){
- return this.setXY([x, this.getY()], this.animTest(arguments, animate, 1));
- },
+ /**
+ * 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;
+ },
- /**
- * Sets the Y position of the element based on page coordinates. Element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
- * @param {Number} The Y position of the element
- * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
- * @return {Ext.Element} this
- */
- setY : function(y, animate){
- return this.setXY([this.getX(), y], this.animTest(arguments, animate, 1));
- },
+ /**
+ * 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 = {};
- /**
- * Sets the element's left position directly using CSS style (instead of {@link #setX}).
- * @param {String} left The left CSS property value
- * @return {Ext.Element} this
- */
- setLeft : function(left){
- this.setStyle(LEFT, this.addUnits(left));
- return this;
- },
+ Ext.each(arr, function(v) {
+ if(!collect[v]){
+ ret.push(v);
+ }
+ collect[v] = true;
+ });
+ return ret;
+ },
- /**
- * Sets the element's top position directly using CSS style (instead of {@link #setY}).
- * @param {String} top The top CSS property value
- * @return {Ext.Element} this
- */
- setTop : function(top){
- this.setStyle(TOP, this.addUnits(top));
- return this;
- },
+ /**
+ * 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);
+ },
- /**
- * Sets the element's CSS right style.
- * @param {String} right The right CSS property value
- * @return {Ext.Element} this
- */
- setRight : function(right){
- this.setStyle(RIGHT, this.addUnits(right));
- return this;
- },
+ /**
+ * 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;
+ },
- /**
- * Sets the element's CSS bottom style.
- * @param {String} bottom The bottom CSS property value
- * @return {Ext.Element} this
- */
- setBottom : function(bottom){
- this.setStyle(BOTTOM, this.addUnits(bottom));
- return this;
- },
+ /**
+ * 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;
+ },
- /**
- * Sets the position of the element in page coordinates, regardless of how the element is positioned.
- * The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
- * @param {Array} pos Contains X & Y [x, y] values for new position (coordinates are page-based)
- * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
- * @return {Ext.Element} this
- */
- setXY : function(pos, animate){
- var me = this;
- if(!animate || !me.anim){
- D.setXY(me.dom, pos);
- }else{
- me.anim({points: {to: pos}}, me.preanim(arguments, 1), 'motion');
- }
- return me;
- },
+ /**
+ * 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;
+ },
- /**
- * Sets the position of the element in page coordinates, regardless of how the element is positioned.
- * The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
- * @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 {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
- * @return {Ext.Element} this
- */
- setLocation : function(x, y, animate){
- return this.setXY([x, y], this.animTest(arguments, animate, 2));
- },
+ /**
+ * 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;
+ },
- /**
- * Sets the position of the element in page coordinates, regardless of how the element is positioned.
- * The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
- * @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 {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
- * @return {Ext.Element} this
- */
- moveTo : function(x, y, animate){
- return this.setXY([x, y], this.animTest(arguments, animate, 2));
- },
-
- /**
- * Gets the left X coordinate
- * @param {Boolean} local True to get the local css position instead of page coordinate
- * @return {Number}
- */
- getLeft : function(local){
- return !local ? this.getX() : parseInt(this.getStyle(LEFT), 10) || 0;
- },
+ /**
+ * 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;
+ },
- /**
- * Gets the right X coordinate of the element (element X position + element width)
- * @param {Boolean} local True to get the local css position instead of page coordinate
- * @return {Number}
- */
- getRight : function(local){
- var me = this;
- return !local ? me.getX() + me.getWidth() : (me.getLeft(true) + me.getWidth()) || 0;
- },
+ /**
+ * 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;
+ },
- /**
- * Gets the top Y coordinate
- * @param {Boolean} local True to get the local css position instead of page coordinate
- * @return {Number}
- */
- getTop : function(local) {
- return !local ? this.getY() : parseInt(this.getStyle(TOP), 10) || 0;
- },
+ /**
+ * 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;
+ },
- /**
- * Gets the bottom Y coordinate of the element (element Y position + element height)
- * @param {Boolean} local True to get the local css position instead of page coordinate
- * @return {Number}
- */
- getBottom : function(local){
- var me = this;
- return !local ? me.getY() + me.getHeight() : (me.getTop(true) + me.getHeight()) || 0;
- },
+ /**
+ * <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 = [];
- /**
- * Initializes positioning on this element. If a desired position is not passed, it will make the
- * the element positioned relative IF it is not already positioned.
- * @param {String} pos (optional) Positioning to use "relative", "absolute" or "fixed"
- * @param {Number} zIndex (optional) The zIndex to apply
- * @param {Number} x (optional) Set the page X position
- * @param {Number} y (optional) Set the page Y position
- */
- position : function(pos, zIndex, x, y){
- var me = this;
-
- if(!pos && me.isStyle(POSITION, STATIC)){
- me.setStyle(POSITION, RELATIVE);
- } else if(pos) {
- me.setStyle(POSITION, pos);
- }
- if(zIndex){
- me.setStyle(ZINDEX, zIndex);
- }
- if(x || y) me.setXY([x || false, y || false]);
- },
+ 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;
+ },
- /**
- * Clear positioning back to the default when the document was loaded
- * @param {String} value (optional) The value to use for the left,right,top,bottom, defaults to '' (empty string). You could use 'auto'.
- * @return {Ext.Element} this
- */
- clearPositioning : function(value){
- value = value || '';
- this.setStyle({
- left : value,
- right : value,
- top : value,
- bottom : value,
- "z-index" : "",
- position : STATIC
- });
- return this;
- },
+ /**
+ * 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);
+ },
- /**
- * Gets an object with all CSS positioning properties. Useful along with setPostioning to get
- * snapshot before performing an update and then restoring the element.
- * @return {Object}
- */
- getPositioning : function(){
- var l = this.getStyle(LEFT);
- var t = this.getStyle(TOP);
- return {
- "position" : this.getStyle(POSITION),
- "left" : l,
- "right" : l ? "" : this.getStyle(RIGHT),
- "top" : t,
- "bottom" : t ? "" : this.getStyle(BOTTOM),
- "z-index" : this.getStyle(ZINDEX)
- };
- },
-
- /**
- * Set positioning with an object returned by getPositioning().
- * @param {Object} posCfg
- * @return {Ext.Element} this
- */
- setPositioning : function(pc){
- var me = this,
- style = me.dom.style;
-
- me.setStyle(pc);
-
- if(pc.right == AUTO){
- style.right = "";
- }
- if(pc.bottom == AUTO){
- style.bottom = "";
- }
-
- return me;
- },
-
- /**
- * Translates the passed page coordinates into left/top css values for this element
- * @param {Number/Array} x The page x or an array containing [x, y]
- * @param {Number} y (optional) The page y, required if x is not an array
- * @return {Object} An object with left and top properties. e.g. {left: (value), top: (value)}
- */
- translatePoints : function(x, y){
- y = isNaN(x[1]) ? y : x[1];
- x = isNaN(x[0]) ? x : x[0];
- var me = this,
- relative = me.isStyle(POSITION, RELATIVE),
- o = me.getXY(),
- l = parseInt(me.getStyle(LEFT), 10),
- t = parseInt(me.getStyle(TOP), 10);
-
- l = !isNaN(l) ? l : (relative ? 0 : me.dom.offsetLeft);
- t = !isNaN(t) ? t : (relative ? 0 : me.dom.offsetTop);
+ /**
+ * 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),
- return {left: (x - o[0] + l), top: (y - o[1] + t)};
- },
-
- animTest : function(args, animate, i) {
- return !!animate && this.preanim ? this.preanim(args, i) : false;
- }
-});
-})();/**
- * @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;
- },
+ // 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;
+ },
- /**
- * 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;
- },
+ intercept : function(o, name, fn, scope){
+ o[name] = o[name].createInterceptor(fn, scope);
+ },
- /**
- * 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
+ // 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.Element.addMethods({
+Ext.apply(Function.prototype, {
/**
- * Returns true if this element is scrollable.
- * @return {Boolean}
- */
- isScrollable : function(){
- var dom = this.dom;
- return dom.scrollHeight > dom.clientHeight || dom.scrollWidth > dom.clientWidth;
- },
+ * 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);
+}
- /**
- * 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.
- * @return {Element} this
- */
- scrollTo : function(side, value){
- this.dom["scroll" + (/top/i.test(side) ? "Top" : "Left")] = value;
- return this;
- },
+sayHi('Fred'); // alerts "Hi, Fred"
- /**
- * Returns the current scroll position of the element.
- * @return {Object} An object containing the scroll position in the format {left: (scrollLeft), top: (scrollTop)}
- */
- getScroll : function(){
- var d = this.dom,
- doc = document,
- body = doc.body,
- docElement = doc.documentElement,
- l,
- t,
- ret;
+var sayGoodbye = sayHi.createSequence(function(name){
+ alert('Bye, ' + name);
+});
- if(d == doc || d == body){
- if(Ext.isIE && Ext.isStrict){
- l = docElement.scrollLeft;
- t = docElement.scrollTop;
- }else{
- l = window.pageXOffset;
- t = window.pageYOffset;
- }
- ret = {left: l || (body ? body.scrollLeft : 0), top: t || (body ? body.scrollTop : 0)};
- }else{
- ret = {left: d.scrollLeft, top: d.scrollTop};
- }
- return ret;
+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 Ext.Element
+});
+
+
+/**
+ * @class String
+ * These functions are available as static methods on the JavaScript String object.
*/
-Ext.Element.addMethods({
+Ext.applyIf(String, {
+
/**
- * 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
+ * Escapes the passed string for ' and \
+ * @param {String} string The string to escape
+ * @return {String} The escaped string
+ * @static
*/
- scrollTo : function(side, value, animate){
- var top = /top/i.test(side), //check if we're scrolling top or left
- me = this,
- dom = me.dom,
- prop;
- if (!animate || !me.anim) {
- prop = 'scroll' + (top ? 'Top' : 'Left'), // just setting the value, so grab the direction
- dom[prop] = value;
- }else{
- prop = 'scroll' + (top ? 'Left' : 'Top'), // if scrolling top, we need to grab scrollLeft, if left, scrollTop
- me.anim({scroll: {to: top ? [dom[prop], value] : [value, dom[prop]]}},
- me.preanim(arguments, 2), 'scroll');
- }
- return me;
+ escape : function(string) {
+ return string.replace(/('|\\)/g, "\\$1");
},
-
+
/**
- * 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
+ * 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
*/
- 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;
+ leftPad : function (val, size, ch) {
+ var result = String(val);
+ if(!ch) {
+ ch = " ";
}
- c.scrollTop = c.scrollTop; // corrects IE, other browsers will ignore
-
- 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;
+ while (result.length < size) {
+ result = ch + result;
}
- 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;
- }
- 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;
+ return result;
}
-});/**
- * @class Ext.Element
+});
+
+/**
+ * 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;
+};
+
/**
- * Visibility mode constant for use with {@link #setVisibilityMode}. Use visibility to hide element
- * @static
- * @type Number
+ * 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
*/
-Ext.Element.VISIBILITY = 1;
+String.prototype.trim = function(){
+ var re = /^\s+|\s+$/g;
+ return function(){ return this.replace(re, ""); };
+}();
+
+// here to prevent dependency on Date.js
/**
- * Visibility mode constant for use with {@link #setVisibilityMode}. Use display to hide element
- * @static
- * @type Number
+ 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
*/
-Ext.Element.DISPLAY = 2;
+Date.prototype.getElapsed = function(date) {
+ return Math.abs((date || new Date()).getTime()-this.getTime());
+};
-Ext.Element.addMethods(function(){
- var VISIBILITY = "visibility",
- DISPLAY = "display",
- HIDDEN = "hidden",
- OFFSETS = "offsets",
- NONE = "none",
- ORIGINALDISPLAY = 'originalDisplay',
- VISMODE = 'visibilityMode',
- ELDISPLAY = Ext.Element.DISPLAY,
- data = Ext.Element.data,
- getDisplay = function(dom){
- var d = data(dom, ORIGINALDISPLAY);
- if(d === undefined){
- data(dom, ORIGINALDISPLAY, d = '');
- }
- return d;
- },
- getVisMode = function(dom){
- var m = data(dom, VISMODE);
- if(m === undefined){
- data(dom, VISMODE, m = 1);
- }
- return m;
- };
- return {
- /**
- * The element's default display mode (defaults to "")
- * @type String
- */
- originalDisplay : "",
- visibilityMode : 1,
+/**
+ * @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 );
- /**
- * Sets the element's visibility mode. When setVisible() is called it
- * will use this to determine whether to set the visibility or the display property.
- * @param {Number} visMode Ext.Element.VISIBILITY or Ext.Element.DISPLAY
- * @return {Ext.Element} this
- */
- setVisibilityMode : function(visMode){
- data(this.dom, VISMODE, visMode);
- return this;
},
- /**
- * Perform custom animation on this element.
- * <div><ul class="mdetail-params">
- * <li><u>Animation Properties</u></li>
- *
- * <p>The Animation Control Object enables gradual transitions for any member of an
- * element's style object that takes a numeric value including but not limited to
- * these properties:</p><div><ul class="mdetail-params">
- * <li><tt>bottom, top, left, right</tt></li>
- * <li><tt>height, width</tt></li>
- * <li><tt>margin, padding</tt></li>
- * <li><tt>borderWidth</tt></li>
- * <li><tt>opacity</tt></li>
- * <li><tt>fontSize</tt></li>
- * <li><tt>lineHeight</tt></li>
- * </ul></div>
- *
- *
- * <li><u>Animation Property Attributes</u></li>
- *
- * <p>Each Animation Property is a config object with optional properties:</p>
- * <div><ul class="mdetail-params">
- * <li><tt>by</tt>* : relative change - start at current value, change by this value</li>
- * <li><tt>from</tt> : ignore current value, start from this value</li>
- * <li><tt>to</tt>* : start at current value, go to this value</li>
- * <li><tt>unit</tt> : any allowable unit specification</li>
- * <p>* do not specify both <tt>to</tt> and <tt>by</tt> for an animation property</p>
- * </ul></div>
- *
- * <li><u>Animation Types</u></li>
- *
- * <p>The supported animation types:</p><div><ul class="mdetail-params">
- * <li><tt>'run'</tt> : Default
- * <pre><code>
-var el = Ext.get('complexEl');
-el.animate(
- // animation control object
- {
- borderWidth: {to: 3, from: 0},
- opacity: {to: .3, from: 1},
- height: {to: 50, from: el.getHeight()},
- width: {to: 300, from: el.getWidth()},
- top : {by: - 100, unit: 'px'},
- },
- 0.35, // animation duration
- null, // callback
- 'easeOut', // easing method
- 'run' // animation type ('run','color','motion','scroll')
-);
- * </code></pre>
- * </li>
- * <li><tt>'color'</tt>
- * <p>Animates transition of background, text, or border colors.</p>
- * <pre><code>
-el.animate(
- // animation control object
- {
- color: { to: '#06e' },
- backgroundColor: { to: '#e06' }
- },
- 0.35, // animation duration
- null, // callback
- 'easeOut', // easing method
- 'color' // animation type ('run','color','motion','scroll')
-);
- * </code></pre>
- * </li>
- *
- * <li><tt>'motion'</tt>
- * <p>Animates the motion of an element to/from specific points using optional bezier
- * way points during transit.</p>
- * <pre><code>
-el.animate(
- // animation control object
- {
- borderWidth: {to: 3, from: 0},
- opacity: {to: .3, from: 1},
- height: {to: 50, from: el.getHeight()},
- width: {to: 300, from: el.getWidth()},
- top : {by: - 100, unit: 'px'},
- points: {
- to: [50, 100], // go to this point
- control: [ // optional bezier way points
- [ 600, 800],
- [-100, 200]
- ]
- }
- },
- 3000, // animation duration (milliseconds!)
- null, // callback
- 'easeOut', // easing method
- 'motion' // animation type ('run','color','motion','scroll')
-);
- * </code></pre>
- * </li>
- * <li><tt>'scroll'</tt>
- * <p>Animate horizontal or vertical scrolling of an overflowing page element.</p>
- * <pre><code>
-el.animate(
- // animation control object
- {
- scroll: {to: [400, 300]}
- },
- 0.35, // animation duration
- null, // callback
- 'easeOut', // easing method
- 'scroll' // animation type ('run','color','motion','scroll')
-);
- * </code></pre>
- * </li>
- * </ul></div>
- *
- * </ul></div>
- *
- * @param {Object} args The animation control args
- * @param {Float} duration (optional) How long the animation lasts in seconds (defaults to <tt>.35</tt>)
- * @param {Function} onComplete (optional) Function to call when animation completes
- * @param {String} easing (optional) {@link Ext.Fx#easing} method to use (defaults to <tt>'easeOut'</tt>)
- * @param {String} animType (optional) <tt>'run'</tt> is the default. Can also be <tt>'color'</tt>,
- * <tt>'motion'</tt>, or <tt>'scroll'</tt>
- * @return {Ext.Element} this
- */
- animate : function(args, duration, onComplete, easing, animType){
- this.anim(args, {duration: duration, callback: onComplete, easing: easing}, animType);
- return this;
+ getArea : function() {
+ var me = this;
+ return ( (me.bottom - me.top) * (me.right - me.left) );
},
- /*
- * @private Internal animation call
- */
- anim : function(args, opt, animType, defaultDur, defaultEase, cb){
- animType = animType || 'run';
- opt = opt || {};
+ intersect : function(region) {
var me = this,
- anim = Ext.lib.Anim[animType](
- me.dom,
- args,
- (opt.duration || defaultDur) || .35,
- (opt.easing || defaultEase) || 'easeOut',
- function(){
- if(cb) cb.call(me);
- if(opt.callback) opt.callback.call(opt.scope || me, me, opt);
- },
- me
- );
- opt.anim = anim;
- return anim;
+ 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);
- // private legacy anim prep
- preanim : function(a, i){
- return !a[i] ? false : (typeof a[i] == 'object' ? a[i]: {duration: a[i+1], callback: a[i+2], easing: a[i+3]});
+ return new Ext.lib.Region(t, r, b, l);
},
- /**
- * Checks whether the element is currently visible using both visibility and display properties.
- * @return {Boolean} True if the element is currently visible, else false
- */
- isVisible : function() {
- return !this.isStyle(VISIBILITY, HIDDEN) && !this.isStyle(DISPLAY, NONE);
+ 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;
},
- /**
- * Sets the visibility of the element (see details). If the visibilityMode is set to Element.DISPLAY, it will use
- * the display property to hide the element, otherwise it uses visibility. The default is to hide and show using the visibility property.
- * @param {Boolean} visible Whether the element is visible
- * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
- * @return {Ext.Element} this
- */
- setVisible : function(visible, animate){
- var me = this, isDisplay, isVisible, isOffsets,
- dom = me.dom;
+ adjust : function(t, l, b, r) {
+ var me = this;
+ me.top += t;
+ me.left += l;
+ me.right += r;
+ me.bottom += b;
+ return me;
+ }
+ };
- // hideMode string override
- if (typeof animate == 'string'){
- isDisplay = animate == DISPLAY;
- isVisible = animate == VISIBILITY;
- isOffsets = animate == OFFSETS;
- animate = false;
+ 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 {
- isDisplay = getVisMode(this.dom) == ELDISPLAY;
- isVisible = !isDisplay;
+ (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;
+ }
- if (!animate || !me.anim) {
- if (isDisplay){
- me.setDisplayed(visible);
- } else if (isOffsets){
- if (!visible){
- me.hideModeStyles = {
- position: me.getStyle('position'),
- top: me.getStyle('top'),
- left: me.getStyle('left')
- };
+ // build as dom
+ /** @ignore */
+ function createDom(o, parentNode){
+ var el,
+ doc = document,
+ useSet,
+ attr,
+ val,
+ cn;
- me.applyStyles({position: 'absolute', top: '-10000px', left: '-10000px'});
- } else {
- me.applyStyles(me.hideModeStyles || {position: '', top: '', left: ''});
+ 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;
+ }
}
- }else{
- me.fixDisplay();
- dom.style.visibility = visible ? "visible" : HIDDEN;
- }
- }else{
- // closure for composites
- if (visible){
- me.setOpacity(.01);
- me.setVisible(true);
}
- me.anim({opacity: { to: (visible?1:0) }},
- me.preanim(arguments, 1),
- null,
- .35,
- 'easeIn',
- function(){
- if(!visible){
- dom.style[isDisplay ? DISPLAY : VISIBILITY] = (isDisplay) ? NONE : HIDDEN;
- Ext.fly(dom).setOpacity(1);
- }
- });
}
- return me;
- },
+ 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 = {
/**
- * Toggles the element's visibility or display, depending on visibility mode.
- * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
- * @return {Ext.Element} this
+ * 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
*/
- toggle : function(animate){
- var me = this;
- me.setVisible(!me.isVisible(), me.preanim(arguments, 0));
- return me;
+ 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,
+
/**
- * Sets the CSS display property. Uses originalDisplay if the specified value is a boolean true.
- * @param {Mixed} value Boolean value to display the element using its default display, or a string to set the display directly.
- * @return {Ext.Element} this
+ * 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)
*/
- setDisplayed : function(value) {
- if(typeof value == "boolean"){
- value = value ? getDisplay(this.dom) : NONE;
- }
- this.setStyle(DISPLAY, value);
- return this;
+ 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');
},
- // private
- fixDisplay : function(){
- var me = this;
- if(me.isStyle(DISPLAY, NONE)){
- me.setStyle(VISIBILITY, HIDDEN);
- me.setStyle(DISPLAY, getDisplay(this.dom)); // first try reverting to default
- if(me.isStyle(DISPLAY, NONE)){ // if that fails, default to block
- me.setStyle(DISPLAY, "block");
- }
- }
+ /**
+ * 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');
},
/**
- * Hide this element - Uses display mode to determine whether to use "display" or "visibility". See {@link #setVisible}.
- * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
- * @return {Ext.Element} this
+ * 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)
*/
- hide : function(animate){
- // hideMode override
- if (typeof animate == 'string'){
- this.setVisible(false, animate);
- return this;
- }
- this.setVisible(false, this.preanim(arguments, 0));
- return this;
+ append: function(el, o, returnElement){
+ return doInsert(el, o, returnElement, beforeend, '', true);
},
/**
- * Show this element - Uses display mode to determine whether to use "display" or "visibility". See {@link #setVisible}.
- * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
- * @return {Ext.Element} this
+ * 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
*/
- show : function(animate){
- // hideMode override
- if (typeof animate == 'string'){
- this.setVisible(true, animate);
- return this;
- }
- this.setVisible(true, this.preanim(arguments, 0));
- return this;
- }
+ createDom: createDom
};
+ return pub;
}());
/**
- * @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);
- }
- if(Ext.isIE && !(Ext.isIE7 && Ext.isStrict) && me.getStyle('height') == 'auto'){ // ie will not expand full height automatically
- 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
+ * @class Ext.Template
*/
-Ext.Element.addMethods({
+Ext.apply(Ext.Template.prototype, {
/**
- * 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
+ * @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}.
*/
- 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);
- },
-
+ disableFormats : false,
/**
- * 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
+ * See <code>{@link #disableFormats}</code>.
+ * @type Boolean
+ * @property disableFormats
*/
- addKeyMap : function(config){
- return new Ext.KeyMap(this, config);
- }
-});
-(function(){
- // contants
- var NULL = null,
- UNDEFINED = undefined,
- TRUE = true,
- FALSE = false,
- SETX = "setX",
- SETY = "setY",
- SETXY = "setXY",
- LEFT = "left",
- BOTTOM = "bottom",
- TOP = "top",
- RIGHT = "right",
- HEIGHT = "height",
- WIDTH = "width",
- POINTS = "points",
- HIDDEN = "hidden",
- ABSOLUTE = "absolute",
- VISIBLE = "visible",
- MOTION = "motion",
- POSITION = "position",
- EASEOUT = "easeOut",
- /*
- * Use a light flyweight here since we are using so many callbacks and are always assured a DOM element
- */
- flyEl = new Ext.Element.Flyweight(),
- queues = {},
- getObject = function(o){
- return o || {};
- },
- fly = function(dom){
- flyEl.dom = dom;
- flyEl.id = Ext.id(dom);
- return flyEl;
- },
- /*
- * Queueing now stored outside of the element due to closure issues
- */
- getQueue = function(id){
- if(!queues[id]){
- queues[id] = [];
- }
- return queues[id];
- },
- setQueue = function(id, value){
- queues[id] = value;
- };
-
-//Notifies Element that fx methods are available
-Ext.enableFx = TRUE;
-/**
- * @class Ext.Fx
- * <p>A class to provide basic animation and visual effects support. <b>Note:</b> This class is automatically applied
- * to the {@link Ext.Element} interface when included, so all effects calls should be performed via {@link Ext.Element}.
- * Conversely, since the effects are not actually defined in {@link Ext.Element}, Ext.Fx <b>must</b> be
- * {@link Ext#enableFx included} in order for the Element effects to work.</p><br/>
- *
- * <p><b><u>Method Chaining</u></b></p>
- * <p>It is important to note that although the Fx methods and many non-Fx Element methods support "method chaining" in that
- * they return the Element object itself as the method return value, it is not always possible to mix the two in a single
- * method chain. The Fx methods use an internal effects queue so that each effect can be properly timed and sequenced.
- * Non-Fx methods, on the other hand, have no such internal queueing and will always execute immediately. For this reason,
- * while it may be possible to mix certain Fx and non-Fx method calls in a single chain, it may not always provide the
- * expected results and should be done with care. Also see <tt>{@link #callback}</tt>.</p><br/>
- *
- * <p><b><u>Anchor Options for Motion Effects</u></b></p>
- * <p>Motion effects support 8-way anchoring, meaning that you can choose one of 8 different anchor points on the Element
- * that will serve as either the start or end point of the animation. Following are all of the supported anchor positions:</p>
-<pre>
-Value Description
------ -----------------------------
-tl The top left corner
-t The center of the top edge
-tr The top right corner
-l The center of the left edge
-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>
- * <b>Note</b>: some Fx methods accept specific custom config parameters. The options shown in the Config Options
- * section below are common options that can be passed to any Fx method unless otherwise noted.</b>
- *
- * @cfg {Function} callback A function called when the effect is finished. Note that effects are queued internally by the
- * Fx class, so a callback is not required to specify another effect -- effects can simply be chained together
- * and called in sequence (see note for <b><u>Method Chaining</u></b> above), for example:<pre><code>
- * el.slideIn().highlight();
- * </code></pre>
- * The callback is intended for any additional code that should run once a particular effect has completed. The Element
- * being operated upon is passed as the first parameter.
- *
- * @cfg {Object} scope The scope (<code>this</code> reference) in which the <tt>{@link #callback}</tt> function is executed. Defaults to the browser window.
- *
- * @cfg {String} easing A valid Ext.lib.Easing value for the effect:</p><div class="mdetail-params"><ul>
- * <li><b><tt>backBoth</tt></b></li>
- * <li><b><tt>backIn</tt></b></li>
- * <li><b><tt>backOut</tt></b></li>
- * <li><b><tt>bounceBoth</tt></b></li>
- * <li><b><tt>bounceIn</tt></b></li>
- * <li><b><tt>bounceOut</tt></b></li>
- * <li><b><tt>easeBoth</tt></b></li>
- * <li><b><tt>easeBothStrong</tt></b></li>
- * <li><b><tt>easeIn</tt></b></li>
- * <li><b><tt>easeInStrong</tt></b></li>
- * <li><b><tt>easeNone</tt></b></li>
- * <li><b><tt>easeOut</tt></b></li>
- * <li><b><tt>easeOutStrong</tt></b></li>
- * <li><b><tt>elasticBoth</tt></b></li>
- * <li><b><tt>elasticIn</tt></b></li>
- * <li><b><tt>elasticOut</tt></b></li>
- * </ul></div>
- *
- * @cfg {String} afterCls A css class to apply after the effect
- * @cfg {Number} duration The length of time (in seconds) that the effect should last
- *
- * @cfg {Number} endOpacity Only applicable for {@link #fadeIn} or {@link #fadeOut}, a number between
- * <tt>0</tt> and <tt>1</tt> inclusive to configure the ending opacity value.
- *
- * @cfg {Boolean} remove Whether the Element should be removed from the DOM and destroyed after the effect finishes
- * @cfg {Boolean} useDisplay Whether to use the <i>display</i> CSS property instead of <i>visibility</i> when hiding Elements (only applies to
- * effects that end with the element being visually hidden, ignored otherwise)
- * @cfg {String/Object/Function} afterStyle A style specification string, e.g. <tt>"width:100px"</tt>, or an object
- * in the form <tt>{width:"100px"}</tt>, or a function which returns such a specification that will be applied to the
- * Element after the effect finishes.
- * @cfg {Boolean} block Whether the effect should block other effects from queueing while it runs
- * @cfg {Boolean} concurrent Whether to allow subsequently-queued effects to run at the same time as the current effect, or to ensure that they run in sequence
- * @cfg {Boolean} stopFx Whether preceding effects should be stopped and removed before running current effect (only applies to non blocking effects)
- */
-Ext.Fx = {
-
- // private - calls the function taking arguments from the argHash based on the key. Returns the return value of the function.
- // this is useful for replacing switch statements (for example).
- switchStatements : function(key, fn, argHash){
- return fn.apply(this, argHash[key]);
- },
-
/**
- * Slides the element into view. An anchor point can be optionally passed to set the point of
- * origin for the slide effect. This function automatically handles wrapping the element with
- * a fixed-size container if needed. See the Fx class overview for valid anchor point options.
- * Usage:
- *<pre><code>
-// default: slide the element in from the top
-el.slideIn();
-
-// custom: slide the element in from the right with a 2-second duration
-el.slideIn('r', { duration: 2 });
+ * 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,
-// common config options shown with default values
-el.slideIn('t', {
- easing: 'easeOut',
- duration: .5
-});
-</code></pre>
- * @param {String} anchor (optional) One of the valid Fx anchor positions (defaults to top: 't')
- * @param {Object} options (optional) Object literal with any of the Fx config options
- * @return {Ext.Element} The Element
+ /**
+ * 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
*/
- slideIn : function(anchor, o){
- o = getObject(o);
+ applyTemplate : function(values){
var me = this,
- dom = me.dom,
- st = dom.style,
- xy,
- r,
- b,
- wrap,
- after,
- st,
- args,
- pt,
- bw,
- bh;
-
- anchor = anchor || "t";
+ useF = me.disableFormats !== true,
+ fm = Ext.util.Format,
+ tpl = me;
- me.queueFx(o, function(){
- xy = fly(dom).getXY();
- // fix display to visibility
- fly(dom).fixDisplay();
-
- // restore values after effect
- r = fly(dom).getFxRestore();
- b = {x: xy[0], y: xy[1], 0: xy[0], 1: xy[1], width: dom.offsetWidth, height: dom.offsetHeight};
- b.right = b.x + b.width;
- b.bottom = b.y + b.height;
-
- // fixed size for slide
- fly(dom).setWidth(b.width).setHeight(b.height);
-
- // wrap if needed
- wrap = fly(dom).fxWrap(r.pos, o, HIDDEN);
-
- st.visibility = VISIBLE;
- st.position = ABSOLUTE;
-
- // clear out temp styles after slide and unwrap
- function after(){
- fly(dom).fxUnwrap(wrap, r.pos, o);
- st.width = r.width;
- st.height = r.height;
- fly(dom).afterFx(o);
- }
-
- // time to calculate the positions
- pt = {to: [b.x, b.y]};
- bw = {to: b.width};
- bh = {to: b.height};
-
- function argCalc(wrap, style, ww, wh, sXY, sXYval, s1, s2, w, h, p){
- var ret = {};
- fly(wrap).setWidth(ww).setHeight(wh);
- if(fly(wrap)[sXY]){
- fly(wrap)[sXY](sXYval);
- }
- style[s1] = style[s2] = "0";
- if(w){
- ret.width = w
- };
- if(h){
- ret.height = h;
- }
- if(p){
- ret.points = p;
+ 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);
}
- return ret;
- };
-
- args = fly(dom).switchStatements(anchor.toLowerCase(), argCalc, {
- t : [wrap, st, b.width, 0, NULL, NULL, LEFT, BOTTOM, NULL, bh, NULL],
- l : [wrap, st, 0, b.height, NULL, NULL, RIGHT, TOP, bw, NULL, NULL],
- r : [wrap, st, b.width, b.height, SETX, b.right, LEFT, TOP, NULL, NULL, pt],
- b : [wrap, st, b.width, b.height, SETY, b.bottom, LEFT, TOP, NULL, bh, pt],
- tl : [wrap, st, 0, 0, NULL, NULL, RIGHT, BOTTOM, bw, bh, pt],
- bl : [wrap, st, 0, 0, SETY, b.y + b.height, RIGHT, TOP, bw, bh, pt],
- br : [wrap, st, 0, 0, SETXY, [b.right, b.bottom], LEFT, TOP, bw, bh, pt],
- tr : [wrap, st, 0, 0, SETX, b.x + b.width, LEFT, BOTTOM, bw, bh, pt]
- });
-
- st.visibility = VISIBLE;
- fly(wrap).show();
-
- arguments.callee.anim = fly(wrap).fxanim(args,
- o,
- MOTION,
- .5,
- EASEOUT,
- after);
- });
- return me;
+ } else {
+ return values[name] !== undefined ? values[name] : "";
+ }
+ }
+ return me.html.replace(me.re, fn);
},
-
+
/**
- * Slides the element out of view. An anchor point can be optionally passed to set the end point
- * for the slide effect. When the effect is completed, the element will be hidden (visibility =
- * 'hidden') but block elements will still take up space in the document. The element must be removed
- * from the DOM using the 'remove' config option if desired. This function automatically handles
- * wrapping the element with a fixed-size container if needed. See the Fx class overview for valid anchor point options.
- * Usage:
- *<pre><code>
-// default: slide the element out to the top
-el.slideOut();
+ * 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;
-// custom: slide the element out to the right with a 2-second duration
-el.slideOut('r', { duration: 2 });
+ 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;
+ },
-// common config options shown with default values
-el.slideOut('t', {
- easing: 'easeOut',
- duration: .5,
- remove: false,
- useDisplay: false
+ // private function used to call members
+ call : function(fnName, value, allValues){
+ return this[fnName](value, allValues);
+ }
});
-</code></pre>
- * @param {String} anchor (optional) One of the valid Fx anchor positions (defaults to top: 't')
- * @param {Object} options (optional) Object literal with any of the Fx config options
- * @return {Ext.Element} The Element
- */
- slideOut : function(anchor, o){
- o = getObject(o);
- var me = this,
- dom = me.dom,
- st = dom.style,
- xy = me.getXY(),
- wrap,
- r,
- b,
- a,
- zero = {to: 0};
-
- anchor = anchor || "t";
+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);
+}
- me.queueFx(o, function(){
-
- // restore values after effect
- r = fly(dom).getFxRestore();
- b = {x: xy[0], y: xy[1], 0: xy[0], 1: xy[1], width: dom.offsetWidth, height: dom.offsetHeight};
- b.right = b.x + b.width;
- b.bottom = b.y + b.height;
-
- // fixed size for slide
- fly(dom).setWidth(b.width).setHeight(b.height);
+sayHi('Fred'); // alerts "Hi, Fred"
- // wrap if needed
- wrap = fly(dom).fxWrap(r.pos, o, VISIBLE);
-
- st.visibility = VISIBLE;
- st.position = ABSOLUTE;
- fly(wrap).setWidth(b.width).setHeight(b.height);
+// create a new function that validates input without
+// directly modifying the original function:
+var sayHiToFriend = Ext.createInterceptor(sayHi, function(name){
+ return name == 'Brian';
+});
- function after(){
- o.useDisplay ? fly(dom).setDisplayed(FALSE) : fly(dom).hide();
- fly(dom).fxUnwrap(wrap, r.pos, o);
- st.width = r.width;
- st.height = r.height;
- fly(dom).afterFx(o);
- }
-
- function argCalc(style, s1, s2, p1, v1, p2, v2, p3, v3){
- var ret = {};
-
- style[s1] = style[s2] = "0";
- ret[p1] = v1;
- if(p2){
- ret[p2] = v2;
- }
- if(p3){
- ret[p3] = v3;
- }
-
- return ret;
+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;
};
-
- a = fly(dom).switchStatements(anchor.toLowerCase(), argCalc, {
- t : [st, LEFT, BOTTOM, HEIGHT, zero],
- l : [st, RIGHT, TOP, WIDTH, zero],
- r : [st, LEFT, TOP, WIDTH, zero, POINTS, {to : [b.right, b.y]}],
- b : [st, LEFT, TOP, HEIGHT, zero, POINTS, {to : [b.x, b.bottom]}],
- tl : [st, RIGHT, BOTTOM, WIDTH, zero, HEIGHT, zero],
- bl : [st, RIGHT, TOP, WIDTH, zero, HEIGHT, zero, POINTS, {to : [b.x, b.bottom]}],
- br : [st, LEFT, TOP, WIDTH, zero, HEIGHT, zero, POINTS, {to : [b.x + b.width, b.bottom]}],
- tr : [st, LEFT, BOTTOM, WIDTH, zero, HEIGHT, zero, POINTS, {to : [b.right, b.y]}]
- });
-
- arguments.callee.anim = fly(wrap).fxanim(a,
- o,
- MOTION,
- .5,
- EASEOUT,
- after);
- });
- return me;
+ }
},
/**
- * Fades the element out while slowly expanding it in all directions. When the effect is completed, the
- * element will be hidden (visibility = 'hidden') but block elements will still take up space in the document.
- * The element must be removed from the DOM using the 'remove' config option if desired.
- * Usage:
- *<pre><code>
-// default
-el.puff();
+ * 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.');
+}
-// common config options shown with default values
-el.puff({
- easing: 'easeOut',
- duration: .5,
- remove: false,
- useDisplay: false
+var btn = new Ext.Button({
+ text: 'Say Hi',
+ renderTo: Ext.getBody()
});
-</code></pre>
- * @param {Object} options (optional) Object literal with any of the Fx config options
- * @return {Ext.Element} The Element
- */
- puff : function(o){
- o = getObject(o);
- var me = this,
- dom = me.dom,
- st = dom.style,
- width,
- height,
- r;
- me.queueFx(o, function(){
- width = fly(dom).getWidth();
- height = fly(dom).getHeight();
- fly(dom).clearOpacity();
- fly(dom).show();
+// 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);
+ };
+ },
- // restore values after effect
- r = fly(dom).getFxRestore();
-
- function after(){
- o.useDisplay ? fly(dom).setDisplayed(FALSE) : fly(dom).hide();
- fly(dom).clearOpacity();
- fly(dom).setPositioning(r.pos);
- st.width = r.width;
- st.height = r.height;
- st.fontSize = '';
- fly(dom).afterFx(o);
- }
+ /**
+ * 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);
+}
- arguments.callee.anim = fly(dom).fxanim({
- width : {to : fly(dom).adjustWidth(width * 2)},
- height : {to : fly(dom).adjustHeight(height * 2)},
- points : {by : [-width * .5, -height * .5]},
- opacity : {to : 0},
- fontSize: {to : 200, unit: "%"}
- },
- o,
- MOTION,
- .5,
- EASEOUT,
- after);
- });
- return me;
+// 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;
},
+
/**
- * Blinks the element as if it was clicked and then collapses on its center (similar to switching off a television).
- * When the effect is completed, the element will be hidden (visibility = 'hidden') but block elements will still
- * take up space in the document. The element must be removed from the DOM using the 'remove' config option if desired.
- * Usage:
- *<pre><code>
-// default
-el.switchOff();
+ * 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:
+ *
-// all config options shown with default values
-el.switchOff({
- easing: 'easeIn',
- duration: .3,
- remove: false,
- useDisplay: false
+var sayHi = function(name){
+ alert('Hi, ' + name);
+}
+
+sayHi('Fred'); // alerts "Hi, Fred"
+
+var sayGoodbye = Ext.createSequence(sayHi, function(name){
+ alert('Bye, ' + name);
});
-</code></pre>
- * @param {Object} options (optional) Object literal with any of the Fx config options
- * @return {Ext.Element} The Element
+
+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
*/
- switchOff : function(o){
- o = getObject(o);
- var me = this,
- dom = me.dom,
- st = dom.style,
- r;
+ 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;
+ };
+ }
+ }
+};
- me.queueFx(o, function(){
- fly(dom).clearOpacity();
- fly(dom).clip();
+/**
+ * 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
+ */
- // restore values after effect
- r = fly(dom).getFxRestore();
-
- function after(){
- o.useDisplay ? fly(dom).setDisplayed(FALSE) : fly(dom).hide();
- fly(dom).clearOpacity();
- fly(dom).setPositioning(r.pos);
- st.width = r.width;
- st.height = r.height;
- fly(dom).afterFx(o);
+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 defer
+ */
+
+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 defer
+ */
+
+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 defer
+ */
+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;
+ }
+ }
};
- fly(dom).fxanim({opacity : {to : 0.3}},
- NULL,
- NULL,
- .1,
- NULL,
- function(){
- fly(dom).clearOpacity();
- (function(){
- fly(dom).fxanim({
- height : {to : 1},
- points : {by : [0, fly(dom).getHeight() * .5]}
- },
- o,
- MOTION,
- 0.3,
- 'easeIn',
- after);
- }).defer(100);
- });
- });
- return me;
- },
+ this[method] = function(){
+ var args = Array.prototype.slice.call(arguments, 0),
+ b;
+ returnValue = v = undefined;
+ cancel = false;
- /**
- * Highlights the Element by setting a color (applies to the background-color by default, but can be
- * changed using the "attr" config option) and then fading back to the original color. If no original
- * color is available, you should provide the "endColor" config option which will be cleared after the animation.
- * Usage:
-<pre><code>
-// default: highlight background to yellow
-el.highlight();
+ 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;
+ }
+ }
-// custom: highlight foreground text to blue for 2 seconds
-el.highlight("0000ff", { attr: 'color', duration: 2 });
+ if((v = e.originalFn.apply(obj, args)) !== undefined){
+ returnValue = v;
+ }
-// common config options shown with default values
-el.highlight("ffff9c", {
- attr: "background-color", //can be any valid CSS property (attribute) that supports a color value
- endColor: (current color) or "ffffff",
- easing: 'easeIn',
- duration: 1
-});
-</code></pre>
- * @param {String} color (optional) The highlight color. Should be a 6 char hex color without the leading # (defaults to yellow: 'ffff9c')
- * @param {Object} options (optional) Object literal with any of the Fx config options
- * @return {Ext.Element} The Element
- */
- highlight : function(color, o){
- o = getObject(o);
- var me = this,
- dom = me.dom,
- attr = o.attr || "backgroundColor",
- a = {},
- restore;
+ 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;
+ }
- me.queueFx(o, function(){
- fly(dom).clearOpacity();
- fly(dom).show();
+ 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
+ });
+ },
- function after(){
- dom.style[attr] = restore;
- fly(dom).afterFx(o);
- }
- restore = dom.style[attr];
- a[attr] = {from: color || "ffff9c", to: o.endColor || fly(dom).getColor(attr) || "ffffff"};
- arguments.callee.anim = fly(dom).fxanim(a,
- o,
- 'color',
- 1,
- 'easeIn',
- after);
- });
- return me;
- },
+ // adds a 'sequence' called after the original method
+ afterMethod : function(method, fn, scope){
+ getMethodEvent.call(this, method).after.push({
+ fn: fn,
+ scope: scope
+ });
+ },
- /**
- * Shows a ripple of exploding, attenuating borders to draw attention to an Element.
- * Usage:
-<pre><code>
-// default: a single light blue ripple
-el.frame();
+ 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;
+ }
+ }
+ },
-// custom: 3 red ripples lasting 3 seconds total
-el.frame("ff0000", 3, { duration: 3 });
+ /**
+ * 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);
+ }
+ },
-// common config options shown with default values
-el.frame("C3DAF9", 1, {
- duration: 1 //duration of each individual ripple.
- // Note: Easing is not configurable and will be ignored if included
+ /**
+ * <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} color (optional) The color of the border. Should be a 6 char hex color without the leading # (defaults to light blue: 'C3DAF9').
- * @param {Number} count (optional) The number of ripples to display (defaults to 1)
- * @param {Object} options (optional) Object literal with any of the Fx config options
- * @return {Ext.Element} The Element
- */
- frame : function(color, count, o){
- o = getObject(o);
- var me = this,
- dom = me.dom,
- proxy,
- active;
+ * @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);
+};
- me.queueFx(o, function(){
- color = color || '#C3DAF9';
- if(color.length == 6){
- color = '#' + color;
- }
- count = count || 1;
- fly(dom).show();
- var xy = fly(dom).getXY(),
- b = {x: xy[0], y: xy[1], 0: xy[0], 1: xy[1], width: dom.offsetWidth, height: dom.offsetHeight},
- queue = function(){
- proxy = fly(document.body || document.documentElement).createChild({
- style:{
- position : ABSOLUTE,
- 'z-index': 35000, // yee haw
- border : '0px solid ' + color
- }
- });
- return proxy.queueFx({}, animFn);
- };
-
-
- arguments.callee.anim = {
- isAnimated: true,
- stop: function() {
- count = 0;
- proxy.stopFx();
- }
- };
-
- function animFn(){
- var scale = Ext.isBorderBox ? 2 : 1;
- active = proxy.anim({
- top : {from : b.y, to : b.y - 20},
- left : {from : b.x, to : b.x - 20},
- borderWidth : {from : 0, to : 10},
- opacity : {from : 1, to : 0},
- height : {from : b.height, to : b.height + 20 * scale},
- width : {from : b.width, to : b.width + 20 * scale}
- },{
- duration: o.duration || 1,
- callback: function() {
- proxy.remove();
- --count > 0 ? queue() : fly(dom).afterFx(o);
- }
- });
- arguments.callee.anim = {
- isAnimated: true,
- stop: function(){
- active.stop();
- }
- };
- };
- queue();
- });
- return me;
- },
+/**
+ * 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)$/,
+ 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);
- /**
- * Creates a pause before any subsequent queued effects begin. If there are
- * no effects queued after the pause it will have no effect.
- * Usage:
-<pre><code>
-el.pause(1);
-</code></pre>
- * @param {Number} seconds The length of time to pause (in seconds)
- * @return {Ext.Element} The Element
- */
- pause : function(seconds){
- var dom = this.dom,
- t;
+ return {
+ // private
+ doResizeEvent: function(){
+ var h = D.getViewHeight(),
+ w = D.getViewWidth();
- this.queueFx({}, function(){
- t = setTimeout(function(){
- fly(dom).afterFx({});
- }, seconds * 1000);
- arguments.callee.anim = {
- isAnimated: true,
- stop: function(){
- clearTimeout(t);
- fly(dom).afterFx({});
- }
- };
- });
- return this;
- },
+ //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);
+ }
+ },
- /**
- * Fade an element in (from transparent to opaque). The ending opacity can be specified
- * using the <tt>{@link #endOpacity}</tt> config option.
- * Usage:
-<pre><code>
-// default: fade in from opacity 0 to 100%
-el.fadeIn();
+ /**
+ * 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);
+ },
-// custom: fade in from opacity 0 to 75% over 2 seconds
-el.fadeIn({ endOpacity: .75, duration: 2});
+ // exposed only to allow manual firing
+ fireWindowResize : function(){
+ if(resizeEvent){
+ resizeTask.delay(100);
+ }
+ },
-// common config options shown with default values
-el.fadeIn({
- endOpacity: 1, //can be any value between 0 and 1 (e.g. .5)
- easing: 'easeOut',
- duration: .5
-});
-</code></pre>
- * @param {Object} options (optional) Object literal with any of the Fx config options
- * @return {Ext.Element} The Element
- */
- fadeIn : function(o){
- o = getObject(o);
- var me = this,
- dom = me.dom,
- to = o.endOpacity || 1;
-
- me.queueFx(o, function(){
- fly(dom).setOpacity(0);
- fly(dom).fixDisplay();
- dom.style.visibility = VISIBLE;
- arguments.callee.anim = fly(dom).fxanim({opacity:{to:to}},
- o, NULL, .5, EASEOUT, function(){
- if(to == 1){
- fly(dom).clearOpacity();
- }
- fly(dom).afterFx(o);
- });
- });
- return me;
- },
+ /**
+ * 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);
+ },
- /**
- * Fade an element out (from opaque to transparent). The ending opacity can be specified
- * using the <tt>{@link #endOpacity}</tt> config option. Note that IE may require
- * <tt>{@link #useDisplay}:true</tt> in order to redisplay correctly.
- * Usage:
-<pre><code>
-// default: fade out from the element's current opacity to 0
-el.fadeOut();
+ /**
+ * 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);
+ }
+ },
-// custom: fade out from the element's current opacity to 25% over 2 seconds
-el.fadeOut({ endOpacity: .25, duration: 2});
+ // private
+ fireResize : function(){
+ if(resizeEvent){
+ resizeEvent.fire(D.getViewWidth(), D.getViewHeight());
+ }
+ },
-// common config options shown with default values
-el.fadeOut({
- endOpacity: 0, //can be any value between 0 and 1 (e.g. .5)
- easing: 'easeOut',
- duration: .5,
- remove: false,
- useDisplay: false
-});
-</code></pre>
- * @param {Object} options (optional) Object literal with any of the Fx config options
- * @return {Ext.Element} The Element
- */
- fadeOut : function(o){
- o = getObject(o);
- var me = this,
- dom = me.dom,
- style = dom.style,
- to = o.endOpacity || 0;
-
- me.queueFx(o, function(){
- arguments.callee.anim = fly(dom).fxanim({
- opacity : {to : to}},
- o,
- NULL,
- .5,
- EASEOUT,
- function(){
- if(to == 0){
- Ext.Element.data(dom, 'visibilityMode') == Ext.Element.DISPLAY || o.useDisplay ?
- style.display = "none" :
- style.visibility = HIDDEN;
-
- fly(dom).clearOpacity();
- }
- fly(dom).afterFx(o);
- });
- });
- return me;
- },
+ /**
+ * The frequency, in milliseconds, to check for text resize events (defaults to 50)
+ */
+ textResizeInterval : 50,
- /**
- * Animates the transition of an element's dimensions from a starting height/width
- * to an ending height/width. This method is a convenience implementation of {@link shift}.
- * Usage:
-<pre><code>
-// change height and width to 100x100 pixels
-el.scale(100, 100);
+ /**
+ * 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';
+ },
-// common config options shown with default values. The height and width will default to
-// the element's existing values if passed as null.
-el.scale(
- [element's width],
- [element's height], {
- easing: 'easeOut',
- duration: .35
- }
-);
-</code></pre>
- * @param {Number} width The new width (pass undefined to keep the original width)
- * @param {Number} height The new height (pass undefined to keep the original height)
- * @param {Object} options (optional) Object literal with any of the Fx config options
- * @return {Ext.Element} The Element
- */
- scale : function(w, h, o){
- this.shift(Ext.apply({}, o, {
- width: w,
- height: h
- }));
- return this;
- },
+ // protected for use inside the framework
+ // detects whether we should use keydown or keypress based on the browser.
+ useKeydown: useKeydown
+ };
+}());
- /**
- * Animates the transition of any combination of an element's dimensions, xy position and/or opacity.
- * Any of these properties not specified in the config object will not be changed. This effect
- * requires that at least one new dimension, position or opacity setting must be passed in on
- * the config object in order for the function to have any effect.
- * Usage:
-<pre><code>
-// slide the element horizontally to x position 200 while changing the height and opacity
-el.shift({ x: 200, height: 50, opacity: .8 });
+Ext.EventManager.on = Ext.EventManager.addListener;
-// common config options shown with default values.
-el.shift({
- width: [element's width],
- height: [element's height],
- x: [element's x position],
- y: [element's y position],
- opacity: [element's opacity],
- easing: 'easeOut',
- duration: .35
-});
-</code></pre>
- * @param {Object} options Object literal with any of the Fx config options
- * @return {Ext.Element} The Element
- */
- shift : function(o){
- o = getObject(o);
- var dom = this.dom,
- a = {};
-
- this.queueFx(o, function(){
- for (var prop in o) {
- if (o[prop] != UNDEFINED) {
- a[prop] = {to : o[prop]};
- }
- }
-
- a.width ? a.width.to = fly(dom).adjustWidth(o.width) : a;
- a.height ? a.height.to = fly(dom).adjustWidth(o.height) : a;
-
- if (a.x || a.y || a.xy) {
- a.points = a.xy ||
- {to : [ a.x ? a.x.to : fly(dom).getX(),
- a.y ? a.y.to : fly(dom).getY()]};
- }
- arguments.callee.anim = fly(dom).fxanim(a,
- o,
- MOTION,
- .35,
- EASEOUT,
- function(){
- fly(dom).afterFx(o);
- });
- });
- return this;
- },
+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;
+ },
- /**
- * Slides the element while fading it out of view. An anchor point can be optionally passed to set the
- * ending point of the effect.
- * Usage:
- *<pre><code>
-// default: slide the element downward while fading out
-el.ghost();
+ 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
+ },
-// custom: slide the element out to the right with a 2-second duration
-el.ghost('r', { duration: 2 });
+ getPoint : function(){
+ return new Ext.lib.Point(this.xy[0], this.xy[1]);
+ },
-// common config options shown with default values
-el.ghost('b', {
- easing: 'easeOut',
- duration: .5,
- remove: false,
- useDisplay: false
-});
-</code></pre>
- * @param {String} anchor (optional) One of the valid Fx anchor positions (defaults to bottom: 'b')
- * @param {Object} options (optional) Object literal with any of the Fx config options
- * @return {Ext.Element} The Element
+ /**
+ * 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
*/
- ghost : function(anchor, o){
- o = getObject(o);
- var me = this,
- dom = me.dom,
- st = dom.style,
- a = {opacity: {to: 0}, points: {}},
- pt = a.points,
- r,
- w,
- h;
-
- anchor = anchor || "b";
-
- me.queueFx(o, function(){
- // restore values after effect
- r = fly(dom).getFxRestore();
- w = fly(dom).getWidth();
- h = fly(dom).getHeight();
-
- function after(){
- o.useDisplay ? fly(dom).setDisplayed(FALSE) : fly(dom).hide();
- fly(dom).clearOpacity();
- fly(dom).setPositioning(r.pos);
- st.width = r.width;
- st.height = r.height;
- fly(dom).afterFx(o);
+ swallowEvent : function(eventName, preventDefault) {
+ var me = this;
+ function fn(e) {
+ e.stopPropagation();
+ if (preventDefault) {
+ e.preventDefault();
}
-
- pt.by = fly(dom).switchStatements(anchor.toLowerCase(), function(v1,v2){ return [v1, v2];}, {
- t : [0, -h],
- l : [-w, 0],
- r : [w, 0],
- b : [0, h],
- tl : [-w, -h],
- bl : [-w, h],
- br : [w, h],
- tr : [w, -h]
+ }
+
+ if (Ext.isArray(eventName)) {
+ Ext.each(eventName, function(e) {
+ me.on(e, fn);
});
-
- arguments.callee.anim = fly(dom).fxanim(a,
- o,
- MOTION,
- .5,
- EASEOUT, after);
- });
+ return me;
+ }
+ me.on(eventName, fn);
return me;
},
/**
- * Ensures that all effects queued after syncFx is called on the element are
- * run concurrently. This is the opposite of {@link #sequenceFx}.
- * @return {Ext.Element} The Element
+ * 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
*/
- syncFx : function(){
- var me = this;
- me.fxDefaults = Ext.apply(me.fxDefaults || {}, {
- block : FALSE,
- concurrent : TRUE,
- stopFx : FALSE
+ relayEvent : function(eventName, observable) {
+ this.on(eventName, function(e) {
+ observable.fireEvent(eventName, e);
});
- return me;
},
/**
- * Ensures that all effects queued after sequenceFx is called on the element are
- * run in sequence. This is the opposite of {@link #syncFx}.
- * @return {Ext.Element} The Element
+ * 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.
*/
- sequenceFx : function(){
- var me = this;
- me.fxDefaults = Ext.apply(me.fxDefaults || {}, {
- block : FALSE,
- concurrent : FALSE,
- stopFx : FALSE
- });
- return me;
- },
+ clean : function(forceReclean) {
+ var me = this,
+ dom = me.dom,
+ n = dom.firstChild,
+ ni = -1;
- /* @private */
- nextFx : function(){
- var ef = getQueue(this.dom.id)[0];
- if(ef){
- ef.call(this);
+ if (Ext.Element.data(dom, 'isCleaned') && forceReclean !== true) {
+ return me;
}
- },
- /**
- * Returns true if the element has any effects actively running or queued, else returns false.
- * @return {Boolean} True if element has active effects, else false
- */
- hasActiveFx : function(){
- return getQueue(this.dom.id)[0];
+ 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;
},
/**
- * Stops any running effects and clears the element's internal effects queue if it contains
- * any additional effects that haven't started yet.
- * @return {Ext.Element} The Element
+ * 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
*/
- stopFx : function(finish){
- var me = this,
- id = me.dom.id;
- if(me.hasActiveFx()){
- var cur = getQueue(id)[0];
- if(cur && cur.anim){
- if(cur.anim.isAnimated){
- setQueue(id, [cur]); //clear
- cur.anim.stop(finish !== undefined ? finish : TRUE);
- }else{
- setQueue(id, []);
- }
- }
- }
- return me;
+ load : function() {
+ var updateManager = this.getUpdater();
+ updateManager.update.apply(updateManager, arguments);
+
+ return this;
},
- /* @private */
- beforeFx : function(o){
- if(this.hasActiveFx() && !o.concurrent){
- if(o.stopFx){
- this.stopFx();
- return TRUE;
- }
- return FALSE;
- }
- return TRUE;
+ /**
+ * Gets this element's {@link Ext.Updater Updater}
+ * @return {Ext.Updater} The Updater
+ */
+ getUpdater : function() {
+ return this.updateManager || (this.updateManager = new Ext.Updater(this));
},
/**
- * Returns true if the element is currently blocking so that no other effect can be queued
- * until this effect is finished, else returns false if blocking is not set. This is commonly
- * used to ensure that an effect initiated by a user action runs to completion prior to the
- * same effect being restarted (e.g., firing only one effect even if the user clicks several times).
- * @return {Boolean} True if blocking, else false
+ * 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
*/
- hasFxBlock : function(){
- var q = getQueue(this.dom.id);
- return q && q[0] && q[0].block;
- },
+ update : function(html, loadScripts, callback) {
+ if (!this.dom) {
+ return this;
+ }
+ html = html || "";
- /* @private */
- queueFx : function(o, fn){
- var me = fly(this.dom);
- if(!me.hasFxBlock()){
- Ext.applyIf(o, me.fxDefaults);
- if(!o.concurrent){
- var run = me.beforeFx(o);
- fn.block = o.block;
- getQueue(me.dom.id).push(fn);
- if(run){
- me.nextFx();
- }
- }else{
- fn.call(me);
+ if (loadScripts !== true) {
+ this.dom.innerHTML = html;
+ if (typeof callback == 'function') {
+ callback();
}
+ return this;
}
- return me;
- },
- /* @private */
- fxWrap : function(pos, o, vis){
- var dom = this.dom,
- wrap,
- wrapXY;
- if(!o.wrap || !(wrap = Ext.getDom(o.wrap))){
- if(o.fixPosition){
- wrapXY = fly(dom).getXY();
+ 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]);
+ }
+ }
}
- var div = document.createElement("div");
- div.style.visibility = vis;
- wrap = dom.parentNode.insertBefore(div, dom);
- fly(wrap).setPositioning(pos);
- if(fly(wrap).isStyle(POSITION, "static")){
- fly(wrap).position("relative");
+
+ el = DOC.getElementById(id);
+ if (el) {
+ Ext.removeNode(el);
}
- fly(dom).clearPositioning('auto');
- fly(wrap).clip();
- wrap.appendChild(dom);
- if(wrapXY){
- fly(wrap).setXY(wrapXY);
+
+ if (typeof callback == 'function') {
+ callback();
}
- }
- return wrap;
+ });
+ dom.innerHTML = html.replace(/(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)/ig, "");
+ return this;
},
- /* @private */
- fxUnwrap : function(wrap, pos, o){
- var dom = this.dom;
- fly(dom).clearPositioning();
- fly(dom).setPositioning(pos);
- if(!o.wrap){
- var pn = fly(wrap).dom.parentNode;
- pn.insertBefore(dom, wrap);
- fly(wrap).remove();
- }
+ // inherit docs, overridden so we can add removeAnchor
+ removeAllListeners : function() {
+ this.removeAnchor();
+ Ext.EventManager.removeAll(this.dom);
+ return this;
},
- /* @private */
- getFxRestore : function(){
- var st = this.dom.style;
- return {pos: this.getPositioning(), width: st.width, height : st.height};
- },
+ /**
+ * 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};
- /* @private */
- afterFx : function(o){
- var dom = this.dom,
- id = dom.id;
- if(o.afterStyle){
- fly(dom).setStyle(o.afterStyle);
- }
- if(o.afterCls){
- fly(dom).addClass(o.afterCls);
- }
- if(o.remove == TRUE){
- fly(dom).remove();
- }
- if(o.callback){
- o.callback.call(o.scope, fly(dom));
- }
- if(!o.concurrent){
- getQueue(id).shift();
- fly(dom).nextFx();
- }
- },
+ var me = this,
+ proxy = renderTo ? Ext.DomHelper.append(renderTo, config, true) :
+ Ext.DomHelper.insertBefore(me.dom, config, true);
- /* @private */
- fxanim : function(args, opt, animType, defaultDur, defaultEase, cb){
- animType = animType || 'run';
- opt = opt || {};
- var anim = Ext.lib.Anim[animType](
- this.dom,
- args,
- (opt.duration || defaultDur) || .35,
- (opt.easing || defaultEase) || EASEOUT,
- cb,
- this
- );
- opt.anim = anim;
- return anim;
+ if (matchBox && me.setBox && me.getBox) { // check to make sure Element.position.js is loaded
+ proxy.setBox(me.getBox());
+ }
+ return proxy;
}
-};
-
-// backwards compat
-Ext.Fx.resize = Ext.Fx.scale;
+});
-//When included, Ext.Fx is automatically applied to Element so that all basic
-//effects are available directly via the Element API
-Ext.Element.addMethods(Ext.Fx);
-})();
+Ext.Element.prototype.getUpdateManager = Ext.Element.prototype.getUpdater;
/**
- * @class 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>
- * Example:<pre><code>
-var els = Ext.select("#some-el div.some-class");
-// or select directly from an existing element
-var el = Ext.get('some-el');
-el.select('div.some-class');
-
-els.setWidth(100); // all elements become 100 width
-els.hide(true); // all elements fade out and hide
-// or
-els.setWidth(100).hide(true);
-</code>
+ * @class Ext.Element
*/
-Ext.CompositeElementLite = function(els, root){
+Ext.Element.addMethods({
/**
- * <p>The Array of DOM elements which this CompositeElement encapsulates. Read-only.</p>
- * <p>This will not <i>usually</i> be accessed in developers' code, but developers wishing
- * to augment the capabilities of the CompositeElementLite class may use it when adding
- * methods to the class.</p>
- * <p>For example to add the <code>nextAll</code> method to the class to <b>add</b> all
- * following siblings of selected elements, the code would be</p><code><pre>
-Ext.override(Ext.CompositeElementLite, {
- nextAll: function() {
- var els = this.elements, i, l = els.length, n, r = [], ri = -1;
-
-// Loop through all elements in this Composite, accumulating
-// an Array of all siblings.
- for (i = 0; i < l; i++) {
- for (n = els[i].nextSibling; n; n = n.nextSibling) {
- r[++ri] = n;
- }
- }
-
-// Add all found siblings to this Composite
- return this.add(r);
- }
-});</pre></code>
- * @type Array
- * @property elements
+ * 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
*/
- this.elements = [];
- this.add(els, root);
- this.el = new Ext.Element.Flyweight();
-};
-
-Ext.CompositeElementLite.prototype = {
- isComposite: true,
-
- // private
- getElement : function(el){
- // Set the shared flyweight dom property to the current element
- var e = this.el;
- e.dom = el;
- e.id = el.id;
- return e;
- },
-
- // private
- transformElement : function(el){
- return Ext.getDom(el);
+ 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];
},
/**
- * Returns the number of elements in this Composite.
- * @return Number
+ * 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 The function to call after the animation finishes
+ * @return {Ext.Element} this
*/
- getCount : function(){
- return this.elements.length;
+ 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;
},
+
/**
- * Adds elements to this Composite object.
- * @param {Mixed} els Either an Array of DOM elements to add, or another Composite object who's elements should be added.
- * @return {CompositeElement} This Composite object.
+ * Remove any anchor to this element. See {@link #anchorTo}.
+ * @return {Ext.Element} this
*/
- add : function(els, root){
+ removeAnchor : function(){
var me = this,
- elements = me.elements;
- if(!els){
- return this;
- }
- if(typeof els == "string"){
- els = Ext.Element.selectorFunction(els, root);
- }else if(els.isComposite){
- els = els.elements;
- }else if(!Ext.isIterable(els)){
- els = [els];
- }
-
- for(var i = 0, len = els.length; i < len; ++i){
- elements.push(me.transformElement(els[i]));
+ 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;
},
-
- invoke : function(fn, args){
- var me = this,
- els = me.elements,
- len = els.length,
- e,
- i;
-
- for(i = 0; i < len; i++) {
- e = els[i];
- if(e){
- Ext.Element.prototype[fn].apply(me.getElement(e), args);
+
+ // 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 me;
+ return anchor;
},
+
/**
- * Returns a flyweight Element of the dom element object at the specified index
- * @param {Number} index
- * @return {Ext.Element}
+ * 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]
*/
- item : function(index){
+ 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,
- el = me.elements[index],
- out = null;
-
- if(el){
- out = me.getElement(el);
+ 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;
}
- return out;
- },
+
+ p1 = m[1];
+ p2 = m[2];
+ c = !!m[3];
- // fixes scope with flyweight
- addListener : function(eventName, handler, scope, opt){
- var els = this.elements,
- len = els.length,
- i, e;
+ //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);
- for(i = 0; i<len; i++) {
- e = els[i];
- if(e) {
- Ext.EventManager.on(e, eventName, handler, scope || e, opt);
- }
- }
- return this;
- },
- /**
- * <p>Calls the passed function for each element in this composite.</p>
- * @param {Function} fn The function to call. The function is passed the following parameters:<ul>
- * <li><b>el</b> : Element<div class="sub-desc">The current Element in the iteration.
- * <b>This is the flyweight (shared) Ext.Element instance, so if you require a
- * a reference to the dom node, use el.dom.</b></div></li>
- * <li><b>c</b> : Composite<div class="sub-desc">This Composite object.</div></li>
- * <li><b>idx</b> : Number<div class="sub-desc">The zero-based index in the iteration.</div></li>
- * </ul>
- * @param {Object} scope (optional) The scope (<i>this</i> reference) in which the function is executed. (defaults to the Element)
- * @return {CompositeElement} this
- */
- each : function(fn, scope){
- var me = this,
- els = me.elements,
- len = els.length,
- i, e;
+ x = a2[0] - a1[0] + o[0];
+ y = a2[1] - a1[1] + o[1];
- for(i = 0; i<len; i++) {
- e = els[i];
- if(e){
- e = this.getElement(e);
- if(fn.call(scope || e, e, me, i) === false){
- break;
- }
+ 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 me;
- },
-
- /**
- * Clears this Composite and adds the elements passed.
- * @param {Mixed} els Either an array of DOM elements, or another Composite from which to fill this Composite.
- * @return {CompositeElement} this
- */
- fill : function(els){
- var me = this;
- me.elements = [];
- me.add(els);
- return me;
+ return [x,y];
},
/**
- * Filters this composite to only elements that match the passed selector.
- * @param {String/Function} selector A string CSS selector or a comparison function.
- * The comparison function will be called with the following arguments:<ul>
- * <li><code>el</code> : Ext.Element<div class="sub-desc">The current DOM element.</div></li>
- * <li><code>index</code> : Number<div class="sub-desc">The current index within the collection.</div></li>
+ * 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>
- * @return {CompositeElement} this
- */
- filter : function(selector){
- var els = [],
- me = this,
- elements = me.elements,
- fn = Ext.isFunction(selector) ? selector
- : function(el){
- return el.is(selector);
- };
+ * 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?");
- me.each(function(el, self, i){
- if(fn(el, i) !== false){
- els[els.length] = me.transformElement(el);
- }
- });
- me.elements = els;
- return me;
- },
+// align the bottom right corner of el with the center left edge of other-el
+el.alignTo("other-el", "br-l?");
- /**
- * Find the index of the passed element within the composite collection.
- * @param el {Mixed} The id of an element, or an Ext.Element, or an HtmlElement to find within the composite collection.
- * @return Number The index of the passed Ext.Element in the composite collection, or -1 if not found.
+// 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
*/
- indexOf : function(el){
- return this.elements.indexOf(this.transformElement(el));
+ 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;
},
- /**
- * Replaces the specified element with the passed element.
- * @param {Mixed} el The id of an element, the Element itself, the index of the element in this composite
- * to replace.
- * @param {Mixed} replacement The id of an element or the Element itself.
- * @param {Boolean} domReplace (Optional) True to remove and replace the element in the document too.
- * @return {CompositeElement} this
- */
- replaceElement : function(el, replacement, domReplace){
- var index = !isNaN(el) ? el : this.indexOf(el),
- d;
- if(index > -1){
- replacement = Ext.getDom(replacement);
- if(domReplace){
- d = this.elements[index];
- d.parentNode.insertBefore(replacement, d);
- Ext.removeNode(d);
+ // 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];
+ }
}
- this.elements.splice(index, 1, replacement);
- }
- return this;
- },
- /**
- * Removes all elements.
- */
- clear : function(){
- this.elements = [];
- }
-};
+ var s = el.getScroll();
-Ext.CompositeElementLite.prototype.on = Ext.CompositeElementLite.prototype.addListener;
+ vx += offsets.left + s.left;
+ vy += offsets.top + s.top;
-(function(){
-var fnName,
- ElProto = Ext.Element.prototype,
- CelProto = Ext.CompositeElementLite.prototype;
-
-for(fnName in ElProto){
- if(Ext.isFunction(ElProto[fnName])){
- (function(fnName){
- CelProto[fnName] = CelProto[fnName] || function(){
- return this.invoke(fnName, arguments);
- };
- }).call(CelProto, fnName);
+ 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;
-if(Ext.DomQuery){
- Ext.Element.selectorFunction = Ext.DomQuery.select;
-}
+ // only move it if it needs it
+ var moved = false;
-/**
- * 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 {HTMLElement/String} root (optional) The root element of the query or id of the root
- * @return {CompositeElementLite/CompositeElement}
- * @member Ext.Element
- * @method select
- */
-Ext.Element.select = function(selector, 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 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 {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.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;
- },
+ // 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});
- /**
- * Returns the first Element
- * @return {Ext.Element}
- */
- first : function(){
- return this.item(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;
+// },
- /**
- * Returns the last Element
- * @return {Ext.Element}
- */
- last : function(){
- return this.item(this.getCount()-1);
+ // private, used internally
+ getConstrainOffset : function(){
+ return 0;
},
-
+
/**
- * 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;
+ * 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');
},
/**
- * 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
+ * Centers the Element in either the viewport, or another Element.
+ * @param {Mixed} centerIn (optional) The element in which to center the element.
*/
- 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;
- }
+ center : function(centerIn){
+ return this.alignTo(centerIn || document, 'c-c');
+ }
});
/**
- * @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>
+ * @class Ext.Element
*/
-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
- */
-
- /**
- * Returns the Element object at the specified index
- * @param {Number} index
- * @return {Ext.Element}
- */
-
+Ext.Element.addMethods({
/**
- * 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
+ * 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
*/
-});
-
-/**
- * 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
- */
-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";
+ select : function(selector, unique){
+ return Ext.Element.select(selector, unique, this.dom);
}
-
- 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
+});/**
+ * @class Ext.Element
*/
-Ext.select = Ext.Element.select;(function(){
- var BEFOREREQUEST = "beforerequest",
- REQUESTCOMPLETE = "requestcomplete",
- REQUESTEXCEPTION = "requestexception",
- UNDEFINED = undefined,
- LOAD = 'load',
- POST = 'POST',
- GET = 'GET',
- WINDOW = window;
-
- /**
- * @class Ext.data.Connection
- * @extends Ext.util.Observable
- * <p>The class encapsulates a connection to the page's originating domain, allowing requests to be made
- * either to a configured URL, or to a URL specified at request time.</p>
- * <p>Requests made by this class are asynchronous, and will return immediately. No data from
- * the server will be available to the statement immediately following the {@link #request} call.
- * To process returned data, use a
- * <a href="#request-option-success" ext:member="request-option-success" ext:cls="Ext.data.Connection">success callback</a>
- * in the request options object,
- * or an {@link #requestcomplete event listener}.</p>
- * <p><h3>File Uploads</h3><a href="#request-option-isUpload" ext:member="request-option-isUpload" ext:cls="Ext.data.Connection">File uploads</a> 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 <tt><form></tt> 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 <tt><iframe></tt> which is inserted into the document
- * but removed after the return data has been gathered.</p>
- * <p>The server response is parsed by the browser to create the document for the IFRAME. If the
- * server is using JSON to send the return object, then the
- * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17">Content-Type</a> header
- * must be set to "text/html" in order to tell the browser to insert the text unchanged into the document body.</p>
- * <p>Characters which are significant to an HTML parser must be sent as HTML entities, so encode
- * "<" as "&lt;", "&" as "&amp;" etc.</p>
- * <p>The response text is retrieved from the document, and a fake XMLHttpRequest object
- * is created containing a <tt>responseText</tt> property in order to conform to the
- * requirements of event handlers and callbacks.</p>
- * <p>Be aware that file upload packets are sent with the content type <a href="http://www.faqs.org/rfcs/rfc2388.html">multipart/form</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>
- * @constructor
- * @param {Object} config a configuration object.
- */
- Ext.data.Connection = function(config){
- Ext.apply(this, config);
- this.addEvents(
- /**
- * @event beforerequest
- * Fires before a network request is made to retrieve a data object.
- * @param {Connection} conn This Connection object.
- * @param {Object} options The options config object passed to the {@link #request} method.
- */
- BEFOREREQUEST,
- /**
- * @event requestcomplete
- * Fires if the request was successfully completed.
- * @param {Connection} conn This Connection object.
- * @param {Object} response The XHR object containing the response data.
- * See <a href="http://www.w3.org/TR/XMLHttpRequest/">The XMLHttpRequest Object</a>
- * for details.
- * @param {Object} options The options config object passed to the {@link #request} method.
- */
- REQUESTCOMPLETE,
- /**
- * @event requestexception
- * Fires if an error HTTP status was returned from the server.
- * See <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html">HTTP Status Code Definitions</a>
- * for details of HTTP status codes.
- * @param {Connection} conn This Connection object.
- * @param {Object} response The XHR object containing the response data.
- * See <a href="http://www.w3.org/TR/XMLHttpRequest/">The XMLHttpRequest Object</a>
- * for details.
- * @param {Object} options The options config object passed to the {@link #request} method.
- */
- REQUESTEXCEPTION
- );
- Ext.data.Connection.superclass.constructor.call(this);
+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
+ */
- Ext.extend(Ext.data.Connection, Ext.util.Observable, {
- /**
- * @cfg {String} url (Optional) <p>The default URL to be used for requests to the server. Defaults to undefined.</p>
- * <p>The <code>url</code> config may be a function which <i>returns</i> the URL to use for the Ajax request. The scope
- * (<code><b>this</b></code> reference) of the function is the <code>scope</code> option passed to the {@link #request} method.</p>
- */
- /**
- * @cfg {Object} extraParams (Optional) An object containing properties which are used as
- * extra parameters to each request made by this object. (defaults to undefined)
- */
- /**
- * @cfg {Object} defaultHeaders (Optional) An object containing request headers which are added
- * to each request made by this object. (defaults to undefined)
- */
- /**
- * @cfg {String} method (Optional) The default HTTP method to be used for requests.
- * (defaults to undefined; if not set, but {@link #request} params are present, POST will be used;
- * otherwise, GET will be used.)
- */
- /**
- * @cfg {Number} timeout (Optional) The timeout in milliseconds to be used for requests. (defaults to 30000)
- */
- timeout : 30000,
- /**
- * @cfg {Boolean} autoAbort (Optional) Whether this request should abort any pending requests. (defaults to false)
- * @type Boolean
- */
- autoAbort:false,
+// 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 {
/**
- * @cfg {Boolean} disableCaching (Optional) True to add a unique cache-buster param to GET requests. (defaults to true)
- * @type Boolean
+ * 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
*/
- disableCaching: true,
+ applyStyles : function(style){
+ Ext.DomHelper.applyStyles(this.dom, style);
+ return this;
+ },
/**
- * @cfg {String} disableCachingParam (Optional) Change the parameter which is sent went disabling caching
- * through a cache buster. Defaults to '_dc'
- * @type String
+ * 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
*/
- disableCachingParam: '_dc',
+ 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;
+ },
/**
- * <p>Sends an HTTP request to a remote server.</p>
- * <p><b>Important:</b> Ajax server requests are asynchronous, and this call will
- * return before the response has been received. Process any returned data
- * in a callback function.</p>
- * <pre><code>
-Ext.Ajax.request({
- url: 'ajax_demo/sample.json',
- success: function(response, opts) {
- var obj = Ext.decode(response.responseText);
- console.dir(obj);
- },
- failure: function(response, opts) {
- console.log('server-side failure with status code ' + response.status);
- }
-});
- * </code></pre>
- * <p>To execute a callback function in the correct scope, use the <tt>scope</tt> option.</p>
- * @param {Object} options An object which may contain the following properties:<ul>
- * <li><b>url</b> : String/Function (Optional)<div class="sub-desc">The URL to
- * which to send the request, or a function to call which returns a URL string. The scope of the
- * function is specified by the <tt>scope</tt> option. Defaults to the configured
- * <tt>{@link #url}</tt>.</div></li>
- * <li><b>params</b> : Object/String/Function (Optional)<div class="sub-desc">
- * An object containing properties which are used as parameters to the
- * request, a url encoded string or a function to call to get either. The scope of the function
- * is specified by the <tt>scope</tt> option.</div></li>
- * <li><b>method</b> : String (Optional)<div class="sub-desc">The HTTP method to use
- * for the request. Defaults to the configured method, or if no method was configured,
- * "GET" if no parameters are being sent, and "POST" if parameters are being sent. Note that
- * the method name is case-sensitive and should be all caps.</div></li>
- * <li><b>callback</b> : Function (Optional)<div class="sub-desc">The
- * function to be called upon receipt of the HTTP response. The callback is
- * called regardless of success or failure and is passed the following
- * parameters:<ul>
- * <li><b>options</b> : Object<div class="sub-desc">The parameter to the request call.</div></li>
- * <li><b>success</b> : Boolean<div class="sub-desc">True if the request succeeded.</div></li>
- * <li><b>response</b> : Object<div class="sub-desc">The XMLHttpRequest object containing the response data.
- * See <a href="http://www.w3.org/TR/XMLHttpRequest/">http://www.w3.org/TR/XMLHttpRequest/</a> for details about
- * accessing elements of the response.</div></li>
- * </ul></div></li>
- * <li><a id="request-option-success"></a><b>success</b> : Function (Optional)<div class="sub-desc">The function
- * to be called upon success of the request. The callback is passed the following
- * parameters:<ul>
- * <li><b>response</b> : Object<div class="sub-desc">The XMLHttpRequest object containing the response data.</div></li>
- * <li><b>options</b> : Object<div class="sub-desc">The parameter to the request call.</div></li>
- * </ul></div></li>
- * <li><b>failure</b> : Function (Optional)<div class="sub-desc">The function
- * to be called upon failure of the request. The callback is passed the
- * following parameters:<ul>
- * <li><b>response</b> : Object<div class="sub-desc">The XMLHttpRequest object containing the response data.</div></li>
- * <li><b>options</b> : Object<div class="sub-desc">The parameter to the request call.</div></li>
- * </ul></div></li>
- * <li><b>scope</b> : Object (Optional)<div class="sub-desc">The scope in
- * which to execute the callbacks: The "this" object for the callback function. If the <tt>url</tt>, or <tt>params</tt> options were
- * specified as functions from which to draw values, then this also serves as the scope for those function calls.
- * Defaults to the browser window.</div></li>
- * <li><b>timeout</b> : Number (Optional)<div class="sub-desc">The timeout in milliseconds to be used for this request. Defaults to 30 seconds.</div></li>
- * <li><b>form</b> : Element/HTMLElement/String (Optional)<div class="sub-desc">The <tt><form></tt>
- * Element or the id of the <tt><form></tt> to pull parameters from.</div></li>
- * <li><a id="request-option-isUpload"></a><b>isUpload</b> : Boolean (Optional)<div class="sub-desc"><b>Only meaningful when used
- * with the <tt>form</tt> option</b>.
- * <p>True if the form object is a file upload (will be set automatically if the form was
- * configured with <b><tt>enctype</tt></b> "multipart/form-data").</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 <tt><form></tt> 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 <tt><iframe></tt> which is inserted into the document
- * but removed after the return data has been gathered.</p>
- * <p>The server response is parsed by the browser to create the document for the IFRAME. If the
- * server is using JSON to send the return object, then the
- * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17">Content-Type</a> header
- * must be set to "text/html" in order to tell the browser to insert the text unchanged into the document body.</p>
- * <p>The response text is retrieved from the document, and a fake XMLHttpRequest object
- * is created containing a <tt>responseText</tt> property in order to conform to the
- * requirements of event handlers and callbacks.</p>
- * <p>Be aware that file upload packets are sent with the content type <a href="http://www.faqs.org/rfcs/rfc2388.html">multipart/form</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>
- * </div></li>
- * <li><b>headers</b> : Object (Optional)<div class="sub-desc">Request
- * headers to set for the request.</div></li>
- * <li><b>xmlData</b> : Object (Optional)<div class="sub-desc">XML document
- * to use for the post. Note: This will be used instead of params for the post
- * data. Any params will be appended to the URL.</div></li>
- * <li><b>jsonData</b> : Object/String (Optional)<div class="sub-desc">JSON
- * data to use as the post. Note: This will be used instead of params for the post
- * data. Any params will be appended to the URL.</div></li>
- * <li><b>disableCaching</b> : Boolean (Optional)<div class="sub-desc">True
- * to add a unique cache-buster param to GET requests.</div></li>
- * </ul></p>
- * <p>The options object may also contain any other property which might be needed to perform
- * postprocessing in a callback because it is passed to callback functions.</p>
- * @return {Number} transactionId The id of the server transaction. This may be used
- * to cancel the request.
+ * 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}
*/
- request : function(o){
- var me = this;
- if(me.fireEvent(BEFOREREQUEST, me, o)){
- if (o.el) {
- if(!Ext.isEmpty(o.indicatorText)){
- me.indicatorText = '<div class="loading-indicator">'+o.indicatorText+"</div>";
- }
- if(me.indicatorText) {
- Ext.getDom(o.el).innerHTML = me.indicatorText;
- }
- o.success = (Ext.isFunction(o.success) ? o.success : function(){}).createInterceptor(function(response) {
- Ext.getDom(o.el).innerHTML = response.responseText;
- });
+ 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;
+ },
- var p = o.params,
- url = o.url || me.url,
- method,
- cb = {success: me.handleResponse,
- failure: me.handleFailure,
- scope: me,
- argument: {options: o},
- timeout : o.timeout || me.timeout
- },
- form,
- serForm;
+ /**
+ * 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));
+ },
- if (Ext.isFunction(p)) {
- p = p.call(o.scope||WINDOW, o);
+ /**
+ * 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;
+ },
- p = Ext.urlEncode(me.extraParams, Ext.isObject(p) ? Ext.urlEncode(p) : p);
+ /**
+ * 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;
+ },
- if (Ext.isFunction(url)) {
- url = url.call(o.scope || WINDOW, o);
- }
+ /**
+ * 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;
+ },
- if((form = Ext.getDom(o.form))){
- url = url || form.action;
- if(o.isUpload || /multipart\/form-data/i.test(form.getAttribute("enctype"))) {
- return me.doFormUpload.call(me, o, p, url);
- }
- serForm = Ext.lib.Ajax.serializeForm(form);
- p = p ? (p + '&' + serForm) : serForm;
- }
+ /**
+ * <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();
- method = o.method || me.method || ((p || o.xmlData || o.jsonData) ? POST : GET);
+ // 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.
+ */
- if(method === GET && (me.disableCaching && o.disableCaching !== false) || o.disableCaching === true){
- var dcp = o.disableCachingParam || me.disableCachingParam;
- url = Ext.urlAppend(url, dcp + '=' + (new Date().getTime()));
- }
+ getViewSize : function(){
+ var doc = document,
+ d = this.dom,
+ isDoc = (d == doc || d == doc.body);
- o.headers = Ext.apply(o.headers || {}, me.defaultHeaders || {});
+ // If the body, use Ext.lib.Dom
+ if (isDoc) {
+ var extdom = Ext.lib.Dom;
+ return {
+ width : extdom.getViewWidth(),
+ height : extdom.getViewHeight()
+ };
- if(o.autoAbort === true || me.autoAbort) {
- me.abort();
- }
+ // Else use clientHeight/clientWidth
+ } else {
+ return {
+ width : d.clientWidth,
+ height : d.clientHeight
+ };
+ }
+ },
- if((method == GET || o.xmlData || o.jsonData) && p){
- url = Ext.urlAppend(url, p);
- p = '';
+ /**
+ * <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');
}
- return (me.transId = Ext.lib.Ajax.request(method, url, cb, p, o));
- }else{
- return o.callback ? o.callback.apply(o.scope, [o,UNDEFINED,UNDEFINED]) : null;
}
+ // Use getWidth/getHeight if style not set.
+ return {width: w || me.getWidth(true), height: h || me.getHeight(true)};
},
/**
- * Determine whether this object has a request outstanding.
- * @param {Number} transactionId (Optional) defaults to the last transaction
- * @return {Boolean} True if there is an outstanding request.
+ * 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)}
*/
- isLoading : function(transId){
- return transId ? Ext.lib.Ajax.isCallInProgress(transId) : !! this.transId;
+ getSize : function(contentSize){
+ return {width: this.getWidth(contentSize), height: this.getHeight(contentSize)};
},
/**
- * Aborts any outstanding request.
- * @param {Number} transactionId (Optional) defaults to the last transaction
+ * Forces the browser to repaint this element
+ * @return {Ext.Element} this
*/
- abort : function(transId){
- if(transId || this.isLoading()){
- Ext.lib.Ajax.abort(transId || this.transId);
- }
+ repaint : function(){
+ var dom = this.dom;
+ this.addClass("x-repaint");
+ setTimeout(function(){
+ Ext.fly(dom).removeClass("x-repaint");
+ }, 1);
+ return this;
},
- // private
- handleResponse : function(response){
- this.transId = false;
- var options = response.argument.options;
- response.argument = options ? options.argument : null;
- this.fireEvent(REQUESTCOMPLETE, this, response, options);
- if(options.success){
- options.success.call(options.scope, response, options);
- }
- if(options.callback){
- options.callback.call(options.scope, options, true, response);
- }
+ /**
+ * Disables text selection for this element (normalized across browsers)
+ * @return {Ext.Element} this
+ */
+ unselectable : function(){
+ this.dom.unselectable = "on";
+ return this.swallowEvent("selectstart", true).
+ applyStyles("-moz-user-select:none;-khtml-user-select:none;").
+ addClass("x-unselectable");
},
- // private
- handleFailure : function(response, e){
- this.transId = false;
- var options = response.argument.options;
- response.argument = options ? options.argument : null;
- this.fireEvent(REQUESTEXCEPTION, this, response, options, e);
- if(options.failure){
- options.failure.call(options.scope, response, options);
- }
- if(options.callback){
- options.callback.call(options.scope, options, false, response);
+ /**
+ * 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;
+ },
- // private
- doFormUpload : function(o, ps, url){
- var id = Ext.id(),
- doc = document,
- frame = doc.createElement('iframe'),
- form = Ext.getDom(o.form),
- hiddens = [],
- hd,
- encoding = 'multipart/form-data',
- buf = {
- target: form.target,
- method: form.method,
- encoding: form.encoding,
- enctype: form.enctype,
- action: form.action
- };
+ /**
+ * 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;
+ },
- /*
- * Originally this behaviour was modified for Opera 10 to apply the secure URL after
- * the frame had been added to the document. It seems this has since been corrected in
- * Opera so the behaviour has been reverted, the URL will be set before being added.
- */
- Ext.fly(frame).set({
- id: id,
- name: id,
- cls: 'x-hidden',
- src: Ext.SSL_SECURE_URL
- });
+ /**
+ * 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;
- doc.body.appendChild(frame);
+ 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;
- // This is required so that IE doesn't pop the response up in a new window.
- if(Ext.isIE){
- document.frames[id].name = id;
+ 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;
- Ext.fly(form).set({
- target: id,
- method: POST,
- enctype: encoding,
- encoding: encoding,
- action: url || buf.action
- });
-
- // add dynamic params
- Ext.iterate(Ext.urlDecode(ps, false), function(k, v){
- hd = doc.createElement('input');
- Ext.fly(hd).set({
- type: 'hidden',
- value: v,
- name: k
- });
- form.appendChild(hd);
- hiddens.push(hd);
- });
-
- function cb(){
- var me = this,
- // bogus response object
- r = {responseText : '',
- responseXML : null,
- argument : o.argument},
- doc,
- firstChild;
-
- try{
- doc = frame.contentWindow.document || frame.contentDocument || WINDOW.frames[id].document;
- if(doc){
- if(doc.body){
- if(/textarea/i.test((firstChild = doc.body.firstChild || {}).tagName)){ // json response wrapped in textarea
- r.responseText = firstChild.value;
- }else{
- r.responseText = doc.body.innerHTML;
- }
- }
- //in IE the document may still have a body even if returns XML.
- r.responseXML = doc.XMLDocument || doc;
+ 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;
}
- catch(e) {}
+ return true;
+ },
- Ext.EventManager.removeListener(frame, LOAD, cb, me);
+ /**
+ * Returns true if display is not "none"
+ * @return {Boolean}
+ */
+ isDisplayed : function() {
+ return !this.isStyle(DISPLAY, NONE);
+ },
- me.fireEvent(REQUESTCOMPLETE, me, r, o);
+ /**
+ * 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;
+ },
- function runCallback(fn, scope, args){
- if(Ext.isFunction(fn)){
- fn.apply(scope, args);
- }
+ /**
+ * 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();
}
- runCallback(o.success, o.scope, [r, o]);
- runCallback(o.callback, o.scope, [o, true, r]);
+ mask = dh.append(dom, {cls : "ext-el-mask"}, true);
+ data(dom, 'mask', mask);
- if(!me.debugUploads){
- setTimeout(function(){Ext.removeNode(frame);}, 100);
+ 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);
}
- }
-
- Ext.EventManager.on(frame, LOAD, cb, this);
- form.submit();
+
+ // 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;
+ },
- Ext.fly(form).set(buf);
- Ext.each(hiddens, function(h) {
- Ext.removeNode(h);
- });
- }
- });
-})();
+ /**
+ * 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]);
+ }
+ },
-/**
- * @class Ext.Ajax
- * @extends Ext.data.Connection
- * <p>The global Ajax request class that provides a simple way to make Ajax requests
- * with maximum flexibility.</p>
- * <p>Since Ext.Ajax is a singleton, you can set common properties/events for it once
- * and override them at the request function level only if necessary.</p>
- * <p>Common <b>Properties</b> you may want to set are:<div class="mdetail-params"><ul>
- * <li><b><tt>{@link #method}</tt></b><p class="sub-desc"></p></li>
- * <li><b><tt>{@link #extraParams}</tt></b><p class="sub-desc"></p></li>
- * <li><b><tt>{@link #url}</tt></b><p class="sub-desc"></p></li>
- * </ul></div>
- * <pre><code>
-// Default headers to pass in every request
-Ext.Ajax.defaultHeaders = {
- 'Powered-By': 'Ext'
-};
- * </code></pre>
- * </p>
- * <p>Common <b>Events</b> you may want to set are:<div class="mdetail-params"><ul>
- * <li><b><tt>{@link Ext.data.Connection#beforerequest beforerequest}</tt></b><p class="sub-desc"></p></li>
- * <li><b><tt>{@link Ext.data.Connection#requestcomplete requestcomplete}</tt></b><p class="sub-desc"></p></li>
- * <li><b><tt>{@link Ext.data.Connection#requestexception requestexception}</tt></b><p class="sub-desc"></p></li>
- * </ul></div>
- * <pre><code>
-// Example: show a spinner during all Ajax requests
-Ext.Ajax.on('beforerequest', this.showSpinner, this);
-Ext.Ajax.on('requestcomplete', this.hideSpinner, this);
-Ext.Ajax.on('requestexception', this.hideSpinner, this);
- * </code></pre>
- * </p>
- * <p>An example request:</p>
- * <pre><code>
-// Basic request
-Ext.Ajax.{@link Ext.data.Connection#request request}({
- url: 'foo.php',
- success: someFn,
- failure: otherFn,
- headers: {
- 'my-header': 'foo'
- },
- params: { foo: 'bar' }
-});
+ /**
+ * Returns true if this element is masked
+ * @return {Boolean}
+ */
+ isMasked : function() {
+ var m = data(this.dom, 'mask');
+ return m && m.isVisible();
+ },
-// Simple ajax form submission
-Ext.Ajax.{@link Ext.data.Connection#request request}({
- form: 'some-form',
- params: 'foo=bar'
-});
- * </code></pre>
- * </p>
- * @singleton
+ /**
+ * 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.Ajax = new Ext.data.Connection({
- /**
- * @cfg {String} url @hide
- */
- /**
- * @cfg {Object} extraParams @hide
- */
- /**
- * @cfg {Object} defaultHeaders @hide
- */
- /**
- * @cfg {String} method (Optional) @hide
- */
- /**
- * @cfg {Number} timeout (Optional) @hide
- */
+Ext.Element.addMethods({
/**
- * @cfg {Boolean} autoAbort (Optional) @hide
+ * 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);
+ },
/**
- * @cfg {Boolean} disableCaching (Optional) @hide
+ * 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;
+ },
/**
- * @property disableCaching
- * True to add a unique cache-buster param to GET requests. (defaults to true)
- * @type Boolean
- */
- /**
- * @property url
- * The default URL to be used for requests to the server. (defaults to undefined)
- * If the server receives all requests through one URL, setting this once is easier than
- * entering it on every request.
- * @type String
+ * Returns the first Element
+ * @return {Ext.Element}
*/
+ first : function(){
+ return this.item(0);
+ },
+
/**
- * @property extraParams
- * An object containing properties which are used as extra parameters to each request made
- * by this object (defaults to undefined). Session information and other data that you need
- * to pass with each request are commonly put here.
- * @type Object
+ * Returns the last Element
+ * @return {Ext.Element}
*/
+ last : function(){
+ return this.item(this.getCount()-1);
+ },
+
/**
- * @property defaultHeaders
- * An object containing request headers which are added to each request made by this object
- * (defaults to undefined).
- * @type Object
+ * 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;
+ },
+
/**
- * @property method
- * The default HTTP method to be used for requests. Note that this is case-sensitive and
- * should be all caps (defaults to undefined; if not set but params are present will use
- * <tt>"POST"</tt>, otherwise will use <tt>"GET"</tt>.)
- * @type String
- */
+ * 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);
+ }
+
/**
- * @property timeout
- * The timeout in milliseconds to be used for requests. (defaults to 30000)
- * @type Number
- */
+ * Adds elements to this composite.
+ * @param {String/Array} els A string CSS selector, an array of elements or an element
+ * @return {CompositeElement} this
+ */
/**
- * @property autoAbort
- * Whether a new request should abort any pending requests. (defaults to false)
- * @type Boolean
+ * Returns the Element object at the specified index
+ * @param {Number} index
+ * @return {Ext.Element}
*/
- autoAbort : false,
/**
- * Serialize the passed form into a url encoded string
- * @param {String/HTMLElement} form
- * @return {String}
+ * 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
*/
- serializeForm : function(form){
- return Ext.lib.Ajax.serializeForm(form);
- }
});
+
+/**
+ * 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
+ */
+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}
this.update(this.defaultUrl, null, callback, true);
}
}
- }
+ };
}());
/**
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
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: "this.getFullYear()",
+ 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')",
s = s || 0;
ms = ms || 0;
- var dt = new Date(y, m - 1, d, h, i, s, ms);
+ // 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 &&
special = false;
code.push("'" + String.escape(ch) + "'");
} else {
- code.push(Date.getFormatCode(ch))
+ code.push(Date.getFormatCode(ch));
}
}
Date.formatFunctions[format] = new Function("return " + code.join('+'));
// these 2 values alone provide sufficient info to create a full date object
// create Date object representing January 1st for the given year
- "v = new Date(y, 0, 1, h, i, s, ms);",
+ // 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);",
"v = null;", // invalid date, so return null
"}else{",
// plain old Date object
- "v = new Date(y, m, d, h, i, s, ms);",
+ // 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);",
"}",
"}",
"}",
calc = [],
regex = [],
special = false,
- ch = "";
+ ch = "",
+ i = 0,
+ obj,
+ last;
- for (var i = 0; i < format.length; ++i) {
+ for (; i < format.length; ++i) {
ch = format.charAt(i);
if (!special && ch == "\\") {
special = true;
special = false;
regex.push(String.escape(ch));
} else {
- var obj = $f(ch, currentGroup);
+ obj = $f(ch, currentGroup);
currentGroup += obj.g;
regex.push(obj.s);
if (obj.g && obj.c) {
- calc.push(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('') + "$");
+ Date.parseRegexes[regexNum] = new RegExp("^" + regex.join('') + "$", 'i');
Date.parseFunctions[format] = new Function("input", "strict", xf(code, regexNum, calc.join('')));
- }
+ };
}(),
// private
g:0,
c:null,
s:"(?:" + a.join("|") +")"
- }
+ };
},
l: function() {
return {
g:0,
c:null,
s:"(?:" + Date.dayNames.join("|") + ")"
- }
+ };
},
N: {
g:0,
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
+ "y = ty > Date.y2kYear ? 1900 + ty : 2000 + ty;\n", // 2-digit year
s:"(\\d{1,2})"
},
- a: {
- g:1,
- c:"if (results[{0}] == 'am') {\n"
- + "if (!h || h == 12) { h = 0; }\n"
- + "} else { if (!h || h < 12) { h = (h || 0) + 12; }}",
- s:"(am|pm)"
+ /**
+ * 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 (results[{0}] == 'AM') {\n"
+ 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)"
+ s:"(AM|PM|am|pm)"
},
g: function() {
return $f("G");
")?",
")?"
].join("")
- }
+ };
},
U: {
g:1,
Wyr = new Date(AWN * ms7d).getUTCFullYear();
return AWN - Math.floor(Date.UTC(Wyr, 0, 7) / ms7d) + 1;
- }
+ };
}(),
/**
var m = this.getMonth();
return m == 1 && this.isLeapYear() ? 29 : daysInMonth[m];
- }
+ };
}(),
/**
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
reorder: function(mapping) {
this.suspendEvents();
- var items = this.items,
- index = 0,
- length = items.length,
- order = [],
- remaining = [];
+ var items = this.items,
+ index = 0,
+ length = items.length,
+ order = [],
+ remaining = [],
+ oldIndex;
//object of {oldPosition: newPosition} reversed to {newPosition: oldPosition}
for (oldIndex in mapping) {
*/
Ext.util.MixedCollection.prototype.get = Ext.util.MixedCollection.prototype.item;
/**
- * @class Ext.util.JSON
- * Modified version of Douglas Crockford"s json.js that doesn"t
- * mess with the Object prototype
- * http://www.json.org/js.html
- * @singleton
+ * @class Ext.AbstractManager
+ * @extends Object
+ * Base Manager class - extended by ComponentMgr and PluginMgr
*/
-Ext.util.JSON = new (function(){
- var useHasOwn = !!{}.hasOwnProperty,
- isNative = function() {
- var useNative = null;
-
- return function() {
- if (useNative === null) {
- useNative = Ext.USE_NATIVE_JSON && window.JSON && JSON.toString() == '[object JSON]';
- }
+Ext.AbstractManager = Ext.extend(Object, {
+ typeName: 'type',
+
+ constructor: function(config) {
+ Ext.apply(this, config || {});
- return useNative;
- };
- }(),
- pad = function(n) {
- return n < 10 ? "0" + n : n;
- },
- doDecode = function(json){
- return eval("(" + json + ')');
- },
- doEncode = function(o){
- if(!Ext.isDefined(o) || o === null){
- return "null";
- }else if(Ext.isArray(o)){
- return encodeArray(o);
- }else if(Ext.isDate(o)){
- return Ext.util.JSON.encodeDate(o);
- }else if(Ext.isString(o)){
- return encodeString(o);
- }else if(typeof o == "number"){
- //don't use isNumber here, since finite checks happen inside isNumber
- return isFinite(o) ? String(o) : "null";
- }else if(Ext.isBoolean(o)){
- return String(o);
- }else {
- var a = ["{"], b, i, v;
- for (i in o) {
- // don't encode DOM objects
- if(!o.getElementsByTagName){
- if(!useHasOwn || o.hasOwnProperty(i)) {
- v = o[i];
- switch (typeof v) {
- case "undefined":
- case "function":
- case "unknown":
- break;
- default:
- if(b){
- a.push(',');
- }
- a.push(doEncode(i), ":",
- v === null ? "null" : doEncode(v));
- b = true;
- }
- }
- }
- }
- a.push("}");
- return a.join("");
- }
- },
- m = {
- "\b": '\\b',
- "\t": '\\t',
- "\n": '\\n',
- "\f": '\\f',
- "\r": '\\r',
- '"' : '\\"',
- "\\": '\\\\'
- },
- encodeString = function(s){
- if (/["\\\x00-\x1f]/.test(s)) {
- return '"' + s.replace(/([\x00-\x1f\\"])/g, function(a, b) {
- var c = m[b];
- if(c){
- return c;
- }
- c = b.charCodeAt();
- return "\\u00" +
- Math.floor(c / 16).toString(16) +
- (c % 16).toString(16);
- }) + '"';
- }
- return '"' + s + '"';
- },
- encodeArray = function(o){
- var a = ["["], b, i, l = o.length, v;
- for (i = 0; i < l; i += 1) {
- v = o[i];
- switch (typeof v) {
- case "undefined":
- case "function":
- case "unknown":
- break;
- default:
- if (b) {
- a.push(',');
- }
- a.push(v === null ? "null" : Ext.util.JSON.encode(v));
- b = true;
- }
- }
- a.push("]");
- return a.join("");
- };
-
+ /**
+ * Contains all of the items currently managed
+ * @property all
+ * @type Ext.util.MixedCollection
+ */
+ this.all = new Ext.util.MixedCollection();
+
+ this.types = {};
+ },
+
/**
- * <p>Encodes a Date. This returns the actual string which is inserted into the JSON string as the literal expression.
- * <b>The returned value includes enclosing double quotation marks.</b></p>
- * <p>The default return format is "yyyy-mm-ddThh:mm:ss".</p>
- * <p>To override this:</p><pre><code>
-Ext.util.JSON.encodeDate = function(d) {
- return d.format('"Y-m-d"');
-};
-</code></pre>
- * @param {Date} d The Date to encode
- * @return {String} The string literal to use in a JSON string.
+ * 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.
*/
- this.encodeDate = function(o){
- return '"' + o.getFullYear() + "-" +
- pad(o.getMonth() + 1) + "-" +
- pad(o.getDate()) + "T" +
- pad(o.getHours()) + ":" +
- pad(o.getMinutes()) + ":" +
- pad(o.getSeconds()) + '"';
- };
-
+ get : function(id){
+ return this.all.get(id);
+ },
+
/**
- * Encodes an Object, Array or other value
- * @param {Mixed} o The variable to encode
- * @return {String} The JSON string
+ * Registers an item to be managed
+ * @param {Mixed} item The item to register
*/
- this.encode = function() {
- var ec;
- return function(o) {
- if (!ec) {
- // setup encoding function on first access
- ec = isNative() ? JSON.stringify : doEncode;
- }
- return ec(o);
- };
- }();
-
-
+ register: function(item) {
+ this.all.add(item);
+ },
+
/**
- * Decodes (parses) a JSON string to an object. If the JSON is invalid, this function throws a SyntaxError unless the safe option is set.
- * @param {String} json The JSON string
- * @return {Object} The resulting object
+ * Unregisters a component by removing it from this manager
+ * @param {Mixed} item The item to unregister
*/
- this.decode = function() {
- var dc;
- return function(json) {
- if (!dc) {
- // setup decoding function on first access
- dc = isNative() ? JSON.parse : doDecode;
+ 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);
}
- return dc(json);
- };
- }();
-
-})();
-/**
- * Shorthand for {@link Ext.util.JSON#encode}
- * @param {Mixed} o The variable to encode
- * @return {String} The JSON string
- * @member Ext
- * @method encode
- */
-Ext.encode = Ext.util.JSON.encode;
-/**
- * Shorthand for {@link Ext.util.JSON#decode}
- * @param {String} json The JSON string
- * @param {Boolean} safe (optional) Whether to return null or throw an exception if the JSON is invalid.
- * @return {Object} The resulting object
- * @member Ext
- * @method decode
- */
-Ext.decode = Ext.util.JSON.decode;
-/**
+ });
+ }
+});/**
* @class Ext.util.Format
* Reusable data formatting functions
* @singleton
*/
-Ext.util.Format = function(){
- var trimRe = /^\s+|\s+$/g,
- stripTagsRE = /<\/?[^>]+>/gi,
+Ext.util.Format = function() {
+ var trimRe = /^\s+|\s+$/g,
+ stripTagsRE = /<\/?[^>]+>/gi,
stripScriptsRe = /(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)/ig,
- nl2brRe = /\r?\n/g;
+ nl2brRe = /\r?\n/g;
return {
/**
* @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),
+ 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)){
+ if (index == -1 || index < (len - 15)) {
return value.substr(0, len - 3) + "...";
- }else{
+ } else {
return vs.substr(0, index) + "...";
}
- } else{
+ } else {
return value.substr(0, len - 3) + "...";
}
}
* @param {Mixed} value Reference to check
* @return {Mixed} Empty string if converted, otherwise the original value
*/
- undef : function(value){
+ undef : function(value) {
return value !== undefined ? value : "";
},
* @param {String} defaultValue The value to insert of it's undefined (defaults to "")
* @return {String}
*/
- defaultValue : function(value, defaultValue){
+ defaultValue : function(value, defaultValue) {
return value !== undefined && value !== '' ? value : defaultValue;
},
* @param {String} value The string to encode
* @return {String} The encoded text
*/
- htmlEncode : function(value){
+ htmlEncode : function(value) {
return !value ? value : String(value).replace(/&/g, "&").replace(/>/g, ">").replace(/</g, "<").replace(/"/g, """);
},
* @param {String} value The string to decode
* @return {String} The decoded text
*/
- htmlDecode : function(value){
+ htmlDecode : function(value) {
return !value ? value : String(value).replace(/>/g, ">").replace(/</g, "<").replace(/"/g, '"').replace(/&/g, "&");
},
* @param {String} value The text to trim
* @return {String} The trimmed text
*/
- trim : function(value){
+ trim : function(value) {
return String(value).replace(trimRe, "");
},
* @param {Number} length The length of the substring
* @return {String} The substring
*/
- substr : function(value, start, length){
+ substr : function(value, start, length) {
return String(value).substr(start, length);
},
* @param {String} value The text to convert
* @return {String} The converted text
*/
- lowercase : function(value){
+ lowercase : function(value) {
return String(value).toLowerCase();
},
* @param {String} value The text to convert
* @return {String} The converted text
*/
- uppercase : function(value){
+ uppercase : function(value) {
return String(value).toUpperCase();
},
* @param {String} value The text to convert
* @return {String} The converted text
*/
- capitalize : function(value){
+ capitalize : function(value) {
return !value ? value : value.charAt(0).toUpperCase() + value.substr(1).toLowerCase();
},
// private
- call : function(value, fn){
- if(arguments.length > 2){
+ 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{
+ } else {
return eval(fn).call(window, value);
}
},
* @param {Number/String} value The numeric value to format
* @return {String} The formatted currency string
*/
- usMoney : function(v){
+ 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);
whole = whole.replace(r, '$1' + ',' + '$2');
}
v = whole + sub;
- if(v.charAt(0) == '-'){
+ if (v.charAt(0) == '-') {
return '-$' + v.substr(1);
}
return "$" + v;
* @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){
+ date : function(v, format) {
+ if (!v) {
return "";
}
- if(!Ext.isDate(v)){
+ if (!Ext.isDate(v)) {
v = new Date(Date.parse(v));
}
return v.dateFormat(format || "m/d/Y");
* @param {String} format Any valid date format string
* @return {Function} The date formatting function
*/
- dateRenderer : function(format){
- return function(v){
+ dateRenderer : function(format) {
+ return function(v) {
return Ext.util.Format.date(v, format);
};
},
* @param {Mixed} value The text from which to strip tags
* @return {String} The stripped text
*/
- stripTags : function(v){
+ stripTags : function(v) {
return !v ? v : String(v).replace(stripTagsRE, "");
},
* @param {Mixed} value The text from which to strip script tags
* @return {String} The stripped text
*/
- stripScripts : function(v){
+ stripScripts : function(v) {
return !v ? v : String(v).replace(stripScriptsRe, "");
},
* @param {Number/String} size The numeric value to format
* @return {String} The formatted file size
*/
- fileSize : function(size){
- if(size < 1024) {
+ fileSize : function(size) {
+ if (size < 1024) {
return size + " bytes";
- } else if(size < 1048576) {
+ } else if (size < 1048576) {
return (Math.round(((size*10) / 1024))/10) + " KB";
} else {
return (Math.round(((size*10) / 1048576))/10) + " MB";
*/
math : function(){
var fns = {};
+
return function(v, a){
- if(!fns[a]){
+ if (!fns[a]) {
fns[a] = new Function('v', 'return v ' + a + ';');
}
return fns[a](v);
- }
+ };
}(),
/**
* @return {String} The formatted number.
*/
number: function(v, format) {
- if(!format){
+ if (!format) {
return v;
}
v = Ext.num(v, NaN);
- if (isNaN(v)){
+ if (isNaN(v)) {
return '';
}
var comma = ',',
- dec = '.',
- i18n = false,
- neg = v < 0;
+ dec = '.',
+ i18n = false,
+ neg = v < 0;
v = Math.abs(v);
- if(format.substr(format.length - 2) == '/i'){
+ if (format.substr(format.length - 2) == '/i') {
format = format.substr(0, format.length - 2);
- i18n = true;
- comma = '.';
- dec = ',';
+ i18n = true;
+ comma = '.';
+ dec = ',';
}
var hasComma = format.indexOf(comma) != -1,
- psplit = (i18n ? format.replace(/[^\d\,]/g, '') : format.replace(/[^\d\.]/g, '')).split(dec);
+ psplit = (i18n ? format.replace(/[^\d\,]/g, '') : format.replace(/[^\d\.]/g, '')).split(dec);
- if(1 < psplit.length){
+ if (1 < psplit.length) {
v = v.toFixed(psplit[1].length);
- }else if(2 < psplit.length){
+ } else if(2 < psplit.length) {
throw ('NumberFormatException: invalid format, formats should have no more than 1 period: ' + format);
- }else{
+ } else {
v = v.toFixed(0);
}
psplit = fnum.split('.');
if (hasComma) {
- var cnum = psplit[0], parr = [], j = cnum.length, m = Math.floor(j / 3), n = cnum.length % 3 || 3;
-
- for (var i = 0; i < j; i += n) {
+ 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;
}
* @param {String} format Any valid number format string for {@link #number}
* @return {Function} The number formatting function
*/
- numberRenderer : function(format){
- return function(v){
+ numberRenderer : function(format) {
+ return function(v) {
return Ext.util.Format.number(v, format);
};
},
* @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){
+ plural : function(v, s, p) {
return v +' ' + (v == 1 ? s : (p ? p : s+'s'));
},
* @param {String} The string value to format.
* @return {String} The string with embedded <br/> tags in place of newlines.
*/
- nl2br : function(v){
+ nl2br : function(v) {
return Ext.isEmpty(v) ? '' : v.replace(nl2brRe, '<br/>');
}
- }
+ };
}();
/**
* @class Ext.XTemplate
@param {Mixed} el The element to listen on
@param {Object} config
*/
-Ext.util.ClickRepeater = function(el, config)
-{
- this.el = Ext.get(el);
- this.el.unselectable();
+Ext.util.ClickRepeater = Ext.extend(Ext.util.Observable, {
+
+ constructor : function(el, config){
+ this.el = Ext.get(el);
+ this.el.unselectable();
- Ext.apply(this, config);
+ Ext.apply(this, config);
- this.addEvents(
- /**
- * @event mousedown
- * Fires when the mouse button is depressed.
- * @param {Ext.util.ClickRepeater} this
- */
+ 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
- */
+ /**
+ * @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
- */
+ /**
+ * @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);
- }
+ if(!this.disabled){
+ this.disabled = true;
+ this.enable();
+ }
- Ext.util.ClickRepeater.superclass.constructor.call(this);
-};
+ // allow inline handler
+ if(this.handler){
+ this.on("click", this.handler, this.scope || this);
+ }
-Ext.extend(Ext.util.ClickRepeater, Ext.util.Observable, {
+ Ext.util.ClickRepeater.superclass.constructor.call(this);
+ },
+
interval : 20,
delay: 250,
preventDefault : true,
this.purgeListeners();
},
- handleDblClick : function(){
+ handleDblClick : function(e){
clearTimeout(this.timer);
this.el.blur();
- this.fireEvent("mousedown", this);
- this.fireEvent("click", this);
+ this.fireEvent("mousedown", this, e);
+ this.fireEvent("click", this, e);
},
// private
- handleMouseDown : function(){
+ handleMouseDown : function(e){
clearTimeout(this.timer);
this.el.blur();
if(this.pressClass){
Ext.getDoc().on("mouseup", this.handleMouseUp, this);
this.el.on("mouseout", this.handleMouseOut, this);
- this.fireEvent("mousedown", this);
- this.fireEvent("click", 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);
+ this.timer = this.click.defer(this.delay || this.interval, this, [e]);
},
// private
- click : function(){
- this.fireEvent("click", this);
+ 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);
+ this.interval, this, [e]);
},
easeOutExpo : function (t, b, c, d) {
},
// private
- handleMouseUp : function(){
+ 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);
+ this.fireEvent("mouseup", this, e);
}
});/**
* @class Ext.KeyNav
// private
relay : function(e){
- var k = e.getKey();
- var h = this.keyToHandler[k];
+ 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);
+ return h.call(this.scope || this, e, hname);
},
// possible handlers
flash {@link Ext.FlashComponent}
grid {@link Ext.grid.GridPanel}
listview {@link Ext.ListView}
+multislider {@link Ext.slider.MultiSlider}
panel {@link Ext.Panel}
progress {@link Ext.ProgressBar}
propertygrid {@link Ext.grid.PropertyGrid}
-slider {@link Ext.Slider}
+slider {@link Ext.slider.SingleSlider}
spacer {@link Ext.Spacer}
splitbutton {@link Ext.SplitButton}
tabpanel {@link Ext.TabPanel}
checkbox {@link Ext.form.Checkbox}
checkboxgroup {@link Ext.form.CheckboxGroup}
combo {@link Ext.form.ComboBox}
+compositefield {@link Ext.form.CompositeField}
datefield {@link Ext.form.DateField}
displayfield {@link Ext.form.DisplayField}
field {@link Ext.form.Field}
if(delay){
this.focusTask = new Ext.util.DelayedTask(this.focus, this, [selectText, false]);
this.focusTask.delay(Ext.isNumber(delay) ? delay : 10);
- return;
+ return this;
}
if(this.rendered && !this.isDestroyed){
this.el.focus();
var isBoxSubclass = t.isXType('box'); // true, descended from BoxComponent
var isBoxInstance = t.isXType('box', true); // false, not a direct BoxComponent instance
</code></pre>
- * @param {String} xtype The xtype to check for this Component
+ * @param {String/Ext.Component/Class} xtype The xtype to check for this Component. Note that the the component can either be an instance
+ * or a component class:
+ * <pre><code>
+var c = new Ext.Component();
+console.log(c.isXType(c));
+console.log(c.isXType(Ext.Component));
+</code></pre>
* @param {Boolean} shallow (optional) False to check whether this Component is descended from the xtype (this is
* the default), or true to check whether this Component is directly of the specified xtype.
* @return {Boolean} True if this component descends from the specified xtype, false otherwise.
/**
* Find a container above this component at any level by xtype or class
- * @param {String/Class} xtype The xtype string for a component, or the class of the component directly
+ * @param {String/Ext.Component/Class} xtype The xtype to check for this Component. Note that the the component can either be an instance
+ * or a component class:
+ * @param {Boolean} shallow (optional) False to check whether this Component is descended from the xtype (this is
+ * the default), or true to check whether this Component is directly of the specified xtype.
* @return {Ext.Container} The first Container which matches the given xtype or class
*/
- findParentByType : function(xtype) {
- return Ext.isFunction(xtype) ?
- this.findParentBy(function(p){
- return p.constructor === xtype;
- }) :
- this.findParentBy(function(p){
- return p.constructor.xtype === xtype;
- });
+ findParentByType : function(xtype, shallow){
+ return this.findParentBy(function(c){
+ return c.isXType(xtype, shallow);
+ });
+ },
+
+ /**
+ * Bubbles up the component/container heirarchy, calling the specified function with each component. The scope (<i>this</i>) of
+ * function call will be the scope provided or the current component. The arguments to the function
+ * will be the args provided or the current component. If the function returns false at any point,
+ * the bubble is stopped.
+ * @param {Function} fn The function to call
+ * @param {Object} scope (optional) The scope of the function (defaults to current node)
+ * @param {Array} args (optional) The args to call the function with (default to passing the current component)
+ * @return {Ext.Component} this
+ */
+ bubble : function(fn, scope, args){
+ var p = this;
+ while(p){
+ if(fn.apply(scope || p, args || [p]) === false){
+ break;
+ }
+ p = p.ownerCt;
+ }
+ return this;
},
// protected
}
});
-Ext.reg('component', Ext.Component);/**
+Ext.reg('component', Ext.Component);
+/**
* @class Ext.Action
* <p>An Action is a piece of reusable functionality that can be abstracted out of any particular component so that it
* can be usefully shared among multiple components. Actions let you share handlers, configuration options and UI
(function(){
Ext.Layer = function(config, existingEl){
config = config || {};
- var dh = Ext.DomHelper;
- var cp = config.parentEl, pel = cp ? Ext.getDom(cp) : document.body;
- if(existingEl){
+ var dh = Ext.DomHelper,
+ cp = config.parentEl, pel = cp ? Ext.getDom(cp) : document.body;
+
+ if (existingEl) {
this.dom = Ext.getDom(existingEl);
}
if(!this.dom){
}
return this;
},
+
+ getConstrainOffset : function(){
+ return this.shadowOffset;
+ },
isVisible : function(){
return this.visible;
* Create a new Shadow
* @param {Object} config The config object
*/
-Ext.Shadow = function(config){
+Ext.Shadow = function(config) {
Ext.apply(this, config);
- if(typeof this.mode != "string"){
+ if (typeof this.mode != "string") {
this.mode = this.defaultMode;
}
- var o = this.offset, a = {h: 0};
- var rad = Math.floor(this.offset/2);
- switch(this.mode.toLowerCase()){ // all this hideous nonsense calculates the various offsets for shadows
+ var o = this.offset,
+ a = {
+ h: 0
+ },
+ rad = Math.floor(this.offset / 2);
+ switch (this.mode.toLowerCase()) {
+ // all this hideous nonsense calculates the various offsets for shadows
case "drop":
a.w = 0;
a.l = a.t = o;
a.t -= 1;
- if(Ext.isIE){
+ if (Ext.isIE) {
a.l -= this.offset + rad;
a.t -= this.offset + rad;
a.w -= rad;
}
break;
case "sides":
- a.w = (o*2);
+ a.w = (o * 2);
a.l = -o;
- a.t = o-1;
- if(Ext.isIE){
+ a.t = o - 1;
+ if (Ext.isIE) {
a.l -= (this.offset - rad);
a.t -= this.offset + rad;
a.l += 1;
- a.w -= (this.offset - rad)*2;
+ a.w -= (this.offset - rad) * 2;
a.w -= rad + 1;
a.h -= 1;
}
break;
case "frame":
- a.w = a.h = (o*2);
+ a.w = a.h = (o * 2);
a.l = a.t = -o;
a.t += 1;
a.h -= 2;
- if(Ext.isIE){
+ if (Ext.isIE) {
a.l -= (this.offset - rad);
a.t -= (this.offset - rad);
a.l += 1;
* Displays the shadow under the target element
* @param {Mixed} targetEl The id or element under which the shadow should display
*/
- show : function(target){
+ show: function(target) {
target = Ext.get(target);
- if(!this.el){
+ if (!this.el) {
this.el = Ext.Shadow.Pool.pull();
- if(this.el.dom.nextSibling != target.dom){
+ if (this.el.dom.nextSibling != target.dom) {
this.el.insertBefore(target);
}
}
- this.el.setStyle("z-index", this.zIndex || parseInt(target.getStyle("z-index"), 10)-1);
- if(Ext.isIE){
- this.el.dom.style.filter="progid:DXImageTransform.Microsoft.alpha(opacity=50) progid:DXImageTransform.Microsoft.Blur(pixelradius="+(this.offset)+")";
+ this.el.setStyle("z-index", this.zIndex || parseInt(target.getStyle("z-index"), 10) - 1);
+ if (Ext.isIE) {
+ this.el.dom.style.filter = "progid:DXImageTransform.Microsoft.alpha(opacity=50) progid:DXImageTransform.Microsoft.Blur(pixelradius=" + (this.offset) + ")";
}
this.realign(
- target.getLeft(true),
- target.getTop(true),
- target.getWidth(),
- target.getHeight()
+ target.getLeft(true),
+ target.getTop(true),
+ target.getWidth(),
+ target.getHeight()
);
this.el.dom.style.display = "block";
},
/**
* Returns true if the shadow is visible, else false
*/
- isVisible : function(){
- return this.el ? true : false;
+ isVisible: function() {
+ return this.el ? true: false;
},
/**
* @param {Number} width The target element width
* @param {Number} height The target element height
*/
- realign : function(l, t, w, h){
- if(!this.el){
+ realign: function(l, t, w, h) {
+ if (!this.el) {
return;
}
- var a = this.adjusts, d = this.el.dom, s = d.style;
- var iea = 0;
- s.left = (l+a.l)+"px";
- s.top = (t+a.t)+"px";
- var sw = (w+a.w), sh = (h+a.h), sws = sw +"px", shs = sh + "px";
- if(s.width != sws || s.height != shs){
+ var a = this.adjusts,
+ d = this.el.dom,
+ s = d.style,
+ iea = 0,
+ sw = (w + a.w),
+ sh = (h + a.h),
+ sws = sw + "px",
+ shs = sh + "px",
+ cn,
+ sww;
+ s.left = (l + a.l) + "px";
+ s.top = (t + a.t) + "px";
+ if (s.width != sws || s.height != shs) {
s.width = sws;
s.height = shs;
- if(!Ext.isIE){
- var cn = d.childNodes;
- var sww = Math.max(0, (sw-12))+"px";
+ if (!Ext.isIE) {
+ cn = d.childNodes;
+ sww = Math.max(0, (sw - 12)) + "px";
cn[0].childNodes[1].style.width = sww;
cn[1].childNodes[1].style.width = sww;
cn[2].childNodes[1].style.width = sww;
- cn[1].style.height = Math.max(0, (sh-12))+"px";
+ cn[1].style.height = Math.max(0, (sh - 12)) + "px";
}
}
},
/**
* Hides this shadow
*/
- hide : function(){
- if(this.el){
+ hide: function() {
+ if (this.el) {
this.el.dom.style.display = "none";
Ext.Shadow.Pool.push(this.el);
delete this.el;
* Adjust the z-index of this shadow
* @param {Number} zindex The new z-index
*/
- setZIndex : function(z){
+ setZIndex: function(z) {
this.zIndex = z;
- if(this.el){
+ if (this.el) {
this.el.setStyle("z-index", z);
}
}
};
// Private utility class that manages the internal Shadow cache
-Ext.Shadow.Pool = function(){
- var p = [];
- var markup = Ext.isIE ?
- '<div class="x-ie-shadow"></div>' :
- '<div class="x-shadow"><div class="xst"><div class="xstl"></div><div class="xstc"></div><div class="xstr"></div></div><div class="xsc"><div class="xsml"></div><div class="xsmc"></div><div class="xsmr"></div></div><div class="xsb"><div class="xsbl"></div><div class="xsbc"></div><div class="xsbr"></div></div></div>';
+Ext.Shadow.Pool = function() {
+ var p = [],
+ markup = Ext.isIE ?
+ '<div class="x-ie-shadow"></div>':
+ '<div class="x-shadow"><div class="xst"><div class="xstl"></div><div class="xstc"></div><div class="xstr"></div></div><div class="xsc"><div class="xsml"></div><div class="xsmc"></div><div class="xsmr"></div></div><div class="xsb"><div class="xsbl"></div><div class="xsbc"></div><div class="xsbr"></div></div></div>';
return {
- pull : function(){
+ pull: function() {
var sh = p.shift();
- if(!sh){
+ if (!sh) {
sh = Ext.get(Ext.DomHelper.insertHtml("beforeBegin", document.body.firstChild, markup));
sh.autoBoxAdjust = false;
}
return sh;
},
- push : function(sh){
+ push: function(sh) {
p.push(sh);
}
};
this.setLayout(this.layout);
// If a CardLayout, the active item set
- if(this.activeItem !== undefined){
+ if(this.activeItem !== undefined && this.layout.setActiveItem){
var item = this.activeItem;
delete this.activeItem;
this.layout.setActiveItem(item);
* @return {Ext.Component} component The Component (or config object) that was
* inserted with the Container's default config values applied.
*/
- insert : function(index, comp){
+ insert : function(index, comp) {
+ var args = arguments,
+ length = args.length,
+ result = [],
+ i, c;
+
this.initItems();
- var a = arguments, len = a.length;
- if(len > 2){
- var result = [];
- for(var i = len-1; i >= 1; --i) {
- result.push(this.insert(index, a[i]));
+
+ if (length > 2) {
+ for (i = length - 1; i >= 1; --i) {
+ result.push(this.insert(index, args[i]));
}
return result;
}
- var c = this.lookupComponent(this.applyDefaults(comp));
+
+ c = this.lookupComponent(this.applyDefaults(comp));
index = Math.min(index, this.items.length);
- if(this.fireEvent('beforeadd', this, c, index) !== false && this.onBeforeAdd(c) !== false){
- if(c.ownerCt == this){
+
+ if (this.fireEvent('beforeadd', this, c, index) !== false && this.onBeforeAdd(c) !== false) {
+ if (c.ownerCt == this) {
this.items.remove(c);
}
this.items.insert(index, c);
this.onAdd(c);
this.fireEvent('add', this, c, index);
}
+
return c;
},
c = Ext.ComponentMgr.get(c);
Ext.apply(c, d);
}else if(!c.events){
- Ext.applyIf(c, d);
+ Ext.applyIf(c.isAction ? c.initialConfig : c, d);
}else{
Ext.apply(c, d);
}
Ext.Container.superclass.beforeDestroy.call(this);
},
- /**
- * Bubbles up the component/container heirarchy, calling the specified function with each component. The scope (<i>this</i>) of
- * function call will be the scope provided or the current component. The arguments to the function
- * will be the args provided or the current component. If the function returns false at any point,
- * the bubble is stopped.
- * @param {Function} fn The function to call
- * @param {Object} scope (optional) The scope of the function (defaults to current node)
- * @param {Array} args (optional) The args to call the function with (default to passing the current component)
- * @return {Ext.Container} this
- */
- bubble : function(fn, scope, args){
- var p = this;
- while(p){
- if(fn.apply(scope || p, args || [p]) === false){
- break;
- }
- p = p.ownerCt;
- }
- return this;
- },
-
/**
* Cascades down the component/container heirarchy from this component (called first), calling the specified function with
* each component. The scope (<i>this</i>) of
/**
* Find a component under this container at any level by id
* @param {String} id
+ * @deprecated Fairly useless method, since you can just use Ext.getCmp. Should be removed for 4.0
+ * If you need to test if an id belongs to a container, you can use getCmp and findParent*.
* @return Ext.Component
*/
findById : function(id){
- var m, ct = this;
+ var m = null,
+ ct = this;
this.cascade(function(c){
if(ct != c && c.id === id){
m = c;
return false;
}
});
- return m || null;
+ return m;
},
/**
/**
* Get a component contained by this container (alias for items.get(key))
* @param {String/Number} key The index or id of the component
+ * @deprecated Should be removed in 4.0, since getComponent does the same thing.
* @return {Ext.Component} Ext.Component
*/
get : function(key){
- return this.items.get(key);
+ return this.getComponent(key);
}
});
if (c) {
if (!c.rendered) {
c.render(target, position);
- this.configureItem(c, position);
+ this.configureItem(c);
} else if (!this.isValidParent(c, target)) {
if (Ext.isNumber(position)) {
position = target.dom.childNodes[position];
target.dom.insertBefore(c.getPositionEl().dom, position || null);
c.container = target;
- this.configureItem(c, position);
+ this.configureItem(c);
}
}
},
getRenderedItems: function(ct){
var t = ct.getLayoutTarget(), cti = ct.items.items, len = cti.length, i, c, items = [];
for (i = 0; i < len; i++) {
- if((c = cti[i]).rendered && this.isValidParent(c, t)){
+ if((c = cti[i]).rendered && this.isValidParent(c, t) && c.shouldLayout !== false){
items.push(c);
}
};
* @private
* Applies extraCls and hides the item if renderHidden is true
*/
- configureItem: function(c, position){
+ configureItem: function(c){
if (this.extraCls) {
var t = c.getPositionEl ? c.getPositionEl() : c;
t.addClass(this.extraCls);
if(this.resizeTask && this.resizeTask.cancel){
this.resizeTask.cancel();
}
+ if(this.container) {
+ this.container.un(this.container.resizeEvent, this.onResize, this);
+ }
if(!Ext.isEmpty(this.targetCls)){
var target = this.container.getLayoutTarget();
if(target){
id: 'card-1',
html: '<p>Step 2 of 3</p>'
},{
- id: 'card-2',
- html: '<h1>Congratulations!</h1><p>Step 3 of 3 - Complete</p>'
+ id: 'card-2',
+ html: '<h1>Congratulations!</h1><p>Step 3 of 3 - Complete</p>'
+ }]
+});
+</code></pre>
+ */
+Ext.layout.CardLayout = Ext.extend(Ext.layout.FitLayout, {
+ /**
+ * @cfg {Boolean} deferredRender
+ * True to render each contained item at the time it becomes active, false to render all contained items
+ * as soon as the layout is rendered (defaults to false). If there is a significant amount of content or
+ * a lot of heavy controls being rendered into panels that are not displayed by default, setting this to
+ * true might improve performance.
+ */
+ deferredRender : false,
+
+ /**
+ * @cfg {Boolean} layoutOnCardChange
+ * True to force a layout of the active item when the active card is changed. Defaults to false.
+ */
+ layoutOnCardChange : false,
+
+ /**
+ * @cfg {Boolean} renderHidden @hide
+ */
+ // private
+ renderHidden : true,
+
+ type: 'card',
+
+ /**
+ * Sets the active (visible) item in the layout.
+ * @param {String/Number} item The string component id or numeric index of the item to activate
+ */
+ setActiveItem : function(item){
+ var ai = this.activeItem,
+ ct = this.container;
+ item = ct.getComponent(item);
+
+ // Is this a valid, different card?
+ if(item && ai != item){
+
+ // Changing cards, hide the current one
+ if(ai){
+ ai.hide();
+ if (ai.hidden !== true) {
+ return false;
+ }
+ ai.fireEvent('deactivate', ai);
+ }
+
+ var layout = item.doLayout && (this.layoutOnCardChange || !item.rendered);
+
+ // Change activeItem reference
+ this.activeItem = item;
+
+ // The container is about to get a recursive layout, remove any deferLayout reference
+ // because it will trigger a redundant layout.
+ delete item.deferLayout;
+
+ // Show the new component
+ item.show();
+
+ this.layout();
+
+ if(layout){
+ item.doLayout();
+ }
+ item.fireEvent('activate', item);
+ }
+ },
+
+ // private
+ renderAll : function(ct, target){
+ if(this.deferredRender){
+ this.renderItem(this.activeItem, undefined, target);
+ }else{
+ Ext.layout.CardLayout.superclass.renderAll.call(this, ct, target);
+ }
+ }
+});
+Ext.Container.LAYOUTS['card'] = Ext.layout.CardLayout;
+/**
+ * @class Ext.layout.AnchorLayout
+ * @extends Ext.layout.ContainerLayout
+ * <p>This is a layout that enables anchoring of contained elements relative to the container's dimensions.
+ * If the container is resized, all anchored items are automatically rerendered according to their
+ * <b><tt>{@link #anchor}</tt></b> rules.</p>
+ * <p>This class is intended to be extended or created via the layout:'anchor' {@link Ext.Container#layout}
+ * config, and should generally not need to be created directly via the new keyword.</p>
+ * <p>AnchorLayout does not have any direct config options (other than inherited ones). By default,
+ * AnchorLayout will calculate anchor measurements based on the size of the container itself. However, the
+ * container using the AnchorLayout can supply an anchoring-specific config property of <b>anchorSize</b>.
+ * If anchorSize is specifed, the layout will use it as a virtual container for the purposes of calculating
+ * anchor measurements based on it instead, allowing the container to be sized independently of the anchoring
+ * logic if necessary. For example:</p>
+ * <pre><code>
+var viewport = new Ext.Viewport({
+ layout:'anchor',
+ anchorSize: {width:800, height:600},
+ items:[{
+ title:'Item 1',
+ html:'Content 1',
+ width:800,
+ anchor:'right 20%'
+ },{
+ title:'Item 2',
+ html:'Content 2',
+ width:300,
+ anchor:'50% 30%'
+ },{
+ title:'Item 3',
+ html:'Content 3',
+ width:600,
+ anchor:'-100 50%'
+ }]
+});
+ * </code></pre>
+ */
+Ext.layout.AnchorLayout = Ext.extend(Ext.layout.ContainerLayout, {
+ /**
+ * @cfg {String} anchor
+ * <p>This configuation option is to be applied to <b>child <tt>items</tt></b> of a container managed by
+ * this layout (ie. configured with <tt>layout:'anchor'</tt>).</p><br/>
+ *
+ * <p>This value is what tells the layout how an item should be anchored to the container. <tt>items</tt>
+ * added to an AnchorLayout accept an anchoring-specific config property of <b>anchor</b> which is a string
+ * containing two values: the horizontal anchor value and the vertical anchor value (for example, '100% 50%').
+ * The following types of anchor values are supported:<div class="mdetail-params"><ul>
+ *
+ * <li><b>Percentage</b> : Any value between 1 and 100, expressed as a percentage.<div class="sub-desc">
+ * The first anchor is the percentage width that the item should take up within the container, and the
+ * second is the percentage height. For example:<pre><code>
+// two values specified
+anchor: '100% 50%' // render item complete width of the container and
+ // 1/2 height of the container
+// one value specified
+anchor: '100%' // the width value; the height will default to auto
+ * </code></pre></div></li>
+ *
+ * <li><b>Offsets</b> : Any positive or negative integer value.<div class="sub-desc">
+ * This is a raw adjustment where the first anchor is the offset from the right edge of the container,
+ * and the second is the offset from the bottom edge. For example:<pre><code>
+// two values specified
+anchor: '-50 -100' // render item the complete width of the container
+ // minus 50 pixels and
+ // the complete height minus 100 pixels.
+// one value specified
+anchor: '-50' // anchor value is assumed to be the right offset value
+ // bottom offset will default to 0
+ * </code></pre></div></li>
+ *
+ * <li><b>Sides</b> : Valid values are <tt>'right'</tt> (or <tt>'r'</tt>) and <tt>'bottom'</tt>
+ * (or <tt>'b'</tt>).<div class="sub-desc">
+ * Either the container must have a fixed size or an anchorSize config value defined at render time in
+ * order for these to have any effect.</div></li>
+ *
+ * <li><b>Mixed</b> : <div class="sub-desc">
+ * Anchor values can also be mixed as needed. For example, to render the width offset from the container
+ * right edge by 50 pixels and 75% of the container's height use:
+ * <pre><code>
+anchor: '-50 75%'
+ * </code></pre></div></li>
+ *
+ *
+ * </ul></div>
+ */
+
+ // private
+ monitorResize : true,
+
+ type : 'anchor',
+
+ /**
+ * @cfg {String} defaultAnchor
+ *
+ * default anchor for all child container items applied if no anchor or specific width is set on the child item. Defaults to '100%'.
+ *
+ */
+ defaultAnchor : '100%',
+
+ parseAnchorRE : /^(r|right|b|bottom)$/i,
+
+
+ getLayoutTargetSize : function() {
+ var target = this.container.getLayoutTarget(), ret = {};
+ if (target) {
+ ret = target.getViewSize();
+
+ // IE in strict mode will return a width of 0 on the 1st pass of getViewSize.
+ // Use getStyleSize to verify the 0 width, the adjustment pass will then work properly
+ // with getViewSize
+ if (Ext.isIE && Ext.isStrict && ret.width == 0){
+ ret = target.getStyleSize();
+ }
+ ret.width -= target.getPadding('lr');
+ ret.height -= target.getPadding('tb');
+ }
+ return ret;
+ },
+
+ // private
+ onLayout : function(container, target) {
+ Ext.layout.AnchorLayout.superclass.onLayout.call(this, container, target);
+
+ var size = this.getLayoutTargetSize(),
+ containerWidth = size.width,
+ containerHeight = size.height,
+ overflow = target.getStyle('overflow'),
+ components = this.getRenderedItems(container),
+ len = components.length,
+ boxes = [],
+ box,
+ anchorWidth,
+ anchorHeight,
+ component,
+ anchorSpec,
+ calcWidth,
+ calcHeight,
+ anchorsArray,
+ totalHeight = 0,
+ i,
+ el;
+
+ if(containerWidth < 20 && containerHeight < 20){
+ return;
+ }
+
+ // find the container anchoring size
+ if(container.anchorSize) {
+ if(typeof container.anchorSize == 'number') {
+ anchorWidth = container.anchorSize;
+ } else {
+ anchorWidth = container.anchorSize.width;
+ anchorHeight = container.anchorSize.height;
+ }
+ } else {
+ anchorWidth = container.initialConfig.width;
+ anchorHeight = container.initialConfig.height;
+ }
+
+ for(i = 0; i < len; i++) {
+ component = components[i];
+ el = component.getPositionEl();
+
+ // If a child container item has no anchor and no specific width, set the child to the default anchor size
+ if (!component.anchor && component.items && !Ext.isNumber(component.width) && !(Ext.isIE6 && Ext.isStrict)){
+ component.anchor = this.defaultAnchor;
+ }
+
+ if(component.anchor) {
+ anchorSpec = component.anchorSpec;
+ // cache all anchor values
+ if(!anchorSpec){
+ anchorsArray = component.anchor.split(' ');
+ component.anchorSpec = anchorSpec = {
+ right: this.parseAnchor(anchorsArray[0], component.initialConfig.width, anchorWidth),
+ bottom: this.parseAnchor(anchorsArray[1], component.initialConfig.height, anchorHeight)
+ };
+ }
+ calcWidth = anchorSpec.right ? this.adjustWidthAnchor(anchorSpec.right(containerWidth) - el.getMargins('lr'), component) : undefined;
+ calcHeight = anchorSpec.bottom ? this.adjustHeightAnchor(anchorSpec.bottom(containerHeight) - el.getMargins('tb'), component) : undefined;
+
+ if(calcWidth || calcHeight) {
+ boxes.push({
+ component: component,
+ width: calcWidth || undefined,
+ height: calcHeight || undefined
+ });
+ }
+ }
+ }
+ for (i = 0, len = boxes.length; i < len; i++) {
+ box = boxes[i];
+ box.component.setSize(box.width, box.height);
+ }
+
+ if (overflow && overflow != 'hidden' && !this.adjustmentPass) {
+ var newTargetSize = this.getLayoutTargetSize();
+ if (newTargetSize.width != size.width || newTargetSize.height != size.height){
+ this.adjustmentPass = true;
+ this.onLayout(container, target);
+ }
+ }
+
+ delete this.adjustmentPass;
+ },
+
+ // private
+ parseAnchor : function(a, start, cstart) {
+ if (a && a != 'none') {
+ var last;
+ // standard anchor
+ if (this.parseAnchorRE.test(a)) {
+ var diff = cstart - start;
+ return function(v){
+ if(v !== last){
+ last = v;
+ return v - diff;
+ }
+ };
+ // percentage
+ } else if(a.indexOf('%') != -1) {
+ var ratio = parseFloat(a.replace('%', ''))*.01;
+ return function(v){
+ if(v !== last){
+ last = v;
+ return Math.floor(v*ratio);
+ }
+ };
+ // simple offset adjustment
+ } else {
+ a = parseInt(a, 10);
+ if (!isNaN(a)) {
+ return function(v) {
+ if (v !== last) {
+ last = v;
+ return v + a;
+ }
+ };
+ }
+ }
+ }
+ return false;
+ },
+
+ // private
+ adjustWidthAnchor : function(value, comp){
+ return value;
+ },
+
+ // private
+ adjustHeightAnchor : function(value, comp){
+ return value;
+ }
+
+ /**
+ * @property activeItem
+ * @hide
+ */
+});
+Ext.Container.LAYOUTS['anchor'] = Ext.layout.AnchorLayout;
+/**
+ * @class Ext.layout.ColumnLayout
+ * @extends Ext.layout.ContainerLayout
+ * <p>This is the layout style of choice for creating structural layouts in a multi-column format where the width of
+ * each column can be specified as a percentage or fixed width, but the height is allowed to vary based on the content.
+ * This class is intended to be extended or created via the layout:'column' {@link Ext.Container#layout} config,
+ * and should generally not need to be created directly via the new keyword.</p>
+ * <p>ColumnLayout does not have any direct config options (other than inherited ones), but it does support a
+ * specific config property of <b><tt>columnWidth</tt></b> that can be included in the config of any panel added to it. The
+ * layout will use the columnWidth (if present) or width of each panel during layout to determine how to size each panel.
+ * If width or columnWidth is not specified for a given panel, its width will default to the panel's width (or auto).</p>
+ * <p>The width property is always evaluated as pixels, and must be a number greater than or equal to 1.
+ * The columnWidth property is always evaluated as a percentage, and must be a decimal value greater than 0 and
+ * less than 1 (e.g., .25).</p>
+ * <p>The basic rules for specifying column widths are pretty simple. The logic makes two passes through the
+ * set of contained panels. During the first layout pass, all panels that either have a fixed width or none
+ * specified (auto) are skipped, but their widths are subtracted from the overall container width. During the second
+ * pass, all panels with columnWidths are assigned pixel widths in proportion to their percentages based on
+ * the total <b>remaining</b> container width. In other words, percentage width panels are designed to fill the space
+ * left over by all the fixed-width and/or auto-width panels. Because of this, while you can specify any number of columns
+ * with different percentages, the columnWidths must always add up to 1 (or 100%) when added together, otherwise your
+ * layout may not render as expected. Example usage:</p>
+ * <pre><code>
+// All columns are percentages -- they must add up to 1
+var p = new Ext.Panel({
+ title: 'Column Layout - Percentage Only',
+ layout:'column',
+ items: [{
+ title: 'Column 1',
+ columnWidth: .25
+ },{
+ title: 'Column 2',
+ columnWidth: .6
+ },{
+ title: 'Column 3',
+ columnWidth: .15
+ }]
+});
+
+// Mix of width and columnWidth -- all columnWidth values must add up
+// to 1. The first column will take up exactly 120px, and the last two
+// columns will fill the remaining container width.
+var p = new Ext.Panel({
+ title: 'Column Layout - Mixed',
+ layout:'column',
+ items: [{
+ title: 'Column 1',
+ width: 120
+ },{
+ title: 'Column 2',
+ columnWidth: .8
+ },{
+ title: 'Column 3',
+ columnWidth: .2
+ }]
+});
+</code></pre>
+ */
+Ext.layout.ColumnLayout = Ext.extend(Ext.layout.ContainerLayout, {
+ // private
+ monitorResize:true,
+
+ type: 'column',
+
+ extraCls: 'x-column',
+
+ scrollOffset : 0,
+
+ // private
+
+ targetCls: 'x-column-layout-ct',
+
+ isValidParent : function(c, target){
+ return this.innerCt && c.getPositionEl().dom.parentNode == this.innerCt.dom;
+ },
+
+ getLayoutTargetSize : function() {
+ var target = this.container.getLayoutTarget(), ret;
+ if (target) {
+ ret = target.getViewSize();
+
+ // IE in strict mode will return a width of 0 on the 1st pass of getViewSize.
+ // Use getStyleSize to verify the 0 width, the adjustment pass will then work properly
+ // with getViewSize
+ if (Ext.isIE && Ext.isStrict && ret.width == 0){
+ ret = target.getStyleSize();
+ }
+
+ ret.width -= target.getPadding('lr');
+ ret.height -= target.getPadding('tb');
+ }
+ return ret;
+ },
+
+ renderAll : function(ct, target) {
+ if(!this.innerCt){
+ // the innerCt prevents wrapping and shuffling while
+ // the container is resizing
+ this.innerCt = target.createChild({cls:'x-column-inner'});
+ this.innerCt.createChild({cls:'x-clear'});
+ }
+ Ext.layout.ColumnLayout.superclass.renderAll.call(this, ct, this.innerCt);
+ },
+
+ // private
+ onLayout : function(ct, target){
+ var cs = ct.items.items,
+ len = cs.length,
+ c,
+ i,
+ m,
+ margins = [];
+
+ this.renderAll(ct, target);
+
+ var size = this.getLayoutTargetSize();
+
+ if(size.width < 1 && size.height < 1){ // display none?
+ return;
+ }
+
+ var w = size.width - this.scrollOffset,
+ h = size.height,
+ pw = w;
+
+ this.innerCt.setWidth(w);
+
+ // some columns can be percentages while others are fixed
+ // so we need to make 2 passes
+
+ for(i = 0; i < len; i++){
+ c = cs[i];
+ m = c.getPositionEl().getMargins('lr');
+ margins[i] = m;
+ if(!c.columnWidth){
+ pw -= (c.getWidth() + m);
+ }
+ }
+
+ pw = pw < 0 ? 0 : pw;
+
+ for(i = 0; i < len; i++){
+ c = cs[i];
+ m = margins[i];
+ if(c.columnWidth){
+ c.setSize(Math.floor(c.columnWidth * pw) - m);
+ }
+ }
+
+ // Browsers differ as to when they account for scrollbars. We need to re-measure to see if the scrollbar
+ // spaces were accounted for properly. If not, re-layout.
+ if (Ext.isIE) {
+ if (i = target.getStyle('overflow') && i != 'hidden' && !this.adjustmentPass) {
+ var ts = this.getLayoutTargetSize();
+ if (ts.width != size.width){
+ this.adjustmentPass = true;
+ this.onLayout(ct, target);
+ }
+ }
+ }
+ delete this.adjustmentPass;
+ }
+
+ /**
+ * @property activeItem
+ * @hide
+ */
+});
+
+Ext.Container.LAYOUTS['column'] = Ext.layout.ColumnLayout;
+/**
+ * @class Ext.layout.BorderLayout
+ * @extends Ext.layout.ContainerLayout
+ * <p>This is a multi-pane, application-oriented UI layout style that supports multiple
+ * nested panels, automatic {@link Ext.layout.BorderLayout.Region#split split} bars between
+ * {@link Ext.layout.BorderLayout.Region#BorderLayout.Region regions} and built-in
+ * {@link Ext.layout.BorderLayout.Region#collapsible expanding and collapsing} of regions.</p>
+ * <p>This class is intended to be extended or created via the <tt>layout:'border'</tt>
+ * {@link Ext.Container#layout} config, and should generally not need to be created directly
+ * via the new keyword.</p>
+ * <p>BorderLayout does not have any direct config options (other than inherited ones).
+ * All configuration options available for customizing the BorderLayout are at the
+ * {@link Ext.layout.BorderLayout.Region} and {@link Ext.layout.BorderLayout.SplitRegion}
+ * levels.</p>
+ * <p>Example usage:</p>
+ * <pre><code>
+var myBorderPanel = new Ext.Panel({
+ {@link Ext.Component#renderTo renderTo}: document.body,
+ {@link Ext.BoxComponent#width width}: 700,
+ {@link Ext.BoxComponent#height height}: 500,
+ {@link Ext.Panel#title title}: 'Border Layout',
+ {@link Ext.Container#layout layout}: 'border',
+ {@link Ext.Container#items items}: [{
+ {@link Ext.Panel#title title}: 'South Region is resizable',
+ {@link Ext.layout.BorderLayout.Region#BorderLayout.Region region}: 'south', // position for region
+ {@link Ext.BoxComponent#height height}: 100,
+ {@link Ext.layout.BorderLayout.Region#split split}: true, // enable resizing
+ {@link Ext.SplitBar#minSize minSize}: 75, // defaults to {@link Ext.layout.BorderLayout.Region#minHeight 50}
+ {@link Ext.SplitBar#maxSize maxSize}: 150,
+ {@link Ext.layout.BorderLayout.Region#margins margins}: '0 5 5 5'
+ },{
+ // xtype: 'panel' implied by default
+ {@link Ext.Panel#title title}: 'West Region is collapsible',
+ {@link Ext.layout.BorderLayout.Region#BorderLayout.Region region}:'west',
+ {@link Ext.layout.BorderLayout.Region#margins margins}: '5 0 0 5',
+ {@link Ext.BoxComponent#width width}: 200,
+ {@link Ext.layout.BorderLayout.Region#collapsible collapsible}: true, // make collapsible
+ {@link Ext.layout.BorderLayout.Region#cmargins cmargins}: '5 5 0 5', // adjust top margin when collapsed
+ {@link Ext.Component#id id}: 'west-region-container',
+ {@link Ext.Container#layout layout}: 'fit',
+ {@link Ext.Panel#unstyled unstyled}: true
+ },{
+ {@link Ext.Panel#title title}: 'Center Region',
+ {@link Ext.layout.BorderLayout.Region#BorderLayout.Region region}: 'center', // center region is required, no width/height specified
+ {@link Ext.Component#xtype xtype}: 'container',
+ {@link Ext.Container#layout layout}: 'fit',
+ {@link Ext.layout.BorderLayout.Region#margins margins}: '5 5 0 0'
}]
});
</code></pre>
+ * <p><b><u>Notes</u></b>:</p><div class="mdetail-params"><ul>
+ * <li>Any container using the BorderLayout <b>must</b> have a child item with <tt>region:'center'</tt>.
+ * The child item in the center region will always be resized to fill the remaining space not used by
+ * the other regions in the layout.</li>
+ * <li>Any child items with a region of <tt>west</tt> or <tt>east</tt> must have <tt>width</tt> defined
+ * (an integer representing the number of pixels that the region should take up).</li>
+ * <li>Any child items with a region of <tt>north</tt> or <tt>south</tt> must have <tt>height</tt> defined.</li>
+ * <li>The regions of a BorderLayout are <b>fixed at render time</b> and thereafter, its child Components may not be removed or added</b>. To add/remove
+ * Components within a BorderLayout, have them wrapped by an additional Container which is directly
+ * managed by the BorderLayout. If the region is to be collapsible, the Container used directly
+ * by the BorderLayout manager should be a Panel. In the following example a Container (an Ext.Panel)
+ * is added to the west region:
+ * <div style="margin-left:16px"><pre><code>
+wrc = {@link Ext#getCmp Ext.getCmp}('west-region-container');
+wrc.{@link Ext.Panel#removeAll removeAll}();
+wrc.{@link Ext.Container#add add}({
+ title: 'Added Panel',
+ html: 'Some content'
+});
+wrc.{@link Ext.Container#doLayout doLayout}();
+ * </code></pre></div>
+ * </li>
+ * <li> To reference a {@link Ext.layout.BorderLayout.Region Region}:
+ * <div style="margin-left:16px"><pre><code>
+wr = myBorderPanel.layout.west;
+ * </code></pre></div>
+ * </li>
+ * </ul></div>
*/
-Ext.layout.CardLayout = Ext.extend(Ext.layout.FitLayout, {
- /**
- * @cfg {Boolean} deferredRender
- * True to render each contained item at the time it becomes active, false to render all contained items
- * as soon as the layout is rendered (defaults to false). If there is a significant amount of content or
- * a lot of heavy controls being rendered into panels that are not displayed by default, setting this to
- * true might improve performance.
- */
- deferredRender : false,
-
- /**
- * @cfg {Boolean} layoutOnCardChange
- * True to force a layout of the active item when the active card is changed. Defaults to false.
- */
- layoutOnCardChange : false,
-
- /**
- * @cfg {Boolean} renderHidden @hide
- */
+Ext.layout.BorderLayout = Ext.extend(Ext.layout.ContainerLayout, {
// private
- renderHidden : true,
+ monitorResize:true,
+ // private
+ rendered : false,
- type: 'card',
+ type: 'border',
- /**
- * Sets the active (visible) item in the layout.
- * @param {String/Number} item The string component id or numeric index of the item to activate
- */
- setActiveItem : function(item){
- var ai = this.activeItem,
- ct = this.container;
- item = ct.getComponent(item);
+ targetCls: 'x-border-layout-ct',
- // Is this a valid, different card?
- if(item && ai != item){
+ getLayoutTargetSize : function() {
+ var target = this.container.getLayoutTarget();
+ return target ? target.getViewSize() : {};
+ },
- // Changing cards, hide the current one
- if(ai){
- ai.hide();
- if (ai.hidden !== true) {
- return false;
+ // private
+ onLayout : function(ct, target){
+ var collapsed, i, c, pos, items = ct.items.items, len = items.length;
+ if(!this.rendered){
+ collapsed = [];
+ for(i = 0; i < len; i++) {
+ c = items[i];
+ pos = c.region;
+ if(c.collapsed){
+ collapsed.push(c);
}
- ai.fireEvent('deactivate', ai);
+ c.collapsed = false;
+ if(!c.rendered){
+ c.render(target, i);
+ c.getPositionEl().addClass('x-border-panel');
+ }
+ this[pos] = pos != 'center' && c.split ?
+ new Ext.layout.BorderLayout.SplitRegion(this, c.initialConfig, pos) :
+ new Ext.layout.BorderLayout.Region(this, c.initialConfig, pos);
+ this[pos].render(target, c);
}
+ this.rendered = true;
+ }
- var layout = item.doLayout && (this.layoutOnCardChange || !item.rendered);
-
- // Change activeItem reference
- this.activeItem = item;
-
- // The container is about to get a recursive layout, remove any deferLayout reference
- // because it will trigger a redundant layout.
- delete item.deferLayout;
-
- // Show the new component
- item.show();
+ var size = this.getLayoutTargetSize();
+ if(size.width < 20 || size.height < 20){ // display none?
+ if(collapsed){
+ this.restoreCollapsed = collapsed;
+ }
+ return;
+ }else if(this.restoreCollapsed){
+ collapsed = this.restoreCollapsed;
+ delete this.restoreCollapsed;
+ }
- this.layout();
+ var w = size.width, h = size.height,
+ centerW = w, centerH = h, centerY = 0, centerX = 0,
+ n = this.north, s = this.south, west = this.west, e = this.east, c = this.center,
+ b, m, totalWidth, totalHeight;
+ if(!c && Ext.layout.BorderLayout.WARN !== false){
+ throw 'No center region defined in BorderLayout ' + ct.id;
+ }
- if(layout){
- item.doLayout();
+ if(n && n.isVisible()){
+ b = n.getSize();
+ m = n.getMargins();
+ b.width = w - (m.left+m.right);
+ b.x = m.left;
+ b.y = m.top;
+ centerY = b.height + b.y + m.bottom;
+ centerH -= centerY;
+ n.applyLayout(b);
+ }
+ if(s && s.isVisible()){
+ b = s.getSize();
+ m = s.getMargins();
+ b.width = w - (m.left+m.right);
+ b.x = m.left;
+ totalHeight = (b.height + m.top + m.bottom);
+ b.y = h - totalHeight + m.top;
+ centerH -= totalHeight;
+ s.applyLayout(b);
+ }
+ if(west && west.isVisible()){
+ b = west.getSize();
+ m = west.getMargins();
+ b.height = centerH - (m.top+m.bottom);
+ b.x = m.left;
+ b.y = centerY + m.top;
+ totalWidth = (b.width + m.left + m.right);
+ centerX += totalWidth;
+ centerW -= totalWidth;
+ west.applyLayout(b);
+ }
+ if(e && e.isVisible()){
+ b = e.getSize();
+ m = e.getMargins();
+ b.height = centerH - (m.top+m.bottom);
+ totalWidth = (b.width + m.left + m.right);
+ b.x = w - totalWidth + m.left;
+ b.y = centerY + m.top;
+ centerW -= totalWidth;
+ e.applyLayout(b);
+ }
+ if(c){
+ m = c.getMargins();
+ var centerBox = {
+ x: centerX + m.left,
+ y: centerY + m.top,
+ width: centerW - (m.left+m.right),
+ height: centerH - (m.top+m.bottom)
+ };
+ c.applyLayout(centerBox);
+ }
+ if(collapsed){
+ for(i = 0, len = collapsed.length; i < len; i++){
+ collapsed[i].collapse(false);
+ }
+ }
+ if(Ext.isIE && Ext.isStrict){ // workaround IE strict repainting issue
+ target.repaint();
+ }
+ // Putting a border layout into an overflowed container is NOT correct and will make a second layout pass necessary.
+ if (i = target.getStyle('overflow') && i != 'hidden' && !this.adjustmentPass) {
+ var ts = this.getLayoutTargetSize();
+ if (ts.width != size.width || ts.height != size.height){
+ this.adjustmentPass = true;
+ this.onLayout(ct, target);
}
- item.fireEvent('activate', item);
}
+ delete this.adjustmentPass;
},
- // private
- renderAll : function(ct, target){
- if(this.deferredRender){
- this.renderItem(this.activeItem, undefined, target);
- }else{
- Ext.layout.CardLayout.superclass.renderAll.call(this, ct, target);
+ destroy: function() {
+ var r = ['north', 'south', 'east', 'west'], i, region;
+ for (i = 0; i < r.length; i++) {
+ region = this[r[i]];
+ if(region){
+ if(region.destroy){
+ region.destroy();
+ }else if (region.split){
+ region.split.destroy(true);
+ }
+ }
}
+ Ext.layout.BorderLayout.superclass.destroy.call(this);
}
+
+ /**
+ * @property activeItem
+ * @hide
+ */
});
-Ext.Container.LAYOUTS['card'] = Ext.layout.CardLayout;
+
/**
- * @class Ext.layout.AnchorLayout
- * @extends Ext.layout.ContainerLayout
- * <p>This is a layout that enables anchoring of contained elements relative to the container's dimensions.
- * If the container is resized, all anchored items are automatically rerendered according to their
- * <b><tt>{@link #anchor}</tt></b> rules.</p>
- * <p>This class is intended to be extended or created via the layout:'anchor' {@link Ext.Container#layout}
- * config, and should generally not need to be created directly via the new keyword.</p>
- * <p>AnchorLayout does not have any direct config options (other than inherited ones). By default,
- * AnchorLayout will calculate anchor measurements based on the size of the container itself. However, the
- * container using the AnchorLayout can supply an anchoring-specific config property of <b>anchorSize</b>.
- * If anchorSize is specifed, the layout will use it as a virtual container for the purposes of calculating
- * anchor measurements based on it instead, allowing the container to be sized independently of the anchoring
- * logic if necessary. For example:</p>
- * <pre><code>
-var viewport = new Ext.Viewport({
- layout:'anchor',
- anchorSize: {width:800, height:600},
- items:[{
- title:'Item 1',
- html:'Content 1',
- width:800,
- anchor:'right 20%'
- },{
- title:'Item 2',
- html:'Content 2',
- width:300,
- anchor:'50% 30%'
- },{
- title:'Item 3',
- html:'Content 3',
- width:600,
- anchor:'-100 50%'
- }]
-});
- * </code></pre>
+ * @class Ext.layout.BorderLayout.Region
+ * <p>This is a region of a {@link Ext.layout.BorderLayout BorderLayout} that acts as a subcontainer
+ * within the layout. Each region has its own {@link Ext.layout.ContainerLayout layout} that is
+ * independent of other regions and the containing BorderLayout, and can be any of the
+ * {@link Ext.layout.ContainerLayout valid Ext layout types}.</p>
+ * <p>Region size is managed automatically and cannot be changed by the user -- for
+ * {@link #split resizable regions}, see {@link Ext.layout.BorderLayout.SplitRegion}.</p>
+ * @constructor
+ * Create a new Region.
+ * @param {Layout} layout The {@link Ext.layout.BorderLayout BorderLayout} instance that is managing this Region.
+ * @param {Object} config The configuration options
+ * @param {String} position The region position. Valid values are: <tt>north</tt>, <tt>south</tt>,
+ * <tt>east</tt>, <tt>west</tt> and <tt>center</tt>. Every {@link Ext.layout.BorderLayout BorderLayout}
+ * <b>must have a center region</b> for the primary content -- all other regions are optional.
*/
-Ext.layout.AnchorLayout = Ext.extend(Ext.layout.ContainerLayout, {
+Ext.layout.BorderLayout.Region = function(layout, config, pos){
+ Ext.apply(this, config);
+ this.layout = layout;
+ this.position = pos;
+ this.state = {};
+ if(typeof this.margins == 'string'){
+ this.margins = this.layout.parseMargins(this.margins);
+ }
+ this.margins = Ext.applyIf(this.margins || {}, this.defaultMargins);
+ if(this.collapsible){
+ if(typeof this.cmargins == 'string'){
+ this.cmargins = this.layout.parseMargins(this.cmargins);
+ }
+ if(this.collapseMode == 'mini' && !this.cmargins){
+ this.cmargins = {left:0,top:0,right:0,bottom:0};
+ }else{
+ this.cmargins = Ext.applyIf(this.cmargins || {},
+ pos == 'north' || pos == 'south' ? this.defaultNSCMargins : this.defaultEWCMargins);
+ }
+ }
+};
+
+Ext.layout.BorderLayout.Region.prototype = {
/**
- * @cfg {String} anchor
- * <p>This configuation option is to be applied to <b>child <tt>items</tt></b> of a container managed by
- * this layout (ie. configured with <tt>layout:'anchor'</tt>).</p><br/>
- *
- * <p>This value is what tells the layout how an item should be anchored to the container. <tt>items</tt>
- * added to an AnchorLayout accept an anchoring-specific config property of <b>anchor</b> which is a string
- * containing two values: the horizontal anchor value and the vertical anchor value (for example, '100% 50%').
- * The following types of anchor values are supported:<div class="mdetail-params"><ul>
- *
- * <li><b>Percentage</b> : Any value between 1 and 100, expressed as a percentage.<div class="sub-desc">
- * The first anchor is the percentage width that the item should take up within the container, and the
- * second is the percentage height. For example:<pre><code>
-// two values specified
-anchor: '100% 50%' // render item complete width of the container and
- // 1/2 height of the container
-// one value specified
-anchor: '100%' // the width value; the height will default to auto
- * </code></pre></div></li>
- *
- * <li><b>Offsets</b> : Any positive or negative integer value.<div class="sub-desc">
- * This is a raw adjustment where the first anchor is the offset from the right edge of the container,
- * and the second is the offset from the bottom edge. For example:<pre><code>
-// two values specified
-anchor: '-50 -100' // render item the complete width of the container
- // minus 50 pixels and
- // the complete height minus 100 pixels.
-// one value specified
-anchor: '-50' // anchor value is assumed to be the right offset value
- // bottom offset will default to 0
- * </code></pre></div></li>
- *
- * <li><b>Sides</b> : Valid values are <tt>'right'</tt> (or <tt>'r'</tt>) and <tt>'bottom'</tt>
- * (or <tt>'b'</tt>).<div class="sub-desc">
- * Either the container must have a fixed size or an anchorSize config value defined at render time in
- * order for these to have any effect.</div></li>
- *
- * <li><b>Mixed</b> : <div class="sub-desc">
- * Anchor values can also be mixed as needed. For example, to render the width offset from the container
- * right edge by 50 pixels and 75% of the container's height use:
- * <pre><code>
-anchor: '-50 75%'
- * </code></pre></div></li>
- *
- *
+ * @cfg {Boolean} animFloat
+ * When a collapsed region's bar is clicked, the region's panel will be displayed as a floated
+ * panel that will close again once the user mouses out of that panel (or clicks out if
+ * <tt>{@link #autoHide} = false</tt>). Setting <tt>{@link #animFloat} = false</tt> will
+ * prevent the open and close of these floated panels from being animated (defaults to <tt>true</tt>).
+ */
+ /**
+ * @cfg {Boolean} autoHide
+ * When a collapsed region's bar is clicked, the region's panel will be displayed as a floated
+ * panel. If <tt>autoHide = true</tt>, the panel will automatically hide after the user mouses
+ * out of the panel. If <tt>autoHide = false</tt>, the panel will continue to display until the
+ * user clicks outside of the panel (defaults to <tt>true</tt>).
+ */
+ /**
+ * @cfg {String} collapseMode
+ * <tt>collapseMode</tt> supports two configuration values:<div class="mdetail-params"><ul>
+ * <li><b><tt>undefined</tt></b> (default)<div class="sub-desc">By default, {@link #collapsible}
+ * regions are collapsed by clicking the expand/collapse tool button that renders into the region's
+ * title bar.</div></li>
+ * <li><b><tt>'mini'</tt></b><div class="sub-desc">Optionally, when <tt>collapseMode</tt> is set to
+ * <tt>'mini'</tt> the region's split bar will also display a small collapse button in the center of
+ * the bar. In <tt>'mini'</tt> mode the region will collapse to a thinner bar than in normal mode.
+ * </div></li>
+ * </ul></div></p>
+ * <p><b>Note</b>: if a collapsible region does not have a title bar, then set <tt>collapseMode =
+ * 'mini'</tt> and <tt>{@link #split} = true</tt> in order for the region to be {@link #collapsible}
+ * by the user as the expand/collapse tool button (that would go in the title bar) will not be rendered.</p>
+ * <p>See also <tt>{@link #cmargins}</tt>.</p>
+ */
+ /**
+ * @cfg {Object} margins
+ * An object containing margins to apply to the region when in the expanded state in the
+ * format:<pre><code>
+{
+ top: (top margin),
+ right: (right margin),
+ bottom: (bottom margin),
+ left: (left margin)
+}</code></pre>
+ * <p>May also be a string containing space-separated, numeric margin values. The order of the
+ * sides associated with each value matches the way CSS processes margin values:</p>
+ * <p><div class="mdetail-params"><ul>
+ * <li>If there is only one value, it applies to all sides.</li>
+ * <li>If there are two values, the top and bottom borders are set to the first value and the
+ * right and left are set to the second.</li>
+ * <li>If there are three values, the top is set to the first value, the left and right are set
+ * to the second, and the bottom is set to the third.</li>
+ * <li>If there are four values, they apply to the top, right, bottom, and left, respectively.</li>
+ * </ul></div></p>
+ * <p>Defaults to:</p><pre><code>
+ * {top:0, right:0, bottom:0, left:0}
+ * </code></pre>
+ */
+ /**
+ * @cfg {Object} cmargins
+ * An object containing margins to apply to the region when in the collapsed state in the
+ * format:<pre><code>
+{
+ top: (top margin),
+ right: (right margin),
+ bottom: (bottom margin),
+ left: (left margin)
+}</code></pre>
+ * <p>May also be a string containing space-separated, numeric margin values. The order of the
+ * sides associated with each value matches the way CSS processes margin values.</p>
+ * <p><ul>
+ * <li>If there is only one value, it applies to all sides.</li>
+ * <li>If there are two values, the top and bottom borders are set to the first value and the
+ * right and left are set to the second.</li>
+ * <li>If there are three values, the top is set to the first value, the left and right are set
+ * to the second, and the bottom is set to the third.</li>
+ * <li>If there are four values, they apply to the top, right, bottom, and left, respectively.</li>
+ * </ul></p>
+ */
+ /**
+ * @cfg {Boolean} collapsible
+ * <p><tt>true</tt> to allow the user to collapse this region (defaults to <tt>false</tt>). If
+ * <tt>true</tt>, an expand/collapse tool button will automatically be rendered into the title
+ * bar of the region, otherwise the button will not be shown.</p>
+ * <p><b>Note</b>: that a title bar is required to display the collapse/expand toggle button -- if
+ * no <tt>title</tt> is specified for the region's panel, the region will only be collapsible if
+ * <tt>{@link #collapseMode} = 'mini'</tt> and <tt>{@link #split} = true</tt>.
+ */
+ collapsible : false,
+ /**
+ * @cfg {Boolean} split
+ * <p><tt>true</tt> to create a {@link Ext.layout.BorderLayout.SplitRegion SplitRegion} and
+ * display a 5px wide {@link Ext.SplitBar} between this region and its neighbor, allowing the user to
+ * resize the regions dynamically. Defaults to <tt>false</tt> creating a
+ * {@link Ext.layout.BorderLayout.Region Region}.</p><br>
+ * <p><b>Notes</b>:</p><div class="mdetail-params"><ul>
+ * <li>this configuration option is ignored if <tt>region='center'</tt></li>
+ * <li>when <tt>split == true</tt>, it is common to specify a
+ * <tt>{@link Ext.SplitBar#minSize minSize}</tt> and <tt>{@link Ext.SplitBar#maxSize maxSize}</tt>
+ * for the {@link Ext.BoxComponent BoxComponent} representing the region. These are not native
+ * configs of {@link Ext.BoxComponent BoxComponent}, and are used only by this class.</li>
+ * <li>if <tt>{@link #collapseMode} = 'mini'</tt> requires <tt>split = true</tt> to reserve space
+ * for the collapse tool</tt></li>
* </ul></div>
*/
+ split:false,
+ /**
+ * @cfg {Boolean} floatable
+ * <tt>true</tt> to allow clicking a collapsed region's bar to display the region's panel floated
+ * above the layout, <tt>false</tt> to force the user to fully expand a collapsed region by
+ * clicking the expand button to see it again (defaults to <tt>true</tt>).
+ */
+ floatable: true,
+ /**
+ * @cfg {Number} minWidth
+ * <p>The minimum allowable width in pixels for this region (defaults to <tt>50</tt>).
+ * <tt>maxWidth</tt> may also be specified.</p><br>
+ * <p><b>Note</b>: setting the <tt>{@link Ext.SplitBar#minSize minSize}</tt> /
+ * <tt>{@link Ext.SplitBar#maxSize maxSize}</tt> supersedes any specified
+ * <tt>minWidth</tt> / <tt>maxWidth</tt>.</p>
+ */
+ minWidth:50,
+ /**
+ * @cfg {Number} minHeight
+ * The minimum allowable height in pixels for this region (defaults to <tt>50</tt>)
+ * <tt>maxHeight</tt> may also be specified.</p><br>
+ * <p><b>Note</b>: setting the <tt>{@link Ext.SplitBar#minSize minSize}</tt> /
+ * <tt>{@link Ext.SplitBar#maxSize maxSize}</tt> supersedes any specified
+ * <tt>minHeight</tt> / <tt>maxHeight</tt>.</p>
+ */
+ minHeight:50,
// private
- monitorResize : true,
-
- type : 'anchor',
+ defaultMargins : {left:0,top:0,right:0,bottom:0},
+ // private
+ defaultNSCMargins : {left:5,top:5,right:5,bottom:5},
+ // private
+ defaultEWCMargins : {left:5,top:0,right:5,bottom:0},
+ floatingZIndex: 100,
/**
- * @cfg {String} defaultAnchor
- *
- * default anchor for all child container items applied if no anchor or specific width is set on the child item. Defaults to '100%'.
- *
+ * True if this region is collapsed. Read-only.
+ * @type Boolean
+ * @property
*/
- defaultAnchor : '100%',
-
- parseAnchorRE : /^(r|right|b|bottom)$/i,
+ isCollapsed : false,
- getLayoutTargetSize : function() {
- var target = this.container.getLayoutTarget();
- if (!target) {
- return {};
- }
- // Style Sized (scrollbars not included)
- return target.getStyleSize();
- },
+ /**
+ * This region's panel. Read-only.
+ * @type Ext.Panel
+ * @property panel
+ */
+ /**
+ * This region's layout. Read-only.
+ * @type Layout
+ * @property layout
+ */
+ /**
+ * This region's layout position (north, south, east, west or center). Read-only.
+ * @type String
+ * @property position
+ */
// private
- onLayout : function(ct, target){
- Ext.layout.AnchorLayout.superclass.onLayout.call(this, ct, target);
- var size = this.getLayoutTargetSize();
-
- var w = size.width, h = size.height;
+ render : function(ct, p){
+ this.panel = p;
+ p.el.enableDisplayMode();
+ this.targetEl = ct;
+ this.el = p.el;
- if(w < 20 && h < 20){
- return;
- }
+ var gs = p.getState, ps = this.position;
+ p.getState = function(){
+ return Ext.apply(gs.call(p) || {}, this.state);
+ }.createDelegate(this);
- // find the container anchoring size
- var aw, ah;
- if(ct.anchorSize){
- if(typeof ct.anchorSize == 'number'){
- aw = ct.anchorSize;
- }else{
- aw = ct.anchorSize.width;
- ah = ct.anchorSize.height;
+ if(ps != 'center'){
+ p.allowQueuedExpand = false;
+ p.on({
+ beforecollapse: this.beforeCollapse,
+ collapse: this.onCollapse,
+ beforeexpand: this.beforeExpand,
+ expand: this.onExpand,
+ hide: this.onHide,
+ show: this.onShow,
+ scope: this
+ });
+ if(this.collapsible || this.floatable){
+ p.collapseEl = 'el';
+ p.slideAnchor = this.getSlideAnchor();
+ }
+ if(p.tools && p.tools.toggle){
+ p.tools.toggle.addClass('x-tool-collapse-'+ps);
+ p.tools.toggle.addClassOnOver('x-tool-collapse-'+ps+'-over');
}
- }else{
- aw = ct.initialConfig.width;
- ah = ct.initialConfig.height;
}
+ },
- var cs = this.getRenderedItems(ct), len = cs.length, i, c, a, cw, ch, el, vs, boxes = [];
- for(i = 0; i < len; i++){
- c = cs[i];
- el = c.getPositionEl();
-
- // If a child container item has no anchor and no specific width, set the child to the default anchor size
- if (!c.anchor && c.items && !Ext.isNumber(c.width) && !(Ext.isIE6 && Ext.isStrict)){
- c.anchor = this.defaultAnchor;
+ // private
+ getCollapsedEl : function(){
+ if(!this.collapsedEl){
+ if(!this.toolTemplate){
+ var tt = new Ext.Template(
+ '<div class="x-tool x-tool-{id}"> </div>'
+ );
+ tt.disableFormats = true;
+ tt.compile();
+ Ext.layout.BorderLayout.Region.prototype.toolTemplate = tt;
}
+ this.collapsedEl = this.targetEl.createChild({
+ cls: "x-layout-collapsed x-layout-collapsed-"+this.position,
+ id: this.panel.id + '-xcollapsed'
+ });
+ this.collapsedEl.enableDisplayMode('block');
- if(c.anchor){
- a = c.anchorSpec;
- if(!a){ // cache all anchor values
- vs = c.anchor.split(' ');
- c.anchorSpec = a = {
- right: this.parseAnchor(vs[0], c.initialConfig.width, aw),
- bottom: this.parseAnchor(vs[1], c.initialConfig.height, ah)
- };
+ if(this.collapseMode == 'mini'){
+ this.collapsedEl.addClass('x-layout-cmini-'+this.position);
+ this.miniCollapsedEl = this.collapsedEl.createChild({
+ cls: "x-layout-mini x-layout-mini-"+this.position, html: " "
+ });
+ this.miniCollapsedEl.addClassOnOver('x-layout-mini-over');
+ this.collapsedEl.addClassOnOver("x-layout-collapsed-over");
+ this.collapsedEl.on('click', this.onExpandClick, this, {stopEvent:true});
+ }else {
+ if(this.collapsible !== false && !this.hideCollapseTool) {
+ var t = this.expandToolEl = this.toolTemplate.append(
+ this.collapsedEl.dom,
+ {id:'expand-'+this.position}, true);
+ t.addClassOnOver('x-tool-expand-'+this.position+'-over');
+ t.on('click', this.onExpandClick, this, {stopEvent:true});
}
- cw = a.right ? this.adjustWidthAnchor(a.right(w) - el.getMargins('lr'), c) : undefined;
- ch = a.bottom ? this.adjustHeightAnchor(a.bottom(h) - el.getMargins('tb'), c) : undefined;
-
- if(cw || ch){
- boxes.push({
- comp: c,
- width: cw || undefined,
- height: ch || undefined
- });
+ if(this.floatable !== false || this.titleCollapse){
+ this.collapsedEl.addClassOnOver("x-layout-collapsed-over");
+ this.collapsedEl.on("click", this[this.floatable ? 'collapseClick' : 'onExpandClick'], this);
}
}
}
- for (i = 0, len = boxes.length; i < len; i++) {
- c = boxes[i];
- c.comp.setSize(c.width, c.height);
- }
+ return this.collapsedEl;
},
// private
- parseAnchor : function(a, start, cstart){
- if(a && a != 'none'){
- var last;
- // standard anchor
- if(this.parseAnchorRE.test(a)){
- var diff = cstart - start;
- return function(v){
- if(v !== last){
- last = v;
- return v - diff;
- }
- }
- // percentage
- }else if(a.indexOf('%') != -1){
- var ratio = parseFloat(a.replace('%', ''))*.01;
- return function(v){
- if(v !== last){
- last = v;
- return Math.floor(v*ratio);
- }
- }
- // simple offset adjustment
- }else{
- a = parseInt(a, 10);
- if(!isNaN(a)){
- return function(v){
- if(v !== last){
- last = v;
- return v + a;
- }
- }
- }
- }
+ onExpandClick : function(e){
+ if(this.isSlid){
+ this.panel.expand(false);
+ }else{
+ this.panel.expand();
}
- return false;
},
// private
- adjustWidthAnchor : function(value, comp){
- return value;
+ onCollapseClick : function(e){
+ this.panel.collapse();
},
// private
- adjustHeightAnchor : function(value, comp){
- return value;
- }
-
- /**
- * @property activeItem
- * @hide
- */
-});
-Ext.Container.LAYOUTS['anchor'] = Ext.layout.AnchorLayout;
-/**
- * @class Ext.layout.ColumnLayout
- * @extends Ext.layout.ContainerLayout
- * <p>This is the layout style of choice for creating structural layouts in a multi-column format where the width of
- * each column can be specified as a percentage or fixed width, but the height is allowed to vary based on the content.
- * This class is intended to be extended or created via the layout:'column' {@link Ext.Container#layout} config,
- * and should generally not need to be created directly via the new keyword.</p>
- * <p>ColumnLayout does not have any direct config options (other than inherited ones), but it does support a
- * specific config property of <b><tt>columnWidth</tt></b> that can be included in the config of any panel added to it. The
- * layout will use the columnWidth (if present) or width of each panel during layout to determine how to size each panel.
- * If width or columnWidth is not specified for a given panel, its width will default to the panel's width (or auto).</p>
- * <p>The width property is always evaluated as pixels, and must be a number greater than or equal to 1.
- * The columnWidth property is always evaluated as a percentage, and must be a decimal value greater than 0 and
- * less than 1 (e.g., .25).</p>
- * <p>The basic rules for specifying column widths are pretty simple. The logic makes two passes through the
- * set of contained panels. During the first layout pass, all panels that either have a fixed width or none
- * specified (auto) are skipped, but their widths are subtracted from the overall container width. During the second
- * pass, all panels with columnWidths are assigned pixel widths in proportion to their percentages based on
- * the total <b>remaining</b> container width. In other words, percentage width panels are designed to fill the space
- * left over by all the fixed-width and/or auto-width panels. Because of this, while you can specify any number of columns
- * with different percentages, the columnWidths must always add up to 1 (or 100%) when added together, otherwise your
- * layout may not render as expected. Example usage:</p>
- * <pre><code>
-// All columns are percentages -- they must add up to 1
-var p = new Ext.Panel({
- title: 'Column Layout - Percentage Only',
- layout:'column',
- items: [{
- title: 'Column 1',
- columnWidth: .25
- },{
- title: 'Column 2',
- columnWidth: .6
- },{
- title: 'Column 3',
- columnWidth: .15
- }]
-});
+ beforeCollapse : function(p, animate){
+ this.lastAnim = animate;
+ if(this.splitEl){
+ this.splitEl.hide();
+ }
+ this.getCollapsedEl().show();
+ var el = this.panel.getEl();
+ this.originalZIndex = el.getStyle('z-index');
+ el.setStyle('z-index', 100);
+ this.isCollapsed = true;
+ this.layout.layout();
+ },
-// Mix of width and columnWidth -- all columnWidth values must add up
-// to 1. The first column will take up exactly 120px, and the last two
-// columns will fill the remaining container width.
-var p = new Ext.Panel({
- title: 'Column Layout - Mixed',
- layout:'column',
- items: [{
- title: 'Column 1',
- width: 120
- },{
- title: 'Column 2',
- columnWidth: .8
- },{
- title: 'Column 3',
- columnWidth: .2
- }]
-});
-</code></pre>
- */
-Ext.layout.ColumnLayout = Ext.extend(Ext.layout.ContainerLayout, {
// private
- monitorResize:true,
-
- type: 'column',
-
- extraCls: 'x-column',
-
- scrollOffset : 0,
+ onCollapse : function(animate){
+ this.panel.el.setStyle('z-index', 1);
+ if(this.lastAnim === false || this.panel.animCollapse === false){
+ this.getCollapsedEl().dom.style.visibility = 'visible';
+ }else{
+ this.getCollapsedEl().slideIn(this.panel.slideAnchor, {duration:.2});
+ }
+ this.state.collapsed = true;
+ this.panel.saveState();
+ },
// private
-
- targetCls: 'x-column-layout-ct',
-
- isValidParent : function(c, target){
- return this.innerCt && c.getPositionEl().dom.parentNode == this.innerCt.dom;
+ beforeExpand : function(animate){
+ if(this.isSlid){
+ this.afterSlideIn();
+ }
+ var c = this.getCollapsedEl();
+ this.el.show();
+ if(this.position == 'east' || this.position == 'west'){
+ this.panel.setSize(undefined, c.getHeight());
+ }else{
+ this.panel.setSize(c.getWidth(), undefined);
+ }
+ c.hide();
+ c.dom.style.visibility = 'hidden';
+ this.panel.el.setStyle('z-index', this.floatingZIndex);
},
- getLayoutTargetSize : function() {
- var target = this.container.getLayoutTarget(), ret;
- if (target) {
- ret = target.getViewSize();
-
- // IE in strict mode will return a width of 0 on the 1st pass of getViewSize.
- // Use getStyleSize to verify the 0 width, the adjustment pass will then work properly
- // with getViewSize
- if (Ext.isIE && Ext.isStrict && ret.width == 0){
- ret = target.getStyleSize();
- }
-
- ret.width -= target.getPadding('lr');
- ret.height -= target.getPadding('tb');
+ // private
+ onExpand : function(){
+ this.isCollapsed = false;
+ if(this.splitEl){
+ this.splitEl.show();
}
- return ret;
+ this.layout.layout();
+ this.panel.el.setStyle('z-index', this.originalZIndex);
+ this.state.collapsed = false;
+ this.panel.saveState();
},
- renderAll : function(ct, target) {
- if(!this.innerCt){
- // the innerCt prevents wrapping and shuffling while
- // the container is resizing
- this.innerCt = target.createChild({cls:'x-column-inner'});
- this.innerCt.createChild({cls:'x-clear'});
+ // private
+ collapseClick : function(e){
+ if(this.isSlid){
+ e.stopPropagation();
+ this.slideIn();
+ }else{
+ e.stopPropagation();
+ this.slideOut();
}
- Ext.layout.ColumnLayout.superclass.renderAll.call(this, ct, this.innerCt);
},
// private
- onLayout : function(ct, target){
- var cs = ct.items.items,
- len = cs.length,
- c,
- i,
- m,
- margins = [];
-
- this.renderAll(ct, target);
-
- var size = this.getLayoutTargetSize();
-
- if(size.width < 1 && size.height < 1){ // display none?
- return;
+ onHide : function(){
+ if(this.isCollapsed){
+ this.getCollapsedEl().hide();
+ }else if(this.splitEl){
+ this.splitEl.hide();
}
+ },
- var w = size.width - this.scrollOffset,
- h = size.height,
- pw = w;
-
- this.innerCt.setWidth(w);
+ // private
+ onShow : function(){
+ if(this.isCollapsed){
+ this.getCollapsedEl().show();
+ }else if(this.splitEl){
+ this.splitEl.show();
+ }
+ },
- // some columns can be percentages while others are fixed
- // so we need to make 2 passes
+ /**
+ * True if this region is currently visible, else false.
+ * @return {Boolean}
+ */
+ isVisible : function(){
+ return !this.panel.hidden;
+ },
- for(i = 0; i < len; i++){
- c = cs[i];
- m = c.getPositionEl().getMargins('lr');
- margins[i] = m;
- if(!c.columnWidth){
- pw -= (c.getWidth() + m);
- }
- }
+ /**
+ * Returns the current margins for this region. If the region is collapsed, the
+ * {@link #cmargins} (collapsed margins) value will be returned, otherwise the
+ * {@link #margins} value will be returned.
+ * @return {Object} An object containing the element's margins: <tt>{left: (left
+ * margin), top: (top margin), right: (right margin), bottom: (bottom margin)}</tt>
+ */
+ getMargins : function(){
+ return this.isCollapsed && this.cmargins ? this.cmargins : this.margins;
+ },
- pw = pw < 0 ? 0 : pw;
+ /**
+ * Returns the current size of this region. If the region is collapsed, the size of the
+ * collapsedEl will be returned, otherwise the size of the region's panel will be returned.
+ * @return {Object} An object containing the element's size: <tt>{width: (element width),
+ * height: (element height)}</tt>
+ */
+ getSize : function(){
+ return this.isCollapsed ? this.getCollapsedEl().getSize() : this.panel.getSize();
+ },
- for(i = 0; i < len; i++){
- c = cs[i];
- m = margins[i];
- if(c.columnWidth){
- c.setSize(Math.floor(c.columnWidth * pw) - m);
- }
- }
+ /**
+ * Sets the specified panel as the container element for this region.
+ * @param {Ext.Panel} panel The new panel
+ */
+ setPanel : function(panel){
+ this.panel = panel;
+ },
- // Browsers differ as to when they account for scrollbars. We need to re-measure to see if the scrollbar
- // spaces were accounted for properly. If not, re-layout.
- if (Ext.isIE) {
- if (i = target.getStyle('overflow') && i != 'hidden' && !this.adjustmentPass) {
- var ts = this.getLayoutTargetSize();
- if (ts.width != size.width){
- this.adjustmentPass = true;
- this.onLayout(ct, target);
- }
- }
- }
- delete this.adjustmentPass;
- }
+ /**
+ * Returns the minimum allowable width for this region.
+ * @return {Number} The minimum width
+ */
+ getMinWidth: function(){
+ return this.minWidth;
+ },
/**
- * @property activeItem
- * @hide
+ * Returns the minimum allowable height for this region.
+ * @return {Number} The minimum height
*/
-});
+ getMinHeight: function(){
+ return this.minHeight;
+ },
-Ext.Container.LAYOUTS['column'] = Ext.layout.ColumnLayout;
-/**
- * @class Ext.layout.BorderLayout
- * @extends Ext.layout.ContainerLayout
- * <p>This is a multi-pane, application-oriented UI layout style that supports multiple
- * nested panels, automatic {@link Ext.layout.BorderLayout.Region#split split} bars between
- * {@link Ext.layout.BorderLayout.Region#BorderLayout.Region regions} and built-in
- * {@link Ext.layout.BorderLayout.Region#collapsible expanding and collapsing} of regions.</p>
- * <p>This class is intended to be extended or created via the <tt>layout:'border'</tt>
- * {@link Ext.Container#layout} config, and should generally not need to be created directly
- * via the new keyword.</p>
- * <p>BorderLayout does not have any direct config options (other than inherited ones).
- * All configuration options available for customizing the BorderLayout are at the
- * {@link Ext.layout.BorderLayout.Region} and {@link Ext.layout.BorderLayout.SplitRegion}
- * levels.</p>
- * <p>Example usage:</p>
- * <pre><code>
-var myBorderPanel = new Ext.Panel({
- {@link Ext.Component#renderTo renderTo}: document.body,
- {@link Ext.BoxComponent#width width}: 700,
- {@link Ext.BoxComponent#height height}: 500,
- {@link Ext.Panel#title title}: 'Border Layout',
- {@link Ext.Container#layout layout}: 'border',
- {@link Ext.Container#items items}: [{
- {@link Ext.Panel#title title}: 'South Region is resizable',
- {@link Ext.layout.BorderLayout.Region#BorderLayout.Region region}: 'south', // position for region
- {@link Ext.BoxComponent#height height}: 100,
- {@link Ext.layout.BorderLayout.Region#split split}: true, // enable resizing
- {@link Ext.SplitBar#minSize minSize}: 75, // defaults to {@link Ext.layout.BorderLayout.Region#minHeight 50}
- {@link Ext.SplitBar#maxSize maxSize}: 150,
- {@link Ext.layout.BorderLayout.Region#margins margins}: '0 5 5 5'
- },{
- // xtype: 'panel' implied by default
- {@link Ext.Panel#title title}: 'West Region is collapsible',
- {@link Ext.layout.BorderLayout.Region#BorderLayout.Region region}:'west',
- {@link Ext.layout.BorderLayout.Region#margins margins}: '5 0 0 5',
- {@link Ext.BoxComponent#width width}: 200,
- {@link Ext.layout.BorderLayout.Region#collapsible collapsible}: true, // make collapsible
- {@link Ext.layout.BorderLayout.Region#cmargins cmargins}: '5 5 0 5', // adjust top margin when collapsed
- {@link Ext.Component#id id}: 'west-region-container',
- {@link Ext.Container#layout layout}: 'fit',
- {@link Ext.Panel#unstyled unstyled}: true
- },{
- {@link Ext.Panel#title title}: 'Center Region',
- {@link Ext.layout.BorderLayout.Region#BorderLayout.Region region}: 'center', // center region is required, no width/height specified
- {@link Ext.Component#xtype xtype}: 'container',
- {@link Ext.Container#layout layout}: 'fit',
- {@link Ext.layout.BorderLayout.Region#margins margins}: '5 5 0 0'
- }]
-});
-</code></pre>
- * <p><b><u>Notes</u></b>:</p><div class="mdetail-params"><ul>
- * <li>Any container using the BorderLayout <b>must</b> have a child item with <tt>region:'center'</tt>.
- * The child item in the center region will always be resized to fill the remaining space not used by
- * the other regions in the layout.</li>
- * <li>Any child items with a region of <tt>west</tt> or <tt>east</tt> must have <tt>width</tt> defined
- * (an integer representing the number of pixels that the region should take up).</li>
- * <li>Any child items with a region of <tt>north</tt> or <tt>south</tt> must have <tt>height</tt> defined.</li>
- * <li>The regions of a BorderLayout are <b>fixed at render time</b> and thereafter, its child Components may not be removed or added</b>. To add/remove
- * Components within a BorderLayout, have them wrapped by an additional Container which is directly
- * managed by the BorderLayout. If the region is to be collapsible, the Container used directly
- * by the BorderLayout manager should be a Panel. In the following example a Container (an Ext.Panel)
- * is added to the west region:
- * <div style="margin-left:16px"><pre><code>
-wrc = {@link Ext#getCmp Ext.getCmp}('west-region-container');
-wrc.{@link Ext.Panel#removeAll removeAll}();
-wrc.{@link Ext.Container#add add}({
- title: 'Added Panel',
- html: 'Some content'
-});
-wrc.{@link Ext.Container#doLayout doLayout}();
- * </code></pre></div>
- * </li>
- * <li> To reference a {@link Ext.layout.BorderLayout.Region Region}:
- * <div style="margin-left:16px"><pre><code>
-wr = myBorderPanel.layout.west;
- * </code></pre></div>
- * </li>
- * </ul></div>
- */
-Ext.layout.BorderLayout = Ext.extend(Ext.layout.ContainerLayout, {
// private
- monitorResize:true,
+ applyLayoutCollapsed : function(box){
+ var ce = this.getCollapsedEl();
+ ce.setLeftTop(box.x, box.y);
+ ce.setSize(box.width, box.height);
+ },
+
// private
- rendered : false,
+ applyLayout : function(box){
+ if(this.isCollapsed){
+ this.applyLayoutCollapsed(box);
+ }else{
+ this.panel.setPosition(box.x, box.y);
+ this.panel.setSize(box.width, box.height);
+ }
+ },
- type: 'border',
+ // private
+ beforeSlide: function(){
+ this.panel.beforeEffect();
+ },
- targetCls: 'x-border-layout-ct',
+ // private
+ afterSlide : function(){
+ this.panel.afterEffect();
+ },
- getLayoutTargetSize : function() {
- var target = this.container.getLayoutTarget();
- return target ? target.getViewSize() : {};
+ // private
+ initAutoHide : function(){
+ if(this.autoHide !== false){
+ if(!this.autoHideHd){
+ this.autoHideSlideTask = new Ext.util.DelayedTask(this.slideIn, this);
+ this.autoHideHd = {
+ "mouseout": function(e){
+ if(!e.within(this.el, true)){
+ this.autoHideSlideTask.delay(500);
+ }
+ },
+ "mouseover" : function(e){
+ this.autoHideSlideTask.cancel();
+ },
+ scope : this
+ };
+ }
+ this.el.on(this.autoHideHd);
+ this.collapsedEl.on(this.autoHideHd);
+ }
},
// private
- onLayout : function(ct, target){
- var collapsed, i, c, pos, items = ct.items.items, len = items.length;
- if(!this.rendered){
- collapsed = [];
- for(i = 0; i < len; i++) {
- c = items[i];
- pos = c.region;
- if(c.collapsed){
- collapsed.push(c);
- }
- c.collapsed = false;
- if(!c.rendered){
- c.render(target, i);
- c.getPositionEl().addClass('x-border-panel');
- }
- this[pos] = pos != 'center' && c.split ?
- new Ext.layout.BorderLayout.SplitRegion(this, c.initialConfig, pos) :
- new Ext.layout.BorderLayout.Region(this, c.initialConfig, pos);
- this[pos].render(target, c);
- }
- this.rendered = true;
+ clearAutoHide : function(){
+ if(this.autoHide !== false){
+ this.el.un("mouseout", this.autoHideHd.mouseout);
+ this.el.un("mouseover", this.autoHideHd.mouseover);
+ this.collapsedEl.un("mouseout", this.autoHideHd.mouseout);
+ this.collapsedEl.un("mouseover", this.autoHideHd.mouseover);
}
+ },
- var size = this.getLayoutTargetSize();
- if(size.width < 20 || size.height < 20){ // display none?
- if(collapsed){
- this.restoreCollapsed = collapsed;
- }
+ // private
+ clearMonitor : function(){
+ Ext.getDoc().un("click", this.slideInIf, this);
+ },
+
+ /**
+ * If this Region is {@link #floatable}, this method slides this Region into full visibility <i>over the top
+ * of the center Region</i> where it floats until either {@link #slideIn} is called, or other regions of the layout
+ * are clicked, or the mouse exits the Region.
+ */
+ slideOut : function(){
+ if(this.isSlid || this.el.hasActiveFx()){
return;
- }else if(this.restoreCollapsed){
- collapsed = this.restoreCollapsed;
- delete this.restoreCollapsed;
}
-
- var w = size.width, h = size.height,
- centerW = w, centerH = h, centerY = 0, centerX = 0,
- n = this.north, s = this.south, west = this.west, e = this.east, c = this.center,
- b, m, totalWidth, totalHeight;
- if(!c && Ext.layout.BorderLayout.WARN !== false){
- throw 'No center region defined in BorderLayout ' + ct.id;
+ this.isSlid = true;
+ var ts = this.panel.tools, dh, pc;
+ if(ts && ts.toggle){
+ ts.toggle.hide();
}
+ this.el.show();
- if(n && n.isVisible()){
- b = n.getSize();
- m = n.getMargins();
- b.width = w - (m.left+m.right);
- b.x = m.left;
- b.y = m.top;
- centerY = b.height + b.y + m.bottom;
- centerH -= centerY;
- n.applyLayout(b);
+ // Temporarily clear the collapsed flag so we can onResize the panel on the slide
+ pc = this.panel.collapsed;
+ this.panel.collapsed = false;
+
+ if(this.position == 'east' || this.position == 'west'){
+ // Temporarily clear the deferHeight flag so we can size the height on the slide
+ dh = this.panel.deferHeight;
+ this.panel.deferHeight = false;
+
+ this.panel.setSize(undefined, this.collapsedEl.getHeight());
+
+ // Put the deferHeight flag back after setSize
+ this.panel.deferHeight = dh;
+ }else{
+ this.panel.setSize(this.collapsedEl.getWidth(), undefined);
}
- if(s && s.isVisible()){
- b = s.getSize();
- m = s.getMargins();
- b.width = w - (m.left+m.right);
- b.x = m.left;
- totalHeight = (b.height + m.top + m.bottom);
- b.y = h - totalHeight + m.top;
- centerH -= totalHeight;
- s.applyLayout(b);
+
+ // Put the collapsed flag back after onResize
+ this.panel.collapsed = pc;
+
+ this.restoreLT = [this.el.dom.style.left, this.el.dom.style.top];
+ this.el.alignTo(this.collapsedEl, this.getCollapseAnchor());
+ this.el.setStyle("z-index", this.floatingZIndex+2);
+ this.panel.el.replaceClass('x-panel-collapsed', 'x-panel-floating');
+ if(this.animFloat !== false){
+ this.beforeSlide();
+ this.el.slideIn(this.getSlideAnchor(), {
+ callback: function(){
+ this.afterSlide();
+ this.initAutoHide();
+ Ext.getDoc().on("click", this.slideInIf, this);
+ },
+ scope: this,
+ block: true
+ });
+ }else{
+ this.initAutoHide();
+ Ext.getDoc().on("click", this.slideInIf, this);
}
- if(west && west.isVisible()){
- b = west.getSize();
- m = west.getMargins();
- b.height = centerH - (m.top+m.bottom);
- b.x = m.left;
- b.y = centerY + m.top;
- totalWidth = (b.width + m.left + m.right);
- centerX += totalWidth;
- centerW -= totalWidth;
- west.applyLayout(b);
+ },
+
+ // private
+ afterSlideIn : function(){
+ this.clearAutoHide();
+ this.isSlid = false;
+ this.clearMonitor();
+ this.el.setStyle("z-index", "");
+ this.panel.el.replaceClass('x-panel-floating', 'x-panel-collapsed');
+ this.el.dom.style.left = this.restoreLT[0];
+ this.el.dom.style.top = this.restoreLT[1];
+
+ var ts = this.panel.tools;
+ if(ts && ts.toggle){
+ ts.toggle.show();
}
- if(e && e.isVisible()){
- b = e.getSize();
- m = e.getMargins();
- b.height = centerH - (m.top+m.bottom);
- totalWidth = (b.width + m.left + m.right);
- b.x = w - totalWidth + m.left;
- b.y = centerY + m.top;
- centerW -= totalWidth;
- e.applyLayout(b);
+ },
+
+ /**
+ * If this Region is {@link #floatable}, and this Region has been slid into floating visibility, then this method slides
+ * this region back into its collapsed state.
+ */
+ slideIn : function(cb){
+ if(!this.isSlid || this.el.hasActiveFx()){
+ Ext.callback(cb);
+ return;
}
- if(c){
- m = c.getMargins();
- var centerBox = {
- x: centerX + m.left,
- y: centerY + m.top,
- width: centerW - (m.left+m.right),
- height: centerH - (m.top+m.bottom)
- };
- c.applyLayout(centerBox);
+ this.isSlid = false;
+ if(this.animFloat !== false){
+ this.beforeSlide();
+ this.el.slideOut(this.getSlideAnchor(), {
+ callback: function(){
+ this.el.hide();
+ this.afterSlide();
+ this.afterSlideIn();
+ Ext.callback(cb);
+ },
+ scope: this,
+ block: true
+ });
+ }else{
+ this.el.hide();
+ this.afterSlideIn();
}
- if(collapsed){
- for(i = 0, len = collapsed.length; i < len; i++){
- collapsed[i].collapse(false);
- }
+ },
+
+ // private
+ slideInIf : function(e){
+ if(!e.within(this.el)){
+ this.slideIn();
}
- if(Ext.isIE && Ext.isStrict){ // workaround IE strict repainting issue
- target.repaint();
+ },
+
+ // private
+ anchors : {
+ "west" : "left",
+ "east" : "right",
+ "north" : "top",
+ "south" : "bottom"
+ },
+
+ // private
+ sanchors : {
+ "west" : "l",
+ "east" : "r",
+ "north" : "t",
+ "south" : "b"
+ },
+
+ // private
+ canchors : {
+ "west" : "tl-tr",
+ "east" : "tr-tl",
+ "north" : "tl-bl",
+ "south" : "bl-tl"
+ },
+
+ // private
+ getAnchor : function(){
+ return this.anchors[this.position];
+ },
+
+ // private
+ getCollapseAnchor : function(){
+ return this.canchors[this.position];
+ },
+
+ // private
+ getSlideAnchor : function(){
+ return this.sanchors[this.position];
+ },
+
+ // private
+ getAlignAdj : function(){
+ var cm = this.cmargins;
+ switch(this.position){
+ case "west":
+ return [0, 0];
+ break;
+ case "east":
+ return [0, 0];
+ break;
+ case "north":
+ return [0, 0];
+ break;
+ case "south":
+ return [0, 0];
+ break;
}
- // Putting a border layout into an overflowed container is NOT correct and will make a second layout pass necessary.
- if (i = target.getStyle('overflow') && i != 'hidden' && !this.adjustmentPass) {
- var ts = this.getLayoutTargetSize();
- if (ts.width != size.width || ts.height != size.height){
- this.adjustmentPass = true;
- this.onLayout(ct, target);
- }
+ },
+
+ // private
+ getExpandAdj : function(){
+ var c = this.collapsedEl, cm = this.cmargins;
+ switch(this.position){
+ case "west":
+ return [-(cm.right+c.getWidth()+cm.left), 0];
+ break;
+ case "east":
+ return [cm.right+c.getWidth()+cm.left, 0];
+ break;
+ case "north":
+ return [0, -(cm.top+cm.bottom+c.getHeight())];
+ break;
+ case "south":
+ return [0, cm.top+cm.bottom+c.getHeight()];
+ break;
}
- delete this.adjustmentPass;
},
- destroy: function() {
- var r = ['north', 'south', 'east', 'west'], i, region;
- for (i = 0; i < r.length; i++) {
- region = this[r[i]];
- if(region){
- if(region.destroy){
- region.destroy();
- }else if (region.split){
- region.split.destroy(true);
- }
- }
+ destroy : function(){
+ if (this.autoHideSlideTask && this.autoHideSlideTask.cancel){
+ this.autoHideSlideTask.cancel();
}
- Ext.layout.BorderLayout.superclass.destroy.call(this);
+ Ext.destroyMembers(this, 'miniCollapsedEl', 'collapsedEl', 'expandToolEl');
}
-
- /**
- * @property activeItem
- * @hide
- */
-});
+};
/**
- * @class Ext.layout.BorderLayout.Region
- * <p>This is a region of a {@link Ext.layout.BorderLayout BorderLayout} that acts as a subcontainer
- * within the layout. Each region has its own {@link Ext.layout.ContainerLayout layout} that is
- * independent of other regions and the containing BorderLayout, and can be any of the
- * {@link Ext.layout.ContainerLayout valid Ext layout types}.</p>
- * <p>Region size is managed automatically and cannot be changed by the user -- for
- * {@link #split resizable regions}, see {@link Ext.layout.BorderLayout.SplitRegion}.</p>
+ * @class Ext.layout.BorderLayout.SplitRegion
+ * @extends Ext.layout.BorderLayout.Region
+ * <p>This is a specialized type of {@link Ext.layout.BorderLayout.Region BorderLayout region} that
+ * has a built-in {@link Ext.SplitBar} for user resizing of regions. The movement of the split bar
+ * is configurable to move either {@link #tickSize smooth or incrementally}.</p>
* @constructor
- * Create a new Region.
+ * Create a new SplitRegion.
* @param {Layout} layout The {@link Ext.layout.BorderLayout BorderLayout} instance that is managing this Region.
* @param {Object} config The configuration options
- * @param {String} position The region position. Valid values are: <tt>north</tt>, <tt>south</tt>,
- * <tt>east</tt>, <tt>west</tt> and <tt>center</tt>. Every {@link Ext.layout.BorderLayout BorderLayout}
- * <b>must have a center region</b> for the primary content -- all other regions are optional.
+ * @param {String} position The region position. Valid values are: north, south, east, west and center. Every
+ * BorderLayout must have a center region for the primary content -- all other regions are optional.
*/
-Ext.layout.BorderLayout.Region = function(layout, config, pos){
- Ext.apply(this, config);
- this.layout = layout;
- this.position = pos;
- this.state = {};
- if(typeof this.margins == 'string'){
- this.margins = this.layout.parseMargins(this.margins);
- }
- this.margins = Ext.applyIf(this.margins || {}, this.defaultMargins);
- if(this.collapsible){
- if(typeof this.cmargins == 'string'){
- this.cmargins = this.layout.parseMargins(this.cmargins);
- }
- if(this.collapseMode == 'mini' && !this.cmargins){
- this.cmargins = {left:0,top:0,right:0,bottom:0};
- }else{
- this.cmargins = Ext.applyIf(this.cmargins || {},
- pos == 'north' || pos == 'south' ? this.defaultNSCMargins : this.defaultEWCMargins);
- }
- }
+Ext.layout.BorderLayout.SplitRegion = function(layout, config, pos){
+ Ext.layout.BorderLayout.SplitRegion.superclass.constructor.call(this, layout, config, pos);
+ // prevent switch
+ this.applyLayout = this.applyFns[pos];
};
-Ext.layout.BorderLayout.Region.prototype = {
- /**
- * @cfg {Boolean} animFloat
- * When a collapsed region's bar is clicked, the region's panel will be displayed as a floated
- * panel that will close again once the user mouses out of that panel (or clicks out if
- * <tt>{@link #autoHide} = false</tt>). Setting <tt>{@link #animFloat} = false</tt> will
- * prevent the open and close of these floated panels from being animated (defaults to <tt>true</tt>).
- */
- /**
- * @cfg {Boolean} autoHide
- * When a collapsed region's bar is clicked, the region's panel will be displayed as a floated
- * panel. If <tt>autoHide = true</tt>, the panel will automatically hide after the user mouses
- * out of the panel. If <tt>autoHide = false</tt>, the panel will continue to display until the
- * user clicks outside of the panel (defaults to <tt>true</tt>).
- */
- /**
- * @cfg {String} collapseMode
- * <tt>collapseMode</tt> supports two configuration values:<div class="mdetail-params"><ul>
- * <li><b><tt>undefined</tt></b> (default)<div class="sub-desc">By default, {@link #collapsible}
- * regions are collapsed by clicking the expand/collapse tool button that renders into the region's
- * title bar.</div></li>
- * <li><b><tt>'mini'</tt></b><div class="sub-desc">Optionally, when <tt>collapseMode</tt> is set to
- * <tt>'mini'</tt> the region's split bar will also display a small collapse button in the center of
- * the bar. In <tt>'mini'</tt> mode the region will collapse to a thinner bar than in normal mode.
- * </div></li>
- * </ul></div></p>
- * <p><b>Note</b>: if a collapsible region does not have a title bar, then set <tt>collapseMode =
- * 'mini'</tt> and <tt>{@link #split} = true</tt> in order for the region to be {@link #collapsible}
- * by the user as the expand/collapse tool button (that would go in the title bar) will not be rendered.</p>
- * <p>See also <tt>{@link #cmargins}</tt>.</p>
- */
- /**
- * @cfg {Object} margins
- * An object containing margins to apply to the region when in the expanded state in the
- * format:<pre><code>
-{
- top: (top margin),
- right: (right margin),
- bottom: (bottom margin),
- left: (left margin)
-}</code></pre>
- * <p>May also be a string containing space-separated, numeric margin values. The order of the
- * sides associated with each value matches the way CSS processes margin values:</p>
- * <p><div class="mdetail-params"><ul>
- * <li>If there is only one value, it applies to all sides.</li>
- * <li>If there are two values, the top and bottom borders are set to the first value and the
- * right and left are set to the second.</li>
- * <li>If there are three values, the top is set to the first value, the left and right are set
- * to the second, and the bottom is set to the third.</li>
- * <li>If there are four values, they apply to the top, right, bottom, and left, respectively.</li>
- * </ul></div></p>
- * <p>Defaults to:</p><pre><code>
- * {top:0, right:0, bottom:0, left:0}
- * </code></pre>
- */
- /**
- * @cfg {Object} cmargins
- * An object containing margins to apply to the region when in the collapsed state in the
- * format:<pre><code>
-{
- top: (top margin),
- right: (right margin),
- bottom: (bottom margin),
- left: (left margin)
-}</code></pre>
- * <p>May also be a string containing space-separated, numeric margin values. The order of the
- * sides associated with each value matches the way CSS processes margin values.</p>
- * <p><ul>
- * <li>If there is only one value, it applies to all sides.</li>
- * <li>If there are two values, the top and bottom borders are set to the first value and the
- * right and left are set to the second.</li>
- * <li>If there are three values, the top is set to the first value, the left and right are set
- * to the second, and the bottom is set to the third.</li>
- * <li>If there are four values, they apply to the top, right, bottom, and left, respectively.</li>
- * </ul></p>
- */
- /**
- * @cfg {Boolean} collapsible
- * <p><tt>true</tt> to allow the user to collapse this region (defaults to <tt>false</tt>). If
- * <tt>true</tt>, an expand/collapse tool button will automatically be rendered into the title
- * bar of the region, otherwise the button will not be shown.</p>
- * <p><b>Note</b>: that a title bar is required to display the collapse/expand toggle button -- if
- * no <tt>title</tt> is specified for the region's panel, the region will only be collapsible if
- * <tt>{@link #collapseMode} = 'mini'</tt> and <tt>{@link #split} = true</tt>.
- */
- collapsible : false,
+Ext.extend(Ext.layout.BorderLayout.SplitRegion, Ext.layout.BorderLayout.Region, {
/**
- * @cfg {Boolean} split
- * <p><tt>true</tt> to create a {@link Ext.layout.BorderLayout.SplitRegion SplitRegion} and
- * display a 5px wide {@link Ext.SplitBar} between this region and its neighbor, allowing the user to
- * resize the regions dynamically. Defaults to <tt>false</tt> creating a
- * {@link Ext.layout.BorderLayout.Region Region}.</p><br>
- * <p><b>Notes</b>:</p><div class="mdetail-params"><ul>
- * <li>this configuration option is ignored if <tt>region='center'</tt></li>
- * <li>when <tt>split == true</tt>, it is common to specify a
- * <tt>{@link Ext.SplitBar#minSize minSize}</tt> and <tt>{@link Ext.SplitBar#maxSize maxSize}</tt>
- * for the {@link Ext.BoxComponent BoxComponent} representing the region. These are not native
- * configs of {@link Ext.BoxComponent BoxComponent}, and are used only by this class.</li>
- * <li>if <tt>{@link #collapseMode} = 'mini'</tt> requires <tt>split = true</tt> to reserve space
- * for the collapse tool</tt></li>
- * </ul></div>
+ * @cfg {Number} tickSize
+ * The increment, in pixels by which to move this Region's {@link Ext.SplitBar SplitBar}.
+ * By default, the {@link Ext.SplitBar SplitBar} moves smoothly.
*/
- split:false,
/**
- * @cfg {Boolean} floatable
- * <tt>true</tt> to allow clicking a collapsed region's bar to display the region's panel floated
- * above the layout, <tt>false</tt> to force the user to fully expand a collapsed region by
- * clicking the expand button to see it again (defaults to <tt>true</tt>).
+ * @cfg {String} splitTip
+ * The tooltip to display when the user hovers over a
+ * {@link Ext.layout.BorderLayout.Region#collapsible non-collapsible} region's split bar
+ * (defaults to <tt>"Drag to resize."</tt>). Only applies if
+ * <tt>{@link #useSplitTips} = true</tt>.
*/
- floatable: true,
+ splitTip : "Drag to resize.",
/**
- * @cfg {Number} minWidth
- * <p>The minimum allowable width in pixels for this region (defaults to <tt>50</tt>).
- * <tt>maxWidth</tt> may also be specified.</p><br>
- * <p><b>Note</b>: setting the <tt>{@link Ext.SplitBar#minSize minSize}</tt> /
- * <tt>{@link Ext.SplitBar#maxSize maxSize}</tt> supersedes any specified
- * <tt>minWidth</tt> / <tt>maxWidth</tt>.</p>
+ * @cfg {String} collapsibleSplitTip
+ * The tooltip to display when the user hovers over a
+ * {@link Ext.layout.BorderLayout.Region#collapsible collapsible} region's split bar
+ * (defaults to "Drag to resize. Double click to hide."). Only applies if
+ * <tt>{@link #useSplitTips} = true</tt>.
*/
- minWidth:50,
+ collapsibleSplitTip : "Drag to resize. Double click to hide.",
/**
- * @cfg {Number} minHeight
- * The minimum allowable height in pixels for this region (defaults to <tt>50</tt>)
- * <tt>maxHeight</tt> may also be specified.</p><br>
- * <p><b>Note</b>: setting the <tt>{@link Ext.SplitBar#minSize minSize}</tt> /
- * <tt>{@link Ext.SplitBar#maxSize maxSize}</tt> supersedes any specified
- * <tt>minHeight</tt> / <tt>maxHeight</tt>.</p>
+ * @cfg {Boolean} useSplitTips
+ * <tt>true</tt> to display a tooltip when the user hovers over a region's split bar
+ * (defaults to <tt>false</tt>). The tooltip text will be the value of either
+ * <tt>{@link #splitTip}</tt> or <tt>{@link #collapsibleSplitTip}</tt> as appropriate.
*/
- minHeight:50,
+ useSplitTips : false,
+
+ // private
+ splitSettings : {
+ north : {
+ orientation: Ext.SplitBar.VERTICAL,
+ placement: Ext.SplitBar.TOP,
+ maxFn : 'getVMaxSize',
+ minProp: 'minHeight',
+ maxProp: 'maxHeight'
+ },
+ south : {
+ orientation: Ext.SplitBar.VERTICAL,
+ placement: Ext.SplitBar.BOTTOM,
+ maxFn : 'getVMaxSize',
+ minProp: 'minHeight',
+ maxProp: 'maxHeight'
+ },
+ east : {
+ orientation: Ext.SplitBar.HORIZONTAL,
+ placement: Ext.SplitBar.RIGHT,
+ maxFn : 'getHMaxSize',
+ minProp: 'minWidth',
+ maxProp: 'maxWidth'
+ },
+ west : {
+ orientation: Ext.SplitBar.HORIZONTAL,
+ placement: Ext.SplitBar.LEFT,
+ maxFn : 'getHMaxSize',
+ minProp: 'minWidth',
+ maxProp: 'maxWidth'
+ }
+ },
// private
- defaultMargins : {left:0,top:0,right:0,bottom:0},
+ applyFns : {
+ west : function(box){
+ if(this.isCollapsed){
+ return this.applyLayoutCollapsed(box);
+ }
+ var sd = this.splitEl.dom, s = sd.style;
+ this.panel.setPosition(box.x, box.y);
+ var sw = sd.offsetWidth;
+ s.left = (box.x+box.width-sw)+'px';
+ s.top = (box.y)+'px';
+ s.height = Math.max(0, box.height)+'px';
+ this.panel.setSize(box.width-sw, box.height);
+ },
+ east : function(box){
+ if(this.isCollapsed){
+ return this.applyLayoutCollapsed(box);
+ }
+ var sd = this.splitEl.dom, s = sd.style;
+ var sw = sd.offsetWidth;
+ this.panel.setPosition(box.x+sw, box.y);
+ s.left = (box.x)+'px';
+ s.top = (box.y)+'px';
+ s.height = Math.max(0, box.height)+'px';
+ this.panel.setSize(box.width-sw, box.height);
+ },
+ north : function(box){
+ if(this.isCollapsed){
+ return this.applyLayoutCollapsed(box);
+ }
+ var sd = this.splitEl.dom, s = sd.style;
+ var sh = sd.offsetHeight;
+ this.panel.setPosition(box.x, box.y);
+ s.left = (box.x)+'px';
+ s.top = (box.y+box.height-sh)+'px';
+ s.width = Math.max(0, box.width)+'px';
+ this.panel.setSize(box.width, box.height-sh);
+ },
+ south : function(box){
+ if(this.isCollapsed){
+ return this.applyLayoutCollapsed(box);
+ }
+ var sd = this.splitEl.dom, s = sd.style;
+ var sh = sd.offsetHeight;
+ this.panel.setPosition(box.x, box.y+sh);
+ s.left = (box.x)+'px';
+ s.top = (box.y)+'px';
+ s.width = Math.max(0, box.width)+'px';
+ this.panel.setSize(box.width, box.height-sh);
+ }
+ },
+
// private
- defaultNSCMargins : {left:5,top:5,right:5,bottom:5},
+ render : function(ct, p){
+ Ext.layout.BorderLayout.SplitRegion.superclass.render.call(this, ct, p);
+
+ var ps = this.position;
+
+ this.splitEl = ct.createChild({
+ cls: "x-layout-split x-layout-split-"+ps, html: " ",
+ id: this.panel.id + '-xsplit'
+ });
+
+ if(this.collapseMode == 'mini'){
+ this.miniSplitEl = this.splitEl.createChild({
+ cls: "x-layout-mini x-layout-mini-"+ps, html: " "
+ });
+ this.miniSplitEl.addClassOnOver('x-layout-mini-over');
+ this.miniSplitEl.on('click', this.onCollapseClick, this, {stopEvent:true});
+ }
+
+ var s = this.splitSettings[ps];
+
+ this.split = new Ext.SplitBar(this.splitEl.dom, p.el, s.orientation);
+ this.split.tickSize = this.tickSize;
+ this.split.placement = s.placement;
+ this.split.getMaximumSize = this[s.maxFn].createDelegate(this);
+ this.split.minSize = this.minSize || this[s.minProp];
+ this.split.on("beforeapply", this.onSplitMove, this);
+ this.split.useShim = this.useShim === true;
+ this.maxSize = this.maxSize || this[s.maxProp];
+
+ if(p.hidden){
+ this.splitEl.hide();
+ }
+
+ if(this.useSplitTips){
+ this.splitEl.dom.title = this.collapsible ? this.collapsibleSplitTip : this.splitTip;
+ }
+ if(this.collapsible){
+ this.splitEl.on("dblclick", this.onCollapseClick, this);
+ }
+ },
+
+ //docs inherit from superclass
+ getSize : function(){
+ if(this.isCollapsed){
+ return this.collapsedEl.getSize();
+ }
+ var s = this.panel.getSize();
+ if(this.position == 'north' || this.position == 'south'){
+ s.height += this.splitEl.dom.offsetHeight;
+ }else{
+ s.width += this.splitEl.dom.offsetWidth;
+ }
+ return s;
+ },
+
// private
- defaultEWCMargins : {left:5,top:0,right:5,bottom:0},
- floatingZIndex: 100,
+ getHMaxSize : function(){
+ var cmax = this.maxSize || 10000;
+ var center = this.layout.center;
+ return Math.min(cmax, (this.el.getWidth()+center.el.getWidth())-center.getMinWidth());
+ },
- /**
- * True if this region is collapsed. Read-only.
- * @type Boolean
- * @property
- */
- isCollapsed : false,
+ // private
+ getVMaxSize : function(){
+ var cmax = this.maxSize || 10000;
+ var center = this.layout.center;
+ return Math.min(cmax, (this.el.getHeight()+center.el.getHeight())-center.getMinHeight());
+ },
+
+ // private
+ onSplitMove : function(split, newSize){
+ var s = this.panel.getSize();
+ this.lastSplitSize = newSize;
+ if(this.position == 'north' || this.position == 'south'){
+ this.panel.setSize(s.width, newSize);
+ this.state.height = newSize;
+ }else{
+ this.panel.setSize(newSize, s.height);
+ this.state.width = newSize;
+ }
+ this.layout.layout();
+ this.panel.saveState();
+ return false;
+ },
/**
- * This region's panel. Read-only.
- * @type Ext.Panel
- * @property panel
+ * Returns a reference to the split bar in use by this region.
+ * @return {Ext.SplitBar} The split bar
*/
+ getSplitBar : function(){
+ return this.split;
+ },
+
+ // inherit docs
+ destroy : function() {
+ Ext.destroy(this.miniSplitEl, this.split, this.splitEl);
+ Ext.layout.BorderLayout.SplitRegion.superclass.destroy.call(this);
+ }
+});
+
+Ext.Container.LAYOUTS['border'] = Ext.layout.BorderLayout;
+/**
+ * @class Ext.layout.FormLayout
+ * @extends Ext.layout.AnchorLayout
+ * <p>This layout manager is specifically designed for rendering and managing child Components of
+ * {@link Ext.form.FormPanel forms}. It is responsible for rendering the labels of
+ * {@link Ext.form.Field Field}s.</p>
+ *
+ * <p>This layout manager is used when a Container is configured with the <tt>layout:'form'</tt>
+ * {@link Ext.Container#layout layout} config option, and should generally not need to be created directly
+ * via the new keyword. See <tt><b>{@link Ext.Container#layout}</b></tt> for additional details.</p>
+ *
+ * <p>In an application, it will usually be preferrable to use a {@link Ext.form.FormPanel FormPanel}
+ * (which is configured with FormLayout as its layout class by default) since it also provides built-in
+ * functionality for {@link Ext.form.BasicForm#doAction loading, validating and submitting} the form.</p>
+ *
+ * <p>A {@link Ext.Container Container} <i>using</i> the FormLayout layout manager (e.g.
+ * {@link Ext.form.FormPanel} or specifying <tt>layout:'form'</tt>) can also accept the following
+ * layout-specific config properties:<div class="mdetail-params"><ul>
+ * <li><b><tt>{@link Ext.form.FormPanel#hideLabels hideLabels}</tt></b></li>
+ * <li><b><tt>{@link Ext.form.FormPanel#labelAlign labelAlign}</tt></b></li>
+ * <li><b><tt>{@link Ext.form.FormPanel#labelPad labelPad}</tt></b></li>
+ * <li><b><tt>{@link Ext.form.FormPanel#labelSeparator labelSeparator}</tt></b></li>
+ * <li><b><tt>{@link Ext.form.FormPanel#labelWidth labelWidth}</tt></b></li>
+ * </ul></div></p>
+ *
+ * <p>Any Component (including Fields) managed by FormLayout accepts the following as a config option:
+ * <div class="mdetail-params"><ul>
+ * <li><b><tt>{@link Ext.Component#anchor anchor}</tt></b></li>
+ * </ul></div></p>
+ *
+ * <p>Any Component managed by FormLayout may be rendered as a form field (with an associated label) by
+ * configuring it with a non-null <b><tt>{@link Ext.Component#fieldLabel fieldLabel}</tt></b>. Components configured
+ * in this way may be configured with the following options which affect the way the FormLayout renders them:
+ * <div class="mdetail-params"><ul>
+ * <li><b><tt>{@link Ext.Component#clearCls clearCls}</tt></b></li>
+ * <li><b><tt>{@link Ext.Component#fieldLabel fieldLabel}</tt></b></li>
+ * <li><b><tt>{@link Ext.Component#hideLabel hideLabel}</tt></b></li>
+ * <li><b><tt>{@link Ext.Component#itemCls itemCls}</tt></b></li>
+ * <li><b><tt>{@link Ext.Component#labelSeparator labelSeparator}</tt></b></li>
+ * <li><b><tt>{@link Ext.Component#labelStyle labelStyle}</tt></b></li>
+ * </ul></div></p>
+ *
+ * <p>Example usage:</p>
+ * <pre><code>
+// Required if showing validation messages
+Ext.QuickTips.init();
+
+// While you can create a basic Panel with layout:'form', practically
+// you should usually use a FormPanel to also get its form functionality
+// since it already creates a FormLayout internally.
+var form = new Ext.form.FormPanel({
+ title: 'Form Layout',
+ bodyStyle: 'padding:15px',
+ width: 350,
+ defaultType: 'textfield',
+ defaults: {
+ // applied to each contained item
+ width: 230,
+ msgTarget: 'side'
+ },
+ items: [{
+ fieldLabel: 'First Name',
+ name: 'first',
+ allowBlank: false,
+ {@link Ext.Component#labelSeparator labelSeparator}: ':' // override labelSeparator layout config
+ },{
+ fieldLabel: 'Last Name',
+ name: 'last'
+ },{
+ fieldLabel: 'Email',
+ name: 'email',
+ vtype:'email'
+ }, {
+ xtype: 'textarea',
+ hideLabel: true, // override hideLabels layout config
+ name: 'msg',
+ anchor: '100% -53'
+ }
+ ],
+ buttons: [
+ {text: 'Save'},
+ {text: 'Cancel'}
+ ],
+ layoutConfig: {
+ {@link #labelSeparator}: '~' // superseded by assignment below
+ },
+ // config options applicable to container when layout='form':
+ hideLabels: false,
+ labelAlign: 'left', // or 'right' or 'top'
+ {@link Ext.form.FormPanel#labelSeparator labelSeparator}: '>>', // takes precedence over layoutConfig value
+ labelWidth: 65, // defaults to 100
+ labelPad: 8 // defaults to 5, must specify labelWidth to be honored
+});
+</code></pre>
+ */
+Ext.layout.FormLayout = Ext.extend(Ext.layout.AnchorLayout, {
+
/**
- * This region's layout. Read-only.
- * @type Layout
- * @property layout
+ * @cfg {String} labelSeparator
+ * See {@link Ext.form.FormPanel}.{@link Ext.form.FormPanel#labelSeparator labelSeparator}. Configuration
+ * of this property at the <b>container</b> level takes precedence.
*/
+ labelSeparator : ':',
+
/**
- * This region's layout position (north, south, east, west or center). Read-only.
+ * Read only. The CSS style specification string added to field labels in this layout if not
+ * otherwise {@link Ext.Component#labelStyle specified by each contained field}.
* @type String
- * @property position
+ * @property labelStyle
*/
- // private
- render : function(ct, p){
- this.panel = p;
- p.el.enableDisplayMode();
- this.targetEl = ct;
- this.el = p.el;
+ /**
+ * @cfg {Boolean} trackLabels
+ * True to show/hide the field label when the field is hidden. Defaults to <tt>true</tt>.
+ */
+ trackLabels: true,
- var gs = p.getState, ps = this.position;
- p.getState = function(){
- return Ext.apply(gs.call(p) || {}, this.state);
- }.createDelegate(this);
+ type: 'form',
- if(ps != 'center'){
- p.allowQueuedExpand = false;
- p.on({
- beforecollapse: this.beforeCollapse,
- collapse: this.onCollapse,
- beforeexpand: this.beforeExpand,
- expand: this.onExpand,
- hide: this.onHide,
- show: this.onShow,
- scope: this
- });
- if(this.collapsible || this.floatable){
- p.collapseEl = 'el';
- p.slideAnchor = this.getSlideAnchor();
+ onRemove: function(c){
+ Ext.layout.FormLayout.superclass.onRemove.call(this, c);
+ if(this.trackLabels){
+ c.un('show', this.onFieldShow, this);
+ c.un('hide', this.onFieldHide, this);
+ }
+ // check for itemCt, since we may be removing a fieldset or something similar
+ var el = c.getPositionEl(),
+ ct = c.getItemCt && c.getItemCt();
+ if (c.rendered && ct) {
+ if (el && el.dom) {
+ el.insertAfter(ct);
}
- if(p.tools && p.tools.toggle){
- p.tools.toggle.addClass('x-tool-collapse-'+ps);
- p.tools.toggle.addClassOnOver('x-tool-collapse-'+ps+'-over');
+ Ext.destroy(ct);
+ Ext.destroyMembers(c, 'label', 'itemCt');
+ if (c.customItemCt) {
+ Ext.destroyMembers(c, 'getItemCt', 'customItemCt');
}
}
},
// private
- getCollapsedEl : function(){
- if(!this.collapsedEl){
- if(!this.toolTemplate){
- var tt = new Ext.Template(
- '<div class="x-tool x-tool-{id}"> </div>'
- );
- tt.disableFormats = true;
- tt.compile();
- Ext.layout.BorderLayout.Region.prototype.toolTemplate = tt;
- }
- this.collapsedEl = this.targetEl.createChild({
- cls: "x-layout-collapsed x-layout-collapsed-"+this.position,
- id: this.panel.id + '-xcollapsed'
- });
- this.collapsedEl.enableDisplayMode('block');
+ setContainer : function(ct){
+ Ext.layout.FormLayout.superclass.setContainer.call(this, ct);
+ if(ct.labelAlign){
+ ct.addClass('x-form-label-'+ct.labelAlign);
+ }
- if(this.collapseMode == 'mini'){
- this.collapsedEl.addClass('x-layout-cmini-'+this.position);
- this.miniCollapsedEl = this.collapsedEl.createChild({
- cls: "x-layout-mini x-layout-mini-"+this.position, html: " "
+ if(ct.hideLabels){
+ Ext.apply(this, {
+ labelStyle: 'display:none',
+ elementStyle: 'padding-left:0;',
+ labelAdjust: 0
+ });
+ }else{
+ this.labelSeparator = Ext.isDefined(ct.labelSeparator) ? ct.labelSeparator : this.labelSeparator;
+ ct.labelWidth = ct.labelWidth || 100;
+ if(Ext.isNumber(ct.labelWidth)){
+ var pad = Ext.isNumber(ct.labelPad) ? ct.labelPad : 5;
+ Ext.apply(this, {
+ labelAdjust: ct.labelWidth + pad,
+ labelStyle: 'width:' + ct.labelWidth + 'px;',
+ elementStyle: 'padding-left:' + (ct.labelWidth + pad) + 'px'
+ });
+ }
+ if(ct.labelAlign == 'top'){
+ Ext.apply(this, {
+ labelStyle: 'width:auto;',
+ labelAdjust: 0,
+ elementStyle: 'padding-left:0;'
});
- this.miniCollapsedEl.addClassOnOver('x-layout-mini-over');
- this.collapsedEl.addClassOnOver("x-layout-collapsed-over");
- this.collapsedEl.on('click', this.onExpandClick, this, {stopEvent:true});
- }else {
- if(this.collapsible !== false && !this.hideCollapseTool) {
- var t = this.toolTemplate.append(
- this.collapsedEl.dom,
- {id:'expand-'+this.position}, true);
- t.addClassOnOver('x-tool-expand-'+this.position+'-over');
- t.on('click', this.onExpandClick, this, {stopEvent:true});
- }
- if(this.floatable !== false || this.titleCollapse){
- this.collapsedEl.addClassOnOver("x-layout-collapsed-over");
- this.collapsedEl.on("click", this[this.floatable ? 'collapseClick' : 'onExpandClick'], this);
- }
}
}
- return this.collapsedEl;
},
// private
- onExpandClick : function(e){
- if(this.isSlid){
- this.panel.expand(false);
- }else{
- this.panel.expand();
+ isHide: function(c){
+ return c.hideLabel || this.container.hideLabels;
+ },
+
+ onFieldShow: function(c){
+ c.getItemCt().removeClass('x-hide-' + c.hideMode);
+
+ // Composite fields will need to layout after the container is made visible
+ if (c.isComposite) {
+ c.doLayout();
}
},
- // private
- onCollapseClick : function(e){
- this.panel.collapse();
+ onFieldHide: function(c){
+ c.getItemCt().addClass('x-hide-' + c.hideMode);
},
- // private
- beforeCollapse : function(p, animate){
- this.lastAnim = animate;
- if(this.splitEl){
- this.splitEl.hide();
+ //private
+ getLabelStyle: function(s){
+ var ls = '', items = [this.labelStyle, s];
+ for (var i = 0, len = items.length; i < len; ++i){
+ if (items[i]){
+ ls += items[i];
+ if (ls.substr(-1, 1) != ';'){
+ ls += ';';
+ }
+ }
}
- this.getCollapsedEl().show();
- var el = this.panel.getEl();
- this.originalZIndex = el.getStyle('z-index');
- el.setStyle('z-index', 100);
- this.isCollapsed = true;
- this.layout.layout();
+ return ls;
},
- // private
- onCollapse : function(animate){
- this.panel.el.setStyle('z-index', 1);
- if(this.lastAnim === false || this.panel.animCollapse === false){
- this.getCollapsedEl().dom.style.visibility = 'visible';
- }else{
- this.getCollapsedEl().slideIn(this.panel.slideAnchor, {duration:.2});
+ /**
+ * @cfg {Ext.Template} fieldTpl
+ * A {@link Ext.Template#compile compile}d {@link Ext.Template} for rendering
+ * the fully wrapped, labeled and styled form Field. Defaults to:</p><pre><code>
+new Ext.Template(
+ '<div class="x-form-item {itemCls}" tabIndex="-1">',
+ '<label for="{id}" style="{labelStyle}" class="x-form-item-label">{label}{labelSeparator}</label>',
+ '<div class="x-form-element" id="x-form-el-{id}" style="{elementStyle}">',
+ '</div><div class="{clearCls}"></div>',
+ '</div>'
+);
+</code></pre>
+ * <p>This may be specified to produce a different DOM structure when rendering form Fields.</p>
+ * <p>A description of the properties within the template follows:</p><div class="mdetail-params"><ul>
+ * <li><b><tt>itemCls</tt></b> : String<div class="sub-desc">The CSS class applied to the outermost div wrapper
+ * that contains this field label and field element (the default class is <tt>'x-form-item'</tt> and <tt>itemCls</tt>
+ * will be added to that). If supplied, <tt>itemCls</tt> at the field level will override the default <tt>itemCls</tt>
+ * supplied at the container level.</div></li>
+ * <li><b><tt>id</tt></b> : String<div class="sub-desc">The id of the Field</div></li>
+ * <li><b><tt>{@link #labelStyle}</tt></b> : String<div class="sub-desc">
+ * A CSS style specification string to add to the field label for this field (defaults to <tt>''</tt> or the
+ * {@link #labelStyle layout's value for <tt>labelStyle</tt>}).</div></li>
+ * <li><b><tt>label</tt></b> : String<div class="sub-desc">The text to display as the label for this
+ * field (defaults to <tt>''</tt>)</div></li>
+ * <li><b><tt>{@link #labelSeparator}</tt></b> : String<div class="sub-desc">The separator to display after
+ * the text of the label for this field (defaults to a colon <tt>':'</tt> or the
+ * {@link #labelSeparator layout's value for labelSeparator}). To hide the separator use empty string ''.</div></li>
+ * <li><b><tt>elementStyle</tt></b> : String<div class="sub-desc">The styles text for the input element's wrapper.</div></li>
+ * <li><b><tt>clearCls</tt></b> : String<div class="sub-desc">The CSS class to apply to the special clearing div
+ * rendered directly after each form field wrapper (defaults to <tt>'x-form-clear-left'</tt>)</div></li>
+ * </ul></div>
+ * <p>Also see <tt>{@link #getTemplateArgs}</tt></p>
+ */
+
+ /**
+ * @private
+ *
+ */
+ renderItem : function(c, position, target){
+ if(c && (c.isFormField || c.fieldLabel) && c.inputType != 'hidden'){
+ var args = this.getTemplateArgs(c);
+ if(Ext.isNumber(position)){
+ position = target.dom.childNodes[position] || null;
+ }
+ if(position){
+ c.itemCt = this.fieldTpl.insertBefore(position, args, true);
+ }else{
+ c.itemCt = this.fieldTpl.append(target, args, true);
+ }
+ if(!c.getItemCt){
+ // Non form fields don't have getItemCt, apply it here
+ // This will get cleaned up in onRemove
+ Ext.apply(c, {
+ getItemCt: function(){
+ return c.itemCt;
+ },
+ customItemCt: true
+ });
+ }
+ c.label = c.getItemCt().child('label.x-form-item-label');
+ if(!c.rendered){
+ c.render('x-form-el-' + c.id);
+ }else if(!this.isValidParent(c, target)){
+ Ext.fly('x-form-el-' + c.id).appendChild(c.getPositionEl());
+ }
+ if(this.trackLabels){
+ if(c.hidden){
+ this.onFieldHide(c);
+ }
+ c.on({
+ scope: this,
+ show: this.onFieldShow,
+ hide: this.onFieldHide
+ });
+ }
+ this.configureItem(c);
+ }else {
+ Ext.layout.FormLayout.superclass.renderItem.apply(this, arguments);
}
- this.state.collapsed = true;
- this.panel.saveState();
},
- // private
- beforeExpand : function(animate){
- if(this.isSlid){
- this.afterSlideIn();
- }
- var c = this.getCollapsedEl();
- this.el.show();
- if(this.position == 'east' || this.position == 'west'){
- this.panel.setSize(undefined, c.getHeight());
- }else{
- this.panel.setSize(c.getWidth(), undefined);
- }
- c.hide();
- c.dom.style.visibility = 'hidden';
- this.panel.el.setStyle('z-index', this.floatingZIndex);
- },
+ /**
+ * <p>Provides template arguments for rendering the fully wrapped, labeled and styled form Field.</p>
+ * <p>This method returns an object hash containing properties used by the layout's {@link #fieldTpl}
+ * to create a correctly wrapped, labeled and styled form Field. This may be overriden to
+ * create custom layouts. The properties which must be returned are:</p><div class="mdetail-params"><ul>
+ * <li><b><tt>itemCls</tt></b> : String<div class="sub-desc">The CSS class applied to the outermost div wrapper
+ * that contains this field label and field element (the default class is <tt>'x-form-item'</tt> and <tt>itemCls</tt>
+ * will be added to that). If supplied, <tt>itemCls</tt> at the field level will override the default <tt>itemCls</tt>
+ * supplied at the container level.</div></li>
+ * <li><b><tt>id</tt></b> : String<div class="sub-desc">The id of the Field</div></li>
+ * <li><b><tt>{@link #labelStyle}</tt></b> : String<div class="sub-desc">
+ * A CSS style specification string to add to the field label for this field (defaults to <tt>''</tt> or the
+ * {@link #labelStyle layout's value for <tt>labelStyle</tt>}).</div></li>
+ * <li><b><tt>label</tt></b> : String<div class="sub-desc">The text to display as the label for this
+ * field (defaults to the field's configured fieldLabel property)</div></li>
+ * <li><b><tt>{@link #labelSeparator}</tt></b> : String<div class="sub-desc">The separator to display after
+ * the text of the label for this field (defaults to a colon <tt>':'</tt> or the
+ * {@link #labelSeparator layout's value for labelSeparator}). To hide the separator use empty string ''.</div></li>
+ * <li><b><tt>elementStyle</tt></b> : String<div class="sub-desc">The styles text for the input element's wrapper.</div></li>
+ * <li><b><tt>clearCls</tt></b> : String<div class="sub-desc">The CSS class to apply to the special clearing div
+ * rendered directly after each form field wrapper (defaults to <tt>'x-form-clear-left'</tt>)</div></li>
+ * </ul></div>
+ * @param (Ext.form.Field} field The {@link Ext.form.Field Field} being rendered.
+ * @return {Object} An object hash containing the properties required to render the Field.
+ */
+ getTemplateArgs: function(field) {
+ var noLabelSep = !field.fieldLabel || field.hideLabel;
- // private
- onExpand : function(){
- this.isCollapsed = false;
- if(this.splitEl){
- this.splitEl.show();
- }
- this.layout.layout();
- this.panel.el.setStyle('z-index', this.originalZIndex);
- this.state.collapsed = false;
- this.panel.saveState();
+ return {
+ id : field.id,
+ label : field.fieldLabel,
+ itemCls : (field.itemCls || this.container.itemCls || '') + (field.hideLabel ? ' x-hide-label' : ''),
+ clearCls : field.clearCls || 'x-form-clear-left',
+ labelStyle : this.getLabelStyle(field.labelStyle),
+ elementStyle : this.elementStyle || '',
+ labelSeparator: noLabelSep ? '' : (Ext.isDefined(field.labelSeparator) ? field.labelSeparator : this.labelSeparator)
+ };
},
// private
- collapseClick : function(e){
- if(this.isSlid){
- e.stopPropagation();
- this.slideIn();
- }else{
- e.stopPropagation();
- this.slideOut();
+ adjustWidthAnchor: function(value, c){
+ if(c.label && !this.isHide(c) && (this.container.labelAlign != 'top')){
+ var adjust = Ext.isIE6 || (Ext.isIE && !Ext.isStrict);
+ return value - this.labelAdjust + (adjust ? -3 : 0);
}
+ return value;
},
- // private
- onHide : function(){
- if(this.isCollapsed){
- this.getCollapsedEl().hide();
- }else if(this.splitEl){
- this.splitEl.hide();
+ adjustHeightAnchor : function(value, c){
+ if(c.label && !this.isHide(c) && (this.container.labelAlign == 'top')){
+ return value - c.label.getHeight();
}
+ return value;
},
// private
- onShow : function(){
- if(this.isCollapsed){
- this.getCollapsedEl().show();
- }else if(this.splitEl){
- this.splitEl.show();
- }
- },
+ isValidParent : function(c, target){
+ return target && this.container.getEl().contains(c.getPositionEl());
+ }
/**
- * True if this region is currently visible, else false.
- * @return {Boolean}
+ * @property activeItem
+ * @hide
*/
- isVisible : function(){
- return !this.panel.hidden;
- },
+});
+Ext.Container.LAYOUTS['form'] = Ext.layout.FormLayout;
+/**
+ * @class Ext.layout.AccordionLayout
+ * @extends Ext.layout.FitLayout
+ * <p>This is a layout that manages multiple Panels in an expandable accordion style such that only
+ * <b>one Panel can be expanded at any given time</b>. Each Panel has built-in support for expanding and collapsing.</p>
+ * <p>Note: Only Ext.Panels <b>and all subclasses of Ext.Panel</b> may be used in an accordion layout Container.</p>
+ * <p>This class is intended to be extended or created via the <tt><b>{@link Ext.Container#layout layout}</b></tt>
+ * configuration property. See <tt><b>{@link Ext.Container#layout}</b></tt> for additional details.</p>
+ * <p>Example usage:</p>
+ * <pre><code>
+var accordion = new Ext.Panel({
+ title: 'Accordion Layout',
+ layout:'accordion',
+ defaults: {
+ // applied to each contained panel
+ bodyStyle: 'padding:15px'
+ },
+ layoutConfig: {
+ // layout-specific configs go here
+ titleCollapse: false,
+ animate: true,
+ activeOnTop: true
+ },
+ items: [{
+ title: 'Panel 1',
+ html: '<p>Panel content!</p>'
+ },{
+ title: 'Panel 2',
+ html: '<p>Panel content!</p>'
+ },{
+ title: 'Panel 3',
+ html: '<p>Panel content!</p>'
+ }]
+});
+</code></pre>
+ */
+Ext.layout.AccordionLayout = Ext.extend(Ext.layout.FitLayout, {
/**
- * Returns the current margins for this region. If the region is collapsed, the
- * {@link #cmargins} (collapsed margins) value will be returned, otherwise the
- * {@link #margins} value will be returned.
- * @return {Object} An object containing the element's margins: <tt>{left: (left
- * margin), top: (top margin), right: (right margin), bottom: (bottom margin)}</tt>
+ * @cfg {Boolean} fill
+ * True to adjust the active item's height to fill the available space in the container, false to use the
+ * item's current height, or auto height if not explicitly set (defaults to true).
*/
- getMargins : function(){
- return this.isCollapsed && this.cmargins ? this.cmargins : this.margins;
- },
-
+ fill : true,
/**
- * Returns the current size of this region. If the region is collapsed, the size of the
- * collapsedEl will be returned, otherwise the size of the region's panel will be returned.
- * @return {Object} An object containing the element's size: <tt>{width: (element width),
- * height: (element height)}</tt>
+ * @cfg {Boolean} autoWidth
+ * True to set each contained item's width to 'auto', false to use the item's current width (defaults to true).
+ * Note that some components, in particular the {@link Ext.grid.GridPanel grid}, will not function properly within
+ * layouts if they have auto width, so in such cases this config should be set to false.
*/
- getSize : function(){
- return this.isCollapsed ? this.getCollapsedEl().getSize() : this.panel.getSize();
- },
-
+ autoWidth : true,
/**
- * Sets the specified panel as the container element for this region.
- * @param {Ext.Panel} panel The new panel
+ * @cfg {Boolean} titleCollapse
+ * True to allow expand/collapse of each contained panel by clicking anywhere on the title bar, false to allow
+ * expand/collapse only when the toggle tool button is clicked (defaults to true). When set to false,
+ * {@link #hideCollapseTool} should be false also.
*/
- setPanel : function(panel){
- this.panel = panel;
- },
-
+ titleCollapse : true,
/**
- * Returns the minimum allowable width for this region.
- * @return {Number} The minimum width
+ * @cfg {Boolean} hideCollapseTool
+ * True to hide the contained panels' collapse/expand toggle buttons, false to display them (defaults to false).
+ * When set to true, {@link #titleCollapse} should be true also.
*/
- getMinWidth: function(){
- return this.minWidth;
- },
-
+ hideCollapseTool : false,
/**
- * Returns the minimum allowable height for this region.
- * @return {Number} The minimum height
+ * @cfg {Boolean} collapseFirst
+ * True to make sure the collapse/expand toggle button always renders first (to the left of) any other tools
+ * in the contained panels' title bars, false to render it last (defaults to false).
*/
- getMinHeight: function(){
- return this.minHeight;
+ collapseFirst : false,
+ /**
+ * @cfg {Boolean} animate
+ * True to slide the contained panels open and closed during expand/collapse using animation, false to open and
+ * close directly with no animation (defaults to false). Note: to defer to the specific config setting of each
+ * contained panel for this property, set this to undefined at the layout level.
+ */
+ animate : false,
+ /**
+ * @cfg {Boolean} sequence
+ * <b>Experimental</b>. If animate is set to true, this will result in each animation running in sequence.
+ */
+ sequence : false,
+ /**
+ * @cfg {Boolean} activeOnTop
+ * True to swap the position of each panel as it is expanded so that it becomes the first item in the container,
+ * false to keep the panels in the rendered order. <b>This is NOT compatible with "animate:true"</b> (defaults to false).
+ */
+ activeOnTop : false,
+
+ type: 'accordion',
+
+ renderItem : function(c){
+ if(this.animate === false){
+ c.animCollapse = false;
+ }
+ c.collapsible = true;
+ if(this.autoWidth){
+ c.autoWidth = true;
+ }
+ if(this.titleCollapse){
+ c.titleCollapse = true;
+ }
+ if(this.hideCollapseTool){
+ c.hideCollapseTool = true;
+ }
+ if(this.collapseFirst !== undefined){
+ c.collapseFirst = this.collapseFirst;
+ }
+ if(!this.activeItem && !c.collapsed){
+ this.setActiveItem(c, true);
+ }else if(this.activeItem && this.activeItem != c){
+ c.collapsed = true;
+ }
+ Ext.layout.AccordionLayout.superclass.renderItem.apply(this, arguments);
+ c.header.addClass('x-accordion-hd');
+ c.on('beforeexpand', this.beforeExpand, this);
},
- // private
- applyLayoutCollapsed : function(box){
- var ce = this.getCollapsedEl();
- ce.setLeftTop(box.x, box.y);
- ce.setSize(box.width, box.height);
+ onRemove: function(c){
+ Ext.layout.AccordionLayout.superclass.onRemove.call(this, c);
+ if(c.rendered){
+ c.header.removeClass('x-accordion-hd');
+ }
+ c.un('beforeexpand', this.beforeExpand, this);
},
// private
- applyLayout : function(box){
- if(this.isCollapsed){
- this.applyLayoutCollapsed(box);
- }else{
- this.panel.setPosition(box.x, box.y);
- this.panel.setSize(box.width, box.height);
+ beforeExpand : function(p, anim){
+ var ai = this.activeItem;
+ if(ai){
+ if(this.sequence){
+ delete this.activeItem;
+ if (!ai.collapsed){
+ ai.collapse({callback:function(){
+ p.expand(anim || true);
+ }, scope: this});
+ return false;
+ }
+ }else{
+ ai.collapse(this.animate);
+ }
+ }
+ this.setActive(p);
+ if(this.activeOnTop){
+ p.el.dom.parentNode.insertBefore(p.el.dom, p.el.dom.parentNode.firstChild);
}
+ // Items have been hidden an possibly rearranged, we need to get the container size again.
+ this.layout();
},
// private
- beforeSlide: function(){
- this.panel.beforeEffect();
+ setItemSize : function(item, size){
+ if(this.fill && item){
+ var hh = 0, i, ct = this.getRenderedItems(this.container), len = ct.length, p;
+ // Add up all the header heights
+ for (i = 0; i < len; i++) {
+ if((p = ct[i]) != item && !p.hidden){
+ hh += p.header.getHeight();
+ }
+ };
+ // Subtract the header heights from the container size
+ size.height -= hh;
+ // Call setSize on the container to set the correct height. For Panels, deferedHeight
+ // will simply store this size for when the expansion is done.
+ item.setSize(size);
+ }
},
- // private
- afterSlide : function(){
- this.panel.afterEffect();
+ /**
+ * Sets the active (expanded) item in the layout.
+ * @param {String/Number} item The string component id or numeric index of the item to activate
+ */
+ setActiveItem : function(item){
+ this.setActive(item, true);
},
// private
- initAutoHide : function(){
- if(this.autoHide !== false){
- if(!this.autoHideHd){
- this.autoHideSlideTask = new Ext.util.DelayedTask(this.slideIn, this);
- this.autoHideHd = {
- "mouseout": function(e){
- if(!e.within(this.el, true)){
- this.autoHideSlideTask.delay(500);
- }
- },
- "mouseover" : function(e){
- this.autoHideSlideTask.cancel();
- },
- scope : this
- };
+ setActive : function(item, expand){
+ var ai = this.activeItem;
+ item = this.container.getComponent(item);
+ if(ai != item){
+ if(item.rendered && item.collapsed && expand){
+ item.expand();
+ }else{
+ if(ai){
+ ai.fireEvent('deactivate', ai);
+ }
+ this.activeItem = item;
+ item.fireEvent('activate', item);
}
- this.el.on(this.autoHideHd);
- this.collapsedEl.on(this.autoHideHd);
}
- },
+ }
+});
+Ext.Container.LAYOUTS.accordion = Ext.layout.AccordionLayout;
- // private
- clearAutoHide : function(){
- if(this.autoHide !== false){
- this.el.un("mouseout", this.autoHideHd.mouseout);
- this.el.un("mouseover", this.autoHideHd.mouseover);
- this.collapsedEl.un("mouseout", this.autoHideHd.mouseout);
- this.collapsedEl.un("mouseover", this.autoHideHd.mouseover);
- }
+//backwards compat
+Ext.layout.Accordion = Ext.layout.AccordionLayout;/**
+ * @class Ext.layout.TableLayout
+ * @extends Ext.layout.ContainerLayout
+ * <p>This layout allows you to easily render content into an HTML table. The total number of columns can be
+ * specified, and rowspan and colspan can be used to create complex layouts within the table.
+ * This class is intended to be extended or created via the layout:'table' {@link Ext.Container#layout} config,
+ * and should generally not need to be created directly via the new keyword.</p>
+ * <p>Note that when creating a layout via config, the layout-specific config properties must be passed in via
+ * the {@link Ext.Container#layoutConfig} object which will then be applied internally to the layout. In the
+ * case of TableLayout, the only valid layout config property is {@link #columns}. However, the items added to a
+ * TableLayout can supply the following table-specific config properties:</p>
+ * <ul>
+ * <li><b>rowspan</b> Applied to the table cell containing the item.</li>
+ * <li><b>colspan</b> Applied to the table cell containing the item.</li>
+ * <li><b>cellId</b> An id applied to the table cell containing the item.</li>
+ * <li><b>cellCls</b> A CSS class name added to the table cell containing the item.</li>
+ * </ul>
+ * <p>The basic concept of building up a TableLayout is conceptually very similar to building up a standard
+ * HTML table. You simply add each panel (or "cell") that you want to include along with any span attributes
+ * specified as the special config properties of rowspan and colspan which work exactly like their HTML counterparts.
+ * Rather than explicitly creating and nesting rows and columns as you would in HTML, you simply specify the
+ * total column count in the layoutConfig and start adding panels in their natural order from left to right,
+ * top to bottom. The layout will automatically figure out, based on the column count, rowspans and colspans,
+ * how to position each panel within the table. Just like with HTML tables, your rowspans and colspans must add
+ * up correctly in your overall layout or you'll end up with missing and/or extra cells! Example usage:</p>
+ * <pre><code>
+// This code will generate a layout table that is 3 columns by 2 rows
+// with some spanning included. The basic layout will be:
+// +--------+-----------------+
+// | A | B |
+// | |--------+--------|
+// | | C | D |
+// +--------+--------+--------+
+var table = new Ext.Panel({
+ title: 'Table Layout',
+ layout:'table',
+ defaults: {
+ // applied to each contained panel
+ bodyStyle:'padding:20px'
},
-
- // private
- clearMonitor : function(){
- Ext.getDoc().un("click", this.slideInIf, this);
+ layoutConfig: {
+ // The total column count must be specified here
+ columns: 3
},
-
+ items: [{
+ html: '<p>Cell A content</p>',
+ rowspan: 2
+ },{
+ html: '<p>Cell B content</p>',
+ colspan: 2
+ },{
+ html: '<p>Cell C content</p>',
+ cellCls: 'highlight'
+ },{
+ html: '<p>Cell D content</p>'
+ }]
+});
+</code></pre>
+ */
+Ext.layout.TableLayout = Ext.extend(Ext.layout.ContainerLayout, {
/**
- * If this Region is {@link #floatable}, this method slides this Region into full visibility <i>over the top
- * of the center Region</i> where it floats until either {@link #slideIn} is called, or other regions of the layout
- * are clicked, or the mouse exits the Region.
+ * @cfg {Number} columns
+ * The total number of columns to create in the table for this layout. If not specified, all Components added to
+ * this layout will be rendered into a single row using one column per Component.
*/
- slideOut : function(){
- if(this.isSlid || this.el.hasActiveFx()){
- return;
- }
- this.isSlid = true;
- var ts = this.panel.tools, dh, pc;
- if(ts && ts.toggle){
- ts.toggle.hide();
- }
- this.el.show();
- // Temporarily clear the collapsed flag so we can onResize the panel on the slide
- pc = this.panel.collapsed;
- this.panel.collapsed = false;
+ // private
+ monitorResize:false,
- if(this.position == 'east' || this.position == 'west'){
- // Temporarily clear the deferHeight flag so we can size the height on the slide
- dh = this.panel.deferHeight;
- this.panel.deferHeight = false;
+ type: 'table',
- this.panel.setSize(undefined, this.collapsedEl.getHeight());
+ targetCls: 'x-table-layout-ct',
- // Put the deferHeight flag back after setSize
- this.panel.deferHeight = dh;
- }else{
- this.panel.setSize(this.collapsedEl.getWidth(), undefined);
- }
+ /**
+ * @cfg {Object} tableAttrs
+ * <p>An object containing properties which are added to the {@link Ext.DomHelper DomHelper} specification
+ * used to create the layout's <tt><table></tt> element. Example:</p><pre><code>
+{
+ xtype: 'panel',
+ layout: 'table',
+ layoutConfig: {
+ tableAttrs: {
+ style: {
+ width: '100%'
+ }
+ },
+ columns: 3
+ }
+}</code></pre>
+ */
+ tableAttrs:null,
- // Put the collapsed flag back after onResize
- this.panel.collapsed = pc;
+ // private
+ setContainer : function(ct){
+ Ext.layout.TableLayout.superclass.setContainer.call(this, ct);
- this.restoreLT = [this.el.dom.style.left, this.el.dom.style.top];
- this.el.alignTo(this.collapsedEl, this.getCollapseAnchor());
- this.el.setStyle("z-index", this.floatingZIndex+2);
- this.panel.el.replaceClass('x-panel-collapsed', 'x-panel-floating');
- if(this.animFloat !== false){
- this.beforeSlide();
- this.el.slideIn(this.getSlideAnchor(), {
- callback: function(){
- this.afterSlide();
- this.initAutoHide();
- Ext.getDoc().on("click", this.slideInIf, this);
- },
- scope: this,
- block: true
- });
- }else{
- this.initAutoHide();
- Ext.getDoc().on("click", this.slideInIf, this);
- }
+ this.currentRow = 0;
+ this.currentColumn = 0;
+ this.cells = [];
},
-
+
// private
- afterSlideIn : function(){
- this.clearAutoHide();
- this.isSlid = false;
- this.clearMonitor();
- this.el.setStyle("z-index", "");
- this.panel.el.replaceClass('x-panel-floating', 'x-panel-collapsed');
- this.el.dom.style.left = this.restoreLT[0];
- this.el.dom.style.top = this.restoreLT[1];
+ onLayout : function(ct, target){
+ var cs = ct.items.items, len = cs.length, c, i;
- var ts = this.panel.tools;
- if(ts && ts.toggle){
- ts.toggle.show();
- }
- },
+ if(!this.table){
+ target.addClass('x-table-layout-ct');
- /**
- * If this Region is {@link #floatable}, and this Region has been slid into floating visibility, then this method slides
- * this region back into its collapsed state.
- */
- slideIn : function(cb){
- if(!this.isSlid || this.el.hasActiveFx()){
- Ext.callback(cb);
- return;
- }
- this.isSlid = false;
- if(this.animFloat !== false){
- this.beforeSlide();
- this.el.slideOut(this.getSlideAnchor(), {
- callback: function(){
- this.el.hide();
- this.afterSlide();
- this.afterSlideIn();
- Ext.callback(cb);
- },
- scope: this,
- block: true
- });
- }else{
- this.el.hide();
- this.afterSlideIn();
+ this.table = target.createChild(
+ Ext.apply({tag:'table', cls:'x-table-layout', cellspacing: 0, cn: {tag: 'tbody'}}, this.tableAttrs), null, true);
}
+ this.renderAll(ct, target);
},
// private
- slideInIf : function(e){
- if(!e.within(this.el)){
- this.slideIn();
+ getRow : function(index){
+ var row = this.table.tBodies[0].childNodes[index];
+ if(!row){
+ row = document.createElement('tr');
+ this.table.tBodies[0].appendChild(row);
}
+ return row;
},
// private
- anchors : {
- "west" : "left",
- "east" : "right",
- "north" : "top",
- "south" : "bottom"
+ getNextCell : function(c){
+ var cell = this.getNextNonSpan(this.currentColumn, this.currentRow);
+ var curCol = this.currentColumn = cell[0], curRow = this.currentRow = cell[1];
+ for(var rowIndex = curRow; rowIndex < curRow + (c.rowspan || 1); rowIndex++){
+ if(!this.cells[rowIndex]){
+ this.cells[rowIndex] = [];
+ }
+ for(var colIndex = curCol; colIndex < curCol + (c.colspan || 1); colIndex++){
+ this.cells[rowIndex][colIndex] = true;
+ }
+ }
+ var td = document.createElement('td');
+ if(c.cellId){
+ td.id = c.cellId;
+ }
+ var cls = 'x-table-layout-cell';
+ if(c.cellCls){
+ cls += ' ' + c.cellCls;
+ }
+ td.className = cls;
+ if(c.colspan){
+ td.colSpan = c.colspan;
+ }
+ if(c.rowspan){
+ td.rowSpan = c.rowspan;
+ }
+ this.getRow(curRow).appendChild(td);
+ return td;
},
// private
- sanchors : {
- "west" : "l",
- "east" : "r",
- "north" : "t",
- "south" : "b"
+ getNextNonSpan: function(colIndex, rowIndex){
+ var cols = this.columns;
+ while((cols && colIndex >= cols) || (this.cells[rowIndex] && this.cells[rowIndex][colIndex])) {
+ if(cols && colIndex >= cols){
+ rowIndex++;
+ colIndex = 0;
+ }else{
+ colIndex++;
+ }
+ }
+ return [colIndex, rowIndex];
},
// private
- canchors : {
- "west" : "tl-tr",
- "east" : "tr-tl",
- "north" : "tl-bl",
- "south" : "bl-tl"
+ renderItem : function(c, position, target){
+ // Ensure we have our inner table to get cells to render into.
+ if(!this.table){
+ this.table = target.createChild(
+ Ext.apply({tag:'table', cls:'x-table-layout', cellspacing: 0, cn: {tag: 'tbody'}}, this.tableAttrs), null, true);
+ }
+ if(c && !c.rendered){
+ c.render(this.getNextCell(c));
+ this.configureItem(c);
+ }else if(c && !this.isValidParent(c, target)){
+ var container = this.getNextCell(c);
+ container.insertBefore(c.getPositionEl().dom, null);
+ c.container = Ext.get(container);
+ this.configureItem(c);
+ }
},
// private
- getAnchor : function(){
- return this.anchors[this.position];
+ isValidParent : function(c, target){
+ return c.getPositionEl().up('table', 5).dom.parentNode === (target.dom || target);
},
+
+ destroy: function(){
+ delete this.table;
+ Ext.layout.TableLayout.superclass.destroy.call(this);
+ }
- // private
- getCollapseAnchor : function(){
- return this.canchors[this.position];
- },
+ /**
+ * @property activeItem
+ * @hide
+ */
+});
- // private
- getSlideAnchor : function(){
- return this.sanchors[this.position];
+Ext.Container.LAYOUTS['table'] = Ext.layout.TableLayout;/**
+ * @class Ext.layout.AbsoluteLayout
+ * @extends Ext.layout.AnchorLayout
+ * <p>This is a layout that inherits the anchoring of <b>{@link Ext.layout.AnchorLayout}</b> and adds the
+ * ability for x/y positioning using the standard x and y component config options.</p>
+ * <p>This class is intended to be extended or created via the <tt><b>{@link Ext.Container#layout layout}</b></tt>
+ * configuration property. See <tt><b>{@link Ext.Container#layout}</b></tt> for additional details.</p>
+ * <p>Example usage:</p>
+ * <pre><code>
+var form = new Ext.form.FormPanel({
+ title: 'Absolute Layout',
+ layout:'absolute',
+ layoutConfig: {
+ // layout-specific configs go here
+ extraCls: 'x-abs-layout-item',
},
+ baseCls: 'x-plain',
+ url:'save-form.php',
+ defaultType: 'textfield',
+ items: [{
+ x: 0,
+ y: 5,
+ xtype:'label',
+ text: 'Send To:'
+ },{
+ x: 60,
+ y: 0,
+ name: 'to',
+ anchor:'100%' // anchor width by percentage
+ },{
+ x: 0,
+ y: 35,
+ xtype:'label',
+ text: 'Subject:'
+ },{
+ x: 60,
+ y: 30,
+ name: 'subject',
+ anchor: '100%' // anchor width by percentage
+ },{
+ x:0,
+ y: 60,
+ xtype: 'textarea',
+ name: 'msg',
+ anchor: '100% 100%' // anchor width and height
+ }]
+});
+</code></pre>
+ */
+Ext.layout.AbsoluteLayout = Ext.extend(Ext.layout.AnchorLayout, {
- // private
- getAlignAdj : function(){
- var cm = this.cmargins;
- switch(this.position){
- case "west":
- return [0, 0];
- break;
- case "east":
- return [0, 0];
- break;
- case "north":
- return [0, 0];
- break;
- case "south":
- return [0, 0];
- break;
- }
+ extraCls: 'x-abs-layout-item',
+
+ type: 'absolute',
+
+ onLayout : function(ct, target){
+ target.position();
+ this.paddingLeft = target.getPadding('l');
+ this.paddingTop = target.getPadding('t');
+ Ext.layout.AbsoluteLayout.superclass.onLayout.call(this, ct, target);
},
// private
- getExpandAdj : function(){
- var c = this.collapsedEl, cm = this.cmargins;
- switch(this.position){
- case "west":
- return [-(cm.right+c.getWidth()+cm.left), 0];
- break;
- case "east":
- return [cm.right+c.getWidth()+cm.left, 0];
- break;
- case "north":
- return [0, -(cm.top+cm.bottom+c.getHeight())];
- break;
- case "south":
- return [0, cm.top+cm.bottom+c.getHeight()];
- break;
- }
+ adjustWidthAnchor : function(value, comp){
+ return value ? value - comp.getPosition(true)[0] + this.paddingLeft : value;
},
- destroy : function(){
- if (this.autoHideSlideTask && this.autoHideSlideTask.cancel){
- this.autoHideSlideTask.cancel();
- }
- Ext.destroy(this.miniCollapsedEl, this.collapsedEl);
+ // private
+ adjustHeightAnchor : function(value, comp){
+ return value ? value - comp.getPosition(true)[1] + this.paddingTop : value;
}
-};
-
-/**
- * @class Ext.layout.BorderLayout.SplitRegion
- * @extends Ext.layout.BorderLayout.Region
- * <p>This is a specialized type of {@link Ext.layout.BorderLayout.Region BorderLayout region} that
- * has a built-in {@link Ext.SplitBar} for user resizing of regions. The movement of the split bar
- * is configurable to move either {@link #tickSize smooth or incrementally}.</p>
- * @constructor
- * Create a new SplitRegion.
- * @param {Layout} layout The {@link Ext.layout.BorderLayout BorderLayout} instance that is managing this Region.
- * @param {Object} config The configuration options
- * @param {String} position The region position. Valid values are: north, south, east, west and center. Every
- * BorderLayout must have a center region for the primary content -- all other regions are optional.
- */
-Ext.layout.BorderLayout.SplitRegion = function(layout, config, pos){
- Ext.layout.BorderLayout.SplitRegion.superclass.constructor.call(this, layout, config, pos);
- // prevent switch
- this.applyLayout = this.applyFns[pos];
-};
-
-Ext.extend(Ext.layout.BorderLayout.SplitRegion, Ext.layout.BorderLayout.Region, {
/**
- * @cfg {Number} tickSize
- * The increment, in pixels by which to move this Region's {@link Ext.SplitBar SplitBar}.
- * By default, the {@link Ext.SplitBar SplitBar} moves smoothly.
+ * @property activeItem
+ * @hide
*/
+});
+Ext.Container.LAYOUTS['absolute'] = Ext.layout.AbsoluteLayout;
+/**
+ * @class Ext.layout.BoxLayout
+ * @extends Ext.layout.ContainerLayout
+ * <p>Base Class for HBoxLayout and VBoxLayout Classes. Generally it should not need to be used directly.</p>
+ */
+Ext.layout.BoxLayout = Ext.extend(Ext.layout.ContainerLayout, {
/**
- * @cfg {String} splitTip
- * The tooltip to display when the user hovers over a
- * {@link Ext.layout.BorderLayout.Region#collapsible non-collapsible} region's split bar
- * (defaults to <tt>"Drag to resize."</tt>). Only applies if
- * <tt>{@link #useSplitTips} = true</tt>.
+ * @cfg {Object} defaultMargins
+ * <p>If the individual contained items do not have a <tt>margins</tt>
+ * property specified, the default margins from this property will be
+ * applied to each item.</p>
+ * <br><p>This property may be specified as an object containing margins
+ * to apply in the format:</p><pre><code>
+{
+ top: (top margin),
+ right: (right margin),
+ bottom: (bottom margin),
+ left: (left margin)
+}</code></pre>
+ * <p>This property may also be specified as a string containing
+ * space-separated, numeric margin values. The order of the sides associated
+ * with each value matches the way CSS processes margin values:</p>
+ * <div class="mdetail-params"><ul>
+ * <li>If there is only one value, it applies to all sides.</li>
+ * <li>If there are two values, the top and bottom borders are set to the
+ * first value and the right and left are set to the second.</li>
+ * <li>If there are three values, the top is set to the first value, the left
+ * and right are set to the second, and the bottom is set to the third.</li>
+ * <li>If there are four values, they apply to the top, right, bottom, and
+ * left, respectively.</li>
+ * </ul></div>
+ * <p>Defaults to:</p><pre><code>
+ * {top:0, right:0, bottom:0, left:0}
+ * </code></pre>
*/
- splitTip : "Drag to resize.",
+ defaultMargins : {left:0,top:0,right:0,bottom:0},
/**
- * @cfg {String} collapsibleSplitTip
- * The tooltip to display when the user hovers over a
- * {@link Ext.layout.BorderLayout.Region#collapsible collapsible} region's split bar
- * (defaults to "Drag to resize. Double click to hide."). Only applies if
- * <tt>{@link #useSplitTips} = true</tt>.
+ * @cfg {String} padding
+ * <p>Sets the padding to be applied to all child items managed by this layout.</p>
+ * <p>This property must be specified as a string containing
+ * space-separated, numeric padding values. The order of the sides associated
+ * with each value matches the way CSS processes padding values:</p>
+ * <div class="mdetail-params"><ul>
+ * <li>If there is only one value, it applies to all sides.</li>
+ * <li>If there are two values, the top and bottom borders are set to the
+ * first value and the right and left are set to the second.</li>
+ * <li>If there are three values, the top is set to the first value, the left
+ * and right are set to the second, and the bottom is set to the third.</li>
+ * <li>If there are four values, they apply to the top, right, bottom, and
+ * left, respectively.</li>
+ * </ul></div>
+ * <p>Defaults to: <code>"0"</code></p>
*/
- collapsibleSplitTip : "Drag to resize. Double click to hide.",
+ padding : '0',
+ // documented in subclasses
+ pack : 'start',
+
+ // private
+ monitorResize : true,
+ type: 'box',
+ scrollOffset : 0,
+ extraCls : 'x-box-item',
+ targetCls : 'x-box-layout-ct',
+ innerCls : 'x-box-inner',
+
+ constructor : function(config){
+ Ext.layout.BoxLayout.superclass.constructor.call(this, config);
+
+ if (Ext.isString(this.defaultMargins)) {
+ this.defaultMargins = this.parseMargins(this.defaultMargins);
+ }
+
+ var handler = this.overflowHandler;
+
+ if (typeof handler == 'string') {
+ handler = {
+ type: handler
+ };
+ }
+
+ var handlerType = 'none';
+ if (handler && handler.type != undefined) {
+ handlerType = handler.type;
+ }
+
+ var constructor = Ext.layout.boxOverflow[handlerType];
+ if (constructor[this.type]) {
+ constructor = constructor[this.type];
+ }
+
+ this.overflowHandler = new constructor(this, handler);
+ },
+
/**
- * @cfg {Boolean} useSplitTips
- * <tt>true</tt> to display a tooltip when the user hovers over a region's split bar
- * (defaults to <tt>false</tt>). The tooltip text will be the value of either
- * <tt>{@link #splitTip}</tt> or <tt>{@link #collapsibleSplitTip}</tt> as appropriate.
+ * @private
+ * Runs the child box calculations and caches them in childBoxCache. Subclasses can used these cached values
+ * when laying out
*/
- useSplitTips : false,
+ onLayout: function(container, target) {
+ Ext.layout.BoxLayout.superclass.onLayout.call(this, container, target);
- // private
- splitSettings : {
- north : {
- orientation: Ext.SplitBar.VERTICAL,
- placement: Ext.SplitBar.TOP,
- maxFn : 'getVMaxSize',
- minProp: 'minHeight',
- maxProp: 'maxHeight'
- },
- south : {
- orientation: Ext.SplitBar.VERTICAL,
- placement: Ext.SplitBar.BOTTOM,
- maxFn : 'getVMaxSize',
- minProp: 'minHeight',
- maxProp: 'maxHeight'
- },
- east : {
- orientation: Ext.SplitBar.HORIZONTAL,
- placement: Ext.SplitBar.RIGHT,
- maxFn : 'getHMaxSize',
- minProp: 'minWidth',
- maxProp: 'maxWidth'
- },
- west : {
- orientation: Ext.SplitBar.HORIZONTAL,
- placement: Ext.SplitBar.LEFT,
- maxFn : 'getHMaxSize',
- minProp: 'minWidth',
- maxProp: 'maxWidth'
+ var tSize = this.getLayoutTargetSize(),
+ items = this.getVisibleItems(container),
+ calcs = this.calculateChildBoxes(items, tSize),
+ boxes = calcs.boxes,
+ meta = calcs.meta;
+
+ //invoke the overflow handler, if one is configured
+ if (tSize.width > 0) {
+ var handler = this.overflowHandler,
+ method = meta.tooNarrow ? 'handleOverflow' : 'clearOverflow';
+
+ var results = handler[method](calcs, tSize);
+
+ if (results) {
+ if (results.targetSize) {
+ tSize = results.targetSize;
+ }
+
+ if (results.recalculate) {
+ items = this.getVisibleItems(container);
+ calcs = this.calculateChildBoxes(items, tSize);
+ boxes = calcs.boxes;
+ }
+ }
}
+
+ /**
+ * @private
+ * @property layoutTargetLastSize
+ * @type Object
+ * Private cache of the last measured size of the layout target. This should never be used except by
+ * BoxLayout subclasses during their onLayout run.
+ */
+ this.layoutTargetLastSize = tSize;
+
+ /**
+ * @private
+ * @property childBoxCache
+ * @type Array
+ * Array of the last calculated height, width, top and left positions of each visible rendered component
+ * within the Box layout.
+ */
+ this.childBoxCache = calcs;
+
+ this.updateInnerCtSize(tSize, calcs);
+ this.updateChildBoxes(boxes);
+
+ // Putting a box layout into an overflowed container is NOT correct and will make a second layout pass necessary.
+ this.handleTargetOverflow(tSize, container, target);
},
- // private
- applyFns : {
- west : function(box){
- if(this.isCollapsed){
- return this.applyLayoutCollapsed(box);
+ /**
+ * Resizes and repositions each child component
+ * @param {Array} boxes The box measurements
+ */
+ updateChildBoxes: function(boxes) {
+ for (var i = 0, length = boxes.length; i < length; i++) {
+ var box = boxes[i],
+ comp = box.component;
+
+ if (box.dirtySize) {
+ comp.setSize(box.width, box.height);
}
- var sd = this.splitEl.dom, s = sd.style;
- this.panel.setPosition(box.x, box.y);
- var sw = sd.offsetWidth;
- s.left = (box.x+box.width-sw)+'px';
- s.top = (box.y)+'px';
- s.height = Math.max(0, box.height)+'px';
- this.panel.setSize(box.width-sw, box.height);
- },
- east : function(box){
- if(this.isCollapsed){
- return this.applyLayoutCollapsed(box);
+ // Don't set positions to NaN
+ if (isNaN(box.left) || isNaN(box.top)) {
+ continue;
}
- var sd = this.splitEl.dom, s = sd.style;
- var sw = sd.offsetWidth;
- this.panel.setPosition(box.x+sw, box.y);
- s.left = (box.x)+'px';
- s.top = (box.y)+'px';
- s.height = Math.max(0, box.height)+'px';
- this.panel.setSize(box.width-sw, box.height);
- },
- north : function(box){
- if(this.isCollapsed){
- return this.applyLayoutCollapsed(box);
+
+ comp.setPosition(box.left, box.top);
+ }
+ },
+
+ /**
+ * @private
+ * Called by onRender just before the child components are sized and positioned. This resizes the innerCt
+ * to make sure all child items fit within it. We call this before sizing the children because if our child
+ * items are larger than the previous innerCt size the browser will insert scrollbars and then remove them
+ * again immediately afterwards, giving a performance hit.
+ * Subclasses should provide an implementation.
+ * @param {Object} currentSize The current height and width of the innerCt
+ * @param {Array} calculations The new box calculations of all items to be laid out
+ */
+ updateInnerCtSize: function(tSize, calcs) {
+ var align = this.align,
+ padding = this.padding,
+ width = tSize.width,
+ height = tSize.height;
+
+ if (this.type == 'hbox') {
+ var innerCtWidth = width,
+ innerCtHeight = calcs.meta.maxHeight + padding.top + padding.bottom;
+
+ if (align == 'stretch') {
+ innerCtHeight = height;
+ } else if (align == 'middle') {
+ innerCtHeight = Math.max(height, innerCtHeight);
}
- var sd = this.splitEl.dom, s = sd.style;
- var sh = sd.offsetHeight;
- this.panel.setPosition(box.x, box.y);
- s.left = (box.x)+'px';
- s.top = (box.y+box.height-sh)+'px';
- s.width = Math.max(0, box.width)+'px';
- this.panel.setSize(box.width, box.height-sh);
- },
- south : function(box){
- if(this.isCollapsed){
- return this.applyLayoutCollapsed(box);
+ } else {
+ var innerCtHeight = height,
+ innerCtWidth = calcs.meta.maxWidth + padding.left + padding.right;
+
+ if (align == 'stretch') {
+ innerCtWidth = width;
+ } else if (align == 'center') {
+ innerCtWidth = Math.max(width, innerCtWidth);
}
- var sd = this.splitEl.dom, s = sd.style;
- var sh = sd.offsetHeight;
- this.panel.setPosition(box.x, box.y+sh);
- s.left = (box.x)+'px';
- s.top = (box.y)+'px';
- s.width = Math.max(0, box.width)+'px';
- this.panel.setSize(box.width, box.height-sh);
}
+
+ this.innerCt.setSize(innerCtWidth || undefined, innerCtHeight || undefined);
},
- // private
- render : function(ct, p){
- Ext.layout.BorderLayout.SplitRegion.superclass.render.call(this, ct, p);
+ /**
+ * @private
+ * This should be called after onLayout of any BoxLayout subclass. If the target's overflow is not set to 'hidden',
+ * we need to lay out a second time because the scrollbars may have modified the height and width of the layout
+ * target. Having a Box layout inside such a target is therefore not recommended.
+ * @param {Object} previousTargetSize The size and height of the layout target before we just laid out
+ * @param {Ext.Container} container The container
+ * @param {Ext.Element} target The target element
+ */
+ handleTargetOverflow: function(previousTargetSize, container, target) {
+ var overflow = target.getStyle('overflow');
- var ps = this.position;
+ if (overflow && overflow != 'hidden' &&!this.adjustmentPass) {
+ var newTargetSize = this.getLayoutTargetSize();
+ if (newTargetSize.width != previousTargetSize.width || newTargetSize.height != previousTargetSize.height){
+ this.adjustmentPass = true;
+ this.onLayout(container, target);
+ }
+ }
- this.splitEl = ct.createChild({
- cls: "x-layout-split x-layout-split-"+ps, html: " ",
- id: this.panel.id + '-xsplit'
- });
+ delete this.adjustmentPass;
+ },
- if(this.collapseMode == 'mini'){
- this.miniSplitEl = this.splitEl.createChild({
- cls: "x-layout-mini x-layout-mini-"+ps, html: " "
- });
- this.miniSplitEl.addClassOnOver('x-layout-mini-over');
- this.miniSplitEl.on('click', this.onCollapseClick, this, {stopEvent:true});
- }
+ // private
+ isValidParent : function(c, target) {
+ return this.innerCt && c.getPositionEl().dom.parentNode == this.innerCt.dom;
+ },
- var s = this.splitSettings[ps];
+ /**
+ * @private
+ * Returns all items that are both rendered and visible
+ * @return {Array} All matching items
+ */
+ getVisibleItems: function(ct) {
+ var ct = ct || this.container,
+ t = ct.getLayoutTarget(),
+ cti = ct.items.items,
+ len = cti.length,
- this.split = new Ext.SplitBar(this.splitEl.dom, p.el, s.orientation);
- this.split.tickSize = this.tickSize;
- this.split.placement = s.placement;
- this.split.getMaximumSize = this[s.maxFn].createDelegate(this);
- this.split.minSize = this.minSize || this[s.minProp];
- this.split.on("beforeapply", this.onSplitMove, this);
- this.split.useShim = this.useShim === true;
- this.maxSize = this.maxSize || this[s.maxProp];
+ i, c, items = [];
- if(p.hidden){
- this.splitEl.hide();
+ for (i = 0; i < len; i++) {
+ if((c = cti[i]).rendered && this.isValidParent(c, t) && c.hidden !== true && c.collapsed !== true && c.shouldLayout !== false){
+ items.push(c);
+ }
}
- if(this.useSplitTips){
- this.splitEl.dom.title = this.collapsible ? this.collapsibleSplitTip : this.splitTip;
- }
- if(this.collapsible){
- this.splitEl.on("dblclick", this.onCollapseClick, this);
- }
+ return items;
},
- //docs inherit from superclass
- getSize : function(){
- if(this.isCollapsed){
- return this.collapsedEl.getSize();
- }
- var s = this.panel.getSize();
- if(this.position == 'north' || this.position == 'south'){
- s.height += this.splitEl.dom.offsetHeight;
- }else{
- s.width += this.splitEl.dom.offsetWidth;
+ // private
+ renderAll : function(ct, target) {
+ if (!this.innerCt) {
+ // the innerCt prevents wrapping and shuffling while the container is resizing
+ this.innerCt = target.createChild({cls:this.innerCls});
+ this.padding = this.parseMargins(this.padding);
}
- return s;
+ Ext.layout.BoxLayout.superclass.renderAll.call(this, ct, this.innerCt);
},
- // private
- getHMaxSize : function(){
- var cmax = this.maxSize || 10000;
- var center = this.layout.center;
- return Math.min(cmax, (this.el.getWidth()+center.el.getWidth())-center.getMinWidth());
- },
+ getLayoutTargetSize : function() {
+ var target = this.container.getLayoutTarget(), ret;
+
+ if (target) {
+ ret = target.getViewSize();
- // private
- getVMaxSize : function(){
- var cmax = this.maxSize || 10000;
- var center = this.layout.center;
- return Math.min(cmax, (this.el.getHeight()+center.el.getHeight())-center.getMinHeight());
+ // IE in strict mode will return a width of 0 on the 1st pass of getViewSize.
+ // Use getStyleSize to verify the 0 width, the adjustment pass will then work properly
+ // with getViewSize
+ if (Ext.isIE && Ext.isStrict && ret.width == 0){
+ ret = target.getStyleSize();
+ }
+
+ ret.width -= target.getPadding('lr');
+ ret.height -= target.getPadding('tb');
+ }
+
+ return ret;
},
// private
- onSplitMove : function(split, newSize){
- var s = this.panel.getSize();
- this.lastSplitSize = newSize;
- if(this.position == 'north' || this.position == 'south'){
- this.panel.setSize(s.width, newSize);
- this.state.height = newSize;
- }else{
- this.panel.setSize(newSize, s.height);
- this.state.width = newSize;
+ renderItem : function(c) {
+ if(Ext.isString(c.margins)){
+ c.margins = this.parseMargins(c.margins);
+ }else if(!c.margins){
+ c.margins = this.defaultMargins;
}
- this.layout.layout();
- this.panel.saveState();
- return false;
+ Ext.layout.BoxLayout.superclass.renderItem.apply(this, arguments);
},
-
+
/**
- * Returns a reference to the split bar in use by this region.
- * @return {Ext.SplitBar} The split bar
+ * @private
*/
- getSplitBar : function(){
- return this.split;
- },
-
- // inherit docs
- destroy : function() {
- Ext.destroy(this.miniSplitEl, this.split, this.splitEl);
- Ext.layout.BorderLayout.SplitRegion.superclass.destroy.call(this);
+ destroy: function() {
+ Ext.destroy(this.overflowHandler);
+
+ Ext.layout.BoxLayout.superclass.destroy.apply(this, arguments);
}
});
-Ext.Container.LAYOUTS['border'] = Ext.layout.BorderLayout;/**
- * @class Ext.layout.FormLayout
- * @extends Ext.layout.AnchorLayout
- * <p>This layout manager is specifically designed for rendering and managing child Components of
- * {@link Ext.form.FormPanel forms}. It is responsible for rendering the labels of
- * {@link Ext.form.Field Field}s.</p>
- *
- * <p>This layout manager is used when a Container is configured with the <tt>layout:'form'</tt>
- * {@link Ext.Container#layout layout} config option, and should generally not need to be created directly
- * via the new keyword. See <tt><b>{@link Ext.Container#layout}</b></tt> for additional details.</p>
- *
- * <p>In an application, it will usually be preferrable to use a {@link Ext.form.FormPanel FormPanel}
- * (which is configured with FormLayout as its layout class by default) since it also provides built-in
- * functionality for {@link Ext.form.BasicForm#doAction loading, validating and submitting} the form.</p>
- *
- * <p>A {@link Ext.Container Container} <i>using</i> the FormLayout layout manager (e.g.
- * {@link Ext.form.FormPanel} or specifying <tt>layout:'form'</tt>) can also accept the following
- * layout-specific config properties:<div class="mdetail-params"><ul>
- * <li><b><tt>{@link Ext.form.FormPanel#hideLabels hideLabels}</tt></b></li>
- * <li><b><tt>{@link Ext.form.FormPanel#labelAlign labelAlign}</tt></b></li>
- * <li><b><tt>{@link Ext.form.FormPanel#labelPad labelPad}</tt></b></li>
- * <li><b><tt>{@link Ext.form.FormPanel#labelSeparator labelSeparator}</tt></b></li>
- * <li><b><tt>{@link Ext.form.FormPanel#labelWidth labelWidth}</tt></b></li>
- * </ul></div></p>
- *
- * <p>Any Component (including Fields) managed by FormLayout accepts the following as a config option:
- * <div class="mdetail-params"><ul>
- * <li><b><tt>{@link Ext.Component#anchor anchor}</tt></b></li>
- * </ul></div></p>
- *
- * <p>Any Component managed by FormLayout may be rendered as a form field (with an associated label) by
- * configuring it with a non-null <b><tt>{@link Ext.Component#fieldLabel fieldLabel}</tt></b>. Components configured
- * in this way may be configured with the following options which affect the way the FormLayout renders them:
- * <div class="mdetail-params"><ul>
- * <li><b><tt>{@link Ext.Component#clearCls clearCls}</tt></b></li>
- * <li><b><tt>{@link Ext.Component#fieldLabel fieldLabel}</tt></b></li>
- * <li><b><tt>{@link Ext.Component#hideLabel hideLabel}</tt></b></li>
- * <li><b><tt>{@link Ext.Component#itemCls itemCls}</tt></b></li>
- * <li><b><tt>{@link Ext.Component#labelSeparator labelSeparator}</tt></b></li>
- * <li><b><tt>{@link Ext.Component#labelStyle labelStyle}</tt></b></li>
- * </ul></div></p>
- *
- * <p>Example usage:</p>
- * <pre><code>
-// Required if showing validation messages
-Ext.QuickTips.init();
-// While you can create a basic Panel with layout:'form', practically
-// you should usually use a FormPanel to also get its form functionality
-// since it already creates a FormLayout internally.
-var form = new Ext.form.FormPanel({
- title: 'Form Layout',
- bodyStyle: 'padding:15px',
- width: 350,
- defaultType: 'textfield',
- defaults: {
- // applied to each contained item
- width: 230,
- msgTarget: 'side'
- },
- items: [{
- fieldLabel: 'First Name',
- name: 'first',
- allowBlank: false,
- {@link Ext.Component#labelSeparator labelSeparator}: ':' // override labelSeparator layout config
- },{
- fieldLabel: 'Last Name',
- name: 'last'
- },{
- fieldLabel: 'Email',
- name: 'email',
- vtype:'email'
- }, {
- xtype: 'textarea',
- hideLabel: true, // override hideLabels layout config
- name: 'msg',
- anchor: '100% -53'
- }
- ],
- buttons: [
- {text: 'Save'},
- {text: 'Cancel'}
- ],
- layoutConfig: {
- {@link #labelSeparator}: '~' // superseded by assignment below
+
+Ext.ns('Ext.layout.boxOverflow');
+
+/**
+ * @class Ext.layout.boxOverflow.None
+ * @extends Object
+ * Base class for Box Layout overflow handlers. These specialized classes are invoked when a Box Layout
+ * (either an HBox or a VBox) has child items that are either too wide (for HBox) or too tall (for VBox)
+ * for its container.
+ */
+
+Ext.layout.boxOverflow.None = Ext.extend(Object, {
+ constructor: function(layout, config) {
+ this.layout = layout;
+
+ Ext.apply(this, config || {});
},
- // config options applicable to container when layout='form':
- hideLabels: false,
- labelAlign: 'left', // or 'right' or 'top'
- {@link Ext.form.FormPanel#labelSeparator labelSeparator}: '>>', // takes precedence over layoutConfig value
- labelWidth: 65, // defaults to 100
- labelPad: 8 // defaults to 5, must specify labelWidth to be honored
+
+ handleOverflow: Ext.emptyFn,
+
+ clearOverflow: Ext.emptyFn
});
-</code></pre>
- */
-Ext.layout.FormLayout = Ext.extend(Ext.layout.AnchorLayout, {
+
+Ext.layout.boxOverflow.none = Ext.layout.boxOverflow.None;
+/**
+ * @class Ext.layout.boxOverflow.Menu
+ * @extends Ext.layout.boxOverflow.None
+ * Description
+ */
+Ext.layout.boxOverflow.Menu = Ext.extend(Ext.layout.boxOverflow.None, {
/**
- * @cfg {String} labelSeparator
- * See {@link Ext.form.FormPanel}.{@link Ext.form.FormPanel#labelSeparator labelSeparator}. Configuration
- * of this property at the <b>container</b> level takes precedence.
+ * @cfg afterCls
+ * @type String
+ * CSS class added to the afterCt element. This is the element that holds any special items such as scrollers,
+ * which must always be present at the rightmost edge of the Container
*/
- labelSeparator : ':',
-
+ afterCls: 'x-strip-right',
+
/**
- * Read only. The CSS style specification string added to field labels in this layout if not
- * otherwise {@link Ext.Component#labelStyle specified by each contained field}.
+ * @property noItemsMenuText
* @type String
- * @property labelStyle
+ * HTML fragment to render into the toolbar overflow menu if there are no items to display
*/
-
+ noItemsMenuText : '<div class="x-toolbar-no-items">(None)</div>',
+
+ constructor: function(layout) {
+ Ext.layout.boxOverflow.Menu.superclass.constructor.apply(this, arguments);
+
+ /**
+ * @property menuItems
+ * @type Array
+ * Array of all items that are currently hidden and should go into the dropdown menu
+ */
+ this.menuItems = [];
+ },
+
/**
- * @cfg {Boolean} trackLabels
- * True to show/hide the field label when the field is hidden. Defaults to <tt>false</tt>.
+ * @private
+ * Creates the beforeCt, innerCt and afterCt elements if they have not already been created
+ * @param {Ext.Container} container The Container attached to this Layout instance
+ * @param {Ext.Element} target The target Element
*/
- trackLabels: false,
-
- type: 'form',
-
- onRemove: function(c){
- Ext.layout.FormLayout.superclass.onRemove.call(this, c);
- if(this.trackLabels){
- c.un('show', this.onFieldShow, this);
- c.un('hide', this.onFieldHide, this);
- }
- // check for itemCt, since we may be removing a fieldset or something similar
- var el = c.getPositionEl(),
- ct = c.getItemCt && c.getItemCt();
- if (c.rendered && ct) {
- if (el && el.dom) {
- el.insertAfter(ct);
- }
- Ext.destroy(ct);
- Ext.destroyMembers(c, 'label', 'itemCt');
- if (c.customItemCt) {
- Ext.destroyMembers(c, 'getItemCt', 'customItemCt');
- }
+ createInnerElements: function() {
+ if (!this.afterCt) {
+ this.afterCt = this.layout.innerCt.insertSibling({cls: this.afterCls}, 'before');
}
},
-
- // private
- setContainer : function(ct){
- Ext.layout.FormLayout.superclass.setContainer.call(this, ct);
- if(ct.labelAlign){
- ct.addClass('x-form-label-'+ct.labelAlign);
+
+ /**
+ * @private
+ */
+ clearOverflow: function(calculations, targetSize) {
+ var newWidth = targetSize.width + (this.afterCt ? this.afterCt.getWidth() : 0),
+ items = this.menuItems;
+
+ this.hideTrigger();
+
+ for (var index = 0, length = items.length; index < length; index++) {
+ items.pop().component.show();
}
-
- if(ct.hideLabels){
- Ext.apply(this, {
- labelStyle: 'display:none',
- elementStyle: 'padding-left:0;',
- labelAdjust: 0
- });
- }else{
- this.labelSeparator = ct.labelSeparator || this.labelSeparator;
- ct.labelWidth = ct.labelWidth || 100;
- if(Ext.isNumber(ct.labelWidth)){
- var pad = Ext.isNumber(ct.labelPad) ? ct.labelPad : 5;
- Ext.apply(this, {
- labelAdjust: ct.labelWidth + pad,
- labelStyle: 'width:' + ct.labelWidth + 'px;',
- elementStyle: 'padding-left:' + (ct.labelWidth + pad) + 'px'
- });
- }
- if(ct.labelAlign == 'top'){
- Ext.apply(this, {
- labelStyle: 'width:auto;',
- labelAdjust: 0,
- elementStyle: 'padding-left:0;'
- });
+
+ return {
+ targetSize: {
+ height: targetSize.height,
+ width : newWidth
}
- }
+ };
},
-
- // private
- isHide: function(c){
- return c.hideLabel || this.container.hideLabels;
+
+ /**
+ * @private
+ */
+ showTrigger: function() {
+ this.createMenu();
+ this.menuTrigger.show();
},
-
- onFieldShow: function(c){
- c.getItemCt().removeClass('x-hide-' + c.hideMode);
-
- // Composite fields will need to layout after the container is made visible
- if (c.isComposite) {
- c.doLayout();
+
+ /**
+ * @private
+ */
+ hideTrigger: function() {
+ if (this.menuTrigger != undefined) {
+ this.menuTrigger.hide();
}
},
+
+ /**
+ * @private
+ * Called before the overflow menu is shown. This constructs the menu's items, caching them for as long as it can.
+ */
+ beforeMenuShow: function(menu) {
+ var items = this.menuItems,
+ len = items.length,
+ item,
+ prev;
- onFieldHide: function(c){
- c.getItemCt().addClass('x-hide-' + c.hideMode);
- },
-
- //private
- getLabelStyle: function(s){
- var ls = '', items = [this.labelStyle, s];
- for (var i = 0, len = items.length; i < len; ++i){
- if (items[i]){
- ls += items[i];
- if (ls.substr(-1, 1) != ';'){
- ls += ';';
- }
+ var needsSep = function(group, item){
+ return group.isXType('buttongroup') && !(item instanceof Ext.Toolbar.Separator);
+ };
+
+ this.clearMenu();
+ menu.removeAll();
+
+ for (var i = 0; i < len; i++) {
+ item = items[i].component;
+
+ if (prev && (needsSep(item, prev) || needsSep(prev, item))) {
+ menu.add('-');
}
+
+ this.addComponentToMenu(menu, item);
+ prev = item;
}
- return ls;
- },
-
- /**
- * @cfg {Ext.Template} fieldTpl
- * A {@link Ext.Template#compile compile}d {@link Ext.Template} for rendering
- * the fully wrapped, labeled and styled form Field. Defaults to:</p><pre><code>
-new Ext.Template(
- '<div class="x-form-item {itemCls}" tabIndex="-1">',
- '<label for="{id}" style="{labelStyle}" class="x-form-item-label">{label}{labelSeparator}</label>',
- '<div class="x-form-element" id="x-form-el-{id}" style="{elementStyle}">',
- '</div><div class="{clearCls}"></div>',
- '</div>'
-);
-</code></pre>
- * <p>This may be specified to produce a different DOM structure when rendering form Fields.</p>
- * <p>A description of the properties within the template follows:</p><div class="mdetail-params"><ul>
- * <li><b><tt>itemCls</tt></b> : String<div class="sub-desc">The CSS class applied to the outermost div wrapper
- * that contains this field label and field element (the default class is <tt>'x-form-item'</tt> and <tt>itemCls</tt>
- * will be added to that). If supplied, <tt>itemCls</tt> at the field level will override the default <tt>itemCls</tt>
- * supplied at the container level.</div></li>
- * <li><b><tt>id</tt></b> : String<div class="sub-desc">The id of the Field</div></li>
- * <li><b><tt>{@link #labelStyle}</tt></b> : String<div class="sub-desc">
- * A CSS style specification string to add to the field label for this field (defaults to <tt>''</tt> or the
- * {@link #labelStyle layout's value for <tt>labelStyle</tt>}).</div></li>
- * <li><b><tt>label</tt></b> : String<div class="sub-desc">The text to display as the label for this
- * field (defaults to <tt>''</tt>)</div></li>
- * <li><b><tt>{@link #labelSeparator}</tt></b> : String<div class="sub-desc">The separator to display after
- * the text of the label for this field (defaults to a colon <tt>':'</tt> or the
- * {@link #labelSeparator layout's value for labelSeparator}). To hide the separator use empty string ''.</div></li>
- * <li><b><tt>elementStyle</tt></b> : String<div class="sub-desc">The styles text for the input element's wrapper.</div></li>
- * <li><b><tt>clearCls</tt></b> : String<div class="sub-desc">The CSS class to apply to the special clearing div
- * rendered directly after each form field wrapper (defaults to <tt>'x-form-clear-left'</tt>)</div></li>
- * </ul></div>
- * <p>Also see <tt>{@link #getTemplateArgs}</tt></p>
- */
+ // put something so the menu isn't empty if no compatible items found
+ if (menu.items.length < 1) {
+ menu.add(this.noItemsMenuText);
+ }
+ },
+
/**
* @private
- *
+ * Returns a menu config for a given component. This config is used to create a menu item
+ * to be added to the expander menu
+ * @param {Ext.Component} component The component to create the config for
+ * @param {Boolean} hideOnClick Passed through to the menu item
*/
- renderItem : function(c, position, target){
- if(c && (c.isFormField || c.fieldLabel) && c.inputType != 'hidden'){
- var args = this.getTemplateArgs(c);
- if(Ext.isNumber(position)){
- position = target.dom.childNodes[position] || null;
- }
- if(position){
- c.itemCt = this.fieldTpl.insertBefore(position, args, true);
- }else{
- c.itemCt = this.fieldTpl.append(target, args, true);
- }
- if(!c.getItemCt){
- // Non form fields don't have getItemCt, apply it here
- // This will get cleaned up in onRemove
- Ext.apply(c, {
- getItemCt: function(){
- return c.itemCt;
- },
- customItemCt: true
- });
- }
- c.label = c.getItemCt().child('label.x-form-item-label');
- if(!c.rendered){
- c.render('x-form-el-' + c.id);
- }else if(!this.isValidParent(c, target)){
- Ext.fly('x-form-el-' + c.id).appendChild(c.getPositionEl());
- }
- if(this.trackLabels){
- if(c.hidden){
- this.onFieldHide(c);
+ createMenuConfig : function(component, hideOnClick){
+ var config = Ext.apply({}, component.initialConfig),
+ group = component.toggleGroup;
+
+ Ext.copyTo(config, component, [
+ 'iconCls', 'icon', 'itemId', 'disabled', 'handler', 'scope', 'menu'
+ ]);
+
+ Ext.apply(config, {
+ text : component.overflowText || component.text,
+ hideOnClick: hideOnClick
+ });
+
+ if (group || component.enableToggle) {
+ Ext.apply(config, {
+ group : group,
+ checked: component.pressed,
+ listeners: {
+ checkchange: function(item, checked){
+ component.toggle(checked);
+ }
}
- c.on({
- scope: this,
- show: this.onFieldShow,
- hide: this.onFieldHide
- });
- }
- this.configureItem(c);
- }else {
- Ext.layout.FormLayout.superclass.renderItem.apply(this, arguments);
+ });
}
+
+ delete config.ownerCt;
+ delete config.xtype;
+ delete config.id;
+
+ return config;
},
/**
- * <p>Provides template arguments for rendering the fully wrapped, labeled and styled form Field.</p>
- * <p>This method returns an object hash containing properties used by the layout's {@link #fieldTpl}
- * to create a correctly wrapped, labeled and styled form Field. This may be overriden to
- * create custom layouts. The properties which must be returned are:</p><div class="mdetail-params"><ul>
- * <li><b><tt>itemCls</tt></b> : String<div class="sub-desc">The CSS class applied to the outermost div wrapper
- * that contains this field label and field element (the default class is <tt>'x-form-item'</tt> and <tt>itemCls</tt>
- * will be added to that). If supplied, <tt>itemCls</tt> at the field level will override the default <tt>itemCls</tt>
- * supplied at the container level.</div></li>
- * <li><b><tt>id</tt></b> : String<div class="sub-desc">The id of the Field</div></li>
- * <li><b><tt>{@link #labelStyle}</tt></b> : String<div class="sub-desc">
- * A CSS style specification string to add to the field label for this field (defaults to <tt>''</tt> or the
- * {@link #labelStyle layout's value for <tt>labelStyle</tt>}).</div></li>
- * <li><b><tt>label</tt></b> : String<div class="sub-desc">The text to display as the label for this
- * field (defaults to the field's configured fieldLabel property)</div></li>
- * <li><b><tt>{@link #labelSeparator}</tt></b> : String<div class="sub-desc">The separator to display after
- * the text of the label for this field (defaults to a colon <tt>':'</tt> or the
- * {@link #labelSeparator layout's value for labelSeparator}). To hide the separator use empty string ''.</div></li>
- * <li><b><tt>elementStyle</tt></b> : String<div class="sub-desc">The styles text for the input element's wrapper.</div></li>
- * <li><b><tt>clearCls</tt></b> : String<div class="sub-desc">The CSS class to apply to the special clearing div
- * rendered directly after each form field wrapper (defaults to <tt>'x-form-clear-left'</tt>)</div></li>
- * </ul></div>
- * @param (Ext.form.Field} field The {@link Ext.form.Field Field} being rendered.
- * @return {Object} An object hash containing the properties required to render the Field.
+ * @private
+ * Adds the given Toolbar item to the given menu. Buttons inside a buttongroup are added individually.
+ * @param {Ext.menu.Menu} menu The menu to add to
+ * @param {Ext.Component} component The component to add
*/
- getTemplateArgs: function(field) {
- var noLabelSep = !field.fieldLabel || field.hideLabel;
+ addComponentToMenu : function(menu, component) {
+ if (component instanceof Ext.Toolbar.Separator) {
+ menu.add('-');
+
+ } else if (Ext.isFunction(component.isXType)) {
+ if (component.isXType('splitbutton')) {
+ menu.add(this.createMenuConfig(component, true));
- return {
- id : field.id,
- label : field.fieldLabel,
- itemCls : (field.itemCls || this.container.itemCls || '') + (field.hideLabel ? ' x-hide-label' : ''),
- clearCls : field.clearCls || 'x-form-clear-left',
- labelStyle : this.getLabelStyle(field.labelStyle),
- elementStyle : this.elementStyle || '',
- labelSeparator: noLabelSep ? '' : (Ext.isDefined(field.labelSeparator) ? field.labelSeparator : this.labelSeparator)
- };
- },
+ } else if (component.isXType('button')) {
+ menu.add(this.createMenuConfig(component, !component.menu));
- // private
- adjustWidthAnchor: function(value, c){
- if(c.label && !this.isHide(c) && (this.container.labelAlign != 'top')){
- var adjust = Ext.isIE6 || (Ext.isIE && !Ext.isStrict);
- return value - this.labelAdjust + (adjust ? -3 : 0);
+ } else if (component.isXType('buttongroup')) {
+ component.items.each(function(item){
+ this.addComponentToMenu(menu, item);
+ }, this);
+ }
}
- return value;
},
-
- adjustHeightAnchor : function(value, c){
- if(c.label && !this.isHide(c) && (this.container.labelAlign == 'top')){
- return value - c.label.getHeight();
+
+ /**
+ * @private
+ * Deletes the sub-menu of each item in the expander menu. Submenus are created for items such as
+ * splitbuttons and buttongroups, where the Toolbar item cannot be represented by a single menu item
+ */
+ clearMenu : function(){
+ var menu = this.moreMenu;
+ if (menu && menu.items) {
+ menu.items.each(function(item){
+ delete item.menu;
+ });
}
- return value;
},
+
+ /**
+ * @private
+ * Creates the overflow trigger and menu used when enableOverflow is set to true and the items
+ * in the layout are too wide to fit in the space available
+ */
+ createMenu: function() {
+ if (!this.menuTrigger) {
+ this.createInnerElements();
+
+ /**
+ * @private
+ * @property menu
+ * @type Ext.menu.Menu
+ * The expand menu - holds items for every item that cannot be shown
+ * because the container is currently not large enough.
+ */
+ this.menu = new Ext.menu.Menu({
+ ownerCt : this.layout.container,
+ listeners: {
+ scope: this,
+ beforeshow: this.beforeMenuShow
+ }
+ });
- // private
- isValidParent : function(c, target){
- return target && this.container.getEl().contains(c.getPositionEl());
- }
-
+ /**
+ * @private
+ * @property menuTrigger
+ * @type Ext.Button
+ * The expand button which triggers the overflow menu to be shown
+ */
+ this.menuTrigger = new Ext.Button({
+ iconCls : 'x-toolbar-more-icon',
+ cls : 'x-toolbar-more',
+ menu : this.menu,
+ renderTo: this.afterCt
+ });
+ }
+ },
+
/**
- * @property activeItem
- * @hide
+ * @private
*/
+ destroy: function() {
+ Ext.destroy(this.menu, this.menuTrigger);
+ }
});
-Ext.Container.LAYOUTS['form'] = Ext.layout.FormLayout;
+Ext.layout.boxOverflow.menu = Ext.layout.boxOverflow.Menu;
+
+
/**
- * @class Ext.layout.AccordionLayout
- * @extends Ext.layout.FitLayout
- * <p>This is a layout that manages multiple Panels in an expandable accordion style such that only
- * <b>one Panel can be expanded at any given time</b>. Each Panel has built-in support for expanding and collapsing.</p>
- * <p>Note: Only Ext.Panels <b>and all subclasses of Ext.Panel</b> may be used in an accordion layout Container.</p>
- * <p>This class is intended to be extended or created via the <tt><b>{@link Ext.Container#layout layout}</b></tt>
- * configuration property. See <tt><b>{@link Ext.Container#layout}</b></tt> for additional details.</p>
- * <p>Example usage:</p>
- * <pre><code>
-var accordion = new Ext.Panel({
- title: 'Accordion Layout',
- layout:'accordion',
- defaults: {
- // applied to each contained panel
- bodyStyle: 'padding:15px'
- },
- layoutConfig: {
- // layout-specific configs go here
- titleCollapse: false,
- animate: true,
- activeOnTop: true
+ * @class Ext.layout.boxOverflow.HorizontalMenu
+ * @extends Ext.layout.boxOverflow.Menu
+ * Description
+ */
+Ext.layout.boxOverflow.HorizontalMenu = Ext.extend(Ext.layout.boxOverflow.Menu, {
+
+ constructor: function() {
+ Ext.layout.boxOverflow.HorizontalMenu.superclass.constructor.apply(this, arguments);
+
+ var me = this,
+ layout = me.layout,
+ origFunction = layout.calculateChildBoxes;
+
+ layout.calculateChildBoxes = function(visibleItems, targetSize) {
+ var calcs = origFunction.apply(layout, arguments),
+ meta = calcs.meta,
+ items = me.menuItems;
+
+ //calculate the width of the items currently hidden solely because there is not enough space
+ //to display them
+ var hiddenWidth = 0;
+ for (var index = 0, length = items.length; index < length; index++) {
+ hiddenWidth += items[index].width;
+ }
+
+ meta.minimumWidth += hiddenWidth;
+ meta.tooNarrow = meta.minimumWidth > targetSize.width;
+
+ return calcs;
+ };
},
- items: [{
- title: 'Panel 1',
- html: '<p>Panel content!</p>'
- },{
- title: 'Panel 2',
- html: '<p>Panel content!</p>'
- },{
- title: 'Panel 3',
- html: '<p>Panel content!</p>'
- }]
+
+ handleOverflow: function(calculations, targetSize) {
+ this.showTrigger();
+
+ var newWidth = targetSize.width - this.afterCt.getWidth(),
+ boxes = calculations.boxes,
+ usedWidth = 0,
+ recalculate = false;
+
+ //calculate the width of all visible items and any spare width
+ for (var index = 0, length = boxes.length; index < length; index++) {
+ usedWidth += boxes[index].width;
+ }
+
+ var spareWidth = newWidth - usedWidth,
+ showCount = 0;
+
+ //see if we can re-show any of the hidden components
+ for (var index = 0, length = this.menuItems.length; index < length; index++) {
+ var hidden = this.menuItems[index],
+ comp = hidden.component,
+ width = hidden.width;
+
+ if (width < spareWidth) {
+ comp.show();
+
+ spareWidth -= width;
+ showCount ++;
+ recalculate = true;
+ } else {
+ break;
+ }
+ }
+
+ if (recalculate) {
+ this.menuItems = this.menuItems.slice(showCount);
+ } else {
+ for (var i = boxes.length - 1; i >= 0; i--) {
+ var item = boxes[i].component,
+ right = boxes[i].left + boxes[i].width;
+
+ if (right >= newWidth) {
+ this.menuItems.unshift({
+ component: item,
+ width : boxes[i].width
+ });
+
+ item.hide();
+ } else {
+ break;
+ }
+ }
+ }
+
+ if (this.menuItems.length == 0) {
+ this.hideTrigger();
+ }
+
+ return {
+ targetSize: {
+ height: targetSize.height,
+ width : newWidth
+ },
+ recalculate: recalculate
+ };
+ }
});
-</code></pre>
+
+Ext.layout.boxOverflow.menu.hbox = Ext.layout.boxOverflow.HorizontalMenu;/**
+ * @class Ext.layout.boxOverflow.Scroller
+ * @extends Ext.layout.boxOverflow.None
+ * Description
*/
-Ext.layout.AccordionLayout = Ext.extend(Ext.layout.FitLayout, {
+Ext.layout.boxOverflow.Scroller = Ext.extend(Ext.layout.boxOverflow.None, {
/**
- * @cfg {Boolean} fill
- * True to adjust the active item's height to fill the available space in the container, false to use the
- * item's current height, or auto height if not explicitly set (defaults to true).
+ * @cfg animateScroll
+ * @type Boolean
+ * True to animate the scrolling of items within the layout (defaults to true, ignored if enableScroll is false)
*/
- fill : true,
+ animateScroll: true,
+
/**
- * @cfg {Boolean} autoWidth
- * True to set each contained item's width to 'auto', false to use the item's current width (defaults to true).
- * Note that some components, in particular the {@link Ext.grid.GridPanel grid}, will not function properly within
- * layouts if they have auto width, so in such cases this config should be set to false.
+ * @cfg scrollIncrement
+ * @type Number
+ * The number of pixels to scroll by on scroller click (defaults to 100)
*/
- autoWidth : true,
+ scrollIncrement: 100,
+
/**
- * @cfg {Boolean} titleCollapse
- * True to allow expand/collapse of each contained panel by clicking anywhere on the title bar, false to allow
- * expand/collapse only when the toggle tool button is clicked (defaults to true). When set to false,
- * {@link #hideCollapseTool} should be false also.
+ * @cfg wheelIncrement
+ * @type Number
+ * The number of pixels to increment on mouse wheel scrolling (defaults to <tt>3</tt>).
*/
- titleCollapse : true,
+ wheelIncrement: 3,
+
/**
- * @cfg {Boolean} hideCollapseTool
- * True to hide the contained panels' collapse/expand toggle buttons, false to display them (defaults to false).
- * When set to true, {@link #titleCollapse} should be true also.
+ * @cfg scrollRepeatInterval
+ * @type Number
+ * Number of milliseconds between each scroll while a scroller button is held down (defaults to 400)
*/
- hideCollapseTool : false,
+ scrollRepeatInterval: 400,
+
/**
- * @cfg {Boolean} collapseFirst
- * True to make sure the collapse/expand toggle button always renders first (to the left of) any other tools
- * in the contained panels' title bars, false to render it last (defaults to false).
+ * @cfg scrollDuration
+ * @type Number
+ * Number of seconds that each scroll animation lasts (defaults to 0.4)
*/
- collapseFirst : false,
+ scrollDuration: 0.4,
+
/**
- * @cfg {Boolean} animate
- * True to slide the contained panels open and closed during expand/collapse using animation, false to open and
- * close directly with no animation (defaults to false). Note: to defer to the specific config setting of each
- * contained panel for this property, set this to undefined at the layout level.
+ * @cfg beforeCls
+ * @type String
+ * CSS class added to the beforeCt element. This is the element that holds any special items such as scrollers,
+ * which must always be present at the leftmost edge of the Container
*/
- animate : false,
+ beforeCls: 'x-strip-left',
+
/**
- * @cfg {Boolean} sequence
- * <b>Experimental</b>. If animate is set to true, this will result in each animation running in sequence.
+ * @cfg afterCls
+ * @type String
+ * CSS class added to the afterCt element. This is the element that holds any special items such as scrollers,
+ * which must always be present at the rightmost edge of the Container
*/
- sequence : false,
+ afterCls: 'x-strip-right',
+
/**
- * @cfg {Boolean} activeOnTop
- * True to swap the position of each panel as it is expanded so that it becomes the first item in the container,
- * false to keep the panels in the rendered order. <b>This is NOT compatible with "animate:true"</b> (defaults to false).
+ * @cfg scrollerCls
+ * @type String
+ * CSS class added to both scroller elements if enableScroll is used
*/
- activeOnTop : false,
-
- type: 'accordion',
+ scrollerCls: 'x-strip-scroller',
+
+ /**
+ * @cfg beforeScrollerCls
+ * @type String
+ * CSS class added to the left scroller element if enableScroll is used
+ */
+ beforeScrollerCls: 'x-strip-scroller-left',
+
+ /**
+ * @cfg afterScrollerCls
+ * @type String
+ * CSS class added to the right scroller element if enableScroll is used
+ */
+ afterScrollerCls: 'x-strip-scroller-right',
+
+ /**
+ * @private
+ * Sets up an listener to scroll on the layout's innerCt mousewheel event
+ */
+ createWheelListener: function() {
+ this.layout.innerCt.on({
+ scope : this,
+ mousewheel: function(e) {
+ e.stopEvent();
- renderItem : function(c){
- if(this.animate === false){
- c.animCollapse = false;
- }
- c.collapsible = true;
- if(this.autoWidth){
- c.autoWidth = true;
- }
- if(this.titleCollapse){
- c.titleCollapse = true;
- }
- if(this.hideCollapseTool){
- c.hideCollapseTool = true;
- }
- if(this.collapseFirst !== undefined){
- c.collapseFirst = this.collapseFirst;
- }
- if(!this.activeItem && !c.collapsed){
- this.setActiveItem(c, true);
- }else if(this.activeItem && this.activeItem != c){
- c.collapsed = true;
- }
- Ext.layout.AccordionLayout.superclass.renderItem.apply(this, arguments);
- c.header.addClass('x-accordion-hd');
- c.on('beforeexpand', this.beforeExpand, this);
+ this.scrollBy(e.getWheelDelta() * this.wheelIncrement * -1, false);
+ }
+ });
},
-
- onRemove: function(c){
- Ext.layout.AccordionLayout.superclass.onRemove.call(this, c);
- if(c.rendered){
- c.header.removeClass('x-accordion-hd');
- }
- c.un('beforeexpand', this.beforeExpand, this);
+
+ /**
+ * @private
+ * Most of the heavy lifting is done in the subclasses
+ */
+ handleOverflow: function(calculations, targetSize) {
+ this.createInnerElements();
+ this.showScrollers();
},
-
- // private
- beforeExpand : function(p, anim){
- var ai = this.activeItem;
- if(ai){
- if(this.sequence){
- delete this.activeItem;
- if (!ai.collapsed){
- ai.collapse({callback:function(){
- p.expand(anim || true);
- }, scope: this});
- return false;
- }
- }else{
- ai.collapse(this.animate);
- }
- }
- this.setActive(p);
- if(this.activeOnTop){
- p.el.dom.parentNode.insertBefore(p.el.dom, p.el.dom.parentNode.firstChild);
+
+ /**
+ * @private
+ */
+ clearOverflow: function() {
+ this.hideScrollers();
+ },
+
+ /**
+ * @private
+ * Shows the scroller elements in the beforeCt and afterCt. Creates the scrollers first if they are not already
+ * present.
+ */
+ showScrollers: function() {
+ this.createScrollers();
+
+ this.beforeScroller.show();
+ this.afterScroller.show();
+
+ this.updateScrollButtons();
+ },
+
+ /**
+ * @private
+ * Hides the scroller elements in the beforeCt and afterCt
+ */
+ hideScrollers: function() {
+ if (this.beforeScroller != undefined) {
+ this.beforeScroller.hide();
+ this.afterScroller.hide();
}
- // Items have been hidden an possibly rearranged, we need to get the container size again.
- this.layout();
},
-
- // private
- setItemSize : function(item, size){
- if(this.fill && item){
- var hh = 0, i, ct = this.getRenderedItems(this.container), len = ct.length, p;
- // Add up all the header heights
- for (i = 0; i < len; i++) {
- if((p = ct[i]) != item && !p.hidden){
- hh += p.header.getHeight();
- }
- };
- // Subtract the header heights from the container size
- size.height -= hh;
- // Call setSize on the container to set the correct height. For Panels, deferedHeight
- // will simply store this size for when the expansion is done.
- item.setSize(size);
+
+ /**
+ * @private
+ * Creates the clickable scroller elements and places them into the beforeCt and afterCt
+ */
+ createScrollers: function() {
+ if (!this.beforeScroller && !this.afterScroller) {
+ var before = this.beforeCt.createChild({
+ cls: String.format("{0} {1} ", this.scrollerCls, this.beforeScrollerCls)
+ });
+
+ var after = this.afterCt.createChild({
+ cls: String.format("{0} {1}", this.scrollerCls, this.afterScrollerCls)
+ });
+
+ before.addClassOnOver(this.beforeScrollerCls + '-hover');
+ after.addClassOnOver(this.afterScrollerCls + '-hover');
+
+ before.setVisibilityMode(Ext.Element.DISPLAY);
+ after.setVisibilityMode(Ext.Element.DISPLAY);
+
+ this.beforeRepeater = new Ext.util.ClickRepeater(before, {
+ interval: this.scrollRepeatInterval,
+ handler : this.scrollLeft,
+ scope : this
+ });
+
+ this.afterRepeater = new Ext.util.ClickRepeater(after, {
+ interval: this.scrollRepeatInterval,
+ handler : this.scrollRight,
+ scope : this
+ });
+
+ /**
+ * @property beforeScroller
+ * @type Ext.Element
+ * The left scroller element. Only created when needed.
+ */
+ this.beforeScroller = before;
+
+ /**
+ * @property afterScroller
+ * @type Ext.Element
+ * The left scroller element. Only created when needed.
+ */
+ this.afterScroller = after;
}
},
-
+
/**
- * Sets the active (expanded) item in the layout.
- * @param {String/Number} item The string component id or numeric index of the item to activate
+ * @private
*/
- setActiveItem : function(item){
- this.setActive(item, true);
+ destroy: function() {
+ Ext.destroy(this.beforeScroller, this.afterScroller, this.beforeRepeater, this.afterRepeater, this.beforeCt, this.afterCt);
},
-
- // private
- setActive : function(item, expand){
- var ai = this.activeItem;
- item = this.container.getComponent(item);
- if(ai != item){
- if(item.rendered && item.collapsed && expand){
- item.expand();
- }else{
- if(ai){
- ai.fireEvent('deactivate', ai);
- }
- this.activeItem = item;
- item.fireEvent('activate', item);
- }
+
+ /**
+ * @private
+ * Scrolls left or right by the number of pixels specified
+ * @param {Number} delta Number of pixels to scroll to the right by. Use a negative number to scroll left
+ */
+ scrollBy: function(delta, animate) {
+ this.scrollTo(this.getScrollPosition() + delta, animate);
+ },
+
+ /**
+ * @private
+ * Normalizes an item reference, string id or numerical index into a reference to the item
+ * @param {Ext.Component|String|Number} item The item reference, id or index
+ * @return {Ext.Component} The item
+ */
+ getItem: function(item) {
+ if (Ext.isString(item)) {
+ item = Ext.getCmp(item);
+ } else if (Ext.isNumber(item)) {
+ item = this.items[item];
}
- }
-});
-Ext.Container.LAYOUTS.accordion = Ext.layout.AccordionLayout;
-
-//backwards compat
-Ext.layout.Accordion = Ext.layout.AccordionLayout;/**
- * @class Ext.layout.TableLayout
- * @extends Ext.layout.ContainerLayout
- * <p>This layout allows you to easily render content into an HTML table. The total number of columns can be
- * specified, and rowspan and colspan can be used to create complex layouts within the table.
- * This class is intended to be extended or created via the layout:'table' {@link Ext.Container#layout} config,
- * and should generally not need to be created directly via the new keyword.</p>
- * <p>Note that when creating a layout via config, the layout-specific config properties must be passed in via
- * the {@link Ext.Container#layoutConfig} object which will then be applied internally to the layout. In the
- * case of TableLayout, the only valid layout config property is {@link #columns}. However, the items added to a
- * TableLayout can supply the following table-specific config properties:</p>
- * <ul>
- * <li><b>rowspan</b> Applied to the table cell containing the item.</li>
- * <li><b>colspan</b> Applied to the table cell containing the item.</li>
- * <li><b>cellId</b> An id applied to the table cell containing the item.</li>
- * <li><b>cellCls</b> A CSS class name added to the table cell containing the item.</li>
- * </ul>
- * <p>The basic concept of building up a TableLayout is conceptually very similar to building up a standard
- * HTML table. You simply add each panel (or "cell") that you want to include along with any span attributes
- * specified as the special config properties of rowspan and colspan which work exactly like their HTML counterparts.
- * Rather than explicitly creating and nesting rows and columns as you would in HTML, you simply specify the
- * total column count in the layoutConfig and start adding panels in their natural order from left to right,
- * top to bottom. The layout will automatically figure out, based on the column count, rowspans and colspans,
- * how to position each panel within the table. Just like with HTML tables, your rowspans and colspans must add
- * up correctly in your overall layout or you'll end up with missing and/or extra cells! Example usage:</p>
- * <pre><code>
-// This code will generate a layout table that is 3 columns by 2 rows
-// with some spanning included. The basic layout will be:
-// +--------+-----------------+
-// | A | B |
-// | |--------+--------|
-// | | C | D |
-// +--------+--------+--------+
-var table = new Ext.Panel({
- title: 'Table Layout',
- layout:'table',
- defaults: {
- // applied to each contained panel
- bodyStyle:'padding:20px'
+
+ return item;
},
- layoutConfig: {
- // The total column count must be specified here
- columns: 3
+
+ /**
+ * @private
+ * @return {Object} Object passed to scrollTo when scrolling
+ */
+ getScrollAnim: function() {
+ return {
+ duration: this.scrollDuration,
+ callback: this.updateScrollButtons,
+ scope : this
+ };
},
- items: [{
- html: '<p>Cell A content</p>',
- rowspan: 2
- },{
- html: '<p>Cell B content</p>',
- colspan: 2
- },{
- html: '<p>Cell C content</p>',
- cellCls: 'highlight'
- },{
- html: '<p>Cell D content</p>'
- }]
-});
-</code></pre>
- */
-Ext.layout.TableLayout = Ext.extend(Ext.layout.ContainerLayout, {
+
/**
- * @cfg {Number} columns
- * The total number of columns to create in the table for this layout. If not specified, all Components added to
- * this layout will be rendered into a single row using one column per Component.
+ * @private
+ * Enables or disables each scroller button based on the current scroll position
*/
-
- // private
- monitorResize:false,
-
- type: 'table',
-
- targetCls: 'x-table-layout-ct',
-
+ updateScrollButtons: function() {
+ if (this.beforeScroller == undefined || this.afterScroller == undefined) {
+ return;
+ }
+
+ var beforeMeth = this.atExtremeBefore() ? 'addClass' : 'removeClass',
+ afterMeth = this.atExtremeAfter() ? 'addClass' : 'removeClass',
+ beforeCls = this.beforeScrollerCls + '-disabled',
+ afterCls = this.afterScrollerCls + '-disabled';
+
+ this.beforeScroller[beforeMeth](beforeCls);
+ this.afterScroller[afterMeth](afterCls);
+ this.scrolling = false;
+ },
+
/**
- * @cfg {Object} tableAttrs
- * <p>An object containing properties which are added to the {@link Ext.DomHelper DomHelper} specification
- * used to create the layout's <tt><table></tt> element. Example:</p><pre><code>
-{
- xtype: 'panel',
- layout: 'table',
- layoutConfig: {
- tableAttrs: {
- style: {
- width: '100%'
- }
- },
- columns: 3
- }
-}</code></pre>
+ * @private
+ * Returns true if the innerCt scroll is already at its left-most point
+ * @return {Boolean} True if already at furthest left point
*/
- tableAttrs:null,
-
- // private
- setContainer : function(ct){
- Ext.layout.TableLayout.superclass.setContainer.call(this, ct);
-
- this.currentRow = 0;
- this.currentColumn = 0;
- this.cells = [];
+ atExtremeBefore: function() {
+ return this.getScrollPosition() === 0;
},
- // private
- onLayout : function(ct, target){
- var cs = ct.items.items, len = cs.length, c, i;
-
- if(!this.table){
- target.addClass('x-table-layout-ct');
-
- this.table = target.createChild(
- Ext.apply({tag:'table', cls:'x-table-layout', cellspacing: 0, cn: {tag: 'tbody'}}, this.tableAttrs), null, true);
- }
- this.renderAll(ct, target);
+ /**
+ * @private
+ * Scrolls to the left by the configured amount
+ */
+ scrollLeft: function(animate) {
+ this.scrollBy(-this.scrollIncrement, animate);
},
-
- // private
- getRow : function(index){
- var row = this.table.tBodies[0].childNodes[index];
- if(!row){
- row = document.createElement('tr');
- this.table.tBodies[0].appendChild(row);
- }
- return row;
+
+ /**
+ * @private
+ * Scrolls to the right by the configured amount
+ */
+ scrollRight: function(animate) {
+ this.scrollBy(this.scrollIncrement, animate);
},
-
- // private
- getNextCell : function(c){
- var cell = this.getNextNonSpan(this.currentColumn, this.currentRow);
- var curCol = this.currentColumn = cell[0], curRow = this.currentRow = cell[1];
- for(var rowIndex = curRow; rowIndex < curRow + (c.rowspan || 1); rowIndex++){
- if(!this.cells[rowIndex]){
- this.cells[rowIndex] = [];
- }
- for(var colIndex = curCol; colIndex < curCol + (c.colspan || 1); colIndex++){
- this.cells[rowIndex][colIndex] = true;
+
+ /**
+ * Scrolls to the given component.
+ * @param {String|Number|Ext.Component} item The item to scroll to. Can be a numerical index, component id
+ * or a reference to the component itself.
+ * @param {Boolean} animate True to animate the scrolling
+ */
+ scrollToItem: function(item, animate) {
+ item = this.getItem(item);
+
+ if (item != undefined) {
+ var visibility = this.getItemVisibility(item);
+
+ if (!visibility.fullyVisible) {
+ var box = item.getBox(true, true),
+ newX = box.x;
+
+ if (visibility.hiddenRight) {
+ newX -= (this.layout.innerCt.getWidth() - box.width);
+ }
+
+ this.scrollTo(newX, animate);
}
}
- var td = document.createElement('td');
- if(c.cellId){
- td.id = c.cellId;
- }
- var cls = 'x-table-layout-cell';
- if(c.cellCls){
- cls += ' ' + c.cellCls;
- }
- td.className = cls;
- if(c.colspan){
- td.colSpan = c.colspan;
- }
- if(c.rowspan){
- td.rowSpan = c.rowspan;
- }
- this.getRow(curRow).appendChild(td);
- return td;
},
+
+ /**
+ * @private
+ * For a given item in the container, return an object with information on whether the item is visible
+ * with the current innerCt scroll value.
+ * @param {Ext.Component} item The item
+ * @return {Object} Values for fullyVisible, hiddenLeft and hiddenRight
+ */
+ getItemVisibility: function(item) {
+ var box = this.getItem(item).getBox(true, true),
+ itemLeft = box.x,
+ itemRight = box.x + box.width,
+ scrollLeft = this.getScrollPosition(),
+ scrollRight = this.layout.innerCt.getWidth() + scrollLeft;
+
+ return {
+ hiddenLeft : itemLeft < scrollLeft,
+ hiddenRight : itemRight > scrollRight,
+ fullyVisible: itemLeft > scrollLeft && itemRight < scrollRight
+ };
+ }
+});
- // private
- getNextNonSpan: function(colIndex, rowIndex){
- var cols = this.columns;
- while((cols && colIndex >= cols) || (this.cells[rowIndex] && this.cells[rowIndex][colIndex])) {
- if(cols && colIndex >= cols){
- rowIndex++;
- colIndex = 0;
- }else{
- colIndex++;
+Ext.layout.boxOverflow.scroller = Ext.layout.boxOverflow.Scroller;
+
+
+/**\r
+ * @class Ext.layout.boxOverflow.VerticalScroller\r
+ * @extends Ext.layout.boxOverflow.Scroller\r
+ * Description\r
+ */\r
+Ext.layout.boxOverflow.VerticalScroller = Ext.extend(Ext.layout.boxOverflow.Scroller, {
+ scrollIncrement: 75,
+ wheelIncrement : 2,
+
+ handleOverflow: function(calculations, targetSize) {
+ Ext.layout.boxOverflow.VerticalScroller.superclass.handleOverflow.apply(this, arguments);
+
+ return {
+ targetSize: {
+ height: targetSize.height - (this.beforeCt.getHeight() + this.afterCt.getHeight()),
+ width : targetSize.width
}
- }
- return [colIndex, rowIndex];
+ };
},
+
+ /**
+ * @private
+ * Creates the beforeCt and afterCt elements if they have not already been created
+ */
+ createInnerElements: function() {
+ var target = this.layout.innerCt;
+
+ //normal items will be rendered to the innerCt. beforeCt and afterCt allow for fixed positioning of
+ //special items such as scrollers or dropdown menu triggers
+ if (!this.beforeCt) {
+ this.beforeCt = target.insertSibling({cls: this.beforeCls}, 'before');
+ this.afterCt = target.insertSibling({cls: this.afterCls}, 'after');
- // private
- renderItem : function(c, position, target){
- // Ensure we have our inner table to get cells to render into.
- if(!this.table){
- this.table = target.createChild(
- Ext.apply({tag:'table', cls:'x-table-layout', cellspacing: 0, cn: {tag: 'tbody'}}, this.tableAttrs), null, true);
+ this.createWheelListener();
}
- if(c && !c.rendered){
- c.render(this.getNextCell(c));
- this.configureItem(c, position);
- }else if(c && !this.isValidParent(c, target)){
- var container = this.getNextCell(c);
- container.insertBefore(c.getPositionEl().dom, null);
- c.container = Ext.get(container);
- this.configureItem(c, position);
+ },
+
+ /**
+ * @private
+ * Scrolls to the given position. Performs bounds checking.
+ * @param {Number} position The position to scroll to. This is constrained.
+ * @param {Boolean} animate True to animate. If undefined, falls back to value of this.animateScroll
+ */
+ scrollTo: function(position, animate) {
+ var oldPosition = this.getScrollPosition(),
+ newPosition = position.constrain(0, this.getMaxScrollBottom());
+
+ if (newPosition != oldPosition && !this.scrolling) {
+ if (animate == undefined) {
+ animate = this.animateScroll;
+ }
+
+ this.layout.innerCt.scrollTo('top', newPosition, animate ? this.getScrollAnim() : false);
+
+ if (animate) {
+ this.scrolling = true;
+ } else {
+ this.scrolling = false;
+ this.updateScrollButtons();
+ }
}
},
-
- // private
- isValidParent : function(c, target){
- return c.getPositionEl().up('table', 5).dom.parentNode === (target.dom || target);
- }
-
+
/**
- * @property activeItem
- * @hide
+ * Returns the current scroll position of the innerCt element
+ * @return {Number} The current scroll position
*/
-});
-
-Ext.Container.LAYOUTS['table'] = Ext.layout.TableLayout;/**
- * @class Ext.layout.AbsoluteLayout
- * @extends Ext.layout.AnchorLayout
- * <p>This is a layout that inherits the anchoring of <b>{@link Ext.layout.AnchorLayout}</b> and adds the
- * ability for x/y positioning using the standard x and y component config options.</p>
- * <p>This class is intended to be extended or created via the <tt><b>{@link Ext.Container#layout layout}</b></tt>
- * configuration property. See <tt><b>{@link Ext.Container#layout}</b></tt> for additional details.</p>
- * <p>Example usage:</p>
- * <pre><code>
-var form = new Ext.form.FormPanel({
- title: 'Absolute Layout',
- layout:'absolute',
- layoutConfig: {
- // layout-specific configs go here
- extraCls: 'x-abs-layout-item',
+ getScrollPosition: function(){
+ return parseInt(this.layout.innerCt.dom.scrollTop, 10) || 0;
},
- baseCls: 'x-plain',
- url:'save-form.php',
- defaultType: 'textfield',
- items: [{
- x: 0,
- y: 5,
- xtype:'label',
- text: 'Send To:'
- },{
- x: 60,
- y: 0,
- name: 'to',
- anchor:'100%' // anchor width by percentage
- },{
- x: 0,
- y: 35,
- xtype:'label',
- text: 'Subject:'
- },{
- x: 60,
- y: 30,
- name: 'subject',
- anchor: '100%' // anchor width by percentage
- },{
- x:0,
- y: 60,
- xtype: 'textarea',
- name: 'msg',
- anchor: '100% 100%' // anchor width and height
- }]
+
+ /**
+ * @private
+ * Returns the maximum value we can scrollTo
+ * @return {Number} The max scroll value
+ */
+ getMaxScrollBottom: function() {
+ return this.layout.innerCt.dom.scrollHeight - this.layout.innerCt.getHeight();
+ },
+
+ /**
+ * @private
+ * Returns true if the innerCt scroll is already at its right-most point
+ * @return {Boolean} True if already at furthest right point
+ */
+ atExtremeAfter: function() {
+ return this.getScrollPosition() >= this.getMaxScrollBottom();
+ }
});
-</code></pre>
- */
-Ext.layout.AbsoluteLayout = Ext.extend(Ext.layout.AnchorLayout, {
- extraCls: 'x-abs-layout-item',
+Ext.layout.boxOverflow.scroller.vbox = Ext.layout.boxOverflow.VerticalScroller;
- type: 'absolute',
- onLayout : function(ct, target){
- target.position();
- this.paddingLeft = target.getPadding('l');
- this.paddingTop = target.getPadding('t');
- Ext.layout.AbsoluteLayout.superclass.onLayout.call(this, ct, target);
+/**
+ * @class Ext.layout.boxOverflow.HorizontalScroller
+ * @extends Ext.layout.boxOverflow.Scroller
+ * Description
+ */
+Ext.layout.boxOverflow.HorizontalScroller = Ext.extend(Ext.layout.boxOverflow.Scroller, {
+ handleOverflow: function(calculations, targetSize) {
+ Ext.layout.boxOverflow.HorizontalScroller.superclass.handleOverflow.apply(this, arguments);
+
+ return {
+ targetSize: {
+ height: targetSize.height,
+ width : targetSize.width - (this.beforeCt.getWidth() + this.afterCt.getWidth())
+ }
+ };
},
-
- // private
- adjustWidthAnchor : function(value, comp){
- return value ? value - comp.getPosition(true)[0] + this.paddingLeft : value;
+
+ /**
+ * @private
+ * Creates the beforeCt and afterCt elements if they have not already been created
+ */
+ createInnerElements: function() {
+ var target = this.layout.innerCt;
+
+ //normal items will be rendered to the innerCt. beforeCt and afterCt allow for fixed positioning of
+ //special items such as scrollers or dropdown menu triggers
+ if (!this.beforeCt) {
+ this.afterCt = target.insertSibling({cls: this.afterCls}, 'before');
+ this.beforeCt = target.insertSibling({cls: this.beforeCls}, 'before');
+
+ this.createWheelListener();
+ }
},
-
- // private
- adjustHeightAnchor : function(value, comp){
- return value ? value - comp.getPosition(true)[1] + this.paddingTop : value;
- }
+
/**
- * @property activeItem
- * @hide
+ * @private
+ * Scrolls to the given position. Performs bounds checking.
+ * @param {Number} position The position to scroll to. This is constrained.
+ * @param {Boolean} animate True to animate. If undefined, falls back to value of this.animateScroll
+ */
+ scrollTo: function(position, animate) {
+ var oldPosition = this.getScrollPosition(),
+ newPosition = position.constrain(0, this.getMaxScrollRight());
+
+ if (newPosition != oldPosition && !this.scrolling) {
+ if (animate == undefined) {
+ animate = this.animateScroll;
+ }
+
+ this.layout.innerCt.scrollTo('left', newPosition, animate ? this.getScrollAnim() : false);
+
+ if (animate) {
+ this.scrolling = true;
+ } else {
+ this.scrolling = false;
+ this.updateScrollButtons();
+ }
+ }
+ },
+
+ /**
+ * Returns the current scroll position of the innerCt element
+ * @return {Number} The current scroll position
+ */
+ getScrollPosition: function(){
+ return parseInt(this.layout.innerCt.dom.scrollLeft, 10) || 0;
+ },
+
+ /**
+ * @private
+ * Returns the maximum value we can scrollTo
+ * @return {Number} The max scroll value
+ */
+ getMaxScrollRight: function() {
+ return this.layout.innerCt.dom.scrollWidth - this.layout.innerCt.getWidth();
+ },
+
+ /**
+ * @private
+ * Returns true if the innerCt scroll is already at its right-most point
+ * @return {Boolean} True if already at furthest right point
*/
+ atExtremeAfter: function() {
+ return this.getScrollPosition() >= this.getMaxScrollRight();
+ }
});
-Ext.Container.LAYOUTS['absolute'] = Ext.layout.AbsoluteLayout;
-/**
- * @class Ext.layout.BoxLayout
- * @extends Ext.layout.ContainerLayout
- * <p>Base Class for HBoxLayout and VBoxLayout Classes. Generally it should not need to be used directly.</p>
+
+Ext.layout.boxOverflow.scroller.hbox = Ext.layout.boxOverflow.HorizontalScroller;/**
+ * @class Ext.layout.HBoxLayout
+ * @extends Ext.layout.BoxLayout
+ * <p>A layout that arranges items horizontally across a Container. This layout optionally divides available horizontal
+ * space between child items containing a numeric <code>flex</code> configuration.</p>
+ * This layout may also be used to set the heights of child items by configuring it with the {@link #align} option.
*/
-Ext.layout.BoxLayout = Ext.extend(Ext.layout.ContainerLayout, {
+Ext.layout.HBoxLayout = Ext.extend(Ext.layout.BoxLayout, {
/**
- * @cfg {Object} defaultMargins
- * <p>If the individual contained items do not have a <tt>margins</tt>
- * property specified, the default margins from this property will be
- * applied to each item.</p>
- * <br><p>This property may be specified as an object containing margins
- * to apply in the format:</p><pre><code>
-{
- top: (top margin),
- right: (right margin),
- bottom: (bottom margin),
- left: (left margin)
-}</code></pre>
- * <p>This property may also be specified as a string containing
- * space-separated, numeric margin values. The order of the sides associated
- * with each value matches the way CSS processes margin values:</p>
+ * @cfg {String} align
+ * Controls how the child items of the container are aligned. Acceptable configuration values for this
+ * property are:
* <div class="mdetail-params"><ul>
- * <li>If there is only one value, it applies to all sides.</li>
- * <li>If there are two values, the top and bottom borders are set to the
- * first value and the right and left are set to the second.</li>
- * <li>If there are three values, the top is set to the first value, the left
- * and right are set to the second, and the bottom is set to the third.</li>
- * <li>If there are four values, they apply to the top, right, bottom, and
- * left, respectively.</li>
- * </ul></div>
- * <p>Defaults to:</p><pre><code>
- * {top:0, right:0, bottom:0, left:0}
- * </code></pre>
+ * <li><b><tt>top</tt></b> : <b>Default</b><div class="sub-desc">child items are aligned vertically
+ * at the <b>top</b> of the container</div></li>
+ * <li><b><tt>middle</tt></b> : <div class="sub-desc">child items are aligned vertically in the
+ * <b>middle</b> of the container</div></li>
+ * <li><b><tt>stretch</tt></b> : <div class="sub-desc">child items are stretched vertically to fill
+ * the height of the container</div></li>
+ * <li><b><tt>stretchmax</tt></b> : <div class="sub-desc">child items are stretched vertically to
+ * the height of the largest item.</div></li>
*/
- defaultMargins : {left:0,top:0,right:0,bottom:0},
+ align: 'top', // top, middle, stretch, strechmax
+
+ type : 'hbox',
+
/**
- * @cfg {String} padding
- * <p>Sets the padding to be applied to all child items managed by this layout.</p>
- * <p>This property must be specified as a string containing
- * space-separated, numeric padding values. The order of the sides associated
- * with each value matches the way CSS processes padding values:</p>
+ * @cfg {String} pack
+ * Controls how the child items of the container are packed together. Acceptable configuration values
+ * for this property are:
* <div class="mdetail-params"><ul>
- * <li>If there is only one value, it applies to all sides.</li>
- * <li>If there are two values, the top and bottom borders are set to the
- * first value and the right and left are set to the second.</li>
- * <li>If there are three values, the top is set to the first value, the left
- * and right are set to the second, and the bottom is set to the third.</li>
- * <li>If there are four values, they apply to the top, right, bottom, and
- * left, respectively.</li>
+ * <li><b><tt>start</tt></b> : <b>Default</b><div class="sub-desc">child items are packed together at
+ * <b>left</b> side of container</div></li>
+ * <li><b><tt>center</tt></b> : <div class="sub-desc">child items are packed together at
+ * <b>mid-width</b> of container</div></li>
+ * <li><b><tt>end</tt></b> : <div class="sub-desc">child items are packed together at <b>right</b>
+ * side of container</div></li>
* </ul></div>
- * <p>Defaults to: <code>"0"</code></p>
*/
- padding : '0',
- // documented in subclasses
- pack : 'start',
-
- // private
- monitorResize : true,
- type: 'box',
- scrollOffset : 0,
- extraCls : 'x-box-item',
- targetCls : 'x-box-layout-ct',
- innerCls : 'x-box-inner',
-
- constructor : function(config){
- Ext.layout.BoxLayout.superclass.constructor.call(this, config);
-
- if (Ext.isString(this.defaultMargins)) {
- this.defaultMargins = this.parseMargins(this.defaultMargins);
- }
- },
+ /**
+ * @cfg {Number} flex
+ * This configuation option is to be applied to <b>child <tt>items</tt></b> of the container managed
+ * by this layout. Each child item with a <tt>flex</tt> property will be flexed <b>horizontally</b>
+ * according to each item's <b>relative</b> <tt>flex</tt> value compared to the sum of all items with
+ * a <tt>flex</tt> value specified. Any child items that have either a <tt>flex = 0</tt> or
+ * <tt>flex = undefined</tt> will not be 'flexed' (the initial size will not be changed).
+ */
/**
* @private
- * Runs the child box calculations and caches them in childBoxCache. Subclasses can used these cached values
- * when laying out
+ * Calculates the size and positioning of each item in the HBox. This iterates over all of the rendered,
+ * visible items and returns a height, width, top and left for each, as well as a reference to each. Also
+ * returns meta data such as maxHeight which are useful when resizing layout wrappers such as this.innerCt.
+ * @param {Array} visibleItems The array of all rendered, visible items to be calculated for
+ * @param {Object} targetSize Object containing target size and height
+ * @return {Object} Object containing box measurements for each child, plus meta data
*/
- onLayout: function(container, target) {
- Ext.layout.BoxLayout.superclass.onLayout.call(this, container, target);
+ calculateChildBoxes: function(visibleItems, targetSize) {
+ var visibleCount = visibleItems.length,
- var items = this.getVisibleItems(container),
- tSize = this.getLayoutTargetSize();
+ padding = this.padding,
+ topOffset = padding.top,
+ leftOffset = padding.left,
+ paddingVert = topOffset + padding.bottom,
+ paddingHoriz = leftOffset + padding.right,
- /**
- * @private
- * @property layoutTargetLastSize
- * @type Object
- * Private cache of the last measured size of the layout target. This should never be used except by
- * BoxLayout subclasses during their onLayout run.
- */
- this.layoutTargetLastSize = tSize;
+ width = targetSize.width - this.scrollOffset,
+ height = targetSize.height,
+ availHeight = Math.max(0, height - paddingVert),
- /**
- * @private
- * @property childBoxCache
- * @type Array
- * Array of the last calculated height, width, top and left positions of each visible rendered component
- * within the Box layout.
- */
- this.childBoxCache = this.calculateChildBoxes(items, tSize);
+ isStart = this.pack == 'start',
+ isCenter = this.pack == 'center',
+ isEnd = this.pack == 'end',
- this.updateInnerCtSize(tSize, this.childBoxCache);
- this.updateChildBoxes(this.childBoxCache.boxes);
+ nonFlexWidth = 0,
+ maxHeight = 0,
+ totalFlex = 0,
+ desiredWidth = 0,
+ minimumWidth = 0,
- // Putting a box layout into an overflowed container is NOT correct and will make a second layout pass necessary.
- this.handleTargetOverflow(tSize, container, target);
- },
+ //used to cache the calculated size and position values for each child item
+ boxes = [],
- /**
- * Resizes and repositions each child component
- * @param {Array} boxes The box measurements
- */
- updateChildBoxes: function(boxes) {
- for (var i = 0, length = boxes.length; i < length; i++) {
- var box = boxes[i],
- comp = box.component;
+ //used in the for loops below, just declared here for brevity
+ child, childWidth, childHeight, childSize, childMargins, canLayout, i, calcs, flexedWidth,
+ horizMargins, vertMargins, stretchHeight;
- if (box.dirtySize) {
- comp.setSize(box.width, box.height);
- }
- // Don't set positions to NaN
- if (isNaN(box.left) || isNaN(box.top)) {
- continue;
- }
- comp.setPosition(box.left, box.top);
- }
- },
+ //gather the total flex of all flexed items and the width taken up by fixed width items
+ for (i = 0; i < visibleCount; i++) {
+ child = visibleItems[i];
+ childHeight = child.height;
+ childWidth = child.width;
+ canLayout = !child.hasLayout && typeof child.doLayout == 'function';
- /**
- * @private
- * Called by onRender just before the child components are sized and positioned. This resizes the innerCt
- * to make sure all child items fit within it. We call this before sizing the children because if our child
- * items are larger than the previous innerCt size the browser will insert scrollbars and then remove them
- * again immediately afterwards, giving a performance hit.
- * Subclasses should provide an implementation.
- * @param {Object} currentSize The current height and width of the innerCt
- * @param {Array} calculations The new box calculations of all items to be laid out
- */
- updateInnerCtSize: Ext.emptyFn,
+ // Static width (numeric) requires no calcs
+ if (typeof childWidth != 'number') {
- /**
- * @private
- * This should be called after onLayout of any BoxLayout subclass. If the target's overflow is not set to 'hidden',
- * we need to lay out a second time because the scrollbars may have modified the height and width of the layout
- * target. Having a Box layout inside such a target is therefore not recommended.
- * @param {Object} previousTargetSize The size and height of the layout target before we just laid out
- * @param {Ext.Container} container The container
- * @param {Ext.Element} target The target element
- */
- handleTargetOverflow: function(previousTargetSize, container, target) {
- var overflow = target.getStyle('overflow');
+ // flex and not 'auto' width
+ if (child.flex && !childWidth) {
+ totalFlex += child.flex;
- if (overflow && overflow != 'hidden' &&!this.adjustmentPass) {
- var newTargetSize = this.getLayoutTargetSize();
- if (newTargetSize.width != previousTargetSize.width || newTargetSize.height != previousTargetSize.height){
- this.adjustmentPass = true;
- this.onLayout(container, target);
+ // Not flexed or 'auto' width or undefined width
+ } else {
+ //Render and layout sub-containers without a flex or width defined, as otherwise we
+ //don't know how wide the sub-container should be and cannot calculate flexed widths
+ if (!childWidth && canLayout) {
+ child.doLayout();
+ }
+
+ childSize = child.getSize();
+ childWidth = childSize.width;
+ childHeight = childSize.height;
+ }
}
- }
- delete this.adjustmentPass;
- },
+ childMargins = child.margins;
+ horizMargins = childMargins.left + childMargins.right;
- // private
- isValidParent : function(c, target){
- return this.innerCt && c.getPositionEl().dom.parentNode == this.innerCt.dom;
- },
+ nonFlexWidth += horizMargins + (childWidth || 0);
+ desiredWidth += horizMargins + (child.flex ? child.minWidth || 0 : childWidth);
+ minimumWidth += horizMargins + (child.minWidth || childWidth || 0);
- /**
- * @private
- * Returns all items that are both rendered and visible
- * @return {Array} All matching items
- */
- getVisibleItems: function(ct) {
- var ct = ct || this.container,
- t = ct.getLayoutTarget(),
- cti = ct.items.items,
- len = cti.length,
+ // Max height for align - force layout of non-laid out subcontainers without a numeric height
+ if (typeof childHeight != 'number') {
+ if (canLayout) {
+ child.doLayout();
+ }
+ childHeight = child.getHeight();
+ }
- i, c, items = [];
+ maxHeight = Math.max(maxHeight, childHeight + childMargins.top + childMargins.bottom);
- for (i = 0; i < len; i++) {
- if((c = cti[i]).rendered && this.isValidParent(c, t) && c.hidden !== true && c.collapsed !== true){
- items.push(c);
- }
+ //cache the size of each child component. Don't set height or width to 0, keep undefined instead
+ boxes.push({
+ component: child,
+ height : childHeight || undefined,
+ width : childWidth || undefined
+ });
}
+
+ var shortfall = desiredWidth - width,
+ tooNarrow = minimumWidth > width;
+
+ //the width available to the flexed items
+ var availableWidth = Math.max(0, width - nonFlexWidth - paddingHoriz);
+
+ if (tooNarrow) {
+ for (i = 0; i < visibleCount; i++) {
+ boxes[i].width = visibleItems[i].minWidth || visibleItems[i].width || boxes[i].width;
+ }
+ } else {
+ //all flexed items should be sized to their minimum width, other items should be shrunk down until
+ //the shortfall has been accounted for
+ if (shortfall > 0) {
+ var minWidths = [];
+
+ /**
+ * When we have a shortfall but are not tooNarrow, we need to shrink the width of each non-flexed item.
+ * Flexed items are immediately reduced to their minWidth and anything already at minWidth is ignored.
+ * The remaining items are collected into the minWidths array, which is later used to distribute the shortfall.
+ */
+ for (var index = 0, length = visibleCount; index < length; index++) {
+ var item = visibleItems[index],
+ minWidth = item.minWidth || 0;
+
+ //shrink each non-flex tab by an equal amount to make them all fit. Flexed items are all
+ //shrunk to their minWidth because they're flexible and should be the first to lose width
+ if (item.flex) {
+ boxes[index].width = minWidth;
+ } else {
+ minWidths.push({
+ minWidth : minWidth,
+ available: boxes[index].width - minWidth,
+ index : index
+ });
+ }
+ }
+
+ //sort by descending amount of width remaining before minWidth is reached
+ minWidths.sort(function(a, b) {
+ return a.available > b.available ? 1 : -1;
+ });
+
+ /*
+ * Distribute the shortfall (difference between total desired with of all items and actual width available)
+ * between the non-flexed items. We try to distribute the shortfall evenly, but apply it to items with the
+ * smallest difference between their width and minWidth first, so that if reducing the width by the average
+ * amount would make that item less than its minWidth, we carry the remainder over to the next item.
+ */
+ for (var i = 0, length = minWidths.length; i < length; i++) {
+ var itemIndex = minWidths[i].index;
+
+ if (itemIndex == undefined) {
+ continue;
+ }
+
+ var item = visibleItems[itemIndex],
+ box = boxes[itemIndex],
+ oldWidth = box.width,
+ minWidth = item.minWidth,
+ newWidth = Math.max(minWidth, oldWidth - Math.ceil(shortfall / (length - i))),
+ reduction = oldWidth - newWidth;
+
+ boxes[itemIndex].width = newWidth;
+ shortfall -= reduction;
+ }
+ } else {
+ //temporary variables used in the flex width calculations below
+ var remainingWidth = availableWidth,
+ remainingFlex = totalFlex;
- return items;
- },
+ //calculate the widths of each flexed item
+ for (i = 0; i < visibleCount; i++) {
+ child = visibleItems[i];
+ calcs = boxes[i];
- // private
- renderAll : function(ct, target){
- if(!this.innerCt){
- // the innerCt prevents wrapping and shuffling while
- // the container is resizing
- this.innerCt = target.createChild({cls:this.innerCls});
- this.padding = this.parseMargins(this.padding);
- }
- Ext.layout.BoxLayout.superclass.renderAll.call(this, ct, this.innerCt);
- },
+ childMargins = child.margins;
+ vertMargins = childMargins.top + childMargins.bottom;
- getLayoutTargetSize : function(){
- var target = this.container.getLayoutTarget(), ret;
- if (target) {
- ret = target.getViewSize();
+ if (isStart && child.flex && !child.width) {
+ flexedWidth = Math.ceil((child.flex / remainingFlex) * remainingWidth);
+ remainingWidth -= flexedWidth;
+ remainingFlex -= child.flex;
- // IE in strict mode will return a width of 0 on the 1st pass of getViewSize.
- // Use getStyleSize to verify the 0 width, the adjustment pass will then work properly
- // with getViewSize
- if (Ext.isIE && Ext.isStrict && ret.width == 0){
- ret = target.getStyleSize();
+ calcs.width = flexedWidth;
+ calcs.dirtySize = true;
+ }
+ }
}
-
- ret.width -= target.getPadding('lr');
- ret.height -= target.getPadding('tb');
}
- return ret;
- },
+
+ if (isCenter) {
+ leftOffset += availableWidth / 2;
+ } else if (isEnd) {
+ leftOffset += availableWidth;
+ }
+
+ //finally, calculate the left and top position of each item
+ for (i = 0; i < visibleCount; i++) {
+ child = visibleItems[i];
+ calcs = boxes[i];
+
+ childMargins = child.margins;
+ leftOffset += childMargins.left;
+ vertMargins = childMargins.top + childMargins.bottom;
+
+ calcs.left = leftOffset;
+ calcs.top = topOffset + childMargins.top;
- // private
- renderItem : function(c){
- if(Ext.isString(c.margins)){
- c.margins = this.parseMargins(c.margins);
- }else if(!c.margins){
- c.margins = this.defaultMargins;
+ switch (this.align) {
+ case 'stretch':
+ stretchHeight = availHeight - vertMargins;
+ calcs.height = stretchHeight.constrain(child.minHeight || 0, child.maxHeight || 1000000);
+ calcs.dirtySize = true;
+ break;
+ case 'stretchmax':
+ stretchHeight = maxHeight - vertMargins;
+ calcs.height = stretchHeight.constrain(child.minHeight || 0, child.maxHeight || 1000000);
+ calcs.dirtySize = true;
+ break;
+ case 'middle':
+ var diff = availHeight - calcs.height - vertMargins;
+ if (diff > 0) {
+ calcs.top = topOffset + vertMargins + (diff / 2);
+ }
+ }
+
+ leftOffset += calcs.width + childMargins.right;
}
- Ext.layout.BoxLayout.superclass.renderItem.apply(this, arguments);
+
+ return {
+ boxes: boxes,
+ meta : {
+ maxHeight : maxHeight,
+ nonFlexWidth: nonFlexWidth,
+ desiredWidth: desiredWidth,
+ minimumWidth: minimumWidth,
+ shortfall : desiredWidth - width,
+ tooNarrow : tooNarrow
+ }
+ };
}
});
-/**
+Ext.Container.LAYOUTS.hbox = Ext.layout.HBoxLayout;/**
* @class Ext.layout.VBoxLayout
* @extends Ext.layout.BoxLayout
* <p>A layout that arranges items vertically down a Container. This layout optionally divides available vertical
* <tt>flex = undefined</tt> will not be 'flexed' (the initial size will not be changed).
*/
- /**
- * @private
- * See parent documentation
- */
- updateInnerCtSize: function(tSize, calcs) {
- var innerCtHeight = tSize.height,
- innerCtWidth = calcs.meta.maxWidth + this.padding.left + this.padding.right;
-
- if (this.align == 'stretch') {
- innerCtWidth = tSize.width;
- } else if (this.align == 'center') {
- innerCtWidth = Math.max(tSize.width, innerCtWidth);
- }
-
- //we set the innerCt size first because if our child items are larger than the previous innerCt size
- //the browser will insert scrollbars and then remove them again immediately afterwards
- this.innerCt.setSize(innerCtWidth || undefined, innerCtHeight || undefined);
- },
-
/**
* @private
* Calculates the size and positioning of each item in the VBox. This iterates over all of the rendered,
nonFlexHeight= 0,
maxWidth = 0,
totalFlex = 0,
+ desiredHeight= 0,
+ minimumHeight= 0,
//used to cache the calculated size and position values for each child item
boxes = [],
-
+
//used in the for loops below, just declared here for brevity
- child, childWidth, childHeight, childSize, childMargins, canLayout, i, calcs, flexedHeight, horizMargins, stretchWidth;
-
- //gather the total flex of all flexed items and the width taken up by fixed width items
- for (i = 0; i < visibleCount; i++) {
- child = visibleItems[i];
- childHeight = child.height;
- childWidth = child.width;
- canLayout = !child.hasLayout && Ext.isFunction(child.doLayout);
-
-
- // Static height (numeric) requires no calcs
- if (!Ext.isNumber(childHeight)) {
-
- // flex and not 'auto' height
- if (child.flex && !childHeight) {
- totalFlex += child.flex;
-
- // Not flexed or 'auto' height or undefined height
- } else {
- //Render and layout sub-containers without a flex or width defined, as otherwise we
- //don't know how wide the sub-container should be and cannot calculate flexed widths
- if (!childHeight && canLayout) {
- child.doLayout();
- }
+ child, childWidth, childHeight, childSize, childMargins, canLayout, i, calcs, flexedWidth,
+ horizMargins, vertMargins, stretchWidth;
- childSize = child.getSize();
- childWidth = childSize.width;
- childHeight = childSize.height;
- }
- }
+ //gather the total flex of all flexed items and the width taken up by fixed width items
+ for (i = 0; i < visibleCount; i++) {
+ child = visibleItems[i];
+ childHeight = child.height;
+ childWidth = child.width;
+ canLayout = !child.hasLayout && typeof child.doLayout == 'function';
- childMargins = child.margins;
+ // Static height (numeric) requires no calcs
+ if (typeof childHeight != 'number') {
- nonFlexHeight += (childHeight || 0) + childMargins.top + childMargins.bottom;
+ // flex and not 'auto' height
+ if (child.flex && !childHeight) {
+ totalFlex += child.flex;
- // Max width for align - force layout of non-layed out subcontainers without a numeric width
- if (!Ext.isNumber(childWidth)) {
- if (canLayout) {
+ // Not flexed or 'auto' height or undefined height
+ } else {
+ //Render and layout sub-containers without a flex or width defined, as otherwise we
+ //don't know how wide the sub-container should be and cannot calculate flexed widths
+ if (!childHeight && canLayout) {
child.doLayout();
}
- childWidth = child.getWidth();
- }
-
- maxWidth = Math.max(maxWidth, childWidth + childMargins.left + childMargins.right);
-
- //cache the size of each child component
- boxes.push({
- component: child,
- height : childHeight || undefined,
- width : childWidth || undefined
- });
- }
- //the height available to the flexed items
- var availableHeight = Math.max(0, (height - nonFlexHeight - paddingVert));
-
- if (isCenter) {
- topOffset += availableHeight / 2;
- } else if (isEnd) {
- topOffset += availableHeight;
- }
-
- //temporary variables used in the flex height calculations below
- var remainingHeight = availableHeight,
- remainingFlex = totalFlex;
-
- //calculate the height of each flexed item, and the left + top positions of every item
- for (i = 0; i < visibleCount; i++) {
- child = visibleItems[i];
- calcs = boxes[i];
-
- childMargins = child.margins;
- horizMargins = childMargins.left + childMargins.right;
-
- topOffset += childMargins.top;
-
- if (isStart && child.flex && !child.height) {
- flexedHeight = Math.ceil((child.flex / remainingFlex) * remainingHeight);
- remainingHeight -= flexedHeight;
- remainingFlex -= child.flex;
-
- calcs.height = flexedHeight;
- calcs.dirtySize = true;
+ childSize = child.getSize();
+ childWidth = childSize.width;
+ childHeight = childSize.height;
}
+ }
+
+ childMargins = child.margins;
+ vertMargins = childMargins.top + childMargins.bottom;
- calcs.left = leftOffset + childMargins.left;
- calcs.top = topOffset;
+ nonFlexHeight += vertMargins + (childHeight || 0);
+ desiredHeight += vertMargins + (child.flex ? child.minHeight || 0 : childHeight);
+ minimumHeight += vertMargins + (child.minHeight || childHeight || 0);
- switch (this.align) {
- case 'stretch':
- stretchWidth = availWidth - horizMargins;
- calcs.width = stretchWidth.constrain(child.minWidth || 0, child.maxWidth || 1000000);
- calcs.dirtySize = true;
- break;
- case 'stretchmax':
- stretchWidth = maxWidth - horizMargins;
- calcs.width = stretchWidth.constrain(child.minWidth || 0, child.maxWidth || 1000000);
- calcs.dirtySize = true;
- break;
- case 'center':
- var diff = availWidth - calcs.width - horizMargins;
- if (diff > 0) {
- calcs.left = leftOffset + horizMargins + (diff / 2);
- }
+ // Max width for align - force layout of non-layed out subcontainers without a numeric width
+ if (typeof childWidth != 'number') {
+ if (canLayout) {
+ child.doLayout();
}
-
- topOffset += calcs.height + childMargins.bottom;
+ childWidth = child.getWidth();
}
- return {
- boxes: boxes,
- meta : {
- maxWidth: maxWidth
- }
- };
- }
-});
-
-Ext.Container.LAYOUTS.vbox = Ext.layout.VBoxLayout;
-
-/**
- * @class Ext.layout.HBoxLayout
- * @extends Ext.layout.BoxLayout
- * <p>A layout that arranges items horizontally across a Container. This layout optionally divides available horizontal
- * space between child items containing a numeric <code>flex</code> configuration.</p>
- * This layout may also be used to set the heights of child items by configuring it with the {@link #align} option.
- */
-Ext.layout.HBoxLayout = Ext.extend(Ext.layout.BoxLayout, {
- /**
- * @cfg {String} align
- * Controls how the child items of the container are aligned. Acceptable configuration values for this
- * property are:
- * <div class="mdetail-params"><ul>
- * <li><b><tt>top</tt></b> : <b>Default</b><div class="sub-desc">child items are aligned vertically
- * at the <b>top</b> of the container</div></li>
- * <li><b><tt>middle</tt></b> : <div class="sub-desc">child items are aligned vertically in the
- * <b>middle</b> of the container</div></li>
- * <li><b><tt>stretch</tt></b> : <div class="sub-desc">child items are stretched vertically to fill
- * the height of the container</div></li>
- * <li><b><tt>stretchmax</tt></b> : <div class="sub-desc">child items are stretched vertically to
- * the height of the largest item.</div></li>
- */
- align: 'top', // top, middle, stretch, strechmax
-
- type : 'hbox',
-
- /**
- * @private
- * See parent documentation
- */
- updateInnerCtSize: function(tSize, calcs) {
- var innerCtWidth = tSize.width,
- innerCtHeight = calcs.meta.maxHeight + this.padding.top + this.padding.bottom;
+ maxWidth = Math.max(maxWidth, childWidth + childMargins.left + childMargins.right);
- if (this.align == 'stretch') {
- innerCtHeight = tSize.height;
- } else if (this.align == 'middle') {
- innerCtHeight = Math.max(tSize.height, innerCtHeight);
+ //cache the size of each child component
+ boxes.push({
+ component: child,
+ height : childHeight || undefined,
+ width : childWidth || undefined
+ });
}
+
+ var shortfall = desiredHeight - height,
+ tooNarrow = minimumHeight > height;
- this.innerCt.setSize(innerCtWidth || undefined, innerCtHeight || undefined);
- },
-
- /**
- * @cfg {String} pack
- * Controls how the child items of the container are packed together. Acceptable configuration values
- * for this property are:
- * <div class="mdetail-params"><ul>
- * <li><b><tt>start</tt></b> : <b>Default</b><div class="sub-desc">child items are packed together at
- * <b>left</b> side of container</div></li>
- * <li><b><tt>center</tt></b> : <div class="sub-desc">child items are packed together at
- * <b>mid-width</b> of container</div></li>
- * <li><b><tt>end</tt></b> : <div class="sub-desc">child items are packed together at <b>right</b>
- * side of container</div></li>
- * </ul></div>
- */
- /**
- * @cfg {Number} flex
- * This configuation option is to be applied to <b>child <tt>items</tt></b> of the container managed
- * by this layout. Each child item with a <tt>flex</tt> property will be flexed <b>horizontally</b>
- * according to each item's <b>relative</b> <tt>flex</tt> value compared to the sum of all items with
- * a <tt>flex</tt> value specified. Any child items that have either a <tt>flex = 0</tt> or
- * <tt>flex = undefined</tt> will not be 'flexed' (the initial size will not be changed).
- */
-
- /**
- * @private
- * Calculates the size and positioning of each item in the HBox. This iterates over all of the rendered,
- * visible items and returns a height, width, top and left for each, as well as a reference to each. Also
- * returns meta data such as maxHeight which are useful when resizing layout wrappers such as this.innerCt.
- * @param {Array} visibleItems The array of all rendered, visible items to be calculated for
- * @param {Object} targetSize Object containing target size and height
- * @return {Object} Object containing box measurements for each child, plus meta data
- */
- calculateChildBoxes: function(visibleItems, targetSize) {
- var visibleCount = visibleItems.length,
-
- padding = this.padding,
- topOffset = padding.top,
- leftOffset = padding.left,
- paddingVert = topOffset + padding.bottom,
- paddingHoriz = leftOffset + padding.right,
-
- width = targetSize.width - this.scrollOffset,
- height = targetSize.height,
- availHeight = Math.max(0, height - paddingVert),
-
- isStart = this.pack == 'start',
- isCenter = this.pack == 'center',
- isEnd = this.pack == 'end',
- // isRestore = ['stretch', 'stretchmax'].indexOf(this.align) == -1,
-
- nonFlexWidth = 0,
- maxHeight = 0,
- totalFlex = 0,
-
- //used to cache the calculated size and position values for each child item
- boxes = [],
-
- //used in the for loops below, just declared here for brevity
- child, childWidth, childHeight, childSize, childMargins, canLayout, i, calcs, flexedWidth, vertMargins, stretchHeight;
-
- //gather the total flex of all flexed items and the width taken up by fixed width items
- for (i = 0; i < visibleCount; i++) {
- child = visibleItems[i];
- childHeight = child.height;
- childWidth = child.width;
- canLayout = !child.hasLayout && Ext.isFunction(child.doLayout);
-
- // Static width (numeric) requires no calcs
- if (!Ext.isNumber(childWidth)) {
-
- // flex and not 'auto' width
- if (child.flex && !childWidth) {
- totalFlex += child.flex;
+ //the height available to the flexed items
+ var availableHeight = Math.max(0, (height - nonFlexHeight - paddingVert));
+
+ if (tooNarrow) {
+ for (i = 0, length = visibleCount; i < length; i++) {
+ boxes[i].height = visibleItems[i].minHeight || visibleItems[i].height || boxes[i].height;
+ }
+ } else {
+ //all flexed items should be sized to their minimum width, other items should be shrunk down until
+ //the shortfall has been accounted for
+ if (shortfall > 0) {
+ var minHeights = [];
- // Not flexed or 'auto' width or undefined width
+ /**
+ * When we have a shortfall but are not tooNarrow, we need to shrink the height of each non-flexed item.
+ * Flexed items are immediately reduced to their minHeight and anything already at minHeight is ignored.
+ * The remaining items are collected into the minHeights array, which is later used to distribute the shortfall.
+ */
+ for (var index = 0, length = visibleCount; index < length; index++) {
+ var item = visibleItems[index],
+ minHeight = item.minHeight || 0;
+
+ //shrink each non-flex tab by an equal amount to make them all fit. Flexed items are all
+ //shrunk to their minHeight because they're flexible and should be the first to lose height
+ if (item.flex) {
+ boxes[index].height = minHeight;
} else {
- //Render and layout sub-containers without a flex or width defined, as otherwise we
- //don't know how wide the sub-container should be and cannot calculate flexed widths
- if (!childWidth && canLayout) {
- child.doLayout();
- }
-
- childSize = child.getSize();
- childWidth = childSize.width;
- childHeight = childSize.height;
+ minHeights.push({
+ minHeight: minHeight,
+ available: boxes[index].height - minHeight,
+ index : index
+ });
}
}
- childMargins = child.margins;
+ //sort by descending minHeight value
+ minHeights.sort(function(a, b) {
+ return a.available > b.available ? 1 : -1;
+ });
- nonFlexWidth += (childWidth || 0) + childMargins.left + childMargins.right;
+ /*
+ * Distribute the shortfall (difference between total desired with of all items and actual height available)
+ * between the non-flexed items. We try to distribute the shortfall evenly, but apply it to items with the
+ * smallest difference between their height and minHeight first, so that if reducing the height by the average
+ * amount would make that item less than its minHeight, we carry the remainder over to the next item.
+ */
+ for (var i = 0, length = minHeights.length; i < length; i++) {
+ var itemIndex = minHeights[i].index;
- // Max height for align - force layout of non-layed out subcontainers without a numeric height
- if (!Ext.isNumber(childHeight)) {
- if (canLayout) {
- child.doLayout();
+ if (itemIndex == undefined) {
+ continue;
}
- childHeight = child.getHeight();
- }
- maxHeight = Math.max(maxHeight, childHeight + childMargins.top + childMargins.bottom);
+ var item = visibleItems[itemIndex],
+ box = boxes[itemIndex],
+ oldHeight = box.height,
+ minHeight = item.minHeight,
+ newHeight = Math.max(minHeight, oldHeight - Math.ceil(shortfall / (length - i))),
+ reduction = oldHeight - newHeight;
- //cache the size of each child component
- boxes.push({
- component: child,
- height : childHeight || undefined,
- width : childWidth || undefined
- });
- }
-
- //the width available to the flexed items
- var availableWidth = Math.max(0, (width - nonFlexWidth - paddingHoriz));
+ boxes[itemIndex].height = newHeight;
+ shortfall -= reduction;
+ }
+ } else {
+ //temporary variables used in the flex height calculations below
+ var remainingHeight = availableHeight,
+ remainingFlex = totalFlex;
+
+ //calculate the height of each flexed item
+ for (i = 0; i < visibleCount; i++) {
+ child = visibleItems[i];
+ calcs = boxes[i];
- if (isCenter) {
- leftOffset += availableWidth / 2;
- } else if (isEnd) {
- leftOffset += availableWidth;
- }
+ childMargins = child.margins;
+ horizMargins = childMargins.left + childMargins.right;
- //temporary variables used in the flex width calculations below
- var remainingWidth = availableWidth,
- remainingFlex = totalFlex;
+ if (isStart && child.flex && !child.height) {
+ flexedHeight = Math.ceil((child.flex / remainingFlex) * remainingHeight);
+ remainingHeight -= flexedHeight;
+ remainingFlex -= child.flex;
- //calculate the widths of each flexed item, and the left + top positions of every item
- for (i = 0; i < visibleCount; i++) {
- child = visibleItems[i];
- calcs = boxes[i];
+ calcs.height = flexedHeight;
+ calcs.dirtySize = true;
+ }
+ }
+ }
+ }
- childMargins = child.margins;
- vertMargins = childMargins.top + childMargins.bottom;
+ if (isCenter) {
+ topOffset += availableHeight / 2;
+ } else if (isEnd) {
+ topOffset += availableHeight;
+ }
- leftOffset += childMargins.left;
+ //finally, calculate the left and top position of each item
+ for (i = 0; i < visibleCount; i++) {
+ child = visibleItems[i];
+ calcs = boxes[i];
- if (isStart && child.flex && !child.width) {
- flexedWidth = Math.ceil((child.flex / remainingFlex) * remainingWidth);
- remainingWidth -= flexedWidth;
- remainingFlex -= child.flex;
+ childMargins = child.margins;
+ topOffset += childMargins.top;
+ horizMargins = childMargins.left + childMargins.right;
+
- calcs.width = flexedWidth;
+ calcs.left = leftOffset + childMargins.left;
+ calcs.top = topOffset;
+
+ switch (this.align) {
+ case 'stretch':
+ stretchWidth = availWidth - horizMargins;
+ calcs.width = stretchWidth.constrain(child.minWidth || 0, child.maxWidth || 1000000);
calcs.dirtySize = true;
- }
-
- calcs.left = leftOffset;
- calcs.top = topOffset + childMargins.top;
-
- switch (this.align) {
- case 'stretch':
- stretchHeight = availHeight - vertMargins;
- calcs.height = stretchHeight.constrain(child.minHeight || 0, child.maxHeight || 1000000);
- calcs.dirtySize = true;
- break;
- case 'stretchmax':
- stretchHeight = maxHeight - vertMargins;
- calcs.height = stretchHeight.constrain(child.minHeight || 0, child.maxHeight || 1000000);
- calcs.dirtySize = true;
- break;
- case 'middle':
- var diff = availHeight - calcs.height - vertMargins;
- if (diff > 0) {
- calcs.top = topOffset + vertMargins + (diff / 2);
- }
- }
- leftOffset += calcs.width + childMargins.right;
+ break;
+ case 'stretchmax':
+ stretchWidth = maxWidth - horizMargins;
+ calcs.width = stretchWidth.constrain(child.minWidth || 0, child.maxWidth || 1000000);
+ calcs.dirtySize = true;
+ break;
+ case 'center':
+ var diff = availWidth - calcs.width - horizMargins;
+ if (diff > 0) {
+ calcs.left = leftOffset + horizMargins + (diff / 2);
+ }
}
+ topOffset += calcs.height + childMargins.bottom;
+ }
+
return {
boxes: boxes,
meta : {
- maxHeight: maxHeight
+ maxWidth : maxWidth,
+ nonFlexHeight: nonFlexHeight,
+ desiredHeight: desiredHeight,
+ minimumHeight: minimumHeight,
+ shortfall : desiredHeight - height,
+ tooNarrow : tooNarrow
}
};
}
});
-Ext.Container.LAYOUTS.hbox = Ext.layout.HBoxLayout;
+Ext.Container.LAYOUTS.vbox = Ext.layout.VBoxLayout;
/**
* @class Ext.layout.ToolbarLayout
* @extends Ext.layout.ContainerLayout
position = -1;
} else if (!c.rendered) {
c.render(this.insertCell(c, side, position));
+ this.configureItem(c);
} else {
if (!c.xtbHidden && !this.isValidParent(c, side.childNodes[position])) {
var td = this.insertCell(c, side, position);
this.itemTpl = Ext.layout.MenuLayout.prototype.itemTpl = new Ext.XTemplate(
'<li id="{itemId}" class="{itemCls}">',
'<tpl if="needsIcon">',
- '<img src="{icon}" class="{iconCls}"/>',
+ '<img alt="{altText}" src="{icon}" class="{iconCls}"/>',
'</tpl>',
'</li>'
);
if (!a.isMenuItem && a.needsIcon) {
c.positionEl.addClass('x-menu-list-item-indent');
}
- this.configureItem(c, position);
+ this.configureItem(c);
}else if(c && !this.isValidParent(c, target)){
if(Ext.isNumber(position)){
position = target.dom.childNodes[position];
},
getItemArgs : function(c) {
- var isMenuItem = c instanceof Ext.menu.Item;
+ var isMenuItem = c instanceof Ext.menu.Item,
+ canHaveIcon = !(isMenuItem || c instanceof Ext.menu.Separator);
+
return {
isMenuItem: isMenuItem,
- needsIcon: !isMenuItem && (c.icon || c.iconCls),
+ needsIcon: canHaveIcon && (c.icon || c.iconCls),
icon: c.icon || Ext.BLANK_IMAGE_URL,
iconCls: 'x-menu-item-icon ' + (c.iconCls || ''),
itemId: 'x-menu-el-' + c.id,
- itemCls: 'x-menu-list-item '
+ itemCls: 'x-menu-list-item ',
+ altText: c.altText || ''
};
},
},
footerCfg: {
tag: 'h2',
- cls: 'x-panel-footer' // same as the Default class
+ cls: 'x-panel-footer', // same as the Default class
html: 'footer html'
},
footerCssClass: 'custom-footer', // additional css class, see {@link Ext.element#addClass addClass}
var hdspan = hd.child('span.' + this.headerTextCls);
if (hdspan) {
Ext.DomHelper.insertBefore(hdspan.dom, {
- tag:'img', src: Ext.BLANK_IMAGE_URL, cls:'x-panel-inline-icon '+this.iconCls
+ tag:'img', alt: '', src: Ext.BLANK_IMAGE_URL, cls:'x-panel-inline-icon '+this.iconCls
});
}
}
* @return {Number} The frame height
*/
getFrameHeight : function() {
- var h = Math.max(0, this.getHeight() - this.body.getHeight());
+ var h = this.el.getFrameWidth('tb') + this.bwrap.getFrameWidth('tb');
+ h += (this.tbar ? this.tbar.getHeight() : 0) +
+ (this.bbar ? this.bbar.getHeight() : 0);
- if (isNaN(h)) {
- h = 0;
+ if(this.frame){
+ h += this.el.dom.firstChild.offsetHeight + this.ft.dom.offsetHeight + this.mc.getFrameWidth('tb');
+ }else{
+ h += (this.header ? this.header.getHeight() : 0) +
+ (this.footer ? this.footer.getHeight() : 0);
}
return h;
-
- /* Deprecate
- var h = this.el.getFrameWidth('tb') + this.bwrap.getFrameWidth('tb');
- h += (this.tbar ? this.tbar.getHeight() : 0) +
- (this.bbar ? this.bbar.getHeight() : 0);
-
- if(this.frame){
- h += this.el.dom.firstChild.offsetHeight + this.ft.dom.offsetHeight + this.mc.getFrameWidth('tb');
- }else{
- h += (this.header ? this.header.getHeight() : 0) +
- (this.footer ? this.footer.getHeight() : 0);
- }
- return h;
- */
},
/**
delete this.field.lastSize;
this.field.setSize(w, h);
if(this.el){
- if(Ext.isGecko2 || Ext.isOpera){
+ // IE7 in strict mode doesn't size properly.
+ if(Ext.isGecko2 || Ext.isOpera || (Ext.isIE7 && Ext.isStrict)){
// prevent layer scrollbars
this.el.setSize(w, h);
}
* be created internally by an {@link Ext.slider.MultiSlider Ext.Slider}.
*/
Ext.slider.Thumb = Ext.extend(Object, {
+
+ /**
+ * True while the thumb is in a drag operation
+ * @type Boolean
+ */
+ dragging: false,
/**
* @constructor
if (this.dragStartValue != value) {
slider.fireEvent('changecomplete', slider, value, this);
}
+ },
+
+ /**
+ * @private
+ * Destroys the thumb
+ */
+ destroy: function(){
+ Ext.destroyMembers(this, 'tracker', 'el');
}
});
* @cfg {Boolean} animate Turn on or off animation. Defaults to true
*/
animate: true,
-
- /**
- * True while the thumb is in a drag operation
- * @type Boolean
- */
- dragging: false,
-
/**
* @cfg {Boolean} constrainThumbs True to disallow thumbs from overlapping one another. Defaults to true
*/
* @event beforechange
* Fires before the slider value is changed. By returning false from an event handler,
* you can cancel the event and prevent the slider from changing.
- * @param {Ext.Slider} slider The slider
+ * @param {Ext.slider.MultiSlider} slider The slider
* @param {Number} newValue The new value which the slider is being changed to.
* @param {Number} oldValue The old value which the slider was previously.
*/
/**
* @event change
* Fires when the slider value is changed.
- * @param {Ext.Slider} slider The slider
+ * @param {Ext.slider.MultiSlider} slider The slider
* @param {Number} newValue The new value which the slider has been changed to.
* @param {Ext.slider.Thumb} thumb The thumb that was changed
*/
/**
* @event changecomplete
* Fires when the slider value is changed by the user and any drag operations have completed.
- * @param {Ext.Slider} slider The slider
+ * @param {Ext.slider.MultiSlider} slider The slider
* @param {Number} newValue The new value which the slider has been changed to.
* @param {Ext.slider.Thumb} thumb The thumb that was changed
*/
/**
* @event dragstart
* Fires after a drag operation has started.
- * @param {Ext.Slider} slider The slider
+ * @param {Ext.slider.MultiSlider} slider The slider
* @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
*/
'dragstart',
/**
* @event drag
* Fires continuously during the drag operation while the mouse is moving.
- * @param {Ext.Slider} slider The slider
+ * @param {Ext.slider.MultiSlider} slider The slider
* @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
*/
'drag',
/**
* @event dragend
* Fires after the drag operation has completed.
- * @param {Ext.Slider} slider The slider
+ * @param {Ext.slider.MultiSlider} slider The slider
* @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
*/
'dragend'
for(; i < len; ++i){
thumbs[i].el.stopFx();
}
- this.innerEl.setWidth(w - (this.el.getPadding('l') + this.endEl.getPadding('r')));
+ // check to see if we're using an auto width
+ if(Ext.isNumber(w)){
+ this.innerEl.setWidth(w - (this.el.getPadding('l') + this.endEl.getPadding('r')));
+ }
this.syncThumb();
Ext.slider.MultiSlider.superclass.onResize.apply(this, arguments);
},
// private
beforeDestroy : function(){
- Ext.destroyMembers(this, 'endEl', 'innerEl', 'thumb', 'halfThumb', 'focusEl', 'tracker', 'thumbHolder');
+ var thumbs = this.thumbs;
+ for(var i = 0, len = thumbs.length; i < len; ++i){
+ thumbs[i].destroy();
+ thumbs[i] = null;
+ }
+ Ext.destroyMembers(this, 'endEl', 'innerEl', 'focusEl', 'thumbHolder');
Ext.slider.MultiSlider.superclass.beforeDestroy.call(this);
}
});
*/
handleMouseDown: function(e, oDD) {
if(Ext.QuickTips){
- Ext.QuickTips.disable();
+ Ext.QuickTips.ddDisable();
}
if(this.dragCurrent){
// the original browser mouseup wasn't handled (e.g. outside FF browser window)
handleMouseUp: function(e) {
if(Ext.QuickTips){
- Ext.QuickTips.enable();
+ Ext.QuickTips.ddEnable();
}
if (! this.dragCurrent) {
return;
* @class Ext.dd.DragTracker
* @extends Ext.util.Observable
* A DragTracker listens for drag events on an Element and fires events at the start and end of the drag,
- * as well as during the drag. This is useful for components such as {@link Ext.Slider}, where there is
+ * as well as during the drag. This is useful for components such as {@link Ext.slider.MultiSlider}, where there is
* an element that can be dragged around to change the Slider's value.
* DragTracker provides a series of template methods that should be overridden to provide functionality
* in response to detected drag operations. These are onBeforeStart, onStart, onDrag and onEnd.
- * See {@link Ext.Slider}'s initEvents function for an example implementation.
+ * See {@link Ext.slider.MultiSlider}'s initEvents function for an example implementation.
*/
Ext.dd.DragTracker = Ext.extend(Ext.util.Observable, {
/**
/**
* @event dragstart
* @param {Object} this
- * @param {Object} startXY the page coordinates of the event
+ * @param {Object} e event object
*/
'dragstart',
/**
destroy : function(){
this.el.un('mousedown', this.onMouseDown, this);
+ delete this.el;
},
onMouseDown: function(e, target){
if(this.preventDefault !== false){
e.preventDefault();
}
- var doc = Ext.getDoc();
- doc.on('mouseup', this.onMouseUp, this);
- doc.on('mousemove', this.onMouseMove, this);
- doc.on('selectstart', this.stopSelect, this);
+ Ext.getDoc().on({
+ scope: this,
+ mouseup: this.onMouseUp,
+ mousemove: this.onMouseMove,
+ selectstart: this.stopSelect
+ });
if(this.autoStart){
- this.timer = this.triggerStart.defer(this.autoStart === true ? 1000 : this.autoStart, this);
+ this.timer = this.triggerStart.defer(this.autoStart === true ? 1000 : this.autoStart, this, [e]);
}
}
},
this.lastXY = xy;
if(!this.active){
if(Math.abs(s[0]-xy[0]) > this.tolerance || Math.abs(s[1]-xy[1]) > this.tolerance){
- this.triggerStart();
+ this.triggerStart(e);
}else{
return;
}
},
onMouseUp: function(e) {
- var doc = Ext.getDoc();
+ var doc = Ext.getDoc(),
+ wasActive = this.active;
+
doc.un('mousemove', this.onMouseMove, this);
doc.un('mouseup', this.onMouseUp, this);
doc.un('selectstart', this.stopSelect, this);
e.preventDefault();
this.clearStart();
- var wasActive = this.active;
this.active = false;
delete this.elRegion;
this.fireEvent('mouseup', this, e);
}
},
- triggerStart: function(isTimer) {
+ triggerStart: function(e) {
this.clearStart();
this.active = true;
- this.onStart(this.startXY);
- this.fireEvent('dragstart', this, this.startXY);
+ this.onStart(e);
+ this.fireEvent('dragstart', this, e);
},
clearStart : function() {
/**
* Template method which should be overridden by each DragTracker instance. Called when a drag operation starts
* (e.g. the user has moved the tracked element beyond the specified tolerance)
- * @param {Array} xy x and y co-ordinates of the original location of the tracked element
+ * @param {Ext.EventObject} e The event object
*/
onStart : function(xy) {
},
getOffset : function(constrain){
- var xy = this.getXY(constrain);
- var s = this.startXY;
+ var xy = this.getXY(constrain),
+ s = this.startXY;
return [s[0]-xy[0], s[1]-xy[1]];
},
proc.el = null;
proc.dir = "";
};
-
+
var startProc = function(el, dir){
clearProc();
proc.el = el;
proc.dir = dir;
- var freq = (el.ddScrollConfig && el.ddScrollConfig.frequency) ?
- el.ddScrollConfig.frequency : Ext.dd.ScrollManager.frequency;
- proc.id = setInterval(doScroll, freq);
+ var group = el.ddScrollConfig ? el.ddScrollConfig.ddGroup : undefined,
+ freq = (el.ddScrollConfig && el.ddScrollConfig.frequency)
+ ? el.ddScrollConfig.frequency
+ : Ext.dd.ScrollManager.frequency;
+
+ if (group === undefined || ddm.dragCurrent.ddGroup == group) {
+ proc.id = setInterval(doScroll, freq);
+ }
};
var onFire = function(e, isDrop){
hthresh : 25,
/**
- * The number of pixels to scroll in each scroll increment (defaults to 50)
+ * The number of pixels to scroll in each scroll increment (defaults to 100)
* @type Number
*/
increment : 100,
*/
animDuration: .4,
+ /**
+ * The named drag drop {@link Ext.dd.DragSource#ddGroup group} to which this container belongs (defaults to undefined).
+ * If a ddGroup is specified, then container scrolling will only occur when a dragged object is in the same ddGroup.
+ * @type String
+ */
+ ddGroup: undefined,
+
/**
* Manually trigger a cache refresh.
*/
* @param {Mixed} el The container element
* @param {Object} config
*/
-Ext.dd.DropTarget = function(el, config){
- this.el = Ext.get(el);
+Ext.dd.DropTarget = Ext.extend(Ext.dd.DDTarget, {
- Ext.apply(this, config);
+ constructor : function(el, config){
+ this.el = Ext.get(el);
- if(this.containerScroll){
- Ext.dd.ScrollManager.register(this.el);
- }
+ Ext.apply(this, config);
+
+ if(this.containerScroll){
+ Ext.dd.ScrollManager.register(this.el);
+ }
+
+ Ext.dd.DropTarget.superclass.constructor.call(this, this.el.dom, this.ddGroup || this.group,
+ {isTarget: true});
+ },
- Ext.dd.DropTarget.superclass.constructor.call(this, this.el.dom, this.ddGroup || this.group,
- {isTarget: true});
-
-};
-
-Ext.extend(Ext.dd.DropTarget, Ext.dd.DDTarget, {
/**
* @cfg {String} ddGroup
* A named drag drop group to which this object belongs. If a group is specified, then this object will only
*/
notifyDrop : function(dd, e, data){
return false;
+ },
+
+ destroy : function(){
+ Ext.dd.DropTarget.superclass.destroy.call(this);
+ if(this.containerScroll){
+ Ext.dd.ScrollManager.unregister(this.el);
+ }
}
});/**
* @class Ext.dd.DragZone
* @param {Mixed} el The container element
* @param {Object} config
*/
-Ext.dd.DragZone = function(el, config){
- Ext.dd.DragZone.superclass.constructor.call(this, el, config);
- if(this.containerScroll){
- Ext.dd.ScrollManager.register(this.el);
- }
-};
-
-Ext.extend(Ext.dd.DragZone, Ext.dd.DragSource, {
+Ext.dd.DragZone = Ext.extend(Ext.dd.DragSource, {
+
+ constructor : function(el, config){
+ Ext.dd.DragZone.superclass.constructor.call(this, el, config);
+ if(this.containerScroll){
+ Ext.dd.ScrollManager.register(this.el);
+ }
+ },
+
/**
* This property contains the data representing the dragged object. This data is set up by the implementation
* of the {@link #getDragData} method. It must contain a <tt>ddel</tt> property, but can contain
*/
getRepairXY : function(e){
return Ext.Element.fly(this.dragData.ddel).getXY();
+ },
+
+ destroy : function(){
+ Ext.dd.DragZone.superclass.destroy.call(this);
+ if(this.containerScroll){
+ Ext.dd.ScrollManager.unregister(this.el);
+ }
}
});/**
* @class Ext.dd.DropZone
dir : 'dir'
},
- /**
- * @property isDestroyed
- * @type Boolean
- * True if the store has been destroyed already. Read only
- */
- isDestroyed: false,
-
- /**
- * @property hasMultiSort
- * @type Boolean
- * True if this store is currently sorted by more than one field/direction combination.
- */
+ isDestroyed: false,
hasMultiSort: false,
// private
batchKey : '_ext_batch_',
constructor : function(config){
+ /**
+ * @property multiSort
+ * @type Boolean
+ * True if this store is currently sorted by more than one field/direction combination.
+ */
+
+ /**
+ * @property isDestroyed
+ * @type Boolean
+ * True if the store has been destroyed already. Read only
+ */
+
this.data = new Ext.util.MixedCollection(false);
this.data.getKey = function(o){
return o.id;
};
-
+
// temporary removed-records cache
this.removed = [];
* @event clear
* Fires when the data cache has been cleared.
* @param {Store} this
- * @param {Record[]} The records that were cleared.
+ * @param {Record[]} records The records that were cleared.
*/
'clear',
/**
* @param {Ext.data.Record[]} records An Array of Ext.data.Record objects
* to add to the cache. See {@link #recordType}.
*/
- add : function(records){
+ add : function(records) {
+ var i, len, record, index;
+
records = [].concat(records);
- if(records.length < 1){
+ if (records.length < 1) {
return;
}
- for(var i = 0, len = records.length; i < len; i++){
- records[i].join(this);
+
+ for (i = 0, len = records.length; i < len; i++) {
+ record = records[i];
+
+ record.join(this);
+
+ if (record.dirty || record.phantom) {
+ this.modified.push(record);
+ }
}
- var index = this.data.length;
+
+ index = this.data.length;
this.data.addAll(records);
- if(this.snapshot){
+
+ if (this.snapshot) {
this.snapshot.addAll(records);
}
+
this.fireEvent('add', this, records, index);
},
var index = this.findInsertIndex(record);
this.insert(index, record);
},
+
+ /**
+ * @private
+ * Update a record within the store with a new reference
+ */
+ doUpdate : function(rec){
+ this.data.replace(rec.id, rec);
+ if(this.snapshot){
+ this.snapshot.replace(rec.id, rec);
+ }
+ this.fireEvent('update', this, rec, Ext.data.Record.COMMIT);
+ },
/**
* Remove Records from the Store and fires the {@link #remove} event.
* @param {Number} index The start index at which to insert the passed Records.
* @param {Ext.data.Record[]} records An Array of Ext.data.Record objects to add to the cache.
*/
- insert : function(index, records){
+ insert : function(index, records) {
+ var i, len, record;
+
records = [].concat(records);
- for(var i = 0, len = records.length; i < len; i++){
- this.data.insert(index, records[i]);
- records[i].join(this);
+ for (i = 0, len = records.length; i < len; i++) {
+ record = records[i];
+
+ this.data.insert(index + i, record);
+ record.join(this);
+
+ if (record.dirty || record.phantom) {
+ this.modified.push(record);
+ }
}
- if(this.snapshot){
+
+ if (this.snapshot) {
this.snapshot.addAll(records);
}
+
this.fireEvent('add', this, records, index);
},
},
/**
+ * @private
* Should not be used directly. Store#add will call this automatically if a Writer is set
* @param {Object} store
- * @param {Object} rs
+ * @param {Object} records
* @param {Object} index
- * @private
*/
- createRecords : function(store, rs, index) {
- for (var i = 0, len = rs.length; i < len; i++) {
- if (rs[i].phantom && rs[i].isValid()) {
- rs[i].markDirty(); // <-- Mark new records dirty
- this.modified.push(rs[i]); // <-- add to modified
+ createRecords : function(store, records, index) {
+ var modified = this.modified,
+ length = records.length,
+ record, i;
+
+ for (i = 0; i < length; i++) {
+ record = records[i];
+
+ if (record.phantom && record.isValid()) {
+ record.markDirty(); // <-- Mark new records dirty (Ed: why?)
+
+ if (modified.indexOf(record) == -1) {
+ modified.push(record);
+ }
}
}
if (this.autoSave === true) {
len,
trans,
batch,
- data = {};
+ data = {},
+ i;
// DESTROY: First check for removed records. Records in this.removed are guaranteed non-phantoms. @see Store#remove
if(this.removed.length){
queue.push(['destroy', this.removed]);
if(rs.length){
// CREATE: Next check for phantoms within rs. splice-off and execute create.
var phantoms = [];
- for(var i = rs.length-1; i >= 0; i--){
+ for(i = rs.length-1; i >= 0; i--){
if(rs[i].phantom === true){
var rec = rs.splice(i, 1).shift();
if(rec.isValid()){
len = queue.length;
if(len){
batch = ++this.batchCounter;
- for(var i = 0; i < len; ++i){
+ for(i = 0; i < len; ++i){
trans = queue[i];
data[trans[0]] = trans[1];
}
if(this.fireEvent('beforesave', this, data) !== false){
- for(var i = 0; i < len; ++i){
+ for(i = 0; i < len; ++i){
trans = queue[i];
this.doTransaction(trans[0], trans[1], batch);
}
var b = this.batches,
key = this.batchKey + batch,
o = b[key],
- data,
arr;
// private
// Called as a callback by the Reader during a load operation.
loadRecords : function(o, options, success){
+ var i, len;
+
if (this.isDestroyed === true) {
return;
}
if(this.pruneModifiedRecords){
this.modified = [];
}
- for(var i = 0, len = r.length; i < len; i++){
+ for(i = 0, len = r.length; i < len; i++){
r[i].join(this);
}
if(this.snapshot){
this.applySort();
this.fireEvent('datachanged', this);
}else{
- this.totalLength = Math.max(t, this.data.length+r.length);
- this.add(r);
+ var toAdd = [],
+ rec,
+ cnt = 0;
+ for(i = 0, len = r.length; i < len; ++i){
+ rec = r[i];
+ if(this.indexOfId(rec.id) > -1){
+ this.doUpdate(rec);
+ }else{
+ toAdd.push(rec);
+ ++cnt;
+ }
+ }
+ this.totalLength = Math.max(t, this.data.length + cnt);
+ this.add(toAdd);
}
this.fireEvent('load', this, r, options);
if(options.callback){
*/
singleSort: function(fieldName, dir) {
var field = this.fields.get(fieldName);
- if (!field) return false;
+ if (!field) {
+ return false;
+ }
var name = field.name,
sortInfo = this.sortInfo || null,
this.applySort();
this.fireEvent('datachanged', this);
}
+ return true;
},
/**
}
/**
+ * Object containing overall sort direction and an ordered array of sorter configs used when sorting on multiple fields
* @property multiSortInfo
* @type Object
- * Object containing overall sort direction and an ordered array of sorter configs used when sorting on multiple fields
*/
this.multiSortInfo = {
sorters : sorters,
* @param {Boolean} exactMatch True to force exact match (^ and $ characters added to the regex). Defaults to false. Ignored if anyMatch is true.
*/
filter : function(property, value, anyMatch, caseSensitive, exactMatch){
+ var fn;
//we can accept an array of filter objects, or a single filter object - normalize them here
if (Ext.isObject(property)) {
property = [property];
filters.push({fn: func, scope: scope});
}
- var fn = this.createMultipleFilterFn(filters);
+ fn = this.createMultipleFilterFn(filters);
} else {
//classic single property filter
- var fn = this.createFilterFn(property, value, anyMatch, caseSensitive, exactMatch);
+ fn = this.createFilterFn(property, value, anyMatch, caseSensitive, exactMatch);
}
return fn ? this.filterBy(fn) : this.clearFilter();
*/
filterBy : function(fn, scope){
this.snapshot = this.snapshot || this.data;
- this.data = this.queryBy(fn, scope||this);
+ this.data = this.queryBy(fn, scope || this);
this.fireEvent('datachanged', this);
},
* Ext.data.Record.COMMIT.
*/
commitChanges : function(){
- var m = this.modified.slice(0);
- this.modified = [];
- for(var i = 0, len = m.length; i < len; i++){
- m[i].commit();
+ var modified = this.modified.slice(0),
+ length = modified.length,
+ i;
+
+ for (i = 0; i < length; i++){
+ modified[i].commit();
}
+
+ this.modified = [];
+ this.removed = [];
},
/**
* {@link Ext.data.Record#reject Reject} outstanding changes on all {@link #getModifiedRecords modified records}.
*/
- rejectChanges : function(){
- var m = this.modified.slice(0);
- this.modified = [];
- for(var i = 0, len = m.length; i < len; i++){
- m[i].reject();
+ rejectChanges : function() {
+ var modified = this.modified.slice(0),
+ removed = this.removed.slice(0).reverse(),
+ mLength = modified.length,
+ rLength = removed.length,
+ i;
+
+ for (i = 0; i < mLength; i++) {
+ modified[i].reject();
}
- var m = this.removed.slice(0).reverse();
- this.removed = [];
- for(var i = 0, len = m.length; i < len; i++){
- this.insert(m[i].lastIndex||0, m[i]);
- m[i].reject();
+
+ for (i = 0; i < rLength; i++) {
+ this.insert(removed[i].lastIndex || 0, removed[i]);
+ removed[i].reject();
}
+
+ this.modified = [];
+ this.removed = [];
},
// private
* javascript millisecond timestamp. See {@link Date}</p>
*/
dateFormat: null,
+
+ /**
+ * @cfg {Boolean} useNull
+ * <p>(Optional) Use when converting received data into a Number type (either int or float). If the value cannot be parsed,
+ * null will be used if useNull is true, otherwise the value will be 0. Defaults to <tt>false</tt>
+ */
+ useNull: false,
+
/**
* @cfg {Mixed} defaultValue
* (Optional) The default value used <b>when a Record is being created by a {@link Ext.data.Reader Reader}</b>
delete data[this.meta.idProperty];
}
} else {
- data[this.meta.idProperty] = rec.id
+ data[this.meta.idProperty] = rec.id;
}
return data;
},
});
// Listen to "exception" event fired by all proxies
-Ext.data.DataProxy.on('exception', function(proxy, type, action) {
+Ext.data.DataProxy.on('exception', function(proxy, type, action, exception) {
console.error(type + action + ' exception);
});
* </code></pre>
* so any Store instance may observe this event.</p>
* <p>In addition to being fired through the DataProxy instance that raised the event, this event is also fired
* through the Ext.data.DataProxy <i>class</i> to allow for centralized processing of exception events from <b>all</b>
- * DataProxies by attaching a listener to the Ext.data.Proxy class itself.</p>
+ * DataProxies by attaching a listener to the Ext.data.DataProxy class itself.</p>
* <p>This event can be fired for one of two reasons:</p>
* <div class="mdetail-params"><ul>
* <li>remote-request <b>failed</b> : <div class="sub-desc">
* <p>Fires before a request is generated for one of the actions Ext.data.Api.actions.create|update|destroy</p>
* <p>In addition to being fired through the DataProxy instance that raised the event, this event is also fired
* through the Ext.data.DataProxy <i>class</i> to allow for centralized processing of beforewrite events from <b>all</b>
- * DataProxies by attaching a listener to the Ext.data.Proxy class itself.</p>
+ * DataProxies by attaching a listener to the Ext.data.DataProxy class itself.</p>
* @param {DataProxy} this The proxy for the request
* @param {String} action [Ext.data.Api.actions.create|update|destroy]
* @param {Record/Record[]} rs The Record(s) to create|update|destroy.
* <p>Fires before the request-callback is called</p>
* <p>In addition to being fired through the DataProxy instance that raised the event, this event is also fired
* through the Ext.data.DataProxy <i>class</i> to allow for centralized processing of write events from <b>all</b>
- * DataProxies by attaching a listener to the Ext.data.Proxy class itself.</p>
+ * DataProxies by attaching a listener to the Ext.data.DataProxy class itself.</p>
* @param {DataProxy} this The proxy that sent the request
* @param {String} action [Ext.data.Api.actions.create|upate|destroy]
* @param {Object} data The data object extracted from the server-response
INT: {
convert: function(v){
return v !== undefined && v !== null && v !== '' ?
- parseInt(String(v).replace(Ext.data.Types.stripRe, ''), 10) : 0;
+ parseInt(String(v).replace(Ext.data.Types.stripRe, ''), 10) : (this.useNull ? null : 0);
},
sortType: st.none,
type: 'int'
FLOAT: {
convert: function(v){
return v !== undefined && v !== null && v !== '' ?
- parseFloat(String(v).replace(Ext.data.Types.stripRe, ''), 10) : 0;
+ parseFloat(String(v).replace(Ext.data.Types.stripRe, ''), 10) : (this.useNull ? null : 0);
},
sortType: st.none,
type: 'float'
*/
Ext.data.JsonWriter = Ext.extend(Ext.data.DataWriter, {
/**
- * @cfg {Boolean} encode <tt>true</tt> to {@link Ext.util.JSON#encode encode} the
- * {@link Ext.data.DataWriter#toHash hashed data}. Defaults to <tt>true</tt>. When using
- * {@link Ext.data.DirectProxy}, set this to <tt>false</tt> since Ext.Direct.JsonProvider will perform
+ * @cfg {Boolean} encode <p><tt>true</tt> to {@link Ext.util.JSON#encode JSON encode} the
+ * {@link Ext.data.DataWriter#toHash hashed data} into a standard HTTP parameter named after this
+ * Reader's <code>meta.root</code> property which, by default is imported from the associated Reader. Defaults to <tt>true</tt>.</p>
+ * <p>If set to <code>false</code>, the hashed data is {@link Ext.util.JSON#encode JSON encoded}, along with
+ * the associated {@link Ext.data.Store}'s {@link Ext.data.Store#baseParams baseParams}, into the POST body.</p>
+ * <p>When using {@link Ext.data.DirectProxy}, set this to <tt>false</tt> since Ext.Direct.JsonProvider will perform
* its own json-encoding. In addition, if you're using {@link Ext.data.HttpProxy}, setting to <tt>false</tt>
* will cause HttpProxy to transmit data using the <b>jsonData</b> configuration-params of {@link Ext.Ajax#request}
- * instead of <b>params</b>. When using a {@link Ext.data.Store#restful} Store, some serverside frameworks are
+ * instead of <b>params</b>.</p>
+ * <p>When using a {@link Ext.data.Store#restful} Store, some serverside frameworks are
* tuned to expect data through the jsonData mechanism. In those cases, one will want to set <b>encode: <tt>false</tt></b>, as in
- * let the lower-level connection object (eg: Ext.Ajax) do the encoding.
+ * let the lower-level connection object (eg: Ext.Ajax) do the encoding.</p>
*/
encode : true,
/**
},
/**
- * Final action of a write event. Apply the written data-object to params.
- * @param {Object} http params-object to write-to.
+ * <p>This method should not need to be called by application code, however it may be useful on occasion to
+ * override it, or augment it with an {@link Function#createInterceptor interceptor} or {@link Function#createSequence sequence}.</p>
+ * <p>The provided implementation encodes the serialized data representing the Store's modified Records into the Ajax request's
+ * <code>params</code> according to the <code>{@link #encode}</code> setting.</p>
+ * @param {Object} Ajax request params object to write into.
* @param {Object} baseParams as defined by {@link Ext.data.Store#baseParams}. The baseParms must be encoded by the extending class, eg: {@link Ext.data.JsonWriter}, {@link Ext.data.XmlWriter}.
- * @param {Object/Object[]} data Data-object representing compiled Store-recordset.
+ * @param {Object/Object[]} data Data object representing the serialized modified records from the Store. May be either a single object,
+ * or an Array of objects - user implementations must handle both cases.
*/
render : function(params, baseParams, data) {
if (this.encode === true) {
// constructor that provides mapping for reading the record data objects
{@link Ext.data.DataReader#fields fields}: [
// map Record's 'firstname' field to data object's key of same name
- {name: 'name'},
+ {name: 'name', mapping: 'firstname'},
// map Record's 'job' field to data object's 'occupation' key
{name: 'job', mapping: 'occupation'}
]
* </ul>
*/
// Encoding the ? here in case it's being included by some kind of page that will parse it (eg. PHP)
- tpl: '<tpl for="."><\u003fxml version="{version}" encoding="{encoding}"\u003f><tpl if="documentRoot"><{documentRoot}><tpl for="baseParams"><tpl for="."><{name}>{value}</{name}</tpl></tpl></tpl><tpl if="records.length>1"><{root}></tpl><tpl for="records"><{parent.record}><tpl for="."><{name}>{value}</{name}></tpl></{parent.record}></tpl><tpl if="records.length>1"></{root}></tpl><tpl if="documentRoot"></{documentRoot}></tpl></tpl>',
+ tpl: '<tpl for="."><\u003fxml version="{version}" encoding="{encoding}"\u003f><tpl if="documentRoot"><{documentRoot}><tpl for="baseParams"><tpl for="."><{name}>{value}</{name}></tpl></tpl></tpl><tpl if="records.length>1"><{root}></tpl><tpl for="records"><{parent.record}><tpl for="."><{name}>{value}</{name}></tpl></{parent.record}></tpl><tpl if="records.length>1"></{root}></tpl><tpl if="documentRoot"></{documentRoot}></tpl></tpl>',
/**
* @return {Ext.data.Response} An instance of {@link Ext.data.Response}
*/
readResponse : function(action, response) {
- var q = Ext.DomQuery,
- doc = response.responseXML;
+ var q = Ext.DomQuery,
+ doc = response.responseXML,
+ root = doc.documentElement || doc;
// create general Response instance.
var res = new Ext.data.Response({
action: action,
- success : this.getSuccess(doc),
- message: this.getMessage(doc),
- data: this.extractData(q.select(this.meta.record, doc) || q.select(this.meta.root, doc), false),
+ success : this.getSuccess(root),
+ message: this.getMessage(root),
+ data: this.extractData(q.select(this.meta.record, root) || q.select(this.meta.root, root), false),
raw: doc
});
createAccessor : function(){
var q = Ext.DomQuery;
return function(key) {
+ if (Ext.isFunction(key)) {
+ return key;
+ }
switch(key) {
case this.meta.totalProperty:
return function(root, def){
*/
groupOnSort:false,
+ /**
+ * @cfg {String} groupDir
+ * The direction to sort the groups. Defaults to <tt>'ASC'</tt>.
+ */
groupDir : 'ASC',
/**
//check the contents of the first sorter. If the field matches the CURRENT groupField (before it is set to the new one),
//remove the sorter as it is actually the grouper. The new grouper is added back in by this.sort
- sorters = this.multiSortInfo.sorters;
+ var sorters = this.multiSortInfo.sorters;
if (sorters.length > 0 && sorters[0].field == this.groupField) {
sorters.shift();
}
* executing.
* @param {Ext.direct.RemotingProvider} provider
* @param {Ext.Direct.Transaction} transaction
+ * @param {Object} meta The meta data
*/
'beforecall',
/**
* NOT fire after the response has come back from the call.
* @param {Ext.direct.RemotingProvider} provider
* @param {Ext.Direct.Transaction} transaction
+ * @param {Object} meta The meta data
*/
'call'
);
cb: scope && Ext.isFunction(hs) ? hs.createDelegate(scope) : hs
});
- if(this.fireEvent('beforecall', this, t) !== false){
+ if(this.fireEvent('beforecall', this, t, m) !== false){
Ext.Direct.addTransaction(t);
this.queueTransaction(t);
- this.fireEvent('call', this, t);
+ this.fireEvent('call', this, t, m);
}
},
isForm: true
});
- if(this.fireEvent('beforecall', this, t) !== false){
+ if(this.fireEvent('beforecall', this, t, m) !== false){
Ext.Direct.addTransaction(t);
var isUpload = String(form.getAttribute("enctype")).toLowerCase() == 'multipart/form-data',
params = {
isUpload: isUpload,
params: callback && Ext.isObject(callback.params) ? Ext.apply(params, callback.params) : params
});
- this.fireEvent('call', this, t);
+ this.fireEvent('call', this, t, m);
this.processForm(t);
}
},
* {@link #collapsed}) when displayed (defaults to true).
*/
expandOnShow : true,
+
+ /**
+ * @cfg {Number} showAnimDuration The number of seconds that the window show animation takes if enabled.
+ * Defaults to 0.25
+ */
+ showAnimDuration: 0.25,
+
+ /**
+ * @cfg {Number} hideAnimDuration The number of seconds that the window hide animation takes if enabled.
+ * Defaults to 0.25
+ */
+ hideAnimDuration: 0.25,
// inherited docs, same default
collapsible : false,
el = f.getEl();
ct = Ext.getDom(this.container);
if (el && ct) {
- if (!Ext.lib.Region.getRegion(ct).contains(Ext.lib.Region.getRegion(el.dom))){
+ if (ct != document.body && !Ext.lib.Region.getRegion(ct).contains(Ext.lib.Region.getRegion(el.dom))){
return;
}
}
callback: this.afterShow.createDelegate(this, [true], false),
scope: this,
easing: 'easeNone',
- duration: 0.25,
+ duration: this.showAnimDuration,
opacity: 0.5
}));
},
this.proxy.shift(Ext.apply(this.animateTarget.getBox(), {
callback: this.afterHide,
scope: this,
- duration: 0.25,
+ duration: this.hideAnimDuration,
easing: 'easeNone',
opacity: 0
}));
Ext.reg('window', Ext.Window);
// private - custom Window DD implementation
-Ext.Window.DD = function(win){
- this.win = win;
- Ext.Window.DD.superclass.constructor.call(this, win.el.id, 'WindowDD-'+win.id);
- this.setHandleElId(win.header.id);
- this.scroll = false;
-};
-
-Ext.extend(Ext.Window.DD, Ext.dd.DD, {
+Ext.Window.DD = Ext.extend(Ext.dd.DD, {
+
+ constructor : function(win){
+ this.win = win;
+ Ext.Window.DD.superclass.constructor.call(this, win.el.id, 'WindowDD-'+win.id);
+ this.setHandleElId(win.header.id);
+ this.scroll = false;
+ },
+
moveOnly:true,
headerOffsets:[100, 25],
startDrag : function(){
var w = this.win;
- this.proxy = w.ghost();
+ this.proxy = w.ghost(w.initialConfig.cls);
if(w.constrain !== false){
var so = w.el.shadowOffset;
this.constrainTo(w.container, {right: so, left: so, bottom: so});
if(!dlg.isVisible() && !opt.width){
dlg.setSize(this.maxWidth, 100); // resize first so content is never clipped from previous shows
}
- msgEl.update(text || ' ');
+ // Append a space here for sizing. In IE, for some reason, it wraps text incorrectly without one in some cases
+ msgEl.update(text ? text + ' ' : ' ');
var iw = iconCls != '' ? (iconEl.getWidth() + iconEl.getMargins('lr')) : 0,
mw = msgEl.getWidth() + msgEl.getMargins('lr'),
bw = dlg.body.getFrameWidth('lr'),
w;
- if (Ext.isIE && iw > 0){
- //3 pixels get subtracted in the icon CSS for an IE margin issue,
- //so we have to add it back here for the overall width to be consistent
- iw += 3;
- }
w = Math.max(Math.min(opt.width || iw+mw+fw+bw, opt.maxWidth || this.maxWidth),
Math.max(opt.minWidth || this.minWidth, bwidth || 0));
if(Ext.isIE && w == bwidth){
w += 4; //Add offset when the content width is smaller than the buttons.
}
+ msgEl.update(text || ' ');
dlg.setSize(w, 'auto').center();
return this;
},
d.focusEl = db;
}
}
- if(opt.iconCls){
+ if(Ext.isDefined(opt.iconCls)){
d.setIconClass(opt.iconCls);
}
this.setIcon(Ext.isDefined(opt.icon) ? opt.icon : bufferIcon);
* @param panel The {@link Ext.Panel} to proxy for
* @param config Configuration options
*/
-Ext.dd.PanelProxy = function(panel, config){
- this.panel = panel;
- this.id = this.panel.id +'-ddproxy';
- Ext.apply(this, config);
-};
-
-Ext.dd.PanelProxy.prototype = {
+Ext.dd.PanelProxy = Ext.extend(Object, {
+
+ constructor : function(panel, config){
+ this.panel = panel;
+ this.id = this.panel.id +'-ddproxy';
+ Ext.apply(this, config);
+ },
+
/**
* @cfg {Boolean} insertProxy True to insert a placeholder proxy element while dragging the panel,
* false to drag with no proxy (defaults to true).
*/
show : function(){
if(!this.ghost){
- this.ghost = this.panel.createGhost(undefined, undefined, Ext.getBody());
+ this.ghost = this.panel.createGhost(this.panel.initialConfig.cls, undefined, Ext.getBody());
this.ghost.setXY(this.panel.el.getXY());
if(this.insertProxy){
this.proxy = this.panel.el.insertSibling({cls:'x-panel-dd-spacer'});
parentNode.insertBefore(this.proxy.dom, before);
}
}
-};
+});
// private - DD implementation for Panels
-Ext.Panel.DD = function(panel, cfg){
- this.panel = panel;
- this.dragData = {panel: panel};
- this.proxy = new Ext.dd.PanelProxy(panel, cfg);
- Ext.Panel.DD.superclass.constructor.call(this, panel.el, cfg);
- var h = panel.header;
- if(h){
- this.setHandleElId(h.id);
- }
- (h ? h : this.panel.body).setStyle('cursor', 'move');
- this.scroll = false;
-};
-
-Ext.extend(Ext.Panel.DD, Ext.dd.DragSource, {
+Ext.Panel.DD = Ext.extend(Ext.dd.DragSource, {
+
+ constructor : function(panel, cfg){
+ this.panel = panel;
+ this.dragData = {panel: panel};
+ this.proxy = new Ext.dd.PanelProxy(panel, cfg);
+ Ext.Panel.DD.superclass.constructor.call(this, panel.el, cfg);
+ var h = panel.header,
+ el = panel.body;
+ if(h){
+ this.setHandleElId(h.id);
+ el = panel.header;
+ }
+ el.setStyle('cursor', 'move');
+ this.scroll = false;
+ },
+
showFrame: Ext.emptyFn,
startDrag: Ext.emptyFn,
b4StartDrag: function(x, y) {
this.proxy.show();
},
b4MouseDown: function(e) {
- var x = e.getPageX();
- var y = e.getPageY();
+ var x = e.getPageX(),
+ y = e.getPageY();
this.autoOffset(x, y);
},
onInitDrag : function(x, y){
* for encoding and decoding <b>typed</b> variables including dates and defines the
* Provider interface.
*/
-Ext.state.Provider = function(){
- /**
- * @event statechange
- * Fires when a state change occurs.
- * @param {Provider} this This state provider
- * @param {String} key The state key which was changed
- * @param {String} value The encoded value for the state
- */
- this.addEvents("statechange");
- this.state = {};
- Ext.state.Provider.superclass.constructor.call(this);
-};
-Ext.extend(Ext.state.Provider, Ext.util.Observable, {
+Ext.state.Provider = Ext.extend(Ext.util.Observable, {
+
+ constructor : function(){
+ /**
+ * @event statechange
+ * Fires when a state change occurs.
+ * @param {Provider} this This state provider
+ * @param {String} key The state key which was changed
+ * @param {String} value The encoded value for the state
+ */
+ this.addEvents("statechange");
+ this.state = {};
+ Ext.state.Provider.superclass.constructor.call(this);
+ },
+
/**
* Returns the current value for a key
* @param {String} name The key name
* @return {Mixed} The decoded value
*/
decodeValue : function(cookie){
- var re = /^(a|n|d|b|s|o)\:(.*)$/;
- var matches = re.exec(unescape(cookie));
- if(!matches || !matches[1]) return; // non state cookie
- var type = matches[1];
- var v = matches[2];
+ /**
+ * a -> Array
+ * n -> Number
+ * d -> Date
+ * b -> Boolean
+ * s -> String
+ * o -> Object
+ * -> Empty (null)
+ */
+ var re = /^(a|n|d|b|s|o|e)\:(.*)$/,
+ matches = re.exec(unescape(cookie)),
+ all,
+ type,
+ v,
+ kv;
+ if(!matches || !matches[1]){
+ return; // non state cookie
+ }
+ type = matches[1];
+ v = matches[2];
switch(type){
- case "n":
+ case 'e':
+ return null;
+ case 'n':
return parseFloat(v);
- case "d":
+ case 'd':
return new Date(Date.parse(v));
- case "b":
- return (v == "1");
- case "a":
- var all = [];
+ case 'b':
+ return (v == '1');
+ case 'a':
+ all = [];
if(v != ''){
Ext.each(v.split('^'), function(val){
all.push(this.decodeValue(val));
}, this);
}
return all;
- case "o":
- var all = {};
+ case 'o':
+ all = {};
if(v != ''){
Ext.each(v.split('^'), function(val){
- var kv = val.split('=');
+ kv = val.split('=');
all[kv[0]] = this.decodeValue(kv[1]);
}, this);
}
* @return {String} The encoded value
*/
encodeValue : function(v){
- var enc;
- if(typeof v == "number"){
- enc = "n:" + v;
- }else if(typeof v == "boolean"){
- enc = "b:" + (v ? "1" : "0");
+ var enc,
+ flat = '',
+ i = 0,
+ len,
+ key;
+ if(v == null){
+ return 'e:1';
+ }else if(typeof v == 'number'){
+ enc = 'n:' + v;
+ }else if(typeof v == 'boolean'){
+ enc = 'b:' + (v ? '1' : '0');
}else if(Ext.isDate(v)){
- enc = "d:" + v.toGMTString();
+ enc = 'd:' + v.toGMTString();
}else if(Ext.isArray(v)){
- var flat = "";
- for(var i = 0, len = v.length; i < len; i++){
+ for(len = v.length; i < len; i++){
flat += this.encodeValue(v[i]);
- if(i != len-1) flat += "^";
+ if(i != len - 1){
+ flat += '^';
+ }
}
- enc = "a:" + flat;
- }else if(typeof v == "object"){
- var flat = "";
- for(var key in v){
- if(typeof v[key] != "function" && v[key] !== undefined){
- flat += key + "=" + this.encodeValue(v[key]) + "^";
+ enc = 'a:' + flat;
+ }else if(typeof v == 'object'){
+ for(key in v){
+ if(typeof v[key] != 'function' && v[key] !== undefined){
+ flat += key + '=' + this.encodeValue(v[key]) + '^';
}
}
- enc = "o:" + flat.substring(0, flat.length-1);
+ enc = 'o:' + flat.substring(0, flat.length-1);
}else{
- enc = "s:" + v;
+ enc = 's:' + v;
}
return escape(enc);
}
* Create a new CookieProvider
* @param {Object} config The configuration object
*/
-Ext.state.CookieProvider = function(config){
- Ext.state.CookieProvider.superclass.constructor.call(this);
- this.path = "/";
- this.expires = new Date(new Date().getTime()+(1000*60*60*24*7)); //7 days
- this.domain = null;
- this.secure = false;
- Ext.apply(this, config);
- this.state = this.readCookies();
-};
-
-Ext.extend(Ext.state.CookieProvider, Ext.state.Provider, {
+Ext.state.CookieProvider = Ext.extend(Ext.state.Provider, {
+
+ constructor : function(config){
+ Ext.state.CookieProvider.superclass.constructor.call(this);
+ this.path = "/";
+ this.expires = new Date(new Date().getTime()+(1000*60*60*24*7)); //7 days
+ this.domain = null;
+ this.secure = false;
+ Ext.apply(this, config);
+ this.state = this.readCookies();
+ },
+
// private
set : function(name, value){
if(typeof value == "undefined" || value === null){
// private
readCookies : function(){
- var cookies = {};
- var c = document.cookie + ";";
- var re = /\s?(.*?)=(.*?);/g;
- var matches;
+ var cookies = {},
+ c = document.cookie + ";",
+ re = /\s?(.*?)=(.*?);/g,
+ matches,
+ name,
+ value;
while((matches = re.exec(c)) != null){
- var name = matches[1];
- var value = matches[2];
+ name = matches[1];
+ value = matches[2];
if(name && name.substring(0,3) == "ys-"){
cookies[name.substr(3)] = this.decodeValue(value);
}
*/
refresh : function() {
this.clearSelections(false, true);
- var el = this.getTemplateTarget();
- el.update("");
- var records = this.store.getRange();
+ var el = this.getTemplateTarget(),
+ records = this.store.getRange();
+
+ el.update('');
if(records.length < 1){
if(!this.deferEmptyText || this.hasSkippedEmptyText){
el.update(this.emptyText);
* contain <i>named</i> properties.
*/
collectData : function(records, startIndex){
- var r = [];
- for(var i = 0, len = records.length; i < len; i++){
- r[r.length] = this.prepareData(records[i].data, startIndex+i, records[i]);
+ var r = [],
+ i = 0,
+ len = records.length;
+ for(; i < len; i++){
+ r[r.length] = this.prepareData(records[i].data, startIndex + i, records[i]);
}
return r;
},
// private
- bufferRender : function(records){
+ bufferRender : function(records, index){
var div = document.createElement('div');
- this.tpl.overwrite(div, this.collectData(records));
+ this.tpl.overwrite(div, this.collectData(records, index));
return Ext.query(this.itemSelector, div);
},
onUpdate : function(ds, record){
var index = this.store.indexOf(record);
if(index > -1){
- var sel = this.isSelected(index);
- var original = this.all.elements[index];
- var node = this.bufferRender([record], index)[0];
+ var sel = this.isSelected(index),
+ original = this.all.elements[index],
+ node = this.bufferRender([record], index)[0];
this.all.replaceElement(index, node, true);
if(sel){
// private
onClick : function(e){
- var item = e.getTarget(this.itemSelector, this.getTemplateTarget());
+ var item = e.getTarget(this.itemSelector, this.getTemplateTarget()),
+ index;
if(item){
- var index = this.indexOf(item);
+ index = this.indexOf(item);
if(this.onItemClick(item, index, e) !== false){
this.fireEvent("click", this, index, item, e);
}
* @return {Array} An array of numeric indexes
*/
getSelectedIndexes : function(){
- var indexes = [], s = this.selected.elements;
- for(var i = 0, len = s.length; i < len; i++){
- indexes.push(s[i].viewIndex);
+ var indexes = [],
+ selected = this.selected.elements,
+ i = 0,
+ len = selected.length;
+
+ for(; i < len; i++){
+ indexes.push(selected[i].viewIndex);
}
return indexes;
},
* @return {Array} An array of {@link Ext.data.Record} objects
*/
getSelectedRecords : function(){
- var r = [], s = this.selected.elements;
- for(var i = 0, len = s.length; i < len; i++){
- r[r.length] = this.store.getAt(s[i].viewIndex);
- }
- return r;
+ return this.getRecords(this.selected.elements);
},
/**
* @return {Array} records The {@link Ext.data.Record} objects
*/
getRecords : function(nodes){
- var r = [], s = nodes;
- for(var i = 0, len = s.length; i < len; i++){
- r[r.length] = this.store.getAt(s[i].viewIndex);
+ var records = [],
+ i = 0,
+ len = nodes.length;
+
+ for(; i < len; i++){
+ records[records.length] = this.store.getAt(nodes[i].viewIndex);
}
- return r;
+ return records;
},
/**
* @return {Array} An array of nodes
*/
getNodes : function(start, end){
- var ns = this.all.elements;
+ var ns = this.all.elements,
+ nodes = [],
+ i;
+
start = start || 0;
end = !Ext.isDefined(end) ? Math.max(ns.length - 1, 0) : end;
- var nodes = [], i;
if(start <= end){
for(i = start; i <= end && ns[i]; i++){
nodes.push(ns[i]);
/*
* IE has issues when setting percentage based widths to 100%. Default to 99.
*/
- maxWidth: Ext.isIE ? 99 : 100,
+ maxColumnWidth: Ext.isIE ? 99 : 100,
initComponent : function(){
if(this.columnResize){
}
if(c.width) {
allocatedWidth += c.width*100;
+ if(allocatedWidth > this.maxColumnWidth){
+ c.width -= (allocatedWidth - this.maxColumnWidth) / 100;
+ }
colsWithWidth++;
}
columns.push(c);
// auto calculate missing column widths
if(colsWithWidth < len){
var remaining = len - colsWithWidth;
- if(allocatedWidth < this.maxWidth){
- var perCol = ((this.maxWidth-allocatedWidth) / remaining)/100;
+ if(allocatedWidth < this.maxColumnWidth){
+ var perCol = ((this.maxColumnWidth-allocatedWidth) / remaining)/100;
for(var j = 0; j < len; j++){
var c = cs[j];
if(!c.width){
return {
columns: this.columns,
rows: rs
- }
+ };
},
verifyInternalSize : function(){
// private
onResize : function(w, h){
- var bd = this.innerBody.dom;
- var hd = this.innerHd.dom;
- if(!bd){
+ var body = this.innerBody.dom,
+ header = this.innerHd.dom,
+ scrollWidth = w - Ext.num(this.scrollOffset, Ext.getScrollBarWidth()) + 'px',
+ parentNode;
+
+ if(!body){
return;
}
- var bdp = bd.parentNode;
+ parentNode = body.parentNode;
if(Ext.isNumber(w)){
- var sw = w - Ext.num(this.scrollOffset, Ext.getScrollBarWidth());
- if(this.reserveScrollOffset || ((bdp.offsetWidth - bdp.clientWidth) > 10)){
- bd.style.width = sw + 'px';
- hd.style.width = sw + 'px';
+ if(this.reserveScrollOffset || ((parentNode.offsetWidth - parentNode.clientWidth) > 10)){
+ body.style.width = scrollWidth;
+ header.style.width = scrollWidth;
}else{
- bd.style.width = w + 'px';
- hd.style.width = w + 'px';
+ body.style.width = w + 'px';
+ header.style.width = w + 'px';
setTimeout(function(){
- if((bdp.offsetWidth - bdp.clientWidth) > 10){
- bd.style.width = sw + 'px';
- hd.style.width = sw + 'px';
+ if((parentNode.offsetWidth - parentNode.clientWidth) > 10){
+ body.style.width = scrollWidth;
+ header.style.width = scrollWidth;
}
}, 10);
}
}
if(Ext.isNumber(h)){
- bdp.style.height = (h - hd.parentNode.offsetHeight) + 'px';
+ parentNode.style.height = Math.max(0, h - header.parentNode.offsetHeight) + 'px';
}
},
this.verifyInternalSize();
},
- findHeaderIndex : function(hd){
- hd = hd.dom || hd;
- var pn = hd.parentNode, cs = pn.parentNode.childNodes;
- for(var i = 0, c; c = cs[i]; i++){
- if(c == pn){
+ findHeaderIndex : function(header){
+ header = header.dom || header;
+ var parentNode = header.parentNode,
+ children = parentNode.parentNode.childNodes,
+ i = 0,
+ c;
+ for(; c = children[i]; i++){
+ if(c == parentNode){
return i;
}
}
},
setHdWidths : function(){
- var els = this.innerHd.dom.getElementsByTagName('div');
- for(var i = 0, cs = this.columns, len = cs.length; i < len; i++){
- els[i].style.width = (cs[i].width*100) + '%';
+ var els = this.innerHd.dom.getElementsByTagName('div'),
+ i = 0,
+ columns = this.columns,
+ len = columns.length;
+
+ for(; i < len; i++){
+ els[i].style.width = (columns[i].width*100) + '%';
}
}
});
},
handleHdMove : function(e, t){
- var hw = 5,
+ var handleWidth = 5,
x = e.getPageX(),
- hd = e.getTarget('em', 3, true);
- if(hd){
- var r = hd.getRegion(),
- ss = hd.dom.style,
- pn = hd.dom.parentNode;
-
- if(x - r.left <= hw && pn != pn.parentNode.firstChild){
- this.activeHd = Ext.get(pn.previousSibling.firstChild);
- ss.cursor = Ext.isWebKit ? 'e-resize' : 'col-resize';
- } else if(r.right - x <= hw && pn != pn.parentNode.lastChild.previousSibling){
- this.activeHd = hd;
- ss.cursor = Ext.isWebKit ? 'w-resize' : 'col-resize';
+ header = e.getTarget('em', 3, true);
+ if(header){
+ var region = header.getRegion(),
+ style = header.dom.style,
+ parentNode = header.dom.parentNode;
+
+ if(x - region.left <= handleWidth && parentNode != parentNode.parentNode.firstChild){
+ this.activeHd = Ext.get(parentNode.previousSibling.firstChild);
+ style.cursor = Ext.isWebKit ? 'e-resize' : 'col-resize';
+ } else if(region.right - x <= handleWidth && parentNode != parentNode.parentNode.lastChild.previousSibling){
+ this.activeHd = header;
+ style.cursor = Ext.isWebKit ? 'w-resize' : 'col-resize';
} else{
delete this.activeHd;
- ss.cursor = '';
+ style.cursor = '';
}
}
},
},
onStart: function(e){
- this.view.disableHeaders = true;
- this.proxy = this.view.el.createChild({cls:'x-list-resizer'});
- this.proxy.setHeight(this.view.el.getHeight());
-
- var x = this.tracker.getXY()[0],
- w = this.view.innerHd.getWidth();
-
- this.hdX = this.dragHd.getX();
- this.hdIndex = this.view.findHeaderIndex(this.dragHd);
-
- this.proxy.setX(this.hdX);
- this.proxy.setWidth(x-this.hdX);
-
- this.minWidth = w*this.minPct;
- this.maxWidth = w - (this.minWidth*(this.view.columns.length-1-this.hdIndex));
+
+ var me = this,
+ view = me.view,
+ dragHeader = me.dragHd,
+ x = me.tracker.getXY()[0];
+
+ me.proxy = view.el.createChild({cls:'x-list-resizer'});
+ me.dragX = dragHeader.getX();
+ me.headerIndex = view.findHeaderIndex(dragHeader);
+
+ me.headersDisabled = view.disableHeaders;
+ view.disableHeaders = true;
+
+ me.proxy.setHeight(view.el.getHeight());
+ me.proxy.setX(me.dragX);
+ me.proxy.setWidth(x - me.dragX);
+
+ this.setBoundaries();
+
+ },
+
+ // Sets up the boundaries for the drag/drop operation
+ setBoundaries: function(relativeX){
+ var view = this.view,
+ headerIndex = this.headerIndex,
+ width = view.innerHd.getWidth(),
+ relativeX = view.innerHd.getX(),
+ minWidth = Math.ceil(width * this.minPct),
+ maxWidth = width - minWidth,
+ numColumns = view.columns.length,
+ headers = view.innerHd.select('em', true),
+ minX = minWidth + relativeX,
+ maxX = maxWidth + relativeX,
+ header;
+
+ if (numColumns == 2) {
+ this.minX = minX;
+ this.maxX = maxX;
+ }else{
+ header = headers.item(headerIndex + 2);
+ this.minX = headers.item(headerIndex).getX() + minWidth;
+ this.maxX = header ? header.getX() - minWidth : maxX;
+ if (headerIndex == 0) {
+ // First
+ this.minX = minX;
+ } else if (headerIndex == numColumns - 2) {
+ // Last
+ this.maxX = maxX;
+ }
+ }
},
onDrag: function(e){
- var cursorX = this.tracker.getXY()[0];
- this.proxy.setWidth((cursorX-this.hdX).constrain(this.minWidth, this.maxWidth));
+ var me = this,
+ cursorX = me.tracker.getXY()[0].constrain(me.minX, me.maxX);
+
+ me.proxy.setWidth(cursorX - this.dragX);
},
onEnd: function(e){
/* calculate desired width by measuring proxy and then remove it */
- var nw = this.proxy.getWidth();
+ var newWidth = this.proxy.getWidth(),
+ index = this.headerIndex,
+ view = this.view,
+ columns = view.columns,
+ width = view.innerHd.getWidth(),
+ newPercent = Math.ceil(newWidth * view.maxColumnWidth / width) / 100,
+ disabled = this.headersDisabled,
+ headerCol = columns[index],
+ otherCol = columns[index + 1],
+ totalPercent = headerCol.width + otherCol.width;
+
this.proxy.remove();
- var index = this.hdIndex,
- vw = this.view,
- cs = vw.columns,
- len = cs.length,
- w = this.view.innerHd.getWidth(),
- minPct = this.minPct * 100,
- pct = Math.ceil((nw * vw.maxWidth) / w),
- diff = (cs[index].width * 100) - pct,
- eachItem = Math.floor(diff / (len-1-index)),
- mod = diff - (eachItem * (len-1-index));
-
- for(var i = index+1; i < len; i++){
- var cw = (cs[i].width * 100) + eachItem,
- ncw = Math.max(minPct, cw);
- if(cw != ncw){
- mod += cw - ncw;
- }
- cs[i].width = ncw / 100;
- }
- cs[index].width = pct / 100;
- cs[index+1].width += (mod / 100);
+ headerCol.width = newPercent;
+ otherCol.width = totalPercent - newPercent;
+
delete this.dragHd;
- vw.setHdWidths();
- vw.refresh();
+ view.setHdWidths();
+ view.refresh();
+
setTimeout(function(){
- vw.disableHeaders = false;
+ view.disableHeaders = disabled;
}, 100);
}
});
// private
delegateUpdates : function(){
+ var rendered = this.rendered;
if(this.suspendUpdates){
return;
}
- if(this.resizeTabs && this.rendered){
+ if(this.resizeTabs && rendered){
this.autoSizeTabs();
}
- if(this.enableTabScroll && this.rendered){
+ if(this.enableTabScroll && rendered){
this.autoScrollTabs();
}
},
this.stack.add(item);
this.layout.setActiveItem(item);
+ // Need to do this here, since setting the active tab slightly changes the size
+ this.delegateUpdates();
if(this.scrolling){
this.scrollToTab(item, this.animScroll);
}
pos = this.getScrollPos(),
l = this.edge.getOffsetsTo(this.stripWrap)[0] + pos;
- if(!this.enableTabScroll || count < 1 || cw < 20){ // 20 to prevent display:none issues
+ if(!this.enableTabScroll || cw < 20){ // 20 to prevent display:none issues
return;
}
- if(l <= tw){
+ if(count == 0 || l <= tw){
+ // ensure the width is set if there's no tabs
wd.scrollLeft = 0;
wrap.setWidth(tw);
if(this.scrolling){
*/
initComponent : function(){
+ if(this.menu){
+ this.menu = Ext.menu.MenuMgr.get(this.menu);
+ this.menu.ownerCt = this;
+ }
+
Ext.Button.superclass.initComponent.call(this);
this.addEvents(
*/
'menutriggerout'
);
- if(this.menu){
- this.menu = Ext.menu.MenuMgr.get(this.menu);
+
+ if (this.menu){
+ this.menu.ownerCt = undefined;
}
if(Ext.isString(this.toggleGroup)){
this.enableToggle = true;
if(this.repeat){
var repeater = new Ext.util.ClickRepeater(btn, Ext.isObject(this.repeat) ? this.repeat : {});
- this.mon(repeater, 'click', this.onClick, this);
+ this.mon(repeater, 'click', this.onRepeatClick, this);
+ }else{
+ this.mon(btn, this.clickEvent, this.onClick, this);
}
- this.mon(btn, this.clickEvent, this.onClick, this);
},
// private
this.clearTip();
}
if(this.menu && this.destroyMenu !== false) {
- Ext.destroy(this.menu);
+ Ext.destroy(this.btnEl, this.menu);
}
Ext.destroy(this.repeater);
},
hasVisibleMenu : function(){
return this.menu && this.menu.ownerCt == this && this.menu.isVisible();
},
+
+ // private
+ onRepeatClick : function(repeat, e){
+ this.onClick(e);
+ },
// private
onClick : function(e){
return;
}
if(!this.disabled){
- if(this.enableToggle && (this.allowDepress !== false || !this.pressed)){
- this.toggle();
- }
+ this.doToggle();
if(this.menu && !this.hasVisibleMenu() && !this.ignoreNextClick){
this.showMenu();
}
}
}
},
+
+ // private
+ doToggle: function(){
+ if (this.enableToggle && (this.allowDepress !== false || !this.pressed)) {
+ this.toggle();
+ }
+ },
// private
isMenuTriggerOver : function(e, internal){
this.arrowHandler.call(this.scope || this, this, e);
}
}else{
- if(this.enableToggle){
- this.toggle();
- }
+ this.doToggle();
this.fireEvent("click", this, e);
if(this.handler){
this.handler.call(this.scope || this, this, e);
/**
* @cfg {Boolean} enableOverflow
- * Defaults to false. Configure <code>true<code> to make the toolbar provide a button
+ * Defaults to false. Configure <tt>true</tt> to make the toolbar provide a button
* which activates a dropdown Menu to show items which overflow the Toolbar's width.
*/
/**
// private
onRemove : function(c){
Ext.Toolbar.superclass.onRemove.call(this);
+ if (c == this.activeMenuBtn) {
+ delete this.activeMenuBtn;
+ }
this.trackMenu(c, true);
},
var currentToken;
function getHash() {
- var href = top.location.href, i = href.indexOf("#");
+ var href = location.href, i = href.indexOf("#");
return i >= 0 ? href.substr(i + 1) : null;
}
// private
afterRender : function(){
Ext.ToolTip.superclass.afterRender.call(this);
- this.anchorEl.setStyle('z-index', this.el.getZIndex() + 1);
+ this.anchorEl.setStyle('z-index', this.el.getZIndex() + 1).setVisibilityMode(Ext.Element.DISPLAY);
},
/**
this.showAt(this.getTargetXY());
if(this.anchor){
- this.syncAnchor();
this.anchorEl.show();
+ this.syncAnchor();
this.constrainPosition = this.origConstrainPosition;
}else{
this.anchorEl.hide();
if(this.anchor && !this.anchorEl.isVisible()){
this.syncAnchor();
this.anchorEl.show();
+ }else{
+ this.anchorEl.hide();
}
},
* configuration properties of Ext.QuickTip. These settings will apply to all
* tooltips shown by the singleton.</p>
* <p>Below is the summary of the configuration properties which can be used.
- * For detailed descriptions see {@link #getQuickTip}</p>
+ * For detailed descriptions see the config options for the {@link Ext.QuickTip QuickTip} class</p>
* <p><b>QuickTips singleton configs (all are optional)</b></p>
* <div class="mdetail-params"><ul><li>dismissDelay</li>
* <li>hideDelay</li>
Ext.apply(Ext.QuickTips.getQuickTip(), {
maxWidth: 200,
minWidth: 100,
- showDelay: 50,
+ showDelay: 50, // Show 50ms after entering target
trackMouse: true
});
title: 'My Tooltip',
text: 'This tooltip was added in code',
width: 100,
- dismissDelay: 20
+ dismissDelay: 10000 // Hide after 10 seconds hover
});
</code></pre>
* <p>To register a quick tip in markup, you simply add one or more of the valid QuickTip attributes prefixed with
* @singleton
*/
Ext.QuickTips = function(){
- var tip, locks = [];
+ var tip,
+ disabled = false;
+
return {
/**
* Initialize the global QuickTips instance and prepare any quick tips.
});
return;
}
- tip = new Ext.QuickTip({elements:'header,body'});
+ tip = new Ext.QuickTip({
+ elements:'header,body',
+ disabled: disabled
+ });
if(autoRender !== false){
tip.render(Ext.getBody());
}
}
},
+
+ // Protected method called by the dd classes
+ ddDisable : function(){
+ // don't disable it if we don't need to
+ if(tip && !disabled){
+ tip.disable();
+ }
+ },
+
+ // Protected method called by the dd classes
+ ddEnable : function(){
+ // only enable it if it hasn't been disabled
+ if(tip && !disabled){
+ tip.enable();
+ }
+ },
/**
* Enable quick tips globally.
*/
enable : function(){
if(tip){
- locks.pop();
- if(locks.length < 1){
- tip.enable();
- }
+ tip.enable();
}
+ disabled = false;
},
/**
if(tip){
tip.disable();
}
- locks.push(1);
+ disabled = true;
},
/**
},
/**
- * Gets the global QuickTips instance.
+ * Gets the single {@link Ext.QuickTip QuickTip} instance used to show tips from all registered elements.
+ * @return {Ext.QuickTip}
*/
getQuickTip : function(){
return tip;
* Alias of {@link #register}.
* @param {Object} config The config object
*/
- tips :function(){
+ tips : function(){
tip.register.apply(tip, arguments);
}
- }
+ };
}();/**
* @class Ext.slider.Tip
* @extends Ext.Tip
* (bSuccess, oLastNode) where bSuccess is if the expand was successful and oLastNode is the last node that was expanded.
*/
expandPath : function(path, attr, callback){
+ if(Ext.isEmpty(path)){
+ if(callback){
+ callback(false, undefined);
+ }
+ return;
+ }
attr = attr || 'id';
var keys = path.split(this.pathSeparator);
var curNode = this.root;
* (bSuccess, oSelNode) where bSuccess is if the selection was successful and oSelNode is the selected node.
*/
selectPath : function(path, attr, callback){
+ if(Ext.isEmpty(path)){
+ if(callback){
+ callback(false, undefined);
+ }
+ return;
+ }
attr = attr || 'id';
var keys = path.split(this.pathSeparator),
v = keys.pop();
* @constructor
* @param {Node} root (optional) The root node
*/
-Ext.data.Tree = function(root){
- this.nodeHash = {};
- /**
- * The root node for this tree
- * @type Node
- */
- this.root = null;
- if(root){
- this.setRootNode(root);
- }
- this.addEvents(
- /**
- * @event append
- * Fires when a new child node is appended to a node in this tree.
- * @param {Tree} tree The owner tree
- * @param {Node} parent The parent node
- * @param {Node} node The newly appended node
- * @param {Number} index The index of the newly appended node
- */
- "append",
- /**
- * @event remove
- * Fires when a child node is removed from a node in this tree.
- * @param {Tree} tree The owner tree
- * @param {Node} parent The parent node
- * @param {Node} node The child node removed
- */
- "remove",
- /**
- * @event move
- * Fires when a node is moved to a new location in the tree
- * @param {Tree} tree The owner tree
- * @param {Node} node The node moved
- * @param {Node} oldParent The old parent of this node
- * @param {Node} newParent The new parent of this node
- * @param {Number} index The index it was moved to
- */
- "move",
- /**
- * @event insert
- * Fires when a new child node is inserted in a node in this tree.
- * @param {Tree} tree The owner tree
- * @param {Node} parent The parent node
- * @param {Node} node The child node inserted
- * @param {Node} refNode The child node the node was inserted before
- */
- "insert",
- /**
- * @event beforeappend
- * Fires before a new child is appended to a node in this tree, return false to cancel the append.
- * @param {Tree} tree The owner tree
- * @param {Node} parent The parent node
- * @param {Node} node The child node to be appended
- */
- "beforeappend",
- /**
- * @event beforeremove
- * Fires before a child is removed from a node in this tree, return false to cancel the remove.
- * @param {Tree} tree The owner tree
- * @param {Node} parent The parent node
- * @param {Node} node The child node to be removed
- */
- "beforeremove",
- /**
- * @event beforemove
- * Fires before a node is moved to a new location in the tree. Return false to cancel the move.
- * @param {Tree} tree The owner tree
- * @param {Node} node The node being moved
- * @param {Node} oldParent The parent of the node
- * @param {Node} newParent The new parent the node is moving to
- * @param {Number} index The index it is being moved to
- */
- "beforemove",
- /**
- * @event beforeinsert
- * Fires before a new child is inserted in a node in this tree, return false to cancel the insert.
- * @param {Tree} tree The owner tree
- * @param {Node} parent The parent node
- * @param {Node} node The child node to be inserted
- * @param {Node} refNode The child node the node is being inserted before
- */
- "beforeinsert"
- );
-
- Ext.data.Tree.superclass.constructor.call(this);
-};
-
-Ext.extend(Ext.data.Tree, Ext.util.Observable, {
+Ext.data.Tree = Ext.extend(Ext.util.Observable, {
+
+ constructor: function(root){
+ this.nodeHash = {};
+ /**
+ * The root node for this tree
+ * @type Node
+ */
+ this.root = null;
+ if(root){
+ this.setRootNode(root);
+ }
+ this.addEvents(
+ /**
+ * @event append
+ * Fires when a new child node is appended to a node in this tree.
+ * @param {Tree} tree The owner tree
+ * @param {Node} parent The parent node
+ * @param {Node} node The newly appended node
+ * @param {Number} index The index of the newly appended node
+ */
+ "append",
+ /**
+ * @event remove
+ * Fires when a child node is removed from a node in this tree.
+ * @param {Tree} tree The owner tree
+ * @param {Node} parent The parent node
+ * @param {Node} node The child node removed
+ */
+ "remove",
+ /**
+ * @event move
+ * Fires when a node is moved to a new location in the tree
+ * @param {Tree} tree The owner tree
+ * @param {Node} node The node moved
+ * @param {Node} oldParent The old parent of this node
+ * @param {Node} newParent The new parent of this node
+ * @param {Number} index The index it was moved to
+ */
+ "move",
+ /**
+ * @event insert
+ * Fires when a new child node is inserted in a node in this tree.
+ * @param {Tree} tree The owner tree
+ * @param {Node} parent The parent node
+ * @param {Node} node The child node inserted
+ * @param {Node} refNode The child node the node was inserted before
+ */
+ "insert",
+ /**
+ * @event beforeappend
+ * Fires before a new child is appended to a node in this tree, return false to cancel the append.
+ * @param {Tree} tree The owner tree
+ * @param {Node} parent The parent node
+ * @param {Node} node The child node to be appended
+ */
+ "beforeappend",
+ /**
+ * @event beforeremove
+ * Fires before a child is removed from a node in this tree, return false to cancel the remove.
+ * @param {Tree} tree The owner tree
+ * @param {Node} parent The parent node
+ * @param {Node} node The child node to be removed
+ */
+ "beforeremove",
+ /**
+ * @event beforemove
+ * Fires before a node is moved to a new location in the tree. Return false to cancel the move.
+ * @param {Tree} tree The owner tree
+ * @param {Node} node The node being moved
+ * @param {Node} oldParent The parent of the node
+ * @param {Node} newParent The new parent the node is moving to
+ * @param {Number} index The index it is being moved to
+ */
+ "beforemove",
+ /**
+ * @event beforeinsert
+ * Fires before a new child is inserted in a node in this tree, return false to cancel the insert.
+ * @param {Tree} tree The owner tree
+ * @param {Node} parent The parent node
+ * @param {Node} node The child node to be inserted
+ * @param {Node} refNode The child node the node is being inserted before
+ */
+ "beforeinsert"
+ );
+ Ext.data.Tree.superclass.constructor.call(this);
+ },
+
/**
* @cfg {String} pathSeparator
* The token used to separate paths in node ids (defaults to '/').
* @constructor
* @param {Object} attributes The attributes/config for the node
*/
-Ext.data.Node = function(attributes){
- /**
- * The attributes supplied for the node. You can use this property to access any custom attributes you supplied.
- * @type {Object}
- */
- this.attributes = attributes || {};
- this.leaf = this.attributes.leaf;
- /**
- * The node id. @type String
- */
- this.id = this.attributes.id;
- if(!this.id){
- this.id = Ext.id(null, "xnode-");
- this.attributes.id = this.id;
- }
- /**
- * All child nodes of this node. @type Array
- */
- this.childNodes = [];
- if(!this.childNodes.indexOf){ // indexOf is a must
- this.childNodes.indexOf = function(o){
- for(var i = 0, len = this.length; i < len; i++){
- if(this[i] == o){
- return i;
- }
- }
- return -1;
- };
- }
- /**
- * The parent node for this node. @type Node
- */
- this.parentNode = null;
- /**
- * The first direct child node of this node, or null if this node has no child nodes. @type Node
- */
- this.firstChild = null;
- /**
- * The last direct child node of this node, or null if this node has no child nodes. @type Node
- */
- this.lastChild = null;
- /**
- * The node immediately preceding this node in the tree, or null if there is no sibling node. @type Node
- */
- this.previousSibling = null;
- /**
- * The node immediately following this node in the tree, or null if there is no sibling node. @type Node
- */
- this.nextSibling = null;
-
- this.addEvents({
- /**
- * @event append
- * Fires when a new child node is appended
- * @param {Tree} tree The owner tree
- * @param {Node} this This node
- * @param {Node} node The newly appended node
- * @param {Number} index The index of the newly appended node
- */
- "append" : true,
- /**
- * @event remove
- * Fires when a child node is removed
- * @param {Tree} tree The owner tree
- * @param {Node} this This node
- * @param {Node} node The removed node
- */
- "remove" : true,
- /**
- * @event move
- * Fires when this node is moved to a new location in the tree
- * @param {Tree} tree The owner tree
- * @param {Node} this This node
- * @param {Node} oldParent The old parent of this node
- * @param {Node} newParent The new parent of this node
- * @param {Number} index The index it was moved to
- */
- "move" : true,
- /**
- * @event insert
- * Fires when a new child node is inserted.
- * @param {Tree} tree The owner tree
- * @param {Node} this This node
- * @param {Node} node The child node inserted
- * @param {Node} refNode The child node the node was inserted before
- */
- "insert" : true,
- /**
- * @event beforeappend
- * Fires before a new child is appended, return false to cancel the append.
- * @param {Tree} tree The owner tree
- * @param {Node} this This node
- * @param {Node} node The child node to be appended
- */
- "beforeappend" : true,
- /**
- * @event beforeremove
- * Fires before a child is removed, return false to cancel the remove.
- * @param {Tree} tree The owner tree
- * @param {Node} this This node
- * @param {Node} node The child node to be removed
- */
- "beforeremove" : true,
- /**
- * @event beforemove
- * Fires before this node is moved to a new location in the tree. Return false to cancel the move.
- * @param {Tree} tree The owner tree
- * @param {Node} this This node
- * @param {Node} oldParent The parent of this node
- * @param {Node} newParent The new parent this node is moving to
- * @param {Number} index The index it is being moved to
- */
- "beforemove" : true,
- /**
- * @event beforeinsert
- * Fires before a new child is inserted, return false to cancel the insert.
- * @param {Tree} tree The owner tree
- * @param {Node} this This node
- * @param {Node} node The child node to be inserted
- * @param {Node} refNode The child node the node is being inserted before
- */
- "beforeinsert" : true
- });
- this.listeners = this.attributes.listeners;
- Ext.data.Node.superclass.constructor.call(this);
-};
+Ext.data.Node = Ext.extend(Ext.util.Observable, {
+
+ constructor: function(attributes){
+ /**
+ * The attributes supplied for the node. You can use this property to access any custom attributes you supplied.
+ * @type {Object}
+ */
+ this.attributes = attributes || {};
+ this.leaf = this.attributes.leaf;
+ /**
+ * The node id. @type String
+ */
+ this.id = this.attributes.id;
+ if(!this.id){
+ this.id = Ext.id(null, "xnode-");
+ this.attributes.id = this.id;
+ }
+ /**
+ * All child nodes of this node. @type Array
+ */
+ this.childNodes = [];
+ /**
+ * The parent node for this node. @type Node
+ */
+ this.parentNode = null;
+ /**
+ * The first direct child node of this node, or null if this node has no child nodes. @type Node
+ */
+ this.firstChild = null;
+ /**
+ * The last direct child node of this node, or null if this node has no child nodes. @type Node
+ */
+ this.lastChild = null;
+ /**
+ * The node immediately preceding this node in the tree, or null if there is no sibling node. @type Node
+ */
+ this.previousSibling = null;
+ /**
+ * The node immediately following this node in the tree, or null if there is no sibling node. @type Node
+ */
+ this.nextSibling = null;
-Ext.extend(Ext.data.Node, Ext.util.Observable, {
+ this.addEvents({
+ /**
+ * @event append
+ * Fires when a new child node is appended
+ * @param {Tree} tree The owner tree
+ * @param {Node} this This node
+ * @param {Node} node The newly appended node
+ * @param {Number} index The index of the newly appended node
+ */
+ "append" : true,
+ /**
+ * @event remove
+ * Fires when a child node is removed
+ * @param {Tree} tree The owner tree
+ * @param {Node} this This node
+ * @param {Node} node The removed node
+ */
+ "remove" : true,
+ /**
+ * @event move
+ * Fires when this node is moved to a new location in the tree
+ * @param {Tree} tree The owner tree
+ * @param {Node} this This node
+ * @param {Node} oldParent The old parent of this node
+ * @param {Node} newParent The new parent of this node
+ * @param {Number} index The index it was moved to
+ */
+ "move" : true,
+ /**
+ * @event insert
+ * Fires when a new child node is inserted.
+ * @param {Tree} tree The owner tree
+ * @param {Node} this This node
+ * @param {Node} node The child node inserted
+ * @param {Node} refNode The child node the node was inserted before
+ */
+ "insert" : true,
+ /**
+ * @event beforeappend
+ * Fires before a new child is appended, return false to cancel the append.
+ * @param {Tree} tree The owner tree
+ * @param {Node} this This node
+ * @param {Node} node The child node to be appended
+ */
+ "beforeappend" : true,
+ /**
+ * @event beforeremove
+ * Fires before a child is removed, return false to cancel the remove.
+ * @param {Tree} tree The owner tree
+ * @param {Node} this This node
+ * @param {Node} node The child node to be removed
+ */
+ "beforeremove" : true,
+ /**
+ * @event beforemove
+ * Fires before this node is moved to a new location in the tree. Return false to cancel the move.
+ * @param {Tree} tree The owner tree
+ * @param {Node} this This node
+ * @param {Node} oldParent The parent of this node
+ * @param {Node} newParent The new parent this node is moving to
+ * @param {Number} index The index it is being moved to
+ */
+ "beforemove" : true,
+ /**
+ * @event beforeinsert
+ * Fires before a new child is inserted, return false to cancel the insert.
+ * @param {Tree} tree The owner tree
+ * @param {Node} this This node
+ * @param {Node} node The child node to be inserted
+ * @param {Node} refNode The child node the node is being inserted before
+ */
+ "beforeinsert" : true
+ });
+ this.listeners = this.attributes.listeners;
+ Ext.data.Node.superclass.constructor.call(this);
+ },
+
// private
fireEvent : function(evtName){
// first do standard event for this node
eachChild : function(fn, scope, args){
var cs = this.childNodes;
for(var i = 0, len = cs.length; i < len; i++) {
- if(fn.apply(scope || this, args || [cs[i]]) === false){
+ if(fn.apply(scope || cs[i], args || [cs[i]]) === false){
break;
}
}
* @constructor
* @param {Object/String} attributes The attributes/config for the node or just a string with the text for the node
*/
-Ext.tree.TreeNode = function(attributes){
- attributes = attributes || {};
- if(Ext.isString(attributes)){
- attributes = {text: attributes};
- }
- this.childrenRendered = false;
- this.rendered = false;
- Ext.tree.TreeNode.superclass.constructor.call(this, attributes);
- this.expanded = attributes.expanded === true;
- this.isTarget = attributes.isTarget !== false;
- this.draggable = attributes.draggable !== false && attributes.allowDrag !== false;
- this.allowChildren = attributes.allowChildren !== false && attributes.allowDrop !== false;
-
- /**
- * Read-only. The text for this node. To change it use <code>{@link #setText}</code>.
- * @type String
- */
- this.text = attributes.text;
- /**
- * True if this node is disabled.
- * @type Boolean
- */
- this.disabled = attributes.disabled === true;
- /**
- * True if this node is hidden.
- * @type Boolean
- */
- this.hidden = attributes.hidden === true;
+Ext.tree.TreeNode = Ext.extend(Ext.data.Node, {
+
+ constructor : function(attributes){
+ attributes = attributes || {};
+ if(Ext.isString(attributes)){
+ attributes = {text: attributes};
+ }
+ this.childrenRendered = false;
+ this.rendered = false;
+ Ext.tree.TreeNode.superclass.constructor.call(this, attributes);
+ this.expanded = attributes.expanded === true;
+ this.isTarget = attributes.isTarget !== false;
+ this.draggable = attributes.draggable !== false && attributes.allowDrag !== false;
+ this.allowChildren = attributes.allowChildren !== false && attributes.allowDrop !== false;
- this.addEvents(
- /**
- * @event textchange
- * Fires when the text for this node is changed
- * @param {Node} this This node
- * @param {String} text The new text
- * @param {String} oldText The old text
- */
- 'textchange',
- /**
- * @event beforeexpand
- * Fires before this node is expanded, return false to cancel.
- * @param {Node} this This node
- * @param {Boolean} deep
- * @param {Boolean} anim
- */
- 'beforeexpand',
- /**
- * @event beforecollapse
- * Fires before this node is collapsed, return false to cancel.
- * @param {Node} this This node
- * @param {Boolean} deep
- * @param {Boolean} anim
- */
- 'beforecollapse',
- /**
- * @event expand
- * Fires when this node is expanded
- * @param {Node} this This node
- */
- 'expand',
- /**
- * @event disabledchange
- * Fires when the disabled status of this node changes
- * @param {Node} this This node
- * @param {Boolean} disabled
- */
- 'disabledchange',
/**
- * @event collapse
- * Fires when this node is collapsed
- * @param {Node} this This node
- */
- 'collapse',
- /**
- * @event beforeclick
- * Fires before click processing. Return false to cancel the default action.
- * @param {Node} this This node
- * @param {Ext.EventObject} e The event object
- */
- 'beforeclick',
- /**
- * @event click
- * Fires when this node is clicked
- * @param {Node} this This node
- * @param {Ext.EventObject} e The event object
- */
- 'click',
- /**
- * @event checkchange
- * Fires when a node with a checkbox's checked property changes
- * @param {Node} this This node
- * @param {Boolean} checked
- */
- 'checkchange',
- /**
- * @event beforedblclick
- * Fires before double click processing. Return false to cancel the default action.
- * @param {Node} this This node
- * @param {Ext.EventObject} e The event object
- */
- 'beforedblclick',
+ * Read-only. The text for this node. To change it use <code>{@link #setText}</code>.
+ * @type String
+ */
+ this.text = attributes.text;
/**
- * @event dblclick
- * Fires when this node is double clicked
- * @param {Node} this This node
- * @param {Ext.EventObject} e The event object
- */
- 'dblclick',
+ * True if this node is disabled.
+ * @type Boolean
+ */
+ this.disabled = attributes.disabled === true;
/**
- * @event contextmenu
- * Fires when this node is right clicked
- * @param {Node} this This node
- * @param {Ext.EventObject} e The event object
- */
- 'contextmenu',
+ * True if this node is hidden.
+ * @type Boolean
+ */
+ this.hidden = attributes.hidden === true;
+
+ this.addEvents(
+ /**
+ * @event textchange
+ * Fires when the text for this node is changed
+ * @param {Node} this This node
+ * @param {String} text The new text
+ * @param {String} oldText The old text
+ */
+ 'textchange',
+ /**
+ * @event beforeexpand
+ * Fires before this node is expanded, return false to cancel.
+ * @param {Node} this This node
+ * @param {Boolean} deep
+ * @param {Boolean} anim
+ */
+ 'beforeexpand',
+ /**
+ * @event beforecollapse
+ * Fires before this node is collapsed, return false to cancel.
+ * @param {Node} this This node
+ * @param {Boolean} deep
+ * @param {Boolean} anim
+ */
+ 'beforecollapse',
+ /**
+ * @event expand
+ * Fires when this node is expanded
+ * @param {Node} this This node
+ */
+ 'expand',
+ /**
+ * @event disabledchange
+ * Fires when the disabled status of this node changes
+ * @param {Node} this This node
+ * @param {Boolean} disabled
+ */
+ 'disabledchange',
+ /**
+ * @event collapse
+ * Fires when this node is collapsed
+ * @param {Node} this This node
+ */
+ 'collapse',
+ /**
+ * @event beforeclick
+ * Fires before click processing. Return false to cancel the default action.
+ * @param {Node} this This node
+ * @param {Ext.EventObject} e The event object
+ */
+ 'beforeclick',
+ /**
+ * @event click
+ * Fires when this node is clicked
+ * @param {Node} this This node
+ * @param {Ext.EventObject} e The event object
+ */
+ 'click',
+ /**
+ * @event checkchange
+ * Fires when a node with a checkbox's checked property changes
+ * @param {Node} this This node
+ * @param {Boolean} checked
+ */
+ 'checkchange',
+ /**
+ * @event beforedblclick
+ * Fires before double click processing. Return false to cancel the default action.
+ * @param {Node} this This node
+ * @param {Ext.EventObject} e The event object
+ */
+ 'beforedblclick',
+ /**
+ * @event dblclick
+ * Fires when this node is double clicked
+ * @param {Node} this This node
+ * @param {Ext.EventObject} e The event object
+ */
+ 'dblclick',
+ /**
+ * @event contextmenu
+ * Fires when this node is right clicked
+ * @param {Node} this This node
+ * @param {Ext.EventObject} e The event object
+ */
+ 'contextmenu',
+ /**
+ * @event beforechildrenrendered
+ * Fires right before the child nodes for this node are rendered
+ * @param {Node} this This node
+ */
+ 'beforechildrenrendered'
+ );
+
+ var uiClass = this.attributes.uiProvider || this.defaultUI || Ext.tree.TreeNodeUI;
+
/**
- * @event beforechildrenrendered
- * Fires right before the child nodes for this node are rendered
- * @param {Node} this This node
- */
- 'beforechildrenrendered'
- );
-
- var uiClass = this.attributes.uiProvider || this.defaultUI || Ext.tree.TreeNodeUI;
-
- /**
- * Read-only. The UI for this node
- * @type TreeNodeUI
- */
- this.ui = new uiClass(this);
-};
-Ext.extend(Ext.tree.TreeNode, Ext.data.Node, {
+ * Read-only. The UI for this node
+ * @type TreeNodeUI
+ */
+ this.ui = new uiClass(this);
+ },
+
preventHScroll : true,
/**
* Returns true if this node is expanded
Ext.tree.TreeNode.superclass.removeChild.apply(this, arguments);
// only update the ui if we're not destroying
if(!destroy){
+ var rendered = node.ui.rendered;
// if it's been rendered remove dom node
- if(node.ui.rendered){
+ if(rendered){
node.ui.remove();
}
- if(this.childNodes.length < 1){
+ if(rendered && this.childNodes.length < 1){
this.collapse(false, false);
}else{
this.ui.updateExpandIcon();
}
this.fireEvent('textchange', this, text, oldText);
},
+
+ /**
+ * Sets the icon class for this node.
+ * @param {String} cls
+ */
+ setIconCls : function(cls){
+ var old = this.attributes.iconCls;
+ this.attributes.iconCls = cls;
+ if(this.rendered){
+ this.ui.onIconClsChange(this, cls, old);
+ }
+ },
+
+ /**
+ * Sets the tooltip for this node.
+ * @param {String} tip The text for the tip
+ * @param {String} title (Optional) The title for the tip
+ */
+ setTooltip : function(tip, title){
+ this.attributes.qtip = tip;
+ this.attributes.qtipTitle = title;
+ if(this.rendered){
+ this.ui.onTipChange(this, tip, title);
+ }
+ },
+
+ /**
+ * Sets the icon for this node.
+ * @param {String} icon
+ */
+ setIcon : function(icon){
+ this.attributes.icon = icon;
+ if(this.rendered){
+ this.ui.onIconChange(this, icon);
+ }
+ },
+
+ /**
+ * Sets the href for the node.
+ * @param {String} href The href to set
+ * @param {String} (Optional) target The target of the href
+ */
+ setHref : function(href, target){
+ this.attributes.href = href;
+ this.attributes.hrefTarget = target;
+ if(this.rendered){
+ this.ui.onHrefChange(this, href, target);
+ }
+ },
+
+ /**
+ * Sets the class on this node.
+ * @param {String} cls
+ */
+ setCls : function(cls){
+ var old = this.attributes.cls;
+ this.attributes.cls = cls;
+ if(this.rendered){
+ this.ui.onClsChange(this, cls, old);
+ }
+ },
/**
* Triggers selection of this node
this.fireEvent('expand', this);
this.runCallback(callback, scope || this, [this]);
if(deep === true){
- this.expandChildNodes(true);
+ this.expandChildNodes(true, true);
}
}.createDelegate(this));
return;
* Expand all child nodes
* @param {Boolean} deep (optional) true if the child nodes should also expand their child nodes
*/
- expandChildNodes : function(deep){
- var cs = this.childNodes;
- for(var i = 0, len = cs.length; i < len; i++) {
- cs[i].expand(deep);
+ expandChildNodes : function(deep, anim) {
+ var cs = this.childNodes,
+ i,
+ len = cs.length;
+ for (i = 0; i < len; i++) {
+ cs[i].expand(deep, anim);
}
},
* This class provides access to the user interface components of an Ext TreeNode, through
* {@link Ext.tree.TreeNode#getUI}
*/
-Ext.tree.TreeNodeUI = function(node){
- this.node = node;
- this.rendered = false;
- this.animating = false;
- this.wasLeaf = true;
- this.ecc = 'x-tree-ec-icon x-tree-elbow';
- this.emptyIcon = Ext.BLANK_IMAGE_URL;
-};
-
-Ext.tree.TreeNodeUI.prototype = {
+Ext.tree.TreeNodeUI = Ext.extend(Object, {
+
+ constructor : function(node){
+ Ext.apply(this, {
+ node: node,
+ rendered: false,
+ animating: false,
+ wasLeaf: true,
+ ecc: 'x-tree-ec-icon x-tree-elbow',
+ emptyIcon: Ext.BLANK_IMAGE_URL
+ });
+ },
+
// private
removeChild : function(node){
if(this.rendered){
this.textNode.innerHTML = text;
}
},
+
+ // private
+ onIconClsChange : function(node, cls, oldCls){
+ if(this.rendered){
+ Ext.fly(this.iconNode).replaceClass(oldCls, cls);
+ }
+ },
+
+ // private
+ onIconChange : function(node, icon){
+ if(this.rendered){
+ //'<img src="', a.icon || this.emptyIcon, '" class="x-tree-node-icon',(a.icon ? " x-tree-node-inline-icon" : ""),(a.iconCls ? " "+a.iconCls : ""),'" unselectable="on" />',
+ var empty = Ext.isEmpty(icon);
+ this.iconNode.src = empty ? this.emptyIcon : icon;
+ Ext.fly(this.iconNode)[empty ? 'removeClass' : 'addClass']('x-tree-node-inline-icon');
+ }
+ },
+
+ // private
+ onTipChange : function(node, tip, title){
+ if(this.rendered){
+ var hasTitle = Ext.isDefined(title);
+ if(this.textNode.setAttributeNS){
+ this.textNode.setAttributeNS("ext", "qtip", tip);
+ if(hasTitle){
+ this.textNode.setAttributeNS("ext", "qtitle", title);
+ }
+ }else{
+ this.textNode.setAttribute("ext:qtip", tip);
+ if(hasTitle){
+ this.textNode.setAttribute("ext:qtitle", title);
+ }
+ }
+ }
+ },
+
+ // private
+ onHrefChange : function(node, href, target){
+ if(this.rendered){
+ this.anchor.href = this.getHref(href);
+ if(Ext.isDefined(target)){
+ this.anchor.target = target;
+ }
+ }
+ },
+
+ // private
+ onClsChange : function(node, cls, oldCls){
+ if(this.rendered){
+ Ext.fly(this.elNode).replaceClass(oldCls, cls);
+ }
+ },
// private
onDisableChange : function(node, state){
if (this.checkbox) {
this.checkbox.disabled = state;
}
- if(state){
- this.addClass("x-tree-node-disabled");
- }else{
- this.removeClass("x-tree-node-disabled");
- }
+ this[state ? 'addClass' : 'removeClass']('x-tree-node-disabled');
},
// private
this.renderElements(n, a, targetNode, bulkRender);
if(a.qtip){
- if(this.textNode.setAttributeNS){
- this.textNode.setAttributeNS("ext", "qtip", a.qtip);
- if(a.qtipTitle){
- this.textNode.setAttributeNS("ext", "qtitle", a.qtipTitle);
- }
- }else{
- this.textNode.setAttribute("ext:qtip", a.qtip);
- if(a.qtipTitle){
- this.textNode.setAttribute("ext:qtitle", a.qtipTitle);
- }
- }
+ this.onTipChange(n, a.qtip, a.qtipTitle);
}else if(a.qtipCfg){
a.qtipCfg.target = Ext.id(this.textNode);
Ext.QuickTips.register(a.qtipCfg);
var cb = Ext.isBoolean(a.checked),
nel,
- href = a.href ? a.href : Ext.isGecko ? "" : "#",
+ href = this.getHref(a.href),
buf = ['<li class="x-tree-node"><div ext:tree-node-id="',n.id,'" class="x-tree-node-el x-tree-node-leaf x-unselectable ', a.cls,'" unselectable="on">',
'<span class="x-tree-node-indent">',this.indentMarkup,"</span>",
- '<img src="', this.emptyIcon, '" class="x-tree-ec-icon x-tree-elbow" />',
- '<img src="', a.icon || this.emptyIcon, '" class="x-tree-node-icon',(a.icon ? " x-tree-node-inline-icon" : ""),(a.iconCls ? " "+a.iconCls : ""),'" unselectable="on" />',
+ '<img alt="" src="', this.emptyIcon, '" class="x-tree-ec-icon x-tree-elbow" />',
+ '<img alt="" src="', a.icon || this.emptyIcon, '" class="x-tree-node-icon',(a.icon ? " x-tree-node-inline-icon" : ""),(a.iconCls ? " "+a.iconCls : ""),'" unselectable="on" />',
cb ? ('<input class="x-tree-node-cb" type="checkbox" ' + (a.checked ? 'checked="checked" />' : '/>')) : '',
'<a hidefocus="on" class="x-tree-node-anchor" href="',href,'" tabIndex="1" ',
a.hrefTarget ? ' target="'+a.hrefTarget+'"' : "", '><span unselectable="on">',n.text,"</span></a></div>",
this.anchor = cs[index];
this.textNode = cs[index].firstChild;
},
+
+ /**
+ * @private Gets a normalized href for the node.
+ * @param {String} href
+ */
+ getHref : function(href){
+ return Ext.isEmpty(href) ? (Ext.isGecko ? '' : '#') : href;
+ },
/**
* Returns the <a> element that provides focus for the node's UI.
while(p){
if(!p.isRoot || (p.isRoot && p.ownerTree.rootVisible)){
if(!p.isLast()) {
- buf.unshift('<img src="'+this.emptyIcon+'" class="x-tree-elbow-line" />');
+ buf.unshift('<img alt="" src="'+this.emptyIcon+'" class="x-tree-elbow-line" />');
} else {
- buf.unshift('<img src="'+this.emptyIcon+'" class="x-tree-icon" />');
+ buf.unshift('<img alt="" src="'+this.emptyIcon+'" class="x-tree-icon" />');
}
}
p = p.parentNode;
}, this);
delete this.node;
}
-};
+});
/**
* @class Ext.tree.RootTreeNodeUI
* @param {TreePanel} tree
* @param {Object} config
*/
-Ext.tree.TreeSorter = function(tree, config){
- /**
+Ext.tree.TreeSorter = Ext.extend(Object, {
+
+ constructor: function(tree, config){
+ /**
* @cfg {Boolean} folderSort True to sort leaf nodes under non-leaf nodes (defaults to false)
*/
/**
*/
Ext.apply(this, config);
- tree.on("beforechildrenrendered", this.doSort, this);
- tree.on("append", this.updateSort, this);
- tree.on("insert", this.updateSort, this);
- tree.on("textchange", this.updateSortParent, this);
-
- var dsc = this.dir && this.dir.toLowerCase() == "desc";
- var p = this.property || "text";
- var sortType = this.sortType;
- var fs = this.folderSort;
- var cs = this.caseSensitive === true;
- var leafAttr = this.leafAttr || 'leaf';
+ tree.on({
+ scope: this,
+ beforechildrenrendered: this.doSort,
+ append: this.updateSort,
+ insert: this.updateSort,
+ textchange: this.updateSortParent
+ });
+ var desc = this.dir && this.dir.toLowerCase() == 'desc',
+ prop = this.property || 'text';
+ sortType = this.sortType;
+ folderSort = this.folderSort;
+ caseSensitive = this.caseSensitive === true;
+ leafAttr = this.leafAttr || 'leaf';
+
+ if(Ext.isString(sortType)){
+ sortType = Ext.data.SortTypes[sortType];
+ }
this.sortFn = function(n1, n2){
- if(fs){
- if(n1.attributes[leafAttr] && !n2.attributes[leafAttr]){
+ var attr1 = n1.attributes,
+ attr2 = n2.attributes;
+
+ if(folderSort){
+ if(attr1[leafAttr] && !attr2[leafAttr]){
return 1;
}
- if(!n1.attributes[leafAttr] && n2.attributes[leafAttr]){
+ if(!attr1[leafAttr] && attr2[leafAttr]){
return -1;
}
}
- var v1 = sortType ? sortType(n1) : (cs ? n1.attributes[p] : n1.attributes[p].toUpperCase());
- var v2 = sortType ? sortType(n2) : (cs ? n2.attributes[p] : n2.attributes[p].toUpperCase());
+ var prop1 = attr1[prop],
+ prop2 = attr2[prop],
+ v1 = sortType ? sortType(prop1) : (caseSensitive ? prop1 : prop1.toUpperCase());
+ v2 = sortType ? sortType(prop2) : (caseSensitive ? prop2 : prop2.toUpperCase());
+
if(v1 < v2){
- return dsc ? +1 : -1;
+ return desc ? 1 : -1;
}else if(v1 > v2){
- return dsc ? -1 : +1;
- }else{
- return 0;
+ return desc ? -1 : 1;
}
+ return 0;
};
-};
-
-Ext.tree.TreeSorter.prototype = {
+ },
+
doSort : function(node){
node.sort(this.sortFn);
},
- compareNodes : function(n1, n2){
- return (n1.text.toUpperCase() > n2.text.toUpperCase() ? 1 : -1);
- },
-
updateSort : function(tree, node){
if(node.childrenRendered){
this.doSort.defer(1, this, [node]);
if(p && p.childrenRendered){
this.doSort.defer(1, this, [p]);
}
- }
-};/**
+ }
+});/**
* @class Ext.tree.TreeDropZone
* @extends Ext.dd.DropZone
* @constructor
var w3cdom = typeof doc.getElementById != UNDEF && typeof doc.getElementsByTagName != UNDEF && typeof doc.createElement != UNDEF,
u = nav.userAgent.toLowerCase(),
p = nav.platform.toLowerCase(),
- windows = p ? /win/.test(p) : /win/.test(u),
- mac = p ? /mac/.test(p) : /mac/.test(u),
+ windows = p ? (/win/).test(p) : /win/.test(u),
+ mac = p ? (/mac/).test(p) : /mac/.test(u),
webkit = /webkit/.test(u) ? parseFloat(u.replace(/^.*webkit\/(\d+(\.\d+)?).*$/, "$1")) : false, // returns either the webkit version or false if not webkit
ie = !+"\v1", // feature detection based on Andrea Giammarchi's solution: http://webreflection.blogspot.com/2009/01/32-bytes-to-know-if-your-browser-is-ie.html
playerVersion = [0,0,0],
if (ua.wk) {
(function(){
if (isDomLoaded) { return; }
- if (!/loaded|complete/.test(doc.readyState)) {
+ if (!(/loaded|complete/).test(doc.readyState)) {
setTimeout(arguments.callee, 0);
return;
}
storedAltContentId = replaceElemIdStr;
}
att.id = EXPRESS_INSTALL_ID;
- if (typeof att.width == UNDEF || (!/%$/.test(att.width) && parseInt(att.width, 10) < 310)) { att.width = "310"; }
- if (typeof att.height == UNDEF || (!/%$/.test(att.height) && parseInt(att.height, 10) < 137)) { att.height = "137"; }
+ if (typeof att.width == UNDEF || (!(/%$/).test(att.width) && parseInt(att.width, 10) < 310)) {
+ att.width = "310";
+ }
+
+ if (typeof att.height == UNDEF || (!(/%$/).test(att.height) && parseInt(att.height, 10) < 137)) {
+ att.height = "137";
+ }
doc.title = doc.title.slice(0, 47) + " - Flash Player Installation";
var pt = ua.ie && ua.win ? "ActiveX" : "PlugIn",
fv = "MMredirectURL=" + win.location.toString().replace(/&/g,"%26") + "&MMplayerType=" + pt + "&MMdoctitle=" + doc.title;
return {
fn: val.fn,
scope: val.scope || this
- }
+ };
}
},
* @static
* @type String
*/
-Ext.chart.Chart.CHART_URL = 'http:/' + '/yui.yahooapis.com/2.8.0/build/charts/assets/charts.swf';
+Ext.chart.Chart.CHART_URL = 'http:/' + '/yui.yahooapis.com/2.8.2/build/charts/assets/charts.swf';
/**
* @class Ext.chart.PieChart
var items = this.items;
for(var i = start, len = items.length; i >= 0 && i < len; i+= step){
var item = items.get(i);
- if(!item.disabled && (item.canActivate || item.isFormField)){
+ if(item.isVisible() && !item.disabled && (item.canActivate || item.isFormField)){
this.setActiveItem(item, false);
return item;
}
return c;
},
- applyDefaults : function(c){
- if(!Ext.isString(c)){
+ applyDefaults : function(c) {
+ if (!Ext.isString(c)) {
c = Ext.menu.Menu.superclass.applyDefaults.call(this, c);
var d = this.internalDefaults;
if(d){
},
// private
- getMenuItem : function(config){
- if(!config.isXType){
- if(!config.xtype && Ext.isBoolean(config.checked)){
- return new Ext.menu.CheckItem(config)
+ getMenuItem : function(config) {
+ if (!config.isXType) {
+ if (!config.xtype && Ext.isBoolean(config.checked)) {
+ return new Ext.menu.CheckItem(config);
}
return Ext.create(config, this.defaultType);
}
* Adds a separator bar to the menu
* @return {Ext.menu.Item} The menu item that was added
*/
- addSeparator : function(){
+ addSeparator : function() {
return this.add(new Ext.menu.Separator());
},
* @param {Mixed} el The element or DOM node to add, or its id
* @return {Ext.menu.Item} The menu item that was added
*/
- addElement : function(el){
+ addElement : function(el) {
return this.add(new Ext.menu.BaseItem({
el: el
}));
* @param {Ext.menu.Item} item The menu item to add
* @return {Ext.menu.Item} The menu item that was added
*/
- addItem : function(item){
+ addItem : function(item) {
return this.add(item);
},
* @param {Object} config A MenuItem config object
* @return {Ext.menu.Item} The menu item that was added
*/
- addMenuItem : function(config){
+ addMenuItem : function(config) {
return this.add(this.getMenuItem(config));
},
}
}
- // private
- function onBeforeCheck(mi, state){
- if(state){
- var g = groups[mi.group];
- for(var i = 0, l = g.length; i < l; i++){
- if(g[i] != mi){
- g[i].setChecked(false);
- }
- }
- }
- }
-
return {
/**
groups[g] = [];
}
groups[g].push(menuItem);
- menuItem.on("beforecheckchange", onBeforeCheck);
}
},
var g = menuItem.group;
if(g){
groups[g].remove(menuItem);
- menuItem.un("beforecheckchange", onBeforeCheck);
+ }
+ },
+
+ // private
+ onCheckChange: function(item, state){
+ if(item.group && state){
+ var group = groups[item.group],
+ i = 0,
+ len = group.length,
+ current;
+
+ for(; i < len; i++){
+ current = group[i];
+ if(current != item){
+ current.setChecked(false);
+ }
+ }
}
},
*/
itemCls : "x-menu-text",
- constructor : function(config){
- if(typeof config == 'string'){
- config = {text: config}
+ constructor : function(config) {
+ if (typeof config == 'string') {
+ config = {
+ text: config
+ };
}
Ext.menu.TextItem.superclass.constructor.call(this, config);
},
// private
- onRender : function(){
+ onRender : function() {
var s = document.createElement("span");
s.className = this.itemCls;
s.innerHTML = this.text;
* @cfg {Number} showDelay Length of time in milliseconds to wait before showing this item (defaults to 200)
*/
showDelay: 200,
+
+ /**
+ * @cfg {String} altText The altText to use for the icon, if it exists. Defaults to <tt>''</tt>.
+ */
+ altText: '',
+
// doc'd in BaseItem
hideDelay: 200,
' target="{hrefTarget}"',
'</tpl>',
'>',
- '<img src="{icon}" class="x-menu-item-icon {iconCls}"/>',
+ '<img alt="{altText}" src="{icon}" class="x-menu-item-icon {iconCls}"/>',
'<span class="x-menu-item-text">{text}</span>',
'</a>'
);
hrefTarget: this.hrefTarget,
icon: this.icon || Ext.BLANK_IMAGE_URL,
iconCls: this.iconCls || '',
- text: this.itemText||this.text||' '
+ text: this.itemText||this.text||' ',
+ altText: this.altText || ''
};
},
/**
* @cfg {Boolean} checked True to initialize this checkbox as checked (defaults to false). Note that
- * if this checkbox is part of a radio group (group = true) only the last item in the group that is
+ * if this checkbox is part of a radio group (group = true) only the first item in the group that is
* initialized with checked = true will be rendered as checked.
*/
checked: false,
setChecked : function(state, suppressEvent){
var suppress = suppressEvent === true;
if(this.checked != state && (suppress || this.fireEvent("beforecheckchange", this, state) !== false)){
+ Ext.menu.MenuMgr.onCheckChange(this, state);
if(this.container){
this.container[state ? "addClass" : "removeClass"]("x-menu-item-checked");
}
// private
initEvents : function(){
- this.mon(this.el, Ext.EventManager.useKeydown ? 'keydown' : 'keypress', this.fireKey, this);
+ this.mon(this.el, Ext.EventManager.getKeyEvent(), this.fireKey, this);
this.mon(this.el, 'focus', this.onFocus, this);
// standardise buffer across all browsers + OS-es for consistent event order.
// private
preFocus : function(){
- var el = this.el;
+ var el = this.el,
+ isEmpty;
if(this.emptyText){
if(el.dom.value == this.emptyText){
this.setRawValue('');
+ isEmpty = true;
}
el.removeClass(this.emptyClass);
}
- if(this.selectOnFocus){
+ if(this.selectOnFocus || isEmpty){
el.dom.select();
}
},
getErrors: function(value) {
var errors = Ext.form.TextField.superclass.getErrors.apply(this, arguments);
- value = value || this.processValue(this.getRawValue());
+ value = Ext.isDefined(value) ? value : this.processValue(this.getRawValue());
if (Ext.isFunction(this.validator)) {
var msg = this.validator(value);
this.wrap = this.el.wrap({cls: 'x-form-field-wrap x-form-field-trigger-wrap'});
this.trigger = this.wrap.createChild(this.triggerConfig ||
- {tag: "img", src: Ext.BLANK_IMAGE_URL, cls: "x-form-trigger " + this.triggerClass});
+ {tag: "img", src: Ext.BLANK_IMAGE_URL, alt: "", cls: "x-form-trigger " + this.triggerClass});
this.initTrigger();
if(!this.width){
this.wrap.setWidth(this.el.getWidth()+this.trigger.getWidth());
}
},
+ /**
+ * Changes the hidden status of the trigger.
+ * @param {Boolean} hideTrigger True to hide the trigger, false to show it.
+ */
setHideTrigger: function(hideTrigger){
if(hideTrigger != this.hideTrigger){
this.hideTrigger = hideTrigger;
},
/**
- * @param {Boolean} value True to allow the user to directly edit the field text
* Allow or prevent the user from directly editing the field text. If false is passed,
* the user will only be able to modify the field using the trigger. Will also add
* a click event to the text field which will call the trigger. This method
- * is the runtime equivalent of setting the 'editable' config option at config time.
+ * is the runtime equivalent of setting the {@link #editable} config option at config time.
+ * @param {Boolean} value True to allow the user to directly edit the field text.
*/
setEditable: function(editable){
if(editable != this.editable){
},
/**
+ * Setting this to true will supersede settings {@link #editable} and {@link #hideTrigger}.
+ * Setting this to false will defer back to {@link #editable} and {@link #hideTrigger}. This method
+ * is the runtime equivalent of setting the {@link #readOnly} config option at config time.
* @param {Boolean} value True to prevent the user changing the field and explicitly
* hide the trigger.
- * Setting this to true will superceed settings editable and hideTrigger.
- * Setting this to false will defer back to editable and hideTrigger. This method
- * is the runtime equivalent of setting the 'readOnly' config option at config time.
*/
setReadOnly: function(readOnly){
if(readOnly != this.readOnly){
this.triggerConfig = {
tag:'span', cls:'x-form-twin-triggers', cn:[
- {tag: "img", src: Ext.BLANK_IMAGE_URL, cls: "x-form-trigger " + this.trigger1Class},
- {tag: "img", src: Ext.BLANK_IMAGE_URL, cls: "x-form-trigger " + this.trigger2Class}
+ {tag: "img", src: Ext.BLANK_IMAGE_URL, alt: "", cls: "x-form-trigger " + this.trigger1Class},
+ {tag: "img", src: Ext.BLANK_IMAGE_URL, alt: "", cls: "x-form-trigger " + this.trigger2Class}
]};
},
getTrigger : function(index){
return this.triggers[index];
},
+
+ afterRender: function(){
+ Ext.form.TwinTriggerField.superclass.afterRender.call(this);
+ var triggers = this.triggers,
+ i = 0,
+ len = triggers.length;
+
+ for(; i < len; ++i){
+ if(this['hideTrigger' + (i + 1)]){
+ triggers[i].hide();
+ }
+
+ }
+ },
initTrigger : function(){
- var ts = this.trigger.select('.x-form-trigger', true);
- var triggerField = this;
+ var ts = this.trigger.select('.x-form-trigger', true),
+ triggerField = this;
+
ts.each(function(t, all, index){
var triggerIndex = 'Trigger'+(index+1);
t.hide = function(){
var w = triggerField.wrap.getWidth();
this.dom.style.display = 'none';
triggerField.el.setWidth(w-triggerField.trigger.getWidth());
- this['hidden' + triggerIndex] = true;
+ triggerField['hidden' + triggerIndex] = true;
};
t.show = function(){
var w = triggerField.wrap.getWidth();
this.dom.style.display = '';
triggerField.el.setWidth(w-triggerField.trigger.getWidth());
- this['hidden' + triggerIndex] = false;
+ triggerField['hidden' + triggerIndex] = false;
};
-
- if(this['hide'+triggerIndex]){
- t.dom.style.display = 'none';
- this['hidden' + triggerIndex] = true;
- }
this.mon(t, 'click', this['on'+triggerIndex+'Click'], this, {preventDefault:true});
t.addClassOnOver('x-form-trigger-over');
t.addClassOnClick('x-form-trigger-click');
doAutoSize : function(e){
return !e.isNavKeyPress() || e.getKey() == e.ENTER;
},
+
+ // inherit docs
+ filterValidation: function(e) {
+ if(!e.isNavKeyPress() || (!this.enterIsSpecial && e.keyCode == e.ENTER)){
+ this.validationTask.delay(this.validationDelay);
+ }
+ },
/**
* Automatically grows the field to accomodate the height of the text up to the maximum field height allowed.
* @cfg {String} fieldClass The default CSS class for the field (defaults to "x-form-field x-form-num-field")
*/
fieldClass: "x-form-field x-form-num-field",
+
/**
* @cfg {Boolean} allowDecimals False to disallow decimal values (defaults to true)
*/
allowDecimals : true,
+
/**
* @cfg {String} decimalSeparator Character(s) to allow as the decimal separator (defaults to '.')
*/
decimalSeparator : ".",
+
/**
* @cfg {Number} decimalPrecision The maximum precision to display after the decimal separator (defaults to 2)
*/
decimalPrecision : 2,
+
/**
* @cfg {Boolean} allowNegative False to prevent entering a negative sign (defaults to true)
*/
allowNegative : true,
+
/**
* @cfg {Number} minValue The minimum allowed value (defaults to Number.NEGATIVE_INFINITY)
*/
minValue : Number.NEGATIVE_INFINITY,
+
/**
* @cfg {Number} maxValue The maximum allowed value (defaults to Number.MAX_VALUE)
*/
maxValue : Number.MAX_VALUE,
+
/**
* @cfg {String} minText Error text to display if the minimum value validation fails (defaults to "The minimum value for this field is {minValue}")
*/
minText : "The minimum value for this field is {0}",
+
/**
* @cfg {String} maxText Error text to display if the maximum value validation fails (defaults to "The maximum value for this field is {maxValue}")
*/
maxText : "The maximum value for this field is {0}",
+
/**
* @cfg {String} nanText Error text to display if the value is not a valid number. For example, this can happen
* if a valid character like '.' or '-' is left in the field with no number (defaults to "{value} is not a valid number")
*/
nanText : "{0} is not a valid number",
+
/**
* @cfg {String} baseChars The base set of characters to evaluate as valid numbers (defaults to '0123456789').
*/
baseChars : "0123456789",
+
+ /**
+ * @cfg {Boolean} autoStripChars True to automatically strip not allowed characters from the field. Defaults to <tt>false</tt>
+ */
+ autoStripChars: false,
// private
- initEvents : function(){
+ initEvents : function() {
var allowed = this.baseChars + '';
if (this.allowDecimals) {
allowed += this.decimalSeparator;
if (this.allowNegative) {
allowed += '-';
}
- this.maskRe = new RegExp('[' + Ext.escapeRe(allowed) + ']');
+ allowed = Ext.escapeRe(allowed);
+ this.maskRe = new RegExp('[' + allowed + ']');
+ if (this.autoStripChars) {
+ this.stripCharsRe = new RegExp('[^' + allowed + ']', 'gi');
+ }
+
Ext.form.NumberField.superclass.initEvents.call(this);
},
getErrors: function(value) {
var errors = Ext.form.NumberField.superclass.getErrors.apply(this, arguments);
- value = value || this.processValue(this.getRawValue());
+ value = Ext.isDefined(value) ? value : this.processValue(this.getRawValue());
if (value.length < 1) { // if it's blank and textfield didn't flag it then it's valid
return errors;
var num = this.parseValue(value);
- if(num < this.minValue){
+ if (num < this.minValue) {
errors.push(String.format(this.minText, this.minValue));
}
- if(num > this.maxValue){
+ if (num > this.maxValue) {
errors.push(String.format(this.maxText, this.maxValue));
}
return errors;
},
- getValue : function(){
+ getValue : function() {
return this.fixPrecision(this.parseValue(Ext.form.NumberField.superclass.getValue.call(this)));
},
- setValue : function(v){
+ setValue : function(v) {
+ v = this.fixPrecision(v);
v = Ext.isNumber(v) ? v : parseFloat(String(v).replace(this.decimalSeparator, "."));
v = isNaN(v) ? '' : String(v).replace(".", this.decimalSeparator);
return Ext.form.NumberField.superclass.setValue.call(this, v);
* Replaces any existing {@link #minValue} with the new value.
* @param {Number} value The minimum value
*/
- setMinValue : function(value){
+ setMinValue : function(value) {
this.minValue = Ext.num(value, Number.NEGATIVE_INFINITY);
},
* Replaces any existing {@link #maxValue} with the new value.
* @param {Number} value The maximum value
*/
- setMaxValue : function(value){
+ setMaxValue : function(value) {
this.maxValue = Ext.num(value, Number.MAX_VALUE);
},
// private
- parseValue : function(value){
+ parseValue : function(value) {
value = parseFloat(String(value).replace(this.decimalSeparator, "."));
return isNaN(value) ? '' : value;
},
- // private
- fixPrecision : function(value){
+ /**
+ * @private
+ *
+ */
+ fixPrecision : function(value) {
var nan = isNaN(value);
- if(!this.allowDecimals || this.decimalPrecision == -1 || nan || !value){
- return nan ? '' : value;
+
+ if (!this.allowDecimals || this.decimalPrecision == -1 || nan || !value) {
+ return nan ? '' : value;
}
+
return parseFloat(parseFloat(value).toFixed(this.decimalPrecision));
},
- beforeBlur : function(){
+ beforeBlur : function() {
var v = this.parseValue(this.getRawValue());
- if(!Ext.isEmpty(v)){
- this.setValue(this.fixPrecision(v));
+
+ if (!Ext.isEmpty(v)) {
+ this.setValue(v);
}
}
});
-Ext.reg('numberfield', Ext.form.NumberField);/**
+
+Ext.reg('numberfield', Ext.form.NumberField);
+/**
* @class Ext.form.DateField
* @extends Ext.form.TriggerField
* Provides a date input field with a {@link Ext.DatePicker} dropdown and automatic date validation.
* @cfg {String} altFormats
* Multiple date formats separated by "<tt>|</tt>" to try when parsing a user input value and it
* does not match the defined format (defaults to
- * <tt>'m/d/Y|n/j/Y|n/j/y|m/j/y|n/d/y|m/j/Y|n/d/Y|m-d-y|m-d-Y|m/d|m-d|md|mdy|mdY|d|Y-m-d'</tt>).
+ * <tt>'m/d/Y|n/j/Y|n/j/y|m/j/y|n/d/y|m/j/Y|n/d/Y|m-d-y|m-d-Y|m/d|m-d|md|mdy|mdY|d|Y-m-d|n-j|n/j'</tt>).
*/
- altFormats : "m/d/Y|n/j/Y|n/j/y|m/j/y|n/d/y|m/j/Y|n/d/Y|m-d-y|m-d-Y|m/d|m-d|md|mdy|mdY|d|Y-m-d",
+ altFormats : "m/d/Y|n/j/Y|n/j/y|m/j/y|n/d/y|m/j/Y|n/d/Y|m-d-y|m-d-Y|m/d|m-d|md|mdy|mdY|d|Y-m-d|n-j|n/j",
/**
* @cfg {String} disabledDaysText
* The tooltip to display when the date falls on a disabled day (defaults to <tt>'Disabled'</tt>)
* the keyboard handler for spacebar that selects the current date (defaults to <tt>true</tt>).
*/
showToday : true,
+
+ /**
+ * @cfg {Number} startDay
+ * Day index at which the week should begin, 0-based (defaults to 0, which is Sunday)
+ */
+ startDay : 0,
+
/**
* @cfg {Date/String} minValue
* The minimum allowed date. Can be either a Javascript date object or a string date in a
} else {
// set time to 12 noon, then clear the time
var parsedDate = Date.parseDate(value + ' ' + this.initTime, format + ' ' + this.initTimeFormat);
-
- if (parsedDate) return parsedDate.clearTime();
+
+ if (parsedDate) {
+ return parsedDate.clearTime();
+ }
}
},
this.menu.picker.setMaxDate(this.maxValue);
}
},
-
+
/**
* Runs all of NumberFields validations and returns an array of any errors. Note that this first
* runs TextField's validations, so the returned array is an amalgamation of all field errors.
*/
getErrors: function(value) {
var errors = Ext.form.DateField.superclass.getErrors.apply(this, arguments);
-
+
value = this.formatDate(value || this.processValue(this.getRawValue()));
-
+
if (value.length < 1) { // if it's blank and textfield didn't flag it then it's valid
return errors;
}
-
+
var svalue = value;
value = this.parseDate(value);
if (!value) {
errors.push(String.format(this.invalidText, svalue, this.format));
return errors;
}
-
+
var time = value.getTime();
- if (this.minValue && time < this.minValue.getTime()) {
+ if (this.minValue && time < this.minValue.clearTime().getTime()) {
errors.push(String.format(this.minText, this.formatDate(this.minValue)));
}
-
- if (this.maxValue && time > this.maxValue.getTime()) {
+
+ if (this.maxValue && time > this.maxValue.clearTime().getTime()) {
errors.push(String.format(this.maxText, this.formatDate(this.maxValue)));
}
-
+
if (this.disabledDays) {
var day = value.getDay();
-
+
for(var i = 0; i < this.disabledDays.length; i++) {
if (day === this.disabledDays[i]) {
errors.push(this.disabledDaysText);
}
}
}
-
+
var fvalue = this.formatDate(value);
if (this.disabledDatesRE && this.disabledDatesRE.test(fvalue)) {
errors.push(String.format(this.disabledDatesText, fvalue));
}
-
+
return errors;
},
disabledDaysText : this.disabledDaysText,
format : this.format,
showToday : this.showToday,
+ startDay: this.startDay,
minText : String.format(this.minText, this.formatDate(this.minValue)),
maxText : String.format(this.maxText, this.formatDate(this.maxValue))
});
* @method autoSize
*/
});
-Ext.reg('datefield', Ext.form.DateField);/**
+Ext.reg('datefield', Ext.form.DateField);
+/**
* @class Ext.form.DisplayField
* @extends Ext.form.Field
* A display-only text field which is not validated and not submitted.
* @cfg {String} hiddenName If specified, a hidden form field with this name is dynamically generated to store the
* field's data value (defaults to the underlying DOM element's name). Required for the combo's value to automatically
* post during a form submission. See also {@link #valueField}.
- * <p><b>Note</b>: the hidden field's id will also default to this name if {@link #hiddenId} is not specified.
- * The ComboBox {@link Ext.Component#id id} and the <tt>{@link #hiddenId}</tt> <b>should be different</b>, since
- * no two DOM nodes should share the same id. So, if the ComboBox <tt>{@link Ext.form.Field#name name}</tt> and
- * <tt>hiddenName</tt> are the same, you should specify a unique <tt>{@link #hiddenId}</tt>.</p>
*/
/**
* @cfg {String} hiddenId If <tt>{@link #hiddenName}</tt> is specified, <tt>hiddenId</tt> can also be provided
- * to give the hidden field a unique id (defaults to the <tt>{@link #hiddenName}</tt>). The <tt>hiddenId</tt>
- * and combo {@link Ext.Component#id id} should be different, since no two DOM
- * nodes should share the same id.
+ * to give the hidden field a unique id. The <tt>hiddenId</tt> and combo {@link Ext.Component#id id} should be
+ * different, since no two DOM nodes should share the same id.
*/
/**
* @cfg {String} hiddenValue Sets the initial value of the hidden field if {@link #hiddenName} is
d.push([value, o.text]);
}
this.store = new Ext.data.ArrayStore({
- 'id': 0,
+ idIndex: 0,
fields: ['value', 'text'],
data : d,
autoDestroy: true
Ext.form.ComboBox.superclass.onRender.call(this, ct, position);
if(this.hiddenName){
this.hiddenField = this.el.insertSibling({tag:'input', type:'hidden', name: this.hiddenName,
- id: (this.hiddenId||this.hiddenName)}, 'before', true);
+ id: (this.hiddenId || Ext.id())}, 'before', true);
}
if(Ext.isGecko){
}
return zindex;
},
+
+ getZIndex : function(listParent){
+ listParent = listParent || Ext.getDom(this.getListParent() || Ext.getBody());
+ var zindex = parseInt(Ext.fly(listParent).getStyle('z-index'), 10);
+ if(!zindex){
+ zindex = this.getParentZIndex();
+ }
+ return (zindex || 12000) + 5;
+ },
// private
initList : function(){
if(!this.list){
var cls = 'x-combo-list',
- listParent = Ext.getDom(this.getListParent() || Ext.getBody()),
- zindex = parseInt(Ext.fly(listParent).getStyle('z-index'), 10);
-
- if (!zindex) {
- zindex = this.getParentZIndex();
- }
+ listParent = Ext.getDom(this.getListParent() || Ext.getBody());
this.list = new Ext.Layer({
parentEl: listParent,
shadow: this.shadow,
cls: [cls, this.listClass].join(' '),
constrain:false,
- zindex: (zindex || 12000) + 5
+ zindex: this.getZIndex(listParent)
});
var lw = this.listWidth || Math.max(this.wrap.getWidth(), this.minListWidth);
},
reset : function(){
- Ext.form.ComboBox.superclass.reset.call(this);
if(this.clearFilterOnReset && this.mode == 'local'){
this.store.clearFilter();
}
+ Ext.form.ComboBox.superclass.reset.call(this);
},
// private
},
// private
- assertValue : function(){
+ assertValue : function(){
var val = this.getRawValue(),
- rec = this.findRecord(this.displayField, val);
+ rec;
+ if(this.valueField && Ext.isDefined(this.value)){
+ rec = this.findRecord(this.valueField, this.value);
+ }
+ if(!rec || rec.get(this.displayField) != val){
+ rec = this.findRecord(this.displayField, val);
+ }
if(!rec && this.forceSelection){
if(val.length > 0 && val != this.emptyText){
this.el.dom.value = Ext.value(this.lastSelectionText, '');
this.clearValue();
}
}else{
- if(rec){
+ if(rec && this.valueField){
// onSelect may have already set the value and by doing so
// set the display field properly. Let's not wipe out the
// valueField here by just sending the displayField.
- if (val == rec.get(this.displayField) && this.value == rec.get(this.valueField)){
+ if (this.value == val){
return;
}
val = rec.get(this.valueField || this.displayField);
// private
getParams : function(q){
- var p = {};
- //p[this.queryParam] = q;
+ var params = {},
+ paramNames = this.store.paramNames;
if(this.pageSize){
- p.start = 0;
- p.limit = this.pageSize;
+ params[paramNames.start] = 0;
+ params[paramNames.limit] = this.pageSize;
}
- return p;
+ return params;
},
/**
this.list.alignTo.apply(this.list, [this.el].concat(this.listAlign));
// zindex can change, re-check it and set it if necessary
- var listParent = Ext.getDom(this.getListParent() || Ext.getBody()),
- zindex = parseInt(Ext.fly(listParent).getStyle('z-index') ,10);
- if (!zindex){
- zindex = this.getParentZIndex();
- }
- if (zindex) {
- this.list.setZIndex(zindex + 5);
- }
+ this.list.setZIndex(this.getZIndex());
this.list.show();
if(Ext.isGecko2){
this.innerList.setOverflow('auto'); // necessary for FF 2.0/Mac
* {tag: 'input', type: 'checkbox', autocomplete: 'off'})
*/
defaultAutoCreate : { tag: 'input', type: 'checkbox', autocomplete: 'off'},
- /**
- * @cfg {String} boxLabel The text that appears beside the checkbox
- */
/**
* @cfg {String} inputValue The value that should go into the generated input element's value attribute
*/
this.checked = this.el.dom.checked;
}
// Need to repaint for IE, otherwise positioning is broken
- if(Ext.isIE){
+ if (Ext.isIE && !Ext.isStrict) {
this.wrap.repaint();
}
this.resizeEl = this.positionEl = this.wrap;
* @return {Ext.form.Field} this
*/
setValue : function(v){
- var checked = this.checked ;
- this.checked = (v === true || v === 'true' || v == '1' || String(v).toLowerCase() == 'on');
+ var checked = this.checked,
+ inputVal = this.inputValue;
+
+ this.checked = (v === true || v === 'true' || v == '1' || (inputVal ? v == inputVal : String(v).toLowerCase() == 'on'));
if(this.rendered){
this.el.dom.checked = this.checked;
this.el.dom.defaultChecked = this.checked;
// private
beforeDestroy: function(){
Ext.destroy(this.panel);
+ if (!this.rendered) {
+ Ext.destroy(this.items);
+ }
Ext.form.CheckboxGroup.superclass.beforeDestroy.call(this);
},
* True to combine errors from the individual fields into a single error message at the CompositeField level (defaults to true)
*/
combineErrors: true,
+
+ /**
+ * @cfg {String} labelConnector The string to use when joining segments of the built label together (defaults to ', ')
+ */
+ labelConnector: ', ',
+
+ /**
+ * @cfg {Object} defaults Any default properties to assign to the child fields.
+ */
//inherit docs
//Builds the composite field label
for (var i=0, j = items.length; i < j; i++) {
item = items[i];
+
+ if (!Ext.isEmpty(item.ref)){
+ item.ref = '../' + item.ref;
+ }
labels.push(item.fieldLabel);
//apply any defaults
- Ext.apply(item, this.defaults);
+ Ext.applyIf(item, this.defaults);
//apply default margins to each item except the last
if (!(i == j - 1 && this.skipLastItemMargin)) {
});
Ext.form.CompositeField.superclass.initComponent.apply(this, arguments);
+
+ this.innerCt = new Ext.Container({
+ layout : 'hbox',
+ items : this.items,
+ cls : 'x-form-composite',
+ defaultMargins: '0 3 0 0',
+ ownerCt: this
+ });
+ this.innerCt.ownerCt = undefined;
+
+ var fields = this.innerCt.findBy(function(c) {
+ return c.isFormField;
+ }, this);
+
+ /**
+ * @property items
+ * @type Ext.util.MixedCollection
+ * Internal collection of all of the subfields in this Composite
+ */
+ this.items = new Ext.util.MixedCollection();
+ this.items.addAll(fields);
+
},
/**
* @type Ext.Container
* A container configured with hbox layout which is responsible for laying out the subfields
*/
- var innerCt = this.innerCt = new Ext.Container({
- layout : 'hbox',
- renderTo: ct,
- items : this.items,
- cls : 'x-form-composite',
- defaultMargins: '0 3 0 0'
- });
+ var innerCt = this.innerCt;
+ innerCt.render(ct);
this.el = innerCt.getEl();
- var fields = innerCt.findBy(function(c) {
- return c.isFormField;
- }, this);
-
- /**
- * @property items
- * @type Ext.util.MixedCollection
- * Internal collection of all of the subfields in this Composite
- */
- this.items = new Ext.util.MixedCollection();
- this.items.addAll(fields);
-
//if we're combining subfield errors into a single message, override the markInvalid and clearInvalid
//methods of each subfield and show them at the Composite level instead
if (this.combineErrors) {
*/
onFieldMarkInvalid: function(field, message) {
var name = field.getName(),
- error = {field: name, error: message};
+ error = {
+ field: name,
+ errorName: field.fieldLabel || name,
+ error: message
+ };
this.fieldErrors.replace(name, error);
for (var i = 0, j = errors.length; i < j; i++) {
error = errors[i];
- combined.push(String.format("{0}: {1}", error.field, error.error));
+ combined.push(String.format("{0}: {1}", error.errorName, error.error));
}
return combined.join("<br />");
* @return {String} The built label
*/
buildLabel: function(segments) {
- return segments.join(", ");
+ return Ext.clean(segments).join(this.labelConnector);
},
/**
//override the behaviour to check sub items.
setReadOnly : function(readOnly) {
- readOnly = readOnly || true;
+ if (readOnly == undefined) {
+ readOnly = true;
+ }
+ readOnly = !!readOnly;
if(this.rendered){
this.eachItem(function(item){
}
});
-Ext.reg('compositefield', Ext.form.CompositeField);
-/**
+Ext.reg('compositefield', Ext.form.CompositeField);/**
* @class Ext.form.Radio
* @extends Ext.form.Checkbox
* Single radio field. Same as Checkbox, but provided as a convenience for automatically setting the input type.
return c ? c.value : null;
},
- // private
- onClick : function(){
- if(this.el.dom.checked != this.checked){
- var els = this.getCheckEl().select('input[name=' + this.el.dom.name + ']');
- els.each(function(el){
- if(el.dom.id == this.id){
- this.setValue(true);
- }else{
- Ext.getCmp(el.dom.id).setValue(false);
- }
- }, this);
- }
- },
-
/**
* Sets either the checked/unchecked status of this Radio, or, if a string value
* is passed, checks a sibling Radio of the same name whose value is the value specified.
* @return {Ext.form.Field} this
*/
setValue : function(v){
+ var checkEl,
+ els,
+ radio;
if (typeof v == 'boolean') {
Ext.form.Radio.superclass.setValue.call(this, v);
} else if (this.rendered) {
- var r = this.getCheckEl().child('input[name=' + this.el.dom.name + '][value=' + v + ']', true);
- if(r){
- Ext.getCmp(r.id).setValue(true);
+ checkEl = this.getCheckEl();
+ radio = checkEl.child('input[name=' + this.el.dom.name + '][value=' + v + ']', true);
+ if(radio){
+ Ext.getCmp(radio.id).setValue(true);
}
}
+ if(this.rendered && this.checked){
+ checkEl = checkEl || this.getCheckEl();
+ els = this.getCheckEl().select('input[name=' + this.el.dom.name + ']');
+ els.each(function(el){
+ if(el.dom.id != this.id){
+ Ext.getCmp(el.dom.id).setValue(false);
+ }
+ }, this);
+ }
return this;
},
// private
getCheckEl: function(){
if(this.inGroup){
- return this.el.up('.x-form-radio-group')
+ return this.el.up('.x-form-radio-group');
}
return this.el.up('form') || Ext.getBody();
}
Ext.form.Hidden = Ext.extend(Ext.form.Field, {
// private
inputType : 'hidden',
+
+ shouldLayout: false,
// private
onRender : function(){
*/
updateRecord : function(record){
record.beginEdit();
- var fs = record.fields;
+ var fs = record.fields,
+ field,
+ value;
fs.each(function(f){
- var field = this.findField(f.name);
+ field = this.findField(f.name);
if(field){
- record.set(f.name, field.getValue());
+ value = field.getValue();
+ if (typeof value != undefined && value.getGroupValue) {
+ value = value.getGroupValue();
+ } else if ( field.eachItem ) {
+ value = [];
+ field.eachItem(function(item){
+ value.push(item.getValue());
+ });
+ }
+ record.set(f.name, value);
}
}, this);
record.endEdit();
if (f.dataIndex == id || f.id == id || f.getName() == id) {
field = f;
return false;
- } else if (f.isComposite && f.rendered) {
+ } else if (f.isComposite) {
return f.items.each(findMatchingField);
+ } else if (f instanceof Ext.form.CheckboxGroup && f.rendered) {
+ return f.eachItem(findMatchingField);
}
}
};
key,
val;
this.items.each(function(f) {
- if (dirtyOnly !== true || f.isDirty()) {
+ if (!f.disabled && (dirtyOnly !== true || f.isDirty())) {
n = f.getName();
key = o[n];
val = f.getValue();
// If a Container, its already destroyed by the time it gets here. Remove any references to destroyed fields.
}else if (c.findBy){
Ext.each(c.findBy(this.isField), this.form.remove, this.form);
- if (c.isDestroyed) {
- this.form.cleanDestroyed();
- }
+ /*
+ * This isn't the most efficient way of getting rid of the items, however it's the most
+ * correct, which in this case is most important.
+ */
+ this.form.cleanDestroyed();
}
}
},
*/
'editmodechange'
);
+ Ext.form.HtmlEditor.superclass.initComponent.call(this);
},
// private
iframe.name = Ext.id();
iframe.frameBorder = '0';
iframe.style.overflow = 'auto';
+ iframe.src = Ext.SSL_SECURE_URL;
this.wrap.dom.appendChild(iframe);
this.iframe = iframe;
* set current design mode. To enable, mode can be true or 'on', off otherwise
*/
setDesignMode : function(mode){
- var doc ;
- if(doc = this.getDoc()){
+ var doc = this.getDoc();
+ if (doc) {
if(this.readOnly){
mode = false;
}
*/
toggleSourceEdit : function(sourceEditMode){
var iframeHeight,
- elHeight,
- ls;
+ elHeight;
if (sourceEditMode === undefined) {
sourceEditMode = !this.sourceEditMode;
}
if (this.sourceEditMode) {
// grab the height of the containing panel before we hide the iframe
- ls = this.getSize();
+ this.previousSize = this.getSize();
iframeHeight = Ext.get(this.iframe).getHeight();
this.el.dom.setAttribute('tabIndex', -1);
this.deferFocus();
- this.setSize(ls);
+ this.setSize(this.previousSize);
+ delete this.previousSize;
this.iframe.style.height = elHeight + 'px';
}
this.fireEvent('editmodechange', this, this.sourceEditMode);
},
// private
- onDestroy : function(){
+ beforeDestroy : function(){
if(this.monitorTask){
Ext.TaskMgr.stop(this.monitorTask);
}
this.wrap.remove();
}
}
-
- if(this.el){
- this.el.removeAllListeners();
- this.el.remove();
- }
- this.purgeListeners();
+ Ext.form.HtmlEditor.superclass.beforeDestroy.call(this);
},
// private
Ext.reg('timefield', Ext.form.TimeField);/**
* @class Ext.form.SliderField
* @extends Ext.form.Field
- * Wraps a {@link Ext.Slider Slider} so it can be used as a form field.
+ * Wraps a {@link Ext.slider.MultiSlider Slider} so it can be used as a form field.
* @constructor
* Creates a new SliderField
* @param {Object} config Configuration options. Note that you can pass in any slider configuration options, as well as
if(o.clientValidation === false || this.form.isValid()){
if (o.submitEmptyText === false) {
var fields = this.form.items,
- emptyFields = [];
- fields.each(function(f) {
- if (f.el.getValue() == f.emptyText) {
- emptyFields.push(f);
- f.el.dom.value = "";
- }
- });
+ emptyFields = [],
+ setupEmptyFields = function(f){
+ if (f.el.getValue() == f.emptyText) {
+ emptyFields.push(f);
+ f.el.dom.value = "";
+ }
+ if(f.isComposite && f.rendered){
+ f.items.each(setupEmptyFields);
+ }
+ };
+
+ fields.each(setupEmptyFields);
}
Ext.Ajax.request(Ext.apply(this.createCallback(o), {
form:this.form.el.dom,
* <p>See <tt>{@link #autoExpandMax}</tt> and <tt>{@link #autoExpandMin}</tt> also.</p>
*/
autoExpandColumn : false,
+
/**
* @cfg {Number} autoExpandMax The maximum width the <tt>{@link #autoExpandColumn}</tt>
* can have (if enabled). Defaults to <tt>1000</tt>.
*/
autoExpandMax : 1000,
+
/**
* @cfg {Number} autoExpandMin The minimum width the <tt>{@link #autoExpandColumn}</tt>
* can have (if enabled). Defaults to <tt>50</tt>.
*/
autoExpandMin : 50,
+
/**
* @cfg {Boolean} columnLines <tt>true</tt> to add css for column separation lines.
* Default is <tt>false</tt>.
*/
columnLines : false,
+
/**
* @cfg {Object} cm Shorthand for <tt>{@link #colModel}</tt>.
*/
* <tt>{0}</tt> is replaced with the number of selected rows.
*/
ddText : '{0} selected row{1}',
+
/**
* @cfg {Boolean} deferRowRender <P>Defaults to <tt>true</tt> to enable deferred row rendering.</p>
* <p>This allows the GridPanel to be initially rendered empty, with the expensive update of the row
* structure deferred so that layouts with GridPanels appear more quickly.</p>
*/
deferRowRender : true,
+
/**
* @cfg {Boolean} disableSelection <p><tt>true</tt> to disable selections in the grid. Defaults to <tt>false</tt>.</p>
* <p>Ignored if a {@link #selModel SelectionModel} is specified.</p>
* with the {@link #enableHdMenu header menu}.
*/
enableColumnHide : true,
+
/**
* @cfg {Boolean} enableColumnMove Defaults to <tt>true</tt> to enable drag and drop reorder of columns. <tt>false</tt>
* to turn off column reordering via drag drop.
*/
enableColumnMove : true,
+
/**
* @cfg {Boolean} enableDragDrop <p>Enables dragging of the selected rows of the GridPanel. Defaults to <tt>false</tt>.</p>
* <p>Setting this to <b><tt>true</tt></b> causes this GridPanel's {@link #getView GridView} to
* to process the {@link Ext.grid.GridDragZone#getDragData data} which is provided.</p>
*/
enableDragDrop : false,
+
/**
* @cfg {Boolean} enableHdMenu Defaults to <tt>true</tt> to enable the drop down button for menu in the headers.
*/
enableHdMenu : true,
+
/**
* @cfg {Boolean} hideHeaders True to hide the grid's header. Defaults to <code>false</code>.
*/
* loading. Defaults to <code>false</code>.
*/
loadMask : false,
+
/**
* @cfg {Number} maxHeight Sets the maximum height of the grid - ignored if <tt>autoHeight</tt> is not on.
*/
* @cfg {Number} minColumnWidth The minimum width a column can be resized to. Defaults to <tt>25</tt>.
*/
minColumnWidth : 25,
+
/**
* @cfg {Object} sm Shorthand for <tt>{@link #selModel}</tt>.
*/
* modifier, or which uses a CSS selector of higher specificity.</p>
*/
stripeRows : false,
+
/**
* @cfg {Boolean} trackMouseOver True to highlight rows when the mouse is over. Default is <tt>true</tt>
* for GridPanel, but <tt>false</tt> for EditorGridPanel.
*/
trackMouseOver : true,
+
/**
* @cfg {Array} stateEvents
* An array of events that, when fired, should trigger this component to save its state.
* Component state.</p>
*/
stateEvents : ['columnmove', 'columnresize', 'sortchange', 'groupchange'],
+
/**
* @cfg {Object} view The {@link Ext.grid.GridView} used by the grid. This can be set
* before a call to {@link Ext.Component#render render()}.
// private
rendered : false,
+
// private
viewReady : false,
// private
- initComponent : function(){
+ initComponent : function() {
Ext.grid.GridPanel.superclass.initComponent.call(this);
- if(this.columnLines){
+ if (this.columnLines) {
this.cls = (this.cls || '') + ' x-grid-with-col-lines';
}
// override any provided value since it isn't valid
store = this.store,
s,
c,
- oldIndex;
+ colIndex;
if(cs){
for(var i = 0, len = cs.length; i < len; i++){
s = cs[i];
c = cm.getColumnById(s.id);
if(c){
- c.hidden = s.hidden;
- c.width = s.width;
- oldIndex = cm.getIndexById(s.id);
- if(oldIndex != i){
- cm.moveColumn(oldIndex, i);
+ colIndex = cm.getIndexById(s.id);
+ cm.setState(colIndex, {
+ hidden: s.hidden,
+ width: s.width,
+ sortable: s.sortable
+ });
+ if(colIndex != i){
+ cm.moveColumn(colIndex, i);
}
}
}
if(c.hidden){
o.columns[i].hidden = true;
}
+ if(c.sortable){
+ o.columns[i].sortable = true;
+ }
}
if(store){
ss = store.getSortState();
Ext.grid.GridPanel.superclass.afterRender.call(this);
var v = this.view;
this.on('bodyresize', v.layout, v);
- v.layout();
+ v.layout(true);
if(this.deferRowRender){
if (!this.deferRowRenderTask){
this.deferRowRenderTask = new Ext.util.DelayedTask(v.afterRender, this.view);
* Returns the grid's GridView object.
* @return {Ext.grid.GridView} The grid view
*/
- getView : function(){
- if(!this.view){
+ getView : function() {
+ if (!this.view) {
this.view = new Ext.grid.GridView(this.viewConfig);
}
+
return this.view;
},
/**
*/
});
Ext.reg('grid', Ext.grid.GridPanel);/**
+ * @class Ext.grid.PivotGrid
+ * @extends Ext.grid.GridPanel
+ * <p>The PivotGrid component enables rapid summarization of large data sets. It provides a way to reduce a large set of
+ * data down into a format where trends and insights become more apparent. A classic example is in sales data; a company
+ * will often have a record of all sales it makes for a given period - this will often encompass thousands of rows of
+ * data. The PivotGrid allows you to see how well each salesperson performed, which cities generate the most revenue,
+ * how products perform between cities and so on.</p>
+ * <p>A PivotGrid is composed of two axes (left and top), one {@link #measure} and one {@link #aggregator aggregation}
+ * function. Each axis can contain one or more {@link #dimension}, which are ordered into a hierarchy. Dimensions on the
+ * left axis can also specify a width. Each dimension in each axis can specify its sort ordering, defaulting to "ASC",
+ * and must specify one of the fields in the {@link Ext.data.Record Record} used by the PivotGrid's
+ * {@link Ext.data.Store Store}.</p>
+<pre><code>
+// This is the record representing a single sale
+var SaleRecord = Ext.data.Record.create([
+ {name: 'person', type: 'string'},
+ {name: 'product', type: 'string'},
+ {name: 'city', type: 'string'},
+ {name: 'state', type: 'string'},
+ {name: 'year', type: 'int'},
+ {name: 'value', type: 'int'}
+]);
+
+// A simple store that loads SaleRecord data from a url
+var myStore = new Ext.data.Store({
+ url: 'data.json',
+ autoLoad: true,
+ reader: new Ext.data.JsonReader({
+ root: 'rows',
+ idProperty: 'id'
+ }, SaleRecord)
+});
+
+// Create the PivotGrid itself, referencing the store
+var pivot = new Ext.grid.PivotGrid({
+ store : myStore,
+ aggregator: 'sum',
+ measure : 'value',
+
+ leftAxis: [
+ {
+ width: 60,
+ dataIndex: 'product'
+ },
+ {
+ width: 120,
+ dataIndex: 'person',
+ direction: 'DESC'
+ }
+ ],
+
+ topAxis: [
+ {
+ dataIndex: 'year'
+ }
+ ]
+});
+</code></pre>
+ * <p>The specified {@link #measure} is the field from SaleRecord that is extracted from each combination
+ * of product and person (on the left axis) and year on the top axis. There may be several SaleRecords in the
+ * data set that share this combination, so an array of measure fields is produced. This array is then
+ * aggregated using the {@link #aggregator} function.</p>
+ * <p>The default aggregator function is sum, which simply adds up all of the extracted measure values. Other
+ * built-in aggregator functions are count, avg, min and max. In addition, you can specify your own function.
+ * In this example we show the code used to sum the measures, but you can return any value you like. See
+ * {@link #aggregator} for more details.</p>
+<pre><code>
+new Ext.grid.PivotGrid({
+ aggregator: function(records, measure) {
+ var length = records.length,
+ total = 0,
+ i;
+
+ for (i = 0; i < length; i++) {
+ total += records[i].get(measure);
+ }
+
+ return total;
+ },
+
+ renderer: function(value) {
+ return Math.round(value);
+ },
+
+ //your normal config here
+});
+</code></pre>
+ * <p><u>Renderers</u></p>
+ * <p>PivotGrid optionally accepts a {@link #renderer} function which can modify the data in each cell before it
+ * is rendered. The renderer is passed the value that would usually be placed in the cell and is expected to return
+ * the new value. For example let's imagine we had height data expressed as a decimal - here's how we might use a
+ * renderer to display the data in feet and inches notation:</p>
+<pre><code>
+new Ext.grid.PivotGrid({
+ //in each case the value is a decimal number of feet
+ renderer : function(value) {
+ var feet = Math.floor(value),
+ inches = Math.round((value - feet) * 12);
+
+ return String.format("{0}' {1}\"", feet, inches);
+ },
+ //normal config here
+});
+</code></pre>
+ * <p><u>Reconfiguring</u></p>
+ * <p>All aspects PivotGrid's configuration can be updated at runtime. It is easy to change the {@link #setMeasure measure},
+ * {@link #setAggregator aggregation function}, {@link #setLeftAxis left} and {@link #setTopAxis top} axes and refresh the grid.</p>
+ * <p>In this case we reconfigure the PivotGrid to have city and year as the top axis dimensions, rendering the average sale
+ * value into the cells:</p>
+<pre><code>
+//the left axis can also be changed
+pivot.topAxis.setDimensions([
+ {dataIndex: 'city', direction: 'DESC'},
+ {dataIndex: 'year', direction: 'ASC'}
+]);
+
+pivot.setMeasure('value');
+pivot.setAggregator('avg');
+
+pivot.view.refresh(true);
+</code></pre>
+ * <p>See the {@link Ext.grid.PivotAxis PivotAxis} documentation for further detail on reconfiguring axes.</p>
+ */
+Ext.grid.PivotGrid = Ext.extend(Ext.grid.GridPanel, {
+
+ /**
+ * @cfg {String|Function} aggregator The aggregation function to use to combine the measures extracted
+ * for each dimension combination. Can be any of the built-in aggregators (sum, count, avg, min, max).
+ * Can also be a function which accepts two arguments (an array of Records to aggregate, and the measure
+ * to aggregate them on) and should return a String.
+ */
+ aggregator: 'sum',
+
+ /**
+ * @cfg {Function} renderer Optional renderer to pass values through before they are rendered to the dom. This
+ * gives an opportunity to modify cell contents after the value has been computed.
+ */
+ renderer: undefined,
+
+ /**
+ * @cfg {String} measure The field to extract from each Record when pivoting around the two axes. See the class
+ * introduction docs for usage
+ */
+
+ /**
+ * @cfg {Array|Ext.grid.PivotAxis} leftAxis Either and array of {@link #dimension} to use on the left axis, or
+ * a {@link Ext.grid.PivotAxis} instance. If an array is passed, it is turned into a PivotAxis internally.
+ */
+
+ /**
+ * @cfg {Array|Ext.grid.PivotAxis} topAxis Either and array of {@link #dimension} to use on the top axis, or
+ * a {@link Ext.grid.PivotAxis} instance. If an array is passed, it is turned into a PivotAxis internally.
+ */
+
+ //inherit docs
+ initComponent: function() {
+ Ext.grid.PivotGrid.superclass.initComponent.apply(this, arguments);
+
+ this.initAxes();
+
+ //no resizing of columns is allowed yet in PivotGrid
+ this.enableColumnResize = false;
+
+ this.viewConfig = Ext.apply(this.viewConfig || {}, {
+ forceFit: true
+ });
+
+ //TODO: dummy col model that is never used - GridView is too tightly integrated with ColumnModel
+ //in 3.x to remove this altogether.
+ this.colModel = new Ext.grid.ColumnModel({});
+ },
+
+ /**
+ * Returns the function currently used to aggregate the records in each Pivot cell
+ * @return {Function} The current aggregator function
+ */
+ getAggregator: function() {
+ if (typeof this.aggregator == 'string') {
+ return Ext.grid.PivotAggregatorMgr.types[this.aggregator];
+ } else {
+ return this.aggregator;
+ }
+ },
+
+ /**
+ * Sets the function to use when aggregating data for each cell.
+ * @param {String|Function} aggregator The new aggregator function or named function string
+ */
+ setAggregator: function(aggregator) {
+ this.aggregator = aggregator;
+ },
+
+ /**
+ * Sets the field name to use as the Measure in this Pivot Grid
+ * @param {String} measure The field to make the measure
+ */
+ setMeasure: function(measure) {
+ this.measure = measure;
+ },
+
+ /**
+ * Sets the left axis of this pivot grid. Optionally refreshes the grid afterwards.
+ * @param {Ext.grid.PivotAxis} axis The pivot axis
+ * @param {Boolean} refresh True to immediately refresh the grid and its axes (defaults to false)
+ */
+ setLeftAxis: function(axis, refresh) {
+ /**
+ * The configured {@link Ext.grid.PivotAxis} used as the left Axis for this Pivot Grid
+ * @property leftAxis
+ * @type Ext.grid.PivotAxis
+ */
+ this.leftAxis = axis;
+
+ if (refresh) {
+ this.view.refresh();
+ }
+ },
+
+ /**
+ * Sets the top axis of this pivot grid. Optionally refreshes the grid afterwards.
+ * @param {Ext.grid.PivotAxis} axis The pivot axis
+ * @param {Boolean} refresh True to immediately refresh the grid and its axes (defaults to false)
+ */
+ setTopAxis: function(axis, refresh) {
+ /**
+ * The configured {@link Ext.grid.PivotAxis} used as the top Axis for this Pivot Grid
+ * @property topAxis
+ * @type Ext.grid.PivotAxis
+ */
+ this.topAxis = axis;
+
+ if (refresh) {
+ this.view.refresh();
+ }
+ },
+
+ /**
+ * @private
+ * Creates the top and left axes. Should usually only need to be called once from initComponent
+ */
+ initAxes: function() {
+ var PivotAxis = Ext.grid.PivotAxis;
+
+ if (!(this.leftAxis instanceof PivotAxis)) {
+ this.setLeftAxis(new PivotAxis({
+ orientation: 'vertical',
+ dimensions : this.leftAxis || [],
+ store : this.store
+ }));
+ };
+
+ if (!(this.topAxis instanceof PivotAxis)) {
+ this.setTopAxis(new PivotAxis({
+ orientation: 'horizontal',
+ dimensions : this.topAxis || [],
+ store : this.store
+ }));
+ };
+ },
+
+ /**
+ * @private
+ * @return {Array} 2-dimensional array of cell data
+ */
+ extractData: function() {
+ var records = this.store.data.items,
+ recCount = records.length,
+ cells = [],
+ record, i, j, k;
+
+ if (recCount == 0) {
+ return [];
+ }
+
+ var leftTuples = this.leftAxis.getTuples(),
+ leftCount = leftTuples.length,
+ topTuples = this.topAxis.getTuples(),
+ topCount = topTuples.length,
+ aggregator = this.getAggregator();
+
+ for (i = 0; i < recCount; i++) {
+ record = records[i];
+
+ for (j = 0; j < leftCount; j++) {
+ cells[j] = cells[j] || [];
+
+ if (leftTuples[j].matcher(record) === true) {
+ for (k = 0; k < topCount; k++) {
+ cells[j][k] = cells[j][k] || [];
+
+ if (topTuples[k].matcher(record)) {
+ cells[j][k].push(record);
+ }
+ }
+ }
+ }
+ }
+
+ var rowCount = cells.length,
+ colCount, row;
+
+ for (i = 0; i < rowCount; i++) {
+ row = cells[i];
+ colCount = row.length;
+
+ for (j = 0; j < colCount; j++) {
+ cells[i][j] = aggregator(cells[i][j], this.measure);
+ }
+ }
+
+ return cells;
+ },
+
+ /**
+ * Returns the grid's GridView object.
+ * @return {Ext.grid.PivotGridView} The grid view
+ */
+ getView: function() {
+ if (!this.view) {
+ this.view = new Ext.grid.PivotGridView(this.viewConfig);
+ }
+
+ return this.view;
+ }
+});
+
+Ext.reg('pivotgrid', Ext.grid.PivotGrid);
+
+
+Ext.grid.PivotAggregatorMgr = new Ext.AbstractManager();
+
+Ext.grid.PivotAggregatorMgr.registerType('sum', function(records, measure) {
+ var length = records.length,
+ total = 0,
+ i;
+
+ for (i = 0; i < length; i++) {
+ total += records[i].get(measure);
+ }
+
+ return total;
+});
+
+Ext.grid.PivotAggregatorMgr.registerType('avg', function(records, measure) {
+ var length = records.length,
+ total = 0,
+ i;
+
+ for (i = 0; i < length; i++) {
+ total += records[i].get(measure);
+ }
+
+ return (total / length) || 'n/a';
+});
+
+Ext.grid.PivotAggregatorMgr.registerType('min', function(records, measure) {
+ var data = [],
+ length = records.length,
+ i;
+
+ for (i = 0; i < length; i++) {
+ data.push(records[i].get(measure));
+ }
+
+ return Math.min.apply(this, data) || 'n/a';
+});
+
+Ext.grid.PivotAggregatorMgr.registerType('max', function(records, measure) {
+ var data = [],
+ length = records.length,
+ i;
+
+ for (i = 0; i < length; i++) {
+ data.push(records[i].get(measure));
+ }
+
+ return Math.max.apply(this, data) || 'n/a';
+});
+
+Ext.grid.PivotAggregatorMgr.registerType('count', function(records, measure) {
+ return records.length;
+});/**
* @class Ext.grid.GridView
* @extends Ext.util.Observable
* <p>This class encapsulates the user interface of an {@link Ext.grid.GridPanel}.
/**
* @cfg {Boolean} forceFit
- * Defaults to <tt>false</tt>. Specify <tt>true</tt> to have the column widths re-proportioned
- * at <b>all times</b>. The {@link Ext.grid.Column#width initially configured width}</tt> of each
+ * <p>Defaults to <tt>false</tt>. Specify <tt>true</tt> to have the column widths re-proportioned
+ * at <b>all times</b>.</p>
+ * <p>The {@link Ext.grid.Column#width initially configured width}</tt> of each
* column will be adjusted to fit the grid width and prevent horizontal scrolling. If columns are
* later resized (manually or programmatically), the other columns in the grid <b>will</b> be resized
- * to fit the grid width. See <tt>{@link #autoFill}</tt> also.
+ * to fit the grid width.</p>
+ * <p>Columns which are configured with <code>fixed: true</code> are omitted from being resized.</p>
+ * <p>See <tt>{@link #autoFill}</tt>.</p>
*/
forceFit : false,
borderWidth : 2,
tdClass : 'x-grid3-cell',
hdCls : 'x-grid3-hd',
+
+
+ /**
+ * @cfg {Boolean} markDirty True to show the dirty cell indicator when a cell has been modified. Defaults to <tt>true</tt>.
+ */
markDirty : true,
/**
* @cfg {Number} cellSelectorDepth The number of levels to search for cells in event delegation (defaults to <tt>4</tt>)
*/
cellSelectorDepth : 4,
+
/**
* @cfg {Number} rowSelectorDepth The number of levels to search for rows in event delegation (defaults to <tt>10</tt>)
*/
* @cfg {String} cellSelector The selector used to find cells internally (defaults to <tt>'td.x-grid3-cell'</tt>)
*/
cellSelector : 'td.x-grid3-cell',
+
/**
* @cfg {String} rowSelector The selector used to find rows internally (defaults to <tt>'div.x-grid3-row'</tt>)
*/
firstRowCls: 'x-grid3-row-first',
lastRowCls: 'x-grid3-row-last',
rowClsRe: /(?:^|\s+)x-grid3-row-(first|last|alt)(?:\s+|$)/g,
+
+ /**
+ * @cfg {String} headerMenuOpenCls The CSS class to add to the header cell when its menu is visible. Defaults to 'x-grid3-hd-menu-open'
+ */
+ headerMenuOpenCls: 'x-grid3-hd-menu-open',
+
+ /**
+ * @cfg {String} rowOverCls The CSS class added to each row when it is hovered over. Defaults to 'x-grid3-row-over'
+ */
+ rowOverCls: 'x-grid3-row-over',
- constructor : function(config){
+ constructor : function(config) {
Ext.apply(this, config);
+
// These events are only used internally by the grid components
this.addEvents(
/**
* @param {Ext.data.Record} record The Record to be removed
*/
'beforerowremoved',
+
/**
* @event beforerowsinserted
* Internal UI Event. Fired before rows are inserted.
* @param {Number} lastRow The index of the last row to be inserted.
*/
'beforerowsinserted',
+
/**
* @event beforerefresh
* Internal UI Event. Fired before the view is refreshed.
* @param {Ext.grid.GridView} view
*/
'beforerefresh',
+
/**
* @event rowremoved
* Internal UI Event. Fired after a row is removed.
* @param {Ext.data.Record} record The Record that was removed
*/
'rowremoved',
+
/**
* @event rowsinserted
* Internal UI Event. Fired after rows are inserted.
* @param {Number} lastRow The index of the last row inserted.
*/
'rowsinserted',
+
/**
* @event rowupdated
* Internal UI Event. Fired after a row has been updated.
* @param {Ext.data.record} record The Record backing the row updated.
*/
'rowupdated',
+
/**
* @event refresh
* Internal UI Event. Fired after the GridView's body has been refreshed.
*/
'refresh'
);
+
Ext.grid.GridView.superclass.constructor.call(this);
},
/* -------------------------------- UI Specific ----------------------------- */
-
- // private
- initTemplates : function(){
- var ts = this.templates || {};
- if(!ts.master){
- ts.master = new Ext.Template(
- '<div class="x-grid3" hidefocus="true">',
- '<div class="x-grid3-viewport">',
- '<div class="x-grid3-header"><div class="x-grid3-header-inner"><div class="x-grid3-header-offset" style="{ostyle}">{header}</div></div><div class="x-clear"></div></div>',
- '<div class="x-grid3-scroller"><div class="x-grid3-body" style="{bstyle}">{body}</div><a href="#" class="x-grid3-focus" tabIndex="-1"></a></div>',
+
+ /**
+ * The master template to use when rendering the GridView. Has a default template
+ * @property Ext.Template
+ * @type masterTpl
+ */
+ masterTpl: new Ext.Template(
+ '<div class="x-grid3" hidefocus="true">',
+ '<div class="x-grid3-viewport">',
+ '<div class="x-grid3-header">',
+ '<div class="x-grid3-header-inner">',
+ '<div class="x-grid3-header-offset" style="{ostyle}">{header}</div>',
'</div>',
- '<div class="x-grid3-resize-marker"> </div>',
- '<div class="x-grid3-resize-proxy"> </div>',
- '</div>'
- );
- }
-
- if(!ts.header){
- ts.header = new Ext.Template(
- '<table border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',
- '<thead><tr class="x-grid3-hd-row">{cells}</tr></thead>',
+ '<div class="x-clear"></div>',
+ '</div>',
+ '<div class="x-grid3-scroller">',
+ '<div class="x-grid3-body" style="{bstyle}">{body}</div>',
+ '<a href="#" class="x-grid3-focus" tabIndex="-1"></a>',
+ '</div>',
+ '</div>',
+ '<div class="x-grid3-resize-marker"> </div>',
+ '<div class="x-grid3-resize-proxy"> </div>',
+ '</div>'
+ ),
+
+ /**
+ * The template to use when rendering headers. Has a default template
+ * @property headerTpl
+ * @type Ext.Template
+ */
+ headerTpl: new Ext.Template(
+ '<table border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',
+ '<thead>',
+ '<tr class="x-grid3-hd-row">{cells}</tr>',
+ '</thead>',
+ '</table>'
+ ),
+
+ /**
+ * The template to use when rendering the body. Has a default template
+ * @property bodyTpl
+ * @type Ext.Template
+ */
+ bodyTpl: new Ext.Template('{rows}'),
+
+ /**
+ * The template to use to render each cell. Has a default template
+ * @property cellTpl
+ * @type Ext.Template
+ */
+ cellTpl: new Ext.Template(
+ '<td class="x-grid3-col x-grid3-cell x-grid3-td-{id} {css}" style="{style}" tabIndex="0" {cellAttr}>',
+ '<div class="x-grid3-cell-inner x-grid3-col-{id}" unselectable="on" {attr}>{value}</div>',
+ '</td>'
+ ),
+
+ /**
+ * @private
+ * Provides default templates if they are not given for this particular instance. Most of the templates are defined on
+ * the prototype, the ones defined inside this function are done so because they are based on Grid or GridView configuration
+ */
+ initTemplates : function() {
+ var templates = this.templates || {},
+ template, name,
+
+ headerCellTpl = new Ext.Template(
+ '<td class="x-grid3-hd x-grid3-cell x-grid3-td-{id} {css}" style="{style}">',
+ '<div {tooltip} {attr} class="x-grid3-hd-inner x-grid3-hd-{id}" unselectable="on" style="{istyle}">',
+ this.grid.enableHdMenu ? '<a class="x-grid3-hd-btn" href="#"></a>' : '',
+ '{value}',
+ '<img alt="" class="x-grid3-sort-icon" src="', Ext.BLANK_IMAGE_URL, '" />',
+ '</div>',
+ '</td>'
+ ),
+
+ rowBodyText = [
+ '<tr class="x-grid3-row-body-tr" style="{bodyStyle}">',
+ '<td colspan="{cols}" class="x-grid3-body-cell" tabIndex="0" hidefocus="on">',
+ '<div class="x-grid3-row-body">{body}</div>',
+ '</td>',
+ '</tr>'
+ ].join(""),
+
+ innerText = [
+ '<table class="x-grid3-row-table" border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',
+ '<tbody>',
+ '<tr>{cells}</tr>',
+ this.enableRowBody ? rowBodyText : '',
+ '</tbody>',
'</table>'
- );
- }
-
- if(!ts.hcell){
- ts.hcell = new Ext.Template(
- '<td class="x-grid3-hd x-grid3-cell x-grid3-td-{id} {css}" style="{style}"><div {tooltip} {attr} class="x-grid3-hd-inner x-grid3-hd-{id}" unselectable="on" style="{istyle}">', this.grid.enableHdMenu ? '<a class="x-grid3-hd-btn" href="#"></a>' : '',
- '{value}<img class="x-grid3-sort-icon" src="', Ext.BLANK_IMAGE_URL, '" />',
- '</div></td>'
- );
- }
-
- if(!ts.body){
- ts.body = new Ext.Template('{rows}');
- }
-
- if(!ts.row){
- ts.row = new Ext.Template(
- '<div class="x-grid3-row {alt}" style="{tstyle}"><table class="x-grid3-row-table" border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',
- '<tbody><tr>{cells}</tr>',
- (this.enableRowBody ? '<tr class="x-grid3-row-body-tr" style="{bodyStyle}"><td colspan="{cols}" class="x-grid3-body-cell" tabIndex="0" hidefocus="on"><div class="x-grid3-row-body">{body}</div></td></tr>' : ''),
- '</tbody></table></div>'
- );
- }
-
- if(!ts.cell){
- ts.cell = new Ext.Template(
- '<td class="x-grid3-col x-grid3-cell x-grid3-td-{id} {css}" style="{style}" tabIndex="0" {cellAttr}>',
- '<div class="x-grid3-cell-inner x-grid3-col-{id}" unselectable="on" {attr}>{value}</div>',
- '</td>'
- );
- }
+ ].join("");
+
+ Ext.applyIf(templates, {
+ hcell : headerCellTpl,
+ cell : this.cellTpl,
+ body : this.bodyTpl,
+ header : this.headerTpl,
+ master : this.masterTpl,
+ row : new Ext.Template('<div class="x-grid3-row {alt}" style="{tstyle}">' + innerText + '</div>'),
+ rowInner: new Ext.Template(innerText)
+ });
- for(var k in ts){
- var t = ts[k];
- if(t && Ext.isFunction(t.compile) && !t.compiled){
- t.disableFormats = true;
- t.compile();
+ for (name in templates) {
+ template = templates[name];
+
+ if (template && Ext.isFunction(template.compile) && !template.compiled) {
+ template.disableFormats = true;
+ template.compile();
}
}
- this.templates = ts;
+ this.templates = templates;
this.colRe = new RegExp('x-grid3-td-([^\\s]+)', '');
},
- // private
- fly : function(el){
- if(!this._flyweight){
+ /**
+ * @private
+ * Each GridView has its own private flyweight, accessed through this method
+ */
+ fly : function(el) {
+ if (!this._flyweight) {
this._flyweight = new Ext.Element.Flyweight(document.body);
}
this._flyweight.dom = el;
},
// private
- getEditorParent : function(){
+ getEditorParent : function() {
return this.scroller.dom;
},
- // private
- initElements : function(){
- var E = Ext.Element;
-
- var el = this.grid.getGridEl().dom.firstChild;
- var cs = el.childNodes;
-
- this.el = new E(el);
-
- this.mainWrap = new E(cs[0]);
- this.mainHd = new E(this.mainWrap.dom.firstChild);
-
- if(this.grid.hideHeaders){
- this.mainHd.setDisplayed(false);
+ /**
+ * @private
+ * Finds and stores references to important elements
+ */
+ initElements : function() {
+ var Element = Ext.Element,
+ el = Ext.get(this.grid.getGridEl().dom.firstChild),
+ mainWrap = new Element(el.child('div.x-grid3-viewport')),
+ mainHd = new Element(mainWrap.child('div.x-grid3-header')),
+ scroller = new Element(mainWrap.child('div.x-grid3-scroller'));
+
+ if (this.grid.hideHeaders) {
+ mainHd.setDisplayed(false);
}
-
- this.innerHd = this.mainHd.dom.firstChild;
- this.scroller = new E(this.mainWrap.dom.childNodes[1]);
- if(this.forceFit){
- this.scroller.setStyle('overflow-x', 'hidden');
+
+ if (this.forceFit) {
+ scroller.setStyle('overflow-x', 'hidden');
}
+
/**
* <i>Read-only</i>. The GridView's body Element which encapsulates all rows in the Grid.
* This {@link Ext.Element Element} is only available after the GridPanel has been rendered.
* @type Ext.Element
* @property mainBody
*/
- this.mainBody = new E(this.scroller.dom.firstChild);
-
- this.focusEl = new E(this.scroller.dom.childNodes[1]);
+
+ Ext.apply(this, {
+ el : el,
+ mainWrap: mainWrap,
+ scroller: scroller,
+ mainHd : mainHd,
+ innerHd : mainHd.child('div.x-grid3-header-inner').dom,
+ mainBody: new Element(Element.fly(scroller).child('div.x-grid3-body')),
+ focusEl : new Element(Element.fly(scroller).child('a')),
+
+ resizeMarker: new Element(el.child('div.x-grid3-resize-marker')),
+ resizeProxy : new Element(el.child('div.x-grid3-resize-proxy'))
+ });
+
this.focusEl.swallowEvent('click', true);
-
- this.resizeMarker = new E(cs[1]);
- this.resizeProxy = new E(cs[2]);
},
// private
- getRows : function(){
+ getRows : function() {
return this.hasRows() ? this.mainBody.dom.childNodes : [];
},
// finder methods, used with delegation
// private
- findCell : function(el){
- if(!el){
+ findCell : function(el) {
+ if (!el) {
return false;
}
return this.fly(el).findParent(this.cellSelector, this.cellSelectorDepth);
* @param {HTMLElement} el The target element
* @return {Number} The column index, or <b>false</b> if the target element is not within a row of this GridView.
*/
- findCellIndex : function(el, requiredCls){
- var cell = this.findCell(el);
- if(cell && (!requiredCls || this.fly(cell).hasClass(requiredCls))){
- return this.getCellIndex(cell);
+ findCellIndex : function(el, requiredCls) {
+ var cell = this.findCell(el),
+ hasCls;
+
+ if (cell) {
+ hasCls = this.fly(cell).hasClass(requiredCls);
+ if (!requiredCls || hasCls) {
+ return this.getCellIndex(cell);
+ }
}
return false;
},
// private
- getCellIndex : function(el){
- if(el){
- var m = el.className.match(this.colRe);
- if(m && m[1]){
- return this.cm.getIndexById(m[1]);
+ getCellIndex : function(el) {
+ if (el) {
+ var match = el.className.match(this.colRe);
+
+ if (match && match[1]) {
+ return this.cm.getIndexById(match[1]);
}
}
return false;
},
// private
- findHeaderCell : function(el){
+ findHeaderCell : function(el) {
var cell = this.findCell(el);
return cell && this.fly(cell).hasClass(this.hdCls) ? cell : null;
},
* @param {HTMLElement} el The target HTMLElement
* @return {HTMLElement} The row element, or null if the target element is not within a row of this GridView.
*/
- findRow : function(el){
- if(!el){
+ findRow : function(el) {
+ if (!el) {
return false;
}
return this.fly(el).findParent(this.rowSelector, this.rowSelectorDepth);
},
/**
- * <p>Return the index of the grid row which contains the passed HTMLElement.</p>
+ * Return the index of the grid row which contains the passed HTMLElement.
* See also {@link #findCellIndex}
* @param {HTMLElement} el The target HTMLElement
* @return {Number} The row index, or <b>false</b> if the target element is not within a row of this GridView.
*/
- findRowIndex : function(el){
- var r = this.findRow(el);
- return r ? r.rowIndex : false;
+ findRowIndex : function(el) {
+ var row = this.findRow(el);
+ return row ? row.rowIndex : false;
},
/**
* @param {HTMLElement} el The target HTMLElement
* @return {HTMLElement} The row body element, or null if the target element is not within a row body of this GridView.
*/
- findRowBody : function(el){
- if(!el){
+ findRowBody : function(el) {
+ if (!el) {
return false;
}
+
return this.fly(el).findParent(this.rowBodySelector, this.rowBodySelectorDepth);
},
* @param {Number} index The row index
* @return {HtmlElement} The div element.
*/
- getRow : function(row){
+ getRow : function(row) {
return this.getRows()[row];
},
* @param {Number} col The column index of the cell.
* @return {HtmlElement} The td at the specified coordinates.
*/
- getCell : function(row, col){
- return this.getRow(row).getElementsByTagName('td')[col];
+ getCell : function(row, col) {
+ return Ext.fly(this.getRow(row)).query(this.cellSelector)[col];
},
/**
* @param {Number} index The column index
* @return {HtmlElement} The td element.
*/
- getHeaderCell : function(index){
+ getHeaderCell : function(index) {
return this.mainHd.dom.getElementsByTagName('td')[index];
},
// manipulating elements
// private - use getRowClass to apply custom row classes
- addRowClass : function(row, cls){
- var r = this.getRow(row);
- if(r){
- this.fly(r).addClass(cls);
+ addRowClass : function(rowId, cls) {
+ var row = this.getRow(rowId);
+ if (row) {
+ this.fly(row).addClass(cls);
}
},
// private
- removeRowClass : function(row, cls){
+ removeRowClass : function(row, cls) {
var r = this.getRow(row);
if(r){
this.fly(r).removeClass(cls);
},
// private
- removeRow : function(row){
+ removeRow : function(row) {
Ext.removeNode(this.getRow(row));
this.syncFocusEl(row);
},
// private
- removeRows : function(firstRow, lastRow){
- var bd = this.mainBody.dom;
- for(var rowIndex = firstRow; rowIndex <= lastRow; rowIndex++){
+ removeRows : function(firstRow, lastRow) {
+ var bd = this.mainBody.dom,
+ rowIndex;
+
+ for (rowIndex = firstRow; rowIndex <= lastRow; rowIndex++){
Ext.removeNode(bd.childNodes[firstRow]);
}
+
this.syncFocusEl(firstRow);
},
- // scrolling stuff
-
+ /* ----------------------------------- Scrolling functions -------------------------------------------*/
+
// private
- getScrollState : function(){
+ getScrollState : function() {
var sb = this.scroller.dom;
- return {left: sb.scrollLeft, top: sb.scrollTop};
+
+ return {
+ left: sb.scrollLeft,
+ top : sb.scrollTop
+ };
},
// private
- restoreScroll : function(state){
+ restoreScroll : function(state) {
var sb = this.scroller.dom;
sb.scrollLeft = state.left;
- sb.scrollTop = state.top;
+ sb.scrollTop = state.top;
},
/**
* Scrolls the grid to the top
*/
- scrollToTop : function(){
- this.scroller.dom.scrollTop = 0;
- this.scroller.dom.scrollLeft = 0;
+ scrollToTop : function() {
+ var dom = this.scroller.dom;
+
+ dom.scrollTop = 0;
+ dom.scrollLeft = 0;
},
// private
- syncScroll : function(){
+ syncScroll : function() {
this.syncHeaderScroll();
var mb = this.scroller.dom;
this.grid.fireEvent('bodyscroll', mb.scrollLeft, mb.scrollTop);
},
// private
- syncHeaderScroll : function(){
- var mb = this.scroller.dom;
- this.innerHd.scrollLeft = mb.scrollLeft;
- this.innerHd.scrollLeft = mb.scrollLeft; // second time for IE (1/2 time first fails, other browsers ignore)
+ syncHeaderScroll : function() {
+ var innerHd = this.innerHd,
+ scrollLeft = this.scroller.dom.scrollLeft;
+
+ innerHd.scrollLeft = scrollLeft;
+ innerHd.scrollLeft = scrollLeft; // second time for IE (1/2 time first fails, other browsers ignore)
},
-
- // private
- updateSortIcon : function(col, dir){
- var sc = this.sortClasses;
- var hds = this.mainHd.select('td').removeClass(sc);
- hds.item(col).addClass(sc[dir == 'DESC' ? 1 : 0]);
+
+ /**
+ * @private
+ * Ensures the given column has the given icon class
+ */
+ updateSortIcon : function(col, dir) {
+ var sortClasses = this.sortClasses,
+ sortClass = sortClasses[dir == "DESC" ? 1 : 0],
+ headers = this.mainHd.select('td').removeClass(sortClasses);
+
+ headers.item(col).addClass(sortClass);
},
- // private
- updateAllColumnWidths : function(){
- var tw = this.getTotalWidth(),
- clen = this.cm.getColumnCount(),
- ws = [],
- len,
- i;
-
- for(i = 0; i < clen; i++){
- ws[i] = this.getColumnWidth(i);
- }
-
- this.innerHd.firstChild.style.width = this.getOffsetWidth();
- this.innerHd.firstChild.firstChild.style.width = tw;
- this.mainBody.dom.style.width = tw;
-
- for(i = 0; i < clen; i++){
- var hd = this.getHeaderCell(i);
- hd.style.width = ws[i];
+ /**
+ * @private
+ * Updates the size of every column and cell in the grid
+ */
+ updateAllColumnWidths : function() {
+ var totalWidth = this.getTotalWidth(),
+ colCount = this.cm.getColumnCount(),
+ rows = this.getRows(),
+ rowCount = rows.length,
+ widths = [],
+ row, rowFirstChild, trow, i, j;
+
+ for (i = 0; i < colCount; i++) {
+ widths[i] = this.getColumnWidth(i);
+ this.getHeaderCell(i).style.width = widths[i];
}
-
- var ns = this.getRows(), row, trow;
- for(i = 0, len = ns.length; i < len; i++){
- row = ns[i];
- row.style.width = tw;
- if(row.firstChild){
- row.firstChild.style.width = tw;
- trow = row.firstChild.rows[0];
- for (var j = 0; j < clen; j++) {
- trow.childNodes[j].style.width = ws[j];
+
+ this.updateHeaderWidth();
+
+ for (i = 0; i < rowCount; i++) {
+ row = rows[i];
+ row.style.width = totalWidth;
+ rowFirstChild = row.firstChild;
+
+ if (rowFirstChild) {
+ rowFirstChild.style.width = totalWidth;
+ trow = rowFirstChild.rows[0];
+
+ for (j = 0; j < colCount; j++) {
+ trow.childNodes[j].style.width = widths[j];
}
}
}
-
- this.onAllColumnWidthsUpdated(ws, tw);
+
+ this.onAllColumnWidthsUpdated(widths, totalWidth);
},
- // private
- updateColumnWidth : function(col, width){
- var w = this.getColumnWidth(col);
- var tw = this.getTotalWidth();
- this.innerHd.firstChild.style.width = this.getOffsetWidth();
- this.innerHd.firstChild.firstChild.style.width = tw;
- this.mainBody.dom.style.width = tw;
- var hd = this.getHeaderCell(col);
- hd.style.width = w;
-
- var ns = this.getRows(), row;
- for(var i = 0, len = ns.length; i < len; i++){
- row = ns[i];
- row.style.width = tw;
- if(row.firstChild){
- row.firstChild.style.width = tw;
- row.firstChild.rows[0].childNodes[col].style.width = w;
+ /**
+ * @private
+ * Called after a column's width has been updated, this resizes all of the cells for that column in each row
+ * @param {Number} column The column index
+ */
+ updateColumnWidth : function(column, width) {
+ var columnWidth = this.getColumnWidth(column),
+ totalWidth = this.getTotalWidth(),
+ headerCell = this.getHeaderCell(column),
+ nodes = this.getRows(),
+ nodeCount = nodes.length,
+ row, i, firstChild;
+
+ this.updateHeaderWidth();
+ headerCell.style.width = columnWidth;
+
+ for (i = 0; i < nodeCount; i++) {
+ row = nodes[i];
+ firstChild = row.firstChild;
+
+ row.style.width = totalWidth;
+ if (firstChild) {
+ firstChild.style.width = totalWidth;
+ firstChild.rows[0].childNodes[column].style.width = columnWidth;
}
}
-
- this.onColumnWidthUpdated(col, w, tw);
+
+ this.onColumnWidthUpdated(column, columnWidth, totalWidth);
},
-
- // private
- updateColumnHidden : function(col, hidden){
- var tw = this.getTotalWidth();
- this.innerHd.firstChild.style.width = this.getOffsetWidth();
- this.innerHd.firstChild.firstChild.style.width = tw;
- this.mainBody.dom.style.width = tw;
- var display = hidden ? 'none' : '';
-
- var hd = this.getHeaderCell(col);
- hd.style.display = display;
-
- var ns = this.getRows(), row;
- for(var i = 0, len = ns.length; i < len; i++){
- row = ns[i];
- row.style.width = tw;
- if(row.firstChild){
- row.firstChild.style.width = tw;
- row.firstChild.rows[0].childNodes[col].style.display = display;
+
+ /**
+ * @private
+ * Sets the hidden status of a given column.
+ * @param {Number} col The column index
+ * @param {Boolean} hidden True to make the column hidden
+ */
+ updateColumnHidden : function(col, hidden) {
+ var totalWidth = this.getTotalWidth(),
+ display = hidden ? 'none' : '',
+ headerCell = this.getHeaderCell(col),
+ nodes = this.getRows(),
+ nodeCount = nodes.length,
+ row, rowFirstChild, i;
+
+ this.updateHeaderWidth();
+ headerCell.style.display = display;
+
+ for (i = 0; i < nodeCount; i++) {
+ row = nodes[i];
+ row.style.width = totalWidth;
+ rowFirstChild = row.firstChild;
+
+ if (rowFirstChild) {
+ rowFirstChild.style.width = totalWidth;
+ rowFirstChild.rows[0].childNodes[col].style.display = display;
}
}
-
- this.onColumnHiddenUpdated(col, hidden, tw);
- delete this.lastViewWidth; // force recalc
+
+ this.onColumnHiddenUpdated(col, hidden, totalWidth);
+ delete this.lastViewWidth; //recalc
this.layout();
},
* @return {String} A string containing the HTML for the rendered rows
*/
doRender : function(columns, records, store, startRow, colCount, stripe) {
- var templates = this.templates,
+ var templates = this.templates,
cellTemplate = templates.cell,
- rowTemplate = templates.row,
- last = colCount - 1;
-
- var tstyle = 'width:' + this.getTotalWidth() + ';';
-
- // buffers
- var rowBuffer = [],
+ rowTemplate = templates.row,
+ last = colCount - 1,
+ tstyle = 'width:' + this.getTotalWidth() + ';',
+ // buffers
+ rowBuffer = [],
colBuffer = [],
rowParams = {tstyle: tstyle},
- meta = {},
+ meta = {},
+ len = records.length,
+ alt,
column,
- record;
+ record, i, j, rowIndex;
//build up each row's HTML
- for (var j = 0, len = records.length; j < len; j++) {
+ for (j = 0; j < len; j++) {
record = records[j];
colBuffer = [];
- var rowIndex = j + startRow;
+ rowIndex = j + startRow;
//build up each column's HTML
- for (var i = 0; i < colCount; i++) {
+ for (i = 0; i < colCount; i++) {
column = columns[i];
-
+
meta.id = column.id;
meta.css = i === 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '');
meta.attr = meta.cellAttr = '';
meta.value = ' ';
}
- if (this.markDirty && record.dirty && Ext.isDefined(record.modified[column.name])) {
+ if (this.markDirty && record.dirty && typeof record.modified[column.name] != 'undefined') {
meta.css += ' x-grid3-dirty-cell';
}
colBuffer[colBuffer.length] = cellTemplate.apply(meta);
}
+ alt = [];
//set up row striping and row dirtiness CSS classes
- var alt = [];
-
if (stripe && ((rowIndex + 1) % 2 === 0)) {
alt[0] = 'x-grid3-row-alt';
}
return rowBuffer.join('');
},
- // private
+ /**
+ * @private
+ * Adds CSS classes and rowIndex to each row
+ * @param {Number} startRow The row to start from (defaults to 0)
+ */
processRows : function(startRow, skipStripe) {
if (!this.ds || this.ds.getCount() < 1) {
return;
}
- var rows = this.getRows(),
- len = rows.length,
- i, r;
+ var rows = this.getRows(),
+ length = rows.length,
+ row, i;
skipStripe = skipStripe || !this.grid.stripeRows;
startRow = startRow || 0;
- for (i = 0; i<len; i++) {
- r = rows[i];
- if (r) {
- r.rowIndex = i;
+ for (i = 0; i < length; i++) {
+ row = rows[i];
+ if (row) {
+ row.rowIndex = i;
if (!skipStripe) {
- r.className = r.className.replace(this.rowClsRe, ' ');
+ row.className = row.className.replace(this.rowClsRe, ' ');
if ((i + 1) % 2 === 0){
- r.className += ' x-grid3-row-alt';
+ row.className += ' x-grid3-row-alt';
}
}
}
Ext.fly(rows[0]).addClass(this.firstRowCls);
}
- Ext.fly(rows[rows.length - 1]).addClass(this.lastRowCls);
+ Ext.fly(rows[length - 1]).addClass(this.lastRowCls);
},
-
- afterRender : function(){
- if(!this.ds || !this.cm){
+
+ /**
+ * @private
+ */
+ afterRender : function() {
+ if (!this.ds || !this.cm) {
return;
}
- this.mainBody.dom.innerHTML = this.renderRows() || ' ';
+
+ this.mainBody.dom.innerHTML = this.renderBody() || ' ';
this.processRows(0, true);
- if(this.deferEmptyText !== true){
+ if (this.deferEmptyText !== true) {
this.applyEmptyText();
}
+
this.grid.fireEvent('viewready', this.grid);
},
-
+
/**
* @private
- * Renders each of the UI elements in turn. This is called internally, once, by this.render. It does not
- * render rows from the store, just the surrounding UI elements. It also sets up listeners on the UI elements
+ * This is always intended to be called after renderUI. Sets up listeners on the UI elements
* and sets up options like column menus, moving and resizing.
*/
- renderUI : function() {
- var templates = this.templates,
- header = this.renderHeaders(),
- body = templates.body.apply({rows:' '});
-
- var html = templates.master.apply({
- body : body,
- header: header,
- ostyle: 'width:' + this.getOffsetWidth() + ';',
- bstyle: 'width:' + this.getTotalWidth() + ';'
- });
-
- var g = this.grid;
-
- g.getGridEl().dom.innerHTML = html;
-
+ afterRenderUI: function() {
+ var grid = this.grid;
+
this.initElements();
// get mousedowns early
});
this.scroller.on('scroll', this.syncScroll, this);
- if (g.enableColumnResize !== false) {
- this.splitZone = new Ext.grid.GridView.SplitDragZone(g, this.mainHd.dom);
+
+ if (grid.enableColumnResize !== false) {
+ this.splitZone = new Ext.grid.GridView.SplitDragZone(grid, this.mainHd.dom);
}
- if (g.enableColumnMove) {
- this.columnDrag = new Ext.grid.GridView.ColumnDragZone(g, this.innerHd);
- this.columnDrop = new Ext.grid.HeaderDropZone(g, this.mainHd.dom);
+ if (grid.enableColumnMove) {
+ this.columnDrag = new Ext.grid.GridView.ColumnDragZone(grid, this.innerHd);
+ this.columnDrop = new Ext.grid.HeaderDropZone(grid, this.mainHd.dom);
}
- if (g.enableHdMenu !== false) {
- this.hmenu = new Ext.menu.Menu({id: g.id + '-hctx'});
+ if (grid.enableHdMenu !== false) {
+ this.hmenu = new Ext.menu.Menu({id: grid.id + '-hctx'});
this.hmenu.add(
{itemId:'asc', text: this.sortAscText, cls: 'xg-hmenu-sort-asc'},
{itemId:'desc', text: this.sortDescText, cls: 'xg-hmenu-sort-desc'}
);
- if (g.enableColumnHide !== false) {
- this.colMenu = new Ext.menu.Menu({id:g.id + '-hcols-menu'});
+ if (grid.enableColumnHide !== false) {
+ this.colMenu = new Ext.menu.Menu({id:grid.id + '-hcols-menu'});
this.colMenu.on({
scope : this,
beforeshow: this.beforeColMenuShow,
this.hmenu.on('itemclick', this.handleHdMenuClick, this);
}
- if (g.trackMouseOver) {
+ if (grid.trackMouseOver) {
this.mainBody.on({
scope : this,
mouseover: this.onRowOver,
});
}
- if (g.enableDragDrop || g.enableDrag) {
- this.dragZone = new Ext.grid.GridDragZone(g, {
- ddGroup : g.ddGroup || 'GridDD'
+ if (grid.enableDragDrop || grid.enableDrag) {
+ this.dragZone = new Ext.grid.GridDragZone(grid, {
+ ddGroup : grid.ddGroup || 'GridDD'
});
}
this.updateHeaderSortState();
},
+ /**
+ * @private
+ * Renders each of the UI elements in turn. This is called internally, once, by this.render. It does not
+ * render rows from the store, just the surrounding UI elements.
+ */
+ renderUI : function() {
+ var templates = this.templates;
+
+ return templates.master.apply({
+ body : templates.body.apply({rows:' '}),
+ header: this.renderHeaders(),
+ ostyle: 'width:' + this.getOffsetWidth() + ';',
+ bstyle: 'width:' + this.getTotalWidth() + ';'
+ });
+ },
+
// private
processEvent : function(name, e) {
- var t = e.getTarget(),
- g = this.grid,
- header = this.findHeaderIndex(t);
- g.fireEvent(name, e);
+ var target = e.getTarget(),
+ grid = this.grid,
+ header = this.findHeaderIndex(target),
+ row, cell, col, body;
+
+ grid.fireEvent(name, e);
+
if (header !== false) {
- g.fireEvent('header' + name, g, header, e);
+ grid.fireEvent('header' + name, grid, header, e);
} else {
- var row = this.findRowIndex(t),
- cell,
- body;
+ row = this.findRowIndex(target);
+
+// Grid's value-added events must bubble correctly to allow cancelling via returning false: cell->column->row
+// We must allow a return of false at any of these levels to cancel the event processing.
+// Particularly allowing rowmousedown to be cancellable by prior handlers which need to prevent selection.
if (row !== false) {
- g.fireEvent('row' + name, g, row, e);
- cell = this.findCellIndex(t);
+ cell = this.findCellIndex(target);
if (cell !== false) {
- g.fireEvent('cell' + name, g, row, cell, e);
+ col = grid.colModel.getColumnAt(cell);
+ if (grid.fireEvent('cell' + name, grid, row, cell, e) !== false) {
+ if (!col || (col.processEvent && (col.processEvent(name, e, grid, row, cell) !== false))) {
+ grid.fireEvent('row' + name, grid, row, e);
+ }
+ }
} else {
- body = this.findRowBody(t);
- if (body) {
- g.fireEvent('rowbody' + name, g, row, e);
+ if (grid.fireEvent('row' + name, grid, row, e) !== false) {
+ (body = this.findRowBody(target)) && grid.fireEvent('rowbody' + name, grid, row, e);
}
}
} else {
- g.fireEvent('container' + name, g, e);
+ grid.fireEvent('container' + name, grid, e);
}
}
},
- // private
- layout : function() {
- if(!this.mainBody){
+ /**
+ * @private
+ * Sizes the grid's header and body elements
+ */
+ layout : function(initial) {
+ if (!this.mainBody) {
return; // not rendered
}
- var g = this.grid;
- var c = g.getGridEl();
- var csize = c.getSize(true);
- var vw = csize.width;
- if(!g.hideHeaders && (vw < 20 || csize.height < 20)){ // display: none?
+ var grid = this.grid,
+ gridEl = grid.getGridEl(),
+ gridSize = gridEl.getSize(true),
+ gridWidth = gridSize.width,
+ gridHeight = gridSize.height,
+ scroller = this.scroller,
+ scrollStyle, headerHeight, scrollHeight;
+
+ if (gridWidth < 20 || gridHeight < 20) {
return;
}
-
- if(g.autoHeight){
- this.scroller.dom.style.overflow = 'visible';
- if(Ext.isWebKit){
- this.scroller.dom.style.position = 'static';
+
+ if (grid.autoHeight) {
+ scrollStyle = scroller.dom.style;
+ scrollStyle.overflow = 'visible';
+
+ if (Ext.isWebKit) {
+ scrollStyle.position = 'static';
}
- }else{
- this.el.setSize(csize.width, csize.height);
-
- var hdHeight = this.mainHd.getHeight();
- var vh = csize.height - (hdHeight);
-
- this.scroller.setSize(vw, vh);
- if(this.innerHd){
- this.innerHd.style.width = (vw)+'px';
+ } else {
+ this.el.setSize(gridWidth, gridHeight);
+
+ headerHeight = this.mainHd.getHeight();
+ scrollHeight = gridHeight - headerHeight;
+
+ scroller.setSize(gridWidth, scrollHeight);
+
+ if (this.innerHd) {
+ this.innerHd.style.width = (gridWidth) + "px";
}
}
- if(this.forceFit){
- if(this.lastViewWidth != vw){
+
+ if (this.forceFit || (initial === true && this.autoFill)) {
+ if (this.lastViewWidth != gridWidth) {
this.fitColumns(false, false);
- this.lastViewWidth = vw;
+ this.lastViewWidth = gridWidth;
}
- }else {
+ } else {
this.autoExpand();
this.syncHeaderScroll();
}
- this.onLayout(vw, vh);
+
+ this.onLayout(gridWidth, scrollHeight);
},
// template functions for subclasses and plugins
// these functions include precalculated values
- onLayout : function(vw, vh){
+ onLayout : function(vw, vh) {
// do nothing
},
- onColumnWidthUpdated : function(col, w, tw){
+ onColumnWidthUpdated : function(col, w, tw) {
//template method
},
- onAllColumnWidthsUpdated : function(ws, tw){
+ onAllColumnWidthsUpdated : function(ws, tw) {
//template method
},
- onColumnHiddenUpdated : function(col, hidden, tw){
+ onColumnHiddenUpdated : function(col, hidden, tw) {
// template method
},
- updateColumnText : function(col, text){
+ updateColumnText : function(col, text) {
// template method
},
- afterMove : function(colIndex){
+ afterMove : function(colIndex) {
// template method
},
/* ----------------------------------- Core Specific -------------------------------------------*/
// private
- init : function(grid){
+ init : function(grid) {
this.grid = grid;
this.initTemplates();
// private
getColumnId : function(index){
- return this.cm.getColumnId(index);
+ return this.cm.getColumnId(index);
},
// private
return (this.cm.getTotalWidth() + this.getScrollOffset()) + 'px';
},
- getScrollOffset: function(){
+ // private
+ getScrollOffset: function() {
return Ext.num(this.scrollOffset, Ext.getScrollBarWidth());
},
* @return {String} Rendered header row
*/
renderHeaders : function() {
- var cm = this.cm,
- ts = this.templates,
- ct = ts.hcell,
- cb = [],
- p = {},
- len = cm.getColumnCount(),
- last = len - 1;
-
- for (var i = 0; i < len; i++) {
- p.id = cm.getColumnId(i);
- p.value = cm.getColumnHeader(i) || '';
- p.style = this.getColumnStyle(i, true);
- p.tooltip = this.getColumnTooltip(i);
- p.css = i === 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '');
-
- if (cm.config[i].align == 'right') {
- p.istyle = 'padding-right:16px';
+ var colModel = this.cm,
+ templates = this.templates,
+ headerTpl = templates.hcell,
+ properties = {},
+ colCount = colModel.getColumnCount(),
+ last = colCount - 1,
+ cells = [],
+ i, cssCls;
+
+ for (i = 0; i < colCount; i++) {
+ if (i == 0) {
+ cssCls = 'x-grid3-cell-first ';
} else {
- delete p.istyle;
+ cssCls = i == last ? 'x-grid3-cell-last ' : '';
}
- cb[cb.length] = ct.apply(p);
+
+ properties = {
+ id : colModel.getColumnId(i),
+ value : colModel.getColumnHeader(i) || '',
+ style : this.getColumnStyle(i, true),
+ css : cssCls,
+ tooltip: this.getColumnTooltip(i)
+ };
+
+ if (colModel.config[i].align == 'right') {
+ properties.istyle = 'padding-right: 16px;';
+ } else {
+ delete properties.istyle;
+ }
+
+ cells[i] = headerTpl.apply(properties);
}
- return ts.header.apply({cells: cb.join(''), tstyle:'width:'+this.getTotalWidth()+';'});
+
+ return templates.header.apply({
+ cells : cells.join(""),
+ tstyle: String.format("width: {0};", this.getTotalWidth())
+ });
},
- // private
- getColumnTooltip : function(i){
- var tt = this.cm.getColumnTooltip(i);
- if(tt){
- if(Ext.QuickTips.isEnabled()){
- return 'ext:qtip="'+tt+'"';
- }else{
- return 'title="'+tt+'"';
+ /**
+ * @private
+ */
+ getColumnTooltip : function(i) {
+ var tooltip = this.cm.getColumnTooltip(i);
+ if (tooltip) {
+ if (Ext.QuickTips.isEnabled()) {
+ return 'ext:qtip="' + tooltip + '"';
+ } else {
+ return 'title="' + tooltip + '"';
}
}
+
return '';
},
// private
- beforeUpdate : function(){
+ beforeUpdate : function() {
this.grid.stopEditing(true);
},
- // private
- updateHeaders : function(){
+ /**
+ * @private
+ * Re-renders the headers and ensures they are sized correctly
+ */
+ updateHeaders : function() {
this.innerHd.firstChild.innerHTML = this.renderHeaders();
- this.innerHd.firstChild.style.width = this.getOffsetWidth();
- this.innerHd.firstChild.firstChild.style.width = this.getTotalWidth();
+
+ this.updateHeaderWidth(false);
+ },
+
+ /**
+ * @private
+ * Ensures that the header is sized to the total width available to it
+ * @param {Boolean} updateMain True to update the mainBody's width also (defaults to true)
+ */
+ updateHeaderWidth: function(updateMain) {
+ var innerHdChild = this.innerHd.firstChild,
+ totalWidth = this.getTotalWidth();
+
+ innerHdChild.style.width = this.getOffsetWidth();
+ innerHdChild.firstChild.style.width = totalWidth;
+
+ if (updateMain !== false) {
+ this.mainBody.dom.style.width = totalWidth;
+ }
},
/**
* Focuses the specified row.
* @param {Number} row The row index
*/
- focusRow : function(row){
+ focusRow : function(row) {
this.focusCell(row, 0, false);
},
* @param {Number} row The row index
* @param {Number} col The column index
*/
- focusCell : function(row, col, hscroll){
+ focusCell : function(row, col, hscroll) {
this.syncFocusEl(this.ensureVisible(row, col, hscroll));
- if(Ext.isGecko){
- this.focusEl.focus();
- }else{
- this.focusEl.focus.defer(1, this.focusEl);
+
+ var focusEl = this.focusEl;
+
+ if (Ext.isGecko) {
+ focusEl.focus();
+ } else {
+ focusEl.focus.defer(1, focusEl);
}
},
- resolveCell : function(row, col, hscroll){
- if(!Ext.isNumber(row)){
+ /**
+ * @private
+ * Finds the Elements corresponding to the given row and column indexes
+ */
+ resolveCell : function(row, col, hscroll) {
+ if (!Ext.isNumber(row)) {
row = row.rowIndex;
}
- if(!this.ds){
+
+ if (!this.ds) {
return null;
}
- if(row < 0 || row >= this.ds.getCount()){
+
+ if (row < 0 || row >= this.ds.getCount()) {
return null;
}
col = (col !== undefined ? col : 0);
- var rowEl = this.getRow(row),
- cm = this.cm,
- colCount = cm.getColumnCount(),
+ var rowEl = this.getRow(row),
+ colModel = this.cm,
+ colCount = colModel.getColumnCount(),
cellEl;
- if(!(hscroll === false && col === 0)){
- while(col < colCount && cm.isHidden(col)){
+
+ if (!(hscroll === false && col === 0)) {
+ while (col < colCount && colModel.isHidden(col)) {
col++;
}
+
cellEl = this.getCell(row, col);
}
return {row: rowEl, cell: cellEl};
},
- getResolvedXY : function(resolved){
- if(!resolved){
+ /**
+ * @private
+ * Returns the XY co-ordinates of a given row/cell resolution (see {@link #resolveCell})
+ * @return {Array} X and Y coords
+ */
+ getResolvedXY : function(resolved) {
+ if (!resolved) {
return null;
}
- var s = this.scroller.dom, c = resolved.cell, r = resolved.row;
- return c ? Ext.fly(c).getXY() : [this.el.getX(), Ext.fly(r).getY()];
+
+ var cell = resolved.cell,
+ row = resolved.row;
+
+ if (cell) {
+ return Ext.fly(cell).getXY();
+ } else {
+ return [this.el.getX(), Ext.fly(row).getY()];
+ }
},
- syncFocusEl : function(row, col, hscroll){
+ /**
+ * @private
+ * Moves the focus element to the x and y co-ordinates of the given row and column
+ */
+ syncFocusEl : function(row, col, hscroll) {
var xy = row;
- if(!Ext.isArray(xy)){
+
+ if (!Ext.isArray(xy)) {
row = Math.min(row, Math.max(0, this.getRows().length-1));
+
if (isNaN(row)) {
return;
}
+
xy = this.getResolvedXY(this.resolveCell(row, col, hscroll));
}
- this.focusEl.setXY(xy||this.scroller.getXY());
+
+ this.focusEl.setXY(xy || this.scroller.getXY());
},
- ensureVisible : function(row, col, hscroll){
+ /**
+ * @private
+ */
+ ensureVisible : function(row, col, hscroll) {
var resolved = this.resolveCell(row, col, hscroll);
- if(!resolved || !resolved.row){
- return;
+
+ if (!resolved || !resolved.row) {
+ return null;
}
- var rowEl = resolved.row,
+ var rowEl = resolved.row,
cellEl = resolved.cell,
c = this.scroller.dom,
- ctop = 0,
p = rowEl,
+ ctop = 0,
stop = this.el.dom;
- while(p && p != stop){
+ while (p && p != stop) {
ctop += p.offsetTop;
p = p.offsetParent;
}
sbot = stop + ch;
- if(ctop < stop){
+ if (ctop < stop) {
c.scrollTop = ctop;
- }else if(cbot > sbot){
+ } else if(cbot > sbot) {
c.scrollTop = cbot-ch;
}
- if(hscroll !== false){
- var cleft = parseInt(cellEl.offsetLeft, 10);
- var cright = cleft + cellEl.offsetWidth;
-
- var sleft = parseInt(c.scrollLeft, 10);
- var sright = sleft + c.clientWidth;
- if(cleft < sleft){
+ if (hscroll !== false) {
+ var cleft = parseInt(cellEl.offsetLeft, 10),
+ cright = cleft + cellEl.offsetWidth,
+ sleft = parseInt(c.scrollLeft, 10),
+ sright = sleft + c.clientWidth;
+
+ if (cleft < sleft) {
c.scrollLeft = cleft;
- }else if(cright > sright){
+ } else if(cright > sright) {
c.scrollLeft = cright-c.clientWidth;
}
}
+
return this.getResolvedXY(resolved);
},
Ext.DomHelper.insertHtml('beforeEnd', this.mainBody.dom, html);
}
if (!isUpdate) {
- this.fireEvent('rowsinserted', this, firstRow, lastRow);
this.processRows(firstRow);
+ this.fireEvent('rowsinserted', this, firstRow, lastRow);
} else if (firstRow === 0 || firstRow >= last) {
//ensure first/last row is kept after an update.
Ext.fly(this.getRow(firstRow)).addClass(firstRow === 0 ? this.firstRowCls : this.lastRowCls);
this.syncFocusEl(firstRow);
},
- // private
- deleteRows : function(dm, firstRow, lastRow){
- if(dm.getRowCount()<1){
+ /**
+ * @private
+ * DEPRECATED - this doesn't appear to be called anywhere in the library, remove in 4.0.
+ */
+ deleteRows : function(dm, firstRow, lastRow) {
+ if (dm.getRowCount() < 1) {
this.refresh();
- }else{
+ } else {
this.fireEvent('beforerowsdeleted', this, firstRow, lastRow);
this.removeRows(firstRow, lastRow);
}
},
- // private
- getColumnStyle : function(col, isHeader){
- var style = !isHeader ? (this.cm.config[col].css || '') : '';
- style += 'width:'+this.getColumnWidth(col)+';';
- if(this.cm.isHidden(col)){
- style += 'display:none;';
+ /**
+ * @private
+ * Builds a CSS string for the given column index
+ * @param {Number} colIndex The column index
+ * @param {Boolean} isHeader True if getting the style for the column's header
+ * @return {String} The CSS string
+ */
+ getColumnStyle : function(colIndex, isHeader) {
+ var colModel = this.cm,
+ colConfig = colModel.config,
+ style = isHeader ? '' : colConfig[colIndex].css || '',
+ align = colConfig[colIndex].align;
+
+ style += String.format("width: {0};", this.getColumnWidth(colIndex));
+
+ if (colModel.isHidden(colIndex)) {
+ style += 'display: none; ';
}
- var align = this.cm.config[col].align;
- if(align){
- style += 'text-align:'+align+';';
+
+ if (align) {
+ style += String.format("text-align: {0};", align);
}
+
return style;
},
- // private
- getColumnWidth : function(col){
- var w = this.cm.getColumnWidth(col);
- if(Ext.isNumber(w)){
- return (Ext.isBorderBox || (Ext.isWebKit && !Ext.isSafari2) ? w : (w - this.borderWidth > 0 ? w - this.borderWidth : 0)) + 'px';
+ /**
+ * @private
+ * Returns the width of a given column minus its border width
+ * @return {Number} The column index
+ * @return {String|Number} The width in pixels
+ */
+ getColumnWidth : function(column) {
+ var columnWidth = this.cm.getColumnWidth(column),
+ borderWidth = this.borderWidth;
+
+ if (Ext.isNumber(columnWidth)) {
+ if (Ext.isBorderBox || (Ext.isWebKit && !Ext.isSafari2)) {
+ return columnWidth + "px";
+ } else {
+ return Math.max(columnWidth - borderWidth, 0) + "px";
+ }
+ } else {
+ return columnWidth;
}
- return w;
},
- // private
- getTotalWidth : function(){
- return this.cm.getTotalWidth()+'px';
+ /**
+ * @private
+ * Returns the total width of all visible columns
+ * @return {String}
+ */
+ getTotalWidth : function() {
+ return this.cm.getTotalWidth() + 'px';
},
- // private
- fitColumns : function(preventRefresh, onlyExpand, omitColumn){
- var cm = this.cm, i;
- var tw = cm.getTotalWidth(false);
- var aw = this.grid.getGridEl().getWidth(true)-this.getScrollOffset();
-
- if(aw < 20){ // not initialized, so don't screw up the default widths
- return;
- }
- var extra = aw - tw;
-
- if(extra === 0){
+ /**
+ * @private
+ * Resizes each column to fit the available grid width.
+ * TODO: The second argument isn't even used, remove it in 4.0
+ * @param {Boolean} preventRefresh True to prevent resizing of each row to the new column sizes (defaults to false)
+ * @param {null} onlyExpand NOT USED, will be removed in 4.0
+ * @param {Number} omitColumn The index of a column to leave at its current width. Defaults to undefined
+ * @return {Boolean} True if the operation succeeded, false if not or undefined if the grid view is not yet initialized
+ */
+ fitColumns : function(preventRefresh, onlyExpand, omitColumn) {
+ var grid = this.grid,
+ colModel = this.cm,
+ totalColWidth = colModel.getTotalWidth(false),
+ gridWidth = this.getGridInnerWidth(),
+ extraWidth = gridWidth - totalColWidth,
+ columns = [],
+ extraCol = 0,
+ width = 0,
+ colWidth, fraction, i;
+
+ // not initialized, so don't screw up the default widths
+ if (gridWidth < 20 || extraWidth === 0) {
return false;
}
-
- var vc = cm.getColumnCount(true);
- var ac = vc-(Ext.isNumber(omitColumn) ? 1 : 0);
- if(ac === 0){
- ac = 1;
+
+ var visibleColCount = colModel.getColumnCount(true),
+ totalColCount = colModel.getColumnCount(false),
+ adjCount = visibleColCount - (Ext.isNumber(omitColumn) ? 1 : 0);
+
+ if (adjCount === 0) {
+ adjCount = 1;
omitColumn = undefined;
}
- var colCount = cm.getColumnCount();
- var cols = [];
- var extraCol = 0;
- var width = 0;
- var w;
- for (i = 0; i < colCount; i++){
- if(!cm.isHidden(i) && !cm.isFixed(i) && i !== omitColumn){
- w = cm.getColumnWidth(i);
- cols.push(i);
- extraCol = i;
- cols.push(w);
- width += w;
+
+ //FIXME: the algorithm used here is odd and potentially confusing. Includes this for loop and the while after it.
+ for (i = 0; i < totalColCount; i++) {
+ if (!colModel.isFixed(i) && i !== omitColumn) {
+ colWidth = colModel.getColumnWidth(i);
+ columns.push(i, colWidth);
+
+ if (!colModel.isHidden(i)) {
+ extraCol = i;
+ width += colWidth;
+ }
}
}
- var frac = (aw - cm.getTotalWidth())/width;
- while (cols.length){
- w = cols.pop();
- i = cols.pop();
- cm.setColumnWidth(i, Math.max(this.grid.minColumnWidth, Math.floor(w + w*frac)), true);
+
+ fraction = (gridWidth - colModel.getTotalWidth()) / width;
+
+ while (columns.length) {
+ colWidth = columns.pop();
+ i = columns.pop();
+
+ colModel.setColumnWidth(i, Math.max(grid.minColumnWidth, Math.floor(colWidth + colWidth * fraction)), true);
}
-
- if((tw = cm.getTotalWidth(false)) > aw){
- var adjustCol = ac != vc ? omitColumn : extraCol;
- cm.setColumnWidth(adjustCol, Math.max(1,
- cm.getColumnWidth(adjustCol)- (tw-aw)), true);
+
+ //this has been changed above so remeasure now
+ totalColWidth = colModel.getTotalWidth(false);
+
+ if (totalColWidth > gridWidth) {
+ var adjustCol = (adjCount == visibleColCount) ? extraCol : omitColumn,
+ newWidth = Math.max(1, colModel.getColumnWidth(adjustCol) - (totalColWidth - gridWidth));
+
+ colModel.setColumnWidth(adjustCol, newWidth, true);
}
-
- if(preventRefresh !== true){
+
+ if (preventRefresh !== true) {
this.updateAllColumnWidths();
}
-
-
+
return true;
},
- // private
- autoExpand : function(preventUpdate){
- var g = this.grid, cm = this.cm;
- if(!this.userResized && g.autoExpandColumn){
- var tw = cm.getTotalWidth(false);
- var aw = this.grid.getGridEl().getWidth(true)-this.getScrollOffset();
- if(tw != aw){
- var ci = cm.getIndexById(g.autoExpandColumn);
- var currentWidth = cm.getColumnWidth(ci);
- var cw = Math.min(Math.max(((aw-tw)+currentWidth), g.autoExpandMin), g.autoExpandMax);
- if(cw != currentWidth){
- cm.setColumnWidth(ci, cw, true);
- if(preventUpdate !== true){
- this.updateColumnWidth(ci, cw);
+ /**
+ * @private
+ * Resizes the configured autoExpandColumn to take the available width after the other columns have
+ * been accounted for
+ * @param {Boolean} preventUpdate True to prevent the resizing of all rows (defaults to false)
+ */
+ autoExpand : function(preventUpdate) {
+ var grid = this.grid,
+ colModel = this.cm,
+ gridWidth = this.getGridInnerWidth(),
+ totalColumnWidth = colModel.getTotalWidth(false),
+ autoExpandColumn = grid.autoExpandColumn;
+
+ if (!this.userResized && autoExpandColumn) {
+ if (gridWidth != totalColumnWidth) {
+ //if we are not already using all available width, resize the autoExpandColumn
+ var colIndex = colModel.getIndexById(autoExpandColumn),
+ currentWidth = colModel.getColumnWidth(colIndex),
+ desiredWidth = gridWidth - totalColumnWidth + currentWidth,
+ newWidth = Math.min(Math.max(desiredWidth, grid.autoExpandMin), grid.autoExpandMax);
+
+ if (currentWidth != newWidth) {
+ colModel.setColumnWidth(colIndex, newWidth, true);
+
+ if (preventUpdate !== true) {
+ this.updateColumnWidth(colIndex, newWidth);
}
}
}
}
},
+
+ /**
+ * Returns the total internal width available to the grid, taking the scrollbar into account
+ * @return {Number} The total width
+ */
+ getGridInnerWidth: function() {
+ return this.grid.getGridEl().getWidth(true) - this.getScrollOffset();
+ },
/**
* @private
* Returns an array of column configurations - one for each column
* @return {Array} Array of column config objects. This includes the column name, renderer, id style and renderer
*/
- getColumnData : function(){
- // build a map for all the columns
- var cs = [],
- cm = this.cm,
- colCount = cm.getColumnCount();
-
- for (var i = 0; i < colCount; i++) {
- var name = cm.getDataIndex(i);
-
- cs[i] = {
- name : (!Ext.isDefined(name) ? this.ds.fields.get(i).name : name),
- renderer: cm.getRenderer(i),
- scope : cm.getRendererScope(i),
- id : cm.getColumnId(i),
+ getColumnData : function() {
+ var columns = [],
+ colModel = this.cm,
+ colCount = colModel.getColumnCount(),
+ fields = this.ds.fields,
+ i, name;
+
+ for (i = 0; i < colCount; i++) {
+ name = colModel.getDataIndex(i);
+
+ columns[i] = {
+ name : Ext.isDefined(name) ? name : (fields.get(i) ? fields.get(i).name : undefined),
+ renderer: colModel.getRenderer(i),
+ scope : colModel.getRendererScope(i),
+ id : colModel.getColumnId(i),
style : this.getColumnStyle(i)
};
}
-
- return cs;
+
+ return columns;
},
- // private
- renderRows : function(startRow, endRow){
- // pull in all the crap needed to render rows
- var g = this.grid, cm = g.colModel, ds = g.store, stripe = g.stripeRows;
- var colCount = cm.getColumnCount();
-
- if(ds.getCount() < 1){
+ /**
+ * @private
+ * Renders rows between start and end indexes
+ * @param {Number} startRow Index of the first row to render
+ * @param {Number} endRow Index of the last row to render
+ */
+ renderRows : function(startRow, endRow) {
+ var grid = this.grid,
+ store = grid.store,
+ stripe = grid.stripeRows,
+ colModel = grid.colModel,
+ colCount = colModel.getColumnCount(),
+ rowCount = store.getCount(),
+ records;
+
+ if (rowCount < 1) {
return '';
}
-
- var cs = this.getColumnData();
-
+
startRow = startRow || 0;
- endRow = !Ext.isDefined(endRow) ? ds.getCount()-1 : endRow;
-
- // records to render
- var rs = ds.getRange(startRow, endRow);
-
- return this.doRender(cs, rs, ds, startRow, colCount, stripe);
+ endRow = Ext.isDefined(endRow) ? endRow : rowCount - 1;
+ records = store.getRange(startRow, endRow);
+
+ return this.doRender(this.getColumnData(), records, store, startRow, colCount, stripe);
},
// private
return this.templates.body.apply({rows: markup});
},
- // private
- refreshRow : function(record){
- var ds = this.ds, index;
- if(Ext.isNumber(record)){
- index = record;
- record = ds.getAt(index);
- if(!record){
- return;
+ /**
+ * @private
+ * Refreshes a row by re-rendering it. Fires the rowupdated event when done
+ */
+ refreshRow: function(record) {
+ var store = this.ds,
+ colCount = this.cm.getColumnCount(),
+ columns = this.getColumnData(),
+ last = colCount - 1,
+ cls = ['x-grid3-row'],
+ rowParams = {
+ tstyle: String.format("width: {0};", this.getTotalWidth())
+ },
+ colBuffer = [],
+ cellTpl = this.templates.cell,
+ rowIndex, row, column, meta, css, i;
+
+ if (Ext.isNumber(record)) {
+ rowIndex = record;
+ record = store.getAt(rowIndex);
+ } else {
+ rowIndex = store.indexOf(record);
+ }
+
+ //the record could not be found
+ if (!record || rowIndex < 0) {
+ return;
+ }
+
+ //builds each column in this row
+ for (i = 0; i < colCount; i++) {
+ column = columns[i];
+
+ if (i == 0) {
+ css = 'x-grid3-cell-first';
+ } else {
+ css = (i == last) ? 'x-grid3-cell-last ' : '';
}
- }else{
- index = ds.indexOf(record);
- if(index < 0){
- return;
+
+ meta = {
+ id : column.id,
+ style : column.style,
+ css : css,
+ attr : "",
+ cellAttr: ""
+ };
+ // Need to set this after, because we pass meta to the renderer
+ meta.value = column.renderer.call(column.scope, record.data[column.name], meta, record, rowIndex, i, store);
+
+ if (Ext.isEmpty(meta.value)) {
+ meta.value = ' ';
+ }
+
+ if (this.markDirty && record.dirty && typeof record.modified[column.name] != 'undefined') {
+ meta.css += ' x-grid3-dirty-cell';
}
+
+ colBuffer[i] = cellTpl.apply(meta);
+ }
+
+ row = this.getRow(rowIndex);
+ row.className = '';
+
+ if (this.grid.stripeRows && ((rowIndex + 1) % 2 === 0)) {
+ cls.push('x-grid3-row-alt');
}
- this.insertRows(ds, index, index, true);
- this.getRow(index).rowIndex = index;
- this.onRemove(ds, record, index+1, true);
- this.fireEvent('rowupdated', this, index, record);
+
+ if (this.getRowClass) {
+ rowParams.cols = colCount;
+ cls.push(this.getRowClass(record, rowIndex, rowParams, store));
+ }
+
+ this.fly(row).addClass(cls).setStyle(rowParams.tstyle);
+ rowParams.cells = colBuffer.join("");
+ row.innerHTML = this.templates.rowInner.apply(rowParams);
+
+ this.fireEvent('rowupdated', this, rowIndex, record);
},
/**
* Refreshs the grid UI
* @param {Boolean} headersToo (optional) True to also refresh the headers
*/
- refresh : function(headersToo){
+ refresh : function(headersToo) {
this.fireEvent('beforerefresh', this);
this.grid.stopEditing(true);
var result = this.renderBody();
this.mainBody.update(result).setWidth(this.getTotalWidth());
- if(headersToo === true){
+ if (headersToo === true) {
this.updateHeaders();
this.updateHeaderSortState();
}
* @private
* Displays the configured emptyText if there are currently no rows to display
*/
- applyEmptyText : function(){
- if(this.emptyText && !this.hasRows()){
+ applyEmptyText : function() {
+ if (this.emptyText && !this.hasRows()) {
this.mainBody.update('<div class="x-grid-empty">' + this.emptyText + '</div>');
}
},
* Adds sorting classes to the column headers based on the bound store's sortInfo. Fires the 'sortchange' event
* if the sorting has changed since this function was last run.
*/
- updateHeaderSortState : function(){
+ updateHeaderSortState : function() {
var state = this.ds.getSortState();
if (!state) {
return;
this.sortState = state;
var sortColumn = this.cm.findColumnIndex(state.field);
- if (sortColumn != -1){
+ if (sortColumn != -1) {
var sortDir = state.direction;
this.updateSortIcon(sortColumn, sortDir);
}
* @private
* Removes any sorting indicator classes from the column headers
*/
- clearHeaderSortState : function(){
+ clearHeaderSortState : function() {
if (!this.sortState) {
return;
}
delete this.sortState;
},
- // private
- destroy : function(){
- if (this.scrollToTopTask && this.scrollToTopTask.cancel){
- this.scrollToTopTask.cancel();
- }
- if(this.colMenu){
- Ext.menu.MenuMgr.unregister(this.colMenu);
- this.colMenu.destroy();
- delete this.colMenu;
- }
- if(this.hmenu){
- Ext.menu.MenuMgr.unregister(this.hmenu);
- this.hmenu.destroy();
- delete this.hmenu;
+ /**
+ * @private
+ * Destroys all objects associated with the GridView
+ */
+ destroy : function() {
+ var me = this,
+ grid = me.grid,
+ gridEl = grid.getGridEl(),
+ dragZone = me.dragZone,
+ splitZone = me.splitZone,
+ columnDrag = me.columnDrag,
+ columnDrop = me.columnDrop,
+ scrollToTopTask = me.scrollToTopTask,
+ columnDragData,
+ columnDragProxy;
+
+ if (scrollToTopTask && scrollToTopTask.cancel) {
+ scrollToTopTask.cancel();
}
+
+ Ext.destroyMembers(me, 'colMenu', 'hmenu');
- this.initData(null, null);
- this.purgeListeners();
- Ext.fly(this.innerHd).un("click", this.handleHdDown, this);
+ me.initData(null, null);
+ me.purgeListeners();
+
+ Ext.fly(me.innerHd).un("click", me.handleHdDown, me);
- if(this.grid.enableColumnMove){
+ if (grid.enableColumnMove) {
+ columnDragData = columnDrag.dragData;
+ columnDragProxy = columnDrag.proxy;
Ext.destroy(
- this.columnDrag.el,
- this.columnDrag.proxy.ghost,
- this.columnDrag.proxy.el,
- this.columnDrop.el,
- this.columnDrop.proxyTop,
- this.columnDrop.proxyBottom,
- this.columnDrag.dragData.ddel,
- this.columnDrag.dragData.header
+ columnDrag.el,
+ columnDragProxy.ghost,
+ columnDragProxy.el,
+ columnDrop.el,
+ columnDrop.proxyTop,
+ columnDrop.proxyBottom,
+ columnDragData.ddel,
+ columnDragData.header
);
- if (this.columnDrag.proxy.anim) {
- Ext.destroy(this.columnDrag.proxy.anim);
+
+ if (columnDragProxy.anim) {
+ Ext.destroy(columnDragProxy.anim);
}
- delete this.columnDrag.proxy.ghost;
- delete this.columnDrag.dragData.ddel;
- delete this.columnDrag.dragData.header;
- this.columnDrag.destroy();
- delete Ext.dd.DDM.locationCache[this.columnDrag.id];
- delete this.columnDrag._domRef;
+
+ delete columnDragProxy.ghost;
+ delete columnDragData.ddel;
+ delete columnDragData.header;
+ columnDrag.destroy();
+
+ delete Ext.dd.DDM.locationCache[columnDrag.id];
+ delete columnDrag._domRef;
- delete this.columnDrop.proxyTop;
- delete this.columnDrop.proxyBottom;
- this.columnDrop.destroy();
- delete Ext.dd.DDM.locationCache["gridHeader" + this.grid.getGridEl().id];
- delete this.columnDrop._domRef;
- delete Ext.dd.DDM.ids[this.columnDrop.ddGroup];
+ delete columnDrop.proxyTop;
+ delete columnDrop.proxyBottom;
+ columnDrop.destroy();
+ delete Ext.dd.DDM.locationCache["gridHeader" + gridEl.id];
+ delete columnDrop._domRef;
+ delete Ext.dd.DDM.ids[columnDrop.ddGroup];
}
- if (this.splitZone){ // enableColumnResize
- this.splitZone.destroy();
- delete this.splitZone._domRef;
- delete Ext.dd.DDM.ids["gridSplitters" + this.grid.getGridEl().id];
+ if (splitZone) { // enableColumnResize
+ splitZone.destroy();
+ delete splitZone._domRef;
+ delete Ext.dd.DDM.ids["gridSplitters" + gridEl.id];
}
- Ext.fly(this.innerHd).removeAllListeners();
- Ext.removeNode(this.innerHd);
- delete this.innerHd;
+ Ext.fly(me.innerHd).removeAllListeners();
+ Ext.removeNode(me.innerHd);
+ delete me.innerHd;
Ext.destroy(
- this.el,
- this.mainWrap,
- this.mainHd,
- this.scroller,
- this.mainBody,
- this.focusEl,
- this.resizeMarker,
- this.resizeProxy,
- this.activeHdBtn,
- this.dragZone,
- this.splitZone,
- this._flyweight
+ me.el,
+ me.mainWrap,
+ me.mainHd,
+ me.scroller,
+ me.mainBody,
+ me.focusEl,
+ me.resizeMarker,
+ me.resizeProxy,
+ me.activeHdBtn,
+ me._flyweight,
+ dragZone,
+ splitZone
);
- delete this.grid.container;
+ delete grid.container;
- if(this.dragZone){
- this.dragZone.destroy();
+ if (dragZone) {
+ dragZone.destroy();
}
Ext.dd.DDM.currentTarget = null;
- delete Ext.dd.DDM.locationCache[this.grid.getGridEl().id];
+ delete Ext.dd.DDM.locationCache[gridEl.id];
- Ext.EventManager.removeResizeListener(this.onWindowResize, this);
+ Ext.EventManager.removeResizeListener(me.onWindowResize, me);
},
// private
- onDenyColumnHide : function(){
+ onDenyColumnHide : function() {
},
// private
- render : function(){
- if(this.autoFill){
+ render : function() {
+ if (this.autoFill) {
var ct = this.grid.ownerCt;
- if (ct && ct.getLayout()){
- ct.on('afterlayout', function(){
+
+ if (ct && ct.getLayout()) {
+ ct.on('afterlayout', function() {
this.fitColumns(true, true);
this.updateHeaders();
+ this.updateHeaderSortState();
}, this, {single: true});
- }else{
- this.fitColumns(true, true);
}
- }else if(this.forceFit){
+ } else if (this.forceFit) {
this.fitColumns(true, false);
- }else if(this.grid.autoExpandColumn){
+ } else if (this.grid.autoExpandColumn) {
this.autoExpand(true);
}
-
- this.renderUI();
+
+ this.grid.getGridEl().dom.innerHTML = this.renderUI();
+
+ this.afterRenderUI();
},
/* --------------------------------- Model Events and Handlers --------------------------------*/
- // private
- initData : function(ds, cm){
- if(this.ds){
- this.ds.un('load', this.onLoad, this);
- this.ds.un('datachanged', this.onDataChange, this);
- this.ds.un('add', this.onAdd, this);
- this.ds.un('remove', this.onRemove, this);
- this.ds.un('update', this.onUpdate, this);
- this.ds.un('clear', this.onClear, this);
- if(this.ds !== ds && this.ds.autoDestroy){
- this.ds.destroy();
- }
- }
- if(ds){
- ds.on({
- scope: this,
- load: this.onLoad,
- datachanged: this.onDataChange,
- add: this.onAdd,
- remove: this.onRemove,
- update: this.onUpdate,
- clear: this.onClear
+
+ /**
+ * @private
+ * Binds a new Store and ColumnModel to this GridView. Removes any listeners from the old objects (if present)
+ * and adds listeners to the new ones
+ * @param {Ext.data.Store} newStore The new Store instance
+ * @param {Ext.grid.ColumnModel} newColModel The new ColumnModel instance
+ */
+ initData : function(newStore, newColModel) {
+ var me = this;
+
+ if (me.ds) {
+ var oldStore = me.ds;
+
+ oldStore.un('add', me.onAdd, me);
+ oldStore.un('load', me.onLoad, me);
+ oldStore.un('clear', me.onClear, me);
+ oldStore.un('remove', me.onRemove, me);
+ oldStore.un('update', me.onUpdate, me);
+ oldStore.un('datachanged', me.onDataChange, me);
+
+ if (oldStore !== newStore && oldStore.autoDestroy) {
+ oldStore.destroy();
+ }
+ }
+
+ if (newStore) {
+ newStore.on({
+ scope : me,
+ load : me.onLoad,
+ add : me.onAdd,
+ remove : me.onRemove,
+ update : me.onUpdate,
+ clear : me.onClear,
+ datachanged: me.onDataChange
});
}
- this.ds = ds;
-
- if(this.cm){
- this.cm.un('configchange', this.onColConfigChange, this);
- this.cm.un('widthchange', this.onColWidthChange, this);
- this.cm.un('headerchange', this.onHeaderChange, this);
- this.cm.un('hiddenchange', this.onHiddenChange, this);
- this.cm.un('columnmoved', this.onColumnMove, this);
- }
- if(cm){
- delete this.lastViewWidth;
- cm.on({
- scope: this,
- configchange: this.onColConfigChange,
- widthchange: this.onColWidthChange,
- headerchange: this.onHeaderChange,
- hiddenchange: this.onHiddenChange,
- columnmoved: this.onColumnMove
+
+ if (me.cm) {
+ var oldColModel = me.cm;
+
+ oldColModel.un('configchange', me.onColConfigChange, me);
+ oldColModel.un('widthchange', me.onColWidthChange, me);
+ oldColModel.un('headerchange', me.onHeaderChange, me);
+ oldColModel.un('hiddenchange', me.onHiddenChange, me);
+ oldColModel.un('columnmoved', me.onColumnMove, me);
+ }
+
+ if (newColModel) {
+ delete me.lastViewWidth;
+
+ newColModel.on({
+ scope : me,
+ configchange: me.onColConfigChange,
+ widthchange : me.onColWidthChange,
+ headerchange: me.onHeaderChange,
+ hiddenchange: me.onHiddenChange,
+ columnmoved : me.onColumnMove
});
}
- this.cm = cm;
+
+ me.ds = newStore;
+ me.cm = newColModel;
},
// private
onDataChange : function(){
- this.refresh();
+ this.refresh(true);
this.updateHeaderSortState();
this.syncFocusEl(0);
},
// private
- onClear : function(){
+ onClear : function() {
this.refresh();
this.syncFocusEl(0);
},
// private
- onUpdate : function(ds, record){
+ onUpdate : function(store, record) {
this.refreshRow(record);
},
// private
- onAdd : function(ds, records, index){
- this.insertRows(ds, index, index + (records.length-1));
+ onAdd : function(store, records, index) {
+ this.insertRows(store, index, index + (records.length-1));
},
// private
- onRemove : function(ds, record, index, isUpdate){
- if(isUpdate !== true){
+ onRemove : function(store, record, index, isUpdate) {
+ if (isUpdate !== true) {
this.fireEvent('beforerowremoved', this, index, record);
}
+
this.removeRow(index);
- if(isUpdate !== true){
+
+ if (isUpdate !== true) {
this.processRows(index);
this.applyEmptyText();
this.fireEvent('rowremoved', this, index, record);
}
},
- // private
- onLoad : function(){
- if (Ext.isGecko){
+ /**
+ * @private
+ * Called when a store is loaded, scrolls to the top row
+ */
+ onLoad : function() {
+ if (Ext.isGecko) {
if (!this.scrollToTopTask) {
this.scrollToTopTask = new Ext.util.DelayedTask(this.scrollToTop, this);
}
this.scrollToTopTask.delay(1);
- }else{
+ } else {
this.scrollToTop();
}
},
// private
- onColWidthChange : function(cm, col, width){
+ onColWidthChange : function(cm, col, width) {
this.updateColumnWidth(col, width);
},
// private
- onHeaderChange : function(cm, col, text){
+ onHeaderChange : function(cm, col, text) {
this.updateHeaders();
},
// private
- onHiddenChange : function(cm, col, hidden){
+ onHiddenChange : function(cm, col, hidden) {
this.updateColumnHidden(col, hidden);
},
// private
- onColumnMove : function(cm, oldIndex, newIndex){
+ onColumnMove : function(cm, oldIndex, newIndex) {
this.indexMap = null;
- var s = this.getScrollState();
this.refresh(true);
- this.restoreScroll(s);
+ this.restoreScroll(this.getScrollState());
+
this.afterMove(newIndex);
this.grid.fireEvent('columnmove', oldIndex, newIndex);
},
// private
- onColConfigChange : function(){
+ onColConfigChange : function() {
delete this.lastViewWidth;
this.indexMap = null;
this.refresh(true);
/* -------------------- UI Events and Handlers ------------------------------ */
// private
- initUI : function(grid){
+ initUI : function(grid) {
grid.on('headerclick', this.onHeaderClick, this);
},
// private
- initEvents : function(){
- },
+ initEvents : Ext.emptyFn,
// private
- onHeaderClick : function(g, index){
- if(this.headersDisabled || !this.cm.isSortable(index)){
+ onHeaderClick : function(g, index) {
+ if (this.headersDisabled || !this.cm.isSortable(index)) {
return;
}
g.stopEditing(true);
g.store.sort(this.cm.getDataIndex(index));
},
- // private
- onRowOver : function(e, t){
- var row;
- if((row = this.findRowIndex(t)) !== false){
- this.addRowClass(row, 'x-grid3-row-over');
+ /**
+ * @private
+ * Adds the hover class to a row when hovered over
+ */
+ onRowOver : function(e, target) {
+ var row = this.findRowIndex(target);
+
+ if (row !== false) {
+ this.addRowClass(row, this.rowOverCls);
}
},
- // private
- onRowOut : function(e, t){
- var row;
- if((row = this.findRowIndex(t)) !== false && !e.within(this.getRow(row), true)){
- this.removeRowClass(row, 'x-grid3-row-over');
+ /**
+ * @private
+ * Removes the hover class from a row on mouseout
+ */
+ onRowOut : function(e, target) {
+ var row = this.findRowIndex(target);
+
+ if (row !== false && !e.within(this.getRow(row), true)) {
+ this.removeRowClass(row, this.rowOverCls);
}
},
// private
- handleWheel : function(e){
- e.stopPropagation();
- },
-
- // private
- onRowSelect : function(row){
+ onRowSelect : function(row) {
this.addRowClass(row, this.selectedRowClass);
},
// private
- onRowDeselect : function(row){
+ onRowDeselect : function(row) {
this.removeRowClass(row, this.selectedRowClass);
},
// private
- onCellSelect : function(row, col){
+ onCellSelect : function(row, col) {
var cell = this.getCell(row, col);
- if(cell){
+ if (cell) {
this.fly(cell).addClass('x-grid3-cell-selected');
}
},
-
- // private
- onCellDeselect : function(row, col){
- var cell = this.getCell(row, col);
- if(cell){
- this.fly(cell).removeClass('x-grid3-cell-selected');
- }
+
+ // private
+ onCellDeselect : function(row, col) {
+ var cell = this.getCell(row, col);
+ if (cell) {
+ this.fly(cell).removeClass('x-grid3-cell-selected');
+ }
+ },
+
+ // private
+ handleWheel : function(e) {
+ e.stopPropagation();
+ },
+
+ /**
+ * @private
+ * Called by the SplitDragZone when a drag has been completed. Resizes the columns
+ */
+ onColumnSplitterMoved : function(cellIndex, width) {
+ this.userResized = true;
+ this.grid.colModel.setColumnWidth(cellIndex, width, true);
+
+ if (this.forceFit) {
+ this.fitColumns(true, false, cellIndex);
+ this.updateAllColumnWidths();
+ } else {
+ this.updateColumnWidth(cellIndex, width);
+ this.syncHeaderScroll();
+ }
+
+ this.grid.fireEvent('columnresize', cellIndex, width);
+ },
+
+ /**
+ * @private
+ * Click handler for the shared column dropdown menu, called on beforeshow. Builds the menu
+ * which displays the list of columns for the user to show or hide.
+ */
+ beforeColMenuShow : function() {
+ var colModel = this.cm,
+ colCount = colModel.getColumnCount(),
+ colMenu = this.colMenu,
+ i;
+
+ colMenu.removeAll();
+
+ for (i = 0; i < colCount; i++) {
+ if (colModel.config[i].hideable !== false) {
+ colMenu.add(new Ext.menu.CheckItem({
+ text : colModel.getColumnHeader(i),
+ itemId : 'col-' + colModel.getColumnId(i),
+ checked : !colModel.isHidden(i),
+ disabled : colModel.config[i].hideable === false,
+ hideOnClick: false
+ }));
+ }
+ }
+ },
+
+ /**
+ * @private
+ * Attached as the 'itemclick' handler to the header menu and the column show/hide submenu (if available).
+ * Performs sorting if the sorter buttons were clicked, otherwise hides/shows the column that was clicked.
+ */
+ handleHdMenuClick : function(item) {
+ var store = this.ds,
+ dataIndex = this.cm.getDataIndex(this.hdCtxIndex);
+
+ switch (item.getItemId()) {
+ case 'asc':
+ store.sort(dataIndex, 'ASC');
+ break;
+ case 'desc':
+ store.sort(dataIndex, 'DESC');
+ break;
+ default:
+ this.handleHdMenuClickDefault(item);
+ }
+ return true;
+ },
+
+ /**
+ * Called by handleHdMenuClick if any button except a sort ASC/DESC button was clicked. The default implementation provides
+ * the column hide/show functionality based on the check state of the menu item. A different implementation can be provided
+ * if needed.
+ * @param {Ext.menu.BaseItem} item The menu item that was clicked
+ */
+ handleHdMenuClickDefault: function(item) {
+ var colModel = this.cm,
+ itemId = item.getItemId(),
+ index = colModel.getIndexById(itemId.substr(4));
+
+ if (index != -1) {
+ if (item.checked && colModel.getColumnsBy(this.isHideableColumn, this).length <= 1) {
+ this.onDenyColumnHide();
+ return;
+ }
+ colModel.setHidden(index, item.checked);
+ }
+ },
+
+ /**
+ * @private
+ * Called when a header cell is clicked - shows the menu if the click happened over a trigger button
+ */
+ handleHdDown : function(e, target) {
+ if (Ext.fly(target).hasClass('x-grid3-hd-btn')) {
+ e.stopEvent();
+
+ var colModel = this.cm,
+ header = this.findHeaderCell(target),
+ index = this.getCellIndex(header),
+ sortable = colModel.isSortable(index),
+ menu = this.hmenu,
+ menuItems = menu.items,
+ menuCls = this.headerMenuOpenCls;
+
+ this.hdCtxIndex = index;
+
+ Ext.fly(header).addClass(menuCls);
+ menuItems.get('asc').setDisabled(!sortable);
+ menuItems.get('desc').setDisabled(!sortable);
+
+ menu.on('hide', function() {
+ Ext.fly(header).removeClass(menuCls);
+ }, this, {single:true});
+
+ menu.show(target, 'tl-bl?');
+ }
+ },
+
+ /**
+ * @private
+ * Attached to the headers' mousemove event. This figures out the CSS cursor to use based on where the mouse is currently
+ * pointed. If the mouse is currently hovered over the extreme left or extreme right of any header cell and the cell next
+ * to it is resizable it is given the resize cursor, otherwise the cursor is set to an empty string.
+ */
+ handleHdMove : function(e) {
+ var header = this.findHeaderCell(this.activeHdRef);
+
+ if (header && !this.headersDisabled) {
+ var handleWidth = this.splitHandleWidth || 5,
+ activeRegion = this.activeHdRegion,
+ headerStyle = header.style,
+ colModel = this.cm,
+ cursor = '',
+ pageX = e.getPageX();
+
+ if (this.grid.enableColumnResize !== false) {
+ var activeHeaderIndex = this.activeHdIndex,
+ previousVisible = this.getPreviousVisible(activeHeaderIndex),
+ currentResizable = colModel.isResizable(activeHeaderIndex),
+ previousResizable = previousVisible && colModel.isResizable(previousVisible),
+ inLeftResizer = pageX - activeRegion.left <= handleWidth,
+ inRightResizer = activeRegion.right - pageX <= (!this.activeHdBtn ? handleWidth : 2);
+
+ if (inLeftResizer && previousResizable) {
+ cursor = Ext.isAir ? 'move' : Ext.isWebKit ? 'e-resize' : 'col-resize'; // col-resize not always supported
+ } else if (inRightResizer && currentResizable) {
+ cursor = Ext.isAir ? 'move' : Ext.isWebKit ? 'w-resize' : 'col-resize';
+ }
+ }
+
+ headerStyle.cursor = cursor;
+ }
+ },
+
+ /**
+ * @private
+ * Returns the index of the nearest currently visible header to the left of the given index.
+ * @param {Number} index The header index
+ * @return {Number/undefined} The index of the nearest visible header
+ */
+ getPreviousVisible: function(index) {
+ while (index > 0) {
+ if (!this.cm.isHidden(index - 1)) {
+ return index;
+ }
+ index--;
+ }
+ return undefined;
+ },
+
+ /**
+ * @private
+ * Tied to the header element's mouseover event - adds the over class to the header cell if the menu is not disabled
+ * for that cell
+ */
+ handleHdOver : function(e, target) {
+ var header = this.findHeaderCell(target);
+
+ if (header && !this.headersDisabled) {
+ var fly = this.fly(header);
+
+ this.activeHdRef = target;
+ this.activeHdIndex = this.getCellIndex(header);
+ this.activeHdRegion = fly.getRegion();
+
+ if (!this.isMenuDisabled(this.activeHdIndex, fly)) {
+ fly.addClass('x-grid3-hd-over');
+ this.activeHdBtn = fly.child('.x-grid3-hd-btn');
+
+ if (this.activeHdBtn) {
+ this.activeHdBtn.dom.style.height = (header.firstChild.offsetHeight - 1) + 'px';
+ }
+ }
+ }
+ },
+
+ /**
+ * @private
+ * Tied to the header element's mouseout event. Removes the hover class from the header cell
+ */
+ handleHdOut : function(e, target) {
+ var header = this.findHeaderCell(target);
+
+ if (header && (!Ext.isIE || !e.within(header, true))) {
+ this.activeHdRef = null;
+ this.fly(header).removeClass('x-grid3-hd-over');
+ header.style.cursor = '';
+ }
+ },
+
+ /**
+ * @private
+ * Used by {@link #handleHdOver} to determine whether or not to show the header menu class on cell hover
+ * @param {Number} cellIndex The header cell index
+ * @param {Ext.Element} el The cell element currently being hovered over
+ */
+ isMenuDisabled: function(cellIndex, el) {
+ return this.cm.isMenuDisabled(cellIndex);
+ },
+
+ /**
+ * @private
+ * Returns true if there are any rows rendered into the GridView
+ * @return {Boolean} True if any rows have been rendered
+ */
+ hasRows : function() {
+ var fc = this.mainBody.dom.firstChild;
+ return fc && fc.nodeType == 1 && fc.className != 'x-grid-empty';
+ },
+
+ /**
+ * @private
+ */
+ isHideableColumn : function(c) {
+ return !c.hidden;
+ },
+
+ /**
+ * @private
+ * DEPRECATED - will be removed in Ext JS 5.0
+ */
+ bind : function(d, c) {
+ this.initData(d, c);
+ }
+});
+
+
+// private
+// This is a support class used internally by the Grid components
+Ext.grid.GridView.SplitDragZone = Ext.extend(Ext.dd.DDProxy, {
+
+ constructor: function(grid, hd){
+ this.grid = grid;
+ this.view = grid.getView();
+ this.marker = this.view.resizeMarker;
+ this.proxy = this.view.resizeProxy;
+ Ext.grid.GridView.SplitDragZone.superclass.constructor.call(this, hd,
+ 'gridSplitters' + this.grid.getGridEl().id, {
+ dragElId : Ext.id(this.proxy.dom), resizeFrame:false
+ });
+ this.scroll = false;
+ this.hw = this.view.splitHandleWidth || 5;
+ },
+
+ b4StartDrag : function(x, y){
+ this.dragHeadersDisabled = this.view.headersDisabled;
+ this.view.headersDisabled = true;
+ var h = this.view.mainWrap.getHeight();
+ this.marker.setHeight(h);
+ this.marker.show();
+ this.marker.alignTo(this.view.getHeaderCell(this.cellIndex), 'tl-tl', [-2, 0]);
+ this.proxy.setHeight(h);
+ var w = this.cm.getColumnWidth(this.cellIndex),
+ minw = Math.max(w-this.grid.minColumnWidth, 0);
+ this.resetConstraints();
+ this.setXConstraint(minw, 1000);
+ this.setYConstraint(0, 0);
+ this.minX = x - minw;
+ this.maxX = x + 1000;
+ this.startPos = x;
+ Ext.dd.DDProxy.prototype.b4StartDrag.call(this, x, y);
+ },
+
+ allowHeaderDrag : function(e){
+ return true;
+ },
+
+ handleMouseDown : function(e){
+ var t = this.view.findHeaderCell(e.getTarget());
+ if(t && this.allowHeaderDrag(e)){
+ var xy = this.view.fly(t).getXY(),
+ x = xy[0],
+ exy = e.getXY(),
+ ex = exy[0],
+ w = t.offsetWidth,
+ adjust = false;
+
+ if((ex - x) <= this.hw){
+ adjust = -1;
+ }else if((x+w) - ex <= this.hw){
+ adjust = 0;
+ }
+ if(adjust !== false){
+ this.cm = this.grid.colModel;
+ var ci = this.view.getCellIndex(t);
+ if(adjust == -1){
+ if (ci + adjust < 0) {
+ return;
+ }
+ while(this.cm.isHidden(ci+adjust)){
+ --adjust;
+ if(ci+adjust < 0){
+ return;
+ }
+ }
+ }
+ this.cellIndex = ci+adjust;
+ this.split = t.dom;
+ if(this.cm.isResizable(this.cellIndex) && !this.cm.isFixed(this.cellIndex)){
+ Ext.grid.GridView.SplitDragZone.superclass.handleMouseDown.apply(this, arguments);
+ }
+ }else if(this.view.columnDrag){
+ this.view.columnDrag.callHandleMouseDown(e);
+ }
+ }
+ },
+
+ endDrag : function(e){
+ this.marker.hide();
+ var v = this.view,
+ endX = Math.max(this.minX, e.getPageX()),
+ diff = endX - this.startPos,
+ disabled = this.dragHeadersDisabled;
+
+ v.onColumnSplitterMoved(this.cellIndex, this.cm.getColumnWidth(this.cellIndex)+diff);
+ setTimeout(function(){
+ v.headersDisabled = disabled;
+ }, 50);
+ },
+
+ autoOffset : function(){
+ this.setDelta(0,0);
+ }
+});
+/**
+ * @class Ext.grid.PivotGridView
+ * @extends Ext.grid.GridView
+ * Specialised GridView for rendering Pivot Grid components. Config can be passed to the PivotGridView via the PivotGrid constructor's
+ * viewConfig option:
+<pre><code>
+new Ext.grid.PivotGrid({
+ viewConfig: {
+ title: 'My Pivot Grid',
+ getCellCls: function(value) {
+ return value > 10 'red' : 'green';
+ }
+ }
+});
+</code></pre>
+ * <p>Currently {@link #title} and {@link #getCellCls} are the only configuration options accepted by PivotGridView. All other
+ * interaction is performed via the {@link Ext.grid.PivotGrid PivotGrid} class.</p>
+ */
+Ext.grid.PivotGridView = Ext.extend(Ext.grid.GridView, {
+
+ /**
+ * The CSS class added to all group header cells. Defaults to 'grid-hd-group-cell'
+ * @property colHeaderCellCls
+ * @type String
+ */
+ colHeaderCellCls: 'grid-hd-group-cell',
+
+ /**
+ * @cfg {String} title Optional title to be placed in the top left corner of the PivotGrid. Defaults to an empty string.
+ */
+ title: '',
+
+ /**
+ * @cfg {Function} getCellCls Optional function which should return a CSS class name for each cell value. This is useful when
+ * color coding cells based on their value. Defaults to undefined.
+ */
+
+ /**
+ * Returns the headers to be rendered at the top of the grid. Should be a 2-dimensional array, where each item specifies the number
+ * of columns it groups (column in this case refers to normal grid columns). In the example below we have 5 city groups, which are
+ * each part of a continent supergroup. The colspan for each city group refers to the number of normal grid columns that group spans,
+ * so in this case the grid would be expected to have a total of 12 columns:
+<pre><code>
+[
+ {
+ items: [
+ {header: 'England', colspan: 5},
+ {header: 'USA', colspan: 3}
+ ]
+ },
+ {
+ items: [
+ {header: 'London', colspan: 2},
+ {header: 'Cambridge', colspan: 3},
+ {header: 'Palo Alto', colspan: 3}
+ ]
+ }
+]
+</code></pre>
+ * In the example above we have cities nested under countries. The nesting could be deeper if desired - e.g. Continent -> Country ->
+ * State -> City, or any other structure. The only constaint is that the same depth must be used throughout the structure.
+ * @return {Array} A tree structure containing the headers to be rendered. Must include the colspan property at each level, which should
+ * be the sum of all child nodes beneath this node.
+ */
+ getColumnHeaders: function() {
+ return this.grid.topAxis.buildHeaders();;
+ },
+
+ /**
+ * Returns the headers to be rendered on the left of the grid. Should be a 2-dimensional array, where each item specifies the number
+ * of rows it groups. In the example below we have 5 city groups, which are each part of a continent supergroup. The rowspan for each
+ * city group refers to the number of normal grid columns that group spans, so in this case the grid would be expected to have a
+ * total of 12 rows:
+<pre><code>
+[
+ {
+ width: 90,
+ items: [
+ {header: 'England', rowspan: 5},
+ {header: 'USA', rowspan: 3}
+ ]
+ },
+ {
+ width: 50,
+ items: [
+ {header: 'London', rowspan: 2},
+ {header: 'Cambridge', rowspan: 3},
+ {header: 'Palo Alto', rowspan: 3}
+ ]
+ }
+]
+</code></pre>
+ * In the example above we have cities nested under countries. The nesting could be deeper if desired - e.g. Continent -> Country ->
+ * State -> City, or any other structure. The only constaint is that the same depth must be used throughout the structure.
+ * @return {Array} A tree structure containing the headers to be rendered. Must include the colspan property at each level, which should
+ * be the sum of all child nodes beneath this node.
+ * Each group may specify the width it should be rendered with.
+ * @return {Array} The row groups
+ */
+ getRowHeaders: function() {
+ return this.grid.leftAxis.buildHeaders();
+ },
+
+ /**
+ * @private
+ * Renders rows between start and end indexes
+ * @param {Number} startRow Index of the first row to render
+ * @param {Number} endRow Index of the last row to render
+ */
+ renderRows : function(startRow, endRow) {
+ var grid = this.grid,
+ rows = grid.extractData(),
+ rowCount = rows.length,
+ templates = this.templates,
+ renderer = grid.renderer,
+ hasRenderer = typeof renderer == 'function',
+ getCellCls = this.getCellCls,
+ hasGetCellCls = typeof getCellCls == 'function',
+ cellTemplate = templates.cell,
+ rowTemplate = templates.row,
+ rowBuffer = [],
+ meta = {},
+ tstyle = 'width:' + this.getGridInnerWidth() + 'px;',
+ colBuffer, column, i;
+
+ startRow = startRow || 0;
+ endRow = Ext.isDefined(endRow) ? endRow : rowCount - 1;
+
+ for (i = 0; i < rowCount; i++) {
+ row = rows[i];
+ colCount = row.length;
+ colBuffer = [];
+
+ rowIndex = startRow + i;
+
+ //build up each column's HTML
+ for (j = 0; j < colCount; j++) {
+ cell = row[j];
+
+ meta.css = j === 0 ? 'x-grid3-cell-first ' : (j == (colCount - 1) ? 'x-grid3-cell-last ' : '');
+ meta.attr = meta.cellAttr = '';
+ meta.value = cell;
+
+ if (Ext.isEmpty(meta.value)) {
+ meta.value = ' ';
+ }
+
+ if (hasRenderer) {
+ meta.value = renderer(meta.value);
+ }
+
+ if (hasGetCellCls) {
+ meta.css += getCellCls(meta.value) + ' ';
+ }
+
+ colBuffer[colBuffer.length] = cellTemplate.apply(meta);
+ }
+
+ rowBuffer[rowBuffer.length] = rowTemplate.apply({
+ tstyle: tstyle,
+ cols : colCount,
+ cells : colBuffer.join(""),
+ alt : ''
+ });
+ }
+
+ return rowBuffer.join("");
+ },
+
+ /**
+ * The master template to use when rendering the GridView. Has a default template
+ * @property Ext.Template
+ * @type masterTpl
+ */
+ masterTpl: new Ext.Template(
+ '<div class="x-grid3 x-pivotgrid" hidefocus="true">',
+ '<div class="x-grid3-viewport">',
+ '<div class="x-grid3-header">',
+ '<div class="x-grid3-header-title"><span>{title}</span></div>',
+ '<div class="x-grid3-header-inner">',
+ '<div class="x-grid3-header-offset" style="{ostyle}"></div>',
+ '</div>',
+ '<div class="x-clear"></div>',
+ '</div>',
+ '<div class="x-grid3-scroller">',
+ '<div class="x-grid3-row-headers"></div>',
+ '<div class="x-grid3-body" style="{bstyle}">{body}</div>',
+ '<a href="#" class="x-grid3-focus" tabIndex="-1"></a>',
+ '</div>',
+ '</div>',
+ '<div class="x-grid3-resize-marker"> </div>',
+ '<div class="x-grid3-resize-proxy"> </div>',
+ '</div>'
+ ),
+
+ /**
+ * @private
+ * Adds a gcell template to the internal templates object. This is used to render the headers in a multi-level column header.
+ */
+ initTemplates: function() {
+ Ext.grid.PivotGridView.superclass.initTemplates.apply(this, arguments);
+
+ var templates = this.templates || {};
+ if (!templates.gcell) {
+ templates.gcell = new Ext.XTemplate(
+ '<td class="x-grid3-hd x-grid3-gcell x-grid3-td-{id} ux-grid-hd-group-row-{row} ' + this.colHeaderCellCls + '" style="{style}">',
+ '<div {tooltip} class="x-grid3-hd-inner x-grid3-hd-{id}" unselectable="on" style="{istyle}">',
+ this.grid.enableHdMenu ? '<a class="x-grid3-hd-btn" href="#"></a>' : '', '{value}',
+ '</div>',
+ '</td>'
+ );
+ }
+
+ this.templates = templates;
+ this.hrowRe = new RegExp("ux-grid-hd-group-row-(\\d+)", "");
+ },
+
+ /**
+ * @private
+ * Sets up the reference to the row headers element
+ */
+ initElements: function() {
+ Ext.grid.PivotGridView.superclass.initElements.apply(this, arguments);
+
+ /**
+ * @property rowHeadersEl
+ * @type Ext.Element
+ * The element containing all row headers
+ */
+ this.rowHeadersEl = new Ext.Element(this.scroller.child('div.x-grid3-row-headers'));
+
+ /**
+ * @property headerTitleEl
+ * @type Ext.Element
+ * The element that contains the optional title (top left section of the pivot grid)
+ */
+ this.headerTitleEl = new Ext.Element(this.mainHd.child('div.x-grid3-header-title'));
+ },
+
+ /**
+ * @private
+ * Takes row headers into account when calculating total available width
+ */
+ getGridInnerWidth: function() {
+ var previousWidth = Ext.grid.PivotGridView.superclass.getGridInnerWidth.apply(this, arguments);
+
+ return previousWidth - this.getTotalRowHeaderWidth();
+ },
+
+ /**
+ * Returns the total width of all row headers as specified by {@link #getRowHeaders}
+ * @return {Number} The total width
+ */
+ getTotalRowHeaderWidth: function() {
+ var headers = this.getRowHeaders(),
+ length = headers.length,
+ total = 0,
+ i;
+
+ for (i = 0; i< length; i++) {
+ total += headers[i].width;
+ }
+
+ return total;
+ },
+
+ /**
+ * @private
+ * Returns the total height of all column headers
+ * @return {Number} The total height
+ */
+ getTotalColumnHeaderHeight: function() {
+ return this.getColumnHeaders().length * 21;
+ },
+
+ /**
+ * @private
+ * Slight specialisation of the GridView renderUI - just adds the row headers
+ */
+ renderUI : function() {
+ var templates = this.templates,
+ innerWidth = this.getGridInnerWidth();
+
+ return templates.master.apply({
+ body : templates.body.apply({rows:' '}),
+ ostyle: 'width:' + innerWidth + 'px',
+ bstyle: 'width:' + innerWidth + 'px'
+ });
+ },
+
+ /**
+ * @private
+ * Make sure that the headers and rows are all sized correctly during layout
+ */
+ onLayout: function(width, height) {
+ Ext.grid.PivotGridView.superclass.onLayout.apply(this, arguments);
+
+ var width = this.getGridInnerWidth();
+
+ this.resizeColumnHeaders(width);
+ this.resizeAllRows(width);
+ },
+
+ /**
+ * Refreshs the grid UI
+ * @param {Boolean} headersToo (optional) True to also refresh the headers
+ */
+ refresh : function(headersToo) {
+ this.fireEvent('beforerefresh', this);
+ this.grid.stopEditing(true);
+
+ var result = this.renderBody();
+ this.mainBody.update(result).setWidth(this.getGridInnerWidth());
+ if (headersToo === true) {
+ this.updateHeaders();
+ this.updateHeaderSortState();
+ }
+ this.processRows(0, true);
+ this.layout();
+ this.applyEmptyText();
+ this.fireEvent('refresh', this);
+ },
+
+ /**
+ * @private
+ * Bypasses GridView's renderHeaders as they are taken care of separately by the PivotAxis instances
+ */
+ renderHeaders: Ext.emptyFn,
+
+ /**
+ * @private
+ * Taken care of by PivotAxis
+ */
+ fitColumns: Ext.emptyFn,
+
+ /**
+ * @private
+ * Called on layout, ensures that the width of each column header is correct. Omitting this can lead to faulty
+ * layouts when nested in a container.
+ * @param {Number} width The new width
+ */
+ resizeColumnHeaders: function(width) {
+ var topAxis = this.grid.topAxis;
+
+ if (topAxis.rendered) {
+ topAxis.el.setWidth(width);
+ }
+ },
+
+ /**
+ * @private
+ * Sets the row header div to the correct width. Should be called after rendering and reconfiguration of headers
+ */
+ resizeRowHeaders: function() {
+ var rowHeaderWidth = this.getTotalRowHeaderWidth(),
+ marginStyle = String.format("margin-left: {0}px;", rowHeaderWidth);
+
+ this.rowHeadersEl.setWidth(rowHeaderWidth);
+ this.mainBody.applyStyles(marginStyle);
+ Ext.fly(this.innerHd).applyStyles(marginStyle);
+
+ this.headerTitleEl.setWidth(rowHeaderWidth);
+ this.headerTitleEl.setHeight(this.getTotalColumnHeaderHeight());
+ },
+
+ /**
+ * @private
+ * Resizes all rendered rows to the given width. Usually called by onLayout
+ * @param {Number} width The new width
+ */
+ resizeAllRows: function(width) {
+ var rows = this.getRows(),
+ length = rows.length,
+ i;
+
+ for (i = 0; i < length; i++) {
+ Ext.fly(rows[i]).setWidth(width);
+ Ext.fly(rows[i]).child('table').setWidth(width);
+ }
+ },
+
+ /**
+ * @private
+ * Updates the Row Headers, deferring the updating of Column Headers to GridView
+ */
+ updateHeaders: function() {
+ this.renderGroupRowHeaders();
+ this.renderGroupColumnHeaders();
+ },
+
+ /**
+ * @private
+ * Renders all row header groups at all levels based on the structure fetched from {@link #getGroupRowHeaders}
+ */
+ renderGroupRowHeaders: function() {
+ var leftAxis = this.grid.leftAxis;
+
+ this.resizeRowHeaders();
+ leftAxis.rendered = false;
+ leftAxis.render(this.rowHeadersEl);
+
+ this.setTitle(this.title);
+ },
+
+ /**
+ * Sets the title text in the top left segment of the PivotGridView
+ * @param {String} title The title
+ */
+ setTitle: function(title) {
+ this.headerTitleEl.child('span').dom.innerHTML = title;
},
-
- // private
- onColumnSplitterMoved : function(i, w){
- this.userResized = true;
- var cm = this.grid.colModel;
- cm.setColumnWidth(i, w, true);
-
- if(this.forceFit){
- this.fitColumns(true, false, i);
- this.updateAllColumnWidths();
- }else{
- this.updateColumnWidth(i, w);
- this.syncHeaderScroll();
- }
-
- this.grid.fireEvent('columnresize', i, w);
+
+ /**
+ * @private
+ * Renders all column header groups at all levels based on the structure fetched from {@link #getColumnHeaders}
+ */
+ renderGroupColumnHeaders: function() {
+ var topAxis = this.grid.topAxis;
+
+ topAxis.rendered = false;
+ topAxis.render(this.innerHd.firstChild);
},
-
- // private
- handleHdMenuClick : function(item){
- var index = this.hdCtxIndex,
- cm = this.cm,
- ds = this.ds,
- id = item.getItemId();
- switch(id){
- case 'asc':
- ds.sort(cm.getDataIndex(index), 'ASC');
- break;
- case 'desc':
- ds.sort(cm.getDataIndex(index), 'DESC');
- break;
- default:
- index = cm.getIndexById(id.substr(4));
- if(index != -1){
- if(item.checked && cm.getColumnsBy(this.isHideableColumn, this).length <= 1){
- this.onDenyColumnHide();
- return false;
- }
- cm.setHidden(index, item.checked);
- }
- }
+
+ /**
+ * @private
+ * Overridden to test whether the user is hovering over a group cell, in which case we don't show the menu
+ */
+ isMenuDisabled: function(cellIndex, el) {
return true;
- },
+ }
+});/**
+ * @class Ext.grid.PivotAxis
+ * @extends Ext.Component
+ * <p>PivotAxis is a class that supports a {@link Ext.grid.PivotGrid}. Each PivotGrid contains two PivotAxis instances - the left
+ * axis and the top axis. Each PivotAxis defines an ordered set of dimensions, each of which should correspond to a field in a
+ * Store's Record (see {@link Ext.grid.PivotGrid} documentation for further explanation).</p>
+ * <p>Developers should have little interaction with the PivotAxis instances directly as most of their management is performed by
+ * the PivotGrid. An exception is the dynamic reconfiguration of axes at run time - to achieve this we use PivotAxis's
+ * {@link #setDimensions} function and refresh the grid:</p>
+<pre><code>
+var pivotGrid = new Ext.grid.PivotGrid({
+ //some PivotGrid config here
+});
- // private
- isHideableColumn : function(c){
- return !c.hidden;
+//change the left axis dimensions
+pivotGrid.leftAxis.setDimensions([
+ {
+ dataIndex: 'person',
+ direction: 'DESC',
+ width : 100
},
+ {
+ dataIndex: 'product',
+ direction: 'ASC',
+ width : 80
+ }
+]);
- // private
- beforeColMenuShow : function(){
- var cm = this.cm, colCount = cm.getColumnCount();
- this.colMenu.removeAll();
- for(var i = 0; i < colCount; i++){
- if(cm.config[i].hideable !== false){
- this.colMenu.add(new Ext.menu.CheckItem({
- itemId: 'col-'+cm.getColumnId(i),
- text: cm.getColumnHeader(i),
- checked: !cm.isHidden(i),
- hideOnClick:false,
- disabled: cm.config[i].hideable === false
- }));
- }
- }
+pivotGrid.view.refresh(true);
+</code></pre>
+ * This clears the previous dimensions on the axis and redraws the grid with the new dimensions.
+ */
+Ext.grid.PivotAxis = Ext.extend(Ext.Component, {
+ /**
+ * @cfg {String} orientation One of 'vertical' or 'horizontal'. Defaults to horizontal
+ */
+ orientation: 'horizontal',
+
+ /**
+ * @cfg {Number} defaultHeaderWidth The width to render each row header that does not have a width specified via
+ {@link #getRowGroupHeaders}. Defaults to 80.
+ */
+ defaultHeaderWidth: 80,
+
+ /**
+ * @private
+ * @cfg {Number} paddingWidth The amount of padding used by each cell.
+ * TODO: From 4.x onwards this can be removed as it won't be needed. For now it is used to account for the differences between
+ * the content box and border box measurement models
+ */
+ paddingWidth: 7,
+
+ /**
+ * Updates the dimensions used by this axis
+ * @param {Array} dimensions The new dimensions
+ */
+ setDimensions: function(dimensions) {
+ this.dimensions = dimensions;
+ },
+
+ /**
+ * @private
+ * Builds the html table that contains the dimensions for this axis. This branches internally between vertical
+ * and horizontal orientations because the table structure is slightly different in each case
+ */
+ onRender: function(ct, position) {
+ var rows = this.orientation == 'horizontal'
+ ? this.renderHorizontalRows()
+ : this.renderVerticalRows();
+
+ this.el = Ext.DomHelper.overwrite(ct.dom, {tag: 'table', cn: rows}, true);
},
+
+ /**
+ * @private
+ * Specialised renderer for horizontal oriented axes
+ * @return {Object} The HTML Domspec for a horizontal oriented axis
+ */
+ renderHorizontalRows: function() {
+ var headers = this.buildHeaders(),
+ rowCount = headers.length,
+ rows = [],
+ cells, cols, colCount, i, j;
+
+ for (i = 0; i < rowCount; i++) {
+ cells = [];
+ cols = headers[i].items;
+ colCount = cols.length;
+
+ for (j = 0; j < colCount; j++) {
+ cells.push({
+ tag: 'td',
+ html: cols[j].header,
+ colspan: cols[j].span
+ });
+ }
- // private
- handleHdDown : function(e, t){
- if(Ext.fly(t).hasClass('x-grid3-hd-btn')){
- e.stopEvent();
- var hd = this.findHeaderCell(t);
- Ext.fly(hd).addClass('x-grid3-hd-menu-open');
- var index = this.getCellIndex(hd);
- this.hdCtxIndex = index;
- var ms = this.hmenu.items, cm = this.cm;
- ms.get('asc').setDisabled(!cm.isSortable(index));
- ms.get('desc').setDisabled(!cm.isSortable(index));
- this.hmenu.on('hide', function(){
- Ext.fly(hd).removeClass('x-grid3-hd-menu-open');
- }, this, {single:true});
- this.hmenu.show(t, 'tl-bl?');
+ rows[i] = {
+ tag: 'tr',
+ cn: cells
+ };
}
+
+ return rows;
},
-
- // private
- handleHdOver : function(e, t){
- var hd = this.findHeaderCell(t);
- if(hd && !this.headersDisabled){
- this.activeHdRef = t;
- this.activeHdIndex = this.getCellIndex(hd);
- var fly = this.fly(hd);
- this.activeHdRegion = fly.getRegion();
- if(!this.cm.isMenuDisabled(this.activeHdIndex)){
- fly.addClass('x-grid3-hd-over');
- this.activeHdBtn = fly.child('.x-grid3-hd-btn');
- if(this.activeHdBtn){
- this.activeHdBtn.dom.style.height = (hd.firstChild.offsetHeight-1)+'px';
- }
+
+ /**
+ * @private
+ * Specialised renderer for vertical oriented axes
+ * @return {Object} The HTML Domspec for a vertical oriented axis
+ */
+ renderVerticalRows: function() {
+ var headers = this.buildHeaders(),
+ colCount = headers.length,
+ rowCells = [],
+ rows = [],
+ rowCount, col, row, colWidth, i, j;
+
+ for (i = 0; i < colCount; i++) {
+ col = headers[i];
+ colWidth = col.width || 80;
+ rowCount = col.items.length;
+
+ for (j = 0; j < rowCount; j++) {
+ row = col.items[j];
+
+ rowCells[row.start] = rowCells[row.start] || [];
+ rowCells[row.start].push({
+ tag : 'td',
+ html : row.header,
+ rowspan: row.span,
+ width : Ext.isBorderBox ? colWidth : colWidth - this.paddingWidth
+ });
}
}
+
+ rowCount = rowCells.length;
+ for (i = 0; i < rowCount; i++) {
+ rows[i] = {
+ tag: 'tr',
+ cn : rowCells[i]
+ };
+ }
+
+ return rows;
},
-
- // private
- handleHdMove : function(e, t){
- var hd = this.findHeaderCell(this.activeHdRef);
- if(hd && !this.headersDisabled){
- var hw = this.splitHandleWidth || 5,
- r = this.activeHdRegion,
- x = e.getPageX(),
- ss = hd.style,
- cur = '';
- if(this.grid.enableColumnResize !== false){
- if(x - r.left <= hw && this.cm.isResizable(this.activeHdIndex-1)){
- cur = Ext.isAir ? 'move' : Ext.isWebKit ? 'e-resize' : 'col-resize'; // col-resize not always supported
- }else if(r.right - x <= (!this.activeHdBtn ? hw : 2) && this.cm.isResizable(this.activeHdIndex)){
- cur = Ext.isAir ? 'move' : Ext.isWebKit ? 'w-resize' : 'col-resize';
- }
+
+ /**
+ * @private
+ * Returns the set of all unique tuples based on the bound store and dimension definitions.
+ * Internally we construct a new, temporary store to make use of the multi-sort capabilities of Store. In
+ * 4.x this functionality should have been moved to MixedCollection so this step should not be needed.
+ * @return {Array} All unique tuples
+ */
+ getTuples: function() {
+ var newStore = new Ext.data.Store({});
+
+ newStore.data = this.store.data.clone();
+ newStore.fields = this.store.fields;
+
+ var sorters = [],
+ dimensions = this.dimensions,
+ length = dimensions.length,
+ i;
+
+ for (i = 0; i < length; i++) {
+ sorters.push({
+ field : dimensions[i].dataIndex,
+ direction: dimensions[i].direction || 'ASC'
+ });
+ }
+
+ newStore.sort(sorters);
+
+ var records = newStore.data.items,
+ hashes = [],
+ tuples = [],
+ recData, hash, info, data, key;
+
+ length = records.length;
+
+ for (i = 0; i < length; i++) {
+ info = this.getRecordInfo(records[i]);
+ data = info.data;
+ hash = "";
+
+ for (key in data) {
+ hash += data[key] + '---';
+ }
+
+ if (hashes.indexOf(hash) == -1) {
+ hashes.push(hash);
+ tuples.push(info);
}
- ss.cursor = cur;
}
+
+ newStore.destroy();
+
+ return tuples;
},
-
- // private
- handleHdOut : function(e, t){
- var hd = this.findHeaderCell(t);
- if(hd && (!Ext.isIE || !e.within(hd, true))){
- this.activeHdRef = null;
- this.fly(hd).removeClass('x-grid3-hd-over');
- hd.style.cursor = '';
+
+ /**
+ * @private
+ */
+ getRecordInfo: function(record) {
+ var dimensions = this.dimensions,
+ length = dimensions.length,
+ data = {},
+ dimension, dataIndex, i;
+
+ //get an object containing just the data we are interested in based on the configured dimensions
+ for (i = 0; i < length; i++) {
+ dimension = dimensions[i];
+ dataIndex = dimension.dataIndex;
+
+ data[dataIndex] = record.get(dataIndex);
}
+
+ //creates a specialised matcher function for a given tuple. The returned function will return
+ //true if the record passed to it matches the dataIndex values of each dimension in this axis
+ var createMatcherFunction = function(data) {
+ return function(record) {
+ for (var dataIndex in data) {
+ if (record.get(dataIndex) != data[dataIndex]) {
+ return false;
+ }
+ }
+
+ return true;
+ };
+ };
+
+ return {
+ data: data,
+ matcher: createMatcherFunction(data)
+ };
},
-
- // private
- hasRows : function(){
- var fc = this.mainBody.dom.firstChild;
- return fc && fc.nodeType == 1 && fc.className != 'x-grid-empty';
- },
-
- // back compat
- bind : function(d, c){
- this.initData(d, c);
- }
-});
-
-
-// private
-// This is a support class used internally by the Grid components
-Ext.grid.GridView.SplitDragZone = Ext.extend(Ext.dd.DDProxy, {
- constructor: function(grid, hd){
- this.grid = grid;
- this.view = grid.getView();
- this.marker = this.view.resizeMarker;
- this.proxy = this.view.resizeProxy;
- Ext.grid.GridView.SplitDragZone.superclass.constructor.call(this, hd,
- 'gridSplitters' + this.grid.getGridEl().id, {
- dragElId : Ext.id(this.proxy.dom), resizeFrame:false
- });
- this.scroll = false;
- this.hw = this.view.splitHandleWidth || 5;
- },
-
- b4StartDrag : function(x, y){
- this.dragHeadersDisabled = this.view.headersDisabled;
- this.view.headersDisabled = true;
- var h = this.view.mainWrap.getHeight();
- this.marker.setHeight(h);
- this.marker.show();
- this.marker.alignTo(this.view.getHeaderCell(this.cellIndex), 'tl-tl', [-2, 0]);
- this.proxy.setHeight(h);
- var w = this.cm.getColumnWidth(this.cellIndex),
- minw = Math.max(w-this.grid.minColumnWidth, 0);
- this.resetConstraints();
- this.setXConstraint(minw, 1000);
- this.setYConstraint(0, 0);
- this.minX = x - minw;
- this.maxX = x + 1000;
- this.startPos = x;
- Ext.dd.DDProxy.prototype.b4StartDrag.call(this, x, y);
- },
-
- allowHeaderDrag : function(e){
- return true;
- },
-
- handleMouseDown : function(e){
- var t = this.view.findHeaderCell(e.getTarget());
- if(t && this.allowHeaderDrag(e)){
- var xy = this.view.fly(t).getXY(),
- x = xy[0],
- y = xy[1],
- exy = e.getXY(), ex = exy[0],
- w = t.offsetWidth, adjust = false;
+ /**
+ * @private
+ * Uses the calculated set of tuples to build an array of headers that can be rendered into a table using rowspan or
+ * colspan. Basically this takes the set of tuples and spans any cells that run into one another, so if we had dimensions
+ * of Person and Product and several tuples containing different Products for the same Person, those Products would be
+ * spanned.
+ * @return {Array} The headers
+ */
+ buildHeaders: function() {
+ var tuples = this.getTuples(),
+ rowCount = tuples.length,
+ dimensions = this.dimensions,
+ colCount = dimensions.length,
+ headers = [],
+ tuple, rows, currentHeader, previousHeader, span, start, isLast, changed, i, j;
+
+ for (i = 0; i < colCount; i++) {
+ dimension = dimensions[i];
+ rows = [];
+ span = 0;
+ start = 0;
+
+ for (j = 0; j < rowCount; j++) {
+ tuple = tuples[j];
+ isLast = j == (rowCount - 1);
+ currentHeader = tuple.data[dimension.dataIndex];
- if((ex - x) <= this.hw){
- adjust = -1;
- }else if((x+w) - ex <= this.hw){
- adjust = 0;
- }
- if(adjust !== false){
- this.cm = this.grid.colModel;
- var ci = this.view.getCellIndex(t);
- if(adjust == -1){
- if (ci + adjust < 0) {
- return;
- }
- while(this.cm.isHidden(ci+adjust)){
- --adjust;
- if(ci+adjust < 0){
- return;
- }
- }
+ /*
+ * 'changed' indicates that we need to create a new cell. This should be true whenever the cell
+ * above (previousHeader) is different from this cell, or when the cell on the previous dimension
+ * changed (e.g. if the current dimension is Product and the previous was Person, we need to start
+ * a new cell if Product is the same but Person changed, so we check the previous dimension and tuple)
+ */
+ changed = previousHeader != undefined && previousHeader != currentHeader;
+ if (i > 0 && j > 0) {
+ changed = changed || tuple.data[dimensions[i-1].dataIndex] != tuples[j-1].data[dimensions[i-1].dataIndex];
}
- this.cellIndex = ci+adjust;
- this.split = t.dom;
- if(this.cm.isResizable(this.cellIndex) && !this.cm.isFixed(this.cellIndex)){
- Ext.grid.GridView.SplitDragZone.superclass.handleMouseDown.apply(this, arguments);
+
+ if (changed) {
+ rows.push({
+ header: previousHeader,
+ span : span,
+ start : start
+ });
+
+ start += span;
+ span = 0;
}
- }else if(this.view.columnDrag){
- this.view.columnDrag.callHandleMouseDown(e);
+
+ if (isLast) {
+ rows.push({
+ header: currentHeader,
+ span : span + 1,
+ start : start
+ });
+
+ start += span;
+ span = 0;
+ }
+
+ previousHeader = currentHeader;
+ span++;
}
- }
- },
-
- endDrag : function(e){
- this.marker.hide();
- var v = this.view,
- endX = Math.max(this.minX, e.getPageX()),
- diff = endX - this.startPos,
- disabled = this.dragHeadersDisabled;
- v.onColumnSplitterMoved(this.cellIndex, this.cm.getColumnWidth(this.cellIndex)+diff);
- setTimeout(function(){
- v.headersDisabled = disabled;
- }, 50);
- },
-
- autoOffset : function(){
- this.setDelta(0,0);
+ headers.push({
+ items: rows,
+ width: dimension.width || this.defaultHeaderWidth
+ });
+
+ previousHeader = undefined;
+ }
+
+ return headers;
}
});
// private
* <tt><b>{@link #defaults}</b></tt> config property.
*/
defaultWidth: 100,
+
/**
* @cfg {Boolean} defaultSortable (optional) Default sortable of columns which have no
* sortable specified (defaults to <tt>false</tt>). This property shall preferably be configured
* through the <tt><b>{@link #defaults}</b></tt> config property.
*/
defaultSortable: false,
+
/**
* @cfg {Array} columns An Array of object literals. The config options defined by
* <b>{@link Ext.grid.Column}</b> are the options which may appear in the object literal for each
* individual column definition.
*/
+
/**
* @cfg {Object} defaults Object literal which will be used to apply {@link Ext.grid.Column}
* configuration options to all <tt><b>{@link #columns}</b></tt>. Configuration options specified with
* individual {@link Ext.grid.Column column} configs will supersede these <tt><b>{@link #defaults}</b></tt>.
*/
- constructor : function(config){
+ constructor : function(config) {
/**
* An Array of {@link Ext.grid.Column Column definition} objects representing the configuration
* of this ColumnModel. See {@link Ext.grid.Column} for the configuration properties that may
* @property config
* @type Array
*/
- if(config.columns){
+ if (config.columns) {
Ext.apply(this, config);
this.setConfig(config.columns, true);
- }else{
+ } else {
this.setConfig(config, true);
}
+
this.addEvents(
/**
* @event widthchange
* @param {Number} newWidth The new width
*/
"widthchange",
+
/**
* @event headerchange
* Fires when the text of a header changes.
* @param {String} newText The new header text
*/
"headerchange",
+
/**
* @event hiddenchange
* Fires when a column is hidden or "unhidden".
* @param {Boolean} hidden true if hidden, false otherwise
*/
"hiddenchange",
+
/**
* @event columnmoved
* Fires when a column is moved.
* @param {Number} newIndex
*/
"columnmoved",
+
/**
* @event configchange
* Fires when the configuration is changed
*/
"configchange"
);
+
Ext.grid.ColumnModel.superclass.constructor.call(this);
},
* @param {Number} index The column index
* @return {String} the id
*/
- getColumnId : function(index){
+ getColumnId : function(index) {
return this.config[index].id;
},
- getColumnAt : function(index){
+ getColumnAt : function(index) {
return this.config[index];
},
* @param {Boolean} initial Specify <tt>true</tt> to bypass cleanup which deletes the <tt>totalWidth</tt>
* and destroys existing editors.
*/
- setConfig : function(config, initial){
+ setConfig : function(config, initial) {
var i, c, len;
- if(!initial){ // cleanup
+
+ if (!initial) { // cleanup
delete this.totalWidth;
- for(i = 0, len = this.config.length; i < len; i++){
+
+ for (i = 0, len = this.config.length; i < len; i++) {
c = this.config[i];
- if(c.setEditor){
+
+ if (c.setEditor) {
//check here, in case we have a special column like a CheckboxSelectionModel
c.setEditor(null);
}
this.config = config;
this.lookup = {};
- for(i = 0, len = config.length; i < len; i++){
+ for (i = 0, len = config.length; i < len; i++) {
c = Ext.applyIf(config[i], this.defaults);
+
// if no id, create one using column's ordinal position
- if(Ext.isEmpty(c.id)){
+ if (Ext.isEmpty(c.id)) {
c.id = i;
}
- if(!c.isColumn){
+
+ if (!c.isColumn) {
var Cls = Ext.grid.Column.types[c.xtype || 'gridcolumn'];
c = new Cls(c);
config[i] = c;
}
+
this.lookup[c.id] = c;
}
- if(!initial){
+
+ if (!initial) {
this.fireEvent('configchange', this);
}
},
* @param {String} id The column id
* @return {Object} the column
*/
- getColumnById : function(id){
+ getColumnById : function(id) {
return this.lookup[id];
},
* @param {String} id The column id
* @return {Number} the index, or -1 if not found
*/
- getIndexById : function(id){
- for(var i = 0, len = this.config.length; i < len; i++){
- if(this.config[i].id == id){
+ getIndexById : function(id) {
+ for (var i = 0, len = this.config.length; i < len; i++) {
+ if (this.config[i].id == id) {
return i;
}
}
* @param {Number} oldIndex The index of the column to move.
* @param {Number} newIndex The position at which to reinsert the coolumn.
*/
- moveColumn : function(oldIndex, newIndex){
- var c = this.config[oldIndex];
- this.config.splice(oldIndex, 1);
- this.config.splice(newIndex, 0, c);
+ moveColumn : function(oldIndex, newIndex) {
+ var config = this.config,
+ c = config[oldIndex];
+
+ config.splice(oldIndex, 1);
+ config.splice(newIndex, 0, c);
this.dataMap = null;
this.fireEvent("columnmoved", this, oldIndex, newIndex);
},
* @param {Boolean} visibleOnly Optional. Pass as true to only include visible columns.
* @return {Number}
*/
- getColumnCount : function(visibleOnly){
- if(visibleOnly === true){
- var c = 0;
- for(var i = 0, len = this.config.length; i < len; i++){
- if(!this.isHidden(i)){
+ getColumnCount : function(visibleOnly) {
+ var length = this.config.length,
+ c = 0,
+ i;
+
+ if (visibleOnly === true) {
+ for (i = 0; i < length; i++) {
+ if (!this.isHidden(i)) {
c++;
}
}
+
return c;
}
- return this.config.length;
+
+ return length;
},
/**
* is executed. Defaults to this ColumnModel.
* @return {Array} result
*/
- getColumnsBy : function(fn, scope){
- var r = [];
- for(var i = 0, len = this.config.length; i < len; i++){
- var c = this.config[i];
- if(fn.call(scope||this, c, i) === true){
- r[r.length] = c;
+ getColumnsBy : function(fn, scope) {
+ var config = this.config,
+ length = config.length,
+ result = [],
+ i, c;
+
+ for (i = 0; i < length; i++){
+ c = config[i];
+
+ if (fn.call(scope || this, c, i) === true) {
+ result[result.length] = c;
}
}
- return r;
+
+ return result;
},
/**
* @param {Number} col The column index
* @return {Boolean}
*/
- isSortable : function(col){
+ isSortable : function(col) {
return !!this.config[col].sortable;
},
* @param {Number} col The column index
* @return {Boolean}
*/
- isMenuDisabled : function(col){
+ isMenuDisabled : function(col) {
return !!this.config[col].menuDisabled;
},
* @param {Number} col The column index.
* @return {Function} The function used to render the cell. See {@link #setRenderer}.
*/
- getRenderer : function(col){
- if(!this.config[col].renderer){
- return Ext.grid.ColumnModel.defaultRenderer;
- }
- return this.config[col].renderer;
+ getRenderer : function(col) {
+ return this.config[col].renderer || Ext.grid.ColumnModel.defaultRenderer;
},
- getRendererScope : function(col){
+ getRendererScope : function(col) {
return this.config[col].scope;
},
* <li><b>colIndex</b> : Number<p class="sub-desc">Column index</p></li>
* <li><b>store</b> : Ext.data.Store<p class="sub-desc">The {@link Ext.data.Store} object from which the Record was extracted.</p></li></ul>
*/
- setRenderer : function(col, fn){
+ setRenderer : function(col, fn) {
this.config[col].renderer = fn;
},
* @param {Number} col The column index
* @return {Number}
*/
- getColumnWidth : function(col){
- return this.config[col].width;
+ getColumnWidth : function(col) {
+ var width = this.config[col].width;
+ if(typeof width != 'number'){
+ width = this.defaultWidth;
+ }
+ return width;
},
/**
* @param {Boolean} suppressEvent True to suppress firing the <code>{@link #widthchange}</code>
* event. Defaults to false.
*/
- setColumnWidth : function(col, width, suppressEvent){
+ setColumnWidth : function(col, width, suppressEvent) {
this.config[col].width = width;
this.totalWidth = null;
- if(!suppressEvent){
+
+ if (!suppressEvent) {
this.fireEvent("widthchange", this, col, width);
}
},
* @param {Boolean} includeHidden True to include hidden column widths
* @return {Number}
*/
- getTotalWidth : function(includeHidden){
- if(!this.totalWidth){
+ getTotalWidth : function(includeHidden) {
+ if (!this.totalWidth) {
this.totalWidth = 0;
- for(var i = 0, len = this.config.length; i < len; i++){
- if(includeHidden || !this.isHidden(i)){
+ for (var i = 0, len = this.config.length; i < len; i++) {
+ if (includeHidden || !this.isHidden(i)) {
this.totalWidth += this.getColumnWidth(i);
}
}
* @param {Number} col The column index
* @return {String}
*/
- getColumnHeader : function(col){
+ getColumnHeader : function(col) {
return this.config[col].header;
},
* @param {Number} col The column index
* @param {String} header The new header
*/
- setColumnHeader : function(col, header){
+ setColumnHeader : function(col, header) {
this.config[col].header = header;
this.fireEvent("headerchange", this, col, header);
},
* @param {Number} col The column index
* @return {String}
*/
- getColumnTooltip : function(col){
+ getColumnTooltip : function(col) {
return this.config[col].tooltip;
},
/**
* @param {Number} col The column index
* @param {String} tooltip The new tooltip
*/
- setColumnTooltip : function(col, tooltip){
+ setColumnTooltip : function(col, tooltip) {
this.config[col].tooltip = tooltip;
},
* @param {Number} col The column index
* @return {String} The column's dataIndex
*/
- getDataIndex : function(col){
+ getDataIndex : function(col) {
return this.config[col].dataIndex;
},
* @param {Number} col The column index
* @param {String} dataIndex The new dataIndex
*/
- setDataIndex : function(col, dataIndex){
+ setDataIndex : function(col, dataIndex) {
this.config[col].dataIndex = dataIndex;
},
* @param {String} col The dataIndex to find
* @return {Number} The column index, or -1 if no match was found
*/
- findColumnIndex : function(dataIndex){
+ findColumnIndex : function(dataIndex) {
var c = this.config;
for(var i = 0, len = c.length; i < len; i++){
if(c[i].dataIndex == dataIndex){
* @param {Number} rowIndex The row index
* @return {Boolean}
*/
- isCellEditable : function(colIndex, rowIndex){
+ isCellEditable : function(colIndex, rowIndex) {
var c = this.config[colIndex],
ed = c.editable;
* @return {Ext.Editor} The {@link Ext.Editor Editor} that was created to wrap
* the {@link Ext.form.Field Field} used to edit the cell.
*/
- getCellEditor : function(colIndex, rowIndex){
+ getCellEditor : function(colIndex, rowIndex) {
return this.config[colIndex].getCellEditor(rowIndex);
},
* @param {Number} col The column index
* @param {Boolean} editable True if the column is editable
*/
- setEditable : function(col, editable){
+ setEditable : function(col, editable) {
this.config[col].editable = editable;
},
* @param {Number} colIndex The column index
* @return {Boolean}
*/
- isHidden : function(colIndex){
+ isHidden : function(colIndex) {
return !!this.config[colIndex].hidden; // ensure returns boolean
},
* @param {Number} colIndex The column index
* @return {Boolean}
*/
- isFixed : function(colIndex){
+ isFixed : function(colIndex) {
return !!this.config[colIndex].fixed;
},
* Returns true if the column can be resized
* @return {Boolean}
*/
- isResizable : function(colIndex){
+ isResizable : function(colIndex) {
return colIndex >= 0 && this.config[colIndex].resizable !== false && this.config[colIndex].fixed !== true;
},
+
/**
* Sets if a column is hidden.
<pre><code>
* @param {Number} colIndex The column index
* @param {Boolean} hidden True if the column is hidden
*/
- setHidden : function(colIndex, hidden){
+ setHidden : function(colIndex, hidden) {
var c = this.config[colIndex];
if(c.hidden !== hidden){
c.hidden = hidden;
* @param {Number} col The column index
* @param {Object} editor The editor object
*/
- setEditor : function(col, editor){
+ setEditor : function(col, editor) {
this.config[col].setEditor(editor);
},
/**
- * Destroys this column model by purging any event listeners, and removing any editors.
+ * Destroys this column model by purging any event listeners. Destroys and dereferences all Columns.
*/
- destroy : function(){
- var c;
- for(var i = 0, len = this.config.length; i < len; i++){
- c = this.config[i];
- if(c.setEditor){
- c.setEditor(null);
- }
+ destroy : function() {
+ var length = this.config.length,
+ i = 0;
+
+ for (; i < length; i++){
+ this.config[i].destroy(); // Column's destroy encapsulates all cleanup.
}
+ delete this.config;
+ delete this.lookup;
this.purgeListeners();
+ },
+
+ /**
+ * @private
+ * Setup any saved state for the column, ensures that defaults are applied.
+ */
+ setState : function(col, state) {
+ state = Ext.applyIf(state, this.defaults);
+ Ext.apply(this.config[col], state);
}
});
// private
-Ext.grid.ColumnModel.defaultRenderer = function(value){
- if(typeof value == "string" && value.length < 1){
+Ext.grid.ColumnModel.defaultRenderer = function(value) {
+ if (typeof value == "string" && value.length < 1) {
return " ";
}
return value;
}
this.rowNav = new Ext.KeyNav(this.grid.getGridEl(), {
- 'up' : function(e){
- if(!e.shiftKey || this.singleSelect){
- this.selectPrevious(false);
- }else if(this.last !== false && this.lastActive !== false){
- var last = this.last;
- this.selectRange(this.last, this.lastActive-1);
- this.grid.getView().focusRow(this.lastActive);
- if(last !== false){
- this.last = last;
- }
- }else{
- this.selectFirstRow();
- }
- },
- 'down' : function(e){
- if(!e.shiftKey || this.singleSelect){
- this.selectNext(false);
- }else if(this.last !== false && this.lastActive !== false){
- var last = this.last;
- this.selectRange(this.last, this.lastActive+1);
- this.grid.getView().focusRow(this.lastActive);
- if(last !== false){
- this.last = last;
- }
- }else{
- this.selectFirstRow();
- }
- },
+ up: this.onKeyPress,
+ down: this.onKeyPress,
scope: this
});
rowremoved: this.onRemove
});
},
+
+ onKeyPress : function(e, name){
+ var up = name == 'up',
+ method = up ? 'selectPrevious' : 'selectNext',
+ add = up ? -1 : 1,
+ last;
+ if(!e.shiftKey || this.singleSelect){
+ this[method](false);
+ }else if(this.last !== false && this.lastActive !== false){
+ last = this.last;
+ this.selectRange(this.last, this.lastActive + add);
+ this.grid.getView().focusRow(this.lastActive);
+ if(last !== false){
+ this.last = last;
+ }
+ }else{
+ this.selectFirstRow();
+ }
+ },
// private
onRefresh : function(){
- var ds = this.grid.store, index;
- var s = this.getSelections();
+ var ds = this.grid.store,
+ s = this.getSelections(),
+ i = 0,
+ len = s.length,
+ index, r;
+
+ this.silent = true;
this.clearSelections(true);
- for(var i = 0, len = s.length; i < len; i++){
- var r = s[i];
+ for(; i < len; i++){
+ r = s[i];
if((index = ds.indexOfId(r.id)) != -1){
this.selectRow(index, true);
}
if(s.length != this.selections.getCount()){
this.fireEvent('selectionchange', this);
}
+ this.silent = false;
},
// private
if(!keepExisting){
this.clearSelections();
}
- var ds = this.grid.store;
- for(var i = 0, len = records.length; i < len; i++){
+ var ds = this.grid.store,
+ i = 0,
+ len = records.length;
+ for(; i < len; i++){
this.selectRow(ds.indexOf(records[i]), true);
}
},
* @return {Boolean} true if all selections were iterated
*/
each : function(fn, scope){
- var s = this.getSelections();
- for(var i = 0, len = s.length; i < len; i++){
+ var s = this.getSelections(),
+ i = 0,
+ len = s.length;
+
+ for(; i < len; i++){
if(fn.call(scope || this, s[i], i) === false){
return false;
}
return;
}
if(fast !== true){
- var ds = this.grid.store;
- var s = this.selections;
+ var ds = this.grid.store,
+ s = this.selections;
s.each(function(r){
this.deselectRow(ds.indexOfId(r.id));
}, this);
if(!preventViewNotify){
this.grid.getView().onRowSelect(index);
}
- this.fireEvent('rowselect', this, index, r);
- this.fireEvent('selectionchange', this);
+ if(!this.silent){
+ this.fireEvent('rowselect', this, index, r);
+ this.fireEvent('selectionchange', this);
+ }
}
},
}
},
- // private
- restoreLast : function(){
- if(this._last){
- this.last = this._last;
- }
- },
-
// private
acceptsNav : function(row, col, cm){
return !cm.isHidden(col) && cm.isCellEditable(col, row);
g = this.grid,
last = g.lastEdit,
ed = g.activeEditor,
+ shift = e.shiftKey,
ae, last, r, c;
- var shift = e.shiftKey;
+
if(k == e.TAB){
e.stopEvent();
ed.completeEdit();
r = newCell[0];
c = newCell[1];
- if(last.row != r){
- this.selectRow(r); // *** highlight newly-selected cell and update selection
- }
+ this.onEditorSelect(r, last.row);
if(g.isEditor && g.editing){ // *** handle tabbing while editorgrid is in edit mode
ae = g.activeEditor;
}
},
- destroy : function(){
- if(this.rowNav){
- this.rowNav.disable();
- this.rowNav = null;
+ onEditorSelect: function(row, lastRow){
+ if(lastRow != row){
+ this.selectRow(row); // *** highlight newly-selected cell and update selection
}
+ },
+
+ destroy : function(){
+ Ext.destroy(this.rowNav);
+ this.rowNav = null;
Ext.grid.RowSelectionModel.superclass.destroy.call(this);
}
-});/**
+});
+/**
* @class Ext.grid.Column
* <p>This class encapsulates column configuration data to be used in the initialization of a
* {@link Ext.grid.ColumnModel ColumnModel}.</p>
* <p>While subclasses are provided to render data in different ways, this class renders a passed
* data field unchanged and is usually used for textual columns.</p>
*/
-Ext.grid.Column = Ext.extend(Object, {
+Ext.grid.Column = Ext.extend(Ext.util.Observable, {
/**
* @cfg {Boolean} editable Optional. Defaults to <tt>true</tt>, enabling the configured
* <tt>{@link #editor}</tt>. Set to <tt>false</tt> to initially disable editing on this column.
/**
* @cfg {String} header Optional. The header text to be used as innerHTML
* (html tags are accepted) to display in the Grid view. <b>Note</b>: to
- * have a clickable header with no text displayed use <tt>' '</tt>.
+ * have a clickable header with no text displayed use <tt>'&#160;'</tt>.
*/
/**
* @cfg {Boolean} groupable Optional. If the grid is being rendered by an {@link Ext.grid.GroupingView}, this option
var ed = this.editor;
delete this.editor;
this.setEditor(ed);
+ this.addEvents(
+ /**
+ * @event click
+ * Fires when this Column is clicked.
+ * @param {Column} this
+ * @param {Grid} The owning GridPanel
+ * @param {Number} rowIndex
+ * @param {Ext.EventObject} e
+ */
+ 'click',
+ /**
+ * @event contextmenu
+ * Fires when this Column is right clicked.
+ * @param {Column} this
+ * @param {Grid} The owning GridPanel
+ * @param {Number} rowIndex
+ * @param {Ext.EventObject} e
+ */
+ 'contextmenu',
+ /**
+ * @event dblclick
+ * Fires when this Column is double clicked.
+ * @param {Column} this
+ * @param {Grid} The owning GridPanel
+ * @param {Number} rowIndex
+ * @param {Ext.EventObject} e
+ */
+ 'dblclick',
+ /**
+ * @event mousedown
+ * Fires when this Column receives a mousedown event.
+ * @param {Column} this
+ * @param {Grid} The owning GridPanel
+ * @param {Number} rowIndex
+ * @param {Ext.EventObject} e
+ */
+ 'mousedown'
+ );
+ Ext.grid.Column.superclass.constructor.call(this);
+ },
+
+ /**
+ * @private
+ * Process and refire events routed from the GridView's processEvent method.
+ * Returns the event handler's status to allow cancelling of GridView's bubbling process.
+ */
+ processEvent : function(name, e, grid, rowIndex, colIndex){
+ return this.fireEvent(name, this, grid, rowIndex, e);
+ },
+
+ /**
+ * @private
+ * Clean up. Remove any Editor. Remove any listeners.
+ */
+ destroy: function() {
+ if(this.setEditor){
+ this.setEditor(null);
+ }
+ this.purgeListeners();
},
/**
* @type Function
*/
renderer : function(value){
- if(Ext.isString(value) && value.length < 1){
- return ' ';
- }
return value;
},
Ext.grid.BooleanColumn = Ext.extend(Ext.grid.Column, {
/**
* @cfg {String} trueText
- * The string returned by the renderer when the column value is not falsey (defaults to <tt>'true'</tt>).
+ * The string returned by the renderer when the column value is not falsy (defaults to <tt>'true'</tt>).
*/
trueText: 'true',
/**
* @cfg {String} falseText
- * The string returned by the renderer when the column value is falsey (but not undefined) (defaults to
+ * The string returned by the renderer when the column value is falsy (but not undefined) (defaults to
* <tt>'false'</tt>).
*/
falseText: 'false',
/**
* @cfg {String} undefinedText
- * The string returned by the renderer when the column value is undefined (defaults to <tt>' '</tt>).
+ * The string returned by the renderer when the column value is undefined (defaults to <tt>'&#160;'</tt>).
*/
undefinedText: ' ',
}
});
+/**
+ * @class Ext.grid.ActionColumn
+ * @extends Ext.grid.Column
+ * <p>A Grid column type which renders an icon, or a series of icons in a grid cell, and offers a scoped click
+ * handler for each icon. Example usage:</p>
+<pre><code>
+new Ext.grid.GridPanel({
+ store: myStore,
+ columns: [
+ {
+ xtype: 'actioncolumn',
+ width: 50,
+ items: [
+ {
+ icon : 'sell.gif', // Use a URL in the icon config
+ tooltip: 'Sell stock',
+ handler: function(grid, rowIndex, colIndex) {
+ var rec = store.getAt(rowIndex);
+ alert("Sell " + rec.get('company'));
+ }
+ },
+ {
+ getClass: function(v, meta, rec) { // Or return a class from a function
+ if (rec.get('change') < 0) {
+ this.items[1].tooltip = 'Do not buy!';
+ return 'alert-col';
+ } else {
+ this.items[1].tooltip = 'Buy stock';
+ return 'buy-col';
+ }
+ },
+ handler: function(grid, rowIndex, colIndex) {
+ var rec = store.getAt(rowIndex);
+ alert("Buy " + rec.get('company'));
+ }
+ }
+ ]
+ }
+ //any other columns here
+ ]
+});
+</pre></code>
+ * <p>The action column can be at any index in the columns array, and a grid can have any number of
+ * action columns. </p>
+ */
+Ext.grid.ActionColumn = Ext.extend(Ext.grid.Column, {
+ /**
+ * @cfg {String} icon
+ * The URL of an image to display as the clickable element in the column.
+ * Optional - defaults to <code>{@link Ext#BLANK_IMAGE_URL Ext.BLANK_IMAGE_URL}</code>.
+ */
+ /**
+ * @cfg {String} iconCls
+ * A CSS class to apply to the icon image. To determine the class dynamically, configure the Column with a <code>{@link #getClass}</code> function.
+ */
+ /**
+ * @cfg {Function} handler A function called when the icon is clicked.
+ * The handler is passed the following parameters:<div class="mdetail-params"><ul>
+ * <li><code>grid</code> : GridPanel<div class="sub-desc">The owning GridPanel.</div></li>
+ * <li><code>rowIndex</code> : Number<div class="sub-desc">The row index clicked on.</div></li>
+ * <li><code>colIndex</code> : Number<div class="sub-desc">The column index clicked on.</div></li>
+ * <li><code>item</code> : Object<div class="sub-desc">The clicked item (or this Column if multiple
+ * {@link #items} were not configured).</div></li>
+ * <li><code>e</code> : Event<div class="sub-desc">The click event.</div></li>
+ * </ul></div>
+ */
+ /**
+ * @cfg {Object} scope The scope (<tt><b>this</b></tt> reference) in which the <code>{@link #handler}</code>
+ * and <code>{@link #getClass}</code> fuctions are executed. Defaults to this Column.
+ */
+ /**
+ * @cfg {String} tooltip A tooltip message to be displayed on hover. {@link Ext.QuickTips#init Ext.QuickTips} must have
+ * been initialized.
+ */
+ /**
+ * @cfg {Boolean} stopSelection Defaults to <code>true</code>. Prevent grid <i>row</i> selection upon mousedown.
+ */
+ /**
+ * @cfg {Function} getClass A function which returns the CSS class to apply to the icon image.
+ * The function is passed the following parameters:<div class="mdetail-params"><ul>
+ * <li><b>v</b> : Object<p class="sub-desc">The value of the column's configured field (if any).</p></li>
+ * <li><b>metadata</b> : Object<p class="sub-desc">An object in which you may set the following attributes:<ul>
+ * <li><b>css</b> : String<p class="sub-desc">A CSS class name to add to the cell's TD element.</p></li>
+ * <li><b>attr</b> : String<p class="sub-desc">An HTML attribute definition string to apply to the data container element <i>within</i> the table cell
+ * (e.g. 'style="color:red;"').</p></li>
+ * </ul></p></li>
+ * <li><b>r</b> : Ext.data.Record<p class="sub-desc">The Record providing the data.</p></li>
+ * <li><b>rowIndex</b> : Number<p class="sub-desc">The row index..</p></li>
+ * <li><b>colIndex</b> : Number<p class="sub-desc">The column index.</p></li>
+ * <li><b>store</b> : Ext.data.Store<p class="sub-desc">The Store which is providing the data Model.</p></li>
+ * </ul></div>
+ */
+ /**
+ * @cfg {Array} items An Array which may contain multiple icon definitions, each element of which may contain:
+ * <div class="mdetail-params"><ul>
+ * <li><code>icon</code> : String<div class="sub-desc">The url of an image to display as the clickable element
+ * in the column.</div></li>
+ * <li><code>iconCls</code> : String<div class="sub-desc">A CSS class to apply to the icon image.
+ * To determine the class dynamically, configure the item with a <code>getClass</code> function.</div></li>
+ * <li><code>getClass</code> : Function<div class="sub-desc">A function which returns the CSS class to apply to the icon image.
+ * The function is passed the following parameters:<ul>
+ * <li><b>v</b> : Object<p class="sub-desc">The value of the column's configured field (if any).</p></li>
+ * <li><b>metadata</b> : Object<p class="sub-desc">An object in which you may set the following attributes:<ul>
+ * <li><b>css</b> : String<p class="sub-desc">A CSS class name to add to the cell's TD element.</p></li>
+ * <li><b>attr</b> : String<p class="sub-desc">An HTML attribute definition string to apply to the data container element <i>within</i> the table cell
+ * (e.g. 'style="color:red;"').</p></li>
+ * </ul></p></li>
+ * <li><b>r</b> : Ext.data.Record<p class="sub-desc">The Record providing the data.</p></li>
+ * <li><b>rowIndex</b> : Number<p class="sub-desc">The row index..</p></li>
+ * <li><b>colIndex</b> : Number<p class="sub-desc">The column index.</p></li>
+ * <li><b>store</b> : Ext.data.Store<p class="sub-desc">The Store which is providing the data Model.</p></li>
+ * </ul></div></li>
+ * <li><code>handler</code> : Function<div class="sub-desc">A function called when the icon is clicked.</div></li>
+ * <li><code>scope</code> : Scope<div class="sub-desc">The scope (<code><b>this</b></code> reference) in which the
+ * <code>handler</code> and <code>getClass</code> functions are executed. Fallback defaults are this Column's
+ * configured scope, then this Column.</div></li>
+ * <li><code>tooltip</code> : String<div class="sub-desc">A tooltip message to be displayed on hover.
+ * {@link Ext.QuickTips#init Ext.QuickTips} must have been initialized.</div></li>
+ * </ul></div>
+ */
+ header: ' ',
+
+ actionIdRe: /x-action-col-(\d+)/,
+
+ /**
+ * @cfg {String} altText The alt text to use for the image element. Defaults to <tt>''</tt>.
+ */
+ altText: '',
+
+ constructor: function(cfg) {
+ var me = this,
+ items = cfg.items || (me.items = [me]),
+ l = items.length,
+ i,
+ item;
+
+ Ext.grid.ActionColumn.superclass.constructor.call(me, cfg);
+
+// Renderer closure iterates through items creating an <img> element for each and tagging with an identifying
+// class name x-action-col-{n}
+ me.renderer = function(v, meta) {
+// Allow a configured renderer to create initial value (And set the other values in the "metadata" argument!)
+ v = Ext.isFunction(cfg.renderer) ? cfg.renderer.apply(this, arguments)||'' : '';
+
+ meta.css += ' x-action-col-cell';
+ for (i = 0; i < l; i++) {
+ item = items[i];
+ v += '<img alt="' + me.altText + '" src="' + (item.icon || Ext.BLANK_IMAGE_URL) +
+ '" class="x-action-col-icon x-action-col-' + String(i) + ' ' + (item.iconCls || '') +
+ ' ' + (Ext.isFunction(item.getClass) ? item.getClass.apply(item.scope||this.scope||this, arguments) : '') + '"' +
+ ((item.tooltip) ? ' ext:qtip="' + item.tooltip + '"' : '') + ' />';
+ }
+ return v;
+ };
+ },
+
+ destroy: function() {
+ delete this.items;
+ delete this.renderer;
+ return Ext.grid.ActionColumn.superclass.destroy.apply(this, arguments);
+ },
+
+ /**
+ * @private
+ * Process and refire events routed from the GridView's processEvent method.
+ * Also fires any configured click handlers. By default, cancels the mousedown event to prevent selection.
+ * Returns the event handler's status to allow cancelling of GridView's bubbling process.
+ */
+ processEvent : function(name, e, grid, rowIndex, colIndex){
+ var m = e.getTarget().className.match(this.actionIdRe),
+ item, fn;
+ if (m && (item = this.items[parseInt(m[1], 10)])) {
+ if (name == 'click') {
+ (fn = item.handler || this.handler) && fn.call(item.scope||this.scope||this, grid, rowIndex, colIndex, item, e);
+ } else if ((name == 'mousedown') && (item.stopSelection !== false)) {
+ return false;
+ }
+ }
+ return Ext.grid.ActionColumn.superclass.processEvent.apply(this, arguments);
+ }
+});
+
/*
* @property types
* @type Object
booleancolumn: Ext.grid.BooleanColumn,
numbercolumn: Ext.grid.NumberColumn,
datecolumn: Ext.grid.DateColumn,
- templatecolumn: Ext.grid.TemplateColumn
+ templatecolumn: Ext.grid.TemplateColumn,
+ actioncolumn: Ext.grid.ActionColumn
};/**
* @class Ext.grid.RowNumberer
* This is a utility class that can be passed into a {@link Ext.grid.ColumnModel} as a column config that provides
hideable: false,
dataIndex : '',
id : 'checker',
+ isColumn: true, // So that ColumnModel doesn't feed this through the Column constructor
constructor : function(){
Ext.grid.CheckboxSelectionModel.superclass.constructor.apply(this, arguments);
-
if(this.checkOnly){
this.handleMouseDown = Ext.emptyFn;
}
initEvents : function(){
Ext.grid.CheckboxSelectionModel.superclass.initEvents.call(this);
this.grid.on('render', function(){
- var view = this.grid.getView();
- view.mainBody.on('mousedown', this.onMouseDown, this);
- Ext.fly(view.innerHd).on('mousedown', this.onHdMouseDown, this);
-
+ Ext.fly(this.grid.getView().innerHd).on('mousedown', this.onHdMouseDown, this);
}, this);
},
- // If handleMouseDown was called from another event (enableDragDrop), set a flag so
- // onMouseDown does not process it a second time
- handleMouseDown : function() {
- Ext.grid.CheckboxSelectionModel.superclass.handleMouseDown.apply(this, arguments);
- this.mouseHandled = true;
+ /**
+ * @private
+ * Process and refire events routed from the GridView's processEvent method.
+ */
+ processEvent : function(name, e, grid, rowIndex, colIndex){
+ if (name == 'mousedown') {
+ this.onMouseDown(e, e.getTarget());
+ return false;
+ } else {
+ return Ext.grid.Column.prototype.processEvent.apply(this, arguments);
+ }
},
// private
if(e.button === 0 && t.className == 'x-grid3-row-checker'){ // Only fire if left-click
e.stopEvent();
var row = e.getTarget('.x-grid3-row');
-
- // mouseHandled flag check for a duplicate selection (handleMouseDown) call
- if(!this.mouseHandled && row){
+ if(row){
var index = row.rowIndex;
if(this.isSelected(index)){
this.deselectRow(index);
}
}
}
- this.mouseHandled = false;
},
// private
- onHdMouseDown : function(e, t){
+ onHdMouseDown : function(e, t) {
if(t.className == 'x-grid3-hd-checker'){
e.stopEvent();
var hd = Ext.fly(t.parentNode);
// private
renderer : function(v, p, record){
return '<div class="x-grid3-row-checker"> </div>';
+ },
+
+ onEditorSelect: function(row, lastRow){
+ if(lastRow != row && !this.checkOnly){
+ this.selectRow(row); // *** highlight newly-selected cell and update selection
+ }
}
});/**
* @class Ext.grid.CellSelectionModel
/** @ignore */
initEvents : function(){
this.grid.on('cellmousedown', this.handleMouseDown, this);
- this.grid.on(Ext.EventManager.useKeydown ? 'keydown' : 'keypress', this.handleKeyDown, this);
+ this.grid.on(Ext.EventManager.getKeyEvent(), this.handleKeyDown, this);
this.grid.getView().on({
scope: this,
refresh: this.onViewChange,
// inherit docs
destroy : function(){
Ext.grid.PropertyColumnModel.superclass.destroy.call(this);
- for(var ed in this.editors){
- Ext.destroy(this.editors[ed]);
+ this.destroyEditors(this.editors);
+ this.destroyEditors(this.grid.customEditors);
+ },
+
+ destroyEditors: function(editors){
+ for(var ed in editors){
+ Ext.destroy(editors[ed]);
}
}
});
* @cfg {Function} groupRenderer This property must be configured in the {@link Ext.grid.Column} for
* each column.
*/
+
+ /**
+ * @cfg {Boolean} cancelEditOnToggle True to cancel any editing when the group header is toggled. Defaults to <tt>true</tt>.
+ */
+ cancelEditOnToggle: true,
// private
initTemplates : function(){
}
if((item = items.get('showGroups'))){
item.setDisabled(disabled);
- item.setChecked(this.enableGrouping, true);
+ item.setChecked(this.canGroup(), true);
}
},
// private
renderUI : function(){
- Ext.grid.GroupingView.superclass.renderUI.call(this);
- this.mainBody.on('mousedown', this.interceptMouse, this);
+ var markup = Ext.grid.GroupingView.superclass.renderUI.call(this);
if(this.enableGroupingMenu && this.hmenu){
this.hmenu.add('-',{
}
this.hmenu.on('beforeshow', this.beforeMenuShow, this);
}
+ return markup;
},
processEvent: function(name, e){
// group value is at the end of the string
var field = this.getGroupField(),
prefix = this.getPrefix(field),
- groupValue = hd.id.substring(prefix.length);
+ groupValue = hd.id.substring(prefix.length),
+ emptyRe = new RegExp('gp-' + Ext.escapeRe(field) + '--hd');
// remove trailing '-hd'
groupValue = groupValue.substr(0, groupValue.length - 3);
- if(groupValue){
+
+ // also need to check for empty groups
+ if(groupValue || emptyRe.test(hd.id)){
this.grid.fireEvent('group' + name, this.grid, field, groupValue, e);
}
+ if(name == 'mousedown' && e.button == 0){
+ this.toggleGroup(hd.parentNode);
+ }
}
},
// private
onGroupByClick : function(){
+ var grid = this.grid;
this.enableGrouping = true;
- this.grid.store.groupBy(this.cm.getDataIndex(this.hdCtxIndex));
- this.grid.fireEvent('groupchange', this, this.grid.store.getGroupState());
+ grid.store.groupBy(this.cm.getDataIndex(this.hdCtxIndex));
+ grid.fireEvent('groupchange', grid, grid.store.getGroupState());
this.beforeMenuShow(); // Make sure the checkboxes get properly set when changing groups
this.refresh();
},
var gel = Ext.get(group);
expanded = Ext.isDefined(expanded) ? expanded : gel.hasClass('x-grid-group-collapsed');
if(this.state[gel.id] !== expanded){
- this.grid.stopEditing(true);
+ if (this.cancelEditOnToggle !== false) {
+ this.grid.stopEditing(true);
+ }
this.state[gel.id] = expanded;
gel[expanded ? 'removeClass' : 'addClass']('x-grid-group-collapsed');
}
this.toggleAllGroups(false);
},
- // private
- interceptMouse : function(e){
- var hd = e.getTarget('.x-grid-group-hd', this.mainBody);
- if(hd){
- e.stopEvent();
- this.toggleGroup(hd.parentNode);
- }
- },
-
// private
getGroup : function(v, r, groupRenderer, rowIndex, colIndex, ds){
- var g = groupRenderer ? groupRenderer(v, {}, r, rowIndex, colIndex, ds) : String(v);
+ var column = this.cm.config[colIndex],
+ g = groupRenderer ? groupRenderer.call(column.scope, v, {}, r, rowIndex, colIndex, ds) : String(v);
if(g === '' || g === ' '){
- g = this.cm.config[colIndex].emptyGroupText || this.emptyGroupText;
+ g = column.emptyGroupText || this.emptyGroupText;
}
return g;
},
this.updateGroupWidths();
}
},
+
+ afterRenderUI: function () {
+ Ext.grid.GroupingView.superclass.afterRenderUI.call(this);
+
+ if (this.enableGroupingMenu && this.hmenu) {
+ this.hmenu.add('-',{
+ itemId:'groupBy',
+ text: this.groupByText,
+ handler: this.onGroupByClick,
+ scope: this,
+ iconCls:'x-group-by-icon'
+ });
+
+ if (this.enableNoGroups) {
+ this.hmenu.add({
+ itemId:'showGroups',
+ text: this.showGroupsText,
+ checked: true,
+ checkHandler: this.onShowGroupsClick,
+ scope: this
+ });
+ }
+
+ this.hmenu.on('beforeshow', this.beforeMenuShow, this);
+ }
+ },
// private
renderRows : function(){