Update : added support for subgrids. Have a look at the last example!
The jQuery grid plugin is an amazing Javascript project providing multi-functions Ajax datagrids for your web applications. With the 2dcJqgrid Rails plugin , you can now add these datagrids to your Ruby on Rails applications with just a few lines of code.
If you don't like the look & feel of this demo, you can easily customize it using jQuery themes .
Communications between your grids and the server will use the JSON format to exchange data.
The source code of this demo application is available on GitHub.
This solution is compatible with most of web browsers (even Internet Explorer 6 !!).
If you have any comments or suggestions, please post them here .
Inside your Rails application :
Add some data to your migration file, you can use this one .
Run the migration :
Open the default layout created for you by the scaffold script (layouts/users.html.erb) and add the required JS & CSS in the header :
Replace the index method in your controller by this one :
def index users = User . find ( :all ) do if params [ :_search ] == " true " pseudo =~ " %#{params[:pseudo]}% " if params [ :pseudo ]. present? firstname =~ " %#{params[:firstname]}% " if params [ :firstname ]. present? lastname =~ " %#{params[:lastname]}% " if params [ :lastname ]. present? email =~ " %#{params[:email]}% " if params [ :email ]. present? role =~ " %#{params[:role]}% " if params [ :role ]. present? end paginate :page => params [ :page ], :per_page => params [ :rows ] order_by " #{params[:sidx]} #{params[:sord]} " end respond_to do | format | format . html format . json { render :json => users . to_jqgrid_json ([ :id , :pseudo , :firstname , :lastname , :email , :role ], params [ :page ], params [ :rows ], users . total_entries ) } end end
The query has been added into the controller for clarity purposes in this demo. It's of course a better idea to create a method in your User class. Notice how the squirrel plugin makes it easy to add filters and pagination to our finder.
Also notice the to_jqgrid_json method, it will generate the required JSON for you. The order of the fields in the first parameter matters, it should be the same than the display order in your datagrid.
You are now ready to create datagrids.
<script type="text/javascript"> var lastsel; jQuery(document).ready(function(){ jQuery("#players").jqGrid({ // adding ?nd='+new Date().getTime() prevent IE caching url:'/users?nd='+new Date().getTime(), editurl:'', datatype: "json", colNames:['ID','Pseudo','Firstname','Lastname','Email','Role'], colModel:[{name:'id', index:'id',width:35,resizable:false},{name:'pseudo', index:'pseudo'},{name:'firstname', index:'firstname'},{name:'lastname', index:'lastname'},{name:'email', index:'email'},{name:'role', index:'role'}], pager: jQuery('#players_pager'), rowNum:10, rowList:[10,25,50,100], imgpath: '/images/themes/lightness/images', sortname: 'id', viewrecords: true, toolbar : [true,"top"], sortorder: 'asc', subGrid:false, caption: "Football Players" }); jQuery("#t_players").height(25).hide().filterGrid("players",{gridModel:true,gridToolbar:true}); jQuery("#players").navGrid('#players_pager',{edit:false,add:false,del:false,search:false,refresh:true}) .navButtonAdd("#players_pager",{caption:"Search",title:"Toggle Search",buttonimg:'/images/jqgrid/search.png', onClickButton:function(){ if(jQuery("#t_players").css("display")=="none") { jQuery("#t_players").css("display",""); } else { jQuery("#t_players").css("display","none"); } } }); }); </script>
The code used to create this grid is :
<%= jqgrid (" Football Players ", " players ", " /users ", [ { :field => " id ", :label => " ID ", :width => 35 , :resizable => false }, { :field => " pseudo ", :label => " Pseudo " }, { :field => " firstname ", :label => " Firstname " }, { :field => " lastname ", :label => " Lastname " }, { :field => " email ", :label => " Email " }, { :field => " role ", :label => " Role " } ] ) %>
The first argument of the jqgrid helper is the title of your grid.
The second one is his DOM ID.
The third one is the URL used to retrieve data.
Finally, it takes an array of hashes to configure columns.
<script type="text/javascript"> function handleSelection(id) { alert('ID selected : ' + id); } </script><script type="text/javascript"> var lastsel; jQuery(document).ready(function(){ jQuery("#players_2").jqGrid({ // adding ?nd='+new Date().getTime() prevent IE caching url:'/users?nd='+new Date().getTime(), editurl:'', datatype: "json", colNames:['ID','Pseudo','Firstname','Lastname','Email','Role'], colModel:[{name:'id', index:'id',width:35,resizable:false},{name:'pseudo', index:'pseudo'},{name:'firstname', index:'firstname'},{name:'lastname', index:'lastname'},{name:'email', index:'email'},{name:'role', index:'role'}], pager: jQuery('#players_2_pager'), rowNum:10, rowList:[10,25,50,100], imgpath: '/images/themes/lightness/images', sortname: 'id', viewrecords: true, toolbar : [true,"top"], sortorder: 'asc', subGrid:false, caption: "Football Players" }); jQuery("#t_players_2").height(25).hide().filterGrid("players_2",{gridModel:true,gridToolbar:true}); jQuery("#players_2_select_button").click( function(){ var id = jQuery("#players_2").getGridParam('selrow'); if (id) { handleSelection(id); } else { alert("Please select a row"); } return false; }); jQuery("#players_2").navGrid('#players_2_pager',{edit:false,add:false,del:false,search:false,refresh:true}) .navButtonAdd("#players_2_pager",{caption:"Search",title:"Toggle Search",buttonimg:'/images/jqgrid/search.png', onClickButton:function(){ if(jQuery("#t_players_2").css("display")=="none") { jQuery("#t_players_2").css("display",""); } else { jQuery("#t_players_2").css("display","none"); } } }); }); </script>
Get ID of selected row
In this case, we added a "Get ID of selected row" link. When this link is clicked, the Javascript method defined by :selection_handler is called, with the ID of the row as a parameter :
<script type="text/javascript"> function handleSelection(id) { alert('ID selected : ' + id); } </script> <%=jqgrid (" Football Players ", " players_2 ", " /users ", [ { :field => " id ", :label => " ID ", :width => 35 , :resizable => false }, { :field => " pseudo ", :label => " Pseudo " }, { :field => " firstname ", :label => " Firstname " }, { :field => " lastname ", :label => " Lastname " }, { :field => " email ", :label => " Email " }, { :field => " role ", :label => " Role " } ], { :selection_handler => " handleSelection " } ) %> <a href="#" id="players_2_select_button">Get ID of selected row</a>
The ID of this link is very important, it must be the ID of the jqgrid + "_select_button". You can use a button instead of a link if you want.
<script type="text/javascript"> var lastsel; jQuery(document).ready(function(){ jQuery("#players_3").jqGrid({ // adding ?nd='+new Date().getTime() prevent IE caching url:'/users?nd='+new Date().getTime(), editurl:'', datatype: "json", colNames:['ID','Pseudo','Firstname','Lastname','Email','Role'], colModel:[{name:'id', index:'id',width:35,resizable:false},{name:'pseudo', index:'pseudo'},{name:'firstname', index:'firstname'},{name:'lastname', index:'lastname'},{name:'email', index:'email'},{name:'role', index:'role'}], pager: jQuery('#players_3_pager'), rowNum:10, rowList:[10,25,50,100], imgpath: '/images/themes/lightness/images', sortname: 'id', viewrecords: true, toolbar : [true,"top"], sortorder: 'asc', onSelectRow: function(id){ if(id){ handleSelection(id); } }, subGrid:false, caption: "Football Players" }); jQuery("#t_players_3").height(25).hide().filterGrid("players_3",{gridModel:true,gridToolbar:true}); jQuery("#players_3").navGrid('#players_3_pager',{edit:false,add:false,del:false,search:false,refresh:true}) .navButtonAdd("#players_3_pager",{caption:"Search",title:"Toggle Search",buttonimg:'/images/jqgrid/search.png', onClickButton:function(){ if(jQuery("#t_players_3").css("display")=="none") { jQuery("#t_players_3").css("display",""); } else { jQuery("#t_players_3").css("display","none"); } } }); }); </script>
If you want to call the handler directly when you select a row instead of clicking on a link/button, use the following options. Of course, you also need the Javascript method "handleSelection" defined in the previous section.
<%=jqgrid (" Football Players ", " players_3 ", " /users ", [ { :field => " id ", :label => " ID ", :width => 35 , :resizable => false }, { :field => " pseudo ", :label => " Pseudo " }, { :field => " firstname ", :label => " Firstname " }, { :field => " lastname ", :label => " Lastname " }, { :field => " email ", :label => " Email " }, { :field => " role ", :label => " Role " } ], { :selection_handler => " handleSelection ", :direct_selection => true } ) %>
<script type="text/javascript"> var lastsel; jQuery(document).ready(function(){ jQuery("#players_4").jqGrid({ // adding ?nd='+new Date().getTime() prevent IE caching url:'/users?nd='+new Date().getTime(), editurl:'', datatype: "json", colNames:['ID','Pseudo','Firstname','Lastname','Email','Role'], colModel:[{name:'id', index:'id',width:35,resizable:false},{name:'pseudo', index:'pseudo'},{name:'firstname', index:'firstname'},{name:'lastname', index:'lastname'},{name:'email', index:'email'},{name:'role', index:'role'}], pager: jQuery('#players_4_pager'), rowNum:10, rowList:[10,25,50,100], imgpath: '/images/themes/lightness/images', sortname: 'id', viewrecords: true, toolbar : [true,"top"], sortorder: 'asc', multiselect: true, subGrid:false, caption: "Football Players" }); jQuery("#t_players_4").height(25).hide().filterGrid("players_4",{gridModel:true,gridToolbar:true}); jQuery("#players_4_select_button").click( function() { var s; s = jQuery("#players_4").getGridParam('selarrrow'); handleSelection(s); return false; }); jQuery("#players_4").navGrid('#players_4_pager',{edit:false,add:false,del:false,search:false,refresh:true}) .navButtonAdd("#players_4_pager",{caption:"Search",title:"Toggle Search",buttonimg:'/images/jqgrid/search.png', onClickButton:function(){ if(jQuery("#t_players_4").css("display")=="none") { jQuery("#t_players_4").css("display",""); } else { jQuery("#t_players_4").css("display","none"); } } }); }); </script>
>%=jqgrid (" Football Players ", " players_4 ", " /users ", [ { :field => " id ", :label => " ID ", :width => 35 , :resizable => false }, { :field => " pseudo ", :label => " Pseudo " }, { :field => " firstname ", :label => " Firstname " }, { :field => " lastname ", :label => " Lastname " }, { :field => " email ", :label => " Email " }, { :field => " role ", :label => " Role " } ], { :selection_handler => " handleSelection ", :multi_selection => true } ) %> <a href="#" id="players_4_select_button">Get IDs of selected rows</a>
<script type="text/javascript"> var lastsel; jQuery(document).ready(function(){ jQuery("#players_5").jqGrid({ // adding ?nd='+new Date().getTime() prevent IE caching url:'/users?nd='+new Date().getTime(), editurl:'', datatype: "json", colNames:['ID','Pseudo','Firstname','Lastname','Email','Role'], colModel:[{name:'id', index:'id',width:35,resizable:false},{name:'pseudo', index:'pseudo'},{name:'firstname', index:'firstname'},{name:'lastname', index:'lastname'},{name:'email', index:'email'},{name:'role', index:'role'}], pager: jQuery('#players_5_pager'), rowNum:10, rowList:[10,25,50,100], imgpath: '/images/themes/lightness/images', sortname: 'id', viewrecords: true, toolbar : [true,"top"], sortorder: 'asc', onSelectRow: function(ids) { if(ids == null) { ids=0; if(jQuery("#players_5_details").getGridParam('records') >0 ) { jQuery("#players_5_details").setGridParam({url:"/users/pets?q=1&id="+ids,page:1}) .setCaption("Pets: "+ids) .trigger('reloadGrid'); } } else { jQuery("#players_5_details").setGridParam({url:"/users/pets?q=1&id="+ids,page:1}) .setCaption("Pets : "+ids) .trigger('reloadGrid'); } }, subGrid:false, caption: "Football Players" }); jQuery("#t_players_5").height(25).hide().filterGrid("players_5",{gridModel:true,gridToolbar:true}); jQuery("#players_5").navGrid('#players_5_pager',{edit:false,add:false,del:false,search:false,refresh:true}) .navButtonAdd("#players_5_pager",{caption:"Search",title:"Toggle Search",buttonimg:'/images/jqgrid/search.png', onClickButton:function(){ if(jQuery("#t_players_5").css("display")=="none") { jQuery("#t_players_5").css("display",""); } else { jQuery("#t_players_5").css("display","none"); } } }); }); </script>
<script type="text/javascript"> var lastsel; jQuery(document).ready(function(){ jQuery("#players_5_details").jqGrid({ // adding ?nd='+new Date().getTime() prevent IE caching url:'/users/pets?nd='+new Date().getTime(), editurl:'', datatype: "json", colNames:['ID','Name'], colModel:[{name:'id', index:'id',width:35,resizable:false},{name:'name', index:'name',align:'center',width:500}], pager: jQuery('#players_5_details_pager'), rowNum:10, rowList:[10,25,50,100], imgpath: '/images/themes/lightness/images', sortname: 'id', viewrecords: true, toolbar : [true,"top"], sortorder: 'asc', subGrid:false, caption: "Pets" }); jQuery("#t_players_5_details").height(25).hide().filterGrid("players_5_details",{gridModel:true,gridToolbar:true}); jQuery("#players_5_details").navGrid('#players_5_details_pager',{edit:false,add:false,del:false,search:false,refresh:true}) .navButtonAdd("#players_5_details_pager",{caption:"Search",title:"Toggle Search",buttonimg:'/images/jqgrid/search.png', onClickButton:function(){ if(jQuery("#t_players_5_details").css("display")=="none") { jQuery("#t_players_5_details").css("display",""); } else { jQuery("#t_players_5_details").css("display","none"); } } }); }); </script>
We need associated data to create this master-details grid :
Add a relationship in your User (has_many :pets) and Pet (belongs_to :user) models.
Then add data to your migration file : you can use this one .
Create the table :
And add the pets method in your users controller :
def pets if params [ :id ]. present? pets = User . find ( params [ :id ]). pets . find ( :all ) do paginate :page => params [ :page ], :per_page => params [ :rows ] order_by " #{params[:sidx]} #{params[:sord]} " end total_entries = pets . total_entries else pets = [] total_entries = 0 end respond_to do | format | format . json { render :json => pets . to_jqgrid_json ([ :id , :name ], params [ :page ], params [ :rows ], total_entries ) } end end
Don't forget to edit your routes and restart the server :
You can finally add your grids :
<%=jqgrid (" Football Players ", " players_5 ", " /users ", [ { :field => " id ", :label => " ID ", :width => 35 , :resizable => false }, { :field => " pseudo ", :label => " Pseudo " }, { :field => " firstname ", :label => " Firstname " }, { :field => " lastname ", :label => " Lastname " }, { :field => " email ", :label => " Email " }, { :field => " role ", :label => " Role " } ], { :master_details => true , :details_url => " /users/pets ", :details_caption => " Pets " } ) %> <br/> <%=jqgrid (" Pets ", " players_5_details ", " /users/pets ", [ { :field => " id ", :label => " ID ", :width => 35 , :resizable => false }, { :field => " name ", :label => " Name ", :width => 500 , :align => ' center ' } ] ) %>
The DOM ID of your details grid is important, it must be the ID of the master grid + "_details".
For evident reasons, data manipulation has been disabled in this demo
<script type="text/javascript"> var lastsel; jQuery(document).ready(function(){ jQuery("#players_6").jqGrid({ // adding ?nd='+new Date().getTime() prevent IE caching url:'/users?nd='+new Date().getTime(), editurl:'/users/post_data', datatype: "json", colNames:['ID','Pseudo','Firstname','Lastname','Email','Role'], colModel:[{name:'id', index:'id',width:35,resizable:false},{name:'pseudo', index:'pseudo',editable:true},{name:'firstname', index:'firstname',editable:true},{name:'lastname', index:'lastname',editable:true},{name:'email', index:'email',editable:true},{name:'role', index:'role',editable:true}], pager: jQuery('#players_6_pager'), rowNum:10, rowList:[10,25,50,100], imgpath: '/images/themes/lightness/images', sortname: 'id', viewrecords: true, toolbar : [true,"top"], sortorder: 'asc', onSelectRow: function(id){ if(id && id!==lastsel){ jQuery('#players_6').restoreRow(lastsel); jQuery('#players_6').editRow(id,true); lastsel=id; } }, subGrid:false, caption: "Football Players" }); jQuery("#t_players_6").height(25).hide().filterGrid("players_6",{gridModel:true,gridToolbar:true}); jQuery("#players_6").navGrid('#players_6_pager',{edit:false,add:true,del:true,search:false,refresh:true}) .navButtonAdd("#players_6_pager",{caption:"Search",title:"Toggle Search",buttonimg:'/images/jqgrid/search.png', onClickButton:function(){ if(jQuery("#t_players_6").css("display")=="none") { jQuery("#t_players_6").css("display",""); } else { jQuery("#t_players_6").css("display","none"); } } }); }); </script>
We need one last method in our controller to handle data manipulation.
Create the post_data method in your users controller :
def post_data if params [ :oper ] == " del " User . find ( params [ :id ]). destroy else user_params = { :pseudo => params [ :pseudo ], :firstname => params [ :firstname ], :lastname => params [ :lastname ], :email => params [ :email ], :role => params [ :role ] } if params [ :id ] == " _empty " User . create ( user_params ) else User . find ( params [ :id ]). update_attributes ( user_params ) end end render :nothing => true end
It's of course your role to add security & validation rules.
If protect_from_forgery is on, disable it for this action :
Edit your routes and restart the server :
You can now add the grid :
<%=jqgrid (" Football Players ", " players_6 ", " /users ", [ { :field => " id ", :label => " ID ", :width => 35 , :resizable => false }, { :field => " pseudo ", :label => " Pseudo ", :editable => true }, { :field => " firstname ", :label => " Firstname ", :editable => true }, { :field => " lastname ", :label => " Lastname ", :editable => true }, { :field => " email ", :label => " Email ", :editable => true }, { :field => " role ", :label => " Role ", :editable => true } ], { :add => true , :edit => true , :inline_edit => true , :delete => true , :edit_url => " /users/post_data " } ) %>
<script type="text/javascript"> var lastsel; jQuery(document).ready(function(){ jQuery("#players_7").jqGrid({ // adding ?nd='+new Date().getTime() prevent IE caching url:'/users?nd='+new Date().getTime(), editurl:'/users/post_data', datatype: "json", colNames:['ID','Pseudo','Firstname','Lastname','Email','Role'], colModel:[{name:'id', index:'id',width:35,resizable:false},{name:'pseudo', index:'pseudo',editable:true},{name:'firstname', index:'firstname',editable:true},{name:'lastname', index:'lastname',editable:true},{name:'email', index:'email',editable:true},{name:'role', index:'role',editable:true}], pager: jQuery('#players_7_pager'), rowNum:10, rowList:[10,25,50,100], imgpath: '/images/themes/lightness/images', sortname: 'id', viewrecords: true, toolbar : [true,"top"], sortorder: 'asc', subGrid:false, caption: "Football Players" }); jQuery("#t_players_7").height(25).hide().filterGrid("players_7",{gridModel:true,gridToolbar:true}); jQuery("#players_7").navGrid('#players_7_pager',{edit:true,add:true,del:true,search:false,refresh:true}) .navButtonAdd("#players_7_pager",{caption:"Search",title:"Toggle Search",buttonimg:'/images/jqgrid/search.png', onClickButton:function(){ if(jQuery("#t_players_7").css("display")=="none") { jQuery("#t_players_7").css("display",""); } else { jQuery("#t_players_7").css("display","none"); } } }); }); </script>
<%=jqgrid (" Football Players ", " players_7 ", " /users ", [ { :field => " id ", :label => " ID ", :width => 35 , :resizable => false }, { :field => " pseudo ", :label => " Pseudo ", :editable => true }, { :field => " firstname ", :label => " Firstname ", :editable => true }, { :field => " lastname ", :label => " Lastname ", :editable => true }, { :field => " email ", :label => " Email ", :editable => true }, { :field => " role ", :label => " Role ", :editable => true } ], { :add => true , :edit => true , :inline_edit => false , :delete => true , :edit_url => " /users/post_data " } ) %>
<script type="text/javascript"> var lastsel; jQuery(document).ready(function(){ jQuery("#players_8&quo