Working with Portlets

Since we’re actually building some form of dashboard in a Rails app, why not use jQuery UI Portlets to make it more awesome?

Portlets

Our widgets now look fuckin’ cool – styled as portlets with a jQuery theme. You can drag and rearrange them, too!

Using a jQuery UI theme

Things are pretty with jQuery. Let’s use the theme eggplant. I like eggplants. Just use good olive oil when frying ’em, and do not forget the salt and pepper.

app/views/layouts/application.html.haml
 1%html
 2%head
 3  = javascript_include_tag "jquery-1.4.2.min"
 4  = javascript_include_tag "jquery-ui-1.8.5.custom.min"
 5  = stylesheet_link_tag "app"
 6  
 7  = stylesheet_link_tag "eggplant/jquery-ui-1.8.5.custom"
 8
 9%body
10  = yield

We just add the eggplant theme stylesheet (line 7).

Drawing the Dashboard

Reading the portlets example source it turns out that we have to alter the dashboard view a bit.

app/views/dashboard/index.html.haml
 1%h1 My Dashboard
 2
 3#dashboard
 4  .column
 5    = render_widget 'parrot'
 6  .column
 7    = render_widget 'bin'
 8  .column
 9
10:javascript
11  $(function() {
12    $(".column").sortable({
13      connectWith: '.column'
14    });
15    
16    $(".portlet").addClass("ui-widget ui-widget-content ui-helper-clearfix ui-corner-all")
17      .find(".portlet-header")
18        .addClass("ui-widget-header ui-corner-all")
19        .prepend('<span class="ui-icon ui-icon-minusthick"></span>')
20        .end()
21      .find(".portlet-content");
22    
23    $(".portlet-header .ui-icon").click(function() {
24      $(this).toggleClass("ui-icon-minusthick").toggleClass("ui-icon-plusthick");
25      $(this).parents(".portlet:first").find(".portlet-content").toggle();
26    });
27  });

That’s three columns (line 4-8) and a bit JavaScript I copied from the example.

Widgets get Portlets

Portlets have some styling requirements and expect some divs and classes. Why not use a layout?

app/cells/twitter_widget.rb (snippet)
 8  def display_form
 9    @tweets = Tweet.find(:all)
10    render :layout => 'portlet'
11  end

The same in the trashbin.

app/cells/trashbin_widget.rb (snippet)
 4  def display
 5    render :layout => 'portlet'
 6  end

Layouts

Yeah, widgets can have layouts, too. That makes sense as soon as your widgets start sharing certain view blocks.

app/cells/layouts/portlet.haml
1- widget_div :class => :portlet, :id => "portlet-#{widget_id}" do
2  .portlet-header
3    = @title || "Eggplant portlet!"
4  .portlet-content(id=widget_id)
5    = yield

The helper widget_div just creates a div with the specified options (line 1).

Line 4 will render to something like

  <div class='portlet-content' id='parrot'>

As the class name implies, this is the content block of our widget.

Simplify views

Now that the layout does all the wrapping work, we should remove calls to widget_div in our views.

app/cells/twitter_widget/display_form.html.haml (snippet)
 1%ul
 2  - for tweet in @tweets
 3    %li{'data-id' => tweet.id}
 4      = tweet.text

Replace gets update

Replace portlet

Now what happens if you post a new tweet?

The state adds the tweet (line 14) and replaces the content block itself with the re-rendered view (line 17).
This is not exactly what we want – see on the right.

app/cells/twitter_widget.rb (snippet)
13  def process_tweet
14    Tweet.new(:text => param(:text)).save
15    
16    @tweets = Tweet.find(:all) # this is wet!
17    replace :view => :display_form
18  end

The generated JavaScript from replace (line 17) would look like this.

$("#parrot").replaceWith("<ul>...");

Solution: the update helper

As the re-rendered view “<ul>...” is not wrapped with the layout, this breaks the .portlet-content div.

According to the jQuery API on replaceWith, the solution is using html instead. Apotomo provides the #update helper for exactly that. Damn, that’s so simple!

app/cells/twitter_widget.rb (snippet)
13  def process_tweet
14    Tweet.new(:text => param(:text)).save
15    
16    @tweets = Tweet.find(:all) # this is wet!
17    update :view => :display_form
18  end

Don’t forget to use update in all trigger states of portlets.

app/cells/twitter_widget.rb (snippet)
20  def redraw
21    update :state => :display_form
22  end

And in the bin.

app/cells/trashbin_widget.rb (snippet)
 8  def trash
 9    Tweet.find(params[:id]).delete
10    trigger :tweetDeleted
11    
12    update :view => :display
13  end

Thanks to José Alberto for “inspiring” me to write this guide. Te quiero, también! ;-)


blog comments powered by Disqus