UNPKG

pourover

Version:

A library for simple, fast filtering and sorting of large collections in the browser

1,087 lines (818 loc) 138 kB
<!DOCTYPE html> <html> <head> <title>pourover.js</title> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <meta name="viewport" content="width=device-width, target-densitydpi=160dpi, initial-scale=1.0; maximum-scale=1.0; user-scalable=0;"> <link rel="stylesheet" media="all" href="docco.css" /> </head> <body> <div id="container"> <div id="background"></div> <ul class="sections"> <li id="title"> <div class="annotation"> <h1>pourover.js</h1> </div> </li> <li id="section-1"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-1">&#182;</a> </div> </div> <div class="content"><div class='highlight'><pre>PourOver = {</pre></div></div> </li> <li id="section-2"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-2">&#182;</a> </div> <p>Utility functions. Skip down to &quot;Collections&quot; for the real meat of PourOver.</p> <h1>The basic sorted set operations</h1> </div> <div class="content"><div class='highlight'><pre> union_sorted: <span class="keyword">function</span>(a,b){</pre></div></div> </li> <li id="section-3"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-3">&#182;</a> </div> <p>Make more efficient by just copying at Infinity</p> </div> <div class="content"><div class='highlight'><pre> <span class="keyword">var</span> lowa = <span class="number">0</span>, lowb = <span class="number">0</span>, higha = a.length, highb = b.length, result=[], la, lb; <span class="keyword">while</span> (higha &gt; lowa || highb &gt; lowb){ la = a[lowa]; lb = b[lowb]; <span class="keyword">if</span>(_.isUndefined(la)) la = <span class="literal">Infinity</span>; <span class="keyword">if</span>(_.isUndefined(lb)) lb = <span class="literal">Infinity</span>; <span class="keyword">if</span>(lowa == higha){ <span class="keyword">return</span> result.concat(b.slice(lowb,highb)); } <span class="keyword">if</span>(lowb == highb){ <span class="keyword">return</span> result.concat(a.slice(lowa,higha)); } <span class="keyword">if</span>(la == lb){ result.push(la); lowa++;lowb++; } <span class="keyword">else</span> <span class="keyword">if</span> (la &lt; lb){ result.push(la); lowa++; } <span class="keyword">else</span> { result.push(lb); lowb++; } } <span class="keyword">return</span> result; }, intersect_sorted: <span class="keyword">function</span>(a,b){ <span class="keyword">var</span> lowa = <span class="number">0</span>, lowb = <span class="number">0</span>, higha = a.length, highb = b.length, result=[], la, lb; <span class="keyword">while</span> (higha &gt; lowa &amp;&amp; highb &gt; lowb){ la = a[lowa]; lb = b[lowb]; <span class="keyword">if</span>(la == lb){ result.push(la); lowa++;lowb++; } <span class="keyword">else</span> <span class="keyword">if</span> (la &lt; lb){ lowa++; } <span class="keyword">else</span> { lowb++; } } <span class="keyword">return</span> result; }, subtract_sorted: <span class="keyword">function</span>(a,b){ <span class="keyword">var</span> lowa = <span class="number">0</span>, lowb = <span class="number">0</span>, higha = a.length, highb = b.length, result=[], la, lb; <span class="keyword">while</span> (higha &gt; lowa || highb &gt; lowb){ la = a[lowa]; lb = b[lowb]; <span class="keyword">if</span>(higha == lowa){ <span class="keyword">return</span> result; } <span class="keyword">if</span>(highb == lowb){ <span class="keyword">return</span> result.concat(a.slice(lowa,higha)); } <span class="keyword">if</span>(la == lb){ lowa++;lowb++; } <span class="keyword">else</span> <span class="keyword">if</span> (la &lt; lb){ result.push(la); lowa++; } <span class="keyword">else</span> { lowb++; } } <span class="keyword">return</span> result; }, insert_sorted: <span class="keyword">function</span>(set,element){ <span class="keyword">var</span> length = set.length, i = <span class="number">0</span>, last_elem = set[length - <span class="number">1</span>]; <span class="keyword">if</span>(element &gt; last_elem){ set.push(element); <span class="keyword">return</span> set; } <span class="keyword">while</span>(i &lt; length){ <span class="keyword">if</span>(element &lt; set[i]){ <span class="keyword">return</span> set.slice(<span class="number">0</span>,i).concat([element]).concat(set.slice(i,length)); } <span class="keyword">else</span> { i++; } } set.push(element); <span class="keyword">return</span> set; },</pre></div></div> </li> <li id="section-4"> <div class="annotation"> <div class="pilwrap for-h1"> <a class="pilcrow" href="#section-4">&#182;</a> </div> <h1>Sort support</h1> </div> </li> <li id="section-5"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-5">&#182;</a> </div> <p>Sort the set according to some function and then store an array of the translations of the indicies. So if the first item went to index 2 after being sorted, put 2 in the first spot of the permutation array.</p> </div> <div class="content"><div class='highlight'><pre> build_permutation_array: <span class="keyword">function</span>(set,sort){ <span class="keyword">var</span> sorted_set = _(set).clone(),perm=[]; <span class="keyword">if</span>(<span class="keyword">typeof</span>(sort) === <span class="string">"function"</span>){ sorted_set.sort(sort); } <span class="keyword">else</span> { sorted_set.sort(<span class="keyword">function</span>(a,b){<span class="keyword">return</span> sort.fn.call(sort,a,b)}); } _(sorted_set).each(<span class="keyword">function</span>(m,i){perm[m.cid] = i;}); <span class="keyword">return</span> perm; },</pre></div></div> </li> <li id="section-6"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-6">&#182;</a> </div> <p>Use a permutation array to resort a subset of a collection.</p> </div> <div class="content"><div class='highlight'><pre> permute_from_array: <span class="keyword">function</span>(collection,perm){ <span class="keyword">var</span> output = []; <span class="keyword">if</span>(<span class="keyword">typeof</span>(collection[<span class="number">0</span>]) === <span class="string">"number"</span>){ _(collection).each(<span class="keyword">function</span>(i){ output[perm[i]] = i }); } <span class="keyword">else</span> { _(collection).each(<span class="keyword">function</span>(i){ output[perm[i.cid]] = i }); } <span class="keyword">return</span> _(output).without(<span class="literal">undefined</span>); },</pre></div></div> </li> <li id="section-7"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-7">&#182;</a> </div> <p>Remove an element from a sorted set.</p> </div> <div class="content"><div class='highlight'><pre> remove_sorted: <span class="keyword">function</span>(set,element){ <span class="keyword">var</span> length = set.length, i = <span class="number">0</span>; <span class="keyword">while</span>(i &lt; length){ <span class="keyword">if</span>(element == set[i]){ <span class="keyword">return</span> set.slice(<span class="number">0</span>,i).concat(set.slice(i+<span class="number">1</span>,length)); } <span class="keyword">else</span> { i++; } } <span class="keyword">return</span> set; },</pre></div></div> </li> <li id="section-8"> <div class="annotation"> <div class="pilwrap for-h1"> <a class="pilcrow" href="#section-8">&#182;</a> </div> <h1>Pre-defined cache methods</h1> <p>Caching is really the raison d&#39;etre of Pourover. Every filter has two cache methods: one for rebuilding the whole filter from scratch and one for adding new items. As Pourover grows it will gain more pre-defined cache methods that correlate with common UI and data patterns.</p> </div> <div class="content"><div class='highlight'><pre> cacheMethods: {</pre></div></div> </li> <li id="section-9"> <div class="annotation"> <div class="pilwrap for-h3"> <a class="pilcrow" href="#section-9">&#182;</a> </div> <h3>Default: the dumb caches.</h3> <p>Just goes through each possible value for the filter and tests every item in the collection against it. As expensive as possible, but simple.</p> </div> <div class="content"><div class='highlight'><pre> defaultCache: <span class="keyword">function</span>(items){ <span class="keyword">var</span> that = <span class="keyword">this</span>; _(that.possibilities).each(<span class="keyword">function</span>(p){ <span class="keyword">var</span> matching_items = _(items).filter(<span class="keyword">function</span>(i){<span class="keyword">return</span> that.fn(p,i)}), matching_cids = _(matching_items).map(<span class="keyword">function</span>(i){<span class="keyword">return</span> i.cid}); p.matching_cids = matching_cids; }); }, defaultAddCache: <span class="keyword">function</span>(items){ <span class="keyword">var</span> that = <span class="keyword">this</span>; _(that.possibilities).each(<span class="keyword">function</span>(p){ <span class="keyword">var</span> matching_items = _(items).filter(<span class="keyword">function</span>(i){<span class="keyword">return</span> that.fn(p,i)}), matching_cids = _(matching_items).map(<span class="keyword">function</span>(i){<span class="keyword">return</span> i.cid}); p.matching_cids = PourOver.union_sorted(p.matching_cids,matching_cids) }); },</pre></div></div> </li> <li id="section-10"> <div class="annotation"> <div class="pilwrap for-h3"> <a class="pilcrow" href="#section-10">&#182;</a> </div> <h3>Exact: the fastest caches.</h3> <p>For filters that evaluate by strict equality (this property === this value). The name of the filter must match the name of the property for exact cache to work.</p> </div> <div class="content"><div class='highlight'><pre> exactCache: <span class="keyword">function</span>(items){ <span class="keyword">var</span> that = <span class="keyword">this</span>; _(items).each(<span class="keyword">function</span>(i){ <span class="keyword">var</span> p = that.possibilities[i[that.name]]; <span class="keyword">if</span> (p) { p.matching_cids = PourOver.insert_sorted(p.matching_cids,i.cid) } }); }, exactAddCache: <span class="keyword">function</span>(items){ <span class="keyword">var</span> that = <span class="keyword">this</span>; _(items).each(<span class="keyword">function</span>(i){ <span class="keyword">var</span> p = that.possibilities[i[that.name]]; <span class="keyword">if</span> (p) { p.matching_cids = PourOver.insert_sorted(p.matching_cids,i.cid) } }); } } }</pre></div></div> </li> <li id="section-11"> <div class="annotation"> <div class="pilwrap for-h1"> <a class="pilcrow" href="#section-11">&#182;</a> </div> <h1>Collections</h1> <p>The main kind of object in Pourover. A collection is basically a wrapper around an array of objects. It adds collection ids to its members and has support for various ways of retrieving all or a part of its members.</p> </div> <div class="content"><div class='highlight'><pre> PourOver.Collection = <span class="keyword">function</span>(items,opts){ <span class="keyword">if</span>(<span class="keyword">typeof</span>(items) == <span class="string">"undefined"</span>){items = [];} <span class="keyword">this</span>.items = []; <span class="keyword">this</span>.filters = {}; <span class="keyword">this</span>.sorts = {} <span class="keyword">this</span>.addItems(items) <span class="keyword">this</span>.on(<span class="string">"change"</span>,<span class="keyword">function</span>(){ _(<span class="keyword">this</span>.filters).each(<span class="keyword">function</span>(f){ <span class="keyword">if</span>(f.current_query){f.current_query.refresh();} }); }) <span class="keyword">this</span>.initialize.apply(<span class="keyword">this</span>, arguments); } _.extend(PourOver.Collection.prototype,Backbone.Events,{ initialize: <span class="keyword">function</span>(){},</pre></div></div> </li> <li id="section-12"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-12">&#182;</a> </div> <p>Force the filters and sorts of a collection to refresh. Generally most useful if you have batched up a bunch of silented actions and you want to refresh once at the end.</p> </div> <div class="content"><div class='highlight'><pre> refresh: <span class="keyword">function</span>(){ <span class="keyword">this</span>.trigger(<span class="string">"queryChange"</span>); },</pre></div></div> </li> <li id="section-13"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-13">&#182;</a> </div> <p>Retrieve the objects associated with an array of cids. Like everything in Pourover, the cids must be sorted. This is not usually an issue as you generally will not be calling <code>collection.get</code> with an array you manually create. You will probably be using the output of some function that keeps it sorted for you.</p> </div> <div class="content"><div class='highlight'><pre> get: <span class="keyword">function</span>(cids){ <span class="keyword">return</span> <span class="keyword">this</span>.getBy(<span class="string">"cid"</span>,cids) },</pre></div></div> </li> <li id="section-14"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-14">&#182;</a> </div> <p>Similar to get, except -- rather than getting items by cid -- you are getting them by [attr_name]. Here vals is an array of [attr_names]s.</p> </div> <div class="content"><div class='highlight'><pre> getBy: <span class="keyword">function</span>(attr_name,vals){ <span class="keyword">if</span>(! _.isArray(vals)){ <span class="keyword">var</span> vals = [vals] } <span class="keyword">var</span> low = <span class="number">0</span>, high = <span class="keyword">this</span>.items.length,lc = <span class="number">0</span>, hc = vals.length, output = [],items = <span class="keyword">this</span>.items,i; <span class="keyword">while</span> (low &lt; high &amp;&amp; lc &lt; hc){ <span class="keyword">if</span> (vals[lc] == (i=items[low])[attr_name]){ output.push(i); low++; lc++; } <span class="keyword">else</span> <span class="keyword">if</span> (vals[lc] &lt; i[attr_name]){ lc++; } <span class="keyword">else</span>{ low++; } } <span class="keyword">return</span> output; },</pre></div></div> </li> <li id="section-15"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-15">&#182;</a> </div> <p>Add items to the collection, triggering the appropriate events to keep all dependent sort and filter sets up-to-date.</p> </div> <div class="content"><div class='highlight'><pre> addItems: <span class="keyword">function</span>(i){ <span class="keyword">if</span>(! _.isArray(i)){ <span class="keyword">var</span> i = [i] } <span class="keyword">var</span> last_id = <span class="keyword">this</span>.items.length &gt; <span class="number">0</span> ? _(<span class="keyword">this</span>.items).last().cid + <span class="number">1</span> : <span class="number">0</span>,new_items; new_items = _(i).map(<span class="keyword">function</span>(c){<span class="keyword">var</span> n = PourOver.Item(c); n.cid = last_id++; <span class="keyword">return</span> n;}); <span class="keyword">this</span>.items = <span class="keyword">this</span>.items.concat(new_items); <span class="keyword">this</span>.regenerateFilterSets(new_items); <span class="keyword">this</span>.trigger(<span class="string">"change"</span>); },</pre></div></div> </li> <li id="section-16"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-16">&#182;</a> </div> <p>Remove items from the collection, triggering the appropriate events to keep all dependent sort and filter sets up-to-date. This functionality is only included begrudgingly. Pourover is best for collections that rarely remove members. TODO: Optimize</p> </div> <div class="content"><div class='highlight'><pre> removeItems: <span class="keyword">function</span>(i,isSorted){ <span class="keyword">if</span>(<span class="keyword">typeof</span>(isSorted) === <span class="string">"undefined"</span>){<span class="keyword">var</span> isSorted = <span class="literal">false</span>} <span class="keyword">if</span>(! _.isArray(i)){ <span class="keyword">var</span> i = [i] } <span class="keyword">if</span>(isSorted){ i = i.sort(<span class="keyword">function</span>(a,b){<span class="keyword">return</span> a.cid - b.cid }); <span class="keyword">var</span> new_items = [],old_items = <span class="keyword">this</span>.items,new_length = i.length,old_length = <span class="keyword">this</span>.items.length,newi = <span class="number">0</span>, oldi = <span class="number">0</span>; <span class="keyword">while</span>(oldi &lt; old_length){ <span class="keyword">if</span>(! newi &lt; new_length){ new_items = new_items.concat(old_items.slice(oldi)) <span class="keyword">break</span>; } <span class="keyword">else</span> <span class="keyword">if</span>(old_items[oldi].cid === i[newi].cid){ newi++; oldi++; } <span class="keyword">else</span> { new_items.push(old_items[oldi]); oldi++; } } } <span class="keyword">else</span> { <span class="keyword">var</span> new_items = [], old_items = <span class="keyword">this</span>.items,old_length = <span class="keyword">this</span>.items.length, oldi = <span class="number">0</span>,delete_cids = _(i).pluck(<span class="string">"cid"</span>); <span class="keyword">while</span>(oldi &lt; old_length &amp;&amp; delete_cids.length &gt; <span class="number">0</span>){ <span class="keyword">if</span>(_(delete_cids).include(old_items[oldi].cid)){ } <span class="keyword">else</span> { new_items.push(old_items[oldi]); } oldi++; } } <span class="keyword">this</span>.items = new_items; <span class="keyword">this</span>.regenerateFilterSets(); <span class="keyword">this</span>.trigger(<span class="string">"change"</span>); },</pre></div></div> </li> <li id="section-17"> <div class="annotation"> <div class="pilwrap for-h1"> <a class="pilcrow" href="#section-17">&#182;</a> </div> <h1>Collection filter functions</h1> <p>All filters are associated to collections rather than views. This allows for multiple views to share the same filter. This is especially useful for modal situations in which you can set filters on a grid view that are reflected in the one up view as well.</p> </div> <div class="content"><div class='highlight'><pre> addFilters: <span class="keyword">function</span>(f){ <span class="keyword">var</span> that = <span class="keyword">this</span>,new_filters; <span class="keyword">if</span>(! _.isArray(f)){ <span class="keyword">var</span> f = [f] } new_filters = _(f).reduce(<span class="keyword">function</span>(m,i){ m[i.name] = _.clone(i); m[i.name].collection = that; <span class="keyword">return</span> m; },{}); <span class="keyword">this</span>.filters = _(<span class="keyword">this</span>.filters).extend(new_filters);</pre></div></div> </li> <li id="section-18"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-18">&#182;</a> </div> <p>Bubble all query change events up from the individual filters to the collection. This allows a developer to specify events that should be triggered whenever any filter&#39;s query is changed.</p> </div> <div class="content"><div class='highlight'><pre> _(new_filters).each(<span class="keyword">function</span>(f){ f.on(<span class="string">"queryChange"</span>,<span class="keyword">function</span>(){ that.trigger(<span class="string">"queryChange"</span>); })</pre></div></div> </li> <li id="section-19"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-19">&#182;</a> </div> <p>All filters precache the result of their filtering. This is the source of pourover&#39;s speed optimizations.</p> </div> <div class="content"><div class='highlight'><pre> f.cacheResults(that.items);</pre></div></div> </li> <li id="section-20"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-20">&#182;</a> </div> <p>If a user passes in an <code>associated_attrs</code> property on a filter, that filter will re-cache its result whenever any object in the collection has an attribute changed. Setting <code>associated_attrs</code> is essential for admins or other uses in which filterable values can change.</p> </div> <div class="content"><div class='highlight'><pre> <span class="keyword">if</span>(f.associated_attrs){ _(f.associated_attrs).each(<span class="keyword">function</span>(a){ that.on(<span class="string">"change:"</span>+a,<span class="keyword">function</span>(objs){ f.removeFromCache(objs); f.addCacheResults(objs); <span class="keyword">if</span>(f.current_query){f.current_query.refresh();} }); }); } }); },</pre></div></div> </li> <li id="section-21"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-21">&#182;</a> </div> <p>A shortcut to re-calculate the results of every filter. This is expensive if you do not pass in <code>new_items</code>, in which cases only the new_items will be cached and the filters updated.</p> </div> <div class="content"><div class='highlight'><pre> regenerateFilterSets: <span class="keyword">function</span>(new_items){ <span class="keyword">var</span> that = <span class="keyword">this</span>;</pre></div></div> </li> <li id="section-22"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-22">&#182;</a> </div> <p>If no new items are passed in, regenerate filters for all items in the collection</p> </div> <div class="content"><div class='highlight'><pre> <span class="keyword">if</span>(<span class="keyword">typeof</span>(new_items) == <span class="string">"undefined"</span>){ _(<span class="keyword">this</span>.filters).each(<span class="keyword">function</span>(f){ f.cacheResults(that.items) }); } <span class="keyword">else</span> { _(<span class="keyword">this</span>.filters).each(<span class="keyword">function</span>(f){ f.addCacheResults(new_items) }); } },</pre></div></div> </li> <li id="section-23"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-23">&#182;</a> </div> <p>A shortcut for returning a match object containing all the items in a collection. More on matches below.</p> </div> <div class="content"><div class='highlight'><pre> getAllItems: <span class="keyword">function</span>(){ <span class="keyword">var</span> cids = _(<span class="keyword">this</span>.items).map(<span class="keyword">function</span>(i){<span class="keyword">return</span> i.cid}); <span class="keyword">return</span> <span class="keyword">new</span> PourOver.MatchSet(cids,<span class="keyword">this</span>,[<span class="string">"all"</span>]); },</pre></div></div> </li> <li id="section-24"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-24">&#182;</a> </div> <p>Get the currently cached results for the last stateful query on a filter (the last time a <code>setQuery</code> was called on that filter.) If <code>empty_default</code> is set to true, the function will return no items if the filter does not have a current query set. Otherwise, the function will return all items in the collection. The former <code>empty_default</code> setting is useful when OR-ing filters together, when you want an unset filter to represent an unselected dimension. The latter is useful when AND-ing filters together, when you want an unset filter to comprise all objects in the collection.</p> </div> <div class="content"><div class='highlight'><pre> getCurrentFilteredItems: <span class="keyword">function</span>(filter_name,empty_default){ <span class="keyword">if</span>(<span class="keyword">typeof</span>(empty_default) === <span class="string">"undefined"</span>){empty_default = <span class="literal">false</span>;} <span class="keyword">if</span>(<span class="keyword">this</span>.filters[filter_name].current_query &amp;&amp; <span class="keyword">this</span>.filters[filter_name].current_query.stack.length &gt; <span class="number">0</span>){ <span class="keyword">return</span> <span class="keyword">this</span>.filters[filter_name].current_query; } <span class="keyword">else</span> { <span class="keyword">if</span>(empty_default){ <span class="keyword">return</span> <span class="keyword">new</span> PourOver.MatchSet([],<span class="keyword">this</span>,[]); } <span class="keyword">else</span> { <span class="keyword">return</span> <span class="keyword">this</span>.getAllItems(); } } },</pre></div></div> </li> <li id="section-25"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-25">&#182;</a> </div> <p>The non-stateful way to query a filter. Simply returns the result of the query but does not store the query on the filter.</p> </div> <div class="content"><div class='highlight'><pre> getFilteredItems: <span class="keyword">function</span>(filter_name,query){ <span class="keyword">var</span> filter = <span class="keyword">this</span>.filters[filter_name],possibility; <span class="keyword">if</span> (_.isUndefined(filter) ) <span class="keyword">throw</span> <span class="string">"The filter "</span> + filter_name + <span class="string">" does not exist."</span> <span class="keyword">return</span> filter.getFn(query); },</pre></div></div> </li> <li id="section-26"> <div class="annotation"> <div class="pilwrap for-h1"> <a class="pilcrow" href="#section-26">&#182;</a> </div> <h1>Sort functions</h1> <p>Sorts, like filters, are generally stored on collections for the same reason that filters are stored on the collection rather than the view. However, whereas filters keep track of their own state and this is shared between views, the state of which sort is enabled is stored on the view.</p> </div> <div class="content"><div class='highlight'><pre> addSort: <span class="keyword">function</span>(sort){ <span class="keyword">var</span> that = <span class="keyword">this</span>; <span class="keyword">this</span>.sorts[sort.name] = sort; sort.collection = <span class="keyword">this</span>; sort.rebuild_sort(); <span class="keyword">this</span>.on(<span class="string">"change"</span>,<span class="keyword">function</span>(){ sort.rebuild_sort(); });</pre></div></div> </li> <li id="section-27"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-27">&#182;</a> </div> <p>Like filters, if you set <code>associated_attrs</code> on a sort, they will rebuild themselves whenever any item in the collection undergoes a change on that attribute. TODO: Consider cloning on add. Also, bring in line with addFilter (events or not!?)</p> </div> <div class="content"><div class='highlight'><pre> <span class="keyword">if</span>(sort.associated_attrs){ _(sort.associated_attrs).each(<span class="keyword">function</span>(a){ that.on(<span class="string">"change:"</span>+a,<span class="keyword">function</span>(objs){ sort.rebuild_sort(); }); }); } },</pre></div></div> </li> <li id="section-28"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-28">&#182;</a> </div> <p>Add multiple sorts.</p> </div> <div class="content"><div class='highlight'><pre> addSorts: <span class="keyword">function</span>(sorts){ <span class="keyword">if</span>(<span class="keyword">typeof</span>(opts) === <span class="string">"undefined"</span>){ opts = {};} <span class="keyword">if</span>(! _(sorts).isArray()){sorts = [sorts]} <span class="keyword">var</span> that = <span class="keyword">this</span>; _(sorts).each(<span class="keyword">function</span>(s){ that.addSort(s); }); },</pre></div></div> </li> <li id="section-29"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-29">&#182;</a> </div> <p>The non-stateful way to retrieve all the items in the collection, sorted.</p> </div> <div class="content"><div class='highlight'><pre> getSortedItems: <span class="keyword">function</span>(sort_name){ <span class="keyword">var</span> s = <span class="keyword">this</span>.sorts[sort_name],that = <span class="keyword">this</span>,output; <span class="keyword">return</span> s.sort(<span class="keyword">this</span>.items); },</pre></div></div> </li> <li id="section-30"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-30">&#182;</a> </div> <p>A silly shortcut, pass in a cid and an attribute, retrieve its value. Useful for template helpers.</p> </div> <div class="content"><div class='highlight'><pre> getItemValue: <span class="keyword">function</span>(cid,attribute){ <span class="keyword">var</span> item = _(<span class="keyword">this</span>.items).find(<span class="keyword">function</span>(i){<span class="keyword">return</span> i.cid === Number(cid);}); <span class="keyword">return</span> item[attribute]; },</pre></div></div> </li> <li id="section-31"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-31">&#182;</a> </div> <p>Update the value of one attribute of one item in the collection.</p> </div> <div class="content"><div class='highlight'><pre> updateItem: <span class="keyword">function</span>(cid,attribute,value){ <span class="keyword">var</span> item = _(<span class="keyword">this</span>.items).find(<span class="keyword">function</span>(i){<span class="keyword">return</span> i.cid === Number(cid);}); item[attribute] = value; <span class="keyword">this</span>.trigger(<span class="string">"incremental_change"</span>,[attribute]); <span class="keyword">this</span>.trigger(<span class="string">"update"</span>,<span class="string">"updateItem"</span>); <span class="keyword">this</span>.trigger(<span class="string">"change:"</span>+attribute,[item]); <span class="keyword">return</span> item.guid; },</pre></div></div> </li> <li id="section-32"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-32">&#182;</a> </div> <p>Change the value of one attribute of many items to the same value.</p> </div> <div class="content"><div class='highlight'><pre> batchUpdateItems: <span class="keyword">function</span>(cids,attribute,value){ <span class="keyword">var</span> items = <span class="keyword">this</span>.get(cids,<span class="literal">true</span>); _(items).each(<span class="keyword">function</span>(i){ i[attribute] = value; }); <span class="keyword">this</span>.trigger(<span class="string">"update"</span>,<span class="string">"batchUpdate"</span>); <span class="keyword">this</span>.trigger(<span class="string">"change:"</span>+attribute,items); <span class="keyword">this</span>.trigger(<span class="string">"incremental_change"</span>,[attribute]); <span class="keyword">return</span> _(items).pluck(<span class="string">"guid"</span>); },</pre></div></div> </li> <li id="section-33"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-33">&#182;</a> </div> <p>Change the value of several attributes of a single item in the collection.</p> </div> <div class="content"><div class='highlight'><pre> updateAttributes: <span class="keyword">function</span>(cid,updates){ <span class="keyword">var</span> item = _(<span class="keyword">this</span>.items).find(<span class="keyword">function</span>(i){<span class="keyword">return</span> i.cid === Number(cid);}); <span class="keyword">var</span> that = <span class="keyword">this</span>; _(updates).each(<span class="keyword">function</span>(v,k){ item[k] = v; that.trigger(<span class="string">"change:"</span>+k,[item]); }); <span class="keyword">this</span>.trigger(<span class="string">"update"</span>,<span class="string">"updateAttribute"</span>); <span class="keyword">this</span>.trigger(<span class="string">"incremental_change"</span>,_(updates).keys()); <span class="keyword">return</span> item.guid; },</pre></div></div> </li> <li id="section-34"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-34">&#182;</a> </div> <p>Change the value of several attributes of several items in the collection. Here &#39;updates&#39; is a hash of attributes -&gt; new values.</p> </div> <div class="content"><div class='highlight'><pre> batchUpdateAttributes: <span class="keyword">function</span>(cids,updates){ <span class="keyword">var</span> items = <span class="keyword">this</span>.get(cids,<span class="literal">true</span>) <span class="keyword">var</span> that = <span class="keyword">this</span>; _(items).each(<span class="keyword">function</span>(item){ _(updates).each(<span class="keyword">function</span>(v,k){ item[k] = v; }); }) _(updates).each(<span class="keyword">function</span>(v,k){ that.trigger(<span class="string">"change:"</span>+k,items); }); <span class="keyword">this</span>.trigger(<span class="string">"update"</span>,<span class="string">"batchUpdateAttribute"</span>); <span class="keyword">this</span>.trigger(<span class="string">"incremental_change"</span>,_(updates).keys()); <span class="keyword">return</span> _(items).pluck(<span class="string">"guid"</span>); } });</pre></div></div> </li> <li id="section-35"> <div class="annotation"> <div class="pilwrap for-h1"> <a class="pilcrow" href="#section-35">&#182;</a> </div> <h1>Items</h1> <p>If we ever need to add properties to items in a collection, the code would go here.</p> </div> <div class="content"><div class='highlight'><pre> PourOver.Item = <span class="keyword">function</span>(i){ <span class="keyword">return</span> i; },</pre></div></div> </li> <li id="section-36"> <div class="annotation"> <div class="pilwrap for-h1"> <a class="pilcrow" href="#section-36">&#182;</a> </div> <h1>Filters</h1> <p>A filter is basically a rule for mapping items of a collection into groups based on attribute values. It caches the results and can be queried either statefully or non-statefully, depending on developer preference.</p> </div> <div class="content"><div class='highlight'><pre> PourOver.Filter = <span class="keyword">function</span>(name,values,opts){ <span class="keyword">if</span>(<span class="keyword">typeof</span>(opts) === <span class="string">"undefined"</span>){opts = {}} <span class="keyword">this</span>.name = name; <span class="keyword">this</span>.possibilities = <span class="keyword">this</span>.create_possibilities(values); <span class="keyword">this</span>.values = _(values).map(<span class="keyword">function</span>(v){<span class="keyword">return</span> v.value}); _.extend(<span class="keyword">this</span>,opts); <span class="keyword">this</span>.initialize.apply(<span class="keyword">this</span>, arguments); } _.extend(PourOver.Filter.prototype,Backbone.Events,{</pre></div></div> </li> <li id="section-37"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-37">&#182;</a> </div> <p>Initialize is a no-op by default.</p> </div> <div class="content"><div class='highlight'><pre> initialize: <span class="keyword">function</span>(){},</pre></div></div> </li> <li id="section-38"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-38">&#182;</a> </div> <p>Given an array of possible values, initializes the object that will store the cached results of querying for that possibility.</p> </div> <div class="content"><div class='highlight'><pre> create_possibilities: <span class="keyword">function</span>(vs){ <span class="keyword">var</span> o = {}; _(vs).each(<span class="keyword">function</span>(v){ <span class="keyword">var</span> name = v.name || String(v.value); o[name] = v; o[name].matching_cids = []; }) <span class="keyword">return</span> o; },</pre></div></div> </li> <li id="section-39"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-39">&#182;</a> </div> <p>cacheResults and addCacheResults are generic methods that must be overridden before instantiating a filter. The preset filters included below provide good examples of how these functions should be written. cacheResults should cache all the items in the collection, whereas addCacheResults incrementally adds new items to already cached, filtered results.</p> </div> <div class="content"><div class='highlight'><pre> cacheResults: <span class="keyword">function</span>(items){ <span class="keyword">throw</span> <span class="string">"No cache function has been defined for this filter '"</span> + <span class="keyword">this</span>.name + <span class="string">"'."</span> }, addCacheResults: <span class="keyword">function</span>(items){ <span class="keyword">throw</span> <span class="string">"No add cache function has been defined for this filter '"</span> + <span class="keyword">this</span>.name + <span class="string">"'."</span> },</pre></div></div> </li> <li id="section-40"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-40">&#182;</a> </div> <p>Generally only used when removing items from a collection or when an item changes value. This will remove the item from the cache so that it can either be recached with its new value or thrown away.</p> </div> <div class="content"><div class='highlight'><pre> removeFromCache: <span class="keyword">function</span>(items){ <span class="keyword">var</span> cids = _(items).map(<span class="keyword">function</span>(i){<span class="keyword">return</span> i.cid}).sort(<span class="keyword">function</span>(a,b){<span class="keyword">return</span> a-b;}); _(<span class="keyword">this</span>.possibilities).each(<span class="keyword">function</span>(p){ p.matching_cids = PourOver.subtract_sorted(p.matching_cids,cids); }); },</pre></div></div> </li> <li id="section-41"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-41">&#182;</a> </div> <p>The stateful way to query a filter. Delegates the retrieval of a MatchSet to the filter&#39;s getFn and caches the results on the filter.</p> </div> <div class="content"><div class='highlight'><pre> query: <span class="keyword">function</span>(q,silent){ <span class="keyword">if</span>(<span class="keyword">typeof</span>(silent) === <span class="string">"undefined"</span>){<span class="keyword">var</span> silent = <span class="literal">false</span>;} <span class="keyword">var</span> match_set = <span class="keyword">this</span>.getFn(q); <span class="keyword">this</span>.setQuery(match_set,silent); },</pre></div></div> </li> <li id="section-42"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-42">&#182;</a> </div> <p>Assigns a MatchSet to a filter (caches the result) and triggers the appropriate events.</p> </div> <div class="content"><div class='highlight'><pre> setQuery: <span class="keyword">function</span>(q,silent){ <span class="keyword">if</span>(<span class="keyword">typeof</span>(silent) === <span class="string">"undefined"</span>){<span class="keyword">var</span> silent = <span class="literal">false</span>;} <span class="keyword">this</span>.current_query = q; <span class="keyword">if</span>(!silent){ <span class="keyword">this</span>.trigger(<span class="string">"queryChange"</span>); } },</pre></div></div> </li> <li id="section-43"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-43">&#182;</a> </div> <p>Removes a cached result from a filter.</p> </div> <div class="content"><div class='highlight'><pre> clearQuery: <span class="keyword">function</span>(silent){ <span class="keyword">if</span>(<span class="keyword">typeof</span>(silent) === <span class="string">"undefined"</span>){<span class="keyword">var</span> silent = <span class="literal">false</span>;} <span class="keyword">this</span>.current_query = <span class="literal">false</span>; <span class="keyword">if</span>(!silent){ <span class="keyword">this</span>.trigger(<span class="string">"queryChange"</span>); } },</pre></div></div> </li> <li id="section-44"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-44">&#182;</a> </div> <p>Unions a cached r