forked from Techsystech/web
[FIX] web_responsive: Complete search feature forward-porting
parent
165a544578
commit
353a10bd0c
|
@ -6,18 +6,4 @@ Features:
|
||||||
* Keyboard shortcuts for easier navigation
|
* Keyboard shortcuts for easier navigation
|
||||||
* Display kanban views for small screens if an action or field One2x
|
* Display kanban views for small screens if an action or field One2x
|
||||||
* Set chatter side (Optional per user)
|
* Set chatter side (Optional per user)
|
||||||
* Quick search (see below)
|
* Quick search
|
||||||
|
|
||||||
The following keyboard shortcuts are implemented:
|
|
||||||
|
|
||||||
* Toggle App Drawer - `ActionKey <https://en.wikipedia.org/wiki/Access_key#Access_in_different_browsers>` + ``A``
|
|
||||||
* Navigate Apps Drawer - Arrow Keys
|
|
||||||
* Type to select App Links
|
|
||||||
* ``esc`` to close App Drawer
|
|
||||||
|
|
||||||
The search feature provided in the App Drawer allows you to easily
|
|
||||||
navigate menus without a mouse.
|
|
||||||
|
|
||||||
To activate the search, just begin typing while within the App Drawer.
|
|
||||||
You can use the arrow keys or mouse to navigate and select results,
|
|
||||||
in a similar fashion to navigating the apps in the drawer.
|
|
||||||
|
|
|
@ -13,10 +13,10 @@ this module.
|
||||||
* Sticky header and footer in list view only works on certain browsers:
|
* Sticky header and footer in list view only works on certain browsers:
|
||||||
https://caniuse.com/#search=sticky (note that the used feature is in
|
https://caniuse.com/#search=sticky (note that the used feature is in
|
||||||
`thead`).
|
`thead`).
|
||||||
* The ``AppDrawer`` JavaScript object currently extends ``Class``.
|
|
||||||
We should extend ``Widget`` instead.
|
|
||||||
* On Android (FireFox) - clicking the search icon does not
|
* On Android (FireFox) - clicking the search icon does not
|
||||||
focus the search input.
|
focus the search input.
|
||||||
* On Android (FireFox & Chrome) - clicking the search query input will
|
* On Android (FireFox & Chrome) - clicking the search query input will
|
||||||
show the on screen keyboard for a split second, but the App Drawer
|
show the on screen keyboard for a split second, but the App Drawer
|
||||||
immediately closes and the keyboard closes with it.
|
immediately closes and the keyboard closes with it.
|
||||||
|
* Filter menu items completely on client-side, to make it smoother and allow
|
||||||
|
users to filter on complete paths of menus and not only on the last item.
|
||||||
|
|
|
@ -5,14 +5,15 @@ odoo.define('web_responsive', function(require) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var Menu = require('web.Menu');
|
var Menu = require('web.Menu');
|
||||||
var Class = require('web.Class');
|
|
||||||
var rpc = require('web.rpc');
|
var rpc = require('web.rpc');
|
||||||
var SearchView = require('web.SearchView');
|
var SearchView = require('web.SearchView');
|
||||||
var core = require('web.core');
|
var core = require('web.core');
|
||||||
var config = require('web.config');
|
var config = require('web.config');
|
||||||
|
var session = require('web.session');
|
||||||
var ViewManager = require('web.ViewManager');
|
var ViewManager = require('web.ViewManager');
|
||||||
var RelationalFields = require('web.relational_fields');
|
var RelationalFields = require('web.relational_fields');
|
||||||
var FormRenderer = require('web.FormRenderer');
|
var FormRenderer = require('web.FormRenderer');
|
||||||
|
var Widget = require('web.Widget');
|
||||||
|
|
||||||
var qweb = core.qweb;
|
var qweb = core.qweb;
|
||||||
|
|
||||||
|
@ -30,7 +31,7 @@ odoo.define('web_responsive', function(require) {
|
||||||
this._super(id);
|
this._super(id);
|
||||||
if (allowOpen) {
|
if (allowOpen) {
|
||||||
return;
|
return;
|
||||||
};
|
}
|
||||||
var $clicked_menu = this.$secondary_menus.find('a[data-menu=' + id + ']');
|
var $clicked_menu = this.$secondary_menus.find('a[data-menu=' + id + ']');
|
||||||
$clicked_menu.parents('.oe_secondary_submenu').css('display', '');
|
$clicked_menu.parents('.oe_secondary_submenu').css('display', '');
|
||||||
}
|
}
|
||||||
|
@ -64,7 +65,7 @@ odoo.define('web_responsive', function(require) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var AppDrawer = Class.extend({
|
var AppDrawer = Widget.extend({
|
||||||
|
|
||||||
/* Provides all features inside of the application drawer navigation.
|
/* Provides all features inside of the application drawer navigation.
|
||||||
|
|
||||||
|
@ -102,7 +103,7 @@ odoo.define('web_responsive', function(require) {
|
||||||
searching: false,
|
searching: false,
|
||||||
|
|
||||||
init: function() {
|
init: function() {
|
||||||
|
this._super.apply(this, arguments);
|
||||||
this.directionCodes = {
|
this.directionCodes = {
|
||||||
'left': this.LEFT,
|
'left': this.LEFT,
|
||||||
'right': this.RIGHT,
|
'right': this.RIGHT,
|
||||||
|
@ -133,7 +134,7 @@ odoo.define('web_responsive', function(require) {
|
||||||
this.$el.find('.drawer-search-close').hide().click(
|
this.$el.find('.drawer-search-close').hide().click(
|
||||||
$.proxy(this.closeSearchMenus, this)
|
$.proxy(this.closeSearchMenus, this)
|
||||||
);
|
);
|
||||||
|
this.filter_timeout = $.Deferred();
|
||||||
core.bus.on('resize', this, this.handleWindowResize);
|
core.bus.on('resize', this, this.handleWindowResize);
|
||||||
core.bus.on('keydown', this, this.handleKeyDown);
|
core.bus.on('keydown', this, this.handleKeyDown);
|
||||||
core.bus.on('keyup', this, this.redirectKeyPresses);
|
core.bus.on('keyup', this, this.redirectKeyPresses);
|
||||||
|
@ -199,29 +200,23 @@ odoo.define('web_responsive', function(require) {
|
||||||
}
|
}
|
||||||
var directionCode = $.hotkeys.specialKeys[e.keyCode.toString()];
|
var directionCode = $.hotkeys.specialKeys[e.keyCode.toString()];
|
||||||
if (Object.keys(this.directionCodes).indexOf(directionCode) !== -1) {
|
if (Object.keys(this.directionCodes).indexOf(directionCode) !== -1) {
|
||||||
var $link = this.findAdjacentAppLink(
|
var $link = false;
|
||||||
this.$el.find('a:first, a:focus').last(),
|
|
||||||
this.directionCodes[directionCode]
|
|
||||||
);
|
|
||||||
this.selectAppLink($link);
|
|
||||||
} else if ($.hotkeys.specialKeys[e.keyCode.toString()] === 'esc') {
|
|
||||||
this.handleClickZones();
|
|
||||||
if (this.searching) {
|
if (this.searching) {
|
||||||
var $collection = this.$el.find('#appDrawerMenuSearch a');
|
var $collection = this.$el.find('#appDrawerMenuSearch a');
|
||||||
var $link = this.findAdjacentLink(
|
$link = this.findAdjacentLink(
|
||||||
this.$el.find('#appDrawerMenuSearch a:first, #appDrawerMenuSearch a.web-responsive-focus').last(),
|
this.$el.find('#appDrawerMenuSearch a:first, #appDrawerMenuSearch a.web-responsive-focus').last(),
|
||||||
this.directionCodes[directionCode],
|
this.directionCodes[directionCode],
|
||||||
$collection,
|
$collection,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
var $link = this.findAdjacentLink(
|
$link = this.findAdjacentLink(
|
||||||
this.$el.find('#appDrawerApps a:first, #appDrawerApps a.web-responsive-focus').last(),
|
this.$el.find('#appDrawerApps a:first, #appDrawerApps a.web-responsive-focus').last(),
|
||||||
this.directionCodes[directionCode]
|
this.directionCodes[directionCode]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
this.selectLink($link);
|
this.selectLink($link);
|
||||||
} else if ($.hotkeys.specialKeys[e.keyCode.toString()] == 'esc') {
|
} else if ($.hotkeys.specialKeys[e.keyCode.toString()] === 'esc') {
|
||||||
// We either back out of the search, or close the app drawer.
|
// We either back out of the search, or close the app drawer.
|
||||||
if (this.searching) {
|
if (this.searching) {
|
||||||
this.closeSearchMenus();
|
this.closeSearchMenus();
|
||||||
|
@ -240,7 +235,6 @@ odoo.define('web_responsive', function(require) {
|
||||||
* @param e The key event that was triggered by ``core.bus``.
|
* @param e The key event that was triggered by ``core.bus``.
|
||||||
*/
|
*/
|
||||||
redirectKeyPresses: function(e) {
|
redirectKeyPresses: function(e) {
|
||||||
|
|
||||||
if ( !this.isOpen ) {
|
if ( !this.isOpen ) {
|
||||||
// Drawer isn't open; Ignore.
|
// Drawer isn't open; Ignore.
|
||||||
return;
|
return;
|
||||||
|
@ -285,9 +279,8 @@ odoo.define('web_responsive', function(require) {
|
||||||
* @listens ``drawer.opened`` and sends to onDrawerOpen
|
* @listens ``drawer.opened`` and sends to onDrawerOpen
|
||||||
*/
|
*/
|
||||||
onDrawerClose: function() {
|
onDrawerClose: function() {
|
||||||
this.closeSearchMenus();
|
|
||||||
this.$searchAction.hide();
|
|
||||||
core.bus.trigger('drawer.closed');
|
core.bus.trigger('drawer.closed');
|
||||||
|
this.closeSearchMenus();
|
||||||
this.$el.one('drawer.opened', $.proxy(this.onDrawerOpen, this));
|
this.$el.one('drawer.opened', $.proxy(this.onDrawerOpen, this));
|
||||||
this.isOpen = false;
|
this.isOpen = false;
|
||||||
// Remove inline style inserted by drawer.js
|
// Remove inline style inserted by drawer.js
|
||||||
|
@ -295,15 +288,17 @@ odoo.define('web_responsive', function(require) {
|
||||||
},
|
},
|
||||||
|
|
||||||
/* Finds app links and register event handlers
|
/* Finds app links and register event handlers
|
||||||
* @fires ``drawer.opened`` to the ``core.bus``
|
* @fires ``drawer.opened`` to the ``core.bus``
|
||||||
* @listens ``drawer.closed`` and sends to :meth:``onDrawerClose``
|
* @listens ``drawer.closed`` and sends to :meth:``onDrawerClose``
|
||||||
*/
|
*/
|
||||||
onDrawerOpen: function() {
|
onDrawerOpen: function() {
|
||||||
|
this.closeSearchMenus();
|
||||||
this.$appLinks = $('.app-drawer-icon-app').parent();
|
this.$appLinks = $('.app-drawer-icon-app').parent();
|
||||||
this.selectLink($(this.$appLinks[0]));
|
this.selectLink($(this.$appLinks[0]));
|
||||||
this.$el.one('drawer.closed', $.proxy(this.onDrawerClose, this));
|
this.$el.one('drawer.closed', $.proxy(this.onDrawerClose, this));
|
||||||
core.bus.trigger('drawer.opened');
|
core.bus.trigger('drawer.opened');
|
||||||
this.isOpen = true;
|
this.isOpen = true;
|
||||||
|
this.$searchInput.val("");
|
||||||
},
|
},
|
||||||
|
|
||||||
// Selects a link visibly & deselects others.
|
// Selects a link visibly & deselects others.
|
||||||
|
@ -314,25 +309,39 @@ odoo.define('web_responsive', function(require) {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/* Searches for menus by name, then triggers showFoundMenus
|
/**
|
||||||
* @param query str to search
|
* Search matching menus immediately
|
||||||
* @return jQuery obj
|
|
||||||
*/
|
*/
|
||||||
searchMenus: function() {
|
_searchMenus: function () {
|
||||||
this.$searchInput = $('#appDrawerSearchInput').focus();
|
|
||||||
var domain = [['name', 'ilike', this.$searchInput.val()],
|
|
||||||
['action', '!=', false]];
|
|
||||||
rpc.query({
|
rpc.query({
|
||||||
model: 'ir.ui.menu',
|
model: 'ir.ui.menu',
|
||||||
method: 'search_read',
|
method: 'search_read',
|
||||||
args: [{
|
kwargs: {
|
||||||
fields: ['action', 'display_name', 'id'],
|
fields: ['action', 'display_name', 'id'],
|
||||||
domain: domain
|
domain: [
|
||||||
|
['name', 'ilike', this.$searchInput.val()],
|
||||||
|
['action', '!=', false],
|
||||||
|
],
|
||||||
|
context: session.user_context,
|
||||||
|
},
|
||||||
|
}).then(this.showFoundMenus.bind(this));
|
||||||
|
},
|
||||||
|
|
||||||
}]
|
/**
|
||||||
}).then(
|
* Queue the next menu search for the search input
|
||||||
$.proxy(this.showFoundMenus, this)
|
*/
|
||||||
|
searchMenus: function() {
|
||||||
|
// Stop current search, if any
|
||||||
|
this.filter_timeout.reject();
|
||||||
|
this.filter_timeout = $.Deferred();
|
||||||
|
// Schedule a new search
|
||||||
|
this.filter_timeout.done(this._searchMenus.bind(this));
|
||||||
|
setTimeout(
|
||||||
|
this.filter_timeout.resolve.bind(this.filter_timeout),
|
||||||
|
200
|
||||||
);
|
);
|
||||||
|
// Focus search input
|
||||||
|
this.$searchInput = $('#appDrawerSearchInput').focus();
|
||||||
},
|
},
|
||||||
|
|
||||||
/* Display the menus that are provided as input.
|
/* Display the menus that are provided as input.
|
||||||
|
@ -372,7 +381,6 @@ odoo.define('web_responsive', function(require) {
|
||||||
this.$el.find('.drawer-search-open').show();
|
this.$el.find('.drawer-search-open').show();
|
||||||
this.$searchResultsContainer.closest('#appDrawerMenuSearch').hide();
|
this.$searchResultsContainer.closest('#appDrawerMenuSearch').hide();
|
||||||
this.$searchAction.show();
|
this.$searchAction.show();
|
||||||
$('#appDrawerSearchInput').val('');
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/* Returns the link adjacent to $link in provided direction.
|
/* Returns the link adjacent to $link in provided direction.
|
||||||
|
@ -393,12 +401,12 @@ odoo.define('web_responsive', function(require) {
|
||||||
*/
|
*/
|
||||||
findAdjacentLink: function($link, direction, $objs, restrictHorizontal) {
|
findAdjacentLink: function($link, direction, $objs, restrictHorizontal) {
|
||||||
|
|
||||||
if ($objs === undefined) {
|
if (_.isUndefined($objs)) {
|
||||||
$objs = this.$appLinks;
|
$objs = this.$appLinks;
|
||||||
}
|
}
|
||||||
|
|
||||||
var obj = [];
|
var obj = [];
|
||||||
var $rows = (restrictHorizontal) ? $objs : this.getRowObjs($link, this.$appLinks);
|
var $rows = restrictHorizontal ? $objs : this.getRowObjs($link, this.$appLinks);
|
||||||
|
|
||||||
switch (direction) {
|
switch (direction) {
|
||||||
case this.LEFT:
|
case this.LEFT:
|
||||||
|
@ -516,11 +524,6 @@ odoo.define('web_responsive', function(require) {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'AppDrawer': AppDrawer,
|
'AppDrawer': AppDrawer,
|
||||||
'SearchView': SearchView,
|
|
||||||
'Menu': Menu,
|
|
||||||
'ViewManager': ViewManager,
|
|
||||||
'FieldStatus': RelationalFields.FieldStatus,
|
|
||||||
'FormRenderer': FormRenderer,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -118,35 +118,6 @@ odoo.define('web_responsive.test', function(require) {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
QUnit.test('It should return keybuffer + new key',
|
|
||||||
function(assert) {
|
|
||||||
assert.expect(1);
|
|
||||||
|
|
||||||
this.drawer.keyBuffer = 'TES';
|
|
||||||
var res = this.drawer.handleKeyBuffer(84);
|
|
||||||
assert.equal(res, 'TEST');
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
QUnit.test('It should clear keybuffer after timeout',
|
|
||||||
function(assert) {
|
|
||||||
assert.expect(1);
|
|
||||||
|
|
||||||
this.drawer.keyBuffer = 'TES';
|
|
||||||
this.drawer.keyBufferTime = 10;
|
|
||||||
this.drawer.handleKeyBuffer(84);
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
var d = $.Deferred();
|
|
||||||
setTimeout(function() {
|
|
||||||
assert.equal(self.drawer.keyBuffer, "");
|
|
||||||
d.resolve();
|
|
||||||
}, 100);
|
|
||||||
|
|
||||||
return d;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
QUnit.test('It should trigger core bus event for drawer close',
|
QUnit.test('It should trigger core bus event for drawer close',
|
||||||
function(assert) {
|
function(assert) {
|
||||||
assert.expect(1);
|
assert.expect(1);
|
||||||
|
@ -224,7 +195,7 @@ odoo.define('web_responsive.test', function(require) {
|
||||||
|
|
||||||
var $appLink = $('#a_1'),
|
var $appLink = $('#a_1'),
|
||||||
$expect = $('#a_2'),
|
$expect = $('#a_2'),
|
||||||
$res = this.drawer.findAdjacentAppLink(
|
$res = this.drawer.findAdjacentLink(
|
||||||
$appLink, this.drawer.RIGHT
|
$appLink, this.drawer.RIGHT
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -239,7 +210,7 @@ odoo.define('web_responsive.test', function(require) {
|
||||||
this.linkGrid();
|
this.linkGrid();
|
||||||
var $appLink = $('#a_2'),
|
var $appLink = $('#a_2'),
|
||||||
$expect = $('#a_1'),
|
$expect = $('#a_1'),
|
||||||
$res = this.drawer.findAdjacentAppLink(
|
$res = this.drawer.findAdjacentLink(
|
||||||
$appLink, this.drawer.LEFT
|
$appLink, this.drawer.LEFT
|
||||||
);
|
);
|
||||||
assert.equal($res[0].id, $expect[0].id);
|
assert.equal($res[0].id, $expect[0].id);
|
||||||
|
@ -253,7 +224,7 @@ odoo.define('web_responsive.test', function(require) {
|
||||||
this.linkGrid();
|
this.linkGrid();
|
||||||
var $appLink = $('#a_1'),
|
var $appLink = $('#a_1'),
|
||||||
$expect = $('#a_0'),
|
$expect = $('#a_0'),
|
||||||
$res = this.drawer.findAdjacentAppLink(
|
$res = this.drawer.findAdjacentLink(
|
||||||
$appLink, this.drawer.UP
|
$appLink, this.drawer.UP
|
||||||
);
|
);
|
||||||
assert.equal($res[0].id, $expect[0].id);
|
assert.equal($res[0].id, $expect[0].id);
|
||||||
|
@ -267,7 +238,7 @@ odoo.define('web_responsive.test', function(require) {
|
||||||
this.linkGrid();
|
this.linkGrid();
|
||||||
var $appLink = $('#a_1'),
|
var $appLink = $('#a_1'),
|
||||||
$expect = $('#a_2'),
|
$expect = $('#a_2'),
|
||||||
$res = this.drawer.findAdjacentAppLink(
|
$res = this.drawer.findAdjacentLink(
|
||||||
$appLink, this.drawer.DOWN
|
$appLink, this.drawer.DOWN
|
||||||
);
|
);
|
||||||
assert.equal($res[0].id, $expect[0].id);
|
assert.equal($res[0].id, $expect[0].id);
|
||||||
|
@ -281,7 +252,7 @@ odoo.define('web_responsive.test', function(require) {
|
||||||
this.linkGrid();
|
this.linkGrid();
|
||||||
var $appLink = $('#b_2'),
|
var $appLink = $('#b_2'),
|
||||||
$expect = $('#a_0'),
|
$expect = $('#a_0'),
|
||||||
$res = this.drawer.findAdjacentAppLink(
|
$res = this.drawer.findAdjacentLink(
|
||||||
$appLink, this.drawer.RIGHT
|
$appLink, this.drawer.RIGHT
|
||||||
);
|
);
|
||||||
assert.equal($res[0].id, $expect[0].id);
|
assert.equal($res[0].id, $expect[0].id);
|
||||||
|
@ -295,7 +266,7 @@ odoo.define('web_responsive.test', function(require) {
|
||||||
this.linkGrid();
|
this.linkGrid();
|
||||||
var $appLink = $('#a_0'),
|
var $appLink = $('#a_0'),
|
||||||
$expect = $('#a_2'),
|
$expect = $('#a_2'),
|
||||||
$res = this.drawer.findAdjacentAppLink(
|
$res = this.drawer.findAdjacentLink(
|
||||||
$appLink, this.drawer.UP
|
$appLink, this.drawer.UP
|
||||||
);
|
);
|
||||||
assert.equal($res[0].id, $expect[0].id);
|
assert.equal($res[0].id, $expect[0].id);
|
||||||
|
@ -309,7 +280,7 @@ odoo.define('web_responsive.test', function(require) {
|
||||||
this.linkGrid();
|
this.linkGrid();
|
||||||
var $appLink = $('#a_2'),
|
var $appLink = $('#a_2'),
|
||||||
$expect = $('#a_0'),
|
$expect = $('#a_0'),
|
||||||
$res = this.drawer.findAdjacentAppLink(
|
$res = this.drawer.findAdjacentLink(
|
||||||
$appLink, this.drawer.DOWN
|
$appLink, this.drawer.DOWN
|
||||||
);
|
);
|
||||||
assert.equal($res[0].id, $expect[0].id);
|
assert.equal($res[0].id, $expect[0].id);
|
||||||
|
|
Loading…
Reference in New Issue