[REF] website, web, *: move part of website into web

* im_livechat, survey, website_forum

When website is installed, the JavaScript code has access to utilities
to define improved widgets which are automatically attached to existing
DOM elements on page load. Those mechanics are more and more needed in
portal which does not depend on website (as it is website which depends
on portal). This commit moves part of website JS to web, so that it can
be used by all 'frontend' apps whose only common dependency is web
(portal, survey, auth_signup, ...).

This PR also questioned several other similar issues which will be
handled later like:

- website should maybe not depends on portal but only on http_routing

- part of portal should be moved to http_routing and http_routing
  should be renamed (especially for frontend layouts, etc) so that apps
  like survey can reuse some common code

Part of https://github.com/odoo/odoo/pull/29442
task-1932066
This commit is contained in:
qsm-odoo
2019-02-26 17:02:59 +00:00
parent dd2ba3c4f3
commit 7b960c66bb
14 changed files with 659 additions and 534 deletions
@@ -147,7 +147,9 @@
<script type="text/javascript" src="/web/static/src/js/core/local_storage.js"></script>
<script type="text/javascript" src="/web/static/src/js/core/ram_storage.js"></script>
<script type="text/javascript" src="/web/static/src/js/core/abstract_storage_service.js"></script>
<script type="text/javascript" src="/web/static/src/js/public/public_root_instance.js"></script>
<script type="text/javascript" src="/web/static/src/js/public/public_root.js"/>
<script type="text/javascript" src="/web/static/src/js/public/public_root_instance.js"/>
<script type="text/javascript" src="/web/static/src/js/public/public_widget.js"/>
<script type="text/javascript" src="/web/static/src/js/services/ajax_service.js"></script>
<script type="text/javascript" src="/web/static/src/js/services/local_storage_service.js"></script>
<!-- Bus, Mail, Livechat -->
+3 -1
View File
@@ -5,7 +5,9 @@
<template id="survey_assets" name="Survey Results assets">
<script src="/web/static/lib/nvd3/d3.v3.js"></script>
<script src="/web/static/lib/nvd3/nv.d3.js"></script>
<script type="text/javascript" src="/web/static/src/js/public/public_root_instance.js"></script>
<script type="text/javascript" src="/web/static/src/js/public/public_root.js"/>
<script type="text/javascript" src="/web/static/src/js/public/public_root_instance.js"/>
<script type="text/javascript" src="/web/static/src/js/public/public_widget.js"/>
<script type="text/javascript" src="/web/static/src/js/services/session.js"></script>
<script type="text/javascript" src="/web/static/src/js/fields/field_utils.js"></script>
@@ -0,0 +1,289 @@
odoo.define('web.public.root', function (require) {
'use strict';
var ajax = require('web.ajax');
var ServiceProviderMixin = require('web.ServiceProviderMixin');
var session = require('web.session');
var utils = require('web.utils');
var publicWidget = require('web.public.widget');
var publicRootRegistry = new publicWidget.RootWidgetRegistry();
// Load localizations outside the PublicRoot to not wait for DOM ready (but
// wait for them in PublicRoot)
function getLang() {
var html = document.documentElement;
return (html.getAttribute('lang') || 'en_US').replace('-', '_');
}
var lang = utils.get_cookie('frontend_lang') || getLang(); // FIXME the cookie value should maybe be in the ctx?
var localeDef = ajax.loadJS('/web/webclient/locale/' + lang.replace('-', '_'));
/**
* Element which is designed to be unique and that will be the top-most element
* in the widget hierarchy. So, all other widgets will be indirectly linked to
* this Class instance. Its main role will be to retrieve RPC demands from its
* children and handle them.
*/
var PublicRoot = publicWidget.RootWidget.extend(ServiceProviderMixin, {
events: _.extend({}, publicWidget.RootWidget.prototype.events || {}, {
'submit .js_website_submit_form': '_onWebsiteFormSubmit',
'click .js_disable_on_click': '_onDisableOnClick',
}),
custom_events: _.extend({}, publicWidget.RootWidget.prototype.custom_events || {}, {
'animation_start_demand': '_onAnimationStartDemand',
'animation_stop_demand': '_onAnimationStopDemand',
'context_get': '_onContextGet',
'main_object_request': '_onMainObjectRequest',
}),
/**
* @constructor
*/
init: function () {
this._super.apply(this, arguments);
ServiceProviderMixin.init.call(this);
this.animations = [];
},
/**
* @override
*/
willStart: function () {
// TODO would be even greater to wait for localeDef only when necessary
return $.when(
this._super.apply(this, arguments),
session.is_bound,
localeDef
);
},
/**
* @override
*/
start: function () {
var defs = [this._super.apply(this, arguments)];
// Animations
defs.push(this._startAnimations());
// Display image thumbnail
this.$(".o_image[data-mimetype^='image']").each(function () {
var $img = $(this);
if (/gif|jpe|jpg|png/.test($img.data('mimetype')) && $img.data('src')) {
$img.css('background-image', "url('" + $img.data('src') + "')");
}
});
// Auto scroll
if (window.location.hash.indexOf("scrollTop=") > -1) {
this.el.scrollTop = +window.location.hash.match(/scrollTop=([0-9]+)/)[1];
}
// Fix for IE:
if ($.fn.placeholder) {
$('input, textarea').placeholder();
}
return $.when.apply($, defs);
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* Automatically adds the global context to RPCs.
*
* @override
*/
_call_service: function (ev) {
if (ev.data.service === 'ajax' && ev.data.method === 'rpc') {
var route = ev.data.args[0];
if (_.str.startsWith(route, '/web/dataset/call_kw/')) {
var params = ev.data.args[1];
var options = ev.data.args[2];
params.kwargs.context = _.extend({}, this._getContext(), params.kwargs.context || {});
if (options) {
params.kwargs.context = _.omit(params.kwargs.context, options.noContextKeys);
ev.data.args[2] = _.omit(options, 'noContextKeys');
}
params.kwargs.context = JSON.parse(JSON.stringify(params.kwargs.context));
}
}
return ServiceProviderMixin._call_service.apply(this, arguments);
},
/**
* Retrieves the global context of the public environment. This is the
* context which is automatically added to each RPC.
*
* @private
* @param {Object} [context]
* @returns {Object}
*/
_getContext: function (context) {
return _.extend({
'lang': getLang(),
}, context || {});
},
/**
* Retrieves the global context of the public environment (as
* @see _getContext) but with extra informations that would be useless to
* send with each RPC.
*
* @private
* @param {Object} [context]
* @returns {Object}
*/
_getExtraContext: function (context) {
return this._getContext(context);
},
/**
* As the root instance is designed to be unique, the associated
* registry has been instantiated outside of the class and is simply
* returned here.
*
* @private
* @override
*/
_getRegistry: function () {
return publicRootRegistry;
},
/**
* Creates an PublicWidget instance for each DOM element which matches the
* `selector` key of one of the registered widgets
* (@see PublicWidget.selector).
*
* @private
* @param {jQuery} [$from]
* only initialize the animations whose `selector` matches the
* element or one of its descendant (default to the wrapwrap element)
* @param {Object} [options]
* @returns {Deferred}
*/
_startAnimations: function ($from, options) {
var self = this;
if ($from === undefined) {
$from = this.$('#wrapwrap');
}
if (options === undefined) {
options = {};
}
this._stopAnimations($from);
var defs = _.map(publicWidget.registry, function (WebsiteWidget) {
var selector = WebsiteWidget.prototype.selector || '';
var $target = $from.find(selector).addBack(selector);
var defs = _.map($target, function (el) {
var animation = new WebsiteWidget(self, options);
self.animations.push(animation);
return animation.attachTo($(el));
});
return $.when.apply($, defs);
});
return $.when.apply($, defs);
},
/**
* Destroys all registered widget instances. Website would need this before
* saving while in edition mode for example.
*
* @private
* @param {jQuery} [$from]
* only stop the animations linked to the given element(s) or one of
* its descendants
*/
_stopAnimations: function ($from) {
var removedAnimations = _.map(this.animations, function (animation) {
if (!$from
|| $from.filter(animation.el).length
|| $from.find(animation.el).length) {
animation.destroy();
return animation;
}
return null;
});
this.animations = _.difference(this.animations, removedAnimations);
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* Called when the root is notified that the animations have to be
* (re)started.
*
* @private
* @param {OdooEvent} ev
*/
_onAnimationStartDemand: function (ev) {
this._startAnimations(ev.data.$target, ev.data.options)
.done(ev.data.onSuccess)
.fail(ev.data.onFailure);
},
/**
* Called when the root is notified that the animations have to be
* stopped.
*
* @private
* @param {OdooEvent} ev
*/
_onAnimationStopDemand: function (ev) {
this._stopAnimations(ev.data.$target);
},
/**
* Called when someone asked for the global public context.
*
* @private
* @param {OdooEvent} ev
*/
_onContextGet: function (ev) {
if (ev.data.extra) {
ev.data.callback(this._getExtraContext(ev.data.context));
} else {
ev.data.callback(this._getContext(ev.data.context));
}
},
/**
* Checks information about the page main object.
*
* @private
* @param {OdooEvent} ev
*/
_onMainObjectRequest: function (ev) {
var repr = $('html').data('main-object');
var m = repr.match(/(.+)\((\d+),(.*)\)/);
ev.data.callback({
model: m[1],
id: m[2] | 0,
});
},
/**
* @todo review
* @private
*/
_onWebsiteFormSubmit: function (ev) {
var $buttons = $(ev.currentTarget).find('button[type="submit"], a.a-submit');
_.each($buttons, function (btn) {
var $btn = $(btn);
$btn.attr('data-loading-text', '<i class="fa fa-spinner fa-spin"></i> ' + $(btn).text());
$btn.button('loading');
});
},
/**
* Called when the root is notified that the button should be
* disabled after the first click.
*
* @private
* @param {Event} ev
*/
_onDisableOnClick: function (ev) {
$(ev.currentTarget).addClass('disabled');
},
});
return {
PublicRoot: PublicRoot,
publicRootRegistry: publicRootRegistry,
};
});
@@ -1,23 +1,17 @@
odoo.define('root.widget', function (require) {
"use strict";
'use strict';
var ServiceProviderMixin = require('web.ServiceProviderMixin');
var Widget = require('web.Widget');
require('web.dom_ready');
var rootData = require('web.public.root');
/**
* This is the root widget in the frontend bundle when website is not installed.
*
* This widget is important, because the tour manager needs a root widget in
* order to work. The root widget must be a service provider with the ajax
* service, so that the tour manager can let the server know when tours have
* been consumed.
*/
var PublicRootWidget = Widget.extend(ServiceProviderMixin, {
init: function () {
this._super.apply(this, arguments);
ServiceProviderMixin.init.call(this);
}
var publicRoot = new rootData.PublicRoot(null);
return publicRoot.attachTo(document.body).then(function () {
return publicRoot;
});
return new PublicRootWidget();
});
@@ -1,6 +1,10 @@
odoo.define('web_editor.root_widget', function (require) {
odoo.define('web.public.widget', function (require) {
'use strict';
/**
* Provides a way to start JS code for snippets' initialization and animations.
*/
var Class = require('web.Class');
var dom = require('web.dom');
var mixins = require('web.mixins');
@@ -12,11 +16,13 @@ var Widget = require('web.Widget');
* to internal DOM elements once it is started. The widgets to instantiate are
* known thanks to a linked registry which contains info about the widget
* classes and jQuery selectors to use to find the elements to attach them to.
*
* @todo Merge with 'PublicWidget' ?
*/
var RootWidget = Widget.extend({
custom_events: _.extend({}, Widget.prototype.custom_events || {}, {
registry_update: '_onRegistryUpdate',
get_session: '_onGetSession',
'registry_update': '_onRegistryUpdate',
'get_session': '_onGetSession',
}),
/**
* @constructor
@@ -160,8 +166,219 @@ var RootWidgetRegistry = Class.extend(mixins.EventDispatcherMixin, {
},
});
//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
/**
* Provides a way for executing code once a website DOM element is loaded in the
* dom.
*/
var PublicWidget = Widget.extend({
/**
* The selector attribute, if defined, allows to automatically create an
* instance of this animation on page load for each DOM element which
* matches this selector. The `Animation.$target` element will then be that
* particular DOM element. This should be the main way of instantiating
* `Animation` elements.
*
* @todo do not make this part of the Widget but rather an info to give when
* registering the widget.
*/
selector: false,
/**
* Extension of @see Widget.events
*
* A description of the event handlers to bind/delegate once the widget
* has been rendered::
*
* 'click .hello .world': 'async _onHelloWorldClick',
* _^_ _^_ _^_ _^_
* | | | |
* | (Optional) jQuery | Handler method name
* | delegate selector |
* | |_ (Optional) space separated options
* | * async: use the automatic system
* |_ Event name with making handlers promise-ready (see
* potential jQuery makeButtonHandler, makeAsyncHandler)
* namespaces
*
* Note: the values may be replaced by a function declaration. This is
* however a deprecated behavior.
*
* @type {Object}
*/
events: {},
/**
* Acts as @see Widget.events except that the events are only binded if the
* Animation instance is instanciated in edit mode.
*/
edit_events: {},
/**
* Acts as @see Widget.events except that the events are only binded if the
* Animation instance is instanciated in readonly mode.
*/
read_events: {},
/**
* Initializes the events that will need to be binded according to the
* given mode.
*
* @constructor
* @param {Object} parent
* @param {Object} options
* @param {boolean} options.editableMode - true if the page is in edition mode
*/
init: function (parent, options) {
this._super.apply(this, arguments);
// Note that the notion of editable mode is kept here for now even
// though nothing can be edited here. This allows to have a readable
// code for frontend widgets and mainly to prevent having loading
// problems an "include" could have caused.
this.options = options || {};
this.editableMode = this.options.editableMode || false;
if (this.editableMode) {
this.events = _.extend({}, this.events || {}, this.edit_events || {});
} else {
this.events = _.extend({}, this.events || {}, this.read_events || {});
}
},
/**
* Destroys the animation and basically restores the target to the state it
* was before the start method was called (unlike standard widget, the
* associated $el DOM is not removed).
*
* Also stops animation effects and destroys them if any.
*/
destroy: function () {
if (this.selector) {
var $oldel = this.$el;
// The difference with the default behavior is that we unset the
// associated element first so that:
// 1) its events are unbinded
// 2) it is not removed from the DOM
this.setElement(null);
}
this._super.apply(this, arguments);
if (this.selector) {
// Reassign the variables afterwards to allow extensions to use them
// after calling the _super method
this.$el = $oldel;
this.el = $oldel[0];
this.$target = this.$el;
this.target = this.el;
}
},
/**
* @override
*/
setElement: function () {
this._super.apply(this, arguments);
if (this.selector) {
this.$target = this.$el;
this.target = this.el;
}
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* @see this.events
* @override
*/
_delegateEvents: function () {
var self = this;
var originalEvents = this.events;
var events = {};
_.each(this.events, function (method, event) {
// If the method is a function, use the default Widget system
if (typeof method !== 'string') {
events[event] = method;
return;
}
// If the method is only a function name without options, use the
// default Widget system
var methodOptions = method.split(' ');
if (methodOptions.length <= 1) {
events[event] = method;
return;
}
// If the method has no meaningful options, use the default Widget
// system
var isAsync = _.contains(methodOptions, 'async');
if (!isAsync) {
events[event] = method;
return;
}
method = self.proxy(methodOptions[methodOptions.length - 1]);
if (_.str.startsWith(event, 'click')) {
// Protect click handler to be called multiple times by
// mistake by the user and add a visual disabling effect
// for buttons.
method = dom.makeButtonHandler(method);
} else {
// Protect all handlers to be recalled while the previous
// async handler call is not finished.
method = dom.makeAsyncHandler(method);
}
events[event] = method;
});
this.events = events;
this._super.apply(this, arguments);
this.events = originalEvents;
},
/**
* @private
* @param {boolean} [extra=false]
* @param {Object} [extraContext]
* @returns {Object}
*/
_getContext: function (extra, extraContext) {
var context;
this.trigger_up('context_get', {
extra: extra || false,
context: extraContext,
callback: function (ctx) {
context = ctx;
},
});
return context;
},
});
//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
/**
* The registry object contains the list of available animations.
*/
var registry = {};
/**
* This is a fix for apple device (<= IPhone 4, IPad 2)
* Standard bootstrap requires data-toggle='collapse' element to be <a/> tags.
* Unfortunatly one snippet uses a <div/> tag instead. The fix forces an empty
* click handler on these div, which allows standard bootstrap to work.
*
* This should be removed in a future odoo snippets refactoring.
*/
registry._fixAppleCollapse = PublicWidget.extend({
selector: 'div[data-toggle="collapse"]',
events: {
'click': function () {},
},
});
return {
RootWidget: RootWidget,
RootWidgetRegistry: RootWidgetRegistry,
Widget: PublicWidget,
registry: registry,
};
});
+3 -1
View File
@@ -23,8 +23,10 @@
<link rel="stylesheet" type="text/scss" href="/web/static/src/scss/layout_boxed.scss"/>
<link rel="stylesheet" type="text/scss" href="/web/static/src/scss/layout_clean.scss"/>
<script type="text/javascript" src="/web/static/src/js/public/public_root_instance.js"/>
<script type="text/javascript" src="/web/static/src/js/services/session.js"/>
<script type="text/javascript" src="/web/static/src/js/public/public_root.js"/>
<script type="text/javascript" src="/web/static/src/js/public/public_root_instance.js"/>
<script type="text/javascript" src="/web/static/src/js/public/public_widget.js"/>
<script type="text/javascript" src="/web/static/src/js/report/utils.js"/>
<script type="text/javascript" src='/web/static/src/js/report/report.js' />
</template>
+2 -1
View File
@@ -340,7 +340,8 @@
<t t-call="web._assets_bootstrap"/>
<link rel="stylesheet" type="text/scss" href="/web/static/src/scss/navbar_mobile.scss"/>
<script type="text/javascript" src="/web/static/src/js/services/session.js"></script>
<script type="text/javascript" src="/web/static/src/js/services/session.js"/>
<script type="text/javascript" src="/web/static/src/js/public/public_root.js"/>
<script type="text/javascript" src="/web/static/src/js/public/public_root_instance.js"/>
<script type="text/javascript" src="/web/static/src/js/public/public_widget.js"/>
</template>
@@ -2,7 +2,7 @@ odoo.define('website.content.lazy_template_call', function (require) {
'use strict';
var Widget = require('web.Widget');
var websiteRootData = require('website.WebsiteRoot');
var websiteRootData = require('website.root');
var LazyTemplateRenderer = Widget.extend({
/**
@@ -7,10 +7,9 @@ odoo.define('website.content.snippets.animation', function (require) {
var Class = require('web.Class');
var core = require('web.core');
var dom = require('web.dom');
var mixins = require('web.mixins');
var utils = require('web.utils');
var Widget = require('web.Widget');
var publicWidget = require('web.public.widget');
var qweb = core.qweb;
@@ -20,16 +19,18 @@ window.requestAnimationFrame = window.requestAnimationFrame
|| window.webkitRequestAnimationFrame
|| window.mozRequestAnimationFrame
|| window.msRequestAnimationFrame
|| window.oRequestAnimationFrame
|| function (callback) { setTimeout(callback, 10); };
|| window.oRequestAnimationFrame;
window.cancelAnimationFrame = window.cancelAnimationFrame
|| window.webkitCancelAnimationFrame
|| window.mozCancelAnimationFrame
|| window.msCancelAnimationFrame
|| window.oCancelAnimationFrame
|| function (id) { clearTimeout(id); };
|| window.oCancelAnimationFrame;
if (!window.performance || !window.performance.now) {
window.performance = {now: function () { return Date.now(); }};
window.performance = {
now: function () {
return Date.now();
}
};
}
/**
@@ -74,10 +75,24 @@ var AnimationEffect = Class.extend(mixins.ParentedMixin, {
this._updateCallback = updateCallback;
this.startEvents = startEvents || 'scroll';
this.$startTarget = $($startTarget || window);
this._getStateCallback = options.getStateCallback
|| ((this.startEvents === 'scroll' && this.$startTarget[0] === window) ? function () { return window.pageYOffset; } : false)
|| ((this.startEvents === 'resize' && this.$startTarget[0] === window) ? function () { return {width: window.innerWidth, height: window.innerHeight}; } : false)
|| function () { return undefined; };
if (options.getStateCallback) {
this._getStateCallback = options.getStateCallback;
} else if (this.startEvents === 'scroll' && this.$startTarget[0] === window) {
this._getStateCallback = function () {
return window.pageYOffset;
};
} else if (this.startEvents === 'resize' && this.$startTarget[0] === window) {
this._getStateCallback = function () {
return {
width: window.innerWidth,
height: window.innerHeight,
};
};
} else {
this._getStateCallback = function () {
return undefined;
};
}
this.endEvents = options.endEvents || false;
this.$endTarget = options.$endTarget ? $(options.$endTarget) : this.$startTarget;
@@ -88,7 +103,7 @@ var AnimationEffect = Class.extend(mixins.ParentedMixin, {
this._uid = '_animationEffect' + _.uniqueId();
this.startEvents = _processEvents(this.startEvents, this._uid);
if (this.endEvents) {
this.endEvents = _processEvents(this.endEvents, this._uid);
this.endEvents = _processEvents(this.endEvents, this._uid);
}
function _processEvents(events, namespace) {
@@ -181,7 +196,9 @@ var AnimationEffect = Class.extend(mixins.ParentedMixin, {
*/
play: function (e) {
this._newEvent = e;
if (!this._paused) return;
if (!this._paused) {
return;
}
this._paused = false;
this._rafID = window.requestAnimationFrame(this._update.bind(this));
this._lastUpdateTimestamp = undefined;
@@ -190,7 +207,9 @@ var AnimationEffect = Class.extend(mixins.ParentedMixin, {
* Forces the requestAnimationFrame loop to stop.
*/
pause: function () {
if (this._paused) return;
if (this._paused) {
return;
}
this._paused = true;
window.cancelAnimationFrame(this._rafID);
this._lastUpdateTimestamp = undefined;
@@ -210,7 +229,9 @@ var AnimationEffect = Class.extend(mixins.ParentedMixin, {
* @param {DOMHighResTimeStamp} timestamp
*/
_update: function (timestamp) {
if (this._paused) return;
if (this._paused) {
return;
}
this._rafID = window.requestAnimationFrame(this._update.bind(this));
// Check the elapsed time since the last update callback call.
@@ -219,7 +240,9 @@ var AnimationEffect = Class.extend(mixins.ParentedMixin, {
var elapsedTime = 0;
if (this._lastUpdateTimestamp) {
elapsedTime = timestamp - this._lastUpdateTimestamp;
if (elapsedTime < this._minFrameTime) return;
if (elapsedTime < this._minFrameTime) {
return;
}
}
// Check the new animation state thanks to the get state callback and
@@ -242,53 +265,9 @@ var AnimationEffect = Class.extend(mixins.ParentedMixin, {
});
/**
* Provides a way for executing code once a website DOM element is loaded in the
* dom and handle the case where the website edit mode is triggered.
*
* Also register AnimationEffect automatically (@see effects, _prepareEffects).
*/
var Animation = Widget.extend({
/**
* The selector attribute, if defined, allows to automatically create an
* instance of this animation on page load for each DOM element which
* matches this selector. The `Animation.$target` element will then be that
* particular DOM element. This should be the main way of instantiating
* `Animation` elements.
*/
selector: false,
/**
* Extension of @see Widget.events
*
* A description of the event handlers to bind/delegate once the widget
* has been rendered::
*
* 'click .hello .world': 'async _onHelloWorldClick',
* _^_ _^_ _^_ _^_
* | | | |
* | (Optional) jQuery | Handler method name
* | delegate selector |
* | |_ (Optional) space separated options
* | * async: use the automatic system
* |_ Event name with making handlers promise-ready (see
* potential jQuery makeButtonHandler, makeAsyncHandler)
* namespaces
*
* Note: the values may be replaced by a function declaration. This is
* however a deprecated behavior.
*
* @type {Object}
*/
events: {},
/**
* Acts as @see Widget.events except that the events are only binded if the
* Animation instance is instanciated in edit mode.
*/
edit_events: {},
/**
* Acts as @see Widget.events except that the events are only binded if the
* Animation instance is instanciated in readonly mode.
*/
read_events: {},
var Animation = publicWidget.Widget.extend({
/**
* The max FPS at which all the automatic animation effects will be
* running by default.
@@ -328,23 +307,6 @@ var Animation = Widget.extend({
*/
effects: [],
/**
* Initializes the events that will need to be binded according to the
* given mode.
*
* @constructor
* @param {Object} parent
* @param {boolean} editableMode - true if the page is in edition mode
*/
init: function (parent, editableMode) {
this._super.apply(this, arguments);
this.editableMode = editableMode;
if (editableMode) {
this.events = _.extend({}, this.events || {}, this.edit_events || {});
} else {
this.events = _.extend({}, this.events || {}, this.read_events || {});
}
},
/**
* Initializes the animation. The method should not be called directly as
* called automatically on animation instantiation and on restart.
@@ -360,89 +322,11 @@ var Animation = Widget.extend({
});
return this._super.apply(this, arguments);
},
/**
* Destroys the animation and basically restores the target to the state it
* was before the start method was called (unlike standard widget, the
* associated $el DOM is not removed).
*
* Also stops animation effects and destroys them if any.
*/
destroy: function () {
var $oldel = this.$el;
// The difference with the default behavior is that we unset the
// associated element first so that:
// 1) its events are unbinded
// 2) it is not removed from the DOM
this.setElement(null);
this._super.apply(this, arguments);
// Reassign the variables afterwards to allow extensions to use them
// after calling the _super method
this.$el = $oldel;
this.el = $oldel[0];
this.$target = this.$el;
this.target = this.el;
},
/**
* @override
*/
setElement: function () {
this._super.apply(this, arguments);
this.$target = this.$el;
this.target = this.el;
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* @see this.events
* @override
*/
_delegateEvents: function () {
var self = this;
var originalEvents = this.events;
var events = {};
_.each(this.events, function (method, event) {
// If the method is a function, use the default Widget system
if (typeof method !== 'string') {
events[event] = method;
return;
}
// If the method is only a function name without options, use the
// default Widget system
var methodOptions = method.split(' ');
if (methodOptions.length <= 1) {
events[event] = method;
return;
}
// If the method has no meaningful options, use the default Widget
// system
var isAsync = _.contains(methodOptions, 'async');
if (!isAsync) {
events[event] = method;
return;
}
method = self.proxy(methodOptions[methodOptions.length - 1]);
if (_.str.startsWith(event, 'click')) {
// Protect click handler to be called multiple times by
// mistake by the user and add a visual disabling effect
// for buttons.
method = dom.makeButtonHandler(method);
} else {
// Protect all handlers to be recalled while the previous
// async handler call is not finished.
method = dom.makeAsyncHandler(method);
}
events[event] = method;
});
this.events = events;
this._super.apply(this, arguments);
this.events = originalEvents;
},
/**
* Registers `AnimationEffect` instances.
*
@@ -487,33 +371,13 @@ var Animation = Widget.extend({
new AnimationEffect(this, updateCallback, startEvents, $startTarget, options)
);
},
/**
* @private
* @param {boolean} [extra=false]
* @param {Object} [extraContext]
* @returns {Object}
*/
_getContext: function (extra, extraContext) {
var context;
this.trigger_up('context_get', {
extra: extra || false,
context: extraContext,
callback: function (ctx) {
context = ctx;
},
});
return context;
},
});
//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
/**
* The registry object contains the list of available animations.
*/
var registry = {};
var registry = publicWidget.registry;
registry.slider = Animation.extend({
registry.slider = publicWidget.Widget.extend({
selector: '.carousel',
edit_events: {
'slid.bs.carousel': '_onEditionSlide',
@@ -651,8 +515,8 @@ registry.parallax = Animation.extend({
// Initialize parallax data according to snippet and viewport dimensions
this.viewport = document.body.clientHeight - $('#wrapwrap').position().top;
this.visible_area = [this.$target.offset().top];
this.visible_area.push(this.visible_area[0] + this.$target.innerHeight() + this.viewport);
this.visibleArea = [this.$target.offset().top];
this.visibleArea.push(this.visibleArea[0] + this.$target.innerHeight() + this.viewport);
this.ratio = this.speed * (this.viewport / 10);
// Provide a "safe-area" to limit parallax
@@ -680,43 +544,43 @@ registry.parallax = Animation.extend({
// Perform translation if the element is visible only
var vpEndOffset = scrollOffset + this.viewport;
if (vpEndOffset >= this.visible_area[0]
&& vpEndOffset <= this.visible_area[1]) {
if (vpEndOffset >= this.visibleArea[0]
&& vpEndOffset <= this.visibleArea[1]) {
this.$bg.css('transform', 'translateY(' + _getNormalizedPosition.call(this, vpEndOffset) + 'px)');
}
function _getNormalizedPosition(pos) {
// Normalize scroll in a 1 to 0 range
var r = (pos - this.visible_area[1]) / (this.visible_area[0] - this.visible_area[1]);
var r = (pos - this.visibleArea[1]) / (this.visibleArea[0] - this.visibleArea[1]);
// Normalize accordingly to current options
return Math.round(this.ratio * (2 * r - 1));
}
},
});
registry.share = Animation.extend({
registry.share = publicWidget.Widget.extend({
selector: '.s_share, .oe_share', // oe_share for compatibility
/**
* @override
*/
start: function () {
var url_regex = /(\?(?:|.*&)(?:u|url|body)=)(.*?)(&|#|$)/;
var title_regex = /(\?(?:|.*&)(?:title|text|subject)=)(.*?)(&|#|$)/;
var urlRegex = /(\?(?:|.*&)(?:u|url|body)=)(.*?)(&|#|$)/;
var titleRegex = /(\?(?:|.*&)(?:title|text|subject)=)(.*?)(&|#|$)/;
var url = encodeURIComponent(window.location.href);
var title = encodeURIComponent($('title').text());
this.$('a').each(function () {
var $a = $(this);
$a.attr('href', function (i, href) {
return href.replace(url_regex, function (match, a, b, c) {
return href.replace(urlRegex, function (match, a, b, c) {
return a + url + c;
}).replace(title_regex, function (match, a, b, c) {
}).replace(titleRegex, function (match, a, b, c) {
return a + title + c;
});
});
if ($a.attr('target') && $a.attr('target').match(/_blank/i) && !$a.closest('.o_editable').length) {
$a.on('click', function () {
window.open(this.href,'','menubar=no,toolbar=no,resizable=yes,scrollbars=yes,height=550,width=600');
window.open(this.href, '', 'menubar=no,toolbar=no,resizable=yes,scrollbars=yes,height=550,width=600');
return false;
});
}
@@ -726,7 +590,7 @@ registry.share = Animation.extend({
},
});
registry.mediaVideo = Animation.extend({
registry.mediaVideo = publicWidget.Widget.extend({
selector: '.media_iframe_video',
/**
@@ -767,7 +631,7 @@ registry.mediaVideo = Animation.extend({
},
});
registry.ul = Animation.extend({
registry.ul = publicWidget.Widget.extend({
selector: 'ul.o_ul_folded, ol.o_ul_folded',
events: {
'click .o_ul_toggle_next': '_onToggleNextClick',
@@ -802,7 +666,7 @@ registry.ul = Animation.extend({
},
});
registry.gallery = Animation.extend({
registry.gallery = publicWidget.Widget.extend({
selector: '.o_gallery:not(.o_slideshow)',
xmlDependencies: ['/website/static/src/xml/website.gallery.xml'],
read_events: {
@@ -831,12 +695,12 @@ registry.gallery = Animation.extend({
var $images = $cur.closest('.o_gallery').find('img');
var size = 0.8;
var dimensions = {
min_width : Math.round( window.innerWidth * size*0.9),
min_height : Math.round( window.innerHeight * size),
max_width : Math.round( window.innerWidth * size*0.9),
max_height : Math.round( window.innerHeight * size),
width : Math.round( window.innerWidth * size*0.9),
height : Math.round( window.innerHeight * size)
min_width: Math.round(window.innerWidth * size * 0.9),
min_height: Math.round(window.innerHeight * size),
max_width: Math.round(window.innerWidth * size * 0.9),
max_height: Math.round(window.innerHeight * size),
width: Math.round(window.innerWidth * size * 0.9),
height: Math.round(window.innerHeight * size)
};
$images.each(function () {
@@ -846,14 +710,13 @@ registry.gallery = Animation.extend({
idx = urls.indexOf($img.attr('src'));
milliseconds = $cur.closest('.o_gallery').data('interval') || false;
params = {
srcs : urls,
var $modal = $(qweb.render('website.gallery.slideshow.lightbox', {
srcs: urls,
index: idx,
dim : dimensions,
interval : milliseconds,
id: _.uniqueId('slideshow_')
};
var $modal = $(qweb.render('website.gallery.slideshow.lightbox', params));
dim: dimensions,
interval: milliseconds,
id: _.uniqueId('slideshow_'),
}));
$modal.modal({
keyboard: true,
backdrop: true,
@@ -875,7 +738,7 @@ registry.gallery = Animation.extend({
},
});
registry.gallerySlider = Animation.extend({
registry.gallerySlider = publicWidget.Widget.extend({
selector: '.o_slideshow',
xmlDependencies: ['/website/static/src/xml/website.gallery.xml'],
@@ -899,7 +762,7 @@ registry.gallerySlider = Animation.extend({
function hide() {
$lis.each(function (i) {
$(this).toggleClass('d-none', !(i >= page*nbPerPage && i < (page+1)*nbPerPage));
$(this).toggleClass('d-none', i < page * nbPerPage || i >= (page + 1) * nbPerPage);
});
if (self.editableMode) { // do not remove DOM in edit mode
return;
@@ -927,7 +790,7 @@ registry.gallerySlider = Animation.extend({
var $item = self.$carousel.find('.carousel-inner .carousel-item-prev, .carousel-inner .carousel-item-next');
var index = $item.index();
$lis.removeClass('active')
.filter('[data-slide-to="'+index+'"]')
.filter('[data-slide-to="' + index + '"]')
.addClass('active');
}, 0);
});
@@ -954,7 +817,7 @@ registry.gallerySlider = Animation.extend({
},
});
registry.socialShare = Animation.extend({
registry.socialShare = publicWidget.Widget.extend({
selector: '.oe_social_share',
xmlDependencies: ['/website/static/src/xml/website.share.xml'],
read_events: {
@@ -978,16 +841,16 @@ registry.socialShare = Animation.extend({
* @private
*/
_render: function () {
this.$target.popover({
this.$el.popover({
content: qweb.render('website.social_hover', {medias: this.socialList}),
placement: 'bottom',
container: this.$target,
container: this.$el,
html: true,
trigger: 'manual',
animation: false,
}).popover("show");
this.$target.off('mouseleave.socialShare').on('mouseleave.socialShare', function () {
this.$el.off('mouseleave.socialShare').on('mouseleave.socialShare', function () {
var self = this;
setTimeout(function () {
if (!$(".popover:hover").length) {
@@ -1003,19 +866,19 @@ registry.socialShare = Animation.extend({
var url = this.$el.data('urlshare') || document.URL.split(/[?#]/)[0];
url = encodeURIComponent(url);
var title = document.title.split(" | ")[0]; // get the page title without the company name
var hashtags = ' #'+ document.title.split(" | ")[1].replace(' ','') + ' ' + this.hashtags; // company name without spaces (for hashtag)
var social_network = {
var hashtags = ' #' + document.title.split(" | ")[1].replace(' ', '') + ' ' + this.hashtags; // company name without spaces (for hashtag)
var socialNetworks = {
'facebook': 'https://www.facebook.com/sharer/sharer.php?u=' + url,
'twitter': 'https://twitter.com/intent/tweet?original_referer=' + url + '&text=' + encodeURIComponent(title + hashtags + ' - ') + url,
'linkedin': 'https://www.linkedin.com/shareArticle?mini=true&url=' + url + '&title=' + encodeURIComponent(title),
'google-plus': 'https://plus.google.com/share?url=' + url,
};
if (!_.contains(_.keys(social_network), social)) {
if (!_.contains(_.keys(socialNetworks), social)) {
return;
}
var wHeight = 500;
var wWidth = 500;
window.open(social_network[social], '', 'menubar=no, toolbar=no, resizable=yes, scrollbar=yes, height=' + wHeight + ',width=' + wWidth);
window.open(socialNetworks[social], '', 'menubar=no, toolbar=no, resizable=yes, scrollbar=yes, height=' + wHeight + ',width=' + wWidth);
},
//--------------------------------------------------------------------------
@@ -1029,16 +892,16 @@ registry.socialShare = Animation.extend({
* @private
*/
_onMouseEnter: function () {
var social = this.$target.data('social');
var social = this.$el.data('social');
this.socialList = social ? social.split(',') : ['facebook', 'twitter', 'linkedin', 'google-plus'];
this.hashtags = this.$target.data('hashtags') || '';
this.hashtags = this.$el.data('hashtags') || '';
this._render();
this._bindSocialEvent();
},
});
registry.facebookPage = Animation.extend({
registry.facebookPage = publicWidget.Widget.extend({
selector: '.o_facebook_page',
/**
@@ -1082,22 +945,7 @@ registry.facebookPage = Animation.extend({
},
});
/**
* This is a fix for apple device (<= IPhone 4, IPad 2)
* Standard bootstrap requires data-toggle='collapse' element to be <a/> tags.
* Unfortunatly one snippet uses a <div/> tag instead. The fix forces an empty
* click handler on these div, which allows standard bootstrap to work.
*
* This should be removed in a future odoo snippets refactoring.
*/
registry._fixAppleCollapse = Animation.extend({
selector: '.s_faq_collapse [data-toggle="collapse"]',
events: {
'click': function () {},
},
});
registry.anchorSlide = Animation.extend({
registry.anchorSlide = publicWidget.Widget.extend({
selector: 'a[href^="/"][href*="#"], a[href^="#"]',
read_events: {
'click': '_onAnimateClick',
@@ -1130,7 +978,10 @@ registry.anchorSlide = Animation.extend({
});
return {
Class: Animation,
Widget: publicWidget.Widget,
Animation: Animation,
registry: registry,
Class: Animation, // Deprecated
};
});
@@ -1,80 +1,30 @@
odoo.define('website.WebsiteRoot', function (require) {
odoo.define('website.root', function (require) {
'use strict';
var ajax = require('web.ajax');
var core = require('web.core');
var Dialog = require('web.Dialog');
var ServiceProviderMixin = require('web.ServiceProviderMixin');
var session = require('web.session');
var utils = require('web.utils');
var rootWidget = require('web_editor.root_widget');
var sAnimation = require('website.content.snippets.animation');
require("website.content.zoomodoo");
var publicRootData = require('web.public.root');
require('website.content.zoomodoo');
var _t = core._t;
var websiteRootRegistry = new rootWidget.RootWidgetRegistry();
var websiteRootRegistry = publicRootData.publicRootRegistry;
// Load localizations outside the WebsiteRoot to not wait for DOM ready (but
// wait for them in WebsiteRoot)
function getLang() {
var html = document.documentElement;
return (html.getAttribute('lang') || 'en_US').replace('-', '_');
}
var lang = utils.get_cookie('frontend_lang') || getLang(); // FIXME the cookie value should maybe be in the ctx?
var localeDef = ajax.loadJS('/web/webclient/locale/' + lang.replace('-', '_'));
/**
* Element which is designed to be unique and that will be the top-most element
* in the widget hierarchy. So, all other widgets will be indirectly linked to
* this Class instance. Its main role will be to retrieve RPC demands from its
* children and handle them.
*/
var WebsiteRoot = rootWidget.RootWidget.extend(ServiceProviderMixin, {
events: _.extend({}, rootWidget.RootWidget.prototype.events || {}, {
var WebsiteRoot = publicRootData.PublicRoot.extend({
events: _.extend({}, publicRootData.PublicRoot.prototype.events || {}, {
'click .js_change_lang': '_onLangChangeClick',
'click .js_publish_management .js_publish_btn': '_onPublishBtnClick',
'submit .js_website_submit_form': '_onWebsiteFormSubmit',
'click .js_disable_on_click': '_onDisableOnClick',
'click .js_multi_website_switch': '_multiWebsiteSwitch',
'click .js_multi_company_switch': '_multiCompanySwitch',
'click .js_multi_website_switch': '_onWebsiteSwitch',
'click .js_multi_company_switch': '_onCompanySwitch',
}),
custom_events: _.extend({}, rootWidget.RootWidget.prototype.custom_events || {}, {
animation_start_demand: '_onAnimationStartDemand',
animation_stop_demand: '_onAnimationStopDemand',
context_get: '_onContextGet',
main_object_request: '_onMainObjectRequest',
ready_to_clean_for_save: '_onAnimationStopDemand',
custom_events: _.extend({}, publicRootData.PublicRoot.prototype.custom_events || {}, {
'ready_to_clean_for_save': '_onAnimationStopDemand',
}),
/**
* @constructor
*/
init: function () {
this._super.apply(this, arguments);
ServiceProviderMixin.init.call(this);
this.animations = [];
},
/**
* @override
*/
willStart: function () {
// TODO would be even greater to wait for those only if necessary
return $.when(
this._super.apply(this, arguments),
session.is_bound,
localeDef
);
},
/**
* @override
*/
start: function () {
var defs = [this._super.apply(this, arguments)];
// Animations
defs.push(this._startAnimations());
// Compatibility lang change ?
if (!this.$('.js_change_lang').length) {
var $links = this.$('ul.js_language_selector li a:not([data-oe-id])');
@@ -89,28 +39,10 @@ var WebsiteRoot = rootWidget.RootWidget.extend(ServiceProviderMixin, {
});
}
// Display image thumbnail
this.$(".o_image[data-mimetype^='image']").each(function () {
var $img = $(this);
if (/gif|jpe|jpg|png/.test($img.data('mimetype')) && $img.data('src')) {
$img.css('background-image', "url('" + $img.data('src') + "')");
}
});
// Enable magnify on zommable img
this.$('.zoomable img[data-zoom]').zoomOdoo();
// Auto scroll
if (window.location.hash.indexOf("scrollTop=") > -1) {
this.el.scrollTop = +window.location.hash.match(/scrollTop=([0-9]+)/)[1];
}
// Fix for IE:
if ($.fn.placeholder) {
$('input, textarea').placeholder();
}
return $.when.apply($, defs);
return this._super.apply(this, arguments);
},
//--------------------------------------------------------------------------
@@ -118,124 +50,24 @@ var WebsiteRoot = rootWidget.RootWidget.extend(ServiceProviderMixin, {
//--------------------------------------------------------------------------
/**
* Automatically add the global context to RPC commands.
*
* @override
*/
_call_service: function (ev) {
if (ev.data.service === 'ajax' && ev.data.method === 'rpc') {
var route = ev.data.args[0];
if (_.str.startsWith(route, '/web/dataset/call_kw/')) {
var params = ev.data.args[1];
var options = ev.data.args[2];
params.kwargs.context = _.extend({}, this._getContext(), params.kwargs.context || {});
if (options) {
params.kwargs.context = _.omit(params.kwargs.context, options.noContextKeys);
ev.data.args[2] = _.omit(options, 'noContextKeys');
}
params.kwargs.context = JSON.parse(JSON.stringify(params.kwargs.context));
}
}
return ServiceProviderMixin._call_service.apply(this, arguments);
},
/**
* Retrieves the global context of the public environment. This is the
* context which is automatically added to each RPC.
*
* @private
* @param {Object} [context]
* @returns {Object}
*/
_getContext: function (context) {
var html = document.documentElement;
return _.extend({
'lang': getLang(),
'website_id': html.getAttribute('data-website-id') | 0,
}, context || {});
}, this._super.apply(this, arguments));
},
/**
* Retrieves the global context of the public environment (as
* @see _getContext) but with extra informations that would be useless to
* send with each RPC.
*
* @private
* @param {Object} [context]
* @returns {Object}
* @override
*/
_getExtraContext: function (context) {
var html = document.documentElement;
return _.extend(this._getContext(), {
return _.extend({
'editable': !!(html.dataset.editable || $('[data-oe-model]').length), // temporary hack, this should be done in python
'translatable': !!html.dataset.translatable,
'edit_translations': !!html.dataset.edit_translations,
}, context || {});
},
/**
* As the WebsiteRoot instance is designed to be unique, the associated
* registry has been instantiated outside of the class and is simply
* returned here.
*
* @private
* @override
*/
_getRegistry: function () {
return websiteRootRegistry;
},
/**
* Creates an Animation instance for each DOM element which matches the
* `selector` key of one of the registered animations
* (@see Animation.selector).
*
* @private
* @param {boolean} [editableMode=false] - true if the page is in edition mode
* @param {jQuery} [$from]
* only initialize the animations whose `selector` matches the
* element or one of its descendant (default to the wrapwrap element)
* @returns {Deferred}
*/
_startAnimations: function (editableMode, $from) {
var self = this;
editableMode = editableMode || false;
if ($from === undefined) {
$from = this.$('#wrapwrap');
}
this._stopAnimations($from);
var defs = _.map(sAnimation.registry, function (Animation, animationName) {
var selector = Animation.prototype.selector || '';
var $target = $from.find(selector).addBack(selector);
var defs = _.map($target, function (el) {
var animation = new Animation(self, editableMode);
self.animations.push(animation);
return animation.attachTo($(el));
});
return $.when.apply($, defs);
});
return $.when.apply($, defs);
},
/**
* Destroys all animation instances. Especially needed before saving while
* in edition mode for example.
*
* @private
* @param {jQuery} [$from]
* only stop the animations linked to the given element(s) or one of
* its descendants
*/
_stopAnimations: function ($from) {
var removedAnimations = _.map(this.animations, function (animation) {
if (!$from
|| $from.filter(animation.el).length
|| $from.find(animation.el).length) {
animation.destroy();
return animation;
}
return null;
});
this.animations = _.difference(this.animations, removedAnimations);
}, this._super.apply(this, arguments));
},
//--------------------------------------------------------------------------
@@ -243,39 +75,12 @@ var WebsiteRoot = rootWidget.RootWidget.extend(ServiceProviderMixin, {
//--------------------------------------------------------------------------
/**
* Called when the root is notified that the animations have to be
* (re)started.
*
* @private
* @param {OdooEvent} ev
* @override
*/
_onAnimationStartDemand: function (ev) {
this._startAnimations(ev.data.editableMode, ev.data.$target)
.done(ev.data.onSuccess)
.fail(ev.data.onFailure);
},
/**
* Called when the root is notified that the animations have to be
* stopped.
*
* @private
* @param {OdooEvent} ev
*/
_onAnimationStopDemand: function (ev) {
this._stopAnimations(ev.data.$target);
},
/**
* Called when someone asked for the global public context.
*
* @private
* @param {OdooEvent} ev
*/
_onContextGet: function (ev) {
if (ev.data.extra) {
ev.data.callback(this._getExtraContext(ev.data.context));
} else {
ev.data.callback(this._getContext(ev.data.context));
}
ev.data.options = _.clone(ev.data.options || {});
ev.data.options.editableMode = ev.data.editableMode;
this._super.apply(this, arguments);
},
/**
* @todo review
@@ -293,20 +98,6 @@ var WebsiteRoot = rootWidget.RootWidget.extend(ServiceProviderMixin, {
};
window.location.href = _.str.sprintf("/website/lang/%(lang)s?r=%(url)s%(hash)s", redirect);
},
/**
* Checks information about the page main object.
*
* @private
* @param {OdooEvent} ev
*/
_onMainObjectRequest: function (ev) {
var repr = $('html').data('main-object');
var m = repr.match(/(.+)\((\d+),(.*)\)/);
ev.data.callback({
model: m[1],
id: m[2] | 0,
});
},
/**
* @todo review
* @private
@@ -343,56 +134,35 @@ var WebsiteRoot = rootWidget.RootWidget.extend(ServiceProviderMixin, {
});
},
/**
* @todo review
* @private
*/
_onWebsiteFormSubmit: function (ev) {
var $buttons = $(ev.currentTarget).find('button[type="submit"], a.a-submit');
_.each($buttons, function (btn) {
var $btn = $(btn);
$btn.attr('data-loading-text', '<i class="fa fa-spinner fa-spin"></i> ' + $(btn).text());
$btn.button('loading');
});
},
/**
* Called when the root is notified that the button should be
* disabled after the first click.
*
* @private
* @param {Event} ev
*/
_onDisableOnClick: function (ev) {
$(ev.currentTarget).addClass('disabled');
},
/**
* Called when clicking on the multi-website switcher.
*
* @param {OdooEvent} ev
*/
_multiWebsiteSwitch: function (ev) {
var website_id_to_switch_to = ev.currentTarget.getAttribute('website-id');
_onWebsiteSwitch: function (ev) {
var websiteID = ev.currentTarget.getAttribute('website-id');
// need to force in each case, even if domain is set
// Website 1: localhost; Website 2: 0.0.0.0; website 3: -
// when you switch 3 <--> 1, you need to force the website
var website_domain = ev.currentTarget.getAttribute('domain');
var url = $.param.querystring(window.location.href, {'fw': website_id_to_switch_to});
if (website_domain && window.location.hostname !== website_domain) {
var websiteDomain = ev.currentTarget.getAttribute('domain');
var url = $.param.querystring(window.location.href, {fw: websiteID});
if (websiteDomain && window.location.hostname !== websiteDomain) {
// if domain unchanged, this line will do a nop while we need to refresh
// the page to load the new forced website.
url = new URL(url);
url.hostname = website_domain;
url.hostname = websiteDomain;
}
window.location.href = url;
},
_multiCompanySwitch: function (ev) {
var company_id_to_switch_to = ev.currentTarget.getAttribute('company-id');
/**
* @private
* @param {Event} ev
*/
_onCompanySwitch: function (ev) {
var companyID = parseInt(ev.currentTarget.getAttribute('company-id'), 10);
this._rpc({model: 'res.users',
method: 'write',
args: [odoo.session_info.user_id, {'company_id': parseInt(company_id_to_switch_to, 10)}],
args: [odoo.session_info.user_id, {'company_id': companyID}],
}).then(function () {
window.location.reload(true);
});
@@ -2,7 +2,7 @@ odoo.define('root.widget', function (require) {
'use strict';
require('web.dom_ready');
var websiteRootData = require('website.WebsiteRoot');
var websiteRootData = require('website.root');
var websiteRoot = new websiteRootData.WebsiteRoot(null);
return websiteRoot.attachTo(document.body).then(function () {
+2 -2
View File
@@ -7,7 +7,7 @@ var Dialog = require('web.Dialog');
var time = require('web.time');
var weWidgets = require('wysiwyg.widgets');
var websiteNavbarData = require('website.navbar');
var websiteRootData = require('website.WebsiteRoot');
var websiteRootData = require('website.root');
var Widget = require('web.Widget');
var _t = core._t;
@@ -406,7 +406,7 @@ var MenuEntryDialog = weWidgets.LinkDialog.extend({
save: function () {
var $e = this.$('#o_link_dialog_label_input');
if (!$e.val() || !$e[0].checkValidity()) {
$e.closest('.form-group').addClass('o_has_error').find('.form-control, .custom-select').addClass('is-invalid')
$e.closest('.form-group').addClass('o_has_error').find('.form-control, .custom-select').addClass('is-invalid');
$e.focus();
return;
}
+10 -10
View File
@@ -1,24 +1,24 @@
odoo.define('website.navbar', function (require) {
'use strict';
var rootWidget = require('web_editor.root_widget');
var publicWidget = require('web.public.widget');
var concurrency = require('web.concurrency');
var Widget = require('web.Widget');
var websiteRootData = require('website.WebsiteRoot');
var websiteRootData = require('website.root');
var websiteNavbarRegistry = new rootWidget.RootWidgetRegistry();
var websiteNavbarRegistry = new publicWidget.RootWidgetRegistry();
var WebsiteNavbar = rootWidget.RootWidget.extend({
events: _.extend({}, rootWidget.RootWidget.prototype.events || {}, {
var WebsiteNavbar = publicWidget.RootWidget.extend({
events: _.extend({}, publicWidget.RootWidget.prototype.events || {}, {
'click [data-action]': '_onActionMenuClick',
'mouseover > ul > li.dropdown:not(.show)': '_onMenuHovered',
'click .o_mobile_menu_toggle': '_onMobileMenuToggleClick',
}),
custom_events: _.extend({}, rootWidget.RootWidget.prototype.custom_events || {}, {
action_demand: '_onActionDemand',
edit_mode: '_onEditMode',
readonly_mode: '_onReadonlyMode',
ready_to_save: '_onSave',
custom_events: _.extend({}, publicWidget.RootWidget.prototype.custom_events || {}, {
'action_demand': '_onActionDemand',
'edit_mode': '_onEditMode',
'readonly_mode': '_onReadonlyMode',
'ready_to_save': '_onSave',
}),
/**
@@ -2,10 +2,8 @@ odoo.define('website_forum.editor', function (require) {
"use strict";
var core = require('web.core');
var Widget = require('web.Widget');
var WebsiteNewMenu = require('website.newMenu');
var wUtils = require('website.utils');
var websiteRootData = require('website.WebsiteRoot');
var _t = core._t;
@@ -62,5 +60,4 @@ WebsiteNewMenu.include({
});
},
});
});