diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/web/static/src/js/public | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/web/static/src/js/public')
| -rw-r--r-- | addons/web/static/src/js/public/lazyloader.js | 111 | ||||
| -rw-r--r-- | addons/web/static/src/js/public/public_crash_manager.js | 31 | ||||
| -rw-r--r-- | addons/web/static/src/js/public/public_env.js | 11 | ||||
| -rw-r--r-- | addons/web/static/src/js/public/public_notification.js | 9 | ||||
| -rw-r--r-- | addons/web/static/src/js/public/public_root.js | 336 | ||||
| -rw-r--r-- | addons/web/static/src/js/public/public_root_instance.js | 33 | ||||
| -rw-r--r-- | addons/web/static/src/js/public/public_widget.js | 356 |
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, +}; +}); |
