Making unobstrusive pagination (Part 3)

In the last lesson I demonstrated how we could quickly write our own pagination using #link_to_event.

A couple of guys (including mislav himself) told me to make the pagination unobstrusive, so here’s how i did this.

simple_paging

You’re gonna learn how to

  • pack unobstrusive JavaScript in Apotomo widgets
  • use Helpers in widgets
  • configure your widget view with #render

1I didn’t have any clue what unobstrusive JavaScript refers to. It means “separating JavaScript and markup”. After lots of tries, I figured out how to let will_paginate handle the markup and do the JS part myself.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
%h3 Things
%p
  = page_entries_info @items
 
%ul
  - @items.each do |item|
    %li
      = item.text
 
= will_paginate @items
 
- javascript_tag do
  $(document.body).observe('click', function(e) {
  var el = e.element()
 
  if (el.match('.pagination a')) {
  page = el.href.split("page=").last(); // I don't know how to do it better.
  = "new Ajax.Request('#{url_for_event(:paginate)}'+'&page='+page, { method: 'get' })"
 
  e.stop()
  }})

Instead of modifying will_paginate's output (line 10) I add an observer that triggers the :pagination event (line 13).

I have to extract the page from the element href, that’s a little bit fiddling. Maybe someone can tell me how to do it better (line 17)?

The interesting part here is the call to #url_for_event (line 18). It computes the url to trigger the paginate event, which happens in Ruby!

That works. After clicking a page, the widgets updates itself via AJAX.

2Anyway, I keeps adding observers as the widgets updates and re-injects the JS into the page over and over again. There are multiple solutions to the problem.

  • Fix the JS to add the observer only once
  • Use two different views in your widget, the initial one with, the following without the JS
  • Write the JS to a global variable using #capture that’s evaluated only once when the page is rendered completely

Personally I hate using global variables (that’s what #capture does). I will use it anyway cause it shows a few interesting concepts.

3We need to render the JavaScript to the page footer.

Cells provide a view method #global_capture for sending local content to the global controller view. That’s not nice. Widgets shouldn’t know anything about the global page.

Apotomo widgets are based on cells, so they may use that technique as well.

In the display.html.erb view

...
- global_capture :ujs do
  - javascript_tag do
    $(document.body).observe('click', function(e) {
...

I embed the #javascript_tag into #global_capture that saves the whole block to the global view’s instance variable @ujs, which is a brutal interface violation.

In the app’s views/layouts/application.html.erb I have to render @ujs in order to embed the JavaScript into the page only once. I still hate myself for doing that.

  </body>
  <%= @ujs %>
</html>

That’s all for the markup.

4 The #global_capture method is not automatically available in the widget view, it’s provided by a helper, so let’s introduce it to our view.

class ItemList < Apotomo::StatefulWidget
  helper Cells::Helpers::CaptureHelper
 
  def display
  #...

That looks like in a controller. Note that you can use #helper to pull any helper in your widget view.

5Ok, the paging is working in some unobstrusive way. I’m still not sure if unobstrusive JavaScript is something I like.

What I like is CSS and how it can help us to beautify elements without adding obstrusive CSS.

To add a background to our item list I add a css class to it’s surrounding div.

1
2
3
4
5
6
7
8
9
10
11
class ItemList < Apotomo::StatefulWidget
  helper Cells::Helpers::CaptureHelper
 
  def display
    respond_to_event :paginate, :with => :display
 
    @items = Todo.paginate :page => param(:page), :per_page => 10, :order => 'created_at DESC'
 
    render :html_options => {:class => 'lined'}
  end
end

All I need is adding :html_options to the #render invocation (line 9), this will add the attributes to our list. Obviously I also added some class to the CSS document, but that’s not part of this course. My CSS-skills suck.

6Let’s view our page again.

lined_list

Looks cooooool, doesn’t it?

I’d love to see some JS fix to prevent the usage of #global_capture. Or do you like it? It’s a common technique in Rails, though. Doesn’t mean that this is a good thing.

In the next lesson we’ll add an interactive form to add Todo items which processes and updates like the list widget, via AJAX and superfast.

Go to: Writing forms

Leave a Reply