Post Meta

Bookmarks

  • Delicious
  • Digg
  • Reddit
  • Magnolia
  • Newsvine
  • Furl
  • Facebook
  • Technorati

Now you can use more than one ajax tree controls on a single page.

By this I’m trying to make more useful Sur’s wonderful Ajax Tree control.

He presented in summer 2006 a drag-and-drop capable ajaxified tree control for acts_as_tree models in Rails. In his example the Ajax Tree control is capable to handle one model / page, and the solution is not DRY. This is my first attempt to DRY this ajax tree control.

As a result you can have as many ajax trees you want on your page, each linked to different models.

Later I’ll try to pluginize this code to make it easier to use.

  1. In each of your acts_as_tree models where you want an ajaxtree control add the following code. For example I’m added this code to my app/models/department.rb and app/models/orgchart.rb
    1
    2
    3
    4
    5
    6
    7
    
    attr_accessor :styledef self.roots
    self.find(:all, :conditions => ["parent_id = ?", 0])
    end
     
    def level
    self.ancestors.size
    end
  2. Then in the controller associated to the page which displays the ajaxtree controls you must initialize the data for the ajax tree controller: My controller is app/controllers/administration_controller.rb.
    1
    2
    3
    4
    5
    6
    
    # firing up Ajaxtree
    @depts = Department.find(:all)
    @dept = Department.find(:first)@orgs = Orgchart.find(:all)
    @org = Orgchart.find(:first)
     
    @draggable = false

    @draggable is switching on/off the draggable capability of the tree. I do not need this option so I’ve turned off.

  3. In your controller’s view (mine is app/views/administration/org_structure.rhtml) you’ll have to set up the two AjaxTree controls.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    
    <%
    @title = Department.localized_model_name
    @item = @dept
    @items = @depts
    @partial = "departments/ajaxtree_item"
    @div = "depts"
    @model = Department
    %>;
    <%= render(:partial  =>  'shared/ajax_tree/container', :object  =>  [@title, @item, @items, @partial, @div, @model]) %>;<%@title = Orgchart.localized_model_name
    @item = @org
    @items = @orgs
    @partial = "orgcharts/ajaxtree_item"
    @div = "orgs"
    @model = Orgchart
    %>;
    <%= render(:partial  =>  'shared/ajax_tree/container', :object  =>  [@title, @item, @items, @partial, @div, @model]) %>;

    This is the screenshot ot the two AjaxTree controls in one single page set up with the code above:
    Two AjaxTree Controls in a page

    1. @title is the title of the AjaxTree (like “Departamente” and “Schema organizationala”)
    2. @item, @items are the data to be displayed, set up in the controller before.
    3. @partial is the link to the partial which will display a selected element in the tree.
    4. @div is the unique ID of each AjaxTree control.
    5. @model is the name of the model to be displayed.
  4. In your shared views folder you must create the ajax_tree folder and place these two files here:
    1. app/views/shared/ajax_tree/_ajax_tree.rhtml
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      
      <p id="<%=h @div %>;&gt;"> class="mytree"&gt;  <% @ancestors = @item.ancestors.collect{|parent| parent.id} if @item.has_parent? %>;</p>
      <%= get_tree_data(@items, nil, @draggable, @div){|n|
      link_to_remote(n.name,
      :url => {:action => 'display_clicked_item', :id => n.id, :partial  =>  @partial, :div  =>  @div, :model  =>  @model},
      :loading => "Element.show('#{@div}-tree_indicator')",
      :complete => "Element.hide('#{@div}-tree_indicator')",
      :after  =>  "$('#{@div}-#{n.id}_tree_item').addClassName('highlighted')",
      :before  =>  "toggleBackground('#{@div}-#{n.id}_tree_item')"
      )}
      %>;
       
      <%= image_tag 'indicator.gif', :id =>  @div + '-tree_indicator', :style => 'display:none' %>;
      <%= image_tag 'indicator.gif', :id =>  @div + '-sort_tree_indicator', :style => 'display:none' %>;
    2. app/views/shared/ajax_tree/_container.rhtml
      1
      2
      3
      4
      5
      
      <p id="ajaxtree-container">&nbsp;</p>
       
      <h3><%=h @title%>;</h3>
      <p id="ajaxtree-<%=h @div%>;" class="ajaxtree"> <%= render :partial => "shared/ajax_tree/ajax_tree", :object => [@item, @items, @partial, @div, @model] %>;</p>
      <p id="selected_item-<%=h @div%>;" class="selected_item"> <%= render :partial => @partial, :object => @item %>;</p>
  5. In each model you display with AjaxTree add a partial view to display the selected item. For example I’ve placed the foolowing code into app/views/departments/_ajax_tree_item.rhtml and app/views/orgcharts/_ajax_tree_item.rhtml.
    1
    2
    3
    4
    5
    6
    7
    8
    
    <% if @item %>;
    <h3><%=h @item.name%>;</h3>
    <!--
    &nbsp;&nbsp;DO WHATEVER YOU WANT WITH YOUR SELECTED ITEM HERE
    &nbsp;&nbsp;-->
    <% else %>;
    Item not found
    <% end %>;
  6. In your application helper add the following method:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    
    # AjaxTree
    def get_tree_data(tree, parent_id, draggable, div)
    ret = "
    <p class="outer_tree_element">"
    tree.each do |node|
    if node.parent_id == parent_id
    node.style = (@ancestors and @ancestors.include?(node.id))? 'display:inline' : 'display:none'
    display_expanded = (@ancestors and @ancestors.include?(node.id))? 'inline' : 'none'
    display_collapsed = (@ancestors and @ancestors.include?(node.id))? 'none' : 'inline'
    ret += "
     
    <p class="inner_tree_element" id="#{div}-#{node.id}_tree_div">"
    if node.has_children?
    ret += "<img src="http://clair.ro/images/expanded.gif" onclick="javascript: return toggleMyTree(\" id="#{div}-#{node.id.to_s}expanded" style="cursor: pointer" />  "
    ret += "<img src="http://clair.ro/images/collapsed.gif" onclick="javascript: return toggleMyTree(\" style="cursor: pointer" id="#{div}-#{node.id.to_s}collapsed" />  "
    end
     
    ret += " <img src="http://clair.ro/images/drag.gif" style="cursor: move" id="#{node.id}_drag_image" class="drag_image" align="absmiddle" /> " if draggable
     
    ret += "<span id="#{div}-#{node.id}_tree_item">"
    ret += yield node
    ret += "</span>"
    ret += "<span id="#{div}-#{node.id}children">"
    ret += get_tree_data(node.children, node.id, draggable, div){|n| yield n}
    ret += "</span>"
    ret += ""
    end
    end
    ret += ""
    return ret
    end
  7. In your controller which displays the page containing the AjaxTree controls add the following method:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
    #for AjaxTree
    def display_clicked_item
    # this action will handle the two way syncronization...all the tree nodes(items) will be linked
    # to this action to show the detailed item on the left of the tree when the item is clicked
    # from the tree
    if request.xhr?
    @item = Department.find(params[:id]) if params[:model] == "Department"
    @item = Orgchart.find(params[:id]) if params[:model] == "Orgchart"
    if @item# the code below will render all your RJS code inline and
    # u need not to have any .rjs file, isnt this interesting
    render :update do |page|
    page.hide "selected_item-" + params[:div]
    page.replace_html "selected_item-" + params[:div], :partial => params[:partial], :object => @item
    page.visual_effect 'toggle_appear', "selected_item-" + params[:div]
    end
    else
    return render :nothing  =>  true
    end
    end
    end
  8. And finally add the ajaxtree.css and ajaxtree.js to your /public directory and update your layout. (app/views/layouts/application.rhtml).
    1
    2
    
    <%= stylesheet_link_tag 'ajaxtree' %>;
    <%= javascript_include_tag 'ajaxtree' %>;
  9. Download the necessary images and put them into your public/images directory.

collapsed.gifexpanded.gifindicator.gif



Related posts

  1. Gravatar

    First off thanks, I’m really interested in what you have here. When I try and run this in Rails 2.0 I have been getting a lot of errors (also copying it from the site is a bit annoying).

    1 error was that .has_parent? is deprecated now is .parent instead without the ?

    2) this one I can’t seem to find a solution too: undefined method `style=’ for #
    seems to be an error in the get_tree_data function, where you say node.style

    Any possible way for you to put together a sample app that works in 2.0? or at least update the blog to 2.0?

    thanks

    04 / 16 / 20:33
  2. Gravatar

    Joe,

    I’m working on a large project ( >1 year) and I’m freezed to Rails 1.2.3 … Around the project’s first release I’ll try to upgrade to the latest Rails, but until then it’s almost impossible, I’m late on some deadlines … Sorry.

    However I would be interested in sharing (also other) views/controls as plugins in a more “official way” if I’ll get some help.

    04 / 19 / 09:44
  3. Gravatar

    Thanks for Walter LOCKHART pointing me out the images are missing ….

    05 / 14 / 09:01

Leave A Comment

+ -