Working with Portlets apotomo-1.0

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  
 6  = stylesheet_link_tag "app"
 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
 9:javascript
10  $(function() {
11    $(".column").sortable({
12      connectWith: '.column'
13    });
14    
15    $(".portlet").addClass("ui-widget ui-widget-content ui-helper-clearfix ui-corner-all")
16      .find(".portlet-header")
17        .addClass("ui-widget-header ui-corner-all")
18        .prepend('<span class="ui-icon ui-icon-minusthick"></span>')
19        .end()
20      .find(".portlet-content");
21    
22    $(".portlet-header .ui-icon").click(function() {
23      $(this).toggleClass("ui-icon-minusthick").toggleClass("ui-icon-plusthick");
24      $(this).parents(".portlet:first").find(".portlet-content").toggle();
25    });
26  });

That’s two columns (line 4-7) 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/widgets/twitter_widget.rb (snippet)
 8  def display
 9    @tweets = Tweet.find(:all)
10    render :layout => "portlet"
11  end

The same in the trashbin.

app/widgets/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/widgets/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/widgets/twitter/display.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 10) and replaces the content block itself with the re-rendered view (line 13).
This is not exactly what we want – see on the right.

app/widgets/twitter_widget.rb (snippet)
 9  def process_tweet(evt)
10    Tweet.new(:text => evt[:text]).save
11
12    @tweets = Tweet.find(:all) # this is wet!
13    replace :view => :display
14  end

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

$("#twitter").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/widgets/twitter_widget.rb (snippet)
 9    @tweets = Tweet.find(:all)
10    render :layout => "portlet"
11  end
12  
13  def process_tweet(evt)
14    Tweet.new(:text => evt[:text]).save

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

app/widgets/twitter_widget.rb (snippet)
16    @tweets = Tweet.find(:all) # this is wet!
17    replace :view => :display
18  end

And in the bin.

app/widgets/trashbin_widget.rb (snippet)
 8  def trash(evt)
 9    Tweet.find(evt[: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