Nested XML in ExtJS

I found a few gotchas while playing around with parsing nested XML in Ext.

Take this bit of XML for example. How do you go about automatically building the associations? How do you parse attributes? How do you mix attributes and element parsing?

<Widgets>  
  <Widget WidgetId="1" WidgetName="Assembly #1">
    <Parts>
      <Part PartId="1">
        <PartName>Bolt</PartName>
        <Material>
          {gfm-js-extract-pre-1}
          <Name>Steel</Name>
        </Material>
        <Material>
          {gfm-js-extract-pre-2}
          <Name>Copper</Name>
        </Material>
        <Material>
          {gfm-js-extract-pre-3}
          <Name>Rubber</Name>
        </Material>
      </Part>
    </Parts>
  </Widget>
  <Widget WidgetId="2" WidgetName="Assembly #2">
    <Parts>
      <Part PartId="4">
        <PartName>Pump</PartName>
        <Material>
          {gfm-js-extract-pre-4}
          <Name>Steel</Name>
        </Material>
        <Material>
          {gfm-js-extract-pre-5}
          <Name>Plastic</Name>
        </Material>
      </Part>
    </Parts>
  </Widget>
</Widgets>  

First convert an xml string to an xml document:

var xml = '<Widgets><Widget WidgetId="1" WidgetName="Assembly #1"><Parts><Part PartId="1"><PartName>Bolt</PartName><Materials><Material>{gfm-js-extract-pre-6}<Name>Steel</Name></Material></Materials></Part><Part PartId="3"><PartName>Washer</PartName><Materials><Material>{gfm-js-extract-pre-7}<Name>Copper</Name></Material><Material>{gfm-js-extract-pre-8}<Name>Rubber</Name></Material></Materials></Part></Parts></Widget><Widget WidgetId="2" WidgetName="Assembly #2"><Parts><Part PartId="4"><PartName>Pump</PartName><Materials><Material>{gfm-js-extract-pre-9}<Name>Steel</Name></Material><Material>{gfm-js-extract-pre-10}<Name>Plastic</Name></Material></Materials></Part></Parts></Widget></Widgets>';

var xmlDoc = new DOMParser().parseFromString(xml, "text/xml");  

Build the models:

Ext.define('Material', (function () {  
  'use strict';
  return {
    extend: 'Ext.data.Model',
    fields: [
      { name: 'materialName', mapping: 'Name', type: 'string' },
      { name: 'materialCode', mapping: 'Code', type: 'string' }
    ],
    proxy: {
      type: 'memory',
      reader: {
        type: 'xml',
        record: 'Material',
        root: 'Materials'
      }
    }
  };
}()));

Ext.define('Part', (function () {  
  'use strict';
  return {
    extend: 'Ext.data.Model',
    requires: [ 'Material' ],
    fields: [
      { name: 'partId', mapping: '@PartId', type: 'numeric' },
      { name: 'partName', mapping: 'PartName', type: 'string' },
    ],
    hasMany: [
      { model: 'Material', name: 'getMaterials', associationKey: 'Materials' }
    ],
    proxy: {
      type: 'memory',
      reader: {
        type: 'xml',
        root: 'Parts',
        record: 'Part'
      }
    }
  };
}()));

Ext.define('Widget', (function () {  
  'use strict';
  return {
    extend: 'Ext.data.Model',
    requires: [ 'Part' ],
    fields: [
      { name: 'widgetId', mapping: '@WidgetId', type: 'numeric' },
      { name: 'widgetName', mapping: '@WidgetName', type: 'string' },
    ],
    hasMany: [
      { model: 'Part', name: 'getParts', associationKey: 'Parts' }
    ],
    proxy: {
      type: 'memory',
      reader: {
        type: 'xml',
        root: 'Widgets',
        record: 'Widget',
      }
    }
  };
}()));

Build your store:

var store = Ext.create('Ext.data.Store', {  
  model: 'Widget',
  data: xmlDoc,
  autoload: true,
  // Removed proxy and reader from here, defer to model instead
});

Output the store:

var template = new Ext.XTemplate(  
  '<tpl for=".">',
    '<div class="widget">',
      '# {widgetName}',
      '<div class="parts">',
      '<tpl for="getParts">',
        '<div class="part">',
          '<h2 class="part-header">Part {partId}: {partName}</h2>',
          '<div class="materials">',
            '<tpl for="getMaterials">',
              '<div class="material">{materialName} - {materialCode}</div>',
            '</tpl>',
          '</div>',
        '</div>',
      '</tpl>',
      '</div>',
    '</div>',
  '</tpl>'
);

var view = Ext.create('Ext.view.View', {  
  store: store,
  tpl: template,
  renderTo: Ext.get('app')
});

You can try out it out here: http://jsfiddle.net/rCh2n/.

Convert a string to an XmlDoc

Strings won't be read, you will need to use the DocParser object to convert the string into an xml document.

Put proxies/readers on the Model

This is considered a best practice to put proxies and readers on the the models. This allows models to automatically build. In the case of nested XML, this is a necessity.

HasMany collections need a root element

Notice in the example that all of the HasMany relationships reside inside of a single parent element. This is required to properly build the association. Without including a container element, you will only get a single item parsed. You can see this in action with these examples:

Good: http://jsfiddle.net/bmancini/Eacyf/
Bad: http://jsfiddle.net/bmancini/PPxmp/

comments powered by Disqus