From bc3fc54fac248c829a6472bcdaf06347238195b0 Mon Sep 17 00:00:00 2001 From: Christophe Matthieu Date: Wed, 18 Jun 2014 12:25:13 +0200 Subject: [PATCH 01/10] [IMP] Tour: add tour in web module; tour became available in backend. --- addons/web/static/src/xml/base.xml | 1 + addons/web/views/database_manager.html | 1 + addons/web/views/webclient_templates.xml | 1 + .../static/src/js/website.tour.banner.js | 3 +- addons/website/static/src/js/website.tour.js | 543 +----------------- .../website/static/src/xml/website.tour.xml | 27 - addons/website/tests/test_ui.py | 2 +- .../static/src/js/website.tour.blog.js | 3 +- addons/website_blog/tests/test_ui.py | 2 +- .../static/src/js/website.tour.event.js | 3 +- addons/website_event/tests/test_ui.py | 2 +- .../static/src/js/website.tour.event_sale.js | 4 +- addons/website_event_sale/tests/test_ui.py | 10 +- addons/website_report/views/layouts.xml | 2 - .../static/src/js/website.tour.sale.js | 7 +- .../static/src/js/website.tour.shop.js | 3 +- .../website_sale/tests/test_sale_process.py | 14 +- 17 files changed, 41 insertions(+), 587 deletions(-) delete mode 100644 addons/website/static/src/xml/website.tour.xml diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index 913783a2396..fb2fcbf303e 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -2038,4 +2038,5 @@
+ diff --git a/addons/web/views/database_manager.html b/addons/web/views/database_manager.html index 5734f60bc62..19ae6a23bb2 100644 --- a/addons/web/views/database_manager.html +++ b/addons/web/views/database_manager.html @@ -51,6 +51,7 @@ + diff --git a/addons/web/views/webclient_templates.xml b/addons/web/views/webclient_templates.xml index 2b022d86f30..7dc160995e4 100644 --- a/addons/web/views/webclient_templates.xml +++ b/addons/web/views/webclient_templates.xml @@ -22,6 +22,7 @@ + diff --git a/addons/website/static/src/js/website.tour.banner.js b/addons/website/static/src/js/website.tour.banner.js index 3d55220084e..c9295635941 100644 --- a/addons/website/static/src/js/website.tour.banner.js +++ b/addons/website/static/src/js/website.tour.banner.js @@ -1,10 +1,9 @@ (function () { 'use strict'; - var website = openerp.website; var _t = openerp._t; - website.Tour.register({ + openerp.Tour.register({ id: 'banner', name: _t("Build a page"), path: '/page/website.homepage', diff --git a/addons/website/static/src/js/website.tour.js b/addons/website/static/src/js/website.tour.js index 0544d29df08..2d7a67fbe1a 100644 --- a/addons/website/static/src/js/website.tour.js +++ b/addons/website/static/src/js/website.tour.js @@ -1,535 +1,24 @@ (function () { 'use strict'; -// raise an error in test mode if openerp don't exist -if (typeof openerp === "undefined") { - var error = "openerp is undefined" - + "\nhref: " + window.location.href - + "\nreferrer: " + document.referrer - + "\nlocalStorage: " + window.localStorage.getItem("tour"); - if (typeof $ !== "undefined") { - error += '\n\n' + $("body").html(); - } - throw new Error(error); -} - -var website = window.openerp.website; - -// don't rewrite T in test mode -if (typeof website.Tour !== "undefined") { - return; -} - -// don't need template to use bootstrap Tour in automatic mode -if (typeof QWeb2 !== "undefined") { - website.add_template_file('/website/static/src/xml/website.tour.xml'); -} - -if (website.EditorBar) { - website.EditorBar.include({ - tours: [], - start: function () { - var self = this; - var menu = $('#help-menu'); - _.each(T.tours, function (tour) { - if (tour.mode === "test") { - return; - } - var $menuItem = $($.parseHTML('
  • '+tour.name+'
  • ')); - $menuItem.click(function () { - T.reset(); - T.run(tour.id); - }); - menu.append($menuItem); +window.openerp.website.EditorBar.include({ + tours: [], + start: function () { + var self = this; + var menu = $('#help-menu'); + _.each(window.openerp.Tour.tours, function (tour) { + if (tour.mode === "test") { + return; + } + var $menuItem = $($.parseHTML('
  • '+tour.name+'
  • ')); + $menuItem.click(function () { + T.reset(); + T.run(tour.id); }); - return this._super(); - } - }); -} - - -///////////////////////////////////////////////// - - -/* jQuery selector to match exact text inside an element - * :containsExact() - case insensitive - * :containsExactCase() - case sensitive - * :containsRegex() - set by user ( use: $(el).find(':containsRegex(/(red|blue|yellow)/gi)') ) - */ -$.extend($.expr[':'],{ - containsExact: function(a,i,m){ - return $.trim(a.innerHTML.toLowerCase()) === m[3].toLowerCase(); - }, - containsExactCase: function(a,i,m){ - return $.trim(a.innerHTML) === m[3]; - }, - // Note all escaped characters need to be double escaped - // inside of the containsRegex, so "\(" needs to be "\\(" - containsRegex: function(a,i,m){ - var regreg = /^\/((?:\\\/|[^\/])+)\/([mig]{0,3})$/, - reg = regreg.exec(m[3]); - return reg ? new RegExp(reg[1], reg[2]).test($.trim(a.innerHTML)) : false; + menu.append($menuItem); + }); + return this._super(); } }); -$.ajaxSetup({ - beforeSend:function(){ - $.ajaxBusy = ($.ajaxBusy|0) + 1; - }, - complete:function(){ - $.ajaxBusy--; - } -}); - -///////////////////////////////////////////////// - -var localStorage = window.localStorage; - -var T = website.Tour = { - tours: {}, - defaultDelay: 50, - retryRunningDelay: 1000, - errorDelay: 5000, - state: null, - $element: null, - timer: null, - testtimer: null, - currentTimer: null, - register: function (tour) { - if (tour.mode !== "test") tour.mode = "tutorial"; - T.tours[tour.id] = tour; - }, - run: function (tour_id, mode) { - var tour = T.tours[tour_id]; - this.time = new Date().getTime(); - if (tour.path && !window.location.href.match(new RegExp("("+T.getLang()+")?"+tour.path+"#?$", "i"))) { - var href = "/"+T.getLang()+tour.path; - console.log("Tour Begin from run method (redirection to "+href+")"); - T.saveState(tour.id, mode || tour.mode, -1, 0); - window.location.href = href; - } else { - console.log("Tour Begin from run method"); - T.saveState(tour.id, mode || tour.mode, 0, 0); - T.running(); - } - }, - registerSteps: function (tour) { - if (tour.register) { - return; - } - tour.register = true; - - for (var index=0, len=tour.steps.length; index 0 && tour.steps[index-1] && - tour.steps[index-1].popover && tour.steps[index-1].popover.next) { - step.waitNot = '.popover.tour.fade.in:visible'; - } - if (!step.waitFor && index > 0 && tour.steps[index-1].snippet) { - step.waitFor = '.oe_overlay_options .oe_options:visible'; - } - - - var snippet = step.element && step.element.match(/#oe_snippets (.*) \.oe_snippet_thumbnail/); - if (snippet) { - step.snippet = snippet[1]; - } else if (step.snippet) { - step.element = '#oe_snippets '+step.snippet+' .oe_snippet_thumbnail'; - } - - if (!step.element) { - step.element = "body"; - step.orphan = true; - step.backdrop = true; - } - } - if (tour.steps[index-1] && - tour.steps[index-1].popover && tour.steps[index-1].popover.next) { - var step = { - _title: "", - id: index, - waitNot: '.popover.tour.fade.in:visible' - }; - tour.steps.push(step); - } - - // rendering bootstrap tour and popover - if (tour.mode !== "test") { - for (var index=0, len=tour.steps.length; index'); - } - - if (step.backdrop || $element.parents("#website-top-navbar, .modal").size()) { - $tip.css("z-index", 2010); - } - - // button click event - $tip.find("button") - .one("click", function () { - step.busy = true; - if (!$(this).is("[data-role='next']")) { - clearTimeout(T.timer); - T.endTour(); - } - T.closePopover(); - }); - - T.repositionPopover(); - }, - repositionPopover: function() { - var popover = T.$element.data("bs.popover"); - var $tip = T.$element.data("bs.popover").tip(); - - if (popover.options.orphan) { - return $tip.css("top", $(window).outerHeight() / 2 - $tip.outerHeight() / 2); - } - - var offsetBottom, offsetHeight, offsetRight, offsetWidth, originalLeft, originalTop, tipOffset; - offsetWidth = $tip[0].offsetWidth; - offsetHeight = $tip[0].offsetHeight; - tipOffset = $tip.offset(); - originalLeft = tipOffset.left; - originalTop = tipOffset.top; - offsetBottom = $(document).outerHeight() - tipOffset.top - $tip.outerHeight(); - if (offsetBottom < 0) { - tipOffset.top = tipOffset.top + offsetBottom; - } - offsetRight = $("html").outerWidth() - tipOffset.left - $tip.outerWidth(); - if (offsetRight < 0) { - tipOffset.left = tipOffset.left + offsetRight; - } - if (tipOffset.top < 0) { - tipOffset.top = 0; - } - if (tipOffset.left < 0) { - tipOffset.left = 0; - } - $tip.offset(tipOffset); - if (popover.options.placement === "bottom" || popover.options.placement === "top") { - var left = T.$element.offset().left + T.$element.outerWidth()/2 - tipOffset.left; - $tip.find(".arrow").css("left", left ? left + "px" : ""); - } else if (popover.options.placement !== "auto") { - var top = T.$element.offset().top + T.$element.outerHeight()/2 - tipOffset.top; - $tip.find(".arrow").css("top", top ? top + "px" : ""); - } - }, - popoverTitle: function (tour, options) { - return openerp.qweb ? openerp.qweb.render('website.tour_popover_title', options) : options.title; - }, - popover: function (options) { - return openerp.qweb ? openerp.qweb.render('website.tour_popover', options) : options.title; - }, - getLang: function () { - return $("html").attr("lang").replace(/-/, '_'); - }, - getState: function () { - var state = JSON.parse(localStorage.getItem("tour") || 'false') || {}; - if (state) { this.time = state.time; } - var tour_id,mode,step_id; - if (!state.id && window.location.href.indexOf("#tutorial.") > -1) { - state = { - "id": window.location.href.match(/#tutorial\.(.*)=true/)[1], - "mode": "tutorial", - "step_id": 0 - }; - window.location.hash = ""; - console.log("Tour Begin from url hash"); - T.saveState(state.id, state.mode, state.step_id, 0); - } - if (!state.id) { - return; - } - state.tour = T.tours[state.id]; - state.step = state.tour && state.tour.steps[state.step_id === -1 ? 0 : state.step_id]; - return state; - }, - error: function (step, message) { - var state = T.getState(); - message += '\n tour: ' + state.id - + '\n step: ' + step.id + ": '" + (step._title || step.title) + "'" - + '\n href: ' + window.location.href - + '\n referrer: ' + document.referrer - + '\n element: ' + Boolean(!step.element || ($(step.element).size() && $(step.element).is(":visible") && !$(step.element).is(":hidden"))) - + '\n waitNot: ' + Boolean(!step.waitNot || !$(step.waitNot).size()) - + '\n waitFor: ' + Boolean(!step.waitFor || $(step.waitFor).size()) - + "\n localStorage: " + JSON.stringify(localStorage) - + '\n\n' + $("body").html(); - T.reset(); - throw new Error(message); - }, - lists: function () { - var tour_ids = []; - for (var k in T.tours) { - tour_ids.push(k); - } - return tour_ids; - }, - saveState: function (tour_id, mode, step_id, number) { - localStorage.setItem("tour", JSON.stringify({"id":tour_id, "mode":mode, "step_id":step_id || 0, "time": this.time, "number": number+1})); - }, - reset: function () { - var state = T.getState(); - if (state) { - for (var k in state.tour.steps) { - state.tour.steps[k].busy = false; - } - } - localStorage.removeItem("tour"); - clearTimeout(T.timer); - clearTimeout(T.testtimer); - T.closePopover(); - }, - running: function () { - function run () { - var state = T.getState(); - if (!state) return; - if (state.tour) { - console.log("Tour '"+state.id+"' is running"); - T.registerSteps(state.tour); - T.nextStep(); - } else { - console.log("Tour '"+state.id+"' wait for running (tour undefined)"); - setTimeout(T.running, state.mode === "test" ? T.defaultDelay : T.retryRunningDelay); - } - } - setTimeout(function () { - if ($.ajaxBusy) { - $(document).ajaxStop(run); - } else { - run(); - } - },0); - }, - check: function (step) { - return (step && - (!step.element || ($(step.element).size() && $(step.element).is(":visible") && !$(step.element).is(":hidden"))) && - (!step.waitNot || !$(step.waitNot).size()) && - (!step.waitFor || $(step.waitFor).size())); - }, - waitNextStep: function () { - var state = T.getState(); - var time = new Date().getTime(); - var timer; - var next = state.tour.steps[state.step.id+1]; - var overlaps = state.mode === "test" ? T.errorDelay : 0; - - window.onbeforeunload = function () { - clearTimeout(T.timer); - clearTimeout(T.testtimer); - }; - - function checkNext () { - T.autoTogglePopover(); - - clearTimeout(T.timer); - if (T.check(next)) { - clearTimeout(T.currentTimer); - // use an other timeout for cke dom loading - T.saveState(state.id, state.mode, state.step.id, 0); - setTimeout(function () { - T.nextStep(next); - }, T.defaultDelay); - } else if (!overlaps || new Date().getTime() - time < overlaps) { - T.timer = setTimeout(checkNext, T.defaultDelay); - } else { - T.error(next, "Can't reach the next step"); - } - } - checkNext(); - }, - nextStep: function (step) { - var state = T.getState(); - - if (!state) { - return; - } - - step = step || state.step; - var next = state.tour.steps[step.id+1]; - - if (state.number > 3) { - T.error(next, "Cycling. Can't reach the next step"); - } - - T.saveState(state.id, state.mode, step.id, state.number); - - if (step.id !== state.step_id) { - console.log("Tour Step: '" + (step._title || step.title) + "' (" + (new Date().getTime() - this.time) + "ms)"); - } - - T.autoTogglePopover(true); - - if (step.onload) { - step.onload(); - } - - if (next) { - setTimeout(function () { - T.waitNextStep(); - if (state.mode === "test") { - setTimeout(function(){ - T.autoNextStep(state.tour, step); - }, T.defaultDelay); - } - }, next.wait || 0); - } else { - T.endTour(); - } - }, - endTour: function () { - var state = T.getState(); - var test = state.step.id >= state.tour.steps.length-1; - T.reset(); - if (test) { - console.log('ok'); - } else { - console.log('error'); - } - }, - autoNextStep: function (tour, step) { - clearTimeout(T.testtimer); - - function autoStep () { - if (!step) return; - - if (step.autoComplete) { - step.autoComplete(tour); - } - - $(".popover.tour [data-role='next']").click(); - - var $element = $(step.element); - if (!$element.size()) return; - - if (step.snippet) { - - T.autoDragAndDropSnippet($element); - - } else if ($element.is(":visible")) { - - $element.trigger($.Event("mouseenter", { srcElement: $element[0] })); - $element.trigger($.Event("mousedown", { srcElement: $element[0] })); - - var evt = document.createEvent("MouseEvents"); - evt.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); - $element[0].dispatchEvent(evt); - - // trigger after for step like: mouseenter, next step click on button display with mouseenter - setTimeout(function () { - $element.trigger($.Event("mouseup", { srcElement: $element[0] })); - $element.trigger($.Event("mouseleave", { srcElement: $element[0] })); - }, 1000); - } - if (step.sampleText) { - - $element.trigger($.Event("keydown", { srcElement: $element })); - if ($element.is("input") ) { - $element.val(step.sampleText); - } if ($element.is("select")) { - $element.find("[value='"+step.sampleText+"'], option:contains('"+step.sampleText+"')").attr("selected", true); - $element.val(step.sampleText); - } else { - $element.html(step.sampleText); - } - setTimeout(function () { - $element.trigger($.Event("keyup", { srcElement: $element })); - $element.trigger($.Event("change", { srcElement: $element })); - }, self.defaultDelay<<1); - - } - } - T.testtimer = setTimeout(autoStep, 100); - }, - autoDragAndDropSnippet: function (selector) { - var $thumbnail = $(selector).first(); - var thumbnailPosition = $thumbnail.position(); - $thumbnail.trigger($.Event("mousedown", { which: 1, pageX: thumbnailPosition.left, pageY: thumbnailPosition.top })); - $thumbnail.trigger($.Event("mousemove", { which: 1, pageX: document.body.scrollWidth/2, pageY: document.body.scrollHeight/2 })); - var $dropZone = $(".oe_drop_zone").first(); - var dropPosition = $dropZone.position(); - $dropZone.trigger($.Event("mouseup", { which: 1, pageX: dropPosition.left, pageY: dropPosition.top })); - } -}; - -//$(document).ready(T.running); -website.ready().then(T.running); - }()); diff --git a/addons/website/static/src/xml/website.tour.xml b/addons/website/static/src/xml/website.tour.xml deleted file mode 100644 index dcecef021bb..00000000000 --- a/addons/website/static/src/xml/website.tour.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - -
    -
    -

    -
    - - - -
    -
    - - - -
    diff --git a/addons/website/tests/test_ui.py b/addons/website/tests/test_ui.py index 7eb88d00df3..e2a49953034 100644 --- a/addons/website/tests/test_ui.py +++ b/addons/website/tests/test_ui.py @@ -8,6 +8,6 @@ class TestUi(openerp.tests.HttpCase): self.phantom_js("/", "console.log('ok')", "openerp.website.editor", login='admin') def test_04_admin_tour_banner(self): - self.phantom_js("/", "openerp.website.Tour.run('banner', 'test')", "openerp.website.Tour.tours.banner", login='admin') + self.phantom_js("/", "openerp.Tour.run('banner', 'test')", "openerp.Tour.tours.banner", login='admin') # vim:et: diff --git a/addons/website_blog/static/src/js/website.tour.blog.js b/addons/website_blog/static/src/js/website.tour.blog.js index 050bf549225..11a68e17b74 100644 --- a/addons/website_blog/static/src/js/website.tour.blog.js +++ b/addons/website_blog/static/src/js/website.tour.blog.js @@ -1,10 +1,9 @@ (function () { 'use strict'; - var website = openerp.website; var _t = openerp._t; - website.Tour.register({ + openerp.Tour.register({ id: 'blog', name: _t("Create a blog post"), steps: [ diff --git a/addons/website_blog/tests/test_ui.py b/addons/website_blog/tests/test_ui.py index b34ef25c002..f6d0f025a45 100644 --- a/addons/website_blog/tests/test_ui.py +++ b/addons/website_blog/tests/test_ui.py @@ -2,5 +2,5 @@ import openerp.tests class TestUi(openerp.tests.HttpCase): def test_admin(self): - self.phantom_js("/", "openerp.website.Tour.run('blog', 'test')", "openerp.website.Tour.tours.blog") + self.phantom_js("/", "openerp.Tour.run('blog', 'test')", "openerp.Tour.tours.blog") diff --git a/addons/website_event/static/src/js/website.tour.event.js b/addons/website_event/static/src/js/website.tour.event.js index 07a12d81f34..245fb2ee41e 100644 --- a/addons/website_event/static/src/js/website.tour.event.js +++ b/addons/website_event/static/src/js/website.tour.event.js @@ -1,10 +1,9 @@ (function () { 'use strict'; - var website = openerp.website; var _t = openerp._t; - website.Tour.register({ + openerp.Tour.register({ id: 'event', name: _t("Create an event"), steps: [ diff --git a/addons/website_event/tests/test_ui.py b/addons/website_event/tests/test_ui.py index 96a17a91563..d353ffd28ea 100644 --- a/addons/website_event/tests/test_ui.py +++ b/addons/website_event/tests/test_ui.py @@ -2,5 +2,5 @@ import openerp.tests class TestUi(openerp.tests.HttpCase): def test_admin(self): - self.phantom_js("/", "openerp.website.Tour.run('event', 'test')", "openerp.website.Tour.tours.event") + self.phantom_js("/", "openerp.Tour.run('event', 'test')", "openerp.Tour.tours.event") diff --git a/addons/website_event_sale/static/src/js/website.tour.event_sale.js b/addons/website_event_sale/static/src/js/website.tour.event_sale.js index ea34fc35687..7b0bf1ab621 100644 --- a/addons/website_event_sale/static/src/js/website.tour.event_sale.js +++ b/addons/website_event_sale/static/src/js/website.tour.event_sale.js @@ -1,9 +1,7 @@ (function () { 'use strict'; - var website = openerp.website; - - website.Tour.register({ + openerp.Tour.register({ id: 'event_buy_tickets', name: "Try to buy tickets for event", path: '/event', diff --git a/addons/website_event_sale/tests/test_ui.py b/addons/website_event_sale/tests/test_ui.py index 3c3e643912a..94869251fad 100644 --- a/addons/website_event_sale/tests/test_ui.py +++ b/addons/website_event_sale/tests/test_ui.py @@ -3,19 +3,19 @@ import os import openerp.tests inject = [ - ("openerp.website.Tour", os.path.join(os.path.dirname(__file__), '../../website/static/src/js/website.tour.js')), - ("openerp.website.Tour.ShopTest", os.path.join(os.path.dirname(__file__), "../static/src/js/website.tour.event_sale.js")), + ("openerp.Tour", os.path.join(os.path.dirname(__file__), '../../web/static/src/js/tour.js')), + ("openerp.Tour.ShopTest", os.path.join(os.path.dirname(__file__), "../static/src/js/website.tour.event_sale.js")), ] @openerp.tests.common.at_install(False) @openerp.tests.common.post_install(True) class TestUi(openerp.tests.HttpCase): def test_admin(self): - self.phantom_js("/", "openerp.website.Tour.run('event_buy_tickets', 'test')", "openerp.website.Tour.tours.event_buy_tickets", inject=inject) + self.phantom_js("/", "openerp.Tour.run('event_buy_tickets', 'test')", "openerp.Tour.tours.event_buy_tickets", inject=inject) def test_demo(self): - self.phantom_js("/", "openerp.website.Tour.run('event_buy_tickets', 'test')", "openerp.website.Tour.tours.event_buy_tickets", login="demo", password="demo", inject=inject); + self.phantom_js("/", "openerp.Tour.run('event_buy_tickets', 'test')", "openerp.Tour.tours.event_buy_tickets", login="demo", password="demo", inject=inject); def test_public(self): - self.phantom_js("/", "openerp.website.Tour.run('event_buy_tickets', 'test')", "openerp.website.Tour.tours.event_buy_tickets", login=None, inject=inject); + self.phantom_js("/", "openerp.Tour.run('event_buy_tickets', 'test')", "openerp.Tour.tours.event_buy_tickets", login=None, inject=inject); diff --git a/addons/website_report/views/layouts.xml b/addons/website_report/views/layouts.xml index 9978d994821..03df41266be 100644 --- a/addons/website_report/views/layouts.xml +++ b/addons/website_report/views/layouts.xml @@ -62,8 +62,6 @@ - - diff --git a/addons/website_sale/static/src/js/website.tour.sale.js b/addons/website_sale/static/src/js/website.tour.sale.js index 8cfb4e2cdad..57365a826a6 100644 --- a/addons/website_sale/static/src/js/website.tour.sale.js +++ b/addons/website_sale/static/src/js/website.tour.sale.js @@ -1,9 +1,6 @@ (function () { 'use strict'; - - var website = openerp.website; - - website.Tour.register({ + openerp.Tour.register({ id: 'shop_customize', name: "Customize the page and search a product", path: '/shop', @@ -37,7 +34,7 @@ ] }); - website.Tour.register({ + openerp.Tour.register({ id: 'shop_buy_product', name: "Try to buy products", path: '/shop', diff --git a/addons/website_sale/static/src/js/website.tour.shop.js b/addons/website_sale/static/src/js/website.tour.shop.js index 3dd5b54b357..c39a8b2f9e8 100644 --- a/addons/website_sale/static/src/js/website.tour.shop.js +++ b/addons/website_sale/static/src/js/website.tour.shop.js @@ -1,10 +1,9 @@ (function () { 'use strict'; - var website = openerp.website; var _t = openerp._t; - website.Tour.register({ + openerp.Tour.register({ id: 'shop', name: _t("Create a product"), steps: [ diff --git a/addons/website_sale/tests/test_sale_process.py b/addons/website_sale/tests/test_sale_process.py index b37b514a825..a57a2d64bca 100644 --- a/addons/website_sale/tests/test_sale_process.py +++ b/addons/website_sale/tests/test_sale_process.py @@ -3,22 +3,22 @@ import os import openerp.tests inject = [ - ("openerp.website.Tour", os.path.join(os.path.dirname(__file__), '../../website/static/src/js/website.tour.js')), - ("openerp.website.Tour.ShopTest", os.path.join(os.path.dirname(__file__), "../static/src/js/website.tour.sale.js")), + ("openerp.Tour", os.path.join(os.path.dirname(__file__), '../../web/static/src/js/tour.js')), + ("openerp.Tour.ShopTest", os.path.join(os.path.dirname(__file__), "../static/src/js/website.tour.sale.js")), ] @openerp.tests.common.at_install(False) @openerp.tests.common.post_install(True) class TestUi(openerp.tests.HttpCase): def test_01_admin_shop_tour(self): - self.phantom_js("/", "openerp.website.Tour.run('shop', 'test')", "openerp.website.Tour.tours.shop", login="admin") + self.phantom_js("/", "openerp.Tour.run('shop', 'test')", "openerp.Tour.tours.shop", login="admin") def test_02_admin_checkout(self): - self.phantom_js("/", "openerp.website.Tour.run('shop_customize', 'test')", "openerp.website.Tour.tours.shop_customize", login="admin", inject=inject) - self.phantom_js("/", "openerp.website.Tour.run('shop_buy_product', 'test')", "openerp.website.Tour.tours.shop_buy_product", login="admin", inject=inject) + self.phantom_js("/", "openerp.Tour.run('shop_customize', 'test')", "openerp.Tour.tours.shop_customize", login="admin", inject=inject) + self.phantom_js("/", "openerp.Tour.run('shop_buy_product', 'test')", "openerp.Tour.tours.shop_buy_product", login="admin", inject=inject) def test_03_demo_checkout(self): - self.phantom_js("/", "openerp.website.Tour.run('shop_buy_product', 'test')", "openerp.website.Tour.tours.shop_buy_product", login="demo", inject=inject) + self.phantom_js("/", "openerp.Tour.run('shop_buy_product', 'test')", "openerp.Tour.tours.shop_buy_product", login="demo", inject=inject) def test_04_public_checkout(self): - self.phantom_js("/", "openerp.website.Tour.run('shop_buy_product', 'test')", "openerp.website.Tour.tours.shop_buy_product", inject=inject) + self.phantom_js("/", "openerp.Tour.run('shop_buy_product', 'test')", "openerp.Tour.tours.shop_buy_product", inject=inject) From 1c8e3c315686646d62e05cf11c7cac01a7ce7d0f Mon Sep 17 00:00:00 2001 From: Christophe Matthieu Date: Wed, 18 Jun 2014 12:25:53 +0200 Subject: [PATCH 02/10] [IMP] Tour: add tour in web module; tour became available in backend. --- addons/web/static/src/js/tour.js | 540 +++++++++++++++++++++ addons/web/static/src/xml/website.tour.xml | 27 ++ 2 files changed, 567 insertions(+) create mode 100644 addons/web/static/src/js/tour.js create mode 100644 addons/web/static/src/xml/website.tour.xml diff --git a/addons/web/static/src/js/tour.js b/addons/web/static/src/js/tour.js new file mode 100644 index 00000000000..634fa5c9421 --- /dev/null +++ b/addons/web/static/src/js/tour.js @@ -0,0 +1,540 @@ +(function () { + 'use strict'; + +// raise an error in test mode if openerp don't exist +if (typeof openerp === "undefined") { + var error = "openerp is undefined" + + "\nhref: " + window.location.href + + "\nreferrer: " + document.referrer + + "\nlocalStorage: " + window.localStorage.getItem("tour"); + if (typeof $ !== "undefined") { + error += '\n\n' + $("body").html(); + } + throw new Error(error); +} + +var website = openerp.website; + +// don't rewrite T in test mode +if (typeof openerp.Tour !== "undefined") { + return; +} + +///////////////////////////////////////////////// + + +/* jQuery selector to match exact text inside an element + * :containsExact() - case insensitive + * :containsExactCase() - case sensitive + * :containsRegex() - set by user ( use: $(el).find(':containsRegex(/(red|blue|yellow)/gi)') ) + */ +$.extend($.expr[':'],{ + containsExact: function(a,i,m){ + return $.trim(a.innerHTML.toLowerCase()) === m[3].toLowerCase(); + }, + containsExactCase: function(a,i,m){ + return $.trim(a.innerHTML) === m[3]; + }, + // Note all escaped characters need to be double escaped + // inside of the containsRegex, so "\(" needs to be "\\(" + containsRegex: function(a,i,m){ + var regreg = /^\/((?:\\\/|[^\/])+)\/([mig]{0,3})$/, + reg = regreg.exec(m[3]); + return reg ? new RegExp(reg[1], reg[2]).test($.trim(a.innerHTML)) : false; + } +}); +$.ajaxSetup({ + beforeSend:function(){ + $.ajaxBusy = ($.ajaxBusy|0) + 1; + }, + complete:function(){ + $.ajaxBusy--; + } +}); + +///////////////////////////////////////////////// + +var localStorage = window.localStorage; + +var Tour = { + tours: {}, + defaultDelay: 50, + retryRunningDelay: 1000, + errorDelay: 5000, + state: null, + $element: null, + timer: null, + testtimer: null, + currentTimer: null, + register: function (tour) { + if (tour.mode !== "test") tour.mode = "tutorial"; + Tour.tours[tour.id] = tour; + }, + run: function (tour_id, mode) { + var tour = Tour.tours[tour_id]; + if (!tour) { + throw new Error("Can't run '"+tour_id+"' (tour undefined)"); + } + this.time = new Date().getTime(); + if (tour.path && !window.location.href.match(new RegExp("("+Tour.getLang()+")?"+tour.path+"#?$", "i"))) { + var href = "/"+Tour.getLang()+tour.path; + console.log("Tour Begin from run method (redirection to "+href+")"); + Tour.saveState(tour.id, mode || tour.mode, -1, 0); + window.location.href = href; + } else { + console.log("Tour Begin from run method"); + Tour.saveState(tour.id, mode || tour.mode, 0, 0); + Tour.running(); + } + }, + registerSteps: function (tour) { + if (tour.register) { + return; + } + tour.register = true; + + for (var index=0, len=tour.steps.length; index 0 && tour.steps[index-1] && + tour.steps[index-1].popover && tour.steps[index-1].popover.next) { + step.waitNot = '.popover.tour.fade.in:visible'; + } + if (!step.waitFor && index > 0 && tour.steps[index-1].snippet) { + step.waitFor = '.oe_overlay_options .oe_options:visible'; + } + + + var snippet = step.element && step.element.match(/#oe_snippets (.*) \.oe_snippet_thumbnail/); + if (snippet) { + step.snippet = snippet[1]; + } else if (step.snippet) { + step.element = '#oe_snippets '+step.snippet+' .oe_snippet_thumbnail'; + } + + if (!step.element) { + step.element = "body"; + step.orphan = true; + step.backdrop = true; + } + } + if (tour.steps[index-1] && + tour.steps[index-1].popover && tour.steps[index-1].popover.next) { + var step = { + _title: "", + id: index, + waitNot: '.popover.tour.fade.in:visible' + }; + tour.steps.push(step); + } + + // rendering bootstrap tour and popover + if (tour.mode !== "test") { + for (var index=0, len=tour.steps.length; index'); + } + + if (step.backdrop || $element.parents("#website-top-navbar, .oe_navbar, .modal").size()) { + $tip.css("z-index", 2010); + } + + // button click event + $tip.find("button") + .one("click", function () { + step.busy = true; + if (!$(this).is("[data-role='next']")) { + clearTimeout(Tour.timer); + Tour.endTour(); + } + Tour.closePopover(); + }); + + Tour.repositionPopover(); + }, + repositionPopover: function() { + var popover = Tour.$element.data("bs.popover"); + var $tip = Tour.$element.data("bs.popover").tip(); + + if (popover.options.orphan) { + return $tip.css("top", $(window).outerHeight() / 2 - $tip.outerHeight() / 2); + } + + var offsetBottom, offsetHeight, offsetRight, offsetWidth, originalLeft, originalTop, tipOffset; + offsetWidth = $tip[0].offsetWidth; + offsetHeight = $tip[0].offsetHeight; + tipOffset = $tip.offset(); + originalLeft = tipOffset.left; + originalTop = tipOffset.top; + offsetBottom = $(document).outerHeight() - tipOffset.top - $tip.outerHeight(); + if (offsetBottom < 0) { + tipOffset.top = tipOffset.top + offsetBottom; + } + offsetRight = $("html").outerWidth() - tipOffset.left - $tip.outerWidth(); + if (offsetRight < 0) { + tipOffset.left = tipOffset.left + offsetRight; + } + if (tipOffset.top < 0) { + tipOffset.top = 0; + } + if (tipOffset.left < 0) { + tipOffset.left = 0; + } + $tip.offset(tipOffset); + if (popover.options.placement === "bottom" || popover.options.placement === "top") { + var left = Tour.$element.offset().left + Tour.$element.outerWidth()/2 - tipOffset.left; + $tip.find(".arrow").css("left", left ? left + "px" : ""); + } else if (popover.options.placement !== "auto") { + var top = Tour.$element.offset().top + Tour.$element.outerHeight()/2 - tipOffset.top; + $tip.find(".arrow").css("top", top ? top + "px" : ""); + } + }, + _load_template: false, + load_template: function () { + // don't need template to use bootstrap Tour in automatic mode + Tour._load_template = true; + if (typeof QWeb2 === "undefined") return $.when(); + var def = $.Deferred(); + openerp.qweb.add_template('/web/static/src/xml/website.tour.xml', function(err) { + if (err) { + def.reject(err); + } else { + def.resolve(); + } + }); + return def; + }, + popoverTitle: function (tour, options) { + return typeof QWeb2 !== "undefined" ? openerp.qweb.render('tour.popover_title', options) : options.title; + }, + popover: function (options) { + return typeof QWeb2 !== "undefined" ? openerp.qweb.render('tour.popover', options) : options.title; + }, + getLang: function () { + return $("html").attr("lang").replace(/-/, '_'); + }, + getState: function () { + var state = JSON.parse(localStorage.getItem("tour") || 'false') || {}; + if (state) { this.time = state.time; } + var tour_id,mode,step_id; + if (!state.id && window.location.href.indexOf("#tutorial.") > -1) { + state = { + "id": window.location.href.match(/#tutorial\.(.*)=true/)[1], + "mode": "tutorial", + "step_id": 0 + }; + window.location.hash = ""; + console.log("Tour Begin from url hash"); + Tour.saveState(state.id, state.mode, state.step_id, 0); + } + if (!state.id) { + return; + } + state.tour = Tour.tours[state.id]; + state.step = state.tour && state.tour.steps[state.step_id === -1 ? 0 : state.step_id]; + return state; + }, + error: function (step, message) { + var state = Tour.getState(); + message += '\n tour: ' + state.id + + '\n step: ' + step.id + ": '" + (step._title || step.title) + "'" + + '\n href: ' + window.location.href + + '\n referrer: ' + document.referrer + + '\n element: ' + Boolean(!step.element || ($(step.element).size() && $(step.element).is(":visible") && !$(step.element).is(":hidden"))) + + '\n waitNot: ' + Boolean(!step.waitNot || !$(step.waitNot).size()) + + '\n waitFor: ' + Boolean(!step.waitFor || $(step.waitFor).size()) + + "\n localStorage: " + JSON.stringify(localStorage) + + '\n\n' + $("body").html(); + Tour.reset(); + throw new Error(message); + }, + lists: function () { + var tour_ids = []; + for (var k in Tour.tours) { + tour_ids.push(k); + } + return tour_ids; + }, + saveState: function (tour_id, mode, step_id, number, wait) { + localStorage.setItem("tour", JSON.stringify({ + "id":tour_id, + "mode":mode, + "step_id":step_id || 0, + "time": this.time, + "number": number+1, + "wait": wait || 0 + })); + }, + reset: function () { + var state = Tour.getState(); + if (state) { + for (var k in state.tour.steps) { + state.tour.steps[k].busy = false; + } + } + localStorage.removeItem("tour"); + clearTimeout(Tour.timer); + clearTimeout(Tour.testtimer); + Tour.closePopover(); + }, + running: function () { + function run () { + var state = Tour.getState(); + if (!state) return; + if (!Tour._load_template) { + Tour.load_template().then(Tour.running); + } + else if (state.tour) { + console.log("Tour '"+state.id+"' is running"); + Tour.registerSteps(state.tour); + Tour.nextStep(); + } else { + if (state.wait > 10) { + Tour.error(step, "Tour '"+state.id+"' undefined"); + } + Tour.saveState(state.id, state.mode, step.id, state.number, state.wait+1); + console.log("Tour '"+state.id+"' wait for running (tour undefined)"); + setTimeout(Tour.running, state.mode === "test" ? Tour.defaultDelay : Tour.retryRunningDelay); + } + } + setTimeout(function () { + if ($.ajaxBusy) { + $(document).ajaxStop(run); + } else { + run(); + } + },0); + }, + check: function (step) { + return (step && + (!step.element || ($(step.element).size() && $(step.element).is(":visible") && !$(step.element).is(":hidden"))) && + (!step.waitNot || !$(step.waitNot).size()) && + (!step.waitFor || $(step.waitFor).size())); + }, + waitNextStep: function () { + var state = Tour.getState(); + var time = new Date().getTime(); + var timer; + var next = state.tour.steps[state.step.id+1]; + var overlaps = state.mode === "test" ? Tour.errorDelay : 0; + + window.onbeforeunload = function () { + clearTimeout(Tour.timer); + clearTimeout(Tour.testtimer); + }; + + function checkNext () { + Tour.autoTogglePopover(); + + clearTimeout(Tour.timer); + if (Tour.check(next)) { + clearTimeout(Tour.currentTimer); + // use an other timeout for cke dom loading + Tour.saveState(state.id, state.mode, state.step.id, 0); + setTimeout(function () { + Tour.nextStep(next); + }, Tour.defaultDelay); + } else if (!overlaps || new Date().getTime() - time < overlaps) { + Tour.timer = setTimeout(checkNext, Tour.defaultDelay); + } else { + Tour.error(next, "Can't reach the next step"); + } + } + checkNext(); + }, + nextStep: function (step) { + var state = Tour.getState(); + + if (!state) { + return; + } + + step = step || state.step; + var next = state.tour.steps[step.id+1]; + + if (state.number > 3) { + Tour.error(next, "Cycling. Can't reach the next step"); + } + + Tour.saveState(state.id, state.mode, step.id, state.number); + + if (step.id !== state.step_id) { + console.log("Tour Step: '" + (step._title || step.title) + "' (" + (new Date().getTime() - this.time) + "ms)"); + } + + Tour.autoTogglePopover(true); + + if (step.onload) { + step.onload(); + } + + if (next) { + setTimeout(function () { + Tour.waitNextStep(); + if (state.mode === "test") { + setTimeout(function(){ + Tour.autoNextStep(state.tour, step); + }, Tour.defaultDelay); + } + }, next.wait || 0); + } else { + Tour.endTour(); + } + }, + endTour: function () { + var state = Tour.getState(); + var test = state.step.id >= state.tour.steps.length-1; + Tour.reset(); + if (test) { + console.log('ok'); + } else { + console.log('error'); + } + }, + autoNextStep: function (tour, step) { + clearTimeout(Tour.testtimer); + + function autoStep () { + if (!step) return; + + if (step.autoComplete) { + step.autoComplete(tour); + } + + $(".popover.tour [data-role='next']").click(); + + var $element = $(step.element); + if (!$element.size()) return; + + if (step.snippet) { + + Tour.autoDragAndDropSnippet($element); + + } else if ($element.is(":visible")) { + + $element.trigger($.Event("mouseenter", { srcElement: $element[0] })); + $element.trigger($.Event("mousedown", { srcElement: $element[0] })); + + var evt = document.createEvent("MouseEvents"); + evt.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); + $element[0].dispatchEvent(evt); + + // trigger after for step like: mouseenter, next step click on button display with mouseenter + setTimeout(function () { + $element.trigger($.Event("mouseup", { srcElement: $element[0] })); + $element.trigger($.Event("mouseleave", { srcElement: $element[0] })); + }, 1000); + } + if (step.sampleText) { + + $element.trigger($.Event("keydown", { srcElement: $element })); + if ($element.is("input") ) { + $element.val(step.sampleText); + } if ($element.is("select")) { + $element.find("[value='"+step.sampleText+"'], option:contains('"+step.sampleText+"')").attr("selected", true); + $element.val(step.sampleText); + } else { + $element.html(step.sampleText); + } + setTimeout(function () { + $element.trigger($.Event("keyup", { srcElement: $element })); + $element.trigger($.Event("change", { srcElement: $element })); + }, self.defaultDelay<<1); + + } + } + Tour.testtimer = setTimeout(autoStep, 100); + }, + autoDragAndDropSnippet: function (selector) { + var $thumbnail = $(selector).first(); + var thumbnailPosition = $thumbnail.position(); + $thumbnail.trigger($.Event("mousedown", { which: 1, pageX: thumbnailPosition.left, pageY: thumbnailPosition.top })); + $thumbnail.trigger($.Event("mousemove", { which: 1, pageX: document.body.scrollWidth/2, pageY: document.body.scrollHeight/2 })); + var $dropZone = $(".oe_drop_zone").first(); + var dropPosition = $dropZone.position(); + $dropZone.trigger($.Event("mouseup", { which: 1, pageX: dropPosition.left, pageY: dropPosition.top })); + } +}; +openerp.Tour = Tour; + +///////////////////////////////////////////////// + +$(document).ready(Tour.running); + +}()); diff --git a/addons/web/static/src/xml/website.tour.xml b/addons/web/static/src/xml/website.tour.xml new file mode 100644 index 00000000000..d3d3fb8c57a --- /dev/null +++ b/addons/web/static/src/xml/website.tour.xml @@ -0,0 +1,27 @@ + + + +
    +
    +

    +
    + + + +
    +
    + + + +
    From fd0e0323ba55388a2c21e1d0f349339f0df96ecd Mon Sep 17 00:00:00 2001 From: Christophe Matthieu Date: Wed, 18 Jun 2014 13:11:17 +0200 Subject: [PATCH 03/10] [FIX] Tour: error log fail; template of modal --- addons/web/static/src/js/tour.js | 19 +++++++++++-------- addons/web/static/src/xml/website.tour.xml | 2 +- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/addons/web/static/src/js/tour.js b/addons/web/static/src/js/tour.js index 634fa5c9421..7e4eabeedda 100644 --- a/addons/web/static/src/js/tour.js +++ b/addons/web/static/src/js/tour.js @@ -73,7 +73,7 @@ var Tour = { run: function (tour_id, mode) { var tour = Tour.tours[tour_id]; if (!tour) { - throw new Error("Can't run '"+tour_id+"' (tour undefined)"); + Tour.error(null, "Can't run '"+tour_id+"' (tour undefined)"); } this.time = new Date().getTime(); if (tour.path && !window.location.href.match(new RegExp("("+Tour.getLang()+")?"+tour.path+"#?$", "i"))) { @@ -313,12 +313,12 @@ var Tour = { error: function (step, message) { var state = Tour.getState(); message += '\n tour: ' + state.id - + '\n step: ' + step.id + ": '" + (step._title || step.title) + "'" + + (step ? '\n step: ' + step.id + ": '" + (step._title || step.title) + "'" : '' ) + '\n href: ' + window.location.href + '\n referrer: ' + document.referrer - + '\n element: ' + Boolean(!step.element || ($(step.element).size() && $(step.element).is(":visible") && !$(step.element).is(":hidden"))) - + '\n waitNot: ' + Boolean(!step.waitNot || !$(step.waitNot).size()) - + '\n waitFor: ' + Boolean(!step.waitFor || $(step.waitFor).size()) + + (step ? '\n element: ' + Boolean(!step.element || ($(step.element).size() && $(step.element).is(":visible") && !$(step.element).is(":hidden"))) : '' ) + + (step ? '\n waitNot: ' + Boolean(!step.waitNot || !$(step.waitNot).size()) : '' ) + + (step ? '\n waitFor: ' + Boolean(!step.waitFor || $(step.waitFor).size()) : '' ) + "\n localStorage: " + JSON.stringify(localStorage) + '\n\n' + $("body").html(); Tour.reset(); @@ -343,7 +343,7 @@ var Tour = { }, reset: function () { var state = Tour.getState(); - if (state) { + if (state && state.tour) { for (var k in state.tour.steps) { state.tour.steps[k].busy = false; } @@ -366,9 +366,9 @@ var Tour = { Tour.nextStep(); } else { if (state.wait > 10) { - Tour.error(step, "Tour '"+state.id+"' undefined"); + Tour.error(state.step, "Tour '"+state.id+"' undefined"); } - Tour.saveState(state.id, state.mode, step.id, state.number, state.wait+1); + Tour.saveState(state.id, state.mode, state.step_id, state.number, state.wait+1); console.log("Tour '"+state.id+"' wait for running (tour undefined)"); setTimeout(Tour.running, state.mode === "test" ? Tour.defaultDelay : Tour.retryRunningDelay); } @@ -454,6 +454,9 @@ var Tour = { } }, next.wait || 0); } else { + setTimeout(function(){ + Tour.autoNextStep(state.tour, step); + }, Tour.defaultDelay); Tour.endTour(); } }, diff --git a/addons/web/static/src/xml/website.tour.xml b/addons/web/static/src/xml/website.tour.xml index d3d3fb8c57a..cae823bd04e 100644 --- a/addons/web/static/src/xml/website.tour.xml +++ b/addons/web/static/src/xml/website.tour.xml @@ -2,7 +2,7 @@
    -
    +

    From ffe85901831f3e15b439362c4ca6ad0095eecfa3 Mon Sep 17 00:00:00 2001 From: Christophe Matthieu Date: Wed, 18 Jun 2014 15:12:52 +0200 Subject: [PATCH 04/10] [FIX] tour: check if the tour is already finish before check next step --- addons/web/static/src/js/tour.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/addons/web/static/src/js/tour.js b/addons/web/static/src/js/tour.js index 7e4eabeedda..2289162732e 100644 --- a/addons/web/static/src/js/tour.js +++ b/addons/web/static/src/js/tour.js @@ -446,12 +446,14 @@ var Tour = { if (next) { setTimeout(function () { + if (Tour.getState()) { Tour.waitNextStep(); - if (state.mode === "test") { - setTimeout(function(){ - Tour.autoNextStep(state.tour, step); - }, Tour.defaultDelay); - } + } + if (state.mode === "test") { + setTimeout(function(){ + Tour.autoNextStep(state.tour, step); + }, Tour.defaultDelay); + } }, next.wait || 0); } else { setTimeout(function(){ From 9f36a77b7b3832f84d1c181e114f89a5b47554ce Mon Sep 17 00:00:00 2001 From: Christophe Matthieu Date: Wed, 18 Jun 2014 15:27:20 +0200 Subject: [PATCH 05/10] [FIX] website: wrong cycling error message --- addons/web/static/src/js/tour.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/web/static/src/js/tour.js b/addons/web/static/src/js/tour.js index 2289162732e..4705fb80af8 100644 --- a/addons/web/static/src/js/tour.js +++ b/addons/web/static/src/js/tour.js @@ -368,7 +368,7 @@ var Tour = { if (state.wait > 10) { Tour.error(state.step, "Tour '"+state.id+"' undefined"); } - Tour.saveState(state.id, state.mode, state.step_id, state.number, state.wait+1); + Tour.saveState(state.id, state.mode, state.step_id, state.number-1, state.wait+1); console.log("Tour '"+state.id+"' wait for running (tour undefined)"); setTimeout(Tour.running, state.mode === "test" ? Tour.defaultDelay : Tour.retryRunningDelay); } From 14e29ce321afa6967892c1a93f39c6caab5081d7 Mon Sep 17 00:00:00 2001 From: Christophe Matthieu Date: Thu, 19 Jun 2014 12:19:30 +0200 Subject: [PATCH 06/10] [FIX] tour.js: clear localStorage before testing; add a better log title --- addons/web/static/src/js/tour.js | 4 ++-- addons/web/views/webclient_templates.xml | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/addons/web/static/src/js/tour.js b/addons/web/static/src/js/tour.js index 4705fb80af8..14908e56f5e 100644 --- a/addons/web/static/src/js/tour.js +++ b/addons/web/static/src/js/tour.js @@ -122,7 +122,7 @@ var Tour = { if (tour.steps[index-1] && tour.steps[index-1].popover && tour.steps[index-1].popover.next) { var step = { - _title: "", + _title: "close popover and finish", id: index, waitNot: '.popover.tour.fade.in:visible' }; @@ -365,7 +365,7 @@ var Tour = { Tour.registerSteps(state.tour); Tour.nextStep(); } else { - if (state.wait > 10) { + if (state.wait >= 10) { Tour.error(state.step, "Tour '"+state.id+"' undefined"); } Tour.saveState(state.id, state.mode, state.step_id, state.number-1, state.wait+1); diff --git a/addons/web/views/webclient_templates.xml b/addons/web/views/webclient_templates.xml index 7dc160995e4..08fc3be483d 100644 --- a/addons/web/views/webclient_templates.xml +++ b/addons/web/views/webclient_templates.xml @@ -247,6 +247,7 @@