Simplifying view code in large Ext.js and Sencha apps

Do you ever feel like your view code in Ext.js is getting out of control? Do you end up copying and pasting button configs across multiple views?

Imagine if you could control all your component config in fewer places, make it easier to share config, test components and update them.

I’d like to introduce the factory pattern as a simple, but effective, solution to this.

View config explosion – A little background

When I built my first Ext.JS application I found myself focussing on what screens I needed to implement, writing classes for each view and then wiring them together. This seemed natural!

Then our design team decided to change 90% of the toolbars in the application.

“Well I’ll add a custom class to the toolbar and apply it to all that need it”.

Soon I was creating custom classes for every component and throwing them throughout all my views.

I started looking at the explosion of JSON config and began thinking “I know no longer know what bits are relevant to what in the actual view”

A few years later a colleague showed me his simple yet effective way of dealing with this – the Factory Pattern – thanks Ian!

The Factory Pattern

For those not familiar with the factory pattern, the idea is that you have a class whose sole purpose is to produce one thing. Think of a car factory – it’s purpose is to produce cars, it might produce variations in the colour or engine, but at the end of the day it will roll out cars.

A factory in programming is the same – for example an Ext.button.Button factory class’s sole purpose would be to produce buttons – maybe the buttons have different colours or text, but it just produces buttons.

Refocusing our application design

In order to benefit from the factory pattern I had to change my perspective – no longer did I just focus on what views my application needed, but I had to look hard at what common components each view used.

In fact the designer on the project created a PDF which showed all the components broken out from their views and scattered across a giant canvas. This made it much easier to see what was being used and the variations on it.

If you have a ux or design team I’d recommend getting them to create an image with just the components in it.

Creating our first factory

So now with an idea of the kinds of components you need, let’s create a Button factory – to keep things simple my hypothetical app will have only 2 types of button:

  1. a green “confirm” style button:
  2. a red “cancel” style button:

Styles for the colours will be driven by CSS, so they just need CSS classes to distinguish their type in the DOM:

Ext.define("MyApp.view.factories.ButtonFactory", {
    makeOkStyleButton: function(text, clickHandler) {
      return new Ext.button.Button({
        cls: "myapp-ok",
        handler: clickHandler
      });
    },

    makeCancelStyleButton: function(text, clickHandler) {
      return new Ext.button.Button({
        cls: "myapp-cancel",
        handler: clickHandler
      });
    }
  }
);

Hopefully this is pretty straight forward to understand. One question came up while writing the above:

Should the factory create new instances of the component or return the JSON config?

I’ve thought this through more and reasoned that if I returned a new instance it should improve rendering performance – this is because if you use an xtype Sencha has to look up the type and perform an Ext.create to instantiate it. From Sencha’s own developers they recognise this is slow and removed all traces of Ext.create from their own code.

Of course if you did want to get the config you could have another version of both methods which returns just the config, like:

Ext.define("MyApp.view.factories.ButtonFactory", {
    makeOkStyleButtonConfig: function(text, clickHandler) {
       return {
        xtype: "button",
        cls: "myapp-ok",
        handler: clickHandler
      };
    },

    makeOkStyleButton: function(text, clickHandler) {
      return new Ext.button.Button(
          this.makeOkStyleButtonConfig(text, clickHandler)
      );
    },
...

Using it in a view

To use this in a view it’s simply a matter of injecting an instance of the factory and invoking it – for example:

Ext.define("MyApp.view.ConfirmWindow", {
    extend: "Ext.window.Window",
    requires: [
        "MyApp.view.factories.ButtonFactory"
    ],
    ...
    initComponent: function() {
        if (!Ext.isDefined(this.buttonFactory)) {
            throw 'You must inject a buttonFactory';
        }
        this.btns = [
            this.btnFactory.makeCancelStyleButton("Cancel", this.handleCancel),
            this.btnFactory.makeOkStyleButton("OK", this.handleOk)
        ]
    },
    handleCancel: function() {
       ...
    },
    handleOk: function() {
       ...
    }
});

The nice thing here is that we’ve made a relatively isolated set of components that we can easily test.

Testing Factories in Jasmine

Here’s a simple Jasmine spec to test our ButtonFactory:

describe("Testing ButtonFactory", function() {

   beforeEach(function() {
       Ext.syncRequire("MyApp.view.factories.ButtonFactory");
   });

   it("Is loaded correctly", function() {
       // simple test to make sure it's loaded
       expect(MyApp.view.factories.ButtonFactory).not.toBe(undefined);
   });

   it("creates an ok button with my title and handler", function() {
       var factory = new MyApp.view.factories.ButtonFactory(),
           title = "Test Title",
           handler = function() {},
           okBtn = factory.makeOkStyleButton(title, handler);

        expect(okBtn.getText()).toBe(title);
        expect(okBtn.handler).toBe(handler);
        expect(okBtn.hasCls("myapp-ok")).toBe(true);
   });

   // repeat above for other functions if needed

});

That’s it – not very exciting, but it’s a start. Where this begins becoming really useful is when you have factories that produce more complex UI components. For example, a factory that produces custom pop-ups or flyovers can now easily be developed and tested in isolation and then wired into your views.

Leave a Reply