Upgrade to ExtJS 4.0.1 - Released 05/18/2011
[extjs.git] / src / app / Controller.js
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 THIS GUIDE for a full explanation. 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 <a href="../guide/application_architecture">
130  * application architecture guide</a>. Also see the {@link Ext.app.Application} documentation.
131  * 
132  * @docauthor Ed Spencer
133  * @constructor
134  */  
135 Ext.define('Ext.app.Controller', {
136     /**
137      * @cfg {String} id The id of this controller. You can use this id when dispatching.
138      */
139
140     mixins: {
141         observable: 'Ext.util.Observable'
142     },
143
144     onClassExtended: function(cls, data) {
145         var className = Ext.getClassName(cls),
146             match = className.match(/^(.*)\.controller\./);
147
148         if (match !== null) {
149             var namespace = Ext.Loader.getPrefix(className) || match[1],
150                 onBeforeClassCreated = data.onBeforeClassCreated,
151                 requires = [],
152                 modules = ['model', 'view', 'store'],
153                 prefix;
154
155             data.onBeforeClassCreated = function(cls, data) {
156                 var i, ln, module,
157                     items, j, subLn, item;
158
159                 for (i = 0,ln = modules.length; i < ln; i++) {
160                     module = modules[i];
161
162                     items = Ext.Array.from(data[module + 's']);
163
164                     for (j = 0,subLn = items.length; j < subLn; j++) {
165                         item = items[j];
166
167                         prefix = Ext.Loader.getPrefix(item);
168
169                         if (prefix === '' || prefix === item) {
170                             requires.push(namespace + '.' + module + '.' + item);
171                         }
172                         else {
173                             requires.push(item);
174                         }
175                     }
176                 }
177
178                 Ext.require(requires, Ext.Function.pass(onBeforeClassCreated, arguments, this));
179             };
180         }
181     },
182
183     constructor: function(config) {
184         this.mixins.observable.constructor.call(this, config);
185
186         Ext.apply(this, config || {});
187
188         this.createGetters('model', this.models);
189         this.createGetters('store', this.stores);
190         this.createGetters('view', this.views);
191
192         if (this.refs) {
193             this.ref(this.refs);
194         }
195     },
196
197     // Template method
198     init: function(application) {},
199     // Template method
200     onLaunch: function(application) {},
201
202     createGetters: function(type, refs) {
203         type = Ext.String.capitalize(type);
204         Ext.Array.each(refs, function(ref) {
205             var fn = 'get',
206                 parts = ref.split('.');
207
208             // Handle namespaced class names. E.g. feed.Add becomes getFeedAddView etc.
209             Ext.Array.each(parts, function(part) {
210                 fn += Ext.String.capitalize(part);
211             });
212             fn += type;
213
214             if (!this[fn]) {
215                 this[fn] = Ext.Function.pass(this['get' + type], [ref], this);
216             }
217             // Execute it right away
218             this[fn](ref);
219         },
220         this);
221     },
222
223     ref: function(refs) {
224         var me = this;
225         refs = Ext.Array.from(refs);
226         Ext.Array.each(refs, function(info) {
227             var ref = info.ref,
228                 fn = 'get' + Ext.String.capitalize(ref);
229             if (!me[fn]) {
230                 me[fn] = Ext.Function.pass(me.getRef, [ref, info], me);
231             }
232         });
233     },
234
235     getRef: function(ref, info, config) {
236         this.refCache = this.refCache || {};
237         info = info || {};
238         config = config || {};
239
240         Ext.apply(info, config);
241
242         if (info.forceCreate) {
243             return Ext.ComponentManager.create(info, 'component');
244         }
245
246         var me = this,
247             selector = info.selector,
248             cached = me.refCache[ref];
249
250         if (!cached) {
251             me.refCache[ref] = cached = Ext.ComponentQuery.query(info.selector)[0];
252             if (!cached && info.autoCreate) {
253                 me.refCache[ref] = cached = Ext.ComponentManager.create(info, 'component');
254             }
255             if (cached) {
256                 cached.on('beforedestroy', function() {
257                     me.refCache[ref] = null;
258                 });
259             }
260         }
261
262         return cached;
263     },
264
265     control: function(selectors, listeners) {
266         this.application.control(selectors, listeners, this);
267     },
268
269     getController: function(name) {
270         return this.application.getController(name);
271     },
272
273     getStore: function(name) {
274         return this.application.getStore(name);
275     },
276
277     getModel: function(model) {
278         return this.application.getModel(model);
279     },
280
281     getView: function(view) {
282         return this.application.getView(view);
283     }
284 });