gemfile中添加:
# 树插件
gem 'ztree'
gem 'ancestry'
gem 'nokogiri', '~> 1.6', '>= 1.6.8'
然后bundle install
用脚手架建立一个品目的类(这个是带命名空间的)
rails g scaffold ancient/Category
db文件夹中的迁移文件中:
def change
create_table :categories do |t|
t.string :ancestry #必写
t.string :name, :comment => "品目名称"
t.integer :ancestry_depth #必写
t.integer :order_num #必写
t.timestamps
end
end
然后执行迁移 rake db:migrate
添加配置文件
在/app/assets/javascripts/admin_content.js中添加:
//= require ztree
//= require sortable_tree
在/app/assets/javascripts文件夹中创建一个sortable_tree.js文件,并加入下面代码:
var contentSelector = '.menu_content',
className = "dark"
// newCount = 1,
function getzTree() {
return $.fn.zTree.getZTreeObj(treeOpts.current_tree);
}
function getChildren(ids,curTreeNode){
var treeNode = curTreeNode.getParentNode()
if (treeNode && treeNode.isParent){
ids.push(treeNode.id);
for(var obj in treeNode.children){
// getChildren(ids,treeNode.children[obj]); //will get all nodes of the treeNode
ids.push(treeNode.children[obj].id); // just get nodes for the current node
}
} else {
ids.push(0);
topTree = getzTree();
for(var obj in topTree.getNodes()){
ids.push(topTree.getNodes()[obj].id);
}
}
return ids;
}
function beforeDrag(treeId, treeNodes) {
for (var i=0,l=treeNodes.length; i<l; i++) {
dragId = treeNodes[i].pId;
if (treeNodes[i].drag === false) {
return false;
}
}
return true;
}
function beforeDrop(treeId, treeNodes, targetNode, moveType) {
return targetNode.pId === dragId
}
function onDrop(event, treeId, treeNodes, targetNode, moveType) {
var sortableIds = getChildren([],treeNodes[0])
$.ajax({
url: '/sort_tree',
type: 'post',
data: {sorted_array: sortableIds, model_name: treeOpts.model_name}
// success: function (r){alert('OK')}
})
};
function beforeEditName(treeId, treeNode) {
className = (className === "dark" ? "":"dark");
getzTree().selectNode(treeNode);
$(contentSelector).load(treeOpts.url + treeNode.id +"/edit");
return false;
}
function beforeRemove(treeId, treeNode) {
className = (className === "dark" ? "":"dark");
getzTree().selectNode(treeNode);
if(confirm("确认删除 " + treeNode.name + " 吗,下级会一并被删除?")){
$.ajax(treeOpts.url + treeNode.id +".json" ,{
type:'post',
data:{_method:"delete"},
success:function(){
$('.success').show();
$('.success').fadeOut(2000);
},
error:function(){
$('.failed').show();
$('.failed').show(2000);
return false;
}
})
}else{
return false;
}
}
function onRemove(e, treeId, treeNode) {
}
function beforeRename(treeId, treeNode, newName, isCancel) {
className = (className === "dark" ? "":"dark");
if (newName.length == 0) {
alert("节点名称不能为空.");
setTimeout(function(){getzTree().editName(treeNode)}, 10);
return false;
}
return true;
}
function onRename(e, treeId, treeNode, isCancel) {
return false;
}
function onClick(e, treeId, treeNode, isCancel) {
$(contentSelector).load(treeOpts.url + treeNode.id +"/edit");
}
function addHoverDom(treeId, treeNode) {
var sObj = $("#" + treeNode.tId + "_span");
if (treeNode.editNameFlag || $("#addBtn_"+treeNode.tId).length>0) return;
var addStr = "<span class='button add' id='addBtn_" + treeNode.tId
+ "' title='add node' onfocus='this.blur();'></span>";
sObj.after(addStr);
var btn = $("#addBtn_"+treeNode.tId);
if (btn) btn.bind("click", function(){
$(contentSelector).load(treeOpts.url + "new?parent_id="+ treeNode.id);
return false;
});
};
function removeHoverDom(treeId, treeNode) {
$("#addBtn_"+treeNode.tId).unbind().remove();
};
在/app/helpers/tree_helper.rb文件夹中创建tree_helper.rb文件,并加入如下代码:
# -*- encoding : utf-8 -*-
module TreeHelper
def init_tree_js(link_title = '')
js = '$(document).ready(function(){
$.fn.zTree.init($("#" + treeOpts.current_tree), ztreeSetting, treeOpts.zNodes);
'
js += '$(".new_btn").click(function (){
$(".menu_content").load(treeOpts.url + "new");
return false;
})' unless link_title.empty?
js += '});'
javascript_tag(js)
end
def tree_content(link_title = '', opts = {})
if link_title.empty?
header = ''.html_safe
else
title_link = link_to link_title, opts[:url], :class => "btn btn-small btn-primary new_btn"
header = content_tag :div, title_link + tip_messages, class: 'page-header'
end
right_content = content_tag :div, '', class: "menu_content fl"
list_content = content_tag :div, tree_list(link_title, style: '') + right_content
content_tag(:section, header + list_content, id: 'tables')
end
def tip_messages
content_tag(:span, "操作成功", class: 'success hide') +
content_tag(:span, "操作失败,原因:", class: 'failed hide')
end
end
在/app/models/category.rb(这个是你创建的类的model)加入如下代码:
has_ancestry
validates :name, presence: true
after_create :update_order_number
在/app/helpers/application_helper.rb中加入无限下拉框,代码如下:
# 无限级联下拉框 搭配js
def dynamic_selects(data_class, value_id, aim_id, text_id = '', prompt = '请选择', options = {})
options = {:class => 'multi-level', :otype => data_class.to_s, :aim_id => aim_id, :text_id => text_id }.merge!(options)
value_object = data_class.find_by_id(value_id)
select_text = value_object.try(:has_children?) ? collection_select('value_object-parent-id', 0, value_object.children, :id, :name, {:selected => value_object.try(:id), :prompt => prompt}, options) : ''
#aim_id = object.class.to_s.tableize.singularize + "_" + ref.to_s.singularize + "_id"
aim_id = aim_id.to_s
while value_object and value_object.parent
select_text = collection_select('value_object-parent-id', value_object.id, value_object.parent.children, :id, :name, {:selected => value_object.try(:id), :prompt => prompt}, options) << select_text
value_object = value_object.parent
end
select_text = collection_select('value_object-parent-id', 0, data_class.roots, :id, :name, {:selected => value_object.try(:id), :prompt => prompt}, options) << select_text
raw select_text
end
在/app/controllers/ancient/categories_controller.rb中加入:
before_action :per_load, except: [:index, :new, :create, :sort]
before_action :refuse_same_name, only: [:create_params]
def index
@roots = Category.all.to_sorted_nodes
end
def show
end
def new
@category = Category.new(:parent_id => params[:parent_id])
render layout: false
end
def edit
render layout: false
end
def create
@category = Category.new(ancient_category_params)
flash[:success]= "创建成功" if @category.save
redirect_to ancient_categories_url
end
def update
flash[:sucess] = "更新成功" if @category.update_attributes(ancient_category_params)
redirect_to ancient_categories_url
end
def destroy
if @category.destroy
respond_to do |format|
flash[:sucess] = "删除成功" #这个写法可能不一样,根据自己的设置,保证统一
format.html{respond_back(@category)}
format.json{render :json => true}
end
end
end
def edit_params
@node = params[:name] ? find_param_node(@doc, params[:name]) : add_element(@doc.root,"<param>")
end
def important_params
unless params[:firstline].blank?
@doc.root.elements.each do |element|
element.set_a("firstline", params[:firstline].include?(element.get_a("name")).to_s)
end
write_to_params
end
render layout: false
end
def update_params # need to refactor
node = find_param_node(@doc, params[:old_name])
%w(name show type).each {|key| node.set_a(key, params[key.to_sym])}
Category::CheckedParams.each {|cp| node.set_a(cp.keys[0].to_s, params[cp.keys[0]] || "false")}
#清空子节点
node.elements.each {|e| e.remove }
1.upto(params[:sub_count].to_i) do |i|
next if !params["select#{i}"]
select = add_element(node, '<select>')
select.set_a("name", params["select#{i}"][:name])
options = params["select#{i}"][:option].split("|")
options.each {|option| add_element(select, '<option>').set_a("name", option)}
end if params[:type] == "下拉型"
write_to_params
redirect_to_edit_params("修改参数信息成功", name: params[:name])
end
def create_params
node = add_element(@doc.root,"<param>")
node.set_a("name", params[:name])
node.set_a("input", "true")
write_to_params(@doc)
redirect_to_edit_params("参数保存成功", name: params[:name])
end
def delete_params
node = find_param_node(@doc, params[:name])
return redirect_to_edit_params("下拉型参数不能被删除", name: params[:name]) if node.get_a("type") == "下拉型"
node.remove
write_to_params
redirect_to_edit_params("删除节点成功")
end
def add_select
name = params[:name]
param_node = find_param_node(@doc, name)
select_node = add_element(param_node,"<select>")
write_to_params
render partial: "select", locals: {index: param_node.elements.size - 1, select: select_node }
end
private
NeedDocActions = %w(update_params edit_params add_select delete_params create_params important_params)
def per_load
@category ||= Category.unscoped.find(params[:id])
get_doc(@category) if NeedDocActions.any? {|a| a == action_name}
end
def get_doc(category)
if !category.params
category.params = Setting.init_xml
category.save
end
@doc ||= Nokogiri::XML(category.params)
end
def write_to_params(doc = @doc, str = '')
@category.params = doc.to_xml
@category.save
end
def refuse_same_name
@doc.root.elements.each do |element|
return redirect_to_edit_params("对不起,存在同名参数") if element.get_a("name") == params[:name]
end
end
def redirect_to_edit_params(*args)
msg, opts = args[0], args.extract_options!
redirect_with_message(msg, opts.merge(id: params[:id], action: 'edit_params'))
end
def ancient_category_params
params.require(:category).permit(:ancestry, :ancestry_depth, :name, :in_use, :parent_id) #根据你迁移文件中的字段去设置
end
end
在/app/views/ancient/categories/index.html.erb:
<script type="text/javascript">
$(document).ready(function(){
window.treeOpts = ({
current_tree: 'tree-list',
url: '/ancient/categories/',
model_name: 'Category',
zNodes: <%= @roots.to_ztree_node.to_json.html_safe %>
})
window.ztreeSetting = <%= ztree_settings %>
});
</script>
<%= tree_content('添加类目', url: new_ancient_category_path) %>
在/app/views/ancient/categories/new.html.erb:
<%= render :partial => 'form' %>
在/Users/apple/ligong/huananligong/app/views/ancient/categories/_form.html.erb:
<%= simple_form_for [:ancient, @category] do |f| %>
<%= f.input :name, :label => "名称" %>
<%= f.input :parent_id, :label => "上级品目" do %>
<%= f.hidden_field :parent_id %>
<%= dynamic_selects Category, @category.parent_id, "category_parent_id" %>
<% end %>
<%= f.input :in_use, :label => "是否可用" %>
<%= f.submit '确认', :class => "btn btn-success btn-small" %>
<% end %>
网友评论