4 var website = openerp.website;
5 website.add_template_file('/website/static/src/xml/website.tour.xml');
7 website.Tour = openerp.Class.extend({
10 tourStorage: window.localStorage,
12 this.tour = new Tour({
14 storage: this.tourStorage,
16 template: this.popover(),
18 window.scrollTo(0, 0);
23 registerSteps: function () {
25 this.tour.addSteps(_.map(this.steps, function (step) {
26 step.title = openerp.qweb.render('website.tour_popover_title', { title: step.title });
31 step.element = '#oe_snippets div.oe_snippet[data-snippet-id="'+step.snippet+'"] .oe_snippet_thumbnail';
34 if (step.trigger === 'click') {
35 step.triggers = function (callback) {
36 $(step.element).one('click', function () {
37 (callback || self.moveToNextStep).apply(self);
40 } else if (step.trigger === 'drag') {
41 step.triggers = function (callback) {
42 self.onSnippetDragged(callback || self.moveToNextStep);
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);
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);
60 } else if (step.trigger.modal) {
61 step.triggers = function (callback) {
62 var $doc = $(document);
64 if (step.trigger.modal.stopOnClose) {
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);
74 (callback || self.moveToNextStep).apply(self);
79 step.onShow = (function () {
83 _.isFunction(step.onStart) && step.onStart();
84 _.isFunction(step.triggers) && step.triggers();
93 this.tourStorage.removeItem(this.id+'_current_step');
94 this.tourStorage.removeItem(this.id+'_end');
95 this.tour._current = 0;
96 $('.popover.tour').remove();
99 if (this.resume() || ((this.currentStepIndex() === 0) && !this.tour.ended())) {
103 currentStepIndex: function () {
104 var index = this.tourStorage.getItem(this.id+'_current_step') || 0;
105 return parseInt(index, 10);
107 indexOfStep: function (stepId) {
109 _.each(this.steps, function (step, i) {
110 if (step.stepId === stepId) {
116 isCurrentStep: function (stepId) {
117 return this.currentStepIndex() === this.indexOfStep(stepId);
119 moveToStep: function (step) {
120 var index = _.isNumber(step) ? step : this.indexOfStep(step);
121 if (index >= this.steps.length) {
123 } else if (index >= 0) {
125 setTimeout(function () {
126 $('.popover.tour').remove();
127 self.tour.goto(index);
131 moveToNextStep: function () {
132 var nextStepIndex = this.currentStepIndex() + 1;
133 this.moveToStep(nextStepIndex);
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);
146 return this.tourStorage.getItem(this.id+'_end') === "yes";
148 resume: function () {
149 // Override if necessary
150 return !this.ended();
152 trigger: function (url) {
153 // Override if necessary
154 url = url || new website.UrlParser(window.location.href);
155 return url.isActive(this.id);
157 testUrl: function (pattern) {
158 var url = new website.UrlParser(window.location.href);
159 return pattern.test(url.pathname+url.search);
161 popover: function (options) {
162 return openerp.qweb.render('website.tour_popover', options);
164 onSnippetDragged: function (callback) {
166 function beginDrag () {
167 $('.popover.tour').remove();
168 function advance () {
169 if (_.isFunction(callback)) {
170 callback.apply(self);
173 $(document.body).one('mouseup', advance);
175 $('#website-top-navbar [data-snippet-id].ui-draggable').one('mousedown', beginDrag);
177 onSnippetDraggedAdvance: function () {
178 onSnippetDragged(self.moveToNextStep);
182 website.UrlParser = openerp.Class.extend({
183 init: function (url) {
184 var a = document.createElement('a');
188 this.protocol = a.protocol;
190 this.hostname = a.hostname;
191 this.pathname = a.pathname;
192 this.origin = a.origin;
193 this.search = a.search;
195 function generateTrigger (id) {
196 return "tutorial."+id+"=true";
198 this.activateTutorial = function (id) {
199 var urlTrigger = generateTrigger(id);
200 var querystring = _.filter(this.search.split('?'), function (str) {
203 if (querystring.length > 0) {
204 var queries = _.filter(querystring[0].split("&"), function (query) {
205 return query.indexOf("tutorial.") < 0
207 queries.push(urlTrigger);
208 return "?"+_.uniq(queries).join("&");
210 return "?"+urlTrigger;
213 this.isActive = function (id) {
214 var urlTrigger = generateTrigger(id);
215 return this.search.indexOf(urlTrigger) >= 0;
220 var TestConsole = website.TestConsole = {
222 test: function (id) {
223 return _.find(this.tests, function (tour) {
224 return tour.id === id;
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 }));
239 website.EditorBar.include({
242 $('.tour-backdrop').click(function (e) {
243 e.stopImmediatePropagation();
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 () {
255 menu.append($menuItem);
256 if (tour.trigger()) {
260 return this._super();
262 registerTour: function (tour) {
263 var testId = 'test_'+tour.id+'_tour';
264 this.tours.push(tour);
265 TestConsole.tests.push({
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);
272 if (force === true) {
275 var actionSteps = _.filter(tour.steps, function (step) {
278 function executeStep (step) {
279 window.localStorage.setItem(testId, step.stepId);
280 step.triggers(function () {
281 var nextStep = actionSteps.shift();
283 setTimeout(function () {
284 executeStep(nextStep);
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;
297 $element.val(newValue).trigger($.Event("change"));
299 $element.trigger($.Event("click", { srcElement: $element }));
302 var lastStepId = window.localStorage.getItem(testId);
303 var currentStep = actionSteps.shift();
305 while (currentStep && lastStepId !== currentStep.stepId) {
306 currentStep = actionSteps.shift();
309 executeStep(currentStep);
312 window.localStorage.removeItem(testId);