📜 ⬆️ ⬇️

Using ViewController in ExtJS 5

ExtJS 5 brings several exciting architecture improvements: we added support for ViewModels, MVVMs, and ViewControllers to enhance MVC applications. Best of all, these functions are not mutually exclusive, so you can enter them step by step or use them simultaneously.


About controllers


In ExtJS 4, a controller is a class inherited from Ext.app.Controller . These controllers use CSS-like selectors (called "Component Queries") to map components and event handlers for their events. They also use the so-called refs for sampling and obtaining instances of components.

These controllers are created when the application is started and run throughout the entire life cycle. Views are created and deleted, there may be several instances, and controllers serve everyone.
')
Difficulties

For large applications, these techniques may have some difficulties.

In such cases, views and controllers can be created by different authors in different development teams, and then integrated into the final application. And to be sure that the controllers respond only to the representations intended for them is already difficult. Also, developers usually want to reduce the number of controllers created when the application is started. And having the opportunity (with some effort) to create controllers is postponed, there is no possibility to remove them, so they remain even if they are no longer needed.

Viewcontrollers


Given that Ext JS 5 is backward compatible with current controllers, it offers a new type of controller designed to solve this kind of problem: Ext.app.ViewController . The solution is achieved as follows:


Listeners

The listeners not new, but in Ext JS 5 it acquired new features. A more complete analysis of new listeners will be in the upcoming article - “Declarative Listeners in Ext JS 5”. For use in ViewControllers, we can look at a couple of examples. The first is the usual use of the listeners in the nested view component:

 Ext.define('MyApp.view.foo.Foo', { extend: 'Ext.panel.Panel', xtype: 'foo', controller: 'foo', items: [{ xtype: 'textfield', fieldLabel: 'Bar', listeners: { change: 'onBarChange' //    (scope) } }] }); Ext.define('MyApp.view.foo.FooController', { extend: 'Ext.app.ViewController', alias: 'controller.foo', onBarChange: function (barTextField) { //     'change' } }); 

In this case, the named onBarChange handler onBarChange not have a specific context ( scope ). The event system for the Bar text field independently makes the default context its own ViewController.

Historically, the listeners config has been reserved for use by the parent component, so the question arises: how can the presentation listen to its own events or perhaps even those that were caused by the base class? The answer is to use an explicit context:

 Ext.define('MyApp.view.foo.Foo', { extend: 'Ext.panel.Panel', xtype: 'foo', controller: 'foo', listeners: { collapse: 'onCollapse', scope: 'controller' }, items: [{ ... }] }); 

This example uses two new features for ExtJS 5: named contexts and declarative listeners ( named scopes and declarative listeners, - approx. Transl. ). We will focus on the named context. For named contexts, two names are valid: "this" and "controller" . When we write MVC applications, we almost always use a "controller" , which obviously will be its own ViewController (and not a ViewController of a view that is higher in the hierarchy).

Since the view is Ext.Component , we have assigned the xtype this view, which allows other views to create an instance of it just as we did with the textfield . To see how everything fits together, create a hierarchy:

 Ext.define('MyApp.view.bar.Bar', { extend: 'Ext.panel.Panel', xtype: 'bar', controller: 'bar', items: [{ xtype: 'foo', listeners: { collapse: 'onCollapse' } }] }); 

In this case, the Bar view creates an instance of Foo as one of its elements. Then it listens to the collapse event, just like the Foo view. In previous versions of Ext JS and Sencha Touch, these definitions would conflict. However, in Ext JS 5 this is solved: the listeners declared in Foo will work in ViewController'e Foo , and those declared in Bar - in ViewController'e Bar .

References

One of the widely used functions in controller logic is getting the necessary component to perform an action. Something like that:

 Ext.define('MyApp.view.foo.Foo', { extend: 'Ext.panel.Panel', xtype: 'foo', controller: 'foo', tbar: [{ xtype: 'button', text: 'Add', handler: 'onAdd' }], items: [{ xtype: 'grid', ... }] }); Ext.define('MyApp.view.foo.FooController', { extend: 'Ext.app.ViewController', alias: 'controller.foo', onAdd: function () { // ...  grid    ... } }); 

But how do we get the table (gird)? In ExtJS 4, you could use the config refs or some other way to get the component. All technicians require you to give a recognizable property to a table in order to uniquely identify it. Older technicians used the id (and Ext.getCmp ) Ext.getCmp or itemId (using refs or another selection method). The advantage of id is quick sampling, but because identifiers must be unique in the entire application and in the DOM, this is not always achievable. Using itemId and different types of queries (query) is more flexible, but you need to perform a search to find the desired component.

With the new reference config in Ext JS 5, we simply add it to the table and use the lookupReference to get it:

 Ext.define('MyApp.view.foo.Foo', { extend: 'Ext.panel.Panel', xtype: 'foo', controller: 'foo', tbar: [{ xtype: 'button', text: 'Add', handler: 'onAdd' }], items: [{ xtype: 'grid', reference: 'fooGrid' ... }] }); Ext.define('MyApp.view.foo.FooController', { extend: 'Ext.app.ViewController', alias: 'controller.foo', onAdd: function () { var grid = this.lookupReference('fooGrid'); } }); 

This is like assigning itemId = 'fooGrid' and further this.down('#fooGrid') . But "under the hood" the difference is significant. First, the reference config requires the component to register itself with the owner-view. Secondly, the lookupReference method asks the cache about the need to update (suppose, due to the addition or removal in the container). If everything is fine, it simply returns the link from the cache. In pseudocode:

 lookupReference: (reference) { var cache = this.references; if (!cache) { Ext.fixReferences(); //   cache = this.references; //    } return cache[reference]; } 

In other words, there is no search, and links corrupted by adding or removing items from the container are corrected on the fly when they are needed. As we will see below, besides efficiency, this approach provides other advantages.

Encapsulation

The use of selectors in the implementation of Ext JS 4 MVC was very flexible, but at the same time carried some risks. The fact that the selectors "saw" all events at all levels was powerful, but inclined to mistakes. For example, a controller that works 100% correctly in isolation could create errors as soon as new views appeared with undesirable matching selectors.

This could be solved by following some practices, but using listeners and links in ViewControllers, these problems are simply excluded. And all because the reference and listeners configs connect the view only with its ViewController. Nested views can use any reference values ​​within the current one, knowing that these names will not be open to the view that is higher in the hierarchy.

In this case, listeners requested from the corresponding ViewController and cannot be processed in other controllers with incorrect selectors. Considering that listeners are more preferable than selectors, these two mechanisms can work simultaneously in those places when the use of a selector-based approach is justified.

To complete this example, consider the case where a view can raise an event that will be processed by a ViewController of a higher level view. To do this, there is a helper method in fireViewEvent : fireViewEvent . For example:

 Ext.define('MyApp.view.foo.Foo', { extend: 'Ext.panel.Panel', xtype: 'foo', controller: 'foo', tbar: [{ xtype: 'button', text: 'Add', handler: 'onAdd' }], items: [{ xtype: 'grid', reference: 'fooGrid' ... }] }); Ext.define('MyApp.view.foo.FooController', { extend: 'Ext.app.ViewController', alias: 'controller.foo', onAdd: function () { var record = new MyApp.model.Thing(); var grid = this.lookupReference('fooGrid'); grid.store.add(record); this.fireViewEvent('addrecord', this, record); } }); 

This makes it possible to use a standard listener in a view that is higher in the hierarchy:

 Ext.define('MyApp.view.bar.Bar', { extend: 'Ext.panel.Panel', xtype: 'bar', controller: 'bar', items: [{ xtype: 'foo', listeners: { collapse: 'onCollapse', addrecord: 'onAddRecord' } }] }); 

From the translator: it sounds confusing, but in fact fireViewEvent allows fireViewEvent to raise an event for a view inside a ViewController. That is, if in this case there were no ViewControllers, it would be equivalent to calling the usual fireEvent in the “Foo” view code.

Listeners and Event Domains (Listeners and Event Domains)

In Ext JS 4.2, event domains have been entered into MVC Event Manager. These domains intercept events as soon as they are invoked and transfer them to controllers that process them by coincidence of selectors. The event domain component has full support for component selectors, while others are limited.

In Ext JS 5, each ViewController creates an instance of a new event domain type called view . This event domain allows ViewControllers to use standard methods of listen and control , while implicitly limiting their scope to their own views. He also adds a new special selector to match his own view:

 Ext.define('MyApp.view.foo.FooController', { extend: 'Ext.app.ViewController', alias: 'controller.foo', control: { '#': { //     collapse: 'onCollapse' }, button: { click: 'onAnyButtonClick' } } }); 

The key difference between listeners and selectors can be seen in the following example. The selector button coincides with any button in this view or in any nested, it does not matter, even in the sub-sub-nested. In other words, handlers based on selectors do not consider inheritance boundaries. This behavior is the same as the previous Ext.app.Controller behavior and in some situations it can be a useful technique.

Finally, these domains take into account nesting and effectively send an event up through the hierarchy of representations ( bubble an event up, - approx. Transl. ). That is, when an event is triggered, at first it is transmitted to standard listeners. Then it is passed to the owning ViewController, and then up the hierarchy to the parent ViewController (if there is one). Ultimately, the event is passed to the standard event domain component for processing in Ext.app.Controller controllers.

Life cycle

Standard practice with large applications is to dynamically create controllers as needed. This can help reduce the time to load an application and improve its performance without affecting other controllers. The limitation of this approach in previous versions was that, when created, these controllers remain working throughout the life of the application. It was impossible to destroy them to free up resources. Also, nothing changed in the sense that the controllers could serve as several instances of representations, and none.

Therefore, in the life cycle of a view, the ViewController is created immediately and it is tied to this view throughout its life. When a view is deleted, the ViewController is also. This means that you no longer need to work from the ViewController when there are no or few views (apparently, it is compared with the classical controllers; note perev. ).

A one-to-one relationship makes it easier to track links and makes it impossible for any components to leak. The following key events are raised in the ViewController life cycle:



Conclusion


We think that ViewController'y perfectly upgrade your applications. They also work well with ViewModels, so you can combine the strengths of both approaches. We welcome the upcoming release and will also be happy to improve your applications.

PS

Previous Post: Ext JS 5: MVC, MVVM, etc

Source: https://habr.com/ru/post/224645/


All Articles