title: ExtJS - using a custom TriggerField as a GridEditor
body: <p>So I posted this last week to the ExtJS forums, but no one has responded and I'm going a bit crazy trying to figure it out:</p>
  
  <p>I'm fairly new to ExtJS (just learned it last week for work), but I've been working with other JavaScript libraries for quite some time now. I'm making a custom control for editing a list of attributes (currently populated by a JSON request). I'm using PropertyGrid's with custom GridEditor's (created by extending various Ext.form fields). All of my custom fields work except one, which is a repeating value editor. Basically the field is going to be passed a simple 2d key/value pair array by the JSON request, which it displays in an EditorGridPanel (inside of a Ext.Window that I've created).</p>
  
  <p>Here is the section of the JSON request that defines the repeating value editor:</p>
  
  <pre><code>{
      key: 'Repeating',
      type: 'repeating',
      category: 'Category A',
      options: {
          dataArray: [
              {key: 'key A', value: 'value A'}, 
              {key: 'key B', value: 'value B'}, 
              {key: 'key C', value: 'value C'}
          ]
      }
  }
  </code></pre>
  
  <p>The <strong>key</strong> is the name for the field (displayed on the left column of the PropertyGrid).
  The <strong>type</strong> tells the function which creates all of the grid editors what type of custom editor to use.
  The <strong>category</strong> is used to determine which PropertyGrid the GridEditor is added to (I have multiple PropertyGird's, all contained in a Panel with layout: 'acordion').
  Anything in <strong>options</strong> is added to the extended Ext.form field when it is created.</p>
  
  <p>So <strong>dataArray</strong> is attached to my repeating value editor for setting up the initial key/value pairs and to store the array passed back to the GridEditor by the Ext.Window used for editing it.</p>
  
  <p>After some experimenting I decided to use a TriggerField as the GridEditor for my repeating value type. Here is the code for the definition of the repeating value field:</p>
  
  <pre><code>Ext.form.customFields = {
      'repeating': Ext.extend(Ext.form.TriggerField, {
          triggerClass: 'x-form-edit-trigger',
          enableKeyEvents: true
      })
  };
  </code></pre>
  
  <p>And here is the code that sets it up:</p>
  
  <pre><code>Ext.form.customFields['repeating'] = Ext.extend(Ext.form.customFields['repeating'], {
      onTriggerClick: function()
      {
          this.editorWindow.show();
      },
  
      listeners: {
          'render': function(field)
          {
              field.editorWindow = new Ext.MultiSelectWindow({
                  data: field.dataArray,
                  parent: field
              });
          },
          'keydown': function(field, event)
          {
              event.stopEvent();
          },
          'beforerender': function()
          {
              for (i in this.opt) {
                  if (i != 'store') {
                      this[i] = this.opt[i];
                  }
                  else {
                      this.store.loadData(this.opt.store);
                  }
              }
  
              if (this.regex != undefined) {
                  this.validator = function(value)
                  {
                      return this.regex.test(value);
                  };
              }
          }
      }
  });
  </code></pre>
  
  <p>And finally, here is the code for the custom editor window:</p>
  
  <pre><code>Ext.MultiSelectWindow = function(args)
  {
      var obj = this;
  
      obj.args = args;
  
      obj.KeyValue = new Ext.data.Record.create([{
          name: 'key'
      }, {
          name: 'value'
      }]);
  
      obj.gridStore = new Ext.data.Store({
          data: obj.args.data,
          reader: new Ext.data.JsonReader({}, obj.KeyValue),
          autoLoad: true
      });
  
      obj.cm = new Ext.grid.ColumnModel([{
          id: 'key',
          header: "Key",
          dataIndex: 'key',
          editor: new Ext.form.TextField({
              allowBlank: false
          }),
          hideable: false,
          sortable: false,
          menuDisabled: true,
          css: 'font-weight: bold;'
      }, {
          id: 'value',
          header: "Value",
          dataIndex: 'value',
          editor: new Ext.form.TextField({}),
          hideable: false,
          sortable: false,
          menuDisabled: true
      }]);
  
      obj.gridEditor = new Ext.grid.EditorGridPanel({
          cm: obj.cm,
          height: 280,
          store: obj.gridStore,
          autoExpandColumn: 'value',
  
          listeners: {
              'render': function()
              {
                  // set up local aliases
                  obj.a = new Array();
                  obj.a.grid = obj.gridEditor;
                  obj.a.store = obj.a.grid.getStore();
                  obj.a.sel = obj.a.grid.getSelectionModel();
              }
          },
  
          bbar: [{
              text: 'Add',
              cls: 'x-btn-text-icon',
              icon: '/lib/images/add.png',
  
              listeners: {
                  'click': function()
                  {
                      var kv = new obj.KeyValue({
                          key: '',
                          value: ''
                      });
  
                      var row = obj.a.store.data.items.length;
  
                      obj.a.grid.stopEditing();
                      obj.a.store.insert(row, kv);
                      obj.a.grid.startEditing(row, 0);
                  }
              }
          }, {
              text: 'Delete',
              cls: 'x-btn-text-icon',
              icon: '/lib/images/delete.png',
  
              listeners: {
                  'click': function()
                  {
                      if (obj.a.sel.selection) 
                          obj.a.store.remove(obj.a.sel.selection.record);
                  }
              }
          }]
      });
  
      obj.panelAll = new Ext.Panel({
          border: false,
          layout: 'absolute',
          items: [new Ext.Panel({
              width: 250,
              border: false,
              x: 0,
              y: 0,
              items: obj.gridEditor
          }), new Ext.Panel({
              border: false,
              x: 254,
              y: 0,
              items: [new Ext.Button({
                  cls: 'x-btn-icon-side',
                  icon: '/lib/images/arrow_up.png',
  
                  listeners: {
                      'click': function()
                      {
  
                          if (obj.a.sel.selection) {
                              var row = obj.a.sel.selection.cell[0];
                              var rec = obj.a.store.getAt(row);
  
                              if (row &gt;= 1) {
                                  obj.a.store.remove(rec);
                                  obj.a.store.insert(row - 1, rec);
                                  obj.a.grid.startEditing(row - 1, 0);
                              }
                          }
                      }
                  }
              }), new Ext.Button({
                  cls: 'x-btn-icon-side',
                  icon: '/lib/images/arrow_down.png',
  
                  listeners: {
                      'click': function()
                      {
                          if (obj.a.sel.selection) {
                              var row = obj.a.sel.selection.cell[0];
                              var rec = obj.a.store.getAt(row);
                              var len = obj.a.store.data.items.length;
  
                              if (row &lt; len - 1) {
                                  obj.a.store.remove(rec);
                                  obj.a.store.insert(row + 1, rec);
                                  obj.a.grid.startEditing(row + 1, 0);
                              }
                          }
                      }
                  }
              })]
          })]
      });
  
      obj.win = new Ext.Window({
          title: 'Repeating Value Editor',
          layout: 'fit',
          closeAction: 'hide',
          border: false,
          items: obj.panelAll,
          width: 300,
          height: 350,
          resizable: false,
          shadow: false,
          buttonAlign: 'left',
  
          buttons: [{
              text: 'OK',
              handler: function()
              {
                  // reset the repeating field data array
                  obj.args.parent.dataArray = [];
  
                  for (r in obj.a.store.data.items) 
                      obj.args.parent.dataArray[r] = obj.a.store.data.items[r].data;
  
                  obj.args.parent.setRawValue(attrValueToString(obj.args.parent.dataArray));
                  obj.win.hide();
              }
          }, {
              text: 'Cancel',
              handler: function()
              {
                  obj.win.hide();
              }
          }]
      });
  
      obj.show = function()
      {
          obj.win.show();
          obj.a.store.loadData(obj.args.parent.dataArray);
      }
  }
  </code></pre>
  
  <p>Now for my problem: all of this works fine, except for the 7th line of the window's 'OK' button handler ( obj.args.parent.setRawValue(attrValueToString(obj.args.parent.dataArray)); ).</p>
  
  <p><strong>obj</strong> is a self-alias.
  <strong>obj.args.parent</strong> is an alias for the field that opened the repeating value editor window.
  <strong>attrValueToString()</strong> is a function that takes in a 2d array and converts it to a string with special formatting so it can be displayed in a readable, meaningful manner in the TriggerField's textbox.</p>
  
  <p>The data is loaded back into the field's dataArray variable and if you open the editor again, it will have the new data included in the view. I can't, however, manage to get any sort of value to be displayed in the TriggerField after it has been created. I have tried both obj.args.parent.setValue('abc') and obj.args.parent.setRawValue('abc') . No exception is thrown, yet the value displayed in the TriggerField does not change. I even tried creating a custom function for setting the value from within the TriggerField - something like this:</p>
  
  <pre><code>Ext.form.customFields['repeating'] = Ext.extend(Ext.form.customFields['repeating'], {
      setFieldValue: function(value){
          this.setValue(value);
      }
  });
  </code></pre>
  
  <p>This custom function works if called from within the TriggerField, but not when called from somewhere else (i.e. the editor window's 'OK' button handler). The function can be called successfuly from anywhere and does not produce any exceptions, however, it only sets the value correctly if called from within the TriggerField.</p>
  
  <p>The custom field works perfectly when instantiated as a basic form field:</p>
  
  <pre><code>var sample = new Ext.form.customFields['repeating']({
      renderTo: Ext.getBody(),
      dataArray: [
          {key: 'key A', value: 'value A'}, 
          {key: 'key B', value: 'value B'}, 
          {key: 'key C', value: 'value C'}
      ]
  });
  </code></pre>
  
  <p>I have scoured the ExtJS API documentation and done every possible google search I can think of. I found a few forum posts that seem to be from people having a similar problem, but they never get a clear answer.</p>
  
  <p>Any help with this matter would be most appreciated - thanks in advance!</p>