HABTM - two lists


#1

Does anyone know of a good example somewhere of an implemention for
adding records with HABTM relations that have lots of possibilities.
The best way that I’ve seen it done is where you have two lists
(multiple select lists) where you have the possibilities on the left and
ones you’ve added on the right with arrows to add and delete items. But
the only ways I know how to implement that solution are ugly (because
the form will only submit selected items from the right).

Does anyone know how to implement the two lists elegantly or of another
solution that works well for many items?


#2

I used a tree.js (http://www.silverstripe.com/downloads/tree/)
controller as my lists
where somewhat hierarchical so it cut down on the screen real-estate for
very large lists.

I put two trees on the page side by side, the left tree is what I
currently have and
what I can choose from on the right.

I added check boxes to the right hand tree nodes, so I select the ones I
want and then
click a button below that basically replaces the items on the left side
with the ones
selected on the right.

Example code explains it better I guess :slight_smile:
This example sets up roles and rights for a standard RBAC.

Rights has_and_belongs_to_many :roles
Roles has_and_belongs_to_many :rights

I setup the data by creating hashes in the controller which are
available to the view…

roll everything up into a hierarchical tree structure

top level is hash of roles

next level is hash of permissions with controller as key and array

of actions as value
@role_tree= {}
roles= Role.find_all
roles.each do |r|
ph= {}
rights= r.rights
rights.each do |p|
ph[p.controller] ||= []
ph[p.controller] << p.action
end
@role_tree[r.name] = ph
end

    # creates a tree of controllers with the actions for each 

controller
# top level is hash of controllers
# next level is an array of actions
@rights_tree= {}
rights= Right.find_all
rights.each do |r|
name= r.controller
@rights_tree[name] ||= []
@rights_tree[name] << r.action
end
end

The view is simple… (edit.rhtml)

Role and Rights Editor

Roles Rights
<%= render_role_tree(@role_tree) %> <%= start_form_tag :action => 'edit' %> <%= render_rights_tree(@rights_tree) %>

The checked items above will replace the Rights of the following Roles:
<% @role_tree.each_key do |role| %> <%= check_box_tag "role", role %> <%= role %> <% end %>

<%= submit_tag "Allocate to selected Roles" %> <%= end_form_tag %>

This part goes in helpers, and creates the html for the two trees this
is specific for
the silverstripe tree control)…

module RoleHelper
def render_role_tree(tree)
ret = ‘’
ret += “

    tree.each_key do |r|
        ret += '<li><a href="#">' + r + '</a>' # list roles
        h= tree[r] # get hash of controllers/[actions]
        unless h.empty?
            ret += '<ul>'
            h.each_key do |c|
                ret += '<li><a href="#">' + c + '</a>' # list 

controllers
a= h[c]
unless a.empty?
ret += ‘

                   end
                   ret += '</ul>'
                end
                ret += '</li>'
            end
            ret += '</ul>'
        end
        ret += '</li>'
    end
    ret += '</ul>'
    ret
end

def render_rights_tree(tree)
    ret = ''
    ret += "<ul class='tree'>"

    tree.each_key do |r|
        ret += "<li><a href=\"#\">#{r}</a>"# list controllers
        aa= tree[r] # get array of actions
        unless aa.empty?
            ret += '<ul>'
            ret += "<li><a href=\"#\" 

onclick=“checkAll(‘right[#{r}][]’); return
false;”>Check All"
aa.each do |a|
ret += ‘

  • ’ + “<input type=“checkbox”
    name=“right[#{r}][]”
    value=”#{a}" />" + a + ‘
  • ’ # list actions
    end
    ret += ‘’
    end
    ret += ‘’
    end
    ret += ‘’
    ret
    end
    end

    the action handler in the controller for the post is a little more
    complicated in my
    case, but basically goes through all the selected checkboxes and sets
    the relevant habtms.

    def edit
    if request.post?
    #p params
    unless params[‘role’].nil?
    a_roles= params[‘role’]
    a_rights= params[‘right’]
    unless a_rights.nil?
    a_roles.each { |ar|
    role= Role.find_by_name(ar)
    unless role.nil?
    role.rights.clear
    a_rights.each { |controller, aa|
    aa.each { |action|
    right=
    Right.find_by_controller_and_action(controller, action)
    unless right.nil?
    role.rights << right
    #puts “Allocating right
    #{controller}/#{action}
    to #{ar}”
    else
    puts “Right
    #{controller}/#{action} not found”
    end
    }
    }
    else
    puts “Role #{ar} not found”
    end
    }
    flash[:notice]= ‘rights updated’
    else
    flash[:warning]= ‘no rights were selected’
    end
    else
    flash[:warning]= ‘You need to select the role to
    allocate to’
    end
    end

    end

    Hope that helps and is not too complex :slight_smile: