views:

108

answers:

3

I have a page that selects all the elements in a form and serializes them like this:

var filter = 'form :not([name^=ww],[id$=IDF] *,.tools *)';
var serialized = $(filter).serialize();

This works, unless the form gets around 600+ elements. Then the user gets s javascript error saying that the script is running slow and may make their browsers unresponsive. It then gives them the option to stop running the script.

I have tried running the filters separately, I have tried using .not on the selectors, then serializing them, but I run into one of two problems. Either it runs faster without the error, but also does not filter the elements, or it does filter the elements and gives me the slow script error.

Any ideas?

+1  A: 

Could you maybe just serialize the whole form and do your filtering on the backend? Also, why-oh-why is the form growing to 600+ fields?

thenduks
This would work, but I need to do some logic on the serialized form before doing a postback
Barlow Tucker
Well, I'd try sending back whatever data you need to perform that logic back with the request and again let the backend do it. This way you just do `serialize` on the whole form which should be pretty fast (eg, not on a list, but on a single form element) and then simple show a spinner while the backend hacks away.
thenduks
A: 

use the :input selector to only select applicable elements..

Gaby
I'll give that a try
Barlow Tucker
@barlow, i think my answer might be wrong.. i believe serialize only selects `:input` elements by default... but jquery api site is down at the moment and can't verify..
Gaby
+5  A: 

With 600+ elements this is going to be dead slow. You need to offer Sizzle (jQuery's selector engine) some opportunities for optimisation.

First, consider the fact that jQuery can use the natively-supported querySelectorAll method (in modern browsers) if your selector complies with the CSS3 spec (or at least to the extent of what's currently supported in browsers).

With your case, that would mean passing only one simple selector to :not instead of 3 (1 simple, 2 complex).

form :not([name^=ww])

That would be quite fast... although you're not being kind to browsers that don't support querySelectorAll.

Look at your selector and think about how much Sizzle has to do with each element. First it needs to get ALL elements within the page (you're not pre-qualifying the :not selector with a tag/class/id). Then, on each element it does the following:

(assume that it exits if a result of a check is false)

  1. Check that the parent has an ancestor with the nodeName.toLowerCase() of form.
  2. Check that it does not have a name attribute starting with ww (basic indexOf operation).
  3. Check that it does not have an ancestor with an id attribute ending in IDF. (expensive operation)
  4. Check that it does not have an ancestor with a class attribute containing tools.

The last two operations are slow.

It may be best to manually construct a filter function, like so:

var jq = $([1]);
$('form :input').filter(function(){

    // Re-order conditions so that
    // most likely to fail is at the top!

    jq[0] = this; // faster than constructing a new jQ obj

    return (

        !jq.closest('[id$=IDF]')[0]
            // this can be improved. Maybe pre-qualify
            // attribute selector with a tag name

        && !jq.closest('.tools')[0]

        && this.name.indexOf('ww') !== 0

    );

});

Note: that function is untested. Hopefully you get the idea...

J-P
+1 Nice analysis.
Ken Redler
Wow, looks great! I will give it a try and post the results here. Thanks.
Barlow Tucker
I might add that I could help in optimising this more if I could see the markup. I still have questions regarding it... like: how many `[id$=IDF]` elements are there, and are they all of a different tag? Is there no way they could be given the same class?
J-P
Well, looks like this fixed the javascript error and filters correctly. It is still super slow, but I guess that is expected for what it is doing.I will look into adding a class, or something that could speed this up. It would not be a trivial update though, so I don't think it will be implemented for a while.
Barlow Tucker
Why this.name.indexOf and not jq.name.indexOf?
Barlow Tucker
@Barlow Tucker, because `jq` is a jQuery object and doesn't have a `name` property. `this` refers to the actual DOM element.
J-P