This example demonstrates how to use multiple NodeList features to build a simple game.
The HTML
<div class='gallery'> <ul class="duck-row"> <li></li> <!-- each of these will be a duck --> <li></li> <li></li> <li></li> <li></li> <li></li> <li></li> <li></li> <li></li> <li></li> </ul> <div class="water"> <img class="shadow" src="../assets/node/images/water-shadow.png"/> <img src="../assets/node/images/water.png"/> </div> </div> <button id="button-reset" class="yui3-button">Reset</button> Ducks remaining: <span class="ducks-remain">10</span> <input type="checkbox" id="show-attitude"/><label for="show-attitude">Show attitude</label>
YUI Instance
The use
statement doesn't include node
because it's loaded as a requirement of transition.
YUI().use('transition', 'button', function(Y){ // code goes here });
Setting Vars
The variable ducks
is used for easily manipulating all the ducks at once.
We'll display various duck comments from the array squawkTextArr
on a rotating basis.
var ducks = Y.all('.duck-row li'), ducksRemaining = 10, // value for UI display squawkTextIndex = 0, // index in the squawkTextArr to use next squawkTextArr = [ // duck comments '#@&~*Q!', 'Hey!?', '911 on U!', "U fly's down", 'duck pile!', 'Ricochets kill!', 'how sporting.', "shoe's untied" ];
Initializing the Ducks
Repetitive markup is added with the .append()
method to all the ducks in the NodeList
.
This keeps the original markup simple and clear.
// append the same content for each duck <li> ducks.append('<img src="../assets/node/images/duck.png"/><div class="squawk"><div class="text">#@&~*Q!</div><div class="small-squawk-bubble"></div></div>');
All the ducks in the NodeList
are given the set-up
class with the .addClass()
method.
This class is found on any duck that has the state of being set up, as opposed to being knocked down.
// give them all the set-up state class ducks.addClass('set-up');
This state could be a Boolean property, but it's handy as a class,
because a NodeList
can be made containing the squawks of all "set up" ducks in this way,
squawks = Y.all('.duck-row .set-up .squawk');
as we'll see in the makeDucksSquawk
function.
Making the Ducks Swim
This uses transition
to make the ducks swim right to left
// this makes the ducks move from right to left. // When the duck on the far left disappears from view, // it's added to the far right end of the row. var makeDucksSwim = function () { var frontDuck; // move the duck row to the left one duck space over 2 seconds Y.one('.duck-row').transition({ easing: 'linear', left: '-119px', duration: 2 }, function () { // when the row finishes its right to left transition... // remove the first duck on the left // which has trasitioned out of view frontDuck = Y.one('.duck-row li').remove(); // append the removed first duck onto the right end Y.one('.duck-row').appendChild(frontDuck); // set the position for the next makeDucksSwim() Y.one('.duck-row').setStyle('left', '10px'); // if there are ducks remaining, make them swim again if (ducksRemaining > 0) { makeDucksSwim(); } }); } makeDucksSwim(); // this initializes the ducks swimming
Click Event Handler
// handles a click on a duck var duckClick = function(e) { var squawks; // remove the squawk belonging to the duck that was clicked e.currentTarget.one('.squawk').setStyles({'top': '-400px', 'opacity': '1'}); // makes the ducks appear to lay back when clicked e.target.transition({ duration: 0.2, height: '3px', width: '133px' }); // the clicked duck will no longer have the 'set-up' class/state e.currentTarget.removeClass('set-up'); makeDucksSquawk(); // makes the ducks squawk updateDucksRemaining(); // update the number of ducks still set up };
Squawking Ducks
// this makes the duck's squawks show and hide and get various text var makeDucksSquawk = function(){ squawks = Y.all('.duck-row .set-up .squawk'); // a NodeList of the squawks of set-up ducks if (Y.one('#show-attitude')._node.checked) { // only have ducks squawk if the checkbox is checked // fill voice bubbles with next text string Y.all('.duck-row .set-up .squawk .text').setHTML(squawkTextArr[squawkTextIndex]); // increment the index to get the next squawk text squawkTextIndex = (squawkTextIndex += 1) % (squawkTextArr.length); squawks.transition({ top: { delay: 0.5, value: '0px', // drop squawks into position from hidden duration: 0 // instant position change }, opacity: { // fade out delay: 3.0, duration: 0.3, value: 0 } }, function(e){ // after squawks are faded out, // move them to hidden position and set opacity to 1 again squawks.setStyles({'top': '-400px', 'opacity': '1'}); }); } }
Reset and Ducks Remaining
// This resets all ducks, "ducks remaining" counters, and row position // make the duck images full height // start them swimming var reset = function() { Y.all('.duck-row li img').setStyle('height', '55px'); Y.all('.duck-row li').addClass('set-up'); updateDucksRemaining(); makeDucksSwim(); } // counts the ducks remaining, and updates the UI counter display var updateDucksRemaining = function() { ducksRemaining = Y.all('.gallery li.set-up').size(); Y.one('.ducks-remain').setHTML(ducksRemaining); }
Prefer node.delegate()
over nodelist.on()
Sometimes you need to create individual subscriptions for each Node in a NodeList, but usually it's preferable to use event delegation as shown in this example.
Listeners
// listeners Y.one('.duck-row').delegate('click', duckClick, 'li'); Y.one('#button-reset').on('click', reset);
Complete Ducks Example Source
<style> .gallery{ position: relative; background: url(../assets/node/images/background.png); width: 638px; height: 180px; overflow: hidden; -moz-border-radius: 8px; -webkit-border-radius: 8px; border-radius: 8px; -moz-box-shadow: 0 0 45px #000 inset, 3px 0 6px rgba(0,0,0,0.8); -webkit-box-shadow: 0 0 45px #000 inset, 3px 0 6px rgba(0,0,0,0.8); border: 4px solid #637073; cursor: crosshair; margin: 0.5em 0 0.8em; } .water{ position: absolute; top: 114px; left: 0; width: 677px; height: 50px; } .water img{ position: absolute; top: 0; left: 0; } .water .shadow { left: 8px; opacity: 0.5; filter: alpha(opacity=30); } .duck-row { position: absolute; left: 10px; top: 57px; width: 1340px; padding: 0; margin: 0; } .duck-row li{ position: relative; display: inline-block; zoom: 1; *display: inline; /* IE < 8: fake inline-block */ width: 133px; height: 70px; margin: 0 -4px 0 0; vertical-align: bottom; } .duck-row li img{ position: absolute; bottom: 7px; left: 0; } /* the voice bubble of the ducks */ .duck-row .squawk { position: absolute; top: -400px; left: 50px; -moz-border-radius: 4px; -webkit-border-radius: 4px; border-radius: 4px; background-color: #ffe; line-height: 1.2em; border: solid 1px #cc8; -moz-box-shadow: 2px 2px 2px rgba(0,0,0,0.2); -webkit-box-shadow: 2px 2px 2px rgba(0,0,0,0.2); } .duck-row .squawk .text{ padding: 0.2em 0.4em 0.2em 0em; text-indent: 0.2em; overflow: hidden; } .small-squawk-bubble{ position: absolute; bottom: -4px; left: -8px; width: 4px; height: 4px; border: solid 1px #cc8; -moz-border-radius: 3px; -webkit-border-radius: 3px; border-radius: 3px; background-color: #ffe; font-size: 2px; } #button-reset{ margin-right: 2em; } .ducks-remain{ font-size: 150%; } #show-attitude{ margin-left: 8em; } </style> <div class='gallery'> <ul class="duck-row"> <li></li> <!-- each of these will be a duck --> <li></li> <li></li> <li></li> <li></li> <li></li> <li></li> <li></li> <li></li> <li></li> </ul> <div class="water"> <img class="shadow" src="../assets/node/images/water-shadow.png"/> <img src="../assets/node/images/water.png"/> </div> </div> <button id="button-reset" class="yui3-button">Reset</button> Ducks remaining: <span class="ducks-remain">10</span> <input type="checkbox" id="show-attitude"/><label for="show-attitude">Show attitude</label> <script> YUI().use('transition', 'button', function (Y) { var ducks = Y.all('.duck-row li'), ducksRemaining = 10, // value for UI display squawkTextIndex = 0, // index in the squawkTextArr to use next squawkTextArr = [ // duck comments '#@&~*Q!', 'Hey!?', '911 on U!', "U fly's down", 'duck pile!', 'Ricochets kill!', 'how sporting.', "shoe's untied" ]; // append the same content for each duck <li> ducks.append('<img src="../assets/node/images/duck.png"/><div class="squawk"><div class="text">#@&~*Q!</div><div class="small-squawk-bubble"></div></div>'); // give them all the set-up state class ducks.addClass('set-up'); // this makes the ducks move from right to left. // When the duck on the far left disappears from view, // it's added to the far right end of the row. var makeDucksSwim = function () { var frontDuck; // move the duck row to the left one duck space over 2 seconds Y.one('.duck-row').transition({ easing: 'linear', left: '-119px', duration: 2 }, function () { // when the row finishes its right to left transition... // remove the first duck on the left // which has trasitioned out of view frontDuck = Y.one('.duck-row li').remove(); // append the removed first duck onto the right end Y.one('.duck-row').appendChild(frontDuck); // set the position for the next makeDucksSwim() Y.one('.duck-row').setStyle('left', '10px'); // if there are ducks remaining, make them swim again if (ducksRemaining > 0) { makeDucksSwim(); } }); } makeDucksSwim(); // this initializes the ducks swimming // handles a click on a duck var duckClick = function(e) { var squawks; // remove the squawk belonging to the duck that was clicked e.currentTarget.one('.squawk').setStyles({'top': '-400px', 'opacity': '1'}); // makes the ducks appear to lay back when clicked e.target.transition({ duration: 0.2, height: '3px', width: '133px' }); // the clicked duck will no longer have the 'set-up' class/state e.currentTarget.removeClass('set-up'); makeDucksSquawk(); updateDucksRemaining(); // update the number of ducks still set up }; // this makes the duck's squawks show and hide and get various text var makeDucksSquawk = function(){ squawks = Y.all('.duck-row .set-up .squawk'); // a NodeList of the squawks of set-up ducks if (Y.one('#show-attitude')._node.checked) { // only have ducks squawk if the checkbox is checked // fill voice bubbles with next text string Y.all('.duck-row .set-up .squawk .text').setHTML(squawkTextArr[squawkTextIndex]); // increment the index to get the next squawk text squawkTextIndex = (squawkTextIndex += 1) % (squawkTextArr.length); squawks.transition({ top: { delay: 0.5, value: '0px', // drop squawks into position from hidden duration: 0 // instant position change }, opacity: { // fade out delay: 3.0, duration: 0.3, value: 0 } }, function(e){ // after squawks are faded out, // move them to hidden position and set opacity to 1 again squawks.setStyles({'top': '-400px', 'opacity': '1'}); }); } } // This resets all ducks, "ducks remaining" counters, and row position // make the duck images full height // start them swimming var reset = function() { Y.all('.duck-row li img').setStyle('height', '55px'); Y.all('.duck-row li').addClass('set-up'); updateDucksRemaining(); makeDucksSwim(); } // counts the ducks remaining, and updates the UI counter display var updateDucksRemaining = function() { ducksRemaining = Y.all('.gallery li.set-up').size(); Y.one('.ducks-remain').setHTML(ducksRemaining); } // listeners Y.one('.duck-row').delegate('click', duckClick, 'li'); Y.one('#button-reset').on('click', reset); }); </script>