PDA

View Full Version : Ext Databinding



Greffin
25 Oct 2009, 3:03 AM
Hi.

I was unhappy with the lack of support for databinding in Ext for regular form panels.
In order to bind a record to datafields in a form, you need to apply listeners to each field listening to changes and storing these back into the datastore.

I've created this small override to Ext.Panel, which should manage the datastore automagically based on values from form fields. It will populate the fields on store load, and resubmit values to store from form fields. This will work with any number of components in the form.

It would be nice with some feedback from the comunity:



//function createOverrides() {
// Override on fieldsets
Ext.form.FieldSet.prototype.onCheckClick = Ext.form.FieldSet.prototype.onCheckClick.createSequence(function() {
this.onCheckFire = true;
this.fireEvent('change', this, !this.checkbox.dom.checked, this.checkbox.dom.checked);
});

Ext.override(Ext.form.FieldSet, {
getCheckbox : function() {
return this.checkbox;
},

parseBoolean : function(value){
if (Ext.isDefined(value) && Ext.isString(value)) {
return (value.toLowerCase() == 'true' ? true:false);
}
return value;
},

getValue : function() {
return this.getCheckbox().dom.checked;
},

setValue : function(value) {
if (!this.onCheckFire) {
this.getCheckbox().dom.checked = this.parseBoolean(value);
this.onCheckClick();
this.onCheckFire = false;
}
}
})


// Override on HtmlEditor
Ext.form.HtmlEditor.prototype.initComponent = Ext.form.HtmlEditor.prototype.initComponent.createSequence(function() {
this.addEvents('change');
this.on('initialize', function(editor) {
if(document.all) {
editor.iframe.attachEvent("onblur", this.onEditorBlur.createDelegate(this));
}
else {
editor.iframe.contentDocument.addEventListener("blur",this.onEditorBlur.createDelegate(this), false);
}
})
this.on('sync', function(editor, html) {
this.value = html;
})
});

Ext.form.HtmlEditor.prototype.setValue = Ext.form.HtmlEditor.prototype.setValue.createSequence(function(value) {
this.oldValue = value;
});

Ext.override(Ext.form.HtmlEditor, {
oldVal : null,
value : null,
onEditorBlur: function() {
this.fireEvent('change', this, this.oldVal, this.value);
}
})
//}

// Apply databinding to items in the panel.
// This should also consider binding to child containers as well.
Ext.Panel.prototype.initComponent = Ext.Panel.prototype.initComponent.createSequence(function() {
// createOverrides();
if (this.store) {
this.store = Ext.StoreMgr.lookup(this.store);
this.store.on({
scope: this,
load : function(store, records, options ) {
// Can only contain one row of data.
if (records.length > 0) {
this.onBind(records[0]);
} else {
this.onUnbind();
}
}
});

// Make sure that edits return to the datastore through fields onChange event.
this.on({
scope: this,
afterRender: function() {
this.walkChildren(this, function(f) {
f.on({
scope: this,
change: function(field) {
if(this.boundRecord) {
var val = (field instanceof Ext.form.RadioGroup ? field.getValue().inputValue : field.getValue());
this.boundRecord.set(field.dataIndex, val);
this.updateBound();
}
}
});
})
}
})
}
});

Ext.override(Ext.Panel, {
enableOnBind: false,
getStore : function() {
return this.store;
},
getDataboundFields : function() {
var fields = [];
if (this.items) {
var j = 0;
for (; j < this.items.getCount(); j++) {
var item = this.items.itemAt(j);
if (item.dataIndex) {
fields.push(item);
}
if (item.getDataboundFields) {
var i = 0;
var other = item.getDataboundFields();
for (; i < other.length; i++) {
fields.push(other[i]);
}
}
}
}
return fields;
},
onBind:function(record) {
if(record && (this.boundRecord !== record)) {
this.internalUpdate = false;
this.boundRecord = record;
this.updateBound();

if (this.enableOnBind) {
Ext.each(this.getDataboundFields(), function(f) {
f.setDisabled(false);
}, this);
}
}
},
onUnbind:function() {
this.internalUpdate = false;
this.boundRecord = null;
Ext.each(this.getDataboundFields(), function(f) {
if (this.enableOnBind) {
f.setDisabled(true);
}
f.setValue(null);
}, this);
// this.updateBound();
},
walkChildren : function(comp, fn) {
if (comp.items) {
comp.items.each(function(f) {
if (f instanceof Ext.form.Field ) {
fn.call(this, f);
}
else if (f instanceof Ext.form.FieldSet) {
fn.call(this, f);
this.walkChildren(f, fn);
}
else if (f instanceof Ext.Panel) {
this.walkChildren(f, fn);
}
}, this);
}
},
updateBound : function() {
if (!this.internalUpdate) {
this.walkChildren(this, function(f) {
if (f.dataIndex) {
this.internalUpdate = true;
f.setValue((this.boundRecord != null ? this.boundRecord.get(f.dataIndex) : null));
this.internalUpdate = false;
}
}, this);
}
}
});
The above will allow an Ext.data.Store to be bound to a Ext.Panel, and fields in the store linked to form fields in said panel. The following code will illustrate this:


var dataStore = new Ext.data.Store({
proxy: new Ext.data.HttpProxy({url: 'storeFetcher', method: 'GET'}),
reader: new Ext.data.JsonReader({
root: 'DataItem',
id: 'ID'
}, [{name: 'ID', type: 'int'},
{name: 'NAME', type: 'string'},
{name: 'TYPE_ID', type: 'int'},
{name: 'SHORTNAME', type: 'string'}]
)
});

var win = new Ext.Window ({
title: 'Databind example',
width: 700,
height: 500,
layout: 'fit',
items: [{
xtype: "panel",
border: false,
store: dataStore,
defaults: { xtype: 'textfield', anchor: '100%'},
items: [{fieldLabel: 'Name', dataIndex: 'NAME'},
{fieldLabel: 'Shortname', dataIndex: 'SHORTNAME'},
{fieldLabel: 'Type', dataIndex: 'SITE_TYPE_ID'}]
}
});
This should work on more complex layouts as well, where the root panel contains a datastore, and all types of form fields.

Please look through, and feel free to use and extend the code as you wish.

Thank you. :)

Greffin
5 Jan 2010, 4:16 AM
Updated!
Added support for FieldSet's and HtmlEditor.
Also tested for Ext 3.1

moegal
21 Jan 2010, 6:53 PM
this looks great, I will try it this weekend.

Marty

Scorpie
21 Jan 2010, 11:19 PM
This is exactly what I need! Will give this a try tonight!

Scorpie
23 Jan 2010, 2:21 PM
Works great, except how do I bind an member thats a nested array?

I tried array.membername and array[0] but no luck as of yet...