PageRenderTime 106ms CodeModel.GetById 37ms app.highlight 43ms RepoModel.GetById 1ms app.codeStats 1ms

/ext-4.1.0_b3/src/app/Controller.js

https://bitbucket.org/srogerf/javascript
JavaScript | 450 lines | 128 code | 43 blank | 279 comment | 24 complexity | c5a67115e832fc601661e736738e3849 MD5 | raw file
  1/**
  2 * @class Ext.app.Controller
  3 *
  4 * Controllers are the glue that binds an application together. All they really do is listen for events (usually from
  5 * views) and take some action. Here's how we might create a Controller to manage Users:
  6 *
  7 *     Ext.define('MyApp.controller.Users', {
  8 *         extend: 'Ext.app.Controller',
  9 *
 10 *         init: function() {
 11 *             console.log('Initialized Users! This happens before the Application launch function is called');
 12 *         }
 13 *     });
 14 *
 15 * The init function is a special method that is called when your application boots. It is called before the
 16 * {@link Ext.app.Application Application}'s launch function is executed so gives a hook point to run any code before
 17 * your Viewport is created.
 18 *
 19 * The init function is a great place to set up how your controller interacts with the view, and is usually used in
 20 * conjunction with another Controller function - {@link Ext.app.Controller#control control}. The control function
 21 * makes it easy to listen to events on your view classes and take some action with a handler function. Let's update
 22 * our Users controller to tell us when the panel is rendered:
 23 *
 24 *     Ext.define('MyApp.controller.Users', {
 25 *         extend: 'Ext.app.Controller',
 26 *
 27 *         init: function() {
 28 *             this.control({
 29 *                 'viewport > panel': {
 30 *                     render: this.onPanelRendered
 31 *                 }
 32 *             });
 33 *         },
 34 *
 35 *         onPanelRendered: function() {
 36 *             console.log('The panel was rendered');
 37 *         }
 38 *     });
 39 *
 40 * We've updated the init function to use this.control to set up listeners on views in our application. The control
 41 * function uses the new ComponentQuery engine to quickly and easily get references to components on the page. If you
 42 * are not familiar with ComponentQuery yet, be sure to check out the {@link Ext.ComponentQuery documentation}. In brief though,
 43 * it allows us to pass a CSS-like selector that will find every matching component on the page.
 44 *
 45 * In our init function above we supplied 'viewport > panel', which translates to "find me every Panel that is a direct
 46 * child of a Viewport". We then supplied an object that maps event names (just 'render' in this case) to handler
 47 * functions. The overall effect is that whenever any component that matches our selector fires a 'render' event, our
 48 * onPanelRendered function is called.
 49 *
 50 * <u>Using refs</u>
 51 *
 52 * One of the most useful parts of Controllers is the new ref system. These use the new {@link Ext.ComponentQuery} to
 53 * make it really easy to get references to Views on your page. Let's look at an example of this now:
 54 *
 55 *     Ext.define('MyApp.controller.Users', {
 56 *         extend: 'Ext.app.Controller',
 57 *
 58 *         refs: [
 59 *             {
 60 *                 ref: 'list',
 61 *                 selector: 'grid'
 62 *             }
 63 *         ],
 64 *
 65 *         init: function() {
 66 *             this.control({
 67 *                 'button': {
 68 *                     click: this.refreshGrid
 69 *                 }
 70 *             });
 71 *         },
 72 *
 73 *         refreshGrid: function() {
 74 *             this.getList().store.load();
 75 *         }
 76 *     });
 77 *
 78 * This example assumes the existence of a {@link Ext.grid.Panel Grid} on the page, which contains a single button to
 79 * refresh the Grid when clicked. In our refs array, we set up a reference to the grid. There are two parts to this -
 80 * the 'selector', which is a {@link Ext.ComponentQuery ComponentQuery} selector which finds any grid on the page and
 81 * assigns it to the reference 'list'.
 82 *
 83 * By giving the reference a name, we get a number of things for free. The first is the getList function that we use in
 84 * the refreshGrid method above. This is generated automatically by the Controller based on the name of our ref, which
 85 * was capitalized and prepended with get to go from 'list' to 'getList'.
 86 *
 87 * The way this works is that the first time getList is called by your code, the ComponentQuery selector is run and the
 88 * first component that matches the selector ('grid' in this case) will be returned. All future calls to getList will
 89 * use a cached reference to that grid. Usually it is advised to use a specific ComponentQuery selector that will only
 90 * match a single View in your application (in the case above our selector will match any grid on the page).
 91 *
 92 * Bringing it all together, our init function is called when the application boots, at which time we call this.control
 93 * to listen to any click on a {@link Ext.button.Button button} and call our refreshGrid function (again, this will
 94 * match any button on the page so we advise a more specific selector than just 'button', but have left it this way for
 95 * simplicity). When the button is clicked we use out getList function to refresh the grid.
 96 *
 97 * You can create any number of refs and control any number of components this way, simply adding more functions to
 98 * your Controller as you go. For an example of real-world usage of Controllers see the Feed Viewer example in the
 99 * examples/app/feed-viewer folder in the SDK download.
100 *
101 * <u>Generated getter methods</u>
102 *
103 * Refs aren't the only thing that generate convenient getter methods. Controllers often have to deal with Models and
104 * Stores so the framework offers a couple of easy ways to get access to those too. Let's look at another example:
105 *
106 *     Ext.define('MyApp.controller.Users', {
107 *         extend: 'Ext.app.Controller',
108 *
109 *         models: ['User'],
110 *         stores: ['AllUsers', 'AdminUsers'],
111 *
112 *         init: function() {
113 *             var User = this.getUserModel(),
114 *                 allUsers = this.getAllUsersStore();
115 *
116 *             var ed = new User({name: 'Ed'});
117 *             allUsers.add(ed);
118 *         }
119 *     });
120 *
121 * By specifying Models and Stores that the Controller cares about, it again dynamically loads them from the appropriate
122 * locations (app/model/User.js, app/store/AllUsers.js and app/store/AdminUsers.js in this case) and creates getter
123 * functions for them all. The example above will create a new User model instance and add it to the AllUsers Store.
124 * Of course, you could do anything in this function but in this case we just did something simple to demonstrate the
125 * functionality.
126 *
127 * <u>Further Reading</u>
128 *
129 * For more information about writing Ext JS 4 applications, please see the
130 * [application architecture guide](#/guide/application_architecture). Also see the {@link Ext.app.Application} documentation.
131 *
132 * @docauthor Ed Spencer
133 */
134Ext.define('Ext.app.Controller', {
135
136    mixins: {
137        observable: 'Ext.util.Observable'
138    },
139
140    /**
141     * @cfg {String} id The id of this controller. You can use this id when dispatching.
142     */
143    
144    /**
145     * @cfg {String[]} models
146     * Array of models to require from AppName.model namespace. For example:
147     * 
148     *     Ext.define("MyApp.controller.Foo", {
149     *         extend: "Ext.app.Controller",
150     *         models: ['User', 'Vehicle']
151     *     });
152     * 
153     * This is equivalent of:
154     * 
155     *     Ext.define("MyApp.controller.Foo", {
156     *         extend: "Ext.app.Controller",
157     *         requires: ['MyApp.model.User', 'MyApp.model.Vehicle']
158     *     });
159     * 
160     */
161
162    /**
163     * @cfg {String[]} views
164     * Array of views to require from AppName.view namespace. For example:
165     * 
166     *     Ext.define("MyApp.controller.Foo", {
167     *         extend: "Ext.app.Controller",
168     *         views: ['List', 'Detail']
169     *     });
170     * 
171     * This is equivalent of:
172     * 
173     *     Ext.define("MyApp.controller.Foo", {
174     *         extend: "Ext.app.Controller",
175     *         requires: ['MyApp.view.List', 'MyApp.view.Detail']
176     *     });
177     * 
178     */
179
180    /**
181     * @cfg {String[]} stores
182     * Array of stores to require from AppName.store namespace. For example:
183     * 
184     *     Ext.define("MyApp.controller.Foo", {
185     *         extend: "Ext.app.Controller",
186     *         stores: ['Users', 'Vehicles']
187     *     });
188     * 
189     * This is equivalent of:
190     * 
191     *     Ext.define("MyApp.controller.Foo", {
192     *         extend: "Ext.app.Controller",
193     *         requires: ['MyApp.store.Users', 'MyApp.store.Vehicles']
194     *     });
195     * 
196     */
197
198    /**
199     * @cfg {Object[]} refs
200     * Array of configs to build up references to views on page. For example:
201     * 
202     *     Ext.define("MyApp.controller.Foo", {
203     *         extend: "Ext.app.Controller",
204     *         refs: [
205     *             {
206     *                 ref: 'list',
207     *                 selector: 'grid'
208     *             }
209     *         ],
210     *     });
211     * 
212     * This will add method `getList` to the controller which will internally use
213     * Ext.ComponentQuery to reference the grid component on page.
214     */
215
216    onClassExtended: function(cls, data, hooks) {
217        var className = Ext.getClassName(cls),
218            match = className.match(/^(.*)\.controller\./);
219
220        if (match !== null) {
221            var namespace = Ext.Loader.getPrefix(className) || match[1],
222                onBeforeClassCreated = hooks.onBeforeCreated,
223                requires = [],
224                modules = ['model', 'view', 'store'],
225                prefix;
226
227            hooks.onBeforeCreated = function(cls, data) {
228                var i, ln, module,
229                    items, j, subLn, item;
230
231                for (i = 0,ln = modules.length; i < ln; i++) {
232                    module = modules[i];
233
234                    items = Ext.Array.from(data[module + 's']);
235
236                    for (j = 0,subLn = items.length; j < subLn; j++) {
237                        item = items[j];
238
239                        prefix = Ext.Loader.getPrefix(item);
240
241                        if (prefix === '' || prefix === item) {
242                            requires.push(namespace + '.' + module + '.' + item);
243                        }
244                        else {
245                            requires.push(item);
246                        }
247                    }
248                }
249
250                Ext.require(requires, Ext.Function.pass(onBeforeClassCreated, arguments, this));
251            };
252        }
253    },
254
255    /**
256     * Creates new Controller.
257     * @param {Object} config (optional) Config object.
258     */
259    constructor: function(config) {
260        this.mixins.observable.constructor.call(this, config);
261
262        Ext.apply(this, config || {});
263        this.createGetters('model', this.models);
264        this.createGetters('store', this.stores);
265        this.createGetters('view', this.views);
266
267        if (this.refs) {
268            this.ref(this.refs);
269        }
270    },
271
272    /**
273     * A template method that is called when your application boots. It is called before the
274     * {@link Ext.app.Application Application}'s launch function is executed so gives a hook point to run any code before
275     * your Viewport is created.
276     *
277     * @param {Ext.app.Application} application
278     * @template
279     */
280    init: function(application) {},
281
282    /**
283     * A template method like {@link #init}, but called after the viewport is created.
284     * This is called after the {@link Ext.app.Application#launch launch} method of Application is executed.
285     *
286     * @param {Ext.app.Application} application
287     * @template
288     */
289    onLaunch: function(application) {},
290
291    createGetters: function(type, refs) {
292        type = Ext.String.capitalize(type);
293
294        var i      = 0,
295            length = (refs) ? refs.length : 0,
296            fn, ref, parts, x, numparts;
297
298        for (; i < length; i++) {
299            fn    = 'get';
300            ref   = refs[i];
301            parts = ref.split('.');
302            numParts = parts.length;
303
304            // Handle namespaced class names. E.g. feed.Add becomes getFeedAddView etc.
305            for (x = 0 ; x < numParts; x++) {
306                fn += Ext.String.capitalize(parts[x]);
307            }
308
309            fn += type;
310
311            if (!this[fn]) {
312                this[fn] = Ext.Function.pass(this['get' + type], [ref], this);
313            }
314            // Execute it right away
315            this[fn](ref);
316        }
317    },
318
319    ref: function(refs) {
320        refs = Ext.Array.from(refs);
321        
322        var me = this,
323            i = 0,
324            length = refs.length,
325            info, ref, fn;
326
327        for (; i < length; i++) {
328            info = refs[i];
329            ref  = info.ref;
330            fn   = 'get' + Ext.String.capitalize(ref);
331
332            if (!me[fn]) {
333                me[fn] = Ext.Function.pass(me.getRef, [ref, info], me);
334            }
335            me.references = me.references || [];
336            me.references.push(ref.toLowerCase());
337        }
338    },
339
340    addRef: function(ref) {
341        return this.ref([ref]);
342    },
343
344    getRef: function(ref, info, config) {
345        this.refCache = this.refCache || {};
346        info = info || {};
347        config = config || {};
348
349        Ext.apply(info, config);
350
351        if (info.forceCreate) {
352            return Ext.ComponentManager.create(info, 'component');
353        }
354
355        var me = this,
356            cached = me.refCache[ref];
357
358        if (!cached) {
359            me.refCache[ref] = cached = Ext.ComponentQuery.query(info.selector)[0];
360            if (!cached && info.autoCreate) {
361                me.refCache[ref] = cached = Ext.ComponentManager.create(info, 'component');
362            }
363            if (cached) {
364                cached.on('beforedestroy', function() {
365                    me.refCache[ref] = null;
366                });
367            }
368        }
369
370        return cached;
371    },
372
373    hasRef: function(ref) {
374        return this.references && this.references.indexOf(ref.toLowerCase()) !== -1;
375    },
376
377    /**
378     * Adds listeners to components selected via {@link Ext.ComponentQuery}. Accepts an
379     * object containing component paths mapped to a hash of listener functions.
380     *
381     * In the following example the `updateUser` function is mapped to to the `click`
382     * event on a button component, which is a child of the `useredit` component.
383     *
384     *     Ext.define('AM.controller.Users', {
385     *         init: function() {
386     *             this.control({
387     *                 'useredit button[action=save]': {
388     *                     click: this.updateUser
389     *                 }
390     *             });
391     *         },
392     *
393     *         updateUser: function(button) {
394     *             console.log('clicked the Save button');
395     *         }
396     *     });
397     *
398     * See {@link Ext.ComponentQuery} for more information on component selectors.
399     *
400     * @param {String/Object} selectors If a String, the second argument is used as the
401     * listeners, otherwise an object of selectors -> listeners is assumed
402     * @param {Object} listeners
403     */
404    control: function(selectors, listeners) {
405        this.application.control(selectors, listeners, this);
406    },
407
408    /**
409     * Returns instance of a {@link Ext.app.Controller controller} with the given name.
410     * When controller doesn't exist yet, it's created.
411     * @param {String} name
412     * @return {Ext.app.Controller} a controller instance.
413     */
414    getController: function(name) {
415        return this.application.getController(name);
416    },
417
418    /**
419     * Returns instance of a {@link Ext.data.Store Store} with the given name.
420     * When store doesn't exist yet, it's created.
421     * @param {String} name
422     * @return {Ext.data.Store} a store instance.
423     */
424    getStore: function(name) {
425        return this.application.getStore(name);
426    },
427
428    /**
429     * Returns a {@link Ext.data.Model Model} class with the given name.
430     * A shorthand for using {@link Ext.ModelManager#getModel}.
431     * @param {String} name
432     * @return {Ext.data.Model} a model class.
433     */
434    getModel: function(model) {
435        return this.application.getModel(model);
436    },
437
438    /**
439     * Returns a View class with the given name.  To create an instance of the view,
440     * you can use it like it's used by Application to create the Viewport:
441     *
442     *     this.getView('Viewport').create();
443     *
444     * @param {String} name
445     * @return {Ext.Base} a view class.
446     */
447    getView: function(view) {
448        return this.application.getView(view);
449    }
450});