---
title: Weekly Widget 3 - Paginated Grid
description: Weekly Widget 3 - Paginated Grid
image: https://www.bitovi.com/hubfs/canjs-banner-background.jpg
---

- ![AI implementation](https://www.bitovi.com/hubfs/AIConsultingIcon.svg)
  
  [AI implementation](https://www.bitovi.com/services/ai-consulting)
- ![Systems engineering](https://www.bitovi.com/hubfs/icon%20-%20backend.svg)
  
  [Systems engineering](https://www.bitovi.com/services/systems-engineering-consulting)
- ![Project Management](https://www.bitovi.com/hubfs/icon%20-%20PM.svg)
  
  [Project Management](https://www.bitovi.com/services/agile-project-management-consulting)
- ![Product Design](https://www.bitovi.com/hubfs/icon%20-%20design.svg)
  
  [Product Design](https://www.bitovi.com/services/product-design-consulting)
- ![Frontend development](https://www.bitovi.com/hubfs/icon%20-%20frontend.svg)
  
  [Frontend development](https://www.bitovi.com/services/frontend-development-consulting)
- [View more
  
  →
  
  ](https://www.bitovi.com/digital-consulting-services)

We're Experts in...

- [JavaScript](https://www.bitovi.com/services/frontend/javascript-consulting)
- [AI training](https://www.bitovi.com/ai-training-for-software-engineers)
- [Angular](https://www.bitovi.com/services/frontend/angular-consulting)
- [Design systems](https://www.bitovi.com/services/axure-figma-migration)
- [React](https://www.bitovi.com/services/frontend/react-consulting)
- [Temporal](https://www.bitovi.com/services/backend/temporal-consulting)
- [React Native](https://www.bitovi.com/services/frontend/react-consulting/react-native)
- [Node.js](https://www.bitovi.com/services/backend/nodejs-consulting)

Showcase

![Yum! Brands](https://www.bitovi.com/hubfs/yum-showcase-link-1.png)

[View case study](https://www.bitovi.com/en/bitovi-yum-case-study)

More Projects

- [![Levi's](https://www.bitovi.com/hubfs/levis.svg)](https://www.bitovi.com/web-application-consulting-work/levis-ecommerce-responsive-redesign)
- [![Christie's International Real Estate](https://www.bitovi.com/hubfs/christies.svg)](https://design.bitovi.com/christies)
- [![BAFS](https://www.bitovi.com/hubfs/bafs.svg)](https://www.bitovi.com/ux-design-consulting/ux-case-studies/bafs-ppp)
- [View more
  
  →
  
  ](https://www.bitovi.com/our-software-consulting-work)

Open Source Tools

We build powerful tools and open source them to support the community.

[See what we've built →](https://www.bitovi.com/open-source)

- [![Blog](https://www.bitovi.com/hubfs/icon%20-%20blog.svg)
  
  BlogWe post about delivering products and solving problems.
  
  ](https://www.bitovi.com/blog)
- [![Partnerships](https://www.bitovi.com/hubfs/Handshake-1.svg)
  
  PartnershipsLearn about Bitovi's technology partners
  
  ](https://www.bitovi.com/partnerships)
- [![Academy](https://www.bitovi.com/hubfs/icon%20-%20academy%20(4).svg)
  
  AcademyFree courses to build delivery skills
  
  ](https://www.bitovi.com/academy)
- [![Open source tools](https://www.bitovi.com/hubfs/icon%20-%20open%20source.svg)
  
  Open source toolsUse or contribute to our community
  
  ](https://www.bitovi.com/open-source)

Let's Connect

- [![Discord](https://www.bitovi.com/hubfs/DiscordLogo.svg)
  
  Discord
  
  ](https://discord.gg/J7ejFsZnJ4)
- [![LinkedIn](https://www.bitovi.com/hubfs/LinkedinLogo.svg)
  
  LinkedIn
  
  ](https://www.linkedin.com/company/bitovi/)
- [![GitHub](https://www.bitovi.com/hubfs/GithubLogo.svg)
  
  GitHub
  
  ](https://github.com/bitovi/)

![Eggbot](https://www.bitovi.com/hubfs/build_assets/bitovi-limbo-cms-react/338/js_client_assets/assets/eggbot-LTGhdSGL.png)

Name *

Work Email *

Phone

What's your project?

Send

### Contact Us

(312) 620-0386contact@bitovi.com

 February 1, 2013

# Weekly Widget 3 - Paginated Grid

 Weekly Widget 3 - Paginated Grid

![Justin Meyer](https://www.bitovi.com/hubfs/justin.jpeg)

 Justin Meyer

Share:

[![Twitter](https://www.bitovi.com/hubfs/limbo-generated/_astro/twitter-white.os3xLc3C_Z2nW4or.svg) ](https://twitter.com/intent/tweet?text=) [![Reddit](https://www.bitovi.com/hubfs/limbo-generated/imgs/icons/reddit.png) ](http://reddit.com/submit?url=)

This weekly widget was suggested by [@Cherif_b](https://twitter.com/Cherif_b/status/296765112386203649). The article covers how to create a basic paginated grid. But even more important, it gives a glipse at how we organize large applications.

## The app

The app allows you to paginate through a list of links:

JSFiddle

## What it does

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.

The app also supports bookmarking of the current page and forward/back button support.

## How it works

Bitovi's goal is a rapid [development](http://bitovi.com/development) pace provided by maintainable, easily adaptable code. As I've said before, the secret to building large applications is to build small, isolated modules and glue them together. But the *key* to making that possible is **organizing state** into the observer pattern (*pub-sub is an anti-pattern*).

I've written this example to illustrate how Bitovi typically builds apps with CanJS. Our pattern is typically:

1. Organize the client's stateful behavior into a `can.Observe` **client state**.
2. Build low-level views that are ignorant of that state.
3. Write models to retrieve data and fixtures to fake services.
4. Create an application controller that: 
     - hooks up the client state to `can.route`.
     - hooks up the client state to low-level widgets.

### Client State

Organizing state is the most important thing you can do to make a maintainable app. For this app, I choose to represent the state with 3 values:

- limit - the number of items I want to display in the list
- offset - the index of the first item currently being displayed
- count - the total number of items that could be displayed

I created a `Paginate` Observe "class" that encapsulates these values (and adds some nice helper functions):

```
var Paginate = can.Observe( .... )
```

This allows me to create a `paginate` instance, set its limit, offset, and count, and also listen to when those values change:

```
var paginate = new Paginate({limit: 5, count: 20})
paginate.bind('offset', function(ev, newVal){
  console.log("offset is now", newVal)
});

paginate.attr('offset',10)
```

I added setters to do bounds checking:

```
setOffset: function(newOffset){
  return newOffset < 0 ? 
    0 : 
    Math.min(newOffset, ! isNaN(this.count - 1) ? 
      this.count - 1 : 
      Infinity )
},
```

This prevents `offset` from being less than 0 or greater or equal to `count`.

```
paginate.attr('offset',-20);
paginate.attr('offset') // ->0
```

I also created a bunch of helpers that represent the state as pages:

```
paginate.attr('offset',0);
paginate.page()         //-> 0
paginate.canPrev()      //-> false
paginate.page(2)
paginate.attr('offset') //-> 10
```

This application is small, so a single state object works. In bigger apps, state may be broken up into multiple state objects.

### Application Controller

An application controller coordinates between the **client state**, view-widgets, and `can.route`. In larger apps, the application controller might coordinate between other, lower-level controllers. For this app, only a single controller is necessary.

When an `AppControl` is initialized, it starts by creating the client state:

```
var paginate = this.options.paginate = new Paginate({
  limit: 5
});
```

And then hooking up the pagination widgets with that state:

```
new NextPrev("#nextprev",{paginate: paginate});

new PageCount("#pagecount",{
  page: can.compute(paginate.page,paginate),
  pageCount: can.compute(paginate.pageCount,paginate)
});
```

To hook up the grid, `AppControl` uses [can.compute](http://canjs.us/#can_observe-can_compute) to translate between the limit/offset and a deferred that resolves to corresponding `Website` items. Through the magic of computed properties, when any widget in the app changes the limit or offset properties, this function will run, causing a new findAll request for websites:

```
var items = can.compute(function(){
  var params = {
    limit: paginate.attr('limit'),
    offset: paginate.attr('offset')
  };
  var websitesDeferred = Website.findAll(params);
  websitesDeferred(function(websites){ 
    paginate.attr('count', websites.count) 
  });
  return websitesDeferred;  
});
```

`items` is passed to the `Grid`:

```
new Grid("#websites",{
  items: items,
  template: "websiteStache"
})
```

Grid will render `items` in a live-bound template. Thus, any change to the client state will automatically cause the app to request and render the correct data. For example, if any widget changes `offset`, items' can.compute function will fire, causing a new Website.findAll request. New data comes back, pushes to the items list, and causes the live-bound grid template to re-render itself. can.compute + live binding = awesome.

Notice that `items` also sets the `count` property of `paginate`. `count` is the total number of websites that would be returned if there was no pagination. I'll show you how to set up a fixture to do this in the next section.

Finally, `AppControl` cross-binds the client state to can.route for bookmarking and back-button support:

```
"{paginate} offset": function(paginate){
  can.route.attr('page', paginate.page());
},
"{can.route} page": function(route){
  this.options.paginate.page(route.attr('page'));
}
```

### Model and Fixtures

I created a `Website` model that retrieves json data from a "/websites" url like:

```
var Website = can.Model({
  findAll: "/websites"
},{});
```

The "/websites" service supports offset and limit parameters and returns a list of websites like:

```
{
  data: [
    {
      "id": 1,
      "name": "CanJS",
      "url": "http://canjs.us"
    },
    ...
  ],
  count: 20
}
```

Where `data` is an array of website data objects and `count` is the total number of websites on the server. When this type of data is loaded by [can.Model.findAll](http://canjs.us/#can_model-findall), the `data` property's items are converted into a [can.Model.List](http://canjs.us/#can_model-can_model_list) and all other properties, like `count` are added as expando properties to the list.

### View Widgets

#### NextPrev Widget

The view widgets are pretty straightforward. `NextPrev` makes use of `paginate`'s helper methods directly:

```
var NextPrev = can.Control({
  init: function(){
    this.element.html( 
       can.view('nextPrevStache',this.options) );     
  },
  ".next click" : function(){
    var paginate = this.options.paginate;
    paginate.next();
  },
  ".prev click" : function(){
    var paginate = this.options.paginate;
    paginate.prev();
  }
});
```

#### PageCount Widget

While `PageCount` simply renders the following template with its `page` and `pageCount` computes passed by `AppControl`:

```
Page {{page}} of {{pageCount}}.
```

**Bonus points to anyone who updates the fiddle to change the page number to a 2-way bound input element!**

#### Grid Widget

Finally, the `Grid` is written to accept an **items** `can.compute` that contains either a list of items or a deferred that will resolve to the list of items. When the grid is initialized or **items** changes, `update` is called, which checks if **items** is a deferred or not. If it is a deferred, it changes the opacity of the `<tbody>` to indicate it is loading and then calls `draw` once the deferred has resolved. If **items** is not a deferred, it assumes that it is a list of items and calls `draw`.

```
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` restores the opacity of the `<tbody>` and renders the template passed in with `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) );
}
```

#### Initializing the App

At the end of the fiddle, an instance of `AppControl` is created and the default route is set:

```
new AppControl(document.body);
can.route(":page",{page: "1"});
```

## Conclusion

Understand and organize your app's state and you'll save yourself a lot of pain.

Hopefully this widget demonstrates the power of combining `can.compute`, `can.Observe`, and live binding. Build applications from the top down by:

1. Creating *client state* (like `paginate`) to represent the state of your app.
2. Creating can.compute functions that listen for changes in client state and request new data (like `items` makes a new request for `Website.findAll`).
3. Passing this data into your widgets, which render templates that are live-bound to changes in the data.
4. Creating controls that listen to UI events and update client state.

Changes in your application just take care of themself.

User changes UI -> Event handler changes client state -> can.compute function fires -> new data is requested -> live bound templates re-render the relevant data

Lets hear some suggestions for next week!

[![Tag for open source](https://www.bitovi.com/hubfs/limbo/icons/tag.svg) open source ](https://www.bitovi.com/blog/topic/open-source)[![Tag for development](https://www.bitovi.com/hubfs/limbo/icons/tag.svg) development ](https://www.bitovi.com/blog/topic/development)

 Previous Post

![canjs-banner-background](https://www.bitovi.com/hs-fs/hubfs/canjs-banner-background.jpg?height=117&name=canjs-banner-background.jpg) [ Weekly Widget 2 - 2-Way Mustache Helpers ](https://www.bitovi.com/blog/weekly-widget-2-2-way-mustache-helpers)

  

 Next Post

![canjs-banner-background](https://www.bitovi.com/hs-fs/hubfs/canjs-banner-background.jpg?height=117&name=canjs-banner-background.jpg) [ Weekly Widget 4 - Show More with $.Range ](https://www.bitovi.com/blog/weekly-widget-4-show-more-with-range)

```json
{
  "@context" : "http://schema.org",
  "@type" : "Organization",
  "address" : {
    "@type" : "PostalAddress",
    "addressCountry" : "United States",
    "addressLocality" : "Libertyville",
    "addressRegion" : "IL",
    "postalCode" : "60048",
    "streetAddress" : "1134 Pine Tree Lane "
  },
  "alternateName" : "Bitovi",
  "areaServed" : {
    "@type" : "GeoCircle",
    "geoMidpoint" : {
      "@type" : "GeoCoordinates",
      "latitude" : "41.8781",
      "longitude" : "87.6298"
    },
    "geoRadius" : "5000 km"
  },
  "description" : "Bitovi is a UX, UI design and front-end JavaScript development consulting company",
  "email" : "contact@bitovi.com",
  "image" : "https://www.bitovi.com/hubfs/bitovi-logo-x2.png",
  "logo" : "https://www.bitovi.com/hubfs/bitovi-logo-23-1.svg",
  "mainEntityOfPage" : {
    "@id" : "https://www.bitovi.com/blog/weekly-widget-3-paginated-grid",
    "@type" : "WebPage",
    "description" : "Weekly Widget 3 - Paginated Grid"
  },
  "naics" : "541511",
  "name" : "Bitovi Web App Consulting",
  "sameAs" : [ "https://www.facebook.com/BitoviLLC/", "https://twitter.com/bitovi", "https://www.linkedin.com/company/bitovi" ],
  "telephone" : "312-620-0386",
  "url" : "http://bitovi.com"
}
```

```json
{
  "@context" : "http://schema.org",
  "@type" : "BlogPosting",
  "author" : {
    "@type" : "Person",
    "name" : "Justin Meyer"
  },
  "dateModified" : "November 29, 2018, 5:52:39 PM",
  "datePublished" : "2013-02-01 20:32:46",
  "description" : "Weekly Widget 3 - Paginated Grid",
  "headline" : "Weekly Widget 3 - Paginated Grid",
  "image" : {
    "@type" : "ImageObject",
    "url" : "https://www.bitovi.com/hubfs/canjs-banner-background.jpg"
  },
  "publisher" : {
    "@type" : "Organization",
    "logo" : {
      "@type" : "ImageObject",
      "url" : "https://www.bitovi.com/hubfs/bitovi-logo-23-1.svg"
    },
    "name" : "Bitovi"
  }
}
```