PDA

View Full Version : Dynamic form fields : cloning fields and fieldsets on the fly



arnold
14 Mar 2009, 12:02 PM
Ever wanted a form to be a bit more dynamic.
Like being able to add a field or fieldset more than once.
This is how you do it :

All type of fields are supported :



{ fieldLabel: 'First Name',
name: 'first',
labelSeparator : '',
width: 230,
allowBlank:false,
dynamic:true,
maxOccurs:5,
listeners : { 'maxoccurs' : {fn: function(field) { Ext.example.msg('maxoccurs', 'Implement behaviour'); }}}
}



fieldsets are supported :



{ xtype : 'fieldset',
dynamic : true,
maxOccurs:5,
listeners : { 'maxoccurs' : {fn: function(field) {Ext.example.msg('maxoccurs', 'Implement behaviour');},
items :...
..
}


Even nested fields of fieldsets are supported!
Note the current amount of clones can also be set programmatically.
This will add/remove clones untill the specified amount is reached.



fieldSet.clones(2);
person.getForm().findField('state').clones(1);
person.doLayout();


Additional features :
Especially for large and dynamic forms the property nameSpace is introduced.
The property nameSpace set on a field or fieldset will bound the component to a named collection
and therefore allow a more fine grained control.



{ fieldLabel: 'First Name',
name: 'first',
nameSpace:'person'
labelSeparator : '',
width: 230,
allowBlank:false,
dynamic:true,
maxOccurs:5,
listeners : { 'maxoccurs' : {fn: function(field) { Ext.example.msg('maxoccurs', 'Implement behaviour'); }}}
}


This will allow you to populate and extract values of fields, fieldsets, dynamic fields and dynamic fieldsets of a given nameSpace :



person.populate(Ext.decode('{"state":["Netherlands","Delaware"]}'),'location','field');
person.populate(Ext.decode('[{"first":["Adriaan","Cornelis"],"last":"Zaanen"},{"first":["Bill"],"last":"Joy"}]'),'person','fieldset');
person.populate(Ext.decode('{"birthDate":"03/12/2009"}'),'person','field');




var location = person.extract('location','field');
var fsperson = person.extract('person','fieldset');
var fperson = person.extract('person','field');


To get this up and running just unzip the attached demo in your examples folder of extjs.
And that's it. See the attached image for a sneak preview of the attached demo.
Have fun!

14032009 : initial, introduced dynamic field by overriding Ext.form.Field
15032009 : introduced dynamic fieldset by overriding Ext.form.FieldSet
16032009 : allowed programmer to set amount of field and fieldset clones
20032009 : introduced namespaces
20032009 : introduced extracting and populating of namespaced fields and fieldsets
22032009 : documented features
23032009 : allowed programmer to retrieve current set of clones of a given field or fieldset

Frenky
15 Mar 2009, 8:38 AM
nice work!!!!!!!

dizor
16 Mar 2009, 12:01 PM
How i can add a fieldset more than once on load form?

arnold
16 Mar 2009, 1:15 PM
Thanks for your feedback. Both fields and fieldsets can be cloned.
You can set the current amount of clones by :



fieldSet.clones(2);
person.getForm().findField('state').clones(1);
person.doLayout();


Download the attached demo to acquire the latest version of the code.

dizor
17 Mar 2009, 6:49 AM
Thanx for plugin. I have problem with cloning FormPanel - i think that in your example it does not work.

arnold
17 Mar 2009, 6:57 AM
cloning fields and fieldsets are supported and tested on both IE and FF
cloning a panel is not supported

dizor
17 Mar 2009, 7:07 AM
Thx for fast reply. My bad ... but i have another problem. In your example tabs don't render on start (in my ext) - i must resize browser window, and than it's ok. Now, I change your tabs to:



...
fieldLabel: 'First Name',
name: 'resource/first',
id:'test'
...




var tabs = new Ext.TabPanel({
width:450,
activeTab: 0,
deferredRender:false,
layoutOnTabChange:true,
defaults:{autoHeight:true,hideMode:'offsets'},
items: [
{title:'Person',id: 'person' ,items: [person]},
{title:'Company',id: 'company' ,items: [company]}
]

});




Ext.getCmp("test").clones(4);
Ext.getCmp("test").doLayout();


but the problem still exists - i must resize window and than cloning is ok. Any ideas?

dizor
17 Mar 2009, 7:10 AM
Ok - i found solution. Do layout must be done of course on tabs.

dizor
17 Mar 2009, 7:25 AM
Last req: a use your plugin on form with a lot of fields - now i haven't any idea how i can auto cloning and load variables with:



Ext.getCmp('mainForm').getForm().load({url:'form/get_customers',params:{id:id}});


Please, help me with this. I think that this possibility will be best for this plugin and make me (and people) happy :)

arnold
20 Mar 2009, 1:33 PM
Especially for large and dynamic forms the property nameSpace is introduced.
The property nameSpace set on a field or fieldset will bound the component to a named collection,
as part of the total set of fields and fieldsets of the form, and therefore introduce a more fine grained control.



{ fieldLabel: 'First Name',
name: 'first',
nameSpace:'person'
labelSeparator : '',
width: 230,
allowBlank:false,
dynamic:true,
maxOccurs:5,
listeners : { 'maxoccurs' : {fn: function(field) { Ext.example.msg('maxoccurs', 'Implement behaviour'); }}}
}


This will allow you to populate and extract values of fields, fieldsets, dynamic fields and dynamic fieldsets of a given nameSpace :



person.populate(Ext.decode('{"state":["Netherlands","Delaware"]}'),'location','field');
person.populate(Ext.decode('[{"first":["Adriaan","Cornelis"],"last":"Zaanen"},{"first":["Bill"],"last":"Joy"}]'),'person','fieldset');
person.populate(Ext.decode('{"birthDate":"03/12/2009"}'),'person','field');




var location = person.extract('location','field');
var fsperson = person.extract('person','fieldset');
var fperson = person.extract('person','field');

robert7
8 Apr 2009, 12:35 AM
Hello Arnold!
your extension is really great! It is almost exactly what I was searching for (and could not easily figure out how to do it). So you helped me very much.

I have a few suggestions for future development:

1, extract() works for standalone checkboxes and radio buttons, but only gets values true/false. Example: I have 3 radios named "fldA" and the third one is checked: extract() will return: {fldA:[false,false,true]}. Config option "inputValue" of the radios is not taken into account. (Compared to Ext.form.BasicForm.getValues(), which will for 3 radios return one string value specified by "inputValue" of the checked radio field.)

2, radios wrapped in Ext.form.RadioGroup are not be included in extract(). Same for CheckBoxGroup. Although I don't need "dynamic" checkboxes/radios it would be nice if extract/populate could support them.

3, horizontal alignment of the plus/minus icons doesn't work for TriggerField (requires just include of 'trigger' xtype in alignIcon())

4, populate() now requires that even the fields with blank values to be specified in the first parameter (or unlisted fields are not reset to blank values). It may be better if the loop in populate would be over all fields found in specified namespace. Example: form contais 4 fields in namespace XY, incoming parameter for populate specifies only 2 of them, populate should setup 2 listed fields and clear the 2 remaining fields.

5, it would be more intuitive for the user, if the plus icon were present always on the last clone of the specified field.
Example: master field is created with "+" icon. After the 1 clone is created, the icon for the first field changes to "-",
a "+" icon moves to the clone. That means always the last field (master or the last clone has "+") and all previous have "-" icons. But I understand, that it would be more difficult to code.

arnold
8 Apr 2009, 12:54 AM
Thanks for your extensive and indepth feedback!
Your absolutely right on the radio buttons and checkboxes. I will look into this
and include it in the next release. About remark 5. That's doable.

Just let me know when you have already implemented some of these features.
When your interested I will merge our implementations and make this a joined effort.

jamie.nicholson
24 Jun 2009, 8:02 PM
Great plugin, didn't quite do what I wanted so I've changed it somewhat.
Perhaps my changes can be integrated to compliment what you've done with the fieldsets rather than replace it. But I replaced it for now as it was easier to get my head around how it all worked.

My changes have altered the way the fieldset cloning works, in short the fieldset + tool doesn't clone a fieldset. Instead it clones a row of column, form, fields within a fieldset. Very similar behavior to gmail 'Contacts' edit form.

Changes have been made to field.js to allow multiple columns within a fieldset

fieldsets are still configured as dynamic, however they have layout:'column'



dynamic : true,
maxOccurs:5,
xtype : 'fieldset',
layout:'column',
nameSpace:'person',
autoHeight:true,
width: 600,
listeners : { 'maxoccurs' : {fn: function(fieldset) {
Ext.example.msg('maxoccurs', 'Implement behaviour of maxoccurs');
}}},


Because you can't define a form element in a column you need to have a nested form with you're desired columnWidth, each form should contain only one field, notice dynamic is no configured


items :[{
layout:'form',
columnWidth:.5,
defaultType: 'textfield',
items:[{
fieldLabel: 'First Name',
name: 'first',
//dynamic:true,
}]
},{
layout:'form',
columnWidth:.5,
defaultType: 'textfield',
items:[{
fieldLabel: 'Last Name',
name: 'last',
}]
}]


That aside I'll e-mail the other changes to arnold in the Ext.form.FormPanel and Ext.form.FieldSet.
More updates to come possibly, need to discuss with arnold.

26-Jun-2009 - updated FormPanel extract so fieldset extract returns array(rows) of objects(fields)

robert7
24 Jun 2009, 10:37 PM
Hey Jamie!
Looks great! I also didn't like the dynamic fieldsets, because the duplicated frame taken too much space.
I implemented diffent aproach to this - trigger field which duplicates as single field and clicking on the trigger open pop/up window which then can be used to edit one "fields set repetition".
This is handy if the fieldset contains many fields.
It could be really nice if we can some way merga all the changes!
Robert

robert7
24 Jun 2009, 10:42 PM
Demo of the dynamic trigger fields - another approach to dynamic fieldsets

jamie.nicholson
25 Jun 2009, 1:19 AM
You're right I agree, cloning a whole form would be nasty.

However wouldn't it be better to display that information in a grid and at least have a form popup to edit the info? My preference for 'maintenance' would be to display the summary in a grid and then bind the other information to a form and just have it on an entirely seperate tab, which takes up most of the screen.

http://extjs.com/deploy/dev/examples/form/form-grid.html

My rationale is if you put everything on one page for 'maintenance' it will get to cluttered and confusing for a new user. However if you seperate it out in a new tab it be easier to understand.

On the display front you could use a portal to summarise the viz information in a protlet->grid or make multiple portlets each containing the relevant viz information.

That's just my five cents.... don't take it too seriously :D

mc2mac
2 Jul 2009, 1:29 PM
This is exactly what I was looking for!
Thank you

k_lib
8 Jul 2009, 10:52 AM
Hey all,

I just used this. It works great but taking the values was such a pain for me. I wanted to give a tip if you were taking the values of this and trying to loop through them to put in a url. It might also help if you were trying to do anything with the values. The main point is that 1 JSON is a string obj and 2 items(clones) are a array.

I had a form with 2 fields in a fieldset named 'parameters' and 'userInput' using the nameSpace property and 'paras' and 'daInputField' for the names property. this is what i had to do to get the values of those fields...




var panel = Ext.getCmp('panel');

var fparams = panel.extract('Parameters','field');
var fuserInput = panel.extract('userInput','field');

var t_url = "";
var sep= "";

//This is what took me forever to figure out because 1 JSON object is a string and 2+ JSON objects are a array!!!!

if(typeof(fparams.Paras) == 'string'){
//i'm a string
t_url = fparams.Paras + "=" + fuserInput.daInputField;
}else{
//i'm a array
for(var i=0; i<fparams.Paras.length; i++){
t_url += sep+ fparams.Paras[i] + "=" + fuserInput.daInputField[i];
sep = "&";
}
}


//this is for my project but maybe you do something like this...
var z_url = proxy.url;
proxy.url += t_url;
ds.load();
proxy.url = z_url;
t_url = "";

Again I might have just not known that since this is my first time using JSON but maybe someone else has had the same problem...

cheers,
k_lib

sonnyg95
12 Sep 2009, 9:06 PM
Thank you for a great extension!

There's a few changes I would like to do for it to apply to what I am doing. First was just removing clone labels and that wasn't too bad (just add hideLabel : true to cloneConfig). Another change I would like to do is to delete all clones on a form reset. I've tried adding a removeClones function for Ext.form.formpanel but my limited experience (about a month) in extjs (and javascript) isn't helping me at all. I would greatly appreciate it if somoene could take a look at what I have below and point me to the right direction.

What I added into Ext.form.FormPanel is removeClones and findFieldsets.

removeClones that will find fieldsets and then remove them.


removeClones : function(ns, xtype) {

var fieldsets = this.findFieldsets();

//iterate through fieldsets and remove clones
for (var col = fieldsets.length; col > 0; col++) {
if (fieldsets[col].isClone()) {
fieldsets[col].remove();
}
}
},
findFieldsets that should return an array of all fieldsets. I think this is where I am going wrong as this isn't actually returning an array of all fieldset components.


findFieldsets : function () {
return this.findBy(function(cmp) {
return cmp.isXType('fieldset');
});
},
//rest of code
Finally, in Jamie.nicholson's Ext.form.Fieldset, I added a check for clones and the actual removal of the fieldset.

isClone : function() {
if (this.clone) {
return true;
}else {
return false;
}
},
remove : function() {
if (this.dynamic) {
if (this.clone) {
var panel = fieldset.ownerCt;
panel.remove(fieldset,true);
panel.doLayout();
}
}
},

tcp
16 Jan 2010, 9:19 AM
I can't explain why but the code no longer seems to work with Ext 3.1 ( FF and Safari ) as there will be a JS error when removing a field. A quick fix seems to be to perform the remove on the panel first, then remove the item. Below is the modified code for Ext.form.Field.js .



listeners : { 'onIcon' : {fn: function(field) {
var item = Ext.get(field.el.findParent('.x-form-item'));
panel.remove(field);
item.remove();
panel.doLayout();
}}


The fieldset shares this problem and a similar solution seems to make it go bye-bye.

Can anyone else confirm this, or offer a better solution?

rsgjenny09
23 Apr 2010, 7:10 AM
Try to delete a fieldset by clicking on the minus icon, receive recusive 'this.dom is undefined' message. Tried 'Ext.layout.FormLayout.prototype.trackLabels = true;', same error. Did anyone try with Ext 3.2?
No validation apply yet.

Code as below:
xtype: 'fieldset'
,nameSpace: 'splitform'
,maxOccurs: 5
,dynamic: true
,items: [{
xtype: 'numberfield'
,name: 'start'
,fieldLabel: 'Start'
},{
xtype: 'numberfield'
,name: 'end'
,fieldLabel: 'End'

}]

rsgjenny09
23 Apr 2010, 9:27 AM
After further research and test, I was able to make the dynamic fieldset with Ext 3.2 only if the fieldset was added to a Ext.form.FormPanel as first level child.

Joanne
16 Jul 2010, 1:38 PM
I also got the same problem: "parentNode is undefine". Could anyone provide a solution to use the plugin without error if a fieldset is not a first level child of the FormPanel?



Try to delete a fieldset by clicking on the minus icon, receive recusive 'this.dom is undefined' message. Tried 'Ext.layout.FormLayout.prototype.trackLabels = true;', same error. Did anyone try with Ext 3.2?
No validation apply yet.

Code as below:
xtype: 'fieldset'
,nameSpace: 'splitform'
,maxOccurs: 5
,dynamic: true
,items: [{
xtype: 'numberfield'
,name: 'start'
,fieldLabel: 'Start'
},{
xtype: 'numberfield'
,name: 'end'
,fieldLabel: 'End'

}]

sagardash
7 Sep 2010, 5:24 AM
Thanks for this plugin... its a very usefull one..
but i am not getting how to write backend page(php) for this..
Here we are getting dynamic fields .. so how to get all those fields in backend to insert in table...

can please help me regarding this.. please

anandejju
10 Oct 2010, 8:46 AM
I also getting the the same error while removing the : "parentNode is undefine". Could anyone provide a solution to use the plugin without error if a fieldset is not a first level child of the FormPanel?

anandejju
10 Oct 2010, 9:02 AM
I am getting an error when i click on Minus button " 'parentNode' is null or not an object". Do you have any solution?

kkavitha05
19 Oct 2010, 10:32 PM
Thanks for this plugin. It is properly working in Ext2.2.Now I updated to 3.2 ,but i am getting an error "(d = c(d)).parentNode is undefined" when i click on Minus button. Do you have any solution?

anjuprema00
19 Oct 2010, 10:46 PM
Hi,

This is exactly what I was looking for. But I am getting an error parentNode not defined. Anyone knows how to solve this? I'm using ext3.2.

parsbin
10 Mar 2011, 1:13 AM
it does n`t support "compositefield".
how can use this for compositefield fields?

parsbin
10 Apr 2011, 3:23 AM
how can use that on Extjs 4 ?
it does n`t work on Extjs4.
thanks

jaibeee
17 Apr 2011, 5:39 AM
This plugin is great but similar to sonny it didn't have one basic method that would complete it's greatness.

I have fixed sonny's idea on implementing a removeClones in the Ext.form.FormPanel override.

Here is how I did it.

I added removeClones into Ext.form.FormPanel which as it says removes cloned components from the panel.



removeClones : function(ns, xtype) {
Ext.each(this.findFormComponents(ns, xtype), function(fst) {
if(fst.isClone()) {
fst.remove();
}
}, this);
}


Then in Ext.form.Fieldset and Ext.form.Field add a check for clones and the actual removal of the component .

The isClone method is just a convenient accessor to determine if a component is a clone.



isClone : function() {
if (this.dynamic && this.clone) {
return true;
}else {
return false;
}
}


The remove method just removes the component from it's parent and call doLayout to reflect the removed component.



remove : function() {
var panel = this.ownerCt;
panel.remove(this, true);
panel.doLayout();
}


Finally, now you can remove all clones by calling removeClones. I used this method in my Ext.form.FormPanel reset method which works great.

I hope this is useful to someone. Please let me know if there are any problems with it.

Arnold, I know it's ages since you wrote this plugin but I think it's a great plugin and wouldn't mind you including this in your release. I hope that as I continue removing bugs or add good-to-have features that it improves what you started with.

darestyan
10 Oct 2012, 12:46 AM
how to count already clones of fieldset? and checking with maxOccurs configuration.
if there is already clones and less than maxOccurs then set alert "fieldset must be added of maxOccurs!"

thanks before.

lamasgergo
17 Dec 2012, 6:58 AM
HI!!!

Yeah this is a great plugin. But in Ext 3.x dynamic fields remove not works because i have got same error up before.

I think its not a big problem if yuo override Ext.Element insertAfter function. Becaouse the element has not parentNode so this cause an error.
Just check if its exists than insertBefore.


Ext.override(Ext.Element,{


insertAfter:function(el){
if((el = Ext.getDom(el)).parentNode) (el = Ext.getDom(el)).parentNode.insertBefore(this.dom, el.nextSibling);
return this;
}
}
);