ExtJS RecordFormPanel

Posted by John Kleijn • Wednesday, December 29. 2010 • Category: JavaScript

Say you have a an overview component, like a grid, and you need to edit that. The best approach would be to load the record into the form and invoke BasicForm's "loadRecord". This means you'll be using the Store for persistence though, and BasicForm.submit() no longer does the trick. Especially when creating new records, directly inserting that into the store a grid uses is extra useful. It means the grid wont have to reload it's data from the server and immediately shows the inserted record.

The solution is creating a form type that loads records and uses a store for persistence. This is relatively simple and seems like this functionality should be available in the Ext library. In any case, below is my implementation.



Ext.namespace("libname.form");

/**
 * @class libname.form.RecordFormPanel
 * @extends Ext.form.FormPanel
 */

libname.form.RecordFormPanel = Ext.extend(Ext.form.FormPanel, {
        trackResetOnLoad: true,
        saving: false,
        initComponent: function(){
                this.addEvents('loadrecord', 'add', 'save', 'cancel');

                libname.form.RecordFormPanel.superclass.initComponent.apply(this, arguments);

                this.store.on('write', this.handleStoreWrite, this);

                this.setRecord(this.activeRecord || new this.store.recordType);
        },

    setRecord: function(record){
                this.form.loadRecord.apply(this.form, arguments);
                this.fireEvent('loadrecord', record, this);
                this.activeRecord = record;
                this.saving = false;
    },

    updateRecord: function(){
        this.getForm().updateRecord(this.activeRecord);
    },

    onCancel: function(){
        this.cleanup(true);
        this.fireEvent('cancel', this, this.activeRecord);
    },

    cleanup: function(hide){
        if(this.activeRecord.dirty){
            this.activeRecord.reject();
        }
        if(this.form.isDirty()){
            this.form.reset();
        }
    },

    submit: function(){
        if(!this.form.isValid()){
            return;
        }
                this.updateRecord();

        if(!this.saving && this.activeRecord.phantom){
                        this.store.add(this.activeRecord);
                        this.fireEvent('add', this.activeRecord, this);
                        this.saving = true;
        }
    },

        handleStoreWrite: function(s, a, r, t, rs){
                Ext.each(rs, function(r){
                        if(r === this.activeRecord){
                                this.saving = false;
                                this.fireEvent('save', this, this.activeRecord);
                        }
                }, this);
        }
});

Ext.reg('recordform', libname.form.RecordFormPanel);
 

One thing you may note is that the component never invokes Store.save(). It assumes autoSave, although you could easily listen on the 'add' event to invoke Store.save() manually. The "saving" flag prevents adding the same to the store more than once.

Finally, the 'save' event only fires when the store has saved the record the form handles.

0 Trackbacks

  1. No Trackbacks

4 Comments

Display comments as (Linear | Threaded)
  1. You might want to add


    this.store = Ext.StoreMgr.lookup(this.store);
     

    to initComponent() function. This will allow passing both config objects as well as Store objects. See source of Ext.grid.GridPanel.initComponent()

  2. I guess. Personally I am not really using referencing by ID, I prefer a stateful interface. I use it for combo's though, for often referenced lists, like an employee list. Technically I should not do that though, for the same reasons a Singleton Registry is ill-advised.

  3. It's not for referencing by ID (though Ext.StoreMgr.lookup()'s signature might suggest that). It's for being able to do something like this.


    var recordForm = {
      xtype: 'recordform',
      ...
      store: {
        xtype: 'jsonstore',
        ...
      }
    }
     

    In other way it lets you defer instantiation of the store until you actually need it.

    Granted, this component will probably be used together with other store enabled components like GridPanel, and would be using stores already associated with them (via Ext.grid.GridPanel.getStore() for example). Nonetheless, adding this one line adds some extra functionality to the RecordPanel which makes it a bit more versatile.

  4. That makes sense, I didn't know StoreMgr.lookup() acts as a factory. It's not something I would personally use, as you point out the primary function of this component is a "shared store" situation. It might be useful in situations where you need a local store though, but then again, that is not a logical use case for this component.

    For sake of completeness it's a good suggestion though :-)

Add Comment

You can use [geshi lang=lang_name [,ln={y|n}]][/geshi] tags to embed source code snippets.
Standard emoticons like :-) and ;-) are converted to images.

To prevent automated Bots from commentspamming, please enter the string you see in the image below in the appropriate input box. Your comment will only be submitted if the strings match. Please ensure that your browser supports and accepts cookies, or your comment cannot be verified correctly.
CAPTCHA


Antiquities and such