[ADD] web_tip: module for tip definition and display
[odoo/odoo.git] / addons / web_tip / static / src / js / tip.js
1 (function() {
2
3     var instance = openerp;
4
5     instance.web.Tip = instance.web.Class.extend({
6         init: function() {
7             var self = this;
8             self.tips = [];
9             self.tip_mutex = new $.Mutex();
10             self.$overlay = null;
11             self.$element = null;
12
13             var Tips = new instance.web.Model('web.tip');
14             Tips.query(['title', 'description', 'action_id', 'model', 'type', 'mode', 'trigger_selector',
15                 'highlight_selector', 'end_selector', 'end_event', 'placement', 'is_consumed'])
16                 .all().then(function(tips) {
17                     self.tips = tips;
18                 })
19             ;
20
21             instance.web.bus.on('action', this, function(action) {
22                 self.on_action(action);
23             });
24
25             instance.web.bus.on('view_shown', this, function(view) {
26                 if (_.keys(view.fields_view).length === 0) {
27                     view.on('view_loaded', this, function(fields_view) {
28                         self.on_view(view);
29                     });
30                 } else {
31                     self.on_view(view);
32                 }
33
34                 view.on('switch_mode', this, function() {
35                 });
36             });
37
38             instance.web.bus.on('view_switch_mode', this, function(viewManager, mode) {
39                 self.on_switch(viewManager, mode);
40             });
41
42             instance.web.bus.on('form_view_shown', this, function(formView) {
43                 self.on_form_view(formView);
44             });
45
46             instance.web.bus.on('form_view_saved', this, function(formView) {
47                 self.on_form_view(formView);
48             });
49         },
50
51         // stub
52         on_action: function(action) {
53             var self = this;
54             var action_id = action.id;
55             var model = action.res_model;
56         },
57
58         on_view: function(view) {
59             var self = this;
60             var fields_view = view.fields_view;
61             var action_id = view.ViewManager.action ? view.ViewManager.action.id : null;
62             var model = fields_view.model;
63
64             // kanban
65             if(fields_view.type === 'kanban') {
66                 var dataset_def = $.Deferred();
67                 var groups_def = $.Deferred();
68                 view.on("kanban_dataset_processed", self, function() {
69                     var length = view.dataset.ids.length;
70                     dataset_def.resolve(length);
71                 });
72                 view.on('kanban_groups_processed', self, function() {
73                     groups_def.resolve();
74                 });
75                 dataset_def.done(function(length) {
76                     self.eval_tip(action_id, model, fields_view.type);
77                 });
78                 groups_def.done(function() {
79                     self.eval_tip(action_id, model, fields_view.type);
80                 });
81             }
82         },
83
84         on_form_view: function(formView) {
85             var self = this;
86             var model = formView.model;
87             var type = formView.datarecord.type ? formView.datarecord.type : null;
88             var mode = 'form';
89             self.eval_tip(null, model, mode, type);
90         },
91
92         // stub
93         on_switch: function (viewManager, mode) {
94             var self = this;
95             var action = viewManager.action;
96             var action_id = action.id;
97             var model = action.res_model;
98         },
99
100         eval_tip: function(action_id, model, mode, type) {
101             var self = this;
102             var filter = {};
103             var valid_tips = [];
104             var tips = [];
105             if (action_id) {
106                 valid_tips = _.filter(self.tips, function (tip) {
107                     return tip.action_id[0] === action_id;
108                 });
109             }
110
111             filter.model = model;
112             filter.mode = mode;
113             tips = _.where(self.tips, filter);
114             if (type) {
115                 tips = _.filter(tips, function(tip) {
116                     if (!tip.type) {
117                         return true;
118                     }
119                     return tip.type === type;
120                 });
121             }
122
123             valid_tips = _.uniq(valid_tips.concat(tips));
124             _.each(valid_tips, function(tip) {
125                 if (!tip.is_consumed) {
126                     self.add_tip(tip);
127                 }
128             });
129         },
130
131
132         add_tip: function(tip) {
133             var self = this;
134             self.tip_mutex.exec(function() {
135                 return $.when(self.do_tip(tip));
136             });
137         },
138
139         do_tip: function (tip) {
140             var self = this;
141             var def = $.Deferred();
142             var Tips = new instance.web.Model('web.tip');
143             var highlight_selector = tip.highlight_selector;
144             var triggers = tip.trigger_selector ? tip.trigger_selector.split(',') : [];
145             var trigger_tip = true;
146
147             if(!$(highlight_selector).length > 0) {
148                 return def.reject();
149             }
150             for (var i = 0; i < triggers.length; i++) {
151                 if(!$(triggers[i]).length > 0) {
152                     trigger_tip = false;
153                 }
154             }
155
156             if (trigger_tip) {
157                 self.$element = $(highlight_selector).first();
158                 var _top = self.$element.offset().top -5;
159                 var _left = self.$element.offset().left -5;
160                 var _width = self.$element.outerWidth() + 10;
161                 var _height = self.$element.outerHeight() + 10;
162
163                 self.$helper = $("<div>", { class: 'oe_tip_helper' });
164                 self.$element.after(self.$helper);
165                 self.$helper.offset({top: _top , left: _left});
166                 self.$helper.width(_width);
167                 self.$helper.height(_height);
168
169                 self.$overlay = $("<div>", { class: 'oe_tip_overlay' });
170                 $('body').append(self.$overlay);
171                 self.$element.addClass('oe_tip_show_element');
172
173                 // fix the stacking context problem
174                 _.each(self.$element.parentsUntil('body'), function(el) {
175                     var zIndex = $(el).css('z-index');
176                     var opacity = parseFloat($(el).css('opacity'));
177
178                     if (/[0-9]+/.test(zIndex) || opacity < 1) {
179                         $(el).addClass('oe_tip_fix_parent');
180                     }
181                 });
182
183                 self.$element.popover({
184                     placement: tip.placement,
185                     title: tip.title,
186                     content: tip.description,
187                     html: true,
188                     container: 'body',
189                 }).popover("show");
190
191                 var $cross = $('<button type="button" class="close">&times;</button>');
192                 $cross.addClass('oe_tip_close');
193
194                 if (tip.title) {
195                     $('.popover-title').prepend($cross);
196                 } else {
197                     $('.popover-content').prepend($cross);
198                 }
199
200                 // consume tip
201                 tip.end_selector = tip.end_selector ? tip.end_selector : tip.highlight_selector;
202                 $(tip.end_selector).one(tip.end_event, function($ev) {
203                     self.end_tip(tip);
204                     def.resolve();
205                 });
206
207                 // dismiss tip
208                 $cross.on('click', function($ev) {
209                     self.end_tip(tip);
210                     def.resolve();
211                 });
212                 self.$overlay.on('click', function($ev) {
213                     self.end_tip(tip);
214                     def.resolve();
215                 });
216                 $(document).on('keyup.web_tip', function($ev) {
217                     if ($ev.which === 27) { // esc
218                         self.end_tip(tip);
219                         def.resolve();
220                     }
221                 });
222             } else {
223                def.reject();
224             }
225             return def;
226         },
227
228         end_tip: function(tip) {
229             var self = this;
230             var Tips = new instance.web.Model('web.tip');
231             self.$element.popover('destroy');
232             self.$overlay.remove();
233             self.$helper.remove();
234             self.$element.removeClass('oe_tip_show_element');
235             _.each($('.oe_tip_fix_parent'), function(el) {
236                 $(el).removeClass('oe_tip_fix_parent');
237             });
238             $(document).off('keyup.web_tip');
239             Tips.call('consume', [tip.id], {});
240             tip.is_consumed = true;
241         }
242     });
243
244     instance.web.WebClient = instance.web.WebClient.extend({
245         show_application: function() {
246             this._super();
247             this.tip_handler = new instance.web.Tip();
248         }
249     });
250 })();