summaryrefslogtreecommitdiff
path: root/addons/web/static/src/js/public
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/web/static/src/js/public
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/web/static/src/js/public')
-rw-r--r--addons/web/static/src/js/public/lazyloader.js111
-rw-r--r--addons/web/static/src/js/public/public_crash_manager.js31
-rw-r--r--addons/web/static/src/js/public/public_env.js11
-rw-r--r--addons/web/static/src/js/public/public_notification.js9
-rw-r--r--addons/web/static/src/js/public/public_root.js336
-rw-r--r--addons/web/static/src/js/public/public_root_instance.js33
-rw-r--r--addons/web/static/src/js/public/public_widget.js356
7 files changed, 887 insertions, 0 deletions
diff --git a/addons/web/static/src/js/public/lazyloader.js b/addons/web/static/src/js/public/lazyloader.js
new file mode 100644
index 00000000..8ca0948c
--- /dev/null
+++ b/addons/web/static/src/js/public/lazyloader.js
@@ -0,0 +1,111 @@
+odoo.define('web.public.lazyloader', function (require) {
+'use strict';
+
+var blockEvents = ['submit', 'click'];
+var blockFunction = function (ev) {
+ ev.preventDefault();
+ ev.stopImmediatePropagation();
+};
+
+var waitingLazy = false;
+
+/**
+ * Blocks the DOM sections which explicitely require the lazy loaded JS to be
+ * working (those sections should be marked with the 'o_wait_lazy_js' class).
+ *
+ * @see stopWaitingLazy
+ */
+function waitLazy() {
+ if (waitingLazy) {
+ return;
+ }
+ waitingLazy = true;
+
+ var lazyEls = document.querySelectorAll('.o_wait_lazy_js');
+ for (var i = 0; i < lazyEls.length; i++) {
+ var element = lazyEls[i];
+ blockEvents.forEach(function (evType) {
+ element.addEventListener(evType, blockFunction);
+ });
+ }
+}
+/**
+ * Unblocks the DOM sections blocked by @see waitLazy and removes the related
+ * 'o_wait_lazy_js' class from the whole DOM.
+ */
+function stopWaitingLazy() {
+ if (!waitingLazy) {
+ return;
+ }
+ waitingLazy = false;
+
+ var lazyEls = document.querySelectorAll('.o_wait_lazy_js');
+ for (var i = 0; i < lazyEls.length; i++) {
+ var element = lazyEls[i];
+ blockEvents.forEach(function (evType) {
+ element.removeEventListener(evType, blockFunction);
+ });
+ element.classList.remove('o_wait_lazy_js');
+ }
+}
+
+// Start waiting for lazy loading as soon as the DOM is available
+if (document.readyState !== 'loading') {
+ waitLazy();
+} else {
+ document.addEventListener('DOMContentLoaded', function () {
+ waitLazy();
+ });
+}
+
+// As soon as everything is fully loaded, start loading all the remaining JS
+// and unblock the related DOM section when all of it have been loaded and
+// executed
+var doResolve = null;
+var _allScriptsLoaded = new Promise(function (resolve) {
+ if (doResolve) {
+ resolve();
+ } else {
+ doResolve = resolve;
+ }
+}).then(function () {
+ stopWaitingLazy();
+});
+if (document.readyState === 'complete') {
+ setTimeout(_loadScripts, 0);
+} else {
+ window.addEventListener('load', function () {
+ setTimeout(_loadScripts, 0);
+ });
+}
+
+/**
+ * @param {DOMElement[]} scripts
+ * @param {integer} index
+ */
+function _loadScripts(scripts, index) {
+ if (scripts === undefined) {
+ scripts = document.querySelectorAll('script[data-src]');
+ }
+ if (index === undefined) {
+ index = 0;
+ }
+ if (index >= scripts.length) {
+ if (typeof doResolve === 'function') {
+ doResolve();
+ } else {
+ doResolve = true;
+ }
+ return;
+ }
+ var script = scripts[index];
+ script.addEventListener('load', _loadScripts.bind(this, scripts, index + 1));
+ script.src = script.dataset.src;
+ script.removeAttribute('data-src');
+}
+
+return {
+ loadScripts: _loadScripts,
+ allScriptsLoaded: _allScriptsLoaded,
+};
+});
diff --git a/addons/web/static/src/js/public/public_crash_manager.js b/addons/web/static/src/js/public/public_crash_manager.js
new file mode 100644
index 00000000..8eae270f
--- /dev/null
+++ b/addons/web/static/src/js/public/public_crash_manager.js
@@ -0,0 +1,31 @@
+odoo.define('web.PublicCrashManager', function (require) {
+"use strict";
+
+const core = require('web.core');
+const CrashManager = require('web.CrashManager').CrashManager;
+
+const PublicCrashManager = CrashManager.extend({
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * @override
+ */
+ _displayWarning(message, title, options) {
+ this.displayNotification(Object.assign({}, options, {
+ title,
+ message,
+ sticky: true,
+ }));
+ },
+});
+
+core.serviceRegistry.add('crash_manager', PublicCrashManager);
+
+return {
+ CrashManager: PublicCrashManager,
+};
+
+});
diff --git a/addons/web/static/src/js/public/public_env.js b/addons/web/static/src/js/public/public_env.js
new file mode 100644
index 00000000..581cccd4
--- /dev/null
+++ b/addons/web/static/src/js/public/public_env.js
@@ -0,0 +1,11 @@
+odoo.define("web.public_env", function (require) {
+ "use strict";
+
+ /**
+ * This file defines the env to use in the public side.
+ */
+
+ const commonEnv = require("web.commonEnv");
+
+ return commonEnv;
+});
diff --git a/addons/web/static/src/js/public/public_notification.js b/addons/web/static/src/js/public/public_notification.js
new file mode 100644
index 00000000..260937f0
--- /dev/null
+++ b/addons/web/static/src/js/public/public_notification.js
@@ -0,0 +1,9 @@
+odoo.define('web.public.Notification', function (require) {
+'use strict';
+
+var Notification = require('web.Notification');
+
+Notification.include({
+ xmlDependencies: ['/web/static/src/xml/notification.xml'],
+});
+});
diff --git a/addons/web/static/src/js/public/public_root.js b/addons/web/static/src/js/public/public_root.js
new file mode 100644
index 00000000..93e1c4fd
--- /dev/null
+++ b/addons/web/static/src/js/public/public_root.js
@@ -0,0 +1,336 @@
+odoo.define('web.public.root', function (require) {
+'use strict';
+
+var ajax = require('web.ajax');
+var dom = require('web.dom');
+const env = require('web.public_env');
+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({
+ 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 || {}, {
+ call_service: '_onCallService',
+ context_get: '_onContextGet',
+ main_object_request: '_onMainObjectRequest',
+ widgets_start_request: '_onWidgetsStartRequest',
+ widgets_stop_request: '_onWidgetsStopRequest',
+ }),
+
+ /**
+ * @constructor
+ */
+ init: function () {
+ this._super.apply(this, arguments);
+ this.env = env;
+ this.publicWidgets = [];
+ },
+ /**
+ * @override
+ */
+ willStart: function () {
+ // TODO would be even greater to wait for localeDef only when necessary
+ return Promise.all([
+ this._super.apply(this, arguments),
+ session.is_bound,
+ localeDef
+ ]);
+ },
+ /**
+ * @override
+ */
+ start: function () {
+ var defs = [
+ this._super.apply(this, arguments),
+ this._startWidgets()
+ ];
+
+ // 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();
+ }
+
+ this.$el.children().on('error.datetimepicker', this._onDateTimePickerError.bind(this));
+
+ return Promise.all(defs);
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * 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);
+ },
+ /**
+ * @private
+ * @param {Object} [options]
+ * @returns {Object}
+ */
+ _getPublicWidgetsRegistry: function (options) {
+ return publicWidget.registry;
+ },
+ /**
+ * 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 public widgets whose `selector` matches the
+ * element or one of its descendant (default to the wrapwrap element)
+ * @param {Object} [options]
+ * @returns {Deferred}
+ */
+ _startWidgets: function ($from, options) {
+ var self = this;
+
+ if ($from === undefined) {
+ $from = this.$('#wrapwrap');
+ if (!$from.length) {
+ // TODO Remove this once all frontend layouts possess a
+ // #wrapwrap element (which is necessary for those pages to be
+ // adapted correctly if the user installs website).
+ $from = this.$el;
+ }
+ }
+ if (options === undefined) {
+ options = {};
+ }
+
+ this._stopWidgets($from);
+
+ var defs = _.map(this._getPublicWidgetsRegistry(options), function (PublicWidget) {
+ var selector = PublicWidget.prototype.selector || '';
+ var $target = dom.cssFind($from, selector, true);
+
+ var defs = _.map($target, function (el) {
+ var widget = new PublicWidget(self, options);
+ self.publicWidgets.push(widget);
+ return widget.attachTo($(el));
+ });
+ return Promise.all(defs);
+ });
+ return Promise.all(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 public widgets linked to the given element(s) or one
+ * of its descendants
+ */
+ _stopWidgets: function ($from) {
+ var removedWidgets = _.map(this.publicWidgets, function (widget) {
+ if (!$from
+ || $from.filter(widget.el).length
+ || $from.find(widget.el).length) {
+ widget.destroy();
+ return widget;
+ }
+ return null;
+ });
+ this.publicWidgets = _.difference(this.publicWidgets, removedWidgets);
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * Calls the requested service from the env. Automatically adds the global
+ * context to RPCs.
+ *
+ * @private
+ * @param {OdooEvent} event
+ */
+ _onCallService: function (ev) {
+ function _computeContext(context, noContextKeys) {
+ context = _.extend({}, this._getContext(), context);
+ if (noContextKeys) {
+ context = _.omit(context, noContextKeys);
+ }
+ return JSON.parse(JSON.stringify(context));
+ }
+
+ const payload = ev.data;
+ let args = payload.args || [];
+ if (payload.service === 'ajax' && payload.method === 'rpc') {
+ // ajax service uses an extra 'target' argument for rpc
+ args = args.concat(ev.target);
+
+ var route = args[0];
+ if (_.str.startsWith(route, '/web/dataset/call_kw/')) {
+ var params = args[1];
+ var options = args[2];
+ var noContextKeys;
+ if (options) {
+ noContextKeys = options.noContextKeys;
+ args[2] = _.omit(options, 'noContextKeys');
+ }
+ params.kwargs.context = _computeContext.call(this, params.kwargs.context, noContextKeys);
+ }
+ } else if (payload.service === 'ajax' && payload.method === 'loadLibs') {
+ args[1] = _computeContext.call(this, args[1]);
+ }
+
+ const service = this.env.services[payload.service];
+ const result = service[payload.method].apply(service, args);
+ payload.callback(result);
+ },
+ /**
+ * 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,
+ });
+ },
+ /**
+ * Called when the root is notified that the public widgets have to be
+ * (re)started.
+ *
+ * @private
+ * @param {OdooEvent} ev
+ */
+ _onWidgetsStartRequest: function (ev) {
+ this._startWidgets(ev.data.$target, ev.data.options)
+ .then(ev.data.onSuccess)
+ .guardedCatch(ev.data.onFailure);
+ },
+ /**
+ * Called when the root is notified that the public widgets have to be
+ * stopped.
+ *
+ * @private
+ * @param {OdooEvent} ev
+ */
+ _onWidgetsStopRequest: function (ev) {
+ this._stopWidgets(ev.data.$target);
+ },
+ /**
+ * @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.html('<i class="fa fa-spinner fa-spin"></i> ' + $btn.text());
+ $btn.prop('disabled', true);
+ });
+ },
+ /**
+ * 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');
+ },
+ /**
+ * Library clears the wrong date format so just ignore error
+ *
+ * @private
+ * @param {Event} ev
+ */
+ _onDateTimePickerError: function (ev) {
+ return false;
+ },
+});
+
+return {
+ PublicRoot: PublicRoot,
+ publicRootRegistry: publicRootRegistry,
+};
+});
diff --git a/addons/web/static/src/js/public/public_root_instance.js b/addons/web/static/src/js/public/public_root_instance.js
new file mode 100644
index 00000000..a7422a97
--- /dev/null
+++ b/addons/web/static/src/js/public/public_root_instance.js
@@ -0,0 +1,33 @@
+odoo.define('root.widget', function (require) {
+'use strict';
+
+const AbstractService = require('web.AbstractService');
+const env = require('web.public_env');
+var lazyloader = require('web.public.lazyloader');
+var rootData = require('web.public.root');
+
+/**
+ * Configure Owl with the public env
+ */
+owl.config.mode = env.isDebug() ? "dev" : "prod";
+owl.Component.env = env;
+
+/**
+ * Deploy services in the env
+ */
+AbstractService.prototype.deployServices(env);
+
+/**
+ * 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 publicRoot = new rootData.PublicRoot(null);
+return lazyloader.allScriptsLoaded.then(function () {
+ return publicRoot.attachTo(document.body).then(function () {
+ return publicRoot;
+ });
+});
+
+});
diff --git a/addons/web/static/src/js/public/public_widget.js b/addons/web/static/src/js/public/public_widget.js
new file mode 100644
index 00000000..79382e11
--- /dev/null
+++ b/addons/web/static/src/js/public/public_widget.js
@@ -0,0 +1,356 @@
+odoo.define('web.public.widget', function (require) {
+'use strict';
+
+/**
+ * Provides a way to start JS code for public contents.
+ */
+
+var Class = require('web.Class');
+var dom = require('web.dom');
+var mixins = require('web.mixins');
+var session = require('web.session');
+var Widget = require('web.Widget');
+
+/**
+ * Specialized Widget which automatically instantiates child widgets to attach
+ * 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',
+ }),
+ /**
+ * @constructor
+ */
+ init: function () {
+ this._super.apply(this, arguments);
+ this._widgets = [];
+ this._listenToUpdates = false;
+ this._getRegistry().setParent(this);
+ },
+ /**
+ * @override
+ * @see _attachComponents
+ */
+ start: function () {
+ var defs = [this._super.apply(this, arguments)];
+
+ defs.push(this._attachComponents());
+ this._listenToUpdates = true;
+
+ return Promise.all(defs);
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * Instantiates a child widget according to the given registry data.
+ *
+ * @private
+ * @param {Object} childInfo
+ * @param {function} childInfo.Widget - the widget class to instantiate
+ * @param {string} childInfo.selector
+ * the jQuery selector to use to find the internal DOM element which
+ * needs to be attached to the instantiated widget
+ * @param {jQuery} [$from] - only check DOM elements which are descendant of
+ * the given one. If not given, use this.$el.
+ * @returns {Deferred}
+ */
+ _attachComponent: function (childInfo, $from) {
+ var self = this;
+ var $elements = dom.cssFind($from || this.$el, childInfo.selector);
+ var defs = _.map($elements, function (element) {
+ var w = new childInfo.Widget(self);
+ self._widgets.push(w);
+ return w.attachTo(element);
+ });
+ return Promise.all(defs);
+ },
+ /**
+ * Instantiates the child widgets that need to be according to the linked
+ * registry.
+ *
+ * @private
+ * @param {jQuery} [$from] - only check DOM elements which are descendant of
+ * the given one. If not given, use this.$el.
+ * @returns {Deferred}
+ */
+ _attachComponents: function ($from) {
+ var self = this;
+ var childInfos = this._getRegistry().get();
+ var defs = _.map(childInfos, function (childInfo) {
+ return self._attachComponent(childInfo, $from);
+ });
+ return Promise.all(defs);
+ },
+ /**
+ * Returns the `RootWidgetRegistry` instance that is linked to this
+ * `RootWidget` instance.
+ *
+ * @abstract
+ * @private
+ * @returns {RootWidgetRegistry}
+ */
+ _getRegistry: function () {},
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * Get the curuent session module.
+ *
+ * @private
+ * @param {OdooEvent} ev
+ */
+ _onGetSession: function (event) {
+ if (event.data.callback) {
+ event.data.callback(session);
+ }
+ },
+ /**
+ * Called when the linked registry is updated after this `RootWidget`
+ *
+ * @private
+ * @param {OdooEvent} ev
+ */
+ _onRegistryUpdate: function (ev) {
+ ev.stopPropagation();
+ if (this._listenToUpdates) {
+ this._attachComponent(ev.data);
+ }
+ },
+});
+
+var RootWidgetRegistry = Class.extend(mixins.EventDispatcherMixin, {
+ /**
+ * @constructor
+ */
+ init: function () {
+ mixins.EventDispatcherMixin.init.call(this);
+ this._registry = [];
+ },
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ /**
+ * Adds an element to the registry (info of what and how to instantiate).
+ *
+ * @param {function} Widget - the widget class to instantiate
+ * @param {string} selector
+ * the jQuery selector to use to find the internal DOM element which
+ * needs to be attached to the instantiated widget
+ */
+ add: function (Widget, selector) {
+ var registryInfo = {
+ Widget: Widget,
+ selector: selector,
+ };
+ this._registry.push(registryInfo);
+ this.trigger_up('registry_update', registryInfo);
+ },
+ /**
+ * Retrieves all the registry elements.
+ */
+ get: function () {
+ return this._registry;
+ },
+});
+
+//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+
+/**
+ * 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 widget on page load for each DOM element which
+ * matches this selector. The `PublicWidget.$target` element will then be
+ * that particular DOM element. This should be the main way of instantiating
+ * `PublicWidget` 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: {},
+
+ /**
+ * @constructor
+ * @param {Object} parent
+ * @param {Object} [options]
+ */
+ init: function (parent, options) {
+ this._super.apply(this, arguments);
+ this.options = options || {};
+ },
+ /**
+ * Destroys the widget 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, if this was instantiated thanks to the
+ * selector property).
+ */
+ 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 widgets that should be instantiated
+ * thanks to their selector property if any.
+ */
+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 some layouts use a <div/> tag instead. The fix forces an empty
+ * click handler on these div, which allows standard bootstrap to work.
+ */
+registry._fixAppleCollapse = PublicWidget.extend({
+ selector: 'div[data-toggle="collapse"]',
+ events: {
+ 'click': function () {},
+ },
+});
+
+return {
+ RootWidget: RootWidget,
+ RootWidgetRegistry: RootWidgetRegistry,
+ Widget: PublicWidget,
+ registry: registry,
+};
+});