Sencha Ext JS 7.7 is Here – Discover What’s New and Exciting – LEARN MORE

Deft JS: Loosely Coupled MVC through Dependency Injection

May 14, 2012 105 Views
Show

Guest Blog Post: John Yanarella of Universal Mind

DeftJS

That application you just deployed? As experienced software developers, we all know it won’t be long before you’re going to need make to significant UI changes. Regardless of the amount of painstaking forethought, consensus gathering and planning backing it, no software design ever survives first contact with its users unscathed. To deliver truly effective software, we have to be prepared to adapt to an evolving understating of our users’ needs.

So… how do we architect our software, so we can rapidly implement UI changes without breaking the underlying business logic?

Model View Controller (MVC)

It starts by applying structure to our code, separating it into manageable units where each is responsible for a specific application concern. To this end, most software applications employ the Model View Controller architectural pattern. There are a wide variety of implementations of this pattern; software development pundit Martin Fowler has catalogued many of those variations here.

Typically, with an MVC architecture, the:

  • Model describes and manages application domain data behaviors and state, and responds to requests to retrieve or persist changes to that state.
  • View presents model data to the user, accepts user input, and announces high-level user gestures such as clicks or selection changes.
  • Controller mediates between the model and view, listening for user gestures to initiate actions on the Model, and instructing the View to reflect Model changes.

By separating the user interface components and layout (the View) from the logic that observes corresponding user gestures (the Controller) ands triggers business logic or changes to application domain data (the Model), your application is better positioned to adapt to inevitable changes.

Deft JS MVC

Deft JS enhances the Model View Controller (MVC) within Sencha Frameworks, where the:

  • Model is expressed using ‘faceless’ business logic components, such as Ext.data.Store and Ext.data.Model.
  • View is realized using any of the rich suite of Ext JS or Sencha Touch containers and components.
  • Controller is implemented by creating view-specific controllers that extend the Deft.mvc.ViewController class.

Deft JS View Controllers

Consider a view to be a composition of multiple components in a container. A View Controller is a lightweight controller responsible for managing the state of a specific view and its child components, listening for events dispatched by the view and its child components in response to user gestures, and delegating work to injected business models and services (such as Ext.data.Stores, Ext.data.Models, etc.).

With Deft JS, a view is typically a subclass of an Ext JS container class, populated with component items. The view is configured to use the Deft.mixin.Controllable mix-in, and annotated with the class name of the associated View Controller. For each view, you would create a corresponding View Controller class that extends Deft.mvc.ViewController. This view-specific view controller would be configured to reference relevant view components and register view controller methods to handle view component events.

The Deft JS View Controller and Controllable mix-in:

  • Provide class annotation driven association between a given view and its view controller
  • Clarify the role of controller classes – i.e. controlling a specific view
  • Support multiple independent instances of a given view, each with its own unique view controller instance
  • Reduce memory usage by automatically creating and destroying view controllers in tandem with their associated views
  • Offer concise configuration for referencing view components and registering event listeners with view controller methods
  • Integrate with the view destruction lifecycle to allow the view controller to potentially cancel removal and destruction
  • Simplify cleanup by automatically removing view and view component references and event listeners

Deft JS Dependency Injection

Most model, view and controller objects are not self-contained; they reference and delegate some of their work to other objects; these objects are referred to as dependencies. Typically, you might explicitly create instances of these dependencies or manually request them from a service locator.

To encourage loose coupling between application components, Deft JS includes a lightweight Inversion of Control (IoC) container for dependency injection. When applying the principal of Inversion of Control, you annotate your class with a list of its dependencies instead of manually creating or obtaining them. When your class is later instantiated, the IoC container is then responsible for resolving those dependencies with the correct object instances, injecting those values into your class at runtime.

With IoC, your class is no longer responsible for creating its dependencies or knowing where its dependencies are defined. Further, it is no longer bound to a specific implementation of a dependency. Provided it offers the expected API, you can configure the IoC container to inject a totally different implementation.

Consequently, you can easily test your classes in isolation by configuring the IoC container with mock versions of any dependencies. You can also create multiple variants of your application, where the IoC container in each is configured to use different implementations of shared dependencies such as Stores or Proxies; one might be configured to use mock Stores and Proxies driven by static JSON files, and another configured to use Stores and Proxies that access production services via JSONP.

The Deft JS IoC Container and Injectable mix-in:

  • Provide class annotation driven dependency injection
  • Map dependencies by user-defined identifiers
  • Resolve dependencies by class instance, factory function or value
  • Support singleton and prototype resolution of class instance and factory function dependencies
  • Offer eager and lazy instantiation of dependencies
  • Inject dependencies into Ext JS class configurations and properties before the class constructor is executed

Example

Imagine a view within a Contact Manager application containing a grid of contacts:

Ext.define( ‘ContactsApp.view.ContactGridView’,
extend: ‘Ext.container.Container’,
mixins: [ ‘Deft.mixin.Controllable’, ‘Deft.mixin.Injectable’ ],
inject: [ ‘contactStore’ ],
controller: ‘ContactsApp.controller.ContactGridViewController’,

config: {
contactStore: null
},

initComponent: function() {
this.items = [{
itemId: ‘contactsGrid’,
xtype: ‘gridpanel’,
store: this.getContactStore(),
…,
bbar: Ext.create( ‘Ext.PagingToolbar’, {
store: this.getContactStore(),

})
},

{
xtype: ‘container’,

items: [{
itemId: ‘editButton’,
xtype: ‘button’,
text: ‘Edit’
}],

}];
}
);

This view class is configured to be injectable, using the Deft.mixin.Injectable mix-in, and its dependencies are described using the ‘inject’ class annotation.

When this view is instantiated via either Ext.create() or Ext.widget(), the IoC container will resolve the ‘contactStore’ dependency and inject the associated value into the ‘contactStore’ configuration. The generated getContactStore() accessor function will return that injected value.

Additionally, this view class is configured to be controllable, using the Deft.mixin.Controllable mix-in, and its controller is specified using the ‘controller’ class annotation.

When this view is instantiated, an instance of the specified view controller class will also be created and configured with a reference to the view. When the view is destroyed, the view controller will also be destroyed.

Ext.define( ‘ContactsApp.controller.ContactGridViewController’,
extend: ‘Deft.mvc.ViewController’,
mixins: [ ‘Deft.mixin.Injectable’ ],
inject: [ ‘contactStore’ ],

config: {
contactStore: null
},

control: {
contactsGrid: {
click: ‘onContactsGridClick’
}
editButton: {
click: ‘onEditButtonClick’
}
},

destroy: function() {
if (this.hasUnsavedChanges) {
// cancel destruction
return false;
}
// allow destruction
return this.callParent( arguments );
},

onEditButtonClick: function () {
this.getEditButton.setDisabled( false );
},

onContactsGridClick: function () {
// add a ContactEditorView to the TabPanel for the selected item

},
);

This view controller extends the Deft.mvc.ViewController abstract base class, which provides a ‘control’ configuration that simplifies the creation of view component accessors and registering view component event listeners.

In this example, the grid and button are referenced implicitly by their itemId (custom selectors are also supported) and their ‘click’ events are configured to be handled by corresponding view controller methods. Two accessor functions will automatically be created: getContactsGrid() and getEditButton(). When the view is destroyed, the view controller’s destroy() method will be called, allowing it to cancel view destruction. If this method returns true, the view will be destroyed, and all references and event listeners created in the view controller using the ‘control’ configuration will automatically be removed.

The IoC container is typically configured in the main application JavaScript file, within Ext.onReady(), using the Deft.Injector.configure() method.

Ext.onReady( function () {
Deft.Injector.configure({
contactStore: ‘ContactsApp.store.ContactStore’
// contactStore: ‘ContactsApp.store.MockContactStore’
});
});

In this example, the Deft JS IoC container has been configured to fulfill all requests for ‘contactStore’ with a singleton instance of ContactsApp.store.ContactStore. The commented line shows how simple it would be to specify a mock class instead.

About Deft JS

Deft JS is an MIT-licensed open source framework that extends the Ext JS and Sencha Touch APIs to provide:

  • Loose-coupling and dependency injection via an Inversion of Control (IoC) container
  • Flexible component-oriented architecture through an alternative Model View Controller (MVC) implementation
  • Elegant asynchronous operation chaining and data processing using Promises and Deferreds

Created by a team of software architects working at the innovative digital solutions agency Universal Mind, Deft JS leverages best practices and proven patterns refined over years of delivering cutting edge solutions across a wide range of platforms and devices.

coming soon

Something Awesome Is

COMING SOON!