jQuery en grote selecties

Niet zelden loopt de gemiddelde programmeur tegen het probleem op: Grote selecties waardoor een pagina tergend traag en soms zelfs de browser vast loopt. Denk bijvoorbeeld aan een pagina waar het hele klantenbestand op staat? Of een lijst met allen producten erin? Dit zijn slechts 2 voorbeeld van pagina's waarbij het niet ondenkbaar is dat meer 1.000 elementen staan waar een jQuery actie aan gehangen moet worden. Maar hoe pak je nou zoiets aan?

Werken met grote selecties

Maak gebruik van de live functie

De ingebouwde jQuery functie Live laat zich hier uitstekend voor meten. Normaal gesproken wanneer er gebruik wordt gemaakt van de click functie wordt er een event aan het element gehangen, bij veel elementen moet de functie dus vaak uitgevoerd worden, wat de snelheid ten koste gaat. Bij de live functie werkt dit anders. Deze maakt gebruik van bubbling.

Bubbling is dat wanneer er op een element geklikt wordt dat gaat het event steeds een element omhoog om hier ook de events te kunnen uitvoeren. Stel: Je hebt een pagina met daarop 2 DIV's in elkaar met daarin weer een link element. Wanneer er op de link geklikt wordt, wordt het click event voor de link uitgevoerd. Daarna wordt de click voor de 1e DIV uitgevoerd, daarna voor de 2e DIV en als laatste voor de BODY.

De live functie maakt hiervan gebruik. Deze plaatst een event op de body, en houdt vervolgens bekijkt alle events of deze voldoet aan de selectie. Is dat het geval dan wordt de functie pas uitgevoerd. Dit wordt vooral gebruikt bij pagina's waarbij de content dynamisch wordt opgebouwd met behulp van AJAX, maar leent zich ook uitstekend hiervoor.

Voorbeeld

In dit voorbeeld ga ik uit van een klantenbestand met daarop een link per klant. Het klantenbestand telt zo'n 5000 klanten, en er zijn 2 links per klant waardoor er 10000 links komen. Wanneer daarop geklikt wordt moet er een popup komen naar de klantinformatie pagina.

In dit voorbeeld zijn er 1 stukje code die het geheel langzamer maakt:

  1. $(".klant-informatie-link").each( function () {
  2.         $(this).attr('href', '#');
  3.         $(this).text('Klik om te opnenen');
  4. })

Deze code gaat de helft van de links af en doet 2 dingen: De href wordt vervangen door een hekje, en de tekst van de link wordt aangepast. Het vervangen van het hekje kan prima gedaan worden bij een live event, ook al is deze code eigenlijk overbodig in dit geval. Het vervangen van de tekst is soms makkelijk om in jQuery te doen, maar wanneer je die bij 5000 links wilt doen is het beter om dit al meteen in de bron op te nemen.

Kijk goed je code na!

Heel veel code wordt geschreven op momenten dat er nog niet veel data is. Dit voornamelijk omdat er simpelweg geen data is voordat de functie bestaat. Maar hierdoor kunnen simpele functies compleet uit hun verband getrokken worden. Acties die op kleine schaal heel snel zijn kunnen door opschaling heel langzaam worden. Kijk bijvoorbeeld naar het volgende voorbeeld. Met een paar checkboxen is dit prima te doen, maar zodra er 500 checkboxen zijn wordt dit een heel ander verhaal.

Voorbeeld

Bij dit voorbeeld ga ik van een webwinkel uit. Deze heeft in zijn backoffice in één categorie 500 producten staan. Nu is dat natuurlijk vrij veel voor één categorie, maar zeker niet onmogelijk. Per product is er een checkbox aanwezig waar ook weer de nodige jQuery aan hangt. Hiermee kunnen meerdere producten tegelijk bewerkt worden. Er zijn daarom 2 stukje jQuery aanwezig:

  • Maak een totaal van het aantal geselecteerde producten
  • Zorg voor een Selecteer alles vinkje die automatisch uitgaat wanneer niet alles meer geselecteerd is

Code technisch kan dit op een aantal manieren opgelost worden. Omdat de snelheid nogal wat achteruit kan gaan heb ik dit in losse pagina's wat verdeeld.

Voorbeeld 1

Let op: Je browser kan langzaam worden of zelfs crashen bij dit script!

Leuk zo'n waarschuwing, maar waarom gebeurt dit zul je je misschien afvragen? Als je naar het script kijkt en logisch nadenkt is dat heel logisch. Wanneer er op de Selecteer alles knop gedrukt wordt wordt de volgende code uitgevoerd:

  1. $(".checkAll").click( function () {
  2.         $(".product_checkbox").each( function () {
  3.                 $(this).trigger('click');
  4.         });
  5. });

Dit script gaat alle checkboxen af en voert hier het click event uit. Dit click event ziet er zo uit:

  1. $(".product_checkbox").click( function () {
  2.         $totalCount = 0;
  3.         $(".product_checkbox").each( function () {
  4.                 if($(this).is(':checked')) {
  5.                         $totalCount++;
  6.                 }
  7.         });
  8.        
  9.         $("div#product_count span").text($totalCount);
  10. });

Nu denk je misschien: Hij loopt over alle checkboxen heen en kijkt of deze geselecteerd zijn, niks boeiends toch? Op zich niet, ware het niet dat door het Selecteer alles knopje er al over alle checkboxen heen geloopt wordt. In deze loop komt dus nog een loop. Bij 500 checkboxen worden er dus 500 x 500 loops uitgevoerd wat een totaal maakt van 50.000 !!! loops. Dan kijk je er toch wel even anders tegenaan

Het gevaar bij dit soort code is dat dit vaak getest wordt bij kleine selecties, en dan valt het aantal loops wel mee waardoor dit soort problemen niet aan het licht komen. Pas bij het werken met "echte" data komt dit dan boven drijven. Daarom is het belangrijk om met "echte" data te testen, dan zul je veel sneller achter dit soort problemen komen.

Voorbeeld 2

Bij dit voorbeeld geen waarschuwing :-)

Stukken sneller is het niet? Hier wordt er gebruik gemaakt van de kracht van jQuery. De code per checkbox ziet er zo uit:

  1. $(".product_checkbox").click( function () {            
  2.         $("div#product_count span").text( $(".product_checkbox:checked").length );
  3. });

En de code om alle check boxen te selecteren zo:

  1. $(".checkAll").click( function () {
  2.         if($(this).is(':checked')) {
  3.                 $(".product_checkbox").attr('checked', 'checked');     
  4.         } else {
  5.                 $(".product_checkbox").attr('checked', '');
  6.         }
  7.         $("div#product_count span").text( $(".product_checkbox:checked").length );
  8. });

Met de jQuery selector :checked kun je alle elementen selecteren die aangevinkt zijn. Door jQuery's selectie methoden is dit vele malen sneller dan alle checkboxen afgaan en dit één voor één te bekijken. Met het length eigenschap kan dan gekeken worden hoelang de selectie is.

Het tweede stuk code is wat langer geworden. Dit omdat hier nu meer moet plaats vinden wat eerder per checkbox uitgevoerd werd. Eerst wordt er een check gedaan om te kijken of de checkbox geselecteerd is of niet, aan de hand daarvan worden alle andere checkboxen ook geselecteerd of niet. Ook is er nog een regel die de telling regelt van het aantal geselecteerde checkboxen. Dat is nu namelijk nodig omdat dit niet meer per checkbox geteld wordt wanneer er op Selecteer alles gedrukt wordt.