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 $('.popover.tour').remove();
126 setTimeout(function () {
127 setTimeout(function () {
128 self.tour.goto(index);
133 moveToNextStep: function () {
134 var nextStepIndex = this.currentStepIndex() + 1;
135 this.moveToStep(nextStepIndex);
140 redirect: function (url) {
141 url = url || new website.UrlParser(window.location.href);
142 var path = (this.startPath && url.pathname !== this.startPath) ? this.startPath : url.pathname;
143 var search = url.activateTutorial(this.id);
144 var newUrl = path + search;
145 window.location.replace(newUrl);
148 return this.tourStorage.getItem(this.id+'_end') === "yes";
150 resume: function () {
151 // Override if necessary
152 return this.tourStorage.getItem(this.id+'_current_step') && !this.ended();
154 trigger: function (url) {
155 // Override if necessary
156 url = url || new website.UrlParser(window.location.href);
157 return url.isActive(this.id);
159 testUrl: function (pattern) {
160 var url = new website.UrlParser(window.location.href);
161 return pattern.test(url.pathname+url.search);
163 popover: function (options) {
164 return openerp.qweb.render('website.tour_popover', options);
166 onSnippetDragged: function (callback) {
168 function beginDrag () {
169 $('.popover.tour').remove();
170 function advance () {
171 if (_.isFunction(callback)) {
172 callback.apply(self);
175 $(document.body).one('mouseup', advance);
177 $('#website-top-navbar [data-snippet-id].ui-draggable').one('mousedown', beginDrag);
179 onSnippetDraggedAdvance: function () {
180 onSnippetDragged(self.moveToNextStep);
184 website.UrlParser = openerp.Class.extend({
185 init: function (url) {
186 var a = document.createElement('a');
190 this.protocol = a.protocol;
192 this.hostname = a.hostname;
193 this.pathname = a.pathname;
194 this.origin = a.origin;
195 this.search = a.search;
197 function generateTrigger (id) {
198 return "tutorial."+id+"=true";
200 this.activateTutorial = function (id) {
201 var urlTrigger = generateTrigger(id);
202 var querystring = _.filter(this.search.split('?'), function (str) {
205 if (querystring.length > 0) {
206 var queries = _.filter(querystring[0].split("&"), function (query) {
207 return query.indexOf("tutorial.") < 0
209 queries.push(urlTrigger);
210 return "?"+_.uniq(queries).join("&");
212 return "?"+urlTrigger;
215 this.isActive = function (id) {
216 var urlTrigger = generateTrigger(id);
217 return this.search.indexOf(urlTrigger) >= 0;
222 var TestConsole = openerp.Class.extend({
225 init: function (editor) {
227 throw new Error("Editor cannot be null or undefined");
229 this.editor = editor;
231 test: function (id) {
232 return _.find(this.tests, function (tour) {
233 return tour.id === id;
236 snippetSelector: function (snippetId) {
237 return '#oe_snippets div.oe_snippet[data-snippet-id="'+snippetId+'"] .oe_snippet_thumbnail';
239 snippetThumbnail: function (snippetId) {
240 return $(this.snippetSelector(snippetId)).first();
242 snippetThumbnailExists: function (snippetId) {
243 return this.snippetThumbnail(snippetId).length > 0;
245 dragAndDropSnippet: function (snippetId) {
246 function actualDragAndDrop ($thumbnail) {
247 var thumbnailPosition = $thumbnail.position();
248 $thumbnail.trigger($.Event("mousedown", { which: 1, pageX: thumbnailPosition.left, pageY: thumbnailPosition.top }));
249 $thumbnail.trigger($.Event("mousemove", { which: 1, pageX: thumbnailPosition.left, pageY: thumbnailPosition.top+500 }));
250 var $dropZone = $(".oe_drop_zone").first();
251 var dropPosition = $dropZone.position();
252 $dropZone.trigger($.Event("mouseup", { which: 1, pageX: dropPosition.left, pageY: dropPosition.top }));
254 if (this.snippetThumbnailExists(snippetId)) {
255 actualDragAndDrop(this.snippetThumbnail(snippetId));
257 this.editor.on('rte:ready', this, function () {
258 actualDragAndDrop(this.snippetThumbnail(snippetId));
264 website.EditorBar.include({
267 var result = this._super();
268 website.TestConsole = new TestConsole(this);
272 $('.tour-backdrop').click(function (e) {
273 e.stopImmediatePropagation();
276 var url = new website.UrlParser(window.location.href);
277 var menu = $('#help-menu');
278 _.each(this.tours, function (tour) {
279 var $menuItem = $($.parseHTML('<li><a href="#">'+tour.name+'</a></li>'));
280 $menuItem.click(function () {
285 menu.append($menuItem);
286 if (tour.trigger()) {
290 return this._super();
292 registerTour: function (tour) {
293 var testId = 'test_'+tour.id+'_tour';
294 this.tours.push(tour);
297 run: function (force) {
298 if (force === true) {
301 var actionSteps = _.filter(tour.steps, function (step) {
304 function executeStep (step) {
305 window.localStorage.setItem(testId, step.stepId);
306 step.triggers(function () {
307 var nextStep = actionSteps.shift();
309 // Ensure the previous step has been fully propagated
310 setTimeout(function () {
311 setTimeout(function () {
312 executeStep(nextStep);
316 window.localStorage.removeItem(testId);
319 var $element = $(step.element);
320 if (step.snippet && step.trigger === 'drag') {
321 website.TestConsole.dragAndDropSnippet(step.snippet);
322 } else if (step.trigger.id === 'change') {
323 var currentValue = $element.val();
324 var options = $element[0].options;
325 // FIXME: It may be necessary to set a particular value
326 var newValue = _.find(options, function (option) {
327 return option.value !== currentValue;
329 $element.val(newValue).trigger($.Event("change"));
331 $element.trigger($.Event("click", { srcElement: $element }));
334 var url = new website.UrlParser(window.location.href);
335 if (tour.startPath && url.pathname !== tour.startPath) {
336 window.localStorage.setItem(testId, actionSteps[0].stepId);
337 window.location.href = tour.startPath;
339 var lastStepId = window.localStorage.getItem(testId);
340 var currentStep = actionSteps.shift();
342 while (currentStep && lastStepId !== currentStep.stepId) {
343 currentStep = actionSteps.shift();
346 setTimeout(function () {
347 executeStep(currentStep);
352 window.localStorage.removeItem(testId);
355 website.TestConsole.tests.push(test);
356 if (window.localStorage.getItem(testId)) {