在 Ajax级联选择框:以中国的省市地区三级联动选择为例 篇中介绍了Rails应用Ajax技术实现三级联动菜单,本篇将介绍Rails应用ExtJs技术实现三级联动菜单。
需求: 允许用户只选择省、市、地区三者之一,同时 为了简化数据库表的结构,只设计一个字段(如, cid字段)来保留省、市、地区三者之一的id,这与 Ajax级联选择框 有所不同,后者是用三个字段来分别保存省、市、地区的id。
同时也是测试Rails应用ExtJs技术的效果。
步骤:
①、工具准备:1、 安装Rails插件acts_as_nested_set(附件)
2、下载ext的js文件包(附件), 将其解压到public目录下
②、确定数据库的设计,下面将省市地区数据以如下方式存入数据库的cities表(附件):
+--------+--------------+-----------+------+------+
| id | name | parent_id | lft | rgt |
+--------+--------------+-----------+------+------+
| 4122 | 中国 | 1 | 1 | 6464 |
| 110000 | 北京市 | 0 | 2 | 39 |
| 110101 | 东城区 | 110000 | 3 | 4 |
| 110102 | 西城区 | 110000 | 5 | 6 |
| 110103 | 崇文区 | 110000 | 7 | 8 |
| 110104 | 宣武区 | 110000 | 9 | 10 |
+--------+--------------+-----------+------+------+
这个数据库表是在Ajax级联选择框 篇的附件中的cities表基础上,结合ext tree的需要设计的,其中parent_id为父节点id、lft为左节点id、rgt为右节点id。至于如何生成parent_id、lft和rgt的值 ,具体请步骤为:遍历citis -> 用sql 查出 下级 ->调用acts_as_nested_set插件的 add_child(child_object)方法,那么就会自动生成 parent_id、lft和rgt的值。
例如:参考: http://www.iteye.com/topic/177501
root = Category.create( :text => 'Root' )
root.add_child(c1 = Category.create( :text => 'Child 1' ))
c1.add_child(Category.create( :text => 'Child 1.1' ))
③、在cities表对应的模型内添加代码,声明、使用acts_as_nested_set插件,如下面代码片段所示:
class City < ActiveRecord::Base
acts_as_nested_set
def children_count
return (self[right_col_name] - self[left_col_name] - 1)/2
end
def leaf?
unknown? || children_count == 0
end
............
end
④、然后修改相应的Controller:
class CitiesController < ApplicationController
def cities_tree (id = params[:node])
cdata = Array.new
c = City.find_all_by_parent_id( id || 1,:order => 'lft')
cdata = get_tree(c, nil)
render :text=>cdata.to_json , :layout=>false
end
def get_tree(c, parent)
data = Array.new
c.each { |cc|
if !cc.leaf?
if data.empty?
data = [{"text" => cc.name,"id" => cc.id, "cls" => "folder" ,"leaf" => false }]
else
data.concat([{"text" => cc.name,"id" => cc.id, "cls" => "folder" ,"leaf" => false}])
end
else
data.concat([{"text" => cc.name,"id" => cc.id, "cls" => "file","leaf" => true}])
end
}
return data
end
def index
@cities = City.all
respond_to do |format|
format.html
format.xml { render :xml => @cities }
end
end
end
⑤、 最后修改相应的视图:如公司所在地的选择框,首先所在地被保存在companies表的cid字段,那么在views/companies/new.html.erb,就可应用Ext.tree控件,实现公司所在地的树型选择,如下代码片段所示:
首先引入Ext的js文件:<%= javascript_include_tag :defaults %>
<%= stylesheet_link_tag "../ext/resources/css/ext-all.css" %>
<%= javascript_include_tag "../ext/adapter/prototype/ext-prototype-adapter.js" %>
<%= javascript_include_tag "../ext/ext-all.js" %>
<%= javascript_include_tag "../ext/ComboBoxTree.js" %>
其次使用ComboBoxTree生成下拉选择框:
<div id="comboboxtree" ></div>
<% javascript_tag do %>
Ext.onReady(function(){
var comboBoxTree = new Ext.ux.ComboBoxTree({
renderTo : 'comboboxtree',
name : 'company[cid]', //companies表的 cid 字段
id : 'company_cid', //companies表的 cid 字段
width : 250,
border: true,
valueField:"id",
tree : new Ext.tree.TreePanel({
loader: new Ext.tree.TreeLoader({
url:'/cities/cities_tree',
requestMethod:'GET',
baseParams:{format:'json' }
}),
animate:true,
enableDD:true,
border:false,
containerScroll: false,
rootVisible:false ,
root: new Ext.tree.AsyncTreeNode({text: '中国',id:'0'})
})
});
comboBoxTree.render('comboboxtree');
});
<% end %>
上面的代码,是参照ExtJs官方网站上使用Ext.Tree控件的例子,修改的只是,数据提供部分,即上面的粗体部分。
下图是上代码的显示效果:用户选择了: 辽宁省 --> 鞍山市 --> 岫岩满族自治县
最终保存到companies 表的 cid 字段的值就是 "岫岩满族自治县" 对应于cities表里的id值,当然用户也可以只选择 " 辽宁省 --> 鞍山市" 或 "辽宁省" ,那么相应存入数据库的就是" 鞍山市" 的id 或 " 辽宁省" 的id。
而显示的时候,只要在cities表对应的Model添加一个encode()方法,利用acts_as_nested_set插件的self_and_ancestors来找到它的上级信息,如下代码片段所示:
def self.encode(id)
str = ''
if not id.nil? then
begin
self.self_and_ancestors.each {|c|
if not (c.name.include? '中国')
str += c.name
end
}
return str
rescue Exception => exc
logger.error("#{exc.message}")
return ""
end
end
end
视图里调用<%=h City.encode(@company.cid ) %>,那么虽然@company.cid = "岫岩满族自治县" 的id,但是显示的却是完整信息:"辽宁省鞍山市岫岩满族自治县"。 可见,在Rails项目中使用ExtJs技术是没问题的,而且 效果符合RIA(富客户端)的要求。
参考资料:http://www.iteye.com/topic/177501
http://www.iteye.com/topic/76860
http://api.rubyonrails.org/classes/ActiveRecord/Acts/NestedSet/ClassMethods.html