[MERGE] from trunk

bzr revid: fva@openerp.com-20140428152938-ib1yl6f7lgat51vm
This commit is contained in:
Frédéric van der Essen 2014-04-28 17:29:38 +02:00
commit 1aaed44874
31 changed files with 949 additions and 1605 deletions

View File

@ -642,7 +642,7 @@ class account_move_line(osv.osv):
(_check_date, 'The date of your Journal Entry is not in the defined period! You should change the date or remove this constraint from the journal.', ['date']), (_check_date, 'The date of your Journal Entry is not in the defined period! You should change the date or remove this constraint from the journal.', ['date']),
(_check_currency, 'The selected account of your Journal Entry forces to provide a secondary currency. You should remove the secondary currency on the account or select a multi-currency view on the journal.', ['currency_id']), (_check_currency, 'The selected account of your Journal Entry forces to provide a secondary currency. You should remove the secondary currency on the account or select a multi-currency view on the journal.', ['currency_id']),
(_check_currency_and_amount, "You cannot create journal items with a secondary currency without recording both 'currency' and 'amount currency' field.", ['currency_id','amount_currency']), (_check_currency_and_amount, "You cannot create journal items with a secondary currency without recording both 'currency' and 'amount currency' field.", ['currency_id','amount_currency']),
(_check_currency_amount, 'The amount expressed in the secondary currency must be positive when the journal item is a debit and negative when if it is a credit.', ['amount_currency']), (_check_currency_amount, 'The amount expressed in the secondary currency must be positive when account is debited and negative when account is credited.', ['amount_currency']),
(_check_currency_company, "You cannot provide a secondary currency if it is the same than the company one." , ['currency_id']), (_check_currency_company, "You cannot provide a secondary currency if it is the same than the company one." , ['currency_id']),
] ]

View File

@ -384,6 +384,7 @@
<group expand="0" string="Group By..."> <group expand="0" string="Group By...">
<filter string="User" context="{'group_by':'user_id'}" icon="terp-personal"/> <filter string="User" context="{'group_by':'user_id'}" icon="terp-personal"/>
<filter string="Type" context="{'group_by':'type'}" icon="terp-stock_symbol-selection"/> <filter string="Type" context="{'group_by':'type'}" icon="terp-stock_symbol-selection"/>
<filter string="Company" context="{'group_by':'company_id'}" icon="terp-go-home" groups="base.group_multi_company"/>
</group> </group>
</search> </search>
</field> </field>
@ -881,6 +882,7 @@
<field name="price_include"/> <field name="price_include"/>
<field name="description"/> <field name="description"/>
<field name="company_id" widget="selection" groups="base.group_multi_company"/> <field name="company_id" widget="selection" groups="base.group_multi_company"/>
<field name="type_tax_use" invisible="1"/>
</tree> </tree>
</field> </field>
</record> </record>
@ -891,6 +893,12 @@
<search string="Search Taxes"> <search string="Search Taxes">
<field name="name" filter_domain="['|', ('name','ilike',self), ('description','ilike',self)]" string="Tax"/> <field name="name" filter_domain="['|', ('name','ilike',self), ('description','ilike',self)]" string="Tax"/>
<field name="company_id" groups="base.group_multi_company"/> <field name="company_id" groups="base.group_multi_company"/>
<filter string="Sale" domain="[('type_tax_use','=','sale')]" />
<filter string="Purchase" domain="[('type_tax_use','=','purchase')]" />
<group string="Group By...">
<filter string="Company" domain="[]" context="{'group_by':'company_id'}"/>
<filter string="Tax Application" domain="[]" context="{'group_by':'type_tax_use'}"/>
</group>
</search> </search>
</field> </field>
</record> </record>

View File

@ -97,7 +97,7 @@
<field name="inherit_id" ref="base.view_partner_form"/> <field name="inherit_id" ref="base.view_partner_form"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<page name="sales_purchases" position="after" version="7.0"> <page name="sales_purchases" position="after" version="7.0">
<page string="Accounting" col="4" name="accounting" attrs="{'invisible': [('is_company','=',False),('parent_id','!=',False)]}"> <page string="Accounting" col="4" name="accounting" attrs="{'invisible': [('is_company','=',False),('parent_id','!=',False)]}" groups="account.group_account_invoice">
<group> <group>
<group> <group>
<field name="property_account_position" widget="selection"/> <field name="property_account_position" widget="selection"/>
@ -127,7 +127,7 @@
</tree> </tree>
</field> </field>
</page> </page>
<page string="Accounting" name="accounting_disabled" attrs="{'invisible': ['|',('is_company','=',True),('parent_id','=',False)]}"> <page string="Accounting" name="accounting_disabled" attrs="{'invisible': ['|',('is_company','=',True),('parent_id','=',False)]}" groups="account.group_account_invoice">
<div> <div>
<p>Accounting-related settings are managed on <button name="open_commercial_entity" type="object" string="the parent company" class="oe_link"/></p> <p>Accounting-related settings are managed on <button name="open_commercial_entity" type="object" string="the parent company" class="oe_link"/></p>
</div> </div>

View File

@ -259,6 +259,12 @@ class account_config_settings(osv.osv_memory):
def onchange_tax_rate(self, cr, uid, ids, rate, context=None): def onchange_tax_rate(self, cr, uid, ids, rate, context=None):
return {'value': {'purchase_tax_rate': rate or False}} return {'value': {'purchase_tax_rate': rate or False}}
def onchange_multi_currency(self, cr, uid, ids, group_multi_currency, context=None):
res = {}
if not group_multi_currency:
res['value'] = {'income_currency_exchange_account_id': False, 'expense_currency_exchange_account_id': False}
return res
def onchange_start_date(self, cr, uid, id, start_date): def onchange_start_date(self, cr, uid, id, start_date):
if start_date: if start_date:
start_date = datetime.datetime.strptime(start_date, "%Y-%m-%d") start_date = datetime.datetime.strptime(start_date, "%Y-%m-%d")

View File

@ -122,7 +122,7 @@
<label for="id" string="Features"/> <label for="id" string="Features"/>
<div> <div>
<div name="group_multi_currency"> <div name="group_multi_currency">
<field name="group_multi_currency" class="oe_inline"/> <field name="group_multi_currency" class="oe_inline" on_change="onchange_multi_currency(group_multi_currency)"/>
<label for="group_multi_currency"/> <label for="group_multi_currency"/>
</div> </div>
<div> <div>

View File

@ -189,7 +189,7 @@ class account_voucher(osv.osv):
if not ids: if not ids:
return [] return []
if context is None: context = {} if context is None: context = {}
return [(r['id'], (str("%.2f" % r['amount']) or '')) for r in self.read(cr, uid, ids, ['amount'], context, load='_classic_write')] return [(r['id'], (r['number'] or _('Voucher'))) for r in self.read(cr, uid, ids, ['number'], context, load='_classic_write')]
def fields_view_get(self, cr, uid, view_id=None, view_type=False, context=None, toolbar=False, submenu=False): def fields_view_get(self, cr, uid, view_id=None, view_type=False, context=None, toolbar=False, submenu=False):
mod_obj = self.pool.get('ir.model.data') mod_obj = self.pool.get('ir.model.data')

View File

@ -259,7 +259,7 @@
<!-- Short thread: Admin ask, Agrolait answer [DEMO: mark thread as done] --> <!-- Short thread: Admin ask, Agrolait answer [DEMO: mark thread as done] -->
<record id="msg_discus1" model="mail.message"> <record id="msg_discus1" model="mail.message">
<field name="subject">Feedback about our On Site Assistance</field> <field name="subject">Feedback about our On Site Assistance</field>
<field name="body"><![CDATA[<p>Hi Virginie,</p><p>I writing to you about our <i>On Site Assistance Service</i> that we delivered to Agrolait last week. Do you have any feedback or remark about our service? I noticed you requested new IP phones. Will it be used for new employees, or did you have any issue with the ones we provided?<br />Best regards,</p>]]></field> <field name="body"><![CDATA[<p>Hi Virginie,</p><p>I wrote to you about our <i>On Site Assistance Service</i> that we delivered to Agrolait last week. Do you have any feedback or remark about our service? I noticed you requested new IP phones. Will it be used for new employees, or did you have any issue with the ones we provided?<br />Best regards,</p>]]></field>
<field name="type">comment</field> <field name="type">comment</field>
<field name="subtype_id" ref="mt_comment"/> <field name="subtype_id" ref="mt_comment"/>
<field name="author_id" ref="base.partner_root"/> <field name="author_id" ref="base.partner_root"/>

View File

@ -49,17 +49,11 @@
<field name="name">Members Analysis</field> <field name="name">Members Analysis</field>
<field name="res_model">report.membership</field> <field name="res_model">report.membership</field>
<field name="view_type">form</field> <field name="view_type">form</field>
<field name="view_mode">graph</field>
<field name="search_view_id" ref="view_report_membership_search"/> <field name="search_view_id" ref="view_report_membership_search"/>
<field name="context">{"search_default_year":1,"search_default_member":1, 'search_default_Revenue':1, 'search_default_this_month':1, 'search_default_salesman':1,'group_by_no_leaf':1}</field> <field name="context">{"search_default_year":1,"search_default_member":1, 'search_default_Revenue':1, 'search_default_this_month':1, 'search_default_salesman':1,'group_by_no_leaf':1}</field>
</record> </record>
<record model="ir.actions.act_window.view" id="action_report_membership_tree_view2">
<field name="sequence" eval="3"/>
<field name="view_mode">graph</field>
<field name="view_id" ref="view_report_membership_graph1"/>
<field name="act_window_id" ref="action_report_membership_tree"/>
</record>
<menuitem name="Members Analysis" parent="base.menu_report_association" <menuitem name="Members Analysis" parent="base.menu_report_association"
action="action_report_membership_tree" action="action_report_membership_tree"
id="menu_report_membership" id="menu_report_membership"

View File

@ -9,20 +9,6 @@
<!-- Report for Users' Timesheet and Task Hours per Month --> <!-- Report for Users' Timesheet and Task Hours per Month -->
<record id="view_report_timesheet_task_user_tree" model="ir.ui.view">
<field name="name">report.timesheet.task.user.tree</field>
<field name="model">report.timesheet.task.user</field>
<field name="arch" type="xml">
<tree string="Timesheet/Task hours Report Per Month" >
<field name="name"/>
<field name="year" invisible="1"/>
<field name="month" invisible="1"/>
<field name="user_id"/>
<field name="timesheet_hrs" widget="float_time" />
<field name="task_hrs" widget="float_time"/>
</tree>
</field>
</record>
<record id="view_report_timesheet_task_user_search" model="ir.ui.view"> <record id="view_report_timesheet_task_user_search" model="ir.ui.view">
<field name="name">report.timesheet.task.user.search</field> <field name="name">report.timesheet.task.user.search</field>
<field name="model">report.timesheet.task.user</field> <field name="model">report.timesheet.task.user</field>
@ -55,7 +41,7 @@
<field name="name">Task Hours Per Month</field> <field name="name">Task Hours Per Month</field>
<field name="res_model">report.timesheet.task.user</field> <field name="res_model">report.timesheet.task.user</field>
<field name="view_type">form</field> <field name="view_type">form</field>
<field name="view_mode">tree,graph</field> <field name="view_mode">graph</field>
<field name="context">{'search_default_year':1,'search_default_month':1, 'search_default_group_user_id':1}</field> <field name="context">{'search_default_year':1,'search_default_month':1, 'search_default_group_user_id':1}</field>
</record> </record>
<menuitem id="menu_timesheet_task_user" parent="hr.menu_hr_reporting_timesheet" <menuitem id="menu_timesheet_task_user" parent="hr.menu_hr_reporting_timesheet"

View File

@ -2506,7 +2506,7 @@ class stock_move(osv.osv):
source_location = move.location_dest_id source_location = move.location_dest_id
if source_location.usage != 'internal': if source_location.usage != 'internal':
#restrict to scrap from a virtual location because it's meaningless and it may introduce errors in stock ('creating' new products from nowhere) #restrict to scrap from a virtual location because it's meaningless and it may introduce errors in stock ('creating' new products from nowhere)
raise osv.except_osv(_('Error!'), _('Forbidden operation: it is not allowed to scrap products from a virtual location.')) raise osv.except_osv(_('Error!'), _('Operation Forbidden! it is not allowed to scrap products from a virtual location: %s' %(move.location_id.complete_name)))
move_qty = move.product_qty move_qty = move.product_qty
uos_qty = quantity / move_qty * move.product_uos_qty uos_qty = quantity / move_qty * move.product_uos_qty
default_val = { default_val = {

View File

@ -1,43 +0,0 @@
.tour-backdrop {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1009;
background-color: #000;
opacity: 0.8;
}
.tour-step-backdrop {
position: relative;
z-index: 1011;
}
.tour-step-background {
position: absolute;
z-index: 1010;
background: #fff;
border-radius: 6px;
}
.popover[class*="tour-"] .popover-navigation {
padding: 9px 14px;
}
.popover[class*="tour-"] .popover-navigation *[data-role=end] {
float: right;
}
.popover[class*="tour-"] .popover-navigation *[data-role=prev],
.popover[class*="tour-"] .popover-navigation *[data-role=next],
.popover[class*="tour-"] .popover-navigation *[data-role=end] {
cursor: pointer;
}
.popover[class*="tour-"] .popover-navigation *[data-role=prev].disabled,
.popover[class*="tour-"] .popover-navigation *[data-role=next].disabled,
.popover[class*="tour-"] .popover-navigation *[data-role=end].disabled {
cursor: default;
}
.popover[class*="tour-"].orphan {
position: fixed;
margin-top: 0;
}
.popover[class*="tour-"].orphan .arrow {
display: none;
}

View File

@ -1,559 +0,0 @@
/* ===========================================================
# bootstrap-tour - v0.6.1
# http://bootstraptour.com
# ==============================================================
# Copyright 2012-2013 Ulrich Sossou
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
*/
(function() {
(function($, window) {
var Tour, document;
document = window.document;
Tour = (function() {
function Tour(options) {
this._options = $.extend({
name: "tour",
container: "body",
keyboard: true,
storage: window.localStorage,
debug: false,
backdrop: false,
redirect: true,
orphan: false,
basePath: "",
template: "<div class='popover'> <div class='arrow'></div> <h3 class='popover-title'></h3> <div class='popover-content'></div> <nav class='popover-navigation'> <div class='btn-group'> <button class='btn btn-sm btn-default' data-role='prev'>&laquo; Prev</button> <button class='btn btn-sm btn-default' data-role='next'>Next &raquo;</button> </div> <button class='btn btn-sm btn-default' data-role='end'>End tour</button> </nav> </div>",
afterSetState: function(key, value) {},
afterGetState: function(key, value) {},
afterRemoveState: function(key) {},
onStart: function(tour) {},
onEnd: function(tour) {},
onShow: function(tour) {},
onShown: function(tour) {},
onHide: function(tour) {},
onHidden: function(tour) {},
onNext: function(tour) {},
onPrev: function(tour) {}
}, options);
this._steps = [];
this.setCurrentStep();
this.backdrop = {
overlay: null,
$element: null,
$background: null
};
}
Tour.prototype.setState = function(key, value) {
var keyName;
if (this._options.storage) {
keyName = "" + this._options.name + "_" + key;
this._options.storage.setItem(keyName, value);
return this._options.afterSetState(keyName, value);
} else {
if (this._state == null) {
this._state = {};
}
return this._state[key] = value;
}
};
Tour.prototype.removeState = function(key) {
var keyName;
if (this._options.storage) {
keyName = "" + this._options.name + "_" + key;
this._options.storage.removeItem(keyName);
return this._options.afterRemoveState(keyName);
} else {
if (this._state != null) {
return delete this._state[key];
}
}
};
Tour.prototype.getState = function(key) {
var keyName, value;
if (this._options.storage) {
keyName = "" + this._options.name + "_" + key;
value = this._options.storage.getItem(keyName);
} else {
if (this._state != null) {
value = this._state[key];
}
}
if (value === void 0 || value === "null") {
value = null;
}
this._options.afterGetState(key, value);
return value;
};
Tour.prototype.addSteps = function(steps) {
var step, _i, _len, _results;
_results = [];
for (_i = 0, _len = steps.length; _i < _len; _i++) {
step = steps[_i];
_results.push(this.addStep(step));
}
return _results;
};
Tour.prototype.addStep = function(step) {
return this._steps.push(step);
};
Tour.prototype.getStep = function(i) {
if (this._steps[i] != null) {
return $.extend({
id: "step-" + i,
path: "",
placement: "right",
title: "",
content: "<p></p>",
next: i === this._steps.length - 1 ? -1 : i + 1,
prev: i - 1,
animation: true,
container: this._options.container,
backdrop: this._options.backdrop,
redirect: this._options.redirect,
orphan: this._options.orphan,
template: this._options.template,
onShow: this._options.onShow,
onShown: this._options.onShown,
onHide: this._options.onHide,
onHidden: this._options.onHidden,
onNext: this._options.onNext,
onPrev: this._options.onPrev
}, this._steps[i]);
}
};
Tour.prototype.start = function(force) {
var promise,
_this = this;
if (force == null) {
force = false;
}
if (this.ended() && !force) {
return this._debug("Tour ended, start prevented.");
}
$(document).off("click.tour-" + this._options.name, ".popover.tour-" + this._options.name + " *[data-role=next]").on("click.tour-" + this._options.name, ".popover.tour-" + this._options.name + " *[data-role=next]:not(.disabled)", function(e) {
e.preventDefault();
return _this.next();
});
$(document).off("click.tour-" + this._options.name, ".popover.tour-" + this._options.name + " *[data-role=prev]").on("click.tour-" + this._options.name, ".popover.tour-" + this._options.name + " *[data-role=prev]:not(.disabled)", function(e) {
e.preventDefault();
return _this.prev();
});
$(document).off("click.tour-" + this._options.name, ".popover.tour-" + this._options.name + " *[data-role=end]").on("click.tour-" + this._options.name, ".popover.tour-" + this._options.name + " *[data-role=end]", function(e) {
e.preventDefault();
return _this.end();
});
this._onResize(function() {
return _this.showStep(_this._current);
});
this._setupKeyboardNavigation();
promise = this._makePromise(this._options.onStart != null ? this._options.onStart(this) : void 0);
return this._callOnPromiseDone(promise, this.showStep, this._current);
};
Tour.prototype.next = function() {
var promise;
if (this.ended()) {
return this._debug("Tour ended, next prevented.");
}
promise = this.hideStep(this._current);
return this._callOnPromiseDone(promise, this._showNextStep);
};
Tour.prototype.prev = function() {
var promise;
if (this.ended()) {
return this._debug("Tour ended, prev prevented.");
}
promise = this.hideStep(this._current);
return this._callOnPromiseDone(promise, this._showPrevStep);
};
Tour.prototype.goto = function(i) {
var promise;
if (this.ended()) {
return this._debug("Tour ended, goto prevented.");
}
promise = this.hideStep(this._current);
return this._callOnPromiseDone(promise, this.showStep, i);
};
Tour.prototype.end = function() {
var endHelper, hidePromise,
_this = this;
endHelper = function(e) {
$(document).off("click.tour-" + _this._options.name);
$(document).off("keyup.tour-" + _this._options.name);
$(window).off("resize.tour-" + _this._options.name);
_this.setState("end", "yes");
if (_this._options.onEnd != null) {
return _this._options.onEnd(_this);
}
};
hidePromise = this.hideStep(this._current);
return this._callOnPromiseDone(hidePromise, endHelper);
};
Tour.prototype.ended = function() {
return !!this.getState("end");
};
Tour.prototype.restart = function() {
this.removeState("current_step");
this.removeState("end");
this.setCurrentStep(0);
return this.start();
};
Tour.prototype.hideStep = function(i) {
var hideStepHelper, promise, step,
_this = this;
step = this.getStep(i);
promise = this._makePromise(step.onHide != null ? step.onHide(this, i) : void 0);
hideStepHelper = function(e) {
var $element;
$element = _this._isOrphan(step) ? $("body") : $(step.element);
$element.popover("destroy");
if (step.reflex) {
$element.css("cursor", "").off("click.tour-" + _this._options.name);
}
if (step.backdrop) {
_this._hideBackdrop();
}
if (step.onHidden != null) {
return step.onHidden(_this);
}
};
this._callOnPromiseDone(promise, hideStepHelper);
return promise;
};
Tour.prototype.showStep = function(i) {
var promise, showStepHelper, skipToPrevious, step,
_this = this;
step = this.getStep(i);
if (!step) {
return;
}
skipToPrevious = i < this._current;
promise = this._makePromise(step.onShow != null ? step.onShow(this, i) : void 0);
showStepHelper = function(e) {
var current_path, path;
_this.setCurrentStep(i);
path = $.isFunction(step.path) ? step.path.call() : _this._options.basePath + step.path;
current_path = [document.location.pathname, document.location.hash].join("");
if (_this._isRedirect(path, current_path)) {
_this._redirect(step, path);
return;
}
if (_this._isOrphan(step)) {
if (!step.orphan) {
_this._debug("Skip the orphan step " + (_this._current + 1) + ". Orphan option is false and the element doesn't exist or is hidden.");
if (skipToPrevious) {
_this._showPrevStep();
} else {
_this._showNextStep();
}
return;
}
_this._debug("Show the orphan step " + (_this._current + 1) + ". Orphans option is true.");
}
if (step.backdrop) {
_this._showBackdrop(!_this._isOrphan(step) ? step.element : void 0);
}
_this._showPopover(step, i);
if (step.onShown != null) {
step.onShown(_this);
}
return _this._debug("Step " + (_this._current + 1) + " of " + _this._steps.length);
};
return this._callOnPromiseDone(promise, showStepHelper);
};
Tour.prototype.setCurrentStep = function(value) {
if (value != null) {
this._current = value;
return this.setState("current_step", value);
} else {
this._current = this.getState("current_step");
return this._current = this._current === null ? 0 : parseInt(this._current, 10);
}
};
Tour.prototype._showNextStep = function() {
var promise, showNextStepHelper, step,
_this = this;
step = this.getStep(this._current);
showNextStepHelper = function(e) {
return _this.showStep(step.next);
};
promise = this._makePromise((step.onNext != null ? step.onNext(this) : void 0));
return this._callOnPromiseDone(promise, showNextStepHelper);
};
Tour.prototype._showPrevStep = function() {
var promise, showPrevStepHelper, step,
_this = this;
step = this.getStep(this._current);
showPrevStepHelper = function(e) {
return _this.showStep(step.prev);
};
promise = this._makePromise((step.onPrev != null ? step.onPrev(this) : void 0));
return this._callOnPromiseDone(promise, showPrevStepHelper);
};
Tour.prototype._debug = function(text) {
if (this._options.debug) {
return window.console.log("Bootstrap Tour '" + this._options.name + "' | " + text);
}
};
Tour.prototype._isRedirect = function(path, currentPath) {
return (path != null) && path !== "" && path.replace(/\?.*$/, "").replace(/\/?$/, "") !== currentPath.replace(/\/?$/, "");
};
Tour.prototype._redirect = function(step, path) {
if ($.isFunction(step.redirect)) {
return step.redirect.call(this, path);
} else if (step.redirect === true) {
this._debug("Redirect to " + path);
return document.location.href = path;
}
};
Tour.prototype._isOrphan = function(step) {
return (step.element == null) || !$(step.element).length || $(step.element).is(":hidden");
};
Tour.prototype._showPopover = function(step, i) {
var $element, $navigation, $template, $tip, isOrphan, options,
_this = this;
options = $.extend({}, this._options);
$template = $.isFunction(step.template) ? $(step.template(i, step)) : $(step.template);
$navigation = $template.find(".popover-navigation");
isOrphan = this._isOrphan(step);
if (isOrphan) {
step.element = "body";
step.placement = "top";
$template = $template.addClass("orphan");
}
$element = $(step.element);
$template.addClass("tour-" + this._options.name);
if (step.options) {
$.extend(options, step.options);
}
if (step.reflex) {
$element.css("cursor", "pointer").on("click.tour-" + this._options.name, function(e) {
if (_this._current < _this._steps.length - 1) {
return _this.next();
} else {
return _this.end();
}
});
}
if (step.prev < 0) {
$navigation.find("*[data-role=prev]").addClass("disabled");
}
if (step.next < 0) {
$navigation.find("*[data-role=next]").addClass("disabled");
}
step.template = $template.clone().wrap("<div>").parent().html();
$element.popover({
placement: step.placement,
trigger: "manual",
title: step.title,
content: step.content,
html: true,
animation: step.animation,
container: step.container,
template: step.template,
selector: step.element
}).popover("show");
$tip = $element.data("bs.popover") ? $element.data("bs.popover").tip() : $element.data("popover").tip();
$tip.attr("id", step.id);
this._scrollIntoView($tip);
this._reposition($tip, step);
if (isOrphan) {
return this._center($tip);
}
};
Tour.prototype._reposition = function($tip, step) {
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 (step.placement === "bottom" || step.placement === "top") {
if (originalLeft !== tipOffset.left) {
return this._replaceArrow($tip, (tipOffset.left - originalLeft) * 2, offsetWidth, "left");
}
} else {
if (originalTop !== tipOffset.top) {
return this._replaceArrow($tip, (tipOffset.top - originalTop) * 2, offsetHeight, "top");
}
}
};
Tour.prototype._center = function($tip) {
return $tip.css("top", $(window).outerHeight() / 2 - $tip.outerHeight() / 2);
};
Tour.prototype._replaceArrow = function($tip, delta, dimension, position) {
return $tip.find(".arrow").css(position, delta ? 50 * (1 - delta / dimension) + "%" : "");
};
Tour.prototype._scrollIntoView = function(tip) {
return $("html, body").stop().animate({
scrollTop: Math.ceil(tip.offset().top - ($(window).height() / 2))
});
};
Tour.prototype._onResize = function(callback, timeout) {
return $(window).on("resize.tour-" + this._options.name, function() {
clearTimeout(timeout);
return timeout = setTimeout(callback, 100);
});
};
Tour.prototype._setupKeyboardNavigation = function() {
var _this = this;
if (this._options.keyboard) {
return $(document).on("keyup.tour-" + this._options.name, function(e) {
if (!e.which) {
return;
}
switch (e.which) {
case 39:
e.preventDefault();
if (_this._current < _this._steps.length - 1) {
return _this.next();
} else {
return _this.end();
}
break;
case 37:
e.preventDefault();
if (_this._current > 0) {
return _this.prev();
}
break;
case 27:
e.preventDefault();
return _this.end();
}
});
}
};
Tour.prototype._makePromise = function(result) {
if (result && $.isFunction(result.then)) {
return result;
} else {
return null;
}
};
Tour.prototype._callOnPromiseDone = function(promise, cb, arg) {
var _this = this;
if (promise) {
return promise.then(function(e) {
return cb.call(_this, arg);
});
} else {
return cb.call(this, arg);
}
};
Tour.prototype._showBackdrop = function(element) {
if (this.backdrop.overlay !== null) {
return;
}
this._showOverlay();
if (element != null) {
return this._showOverlayElement(element);
}
};
Tour.prototype._hideBackdrop = function() {
if (this.backdrop.overlay === null) {
return;
}
if (this.backdrop.$element) {
this._hideOverlayElement();
}
return this._hideOverlay();
};
Tour.prototype._showOverlay = function() {
this.backdrop = $("<div/>", {
"class": "tour-backdrop"
});
return $("body").append(this.backdrop);
};
Tour.prototype._hideOverlay = function() {
this.backdrop.remove();
return this.backdrop.overlay = null;
};
Tour.prototype._showOverlayElement = function(element) {
var $background, $element, offset;
$element = $(element);
$background = $("<div/>");
offset = $element.offset();
offset.top = offset.top;
offset.left = offset.left;
$background.width($element.innerWidth()).height($element.innerHeight()).addClass("tour-step-background").offset(offset);
$element.addClass("tour-step-backdrop");
$("body").append($background);
this.backdrop.$element = $element;
return this.backdrop.$background = $background;
};
Tour.prototype._hideOverlayElement = function() {
this.backdrop.$element.removeClass("tour-step-backdrop");
this.backdrop.$background.remove();
this.backdrop.$element = null;
return this.backdrop.$background = null;
};
return Tour;
})();
return window.Tour = Tour;
})(jQuery, window);
}).call(this);

View File

@ -518,10 +518,30 @@ div.tour-backdrop {
z-index: 2009; z-index: 2009;
} }
.popover.tour { .popover.tour.orphan .arrow {
z-index: 2010; display: none;
}
.popover.tour .popover-navigation {
padding: 9px 14px;
}
.popover.tour .popover-navigation *[data-role="end"] {
float: right;
}
.popover.tour .popover-navigation *[data-role="next"], .popover.tour .popover-navigation *[data-role="end"] {
cursor: pointer;
} }
.popover.fixed { .popover.fixed {
position: fixed; position: fixed;
} }
.tour-backdrop {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1100;
background-color: black;
opacity: 0.8;
}

View File

@ -451,9 +451,26 @@ $editorbar_height: 30px
div.tour-backdrop div.tour-backdrop
z-index: 2009 z-index: 2009
.popover.tour .popover.tour
z-index: 2010 &.orphan .arrow
display: none
.popover-navigation
padding: 9px 14px
*[data-role="end"]
float: right
*[data-role="next"],*[data-role="end"]
cursor: pointer
.popover.fixed .popover.fixed
position: fixed position: fixed
.tour-backdrop
position: fixed
top: 0
right: 0
bottom: 0
left: 0
z-index: 1100
background-color: #000
opacity: 0.8
// }}} // }}}

View File

@ -545,7 +545,7 @@
observer.disconnect(); observer.disconnect();
var editor = this.rte.editor; var editor = this.rte.editor;
var root = editor.element.$; var root = editor.element && editor.element.$;
editor.destroy(); editor.destroy();
// FIXME: select editables then filter by dirty? // FIXME: select editables then filter by dirty?
var defs = this.rte.fetch_editables(root) var defs = this.rte.fetch_editables(root)

View File

@ -362,7 +362,6 @@
}, },
clean_for_save: function () { clean_for_save: function () {
var self = this; var self = this;
$("*[contentEditable], *[attributeEditable]") $("*[contentEditable], *[attributeEditable]")
.removeAttr('contentEditable') .removeAttr('contentEditable')
.removeAttr('attributeEditable'); .removeAttr('attributeEditable');

View File

@ -4,127 +4,116 @@
var website = openerp.website; var website = openerp.website;
var _t = openerp._t; var _t = openerp._t;
website.EditorBar.include({ website.Tour.register({
start: function () {
this.registerTour(new website.Tour.Banner(this));
return this._super();
},
});
website.Tour.Banner = website.Tour.extend({
id: 'banner', id: 'banner',
name: "Build a page", name: _t("Build a page"),
path: '/page/website.homepage', path: '/page/website.homepage',
init: function () { steps: [
var self = this; {
self.steps = [ title: _t("Welcome to your website!"),
{ content: _t("This tutorial will guide you to build your home page. We will start by adding a banner."),
title: _t("Welcome to your website!"), popover: { next: _t("Start Tutorial"), end: _t("Skip It") },
content: _t("This tutorial will guide you to build your home page. We will start by adding a banner."), },
popover: { next: _t("Start Tutorial"), end: _t("Skip It") }, {
}, waitNot: '.popover.tour',
{ element: 'button[data-action=edit]',
waitNot: '.popover.tour', placement: 'bottom',
element: 'button[data-action=edit]', title: _t("Edit this page"),
placement: 'bottom', content: _t("Every page of your website can be modified through the <i>Edit</i> button."),
title: _t("Edit this page"), popover: { fixed: true },
content: _t("Every page of your website can be modified through the <i>Edit</i> button."), },
popover: { fixed: true }, {
}, element: 'button[data-action=snippet]',
{ placement: 'bottom',
element: 'button[data-action=snippet]', title: _t("Insert building blocks"),
placement: 'bottom', content: _t("Click here to insert blocks of content in the page."),
title: _t("Insert building blocks"), popover: { fixed: true },
content: _t("Click here to insert blocks of content in the page."), },
popover: { fixed: true }, {
}, snippet: '#snippet_structure .oe_snippet:first',
{ placement: 'bottom',
snippet: '#snippet_structure .oe_snippet:first', title: _t("Drag & Drop a Banner"),
placement: 'bottom', content: _t("Drag the Banner block and drop it in your page."),
title: _t("Drag & Drop a Banner"), popover: { fixed: true },
content: _t("Drag the Banner block and drop it in your page."), },
popover: { fixed: true }, {
}, waitFor: '.oe_overlay_options .oe_options:visible',
{ element: '#wrap .carousel:first div.carousel-content',
waitFor: '.oe_overlay_options .oe_options:visible', placement: 'top',
element: '#wrap .carousel:first div.carousel-content', title: _t("Customize banner's text"),
placement: 'top', content: _t("Click in the text and start editing it."),
title: _t("Customize banner's text"), sampleText: 'Here, a customized text',
content: _t("Click in the text and start editing it."), },
sampleText: 'Here, a customized text', {
}, waitNot: '#wrap .carousel:first div.carousel-content:has(h2:'+
{ 'containsExact('+_t('Your Banner Title')+')):has(h3:'+
waitNot: '#wrap .carousel:first div.carousel-content:has(h2:'+ 'containsExact('+_t('Click to customize this text')+'))',
'containsExact('+_t('Your Banner Title')+')):has(h3:'+ element: '.oe_snippet_parent:visible',
'containsExact('+_t('Click to customize this text')+'))', placement: 'bottom',
element: '.oe_snippet_parent:visible', title: _t("Get banner properties"),
placement: 'bottom', content: _t("Select the parent container to get the global options of the banner."),
title: _t("Get banner properties"), popover: { fixed: true },
content: _t("Select the parent container to get the global options of the banner."), },
popover: { fixed: true }, {
}, element: '.oe_overlay_options .oe_options:visible',
{ placement: 'left',
element: '.oe_overlay_options .oe_options:visible', title: _t("Customize the banner"),
placement: 'left', content: _t("Customize any block through this menu. Try to change the background of the banner."),
title: _t("Customize the banner"), popover: { next: _t("Continue") },
content: _t("Customize any block through this menu. Try to change the background of the banner."), },
popover: { next: _t("Continue") }, {
}, waitNot: '.popover.tour',
{ element: 'button[data-action=snippet]',
waitNot: '.popover.tour', placement: 'bottom',
element: 'button[data-action=snippet]', title: _t("Add Another Block"),
placement: 'bottom', content: _t("Let's add another building block to your page."),
title: _t("Add Another Block"), popover: { fixed: true },
content: _t("Let's add another building block to your page."), },
popover: { fixed: true }, {
}, snippet: '#snippet_structure .oe_snippet:eq(6)',
{ placement: 'bottom',
snippet: '#snippet_structure .oe_snippet:eq(6)', title: _t("Drag & Drop This Block"),
placement: 'bottom', content: _t("Drag the <em>'Features'</em> block and drop it below the banner."),
title: _t("Drag & Drop This Block"), popover: { fixed: true },
content: _t("Drag the <em>'Features'</em> block and drop it below the banner."), },
popover: { fixed: true }, {
}, waitFor: '.oe_overlay_options .oe_options:visible',
{ element: 'button[data-action=save]',
waitFor: '.oe_overlay_options .oe_options:visible', placement: 'right',
element: 'button[data-action=save]', title: _t("Save your modifications"),
placement: 'right', content: _t("Publish your page by clicking on the <em>'Save'</em> button."),
title: _t("Save your modifications"), popover: { fixed: true },
content: _t("Publish your page by clicking on the <em>'Save'</em> button."), },
popover: { fixed: true }, {
}, waitFor: 'button[data-action=edit]:visible',
{ title: _t("Good Job!"),
waitFor: 'button[data-action=edit]:visible', content: _t("Well done, you created your homepage."),
title: _t("Good Job!"), popover: { next: _t("Continue") },
content: _t("Well done, you created your homepage."), },
popover: { next: _t("Continue") }, {
}, waitNot: '.popover.tour',
{ element: 'a[data-action=show-mobile-preview]',
waitNot: '.popover.tour', placement: 'bottom',
element: 'a[data-action=show-mobile-preview]', title: _t("Test Your Mobile Version"),
placement: 'bottom', content: _t("Let's check how your homepage looks like on mobile devices."),
title: _t("Test Your Mobile Version"), popover: { fixed: true },
content: _t("Let's check how your homepage looks like on mobile devices."), },
popover: { fixed: true }, {
}, element: '.modal:has(#mobile-viewport) button[data-dismiss=modal]',
{ placement: 'right',
element: '.modal:has(#mobile-viewport) button[data-dismiss=modal]', title: _t("Check Mobile Preview"),
placement: 'right', content: _t("Scroll to check rendering and then close the mobile preview."),
title: _t("Check Mobile Preview"), popover: { next: _t("Continue") },
content: _t("Scroll to check rendering and then close the mobile preview."), },
popover: { next: _t("Continue") }, {
}, waitNot: '.modal',
{ element: '#content-menu-button',
waitNot: '.modal', placement: 'left',
element: '#content-menu-button', title: _t("Add new pages and menus"),
placement: 'left', content: _t("The 'Content' menu allows you to add pages or add the top menu."),
title: _t("Add new pages and menus"), popover: { next: _t("Close Tutorial") },
content: _t("The 'Content' menu allows you to add pages or add the top menu."), },
popover: { next: _t("Close Tutorial") }, ]
},
];
return this._super();
},
}); });
}()); }());

View File

@ -6,7 +6,7 @@ if (typeof openerp === "undefined") {
var error = "openerp is undefined" var error = "openerp is undefined"
+ "\nhref: " + window.location.href + "\nhref: " + window.location.href
+ "\nreferrer: " + document.referrer + "\nreferrer: " + document.referrer
+ "\nlocalStorage: " + JSON.stringify(window.localStorage); + "\nlocalStorage: " + window.localStorage.getItem("tour");
if (typeof $ !== "undefined") { if (typeof $ !== "undefined") {
error += '\n\n' + $("body").html(); error += '\n\n' + $("body").html();
} }
@ -15,7 +15,7 @@ if (typeof openerp === "undefined") {
var website = window.openerp.website; var website = window.openerp.website;
// don't rewrite website.Tour in test mode // don't rewrite T in test mode
if (typeof website.Tour !== "undefined") { if (typeof website.Tour !== "undefined") {
return; return;
} }
@ -23,40 +23,26 @@ if (typeof website.Tour !== "undefined") {
// don't need template to use bootstrap Tour in automatic mode // don't need template to use bootstrap Tour in automatic mode
if (typeof QWeb2 !== "undefined") { if (typeof QWeb2 !== "undefined") {
website.add_template_file('/website/static/src/xml/website.tour.xml'); website.add_template_file('/website/static/src/xml/website.tour.xml');
} }
// don't need to use bootstrap Tour to launch an automatic tour
function bootstrap_tour_stub () {
if (typeof Tour === "undefined") {
window.Tour = function Tour() {};
Tour.prototype.addSteps = function () {};
Tour.prototype.end = function () {};
Tour.prototype.goto = function () {};
}
}
if (website.EditorBar) { if (website.EditorBar) {
website.EditorBar.include({ website.EditorBar.include({
tours: [], tours: [],
start: function () { start: function () {
var self = this; var self = this;
var menu = $('#help-menu'); var menu = $('#help-menu');
_.each(this.tours, function (tour) { _.each(T.tours, function (tour) {
if (tour.mode === "test") {
return;
}
var $menuItem = $($.parseHTML('<li><a href="#">'+tour.name+'</a></li>')); var $menuItem = $($.parseHTML('<li><a href="#">'+tour.name+'</a></li>'));
$menuItem.click(function () { $menuItem.click(function () {
tour.reset(); T.reset();
tour.run(); T.run(tour.id);
}); });
menu.append($menuItem); menu.append($menuItem);
}); });
return this._super(); return this._super();
},
registerTour: function (tour) {
website.Tour.add(tour);
this.tours.push(tour);
} }
}); });
} }
@ -94,351 +80,401 @@ $.ajaxSetup({
} }
}); });
website.Tour = openerp.Class.extend({ /////////////////////////////////////////////////
steps: [],
defaultDelay: 50, //ms
defaultOverLaps: 5000, //ms
localStorage: window.localStorage,
init: function () {},
run: function (automatic) { var localStorage = window.localStorage;
this.reset();
for (var k in this.localStorage) { var T = website.Tour = {
if (!k.indexOf("tour-") && k.indexOf("-test") > -1) return; tours: {},
} defaultDelay: 50,
retryRunningDelay: 1000,
website.Tour.busy = true; errorDelay: 5000,
state: null,
if (automatic) { $element: null,
this.localStorage.setItem("tour-"+this.id+"-test-automatic", true); 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);
window.location.href = href;
} else { } else {
this.localStorage.removeItem("tour-"+this.id+"-test-automatic"); console.log("Tour Begin from run method");
T.saveState(tour.id, mode || tour.mode, 0);
T.running();
} }
this.automatic = automatic;
if (this.path) {
// redirect to begin of the tour in function of the language
if (!this.testUrl(this.path+"(#.*)?$")) {
var path = this.path.split('#');
window.location.href = "/"+this.getLang()+path[0] + "#tutorial."+this.id+"=true&" + path.slice(1, path.length).join("#");
return;
}
}
var self = this;
this.localStorage.setItem("tour-"+this.id+"-test", 0);
website.Tour.waitReady.call(this, function () {self._running();});
}, },
running: function () { registerSteps: function (tour) {
var self = this; if (tour.register) {
if (+this.localStorage.getItem("tour-"+this.id+"-test") >= this.steps.length-1) {
this.endTour();
return; return;
} }
tour.register = true;
if (website.Tour.is_busy()) return; for (var index=0, len=tour.steps.length; index<len; index++) {
var step = tour.steps[index];
step.id = index;
// launch tour with url if (!step.waitNot && index > 0 && tour.steps[index-1] &&
this.checkRunningUrl(); tour.steps[index-1].popover && tour.steps[index-1].popover.next) {
step.waitNot = '.popover.tour.fade.in:visible';
// mark tour as busy (only one test running)
if (this.localStorage.getItem("tour-"+this.id+"-test") != null) {
website.Tour.busy = true;
this.automatic = !!this.localStorage.getItem("tour-"+this.id+"-test-automatic");
}
if (!this.testPathUrl()) {
if (this.automatic) {
this.timer = setTimeout(function () {
self.reset();
throw new Error("Wrong url for running " + self.id
+ '\ntestPath: ' + self.testPath
+ '\nhref: ' + window.location.href
+ "\nreferrer: " + document.referrer
);
},this.defaultOverLaps);
} }
return; if (!step.waitFor && index > 0 && tour.steps[index-1].snippet) {
}
var self = this;
website.Tour.waitReady.call(this, function () {self._running();});
},
_running: function () {
var stepId = this.localStorage.getItem("tour-"+this.id+"-test");
if (stepId != null) {
this.registerTour();
this.nextStep(stepId, this.automatic ? this.autoNextStep : null, this.automatic ? this.defaultOverLaps : null);
}
},
reset: function () {
website.Tour.busy = false;
for (var k in this.steps) {
this.steps[k].busy = false;
}
clearTimeout(self.timer);
clearTimeout(self.testtimer);
for (var k in this.localStorage) {
if (!k.indexOf("tour-") || !k.indexOf(this.id)) {
this.localStorage.removeItem(k);
}
}
$('.popover.tour').remove();
},
getLang: function () {
return $("html").attr("lang").replace(/-/, '_');
},
testUrl: function (url) {
return new RegExp("(/"+this.getLang()+")?"+url, "i").test(window.location.href);
},
testPathUrl: function () {
if (!this.testPath || this.testUrl(this.testPath)) return true;
},
checkRunningUrl: function () {
if (window.location.hash.indexOf("tutorial."+this.id+"=true") > -1) {
this.localStorage.setItem("tour-"+this.id+"-test", 0);
window.location.hash = window.location.hash.replace(/tutorial.+=true&?/, '');
}
},
registerTour: function () {
if (this.automatic) {
bootstrap_tour_stub();
}
this.tour = new Tour({
name: this.id,
storage: this.tourStorage,
keyboard: false,
template: this.popover(),
onHide: function () {
window.scrollTo(0, 0);
}
});
this.registerSteps();
},
registerSteps: function () {
for (var index=0, len=this.steps.length; index<len; index++) {
var step = this.steps[index];
step.stepId = step.stepId || ""+index;
if (!step.waitNot && index > 0 && this.steps[index-1] &&
this.steps[index-1].popover && this.steps[index-1].popover.next) {
step.waitNot = '.popover.tour:visible';
}
if (!step.waitFor && index > 0 && this.steps[index-1].snippet) {
step.waitFor = '.oe_overlay_options .oe_options:visible'; step.waitFor = '.oe_overlay_options .oe_options:visible';
} }
step._title = step._title || step.title;
step.title = this.popoverTitle({ title: step._title });
step.template = step.template || this.popover( step.popover );
if (!step.element) step.orphan = true; var snippet = step.element && step.element.match(/#oe_snippets (.*) \.oe_snippet_thumbnail/);
if (step.snippet) { if (snippet) {
step.snippet = snippet[1];
} else if (step.snippet) {
step.element = '#oe_snippets '+step.snippet+' .oe_snippet_thumbnail'; 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] &&
if (this.steps[index-1] && tour.steps[index-1].popover && tour.steps[index-1].popover.next) {
this.steps[index-1].popover && this.steps[index-1].popover.next) {
var step = { var step = {
stepId: ""+index, _title: "",
waitNot: '.popover.tour:visible' id: index,
waitNot: '.popover.tour.fade.in:visible'
}; };
this.steps.push(step); tour.steps.push(step);
} }
this.tour.addSteps(this.steps); // rendering bootstrap tour and popover
if (tour.mode !== "test") {
for (var index=0, len=tour.steps.length; index<len; index++) {
var step = tour.steps[index];
step._title = step._title || step.title;
step.title = T.popoverTitle(tour, { title: step._title });
step.template = step.template || T.popover( step.popover );
}
}
}, },
closePopover: function () {
popoverTitle: function (options) { if (T.$element) {
try { T.$element.popover('destroy');
return openerp.qweb.render('website.tour_popover_title', options); T.$element.removeData("tour");
} catch (e) { T.$element.removeData("tour-step");
if (!this.automatic) throw e; $(".tour-backdrop").remove();
return options.title; $(".popover.tour").remove();
T.$element = null;
} }
}, },
autoTogglePopover: function () {
var state = T.getState();
var step = state.step;
if (T.$element &&
T.$element.is(":visible") &&
T.$element.data("tour") === state.id &&
T.$element.data("tour-step") === step.id) {
T.repositionPopover();
return;
}
if (step.busy) {
return;
}
T.closePopover();
var $element = $(step.element).first();
if (!step.element || !$element.size() || !$element.is(":visible")) {
return;
}
T.$element = $element;
$element.data("tour", state.id);
$element.data("tour-step", step.id);
$element.popover({
placement: step.placement || "auto",
animation: true,
trigger: "manual",
title: step.title,
content: step.content,
html: true,
container: "body",
template: step.template,
orphan: step.orphan
}).popover("show");
var $tip = $element.data("bs.popover").tip();
// add popover style (orphan, static, backdrop)
if (step.orphan) {
$tip.addClass("orphan");
}
var node = $element[0];
var css;
do {
css = window.getComputedStyle(node);
if (!css || css.position == "fixed") {
$tip.addClass("fixed");
break;
}
} while ((node = node.parentNode) && node !== document);
if (step.backdrop) {
$("body").append('<div class="tour-backdrop"></div>');
}
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) { popover: function (options) {
try { return openerp.qweb ? openerp.qweb.render('website.tour_popover', options) : options.title;
return openerp.qweb.render('website.tour_popover', options); },
} catch (e) { getLang: function () {
if (!this.automatic) throw e; return $("html").attr("lang").replace(/-/, '_');
return ""; },
} 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);
}
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) {
localStorage.setItem("tour", JSON.stringify({"id":tour_id, "mode":mode, "step_id":step_id || 0, "time": this.time}));
},
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);
}, },
timer: null,
testtimer: null,
check: function (step) { check: function (step) {
return (step && return (step &&
(!step.element || ($(step.element).size() && $(step.element).is(":visible") && !$(step.element).is(":hidden"))) && (!step.element || ($(step.element).size() && $(step.element).is(":visible") && !$(step.element).is(":hidden"))) &&
(!step.waitNot || !$(step.waitNot).size()) && (!step.waitNot || !$(step.waitNot).size()) &&
(!step.waitFor || $(step.waitFor).size())); (!step.waitFor || $(step.waitFor).size()));
}, },
waitNextStep: function (step, callback, overlaps) { waitNextStep: function () {
var self = this; var state = T.getState();
var time = new Date().getTime(); var time = new Date().getTime();
var timer; var timer;
var next = state.tour.steps[state.step.id+1];
var overlaps = state.mode === "test" ? T.errorDelay : 0;
window.onbeforeunload = function () { window.onbeforeunload = function () {
clearTimeout(self.timer); clearTimeout(T.timer);
clearTimeout(self.testtimer); clearTimeout(T.testtimer);
}; };
// check popover activity
$(".popover.tour button")
.off()
.on("click", function () {
var help = $("#help-menu-button");
var offset = help.offset();
var left = (offset.left > 0) ? (offset.left + help.width()) : offset.left;
var top = (help.height() > 0) ? (offset.top + help.height()) : offset.top;
if ($(this).is("[data-role='next']") && step.element) {
$(".popover.tour").remove();
}
if (step.busy) return;
if (!$(this).is("[data-role='next']") || !step.element) {
$('.popover.tour')
.animate({
left: left,
top: top,
width: '1px',
height: '1px',
opacity: 0
}, 800,
function(){
$(".popover.tour").remove();
clearTimeout(self.timer);
step.busy = true;
self.tour.end();
self.endTour(callback);
});
}
});
function checkNext () { function checkNext () {
clearTimeout(self.timer); T.autoTogglePopover();
if (step.busy) return;
if (self.check(step)) { clearTimeout(T.timer);
step.busy = true; if (T.check(next)) {
clearTimeout(T.currentTimer);
// use an other timeout for cke dom loading // use an other timeout for cke dom loading
setTimeout(function () { setTimeout(function () {
self.nextStep(step.stepId, callback, overlaps); T.nextStep(next);
}, self.defaultDelay); }, T.defaultDelay);
} else if (!overlaps || new Date().getTime() - time < overlaps) { } else if (!overlaps || new Date().getTime() - time < overlaps) {
if (self.current.element) { T.timer = setTimeout(checkNext, T.defaultDelay);
var $popover = $(".popover.tour");
if(!$(self.current.element).is(":visible")) {
$popover.data("hide", true).fadeOut(300);
} else if($popover.data("hide")) {
$popover.data("hide", false).fadeIn(150);
}
}
self.timer = setTimeout(checkNext, self.defaultDelay);
} else { } else {
self.reset(); T.error(next, "Can't reach the next step");
throw new Error("Can't arrive to step " + step.stepId + ": '" + step._title + "'"
+ '\nhref: ' + window.location.href
+ '\nelement: ' + Boolean(!step.element || ($(step.element).size() && $(step.element).is(":visible") && !$(step.element).is(":hidden")))
+ '\nwaitNot: ' + Boolean(!step.waitNot || !$(step.waitNot).size())
+ '\nwaitFor: ' + Boolean(!step.waitFor || $(step.waitFor).size())
+ '\n\n' + $("body").html()
);
} }
} }
checkNext(); checkNext();
}, },
step: function (stepId) { nextStep: function (step) {
var steps = this.steps.slice(0,this.steps.length), var state = T.getState();
step;
while (step = steps.shift()) { if (!state) {
if (!stepId || step.stepId === stepId) return;
return step;
} }
return null;
}, step = step || state.step;
next: function (stepId) { T.saveState(state.id, state.mode, step.id);
var steps = this.steps.slice(0,this.steps.length),
step, next, index=0; if (step.id !== state.step_id) {
while (step = steps.shift()) { console.log("Tour Step: '" + (step._title || step.title) + "' (" + (new Date().getTime() - this.time) + "ms)");
if (!stepId || step.stepId === stepId) {
// clear popover (fix for boostrap tour if the element is removed before destroy popover)
$(".popover.tour").remove();
// go to step in bootstrap tour
this.tour.goto(index);
if (step.onload) step.onload();
next = steps.shift();
break;
}
index++;
} }
return next;
},
nextStep: function (stepId, callback, overlaps) {
var self = this;
if (!this.localStorage.getItem("tour-"+this.id+"-test")) return;
this.localStorage.setItem("tour-"+this.id+"-test", stepId || 0); T.autoTogglePopover(true);
this.current = this.step(stepId); if (step.onload) {
var next = this.next(stepId); step.onload();
}
var next = state.tour.steps[step.id+1];
if (next) { if (next) {
setTimeout(function () { setTimeout(function () {
self.waitNextStep(next, callback, overlaps); T.waitNextStep();
if (callback) setTimeout(function(){callback.call(self, next);}, self.defaultDelay); if (state.mode === "test") {
}, next && next.wait || 0); setTimeout(function(){
T.autoNextStep(state.tour, step);
}, T.defaultDelay);
}
}, next.wait || 0);
} else { } else {
this.endTour(); T.endTour();
} }
}, },
endTour: function () { endTour: function () {
var test = parseInt(this.localStorage.getItem("tour-"+this.id+"-test"),10) >= this.steps.length-1; var state = T.getState();
this.reset(); var test = state.step.id >= state.tour.steps.length-1;
T.reset();
if (test) { if (test) {
console.log('ok'); console.log('ok');
} else { } else {
console.log('error'); console.log('error');
} }
}, },
autoNextStep: function () { autoNextStep: function (tour, step) {
var self = this; clearTimeout(T.testtimer);
clearTimeout(self.testtimer);
function autoStep () { function autoStep () {
var step = self.current;
if (!step) return; if (!step) return;
if (step.autoComplete) { if (step.autoComplete) {
step.autoComplete(tour); step.autoComplete(tour);
} }
var $popover = $(".popover.tour"); $(".popover.tour [data-role='next']").click();
if ($popover.find("button[data-role='next']:visible").size()) {
$popover.find("button[data-role='next']:visible").click();
$popover.remove();
}
var $element = $(step.element); var $element = $(step.element);
if (!$element.size()) return; if (!$element.size()) return;
if (step.snippet) { if (step.snippet) {
var selector = '#oe_snippets '+step.snippet+' .oe_snippet_thumbnail'; T.autoDragAndDropSnippet($element);
self.autoDragAndDropSnippet(selector);
} else if (step.element.match(/#oe_snippets .* \.oe_snippet_thumbnail/)) {
self.autoDragAndDropSnippet($element);
} else if ($element.is(":visible")) { } else if ($element.is(":visible")) {
@ -473,7 +509,7 @@ website.Tour = openerp.Class.extend({
} }
} }
self.testtimer = setTimeout(autoStep, 100); T.testtimer = setTimeout(autoStep, 100);
}, },
autoDragAndDropSnippet: function (selector) { autoDragAndDropSnippet: function (selector) {
var $thumbnail = $(selector).first(); var $thumbnail = $(selector).first();
@ -483,64 +519,11 @@ website.Tour = openerp.Class.extend({
var $dropZone = $(".oe_drop_zone").first(); var $dropZone = $(".oe_drop_zone").first();
var dropPosition = $dropZone.position(); var dropPosition = $dropZone.position();
$dropZone.trigger($.Event("mouseup", { which: 1, pageX: dropPosition.left, pageY: dropPosition.top })); $dropZone.trigger($.Event("mouseup", { which: 1, pageX: dropPosition.left, pageY: dropPosition.top }));
},
});
website.Tour.tours = {};
website.Tour.busy = false;
website.Tour.add = function (tour) {
website.Tour.waitReady(function () {
tour = tour.id ? tour : new tour();
if (!website.Tour.tours[tour.id]) {
website.Tour.tours[tour.id] = tour;
tour.running();
}
});
};
website.Tour.get = function (id) {
return website.Tour.tours[id];
};
website.Tour.each = function (callback) {
website.Tour.waitReady(function () {
for (var k in website.Tour.tours) {
callback.call(website.Tour.tours[k]);
}
});
};
website.Tour.waitReady = function (callback) {
var self = this;
$(document).ready(function () {
if ($.ajaxBusy) {
$(document).ajaxStop(function() {
setTimeout(function () {
callback.call(self);
},0);
});
}
else {
setTimeout(function () {
callback.call(self);
},0);
}
});
};
website.Tour.run_test = function (id) {
website.Tour.waitReady(function () {
if (!website.Tour.is_busy()) {
website.Tour.tours[id].run(true);
}
});
};
website.Tour.is_busy = function () {
for (var k in this.localStorage) {
if (!k.indexOf("tour-")) {
return k;
}
} }
return website.Tour.busy;
}; };
//$(document).ready(T.running);
website.ready().then(T.running);
}()); }());

View File

@ -8,6 +8,6 @@ class TestUi(openerp.tests.HttpCase):
self.phantom_js("/", "console.log('ok')", "openerp.website.editor", login='admin') self.phantom_js("/", "console.log('ok')", "openerp.website.editor", login='admin')
def test_04_admin_tour_banner(self): def test_04_admin_tour_banner(self):
self.phantom_js("/", "openerp.website.Tour.run_test('banner')", "openerp.website.Tour.tours.banner", login='admin') self.phantom_js("/", "openerp.website.Tour.run('banner', 'test')", "openerp.website.Tour.tours.banner", login='admin')
# vim:et: # vim:et:

View File

@ -256,7 +256,6 @@
<xpath expr='//script[@src="/web/static/lib/bootstrap/js/bootstrap.js"]' position="before"> <xpath expr='//script[@src="/web/static/lib/bootstrap/js/bootstrap.js"]' position="before">
<link rel='stylesheet' href='/website/static/src/css/snippets.css'/> <link rel='stylesheet' href='/website/static/src/css/snippets.css'/>
<link rel='stylesheet' href='/website/static/src/css/editor.css'/> <link rel='stylesheet' href='/website/static/src/css/editor.css'/>
<link rel='stylesheet' href='/website/static/lib/bootstrap-tour/bootstrap-tour.css'/>
<link rel="stylesheet" href="/web/static/lib/select2/select2.css"/> <link rel="stylesheet" href="/web/static/lib/select2/select2.css"/>
@ -268,7 +267,6 @@
<script t-if="not translatable" type="text/javascript" src="/website/static/lib/ace/ace.js"></script> <script t-if="not translatable" type="text/javascript" src="/website/static/lib/ace/ace.js"></script>
<script type="text/javascript" src="/website/static/lib/vkbeautify/vkbeautify.0.99.00.beta.js"></script> <script type="text/javascript" src="/website/static/lib/vkbeautify/vkbeautify.0.99.00.beta.js"></script>
<script type="text/javascript" src="/web/static/lib/jquery.ui/js/jquery-ui-1.9.1.custom.js"></script> <script type="text/javascript" src="/web/static/lib/jquery.ui/js/jquery-ui-1.9.1.custom.js"></script>
<script type="text/javascript" src="/website/static/lib/bootstrap-tour/bootstrap-tour.js"></script>
<!-- mutation observers shim backed by mutation events (8 < IE < 11, Safari < 6, FF < 14, Chrome < 17) --> <!-- mutation observers shim backed by mutation events (8 < IE < 11, Safari < 6, FF < 14, Chrome < 17) -->
<script type="text/javascript" src="/website/static/lib//jquery.mjs.nestedSortable/jquery.mjs.nestedSortable.js"></script> <script type="text/javascript" src="/website/static/lib//jquery.mjs.nestedSortable/jquery.mjs.nestedSortable.js"></script>
<script type="text/javascript" src="/website/static/lib/MutationObservers/test/sidetable.js"></script> <script type="text/javascript" src="/website/static/lib/MutationObservers/test/sidetable.js"></script>

View File

@ -4,117 +4,105 @@
var website = openerp.website; var website = openerp.website;
var _t = openerp._t; var _t = openerp._t;
website.EditorBar.include({ website.Tour.register({
start: function () { id: 'blog',
this.registerTour(new website.Tour.Blog(this)); name: _t("Create a blog post"),
return this._super(); steps: [
}, {
}); title: _t("New Blog Post"),
content: _t("Let's go through the first steps to write beautiful blog posts."),
website.Tour.Blog = website.Tour.extend({ popover: { next: _t("Start Tutorial"), end: _t("Skip") },
id: 'blog', },
name: "Create a blog post", {
testPath: '/(blog|blogpost)', element: '#content-menu-button',
init: function () { placement: 'left',
var self = this; title: _t("Add Content"),
self.steps = [ content: _t("Use this <em>'Content'</em> menu to create a new blog post like any other document (page, menu, products, event, ...)."),
{ popover: { fixed: true },
title: _t("New Blog Post"), },
content: _t("Let's go through the first steps to write beautiful blog posts."), {
popover: { next: _t("Start Tutorial"), end: _t("Skip") }, element: 'a[data-action=new_blog_post]',
}, placement: 'left',
{ title: _t("New Blog Post"),
element: '#content-menu-button', content: _t("Select this menu item to create a new blog post."),
placement: 'left', popover: { fixed: true },
title: _t("Add Content"), },
content: _t("Use this <em>'Content'</em> menu to create a new blog post like any other document (page, menu, products, event, ...)."), {
popover: { fixed: true }, element: '.modal:has(#editor_new_blog) button.btn-primary',
}, placement: 'right',
{ title: _t("Create Blog Post"),
element: 'a[data-action=new_blog_post]', content: _t("Click <em>Continue</em> to create the blog post."),
placement: 'left', },
title: _t("New Blog Post"), {
content: _t("Select this menu item to create a new blog post."), waitFor: 'body:has(button[data-action=save]:visible):has(.js_blog)',
popover: { fixed: true }, title: _t("Blog Post Created"),
}, content: _t("This is your new blog post. Let's edit it."),
{ popover: { next: _t("Continue") },
element: '.modal:has(#editor_new_blog) button.btn-primary', },
placement: 'right', {
title: _t("Create Blog Post"), element: 'h1[data-oe-expression="blog_post.name"]',
content: _t("Click <em>Continue</em> to create the blog post."), placement: 'bottom',
}, sampleText: 'New Blog',
{ title: _t("Set a Title"),
waitFor: 'body:has(button[data-action=save]:visible):has(.js_blog)', content: _t("Click on this area and set a catchy title for your blog post."),
title: _t("Blog Post Created"), },
content: _t("This is your new blog post. Let's edit it."), {
popover: { next: _t("Continue") }, waitNot: '#wrap h1[data-oe-model="blog.post"]:contains("Blog Post Title")',
}, element: 'button[data-action=snippet]',
{ placement: 'left',
element: 'h1[data-oe-expression="blog_post.name"]', title: _t("Layout Your Blog Post"),
placement: 'bottom', content: _t("Use well designed building blocks to structure the content of your blog. Click 'Insert Blocks' to add new content."),
sampleText: 'New Blog', popover: { fixed: true },
title: _t("Set a Title"), },
content: _t("Click on this area and set a catchy title for your blog post."), {
}, snippet: '#snippet_structure .oe_snippet:eq(2)',
{ placement: 'bottom',
waitNot: '#wrap h1[data-oe-model="blog.post"]:contains("Blog Post Title")', title: _t("Drag & Drop a Block"),
element: 'button[data-action=snippet]', content: _t("Drag this block and drop it in your page."),
placement: 'left', popover: { fixed: true },
title: _t("Layout Your Blog Post"), },
content: _t("Use well designed building blocks to structure the content of your blog. Click 'Insert Blocks' to add new content."), {
popover: { fixed: true }, element: 'button[data-action=snippet]',
}, placement: 'bottom',
{ title: _t("Add Another Block"),
snippet: '#snippet_structure .oe_snippet:eq(2)', content: _t("Let's add another block to your post."),
placement: 'bottom', popover: { fixed: true },
title: _t("Drag & Drop a Block"), },
content: _t("Drag this block and drop it in your page."), {
popover: { fixed: true }, snippet: '#snippet_structure .oe_snippet:eq(4)',
}, placement: 'bottom',
{ title: _t("Drag & Drop a block"),
element: 'button[data-action=snippet]', content: _t("Drag this block and drop it below the image block."),
placement: 'bottom', popover: { fixed: true },
title: _t("Add Another Block"), },
content: _t("Let's add another block to your post."), {
popover: { fixed: true }, element: '.oe_active .oe_snippet_remove',
}, placement: 'top',
{ title: _t("Delete the block"),
snippet: '#snippet_structure .oe_snippet:eq(4)', content: _t("From this toolbar you can move, duplicate or delete the selected zone. Click on the garbage can image to delete the block. Or click on the Title and delete it."),
placement: 'bottom', },
title: _t("Drag & Drop a block"), {
content: _t("Drag this block and drop it below the image block."), waitNot: '.oe_active .oe_snippet_remove:visible',
popover: { fixed: true }, element: 'button[data-action=save]',
}, placement: 'right',
{ title: _t("Save Your Blog"),
element: '.oe_active .oe_snippet_remove', content: _t("Click the <em>Save</em> button to record changes on the page."),
placement: 'top', popover: { fixed: true },
title: _t("Delete the block"), },
content: _t("From this toolbar you can move, duplicate or delete the selected zone. Click on the garbage can image to delete the block. Or click on the Title and delete it."), {
}, waitFor: 'button[data-action=edit]:visible',
{ element: 'button.btn-danger.js_publish_btn',
waitNot: '.oe_active .oe_snippet_remove:visible', placement: 'top',
element: 'button[data-action=save]', title: _t("Publish Your Post"),
placement: 'right', content: _t("Your blog post is not yet published. You can update this draft version and publish it once you are ready."),
title: _t("Save Your Blog"), },
content: _t("Click the <em>Save</em> button to record changes on the page."), {
popover: { fixed: true }, waitFor: '.js_publish_management button.js_publish_btn.btn-success:visible',
}, title: "Thanks!",
{ content: _t("This tutorial is finished. To discover more features, improve the content of this page and try the <em>Promote</em> button in the top right menu."),
waitFor: 'button[data-action=edit]:visible', popover: { next: _t("Close Tutorial") },
element: 'button.btn-danger.js_publish_btn', },
placement: 'top', ]
title: _t("Publish Your Post"),
content: _t("Your blog post is not yet published. You can update this draft version and publish it once you are ready."),
},
{
waitFor: '.js_publish_management button.js_publish_btn.btn-success:visible',
title: "Thanks!",
content: _t("This tutorial is finished. To discover more features, improve the content of this page and try the <em>Promote</em> button in the top right menu."),
popover: { next: _t("Close Tutorial") },
},
];
return this._super();
},
}); });
}()); }());

View File

@ -2,5 +2,5 @@ import openerp.tests
class TestUi(openerp.tests.HttpCase): class TestUi(openerp.tests.HttpCase):
def test_admin(self): def test_admin(self):
self.phantom_js("/", "openerp.website.Tour.run_test('blog')", "openerp.website.Tour") self.phantom_js("/", "openerp.website.Tour.run('blog', 'test')", "openerp.website.Tour.tours.blog")

View File

@ -4,115 +4,103 @@
var website = openerp.website; var website = openerp.website;
var _t = openerp._t; var _t = openerp._t;
website.EditorBar.include({ website.Tour.register({
start: function () { id: 'event',
this.registerTour(new website.EventTour(this)); name: _t("Create an event"),
return this._super(); steps: [
}, {
}); title: _t("Create an Event"),
content: _t("Let's go through the first steps to publish a new event."),
website.EventTour = website.Tour.extend({ popover: { next: _t("Start Tutorial"), end: _t("Skip It") },
id: 'event', },
name: "Create an event", {
testPath: '/event(/[0-9]+/register)?', element: '#content-menu-button',
init: function (editor) { placement: 'left',
var self = this; title: _t("Add Content"),
self.steps = [ content: _t("The <em>Content</em> menu allows you to create new pages, events, menus, etc."),
{ popover: { fixed: true },
title: _t("Create an Event"), },
content: _t("Let's go through the first steps to publish a new event."), {
popover: { next: _t("Start Tutorial"), end: _t("Skip It") }, element: 'a[data-action=new_event]',
}, placement: 'left',
{ title: _t("New Event"),
element: '#content-menu-button', content: _t("Click here to create a new event."),
placement: 'left', popover: { fixed: true },
title: _t("Add Content"), },
content: _t("The <em>Content</em> menu allows you to create new pages, events, menus, etc."), {
popover: { fixed: true }, element: '.modal #editor_new_event input[type=text]',
}, sampleText: 'Advanced Technical Training',
{ placement: 'right',
element: 'a[data-action=new_event]', title: _t("Create an Event Name"),
placement: 'left', content: _t("Create a name for your new event and click <em>'Continue'</em>. e.g: Technical Training"),
title: _t("New Event"), },
content: _t("Click here to create a new event."), {
popover: { fixed: true }, waitNot: '.modal input[type=text]:not([value!=""])',
}, element: '.modal button.btn-primary',
{ placement: 'right',
element: '.modal #editor_new_event input[type=text]', title: _t("Create Event"),
sampleText: 'Advanced Technical Training', content: _t("Click <em>Continue</em> to create the event."),
placement: 'right', },
title: _t("Create an Event Name"), {
content: _t("Create a name for your new event and click <em>'Continue'</em>. e.g: Technical Training"), waitFor: 'body:has(button[data-action=save]:visible):has(.js_event)',
}, title: _t("New Event Created"),
{ content: _t("This is your new event page. We will edit the event presentation page."),
waitNot: '.modal input[type=text]:not([value!=""])', popover: { next: _t("Continue") },
element: '.modal button.btn-primary', },
placement: 'right', {
title: _t("Create Event"), element: 'button[data-action=snippet]',
content: _t("Click <em>Continue</em> to create the event."), placement: 'bottom',
}, title: _t("Layout your event"),
{ content: _t("Insert blocks to layout the body of your event."),
waitFor: 'body:has(button[data-action=save]:visible):has(.js_event)', popover: { fixed: true },
title: _t("New Event Created"), },
content: _t("This is your new event page. We will edit the event presentation page."), {
popover: { next: _t("Continue") }, snippet: '#snippet_structure .oe_snippet:eq(2)',
}, placement: 'bottom',
{ title: _t("Drag & Drop a block"),
element: 'button[data-action=snippet]', content: _t("Drag the 'Image-Text' block and drop it in your page."),
placement: 'bottom', popover: { fixed: true },
title: _t("Layout your event"), },
content: _t("Insert blocks to layout the body of your event."), {
popover: { fixed: true },
}, element: 'button[data-action=snippet]',
{ placement: 'bottom',
snippet: '#snippet_structure .oe_snippet:eq(2)', title: _t("Layout your event"),
placement: 'bottom', content: _t("Insert another block to your event."),
title: _t("Drag & Drop a block"), popover: { fixed: true },
content: _t("Drag the 'Image-Text' block and drop it in your page."), },
popover: { fixed: true }, {
}, snippet: '#snippet_structure .oe_snippet:eq(4)',
{ placement: 'bottom',
title: _t("Drag & Drop a block"),
element: 'button[data-action=snippet]', content: _t("Drag the 'Text Block' in your event page."),
placement: 'bottom', popover: { fixed: true },
title: _t("Layout your event"), },
content: _t("Insert another block to your event."), {
popover: { fixed: true }, element: 'button[data-action=save]',
}, placement: 'right',
{ title: _t("Save your modifications"),
snippet: '#snippet_structure .oe_snippet:eq(4)', content: _t("Once you click on save, your event is updated."),
placement: 'bottom', popover: { fixed: true },
title: _t("Drag & Drop a block"), },
content: _t("Drag the 'Text Block' in your event page."), {
popover: { fixed: true }, waitFor: 'button[data-action=edit]:visible',
}, element: 'button.btn-danger.js_publish_btn',
{ placement: 'top',
element: 'button[data-action=save]', title: _t("Publish your event"),
placement: 'right', content: _t("Click to publish your event."),
title: _t("Save your modifications"), },
content: _t("Once you click on save, your event is updated."), {
popover: { fixed: true }, waitFor: '.js_publish_management button.js_publish_btn.btn-success:visible',
}, element: '.js_publish_management button[data-toggle="dropdown"]',
{ placement: 'left',
waitFor: 'button[data-action=edit]:visible', title: _t("Customize your event"),
element: 'button.btn-danger.js_publish_btn', content: _t("Click here to customize your event further."),
placement: 'top', },
title: _t("Publish your event"), {
content: _t("Click to publish your event."), element: '.js_publish_management ul>li>a:last:visible',
}, },
{ ]
waitFor: '.js_publish_management button.js_publish_btn.btn-success:visible',
element: '.js_publish_management button[data-toggle="dropdown"]',
placement: 'left',
title: _t("Customize your event"),
content: _t("Click here to customize your event further."),
},
{
element: '.js_publish_management ul>li>a:last:visible',
},
];
return this._super();
}
}); });
}()); }());

View File

@ -2,5 +2,5 @@ import openerp.tests
class TestUi(openerp.tests.HttpCase): class TestUi(openerp.tests.HttpCase):
def test_admin(self): def test_admin(self):
self.phantom_js("/", "openerp.website.Tour.run_test('event')", "openerp.website.Tour") self.phantom_js("/", "openerp.website.Tour.run('event', 'test')", "openerp.website.Tour.tours.event")

View File

@ -3,77 +3,63 @@
var website = openerp.website; var website = openerp.website;
website.Tour.EventSaleTest = website.Tour.extend({ website.Tour.register({
id: 'event_buy_tickets', id: 'event_buy_tickets',
name: "Try to buy tickets for event", name: "Try to buy tickets for event",
path: '/event', path: '/event',
init: function () { mode: 'test',
var self = this; steps: [
self.steps = [ {
{ title: "select event",
title: "select event", element: 'a[href*="/event"]:contains("Conference on Business Applications"):first',
element: 'a[href*="/event"]:contains("Open Days in Los Angeles")', },
{
waitNot: 'a[href*="/event"]:contains("Conference on Business Applications")',
title: "select 2 Standard tickets",
element: 'select:eq(0)',
sampleText: '2',
},
{
title: "select 3 VIP tickets",
waitFor: 'select:eq(0) option:contains(2):selected',
element: 'select:eq(1)',
sampleText: '3',
},
{
title: "Order Now",
waitFor: 'select:eq(1) option:contains(3):selected',
element: '.btn-primary:contains("Order Now")',
},
{
title: "Complete checkout",
waitFor: '#top_menu .my_cart_quantity:contains(5)',
element: 'form[action="/shop/confirm_order"] .btn:contains("Confirm")',
autoComplete: function (tour) {
if ($("input[name='name']").val() === "")
$("input[name='name']").val("website_sale-test-shoptest");
if ($("input[name='email']").val() === "")
$("input[name='email']").val("website_event_sale_test_shoptest@websiteeventsaletest.optenerp.com");
$("input[name='phone']").val("123");
$("input[name='street']").val("123");
$("input[name='city']").val("123");
$("input[name='zip']").val("123");
$("select[name='country_id']").val("21");
}, },
{ },
title: "go to register page", {
waitNot: 'a[href*="/event"]:contains("Functional Webinar")', title: "select payment",
onload: function () { element: '#payment_method label:has(img[title="Wire Transfer"]) input',
// use onload if website_event_track is installed },
if (!$('form:contains("Ticket Type")').size()) { {
window.location.href = $('a[href*="/event"][href*="/register"]').attr("href"); title: "Pay Now",
} waitFor: '#payment_method label:has(input:checked):has(img[title="Wire Transfer"])',
}, element: '.oe_sale_acquirer_button .btn[name="submit"]:visible',
}, },
{ {
title: "select 2 Standard tickets", title: "finish",
element: 'select[name="ticket-1"]', waitFor: '.oe_website_sale:contains("Thank you for your order")',
sampleText: '2', }
}, ]
{
title: "select 3 VIP tickets",
waitFor: 'select[name="ticket-1"] option:contains(2):selected',
element: 'select[name="ticket-2"]',
sampleText: '3',
},
{
title: "Order Now",
waitFor: 'select[name="ticket-2"] option:contains(3):selected',
element: '.btn-primary:contains("Order Now")',
},
{
title: "Complete checkout",
waitFor: '#top_menu .my_cart_quantity:contains(5)',
element: 'form[action="/shop/confirm_order"] .btn:contains("Confirm")',
onload: function (tour) {
if ($("input[name='name']").val() === "")
$("input[name='name']").val("website_sale-test-shoptest");
if ($("input[name='email']").val() === "")
$("input[name='email']").val("website_event_sale_test_shoptest@websiteeventsaletest.optenerp.com");
$("input[name='phone']").val("123");
$("input[name='street']").val("123");
$("input[name='city']").val("123");
$("input[name='zip']").val("123");
$("select[name='country_id']").val("21");
},
},
{
title: "select payment",
element: '#payment_method label:has(img[title="transfer"]) input',
},
{
title: "Pay Now",
waitFor: '#payment_method label:has(input:checked):has(img[title="transfer"])',
element: '.oe_sale_acquirer_button .btn[name="submit"]:visible',
},
{
title: "finish",
waitFor: '.oe_website_sale:contains("Thank you for your order")',
}
];
return this._super();
},
}); });
// for test without editor bar
website.Tour.add(website.Tour.EventSaleTest);
}()); }());

View File

@ -1 +1 @@
#import test_ui import test_ui

View File

@ -1,19 +1,21 @@
import os
import openerp.tests import openerp.tests
inject = [ inject = [
"./../../../website/static/src/js/website.tour.test.js", ("openerp.website.Tour", os.path.join(os.path.dirname(__file__), '../../website/static/src/js/website.tour.js')),
"./../../../website_event_sale/static/src/js/website.tour.event_sale.js", ("openerp.website.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.at_install(False)
@openerp.tests.common.post_install(True) @openerp.tests.common.post_install(True)
class TestUi(openerp.tests.HttpCase): class TestUi(openerp.tests.HttpCase):
def test_admin(self): def test_admin(self):
self.phantom_js("/", "openerp.website.Tour.run_test('event_buy_tickets')", "openerp.website.Tour", inject=inject) self.phantom_js("/", "openerp.website.Tour.run('event_buy_tickets', 'test')", "openerp.website.Tour.tours.event_buy_tickets", inject=inject)
def test_demo(self): def test_demo(self):
self.phantom_js("/", "openerp.website.Tour.run_test('event_buy_tickets')", "openerp.website.Tour", login="demo", password="demo", inject=inject); self.phantom_js("/", "openerp.website.Tour.run('event_buy_tickets', 'test')", "openerp.website.Tour.tours.event_buy_tickets", login="demo", password="demo", inject=inject);
def test_public(self): def test_public(self):
self.phantom_js("/", "openerp.website.Tour.run_test('event_buy_tickets')", "openerp.website.Tour", login=None, inject=inject); self.phantom_js("/", "openerp.website.Tour.run('event_buy_tickets', 'test')", "openerp.website.Tour.tours.event_buy_tickets", login=None, inject=inject);

View File

@ -38,7 +38,6 @@
<xpath expr='//t[@name="layout_head"]' position="before"> <xpath expr='//t[@name="layout_head"]' position="before">
<link rel='stylesheet' href='/website/static/src/css/snippets.css'/> <link rel='stylesheet' href='/website/static/src/css/snippets.css'/>
<link rel='stylesheet' href='/website/static/src/css/editor.css'/> <link rel='stylesheet' href='/website/static/src/css/editor.css'/>
<link rel='stylesheet' href='/website/static/lib/bootstrap-tour/bootstrap-tour.css'/>
<link rel="stylesheet" href="/web/static/lib/select2/select2.css"/> <link rel="stylesheet" href="/web/static/lib/select2/select2.css"/>
@ -50,7 +49,6 @@
<script t-if="not translatable" type="text/javascript" src="/website/static/lib/ace/ace.js"></script> <script t-if="not translatable" type="text/javascript" src="/website/static/lib/ace/ace.js"></script>
<script type="text/javascript" src="/website/static/lib/vkbeautify/vkbeautify.0.99.00.beta.js"></script> <script type="text/javascript" src="/website/static/lib/vkbeautify/vkbeautify.0.99.00.beta.js"></script>
<script type="text/javascript" src="/web/static/lib/jquery.ui/js/jquery-ui-1.9.1.custom.js"></script> <script type="text/javascript" src="/web/static/lib/jquery.ui/js/jquery-ui-1.9.1.custom.js"></script>
<script type="text/javascript" src="/website/static/lib/bootstrap-tour/bootstrap-tour.js"></script>
<!-- mutation observers shim backed by mutation events (8 < IE < 11, Safari < 6, FF < 14, Chrome < 17) --> <!-- mutation observers shim backed by mutation events (8 < IE < 11, Safari < 6, FF < 14, Chrome < 17) -->
<script type="text/javascript" src="/website/static/lib//jquery.mjs.nestedSortable/jquery.mjs.nestedSortable.js"></script> <script type="text/javascript" src="/website/static/lib//jquery.mjs.nestedSortable/jquery.mjs.nestedSortable.js"></script>
<script type="text/javascript" src="/website/static/lib/MutationObservers/test/sidetable.js"></script> <script type="text/javascript" src="/website/static/lib/MutationObservers/test/sidetable.js"></script>

View File

@ -3,92 +3,87 @@
var website = openerp.website; var website = openerp.website;
website.Tour.ShopTest = website.Tour.extend({ website.Tour.register({
id: 'shop_buy_product', id: 'shop_buy_product',
name: "Try to buy products", name: "Try to buy products",
path: '/shop', path: '/shop',
init: function () { mode: 'test',
var self = this; steps: [
self.steps = [ {
{ title: "select ipod",
title: "select ipod", element: '.oe_product_cart a:contains("iPod")',
element: '.oe_product_cart a:contains("iPod")', },
{
title: "select ipod 32Go",
element: 'input[name="product_id"]:not([checked])',
},
{
title: "click on add to cart",
waitFor: 'input[name="product_id"]:eq(1)[checked]',
element: 'form[action="/shop/add_cart"] .btn',
},
{
title: "add suggested",
element: 'form[action="/shop/add_cart"] .btn-link:contains("Add to Cart")',
},
{
title: "add one more iPod",
waitFor: '.my_cart_quantity:contains(2)',
element: '#mycart_products tr:contains("iPod: 32 Gb") a.js_add_cart_json:eq(1)',
},
{
title: "remove Headphones",
waitFor: '#mycart_products tr:contains("iPod: 32 Gb") input.js_quantity[value=2]',
element: '#mycart_products tr:contains("Apple In-Ear Headphones") a.js_add_cart_json:first',
},
{
title: "set one iPod",
waitNot: '#mycart_products tr:contains("Apple In-Ear Headphones")',
element: '#mycart_products input.js_quantity',
sampleText: '1',
},
{
title: "go to checkout",
waitFor: '#mycart_products input.js_quantity[value=1]',
element: 'a[href="/shop/checkout"]',
},
{
title: "test with input error",
element: 'form[action="/shop/confirm_order"] .btn:contains("Confirm")',
onload: function (tour) {
$("input[name='phone']").val("");
}, },
{ },
title: "select ipod 32Go", {
element: 'input[name="product_id"]:not([checked])', title: "test without input error",
waitFor: 'form[action="/shop/confirm_order"] .has-error',
element: 'form[action="/shop/confirm_order"] .btn:contains("Confirm")',
onload: function (tour) {
if ($("input[name='name']").val() === "")
$("input[name='name']").val("website_sale-test-shoptest");
if ($("input[name='email']").val() === "")
$("input[name='email']").val("website_sale_test_shoptest@websitesaletest.optenerp.com");
$("input[name='phone']").val("123");
$("input[name='street']").val("123");
$("input[name='city']").val("123");
$("input[name='zip']").val("123");
$("select[name='country_id']").val("21");
}, },
{ },
title: "click on add to cart", {
waitFor: 'input[name="product_id"]:eq(1)[checked]', title: "select payment",
element: 'form[action="/shop/add_cart"] .btn', element: '#payment_method label:has(img[title="Wire Transfer"]) input',
}, },
{ {
title: "add suggested", title: "Pay Now",
element: 'form[action="/shop/add_cart"] .btn-link:contains("Add to Cart")', waitFor: '#payment_method label:has(input:checked):has(img[title="Wire Transfer"])',
}, element: '.oe_sale_acquirer_button .btn[name="submit"]:visible',
{ },
title: "add one more iPod", {
waitFor: '.my_cart_quantity:contains(2)', title: "finish",
element: '#mycart_products tr:contains("iPod: 32 Gb") a.js_add_cart_json:eq(1)', waitFor: '.oe_website_sale:contains("Thank you for your order")',
}, }
{ ]
title: "remove Headphones",
waitFor: '#mycart_products tr:contains("iPod: 32 Gb") input.js_quantity[value=2]',
element: '#mycart_products tr:contains("Apple In-Ear Headphones") a.js_add_cart_json:first',
},
{
title: "set one iPod",
waitNot: '#mycart_products tr:contains("Apple In-Ear Headphones")',
element: '#mycart_products input.js_quantity',
sampleText: '1',
},
{
title: "go to checkout",
waitFor: '#mycart_products input.js_quantity[value=1]',
element: 'a[href="/shop/checkout"]',
},
{
title: "test with input error",
element: 'form[action="/shop/confirm_order"] .btn:contains("Confirm")',
onload: function (tour) {
$("input[name='phone']").val("");
},
},
{
title: "test without input error",
waitFor: 'form[action="/shop/confirm_order"] .has-error',
element: 'form[action="/shop/confirm_order"] .btn:contains("Confirm")',
onload: function (tour) {
if ($("input[name='name']").val() === "")
$("input[name='name']").val("website_sale-test-shoptest");
if ($("input[name='email']").val() === "")
$("input[name='email']").val("website_sale_test_shoptest@websitesaletest.optenerp.com");
$("input[name='phone']").val("123");
$("input[name='street']").val("123");
$("input[name='city']").val("123");
$("input[name='zip']").val("123");
$("select[name='country_id']").val("21");
},
},
{
title: "select payment",
element: '#payment_method label:has(img[title="Wire Transfer"]) input',
},
{
title: "Pay Now",
waitFor: '#payment_method label:has(input:checked):has(img[title="Wire Transfer"])',
element: '.oe_sale_acquirer_button .btn[name="submit"]:visible',
},
{
title: "finish",
waitFor: '.oe_website_sale:contains("Thank you for your order")',
}
];
return this._super();
},
}); });
// for test without editor bar
website.Tour.add(website.Tour.ShopTest);
}()); }());

View File

@ -4,125 +4,114 @@
var website = openerp.website; var website = openerp.website;
var _t = openerp._t; var _t = openerp._t;
website.EditorBar.include({ website.Tour.register({
start: function () {
this.registerTour(new website.Tour.Shop(this));
return this._super();
},
});
website.Tour.Shop = website.Tour.extend({
id: 'shop', id: 'shop',
name: "Create a product", name: _t("Create a product"),
testPath: '/shop', steps: [
init: function () { {
var self = this; title: _t("Welcome to your shop"),
self.steps = [ content: _t("You successfully installed the e-commerce. This guide will help you to create your product and promote your sales."),
{ popover: { next: _t("Start Tutorial"), end: _t("Skip It") },
title: _t("Welcome to your shop"), },
content: _t("You successfully installed the e-commerce. This guide will help you to create your product and promote your sales."), {
popover: { next: _t("Start Tutorial"), end: _t("Skip It") }, element: '#content-menu-button',
}, placement: 'left',
{ title: _t("Create your first product"),
element: '#content-menu-button', content: _t("Click here to add a new product."),
placement: 'left', popover: { fixed: true },
title: _t("Create your first product"), },
content: _t("Click here to add a new product."), {
popover: { fixed: true }, element: 'a[data-action=new_product]',
}, placement: 'left',
{ title: _t("Create a new product"),
element: 'a[data-action=new_product]', content: _t("Select 'New Product' to create it and manage its properties to boost your sales."),
placement: 'left', popover: { fixed: true },
title: _t("Create a new product"), },
content: _t("Select 'New Product' to create it and manage its properties to boost your sales."), {
popover: { fixed: true }, element: '.modal #editor_new_product input[type=text]',
}, sampleText: 'New Product',
{ placement: 'right',
element: '.modal #editor_new_product input[type=text]', title: _t("Choose name"),
sampleText: 'New Product', content: _t("Enter a name for your new product then click 'Continue'."),
placement: 'right', },
title: _t("Choose name"), {
content: _t("Enter a name for your new product then click 'Continue'."), waitNot: '.modal input[type=text]:not([value!=""])',
}, element: '.modal button.btn-primary',
{ placement: 'right',
waitNot: '.modal input[type=text]:not([value!=""])', title: _t("Create Product"),
element: '.modal button.btn-primary', content: _t("Click <em>Continue</em> to create the product."),
placement: 'right', },
title: _t("Create Product"), {
content: _t("Click <em>Continue</em> to create the product."), waitFor: 'body:has(button[data-action=save]:visible):has(.js_sale)',
}, title: _t("New product created"),
{ content: _t("This page contains all the information related to the new product."),
waitFor: 'body:has(button[data-action=save]:visible):has(.js_sale)', popover: { next: _t("Continue") },
title: _t("New product created"), },
content: _t("This page contains all the information related to the new product."), {
popover: { next: _t("Continue") }, element: '.product_price .oe_currency_value',
}, sampleText: '20.50',
{ placement: 'left',
element: '.product_price .oe_currency_value', title: _t("Change the price"),
sampleText: '20.50', content: _t("Edit the price of this product by clicking on the amount."),
placement: 'left', },
title: _t("Change the price"),
content: _t("Edit the price of this product by clicking on the amount."),
},
{
waitNot: '.product_price .oe_currency_value:containsExact(1.00)',
element: '#wrap img.product_detail_img',
placement: 'top',
title: _t("Update image"),
content: _t("Click here to set an image describing your product."),
},
{
element: 'img[alt=ipad]',
placement: 'top',
title: _t("Select an Image"),
content: _t("Let's select an ipad image."),
},
{
waitFor: '.media_selected img[alt=ipad]',
element: '.modal-content button.save',
placement: 'top',
title: _t("Save this Image"),
content: _t("Click on save to add the image to the product decsription."),
},
{
waitNot: '.modal-content:visible',
element: 'button[data-action=snippet]',
placement: 'bottom',
title: _t("Describe the Product"),
content: _t("Insert blocks like text-image, or gallery to fully describe the product."),
popover: { fixed: true },
},
{
snippet: '#snippet_structure .oe_snippet:eq(7)',
placement: 'bottom',
title: _t("Drag & Drop a block"),
content: _t("Drag the 'Big Picture' block and drop it in your page."),
popover: { fixed: true },
},
{
element: 'button[data-action=save]',
placement: 'right',
title: _t("Save your modifications"),
content: _t("Once you click on save, your product is updated."),
popover: { fixed: true },
},
{ {
waitFor: '#website-top-navbar button[data-action="edit"]:visible', waitNot: '.product_price .oe_currency_value:containsExact(1.00)',
element: '.js_publish_management button.js_publish_btn.btn-danger', element: '#wrap img.product_detail_img',
placement: 'top', placement: 'top',
title: _t("Publish your product"), title: _t("Update image"),
content: _t("Click to publish your product so your customers can see it."), content: _t("Click here to set an image describing your product."),
}, },
{ {
waitFor: '.js_publish_management button.js_publish_btn.btn-success:visible', element: 'img[alt=ipad]',
title: _t("Congratulations"), placement: 'top',
content: _t("Congratulations! You just created and published your first product."), title: _t("Select an Image"),
popover: { next: _t("Close Tutorial") }, content: _t("Let's select an ipad image."),
}, },
]; {
return this._super(); waitFor: '.media_selected img[alt=ipad]',
} element: '.modal-content button.save',
placement: 'top',
title: _t("Save this Image"),
content: _t("Click on save to add the image to the product decsription."),
},
{
waitNot: '.modal-content:visible',
element: 'button[data-action=snippet]',
placement: 'bottom',
title: _t("Describe the Product"),
content: _t("Insert blocks like text-image, or gallery to fully describe the product."),
popover: { fixed: true },
},
{
snippet: '#snippet_structure .oe_snippet:eq(7)',
placement: 'bottom',
title: _t("Drag & Drop a block"),
content: _t("Drag the 'Big Picture' block and drop it in your page."),
popover: { fixed: true },
},
{
element: 'button[data-action=save]',
placement: 'right',
title: _t("Save your modifications"),
content: _t("Once you click on save, your product is updated."),
popover: { fixed: true },
},
{
waitFor: '#website-top-navbar button[data-action="edit"]:visible',
element: '.js_publish_management button.js_publish_btn.btn-danger',
placement: 'top',
title: _t("Publish your product"),
content: _t("Click to publish your product so your customers can see it."),
},
{
waitFor: '.js_publish_management button.js_publish_btn.btn-success:visible',
title: _t("Congratulations"),
content: _t("Congratulations! You just created and published your first product."),
popover: { next: _t("Close Tutorial") },
},
]
}); });
}()); }());

View File

@ -11,13 +11,13 @@ inject = [
@openerp.tests.common.post_install(True) @openerp.tests.common.post_install(True)
class TestUi(openerp.tests.HttpCase): class TestUi(openerp.tests.HttpCase):
def test_01_admin_shop_tour(self): def test_01_admin_shop_tour(self):
self.phantom_js("/", "openerp.website.Tour.run_test('shop')", "openerp.website.Tour.Shop", login="admin") self.phantom_js("/", "openerp.website.Tour.run('shop', 'test')", "openerp.website.Tour.tours.shop", login="admin")
def test_02_admin_checkout(self): def test_02_admin_checkout(self):
self.phantom_js("/", "openerp.website.Tour.run_test('shop_buy_product')", "openerp.website.Tour.ShopTest", 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)
def test_03_demo_checkout(self): def test_03_demo_checkout(self):
self.phantom_js("/", "openerp.website.Tour.run_test('shop_buy_product')", "openerp.website.Tour.ShopTest", login="demo", inject=inject) self.phantom_js("/", "openerp.website.Tour.run('shop_buy_product', 'test')", "openerp.website.Tour.tours.shop_buy_product", login="demo", inject=inject)
def test_04_public_checkout(self): def test_04_public_checkout(self):
self.phantom_js("/", "openerp.website.Tour.run_test('shop_buy_product')", "openerp.website.Tour.ShopTest", inject=inject) self.phantom_js("/", "openerp.website.Tour.run('shop_buy_product', 'test')", "openerp.website.Tour.tours.shop_buy_product", inject=inject)