Original: Using viewcontrollers in Ext JS 5
Brief introduction
In ext JS 5, there are some exciting improvements in application architecture, such as the addition of ViewModels, MVVM, and viewcontrollers to strengthen the MVC application. Most importantly, these choices are not mutually exclusive, so you can introduce these features incrementally, or mix them together.
Review Controller
In ext JS 4, the controller is a derived class from Ext.app.Controller. These controllers use similar CSS selectors, called component queries, to find components and respond to their events. You can also use refs to select or return a component instance.
These controllers are created at application startup and will exist throughout the application lifecycle. In the controller's life cycle, the controller's attention view can be said to come and go. It may even be a controller that manages many instances.
Challenge
For large applications, this technology may face some challenges.
In this environment, views and controllers may have been developed and inherited by multiple development teams to the final application. To make sure that the controller responds only to the views they are interested in will be a difficult task. Also, for developers, you will typically want to limit the number of controller creation when the application starts. While it is possible to delay the creation of the controller by effort, they cannot be destroyed, so they remain in memory when they are no longer needed.
Viewcontrollers
Although the EXT JS 5 can be backwards compatible with these controllers, it introduces a new controller that can be designed to handle these challenges: Ext.app.ViewController. The Viewcontroller is implemented in the following ways:
- Simplifying the use of listeners and reference configuration items for views
- Use the life cycle of views to automatically manage their associated Viewcontroller
- Correlate management views based on one-to-one to reduce viewcontroller complexity
- Provides encapsulation to make nested views more reliable
- Retains the ability to select components and listen to their events for any layer above the associated view
Listeners
Configuration item listeners although not new, but in Ext JS 5 added new capabilities to it. The new Listeners feature will be explored comprehensively in the future article "declarative Listeners in Ext JS 5". For Viewcontrollers, let's take a look at two examples. The first is the most basic, using the listeners configuration item in the sub-components of a view:
Ext.define (' MyApp.view.foo.Foo ', { extend: ' Ext.panel.Panel ', xtype: ' foo ', controller: ' foo ', Items: [{ xtype: ' TextField ', fieldlabel: ' Bar ', listeners: {change : ' Onbarchange ' //No scope Given (here } }]}); Ext.define (' MyApp.view.foo.FooController ', { extend: ' Ext.app.ViewController ', alias: ' Controller.foo ', onbarchange:function (Bartextfield) { //called by ' change ' event
In the listeners of the above code, a named event handle (Onbarchange) is used, but no scope is specified. Inside the event system, the scope of the bar text field is resolved to the Viewcontroller to which it belongs.
In the past, listeners configuration items were reserved for use by the creator of the component, so how do you let the view listen to its own events, and let its base classes trigger? The solution is to use an explicit scope:
Ext.define (' MyApp.view.foo.Foo ', { extend: ' Ext.panel.Panel ', xtype: ' foo ', controller: ' foo ', Listeners: { collapse: ' Oncollapse ', scope: ' Controller ' }, items: [{... }]});
The example code above takes advantage of the new features of two ext JS 5: Named scopes and declarative snooping. The focus here is on the named scopes. Here you can use two valid scopes for naming: this and controller. When writing an MVC application, the controller is basically always used, not to mention the Viewcontroller of the view (not the viewcontroller of the view that created the view instance).
Because a view is a type of ext.component, you can define a xtype for it so that other views create the view instance and its text fields in the same way. To explore how to integrate these together, consider how to use the change view, for example:
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 the Foo view as one of its subcomponents. In addition, the collapse event is bound for the Foo view. In previous versions of Ext JS or Sencha Touch, these statements will be conflicting, while the Ext JS 5, as expected, has been resolved. The listener declared through the Foo view will only be triggered within the Viewcontroller of Foo, while the listener declared in Bar view will only be triggered within the bar's Viewcontroller.
Reference (citation)
When writing controller logic, the most annoying thing is to get the required components to complete only the operation, 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 ', ... }]}); Ext.define (' MyApp.view.foo.FooController ', { extend: ' Ext.app.ViewController ', alias: ' Controller.foo ', onadd:function () { //... get the grid and add a record ... });
How should I get the grid component now? In ext JS 4, you can use refs configuration items or some other way to find components. So these techniques require that you place some properties on the grid that you can identify and uniquely identify the grid. The old technology uses the ID configuration item (ext.getcmp) or itemid (for refs or some component query syntax) to configure the item. The advantage of using an ID is that it enables quick lookups, but it is not advisable to ensure that the identity is unique throughout the application or DOM. Using Itemid or some other query can be more flexible, but this requires performing a search to find the components you need.
The Ext JS 5 can be implemented using the new reference configuration item, just add reference to the grid 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 similar to assigning a itemid named Foogrid and executing "this.down (' #fooGrid ')". However, this implementation at the bottom is different, and the difference is great. First, the reference configuration item registers the component itself into the view it belongs to (usually identified in Viewcontroller). Second, the Lookupreference method only queries the cache to determine if a reference needs to be refreshed (referring to the container's add or remove operations). If all goes well, you can return a reference from the cache. In addition, the pseudo code is as follows:
Lookupreference: (Reference) { var cache = this.references; if (!cache) { ext.fixreferences ();//fix all references cache = this.references;//Now the cache is valid }
In other words, this does not require a search and will not cause problems because of the addition or removal of subcomponents due to the fact that the container is not being molded at once. As you can see, the advantage is that it provides efficiency.
Package (encapsulation)
Although the use of the Ext JS 4 Selector is very flexible implementation, but at the same time there are some risks. In fact, these selectors can "see" all of the component hierarchies, both powerful and prone to errors. For example, a controller can function properly when isolated, but once a different view is introduced, it can fail because its selectors might accidentally match the new view.
These issues can be managed through the following practices, but it becomes impossible to solve these problems when using listeners or references in Viewcontroller. This is because the listeners and references configuration items can only connect to the Viewcontroller they belong to. Within the view, you can use any unique reference value, because the view knows that these names are not exposed to the creator of the view.
Similarly, listeners can be reserved to the Viewcontroller it belongs to and not accidentally distributed to other controllers that use the wrong selector for event handling. While listening takes precedence over selectors, these two mechanisms can work together when a selector is required.
To achieve this, the view needs to trigger the event through the viewcontroller of the view it belongs to. In Viewcontroller there is an auxiliary method that can be used to implement this: Fireviewevent, for example:
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);
The creator of the view can use the standard form of monitoring:
Ext.define (' MyApp.view.bar.Bar ', { extend: ' Ext.panel.Panel ', xtype: ' Bar ', controller: ' Bar ', Items: [{ xtype: ' foo ', listeners: { collapse: ' Oncollapse ', addrecord: ' Onaddrecord ' } }]});
Monitoring and event domains
The Ext JS 4.2,MVC event distribution is generalized as a reference to the event domain. Event domains intercept events when they are triggered and distribute them to controllers that are controlled by a selector match. Although there are only a limited number of selectors for other domains, the component event domain has a complete component query selector.
At Ext JS 5, each viewcontroller creates an instance of the new event domain named view. The event domain allows Viewcontroller to use the standard implicit restriction of its scope for the owning view of the listen or control method. It also adds a specific selection to match the view itself.
Ext.define (' MyApp.view.foo.FooController ', { extend: ' Ext.app.ViewController ', alias: ' Controller.foo ', control: { ' # ': { //matches the view itself collapse: ' Oncollapse ' }, button: { click: ' Onanybuttonclick ' }
From the above code you can see the difference between listeners and selectors. The selector button matches any button in the view and sub-view (regardless of depth, even the great-grandchild view), that is, the selector-based processing ignores the encapsulation boundary, and the behavior of the change is consistent with the behavior of the previous Ext.app.Controller, which is a useful technique in limited cases.
Finally, the event domain follows nesting and effectively bubbles up the event according to the view hierarchy, that is, when an event fires, it is first passed to any standard listeners. It then passes to the Viewcontroller it belongs to, and then passes through its parent viewcontroller (if present) to the topmost layer. Eventually, events are passed to the standard component event domain for processing by Ext.app.Controller-derived controllers.
Life cycle
A common technique for large applications is to dynamically create the required controllers as needed. This helps reduce the load time of the application and helps to provide performance at run time by not activating the potential controller. This is not possible in previous versions, once the controller is created, it will remain active in the application and will not be destroyed and freed of resources. Again, this does not change the fact that a controller may contain any number of related views, including no views.
However, Viewcontroller is created early in the component lifecycle and binds its entire life cycle to the view. When the view is destroyed, the Viewcontroller will also be destroyed. This means that Viewcontroller does not have to be forced to manage state when there is no view or many views.
This one-to-one management means that reference tracking is simplified and is no longer easily compromised by component destruction. Viewcontroller can implement any method to process transactions within any key point in its life cycle.
beforeinit--the method can be overridden, it executes before the view calls the InitComponent method. This method is called immediately after the controller is created, and this occurs during the call to the Initconfig method within the component's constructor.
init--The method is called shortly after the view calls the InitComponent method. This is usually the time the view is initialized, when the controller is initialized,
initviewmodel--The method is called when the ViewModel of the view is created (if present).
destroy--Clean up any resources (make sure the Callparent method is called).
Summary
We believe Viewcontroller will greatly simplify your MVC application. They can also be used with ViewModels. Therefore, you can combine these methods with their respective strengths. We are excited about the upcoming release and see the improvements that have been made in your application.
Don Griffin
Don Griffin is a member of the EXT JS core team. He is an Ext JS user for 2 years before joining Sencha and have over years of software engineering experience on a Broa D range of platforms. His experience includes designing Web application Front-ends and back-ends, native GUI applications, network protocols and Device drivers. Don's passion is to the build world class products, people love to use.