Skip to content

Adding a page widget

jamesu edited this page Sep 12, 2010 · 7 revisions

Introduction

Want to add a page widget to Rucksack? Well here’s how you do it.

The details

Widgets are essentially the content in a Rucksack page. While they are linked to the page by a belongs_to relationship, they are positioned via another model, the PageSlot.

In order to add a Widget to RuckSack, you’ll need to implement the following:

  • A model for the Widget
  • A RESTful controller for the Widget
  • JavaScript code to handle insertion and editing

Then to link it into the system:

  • Add a route for it in “config/routes.rb”
  • Add it to “self.widgets” in the Page model

The model

There are a few important things to include in the model. These are:

  • The associations with the page and page_slot
  • The associations with the application_logs
  • Handlers for create, update, and destroy to add application logs
  • object_name to display the objects name
  • view_partial which is used to display the object
  • form_partial which is used to display the objects form on the InsertionBar tablet (set to nil if you want to create the object as-is)
  • duplicate method which is called when the page is being duplicated
  • The permissions handlers (technically controller-specific, so not necessarily needed)

class Cake < ActiveRecord::Base
  belongs_to :page
  has_one :page_slot, :as => :rel_object

  has_many :application_logs, :as => :rel_object, :dependent => :destroy

  belongs_to :created_by, :class_name => 'User', :foreign_key => 'created_by_id'
  belongs_to :updated_by, :class_name => 'User', :foreign_key => 'updated_by_id'

  after_create   :process_create
  before_update  :process_update_params
  before_destroy :process_destroy

  def process_create
    ApplicationLog.new_log(self, self.created_by, :add)
  end

  def process_update_params
    ApplicationLog.new_log(self, self.updated_by, :edit)
  end

  def process_destroy
    ApplicationLog.new_log(self, self.updated_by, :delete)
  end

  def object_name
    "A delicious cake"
  end

  def view_partial
    "cake/show"
  end

  def self.form_partial
    nil
  end

  def duplicate(new_page)
    new_cake = self.clone
    new_cake.created_by = new_page.created_by
    new_cake.page = new_page

    new_cake.save!
    new_cake
  end

  # Common permissions

  def self.can_be_created_by(user, page)
     page.can_add_widget(user, Cake)
  end

  def can_be_edited_by(user)
     self.page.can_be_edited_by(user)
  end

  def can_be_deleted_by(user)
     self.page.can_be_edited_by(user)
  end

  def can_be_seen_by(user)
     self.page.can_be_seen_by(user)
  end
end

The controller

To start off with, you’ll need a standard RESTful rails controller. There are a few specifics which need to be addressed

  • JavaScript needs to be returned on js requests for edit, create, update and destroy
  • Permissions need to be enforced with a suitable rejection mechanism

For edit, make sure you return something like this:

page.replace_html "page_slot_#{@cake.page_slot.id}", :partial => 'form'

For create, you’ll first need to make sure you create your widget with a slot assigned using something like this:


    # Calculate target position
    calculate_position

    # Make the darn cake
    @cake = @cake.separators.build(params[:separator])
    @cake.created_by = @logged_user
    saved = @cake.save

    # And the slot, don't forget the slot
    save_slot(@cake) if saved

    @cake.save # ...

Then for the js handler, something like this should suffice:


page.insert_html((@insert_before ? :before : :after), 
                             @insert_element, 
  "<div class=\"pageSlot\" id=\"page_slot_#{@slot.id}\" slot=\"#{@slot.id}\"></div>")

page.replace_html("page_slot_#{@slot.id}", 
                             :partial => 'pages/slot', 
                             :locals => {:object => @cake, 
                                               :slot => @cake.page_slot})
page.call "Page.makeSortable"

Note the call to “Page.makeSortable”, which makes sure that the new page widget can be sorted.

For update, this should suffice for the js request:


page.replace_html "page_slot_#{@cake.page_slot.id}", 
                               :partial => 'pages/slot',
                               :locals => {
                                 :object => @cake, 
                                 :slot => @cake.page_slot 
                               }
page.call "Page.makeSortable"

For destroy, you can simply return this:


page.remove "page_slot_#{@slot_id}"

The view

For the show partial, the only special thing you will need is to assign a hoverhandler attribute to any element for which you want the page slot’s hover handle to pop up.


- hhandle = "page_slot_handle_#{object.page_slot.id}"
.pageCake{:hover_handle => hhandle}
  %img{:src => 'cake.png', :alt => 'A lie. A lie.'}

The JavaScript code

For the bare essentials, all you need is a click handler registering for ‘.add_(Insert class of your widget here)’. Make sure you add the bind to the bind function in Page, and the unbind to the unbind function in Page.


var Page =
{
...
  bind: function(){
...
      $('.add_Cake').click(function(evt) {
        // Set to top of page if on top toolbar
        if ($(this).hasClass('atTop'))
          InsertionMarker.set(null, false);

        // Use if you have a form in InsertionBar's tablet
        //var form = $('#add_CakeForm');
        //InsertionBar.setWidgetForm(form);        
        //form.autofocus();

        // Otherwise send a request
        Page.insertWidget('cake');

        InsertionBar.hide();
        InsertionMarker.setEnabled(true);
        HoverHandle.setEnabled(true);

        return false;
      });
...
  }
...
  function unbind() {
...
    $('.add_Note').unbind();
...
  }
...
}

Routing

When adding the route to “config/routes.rb”, make sure it is nested in the :pages route.


  map.resources :pages do |page|
    page.resources :lists, :member => {:reorder => :post, :transfer => :put} do |list|
        list.resources :list_items, :as => 'items', :member => {:status => :put}
    end
    page.resources :notes
    page.resources :separators
    page.resources :cake # Cake widget
  end

Widgets list

For a widget to be listed in the “Add to page:” list, it needs to be in the “Page.widgets” list. This is located in “app/models/page.rb”


	def self.widgets
	   [List, Note, Separator, Cake] # Add Cake widget
	end