Monday, June 30, 2014

This is a cross-post from the Rally Engineering Blog.

Rally publishes a public javascript AppSDK to give customers the ability to easily access their user story data within custom applications. In order to make the SDK components more reusable in different contexts and provide easier application development, there must be a clear separation between view code and model code within the SDK.

ExtJs with MVC

First off, "MVC" in this post refers to unopinionated, generic MVC. View == UI, Model == application logic, Controller == glue between View and Model. "Component" refers to a class that extends Ext.Component.

There are two popular ways to implement MVC with ExtJs applications. Sencha provides an MVC implementation distributed with ExtJs and DeftJs is a third-party library. I have experimented with both of these MVC implementations, and found them lacking.

Sencha's built-in MVC implementation requires a specific directory structure and configuration with Ext.application. These restrictions make it difficult to adapt the pattern to existing code bases.

Sencha's controllers also encourage code that listens for events fired from a Container's child Components. This is a code smell. Controller code should never "know" about child Components, because refactorring the UI structure will break the controller. Replacing a text input with a slider shouldn't force you to change controller code.

DeftJs is easier to adapt to existing code, but there are some odd object lifecycle issues that make it difficult to work with. Components must mix in Deft's Controllable class and DeftJs controllers must be configured when a Component is defined, rather than when it is instantiated. If you find the need to re-use a Component with two different controllers, you will need to create two separate classes, each defined with a different DeftJS controller.

A Deft controller's init method is not called until after the Component has been rendered, which makes things rather challenging if the controller needs to hook into any pre-render events such as staterestore or render.

Plugins as Controllers

I've found that standard Ext Plugins are a great alternative to Sencha and DeftJs controllers. Plugins fit nicely into the Component life cycle. They can be configured at instantiation time, so reusing the same UI with different behavior is trivial.

Multiple Plugins can be configured for a single Component. This may seem like an odd property for a controller, but it's actually quite nice to split code into multiple controllers for Components that have many different behaviors.

A Plugin's init method is called during the Component's constructor call, so it can respond to any Component events fired during or after the Component's initComponent call. A Plugin's destroy method is called during the Component's destroy call, which allows for easy cleanup of listeners and models orchestrated by the Plugin.

So how does it Work?

Wire up controller Plugins just like any other Plugin.

    /**
     * Controller Plugin
     */
    Ext.define('MyViewController', {
        extend: 'Ext.AbstractPlugin',
        alias: 'plugin.myviewcontroller',

        /**
         * @cfg {Ext.data.Store}
         */
        store: null,

        init: function(cmp) {
            // add listeners to respond to UI events
            cmp.on('save', this.onSave);
        },

        onSave: function(cmp, data) {
            // respond to UI events by calling public view and model methods
            cmp.setLoading(true);
            this.store.add(new this.store.model(data))
            this.store.sync({
                success: function() {
                    cmp.setLoading(false);
                    cmp.showNotification('Record Saved');
                }
            });
        }   
    });
    /**
     * View Component
     */
    Ext.define('MyView', {
        extend: 'Ext.Component',
        alias: 'widget.myview',

        items: [{
            xtype: 'button',
            label: 'Save'
        }],

        initComponent: function() {
            this.callParent(arguments);

            this.addEvents(['save']);

            // button click is abstracted into semantic event to avoid
            // controller having to know UI implementation details
            this.down('button').on('click', function() {
                this.fireEvent('save', {...});
            });
        },

        showNotification: ...
    });
    // instantiate view with controller configuration
    var cmp = container.add('myview', {
        plugins: [{
            ptype: 'myviewcontroller',
            store: myStore
        }]
    });

Components architected with a plugin controller like the example above become easier to maintain and easier to consume. This method of adding controllers to components is relatively painless to adapt to existing code. We are actively refactoring legacy code within Rally's SDK to use this architecture.

Wednesday, January 16, 2013

Customizing ExtJs Grid Components

Many ExtJs apps will eventually make use of the Ext.grid.Panel component. The grid component provides a quick and convenient method for showing large amounts of information. In my experience developing against the grid component goes something like this:
  • This grid component is great, it enables crud for my models out of the box with no coding effort!
  • We need to style this thing to not look like Excel 98.
    • Hmmm... styling is pretty tricky since everything is stuck in a table.
  • We need an extra feature, I bet it's easy to add to the grid with a plugin.
    • Hmmm... This extra feature conflicts with some of the grid's default behavior.
    • I guess we'll have to override some private methods to change the default behavior.
  • We need to upgrade Ext
    • Those custom styles are all overly dependent on the table layout, and they all broke...
    • Those overridden private methods have changed, and they all broke...
  • Ahhh, the grid sucks!
So how should developers handle customizing the grid? It turns out that Ext.grid.Panel uses an Ext.view.View component behind the scenes. Ext.view.View handles grabbing data from a store and rendering it to the dom, and keeping the two in sync. However, unlike the Ext.grid.Panel, it makes no assumptions about layout or behavior. Ext.view.View turns out to be one of the most useful components in Ext, because it provides the basic foundation a developer needs, but leaves all custom behavior up to the developer.

I suggest that any plans to create a heavily customized Ext.grid.Panel component should start with a  Ext.view.View instead. Even if you need to redevelop features present in Ext.grid.Panel, the flexibility to define your own layout and behavior will end up saving tons of time in the long run. 

Tuesday, October 30, 2012

MVC for client-side Javascript with DeftJS


This post is a cross post from the Rally Engineering Bloghttp://www.rallydev.com/community/engineering/mvc-client-side-javascript-deftjs

If you've worked with Javascript for any significant amount of time, you've likely run across tightly coupled UI/logic code that is difficult to test, debug, reuse, and modify. In Javascript land, this problem most often manifests itself as AJAX calls embedded inside of GUI components. While this pattern may work OK for small projects or projects using server-side generated html augmented with 'progressive enhancement' Javascript, it quickly turns into spaghetti code for large Javascript client applications. Our codebase usually does a good job of separating concerns, but occasionally we have logic sneaking into our UI code. Below is an example from a UI component that adds a new business artifact.


    Ext.define('Rally.ui.AddNew', {
        requires: ['Rally.ui.Button'],
        extend: 'Ext.Container',

       ...

        _onAddClicked: function() {
            var record = Ext.create(this.models[this._getSelectedRecordType()]),
                params = {fetch: true};

            record.set('Name', this.down('#name').getValue());
            if (this.fireEvent('beforecreate', this, record, params) !== false) {
                Rally.ui.flair.FlairManager.showStatusFlair({
                    message: 'Creating ' + record.self.displayName + '...'
                });

                record.save({
                    requester: this,
                    callback: this._onAddOperationCompleted,
                    scope: this,
                    params: params
                });
            }
        },


This component responds to a button click by creating an Ext record object and saving it by performing an AJAX call to Rally's REST API. This anti-pattern makes code reuse and testing very difficult. I can't reuse the UI with different logic, and I can't reuse the logic with a different UI. Testing the UI requires mocking out the business logic and AJAX calls, and testing the business logic and AJAX calls requires mocking out the UI. This component is clearly a candidate for being refactored using MVC to separate the logic and UI into reusable and testable units.

Enter DeftJs. DeftJs is an MVC library written specifically to work with Sencha's Ext class system. The small library makes it easy to refactor existing Ext components by adding three new features to the Ext environment:

  • Dependency injection (similar to Spring, allows dependencies to be instantiated and injected when required)

  • View Controllers (the bridge between UI components and model logic)

  • Promises/Deferreds (makes it easier to work with maybe asynchronous operations)


  • Each of Deft's features is described in detail on their GitHub page, so I will focus on how to use the features together to architect an application. The diagram below represents an architecture that keeps separation of concerns clear, while utilizing Ext's core strengths, and being backwards compatible with existing components that are not MVC.

    DeftJs Separation of Concerns

    So how does this architecture affect the code of our AddNew component example? The controller is attached to the component by including an extra mixin and property in the component's definition, and all of the business logic is removed from the component:


        Ext.define('Rally.ui.AddNew', {
            requires: ['Rally.ui.Button'],
            extend: 'Ext.Container',
            mixins: [ 'Deft.mixin.Controllable' ], // enable controller
            controller: 'Rally.deft.controller.AddNewController', // controller class to attach to view

           ... ui logic only ...


    The controller class (in Coffeescript) wires the component to business logic encapsulated in Action objects:


    Ext.define 'Rally.deft.controller.AddNewController',
      extend: 'Deft.mvc.ViewController'
      mixins: ['Deft.mixin.Injectable'] # enable dependency injection
      inject: ['createRecordAction'] # inject createRecordAction attribute

      control:
        view:
          # register handlers for view events here
          submit: '_onSubmit' .

      # this method called when the view component fires a 'submit' event
      _onSubmit: (cmp, model, recordName) ->
        view = @getView() # Deft automatically creates an accessor to the view
        view.setLoading true

        # injected properties (by default) are assigned to an instance.
        # variable with the same name as the injected property
        # invoke the injected action's method to create a new record
        promise = @createRecordAction.createRecord
          model: model
          data:
            Name: recordName

        # a Promise object represents an operation that may or may not be asynchronous
        promise.then
          success: =>
            # record creation was successful, reset view
            view.reset()
        promise.always =>
          # remove loading mask and refocus view, whether the operation was successful or not
          view.setLoading false
          view.focus()


    Notice that there are no AJAX calls in either the view or the controller. The AJAX logic lives in an Action class. Each Action returns a Deft.promise.Promise object, so that callers don't have to worry about whether or not the operation is asynchronous. Promise objects can also be chained or grouped together, which makes it easy to compose complex Actions from multiple simple Actions. The action code is listed below.


    Ext.define 'Rally.deft.action.CreateRecord',
      mixins: ['Deft.mixin.Injectable'] # enable dependency injection
      inject: ['messageBus'] # inject messageBus property

      createRecord: (options) ->
        # the 'deferred' is the private part of the deferred/promise pair
        # the deferred object should not be exposed to callers
        deferred = Ext.create 'Deft.Deferred'

        record = Ext.create options.model, options.data

        # this Ext call initiates an AJAX operation
        record.save
          callback: (record, operation) =>;
            @_onRecordSave(record, operation, options, deferred)

        # return a promise object to the caller
        deferred.getPromise()

      _onRecordSave: (record, operation, options, deferred) ->
        if operation.success
          # call a method on an injected property.
          # listeners can subscribe to the messageBus and be
          # notified when an object is created
          @messageBus.publish Rally.Message.objectCreate, record, this

          # mark the deferred/promise pair as successful
          deferred.resolve record
        else
          # mark the deferred/promise pair as failed
          deferred.reject operation


    Now that we have our logic code separated out, testing becomes much easier. The unit test below tests the functionality of a composed Action. All dependencies and AJAX calls are mocked out, so that the test is specific to the functionality of the class being tested. The syntax of the test assumes using the Jasmine testing framework and sinon.js mocking library.


    describe 'CreateRecordOrOpenEditor', ->

      beforeEach ->
        # Reset injectors to make sure we have a clean slate before running test
        Deft.Injector.reset()

        # Mock dependencies needed for all tests.
        Deft.Injector.configure
          createRecordAction: value: {}
          openCreateEditorAction: value: {}
          messageBus: value:
            publish: @stub

      describe 'when createRecordAction fails with a validation error', ->

        beforeEach ->
          # mock failed AJAX operation
          Deft.Injector.configure
            createRecordAction:
              createRecord: ->
                deferred = Ext.create 'Deft.Deferred'
                deferred.reject # immediately reject promise to simulate failed AJAX
              deferred.getPromise()
            openCreateEditorAction:
              openCreateEditor: @stub

        it 'should open editor', ->
          createRecordOrOpenEditorAction = Ext.create 'Rally.deft.action.CreateRecordOrOpenEditor'

          createRecordOrOpenEditorAction.createRecordOrOpenEditor({})

          sinon.assert.calledOnce createRecordOrOpenEditorAction.openCreateEditorAction.openCreateEditor

        it 'should publish displayNotification message', ->
          createRecordOrOpenEditorAction = Ext.create 'Rally.deft.action.CreateRecordOrOpenEditor'

          createRecordOrOpenEditorAction.createRecordOrOpenEditor({})

          sinon.assert.calledOnce createRecordOrOpenEditorAction.messageBus.publish
          sinon.assert.calledWith createRecordOrOpenEditorAction.messageBus.publish, Rally.Message.displayNotification


    This experiment with DeftJs was successful in that we were able to refactor this component to be more maintainable, reusable, and testable, without requiring rewrites to other parts of the application. We're hoping to implement this architecture in our production code base to clean up some of the areas where concerns bleed together.

    Thursday, May 17, 2012

    Rally Github Integration

    Rally Software allows its engineers 6 weeks of hackathon per year. Hackathon give developers a chance to hack on whatever projects and technologies excite them. This past hackathon my teammate Jacob Burton and I used Rally's App SDK 2.0 preview and Github's REST api to create a Github integration app that runs inside of Rally. It's pretty cool because users can view their commits and diffs without ever having to leave Rally's interface. Checkout the app here:




    Monday, April 23, 2012

    Over this past weekend Rene Saarsoo helped me integrate some code changes into JSDuck, a javascript documentation generator. The changes I added allow users to discover and run all of the inline example code for javascript Class documentation, and will report back any failures. The extension is activated by including the flag "--doctests" when executing jsduck from the command line. The code is available from the doctests branch.

    Thursday, April 5, 2012

    This is a cross post from: Rally Engineering Blog


    Sometimes I run across a task where I need to attach event listeners to many different dom elements of the same type. I am usually tempted to do something similar to the code below.


    Ext.each(Ext.query('a', parentNode), function(el) {
        el.on('click', doSomethingAwesome);
    });
    

    This is ExtJs's way to loop through each dom element matching the passed css selector and attach an event listener to it. I'm lazy and it's less code than the correct implementation, which would be to add a listener to the parent element and filter for the correct target element inside the handler function. The disadvantages to the lazy method are that multiple listeners are created, all of which have some amount of overhead attached, and that listeners need to be removed and/or reattached whenever the innerHTML of the parent element changes.


    An ExtJs specific example of when the 'lazy' method doesn't work is when event listeners need to be attached to elements created by a renderer inside of a GridPanel cell. The GridPanel does not expose any row or cell render events, so there is no reliable way to add event listeners to dom elements located inside cells.


    Fortunately ExtJs's Element.on method has a helpful 'delegate' option that does all of this for you automatically. Use ExtJs's Element.on method to attach a listener to the parent dom element and specify a css selector for the 'delegate' option to filter out events whose target node does not match the passed css selector.


    var parentEl = Ext.fly(parentNode);
    parentEl.on('click', function(event, target, options) {
        console.log('the "target" argument is the anchor element that is a child of parentNode');
    }, this, {
        delegate: 'a'
    });
    

    Saturday, December 10, 2011

    AmFast 0.5.3 Released

    AmFast version 0.5.3 has been released. This release contains several important bug fixes. The code can be downloaded from PyPi or checked out from SVN.