/ext-4.1.0_b3/src/app/Controller.js
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});