Thursday, February 02, 2006

Prototype: Form Serialization and Deserialization

In the web community Prototype is a "must" for who wants to implement unobstrusive javascript and web2.0 applications. Why? 'Cause prototype makes things simplest and more Object Oriented.
The fact is that Javascript is often used as a "functional language" and not Object Oriented. Maybe because Javascript is not a clean OO language or maybe because, for historical reasons, people used it to handle (most of the time) the "onclick" event...
With the revolution of these days, javascript is becoming a way to enhance the user experience in web applications (take Ajax for example...).
So here's it: Prototype is coming to help us develop web2.0 application, using Objects in a more simple way, introducing classes for Ajax integration and much more. Maybe one of these days I'll write a good Prototype in Action post, but for the moment use this link as reference:

http://www.sergiopereira.com/articles/prototype.js.html

Update: now Prototype has its own official documentation. Read it here: http://www.prototypejs.org/learn.

Ok...now that you have "the tool" let's see an interesting feature: with prototype you can serialize a form given its id with a simple call:

var serializedForm = Form.serialize('myform');

This function allow you transform a form in a encoded string, containing all values from the passed form.
Ok, now you can save your form (for example via AJAX on your server...), but what you can do if you want to deserialize back it to your client? No way with the current version of Prototype (1.4)!
So I wrote a simple extension to allow you deserialize forms given a serialized string.
Here is a simple example:










radio one

radio two

radio three

Check box 1

Check box 2












The call to the function is simple:
Form.deserialize(formElement, serializedString)

You can find the .js file here
A complete example can be downloaded here

22 Comments:

Anonymous Anonymous said...

modified to ensure that multi-select textboxes get selected properly.

@@ -82,6 +82,7 @@
selectMany: function(element, data) {
for(i = 0; i < element.options.length; i++) {
op = element.options[i];
+ op.selected = false;
if(op.value == data)
op.selected = true;
}

June 27, 2006 7:30 AM  
Blogger Madchicken said...

Thank you very much, I modified the source code for the download.

June 29, 2006 9:35 AM  
Anonymous MattJ said...

When using your script, I found it difficult, in my situation to produce the same form that you had as your example, ie with the appropriate IDs on every input. Instead, I ended up modifying the script to work with names rather than IDs. In case you are interested, it is available here.

September 20, 2006 8:25 PM  
Blogger Madchicken said...

Thank you very much mattj. Nice work...I think we could modify this source code to allow both solutions to live togheter, maybe using a boolean parameter in the deserialize method and then switching to the name or to the id. What do you think?

September 21, 2006 8:58 AM  
Anonymous Anonymous said...

What about cross browser...it shows errors on IE and does not work. Anyone?

November 03, 2006 4:45 AM  
Blogger Madchicken said...

Hi...the script IS cross browser, so you can download and use it without problem. There is an error here in this page, I will fix it as soon as possible.

November 15, 2006 1:05 PM  
Anonymous Wesley said...

An excellent post...just what I was looking for. I just wanted to let you know that there is indeed one error in the js file:

Object.extend(Form, {
deserialize: function(form, data) {
data = decodeURIComponent(data);
var tokens = data.split('&');

tokens.each(
function(input, index) {
var data = input.split('=');
var id = data[0];
var value = data[1];
if(id != form.id && value != 'undefined' && value != null)
Form.Element.deserialize(id, value);
}
);
}, <-- no comma here
});

It's an easy fix. This is a great addition to the prototype library, and did exactly what I needed. Thanks for sharing! Nice site.

February 20, 2007 4:21 AM  
Blogger Madchicken said...

Thank you very much Wesley. I fixed the problem and uploaded the new js file on the server. I think this was the error showing on IE...

February 20, 2007 8:31 AM  
Anonymous Anonymous said...

I have a question. Suppose someone has a street address like 5th & Main. The user has decided to use the & character in the address which is a common problem and I have to let them use it, but Form.Deserialize splits on the & character.

var tokens = data.split('&');

any suggestions on how to deal with this?

thanks
Nick

March 07, 2007 8:26 PM  
Blogger Madchicken said...

Thank you Nick. This was a bug and was not so complicated to be solved. I updated this post and I also made a new one to announce the new release.

March 08, 2007 10:11 AM  
Blogger Ben said...

I have been trying to use this and I can not seem to get it to work with multi-select boxes. I dont know if it has to do with the values having spaces in them or not. I tried removing the spaces and it would select the last one that was selected but it would only select one

August 02, 2007 3:11 AM  
Blogger Ben said...

I cant determine how to edit a post so I am following up.
I found out why it is only selecting one and not others.
It is going though the entire select box again and is setting the selected = false to ones that have possibly already been set to true.
So what I did was before deserialization occurs I clear the form and do not reset the selected = false

so undo the change recommended earlier to have op.selected = false

August 02, 2007 5:11 AM  
Anonymous Anonymous said...

another bug: if inputs have different value for the name and id attribute, it doesn't work.

August 09, 2007 4:07 PM  
Blogger Madchicken said...

Ok guys, it should be working (again). Sorry for these bugs, I think the new version of prototype changed the way to manage form inputs...

August 13, 2007 12:02 PM  
Blogger Gliebster said...

Thanks a lot for this.

I think I found a bug. I wasn't able to get [a href=""]Whatever[/a] (excuse my brackets) to deserialize (textarea). It was getting cutoff at the =. I did this to fix it.

tokens.each(
function(data, index) {
data = data.split('=');
var id = data[0];
var value = decodeURIComponent(data[1]);
if(id != form.id && value != 'undefined' && value != null)
Form.Element.deserialize(form, id, value);
}
);

December 10, 2007 11:35 PM  
Anonymous Art Harrison said...

Thanks for the awesome code - exactly what I needed (I can't believe more people haven't needed this..)

Anyway, I found one improvement. I use checkbox arrays in some of my forms (checkboxes with the same id/name - just different values). The current js won't handle these elements.
I played around and found that if you use the same code as the radio button code instead of 1-1 compare, everything works fine (plus the array method also works fine for regular one-off checkboxes.)

So, just change the inputSelector: code to the following

****

inputSelector: function(element, data) {

var name = element.name;
var checkboxs = Form.getInputs(element.form, 'checkbox', element.name);
for(var i = 0, len = checkboxs.length; i < len; i++) {
var checkbox = checkboxs[i];
if(checkbox.value == data)
checkbox.checked = true;
}

},

***

February 24, 2008 6:24 PM  
Blogger Madchicken said...

Thank you very much Art Harrison, I just updated the .js file and the .tgz example archive.

February 25, 2008 10:11 AM  
Blogger solgae said...

Hi, I thank you for your fantastic code, but I'm having some problems.

Right now, I'm serializing to object hash since that is the preferred way since prototype 1.5. Then I convert it to JSON string using toJSON and save that string to the database so I can retrieve it again when I need it.

The problem is, my form selects something on my multi-select elements and checks something on checkboxes by default when you load the page. Since your script does the form.reset, the form will get resetted to whatever it was by default, so when you call deserialize (in my case, I use deserializeJSON) it will apply whatever you serialized before with the default ones, rather than selecting/checking only the ones that you saved. This problem also has something to do with how serialize works - it does not include the checkbox element that you have not checked, so it won't process those elements when you deserialize. Also, it won't deselect the option on the multi-select element that you don't want it selected.

I am using prototype 1.6.0.2.

August 14, 2008 6:05 PM  
Blogger solgae said...

I also found out that checkboxes don't work properly in latest version of Safari on Windows. It won't check on the boxes that is supposed to. Might be a Safari bug.

August 14, 2008 6:51 PM  
Blogger solgae said...

I got it fixed (at least from what I see) and tested on the example page from your blog, modified so it has something selected/checked by default.

Here is the modified code on object.extend Form:

Object.extend(Form, {
deselectCheckBoxes: function(form) {
var elements = Form.getElements(form);
for (var index = 0, len = elements.length; index < len; index++) {
var item = elements[index];
if(item.type.toLowerCase() == 'checkbox') {
var checkboxs = Form.getInputs(item.form, 'checkbox', item.name);
for(var i = 0, len = checkboxs.length; i < len; i++) {
checkboxs[i].checked = false;
}
}
}
},

deserialize: function(form, data) {
form = $(form);
form.reset();
Form.deselectCheckBoxes(form);
(rest of the code)
},

deserializeJSON: function(form, data) {
form = $(form);
form.reset();
Form.deselectCheckBoxes(form);
(rest of the code)
}

});



and add this code into selectMany:

selectMany: function(element, data) {
for(var index = 0, len = element.options.length; index < len; index++) {
element.options[index].selected = false;
}
(rest of the code)

This is working for me as far as I know.

Also, another problem is the input text box will not blank out the default value even though the serialized string points out that the text box should be blanked. Try testing on the test page and serialize it while the text box is empty.

August 14, 2008 8:32 PM  
Blogger solgae said...

Sorry for this constant bugging. I hope this is the last.

Upon reading the inputSelector, I don't see why you need to check the value of the checkbox. Since the only checkbox elements that will be on the serialized string are the ones that are actually checked, there's no need to check on their values. So I removed that line and I confirmed that it works on IE6/7, Firefox 2/3, and Safari in Windows. I haven't tested on opera, but I wasn't going to install another browser on it XD

Here is the complete code:

inputSelector: function(element, data) {
var name = element.name;
var checkboxs = Form.getInputs(element.form, 'checkbox', element.name);
for(var i = 0, len = checkboxs.length; i < len; i++) {
checkboxs[i].checked = true;
}
},


I have confirmed this Form.serialize behavior in prototype 1.5.0.0 and up.

August 14, 2008 9:36 PM  
Blogger Madchicken said...

Thanks solgae, I updated the script, merging and modifying a little your code. I updated the release, here the new post

September 12, 2008 10:14 AM  

Post a Comment

Links to this post:

Create a Link

<< Home