From 4edb9c4f60f98719cb7f8666b5ee7f6ab4c7441f Mon Sep 17 00:00:00 2001 From: qsm-odoo Date: Tue, 18 Dec 2018 14:08:40 +0000 Subject: [PATCH 01/11] [IMP] website: improve theme customize dialog - Make the controller part more readable and more efficient (O(1) ORM operations instead of O(n)) and remove deprecated code. - Add the possibility to have a toggle standalone option. This is used as soon as an `` is alone in its container (note that now, the container can be any tag and will be ignored during rendering). - If an option has no specified 'string' to attach to it, the name of the first view it toggles will be used. PR https://github.com/odoo/odoo/pull/29624 task-1904244 --- addons/website/controllers/main.py | 42 ++---- addons/website/static/src/js/widgets/theme.js | 123 +++++++++--------- .../website/static/src/xml/website.editor.xml | 14 +- addons/website/views/website_templates.xml | 10 +- 4 files changed, 95 insertions(+), 94 deletions(-) diff --git a/addons/website/controllers/main.py b/addons/website/controllers/main.py index 3852fefe740..6ba749bc3c2 100644 --- a/addons/website/controllers/main.py +++ b/addons/website/controllers/main.py @@ -331,45 +331,27 @@ class Website(Home): # Themes # ------------------------------------------------------ - def get_view_ids(self, xml_ids): - ids = [] + def _get_customize_views(self, xml_ids): View = request.env["ir.ui.view"].with_context(active_test=False) - for xml_id in xml_ids: - if "." in xml_id: - # Get website-specific view if possible - record_id = View.search([ - ("website_id", "=", request.website.id), - ("key", "=", xml_id), - ], limit=1).id or request.env.ref(xml_id).id - else: - record_id = int(xml_id) - ids.append(record_id) - return ids + if not xml_ids: + return View + domain = [("key", "in", xml_ids)] + request.website.website_domain() + return View.search(domain).filter_duplicate() @http.route(['/website/theme_customize_get'], type='json', auth="public", website=True) def theme_customize_get(self, xml_ids): - enable = [] - disable = [] - ids = self.get_view_ids(xml_ids) - for view in request.env['ir.ui.view'].browse(ids): - if view.active: - enable.append(view.key) - else: - disable.append(view.key) - return [enable, disable] + views = self._get_customize_views(xml_ids) + return { + 'enabled': views.filtered('active').mapped('key'), + 'names': {view.key: view.name for view in views}, + } @http.route(['/website/theme_customize'], type='json', auth="public", website=True) def theme_customize(self, enable=None, disable=None, get_bundle=False): """ enable or Disable lists of ``xml_id`` of the inherit templates """ - def set_active(xml_ids, active): - if xml_ids: - real_ids = self.get_view_ids(xml_ids) - request.env['ir.ui.view'].browse(real_ids).write({'active': active}) - if disable: - set_active(disable, False) - if enable: - set_active(enable, True) + self._get_customize_views(disable).write({'active': False}) + self._get_customize_views(enable).write({'active': True}) if get_bundle: context = dict(request.context) diff --git a/addons/website/static/src/js/widgets/theme.js b/addons/website/static/src/js/widgets/theme.js index 36f59a23762..dd32defb40f 100644 --- a/addons/website/static/src/js/widgets/theme.js +++ b/addons/website/static/src/js/widgets/theme.js @@ -18,8 +18,8 @@ var ThemeCustomizeDialog = Dialog.extend({ template: 'website.theme_customize', events: { - 'change [data-xmlid], [data-enable], [data-disable]': '_onChange', - 'click .checked [data-xmlid], .checked [data-enable], .checked [data-disable]': '_onChange', + 'change .o_theme_customize_option_input': '_onChange', + 'click .o_theme_customize_option_input[type="radio"]:checked': '_onChange', 'click .o_theme_customize_color': '_onColorClick', }, @@ -58,11 +58,21 @@ var ThemeCustomizeDialog = Dialog.extend({ */ start: function () { var self = this; - this._generateDialogHTML(); + this.$modal.addClass('o_theme_customize_modal'); + var $tabs; + var loadDef = this._loadViews().then(function (data) { + self._generateDialogHTML(data); + $tabs = self.$('[data-toggle="tab"]'); + + // Hide the tab navigation if only one tab + if ($tabs.length <= 1) { + $tabs.closest('.nav').addClass('d-none'); + } + }); + // Enable the first option tab or the given default tab - var $tabs = this.$('[data-toggle="tab"]'); this.opened().then(function () { $tabs.eq(self.defaultTab).tab('show'); @@ -88,17 +98,7 @@ var ThemeCustomizeDialog = Dialog.extend({ } }); - // Hide the tab navigation if only one tab - if ($tabs.length <= 1) { - $tabs.closest('.nav').addClass('d-none'); - } - - this.$inputs = this.$('[data-xmlid], [data-enable], [data-disable]'); - - return $.when( - this._super.apply(this, arguments), - this._loadViews() - ); + return $.when(this._super.apply(this, arguments), loadDef); }, //-------------------------------------------------------------------------- @@ -107,8 +107,10 @@ var ThemeCustomizeDialog = Dialog.extend({ /** * @private + * @param {Object} data - @see this._loadViews */ - _generateDialogHTML: function () { + _generateDialogHTML: function (data) { + var self = this; var $contents = this.$el.children('content'); if ($contents.length === 0) { return; @@ -148,8 +150,20 @@ var ThemeCustomizeDialog = Dialog.extend({ this.$('[title]').tooltip(); + this.$inputs = self.$('[data-xmlid], [data-enable], [data-disable]'); + // Enable data-xmlid="" inputs if none of their neighbors were enabled + _.each(this.$inputs.filter('[data-xmlid=""]'), function (input) { + var $input = $(input); + var $neighbors = self.$inputs.filter('[name="' + $input.attr('name') + '"]').not($input); + if ($neighbors.length && !$neighbors.filter(':checked').length) { + $input.prop('checked', true); + } + }); + this._setActive(); + function _processItems($items, $container) { var optionsName = _.uniqueId('option-'); + var alone = ($items.length === 1); _.each($items, function (item) { var $item = $(item); @@ -159,22 +173,29 @@ var ThemeCustomizeDialog = Dialog.extend({ case 'OPT': var widgetName = $item.data('widget'); + var xmlid = $item.data('xmlid'); + // Build the options template var $option = $(core.qweb.render('website.theme_customize_modal_option', { + alone: alone, name: optionsName, id: $item.attr('id') || _.uniqueId('o_theme_customize_input_id_'), + checked: xmlid && (!_.difference(self._getXMLIDs($item), data.enabled).length), - string: $item.attr('string'), + string: $item.attr('string') || data.names[xmlid.split(',')[0].trim()], icon: $item.data('icon'), font: $item.data('font'), - xmlid: $item.data('xmlid'), - enable: $item.data('enable'), - disable: $item.data('disable'), - reload: $item.data('reload'), - widget: widgetName, })); + $option.find('input') + .addClass('o_theme_customize_option_input') + .attr({ + 'data-xmlid': xmlid, + 'data-enable': $item.data('enable'), + 'data-disable': $item.data('disable'), + 'data-reload': $item.data('reload'), + }); if (widgetName) { var $widget = $(core.qweb.render('website.theme_customize_' + widgetName)); @@ -201,6 +222,10 @@ var ThemeCustomizeDialog = Dialog.extend({ $container.append($col); _processItems($item.children(), $listContainer); break; + + default: + _processItems($item.children(), $container); + break; } }); } @@ -209,29 +234,11 @@ var ThemeCustomizeDialog = Dialog.extend({ * @private */ _loadViews: function () { - var self = this; return this._rpc({ route: '/website/theme_customize_get', params: { - xml_ids: this._getXMLIDs(this.$inputs), + 'xml_ids': this._getXMLIDs(this.$inputs || this.$('[data-xmlid]')), }, - }).done(function (data) { - self.$inputs.prop('checked', false); - _.each(self.$inputs.filter('[data-xmlid]:not([data-xmlid=""])'), function (input) { - var $input = $(input); - if (!_.difference(self._getXMLIDs($input), data[0]).length) { - $input.prop('checked', true); - } - }); - _.each(self.$inputs.filter('[data-xmlid=""]'), function (input) { - var $input = $(input); - if (!self.$inputs.filter('[name="' + $input.attr('name') + '"]:checked').length) { - $input.prop('checked', true); - } - }); - self._setActive(); - }).fail(function (d, error) { - Dialog.alert(this, error.data.message); }); }, /** @@ -316,7 +323,7 @@ var ThemeCustomizeDialog = Dialog.extend({ // Mark the labels as checked accordingly this.$('label').removeClass('checked'); - $enable.closest('label').addClass('checked'); + $enable.closest('label:not(.o_switch)').addClass('checked'); // Mark the option sets as checked if all their option are checked/unchecked var $sets = this.$inputs.filter('[data-enable], [data-disable]').not('[data-xmlid]'); @@ -329,7 +336,7 @@ var ThemeCustomizeDialog = Dialog.extend({ if (self._getInputs($set.data('disable')).filter(':checked').length) { checked = false; } - $set.prop('checked', checked).closest('label').toggleClass('checked', checked); + $set.prop('checked', checked).closest('label:not(.o_switch)').toggleClass('checked', checked); }); // Make the hidden sections visible if their dependencies are met @@ -359,9 +366,9 @@ var ThemeCustomizeDialog = Dialog.extend({ return this._rpc({ route: '/website/theme_customize', params: { - enable: enable, - disable: disable, - get_bundle: true, + 'enable': enable, + 'disable': disable, + 'get_bundle': true, }, }).then(function (bundles) { var defs = _.map(bundles, function (bundleContent, bundleName) { @@ -464,13 +471,13 @@ var ThemeCustomizeDialog = Dialog.extend({ self._rpc({ route: '/web_editor/get_assets_editor_resources', params: { - key: 'website.layout', - get_views: false, - get_scss: true, - get_js: false, - bundles: false, - bundles_restriction: [], - only_user_custom_files: false, + 'key': 'website.layout', + 'get_views': false, + 'get_scss': true, + 'get_js': false, + 'bundles': false, + 'bundles_restriction': [], + 'only_user_custom_files': false, }, }).then(function (data) { var files = data.scss[0][1]; @@ -505,10 +512,10 @@ var ThemeCustomizeDialog = Dialog.extend({ return self._rpc({ route: '/web_editor/save_scss_or_js', params: { - url: file.url, - bundle_xmlid: 'web.assets_common', - content: updatedFileContent, - file_type: 'scss', + 'url': file.url, + 'bundle_xmlid': 'web.assets_common', + 'content': updatedFileContent, + 'file_type': 'scss', }, }); }).then(function () { @@ -521,7 +528,7 @@ var ThemeCustomizeDialog = Dialog.extend({ var ThemeCustomizeMenu = websiteNavbarData.WebsiteNavbarActionWidget.extend({ actions: _.extend({}, websiteNavbarData.WebsiteNavbarActionWidget.prototype.actions || {}, { - customize_theme: '_openThemeCustomizeDialog', + 'customize_theme': '_openThemeCustomizeDialog', }), /** diff --git a/addons/website/static/src/xml/website.editor.xml b/addons/website/static/src/xml/website.editor.xml index 46d5de05c12..293b68a93a6 100644 --- a/addons/website/static/src/xml/website.editor.xml +++ b/addons/website/static/src/xml/website.editor.xml @@ -22,9 +22,9 @@ @@ -39,9 +39,13 @@
-
diff --git a/addons/website/views/website_templates.xml b/addons/website/views/website_templates.xml index e4598c5d52c..43b19a721cb 100644 --- a/addons/website/views/website_templates.xml +++ b/addons/website/views/website_templates.xml @@ -448,7 +448,7 @@ -> string = tab's nav text -> title = tab's heading - Allowed tags in the elements: + In the elements: : Declares a new toggle option -> id (optional) = an ID to associate to the option @@ -476,6 +476,14 @@ : Declares a subset of options showed as a list -> string = list's title -> data-col (optional, default 6) = bootstrap column size for the list + + Other elements will be ignored and their children will be processed as if + that element was omitted, except that those children will be considered as + in a new set of option. That allows to wrap an option in a fake element to + indicate it is a standalone option for example. + + Note: if an option is the only one of its group, a toggle element will be + rendered. --> -