Hybrid View
-
1 Sep 2009 1:03 AM #1Sencha - Community Support Team
- Join Date
- Mar 2007
- Location
- The Netherlands
- Posts
- 24,251
- Vote Rating
- 41
ComponentDataView - Ext components inside a dataview or listview
ComponentDataView - Ext components inside a dataview or listview
DataView and ListView are very useful to render store based data as HTML. This extension adds the ability to include Ext components in the HTML without the need to keep track of them (e.g. you won't get a memory leak when you update or delete a record).
It also supports automatically getting and setting the value from the store if the component is an Ext.form.Field.
Ext.ux.ComponentDataView:
Ext.ux.ComponentListView:Code:Ext.ns('Ext.ux'); Ext.ux.ComponentDataView = Ext.extend(Ext.DataView, { defaultType: 'textfield', initComponent : function(){ Ext.ux.ComponentDataView.superclass.initComponent.call(this); this.components = []; }, refresh : function(){ Ext.destroy(this.components); this.components = []; Ext.ux.ComponentDataView.superclass.refresh.call(this); this.renderItems(0, this.store.getCount() - 1); }, onUpdate : function(ds, record){ var index = ds.indexOf(record); if(index > -1){ this.destroyItems(index); } Ext.ux.ComponentDataView.superclass.onUpdate.apply(this, arguments); if(index > -1){ this.renderItems(index, index); } }, onAdd : function(ds, records, index){ var count = this.all.getCount(); Ext.ux.ComponentDataView.superclass.onAdd.apply(this, arguments); if(count !== 0){ this.renderItems(index, index + records.length - 1); } }, onRemove : function(ds, record, index){ this.destroyItems(index); Ext.ux.ComponentDataView.superclass.onRemove.apply(this, arguments); }, onDestroy : function(){ Ext.ux.ComponentDataView.onDestroy.call(this); Ext.destroy(this.components); this.components = []; }, renderItems : function(startIndex, endIndex){ var ns = this.all.elements; var args = [startIndex, 0]; for(var i = startIndex; i <= endIndex; i++){ var r = args[args.length] = []; for(var items = this.items, j = 0, len = items.length, c; j < len; j++){ c = items[j].render ? c = items[j].cloneConfig() : Ext.create(items[j], this.defaultType); r[j] = c; if(c.renderTarget){ c.render(Ext.DomQuery.selectNode(c.renderTarget, ns[i])); }else if(c.applyTarget){ c.applyToMarkup(Ext.DomQuery.selectNode(c.applyTarget, ns[i])); }else{ c.render(ns[i]); } if(Ext.isFunction(c.setValue) && c.applyValue){ c.setValue(this.store.getAt(i).get(c.applyValue)); c.on('blur', function(f){ this.store.getAt(this.index).data[this.dataIndex] = f.getValue(); }, {store: this.store, index: i, dataIndex: c.applyValue}); } } } this.components.splice.apply(this.components, args); }, destroyItems : function(index){ Ext.destroy(this.components[index]); this.components.splice(index, 1); } }); Ext.reg('compdataview', Ext.ux.ComponentDataView);
Usage example:Code:Ext.ux.ComponentListView = Ext.extend(Ext.ListView, { defaultType: 'textfield', initComponent : function(){ Ext.ux.ComponentListView.superclass.initComponent.call(this); this.components = []; }, refresh : function(){ Ext.destroy(this.components); this.components = []; Ext.ux.ComponentListView.superclass.refresh.apply(this, arguments); this.renderItems(0, this.store.getCount() - 1); }, onUpdate : function(ds, record){ var index = ds.indexOf(record); if(index > -1){ this.destroyItems(index); } Ext.ux.ComponentListView.superclass.onUpdate.apply(this, arguments); if(index > -1){ this.renderItems(index, index); } }, onAdd : function(ds, records, index){ var count = this.all.getCount(); Ext.ux.ComponentListView.superclass.onAdd.apply(this, arguments); if(count !== 0){ this.renderItems(index, index + records.length - 1); } }, onRemove : function(ds, record, index){ this.destroyItems(index); Ext.ux.ComponentListView.superclass.onRemove.apply(this, arguments); }, onDestroy : function(){ Ext.ux.ComponentDataView.onDestroy.call(this); Ext.destroy(this.components); this.components = []; }, renderItems : function(startIndex, endIndex){ var ns = this.all.elements; var args = [startIndex, 0]; for(var i = startIndex; i <= endIndex; i++){ var r = args[args.length] = []; for(var columns = this.columns, j = 0, len = columns.length, c; j < len; j++){ var component = columns[j].component; c = component.render ? c = component.cloneConfig() : Ext.create(component, this.defaultType); r[j] = c; var node = ns[i].getElementsByTagName('dt')[j].firstChild; if(c.renderTarget){ c.render(Ext.DomQuery.selectNode(c.renderTarget, node)); }else if(c.applyTarget){ c.applyToMarkup(Ext.DomQuery.selectNode(c.applyTarget, node)); }else{ c.render(node); } if(c.applyValue === true){ c.applyValue = columns[j].dataIndex; } if(Ext.isFunction(c.setValue) && c.applyValue){ c.setValue(this.store.getAt(i).get(c.applyValue)); c.on('blur', function(f){ this.store.getAt(this.index).data[this.dataIndex] = f.getValue(); }, {store: this.store, index: i, dataIndex: c.applyValue}); } } } this.components.splice.apply(this.components, args); }, destroyItems : function(index){ Ext.destroy(this.components[index]); this.components.splice(index, 1); } }); Ext.reg('complistview', Ext.ux.ComponentListView);
Code:Ext.onReady(function() { new Ext.Viewport({ layout: 'hbox', layoutConfig: { align: 'stretch' }, defaults: { flex: 1 }, items: [{ title: 'ComponentDataView example', items: { xtype: 'compdataview', store: [[1, 'One'], [2, 'Two'], [3, 'Three']], itemSelector: 'tbody tr', tpl: '<table><thead><tr><td>Value</td><td>Text</td></tr></thead><tbody><tpl for="."><tr><td></td><td></td></tr></tpl></tbody></table>', items: [{ xtype: 'numberfield', minValue: 0, maxValue: 100, renderTarget: 'td:nth(1)', applyValue: 'field1' },{ allowBlank: false, renderTarget: 'td:nth(2)', applyValue: 'field2' }] } },{ title: 'ComponentListView example', items: { xtype: 'complistview', store: [[1, 'One'], [2, 'Two'], [3, 'Three']], columns: [{ header: 'Value', width: .5, dataIndex: 'field1', tpl: ' ', component: { xtype: 'numberfield', minValue: 0, maxValue: 100, applyValue: true } },{ header: 'Text', width: .5, dataIndex: 'field2', tpl: ' ', component: { allowBlank: false, applyValue: true } }] } }] }); });
-
1 Sep 2009 8:20 AM #2
very slick
-
1 Sep 2009 8:45 AM #3
i was just looking for a solution like this! how do i renderTarget to div or input instead of tr?
also it would be nice if the form containing your extension can submit and load the folloing json:
PHP Code:{
first:'John',
last:'Doe',
company:'Some Company',
email:'email@abc.com',
phones:[{
phone:'11111111',type:'mobile'
},{
phone:'22222222',type:'office'
},{
phone:'33333333',type:'home'
}]
}
this is what i'm experimenting with:
PHP Code:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Complex form test</title>
<link rel='stylesheet' type='text/css' href='http://extjs.com/deploy/dev/resources/css/ext-all.css'/>
<script type="text/javascript" src="http://extjs.com/deploy/dev/adapter/ext/ext-base.js"></script>
<script type="text/javascript" src="http://extjs.com/deploy/dev/ext-all-debug.js"></script>
</head>
<body>
</body>
</html>
<script language="javascript" type="text/javascript">
Ext.QuickTips.init();
</script>
<script type="text/javascript" language="javascript">
Ext.ns('Ext.ux.form');
//MyForm=Ext.extend(Ext.form.FormPanel,{
MyForm=Ext.extend(Ext.Panel,{
initComponent:function(){
Ext.apply(this,{
//baseCls: 'x-plain',
labelWidth: 55,
//url:'save-form.php',
defaultType: 'textfield',
layout:'hbox',
items: [{
fieldLabel: 'Phone',
name: 'phone',
anchor:'100%', // anchor width by percentage
xtype:'textfield'
},{
fieldLabel: 'Type',
name: 'type',
anchor: '100%' // anchor width by percentage
}]
})
MyForm.superclass.initComponent.call(this)
},
initFields : function(){
//var f = this.form;
var formPanel = this;
var fn = function(c){
if(formPanel.isField(c)){
//f.add(c);
}if(c.isFieldWrap){
Ext.applyIf(c, {
labelAlign: c.ownerCt.labelAlign,
labelWidth: c.ownerCt.labelWidth,
itemCls: c.ownerCt.itemCls
});
//f.add(c.field);
}else if(c.doLayout && c != formPanel){
Ext.applyIf(c, {
labelAlign: c.ownerCt.labelAlign,
labelWidth: c.ownerCt.labelWidth,
itemCls: c.ownerCt.itemCls
});
if(c.items){
c.items.each(fn, this);
}
}
};
this.items.each(fn, this);
},
onRender : function(ct, position){
this.initFields();
Ext.FormPanel.superclass.onRender.call(this, ct, position);
//this.form.initEl(this.body);
},
isField : function(c) {
return !!c.setValue && !!c.getValue && !!c.markInvalid && !!c.clearInvalid;
},
setValues : function(values){
if(Ext.isArray(values)){ // array of objects
for(var i = 0, len = values.length; i < len; i++){
var v = values[i];
var f = this.findField(v.id);
if(f){
f.setValue(v.value);
if(this.trackResetOnLoad){
f.originalValue = f.getValue();
}
}
}
}else{ // object hash
var field, id;
for(id in values){
if(!Ext.isFunction(values[id]) && (field = this.findField(id))){
field.setValue(values[id]);
if(this.trackResetOnLoad){
field.originalValue = field.getValue();
}
}
}
}
return this;
},
findField : function(id){
var field = this.items.get(id);
if(!Ext.isObject(field)){
this.items.each(function(f){
if(f.isFormField && (f.dataIndex == id || f.id == id || f.getName() == id)){
field = f;
return false;
}
});
}
return field || null;
}
})
Ext.ux.form.ArrayField = Ext.extend(Ext.form.Field,{
onRender : function(ct, position){
this.panel =new Ext.Panel({
renderTo: ct,
width:230,
height:50,
autoScroll:true,
layout:'vbox',
items:[]
//layout:'vbox',
});
this.el=this.panel.getEl();
//Ext.ux.form.ArrayField.superclass.onRender.call(this, ct, position);
this.hiddenField = Ext.DomHelper.append(this.el,{tag:'input', type:'hidden',name:this.name});
},
setValue:function(val) {
var v = Ext.util.JSON.encode(val);
this.hiddenField.value=v;
//alert(v);
//this.panel.removeAll();
for (var i=0;i<val.length;i++){
var f = new MyForm()
this.panel.add(f)
this.panel.doLayout();
f.setValues(val[i])
}
},
getValues:function(){
}
});
// register xtype
Ext.reg('arrayfield', Ext.ux.form.ArrayField);
</script>
<script type="text/javascript">
Ext.onReady(function(){
var f = new Ext.FormPanel({
labelWidth: 75,
url:'save-form.php',
frame:true,
title: 'Simple Form',
bodyStyle:'padding:5px 5px 0',
width: 350,
defaults: {width: 230},
defaultType: 'textfield',
items: [{
fieldLabel: 'First Name',
name: 'first',
allowBlank:false
},{
fieldLabel: 'Last Name',
name: 'last'
},{
fieldLabel: 'Company',
name: 'company'
}, {
fieldLabel: 'Email',
name: 'email',
vtype:'email'
}, new Ext.form.TimeField({
fieldLabel: 'Time',
name: 'time',
minValue: '8:00am',
maxValue: '6:00pm'
}),
this.a=new Ext.ux.form.ArrayField({name:'items',form:MyForm})
],
buttons: [{
text: 'Save',
handler: function(){
var i=0;
},
scope:this
},{
text: 'Cancel'
}]
});
f.render(document.body);
var rec={}
rec.data={
first:'John',
last:'Doe',
company:'Some Company',
email:'email@abc.com',
items:[{
phone:'11111111',type:'mobile'
},{
phone:'22222222',type:'office'
},{
phone:'33333333',type:'home'
}]
}
f.form.loadRecord(rec);
})
</script>
-
19 Apr 2010 11:33 PM #4
Someone before asked how to render component in different place, other than TD.. As the question remained unanswered, just posting some examples.
To render component in different place of template use CSS structural pseudo classes!
For example, to have component in 8th div in template :second liCode:renderTarget: 'div:nth(8)'
third child of fooCode:renderTarget: 'li:nth(2)'
Code:renderTarget: 'foo:nth-child(3)'
-
19 Apr 2010 11:44 PM #5Sencha - Community Support Team
- Join Date
- Mar 2007
- Location
- The Netherlands
- Posts
- 24,251
- Vote Rating
- 41
I'm not a big fan of using 'nth', because changing your HTML markup would directly invalidate your render targets.
I would assign a class="my-target" to the target element and use a '.my-target' selector.
-
20 Apr 2010 12:17 AM #6
Hey guys,
I think rendering the component in diferent targets is not the real problem here. Try to think about the use case I said before: a DataView which display products as its items and each item will have its HTML markup as usual and two components: a button and a rating comp.
As a possible solution I think extending the XTemplate to provide component building capabilities, something like:
Then, when the template proccessor finds a <comp> tag it call a function, which is reponsible for building that component:Code:<tpl for"."> <div id="product_{id}"> <img /> <div> <comp name="rating" /> </div> <p>{name}</p> <p>{shortDescription}</p> <div> <comp name="button" /> </div> </div> </tpl>
It should also provide some cache capability to avoid creating the same component for the same item more than one time. Probably it should handle unique IDs too, just like Ext does.Code:function(compName, values) { // create the component and return so the template can render it }
I'm not sure if I was clear in my example, but ASAP I'll work on some code.Last edited by daniel.rochetti; 20 Apr 2010 at 12:20 AM. Reason: typos
-
1 Sep 2009 9:26 AM #7
i replied earlier but somehow it had to be approved by the moderator. any idea why is it so?
-
16 Oct 2009 7:40 PM #8
onDestroy error
onDestroy error
I had to patch onDestroy with these:
Code:Ext.override(Ext.ux.ComponentListView, { onDestroy : function(){ Ext.ux.ComponentListView.superclass.onDestroy.call(this); Ext.destroy(this.components); this.components = []; }, }); Ext.override(Ext.ux.ComponentDataView, { onDestroy : function(){ Ext.ux.ComponentDataView.superclass.onDestroy.call(this); Ext.destroy(this.components); this.components = []; }, });
-
18 Mar 2010 9:09 PM #9
-
18 Mar 2010 10:24 PM #10Sencha - Community Support Team
- Join Date
- Mar 2007
- Location
- The Netherlands
- Posts
- 24,251
- Vote Rating
- 41
Are you sure the combobox store is loaded when the dataview is rendered?
(also see this feature request)


Reply With Quote

