diff options
| author | Christophe Simonis <chs@odoo.com> | 2018-03-21 11:04:48 +0100 |
|---|---|---|
| committer | Christophe Simonis <chs@odoo.com> | 2018-03-21 11:04:48 +0100 |
| commit | b51f2d38c247dce9afc04c29774a77d773e3f393 (patch) | |
| tree | 1133e0dd4c082543d1f361fb7ec160ac0b2cccc6 /addons/web/static/src | |
| parent | c7dff653d56910fd46e4dcf745c2c6088ce9fc58 (diff) | |
| parent | 500e3f497057c5d00ae5cc34da7012c6f31f81d4 (diff) | |
[MERGE] forward port branch saas-11.2 up to 500e3f49705
Diffstat (limited to 'addons/web/static/src')
| -rw-r--r-- | addons/web/static/src/js/chrome/action_manager.js | 28 | ||||
| -rw-r--r-- | addons/web/static/src/js/chrome/action_manager_act_window.js | 48 | ||||
| -rw-r--r-- | addons/web/static/src/js/chrome/control_panel.js | 3 | ||||
| -rw-r--r-- | addons/web/static/src/js/chrome/menu.js | 17 | ||||
| -rw-r--r-- | addons/web/static/src/js/chrome/web_client.js | 4 | ||||
| -rw-r--r-- | addons/web/static/src/js/fields/basic_fields.js | 5 | ||||
| -rw-r--r-- | addons/web/static/src/js/fields/relational_fields.js | 29 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/abstract_controller.js | 2 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/basic/basic_model.js | 19 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/form/form_renderer.js | 4 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/view_dialogs.js | 1 | ||||
| -rw-r--r-- | addons/web/static/src/less/kanban_view.less | 4 | ||||
| -rw-r--r-- | addons/web/static/src/less/kanban_view_mobile.less | 3 | ||||
| -rw-r--r-- | addons/web/static/src/xml/base.xml | 28 |
14 files changed, 127 insertions, 68 deletions
diff --git a/addons/web/static/src/js/chrome/action_manager.js b/addons/web/static/src/js/chrome/action_manager.js index 4cf10b8813a..c4d3f591545 100644 --- a/addons/web/static/src/js/chrome/action_manager.js +++ b/addons/web/static/src/js/chrome/action_manager.js @@ -62,7 +62,6 @@ var ActionManager = Widget.extend({ // dialog (i.e. coming from an action with target='new') this.currentDialogController = null; }, - /** * @override */ @@ -140,7 +139,7 @@ var ActionManager = Widget.extend({ * loaded and appended to the DOM ; rejected if the action can't be * executed (e.g. if doAction has been called to execute another action * before this one was complete). - */ + */ doAction: function (action, options) { var self = this; options = _.defaults({}, options, { @@ -177,6 +176,11 @@ var ActionManager = Widget.extend({ self._preprocessAction(action, options); return self._handleAction(action, options).then(function () { + // now that the action has been executed, force its 'pushState' + // flag to 'true', as we don't want to prevent its controller + // from pushing its state if it changes in the future + action.pushState = true; + return action; }); }); @@ -354,11 +358,6 @@ var ActionManager = Widget.extend({ // toggle the fullscreen mode for actions in target='fullscreen' self._toggleFullscreen(); - // now that the action has been executed, force its 'pushState' - // flag to 'true', as we don't want to prevent its controller - // from pushing its state if it changes in the future - action.pushState = true; - return action; }) .fail(function () { @@ -451,6 +450,7 @@ var ActionManager = Widget.extend({ } var controllerID = _.uniqueId('controller_'); + options.controllerID = controllerID; var controller = { actionID: action.jsID, jsID: controllerID, @@ -903,13 +903,13 @@ var ActionManager = Widget.extend({ } }, /** - * Intercepts and triggers a redirection on a link - * - * @private - * @param {OdooEvent} ev - * @param {integer} ev.data.res_id - * @param {string} ev.data.res_model - */ + * Intercepts and triggers a redirection on a link. + * + * @private + * @param {OdooEvent} ev + * @param {integer} ev.data.res_id + * @param {string} ev.data.res_model + */ _onRedirect: function (ev) { this.do_action({ type:'ir.actions.act_window', diff --git a/addons/web/static/src/js/chrome/action_manager_act_window.js b/addons/web/static/src/js/chrome/action_manager_act_window.js index dfb512900e8..4e636de6355 100644 --- a/addons/web/static/src/js/chrome/action_manager_act_window.js +++ b/addons/web/static/src/js/chrome/action_manager_act_window.js @@ -224,6 +224,31 @@ ActionManager.include({ return action.controllers[viewType]; }, /** + * Destroys the controllers and search view of a given action of type + * 'ir.actions.act_window'. + * + * @private + * @param {Object} action + */ + _destroyWindowAction: function (action) { + var self = this; + _.each(action.controllers, function (controllerDef) { + controllerDef.then(function (controller) { + delete self.controllers[controller.jsID]; + if (controller.widget) { + controller.widget.destroy(); + } + }); + // reject the deferred if it is not yet resolved, so that the + // controller is correctly destroyed as soon as it is ready, and + // its reference is removed + controllerDef.reject(); + }); + if (action.searchView) { + action.searchView.destroy(); + } + }, + /** * Executes actions of type 'ir.actions.act_window'. * * @private @@ -245,7 +270,7 @@ ActionManager.include({ } action.flags = this._generateActionFlags(action); - return this._loadViews(action).then(function (fieldsViews) { + return this.dp.add(this._loadViews(action)).then(function (fieldsViews) { var views = self._generateActionViews(action, fieldsViews); action._views = action.views; // save the initial attribute action.views = views; @@ -291,7 +316,7 @@ ActionManager.include({ if (mainView) { defs.push(self._createViewController(action, mainView.type, {}, {lazy: true})); } - return $.when.apply($, defs); + return self.dp.add($.when.apply($, defs)); }).then(function (controller, lazyLoadedController) { action.controllerID = controller.jsID; return self._executeAction(action, options).done(function () { @@ -302,7 +327,7 @@ ActionManager.include({ }, {clear: false}); } }); - }); + }).fail(self._destroyWindowAction.bind(self, action)); }); }, /** @@ -490,25 +515,10 @@ ActionManager.include({ * @private */ _removeAction: function (actionID) { - var self = this; var action = this.actions[actionID]; if (action.type === 'ir.actions.act_window') { delete this.actions[action.jsID]; - _.each(action.controllers, function (controllerDef) { - controllerDef.then(function (controller) { - delete self.controllers[controller.jsID]; - if (controller.widget) { - controller.widget.destroy(); - } - }); - // reject the deferred if it is not yet resolved, so that the - // controller is correctly destroyed as soon as it is ready, and - // its reference is removed - controllerDef.reject(); - }); - if (action.searchView) { - action.searchView.destroy(); - } + this._destroyWindowAction(action); } else { this._super.apply(this, arguments); } diff --git a/addons/web/static/src/js/chrome/control_panel.js b/addons/web/static/src/js/chrome/control_panel.js index 03b9e756bb7..558dde933e9 100644 --- a/addons/web/static/src/js/chrome/control_panel.js +++ b/addons/web/static/src/js/chrome/control_panel.js @@ -252,9 +252,6 @@ var ControlPanel = Widget.extend({ */ _update_search_view: function (searchview, isHidden, groupable) { if (searchview) { - // Set the $buttons div (in the DOM) of the searchview as the $buttons - // have been appended to a jQuery node not in the DOM at SearchView initialization - searchview.$buttons = this.nodes.$searchview_buttons; searchview.toggle_visibility(!isHidden); if (groupable !== undefined){ searchview.groupby_menu.do_toggle(groupable); diff --git a/addons/web/static/src/js/chrome/menu.js b/addons/web/static/src/js/chrome/menu.js index 1e5e2922186..1e253ba54f7 100644 --- a/addons/web/static/src/js/chrome/menu.js +++ b/addons/web/static/src/js/chrome/menu.js @@ -147,15 +147,22 @@ var Menu = Widget.extend({ }); }, /** - * Call open_menu with the first menu_item matching an action_id + * Call open_menu on a menu_item that matches the action_id + * + * If `menuID` is a match on this action, open this menu_item. + * Otherwise open the first menu_item that matches the action_id. * * @param {Number} id the action_id to match + * @param {Number} [menuID] a menu ID that may match with provided action */ - open_action: function (id) { + open_action: function (id, menuID) { var $menu = this.$el.add(this.$secondary_menus).find('a[data-action-id="' + id + '"]'); - var menu_id = $menu.data('menu'); - if (menu_id) { - this.open_menu(menu_id); + if (!(menuID && $menu.filter("[data-menu='" + menuID + "']").length)) { + // menuID doesn't match action, so pick first menu_item + menuID = $menu.data('menu'); + } + if (menuID) { + this.open_menu(menuID); } }, /** diff --git a/addons/web/static/src/js/chrome/web_client.js b/addons/web/static/src/js/chrome/web_client.js index 3f832a6a49f..e0ce1f2c9f2 100644 --- a/addons/web/static/src/js/chrome/web_client.js +++ b/addons/web/static/src/js/chrome/web_client.js @@ -136,7 +136,7 @@ return AbstractWebClient.extend({ self.action_manager.loadState(state, !!self._current_state).then(function () { var action = self.action_manager.getCurrentAction(); if (action) { - self.menu.open_action(action.id); + self.menu.open_action(action.id, state.menu_id); } }); } @@ -157,7 +157,7 @@ return AbstractWebClient.extend({ var completed = $.Deferred(); self.action_manager.doAction(result, { clear_breadcrumbs: true, - action_menu_id: self.menu.current_menu, + action_menu_id: options.id, }).fail(function() { self.menu.open_menu(options.previous_menu_id); }).always(function() { diff --git a/addons/web/static/src/js/fields/basic_fields.js b/addons/web/static/src/js/fields/basic_fields.js index 3b55efbc826..3a5a1537387 100644 --- a/addons/web/static/src/js/fields/basic_fields.js +++ b/addons/web/static/src/js/fields/basic_fields.js @@ -2062,8 +2062,9 @@ var FieldToggleBoolean = AbstractField.extend({ * @private */ _render: function () { - var className = this.value ? 'o_toggle_button_success' : 'text-muted'; - this.$('i').addClass('fa fa-circle ' + className); + this.$('i') + .toggleClass('o_toggle_button_success', !!this.value) + .toggleClass('text-muted', !this.value); var title = this.value ? this.attrs.options.active : this.attrs.options.inactive; this.$el.attr('title', title); }, diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js index ecd7dd688e6..8f5ffd65fac 100644 --- a/addons/web/static/src/js/fields/relational_fields.js +++ b/addons/web/static/src/js/fields/relational_fields.js @@ -878,10 +878,15 @@ var FieldX2Many = AbstractField.extend({ this.pager = new Pager(this, this.value.count, this.value.offset + 1, this.value.limit, { single_page_hidden: true, withAccessKey: false, + validate: function () { + var isList = self.view.arch.tag === 'tree'; + // TODO: we should have some common method in the basic renderer... + return isList ? self.renderer.unselectRow() : $.when(); + }, }); this.pager.on('pager_changed', this, function (new_state) { - this.trigger_up('load', { - id: this.value.id, + self.trigger_up('load', { + id: self.value.id, limit: new_state.limit, offset: new_state.current_min - 1, on_success: function (value) { @@ -930,6 +935,7 @@ var FieldX2Many = AbstractField.extend({ * did not agree to discard. */ _saveLine: function (recordID) { + var self = this; var def = $.Deferred(); var fieldNames = this.renderer.canBeSaved(recordID); if (fieldNames.length) { @@ -941,7 +947,9 @@ var FieldX2Many = AbstractField.extend({ } else { this.renderer.setRowMode(recordID, 'readonly').done(def.resolve.bind(def)); } - return def; + return def.then(function () { + self.pager.updateState({ size: self.value.count }); + }); }, /** * Parses the 'columnInvisibleFields' attribute to search for the domains @@ -1178,7 +1186,20 @@ var FieldOne2Many = FieldX2Many.extend({ return this._super.apply(this, arguments).then(function () { if (ev && ev.target === self && ev.data.changes && self.view.arch.tag === 'tree') { if (ev.data.changes[self.name].operation === 'CREATE') { - var index = self.editable === 'top' ? 0 : self.value.data.length - 1; + var index = 0; + if (self.editable !== 'top') { + index = self.value.data.length - 1; + // we consider that a new record, created in the bottom, + // does not count as a record worth mentioning in the + // pager, at least not until the line has been saved. + // We prevent the pager from increasing its size, which + // means that the pager is not displayed when we just + // reach the limit. For example, if limit is 3, if we + // have 3 records, and we click on add, we will see the + // 4 records on the same page, but we do not want a + // pager. + self.pager.updateState({ size: self.value.count - 1}); + } var newID = self.value.data[index].id; self.renderer.editRecord(newID); } diff --git a/addons/web/static/src/js/views/abstract_controller.js b/addons/web/static/src/js/views/abstract_controller.js index ee39fd95596..4fff15d5663 100644 --- a/addons/web/static/src/js/views/abstract_controller.js +++ b/addons/web/static/src/js/views/abstract_controller.js @@ -330,7 +330,7 @@ var AbstractController = AbstractAction.extend(ControlPanelMixin, { if (this.searchView) { _.extend(cpContent, { $searchview: this.searchView.$el, - $searchview_buttons: this.searchView.$buttons.contents(), + $searchview_buttons: this.searchView.$buttons, }); } this.update_control_panel({ diff --git a/addons/web/static/src/js/views/basic/basic_model.js b/addons/web/static/src/js/views/basic/basic_model.js index 3013cb32f85..4cf6f9fa27b 100644 --- a/addons/web/static/src/js/views/basic/basic_model.js +++ b/addons/web/static/src/js/views/basic/basic_model.js @@ -370,6 +370,11 @@ var BasicModel = AbstractModel.extend({ elem._changes = null; elem._isDirty = false; } + elem.offset = 0; + if (elem.tempLimitIncrement) { + elem.limit -= elem.tempLimitIncrement; + delete elem.tempLimitIncrement; + } }); }, /** @@ -820,6 +825,7 @@ var BasicModel = AbstractModel.extend({ this.discardChanges(id, {rollback: false}); } } else if (element._changes) { + delete element.tempLimitIncrement; _.each(element._changes, function (change) { delete change.isNew; }); @@ -1657,6 +1663,12 @@ var BasicModel = AbstractModel.extend({ // handle multiple add: command[2] may be a dict of values (1 // record added) or an array of dict of values var data = _.isArray(command.ids) ? command.ids : [command.ids]; + + // Ensure the local data repository (list) boundaries can handle incoming records (data) + if (data.length + list.res_ids.length > list.limit) { + list.limit = data.length + list.res_ids.length; + } + var list_records = {}; _.each(data, function (d) { rec = self._makeDataPoint({ @@ -1701,6 +1713,10 @@ var BasicModel = AbstractModel.extend({ case 'CREATE': var options = {position: command.position}; def = this._addX2ManyDefaultRecord(list, options).then(function (id) { + if (command.position === 'bottom' && list.orderedResIDs.length >= list.limit) { + list.tempLimitIncrement = (list.tempLimitIncrement || 0) + 1; + list.limit += 1; + } // FIXME: hack for lunch widget, which does useless default_get and onchange if (command.data) { return self._applyChange(id, command.data); @@ -3856,7 +3872,8 @@ var BasicModel = AbstractModel.extend({ // generate the current count and res_ids list by applying the changes var currentCount = list.count; var currentResIDs = list.res_ids; - var upperBound = list.limit ? Math.min(list.offset + list.limit, currentCount) : currentCount; + var effectiveLimit = (list.limit || 0) - (list.tempLimitIncrement || 0); + var upperBound = effectiveLimit ? Math.min(list.offset + effectiveLimit, currentCount) : currentCount; var fieldNames = list.getFieldNames(); for (var i = list.offset; i < upperBound; i++) { var resId = currentResIDs[i]; diff --git a/addons/web/static/src/js/views/form/form_renderer.js b/addons/web/static/src/js/views/form/form_renderer.js index b28d25c5b90..f6430d4a251 100644 --- a/addons/web/static/src/js/views/form/form_renderer.js +++ b/addons/web/static/src/js/views/form/form_renderer.js @@ -613,6 +613,10 @@ var FormRenderer = BasicRenderer.extend({ var $result = $('<div/>', {class: 'o_group'}); var colSize = Math.max(1, Math.round(12 / (parseInt(node.attrs.col, 10) || 2))); + if (node.attrs.string) { + var $sep = $('<div/>', {class: 'o_horizontal_separator'}).text(node.attrs.string); + $result.append($sep); + } $result.append(_.map(node.children, function (child) { if (child.tag === 'newline') { return $('<br/>'); diff --git a/addons/web/static/src/js/views/view_dialogs.js b/addons/web/static/src/js/views/view_dialogs.js index 1b74068d44d..d4ae709008c 100644 --- a/addons/web/static/src/js/views/view_dialogs.js +++ b/addons/web/static/src/js/views/view_dialogs.js @@ -252,6 +252,7 @@ var SelectCreateDialog = ViewDialog.extend({ } }, selection_changed: function (event) { + event.stopPropagation(); this.$footer.find(".o_select_button").prop('disabled', !event.data.selection.length); }, search: function (event) { diff --git a/addons/web/static/src/less/kanban_view.less b/addons/web/static/src/less/kanban_view.less index d28221ede63..b9eec962eaf 100644 --- a/addons/web/static/src/less/kanban_view.less +++ b/addons/web/static/src/less/kanban_view.less @@ -229,7 +229,7 @@ height: 20px; } .oe_kanban_bottom_left { - .o-flex(1, 0, auto); + .o-flex(1, 1, auto); > * { margin-right: 6px; @@ -242,7 +242,7 @@ } } .oe_kanban_bottom_right{ - .o-flex(0, 0, auto); + .o-flex(0, 1, auto); .oe_kanban_avatar { width: 20px; diff --git a/addons/web/static/src/less/kanban_view_mobile.less b/addons/web/static/src/less/kanban_view_mobile.less index 72ba087e0c2..d09b6968925 100644 --- a/addons/web/static/src/less/kanban_view_mobile.less +++ b/addons/web/static/src/less/kanban_view_mobile.less @@ -29,7 +29,8 @@ } } - &.o_kanban_small_column .o_kanban_group:not(.o_column_folded) { + // [class] to get same specificity as elsewhere (kanban_view.less) + &[class] .o_kanban_group:not(.o_column_folded) { .o-position-absolute(@top: @o-kanban-mobile-tabs-height, @left: 0, @bottom: 0); overflow-x: scroll; width: 100%; diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index 748addc4b87..cc046a2bb89 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -209,7 +209,7 @@ <t t-name="WebClient.DebugManager"> <li class="o_debug_manager"> - <a href="#" title="Open Developer Tools" class="dropdown-toggle" data-toggle="dropdown"> + <a href="#" title="Open Developer Tools" class="dropdown-toggle" data-toggle="dropdown" aria-expanded="false"> <span class="fa fa-bug"/> </a> <ul class="dropdown-menu o_debug_dropdown" role="menu"/> @@ -325,12 +325,12 @@ <t t-name="Sidebar"> <t t-foreach="widget.sections" t-as="section"> <div class="btn-group o_dropdown"> - <button t-if="section.name != 'buttons'" class="o_dropdown_toggler_btn btn btn-sm dropdown-toggle" data-toggle="dropdown"> + <button t-if="section.name != 'buttons'" class="o_dropdown_toggler_btn btn btn-sm dropdown-toggle" data-toggle="dropdown" aria-expanded="false"> <t t-if="section.name == 'files'" t-raw="widget.items[section.name].length || ''"/> <t t-esc="section.label"/> <span class="caret"/> </button> <t t-if="section.name == 'buttons'" t-foreach="widget.items[section.name]" t-as="item" t-att-class="item.classname"> - <a t-att-title="item.title or None" t-att-data-section="section.name" t-att-data-index="item_index" t-att-href="item.url" target="_blank"> + <a t-att-title="item.title or None" t-att-data-section="section.name" t-att-data-index="item_index" t-att-href="item.url or '#'" target="_blank"> <t t-raw="item.label"/> </a> </t> @@ -352,7 +352,7 @@ <t t-raw="item.write_uid[1]"/> <t t-esc="item.write_date_string"/> </t> </t> - <a t-att-title="item.title or None" t-att-data-section="section.name" t-att-data-index="item_index" t-att-href="item.url"> + <a t-att-title="item.title or None" t-att-data-section="section.name" t-att-data-index="item_index" t-att-href="item.url or '#'"> <t t-raw="item.label"/> <span t-if="section.name == 'files' and widget.options.editable and !item.callback" class="fa fa-trash-o o_sidebar_delete_attachment" t-att-data-id="item.id" title="Delete this attachment"/> </a> @@ -469,7 +469,7 @@ </form> <t t-name="GraphView.buttons"> <div class="btn-group btn-group-sm" role="group"> - <button class="btn btn-primary btn-sm dropdown-toggle" data-toggle="dropdown"> + <button class="btn btn-primary btn-sm dropdown-toggle" data-toggle="dropdown" aria-expanded="false"> Measures <span class="caret"/> </button> <ul class="dropdown-menu o_graph_measures_list"> @@ -498,7 +498,7 @@ </div> <t t-name="PivotView.buttons"> <div class="btn-group btn-group-sm" role="group"> - <button class="btn btn-primary btn-sm dropdown-toggle" data-toggle="dropdown"> + <button class="btn btn-primary btn-sm dropdown-toggle" data-toggle="dropdown" aria-expanded="false"> Measures <span class="caret"/> </button> <ul class="dropdown-menu o_pivot_measures_list"> @@ -852,7 +852,7 @@ <t t-call="FieldStatus.content.button"/> </li> </ul> - <button type="button" class="btn btn-sm o_arrow_button btn-default dropdown-toggle" data-toggle="dropdown">More <span class="caret"/></button> + <button type="button" class="btn btn-sm o_arrow_button btn-default dropdown-toggle" data-toggle="dropdown" aria-expanded="false">More <span class="caret"/></button> </t> <t t-foreach="selection_unfolded.reverse()" t-as="i"> <t t-call="FieldStatus.content.button"/> @@ -1011,7 +1011,7 @@ <ul class="dropdown-menu o_searchview_autocomplete" role="menu"></ul> </t> <div t-name="SearchView.FilterMenu" class="btn-group o_dropdown"> - <button class="o_dropdown_toggler_btn btn btn-sm dropdown-toggle" data-toggle="dropdown"> + <button class="o_dropdown_toggler_btn btn btn-sm dropdown-toggle" data-toggle="dropdown" aria-expanded="false"> <span class="fa fa-filter"/> Filters <span class="caret"/> </button> <ul class="dropdown-menu o_filters_menu" role="menu"> @@ -1123,7 +1123,7 @@ </select> </t> <div t-name="SearchView.GroupByMenu" class="btn-group hidden-xs o_dropdown"> - <button class="o_dropdown_toggler_btn btn btn-sm dropdown-toggle" data-toggle="dropdown"> + <button class="o_dropdown_toggler_btn btn btn-sm dropdown-toggle" data-toggle="dropdown" aria-expanded="false"> <span class="fa fa-bars"/> Group By <span class="caret"/> </button> <ul class="dropdown-menu o_group_by_menu" role="menu"> @@ -1144,7 +1144,7 @@ </li> </t> <div t-name="SearchView.FavoriteMenu" class="btn-group o_dropdown"> - <button class="o_dropdown_toggler_btn btn btn-sm dropdown-toggle" data-toggle="dropdown"> + <button class="o_dropdown_toggler_btn btn btn-sm dropdown-toggle" data-toggle="dropdown" aria-expanded="false"> <span class="fa fa-star"/> Favorites <span class="caret"/> </button> <ul class="dropdown-menu o_favorites_menu" role="menu"> @@ -1273,8 +1273,8 @@ <span class="o_stat_text"><t t-esc="text"/></span> </t> <t t-name="toggle_button"> - <button type="button" class="o_icon_button" t-att-title="widget.string"> - <i t-attf-class="fa #{widget.icon}" t-att-title="widget.string"/> + <button type="button" class="o_icon_button" t-att-title="widget.string" t-att-aria-label="widget.string"> + <i class="fa fa-circle" t-att-title="widget.string"/> </button> </t> @@ -1288,8 +1288,8 @@ <t t-set="att_prev" t-value="{'accesskey': 'p'}" /> <t t-set="att_next" t-value="{'accesskey': 'n'}" /> </t> - <button type="button" class="fa fa-chevron-left btn btn-icon o_pager_previous" t-att="att_prev"/> - <button type="button" class="fa fa-chevron-right btn btn-icon o_pager_next" t-att="att_next"/> + <button type="button" class="fa fa-chevron-left btn btn-icon o_pager_previous" t-att="att_prev" aria-label="Previous"/> + <button type="button" class="fa fa-chevron-right btn btn-icon o_pager_next" t-att="att_next" aria-label="Next"/> </span> </div> |
