ExtTree
This is a starting point for make a tree with the extjs (v 1.0) framework.
1 # app/model/category.rb
2 class Category < ActiveRecord::Base
3 validates_presence_of :name
4
5 belongs_to :image
6
7 has_many :categorizations, :dependent => :destroy
8 has_many :products, :through => :categorizations
9
10 acts_as_sluggable :with => :name
11
12 acts_as_nested_set
13
14 ...
In the view a script like that
1 EditableCategories = function(){
2 return {
3 init : function(){
4 // turn on quick tips
5 Ext.QuickTips.init();
6 // seeds for the new node suffix
7 var cseed = 0, oseed = 0;
8 // create the primary toolbar
9
10 var tb = new Ext.Toolbar('toolbar');
11 tb.add({
12 id:'add',
13 text: 'Add',
14 disabled:false,
15 handler: add,
16 cls: 'x-btn-text-icon add',
17 tooltip: 'Add a new Menu'
18 },'-',{
19 id:'remove',
20 text:'Delete',
21 disabled:false,
22 handler:removeNode,
23 cls:'x-btn-text-icon remove',
24 tooltip:'Delete the Menu'
25 });
26
27 layout = new Ext.BorderLayout("main", {
28 north: {
29 split:false,
30 titlebar: false
31 },
32 west: {
33 split:false,
34 initialSize: 200,
35 border: ''
36 },
37 center: {
38 split:false,
39 autoScroll:true,
40 tabPosition:'top',
41 toolbar: tb
42 }
43 });
44
45 layout.beginUpdate();
46
47 layout.add('north', new Ext.ContentPanel('toolbar', 'Header'));
48 layout.add('west', new Ext.ContentPanel('content-items', {title: 'Menu', fitToFrame:true}));
49 layout.add('center', new Ext.ContentPanel('form', {title: 'Contentuto', closable: false}));
50
51 layout.restoreState();
52 layout.endUpdate();
53
54 Lipsiadmin.getLayout().add("center", new Ext.NestedLayoutPanel(layout));
55
56 // for enabling and disabling
57 var btns = tb.items.map;
58
59 // editable ctree
60 var xt = Ext.tree;
61
62 var ctree = new xt.TreePanel('content-items', {
63 animate:true,
64 loader: new xt.TreeLoader({dataUrl:'/admin/categories/data'}),
65 enableDD:true,
66 containerScroll: true
67 });
68
69 ctree.el.addKeyListener(Ext.EventObject.DELETE, removeNode);
70
71 // set the root node
72 var croot = new xt.AsyncTreeNode({
73 text: 'Home',
74 draggable:false,
75 id:'source'
76 });
77
78 ctree.setRootNode(croot);
79
80 // render the ctree
81 ctree.render();
82 croot.expand(true);
83
84 ctree.el.on('keypress', function(e){
85 if(e.isNavKeyPress()){
86 e.stopEvent();
87 }
88 });
89
90 // when the tree selection changes, enable/disable the toolbar buttons
91 var sm = ctree.getSelectionModel();
92 sm.on('selectionchange', function(){
93 var n = sm.getSelectedNode();
94 if(!n || !n.parentNode){
95 btns.remove.disable();
96 return;
97 } else {
98 btns.remove.enable();
99 return;
100 }
101 });
102
103 ctree.on('click', function(node){click(node)});
104 ctree.on('enddrag', save);
105
106 // create the editor for the component ctree
107 var ge = new xt.TreeEditor(ctree, {
108 allowBlank:false,
109 blankText:'You must insert a name!',
110 selectOnFocus:true
111 });
112
113 ge.on('complete', save);
114
115 // add component handler
116 function add(){
117 var id = guid('c-');
118 var text = 'Menu '+(++cseed);
119 var node = new xt.TreeNode({
120 text: text,
121 iconCls:'folder',
122 cls:'folder',
123 type:'folder',
124 allowDelete:true,
125 allowEdit:true
126 });
127 croot.appendChild(node);
128 node.expand(false, false);
129 node.select();
130 ge.triggerEdit(node);
131 }
132
133 // remove handler
134 function removeNode(){
135 var n = sm.getSelectedNode();
136 if(n){
137 Ext.get('main').mask('Sending data to server...', 'x-mask-loading');
138 new Ajax.Request('/admin/categories/destroy',
139 {method:'post', parameters:'id='+n.id, onSuccess:refresh, onFailure:refresh});
140 }
141 }
142
143 function getChildren(c){
144 var ch = [];
145
146 if (c.parentNode){
147 var cld = {
148 id: c.id,
149 text:c.text,
150 parent_id: c.parentNode.id
151 }
152 }
153
154 ch.push(cld)
155
156 if (c.childrenRendered){
157 c.eachChild(function(o){
158 getChildren(o).each(function(z){ch.push(z);});
159 });
160 }
161
162 return ch;
163 }
164
165 // save to the server in a format usable in ruby
166 function save(){
167 croot.expand(true) // WHY I NEED TO DO THAT? U KNOW A BETTER WAY?
168 var ch = getChildren(croot);
169
170 Ext.get('main').mask('Sending data to server...', 'x-mask-loading');
171
172 new Ajax.Request('/admin/categories/update',
173 {method:'post', parameters:'data='+encodeURIComponent(Ext.encode(ch)), onSuccess:refresh, onFailure:refresh});
174
175 }
176
177 function refresh(){
178 Ext.get('main').unmask();
179 croot.reload();
180 croot.expand(true);
181 }
182
183 // semi unique ids across edits
184 function guid(prefix){
185 return prefix+(new Date().getTime());
186 }
187
188 function click(node){
189 new Ajax.Request('/admin/categories/load_details/'+node.id, {asynchronous:true, evalScripts:true});
190 }
191 },
192
193 refresh : function(id){
194 new Ajax.Request('/admin/categories/load_details/'+id, {asynchronous:true, evalScripts:true});
195 }
196 }
197 }();
198
199 Ext.onReady(EditableCategories.init, EditableCategories, true);
Finally for get records in the controller some similar to this:
1 def data
2 categories = Category.find_by_sql("select * from categories where parent_id is null")
3 data = get_tree(categories,nil)
4 render :text => data.to_json, :layout=>false
5 end
6
7 def get_tree(categories, parent)
8 data = Array.new
9 categories.each { |category|
10 if !category.leaf?
11 if data.empty?
12 data = [{"text" => category.name, "id" => category.id, "leaf" => false,
13 "children" => get_tree(category.children,category) }]
14 else
15 data.concat([{"text" => category.name, "id" => category.id, "leaf" => false,
16 "children" => get_tree(category.children,category)}])
17 end
18 else
19 data.concat([{"text" => category.name, "id" => category.id, "cls" => "folder", "leaf" => false, "children" => []}])
20 end
21 }
22 return data
23 end
