[WIP] Breadcrumb
[odoo/odoo.git] / addons / web / static / src / js / view_tree.js
1 /*---------------------------------------------------------
2  * OpenERP web library
3  *---------------------------------------------------------*/
4
5 openerp.web.view_tree = function(instance) {
6 var QWeb = instance.web.qweb,
7       _lt = instance.web._lt;
8
9 instance.web.views.add('tree', 'instance.web.TreeView');
10 instance.web.TreeView = instance.web.View.extend(/** @lends instance.web.TreeView# */{
11     display_name: _lt('Tree'),
12     /**
13      * Indicates that this view is not searchable, and thus that no search
14      * view should be displayed (if there is one active).
15      */
16     searchable : false,
17     /**
18      * Genuine tree view (the one displayed as a tree, not the list)
19      *
20      * @constructs instance.web.TreeView
21      * @extends instance.web.View
22      *
23      * @param parent
24      * @param dataset
25      * @param view_id
26      * @param options
27      */
28     init: function(parent, dataset, view_id, options) {
29         this._super(parent);
30         this.dataset = dataset;
31         this.model = dataset.model;
32         this.view_id = view_id;
33
34         this.records = {};
35
36         this.options = _.extend({}, this.defaults, options || {});
37
38         _.bindAll(this, 'color_for');
39     },
40
41     start: function () {
42         return this.rpc("/web/treeview/load", {
43             model: this.model,
44             view_id: this.view_id,
45             view_type: "tree",
46             toolbar: this.view_manager ? !!this.view_manager.sidebar : false,
47             context: this.dataset.get_context()
48         }, this.on_loaded);
49     },
50     /**
51      * Returns the list of fields needed to correctly read objects.
52      *
53      * Gathers the names of all fields in fields_view_get, and adds the
54      * field_parent (children_field in the tree view) if it's not already one
55      * of the fields to fetch
56      *
57      * @returns {Array} an array of fields which can be provided to DataSet.read_slice and others
58      */
59     fields_list: function () {
60         var fields = _.keys(this.fields);
61         if (!_(fields).contains(this.children_field)) {
62             fields.push(this.children_field);
63         }
64         return fields;
65     },
66     on_loaded: function (fields_view) {
67         var self = this;
68         var has_toolbar = !!fields_view.arch.attrs.toolbar;
69         // field name in OpenERP is kinda stupid: this is the name of the field
70         // holding the ids to the children of the current node, why call it
71         // field_parent?
72         this.children_field = fields_view['field_parent'];
73         this.fields_view = fields_view;
74         _(this.fields_view.arch.children).each(function (field) {
75             if (field.attrs.modifiers) {
76                 field.attrs.modifiers = JSON.parse(field.attrs.modifiers);
77             }
78         });
79         this.fields = fields_view.fields;
80         this.hook_row_click();
81         this.$element.html(QWeb.render('TreeView', {
82             'title': this.fields_view.arch.attrs.string,
83             'fields_view': this.fields_view.arch.children,
84             'fields': this.fields,
85             'toolbar': has_toolbar
86         }));
87         this.$element.addClass(this.fields_view.arch.attrs['class']);
88
89         this.dataset.read_slice(this.fields_list()).then(function(records) {
90             if (!has_toolbar) {
91                 // WARNING: will do a second read on the same ids, but only on
92                 //          first load so not very important
93                 self.getdata(null, _(records).pluck('id'));
94                 return;
95             }
96
97             var $select = self.$element.find('select')
98                 .change(function () {
99                     var $option = $(this).find(':selected');
100                     self.getdata($option.val(), $option.data('children'));
101                 });
102             _(records).each(function (record) {
103                 self.records[record.id] = record;
104                 $('<option>')
105                         .val(record.id)
106                         .text(record.name)
107                         .data('children', record[self.children_field])
108                     .appendTo($select);
109             });
110
111             if (!_.isEmpty(records)) {
112                 $select.change();
113             }
114         });
115
116         // TODO store open nodes in url ?...
117         this.do_push_state({});
118
119         if (!this.fields_view.arch.attrs.colors) {
120             return;
121         }
122         this.colors = _(this.fields_view.arch.attrs.colors.split(';')).chain()
123             .compact()
124             .map(function(color_pair) {
125                 var pair = color_pair.split(':'),
126                     color = pair[0],
127                     expr = pair[1];
128                 return [color, py.parse(py.tokenize(expr)), expr];
129             }).value();
130     },
131     /**
132      * Returns the color for the provided record in the current view (from the
133      * ``@colors`` attribute)
134      *
135      * @param {Object} record record for the current row
136      * @returns {String} CSS color declaration
137      */
138     color_for: function (record) {
139         if (!this.colors) { return ''; }
140         var context = _.extend({}, record, {
141             uid: this.session.uid,
142             current_date: new Date().toString('yyyy-MM-dd')
143             // TODO: time, datetime, relativedelta
144         });
145         for(var i=0, len=this.colors.length; i<len; ++i) {
146             var pair = this.colors[i],
147                 color = pair[0],
148                 expression = pair[1];
149             if (py.evaluate(expression, context).toJSON()) {
150                 return 'color: ' + color + ';';
151             }
152             // TODO: handle evaluation errors
153         }
154         return '';
155     },
156     /**
157      * Sets up opening a row
158      */
159     hook_row_click: function () {
160         var self = this;
161         this.$element.delegate('.treeview-td span, .treeview-tr span', 'click', function (e) {
162             e.stopImmediatePropagation();
163             self.activate($(this).closest('tr').data('id'));
164         });
165
166         this.$element.delegate('.treeview-tr', 'click', function () {
167             var is_loaded = 0,
168                 $this = $(this),
169                 record_id = $this.data('id'),
170                 record = self.records[record_id],
171                 children_ids = record[self.children_field];
172
173             _(children_ids).each(function(childid) {
174                 if (self.$element.find('#treerow_' + childid).length) {
175                     if (self.$element.find('#treerow_' + childid).is(':hidden')) {
176                         is_loaded = -1;
177                     } else {
178                         is_loaded++;
179                     }
180                 }
181             });
182             if (is_loaded === 0) {
183                 if (!$this.parent().hasClass('oe_open')) {
184                     self.getdata(record_id, children_ids);
185                 }
186             } else {
187                 self.showcontent(record_id, is_loaded < 0);
188             }
189         });
190     },
191     // get child data of selected value
192     getdata: function (id, children_ids) {
193         var self = this;
194
195         self.dataset.read_ids(children_ids, this.fields_list()).then(function(records) {
196             _(records).each(function (record) {
197                 self.records[record.id] = record;
198             });
199
200             var $curr_node = self.$element.find('#treerow_' + id);
201             var children_rows = QWeb.render('TreeView.rows', {
202                 'records': records,
203                 'children_field': self.children_field,
204                 'fields_view': self.fields_view.arch.children,
205                 'fields': self.fields,
206                 'level': $curr_node.data('level') || 0,
207                 'render': instance.web.format_value,
208                 'color_for': self.color_for
209             });
210
211             if ($curr_node.length) {
212                 $curr_node.addClass('oe_open');
213                 $curr_node.after(children_rows);
214             } else {
215                 self.$element.find('tbody').html(children_rows);
216             }
217         });
218     },
219
220     // Get details in listview
221     activate: function(id) {
222         var self = this;
223         var local_context = {
224             active_model: self.dataset.model,
225             active_id: id,
226             active_ids: [id]};
227         return this.rpc('/web/treeview/action', {
228             id: id,
229             model: this.dataset.model,
230             context: new instance.web.CompoundContext(
231                 this.dataset.get_context(), local_context)
232         }).pipe(function (actions) {
233             if (!actions.length) { return; }
234             var action = actions[0][2];
235             var c = new instance.web.CompoundContext(local_context);
236             if (action.context) {
237                 c.add(action.context);
238             }
239             return self.rpc('/web/session/eval_domain_and_context', {
240                 contexts: [c], domains: []
241             }).pipe(function (res) {
242                 action.context = res.context;
243                 return self.do_action(action);
244             }, null);
245         }, null);
246     },
247
248     // show & hide the contents
249     showcontent: function (record_id, show) {
250         this.$element.find('#treerow_' + record_id)
251                 .toggleClass('oe_open', show);
252
253         _(this.records[record_id][this.children_field]).each(function (child_id) {
254             var $child_row = this.$element.find('#treerow_' + child_id);
255             if ($child_row.hasClass('oe_open')) {
256                 this.showcontent(child_id, false);
257             }
258             $child_row.toggle(show);
259         }, this);
260     },
261
262     do_show: function () {
263         this.$element.show();
264     },
265
266     do_hide: function () {
267         this.$element.hide();
268         this.hidden = true;
269     }
270 });
271 };