/ext-4.1.0_b3/docs/extjs/examples/kitchensink/all-classes.js
JavaScript | 14401 lines | 9947 code | 835 blank | 3619 comment | 856 complexity | 1a8790657b1626f7a21dd493d975bf3f MD5 | raw file
Large files files are truncated, but you can click here to view the full file
1/*
2Copyright(c) 2011 Sencha
3*/
4/**
5 * Base class that provides a common interface for publishing events. Subclasses are expected to to have a property
6 * "events" with all the events defined, and, optionally, a property "listeners" with configured listeners defined.
7 *
8 * For example:
9 *
10 * Ext.define('Employee', {
11 * mixins: {
12 * observable: 'Ext.util.Observable'
13 * },
14 *
15 * constructor: function (config) {
16 * // The Observable constructor copies all of the properties of `config` on
17 * // to `this` using {@link Ext#apply}. Further, the `listeners` property is
18 * // processed to add listeners.
19 * //
20 * this.mixins.observable.constructor.call(this, config);
21 *
22 * this.addEvents(
23 * 'fired',
24 * 'quit'
25 * );
26 * }
27 * });
28 *
29 * This could then be used like this:
30 *
31 * var newEmployee = new Employee({
32 * name: employeeName,
33 * listeners: {
34 * quit: function() {
35 * // By default, "this" will be the object that fired the event.
36 * alert(this.name + " has quit!");
37 * }
38 * }
39 * });
40 */
41Ext.define('Ext.util.Observable', {
42
43 /* Begin Definitions */
44
45 requires: ['Ext.util.Event'],
46
47 statics: {
48 /**
49 * Removes **all** added captures from the Observable.
50 *
51 * @param {Ext.util.Observable} o The Observable to release
52 * @static
53 */
54 releaseCapture: function(o) {
55 o.fireEvent = this.prototype.fireEvent;
56 },
57
58 /**
59 * Starts capture on the specified Observable. All events will be passed to the supplied function with the event
60 * name + standard signature of the event **before** the event is fired. If the supplied function returns false,
61 * the event will not fire.
62 *
63 * @param {Ext.util.Observable} o The Observable to capture events from.
64 * @param {Function} fn The function to call when an event is fired.
65 * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed. Defaults to
66 * the Observable firing the event.
67 * @static
68 */
69 capture: function(o, fn, scope) {
70 o.fireEvent = Ext.Function.createInterceptor(o.fireEvent, fn, scope);
71 },
72
73 /**
74 * Sets observability on the passed class constructor.
75 *
76 * This makes any event fired on any instance of the passed class also fire a single event through
77 * the **class** allowing for central handling of events on many instances at once.
78 *
79 * Usage:
80 *
81 * Ext.util.Observable.observe(Ext.data.Connection);
82 * Ext.data.Connection.on('beforerequest', function(con, options) {
83 * console.log('Ajax request made to ' + options.url);
84 * });
85 *
86 * @param {Function} c The class constructor to make observable.
87 * @param {Object} listeners An object containing a series of listeners to add. See {@link #addListener}.
88 * @static
89 */
90 observe: function(cls, listeners) {
91 if (cls) {
92 if (!cls.isObservable) {
93 Ext.applyIf(cls, new this());
94 this.capture(cls.prototype, cls.fireEvent, cls);
95 }
96 if (Ext.isObject(listeners)) {
97 cls.on(listeners);
98 }
99 return cls;
100 }
101 }
102 },
103
104 /* End Definitions */
105
106 /**
107 * @cfg {Object} listeners
108 *
109 * A config object containing one or more event handlers to be added to this object during initialization. This
110 * should be a valid listeners config object as specified in the {@link #addListener} example for attaching multiple
111 * handlers at once.
112 *
113 * **DOM events from Ext JS {@link Ext.Component Components}**
114 *
115 * While _some_ Ext JS Component classes export selected DOM events (e.g. "click", "mouseover" etc), this is usually
116 * only done when extra value can be added. For example the {@link Ext.view.View DataView}'s **`{@link
117 * Ext.view.View#itemclick itemclick}`** event passing the node clicked on. To access DOM events directly from a
118 * child element of a Component, we need to specify the `element` option to identify the Component property to add a
119 * DOM listener to:
120 *
121 * new Ext.panel.Panel({
122 * width: 400,
123 * height: 200,
124 * dockedItems: [{
125 * xtype: 'toolbar'
126 * }],
127 * listeners: {
128 * click: {
129 * element: 'el', //bind to the underlying el property on the panel
130 * fn: function(){ console.log('click el'); }
131 * },
132 * dblclick: {
133 * element: 'body', //bind to the underlying body property on the panel
134 * fn: function(){ console.log('dblclick body'); }
135 * }
136 * }
137 * });
138 */
139
140 /**
141 * @property {Boolean} isObservable
142 * `true` in this class to identify an objact as an instantiated Observable, or subclass thereof.
143 */
144 isObservable: true,
145
146 constructor: function(config) {
147 var me = this;
148
149 Ext.apply(me, config);
150
151 // Hash of event "hasListeners" flags.
152 // For repeated events in time-critical code, the firing code should use
153 // if (!me.hasListeners.beforerender || me.fireEvent('beforerender', me) !== false) { //code... }
154 // Bubbling the events counts as one listener.
155 // The subclass may have already initialized it.
156 me.hasListeners = me.hasListeners || {};
157
158 me.events = me.events || {};
159 if (me.listeners) {
160 me.on(me.listeners);
161 me.listeners = null; //Set as an instance property to pre-empt the prototype in case any are set there.
162 }
163
164 if (me.bubbleEvents) {
165 me.enableBubble(me.bubbleEvents);
166 }
167
168 },
169
170 // @private
171 eventOptionsRe : /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate|element|vertical|horizontal|freezeEvent)$/,
172
173 /**
174 * Adds listeners to any Observable object (or Ext.Element) which are automatically removed when this Component is
175 * destroyed.
176 *
177 * @param {Ext.util.Observable/Ext.Element} item The item to which to add a listener/listeners.
178 * @param {Object/String} ename The event name, or an object containing event name properties.
179 * @param {Function} fn (optional) If the `ename` parameter was an event name, this is the handler function.
180 * @param {Object} scope (optional) If the `ename` parameter was an event name, this is the scope (`this` reference)
181 * in which the handler function is executed.
182 * @param {Object} opt (optional) If the `ename` parameter was an event name, this is the
183 * {@link Ext.util.Observable#addListener addListener} options.
184 */
185 addManagedListener : function(item, ename, fn, scope, options) {
186 var me = this,
187 managedListeners = me.managedListeners = me.managedListeners || [],
188 config;
189
190 if (typeof ename !== 'string') {
191 options = ename;
192 for (ename in options) {
193 if (options.hasOwnProperty(ename)) {
194 config = options[ename];
195 if (!me.eventOptionsRe.test(ename)) {
196 me.addManagedListener(item, ename, config.fn || config, config.scope || options.scope, config.fn ? config : options);
197 }
198 }
199 }
200 }
201 else {
202 managedListeners.push({
203 item: item,
204 ename: ename,
205 fn: fn,
206 scope: scope,
207 options: options
208 });
209
210 item.on(ename, fn, scope, options);
211 }
212 },
213
214 /**
215 * Removes listeners that were added by the {@link #mon} method.
216 *
217 * @param {Ext.util.Observable/Ext.Element} item The item from which to remove a listener/listeners.
218 * @param {Object/String} ename The event name, or an object containing event name properties.
219 * @param {Function} fn (optional) If the `ename` parameter was an event name, this is the handler function.
220 * @param {Object} scope (optional) If the `ename` parameter was an event name, this is the scope (`this` reference)
221 * in which the handler function is executed.
222 */
223 removeManagedListener : function(item, ename, fn, scope) {
224 var me = this,
225 options,
226 config,
227 managedListeners,
228 length,
229 i;
230
231 if (typeof ename !== 'string') {
232 options = ename;
233 for (ename in options) {
234 if (options.hasOwnProperty(ename)) {
235 config = options[ename];
236 if (!me.eventOptionsRe.test(ename)) {
237 me.removeManagedListener(item, ename, config.fn || config, config.scope || options.scope);
238 }
239 }
240 }
241 }
242
243 managedListeners = me.managedListeners ? me.managedListeners.slice() : [];
244
245 for (i = 0, length = managedListeners.length; i < length; i++) {
246 me.removeManagedListenerItem(false, managedListeners[i], item, ename, fn, scope);
247 }
248 },
249
250 /**
251 * Fires the specified event with the passed parameters (minus the event name, plus the `options` object passed
252 * to {@link #addListener}).
253 *
254 * An event may be set to bubble up an Observable parent hierarchy (See {@link Ext.Component#getBubbleTarget}) by
255 * calling {@link #enableBubble}.
256 *
257 * @param {String} eventName The name of the event to fire.
258 * @param {Object...} args Variable number of parameters are passed to handlers.
259 * @return {Boolean} returns false if any of the handlers return false otherwise it returns true.
260 */
261 fireEvent: function(eventName) {
262 eventName = eventName.toLowerCase();
263 var me = this,
264 events = me.events,
265 event = events && events[eventName];
266
267 // Only continue firing the event if there are listeners to be informed.
268 // Bubbled events will always have a listener count, so will be fired.
269 if (event && me.hasListeners[eventName]) {
270 return me.continueFireEvent(eventName, Ext.Array.slice(arguments, 1), event.bubble);
271 }
272 },
273
274 /**
275 * Continue to fire event.
276 * @private
277 *
278 * @param {String} eventName
279 * @param {Array} args
280 * @param {Boolean} bubbles
281 */
282 continueFireEvent: function(eventName, args, bubbles) {
283 var target = this,
284 queue, event,
285 ret = true;
286
287 do {
288 if (target.eventsSuspended === true) {
289 if ((queue = target.eventQueue)) {
290 queue.push([eventName, args, bubbles]);
291 }
292 return ret;
293 } else {
294 event = target.events[eventName];
295 // Continue bubbling if event exists and it is `true` or the handler didn't returns false and it
296 // configure to bubble.
297 if (event && event != true) {
298 if ((ret = event.fire.apply(event, args)) === false) {
299 break;
300 }
301 }
302 }
303 } while (bubbles && (target = target.getBubbleParent()));
304 return ret;
305 },
306
307 /**
308 * Gets the bubbling parent for an Observable
309 * @private
310 * @return {Ext.util.Observable} The bubble parent. null is returned if no bubble target exists
311 */
312 getBubbleParent: function(){
313 var me = this, parent = me.getBubbleTarget && me.getBubbleTarget();
314 if (parent && parent.isObservable) {
315 return parent;
316 }
317 return null;
318 },
319
320 /**
321 * Appends an event handler to this object. For example:
322 *
323 * myGridPanel.on("mouseover", this.onMouseOver, this);
324 *
325 * The method also allows for a single argument to be passed which is a config object
326 * containing properties which specify multiple events. For example:
327 *
328 * myGridPanel.on({
329 * cellClick: this.onCellClick,
330 * mouseover: this.onMouseOver,
331 * mouseout: this.onMouseOut,
332 * scope: this // Important. Ensure "this" is correct during handler execution
333 * });
334 *
335 * One can also specify options for each event handler separately:
336 *
337 * myGridPanel.on({
338 * cellClick: {fn: this.onCellClick, scope: this, single: true},
339 * mouseover: {fn: panel.onMouseOver, scope: panel}
340 * });
341 *
342 * *Names* of methods in a specified scope may also be used. Note that
343 * `scope` MUST be specified to use this option:
344 *
345 * myGridPanel.on({
346 * cellClick: {fn: 'onCellClick', scope: this, single: true},
347 * mouseover: {fn: 'onMouseOver', scope: panel}
348 * });
349 *
350 * @param {String/Object} eventName The name of the event to listen for.
351 * May also be an object who's property names are event names.
352 *
353 * @param {Function} [fn] The method the event invokes, or *if `scope` is specified, the *name* of the method within
354 * the specified `scope`. Will be called with arguments
355 * given to {@link #fireEvent} plus the `options` parameter described below.
356 *
357 * @param {Object} [scope] The scope (`this` reference) in which the handler function is
358 * executed. **If omitted, defaults to the object which fired the event.**
359 *
360 * @param {Object} [options] An object containing handler configuration.
361 *
362 * **Note:** Unlike in ExtJS 3.x, the options object will also be passed as the last
363 * argument to every event handler.
364 *
365 * This object may contain any of the following properties:
366 *
367 * @param {Object} options.scope
368 * The scope (`this` reference) in which the handler function is executed. **If omitted,
369 * defaults to the object which fired the event.**
370 *
371 * @param {Number} options.delay
372 * The number of milliseconds to delay the invocation of the handler after the event fires.
373 *
374 * @param {Boolean} options.single
375 * True to add a handler to handle just the next firing of the event, and then remove itself.
376 *
377 * @param {Number} options.buffer
378 * Causes the handler to be scheduled to run in an {@link Ext.util.DelayedTask} delayed
379 * by the specified number of milliseconds. If the event fires again within that time,
380 * the original handler is _not_ invoked, but the new handler is scheduled in its place.
381 *
382 * @param {Ext.util.Observable} options.target
383 * Only call the handler if the event was fired on the target Observable, _not_ if the event
384 * was bubbled up from a child Observable.
385 *
386 * @param {String} options.element
387 * **This option is only valid for listeners bound to {@link Ext.Component Components}.**
388 * The name of a Component property which references an element to add a listener to.
389 *
390 * This option is useful during Component construction to add DOM event listeners to elements of
391 * {@link Ext.Component Components} which will exist only after the Component is rendered.
392 * For example, to add a click listener to a Panel's body:
393 *
394 * new Ext.panel.Panel({
395 * title: 'The title',
396 * listeners: {
397 * click: this.handlePanelClick,
398 * element: 'body'
399 * }
400 * });
401 *
402 * **Combining Options**
403 *
404 * Using the options argument, it is possible to combine different types of listeners:
405 *
406 * A delayed, one-time listener.
407 *
408 * myPanel.on('hide', this.handleClick, this, {
409 * single: true,
410 * delay: 100
411 * });
412 *
413 */
414 addListener: function(ename, fn, scope, options) {
415 var me = this,
416 config,
417 event;
418
419 if (typeof ename !== 'string') {
420 options = ename;
421 for (ename in options) {
422 if (options.hasOwnProperty(ename)) {
423 config = options[ename];
424 if (!me.eventOptionsRe.test(ename)) {
425 me.addListener(ename, config.fn || config, config.scope || options.scope, config.fn ? config : options);
426 }
427 }
428 }
429 }
430 else {
431 ename = ename.toLowerCase();
432 me.events[ename] = me.events[ename] || true;
433 event = me.events[ename] || true;
434 if (Ext.isBoolean(event)) {
435 me.events[ename] = event = new Ext.util.Event(me, ename);
436 }
437
438 // Allow listeners: { click: 'onClick', scope: myObject }
439 if (typeof fn === 'string') {
440 fn = scope[fn] || me.fn;
441 }
442 event.addListener(fn, scope, Ext.isObject(options) ? options : {});
443
444 // Maintain count of listeners for each event name.
445 // For repeated events in time-critical code, the firing code should use
446 // if (!me.hasListeners.beforerender || me.fireEvent('beforerender', me) !== false) { //code... }
447 me.hasListeners[ename] = (me.hasListeners[ename]||0) + 1;
448 }
449 },
450
451 /**
452 * Removes an event handler.
453 *
454 * @param {String} eventName The type of event the handler was associated with.
455 * @param {Function} fn The handler to remove. **This must be a reference to the function passed into the
456 * {@link #addListener} call.**
457 * @param {Object} scope (optional) The scope originally specified for the handler. It must be the same as the
458 * scope argument specified in the original call to {@link #addListener} or the listener will not be removed.
459 */
460 removeListener: function(ename, fn, scope) {
461 var me = this,
462 config,
463 event,
464 options;
465
466 if (typeof ename !== 'string') {
467 options = ename;
468 for (ename in options) {
469 if (options.hasOwnProperty(ename)) {
470 config = options[ename];
471 if (!me.eventOptionsRe.test(ename)) {
472 me.removeListener(ename, config.fn || config, config.scope || options.scope);
473 }
474 }
475 }
476 } else {
477 ename = ename.toLowerCase();
478 event = me.events[ename];
479 if (event && event.isEvent) {
480 event.removeListener(fn, scope);
481
482 // Maintain count of listeners for each event name.
483 // For repeated events in time-critical code, the firing code should use
484 // if (!me.hasListeners.beforerender || me.fireEvent('beforerender', me) !== false) { //code... }
485 me.hasListeners[ename]--;
486 }
487 }
488 },
489
490 /**
491 * Removes all listeners for this object including the managed listeners
492 */
493 clearListeners: function() {
494 var events = this.events,
495 event,
496 key;
497
498 for (key in events) {
499 if (events.hasOwnProperty(key)) {
500 event = events[key];
501 if (event.isEvent) {
502 event.clearListeners();
503 }
504 }
505 }
506
507 this.clearManagedListeners();
508 },
509
510 purgeListeners : function() {
511 if (Ext.global.console) {
512 Ext.global.console.warn('Observable: purgeListeners has been deprecated. Please use clearListeners.');
513 }
514 return this.clearListeners.apply(this, arguments);
515 },
516
517 /**
518 * Removes all managed listeners for this object.
519 */
520 clearManagedListeners : function() {
521 var managedListeners = this.managedListeners || [],
522 i = 0,
523 len = managedListeners.length;
524
525 for (; i < len; i++) {
526 this.removeManagedListenerItem(true, managedListeners[i]);
527 }
528
529 this.managedListeners = [];
530 },
531
532 /**
533 * Remove a single managed listener item
534 * @private
535 * @param {Boolean} isClear True if this is being called during a clear
536 * @param {Object} managedListener The managed listener item
537 * See removeManagedListener for other args
538 */
539 removeManagedListenerItem: function(isClear, managedListener, item, ename, fn, scope){
540 if (isClear || (managedListener.item === item && managedListener.ename === ename && (!fn || managedListener.fn === fn) && (!scope || managedListener.scope === scope))) {
541 managedListener.item.un(managedListener.ename, managedListener.fn, managedListener.scope);
542 if (!isClear) {
543 Ext.Array.remove(this.managedListeners, managedListener);
544 }
545 }
546 },
547
548 purgeManagedListeners : function() {
549 if (Ext.global.console) {
550 Ext.global.console.warn('Observable: purgeManagedListeners has been deprecated. Please use clearManagedListeners.');
551 }
552 return this.clearManagedListeners.apply(this, arguments);
553 },
554
555 /**
556 * Adds the specified events to the list of events which this Observable may fire.
557 *
558 * @param {Object/String...} eventNames Either an object with event names as properties with
559 * a value of `true`. For example:
560 *
561 * this.addEvents({
562 * storeloaded: true,
563 * storecleared: true
564 * });
565 *
566 * Or any number of event names as separate parameters. For example:
567 *
568 * this.addEvents('storeloaded', 'storecleared');
569 *
570 */
571 addEvents: function(o) {
572 var me = this,
573 events = me.events || (me.events = {}),
574 arg, args, i;
575
576 if (typeof o == 'string') {
577 for (args = arguments, i = args.length; i--; ) {
578 arg = args[i];
579 if (!events[arg]) {
580 events[arg] = true;
581 }
582 }
583 } else {
584 Ext.applyIf(me.events, o);
585 }
586 },
587
588 /**
589 * Checks to see if this object has any listeners for a specified event, or whether the event bubbles. The answer
590 * indicates whether the event needs firing or not.
591 *
592 * @param {String} eventName The name of the event to check for
593 * @return {Boolean} `true` if the event is being listened for or bubbles, else `false`
594 */
595 hasListener: function(ename) {
596 return !!this.hasListeners[ename.toLowerCase()];
597 },
598
599 /**
600 * Suspends the firing of all events. (see {@link #resumeEvents})
601 *
602 * @param {Boolean} queueSuspended Pass as true to queue up suspended events to be fired
603 * after the {@link #resumeEvents} call instead of discarding all suspended events.
604 */
605 suspendEvents: function(queueSuspended) {
606 this.eventsSuspended = true;
607 if (queueSuspended && !this.eventQueue) {
608 this.eventQueue = [];
609 }
610 },
611
612 /**
613 * Resumes firing events (see {@link #suspendEvents}).
614 *
615 * If events were suspended using the `queueSuspended` parameter, then all events fired
616 * during event suspension will be sent to any listeners now.
617 */
618 resumeEvents: function() {
619 var me = this,
620 queued = me.eventQueue,
621 qLen, q;
622
623 me.eventsSuspended = false;
624 delete me.eventQueue;
625
626 if (queued) {
627 qLen = queued.length;
628 for (q = 0; q < qLen; q++) {
629 me.continueFireEvent.apply(me, queued[q]);
630 }
631 }
632 },
633
634 /**
635 * Relays selected events from the specified Observable as if the events were fired by `this`.
636 *
637 * For example if you are extending Grid, you might decide to forward some events from store.
638 * So you can do this inside your initComponent:
639 *
640 * this.relayEvents(this.getStore(), ['load']);
641 *
642 * The grid instance will then have an observable 'load' event which will be passed the
643 * parameters of the store's load event and any function fired with the grid's load event
644 * would have access to the grid using the `this` keyword.
645 *
646 * @param {Object} origin The Observable whose events this object is to relay.
647 * @param {String[]} events Array of event names to relay.
648 * @param {String} [prefix] A common prefix to attach to the event names. For example:
649 *
650 * this.relayEvents(this.getStore(), ['load', 'clear'], 'store');
651 *
652 * Now the grid will forward 'load' and 'clear' events of store as 'storeload' and 'storeclear'.
653 */
654 relayEvents : function(origin, events, prefix) {
655 prefix = prefix || '';
656 var me = this,
657 len = events.length,
658 i = 0,
659 oldName,
660 newName;
661
662 for (; i < len; i++) {
663 oldName = events[i];
664 newName = prefix + oldName;
665 me.events[newName] = me.events[newName] || true;
666 origin.on(oldName, me.createRelayer(newName));
667 }
668 },
669
670 /**
671 * @private
672 * Creates an event handling function which refires the event from this object as the passed event name.
673 * @param newName
674 * @param {Array} beginEnd (optional) The caller can specify on which indices to slice
675 * @returns {Function}
676 */
677 createRelayer: function(newName, beginEnd){
678 var me = this;
679 return function(){
680 return me.fireEvent.apply(me, [newName].concat(Array.prototype.slice.apply(arguments, beginEnd || [0, -1])));
681 };
682 },
683
684 /**
685 * Enables events fired by this Observable to bubble up an owner hierarchy by calling `this.getBubbleTarget()` if
686 * present. There is no implementation in the Observable base class.
687 *
688 * This is commonly used by Ext.Components to bubble events to owner Containers.
689 * See {@link Ext.Component#getBubbleTarget}. The default implementation in Ext.Component returns the
690 * Component's immediate owner. But if a known target is required, this can be overridden to access the
691 * required target more quickly.
692 *
693 * Example:
694 *
695 * Ext.override(Ext.form.field.Base, {
696 * // Add functionality to Field's initComponent to enable the change event to bubble
697 * initComponent : Ext.Function.createSequence(Ext.form.field.Base.prototype.initComponent, function() {
698 * this.enableBubble('change');
699 * }),
700 *
701 * // We know that we want Field's events to bubble directly to the FormPanel.
702 * getBubbleTarget : function() {
703 * if (!this.formPanel) {
704 * this.formPanel = this.findParentByType('form');
705 * }
706 * return this.formPanel;
707 * }
708 * });
709 *
710 * var myForm = new Ext.formPanel({
711 * title: 'User Details',
712 * items: [{
713 * ...
714 * }],
715 * listeners: {
716 * change: function() {
717 * // Title goes red if form has been modified.
718 * myForm.header.setStyle('color', 'red');
719 * }
720 * }
721 * });
722 *
723 * @param {String/String[]} eventNames The event name to bubble, or an Array of event names.
724 */
725 enableBubble: function(eventNames) {
726 if (eventNames) {
727 var me = this,
728 names = (typeof eventNames == 'string') ? arguments : eventNames,
729 length = names.length,
730 events = me.events,
731 ename, event, i;
732
733 for (i = 0; i < length; ++i) {
734 ename = names[i].toLowerCase();
735 event = events[ename];
736
737 if (!event || typeof event == 'boolean') {
738 events[ename] = event = new Ext.util.Event(me, ename);
739 }
740
741 // Event must fire if it bubbles (We don't know if anyone up the bubble hierarchy has listeners added)
742 me.hasListeners[ename] = (me.hasListeners[ename]||0) + 1;
743
744 event.bubble = true;
745 }
746 }
747 }
748}, function() {
749
750 this.createAlias({
751 /**
752 * @method
753 * Shorthand for {@link #addListener}.
754 * @inheritdoc Ext.util.Observable#addListener
755 */
756 on: 'addListener',
757 /**
758 * @method
759 * Shorthand for {@link #removeListener}.
760 * @inheritdoc Ext.util.Observable#removeListener
761 */
762 un: 'removeListener',
763 /**
764 * @method
765 * Shorthand for {@link #addManagedListener}.
766 * @inheritdoc Ext.util.Observable#addManagedListener
767 */
768 mon: 'addManagedListener',
769 /**
770 * @method
771 * Shorthand for {@link #removeManagedListener}.
772 * @inheritdoc Ext.util.Observable#removeManagedListener
773 */
774 mun: 'removeManagedListener'
775 });
776
777 //deprecated, will be removed in 5.0
778 this.observeClass = this.observe;
779
780 Ext.apply(Ext.util.Observable.prototype, function(){
781 // this is considered experimental (along with beforeMethod, afterMethod, removeMethodListener?)
782 // allows for easier interceptor and sequences, including cancelling and overwriting the return value of the call
783 // private
784 function getMethodEvent(method){
785 var e = (this.methodEvents = this.methodEvents || {})[method],
786 returnValue,
787 v,
788 cancel,
789 obj = this;
790
791 if (!e) {
792 this.methodEvents[method] = e = {};
793 e.originalFn = this[method];
794 e.methodName = method;
795 e.before = [];
796 e.after = [];
797
798 var makeCall = function(fn, scope, args){
799 if((v = fn.apply(scope || obj, args)) !== undefined){
800 if (typeof v == 'object') {
801 if(v.returnValue !== undefined){
802 returnValue = v.returnValue;
803 }else{
804 returnValue = v;
805 }
806 cancel = !!v.cancel;
807 }
808 else
809 if (v === false) {
810 cancel = true;
811 }
812 else {
813 returnValue = v;
814 }
815 }
816 };
817
818 this[method] = function(){
819 var args = Array.prototype.slice.call(arguments, 0),
820 b, i, len;
821 returnValue = v = undefined;
822 cancel = false;
823
824 for(i = 0, len = e.before.length; i < len; i++){
825 b = e.before[i];
826 makeCall(b.fn, b.scope, args);
827 if (cancel) {
828 return returnValue;
829 }
830 }
831
832 if((v = e.originalFn.apply(obj, args)) !== undefined){
833 returnValue = v;
834 }
835
836 for(i = 0, len = e.after.length; i < len; i++){
837 b = e.after[i];
838 makeCall(b.fn, b.scope, args);
839 if (cancel) {
840 return returnValue;
841 }
842 }
843 return returnValue;
844 };
845 }
846 return e;
847 }
848
849 return {
850 // these are considered experimental
851 // allows for easier interceptor and sequences, including cancelling and overwriting the return value of the call
852 // adds an 'interceptor' called before the original method
853 beforeMethod : function(method, fn, scope){
854 getMethodEvent.call(this, method).before.push({
855 fn: fn,
856 scope: scope
857 });
858 },
859
860 // adds a 'sequence' called after the original method
861 afterMethod : function(method, fn, scope){
862 getMethodEvent.call(this, method).after.push({
863 fn: fn,
864 scope: scope
865 });
866 },
867
868 removeMethodListener: function(method, fn, scope){
869 var e = this.getMethodEvent(method),
870 i, len;
871 for(i = 0, len = e.before.length; i < len; i++){
872 if(e.before[i].fn == fn && e.before[i].scope == scope){
873 Ext.Array.erase(e.before, i, 1);
874 return;
875 }
876 }
877 for(i = 0, len = e.after.length; i < len; i++){
878 if(e.after[i].fn == fn && e.after[i].scope == scope){
879 Ext.Array.erase(e.after, i, 1);
880 return;
881 }
882 }
883 },
884
885 toggleEventLogging: function(toggle) {
886 Ext.util.Observable[toggle ? 'capture' : 'releaseCapture'](this, function(en) {
887 if (Ext.isDefined(Ext.global.console)) {
888 Ext.global.console.log(en, arguments);
889 }
890 });
891 }
892 };
893 }());
894});
895
896/**
897 * @author Ed Spencer
898 *
899 * Associations enable you to express relationships between different {@link Ext.data.Model Models}. Let's say we're
900 * writing an ecommerce system where Users can make Orders - there's a relationship between these Models that we can
901 * express like this:
902 *
903 * Ext.define('User', {
904 * extend: 'Ext.data.Model',
905 * fields: ['id', 'name', 'email'],
906 *
907 * hasMany: {model: 'Order', name: 'orders'}
908 * });
909 *
910 * Ext.define('Order', {
911 * extend: 'Ext.data.Model',
912 * fields: ['id', 'user_id', 'status', 'price'],
913 *
914 * belongsTo: 'User'
915 * });
916 *
917 * We've set up two models - User and Order - and told them about each other. You can set up as many associations on
918 * each Model as you need using the two default types - {@link Ext.data.HasManyAssociation hasMany} and {@link
919 * Ext.data.BelongsToAssociation belongsTo}. There's much more detail on the usage of each of those inside their
920 * documentation pages. If you're not familiar with Models already, {@link Ext.data.Model there is plenty on those too}.
921 *
922 * **Further Reading**
923 *
924 * - {@link Ext.data.association.HasMany hasMany associations}
925 * - {@link Ext.data.association.BelongsTo belongsTo associations}
926 * - {@link Ext.data.association.HasOne hasOne associations}
927 * - {@link Ext.data.Model using Models}
928 *
929 * # Self association models
930 *
931 * We can also have models that create parent/child associations between the same type. Below is an example, where
932 * groups can be nested inside other groups:
933 *
934 * // Server Data
935 * {
936 * "groups": {
937 * "id": 10,
938 * "parent_id": 100,
939 * "name": "Main Group",
940 * "parent_group": {
941 * "id": 100,
942 * "parent_id": null,
943 * "name": "Parent Group"
944 * },
945 * "child_groups": [{
946 * "id": 2,
947 * "parent_id": 10,
948 * "name": "Child Group 1"
949 * },{
950 * "id": 3,
951 * "parent_id": 10,
952 * "name": "Child Group 2"
953 * },{
954 * "id": 4,
955 * "parent_id": 10,
956 * "name": "Child Group 3"
957 * }]
958 * }
959 * }
960 *
961 * // Client code
962 * Ext.define('Group', {
963 * extend: 'Ext.data.Model',
964 * fields: ['id', 'parent_id', 'name'],
965 * proxy: {
966 * type: 'ajax',
967 * url: 'data.json',
968 * reader: {
969 * type: 'json',
970 * root: 'groups'
971 * }
972 * },
973 * associations: [{
974 * type: 'hasMany',
975 * model: 'Group',
976 * primaryKey: 'id',
977 * foreignKey: 'parent_id',
978 * autoLoad: true,
979 * associationKey: 'child_groups' // read child data from child_groups
980 * }, {
981 * type: 'belongsTo',
982 * model: 'Group',
983 * primaryKey: 'id',
984 * foreignKey: 'parent_id',
985 * associationKey: 'parent_group' // read parent data from parent_group
986 * }]
987 * });
988 *
989 * Ext.onReady(function(){
990 *
991 * Group.load(10, {
992 * success: function(group){
993 * console.log(group.getGroup().get('name'));
994 *
995 * group.groups().each(function(rec){
996 * console.log(rec.get('name'));
997 * });
998 * }
999 * });
1000 *
1001 * });
1002 *
1003 */
1004Ext.define('Ext.data.association.Association', {
1005 alternateClassName: 'Ext.data.Association',
1006 /**
1007 * @cfg {String} ownerModel (required)
1008 * The string name of the model that owns the association.
1009 */
1010
1011 /**
1012 * @cfg {String} associatedModel (required)
1013 * The string name of the model that is being associated with.
1014 */
1015
1016 /**
1017 * @cfg {String} primaryKey
1018 * The name of the primary key on the associated model. In general this will be the
1019 * {@link Ext.data.Model#idProperty} of the Model.
1020 */
1021 primaryKey: 'id',
1022
1023 /**
1024 * @cfg {Ext.data.reader.Reader} reader
1025 * A special reader to read associated data
1026 */
1027
1028 /**
1029 * @cfg {String} associationKey
1030 * The name of the property in the data to read the association from. Defaults to the name of the associated model.
1031 */
1032
1033 defaultReaderType: 'json',
1034
1035 statics: {
1036 create: function(association){
1037 if (!association.isAssociation) {
1038 if (Ext.isString(association)) {
1039 association = {
1040 type: association
1041 };
1042 }
1043
1044 switch (association.type) {
1045 case 'belongsTo':
1046 return new Ext.data.association.BelongsTo(association);
1047 case 'hasMany':
1048 return new Ext.data.association.HasMany(association);
1049 case 'hasOne':
1050 return new Ext.data.association.HasOne(association);
1051 //TODO Add this back when it's fixed
1052// case 'polymorphic':
1053// return Ext.create('Ext.data.PolymorphicAssociation', association);
1054 default:
1055 Ext.Error.raise('Unknown Association type: "' + association.type + '"');
1056 }
1057 }
1058 return association;
1059 }
1060 },
1061
1062 /**
1063 * Creates the Association object.
1064 * @param {Object} [config] Config object.
1065 */
1066 constructor: function(config) {
1067 Ext.apply(this, config);
1068
1069 var types = Ext.ModelManager.types,
1070 ownerName = config.ownerModel,
1071 associatedName = config.associatedModel,
1072 ownerModel = types[ownerName],
1073 associatedModel = types[associatedName],
1074 ownerProto;
1075
1076 if (ownerModel === undefined) {
1077 Ext.Error.raise("The configured ownerModel was not valid (you tried " + ownerName + ")");
1078 }
1079 if (associatedModel === undefined) {
1080 Ext.Error.raise("The configured associatedModel was not valid (you tried " + associatedName + ")");
1081 }
1082
1083 this.ownerModel = ownerModel;
1084 this.associatedModel = associatedModel;
1085
1086 /**
1087 * @property {String} ownerName
1088 * The name of the model that 'owns' the association
1089 */
1090
1091 /**
1092 * @property {String} associatedName
1093 * The name of the model is on the other end of the association (e.g. if a User model hasMany Orders, this is
1094 * 'Order')
1095 */
1096
1097 Ext.applyIf(this, {
1098 ownerName : ownerName,
1099 associatedName: associatedName
1100 });
1101 },
1102
1103 /**
1104 * Get a specialized reader for reading associated data
1105 * @return {Ext.data.reader.Reader} The reader, null if not supplied
1106 */
1107 getReader: function(){
1108 var me = this,
1109 reader = me.reader,
1110 model = me.associatedModel;
1111
1112 if (reader) {
1113 if (Ext.isString(reader)) {
1114 reader = {
1115 type: reader
1116 };
1117 }
1118 if (reader.isReader) {
1119 reader.setModel(model);
1120 } else {
1121 Ext.applyIf(reader, {
1122 model: model,
1123 type : me.defaultReaderType
1124 });
1125 }
1126 me.reader = Ext.createByAlias('reader.' + reader.type, reader);
1127 }
1128 return me.reader || null;
1129 }
1130});
1131
1132/**
1133 * @author Don Griffin
1134 *
1135 * This class is a base for all id generators. It also provides lookup of id generators by
1136 * their id.
1137 *
1138 * Generally, id generators are used to generate a primary key for new model instances. There
1139 * are different approaches to solving this problem, so this mechanism has both simple use
1140 * cases and is open to custom implementations. A {@link Ext.data.Model} requests id generation
1141 * using the {@link Ext.data.Model#idgen} property.
1142 *
1143 * # Identity, Type and Shared IdGenerators
1144 *
1145 * It is often desirable to share IdGenerators to ensure uniqueness or common configuration.
1146 * This is done by giving IdGenerator instances an id property by which they can be looked
1147 * up using the {@link #get} method. To configure two {@link Ext.data.Model Model} classes
1148 * to share one {@link Ext.data.SequentialIdGenerator sequential} id generator, you simply
1149 * assign them the same id:
1150 *
1151 * Ext.define('MyApp.data.MyModelA', {
1152 * extend: 'Ext.data.Model',
1153 * idgen: {
1154 * type: 'sequential',
1155 * id: 'foo'
1156 * }
1157 * });
1158 *
1159 * Ext.define('MyApp.data.MyModelB', {
1160 * extend: 'Ext.data.Model',
1161 * idgen: {
1162 * type: 'sequential',
1163 * id: 'foo'
1164 * }
1165 * });
1166 *
1167 * To make this as simple as possible for generator types that are shared by many (or all)
1168 * Models, the IdGenerator types (such as 'sequential' or 'uuid') are also reserved as
1169 * generator id's. This is used by the {@link Ext.data.UuidGenerator} which has an id equal
1170 * to its type ('uuid'). In other words, the following Models share the same generator:
1171 *
1172 * Ext.define('MyApp.data.MyModelX', {
1173 * extend: 'Ext.data.Model',
1174 * idgen: 'uuid'
1175 * });
1176 *
1177 * Ext.define('MyApp.data.MyModelY', {
1178 * extend: 'Ext.data.Model',
1179 * idgen: 'uuid'
1180 * });
1181 *
1182 * This can be overridden (by specifying the id explicitly), but there is no particularly
1183 * good reason to do so for this generator type.
1184 *
1185 * # Creating Custom Generators
1186 *
1187 * An id generator should derive from this class and implement the {@link #generate} method.
1188 * The constructor will apply config properties on new instances, so a constructor is often
1189 * not necessary.
1190 *
1191 * To register an id generator type, a derived class should provide an `alias` like so:
1192 *
1193 * Ext.define('MyApp.data.CustomIdGenerator', {
1194 * extend: 'Ext.data.IdGenerator',
1195 * alias: 'idgen.custom',
1196 *
1197 * configProp: 42, // some config property w/default value
1198 *
1199 * generate: function () {
1200 * return ... // a new id
1201 * }
1202 * });
1203 *
1204 * Using the custom id generator is then straightforward:
1205 *
1206 * Ext.define('MyApp.data.MyModel', {
1207 * extend: 'Ext.data.Model',
1208 * idgen: 'custom'
1209 * });
1210 * // or...
1211 *
1212 * Ext.define('MyApp.data.MyModel', {
1213 * extend: 'Ext.data.Model',
1214 * idgen: {
1215 * type: 'custom',
1216 * configProp: value
1217 * }
1218 * });
1219 *
1220 * It is not recommended to mix shared generators with generator configuration. This leads
1221 * to unpredictable results unless all configurations match (which is also redundant). In
1222 * such cases, a custom generator with a default id is the best approach.
1223 *
1224 * Ext.define('MyApp.data.CustomIdGenerator', {
1225 * extend: 'Ext.data.SequentialIdGenerator',
1226 * alias: 'idgen.custom',
1227 *
1228 * id: 'custom', // shared by default
1229 *
1230 * prefix: 'ID_',
1231 * seed: 1000
1232 * });
1233 *
1234 * Ext.define('MyApp.data.MyModelX', {
1235 * extend: 'Ext.data.Model',
1236 * idgen: 'custom'
1237 * });
1238 *
1239 * Ext.define('MyApp.data.MyModelY', {
1240 * extend: 'Ext.data.Model',
1241 * idgen: 'custom'
1242 * });
1243 *
1244 * // the above models share a generator that produces ID_1000, ID_1001, etc..
1245 *
1246 */
1247Ext.define('Ext.data.IdGenerator', {
1248
1249 /**
1250 * @property {Boolean} isGenerator
1251 * `true` in this class to identify an objact as an instantiated IdGenerator, or subclass thereof.
1252 */
1253 isGenerator: true,
1254
1255 /**
1256 * Initializes a new instance.
1257 * @param {Object} config (optional) Configuration object to be applied to the new instance.
1258 */
1259 constructor: function(config) {
1260 var me = this;
1261
1262 Ext.apply(me, config);
1263
1264 if (me.id) {
1265 Ext.data.IdGenerator.all[me.id] = me;
1266 }
1267 },
1268
1269 /**
1270 * @cfg {String} id
1271 * The id by which to register a new instance. This instance can be found using the
1272 * {@link Ext.data.IdGenerator#get} static method.
1273 */
1274
1275 getRecId: function (rec) {
1276 return rec.modelName + '-' + rec.internalId;
1277 },
1278
1279 /**
1280 * Generates and returns the next id. This method must be implemented by the derived
1281 * class.
1282 *
1283 * @return {String} The next id.
1284 * @method generate
1285 * @abstract
1286 */
1287
1288 statics: {
1289 /**
1290 * @property {Object} all
1291 * This object is keyed by id to lookup instances.
1292 * @private
1293 * @static
1294 */
1295 all: {},
1296
1297 /**
1298 * Returns the IdGenerator given its config description.
1299 * @param {String/Object} config If this parameter is an IdGenerator instance, it is
1300 * simply returned. If this is a string, it is first used as an id for lookup and
1301 * then, if there is no match, as a type to create a new instance. This parameter
1302 * can also be a config object that contains a `type` property (among others) that
1303 * are used to create and configure the instance.
1304 * @static
1305 */
1306 get: function (config) {
1307 var generator,
1308 id,
1309 type;
1310
1311 if (typeof config == 'string') {
1312 id = type = config;
1313 config = null;
1314 } else if (config.isGenerator) {
1315 return config;
1316 } else {
1317 id = config.id || config.type;
1318 type = config.type;
1319 }
1320
1321 generator = this.all[id];
1322 if (!generator) {
1323 generator = Ext.create('idgen.' + type, config);
1324 }
1325
1326 return generator;
1327 }
1328 }
1329});
1330
1331/**
1332 * @author Ed Spencer
1333 *
1334 * Represents a single read or write operation performed by a {@link Ext.data.proxy.Proxy Proxy}. Operation objects are
1335 * used to enable communication between Stores and Proxies. Application developers should rarely need to interact with
1336 * Operation objects directly.
1337 *
1338 * Several Operations can be batched together in a {@link Ext.data.Batch batch}.
1339 */
1340Ext.define('Ext.data.Operation', {
1341 /**
1342 * @cfg {Boolean} synchronous
1343 * True if this Operation is to be executed synchronously. This property is inspected by a
1344 * {@link Ext.data.Batch Batch} to see if a series of Operations can be executed in parallel or not.
1345 */
1346 synchronous: true,
1347
1348 /**
1349 * @cfg {String} action
1350 * The action being performed by this Operation. Should be one of 'create', 'read', 'update' or 'destroy'.
1351 */
1352 action: undefined,
1353
1354 /**
1355 * @cfg {Ext.util.Filter[]} filters
1356 * Optional array of filter objects. Only applies to 'read' actions.
1357 */
1358 filters: undefined,
1359
1360 /**
1361 * @cfg {Ext.util.Sorter[]} sorters
1362 * Optional array of sorter objects. Only applies to 'read' actions.
1363 */
1364 sorters: undefined,
1365
1366 /**
1367 * @cfg {Ext.util.Grouper[]} groupers
1368 * Optional grouping configuration. Only applies to 'read' actions where grouping is desired.
1369 */
1370 groupers: undefined,
1371
1372 /**
1373 * @cfg {Number} start
1374 * The start index (offset), used in paging when running a 'read' action.
1375 */
1376 start: undefined,
1377
1378 /**
1379 * @cfg {Number} limit
1380 * The number of records to load. Used on 'read' actions when paging is being used.
1381 */
1382 limit: undefined,
1383
1384 /**
1385 * @cfg {Ext.data.Batch} batch
1386 * The batch that this Operation is a part of.
1387 */
1388 batch: undefined,
1389
1390 /**
1391 * @cfg {Function} callback
1392 * Function to execute when operation completed.
1393 * @cfg {Ext.data.Model[]} callback.records Array of records.
1394 * @cfg {Ext.data.Operation} callback.operation The Operation itself.
1395 * @cfg {Boolean} callback.success True when operation completed successfully.
1396 */
1397 callback: undefined,
1398
1399 /**
1400 * @cfg {Object} scope
1401 * Scope for…
Large files files are truncated, but you can click here to view the full file