diff --git a/src/common.js b/src/common.js
index 4b6c28fabc..2d84dcf69e 100644
--- a/src/common.js
+++ b/src/common.js
@@ -409,6 +409,52 @@
});
Sao.common.MODELHISTORY = new Sao.common.ModelHistory();
+ Sao.common.ViewSearch = Sao.class_(Object, {
+ load_searches: function() {
+ this.searches = {};
+ return Sao.rpc({
+ 'method': 'model.ir.ui.view_search.get_search',
+ 'params': [{}]
+ }, Sao.Session.current_session).then(function(searches) {
+ this.searches = searches;
+ }.bind(this));
+ },
+ get: function(model) {
+ return this.searches[model] || [];
+ },
+ add: function(model, name, domain) {
+ return Sao.rpc({
+ 'method': 'model.ir.ui.view_search.create',
+ 'params': [[{
+ 'model': model,
+ 'name': name,
+ 'domain': new Sao.PYSON.Encoder().encode(domain)
+ }], {}]
+ }, Sao.Session.current_session).then(function(ids) {
+ var id = ids[0];
+ if (this.searches[model] === undefined) {
+ this.searches[model] = [];
+ }
+ this.searches[model].push([id, name, domain]);
+ }.bind(this));
+ },
+ remove: function(model, id) {
+ return Sao.rpc({
+ 'method': 'model.ir.ui.view_search.delete',
+ 'params': [[id], {}]
+ }, Sao.Session.current_session).then(function() {
+ for (var i = 0; i < this.searches[model].length; i++) {
+ var domain = this.searches[model][i];
+ if (domain[0] === id) {
+ this.searches[model].splice(i, 1);
+ break;
+ }
+ }
+ }.bind(this));
+ }
+ });
+ Sao.common.VIEW_SEARCH = new Sao.common.ViewSearch();
+
Sao.common.humanize = function(size) {
var sizes = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];
for (var i =0, len = sizes.length; i < len; i++) {
@@ -844,6 +890,32 @@
throw e;
}
},
+ stringable: function(domain) {
+ var stringable_ = function(clause) {
+ if (!clause) {
+ return true;
+ }
+ var is_array = function(e) {
+ return e instanceof Array;
+ };
+ if ((~['AND', 'OR'].indexOf(clause[0]) ||
+ (is_array(clause[0]))) &&
+ clause.slice(1).every(is_array)) {
+ return this.stringable(clause);
+ }
+ if ((clause[0] in this.fields) || clause[0] == 'rec_name') {
+ return true;
+ }
+ return false;
+ }.bind(this);
+ if (!domain) {
+ return true;
+ }
+ if (~['AND', 'OR'].indexOf(domain[0])) {
+ domain = domain.slice(1);
+ }
+ return domain.every(stringable_);
+ },
string: function(domain) {
var string = function(clause) {
diff --git a/src/sao.js b/src/sao.js
index b2470f66a6..5425bbea9f 100644
--- a/src/sao.js
+++ b/src/sao.js
@@ -151,6 +151,7 @@ var Sao = {};
deferreds.push(Sao.common.MODELACCESS.load_models());
deferreds.push(Sao.common.ICONFACTORY.load_icons());
deferreds.push(Sao.common.MODELHISTORY.load_history());
+ deferreds.push(Sao.common.VIEW_SEARCH.load_searches());
return jQuery.when.apply(jQuery, deferreds).then(function() {
(preferences.actions || []).forEach(function(action_id) {
Sao.Action.execute(action_id, {}, null, {});
diff --git a/src/screen.js b/src/screen.js
index 966f800f74..67096c2ae1 100644
--- a/src/screen.js
+++ b/src/screen.js
@@ -25,18 +25,45 @@
}).append('Filters'); // TODO translation
this.filter_button.click(this.search_box.bind(this));
this.search_entry = jQuery('', {
- 'class': 'form-control'
+ 'class': 'form-control',
+ 'placeholder': 'Search'
});
- this.search_entry.keypress(function(e) {
- if (e.which == 13) {
- this.do_search();
- return false;
- }
- }.bind(this));
+ this.search_entry.keypress(this.key_press.bind(this));
this.but_bookmark = jQuery('', {
type: 'button',
- 'class': 'btn btn-default'
- }).append('Bookmark'); // TODO translation
+ 'class': 'btn btn-default dropdown-toggle',
+ 'data-toggle': 'dropdown',
+ 'aria-expanded': false,
+ 'aria-label': 'Bookmarks'
+ }).append(jQuery('', {
+ 'class': 'glyphicon glyphicon-bookmark',
+ 'aria-hidden': true
+ }));
+ var dropdown_bookmark = jQuery('
', {
+ 'class': 'dropdown-menu',
+ 'role': 'menu'
+ });
+ this.but_bookmark.click(function() {
+ dropdown_bookmark.children().remove();
+ var bookmarks = this.bookmarks();
+ for (var i=0; i < bookmarks.length; i++) {
+ var name = bookmarks[i][1];
+ var domain = bookmarks[i][2];
+ jQuery('')
+ .append(jQuery('', {
+ 'href': '#'
+ }).append(name)
+ .click(domain, this.bookmark_activate.bind(this)))
+ .appendTo(dropdown_bookmark);
+ }
+ }.bind(this));
+ this.but_star = jQuery('', {
+ 'class': 'btn btn-default',
+ }).append(jQuery('', {
+ 'class': 'glyphicon',
+ 'aria-hidden': true
+ })).click(this.star_click.bind(this));
+ this.set_star();
jQuery('', {
'class': 'input-group'
@@ -47,20 +74,30 @@
.append(this.search_entry)
.append(jQuery('', {
'class': 'input-group-btn'
- }).append(this.but_bookmark))
+ }).append(this.but_star)
+ .append(this.but_bookmark)
+ .append(dropdown_bookmark))
.appendTo(jQuery('', {
'class': 'col-md-8'
}).appendTo(this.filter_box));
this.but_prev = jQuery('', {
type: 'button',
- 'class': 'btn btn-default'
- }).append('Previous');
+ 'class': 'btn btn-default',
+ 'aria-label': 'Previous'
+ }).append(jQuery('', {
+ 'class': 'glyphicon glyphicon-menu-left',
+ 'aria-hidden': true
+ }));
this.but_prev.click(this.search_prev.bind(this));
this.but_next = jQuery('', {
type: 'button',
- 'class': 'btn btn-default'
- }).append('Next');
+ 'class': 'btn btn-default',
+ 'aria-label': 'Next'
+ }).append(jQuery('', {
+ 'class': 'glyphicon glyphicon-menu-right',
+ 'aria-hidden': true
+ }));
this.but_next.click(this.search_next.bind(this));
jQuery('', {
@@ -114,6 +151,86 @@
},
set_text: function(value) {
this.search_entry.val(value);
+ this.bookmark_match();
+ },
+ set_star: function(star) {
+ var glyphicon = this.but_star.children('span.glyphicon');
+ if (star) {
+ glyphicon.removeClass('glyphicon-star-empty');
+ glyphicon.addClass('glyphicon-star');
+ this.but_star.attr('aria-label', 'Remove this bookmark');
+ } else {
+ glyphicon.removeClass('glyphicon-star');
+ glyphicon.addClass('glyphicon-star-empty');
+ this.but_star.attr('aria-label', 'Bookmark this filter');
+ }
+ },
+ get_star: function() {
+ var glyphicon = this.but_star.children('span.glyphicon');
+ return glyphicon.hasClass('glyphicon-star');
+ },
+ star_click: function() {
+ var star = this.get_star();
+ var model_name = this.screen.model_name;
+ var refresh = function() {
+ this.bookmark_match();
+ this.but_bookmark.prop('disabled',
+ jQuery.isEmptyObject(this.bookmarks()));
+ }.bind(this);
+ if (!star) {
+ var text = this.get_text();
+ if (!text) {
+ return;
+ }
+ Sao.common.ask.run('Bookmark Name:')
+ .then(function(name) {
+ if (!name) {
+ return;
+ }
+ var domain = this.screen.domain_parser.parse(text);
+ Sao.common.VIEW_SEARCH.add(model_name, name, domain)
+ .then(function() {
+ refresh();
+ });
+ this.set_text(
+ this.screen.domain_parser.string(domain));
+ }.bind(this));
+ } else {
+ var id = this.bookmark_match();
+ Sao.common.VIEW_SEARCH.remove(model_name, id).then(function() {
+ refresh();
+ });
+ }
+ },
+ bookmarks: function() {
+ var searches = Sao.common.VIEW_SEARCH.get(this.screen.model_name);
+ return searches.filter(function(search) {
+ return this.screen.domain_parser.stringable(search[2]);
+ }.bind(this));
+ },
+ bookmark_activate: function(e) {
+ var domain = e.data;
+ this.set_text(this.screen.domain_parser.string(domain));
+ this.do_search();
+ },
+ bookmark_match: function() {
+ var current_text = this.get_text();
+ var current_domain = this.screen.domain_parser.parse(current_text);
+ this.but_star.prop('disabled', !current_text);
+ var star = this.get_star();
+ var bookmarks = this.bookmarks();
+ for (var i=0; i < bookmarks.length; i++) {
+ var id = bookmarks[i][0];
+ var name = bookmarks[i][1];
+ var domain = bookmarks[i][2];
+ var text = this.screen.domain_parser.string(domain);
+ if ((text === current_text) ||
+ (Sao.common.compare(domain, current_domain))) {
+ this.set_star(true);
+ return id;
+ }
+ }
+ this.set_star(false);
},
search_prev: function() {
this.screen.search_prev(this.search_entry.val());
@@ -131,8 +248,21 @@
do_search: function() {
return this.screen.search_filter(this.search_entry.val());
},
+ key_press: function(e) {
+ if (e.which == Sao.common.RETURN_KEYCODE) {
+ this.do_search();
+ return false;
+ }
+ // Wait the current event finished
+ window.setTimeout(function() {
+ this.bookmark_match();
+ }.bind(this));
+ },
set_screen: function(screen) {
this.screen = screen;
+ this.but_bookmark.prop('disabled',
+ jQuery.isEmptyObject(this.bookmarks()));
+ this.bookmark_match();
},
show_filter: function() {
this.filter_box.show();
diff --git a/tests/sao.js b/tests/sao.js
index 706e058b91..be96b69fa2 100644
--- a/tests/sao.js
+++ b/tests/sao.js
@@ -1370,6 +1370,23 @@
].forEach(test_func, field);
});
+ QUnit.test('DomainParser.stringable', function() {
+ var parser = new Sao.common.DomainParser({
+ 'name': {
+ 'string': 'Name',
+ 'type': 'char',
+ }
+ });
+ var valid = ['name', '=', 'Doe'];
+ var invalid = ['surname', '=', 'John'];
+ QUnit.ok(parser.stringable([valid]));
+ QUnit.ok(!parser.stringable([invalid]));
+ QUnit.ok(parser.stringable(['AND', valid]));
+ QUnit.ok(!parser.stringable(['AND', valid, invalid]));
+ QUnit.ok(parser.stringable([[valid]]));
+ QUnit.ok(!parser.stringable([[valid], [invalid]]));
+ });
+
QUnit.test('DomainParser.string', function() {
var parser = new Sao.common.DomainParser({
'name': {