<table id='websites'></table> <div id='nextprev'></div> <div id='pagecount'></div> <!-- TEMPLATES --> <script id="nextPrevStache" type="text/mustache"> <a href="javascript://" class="prev {{#paginate.canPrev}}enabled{{/paginate.canPrev}}">Prev</a> <a href="javascript://" class="next {{#paginate.canNext}}enabled{{/paginate.canNext}}">Next</a> </script> <script id="pageCountStache" type="text/mustache"> Page <span>{{page}}</span> of <span>{{pageCount}}</span>. </script> <script id="websiteStache" type="text/mustache"> <thead> <tr> <th>Name</th><th>Url</th> </tr> </thead> <tbody> {{#items}} <tr> <td width="40%">{{name}}</td> <td width="70%">{{url}}</td> </tr> {{/items}} </tbody> </script>
(function() { // Organizes pagination state and // provides some helper methods var Paginate = can.Observe({ defaults : { count: Infinity, offset: 0, limit: 100 } },{ // Prevent negative counts setCount: function(newCount, success, error){ return newCount < 0 ? 0 : newCount; }, // Prevent negative offsets setOffset: function(newOffset){ return newOffset < 0 ? 0 : Math.min(newOffset, ! isNaN(this.count - 1) ? this.count - 1 : Infinity ) }, // move next next: function(){ this.attr('offset', this.offset+this.limit); }, prev : function(){ this.attr('offset', this.offset - this.limit ) }, canNext : function(){ return this.attr('offset') < this.attr('count') - this.attr('limit') }, canPrev: function(){ return this.attr('offset') > 0 }, page: function(newVal){ if(newVal === undefined){ return Math.floor( this.attr('offset') / this.attr('limit') )+1; } else { this.attr('offset', ( parseInt(newVal) - 1 ) * this.attr('limit') ); } }, pageCount: function(){ return this.attr('count') ? Math.ceil( this.attr('count') / this.attr('limit') ) : null; } }); // Hooks up client state to can.route and // various controls. var AppControl = can.Control({ init: function(){ // Create an Observe that organizes // the page's state. var paginate = this.options.paginate = new Paginate({ limit: 5 }); // Create widget that uses the page's state. new NextPrev("#nextprev",{paginate: paginate}); // Create widget by mapping page's state // to the options the widget it accepts. new PageCount("#pagecount",{ page: can.compute(paginate.page,paginate), pageCount: can.compute(paginate.pageCount,paginate) }); // Map the page state the // the list of options the grid // should display var items = can.compute(function(){ var params = { limit: paginate.attr('limit'), offset: paginate.attr('offset') }; var websitesDeferred = Website.findAll(params); websitesDeferred.then(function(websites){ paginate.attr('count', websites.count) }); return websitesDeferred }) // Create the grid. new Grid("#websites",{ items: items, template: "websiteStache" }); // Call on to be able to listen to this.options.paginate this.on(); }, // update the route when the page state changes "{paginate} offset": function(paginate){ can.route.attr('page', paginate.page()); }, // update the page's state when the route changes "{can.route} page": function(route){ this.options.paginate.page(route.attr('page')); } }); var Website = can.Model({ findAll: "/websites" },{}); can.fixture("/websites",function(request){ var websites = [ {id:1,name:"CanJS",url:"http://canjs.us"},{id: 2,name:"jQuery++",url:"http://jquerypp.com"}, {id:3,name:"JavaScriptMVC",url:"http://javascriptmvc.com"},{id: 4,name:"Bitovi",url:"http://bitovi.com"}, {id:5,name:"FuncUnit",url:"http://funcunit.com"},{id: 6,name:"StealJS",url:"http://stealjs.com"}, {id:7,name:"jQuery",url:"http://jquery.com"},{id: 8,name:"Mootools",url:"http://mootools.com"}, {id:9,name:"Dojo",url:"http://dojo.com"},{id: 10,name:"YUI",url:"http://yui.com"}, {id:11,name:"DoneJS",url:"http://donejs.com"},{id: 12,name:"Mindjet Connect",url:"http://connect.mindjet.com"}, {id:13,name:"JSFiddle",url:"http://jsfiddle.net"},{id: 14,name:"Zepto",url:"http://zepto.com"}, {id:15,name:"Spill",url:"http://spill.com"},{id: 16,name:"Github",url:"http://github.com"} ], start = request.data.offset || 0 end = start + ( request.data.limit || websites.length); return { count: websites.length, data: websites.slice(start, end) } }); var NextPrev = can.Control({ init: function(){ this.element.html( can.view('nextPrevStache',this.options) ); }, ".next click" : function(){ var paginate = this.options.paginate; paginate.attr('offset', paginate.offset+paginate.limit); }, ".prev click" : function(){ var paginate = this.options.paginate; paginate.attr('offset', paginate.offset-paginate.limit ); } }); var PageCount = can.Control({ init: function(){ this.element.html( can.view('pageCountStache',this.options)) } }); Grid = can.Control({ init: function(){ this.update(); }, "{items} change": "update", update: function(){ var items = this.options.items(); if(can.isDeferred( items )){ this.element.find('tbody').css('opacity',0.5) items.then(this.proxy('draw')); } else { this.draw(items); } }, draw: function(items){ this.element.find('tbody').css('opacity',1); var data = $.extend({}, this.options,{items: items}) this.element.html( can.view(this.options.template, data) ); } }); new AppControl(document.body); // Setup a pretty route that defaults to page=1 can.route(":page",{page: "1"}); })();