ExtJS Textmate bundle

** Update 2: I’ve recently cleaned up the bundle, removing stale snippets. It’s now located at https://github.com/edspencer/Sencha.tmbundle

** Update: Added extra instructions when downloading the bundle instead of git cloning it. Thanks to TopKatz for his help**

I develop on both OSX and Windows machines, and my editors of choice are Textmate and the excellent Windows clone E. One of the great things about Textmate is its bundle support, which allows you to create reusable code snippets (among other things).

I’ve got a good collection of these built up so thought I’d make them available on Github. You can install it like this:

Mac OSX:

cd ~/Library/Application Support/TextMate/Bundles
git clone git://github.com/edspencer/Sencha.tmbundle.git

Windows:

cd C:Documents and Settings{YOUR USERNAME}Application DataeBundles
git clone git://github.com/edspencer/Sencha.tmbundle.git

If you don’t have git installed you can simply download the bundle as a zip file, and extract it into the directory as above. You need to rename the extracted directory to something like extjs.tmbundle or it won’t show up. If you do go the git route you can of course cd into that git directory at any point and use git pull to update to the latest bundle version.

I’ll give one example of the usefulness of snippets like these; here’s the Ext.extend snippet from the bundle:

/**
 * @class ${1:ClassName}
 * @extends ${2:extendsClass}
 * ${5:Description}
 */
${1:ClassName} = function(config) {
  var config = config || {};
 
  Ext.applyIf(config, {
    $0
  });
 
  ${1:ClassName}.superclass.constructor.call(this, config);
};
Ext.extend(${1:ClassName}, ${2:extendsClass});

${3:Ext.reg('${4:xtype}', ${1:ClassName});}

To use this you can just type ‘extend’ into a JS file in TextMate/E and press tab. The snippet takes you through a few editable areas such as the name of your new class, the name of the class you’re extending, xtype definition and description, then dumps the cursor inside the Ext.applyIf block. The actual characters typed are these:

extend [tab] MyWindow [tab] Ext.Window [tab] [tab] mywindow [tab] Special window class [tab]

Which produces this:

/**
 * @class MyWindow
 * @extends Ext.Window
 * Special window class
 */
MyWindow = function(config) {
  var config = config || {};
 
  Ext.applyIf(config, {
    
  });
 
  MyWindow.superclass.constructor.call(this, config);
};
Ext.extend(MyWindow, Ext.Window);

Ext.reg('mywindow', MyWindow);

Hopefully it’s obvious how much time things like this can save when generating repetitive, boilerplate code. The extend snippet is one of the larger ones but even the small ones are very useful (pressing c then tab is much nicer than typing console.log(”); each time).

Any suggestions/contributions are welcome. Thanks go to rdougan for his contributions and organisation also.

There is also another ExtJS textmate bundle available at http://hakore.com/extjs.tmbundle/, written by krzak from the Ext forums.

Using Ext.History

Ext.History is a small class that was released with ExtJS 2.2, making it easy to use the browser’s back and forward buttons without breaking your AJAX-only pages.

This can be really useful for any ExtJS application with more than one view, for example a simple app with a grid of Products, which can be double-clicked to reveal an edit form. Ext.History allows the user to click the back button to go back to the grid if they’re on the form, and even forward again from the grid. It does this by appending a token to the end of the url:

http://myurl.com/ (default url for the app)
http://myurl.com/#products (shows the products grid)
http://myurl.com/#products/edit/1 (shows the edit form for product 1)

This is useful, so let’s look at how to set it up. Ext.History requires that a form field and an iframe are present in the document, such as this:

<form id="history-form" class="x-hidden" action="#">
  <div>
    <input id="x-history-field" type="hidden" />
    
  </div>
</form>

The div is just there to make the markup valid. Ext.History uses the iframe to make IE play nice. Generally I don’t like to make any assumptions about what is in the DOM structure so I use Ext to generate these elements:

/**
* Creates the necessary DOM elements required for Ext.History to manage state
* Sets up a listener on Ext.History's change event to fire this.handleHistoryChange
*/
initialiseHistory: function() {
  this.historyForm = Ext.getBody().createChild({
    tag:    'form',
    action: '#',
    cls:    'x-hidden',
    id:     'history-form',
    children: [
      {
        tag: 'div',
        children: [
          {
            tag:  'input',
            id:   Ext.History.fieldId,
            type: 'hidden'
          },
          {
            tag:  'iframe',
            id:   Ext.History.iframeId
          }
        ]
      }
    ]
  });

  //initialize History management
  Ext.History.init();
  Ext.History.on('change', this.handleHistoryChange, this);
}

Ext.History.fieldId and Ext.History.iframeId default to ‘x-history-field’ and ‘x-history-frame’ respectively. Change them before running initialiseHistory if you need to customise them (Ext.History is just a singleton object so you can call Ext.History.fieldId = ‘something-else’).

The main method you’ll be using is Ext.History.add(‘someurl’). This adds a token to the history stack and effectively redirects the browser to http://myurl.com/#someurl. To create something like the grid/form example above, you could write something like this:

Ext.ns('MyApp');

MyApp.Application = function() {
  this.initialiseHistory();

  this.grid = new Ext.grid.GridPanel({
    //set up the grid...
    store: someProductsStore,
    columns: ['some', 'column', 'headers'],

    //this is the important bit - redirects when you double click a row
    listeners: {
      'rowdblclick': {
        handler: function(grid, rowIndex) {
          Ext.History.add("products/edit/" + rowIndex);
        }
      }
    }
  });

  this.form = new Ext.form.FormPanel({
    items: ['some', 'form', 'items'],

    //adds a cancel button which redirects back to the grid
    buttons: [
      {
        text: 'cancel',
        handler: function() {
          Ext.History.add("products");
        }
      }
    ]
  });

//any other app startup processing you need to perform
};

MyApp.Application.prototype = {
  initialiseHistory: function() {
    //as above
  },

  /**
   * @param {String} token The url token which has just been navigated to
   * (e.g. if we just went to http://myurl.com/#someurl, token would be 'someurl')
   */
  handleHistoryChange: function(token) {
    var token = token || "";
    switch(token) {
      case 'products':        this.showProductsGrid();     break;
      case 'products/edit/1': this.showProductEditForm(1); break;
      case '':                //nothing after the #, show a default view
    }
  },

  showProductsGrid: function() {
    //some logic to display the grid, depending on how your app is structured
  },

  showProductEditForm: function(product_id) {
    //displays the product edit form for the given product ID.
  }
};

Ext.onReady(function() {
  var app = new MyApp.Application();
});

So when you visit http://myurl.com/#products, showProductsGrid() will be called automatically, and when you visit http://myurl.com/#products/edit/1, showProductEditForm() will be called with the argument 1. You can write your own logic here to change tab or show a window or whatever it is you do to show a different view to the user.

I’m not suggesting you parse the url token using a giant switch statement like I have above – this is only an example. You could get away with something like that for a very small app but for anything bigger you’ll probably want some kind of a router. That goes a little beyond the scope of this article but it is something I will return to at a later date.

There is also an example of Ext.History available on the Ext samples pages.

Custom containers with ExtJS

ExtJS has several built-in Container classes – classes which can contain one or more other Ext.Components (such as Grids, Forms, other Panels, etc). The most obvious example of a Container is the Ext.Panel class, along with its subclasses such as Ext.TabPanel, Ext.form.FormPanel and Ext.Window. With each container class you can add a bunch of components, like this:

//a child component to be added to the container below
var myComponent = new Ext.Panel({html: 'component 1'});

//Ext.Panel is a subclass of Ext.Container
var myPanel = new Ext.Panel({
  items: [
    myComponent,
    {html: 'component 2'},
    {html: 'component 3'}
  ]
});

Which will just create a Panel with three other Panels as its child components (‘panel’ is the default xtype, so we don’t have to specify it). More to the point, you can add and remove components from the Container like this:

myPanel.add({html: 'component 4'});
myPanel.remove(myComponent);

As myPanel is an Ext.Container subclass, the methods add() and remove() automatically add or remove child components from within the Container, and take care of any rendering that needs to be performed. Most of the time this is great, but what if you want to write your own custom Container? Say you had a bunch of shortcut links which performed some action in your application, and for styling or other reasons you want to put them into markup like this:

<div class="x-shortcuts-wrapper">
  <div class="x-shortcuts-header"></div>
  <div class="x-shortcuts">
    <!-- child components to go here -->
  </div>
  <div class="x-shortcuts-footer"></div>
  <button class="x-shortcuts-add">Add</button>
</div>

You might write something like this:

Ext.ns('MyApp');
/**
 * @class MyApp.Shortcuts
 * @extends Ext.Container
 * Container for application shortcuts
 */
MyApp.Shortcuts = Ext.extend(Ext.Container, {
  /**
   * Creates the HTML markup for the shortcuts container
   * @param {Ext.Container} ct The container into which this container will be rendered
   */
  onRender: function(ct) {
    this.el = ct.createChild({
      cls: 'x-shortcuts-wrapper',
      children: [
        {cls: 'x-shortcuts-header'},
        {cls: 'x-shortcuts'},
        {cls: 'x-shortcuts-footer'},
        {cls: 'x-shortcuts-add', tag: 'button'}
      ]
    });
    
    MyApp.Shortcuts.superclass.onRender.apply(this, arguments);
    
    this.shortcutsHolder = this.el.child('.x-shortcuts');
  },
  
  //tells the container which element to add child components into
  getLayoutTarget: function() {
    return this.shortcutsHolder;
  }
});

So our onRender method is responsible for creating some markup, which must be assigned to this.el. We’re also calling the onRender() function of the superclass (Ext.Container) to make sure nothing is missed out.

The critical elements here are the getLayoutTarget() function, and the last line on onRender(). Usually when you subclass Ext.Container, the add() and remove() functions add and remove from this.el, which would result in something like this:

<div class="x-shortcuts-wrapper">
  <div class="x-shortcuts-header"></div>
  <div class="x-shortcuts"></div>
  <div class="x-shortcuts-footer"></div>
  <button class="x-shortcuts-add">Add</button>
  <!-- child components will end up here -->
</div>

To prevent this from happening, we obtain a reference to the element we want components to actually be rendered to, and return that with getLayoutTarget(). After that the Container will once again do your bidding.

As of the time of writing getLayoutTarget() is not to be found anywhere in the Ext documentation (version 2.2), so my thanks go to Condor and Animal for answering my question on the ExtJS forum thread.

To round off the example, say your Shortcut class looked something like this:

/**
 * @class MyApp.Shortcut
 * @extends Ext.Component
 * Clickable shortcut class which renders some HTML for a standard application shortcut
 */
MyApp.Shortcut = function(config) {
  var config = config || {};
 
  //apply some defaults
  Ext.applyIf(config, {
    text: 'Shortcut Name',
    icon: 'default_shortcut.gif'
  });
 
  //call the superclass constructor
  MyApp.Shortcut.superclass.constructor.call(this, config);
};
Ext.extend(MyApp.Shortcut, Ext.Component, {
  onRender: function(ct) {
    this.el = ct.createChild({
      cls: 'x-shortcut',
      children: [
        {
          tag: 'img',
          src: this.initialConfig.icon
        },
        {
          tag:  'span',
          html: this.initialConfig.text
        }
      ]
    });
    
    MyApp.Shortcut.superclass.onRender.apply(this, arguments);
  }
});

Ext.reg('shortcut', MyApp.Shortcut);

Then our container would be created like this:

new MyApp.Shortcuts({
  items: [
    new MyApp.Shortcut({text: 'Shortcut 1', icon: 'shatner.gif'}),
    {xtype: 'shortcut', text: 'Shortcut 2', icon: 'nimoy.gif'},
    {xtype: 'shortcut'}
  ]
});

Which would produce HTML like this:

<div class="x-shortcuts-wrapper">
  <div class="x-shortcuts-header"></div>
  <div class="x-shortcuts">
    <div class="x-shortcut">
      <img src="shatner.gif" />
      <span>Shortcut 1</span>
    </div>
    <div class="x-shortcut">
      <img src="nimoy.gif" />
      <span>Shortcut 2</span>
    </div>
    <div class="x-shortcut">
      <img src="default_shortcut.gif" />
      <span>Shortcut Name</span>
    </div>
  </div>
  <div class="x-shortcuts-footer"></div>
  <button class="x-shortcuts-add">Add</button>
</div>

Weird bug preventing ExtJS checkboxes from submitting properly

This applies to ExtJS 2.2, the most current version as of the time of writing.

Checkboxes often make their way into my Ext JS forms. Sometimes, though, they don’t behave as expected. Checking and unchecking them would frequently fail, simply not doing anything. Sometimes it would work, sometimes it wouldn’t – how frustrating!

It turns out there is a bug with ticking/unticking checkboxes in Ext. If you click on the checkbox itself everything works fine – the image of the checkbox updates and the correct value is submitted. If however you click on the checkbox’s label, the image of the checkbox is updated but the correct value is not submitted. So if the box started off unticked and you ticked it by clicking the label, the image is updated but nothing else happens.

This is extremely unintuitive because you can see that the box has been checked, but its internal representation hasn’t actually changed. Because I usually click the label this took me over an hour to track down, so I hope this helps someone out. Once I had identified the bug, a quick Google search points to this thread on the ExtJS forums, which has some guidance on this.

How Ext.apply works, and how to avoid a big headache

Ext.apply is one of those magic Ext JS methods which copies the essence of one object onto another. You usually call it like this:

Ext.apply(receivingObject, sendingObject, defaults)

Where defaults are optional. If you supply defaults, Ext.apply actually does this:

Ext.apply(receivingObject, defaults);
Ext.apply(receivingObject, sendingObject);

In other words, the order of precedence of the three arguments goes like this: any properties in receivingObject which are also present in defaults will be overwritten by the property in defaults. After that has happened, any properties which are present receivingObject (after defaults have been applied) and also present in sendingObject will be overwritten by the sendingObject value. More graphically:

Ext.apply({a: 'receiver'}, {a: 'sender'}, {a: 'default'}); // = {a: 'sender'}

For me, this was slightly unexpected as I expected the default options to have the lowest priority – that is the default option would only be copied across if it was not present in either the receiving or the sending objects, so watch out for that.

Anyway that’s all well and good once you know how it works inside, but while watching an otherwise excellent screencast from Jay Garcia (see http://tdg-i.com/42/ext-js-screencast-003-extapply-published), something odd happened. The example he gave went like this (commented lines signify the output returned by Firebug):

var obj1 = {x: 'x string', y: 'y string'}
// = {x: 'x string', y: 'y string'}

var obj2 = {a: 'a string', b: 4289, c: function(){}}
// = {a: 'a string', b: 4289, c: function(){}}

var obj3 = Ext.apply(obj2, obj1, {pxyz: 'soifje'})
obj3 
// = {a: 'a string', b: 4289, pxyz: 'soifje', x: 'x string', y: 'y string'}
obj2
// = {a: 'a string', b: 4289, pxyz: 'soifje', x: 'x string', y: 'y string'}
obj3 === obj2 
// true - obj3 and obj2 are the same object

var obj4 = Ext.apply(obj3, obj2, {a: 'fwaifewfaije'})
// obj4 = {a: 'fwaifewfaije', b: 4289, pxyz: 'soifje', x: 'x string', y: 'y string'}

So basically he set up obj1 and obj2 with non-conflicting properties, then merged them with some defaults to create obj3. In this case the defaults didn’t conflict with the properties from obj1 or obj2, so obj3 is essentially a straightforward combination of obj1 and obj2, plus a default pxyz value.

What he did then however was to create obj4 as a combination of obj2 and obj3, along with a default value for the ‘a’ property, which was a property of obj2 and obj3. Crucially, obj4’s ‘a’ property was set to the default value, which as we’ve seen from how Ext.apply works above, should never happen (it should be set to the default value but then immediately set back again on the second internal Ext.apply call).

So what gives? Well, it turns out this is because when calling:

obj3 = Ext.apply(obj2, obj1, {pxyz: 'soifje'})

obj3 and obj2 are the exact same object, as Ext.apply returns the first argument after the apply process has taken place. So in the next call:

obj4 = Ext.apply(obj3, obj2, {a: 'fwaifewfaije'})

obj3 and obj2 are in fact both references to the same object, which is causing the unexpected default value. We can show this by manually creating a new obj3 with the exact same properties, and running the example again:

var obj1 = {x: 'x string', y: 'y string'}
// obj1 = {x: 'x string', y: 'y string'}

var obj2 = {a: 'a string', b: 4289, c: function(){}}
// obj2 = {a: 'a string', b: 4289, c: function(){}}

var obj3 = {a: 'a string', b: 4289, pxyz: 'soifje', x: 'x string', y: 'y string'}
// obj2 = {a: 'a string', b: 4289, pxyz: 'soifje', x: 'x string', y: 'y string'}
// obj3 === obj2 => false... obj3 and obj2 are the same object

var obj4 = Ext.apply(obj3, obj2, {a: 'fwaifewfaije'})
// obj4 = {a: 'a string', b: 4289, pxyz: 'soifje', x: 'x string', y: 'y string'}

This time around we get what we expect – the default value is not applied because it is already present in obj2. Here, obj2 is not the same object as obj3, even though their properties are identical.

I’m not completely certain why two references to the same object cause this behaviour, but the code above does appear to demonstrate that this is what is happening (you can just copy/paste each example into Firebug to reproduce it).

Moral of the story? Well, now you have a more detailed understanding of Ext.apply, and hopefully you’ll be on your guard about referencing the same object by two different variables when performing this type of operation. I know I will be 😉

Cleaning up an example Ext JS form

One of my recent Ext JS forms had a section which looked like this:

items: [
  new Ext.Button({
    text: 'Preview Video',
    iconCls: 'play',
    handler: function() {
      var win;
      
      if (!win) {
        win = new Ext.Window({
          title: 'Preview Video',
          modal: true,
          height: 377,
          width: 368,
          items: [
            new Ext.Panel({
              autoLoad: '/admin/videos/' + video_id + '/preview.html'
            })
          ],
          buttons: [
            {
              text: 'OK',
              handler: function() {
                win.close();
              }
            }
          ]
        });
        
      };
      win.show();
      
    }
  })
]

Not horrific but not nice either – let’s DRY this up. It’s not too pleasant to read but all it’s really doing is rendering a customised Ext.Button which opens up a modal Ext.Window, in which is loaded the contents of a known url.

Ok so let’s start with that Window. First, we’ll make a subclass of Ext.Window:

/**
 * AdFunded.views.Video.PreviewWindow
 * @extends Ext.Window
 * A simple Preview window for the given video_id
 */
AdFunded.views.Video.PreviewWindow = function(config) {
  var config = config || {};
    
  Ext.applyIf(config, {
    title: 'Preview Video',
    modal: true,
    height: 377,
    width: 368,
    items: [
      new Ext.Panel({
        autoLoad: '/admin/videos/' + config.video_id + '/preview.html'
      })
    ],
    buttons: [
      {
        text: 'OK',
        scope: this,
        handler: function() {
          this.window.close();
        }
      }
    ]
  });
  
  AdFunded.views.Video.PreviewWindow.superclass.constructor.call(this, config);
  
  this.window = this;
};
Ext.extend(AdFunded.views.Video.PreviewWindow, Ext.Window);
Ext.reg('video_preview_window', AdFunded.views.Video.PreviewWindow);

Note the namespacing employed above – within an Ext MVC framework I have been developing across several projects for the last few months, all views follow this structure. AdFunded is the name of the application. The precise structure doesn’t matter here, but using a namespace for each app does.

So we’ve taken the Window setup out of our view now, which leaves us with:

items: [
  new Ext.Button({
    text: 'Preview Video',
    iconCls: 'play',
    handler: function() {
      var win;
      
      if (!win) {
        win = new AdFunded.views.Video.PreviewWindow({video_id: id});
      };
      win.show();
      
    }
  })
]

Great – we’ve gone from 34 lines in our view to 15, and scored ourselves a reusable Window component which we can call from anywhere in the app. Nice work, but there’s more to come… If we’re going to use the Preview Window again, we’ll probably need to use that Preview Button again too. Let’s see:

/**
 * AdFunded.views.Video.PreviewButton
 * @extends Ext.Button
 * Displays a Preview Window for the given video_id
 */
AdFunded.views.Video.PreviewButton = function(config) {
  var config = config || {};
  
  Ext.applyIf(config, {
    text: 'Preview Video',
    iconCls: 'play',
    handler: function() {
      var win = new AdFunded.views.Video.PreviewWindow({video_id: config.video_id});
      win.show();
    }
  });
  
  AdFunded.views.Video.PreviewButton.superclass.constructor.call(this, config);
};
Ext.extend(AdFunded.views.Video.PreviewButton, Ext.Button);
Ext.reg('video_preview_button', AdFunded.views.Video.PreviewButton);

Which leaves us with the following the the view:

items: [
  {
    xtype: 'video_preview_button',
    video_id: id
  }
]

We’ve now gone from 34 lines to 6 (in the view at least), but the point is not about cutting out lines of code – it’s creating reusable components. We’ve added 20 lines overall this way but we now have two extra components that we can call on at any time (with minimal lines of code), safe in the knowledge that they will provide a consistent experience each time.

ExtJS Radio Buttons and Square Brackets

While creating an ExtJS form with several radio buttons today I ran into a bug which caused none of them to work as expected, even though there were no errors/exceptions. To cut a long story short, it was because I was setting the name to “schedule[include_type]” – like this:

{
  xtype: 'radio',
  name: 'schedule[include_type]',
  inputValue: 'page',
  boxLabel: 'Show page:'
}

This radio button is one of 4, which allows the user which type of file they want to include on a particular model (a Schedule in this case) – be it Page, Video, Category or one other. The thing is – none of them work with the square brackets in the name. If you remove the brackets, they all work correctly, but the server-side is relying on those brackets to be present to group the data correctly.

In the end I bit the bullet and updated my submit method to add a new parameter directly – here’s a full example:

form = new Ext.form.FormPanel({
  items: [
    {
      xtype: 'radio',
      name: 'include_type',
      inputValue: 'page',
      boxLabel: 'Show page:'
    },
    {
      xtype: 'radio',
      name: 'include_type',
      inputValue: 'category',
      boxLabel: 'Show category:'
    },
    ... plus some extra items
  ],
  buttons: [
    {
      text: 'Save',
      handler: function() {
        
        //find the currently selected include_type from the form
        var include_type = this.form.getValues()['include_type'];
        
        //note the params option - this needs to be added manually otherwhise 
        //schedule[include_type] won't appear
        form.form.submit({
          waitMsg: 'Saving Data...',
          params: "schedule[include_type]=" + include_type,
          url: some url...
        });
      }
    }
  ]
})

Note: I don’t usually add buttons in the way above so I’m not sure if the form.form.submit will work correctly here – see http://extjs.com/deploy/dev/docs/?class=Ext.form.FormPanel for information about overriding submit.

So what we’re doing here is finding which radio button is currently checked, and appending this under “schedule[include_type]” when POSTing the form variables to the server. This really isn’t pleasant but seems to be the best way around this limitation for now.

I regularly use square brackets in other Ext JS Fields – Radio Buttons seem to be the only ones that have this problem. http://extjs.com/forum/showthread.php?p=185296 has a bit of background behind this, but no real solution.

Useful Rails javascript expansions for EXTJS

If you’re using Edge Rails (or > 2.1, which isn’t out at time of writing), and are using the EXT JS framework anywhere, here are a couple of handy javascript include tag expansions to clean up your views. Just chuck them into any file in your config/initializers directory:

ActionView::Helpers::AssetTagHelper.register_javascript_expansion :ext => ['ext/adapter/ext/ext-base', 'ext/ext-all']

ActionView::Helpers::AssetTagHelper.register_javascript_expansion :ext_grid_filter => ['ext/ux/menu/EditableItem', 'ext/ux/menu/RangeMenu', 'ext/ux/grid/GridFilters', 'ext/ux/grid/filter/Filter', 'ext/ux/grid/filter/StringFilter', 'ext/ux/grid/filter/DateFilter', 'ext/ux/grid/filter/ListFilter', 'ext/ux/grid/filter/NumericFilter', 'ext/ux/grid/filter/BooleanFilter']

The top one includes the relevant EXT base files and the second one includes all the Grid Filters from the excellent Filter Grid plugin (see http://ccinct.com/lab/filter-grid/.

Include them as usual like this:

javascript_include_tag :ext, :ext_grid_filter, :cache => 'ext_javascripts'

EXT remote-loading forms with Combo boxes

Something that’s harder than it should be is populating an EXT edit form with form data, where one of the form fields is a select box. If there is a specific set of values that this select can take, then you can hard code that using a SimpleStore, like this:

var exampledata = [['AL', 'Alabama'],
                   ['AK', 'Alaska'],
                   ['AZ', 'Arizona'],
                   ['AR', 'Arkansas'],
                   ['CA', 'California']];

var store = new Ext.data.SimpleStore({
    fields: ['abbr', 'state'],
    data : exampleData
});

var combo = new Ext.form.ComboBox({
    store: store,
    displayField: 'state',
    valueField: 'abbr',
    ... etc ...
});

form = new Ext.form({
  items: [combo],
  ... etc ...
});

form.load(url_to_load_from);

So that will populate the select box with the static values you’ve defined (the 5 states above), then when the form loads it will select the appropriate option automatically.

So far so good, but what if you need to load what goes into the select box dynamically? Well, first you’ll need to set up your remote data store (my server is sending back JSON data, hence the JsonReader):

store = new Ext.data.Store({
  url: 'url_to_load_combo_data_from',
  reader: new Ext.data.JsonReader(
    {root: 'states',totalProperty: 'results'},
    [
      {name: 'name', type: 'string', mapping: 'state.name'},
      {name: 'abbr', type: 'string', mapping: 'state.abbr'}
    ]
  )
});

This will consume data like this:

{
  "states": [
    {"state": {"name": "Alabama", "abbr": "AL"}}, 
    {"state": {"name": "Alaska",  "abbr": "AK"}}
  ], 
  "results": "2"
}

And populate the store with a collection of state records which can then be loaded into the combobox.

Then all you need to do is load the store before loading the form data, and your comboboxes will be correctly populated, displaying the correct option. Here’s the full example:

store = new Ext.data.Store({
  url: 'url_to_load_combo_data_from',
  reader: new Ext.data.JsonReader(
    {root: 'states',totalProperty: 'results'},
    [
      {name: 'name', type: 'string', mapping: 'state.name'},
      {name: 'abbr', type: 'string', mapping: 'state.abbr'}
    ]
  )
});

var combo = new Ext.form.ComboBox({
    store: store,
    displayField: 'state',
    valueField: 'abbr'
    ... etc ...
});

form = new Ext.formPanel({
  items: [combo]
});

store.load();
form.load(your_form_data_url);

Be wary using the pagination options on the combobox here (see http://extjs.com/deploy/dev/docs/?class=Ext.form.ComboBox) – the reason being if your state’s ‘abbr’ features on the second page of the results it won’t populate the correct options into the combo box.

Getting EXT PagingToolbars to save state

A problem that has recently had me pulling my hair out is how to save state in an EXT PagingToolbar.

Ext makes it easy to save the state of most of its components – by default it does this by setting a cookie with the relevant configuration info, then just reading it back when you load the component again. I’ve been using it to save the state of a few EXT grids I’ve been using on a recent project, this saves config such as which columns you have visible, which column you’re sorting by, and how the columns are ordered.

That works great, and is trivial to implement – just set your provider (see http://extjs.com/deploy/dev/docs/?class=Ext.state.CookieProvider) and be sure to give your grid an id in its config – this is used as the key in the state provider and needs to be unique for each component.

The problem comes when you’re using a paging toolbar though, as this does not save state, so every time you view the grid you’re back to page 1. You can add state behaviour to the paginator by piggybacking the grid’s state store, here’s how it’s done:

Ext.PagingToolbar.override({
  init : function (grid) {
    this.grid = grid;        
    this.grid.on("beforestatesave", this.saveState, this);    
    Ext.util.Observable.capture(grid.store, this.onStateChange, this);
  },
  saveState : function(grid, state) {
    state.start = grid.store.lastOptions.params.start;
  },
  onStateChange : function(ev, store, records, options) {
    if (ev == "load") {this.grid.saveState(); };
  }
});

Basically we’re intercepting the attached Grid’s saveState() event and appending the current start value as stored in the Grid’s DataStore (e.g. if you’re looking at page 3 with 25 rows per page then start = 50). If you examine the contents of your state provider using Firebug (Ext.state.Manager.getProvider().state, then look for the key that matches the id of your grid), you’ll see that there is now a record for ‘start’, which grabbed the correct value from the Grid’s store.

All you need to do then is retrieve that value from the state provider and load your store accordingly:

store = new Ext.data.Store({... your store config ...});

grid = new Ext.grid.GridPanel({
  id: 'unique_grid_id',
  store: store,
  ... other grid config ...
});

//shorthand way of retrieving state information
var state = Ext.state.Manager.getProvider();

var start = state.get(options.id).start || 0);
store.load({params: {start: start, limit: 25}});

If the start value for this grid has never been set it’ll default to zero – e.g. the first page. Next time you come back to this grid it’ll take you right back to where you were, including all column setup and sorting behaviour you have specified.

%d bloggers like this: