SPServices and jQuery 1.10 (and beyond)

Jul 8, 2014 at 8:04 PM
I've been doing a lot of testing with SPServices 2014.01 with different versions of jQuery. I have experienced a noticeable performance drag when using any jQuery version from 1.10 forward. Take this code snippet for instance....
var strHTML = "";
$().SPServices({
    operation: "GetUserCollectionFromSite",
    async: false,
    completefunc: function(xData, Status) {
        $(xData.responseXML).SPFilterNode("User").each(function() {
            strHTML += "<option value='" + $(this).attr("LoginName") + "'>" + $(this).attr("Name") + "</option>";
        });
        $("#users_SiteUsers").append(strHTMLSiteUsers);
    }
});
When I run it with jQuery 1.10 or later, it takes 3-4 times as long to process the records as when I run it with jQuery 1.8.3 and older versions. Has anyone else noticed a lag like this with later versions of jQuery used with SPServices? If so, do you have any ideas what may be causing it?

Geoff
Jul 9, 2014 at 12:00 AM
Geoff,
I have been using 1.10 and .11, but have not notice any performance issues - but I also was not looking for it :)
Did you by any chance run your code in chrome and try to profile it? Wondering where the lag is (jQuery, SPServices or your code).
Also, look at the net tab and see if the network latency looks the same in your A/B testing.



--
Paul T.

-- Sent from Mobile

Coordinator
Jul 9, 2014 at 12:52 AM
I've been using 1.1x.x as well, and I haven't noticed anything.

A couple of quick observations about your code above:
  • You don't need to use SPFilterNode unless the XML elements are odd, like z:row. I haven't found any other places where I've needed it. But it works everywhere. You can use .find("User") instead.
  • You're doing the call async: false. You should really learn promises. Once you do, your code efficiency will go way up, and it's also fun - makes you feel like a smarter guy in the room. ;+)
M.
Jul 9, 2014 at 4:26 PM
Edited Jul 9, 2014 at 4:45 PM
I tried using ".find" in place of ".SPFilterNode" and there is no noticeable difference. Admittedly, I have no experience with the "Promise" object but I do see more information on it regularly. Alas, I will have to learn it in time ;-) However, I don't believe that will help me here.

I did some more testing today and discovered the lag isn't in the iteration of the returned XML (as I originally suspected) but rather in trying to insert the strHTML I build from it into the DOM. In my example code, I run the ".each()" method to iterate over all of the 'User' elements (>13,000). In every combination of jQuery and SPServices I tried, I'm able to get the XML return in about 3.4 seconds and complete the iteration in about 1 additional second--no problem there. The difference is in the time it takes to append my <select> control (id = 'users_SiteUsers') with the string of options (>13000). As long as I'm running jQuery 1.8.3 or earlier, I can complete the insertion in about 1.9 seconds. As soon as I upgrade to 1.10 or later, the insertion time ramps up to about 67.7 seconds. I tried using the following variants and came up with the same result each time:
    $("#users_SiteUsers").append(strHTML);
    $(strHTML).appendTo("#users_SiteUsers");
    $("#users_SiteUsers").html(strHTML);
I get the same results on IE8 and IE10. I now am sure that none of this is related to SPServices but is certainly good info to have for SPServices users. I will try to get on the jQuery user forum to bring this up but my network configuration is very restricted and to date, I've not been able to get the forum pages to function on my machine. It would be nice to know if you are able to duplicate the issue in your environment with a large list.

FYI, here's the latest version of the test script that includes the time hacks....
var dtTimeStart = new Date();
var lngTimeStart = dtTimeStart.getTime();
var lngTimeReturn;
var lngTimeIterationEnd;
console.log("");
console.log("Start: " + dtTimeStart);
var strHTML = "";
$().SPServices({
    operation: "GetUserCollectionFromSite",
    async: false,
    completefunc: function(xData, Status) {
        lngTimeReturn = new Date().getTime();
        console.log("Seconds to Return: " + (((lngTimeReturn - lngTimeStart) / 1000).toFixed(2)));
        $(xData.responseXML).find("User").each(function() {
            strHTML += "<option value='" + $(this).attr("LoginName") + "'>" + $(this).attr("Name") + "</option>";
        });
        lngTimeIterationEnd = new Date().getTime();
        console.log("Seconds to Iterate Return Data: " + (((lngTimeIterationEnd - lngTimeReturn) / 1000).toFixed(2)));
    }
});
$("#users_SiteUsers").append(strHTML);  //Slow and painful using jQuery >= 1.10.x
var dtTimeEnd = new Date();
console.log("End: " + dtTimeEnd);
console.log("Seconds to append select control: " + (((dtTimeEnd.getTime() - lngTimeIterationEnd) / 1000).toFixed(2)));
console.log("Total Time: " + (((dtTimeEnd.getTime() - lngTimeStart) / 1000).toFixed(2)));

Geoff
Jul 9, 2014 at 5:20 PM
Geof,
Good info.
A few suggestions that may help speed things up.

- append items to DOM every 1000 rows. Maybe even less if it makes a difference.


- use native DOM methods to append rather than jQuery wrapper. I don't think it will matters, but worth a try. Something like:
$("#users_SiteUsers")[0].html = strHTML;
Or maybe a detached DocumentFragment and keep appending the items to it. Then, in the end, just append/attach the fragment to DOM.


- any chance you can use a widget to present your list if items rather than a Select element? Something with an AutoComplete type of UI? That way you don't have to load up so many DOM elements at once. I see you are working with users. Maybe this widget could help (disclosure: I'm the creator of it): people picker - https://github.com/purtuga/SPWidgets/blob/master/documentation/SPWidgets.pickSPUser.md (http://purtuga.github.com/SPWidgets/)


I'm not near a computer (until next week), but wonder what changed in jQuery between those versions.

Hope this helps. Good luck.


--
Paul T.

-- Sent from Mobile

Jul 9, 2014 at 7:18 PM
OK, I think we've narrowed it down to the jQuery 1.1x.x ".append()" and ".appendTo()" methods (they behave the same). I was not able to append options directly to the <select> tag using the innerHTML property (it's an IE thing). So, I converted the placeholder element to a <span>. In my code, I constructed the <select> and all 13K+ of its options, then appended the entire thing to the span element using the innerHTML property and everything works fine. The final results are

Using jQuery 1.8.3
$("#users_SiteUsersSpan").append(strHTML); < 2 seconds to modify DOM
$("#users_SiteUsersSpan")[0].innerHTML = strHTML; < 2 seconds to modify DOM


Using jQuery 1.1x.x....
$("#users_SiteUsersSpan").append(strHTML); 67 seconds to modify DOM
$("#users_SiteUsersSpan")[0].innerHTML = strHTML; < 2 seconds to modify DOM

I have no idea what they changed about the .append() and .appendTo() methods but they are definitely hoarked up.

I will take a closer look at your widget as well.

Thanks for your inputs,

Geoff
Jul 9, 2014 at 7:59 PM
Glad you found a solution. And good investigative work :)

I wonder if jQuery is now first doing a .parseHTML on string inputs before appending. I seem to remember a blogpost indicating that using HTML strings in selections or append type methods would be deprecated (security concerns). They suggest using .parseHTML first to return an array of HTMLElements first and the wrap that with jQuery.



--
Paul T.

-- Sent from Mobile