c79c483551d43ad7c6dc899b6cab267db1456b41
[odoo/odoo.git] / addons / website / static / src / js / website.tour.js
1 (function () {
2     'use strict';
3
4     var website = openerp.website;
5     website.add_template_file('/website/static/src/xml/website.tour.xml');
6
7     website.Tour = openerp.Class.extend({
8         tour: undefined,
9         steps: [],
10         tourStorage: window.localStorage,
11         init: function () {
12             this.tour = new Tour({
13                 name: this.id,
14                 storage: this.tourStorage,
15                 keyboard: false,
16                 template: this.popover(),
17                 onHide: function () {
18                     window.scrollTo(0, 0);
19                 }
20             });
21             this.registerSteps();
22         },
23         registerSteps: function () {
24             var self = this;
25             this.tour.addSteps(_.map(this.steps, function (step) {
26                 step.title = openerp.qweb.render('website.tour_popover_title', { title: step.title });
27                 if (!step.element) {
28                     step.orphan = true;
29                 }
30                 if (step.snippet) {
31                     step.element = '#oe_snippets div.oe_snippet[data-snippet-id="'+step.snippet+'"] .oe_snippet_thumbnail';
32                 }
33                 if (step.trigger) {
34                     if (step.trigger === 'click') {
35                         step.triggers = function (callback) {
36                             $(step.element).one('click', function () {
37                                 (callback || self.moveToNextStep).apply(self);
38                             });
39                         };
40                     } else if (step.trigger === 'drag') {
41                         step.triggers = function (callback) {
42                             self.onSnippetDragged(callback || self.moveToNextStep);
43                         };
44                     } else if (step.trigger && step.trigger.id) {
45                         if (step.trigger.emitter && step.trigger.type === 'openerp') {
46                             step.triggers = function (callback) {
47                                 step.trigger.emitter.on(step.trigger.id, self, function customHandler () {
48                                     step.trigger.emitter.off(step.trigger.id, customHandler);
49                                     (callback || self.moveToNextStep).apply(self, arguments);
50                                 });
51                             };
52                         } else {
53                             step.triggers = function (callback) {
54                                 var emitter = _.isString(step.trigger.emitter) ? $(step.trigger.emitter) : (step.trigger.emitter || $(step.element));
55                                 emitter.on(step.trigger.id, function () {
56                                     (callback || self.moveToNextStep).apply(self, arguments);
57                                 });
58                             };
59                         }
60                     } else if (step.trigger.modal) {
61                         step.triggers = function (callback) {
62                             var $doc = $(document);
63                             function onStop () {
64                                 if (step.trigger.modal.stopOnClose) {
65                                     self.stop();
66                                 }
67                             }
68                             $doc.on('hide.bs.modal', onStop);
69                             $doc.one('shown.bs.modal', function () {
70                                 $('.modal button.btn-primary').one('click', function () {
71                                     $doc.off('hide.bs.modal', onStop);
72                                     self.moveToStep(step.trigger.modal.afterSubmit);
73                                 });
74                                 (callback || self.moveToNextStep).apply(self);
75                             });
76                         };
77                     }
78                 }
79                 step.onShow = (function () {
80                     var executed = false;
81                     return function () {
82                         if (!executed) {
83                             _.isFunction(step.onStart) && step.onStart();
84                             _.isFunction(step.triggers) && step.triggers();
85                             executed = true;
86                         }
87                     };
88                 }());
89                 return step;
90             }));
91         },
92         reset: function () {
93             this.tourStorage.removeItem(this.id+'_current_step');
94             this.tourStorage.removeItem(this.id+'_end');
95             this.tour._current = 0;
96             $('.popover.tour').remove();
97         },
98         start: function () {
99             if (this.resume() || ((this.currentStepIndex() === 0) && !this.tour.ended())) {
100                 this.tour.start();
101             }
102         },
103         currentStepIndex: function () {
104             var index = this.tourStorage.getItem(this.id+'_current_step') || 0;
105             return parseInt(index, 10);
106         },
107         indexOfStep: function (stepId) {
108             var index = -1;
109             _.each(this.steps, function (step, i) {
110                if (step.stepId === stepId) {
111                    index = i;
112                }
113             });
114             return index;
115         },
116         isCurrentStep: function (stepId) {
117             return this.currentStepIndex() === this.indexOfStep(stepId);
118         },
119         moveToStep: function (step) {
120             var index = _.isNumber(step) ? step : this.indexOfStep(step);
121             if (index >= this.steps.length) {
122                 this.stop();
123             } else if (index >= 0) {
124                 var self = this;
125                 setTimeout(function () {
126                     $('.popover.tour').remove();
127                     self.tour.goto(index);
128                 }, 0);
129             }
130         },
131         moveToNextStep: function () {
132             var nextStepIndex = this.currentStepIndex() + 1;
133             this.moveToStep(nextStepIndex);
134         },
135         stop: function () {
136             this.tour.end();
137         },
138         redirect: function (url) {
139             url = url || new website.UrlParser(window.location.href);
140             var path = (this.startPath && url.pathname !== this.startPath) ? this.startPath : url.pathname;
141             var search = url.activateTutorial(this.id);
142             var newUrl = path + search;
143             window.location.replace(newUrl);
144         },
145         ended: function () {
146             return this.tourStorage.getItem(this.id+'_end') === "yes";
147         },
148         resume: function () {
149             // Override if necessary
150             return !this.ended();
151         },
152         trigger: function (url) {
153             // Override if necessary
154             url = url || new website.UrlParser(window.location.href);
155             return url.isActive(this.id);
156         },
157         testUrl: function (pattern) {
158             var url = new website.UrlParser(window.location.href);
159             return pattern.test(url.pathname+url.search);
160         },
161         popover: function (options) {
162             return openerp.qweb.render('website.tour_popover', options);
163         },
164         onSnippetDragged: function (callback) {
165             var self = this;
166             function beginDrag () {
167                 $('.popover.tour').remove();
168                 function advance () {
169                     if (_.isFunction(callback)) {
170                         callback.apply(self);
171                     }
172                 }
173                 $(document.body).one('mouseup', advance);
174             }
175             $('#website-top-navbar [data-snippet-id].ui-draggable').one('mousedown', beginDrag);
176         },
177         onSnippetDraggedAdvance: function () {
178             onSnippetDragged(self.moveToNextStep);
179         },
180     });
181
182     website.UrlParser = openerp.Class.extend({
183         init: function (url) {
184             var a = document.createElement('a');
185             a.href = url;
186             this.href = a.href;
187             this.host = a.host;
188             this.protocol = a.protocol;
189             this.port = a.port;
190             this.hostname = a.hostname;
191             this.pathname = a.pathname;
192             this.origin = a.origin;
193             this.search = a.search;
194             this.hash = a.hash;
195             function generateTrigger (id) {
196                 return "tutorial."+id+"=true";
197             }
198             this.activateTutorial = function (id) {
199                 var urlTrigger = generateTrigger(id);
200                 var querystring = _.filter(this.search.split('?'), function (str) {
201                     return str;
202                 });
203                 if (querystring.length > 0) {
204                     var queries = _.filter(querystring[0].split("&"), function (query) {
205                         return query.indexOf("tutorial.") < 0
206                     });
207                     queries.push(urlTrigger);
208                     return "?"+_.uniq(queries).join("&");
209                 } else {
210                     return "?"+urlTrigger;
211                 }
212             };
213             this.isActive = function (id) {
214                 var urlTrigger = generateTrigger(id);
215                 return this.search.indexOf(urlTrigger) >= 0;
216             };
217         },
218     });
219
220     var TestConsole = website.TestConsole = {
221         tests: [],
222         test: function (id) {
223             return _.find(this.tests, function (tour) {
224                return tour.id === id;
225             });
226         },
227         dragAndDropSnippet: function (snippetId) {
228             var selector = '#oe_snippets div.oe_snippet[data-snippet-id="'+snippetId+'"] .oe_snippet_thumbnail';
229             var $thumbnail = $(selector).first();
230             var thumbnailPosition = $thumbnail.position();
231             $thumbnail.trigger($.Event("mousedown", { which: 1, pageX: thumbnailPosition.left, pageY: thumbnailPosition.top }));
232             $thumbnail.trigger($.Event("mousemove", { which: 1, pageX: thumbnailPosition.left+100, pageY: thumbnailPosition.top+700 }));
233             var $dropZone = $(".oe_drop_zone").first();
234             var dropPosition = $dropZone.position();
235             $dropZone.trigger($.Event("mouseup", { which: 1, pageX: dropPosition.left, pageY: dropPosition.top }));
236         },
237     };
238
239     website.EditorBar.include({
240         tours: [],
241         start: function () {
242             $('.tour-backdrop').click(function (e) {
243                 e.stopImmediatePropagation();
244                 e.preventDefault();
245             });
246             var url = new website.UrlParser(window.location.href);
247             var menu = $('#help-menu');
248             _.each(this.tours, function (tour) {
249                 var $menuItem = $($.parseHTML('<li><a href="#">'+tour.name+'</a></li>'));
250                 $menuItem.click(function () {
251                     tour.redirect(url);
252                     tour.reset();
253                     tour.start();
254                 });
255                 menu.append($menuItem);
256                 if (tour.trigger()) {
257                     tour.start();
258                 }
259             });
260             return this._super();
261         },
262         registerTour: function (tour) {
263             var testId = 'test_'+tour.id+'_tour';
264             this.tours.push(tour);
265             TestConsole.tests.push({
266                 id: tour.id,
267                 run: function (force) {
268                     var url = new website.UrlParser(window.location.href);
269                     if (tour.startPath && url.pathname !== tour.startPath) {
270                         throw new Error(tour.startPath);
271                     }
272                     if (force === true) {
273                         this.reset();
274                     }
275                     var actionSteps = _.filter(tour.steps, function (step) {
276                        return step.trigger;
277                     });
278                     function executeStep (step) {
279                         window.localStorage.setItem(testId, step.stepId);
280                         step.triggers(function () {
281                             var nextStep = actionSteps.shift();
282                             if (nextStep) {
283                                 setTimeout(function () {
284                                     executeStep(nextStep);
285                                 }, 0);
286                             }
287                         });
288                         var $element = $(step.element);
289                         if (step.snippet && step.trigger === 'drag') {
290                             TestConsole.dragAndDropSnippet(step.snippet);
291                         } else if (step.trigger.id === 'change') {
292                             var currentValue = $element.val();
293                             var options = $element[0].options;
294                             var newValue = _.find(options, function (option) {
295                                 return option.value !== currentValue;
296                             }).value;
297                             $element.val(newValue).trigger($.Event("change"));
298                         } else {
299                             $element.trigger($.Event("click", { srcElement: $element }));
300                         }
301                     }
302                     var lastStepId = window.localStorage.getItem(testId);
303                     var currentStep = actionSteps.shift();
304                     if (lastStepId) {
305                         while (currentStep && lastStepId !== currentStep.stepId) {
306                             currentStep = actionSteps.shift();
307                         }
308                     }
309                     executeStep(currentStep);
310                 },
311                 reset: function () {
312                     window.localStorage.removeItem(testId);
313                 },
314             });
315         },
316     });
317
318 }());