ExtJS 4 introduced Sencha’s wonderful data model. One aspect of that is Associations which let you define relationships between different models in your application. It’s a powerful feature with only two, minor side effects.

  1. You can’t use the association data in an XTemplate because the associated data is not in array format
  2. Displaying associated data in a grid is not intuitive

Again, minor issues on an otherwise great feature of the framework. This article covers the latter of the issues.

The Associated Models

In my example, I have Documents, to which Components can be assigned. In our Natural Language Processing [NLP] model building application, each document can be used to train different aspect of the model. However, Components can change, be erased from the system, or new ones can be added, so I’m tracking those as a separate object and associating them to the Document object.

/**
 *  A simple document Model.
 */
Ext.define("BigData.Document", {
   extend: "Ext.data.Model",
   fields: [
      { name: "id",            type: "int" },
      { name: "filetype",      type: "string" },
      { name: "filename",      type: "string" },
      { name: "size",          type: "int" }
   ],
   hasMany: { model: "Component", name: "components" }
});

/**
 *  Each document has n Components, the number, and value,
 *  of which can be modified over time, so I'm not hard
 *  coding those fields directly in the document.
 */
Ext.define("BigData.Component", {
   extend: "Ext.data.Model",
   fields: [
      {name: "id",       type: "int"},
      {name: "name",     type: "string"}
   ]
});

Now that the models have a relationship, I create a store for the Documents the user wants to manage.

/*
 *  Create a SimpleDocumentStore
 */
SimpleDocumentStore = Ext.create("Ext.data.Store", {
    model: "Document",
    proxy: { type: "memory", reader: { type: "json" } }
});

Creating Related Data

Once the models and stores are set up, you can create a Document object, and assign one, or more, Components to it.

/**
 *  Create a document
 */
var SimpleDocument = BigData.Document.create({ id: 1, filename: "sample.txt", filetype: "user-defined", size: 10221 });

/**
 *  Create an instance of the document's components
 */
var DocComponents = SimpleDocument.components();

/**
 *  Add some components to the document
 */
var TitleComponent = BigData.Component.create({ id: 23, name: "Title" });
var BodyComponent  = BigData.Component.create({ id: 23, name: "Body" });

DocComponents.add([TitleComponent, BodyComponent]);

// Add the document to the store
SimpleDocumentStore.add(SimpleDocument);

Rendering Associated Components in the Document Grid

Now I want to display a grid that is tied to the Document store, but also have columns for each Component and show which components were assigned to each of the Documents. To do this, I define a column for each Component and then define a custom renderer for each one. This renderer function looks for each specific Component in the Document’s associated store and simply marks the cell with an ‘X’.

/**
 *  Now when you want to render those components in a grid, you need a custom
 *  render function, because the components aren't direct properties of a Document.
 */
xtype: "grid",
store: SimpleDocumentStore,
columns:[{
   header: "Name",
   dataIndex: name,
   width: 100
},{
   header: "Title",
   width: 60,
   renderer : function (value, cell, doc, idx) {
      var components;

      if (doc.hasOwnProperty("componentsStore")) {
         components = doc.componentsStore;

         if (components.findExact("id", "Title") !== -1) {
            return "X";
         }
      }
   }
},{
   header: "Body",
   width: 90,
   renderer : function (value, cell, doc, idx) {
      var components;

      if (doc.hasOwnProperty("componentsStore")) {
         components = doc.componentsStore;

         if (components.findExact("id", "Body") !== -1) {
            return "X";
         }
      }
   }
}]