Ajaxified Drag Drop Tree In Ror

  • May 2020
  • PDF

This document was uploaded by user and they confirmed that they have the permission to share it. If you are author or own the copyright of this book, please report to us by using this DMCA report form. Report DMCA


Overview

Download & View Ajaxified Drag Drop Tree In Ror as PDF for free.

More details

  • Words: 4,263
  • Pages: 20
Ajaxified Drag Drop Tree in RoR CASE STUDY I m providing a very generalized use case where the tree fits in a good position. Here it is… Consider a model Item, a controller Items. Item model is using a fabulous acts_as_tree and we are going to put a seed for Item to grow it in an ajax tree … Ok no more non-code talk. So, lets start the code now… ========================================================== I have also incorporated the code into a sample application which you can directly check out and try the tree yourself if you find it a headache to add the following code in a number of described files. So, here is the Sample Tree Application or you can try to code yourself as… Create a sample rails application say treeapp by running rails treeapp

from the command prompt. Now simply change your directiry into just created treeapp and make sure that you are in the directory treeapp Now configure the database settings for this application by modifying the file /config/database.yml as … development: adapter: mysql database: tree_dev username: root password: root host: localhost

Here it simply shows that you have a mysql database named tree_dev and a user root with password root can access this database. So make sure about these settings. From the command prompt in application root(i.e. you are in the directory treeapp) run this command to generate the model Item… treeapp> ruby script/generate model item

Add the following code to the file app/models/itme.rb class Item < ActiveRecord::Base acts_as_tree validates_presence_of :name attr_accessor :style def self.roots self.find(:all, :conditions=>["parent_id = ?", 0]) end

def level self.ancestors.size end end

This simply shows that you should have a table named tems in your database… so why we havnt mentioned it earlier ? Thats the thing which will make you feel an agile web development. Now look at the directory db/migrateand a you will find a file named as db/migrate/001_create_items.rb Add the following code to this file 001_create_items.rb Here we are creating our database table and also adding some initial data to work with. class CreateItems < ActiveRecord::Migration def self.up create_table "items", :force => true do |t| t.column "name", :string t.column "created_at", :datetime t.column "parent_id", :integer, :default => 0, :null => false end %w(item1 item2 item3 item4 item5).each do |name| parent = Item.new(:name=>name) parent.save Item.create(:name=>name+".1", :parent_id=>parent.id) Item.create(:name=>name+".2", :parent_id=>parent.id) Item.create(:name=>name+".3", :parent_id=>parent.id) end end def self.down drop_table :items end end

Now from the command line from the root of your application run the following command to have a table named Item in your database with some initial data. treeapp> rake db:migrate

Before we start handling our views and controller part just have a smart small image named as drag.gif in your public/images directory that we will use as a handle to drag the nodes. So, now you can see a small image at public/images/drag.gif, cool !. Now from the command line from the root of your application run the following command to create a controller … treeapp> ruby script/generate controller items show

Make sure that now you have the files app/controllers/items_controller.rb and app/views/items/show.rhtml. Add the following code in the file app/controllers/items_controller.rb class ItemsController < ApplicationController def show @items = Item.find(:all)

@item = Item.find(:first) # select according to your choice... #this item will be selected node by default in the tree when it will first be loaded. end 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 = Item.find(params[:id]) rescue nil 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" page.replace_html "selected_item", :partial=>"items/item", :object=>@item page.visual_effect 'toggle_appear', "selected_item" end else return render :nothing => true end end end def sort_ajax_tree if request.xhr? if @item = Item.find(params[:id].split("_").first) rescue nil parent_item = Item.find(params[:parent_id]) render :update do |page| @item.parent_id = parent_item.id @item.save @items=Item.find(:all) page.replace_html "ajaxtree", :partial=>"items/ajax_tree", :object=>[@item,@items] page.hide "selected_item" page.replace_html "selected_item", :partial=>"items/item", :object=>@item page.visual_effect 'toggle_appear', "selected_item" end else return render :nothing => true end end end end

Add the following code in the file app/views/items/show.rhtml

Ajax Tree Application

<%= render :partial=>’items/ajax_tree’, :object=>[@item,@items] %>
<%= render :partial=>’items/item’, :object=>@item %>



Add the following code in the file app/views/items/_item.rhtml <% if @item %>

Selected Item is <%=h @item.name%>

<% else %> Item not found <% end %>

Add the following code in the file app/views/items/_ajax_tree.rhtml <script type="text/javascript"> function toggleDiv() { Element.toggle('mytree'); Element.toggle('expanded'); Element.toggle('collapsed'); return false; } function showDrag() { var drag_images = $$('img.drag_image'); drag_images.all(function(value,index){return value.style.display='inline';}); Element.toggle('done'); Element.toggle('reorder'); return false; } function hideDrag() { var drag_images = $$('img.drag_image'); drag_images.all(function(value,index){return value.style.display='none';}); Element.toggle('done'); Element.toggle('reorder'); return false; } <style> .mytree{padding:0 0 0 0px;} .mytree li {padding:2 0 0 3px;} .outer_tree_element{margin:0 0 0 10px;} .inner_tree_element{margin:5px 0 0 10px;} .mytree a{text-decoration:none; font-size:13px; color:black;} .mytree a:hover{background-color:lightblue;} .mytree label{font-weight:normal;} .highlighted{background-color:lightblue;} .normal{background-color:white;}

.drag_image{border:0px;}
<% @ancestors = @item.ancestors.collect{|parent| parent.id} if @item.has_parent? %> <% @items = Item.find(:all) %> <%= get_tree_data(@items, 0){|n| link_to_remote(n.name, :url=>{:controller=>'items', :action=>'display_clicked_item', :id=>n.id}, :loading=>"Element.show('tree_indicator')", :complete=>"Element.hide('tree_indicator')" )} %> <% @items.each do |node| %> <%= draggable_element node.id.to_s+'_tree_div',:revert=>true,:snap=>false, :handle=>"'#{node.id.to_s}_drag_image'" %> <%= drop_receiving_element node.id.to_s+'_tree_div', :accept=>'inner_tree_element', :url=>{:controller=>'items',:action=>'sort_ajax_tree', :parent_id=>node.id,:id=>nil}, :loading=>"Element.show('sort_tree_indicator')", :complete=>"Element.hide('sort_tree_indicator')" %> <% end %> <%= image_tag 'indicator.gif', :id=>'tree_indicator', :style=>'display:none' %> <%= image_tag 'indicator.gif', :id=>'sort_tree_indicator', :style=>'display:none' %>
<script type="text/javascript"> var selected_el = document.getElementById('<%[email protected]%>_tree_item'); selected_el.className='highlighted'; function toggleMyTree(id) { Element.toggle(id+'collapsed'); Element.toggle(id+'expanded'); Element.toggle(id+'children'); return false; } function toggleBackground(el) { // using collection proxies to change the background var highlighted_el = $$("span.highlighted"); highlighted_el.all(function(value,index){return value.className='normal'}); el.className='highlighted'; selected_el = el; return false; } function openMyTree(id)

{ Element.hide(id+'collapsed'); Element.show(id+'expanded'); Element.show(id+'children'); return false; }

As you can see in the above file we have used some indicator and toggle images. So you will be required to have three more images in the directory public/images/. Here is the small description about these images… •

• •

An indicator image that will be displayed at the bottom of the tree whenever a tree node is clicked. You can select from a number of Ajax Inidicatorsavailable on the web. Select one indicator image and save in your app with the name indicator.gif. So, now make sure that you can see the image at public/images/indicator.gif Second, we need to have a small image with + sign. That will be used to toggle the tree. save it as public/images/collapsed.gif Similarly, an image with - sign. Save it as public/images/expanded.gif

We have to include the prototype and scriptaculous javascript libraries in the application. So just manually create a layout file app/views/layouts/application.rhtml and add the following code in the file application.rhtml <%= javascript_include_tag :defaults %> <%= @content_for_layout %>

Now the last but the most importatnt…The recursion to obtain the tree. Add the following code in the file app/helpers/application_helper.rb module ApplicationHelper def get_tree_data(tree, parent_id) ret = "
" 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 += "
" if node.has_children? ret += " "

ret += " " end ret += " " ret += "<span id='#{node.id}_tree_item'>" ret += yield node ret += "" ret += "<span id='#{node.id}children' style='#{node.style}' >" ret += get_tree_data(node.children, node.id){|n| yield n} ret += "" ret += "
" end end ret += "
" return ret end end

Now you can check the tree functionality at http://localhost:3000/items/show.. assuming that you are running your server on port 3000. njoy!! Entry Filed under: ajax, rails, tree, ajax tree, drag drop tree, navigation tree

26 Comments Add your own •

1. abap | August 22nd, 2006 at 6:32 am Good



2. Ajax on Rails » Ful&hellip | August 22nd, 2006 at 4:49 pm […] This tree works very fine in my application and hope it will help u also. Check out the Source Code of the tree. […]



3. Source Required | September 26th, 2006 at 8:18 pm Give me the source



4.

SUR | September 27th, 2006 at 5:37 am

Hi Source Required !! Check out the Source Code •

5. Rana | November 5th, 2006 at 2:51 pm Hello Thanks for the code although i am still having problems adding it to my project

Showing app/views/items/_ajax_tree.rhtml where line #70 raised: can’t convert Array into String 70: •

6.

Sur Max | November 8th, 2006 at 2:44 am

Hi Rana !! I am figuring it out where the problem is exactly by trying it in a new test application. I will post the corrected one soon. •

7. eastviking | November 13th, 2006 at 9:29 am can’t convert Array into String Yes,I got the error too. and: Add the following code in the file app/views/items/_item.rhtml Selected Item is Item not found should be: Add the following code in the file app/views/items/_item.rhtml Selected Item is Item not found Add the following code in the file app/views/items/show.rhtml Ajax Tree Application \’items/ajaxtree\’, :object=>[@item,@items] %> should be: Add the following code in the file app/views/items/show.rhtml Ajax Tree Application \’items/ajax_tree\’, :object=>[@item,@items] %> _ajax_tree.rhtml {:controller=>\’items\’,:action=>\’display_clicked_item\’,:id=>n.id} :loading=>\”Element.show(\’tree_indicator\’)\”,

:complete=>\”Element.hide(\’tree_indicator\’)\”, } should be: {:controller=>\’items\’,:action=>\’display_clicked_item\’,:id=>n.id}, :loading=>\”Element.show(\’tree_indicator\’)\”, :complete=>\”Element.hide(\’tree_indicator\’)\” } •

8. Alex | November 16th, 2006 at 9:23 am Hello Thanks for the code but i am also have can’t convert Array into String Showing app/views/items/_ajax_tree.rhtml where line #70 in just created, clear project



9.

Sur Max | November 16th, 2006 at 10:36 am

Hello everyone !! I am correcting the code and will upload it by tomorrow and will post a comment thereby. •

10.

Sur Max | November 21st, 2006 at 7:50 pm

Hello Everyone !! Sorry for the delay… Hi Alex, Eastviking, Rana, Eric… I was through with the code this weekend and i found some of my stupid mistakes, sorry for that… anwaz I have uploaded the modified corrected code. I have also tested it in a fresh newly created application and it is working fine. Thanks. •

11. schmii | November 22nd, 2006 at 5:44 am Programmers inhumanity to man Its amazing how such a simple code fragment has spanned 3-months to rectify the sample code. Could you please direct me to the latest source code. thanks schmii



12.

Sur Max | November 22nd, 2006 at 5:55 am

Hi schmii !! sorry to say but i am disappointed by ur invalid perception. The code was running fine before November, it was broken after it when i make it a bit generalized… so my maths says that it has taken around 15 days not 3 months and that too coz i was busy in my commercial projects. Anyways.. the published code in this post is now working. •

13. Ajax based drag drop and &hellip | November 25th, 2006 at 1:06 pm […] My friend sur wrote and shares his code for Ajax based drag drop and sortable tree for rails. He is also trying to pluginize this, and soon it will be publicly available. Find more detail here. […]



14. schmii | November 25th, 2006 at 5:59 pm Hi Sur thank you for sending me the zip files for ‘testapp’. I followed your instructions and it works great. I’m new to ruby and rails and I’m attempting to develop my first application. Your sample code has given me a working example from which I can apply to my application. thanks schmii



15. Sample Rails Application &hellip | November 26th, 2006 at 9:27 am […] I have provided the source code of the ajax based drag drop tree in rubyonrails in one of my previous posts. I found some of the people are getting problems to incorporate the code into their running applications so i am providing a sample rails application in which all the code for tree is already been placed well. […]



16. Gaurav | November 28th, 2006 at 1:31 pm Cool, Nice code. Now all I need is an web application to make use of this code.



17. Web On Rails » Blog&hellip | December 1st, 2006 at 6:35 am […] My friend sur wrote and shares his code for Ajax based drag drop sortable tree for rails. He is also trying to pluginize this, and soon it will be publicly available. Find more detail here. […]



18. andy | December 5th, 2006 at 4:41 pm hello! your code looks very interesting! i would greatly appreciate the sample poject to play with. thank you for sharing your hard work with all of us!

much appreciated, cheers, andy •

19.

Sur Max | December 5th, 2006 at 5:45 pm

Thanks Andy !! •

20. TimN | December 10th, 2006 at 10:40 pm Sur, Great project you are working on. I’ve implemented your code in the way you describe, but am still running into an RJS error. When I click on a parent group on the ’show’ page, my browser shows a javascript error: RJS Error: TypeError: Effect.toggle is not a function This happens to me on the Mac (Safari & Firefox) as well as on a PC (IE). I am fairly certain I implemented your code correctly (I did it twice, just to be sure and named all files and DB table the same as your example). Any ideas what I may be doing wrong? Thanks, Tim



21.

Sur Max | December 11th, 2006 at 3:36 am

Hi TimN !! Well, before i figure out if there is any problem, could you try the Sample Application in which you need not to code a single line but just need to follow 4 steps described Here. I will look forward if the problem still persists, let me know in any case whether or not the application is running fine. Thanks. •

22. TimN | December 11th, 2006 at 4:38 am Sur, I created my own “items” table with the fields you had in your schema, but other than that, I did follow the steps you described… I think Do you know of a publicly available URL where your example app is running so that I could check it out?

Best, Tim •

23. Kunjan | December 13th, 2006 at 1:43 pm How do I display the tree upto 2 or more Levels? Currently it is being displayed till only 1 level..



24.

Sur Max | December 13th, 2006 at 2:34 pm

Hi Kunjan !! Drag any element and drop it onto an element of second level, and the dropped element will become child and become a third level element. How you need not to explicitly specify any level for nodes, but just add any element having parent_id as the id of second level… third level… and so on. •

25. StevenG | December 26th, 2006 at 6:16 am Thanks for submitting this code. I have been looking at Javascript versions, but yours is much simpler and RoR native! It seemed the toggleBackground function was never called, so the selected item would never highlight. There may be a better way, but it can be fixed by adding ; toggleBackground($('#{n.id}_tree_item'));

to the loading or complete portion of {...}, :loading=>"Element.show('tree_indicator')", :complete=>"Element.hide('tree_indicator'); " )} %>

in _ajax_tree.rhtml. In other words, you want :loading=>"Element.show('tree_indicator'); toggleBackground($('#{n.id}_tree_item'));",



26.

Sur Max | December 26th, 2006 at 7:47 am

Thanks Steven, I guess i have missed that while extracting it from my application. I will add it now. Thanks.

Creating sortable lists with PHP and AJAX By Quentin Zervaas, 24 February 2006

Adding drag and drop functionality to our list We will now add the drag/drop functionality to our list, as well as applying CSS styles to the list. At this point the ordering of the list will not be saved, as we will do this in the next step.

Installing Scriptaculous Since we are using Scriptaculous to create the drag/drop effect, we must now download and install it. Note that we also need the Prototype library, however, this is included with the Scriptaculous download. •

Scriptaculous download page

This example uses Scriptaculous 1.5.3. Once downloaded, extract the library in the directory where you saved index.php. You may save this elsewhere, but we will assume this is where you have saved it.

Styling the list – styles.css Before we add the drag/drop, we will style the list. Below is a generic CSS class we will save to a file called styles.css.

Highlight: CSS .sortable-list { list-style-type : none; margin : 0; } .sortable-list li { border : 1px solid #000; cursor : move; margin : 2px 0 2px 0; padding : 3px; background : #f7f7f7; border : #ccc; width : 400px; }

The Scriptaculous drag sort code It’s really simple to make our list drag-sortable. At this point we’re not actually saving the drag changes, but to make the list sortable, the following code is used:

Highlight: JavaScript Sortable.create('movies_list');

The name movies_list refers to the ID of our unordered list. There are many more options and effects that can be applied, but the default options work just fine for what we’re doing. You can always read the Scriptaculous documentation for more options.

Our new index.php So here is the new version of index.php, with styles added, Scriptaculous and Prototype loaded, and our draggable list created:

Highlight: PHP phpRiot Sortable Lists <script type="text/javascript" src="scriptaculous-js1.5.3/lib/prototype.js"> <script type="text/javascript" src="scriptaculous-js1.5.3/src/scriptaculous.js">

phpRiot Sortable Lists

    $title) { ?>
<script type="text/javascript"> Sortable.create('movies_list');

Note that we also added an ID to each list item, as these are the values that will be passed to the form. Note that these IDs—and the ID of the list—should use underscores as separators, not hyphens.

So at this point, if you view this page, you should be able to drag the items in your list up and down! Cool eh?

Creating sortable lists with PHP and AJAX By Quentin Zervaas, 24 February 2006

Creating the order processing script Now we need to write the script that processes any ordering changes to the list. Once this is done, we’ll add the functionality to our list to actually call this script. When a change to the list occurs, an array of the movie ID’s in their new order is generated, so our processor needs to take this array, and then update the ranking field in the database accordingly. To achieve this, we create a new function in our movies.php, called processMoviesOrder(). Add this function after the getMovies() function in movies.php.

processMoviesOrder() for MySQL

Highlight: PHP
= %d',

$ranking, $movie_id);

} ?>

mysql_query($query); $ranking++;

}

processMoviesOrder() for PostgreSQL

Highlight: PHP
{ if (!isset($_POST[$key]) || !is_array($_POST[$key])) return; $movies = getMovies(); $queries = array(); $ranking = 1; foreach ($_POST[$key] as $movie_id) { if (!array_key_exists($movie_id, $movies)) continue; $query = sprintf('update movies set ranking = %d where movie_id = %d',

$ranking, $movie_id); pg_query($query); $ranking++;

}

}

?>

processor.php for MySQL and PostgreSQL Now here is the script that calls the processMoviesOrder script. Note that we pass the form index that holds the ordering values. There’s no great reason for doing this other than if you change the form key then you only have to change it here (note that this is the unordered list ID from index.php).

Highlight: PHP

processMoviesOrder('movies_list');

Adding the JavaScript sorting callback The final item we must add is the JavaScript code to invoke processor.php when the list is updated. This involves creating a function that makes the Ajax update request, as well as telling the Scriptaculous Sortable.create() method about it. Here’s the callback function:

Highlight: JavaScript function updateOrder() { var options = { method : 'post', parameters : Sortable.serialize('movies_list') };

new Ajax.Request('processor.php', options); }

Here we invoke the Prototype library’s Ajax request handler to call processor.php. Additionally, we use the serialize() method on the Scriptaculous Sortable object to create the POST variable we access in processor.php. Finally, we modify our list creation to tell it about this updateOrder() callback:

Highlight: JavaScript Sortable.create('movies_list', { onUpdate : updateOrder });

The second parameter to Sortable.create() is an optional list of extra parameters. In this case we are just specifying the onUpdate parameter, which tells Sortable which function to call when the list is changed.

index.php for MySQL and PostgreSQL in full

Highlight: PHP phpRiot Sortable Lists <script type="text/javascript" src="scriptaculous-js1.5.3/lib/prototype.js"> <script type="text/javascript" src="scriptaculous-js1.5.3/src/scriptaculous.js">

phpRiot Sortable Lists

    $title) { ?>
<script type="text/javascript"> function updateOrder() { var options = { method : 'post', parameters : Sortable.serialize('movies_list')

}; new Ajax.Request('processor.php', options); } Sortable.create('movies_list', { onUpdate : updateOrder });

Now, when you visit this page, you will see the list just as you did before, but now when you drag an item to a new location, it will be saved in the database. If you don’t believe me, try dragging an item, closing your browser, then reloading the page. The order will be just as you left it after dragging the item.

Summary In this article we learned how to create a sortable list using PHP and Ajax. We used Scriptaculous and Prototype libraries to make light work of our JavaScript requirements (the sorting and Ajax requests), as these libraries provide a very powerful and simple interface to advanced features and effects.

Error handling We didn’t deal with error handling at all in this article, for the sake of simplicity. Specifically, we didn’t specify what would happen if the update didn’t work. If the update failed, the list would appear to be updated, but when you refreshed the list it would be the old state. One possible way to handle this would be to send a success/failure indication from processor.php, and then to read this response in index.php, rolling back the drag and drop if failure was returned.

Extra features When you update the list, the saving of the new ordering is a very quick process, but it is possible that sometimes it could take longer due to latency or server load. As such, you might think about showing then hiding a message while performing the update. To do this, you would make the message appear when updateOrder() is called, and then create another function to hide the message once complete. This is achieved by specifying the onComplete parameter in the options array for the Ajax request. Here’s an example:

Highlight: JavaScript function updateOrder() { // turn on update message here var options = {

method : 'post', parameters : Sortable.serialize('movies_list'), onComplete : function(request) {

// turn off update message here }

};

new Ajax.Request('processor.php', options); }

I’ll leave this as an exercise for you to complete. Hint: create a div which you initially set the CSS display property to ‘none’. Then set it to ‘block’ to show the div, and set it back to ‘none’ to hide it again.

Sortable Lists Demo Discuss (48 comments) Print E-mail

This is the first list • •

Item 2 from first list. Item 3 from first list.

And now the second list • • • •

DRAG HEREItem 1 from second list. DRAG HERE Item 2 from second list. Item 1 from first list. DRAG HERE Item 3 from second list.

Note: you can drag-and-drop between lists as well.

This is the first list

  • Item 1 from first list.
  • Item 2 from first list.
  • Item 3 from first list.

And now the second list

  • <span class="handle">DRAG HERE Item 1 from second list.
  • <span class="handle">DRAG HERE Item 2 from second list.


  • <span class="handle">DRAG HERE Item 3 from second list.
<script type="text/javascript"> //

note: The script tag that encloses the Sortable.create function calls needs to occur after all of the lists that you intend to use. IE.. in this example if you were place the “Sortable.create(“firstlist”...);” call immediately after that list (and before the second list) you would only be able to drag from list 1 to list 2 and not from list 2 to list 1. The easiest way avoid this is just to call all of your Sortable.creates in the same script tag near the end of your page. Ruby on Rails Single List M[V]C # view
    <% 6.times do |i| -%>
  • I'm number <%= i+1 %>
  • <% end -%>

<%= sortable_element 'list', :update => 'list-info', :complete => visual_effect(:highlight, 'list'), :url => { :action => "order" } %> # controller def order params[:list].each_with_index { |id,idx| Model.update(id, :position => idx) } render :text => 'Updated sort order' end

Related Documents