diff --git a/addons/web/static/src/js/chrome/control_panel.js b/addons/web/static/src/js/chrome/control_panel.js index 4104f37bd3e..0913b574a3d 100644 --- a/addons/web/static/src/js/chrome/control_panel.js +++ b/addons/web/static/src/js/chrome/control_panel.js @@ -148,15 +148,25 @@ var ControlPanel = Widget.extend({ } // Detach control_panel old content and attach new elements + var toDetach = this.nodes; + if (status.searchview && this.searchview === status.searchview) { + // If the searchview is the same as before, don't detach it s.t. + // we don't loose any floating content, nor the focus + toDetach = _.omit(toDetach, '$searchview'); + new_cp_content = _.omit(new_cp_content, '$searchview'); + } if (options.clear) { - this._detach_content(this.nodes); + this._detach_content(toDetach); // Show the searchview buttons area, which might have been hidden by // the searchview, as client actions may insert elements into it this.nodes.$searchview_buttons.show(); } else { - this._detach_content(_.pick(this.nodes, _.keys(new_cp_content))); + this._detach_content(_.pick(toDetach, _.keys(new_cp_content))); } this._attach_content(new_cp_content); + if (options.clear || status.searchview) { + this.searchview = status.searchview; + } // Update the searchview and switch buttons if (status.searchview || options.clear) { diff --git a/addons/web/static/tests/chrome/action_manager_tests.js b/addons/web/static/tests/chrome/action_manager_tests.js index e057db97f7a..241a125dd3c 100644 --- a/addons/web/static/tests/chrome/action_manager_tests.js +++ b/addons/web/static/tests/chrome/action_manager_tests.js @@ -3119,6 +3119,62 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); + + QUnit.module('Search View Action'); + + QUnit.test('search view should keep focus during do_search', function (assert) { + assert.expect(5); + + /* One should be able to type something in the search view, press on enter to + * make the facet and trigger the search, then do this process + * over and over again seamlessly. + * Verifying the input's value is a lot trickier than verifying the search_read + * because of how native events are handled in tests + */ + + var searchDeferred = $.Deferred(); + + var actionManager = createActionManager({ + actions: this.actions, + archs: this.archs, + data: this.data, + mockRPC: function (route, args) { + if (route === '/web/dataset/search_read') { + assert.step('search_read ' + args.domain); + if ( _.isEqual(args.domain, [['foo', 'ilike', 'm']])) { + return searchDeferred.then(this._super.bind(this, route, args)); + } + } + return this._super.apply(this, arguments); + } + }); + + actionManager.doAction(3); + + var $searchInput = $('.o_searchview input'); + $searchInput.trigger($.Event('keypress', {key: 'm', which: 109, keyCode: 109})); + $searchInput.trigger($.Event('keydown', {key: 'Enter', which: 13, keyCode: 13})); + + assert.verifySteps(["search_read ", + "search_read foo,ilike,m"]); + + // Triggering the do_search above will kill the current searchview Input + $searchInput = $('.o_searchview input'); + $searchInput.trigger($.Event('keypress', {key: 'o', which: 111, keyCode: 111})); + + // We have something in the input of the search view. Making the search_read + // return at this point will trigger the redraw of the view. + // However we want to hold on to what we just typed + searchDeferred.resolve(); + + $searchInput.trigger($.Event('keydown', {key: 'Enter', which: 13, keyCode: 13})); + + assert.verifySteps(["search_read ", + "search_read foo,ilike,m", + "search_read |,foo,ilike,m,foo,ilike,o"]); + + actionManager.destroy(); + }); }); });