summaryrefslogtreecommitdiff
path: root/addons/web/static/src/js/public/public_widget.js
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/public_widget.js
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/web/static/src/js/public/public_widget.js')
-rw-r--r--addons/web/static/src/js/public/public_widget.js356
1 files changed, 356 insertions, 0 deletions
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,
+};
+});