Loop for each

Let's look at another way to loop across the elements of an array.

Try this jQueryMobile page on your computer or phone. You'll need audio.

An array

Here's the array:

  1. var dogz = [
  2.   {
  3.     name: "Barry",
  4.     motto: "Baby, bassets are the best.",
  5.     woof: "barry.mp3"
  6.   },
  7.   {
  8.     name: "Joe",
  9.     motto: "Wanna play? Huh? Wanna play? Huh? Huh?",
  10.     woof: "joe.mp3"
  11.   },
  12.   {
  13.     name: "Lilo",
  14.     motto: "Lie low, like the Lilo. Yeahhh.",
  15.     woof: "lilo.wav"
  16.   },
  17.   {
  18.     name: "Lucy",
  19.     motto: "Lucy loves y'all, y'all!",
  20.     woof: "lucy.mp3"
  21.   },
  22.   {
  23.     name: "Pete",
  24.     motto: "Tired. Sooo tired.",
  25.     woof: "pete.wav"
  26.   }
  27. ];

Figure 1. Array

There are five elements in the array. Each element is an object, with three properties: name, motto, and woof.

Making a display for an array element

The page has one chunk of HTML for each element in the array. Here's the output for one of them:

Output

Figure 2. Display for one dog

Here's the HTML that makes the display:

  1. <div class='ui-body ui-body-d'>
  2.   <h3>Barry</h3>
  3.   <p>Baby, bassets are the best.</p>
  4.   <p>
  5.     <a data-ajax='false' href='barry.mp3' data-role='button'>Woof</a>
  6.   </p>
  7. </div>

Figure 3. HTML that creates the display in Figure 2

Compare it to the display. You can see the <h3> with the dog's name, the <p> with the motto, and the button.

We don't want to actually type in "<h3>Barry</h3>" though. We want to generate the HTML from the data in the array.

We'll take the data for one dog, and insert it into a template. Here's the data for Barry:

  1.   {
  2.     name: "Barry",
  3.     motto: "Baby, bassets are the best.",
  4.     woof: "barry.mp3"
  5.   },

Figure 4. One array element

Here's the template:

  1. <div class='ui-body ui-body-d'>
  2.   <h3>[dog.name]</h3>
  3.   <p>[dog.motto]</p>
  4.   <p>
  5.     <a data-ajax='false' href='[dog.woof]' data-role='button'>Woof</a>
  6.   </p>
  7. </div>

Figure 5. Template

Put the data from Figure 4 into the template in Figure 5, and you get:

  1. <div class='ui-body ui-body-d'>
  2.   <h3>Barry</h3>
  3.   <p>Baby, bassets are the best.</p>
  4.   <p>
  5.     <a data-ajax='false' href='barry.mp3' data-role='button'>Woof</a>
  6.   </p>
  7. </div>

Figure 3 (again). HTML that creates the display in Figure 2

That creates the display we want.

We want to write JavaScript that will use the data for the first dog with the template, then the data for the second dog with the template, data for the third dog with the template, etc. Until we run out of dogs.

When our code instantiates the template for a dog (instantiates ⇒ fancy word for "creates a new version of"), we want to add the code that's created (e.g., Figure 3) to the page. There's a jQuery function for that.

Code for the win

Here's some pseudocode:

  1. For each dog:
  2.   Instantiate the template, i.e., put the dog data into the template
  3.   Append the instantiation to the page

Figure 6. Pseudocode

Here's the code that will do it:

  1. <div id="output-area"></div>
  2. ...
  3. $(document).ready( function(){
  4.   //Loop. Repeat some code, for each dog.
  5.   $.each(dogz, function(index, dog) {
  6.     item_html =
  7.       "<div class='ui-body ui-body-d'>"
  8.       + "<h3>" + dog.name + "</h3>"
  9.       + "<p>" + dog.motto + "</p>"
  10.       + "<p><a data-ajax='false' href='" + dog.woof + "' "
  11.       +     "data-role='button'>Woof</a></p>"
  12.       + "</div>";
  13.     $("#output-area").append(item_html);
  14.   }); //End each loop.
  15.   //Necessary so jQM will render the new button.
  16.   $("#output-area").trigger('create');        
  17. });

Figure 7. Some code

The code runs after the page loads. We're not using Ajax, hence $(document).ready(). You'd do it a little differently if it was an Ajax app.

The loop runs from lines 5 to 14. The body of the loop is the code that runs for each dog. That's lines 6 to 13. Lines 5 and 14 wrap the body of the loop.

Look at line 5:

$.each(dogz, function(index, dog) {

each is a jQuery function that makes it easier to work with arrays. The first argument is the array you want to iterate over (iterate ⇒ fancy word for "loop"). Our array is called dogz, so that's what we put there.

The second argument for each is the function you want to run for each element of the array. index is the index of the element in the array (0, 1, 2, etc.). dog is the element itself. We can use any variable names we want.

Another loop

Here's another example. Try it. Here's the code:

  1. <div id="output-area"></div>
  2. ...
  3. var languages = [
  4.   { name: "Python", description: "Named after Monty." },
  5.   { name: "FORTAN", description: "First language I learned." },
  6.   { name: "PHP", description: "The most common server-side Web language." },
  7.   { name: "Assembler",
  8.     description: "I wrote a lot of this in my first full-time job." },
  9. ];
  10. $(document).on('pageinit', function() {
  11.   $.each(languages, function(not_used_here, lang) {
  12.     $("#output-area").append(
  13.       "<p>" + lang.name + ": " + lang.description + "</p>"
  14.     );
  15.   });
  16. });

Figure 8. Another example

Line 1 is where the output will go.

The array is in lines 3 to 9. There are four elements, each with two properties. I used whitespace differently that I did earlier. JavaScript just don't care.

Line 10 is a replacement for $(document).ready(). Does the same thing: waits to run the code until the page is ready. It's more Ajax-friendly.

Line 11 wraps the loop body. In English:

Loop over the array languages. For each element, execute lines 12 to 14. Put the index of the element into the variable not_used_here. Put the element into the variable lang.

You can call the two variables not_used_here and langindex and dog, key and peele, whatever you like. As long as there are two of them. The second one is always the array element you're messing with this time through the loop.

Line 13 applies a simple template: <p>[lang.name]: [lang.description]</p>. In JavaScript, + concatenates strings (joins them together).

Line 12 appends the instantiated template to output-area, created in line 1.

Back to the dogs

OK, back to our original goal:

  1. $(document).ready( function(){
  2.   //Loop. Repeat some code, for each dog.
  3.   $.each(dogz, function(index, dog) {
  4.     item_html =
  5.       "<div class='ui-body ui-body-d'>"
  6.       + "<h3>" + dog.name + "</h3>"
  7.       + "<p>" + dog.moto + "</p>"
  8.       + "<p><a data-ajax='false' href='" + dog.woof + "' "
  9.       +     "data-role='button'>Woof</a></p>"
  10.       + "</div>";
  11.     $("#output-area").append(item_html);
  12.   }); //End each loop.
  13.   //Necessary so jQM will render the new button.
  14.   $("#output-area").trigger('create');        
  15. });

Figure 7 (again). Some code

Line 3 controls the loop. For each element in dogz, do lines 4 to 11, with the element's array index in index, and the element itself in dog.

Lines 4 to 10 plug the data for a dog into a template, and store the result in item_html. Line 11 appends the contents of item_html to the output area.

There's one problem. As you know, when you add data-role='button' to an <a> tag, jQueryMobile makes the <a> look like a button. In this...

Output

Figure 2 (again). Display for one dog

... the Woof button is created by the <a> tag in lines 8 and 8 of Figure 7:

  1.       + "<p><a data-ajax='false' href='" + dog.woof + "' "
  2.       +     "data-role='button'>Woof</a></p>"

That's done by some JavaScript that's included with jQM.

The problem is, that JS runs before your JS. So, jQM thinks that it's done with all the button creation before even seeing your code. Your code adds some <a> tags, but they aren't changed into buttons, because the tag-to-button presto chango code has already done its thing.

We want to add buttons in code. Once we've appended the new <a> tags, we need to tell jQueryMobile to run its tag-to-button presto chango code on the HTML we just appended.

Fortunately, that's easy. Remember that we created an output area, and added all the new HTML to it:

<div id="output-area"></div>
...

$("#output-area").append(item_html);

We can tell jQM to do its magic on that part of the page:

$("#output-area").trigger('create');

Where does that line go? Here's the code again:

  1. $(document).ready( function(){
  2.   //Loop. Repeat some code, for each dog.
  3.   $.each(dogz, function(index, dog) {
  4.     item_html =
  5.       "<div class='ui-body ui-body-d'>"
  6.       + "<h3>" + dog.name + "</h3>"
  7.       + "<p>" + dog.moto + "</p>"
  8.       + "<p><a data-ajax='false' href='" + dog.woof + "' "
  9.       +     "data-role='button'>Woof</a></p>"
  10.       + "</div>";
  11.     $("#output-area").append(item_html);
  12.   }); //End each loop.
  13.   //Necessary so jQM will render the new button.
  14.   $("#output-area").trigger('create');        
  15. });

Figure 7 (again). Some code

Look at line 14. There it is! Notice that the loop ends at line 12. So we add all the new HTML to the page, and only then tell jQM to do its buttony stuff. We could move line 14 to after line 11, so jQM would do its work after each new piece of HTML. That would work just as well, but it would slow down the page, because trigger would run a bunch o' times, instead of just once.

Summary

Arrays have elements. jQuery's each function runs the same code on each element.