<article> <h1 class="post-title">Weekly Widget 1 - TreeCombo</h1> <div class="author-time">18 January 2013 by justinbmeyer</div> <p>I, <a href="http://twitter.com/justinbmeyer">@justinbmeyer</a>, am going to post a weekly widget made with CanJS. I hope to continue this for as long as I have widgets to write. If you <strong>want to see something</strong>, please tweet it to <a href="http://twitter.com/justinbmeyer">@canjs</a>. These posts are going to be quick and dirty. Eventually, I will put these up on <a href="http://canjs.us/recipes.html">CanJS’s recipe page</a> with a full description. </p> <p>I’m starting with the <code>TreeCombo</code> below:</p> <iframe style="width: 100%; height: 300px" src="http://jsfiddle.net/sTLhX/35/embedded/result,html,js" allowfullscreen="allowfullscreen" frameborder="0">JSFiddle</iframe> <h2 id="what-it-does">What it does</h2> <p>The tree combo allows you to select items within and navigate through a hierarchial collection of data. Click “→” to see child items. Click the breadcrumb navigation at the top to return to parent items.</p> <p>It also displays a list of the items the user has selected under “You’ve selected:”.</p> </article> <article> <h1 class="post-title">Weekly Widget 2 - 2-Way Mustache Helpers </h1> <div class="author-time">27 January 2013 by justinbmeyer</div> <p>This article shows how to make 2-way binding mustache helpers and use them in a basic form.</p> <p>By default, CanJS supports 1-way binding templates. 1-way binding means that if data changes, the template is automatically updated. For example, if <code>attendee.name</code> changes, the following input’s value is updated: </p> <pre><code class="xml"><span class="tag"><<span class="title">input</span> <span class="attribute">type</span>=<span class="value">'text'</span> <span class="attribute">value</span>=<span class="value">"{{attendee.name}}"</span>/></span> </code></pre> <p>2-way binding means the reverse is also true. If the user types a new value, the <code>attendee.name</code> is automatically updated. This article shows how roll your own 2-way mustache helpers that can be used like:</p> <pre><code class="xml"><span class="tag"><<span class="title">input</span> <span class="attribute">type</span>=<span class="value">'text'</span> {{<span class="attribute">value</span> <span class="attribute">attendee.name</span>}}/></span> <span class="tag"><<span class="title">input</span> <span class="attribute">value</span>=<span class="value">"yes"</span> <span class="attribute">type</span>=<span class="value">"radio"</span> {{<span class="attribute">checked</span> <span class="attribute">attendee.attending</span>}}/></span> </code></pre> </article> <article> <h1 class="post-title">Weekly Widget 3 - Paginated Grid </h1> <div class="author-time">01 February 2013 by justinbmeyer</div> <p>This weekly widget was suggested by <a href="https://twitter.com/Cherif_b/status/296765112386203649">@Cherif_b</a>. The article covers how to create a basic paginated grid. But even more important, it gives a glipse at how we organize large applications. </p> <h2 id="the-app">The app</h2> <p>The app allows you to paginate through a list of links:</p> <iframe style="width: 100%; height: 300px" src="http://jsfiddle.net/SyEXx/4/embedded/result,html,js" allowfullscreen="allowfullscreen" frameborder="0">JSFiddle</iframe> <h2 id="what-it-does">What it does</h2> <p>You can go through each page by clicking the “Next” and “Prev” links. Those buttons become gray when clicking on one would move to a page without data.</p> <p>The app also supports bookmarking of the current page and forward/back button support.</p> </article>
(function(){ /** * $('.body').more({ * moreHTML: "<a href='javascript://' class='more'>...</a>", * moreWidth: 50, * lessHTML: " <a href='javascript://' class='less'>less</a>", * lines: 2 * }) */ $.fn.more = function(options){ // setup defaults options = $.extend({ lessHTML: " <a href='javascript://' class='less'>-</a>", moreHTML: " <a href='javascript://' class='more'>+</a>", moreWidth: 50, lines: 2 },options||{}); this.each(function(el){ var $el = $(this); // save current HTML for later $el.data('originalHTML', $el.html()) // the active range we will be moving around var range = $el.range(), // the end of the body's text for comparison end = range.clone().collapse(false).start("-1"), // the start of the body's text for comparison start = nextChar(range.collapse().end("+1"),end).clone(), // the current line's first character's coordinates prevRect = start.rect(), // how many lines we've come across lines = 0; // go until we reach the end of the body while(range.compare("START_TO_START",end) != 0){ range.end("+1").start("+1"); var rect = range.rect(); // if the charcter is on a new line if( rect && (rect.top -prevRect.top > 4) ) { lines++; // quit on second line if(lines == options.lines){ break; } prevStart = range.clone() prevRect = rect; } } if(lines === options.lines){ // backup to previous line range.end('-1').start('-1'); } // get the last visible text element prevChar(range, start) var movedLeft = false, offset = $el.offset(), width = $el.width(); // keep moving back until there is room for more while(range.compare("START_TO_START",start) != -1 ){ if( range.rect(true).left <= (offset.left+width-options.moreWidth) ) { break; } movedLeft = true; range.end("-1").start("-1") } // exit if we don't need to add more button if(!movedLeft && (lines < options.lines ) ) { return } var parent = range.start().container; // remove remaining text if( parent.nodeType === Node.TEXT_NODE || parent.nodeType === Node.CDATA_SECTION_NODE ) { parent.nodeValue = parent.nodeValue.slice(0,range.start().offset+1) } var removeAfter = parent; // remove everything after while(removeAfter !== this){ var parentEl = removeAfter.parentNode, childNodes = parentEl.childNodes, index = $.inArray(removeAfter,childNodes ); for(var i = parentEl.childNodes.length-1; i > index; i--){ parentEl.removeChild( childNodes[i] ); } removeAfter = parentEl; } // add more after parent if( parent.nodeType === Node.TEXT_NODE || parent.nodeType === Node.CDATA_SECTION_NODE ) { parent = parent.parentElement } $(parent).append(options.moreHTML); $el.data('shortenedHTML',$el.html()) // show more / hide listeners .on("click","a.more",function(){ $el.html($el.data('originalHTML')+options.lessHTML) }) .on("click","a.less",function(){ $el.html($el.data('shortenedHTML')) }); }) }; // Moves the range until it hits something other than a space var nextChar = function(range, boundary){ while(/[\s\n]/.test(range) && range.compare("END_TO_END",boundary) != 1){ range.start("+1").end("+1") } return range; }; // Moves the range until it hits something other than a space var prevChar = function(range, boundary){ while(/[\s\n]/.test(range) && range.compare("START_TO_START",boundary) != -1){ range.start("-1").end("-1") } return range; } })(); $("article").more({lines: 5, moreWidth: 20})