I try to use the prototype form method called serializeElements, to
get all elements in form and build a JSON var.
It work, but not in the best way.
It method get all fields in a form and build a JSON, but it dont put
JSON inside JSON.
Example:
If i have the follow form:
input name="person.name"
input name="person.age"
input name="person.address.street"
The serializeElements's method build a JSON like this
{ "person.name": "??", "person.age": "??", "person.address.street":
"??" }
With my modified method the JSON will be generated like this
{ name: "??", age: "??", address: { street: "??" } }
Or
{ car: { name: "??", age: "??", address: { street: "??" } } }
What are you think about this method?
Can i add this method in prototype framework? How?
/**
* Serialize a form into a JSON var
*
* How to use:
*
* var json = formToJSON('form_test', { prefix : 'car.', exclude:
['description'] } );
*
* <form id="form_test">
* <input type="text" name="car.id" value="2" />
* <input type="text" name="car.name" value="Z5" />
* <input type="text" name="car.description" value="Sport car" />
* <select name="car.company.id">
* <option value="1" selected="selected">BMW</value>
* <option value="2">Mercedes</value>
* </select>
* <input type="checkbox" name="car.optionals" value="carsound"
checked="checked">Car Sound
* <input type="checkbox" name="car.optionals" value="gps">GPS
* <input type="checkbox" name="car.optionals" value="absbreak"
checked="checked">ABS Break
* </form>
*
* The example above will generate the folow JSON.
*
* { id: '2', name: 'Z5', company: { id: 1 }, optionals: ['carsound',
'absbreak'] }
*/
function formToJSON(form, options) {
elements = $(form).getElements();
if (typeof options != 'object') options = { hash: !!options };
else if (Object.isUndefined(options.hash)) options.hash = true;
var key, value, submitted = false, submit = options.submit;
var data = elements.inject({ }, function(result, element) {
if (!element.disabled && element.name) {
key = element.name;
value = $(element).getValue();
if (value != null && (element.type != 'submit' || (!submitted &&
submit !== false && (!submit || key == submit) && (submitted
= true)))) {
key = ( !Object.isUndefined(options.prefix) ) ?
key.replace(options.prefix,"") : key;
if( Object.isArray(options.exclude) )
if( options.exclude.indexOf(key) != -1 )
return result;
if( key.indexOf(".") != -1 ) {
var processkey = key;
var tree = "";
while( processkey.indexOf(".") != -1 ) {
var newkey = processkey.substring( 0,
processkey.indexOf(".") );
processkey = processkey.replace( newkey + ".", "" );
tree += (tree == "") ? newkey : "." + newkey;
if( eval("result." + tree) == undefined ||
eval("result." + tree) == null )
eval("result." + tree + " = new Object()") ;
if( processkey.indexOf(".") == -1 )
if( processkey in eval( "result." + tree ) ) {
if ( !Object.isArray( eval( "result." + tree + "." +
processkey ) ) )
eval("result." + tree + "." + processkey + " = [
result." +
tree + "." + processkey + "]");
eval("result." + tree + "." + processkey +
".push(value)");
} else
eval("result." + tree + "." + processkey + " =
value");
}
} else
if (key in result) {
if ( !Object.isArray( result[key] ) )
result[key] = [ result[key] ];
result[key].push(value);
} else
result[key] = value;
}
}
return result;
});
return options.hash ? data : Object.toQueryString(data);
};
thx,
Thiago Antonius
on 2008-06-11 20:01
on 2008-06-11 20:22
Wow, first of all, lose the eval()'s.
You don't need to dynamically construct a call to eval() to resolve a
property. Instead of:
eval("someObject." + someProperty)
You should say:
someObject[someProperty]
And this is, of course, chainable:
a[property1][property2]
-Fred
On Wed, Jun 11, 2008 at 1:01 PM, TAOS <thiagoaos@gmail.com> wrote:
> input name="person.name"
> { name: "??", age: "??", address: { street: "??" } }
> * How to use:
> * <option value="2">Mercedes</value>
> * { id: '2', name: 'Z5', company: { id: 1 }, optionals: ['carsound',
> key = element.name;
> if( Object.isArray(options.exclude) )
> processkey = processkey.replace(
>
>
> if ( !Object.isArray( result[key] ) )
> return options.hash ? data : Object.toQueryString(data);
> };
>
>
> thx,
> Thiago Antonius
> >
>
--
Science answers questions; philosophy questions answers.
on 2008-06-11 21:12
This looks like an edge case to me ;)
Prototype.js can accomplish similar conversion quite easily:
Object.toJSON($('someForm').serialize());
Besides that, I don't see a reason to duplicate half of Form#serialize
in here. Why not simply iterate over a resulting object?
- kangax
on 2008-06-11 21:49
How can i lose the eval in this case. The tree's var is a hierach of the iterable key. like person.address.phone.something ... I can't do this someObject['person.address.phone.something'], then i need to use eval
on 2008-06-11 21:56
kangax,
why i will iterate something twice? if in a iterate i can do two
things?
Is more easy i use a prefix in my adpted method to works like this $
('form_test').serialize()
of course the method need get better the code, but its is more useful
then the original.
sorry to my bad english.
on 2008-06-11 22:05
Sorry, i am refering a attribute options var instead prefix.
Is more easy i use a prefix in my adpted method to works like this $
('form_test').serialize()
on 2008-06-11 22:23
In this case, iterating twice simply removes duplication. The method
itself is barely performance "critical" (few item collections with not
too "heavy" calculations). Not repeating yourself is, imo, critical.
As far as "eval" goes, you seem to be confused about property access:
eval("result." + tree + "." + processkey + ".push(value)");
// is "same" as
result[tree][processkey].push(value);
- kangax
on 2008-06-11 23:05
Is not same.
i need to break in each point.
someObject['person.address.phone.something']['property'] = 5 will
generate
{ 'person.address.phone.something': { 'property': 5 } }
using eval will generate
{ person: { address: { phone: { something: { property: 5 } } } } }
this is the diference.
on 2008-06-11 23:22
I too dislike repeat code. Then i am purpose improve this method code and replace the old method. Is easy do whit this adpted method do too what the old do.
on 2008-06-11 23:25
I think I understand what you're trying to do - you want to take form elements in the form of "a.b.c.d" and generate the correct nested JSON object. Since I have a soft spot for recursive algorithms, I'll probably try to implement this on the way home on the train. ;-) But while the underlying structure will be different, there still isn't a good reason to use eval(). Not just because it's slower, but because it's a lot harder to comprehend. All the expressions you're trying to build up and eval() can be expressed with bracket notation and, possibly, string concatenation to build up property strings that refer to form elements. I'll play with this a little tonight. -Fred On Wed, Jun 11, 2008 at 4:04 PM, TAOS <thiagoaos@gmail.com> wrote: > using eval will generate > > > > > why i will iterate something twice? if in a iterate i can do two > > > On 11 jun, 16:12, kangax <kan...@gmail.com> wrote: > > > > - kangax > > > > > > > "??" } > > > > > > > * <input type="text" name="car.name" value="Z5" /> > > > > > * </form> > }; > > > > > submit !== false && (!submit || key == > > > > > return result; > processkey.replace( newkey + ".", "" ); > > > > > if( > > > > > > > > > > result[key] = [ result[key] ]; > > > > > return options.hash ? data : Object.toQueryString(data); > > > > > > > }; > > > > > > > thx, > > > > > Thiago Antonius > > > -- Science answers questions; philosophy questions answers.
on 2008-06-11 23:43
Fair enough, but still makes little sense to use eval:
function toObject(str) {
var result = { };
str.split('.').inject(result, function(parent, child) {
return parent[child] = parent[child] || { };
});
return result;
};
toObject('foo.bar.baz'); // { foo: { bar: baz: { }}}
- kangax
on 2008-06-12 03:48
Right, all the uses of eval were unnecessary.
I came up with a quick utility along these lines, that allows you to
pass in
an object like:
{"a.b.c": 1, "a.b.d":2, "a.b.e[0].f": 3, "a.b.e[1].g": 4}
Which you might get from a Prototype form utility function, and get back
an
exploded object like:
{"a": {"b": {"c":1, "d":2, "e": [{"f": 3}, {"g": 4}]}}}
Which is suitable for passing to Object.toJSON(). It basically just
handles
intermediate objects that can be ordinary values or arrays, but that
handles
all the cases I can think of. Let me know what you think.
function to_exploded_object(object) {
var root = {};
$H(object).each(function(property) {
var current = root,
path = property.key.split("."),
last = path.pop();
function set_and_advance_key(key, value) {
var match = key.match(/^(\w+)(?:\[(\d+)\])?/),
name = match[1],
index = match[2];
if (index) {
index = parseInt(index);
current[name] = current[name] || [];
current[name][index] = current[name][index] || value;
current = current[name][index];
} else {
current[name] = current[name] || value;
current = current[name];
}
}
path.each(function(key) { set_and_advance_key(key, {}); });
set_and_advance_key(last, property.value);
});
return root;
}
-Fred
On Wed, Jun 11, 2008 at 4:43 PM, kangax <kangax@gmail.com> wrote:
>
> toObject('foo.bar.baz'); // { foo: { bar: baz: { }}}
--
Science answers questions; philosophy questions answers.
on 2008-06-12 14:44
How to use this?
json = to_exploded_object( $('form_test').serialize() );
or
json = to_exploded_object( Object.toJSON( $
('form_test').serialize() ) );
Look the result in both examples, this don't work like i expected, but
i get the ideia to don't use eval. thx both.
{ 0:""", 1:"c", 2:"o", 3:"n", 4:"c", 5:"e", 6:"s",
7:"s", 8:"i", 9:"o", 10:"n", 11:"a", 12:"r", 13:"i",
14:"a", 15:".", 16:"c", 17:"a", 18:"r", 19:"r",
20:"o", 21:".", 22:"m", 23:"a", 24:"r", 25:"c",
26:"a", 27:".", 28:"c", 29:"o", 30:"d", 31:"i", 32:"g",
33:"o", 34:"=", 35:"2", 36:"0", 37:"&",
38:"c", 39:"o", 40:"n", 41:"c", 42:"e", 43:"s",
44:"s", 45:"i", ....
on 2008-06-12 14:49
This don't work too
var json = toObject( $('form_test').serialize() );
this result undefined in the second object in hirarch.
I will adapted my method, leave eval, and make with that do what
prototype method do in default, and with option do more.
on 2008-06-12 15:13
You passed the serialized string into the function, which won't work.
(It'll iterate through the characters, as you can see.)
There isn't a Prototype function that collects a form's values into an
object, but it's pretty simple to write one:
function to_object(form) {
return $(form).getElements().inject({}, function(object, element) {
o[e.name] = $F(e);
return o;
});
}
This doesn't handle multiple valued elements like checkboxes and
multiselects, but it gives the basic idea. You'd then pass the result
of
that to to_exploded_object() to handle the rest:
var object = to_exploded_object(to_object('form_test'));
I might refactor this a bit into a single function. I think it has a
pretty
important use-case: where you want to submit a JSON request directly
from a
form in HTML/JS without going through a server-side framework that
handles
this unpacking for you.
-Fred
On Thu, Jun 12, 2008 at 7:43 AM, TAOS <thiagoaos@gmail.com> wrote:
> Look the result in both examples, this don't work like i expected, but
>
> >
> > all the cases I can think of. Let me know what you think.
> > var match = key.match(/^(\w+)(?:\[(\d+)\])?/),
> > current = current[name];
> > -Fred
> > > return result;
> > > };
> >
> > > toObject('foo.bar.baz'); // { foo: { bar: baz: { }}}
> >
> > --
> > Science answers questions; philosophy questions answers.
> >
>
--
Science answers questions; philosophy questions answers.
on 2008-06-12 15:36
Here's an updated version of my to_exploded_object function, that takes
a
form instead of an object, so you can pass the form in in one shot. It
still doesn't handle multivalued inputs, but it does handle arrays.
function toJSONObject(form) {
var object = {};
Form.getElements(form).each(function(element) {
var current = object,
path = element.name.split("."),
last = path.pop();
function advanceToNextNode(key, value) {
var match = key.match(/^(\w+)(?:\[(\d+)\])?/),
name = match[1],
index = match[2];
if (index) {
index = parseInt(index);
current[name] = current[name] || [];
current[name][index] = current[name][index] || value;
current = current[name][index];
} else {
current[name] = current[name] || value;
current = current[name];
}
}
path.each(function(key) { advanceToNextNode(key, {}); });
advanceToNextNode(last, Form.Element.getValue(element));
});
return object;
}
Object.toJSON(toJSONObject('form_test'));
(The function still returns an object, not a JSON string, because there
are
a lot of use-cases for keeping it in that form as well.)
-Fred
--
Science answers questions; philosophy questions answers.
on 2008-06-12 15:52
Now i fix my method. It work like a prototype serialize method without options and with ths option hierarchy = true work like my way, broke in each point. examples with this form: Look here http://pastebin.com/f41ba8348 to view the code with better look. <form id="form_person"> <input name="person.name" value="Thiago"/> <input name="person.age" value="23"/> <input name="person.address.street" value="Marketplace Street" /> <select name="person.country.id"> <option value="BR">BR</option> <option value="EUA">EUA</option> </select> <input type="checkbox" name="person.intersting" value="Prototype" checked="checked"/> <input type="checkbox" name="person.intersting" value="Ruby" checked="checked"/> <input type="checkbox" name="person.intersting" value="Java" /> </form> formToJSON('form_person'); "person.name=Thiago&person.age=23&person.address.street=Marketplace %20Street&person.country.id=BR&person.intersting=Prototype&person.intersting=Ruby" formToJSON('form_person', {}); { person.name:"Thiago", person.age:"23", person.address.street:"Marketplace Street", person.country.id:"BR", person.intersting:["Prototype", "Ruby"] } formToJSON('form_person', { hierarchy: true, prefix : 'person.', exclude: ['intersting'] } ); { name:"Thiago", age:"23", address:{street:"Marketplac..."}, country:{id:"BR"} } And finally the method. No repeat code, and no eval function formToJSON(form, options) { elements = $(form).getElements(); if (typeof options != 'object') options = { hash: !!options }; else if (Object.isUndefined(options.hash)) options.hash = true; var key, value, submitted = false, submit = options.submit; var data = elements.inject({ }, function(result, element) { if (!element.disabled && element.name) { key = element.name; value = $(element).getValue(); if (value != null && (element.type != 'submit' || (!submitted && submit !== false && (!submit || key == submit) && (submitted = true)))) { function set_key(key, obj, value) { if (key in obj) { if ( !Object.isArray( obj[key] ) ) obj[key] = [ obj[key] ]; obj[key].push(value); } else obj[key] = value; return obj; } function is_exclude_key(key) { if( Object.isArray(options.exclude) && options.exclude.indexOf(key) != -1 ) return true; return false; } if( !Object.isUndefined(options.hierarchy) && options.hierarchy ) { key = ( !Object.isUndefined(options.prefix) ) ? key.replace(options.prefix,"") : key; if( is_exclude_key(key) ) return result; if( key.indexOf(".") != -1 ) { var newkey = key.substring( 0, key.indexOf(".") ); key = key.replace( newkey + ".", "" ); function set_and_advance_key(processkey, obj, value) { if( processkey.indexOf(".") != -1 ) { var newkey = processkey.substring( 0, processkey.indexOf(".") ); processkey = processkey.replace( newkey + ".", "" ); obj[newkey] = set_and_advance_key( processkey, {}, value ) } else obj = set_key(processkey, obj, value); return obj; } result[newkey] = set_and_advance_key(key, {}, value); } else result = set_key(key, result, value); } else if( !is_exclude_key(key) ) result = set_key(key, result, value); } } return result; }); return options.hash ? data : Object.toQueryString(data); }; and now, with yout tips i think that the method is better. How can i get more better yet?
on 2008-06-12 21:06
Find don't you just define a toJSON method for forms and form elements. Prototype's implementation was designed with that in mind. See http://prototypejs.org/learn/json for details. (About midways) Best, Tobie
on 2008-06-12 22:25
I don't understand what you want to tell me. the toJSON method is diferent that the method that i do. Did you refer about the method's name? formToObject is better?
on 2008-06-12 22:51
Tobie suggests that extending form elements with such method would
make more sense in your case:
Element.addMethods('form', {
toJSON: function(element) {
// your logic here
}
});
...
Object.toJSON($('someForm'));
// or:
$('someForm').toJSON();
- kangax
on 2008-06-12 23:03
Ok, i will extend form element, but first i need to be if the method now is better and make sense.