<com:TContent ID="body" > <h1 id="6601">Developer Notes for prototype.js</h1> This guide is based on the <a href="http://www.sergiopereira.com/articles/prototype.js.html"> Developer Notes for prototype.js</a> by Sergio Pereira. <h2 id="6603">What is that?</h2> <p> In case you haven't already used it, <a href="http://prototype.conio.net">prototype.js</a> is a JavaScript library written by <a href="http://www.conio.net">Sam Stephenson</a>. This amazingly well thought and well written piece of <b>standards-compliant</b> code takes a lot of the burden associated with creating rich, highly interactive web pages that characterize the Web 2.0 off your back. </p> <p> If you tried to use this library recently, you probably noticed that documentation is not one of its strongest points. As many other developers before me, I got my head around prototype.js by reading the source code and experimenting with it. I thought it would be nice to take notes while I learned and share with everybody else. </p> <p> As you read the examples and the reference, developers familiar with the Ruby programming language will notice an intentional similarity between Ruby's built-in classes and many of the extensions implemented by this library. </p> <h2 id="6604">Using the <tt>$()</tt> function</h2> <p> The <tt>$()</tt> function is a handy shortcut to the all-too-frequent <tt>document.getElementById()</tt> function of the DOM. Like the DOM function, this one returns the element that has the id passed as an argument. </p> <p> Unlike the DOM function, though, this one goes further. You can pass more than one id and <tt>$()</tt> will return an <tt>Array</tt> object with all the requested elements. The example below should illustrate this. </p> <com:TTextHighlighter Language="javascript" CssClass="source"> <com:TClientScript UsingClientScripts="prado" /> <div id="myDiv"> <p>This is a paragraph</p> </div> <div id="myOtherDiv"> <p>This is another paragraph</p> </div> <input type="button" value=Test1 onclick="test1();" /> <input type="button" value=Test2 onclick="test2();" /> <script type="text/javascript"> /*<![CDATA[*/ function test1() { var d = $('myDiv'); alert(d.innerHTML); } function test2() { var divs = $('myDiv','myOtherDiv'); for(i=0; i<divs.length; i++) { alert(divs[i].innerHTML); } } /*]]>*/ </script> </com:TTextHighlighter> <p> Another nice thing about this function is that you can pass either the <tt>id</tt> string or the element object itself, which makes this function very useful when creating other functions that can also take either form of argument. </p> <h2 id="6605">Using the <tt>$F()</tt> function</h2> <p> The <tt>$F()</tt> function is a another welcome shortcut. It returns the value of any field input control, like text boxes or drop-down lists. The function can take as argument either the element <tt>id</tt> or the element object itself. </p> <com:TTextHighlighter Language="javascript" CssClass="source"> <input type="text" id="userName" value="Joe Doe" /> <input type="button" value=Test3 onclick="test3();" /> <script type="text/javascript"> /*<![CDATA[*/ function test3() { alert($F('userName')); } /*]]>*/ </script> </com:TTextHighlighter> <h2 id="6606">Using the <tt>$A()</tt> function</h2> <p> The <tt>$A()</tt> function converts the single argument it receives into an <tt>Array</tt> object. </p> <p> This function, combined with the extensions for the Array class, makes it easier to convert or copy any enumerable list into an <tt>Array</tt> object. One suggested use is to convert DOM <tt>NodeLists</tt> into regular arrays, which can be traversed more efficiently. See example below. </p> <com:TTextHighlighter Language="javascript" CssClass="source"> <select id="lstEmployees" size="10" > <option value="5">Buchanan, Steven</option> <option value="8">Callahan, Laura</option> <option value="1">Davolio, Nancy</option> </select> <input type="button" value="Show the options" onclick="showOptions();" /> <script type="text/javascript"> /*<![CDATA[*/ function showOptions() { var someNodeList = $('lstEmployees').options; var nodes = $A(someNodeList); nodes.each(function(node) { alert(node.nodeName + ': ' + node.innerHTML); }); } /*]]>*/ </script> </com:TTextHighlighter> <h2 id="6607">Using the <tt>$H()</tt> function</h2> <p> The <tt>$H()</tt> function converts objects into enumerable Hash objects that resemble associative arrays. </p> <com:TTextHighlighter Language="javascript" CssClass="source"> function testHash() { //let's create the object var a = { first: 10, second: 20, third: 30 }; //now transform it into a hash var h = $H(a); alert(h.toQueryString()); //displays: first=10&second=20&third=30 } </com:TTextHighlighter> <h2 id="6608">Enumerating... Wow! Damn! Wahoo!</h2> <p> We are all familar with for loops. You know, create yourself an array, populate it with elements of the same kind, create a loop control structure (for, foreach, while, repeat, etc,) access each element sequentially, by its numeric index, and do something with the element. </p> <p> When you come to think about it, almost every time you have an array in your code it means that you'll be using that array in a loop sooner or later. Wouldn't it be nice if the array objects had more functionality to deal with these iterations? Yes, it would, and many programming languages provide such functionality in their arrays or equivalent structures (like collections and lists.) </p> <p> Well, it turns out that prototype.js gives us the Enumerable object, which implements a plethora of tricks for us to use when dealing with iterable data. The prototype.js library goes one step further and extends the <tt>Array</tt> class with all the methods of <tt>Enumerable</tt>. </p> <a name="Loops"></a> <h3 id="6617">Loops and iterator</h3> <p> In standard javascript, if you wanted to sequentially display the elements of an array, you could very well write something like this. </p> <com:TTextHighlighter Language="javascript" CssClass="source"> <script type="text/javascript"> /*<![CDATA[*/ function showList() { var simpsons = ['Homer', 'Marge', 'Lisa', 'Bart', 'Meg']; for(i=0; i < simpsons.length; i++) { alert(simpsons[i]); } } /*]]>*/ </script> <input type="button" value="Show List" onclick="showList();" /> </com:TTextHighlighter> <p> With our new best friend, prototype.js, we can rewrite this loop like this. </p> <com:TTextHighlighter Language="javascript" CssClass="source"> function showList() { var simpsons = ['Homer', 'Marge', 'Lisa', 'Bart', 'Meg']; simpsons.each( function(familyMember) { alert(familyMember); }); } </com:TTextHighlighter> <p> You are probably thinking "big freaking deal...just a weird syntax for the same old thing." Well, in the above example, yes, there's nothing too earth shattering going on. Afterall, there's not much to be changed in such a drop-dead-simple example. But keep reading, nonetheless. </p> <p> Before we move on. Do you see this function that is being passed as an argument to the <tt>each</tt> method? Let's start referring to it as an <b>iterator</b> function. </p> <h3 id="6618">Your arrays on steroids</h3> <p> Like we mentioned above, it's very common for all the elements in your array to be of the same kind, with the same properties and methods. Let's see how we can take advantage of iterator functions with our new souped-up arrays. </p> <p> Finding an element according to a criteria. <p> <com:TTextHighlighter Language="javascript" CssClass="source"> <script type="text/javascript"> /*<![CDATA[*/ function findEmployeeById(emp_id) { var listBox = $('lstEmployees') var options = $A(listBox.options); var opt = options.find( function(employee) { return (employee.value == emp_id); }); alert(opt.innerHTML); //displays the employee name } /*]]>*/ </script> <select id="lstEmployees" size="10" > <option value="5">Buchanan, Steven</option> <option value="8">Callahan, Laura</option> <option value="1">Davolio, Nancy</option> </select> <input type="button" value="Find Laura" onclick="findEmployeeById(8);" /> </com:TTextHighlighter> <p> Now let's kick it up another notch. See how we can filter out items in arrays, then retrieve just a desired member from each element. <p> <com:TTextHighlighter Language="javascript" CssClass="source"> <script type="text/javascript"> /*<![CDATA[*/ function showLocalLinks(paragraph) { paragraph = $(paragraph); var links = $A(paragraph.getElementsByTagName('a')); //find links that do not start with 'http' var localLinks = links.findAll( function(link) { var start = link.href.substring(0,4); return start !='http'; }); //now the link texts var texts = localLinks.pluck('innerHTML'); //get them in a single string var result = texts.inspect(); alert(result); } /*]]>*/ </script> <p id="someText"> This <a href="http://othersite.com/page.html">text</a> has a <a href="#localAnchor">lot</a> of <a href="#otherAnchor">links</a>. Some are <a href="http://wherever.com/page.html">external</a> and some are <a href="#someAnchor">local</a> </p> <input type=button value="Find Local Links" onclick="showLocalLinks('someText')" /> </com:TTextHighlighter> <p> It takes just a little bit of practice to get completely addicted to this syntax. Next we will go through the available functions with the following example. </p> <h1 id="6602">Enumerable Functions</h1> The sample data for the following examples. <com:TTextHighlighter Language="javascript" CssClass="source"> var Fixtures = { Products: [ {name: 'Basecamp', company: '37signals', type: 'Project Management'}, {name: 'Shopify', company: 'JadedPixel', type: 'E-Commerce'}, {name: 'Mint', company: 'Shaun Inman',type: 'Statistics'} ], Artist: [ 'As I Lay Dying', '36 Crazyfist', 'Shadows Fall', 'Trivium', 'In Flames' ], Numbers: [0, 1, 4, 5, 98, 32, 12, 9] }; var F = Fixtures; </com:TTextHighlighter> <h2 id="6609"><tt>Enumerable.each</tt> function</h2> <p>I used to find myself writing a lot of for loops. Although, Prototype doesn’t by any means eliminate the need to do for loops, it does give you access to what I consider to be a cleaner, easier to read method in each. <com:TTextHighlighter Language="javascript" CssClass="source"> for(var i = 0; i < F.Numbers.length; i++) { Logger.info(F.Numbers[i]); } </com:TTextHighlighter> <p> The <tt>each</tt> function allows us to iterate over these objects Ruby style. </p> <com:TTextHighlighter Language="javascript" CssClass="source"> F.Numbers.each(function(num) { Logger.info(num); }); //Output 0 1 4 5 98 32 12 9 </com:TTextHighlighter> <p>The <tt>each</tt> function takes one argument, an <b>iterator</b> function. This iterator is invoked once for every item in the array, and that item along with the optional index is passed to the iterator. So if we also needed the index we could do something like the code below. </p> <com:TTextHighlighter Language="javascript" CssClass="source"> F.Numbers.each(function(num, index) { Logger.info(index + ": " + num); }); //Output 0: 0 1: 1 2: 4 3: 5 4: 98 5: 32 6: 12 7: 9 </com:TTextHighlighter> <h2 id="6610">Hash key/value pairs</h2> <p>Hashes can be created by wrapping an Object (associative array) in <tt>$H()</tt> and can have their key/value pairs exposed.</p> <com:TTextHighlighter Language="javascript" CssClass="source"> $H(F.Products[0]).each(function(product) { Logger.info(product.key + ": " + product.value); }); //Outputs name: Basecamp company: 37signals type: Project Management </com:TTextHighlighter> <p> We can also directly access the keys and values of a Hash without iterating over it. </p> <com:TTextHighlighter Language="javascript" CssClass="source"> $H(F.Products[1]).keys(); //Outputs name,company,type $H(F.Products[1]).values(); //Outputs Shopify,JadedPixel,E-Commerce </com:TTextHighlighter> <h2 id="6611"><tt>Enumerable.collect</tt> function</h2> <p>The <tt>collect</tt> function allows you to iterate over an <tt>Array</tt> and return the results as a new array. Each item returned as a result of the iteration will be pushed onto the end of the new array.</p> <com:TTextHighlighter Language="javascript" CssClass="source"> var companies = F.Products.collect(function(product) { return product.company; }); Logger.info(companies.join(', ')); // Outputs // 37signals, JadedPixel, Shaun Inman </com:TTextHighlighter> <p>You can even join on the end of the block.</p> <com:TTextHighlighter Language="javascript" CssClass="source"> return F.Products.collect(function(product) { return product.company; }).join(', '); </com:TTextHighlighter> <h2 id="6612"><tt>Enumerable.include</tt> function</h2> <p>The <tt>include</tt> function allows you to check if a value is included in an array and returns true or false depending on if a match was made. Assuming I put up a form asking the user to name some artist in my iTunes playlist, we could do something like the code below. Prime candidate for some conditional madness. </p> <com:TTextHighlighter Language="javascript" CssClass="source"> return F.Artists.include('Britney Spears'); // returns false </com:TTextHighlighter> <h2 id="6613"><tt>Enumerable.inject</tt> function</h2> <p>The <tt>inject</tt> function is good for getting a collective sum from an array of values. For instance, to add up all the numbers. </p> <com:TTextHighlighter Language="javascript" CssClass="source"> var score = F.Numbers.inject(0, function(sum, value) { return sum + value; }); Logger.info(score); //Output 161 </com:TTextHighlighter> <p>The first argument to <tt>inject</tt> is just an initial value that would be added to the sum, so if we added 1 instead of 0, the output would be 162.</p> <h2 id="6614"><tt>Enumerable.findAll</tt> function</h2> <p> When given an Array, the <tt>findAll</tt> function will return an array of items for which the iterator evaluated to true. Basically, it allows you to build a new array of values based on some search criteria. If we wanted to find all products whose type was “E-Commerce” we could do something like the code below. </p> <com:TTextHighlighter Language="javascript" CssClass="source"> var ecom = F.Products.findAll(function(product) { return product.type == 'E-Commerce'; }); Logger.info(ecom[0].company + " produces " + ecom[0].name); //Outputs JadedPixel produces Shopify </com:TTextHighlighter> <p>Note that even if only one match is made, just as in this case, the result is still returned as an array. In that case, <tt>ecom.company</tt> would return <tt>undefined</tt>.</p> <h2 id="6615"><tt>Enumerable.detect</tt> function</h2> <p>Unlike the <tt>findAll</tt> function, the <tt>detect</tt> function will only return the first item for which the expression inside the iterator is true. So, if we wanted to find the first number that was greater than 5 we’d do something like the code below. </p> <com:TTextHighlighter Language="javascript" CssClass="source"> var low = F.Numbers.detect(function(num) { return num > 5 }); Logger.info(low); //Outputs 98 </com:TTextHighlighter> <p>Even though, there are other numbers above 5 in our array, detect only gives us the first match back.</p> <h2 id="6616"><tt>Enumerable.invoke</tt> function</h2> <p>The <tt>invoke</tt> function allows us to pass a method as a string and have that method invoked. For instance, if we wanted to sort our array of artists we’d do something like this:</p> <com:TTextHighlighter Language="javascript" CssClass="source"> [F.Artists].invoke('sort') //Outputs 36 Crazyfist,As I Lay Dying,In Flames,Shadows Fall,Trivium </com:TTextHighlighter> <p>Why not just use <tt>F.Artists.sort</tt>? Well, for the example above we could do just that, but here is where <tt>invoke</tt> shines.</p> <com:TTextHighlighter Language="javascript" CssClass="source"> [F.Artists, F.Letters].invoke('sort'); //Outputs 36 Crazyfist,As I Lay Dying,In Flames,... </com:TTextHighlighter> <p>So we invoked sort for each sub-array. Note that the code below will not work.</p> <com:TTextHighlighter Language="javascript" CssClass="source"> F.Artists.invoke('sort'); </com:TTextHighlighter> <p>The reason this will not work is because it is taking each item in that array and trying to apply sort to it, thus if we wrote it outright, it would look something like this:</p> <com:TTextHighlighter Language="javascript" CssClass="source"> "36 Crazy Fists".sort(); </com:TTextHighlighter> <p>We could however do something like this:</p> <com:TTextHighlighter Language="javascript" CssClass="source"> F.Artists.invoke('toLowerCase'); //Outputs 36 crazyfist,as i lay dying,in flames,shadows ... </com:TTextHighlighter> <p> Now, what about passing arguments to the <tt>invoke</tt> function? The first argument passed to <tt>invoke</tt> is the method to be invoked, and any other arguments beyond that will be passed as arguments to the invoked method.</p> <com:TTextHighlighter Language="javascript" CssClass="source"> F.Artists.invoke('concat', " is awesome ") //Outputs 36 Crazyfist is awesome ,As I Lay Dying is awesome ,... </com:TTextHighlighter> </com:TContent>