tag:www.rhnh.net,2008:/javascript Javascript - Xavier Shay's Blog 2010-07-02T11:39:01Z Enki Xavier Shay notreal@rhnh.net tag:www.rhnh.net,2008:Post/820 2010-07-02T11:39:00Z 2010-07-02T11:39:01Z Ultimate NYTimes jQuery Slidebox <p>The New York Times has a pretty fancy box that slides out when you hit the bottom of an article. It draws attention without being too distracting. Very nice. Here&#8217;s how you can do it yourself with all the trendiest bells and whistles, <span class="caps">CSS</span> animation (with backup jQuery for crippled browsers), and google analytics tracking. See it in the wild over at my other blog <a href="http://www.two-shay.com/articles/why-i-quit-a-six-figure-job">TwoShay</a>, or <a href="http://xaviershay.github.com/nytimes-slider/">jump straight to the demo</a> to grab the code.</p> <p>To start with, some basic skeleton code. I&#8217;m using new HTML5 selectors, you can just use divs if you&#8217;re not that cool.</p><table class="CodeRay"><tr> <td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt> </tt>2<tt> </tt>3<tt> </tt>4<tt> </tt>5<tt> </tt>6<tt> </tt>7<tt> </tt>8<tt> </tt>9<tt> </tt><strong>10</strong><tt> </tt>11<tt> </tt></pre></td> <td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }"><span class="ta">&lt;section</span> <span class="an">id</span>=<span class="s"><span class="dl">'</span><span class="k">slidebox</span><span class="dl">'</span></span><span class="ta">&gt;</span><tt> </tt> <span class="ta">&lt;a</span> <span class="an">name</span>=<span class="s"><span class="dl">'</span><span class="k">close</span><span class="dl">'</span></span><span class="ta">&gt;</span><span class="ta">&lt;/a&gt;</span><tt> </tt> <span class="ta">&lt;h1&gt;</span>Related Reading<span class="ta">&lt;/h1&gt;</span><tt> </tt> <span class="ta">&lt;div</span> <span class="an">class</span>=<span class="s"><span class="dl">'</span><span class="k">related</span><span class="dl">'</span></span><span class="ta">&gt;</span><tt> </tt> <span class="ta">&lt;h2&gt;</span>Sense and Sensibility<span class="ta">&lt;/h2&gt;</span><tt> </tt> <span class="ta">&lt;p</span> <span class="an">class</span>=<span class="s"><span class="dl">'</span><span class="k">desc</span><span class="dl">'</span></span><span class="ta">&gt;</span><tt> </tt> Another book by Jane Austen you will enjoy<tt> </tt> <span class="ta">&lt;a</span> <span class="an">href</span>=<span class="s"><span class="dl">'</span><span class="k">#</span><span class="dl">'</span></span> <span class="an">rel</span>=<span class="s"><span class="dl">'</span><span class="k">related</span><span class="dl">'</span></span> <span class="an">class</span>=<span class="s"><span class="dl">'</span><span class="k">more</span><span class="dl">'</span></span><span class="ta">&gt;</span>Read »<span class="ta">&lt;/a&gt;</span> <tt> </tt> <span class="ta">&lt;/p&gt;</span><tt> </tt> <span class="ta">&lt;/div&gt;</span><tt> </tt><span class="ta">&lt;/section&gt;</span><tt> </tt></pre></td> </tr></table> <table class="CodeRay"><tr> <td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt> </tt>2<tt> </tt>3<tt> </tt>4<tt> </tt>5<tt> </tt>6<tt> </tt>7<tt> </tt>8<tt> </tt>9<tt> </tt><strong>10</strong><tt> </tt>11<tt> </tt>12<tt> </tt>13<tt> </tt>14<tt> </tt></pre></td> <td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }"><span class="c">/*</span> <span class="c">Just</span> <span class="c">the</span> <span class="c">important</span> <span class="c">styles</span> <span class="c">-</span> <span class="c">see</span> <span class="c">the</span> <span class="c">demo</span> <span class="c">source</span> <span class="c">for</span> <span class="c">a</span> <span class="c">fuller</span> <span class="c">account</span> <span class="c">*/</span><tt> </tt><span class="co">#slidebox</span> {<tt> </tt> <span class="ke">position</span>:<span class="vl">fixed</span>;<tt> </tt> <span class="ke">width</span>:<span class="fl">400px</span>;<tt> </tt> <span class="ke">right</span>: <span class="fl">-430px</span>;<tt> </tt> <span class="ke">bottom</span>:<span class="fl">20px</span>;<tt> </tt><tt> </tt> <span class="ke">-webkit-transition</span>: <span class="vl">right</span> <span class="fl">100</span><span class="vl">ms</span> <span class="vl">linear</span>;<tt> </tt>}<tt> </tt><tt> </tt><span class="co">#slidebox</span><span class="cl">.open</span> { <tt> </tt> <span class="ke">right</span>: <span class="fl">0px</span>; <tt> </tt> <span class="ke">-webkit-transition</span>: <span class="vl">right</span> <span class="fl">300</span><span class="vl">ms</span> <span class="vl">linear</span>;<tt> </tt>}<tt> </tt></pre></td> </tr></table> <p>This sets up an absolutely positioned box, hidden off to the right of screen. Adding a class of <code>open</code> to the box using jQuery will trigger a 300ms <span class="caps">CSS</span> animation to slide the box in, nice and smooth. The correct time to do this is when the user scrolls to the last bit of content on the page. What this content is will be dependent on your site, but whatever it is flag it with an id of <code>#last</code>. The following javascript is all we need:</p><table class="CodeRay"><tr> <td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt> </tt>2<tt> </tt>3<tt> </tt>4<tt> </tt>5<tt> </tt>6<tt> </tt>7<tt> </tt>8<tt> </tt>9<tt> </tt><strong>10</strong><tt> </tt>11<tt> </tt>12<tt> </tt>13<tt> </tt>14<tt> </tt>15<tt> </tt>16<tt> </tt>17<tt> </tt>18<tt> </tt>19<tt> </tt><strong>20</strong><tt> </tt>21<tt> </tt>22<tt> </tt>23<tt> </tt>24<tt> </tt>25<tt> </tt></pre></td> <td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">(<span class="kw">function</span> (<span class="pd">$</span>) {<tt> </tt> <span class="c">/* Add a function to jQuery to slidebox any elements */</span><tt> </tt> jQuery.fn.slidebox = <span class="kw">function</span>() {<tt> </tt> <span class="kw">var</span> slidebox = <span class="lv">this</span>;<tt> </tt> <span class="kw">var</span> originalPosition = slidebox.css(<span class="s"><span class="dl">'</span><span class="k">right</span><span class="dl">'</span></span>);<tt> </tt> <span class="kw">var</span> boxAnimations = {<tt> </tt> <span class="ke">open</span>: <span class="kw">function</span>() { slidebox.addClass(<span class="s"><span class="dl">'</span><span class="k">open</span><span class="dl">'</span></span>); },<tt> </tt> <span class="ke">close</span>: <span class="kw">function</span>() { slidebox.removeClass(<span class="s"><span class="dl">'</span><span class="k">open</span><span class="dl">'</span></span>); },<tt> </tt> }<tt> </tt><tt> </tt> <span class="pd">$</span>(window).scroll(<span class="kw">function</span>() {<tt> </tt> <span class="kw">var</span> distanceTop = <span class="pd">$</span>(<span class="s"><span class="dl">'</span><span class="k">#last</span><span class="dl">'</span></span>).offset().top - <span class="pd">$</span>(window).height();<tt> </tt><tt> </tt> <span class="kw">if</span> (<span class="pd">$</span>(window).scrollTop() &gt; distanceTop) {<tt> </tt> boxAnimations.open();<tt> </tt> } <span class="kw">else</span> {<tt> </tt> boxAnimations.close();<tt> </tt> }<tt> </tt> });<tt> </tt> }<tt> </tt><tt> </tt> <span class="pd">$</span>(<span class="kw">function</span>() { <span class="c">/* onload */</span><tt> </tt> <span class="pd">$</span>(<span class="s"><span class="dl">'</span><span class="k">#slidebox</span><span class="dl">'</span></span>).slidebox();<tt> </tt> });<tt> </tt>});<tt> </tt></pre></td> </tr></table> <p>That&#8217;s it! Everything from here on is gravy.</p> <p>To deal with browsers that don&#8217;t support <span class="caps">CSS</span> animations yet, provide a fallback that uses jQuery animation using <a href="http://www.modernizr.com/">Modernizr</a> to detect the browser&#8217;s capabilities:</p><table class="CodeRay"><tr> <td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt> </tt>2<tt> </tt>3<tt> </tt>4<tt> </tt>5<tt> </tt>6<tt> </tt>7<tt> </tt>8<tt> </tt>9<tt> </tt><strong>10</strong><tt> </tt>11<tt> </tt>12<tt> </tt>13<tt> </tt>14<tt> </tt>15<tt> </tt>16<tt> </tt>17<tt> </tt>18<tt> </tt>19<tt> </tt><strong>20</strong><tt> </tt>21<tt> </tt></pre></td> <td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }"><span class="c">/* replacing the boxAnimations definition above */</span><tt> </tt><span class="kw">var</span> boxAnimations;<tt> </tt><span class="kw">if</span> (Modernizr.cssanimations) {<tt> </tt> boxAnimations = {<tt> </tt> <span class="ke">open</span>: <span class="kw">function</span>() { slidebox.addClass(<span class="s"><span class="dl">'</span><span class="k">open</span><span class="dl">'</span></span>); },<tt> </tt> <span class="ke">close</span>: <span class="kw">function</span>() { slidebox.removeClass(<span class="s"><span class="dl">'</span><span class="k">open</span><span class="dl">'</span></span>); },<tt> </tt> }<tt> </tt>} <span class="kw">else</span> {<tt> </tt> boxAnimations = {<tt> </tt> <span class="ke">open</span>: <span class="kw">function</span>() {<tt> </tt> slidebox.animate({<tt> </tt> <span class="ke"><span class="dl">'</span><span class="k">right</span><span class="dl">'</span></span>: <span class="s"><span class="dl">'</span><span class="k">0px</span><span class="dl">'</span></span><tt> </tt> }, <span class="i">300</span>);<tt> </tt> },<tt> </tt> <span class="ke">close</span>: <span class="kw">function</span>() {<tt> </tt> slidebox.stop(<span class="kw">true</span>).animate({<tt> </tt> <span class="ke"><span class="dl">'</span><span class="k">right</span><span class="dl">'</span></span>: originalPosition<tt> </tt> }, <span class="i">100</span>);<tt> </tt> }<tt> </tt> }<tt> </tt>}<tt> </tt></pre></td> </tr></table> <p>A close button is polite, allowing the user to dismiss the slidebox if they are not interested:</p><table class="CodeRay"><tr> <td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt> </tt>2<tt> </tt>3<tt> </tt></pre></td> <td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">slidebox.find(<span class="s"><span class="dl">'</span><span class="k">.close</span><span class="dl">'</span></span>).click(<span class="kw">function</span>() {<tt> </tt> <span class="pd">$</span>(<span class="lv">this</span>).parent().remove();<tt> </tt>});<tt> </tt></pre></td> </tr></table> <p>And finally, no point adding all this shiny without knowing whether people are using it! Google analytics allows us to track custom javascript events, which is a perfect tool for gaining an insight into how the slidebox is performing. It&#8217;s easy to use: simply push a <code>_trackEvent</code> method call to the <code>_gaq</code> variable (defined in the analytics snippet you copy and paste into your layout) and google takes care of the rest. Observe the full javascript code, with tracking added:</p><table class="CodeRay"><tr> <td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt> </tt>2<tt> </tt>3<tt> </tt>4<tt> </tt>5<tt> </tt>6<tt> </tt>7<tt> </tt>8<tt> </tt>9<tt> </tt><strong>10</strong><tt> </tt>11<tt> </tt>12<tt> </tt>13<tt> </tt>14<tt> </tt>15<tt> </tt>16<tt> </tt>17<tt> </tt>18<tt> </tt>19<tt> </tt><strong>20</strong><tt> </tt>21<tt> </tt>22<tt> </tt>23<tt> </tt>24<tt> </tt>25<tt> </tt>26<tt> </tt>27<tt> </tt>28<tt> </tt>29<tt> </tt><strong>30</strong><tt> </tt>31<tt> </tt>32<tt> </tt>33<tt> </tt>34<tt> </tt>35<tt> </tt>36<tt> </tt>37<tt> </tt>38<tt> </tt>39<tt> </tt><strong>40</strong><tt> </tt>41<tt> </tt>42<tt> </tt>43<tt> </tt>44<tt> </tt>45<tt> </tt>46<tt> </tt>47<tt> </tt>48<tt> </tt>49<tt> </tt><strong>50</strong><tt> </tt>51<tt> </tt>52<tt> </tt>53<tt> </tt>54<tt> </tt>55<tt> </tt>56<tt> </tt>57<tt> </tt>58<tt> </tt>59<tt> </tt><strong>60</strong><tt> </tt>61<tt> </tt>62<tt> </tt>63<tt> </tt>64<tt> </tt></pre></td> <td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">(<span class="kw">function</span> (<span class="pd">$</span>) {<tt> </tt> jQuery.fn.slidebox = <span class="kw">function</span>() {<tt> </tt> <span class="kw">var</span> slidebox = <span class="lv">this</span>;<tt> </tt> <span class="kw">var</span> originalPosition = slidebox.css(<span class="s"><span class="dl">'</span><span class="k">right</span><span class="dl">'</span></span>);<tt> </tt> <span class="kw">var</span> open = <span class="kw">false</span>;<tt> </tt><tt> </tt> <span class="c">/* GA tracking */</span><tt> </tt> <span class="kw">var</span> track = <span class="kw">function</span>(label) {<tt> </tt> <span class="kw">return</span> _gaq.push([<span class="s"><span class="dl">'</span><span class="k">_trackEvent</span><span class="dl">'</span></span>, <span class="s"><span class="dl">'</span><span class="k">Slidebox</span><span class="dl">'</span></span>, label]);<tt> </tt> }<tt> </tt><tt> </tt> <span class="kw">var</span> boxAnimations;<tt> </tt> <span class="kw">if</span> (Modernizr.cssanimations) {<tt> </tt> boxAnimations = {<tt> </tt> <span class="ke">open</span>: <span class="kw">function</span>() { slidebox.addClass(<span class="s"><span class="dl">'</span><span class="k">open</span><span class="dl">'</span></span>); },<tt> </tt> <span class="ke">close</span>: <span class="kw">function</span>() { slidebox.removeClass(<span class="s"><span class="dl">'</span><span class="k">open</span><span class="dl">'</span></span>); },<tt> </tt> }<tt> </tt> } <span class="kw">else</span> {<tt> </tt> boxAnimations = {<tt> </tt> <span class="ke">open</span>: <span class="kw">function</span>() {<tt> </tt> slidebox.animate({<tt> </tt> <span class="ke"><span class="dl">'</span><span class="k">right</span><span class="dl">'</span></span>: <span class="s"><span class="dl">'</span><span class="k">0px</span><span class="dl">'</span></span><tt> </tt> }, <span class="i">300</span>);<tt> </tt> },<tt> </tt> <span class="ke">close</span>: <span class="kw">function</span>() {<tt> </tt> slidebox.stop(<span class="kw">true</span>).animate({<tt> </tt> <span class="ke"><span class="dl">'</span><span class="k">right</span><span class="dl">'</span></span>: originalPosition<tt> </tt> }, <span class="i">100</span>);<tt> </tt> }<tt> </tt> }<tt> </tt> }<tt> </tt><tt> </tt> <span class="pd">$</span>(window).scroll(<span class="kw">function</span>() {<tt> </tt> <span class="kw">var</span> distanceTop = <span class="pd">$</span>(<span class="s"><span class="dl">'</span><span class="k">#last</span><span class="dl">'</span></span>).offset().top - <span class="pd">$</span>(window).height();<tt> </tt><tt> </tt> <span class="kw">if</span> (<span class="pd">$</span>(window).scrollTop() &gt; distanceTop) {<tt> </tt> <span class="c">/* Extra protection necessary so we don't send multiple open events to GA */</span><tt> </tt> <span class="kw">if</span> (!open) {<tt> </tt> open = <span class="kw">true</span>;<tt> </tt> boxAnimations.open();<tt> </tt> track(<span class="s"><span class="dl">&quot;</span><span class="k">Open</span><span class="dl">&quot;</span></span>);<tt> </tt> }<tt> </tt> } <span class="kw">else</span> {<tt> </tt> open = <span class="kw">false</span>;<tt> </tt> boxAnimations.close();<tt> </tt> }<tt> </tt> });<tt> </tt><tt> </tt> slidebox.find(<span class="s"><span class="dl">'</span><span class="k">.close</span><span class="dl">'</span></span>).click(<span class="kw">function</span>() {<tt> </tt> <span class="pd">$</span>(<span class="lv">this</span>).parent().remove();<tt> </tt> track(<span class="s"><span class="dl">&quot;</span><span class="k">Close</span><span class="dl">&quot;</span></span>);<tt> </tt> });<tt> </tt> slidebox.find(<span class="s"><span class="dl">'</span><span class="k">.related a</span><span class="dl">'</span></span>).click(<span class="kw">function</span>() {<tt> </tt> track(<span class="s"><span class="dl">&quot;</span><span class="k">Read More</span><span class="dl">&quot;</span></span>);<tt> </tt> });<tt> </tt> }<tt> </tt><tt> </tt> <span class="pd">$</span>(<span class="kw">function</span>() {<tt> </tt> <span class="pd">$</span>(<span class="s"><span class="dl">'</span><span class="k">#slidebox</span><span class="dl">'</span></span>).slidebox();<tt> </tt> });<tt> </tt>})(jQuery);<tt> </tt><tt> </tt><span class="c">/* Google analytics code provides this variable */</span><tt> </tt><span class="kw">var</span> _gaq = _gaq || [];<tt> </tt></pre></td> </tr></table> <p>Tasty. For the entire code and complete styles, see the <a href="http://xaviershay.github.com/nytimes-slider/">demo page</a>.</p> <p><em>Kudos to <a href="http://tympanus.net/codrops/2010/04/13/end-of-page-slide-out-box/">http://tympanus.net</a> for getting the ball rolling.</em></p> tag:www.rhnh.net,2008:Post/791 2008-12-28T17:16:03Z 2008-12-28T17:16:03Z inject and collect with jQuery <p>You know, I would have thought someone had already made an enumerable plugin for <a href="http://jquery.com">jQuery</a>. Maybe someone has. Mine is better.</p> <ul> <li>Complete coverage with screw-unit</li> <li>Interface so consistent with jQuery you&#8217;ll think it was core</li> </ul><table class="CodeRay"><tr> <td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt> </tt>2<tt> </tt>3<tt> </tt>4<tt> </tt></pre></td> <td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">squares = $([1,2,3]).collect(function () {<tt> </tt> return this * this;<tt> </tt>});<tt> </tt>squares // =&gt; [1, 4, 9]<tt> </tt></pre></td> </tr></table> <p><a href="http://github.com/xaviershay/jquery-enumerable/tree/master">It&#8217;s on github</a>. It deliberately doesn&#8217;t have the kitchen sink &#8211; fork and add methods you need, there&#8217;s enough code it should be obvious the correct way to do it.</p> <p>As an aside, it&#8217;s really hard to spec these methods concisely. I consulted the rubyspec project and it turns out they had trouble as well, check out this all encompassing spec for inject: &#8220;Enumerable#inject: inject with argument takes a block with an accumulator (with argument as initial value) and the current element. Value of block becomes new accumulator&#8221;. Bit of a mouthful eh.</p> <p>Post your improvements in the comments.</p> tag:www.rhnh.net,2008:Post/374 2008-01-09T12:35:00Z 2008-04-12T16:13:39Z Unobtrusive live comment preview with jQuery <p>Live preview is shiny. First get your self a <span class="caps">URL</span> that renders a comment. In rails maybe something like the following.</p><table class="CodeRay"><tr> <td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt> </tt>2<tt> </tt>3<tt> </tt>4<tt> </tt>5<tt> </tt>6<tt> </tt>7<tt> </tt>8<tt> </tt>9<tt> </tt></pre></td> <td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }"><span class="r">def</span> <span class="fu">new</span><tt> </tt> <span class="iv">@comment</span> = <span class="co">Comment</span>.build_for_preview(params[<span class="sy">:comment</span>])<tt> </tt><tt> </tt> respond_to <span class="r">do</span> |format|<tt> </tt> format.js <span class="r">do</span><tt> </tt> render <span class="sy">:partial</span> =&gt; <span class="s"><span class="dl">'</span><span class="k">comment.html.erb</span><span class="dl">'</span></span><tt> </tt> <span class="r">end</span><tt> </tt> <span class="r">end</span><tt> </tt><span class="r">end</span><tt> </tt></pre></td> </tr></table> <p>Now you should have a form or div with an ID something like &#8220;new_comment&#8221;. Just drop in the following JS (you may need to customize the <code>submit_url</code>).</p><table class="CodeRay"><tr> <td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt> </tt>2<tt> </tt>3<tt> </tt>4<tt> </tt>5<tt> </tt>6<tt> </tt>7<tt> </tt>8<tt> </tt>9<tt> </tt><strong>10</strong><tt> </tt>11<tt> </tt>12<tt> </tt>13<tt> </tt>14<tt> </tt>15<tt> </tt>16<tt> </tt>17<tt> </tt>18<tt> </tt>19<tt> </tt><strong>20</strong><tt> </tt>21<tt> </tt>22<tt> </tt>23<tt> </tt>24<tt> </tt>25<tt> </tt>26<tt> </tt>27<tt> </tt>28<tt> </tt></pre></td> <td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">$(function() { // onload<tt> </tt> var comment_form = $('#new_comment')<tt> </tt> var input_elements = comment_form.find(':text, textarea')<tt> </tt> var submit_url = '/comments/new' <tt> </tt> <tt> </tt> var fetch_comment_preview = function() {<tt> </tt> jQuery.ajax({<tt> </tt> data: comment_form.serialize(),<tt> </tt> url: submit_url,<tt> </tt> timeout: 2000,<tt> </tt> error: function() {<tt> </tt> console.log(&quot;Failed to submit&quot;);<tt> </tt> },<tt> </tt> success: function(r) { <tt> </tt> if ($('#comment-preview').length == 0) {<tt> </tt> comment_form.after('&lt;h2&gt;Your comment will look like this:&lt;/h2&gt;&lt;div id=&quot;comment-preview&quot;&gt;&lt;/div&gt;')<tt> </tt> }<tt> </tt> $('#comment-preview').html(r)<tt> </tt> }<tt> </tt> })<tt> </tt> }<tt> </tt><tt> </tt> input_elements.keyup(function () {<tt> </tt> fetch_comment_preview.only_every(1000);<tt> </tt> })<tt> </tt> if (input_elements.any(function() { return $(this).val().length &gt; 0 }))<tt> </tt> fetch_comment_preview();<tt> </tt>})<tt> </tt></pre></td> </tr></table> <p>The <code>only_every</code> function is they key to this piece &#8211; it ensures that an <span class="caps">AJAX</span> request will be sent at most only once a second so you don&#8217;t overload your server or your client&#8217;s connection.</p> <p>Obviously you&#8217;ll need <a href="http://jquery.com">jQuery</a>, less obviously you&#8217;ll also need these support functions</p><table class="CodeRay"><tr> <td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt> </tt>2<tt> </tt>3<tt> </tt>4<tt> </tt>5<tt> </tt>6<tt> </tt>7<tt> </tt>8<tt> </tt>9<tt> </tt><strong>10</strong><tt> </tt>11<tt> </tt>12<tt> </tt>13<tt> </tt></pre></td> <td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">// Based on http://www.germanforblack.com/javascript-sleeping-keypress-delays-and-bashing-bad-articles<tt> </tt>Function.prototype.only_every = function (millisecond_delay) {<tt> </tt> if (!window.only_every_func)<tt> </tt> {<tt> </tt> var function_object = this;<tt> </tt> window.only_every_func = setTimeout(function() { function_object(); window.only_every_func = null}, millisecond_delay);<tt> </tt> }<tt> </tt>};<tt> </tt><tt> </tt>// jQuery extensions<tt> </tt>jQuery.prototype.any = function(callback) { <tt> </tt> return (this.filter(callback).length &gt; 0)<tt> </tt>}<tt> </tt></pre></td> </tr></table> <p>Viola, now you&#8217;re shimmering in awesomeness. Demo up soon, but it&#8217;s similar to what you see on this blog (though this blog is done with inline prototype).</p> tag:www.rhnh.net,2008:Post/120 2006-07-23T11:04:00Z 2007-07-17T11:08:12Z DOM Quirks <p>Unobtrusive javascript is undoubtably the nicest way to add Javascript behaviours to a web page. It keeps the <span class="caps">HTML</span> clean and (hopefully) ensures it will degrade properly in older browsers. That said, the methods you generally use for this type of design (see <a href="http://www.onlinetools.org/articles/unobtrusivejavascript/">Unobtrusive Javascript</a> for an excellent introduction) contain a number of quirks you should be aware, of which this article addresses a few. In particular, unexpected or non-obvious behaviour in <code>createElement</code>, <code>appendChild</code>, and <code>getElementsByTagName</code>.</p> <h3>Table of Contents</h3> <ol> <li><a href="#creating">Creating Elements</a></li> <li><a href="#appending">Appending Elements</a></li> <li><a href="#finding">Finding Elements</a></li> <li><a href="#conclusion">Conclusion</a></li> </ol> <h3 id="creating">Creating Elements</h3> <p>The <code>createElement</code> function allows the dynamic creation of <span class="caps">HTML</span> elements. It takes one parameter: the type of element to create. It is used in conjunction with <code>setAttribute</code> to modify the attributes of a new element. Elements created in this way will not actually be displayed in the document until added with <code>appendChild</code>, <code>insertBefore</code> or <code>replaceChild</code>. The following code creates an image (but does not display it):</p><table class="CodeRay"><tr> <td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt> </tt>2<tt> </tt></pre></td> <td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">element = document.createElement(&quot;img&quot;);<tt> </tt>element.setAttribute(&quot;src&quot;, &quot;img1.jpg&quot;);<tt> </tt></pre></td> </tr></table> <p>While support for this is good in the major browsers, there is a small quirk in IE that can cause some pain when creating forms. To quote <a href="http://msdn.microsoft.com/workshop/author/dhtml/reference/methods/createelement.asp"><span class="caps">MSDN</span></a>:</p> <blockquote> <p>Attributes can be included with the sTag as long as the entire string is valid <span class="caps">HTML</span>. You should do this if you wish to include the <span class="caps">NAME</span> attribute at run time on objects created with the createElement method.</p> </blockquote> <p>What this means is that in IE, you can do the following (which is equivalent to the above snippet of code):</p><table class="CodeRay"><tr> <td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt> </tt>2<tt> </tt></pre></td> <td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">str = '&lt;img src=&quot;img1.jpg&quot; /&gt;';<tt> </tt>element = document.createElement(str);<tt> </tt></pre></td> </tr></table> <p>While IE supports the first method shown for most attributes, if you want to set the &#8220;name&#8221; attribute of an element you <strong>must</strong> use the second method. This is a problem since Mozilla will throw an exception on the latter. Thankfully, we can use exception handling for an easy workaround:</p><table class="CodeRay"><tr> <td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt> </tt>2<tt> </tt>3<tt> </tt>4<tt> </tt>5<tt> </tt>6<tt> </tt>7<tt> </tt>8<tt> </tt></pre></td> <td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">try {<tt> </tt> str = &quot;&lt;input name='aradiobutton' type='radio' /&gt;&quot;<tt> </tt> element = document.createElement(str);<tt> </tt>} catch (e) {<tt> </tt> element = document.createElement(&quot;input&quot;);<tt> </tt> element.setAttribute(&quot;name&quot;, &quot;aradiobutton&quot;);<tt> </tt> element.setAttribute(&quot;type&quot;, &quot;radio&quot;);<tt> </tt>}<tt> </tt></pre></td> </tr></table> <h3 id="appending">Appending Elements</h3> <p>Using <code>appendChild</code> (or <code>replaceChild</code>) is the &#8220;correct&#8221; way to add content to a <span class="caps">DOM</span>, rather than the more popular <code>innerHTML</code> property.</p> <p>When using this function to add rows to a table, you should add the rows to a <code>tbody</code> or equivalent tag inside the table, not the <code>table</code> tag itself. Mozilla and Opera will pick up the new rows if you add them directly to the table tag, whereas IE will not.</p> <h3 id="finding">Finding Elements</h3> <p>You can get a collection of all tags of a specific type using the <code>getElementsByTagName</code> function. Not only is this handy for standard unobtrusive javascript behaviours, you can also use it to do cool things like automatically process all elements in a form.</p><table class="CodeRay"><tr> <td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt> </tt>2<tt> </tt>3<tt> </tt>4<tt> </tt>5<tt> </tt>6<tt> </tt>7<tt> </tt>8<tt> </tt></pre></td> <td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">function showData(form) {<tt> </tt> inputs = form.getElementsByTagName(&quot;input&quot;);<tt> </tt> buffer = &quot;&quot;;<tt> </tt> for (i = 0; i &lt; inputs.length; i++)<tt> </tt> buffer += inputs[i].name + &quot;=&quot; + inputs[i].value + &quot;\n&quot;;<tt> </tt><tt> </tt> alert(buffer);<tt> </tt>}<tt> </tt></pre></td> </tr></table> <p>Although it may appear to act like an array, it is very important to remember that the returned object is actually an <code>HTMLCollection</code>. It does not support any array-like functions (concat, splice, etc&#8230;) bar those presented above. This is because the <code>HTMLCollection</code> is a live representation of the page&#8217;s <span class="caps">HTML</span>, and such functions would interfere.</p><table class="CodeRay"><tr> <td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt> </tt>2<tt> </tt>3<tt> </tt>4<tt> </tt>5<tt> </tt></pre></td> <td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">// Assume an empty document<tt> </tt>images = document.getElementsByTagName(&quot;img&quot;); <tt> </tt>// images.length = 0<tt> </tt>addImgElementToDocument(); // function implemented elsewhere <tt> </tt>// images.length = 1;<tt> </tt></pre></td> </tr></table> <p>This can be an annoyance when we know that the <span class="caps">HTML</span> structure will not be changing, and is easily worked around:</p><table class="CodeRay"><tr> <td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt> </tt>2<tt> </tt>3<tt> </tt>4<tt> </tt>5<tt> </tt>6<tt> </tt>7<tt> </tt>8<tt> </tt>9<tt> </tt><strong>10</strong><tt> </tt>11<tt> </tt>12<tt> </tt>13<tt> </tt>14<tt> </tt>15<tt> </tt>16<tt> </tt>17<tt> </tt>18<tt> </tt></pre></td> <td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">function collectionToArray(col) {<tt> </tt> a = new Array();<tt> </tt> for (i = 0; i &lt; col.length; i++)<tt> </tt> a[a.length] = col[i];<tt> </tt> return a;<tt> </tt>}<tt> </tt><tt> </tt>function showData(form) {<tt> </tt> elems = form.getElementsByTagName(&quot;input&quot;);<tt> </tt> inputs = collectionToArray(elems);<tt> </tt> elems = form.getElementsByTagName(&quot;select&quot;);<tt> </tt> inputs = inputs.concat(collectionToArray(elems));<tt> </tt> buffer = &quot;&quot;;<tt> </tt> for (i = 0; i &lt; inputs.length; i++)<tt> </tt> buffer += inputs[i].name + &quot;=&quot; + inputs[i].value + &quot;\n&quot;;<tt> </tt> <tt> </tt> alert(buffer);<tt> </tt>}<tt> </tt></pre></td> </tr></table> <p>It would be nice if the <code>collectionToArray</code> function above could be added to <code>HTMLCollection</code>&#8217;s prototype, however for some reason it is read-only.</p> <h3 id="conclusion">Conclusion</h3> <p>These quirks may be minor and their solutions trivial, but it helps to be aware of them when coding any sort of unobtrusive javascript as it can reduce the amount of time you spend debugging seemingly illogical behaviour.</p>