/ext-4.0.7/pkgs/classes.js
JavaScript | 14596 lines | 6692 code | 1488 blank | 6416 comment | 1428 complexity | a081e388c7e0f979768b033e444b0e49 MD5 | raw file
Large files files are truncated, but you can click here to view the full file
1/*
2
3This file is part of Ext JS 4
4
5Copyright (c) 2011 Sencha Inc
6
7Contact: http://www.sencha.com/contact
8
9GNU General Public License Usage
10This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
11
12If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14*/
15/**
16 * Base class that provides a common interface for publishing events. Subclasses are expected to to have a property
17 * "events" with all the events defined, and, optionally, a property "listeners" with configured listeners defined.
18 *
19 * For example:
20 *
21 * Ext.define('Employee', {
22 * extend: 'Ext.util.Observable',
23 * constructor: function(config){
24 * this.name = config.name;
25 * this.addEvents({
26 * "fired" : true,
27 * "quit" : true
28 * });
29 *
30 * // Copy configured listeners into *this* object so that the base class's
31 * // constructor will add them.
32 * this.listeners = config.listeners;
33 *
34 * // Call our superclass constructor to complete construction process.
35 * this.callParent(arguments)
36 * }
37 * });
38 *
39 * This could then be used like this:
40 *
41 * var newEmployee = new Employee({
42 * name: employeeName,
43 * listeners: {
44 * quit: function() {
45 * // By default, "this" will be the object that fired the event.
46 * alert(this.name + " has quit!");
47 * }
48 * }
49 * });
50 */
51Ext.define('Ext.util.Observable', {
52
53 /* Begin Definitions */
54
55 requires: ['Ext.util.Event'],
56
57 statics: {
58 /**
59 * Removes **all** added captures from the Observable.
60 *
61 * @param {Ext.util.Observable} o The Observable to release
62 * @static
63 */
64 releaseCapture: function(o) {
65 o.fireEvent = this.prototype.fireEvent;
66 },
67
68 /**
69 * Starts capture on the specified Observable. All events will be passed to the supplied function with the event
70 * name + standard signature of the event **before** the event is fired. If the supplied function returns false,
71 * the event will not fire.
72 *
73 * @param {Ext.util.Observable} o The Observable to capture events from.
74 * @param {Function} fn The function to call when an event is fired.
75 * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed. Defaults to
76 * the Observable firing the event.
77 * @static
78 */
79 capture: function(o, fn, scope) {
80 o.fireEvent = Ext.Function.createInterceptor(o.fireEvent, fn, scope);
81 },
82
83 /**
84 * Sets observability on the passed class constructor.
85 *
86 * This makes any event fired on any instance of the passed class also fire a single event through
87 * the **class** allowing for central handling of events on many instances at once.
88 *
89 * Usage:
90 *
91 * Ext.util.Observable.observe(Ext.data.Connection);
92 * Ext.data.Connection.on('beforerequest', function(con, options) {
93 * console.log('Ajax request made to ' + options.url);
94 * });
95 *
96 * @param {Function} c The class constructor to make observable.
97 * @param {Object} listeners An object containing a series of listeners to add. See {@link #addListener}.
98 * @static
99 */
100 observe: function(cls, listeners) {
101 if (cls) {
102 if (!cls.isObservable) {
103 Ext.applyIf(cls, new this());
104 this.capture(cls.prototype, cls.fireEvent, cls);
105 }
106 if (Ext.isObject(listeners)) {
107 cls.on(listeners);
108 }
109 return cls;
110 }
111 }
112 },
113
114 /* End Definitions */
115
116 /**
117 * @cfg {Object} listeners
118 *
119 * A config object containing one or more event handlers to be added to this object during initialization. This
120 * should be a valid listeners config object as specified in the {@link #addListener} example for attaching multiple
121 * handlers at once.
122 *
123 * **DOM events from Ext JS {@link Ext.Component Components}**
124 *
125 * While _some_ Ext JS Component classes export selected DOM events (e.g. "click", "mouseover" etc), this is usually
126 * only done when extra value can be added. For example the {@link Ext.view.View DataView}'s **`{@link
127 * Ext.view.View#itemclick itemclick}`** event passing the node clicked on. To access DOM events directly from a
128 * child element of a Component, we need to specify the `element` option to identify the Component property to add a
129 * DOM listener to:
130 *
131 * new Ext.panel.Panel({
132 * width: 400,
133 * height: 200,
134 * dockedItems: [{
135 * xtype: 'toolbar'
136 * }],
137 * listeners: {
138 * click: {
139 * element: 'el', //bind to the underlying el property on the panel
140 * fn: function(){ console.log('click el'); }
141 * },
142 * dblclick: {
143 * element: 'body', //bind to the underlying body property on the panel
144 * fn: function(){ console.log('dblclick body'); }
145 * }
146 * }
147 * });
148 */
149 // @private
150 isObservable: true,
151
152 constructor: function(config) {
153 var me = this;
154
155 Ext.apply(me, config);
156 if (me.listeners) {
157 me.on(me.listeners);
158 delete me.listeners;
159 }
160 me.events = me.events || {};
161
162 if (me.bubbleEvents) {
163 me.enableBubble(me.bubbleEvents);
164 }
165 },
166
167 // @private
168 eventOptionsRe : /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate|element|vertical|horizontal|freezeEvent)$/,
169
170 /**
171 * Adds listeners to any Observable object (or Ext.Element) which are automatically removed when this Component is
172 * destroyed.
173 *
174 * @param {Ext.util.Observable/Ext.Element} item The item to which to add a listener/listeners.
175 * @param {Object/String} ename The event name, or an object containing event name properties.
176 * @param {Function} fn (optional) If the `ename` parameter was an event name, this is the handler function.
177 * @param {Object} scope (optional) If the `ename` parameter was an event name, this is the scope (`this` reference)
178 * in which the handler function is executed.
179 * @param {Object} opt (optional) If the `ename` parameter was an event name, this is the
180 * {@link Ext.util.Observable#addListener addListener} options.
181 */
182 addManagedListener : function(item, ename, fn, scope, options) {
183 var me = this,
184 managedListeners = me.managedListeners = me.managedListeners || [],
185 config;
186
187 if (typeof ename !== 'string') {
188 options = ename;
189 for (ename in options) {
190 if (options.hasOwnProperty(ename)) {
191 config = options[ename];
192 if (!me.eventOptionsRe.test(ename)) {
193 me.addManagedListener(item, ename, config.fn || config, config.scope || options.scope, config.fn ? config : options);
194 }
195 }
196 }
197 }
198 else {
199 managedListeners.push({
200 item: item,
201 ename: ename,
202 fn: fn,
203 scope: scope,
204 options: options
205 });
206
207 item.on(ename, fn, scope, options);
208 }
209 },
210
211 /**
212 * Removes listeners that were added by the {@link #mon} method.
213 *
214 * @param {Ext.util.Observable/Ext.Element} item The item from which to remove a listener/listeners.
215 * @param {Object/String} ename The event name, or an object containing event name properties.
216 * @param {Function} fn (optional) If the `ename` parameter was an event name, this is the handler function.
217 * @param {Object} scope (optional) If the `ename` parameter was an event name, this is the scope (`this` reference)
218 * in which the handler function is executed.
219 */
220 removeManagedListener : function(item, ename, fn, scope) {
221 var me = this,
222 options,
223 config,
224 managedListeners,
225 length,
226 i;
227
228 if (typeof ename !== 'string') {
229 options = ename;
230 for (ename in options) {
231 if (options.hasOwnProperty(ename)) {
232 config = options[ename];
233 if (!me.eventOptionsRe.test(ename)) {
234 me.removeManagedListener(item, ename, config.fn || config, config.scope || options.scope);
235 }
236 }
237 }
238 }
239
240 managedListeners = me.managedListeners ? me.managedListeners.slice() : [];
241
242 for (i = 0, length = managedListeners.length; i < length; i++) {
243 me.removeManagedListenerItem(false, managedListeners[i], item, ename, fn, scope);
244 }
245 },
246
247 /**
248 * Fires the specified event with the passed parameters (minus the event name, plus the `options` object passed
249 * to {@link #addListener}).
250 *
251 * An event may be set to bubble up an Observable parent hierarchy (See {@link Ext.Component#getBubbleTarget}) by
252 * calling {@link #enableBubble}.
253 *
254 * @param {String} eventName The name of the event to fire.
255 * @param {Object...} args Variable number of parameters are passed to handlers.
256 * @return {Boolean} returns false if any of the handlers return false otherwise it returns true.
257 */
258 fireEvent: function(eventName) {
259 var name = eventName.toLowerCase(),
260 events = this.events,
261 event = events && events[name],
262 bubbles = event && event.bubble;
263
264 return this.continueFireEvent(name, Ext.Array.slice(arguments, 1), bubbles);
265 },
266
267 /**
268 * Continue to fire event.
269 * @private
270 *
271 * @param {String} eventName
272 * @param {Array} args
273 * @param {Boolean} bubbles
274 */
275 continueFireEvent: function(eventName, args, bubbles) {
276 var target = this,
277 queue, event,
278 ret = true;
279
280 do {
281 if (target.eventsSuspended === true) {
282 if ((queue = target.eventQueue)) {
283 queue.push([eventName, args, bubbles]);
284 }
285 return ret;
286 } else {
287 event = target.events[eventName];
288 // Continue bubbling if event exists and it is `true` or the handler didn't returns false and it
289 // configure to bubble.
290 if (event && event != true) {
291 if ((ret = event.fire.apply(event, args)) === false) {
292 break;
293 }
294 }
295 }
296 } while (bubbles && (target = target.getBubbleParent()));
297 return ret;
298 },
299
300 /**
301 * Gets the bubbling parent for an Observable
302 * @private
303 * @return {Ext.util.Observable} The bubble parent. null is returned if no bubble target exists
304 */
305 getBubbleParent: function(){
306 var me = this, parent = me.getBubbleTarget && me.getBubbleTarget();
307 if (parent && parent.isObservable) {
308 return parent;
309 }
310 return null;
311 },
312
313 /**
314 * Appends an event handler to this object.
315 *
316 * @param {String} eventName The name of the event to listen for. May also be an object who's property names are
317 * event names.
318 * @param {Function} fn The method the event invokes. Will be called with arguments given to
319 * {@link #fireEvent} plus the `options` parameter described below.
320 * @param {Object} [scope] The scope (`this` reference) in which the handler function is executed. **If
321 * omitted, defaults to the object which fired the event.**
322 * @param {Object} [options] An object containing handler configuration.
323 *
324 * **Note:** Unlike in ExtJS 3.x, the options object will also be passed as the last argument to every event handler.
325 *
326 * This object may contain any of the following properties:
327 *
328 * - **scope** : Object
329 *
330 * The scope (`this` reference) in which the handler function is executed. **If omitted, defaults to the object
331 * which fired the event.**
332 *
333 * - **delay** : Number
334 *
335 * The number of milliseconds to delay the invocation of the handler after the event fires.
336 *
337 * - **single** : Boolean
338 *
339 * True to add a handler to handle just the next firing of the event, and then remove itself.
340 *
341 * - **buffer** : Number
342 *
343 * Causes the handler to be scheduled to run in an {@link Ext.util.DelayedTask} delayed by the specified number of
344 * milliseconds. If the event fires again within that time, the original handler is _not_ invoked, but the new
345 * handler is scheduled in its place.
346 *
347 * - **target** : Observable
348 *
349 * Only call the handler if the event was fired on the target Observable, _not_ if the event was bubbled up from a
350 * child Observable.
351 *
352 * - **element** : String
353 *
354 * **This option is only valid for listeners bound to {@link Ext.Component Components}.** The name of a Component
355 * property which references an element to add a listener to.
356 *
357 * This option is useful during Component construction to add DOM event listeners to elements of
358 * {@link Ext.Component Components} which will exist only after the Component is rendered.
359 * For example, to add a click listener to a Panel's body:
360 *
361 * new Ext.panel.Panel({
362 * title: 'The title',
363 * listeners: {
364 * click: this.handlePanelClick,
365 * element: 'body'
366 * }
367 * });
368 *
369 * **Combining Options**
370 *
371 * Using the options argument, it is possible to combine different types of listeners:
372 *
373 * A delayed, one-time listener.
374 *
375 * myPanel.on('hide', this.handleClick, this, {
376 * single: true,
377 * delay: 100
378 * });
379 *
380 * **Attaching multiple handlers in 1 call**
381 *
382 * The method also allows for a single argument to be passed which is a config object containing properties which
383 * specify multiple events. For example:
384 *
385 * myGridPanel.on({
386 * cellClick: this.onCellClick,
387 * mouseover: this.onMouseOver,
388 * mouseout: this.onMouseOut,
389 * scope: this // Important. Ensure "this" is correct during handler execution
390 * });
391 *
392 * One can also specify options for each event handler separately:
393 *
394 * myGridPanel.on({
395 * cellClick: {fn: this.onCellClick, scope: this, single: true},
396 * mouseover: {fn: panel.onMouseOver, scope: panel}
397 * });
398 *
399 */
400 addListener: function(ename, fn, scope, options) {
401 var me = this,
402 config,
403 event;
404
405 if (typeof ename !== 'string') {
406 options = ename;
407 for (ename in options) {
408 if (options.hasOwnProperty(ename)) {
409 config = options[ename];
410 if (!me.eventOptionsRe.test(ename)) {
411 me.addListener(ename, config.fn || config, config.scope || options.scope, config.fn ? config : options);
412 }
413 }
414 }
415 }
416 else {
417 ename = ename.toLowerCase();
418 me.events[ename] = me.events[ename] || true;
419 event = me.events[ename] || true;
420 if (Ext.isBoolean(event)) {
421 me.events[ename] = event = new Ext.util.Event(me, ename);
422 }
423 event.addListener(fn, scope, Ext.isObject(options) ? options : {});
424 }
425 },
426
427 /**
428 * Removes an event handler.
429 *
430 * @param {String} eventName The type of event the handler was associated with.
431 * @param {Function} fn The handler to remove. **This must be a reference to the function passed into the
432 * {@link #addListener} call.**
433 * @param {Object} scope (optional) The scope originally specified for the handler. It must be the same as the
434 * scope argument specified in the original call to {@link #addListener} or the listener will not be removed.
435 */
436 removeListener: function(ename, fn, scope) {
437 var me = this,
438 config,
439 event,
440 options;
441
442 if (typeof ename !== 'string') {
443 options = ename;
444 for (ename in options) {
445 if (options.hasOwnProperty(ename)) {
446 config = options[ename];
447 if (!me.eventOptionsRe.test(ename)) {
448 me.removeListener(ename, config.fn || config, config.scope || options.scope);
449 }
450 }
451 }
452 } else {
453 ename = ename.toLowerCase();
454 event = me.events[ename];
455 if (event && event.isEvent) {
456 event.removeListener(fn, scope);
457 }
458 }
459 },
460
461 /**
462 * Removes all listeners for this object including the managed listeners
463 */
464 clearListeners: function() {
465 var events = this.events,
466 event,
467 key;
468
469 for (key in events) {
470 if (events.hasOwnProperty(key)) {
471 event = events[key];
472 if (event.isEvent) {
473 event.clearListeners();
474 }
475 }
476 }
477
478 this.clearManagedListeners();
479 },
480
481 //<debug>
482 purgeListeners : function() {
483 if (Ext.global.console) {
484 Ext.global.console.warn('Observable: purgeListeners has been deprecated. Please use clearListeners.');
485 }
486 return this.clearListeners.apply(this, arguments);
487 },
488 //</debug>
489
490 /**
491 * Removes all managed listeners for this object.
492 */
493 clearManagedListeners : function() {
494 var managedListeners = this.managedListeners || [],
495 i = 0,
496 len = managedListeners.length;
497
498 for (; i < len; i++) {
499 this.removeManagedListenerItem(true, managedListeners[i]);
500 }
501
502 this.managedListeners = [];
503 },
504
505 /**
506 * Remove a single managed listener item
507 * @private
508 * @param {Boolean} isClear True if this is being called during a clear
509 * @param {Object} managedListener The managed listener item
510 * See removeManagedListener for other args
511 */
512 removeManagedListenerItem: function(isClear, managedListener, item, ename, fn, scope){
513 if (isClear || (managedListener.item === item && managedListener.ename === ename && (!fn || managedListener.fn === fn) && (!scope || managedListener.scope === scope))) {
514 managedListener.item.un(managedListener.ename, managedListener.fn, managedListener.scope);
515 if (!isClear) {
516 Ext.Array.remove(this.managedListeners, managedListener);
517 }
518 }
519 },
520
521 //<debug>
522 purgeManagedListeners : function() {
523 if (Ext.global.console) {
524 Ext.global.console.warn('Observable: purgeManagedListeners has been deprecated. Please use clearManagedListeners.');
525 }
526 return this.clearManagedListeners.apply(this, arguments);
527 },
528 //</debug>
529
530 /**
531 * Adds the specified events to the list of events which this Observable may fire.
532 *
533 * @param {Object/String} o Either an object with event names as properties with a value of `true` or the first
534 * event name string if multiple event names are being passed as separate parameters. Usage:
535 *
536 * this.addEvents({
537 * storeloaded: true,
538 * storecleared: true
539 * });
540 *
541 * @param {String...} more (optional) Additional event names if multiple event names are being passed as separate
542 * parameters. Usage:
543 *
544 * this.addEvents('storeloaded', 'storecleared');
545 *
546 */
547 addEvents: function(o) {
548 var me = this,
549 args,
550 len,
551 i;
552
553 me.events = me.events || {};
554 if (Ext.isString(o)) {
555 args = arguments;
556 i = args.length;
557
558 while (i--) {
559 me.events[args[i]] = me.events[args[i]] || true;
560 }
561 } else {
562 Ext.applyIf(me.events, o);
563 }
564 },
565
566 /**
567 * Checks to see if this object has any listeners for a specified event
568 *
569 * @param {String} eventName The name of the event to check for
570 * @return {Boolean} True if the event is being listened for, else false
571 */
572 hasListener: function(ename) {
573 var event = this.events[ename.toLowerCase()];
574 return event && event.isEvent === true && event.listeners.length > 0;
575 },
576
577 /**
578 * Suspends the firing of all events. (see {@link #resumeEvents})
579 *
580 * @param {Boolean} queueSuspended Pass as true to queue up suspended events to be fired
581 * after the {@link #resumeEvents} call instead of discarding all suspended events.
582 */
583 suspendEvents: function(queueSuspended) {
584 this.eventsSuspended = true;
585 if (queueSuspended && !this.eventQueue) {
586 this.eventQueue = [];
587 }
588 },
589
590 /**
591 * Resumes firing events (see {@link #suspendEvents}).
592 *
593 * If events were suspended using the `queueSuspended` parameter, then all events fired
594 * during event suspension will be sent to any listeners now.
595 */
596 resumeEvents: function() {
597 var me = this,
598 queued = me.eventQueue;
599
600 me.eventsSuspended = false;
601 delete me.eventQueue;
602
603 if (queued) {
604 Ext.each(queued, function(e) {
605 me.continueFireEvent.apply(me, e);
606 });
607 }
608 },
609
610 /**
611 * Relays selected events from the specified Observable as if the events were fired by `this`.
612 *
613 * @param {Object} origin The Observable whose events this object is to relay.
614 * @param {String[]} events Array of event names to relay.
615 * @param {String} prefix
616 */
617 relayEvents : function(origin, events, prefix) {
618 prefix = prefix || '';
619 var me = this,
620 len = events.length,
621 i = 0,
622 oldName,
623 newName;
624
625 for (; i < len; i++) {
626 oldName = events[i].substr(prefix.length);
627 newName = prefix + oldName;
628 me.events[newName] = me.events[newName] || true;
629 origin.on(oldName, me.createRelayer(newName));
630 }
631 },
632
633 /**
634 * @private
635 * Creates an event handling function which refires the event from this object as the passed event name.
636 * @param newName
637 * @returns {Function}
638 */
639 createRelayer: function(newName){
640 var me = this;
641 return function(){
642 return me.fireEvent.apply(me, [newName].concat(Array.prototype.slice.call(arguments, 0, -1)));
643 };
644 },
645
646 /**
647 * Enables events fired by this Observable to bubble up an owner hierarchy by calling `this.getBubbleTarget()` if
648 * present. There is no implementation in the Observable base class.
649 *
650 * This is commonly used by Ext.Components to bubble events to owner Containers.
651 * See {@link Ext.Component#getBubbleTarget}. The default implementation in Ext.Component returns the
652 * Component's immediate owner. But if a known target is required, this can be overridden to access the
653 * required target more quickly.
654 *
655 * Example:
656 *
657 * Ext.override(Ext.form.field.Base, {
658 * // Add functionality to Field's initComponent to enable the change event to bubble
659 * initComponent : Ext.Function.createSequence(Ext.form.field.Base.prototype.initComponent, function() {
660 * this.enableBubble('change');
661 * }),
662 *
663 * // We know that we want Field's events to bubble directly to the FormPanel.
664 * getBubbleTarget : function() {
665 * if (!this.formPanel) {
666 * this.formPanel = this.findParentByType('form');
667 * }
668 * return this.formPanel;
669 * }
670 * });
671 *
672 * var myForm = new Ext.formPanel({
673 * title: 'User Details',
674 * items: [{
675 * ...
676 * }],
677 * listeners: {
678 * change: function() {
679 * // Title goes red if form has been modified.
680 * myForm.header.setStyle('color', 'red');
681 * }
682 * }
683 * });
684 *
685 * @param {String/String[]} events The event name to bubble, or an Array of event names.
686 */
687 enableBubble: function(events) {
688 var me = this;
689 if (!Ext.isEmpty(events)) {
690 events = Ext.isArray(events) ? events: Ext.Array.toArray(arguments);
691 Ext.each(events,
692 function(ename) {
693 ename = ename.toLowerCase();
694 var ce = me.events[ename] || true;
695 if (Ext.isBoolean(ce)) {
696 ce = new Ext.util.Event(me, ename);
697 me.events[ename] = ce;
698 }
699 ce.bubble = true;
700 });
701 }
702 }
703}, function() {
704
705 this.createAlias({
706 /**
707 * @method
708 * Shorthand for {@link #addListener}.
709 * @alias Ext.util.Observable#addListener
710 */
711 on: 'addListener',
712 /**
713 * @method
714 * Shorthand for {@link #removeListener}.
715 * @alias Ext.util.Observable#removeListener
716 */
717 un: 'removeListener',
718 /**
719 * @method
720 * Shorthand for {@link #addManagedListener}.
721 * @alias Ext.util.Observable#addManagedListener
722 */
723 mon: 'addManagedListener',
724 /**
725 * @method
726 * Shorthand for {@link #removeManagedListener}.
727 * @alias Ext.util.Observable#removeManagedListener
728 */
729 mun: 'removeManagedListener'
730 });
731
732 //deprecated, will be removed in 5.0
733 this.observeClass = this.observe;
734
735 Ext.apply(Ext.util.Observable.prototype, function(){
736 // this is considered experimental (along with beforeMethod, afterMethod, removeMethodListener?)
737 // allows for easier interceptor and sequences, including cancelling and overwriting the return value of the call
738 // private
739 function getMethodEvent(method){
740 var e = (this.methodEvents = this.methodEvents || {})[method],
741 returnValue,
742 v,
743 cancel,
744 obj = this;
745
746 if (!e) {
747 this.methodEvents[method] = e = {};
748 e.originalFn = this[method];
749 e.methodName = method;
750 e.before = [];
751 e.after = [];
752
753 var makeCall = function(fn, scope, args){
754 if((v = fn.apply(scope || obj, args)) !== undefined){
755 if (typeof v == 'object') {
756 if(v.returnValue !== undefined){
757 returnValue = v.returnValue;
758 }else{
759 returnValue = v;
760 }
761 cancel = !!v.cancel;
762 }
763 else
764 if (v === false) {
765 cancel = true;
766 }
767 else {
768 returnValue = v;
769 }
770 }
771 };
772
773 this[method] = function(){
774 var args = Array.prototype.slice.call(arguments, 0),
775 b, i, len;
776 returnValue = v = undefined;
777 cancel = false;
778
779 for(i = 0, len = e.before.length; i < len; i++){
780 b = e.before[i];
781 makeCall(b.fn, b.scope, args);
782 if (cancel) {
783 return returnValue;
784 }
785 }
786
787 if((v = e.originalFn.apply(obj, args)) !== undefined){
788 returnValue = v;
789 }
790
791 for(i = 0, len = e.after.length; i < len; i++){
792 b = e.after[i];
793 makeCall(b.fn, b.scope, args);
794 if (cancel) {
795 return returnValue;
796 }
797 }
798 return returnValue;
799 };
800 }
801 return e;
802 }
803
804 return {
805 // these are considered experimental
806 // allows for easier interceptor and sequences, including cancelling and overwriting the return value of the call
807 // adds an 'interceptor' called before the original method
808 beforeMethod : function(method, fn, scope){
809 getMethodEvent.call(this, method).before.push({
810 fn: fn,
811 scope: scope
812 });
813 },
814
815 // adds a 'sequence' called after the original method
816 afterMethod : function(method, fn, scope){
817 getMethodEvent.call(this, method).after.push({
818 fn: fn,
819 scope: scope
820 });
821 },
822
823 removeMethodListener: function(method, fn, scope){
824 var e = this.getMethodEvent(method),
825 i, len;
826 for(i = 0, len = e.before.length; i < len; i++){
827 if(e.before[i].fn == fn && e.before[i].scope == scope){
828 Ext.Array.erase(e.before, i, 1);
829 return;
830 }
831 }
832 for(i = 0, len = e.after.length; i < len; i++){
833 if(e.after[i].fn == fn && e.after[i].scope == scope){
834 Ext.Array.erase(e.after, i, 1);
835 return;
836 }
837 }
838 },
839
840 toggleEventLogging: function(toggle) {
841 Ext.util.Observable[toggle ? 'capture' : 'releaseCapture'](this, function(en) {
842 if (Ext.isDefined(Ext.global.console)) {
843 Ext.global.console.log(en, arguments);
844 }
845 });
846 }
847 };
848 }());
849});
850
851/**
852 * @class Ext.util.Animate
853 * This animation class is a mixin.
854 *
855 * Ext.util.Animate provides an API for the creation of animated transitions of properties and styles.
856 * This class is used as a mixin and currently applied to {@link Ext.Element}, {@link Ext.CompositeElement},
857 * {@link Ext.draw.Sprite}, {@link Ext.draw.CompositeSprite}, and {@link Ext.Component}. Note that Components
858 * have a limited subset of what attributes can be animated such as top, left, x, y, height, width, and
859 * opacity (color, paddings, and margins can not be animated).
860 *
861 * ## Animation Basics
862 *
863 * All animations require three things - `easing`, `duration`, and `to` (the final end value for each property)
864 * you wish to animate. Easing and duration are defaulted values specified below.
865 * Easing describes how the intermediate values used during a transition will be calculated.
866 * {@link Ext.fx.Anim#easing Easing} allows for a transition to change speed over its duration.
867 * You may use the defaults for easing and duration, but you must always set a
868 * {@link Ext.fx.Anim#to to} property which is the end value for all animations.
869 *
870 * Popular element 'to' configurations are:
871 *
872 * - opacity
873 * - x
874 * - y
875 * - color
876 * - height
877 * - width
878 *
879 * Popular sprite 'to' configurations are:
880 *
881 * - translation
882 * - path
883 * - scale
884 * - stroke
885 * - rotation
886 *
887 * The default duration for animations is 250 (which is a 1/4 of a second). Duration is denoted in
888 * milliseconds. Therefore 1 second is 1000, 1 minute would be 60000, and so on. The default easing curve
889 * used for all animations is 'ease'. Popular easing functions are included and can be found in {@link Ext.fx.Anim#easing Easing}.
890 *
891 * For example, a simple animation to fade out an element with a default easing and duration:
892 *
893 * var p1 = Ext.get('myElementId');
894 *
895 * p1.animate({
896 * to: {
897 * opacity: 0
898 * }
899 * });
900 *
901 * To make this animation fade out in a tenth of a second:
902 *
903 * var p1 = Ext.get('myElementId');
904 *
905 * p1.animate({
906 * duration: 100,
907 * to: {
908 * opacity: 0
909 * }
910 * });
911 *
912 * ## Animation Queues
913 *
914 * By default all animations are added to a queue which allows for animation via a chain-style API.
915 * For example, the following code will queue 4 animations which occur sequentially (one right after the other):
916 *
917 * p1.animate({
918 * to: {
919 * x: 500
920 * }
921 * }).animate({
922 * to: {
923 * y: 150
924 * }
925 * }).animate({
926 * to: {
927 * backgroundColor: '#f00' //red
928 * }
929 * }).animate({
930 * to: {
931 * opacity: 0
932 * }
933 * });
934 *
935 * You can change this behavior by calling the {@link Ext.util.Animate#syncFx syncFx} method and all
936 * subsequent animations for the specified target will be run concurrently (at the same time).
937 *
938 * p1.syncFx(); //this will make all animations run at the same time
939 *
940 * p1.animate({
941 * to: {
942 * x: 500
943 * }
944 * }).animate({
945 * to: {
946 * y: 150
947 * }
948 * }).animate({
949 * to: {
950 * backgroundColor: '#f00' //red
951 * }
952 * }).animate({
953 * to: {
954 * opacity: 0
955 * }
956 * });
957 *
958 * This works the same as:
959 *
960 * p1.animate({
961 * to: {
962 * x: 500,
963 * y: 150,
964 * backgroundColor: '#f00' //red
965 * opacity: 0
966 * }
967 * });
968 *
969 * The {@link Ext.util.Animate#stopAnimation stopAnimation} method can be used to stop any
970 * currently running animations and clear any queued animations.
971 *
972 * ## Animation Keyframes
973 *
974 * You can also set up complex animations with {@link Ext.fx.Anim#keyframes keyframes} which follow the
975 * CSS3 Animation configuration pattern. Note rotation, translation, and scaling can only be done for sprites.
976 * The previous example can be written with the following syntax:
977 *
978 * p1.animate({
979 * duration: 1000, //one second total
980 * keyframes: {
981 * 25: { //from 0 to 250ms (25%)
982 * x: 0
983 * },
984 * 50: { //from 250ms to 500ms (50%)
985 * y: 0
986 * },
987 * 75: { //from 500ms to 750ms (75%)
988 * backgroundColor: '#f00' //red
989 * },
990 * 100: { //from 750ms to 1sec
991 * opacity: 0
992 * }
993 * }
994 * });
995 *
996 * ## Animation Events
997 *
998 * Each animation you create has events for {@link Ext.fx.Anim#beforeanimate beforeanimate},
999 * {@link Ext.fx.Anim#afteranimate afteranimate}, and {@link Ext.fx.Anim#lastframe lastframe}.
1000 * Keyframed animations adds an additional {@link Ext.fx.Animator#keyframe keyframe} event which
1001 * fires for each keyframe in your animation.
1002 *
1003 * All animations support the {@link Ext.util.Observable#listeners listeners} configuration to attact functions to these events.
1004 *
1005 * startAnimate: function() {
1006 * var p1 = Ext.get('myElementId');
1007 * p1.animate({
1008 * duration: 100,
1009 * to: {
1010 * opacity: 0
1011 * },
1012 * listeners: {
1013 * beforeanimate: function() {
1014 * // Execute my custom method before the animation
1015 * this.myBeforeAnimateFn();
1016 * },
1017 * afteranimate: function() {
1018 * // Execute my custom method after the animation
1019 * this.myAfterAnimateFn();
1020 * },
1021 * scope: this
1022 * });
1023 * },
1024 * myBeforeAnimateFn: function() {
1025 * // My custom logic
1026 * },
1027 * myAfterAnimateFn: function() {
1028 * // My custom logic
1029 * }
1030 *
1031 * Due to the fact that animations run asynchronously, you can determine if an animation is currently
1032 * running on any target by using the {@link Ext.util.Animate#getActiveAnimation getActiveAnimation}
1033 * method. This method will return false if there are no active animations or return the currently
1034 * running {@link Ext.fx.Anim} instance.
1035 *
1036 * In this example, we're going to wait for the current animation to finish, then stop any other
1037 * queued animations before we fade our element's opacity to 0:
1038 *
1039 * var curAnim = p1.getActiveAnimation();
1040 * if (curAnim) {
1041 * curAnim.on('afteranimate', function() {
1042 * p1.stopAnimation();
1043 * p1.animate({
1044 * to: {
1045 * opacity: 0
1046 * }
1047 * });
1048 * });
1049 * }
1050 *
1051 * @docauthor Jamie Avins <jamie@sencha.com>
1052 */
1053Ext.define('Ext.util.Animate', {
1054
1055 uses: ['Ext.fx.Manager', 'Ext.fx.Anim'],
1056
1057 /**
1058 * <p>Perform custom animation on this object.<p>
1059 * <p>This method is applicable to both the {@link Ext.Component Component} class and the {@link Ext.Element Element} class.
1060 * It performs animated transitions of certain properties of this object over a specified timeline.</p>
1061 * <p>The sole parameter is an object which specifies start property values, end property values, and properties which
1062 * describe the timeline. Of the properties listed below, only <b><code>to</code></b> is mandatory.</p>
1063 * <p>Properties include<ul>
1064 * <li><code>from</code> <div class="sub-desc">An object which specifies start values for the properties being animated.
1065 * If not supplied, properties are animated from current settings. The actual properties which may be animated depend upon
1066 * ths object being animated. See the sections below on Element and Component animation.<div></li>
1067 * <li><code>to</code> <div class="sub-desc">An object which specifies end values for the properties being animated.</div></li>
1068 * <li><code>duration</code><div class="sub-desc">The duration <b>in milliseconds</b> for which the animation will run.</div></li>
1069 * <li><code>easing</code> <div class="sub-desc">A string value describing an easing type to modify the rate of change from the default linear to non-linear. Values may be one of:<code><ul>
1070 * <li>ease</li>
1071 * <li>easeIn</li>
1072 * <li>easeOut</li>
1073 * <li>easeInOut</li>
1074 * <li>backIn</li>
1075 * <li>backOut</li>
1076 * <li>elasticIn</li>
1077 * <li>elasticOut</li>
1078 * <li>bounceIn</li>
1079 * <li>bounceOut</li>
1080 * </ul></code></div></li>
1081 * <li><code>keyframes</code> <div class="sub-desc">This is an object which describes the state of animated properties at certain points along the timeline.
1082 * it is an object containing properties who's names are the percentage along the timeline being described and who's values specify the animation state at that point.</div></li>
1083 * <li><code>listeners</code> <div class="sub-desc">This is a standard {@link Ext.util.Observable#listeners listeners} configuration object which may be used
1084 * to inject behaviour at either the <code>beforeanimate</code> event or the <code>afteranimate</code> event.</div></li>
1085 * </ul></p>
1086 * <h3>Animating an {@link Ext.Element Element}</h3>
1087 * When animating an Element, the following properties may be specified in <code>from</code>, <code>to</code>, and <code>keyframe</code> objects:<ul>
1088 * <li><code>x</code> <div class="sub-desc">The page X position in pixels.</div></li>
1089 * <li><code>y</code> <div class="sub-desc">The page Y position in pixels</div></li>
1090 * <li><code>left</code> <div class="sub-desc">The element's CSS <code>left</code> value. Units must be supplied.</div></li>
1091 * <li><code>top</code> <div class="sub-desc">The element's CSS <code>top</code> value. Units must be supplied.</div></li>
1092 * <li><code>width</code> <div class="sub-desc">The element's CSS <code>width</code> value. Units must be supplied.</div></li>
1093 * <li><code>height</code> <div class="sub-desc">The element's CSS <code>height</code> value. Units must be supplied.</div></li>
1094 * <li><code>scrollLeft</code> <div class="sub-desc">The element's <code>scrollLeft</code> value.</div></li>
1095 * <li><code>scrollTop</code> <div class="sub-desc">The element's <code>scrollLeft</code> value.</div></li>
1096 * <li><code>opacity</code> <div class="sub-desc">The element's <code>opacity</code> value. This must be a value between <code>0</code> and <code>1</code>.</div></li>
1097 * </ul>
1098 * <p><b>Be aware than animating an Element which is being used by an Ext Component without in some way informing the Component about the changed element state
1099 * will result in incorrect Component behaviour. This is because the Component will be using the old state of the element. To avoid this problem, it is now possible to
1100 * directly animate certain properties of Components.</b></p>
1101 * <h3>Animating a {@link Ext.Component Component}</h3>
1102 * When animating an Element, the following properties may be specified in <code>from</code>, <code>to</code>, and <code>keyframe</code> objects:<ul>
1103 * <li><code>x</code> <div class="sub-desc">The Component's page X position in pixels.</div></li>
1104 * <li><code>y</code> <div class="sub-desc">The Component's page Y position in pixels</div></li>
1105 * <li><code>left</code> <div class="sub-desc">The Component's <code>left</code> value in pixels.</div></li>
1106 * <li><code>top</code> <div class="sub-desc">The Component's <code>top</code> value in pixels.</div></li>
1107 * <li><code>width</code> <div class="sub-desc">The Component's <code>width</code> value in pixels.</div></li>
1108 * <li><code>width</code> <div class="sub-desc">The Component's <code>width</code> value in pixels.</div></li>
1109 * <li><code>dynamic</code> <div class="sub-desc">Specify as true to update the Component's layout (if it is a Container) at every frame
1110 * of the animation. <i>Use sparingly as laying out on every intermediate size change is an expensive operation</i>.</div></li>
1111 * </ul>
1112 * <p>For example, to animate a Window to a new size, ensuring that its internal layout, and any shadow is correct:</p>
1113 * <pre><code>
1114myWindow = Ext.create('Ext.window.Window', {
1115 title: 'Test Component animation',
1116 width: 500,
1117 height: 300,
1118 layout: {
1119 type: 'hbox',
1120 align: 'stretch'
1121 },
1122 items: [{
1123 title: 'Left: 33%',
1124 margins: '5 0 5 5',
1125 flex: 1
1126 }, {
1127 title: 'Left: 66%',
1128 margins: '5 5 5 5',
1129 flex: 2
1130 }]
1131});
1132myWindow.show();
1133myWindow.header.el.on('click', function() {
1134 myWindow.animate({
1135 to: {
1136 width: (myWindow.getWidth() == 500) ? 700 : 500,
1137 height: (myWindow.getHeight() == 300) ? 400 : 300,
1138 }
1139 });
1140});
1141</code></pre>
1142 * <p>For performance reasons, by default, the internal layout is only updated when the Window reaches its final <code>"to"</code> size. If dynamic updating of the Window's child
1143 * Components is required, then configure the animation with <code>dynamic: true</code> and the two child items will maintain their proportions during the animation.</p>
1144 * @param {Object} config An object containing properties which describe the animation's start and end states, and the timeline of the animation.
1145 * @return {Object} this
1146 */
1147 animate: function(animObj) {
1148 var me = this;
1149 if (Ext.fx.Manager.hasFxBlock(me.id)) {
1150 return me;
1151 }
1152 Ext.fx.Manager.queueFx(Ext.create('Ext.fx.Anim', me.anim(animObj)));
1153 return this;
1154 },
1155
1156 // @private - process the passed fx configuration.
1157 anim: function(config) {
1158 if (!Ext.isObject(config)) {
1159 return (config) ? {} : false;
1160 }
1161
1162 var me = this;
1163
1164 if (config.stopAnimation) {
1165 me.stopAnimation();
1166 }
1167
1168 Ext.applyIf(config, Ext.fx.Manager.getFxDefaults(me.id));
1169
1170 return Ext.apply({
1171 target: me,
1172 paused: true
1173 }, config);
1174 },
1175
1176 /**
1177 * @deprecated 4.0 Replaced by {@link #stopAnimation}
1178 * Stops any running effects and clears this object's internal effects queue if it contains
1179 * any additional effects that haven't started yet.
1180 * @return {Ext.Element} The Element
1181 * @method
1182 */
1183 stopFx: Ext.Function.alias(Ext.util.Animate, 'stopAnimation'),
1184
1185 /**
1186 * Stops any running effects and clears this object's internal effects queue if it contains
1187 * any additional effects that haven't started yet.
1188 * @return {Ext.Element} The Element
1189 */
1190 stopAnimation: function() {
1191 Ext.fx.Manager.stopAnimation(this.id);
1192 return this;
1193 },
1194
1195 /**
1196 * Ensures that all effects queued after syncFx is called on this object are
1197 * run concurrently. This is the opposite of {@link #sequenceFx}.
1198 * @return {Object} this
1199 */
1200 syncFx: function() {
1201 Ext.fx.Manager.setFxDefaults(this.id, {
1202 concurrent: true
1203 });
1204 return this;
1205 },
1206
1207 /**
1208 * Ensures that all effects queued after sequenceFx is called on this object are
1209 * run in sequence. This is the opposite of {@link #syncFx}.
1210 * @return {Object} this
1211 */
1212 sequenceFx: function() {
1213 Ext.fx.Manager.setFxDefaults(this.id, {
1214 concurrent: false
1215 });
1216 return this;
1217 },
1218
1219 /**
1220 * @deprecated 4.0 Replaced by {@link #getActiveAnimation}
1221 * @alias Ext.util.Animate#getActiveAnimation
1222 * @method
1223 */
1224 hasActiveFx: Ext.Function.alias(Ext.util.Animate, 'getActiveAnimation'),
1225
1226 /**
1227 * Returns the current animation if this object has any effects actively running or queued, else returns false.
1228 * @return {Ext.fx.Anim/Boolean} Anim if element has active effects, else false
1229 */
1230 getActiveAnimation: function() {
1231 return Ext.fx.Manager.getActiveAnimation(this.id);
1232 }
1233}, function(){
1234 // Apply Animate mixin manually until Element is defined in the proper 4.x way
1235 Ext.applyIf(Ext.Element.prototype, this.prototype);
1236 // We need to call this again so the animation methods get copied over to CE
1237 Ext.CompositeElementLite.importElementMethods();
1238});
1239/**
1240 * @class Ext.state.Provider
1241 * <p>Abstract base class for state provider implementations. The provider is responsible
1242 * for setting values and extracting values to/from the underlying storage source. The
1243 * storage source can vary and the details should be implemented in a subclass. For example
1244 * a provider could use a server side database or the browser localstorage where supported.</p>
1245 *
1246 * <p>This class provides methods for encoding and decoding <b>typed</b> variables including
1247 * dates and defines the Provider interface. By default these methods put the value and the
1248 * type information into a delimited string that can be stored. These should be overridden in
1249 * a subclass if you want to change the format of the encoded value and subsequent decoding.</p>
1250 */
1251Ext.define('Ext.state.Provider', {
1252 mixins: {
1253 observable: 'Ext.util.Observable'
1254 },
1255
1256 /**
1257 * @cfg {String} prefix A string to prefix to items stored in the underlying state store.
1258 * Defaults to <tt>'ext-'</tt>
1259 */
1260 prefix: 'ext-',
1261
1262 constructor : function(config){
1263 config = config || {};
1264 var me = this;
1265 Ext.apply(me, config);
1266 /**
1267 * @event statechange
1268 * Fires when a state change occurs.
1269 * @param {Ext.state.Provider} this This state provider
1270 * @param {String} key The state key which was changed
1271 * @param {String} value The encoded value for the state
1272 */
1273 me.addEvents("statechange");
1274 me.state = {};
1275 me.mixins.observable.constructor.call(me);
1276 },
1277
1278 /**
1279 * Returns the current value for a key
1280 * @param {String} name The key name
1281 * @param {Object} defaultValue A default value to return if the key's value is not found
1282 * @return {Object} The state data
1283 */
1284 get : function(name, defaultValue){
1285 return typeof this.state[name] == "undefined" ?
1286 defaultValue : this.state[name];
1287 },
1288
1289 /**
1290 * Clears a value from the state
1291 * @param {String} name The key name
1292 */
1293 clear : function(name){
1294 var me = this;
1295 delete me.state[name];
1296 me.fireEvent("statechange", me, name, null);
1297 },
1298
1299 /**
1300 * Sets the value for a key
1301 * @param {String} name The key name
1302 * @param {Object} value The value to set
1303 */
1304 set : function(name, value){
1305 var me = this;
1306 me.sta…
Large files files are truncated, but you can click here to view the full file