From af7eb0cc0689f270ec212450f56ace776b260573 Mon Sep 17 00:00:00 2001 From: Niko Date: Thu, 27 Dec 2012 17:18:37 +0100 Subject: [PATCH 01/56] [IE] bug 1092846 bzr revid: nwi@openerp.com-20121227161837-7qzx93roddfrtlhb --- addons/web/__openerp__.py | 1 + .../jquery.placeholder/jquery.placeholder.min.js | 10 ++++++++++ addons/web/static/src/css/base.css | 15 +++++++++------ addons/web/static/src/css/base.sass | 3 +++ 4 files changed, 23 insertions(+), 6 deletions(-) create mode 100755 addons/web/static/lib/jquery.placeholder/jquery.placeholder.min.js diff --git a/addons/web/__openerp__.py b/addons/web/__openerp__.py index 2303b6a68e8..970d0f52e7b 100644 --- a/addons/web/__openerp__.py +++ b/addons/web/__openerp__.py @@ -39,6 +39,7 @@ This module provides the core of the OpenERP Web Client. "static/lib/underscore/underscore.string.js", "static/lib/backbone/backbone.js", "static/lib/cleditor/jquery.cleditor.js", + "static/lib/jquery.placeholder/jquery.placeholder.min.js", "static/lib/py.js/lib/py.js", "static/src/js/boot.js", "static/src/js/testing.js", diff --git a/addons/web/static/lib/jquery.placeholder/jquery.placeholder.min.js b/addons/web/static/lib/jquery.placeholder/jquery.placeholder.min.js new file mode 100755 index 00000000000..1ac0df1b3e1 --- /dev/null +++ b/addons/web/static/lib/jquery.placeholder/jquery.placeholder.min.js @@ -0,0 +1,10 @@ +/*! http://mths.be/placeholder v2.0.7 by @mathias */ +if ($.browser.msie && $.browser.version<=9) { +;(function(f,h,$){var a='placeholder' in h.createElement('input'),d='placeholder' in h.createElement('textarea'),i=$.fn,c=$.valHooks,k,j;if(a&&d){j=i.placeholder=function(){return this};j.input=j.textarea=true}else{j=i.placeholder=function(){var l=this;l.filter((a?'textarea':':input')+'[placeholder]').not('.placeholder').bind({'focus.placeholder':b,'blur.placeholder':e}).data('placeholder-enabled',true).trigger('blur.placeholder');return l};j.input=a;j.textarea=d;k={get:function(m){var l=$(m);return l.data('placeholder-enabled')&&l.hasClass('placeholder')?'':m.value},set:function(m,n){var l=$(m);if(!l.data('placeholder-enabled')){return m.value=n}if(n==''){m.value=n;if(m!=h.activeElement){e.call(m)}}else{if(l.hasClass('placeholder')){b.call(m,true,n)||(m.value=n)}else{m.value=n}}return l}};a||(c.input=k);d||(c.textarea=k);$(function(){$(h).delegate('form','submit.placeholder',function(){var l=$('.placeholder',this).each(b);setTimeout(function(){l.each(e)},10)})});$(f).bind('beforeunload.placeholder',function(){$('.placeholder').each(function(){this.value=''})})}function g(m){var l={},n=/^jQuery\d+$/;$.each(m.attributes,function(p,o){if(o.specified&&!n.test(o.name)){l[o.name]=o.value}});return l}function b(m,n){var l=this,o=$(l);if(l.value==o.attr('placeholder')&&o.hasClass('placeholder')){if(o.data('placeholder-password')){o=o.hide().next().show().attr('id',o.removeAttr('id').data('placeholder-id'));if(m===true){return o[0].value=n}o.focus()}else{l.value='';o.removeClass('placeholder');l==h.activeElement&&l.select()}}}function e(){var q,l=this,p=$(l),m=p,o=this.id;if(l.value==''){if(l.type=='password'){if(!p.data('placeholder-textinput')){try{q=p.clone().attr({type:'text'})}catch(n){q=$('').attr($.extend(g(this),{type:'text'}))}q.removeAttr('name').data({'placeholder-password':true,'placeholder-id':o}).bind('focus.placeholder',b);p.data({'placeholder-textinput':q,'placeholder-id':o}).before(q)}p=p.removeAttr('id').hide().prev().attr('id',o).show()}p.addClass('placeholder');p[0].value=p.attr('placeholder')}else{p.removeClass('placeholder')}}}(this,document,jQuery)); + document.addEventListener("DOMNodeInserted",function(event){ + if ( $(event.target).is("input") || $(event.target).is("textarea") ) { + $(event.target).placeholder(); + } + }); +} + diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index 8a5aa5f9ad0..cf356f38185 100644 --- a/addons/web/static/src/css/base.css +++ b/addons/web/static/src/css/base.css @@ -1,4 +1,4 @@ -@charset "utf-8"; +@charset "UTF-8"; @font-face { font-family: "mnmliconsRegular"; src: url("/web/static/src/font/mnmliconsv21-webfont.eot") format("eot"); @@ -30,7 +30,7 @@ text-shadow: 0 1px 1px rgba(255, 255, 255, 0.5); /* http://www.quirksmode.org/dom/inputfile.html * http://stackoverflow.com/questions/2855589/replace-input-type-file-by-an-image - */ */ + */ } .openerp.openerp_webclient_container { height: 100%; @@ -1265,7 +1265,7 @@ color: white; padding: 2px 4px; margin: 1px 6px 0 0; - border: 1px solid lightGray; + border: 1px solid lightgrey; text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); -moz-border-radius: 4px; -webkit-border-radius: 4px; @@ -1290,7 +1290,7 @@ transform: scale(1.1); } .openerp .oe_secondary_submenu .oe_active { - border-top: 1px solid lightGray; + border-top: 1px solid lightgrey; border-bottom: 1px solid #dedede; text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); -moz-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.2), inset 0 -1px 3px rgba(40, 40, 40, 0.2); @@ -2240,7 +2240,7 @@ } .openerp .oe_form .oe_form_label_help[for] span, .openerp .oe_form .oe_form_label[for] span { font-size: 80%; - color: darkGreen; + color: darkgreen; vertical-align: top; position: relative; top: -4px; @@ -3131,6 +3131,10 @@ div.ui-widget-overlay { border-radius: 3px; } +.openerp_ie .placeholder { + color: #afafb6 !important; + font-style: italic !important; +} .openerp_ie .oe_form_field_boolean input { background: white; } @@ -3291,7 +3295,6 @@ div.ui-widget-overlay { overflow: hidden !important; } } - .blockUI.blockOverlay { background-color: black; opacity: 0.6; diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass index 3aeba573041..ffe2d631337 100644 --- a/addons/web/static/src/css/base.sass +++ b/addons/web/static/src/css/base.sass @@ -2472,6 +2472,9 @@ div.ui-widget-overlay // Internet Explorer 9+ specifics {{{ .openerp_ie + .placeholder + color: $tag-border !important + font-style: italic !important .oe_form_field_boolean input background: #fff input[type='checkbox'] From 15f98fa00413e6512a7f2835822a843e399eef16 Mon Sep 17 00:00:00 2001 From: Niko Date: Fri, 28 Dec 2012 10:42:56 +0100 Subject: [PATCH 02/56] [IMP] change placeholder inclusion logic bzr revid: nwi@openerp.com-20121228094256-h7kxphh5lexb3ff5 --- addons/web/__openerp__.py | 1 - addons/web/controllers/main.py | 15 +++++++++++++-- .../jquery.placeholder/jquery.placeholder.min.js | 10 +--------- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/addons/web/__openerp__.py b/addons/web/__openerp__.py index 970d0f52e7b..2303b6a68e8 100644 --- a/addons/web/__openerp__.py +++ b/addons/web/__openerp__.py @@ -39,7 +39,6 @@ This module provides the core of the OpenERP Web Client. "static/lib/underscore/underscore.string.js", "static/lib/backbone/backbone.js", "static/lib/cleditor/jquery.cleditor.js", - "static/lib/jquery.placeholder/jquery.placeholder.min.js", "static/lib/py.js/lib/py.js", "static/src/js/boot.js", "static/src/js/testing.js", diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index 1d5cfabec2c..cfe25bb9e97 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -522,8 +522,19 @@ html_template = """ + + diff --git a/addons/web/static/lib/jquery.placeholder/jquery.placeholder.min.js b/addons/web/static/lib/jquery.placeholder/jquery.placeholder.min.js index 1ac0df1b3e1..698ddc5188d 100755 --- a/addons/web/static/lib/jquery.placeholder/jquery.placeholder.min.js +++ b/addons/web/static/lib/jquery.placeholder/jquery.placeholder.min.js @@ -1,10 +1,2 @@ /*! http://mths.be/placeholder v2.0.7 by @mathias */ -if ($.browser.msie && $.browser.version<=9) { -;(function(f,h,$){var a='placeholder' in h.createElement('input'),d='placeholder' in h.createElement('textarea'),i=$.fn,c=$.valHooks,k,j;if(a&&d){j=i.placeholder=function(){return this};j.input=j.textarea=true}else{j=i.placeholder=function(){var l=this;l.filter((a?'textarea':':input')+'[placeholder]').not('.placeholder').bind({'focus.placeholder':b,'blur.placeholder':e}).data('placeholder-enabled',true).trigger('blur.placeholder');return l};j.input=a;j.textarea=d;k={get:function(m){var l=$(m);return l.data('placeholder-enabled')&&l.hasClass('placeholder')?'':m.value},set:function(m,n){var l=$(m);if(!l.data('placeholder-enabled')){return m.value=n}if(n==''){m.value=n;if(m!=h.activeElement){e.call(m)}}else{if(l.hasClass('placeholder')){b.call(m,true,n)||(m.value=n)}else{m.value=n}}return l}};a||(c.input=k);d||(c.textarea=k);$(function(){$(h).delegate('form','submit.placeholder',function(){var l=$('.placeholder',this).each(b);setTimeout(function(){l.each(e)},10)})});$(f).bind('beforeunload.placeholder',function(){$('.placeholder').each(function(){this.value=''})})}function g(m){var l={},n=/^jQuery\d+$/;$.each(m.attributes,function(p,o){if(o.specified&&!n.test(o.name)){l[o.name]=o.value}});return l}function b(m,n){var l=this,o=$(l);if(l.value==o.attr('placeholder')&&o.hasClass('placeholder')){if(o.data('placeholder-password')){o=o.hide().next().show().attr('id',o.removeAttr('id').data('placeholder-id'));if(m===true){return o[0].value=n}o.focus()}else{l.value='';o.removeClass('placeholder');l==h.activeElement&&l.select()}}}function e(){var q,l=this,p=$(l),m=p,o=this.id;if(l.value==''){if(l.type=='password'){if(!p.data('placeholder-textinput')){try{q=p.clone().attr({type:'text'})}catch(n){q=$('').attr($.extend(g(this),{type:'text'}))}q.removeAttr('name').data({'placeholder-password':true,'placeholder-id':o}).bind('focus.placeholder',b);p.data({'placeholder-textinput':q,'placeholder-id':o}).before(q)}p=p.removeAttr('id').hide().prev().attr('id',o).show()}p.addClass('placeholder');p[0].value=p.attr('placeholder')}else{p.removeClass('placeholder')}}}(this,document,jQuery)); - document.addEventListener("DOMNodeInserted",function(event){ - if ( $(event.target).is("input") || $(event.target).is("textarea") ) { - $(event.target).placeholder(); - } - }); -} - +;(function(f,h,$){var a='placeholder' in h.createElement('input'),d='placeholder' in h.createElement('textarea'),i=$.fn,c=$.valHooks,k,j;if(a&&d){j=i.placeholder=function(){return this};j.input=j.textarea=true}else{j=i.placeholder=function(){var l=this;l.filter((a?'textarea':':input')+'[placeholder]').not('.placeholder').bind({'focus.placeholder':b,'blur.placeholder':e}).data('placeholder-enabled',true).trigger('blur.placeholder');return l};j.input=a;j.textarea=d;k={get:function(m){var l=$(m);return l.data('placeholder-enabled')&&l.hasClass('placeholder')?'':m.value},set:function(m,n){var l=$(m);if(!l.data('placeholder-enabled')){return m.value=n}if(n==''){m.value=n;if(m!=h.activeElement){e.call(m)}}else{if(l.hasClass('placeholder')){b.call(m,true,n)||(m.value=n)}else{m.value=n}}return l}};a||(c.input=k);d||(c.textarea=k);$(function(){$(h).delegate('form','submit.placeholder',function(){var l=$('.placeholder',this).each(b);setTimeout(function(){l.each(e)},10)})});$(f).bind('beforeunload.placeholder',function(){$('.placeholder').each(function(){this.value=''})})}function g(m){var l={},n=/^jQuery\d+$/;$.each(m.attributes,function(p,o){if(o.specified&&!n.test(o.name)){l[o.name]=o.value}});return l}function b(m,n){var l=this,o=$(l);if(l.value==o.attr('placeholder')&&o.hasClass('placeholder')){if(o.data('placeholder-password')){o=o.hide().next().show().attr('id',o.removeAttr('id').data('placeholder-id'));if(m===true){return o[0].value=n}o.focus()}else{l.value='';o.removeClass('placeholder');l==h.activeElement&&l.select()}}}function e(){var q,l=this,p=$(l),m=p,o=this.id;if(l.value==''){if(l.type=='password'){if(!p.data('placeholder-textinput')){try{q=p.clone().attr({type:'text'})}catch(n){q=$('').attr($.extend(g(this),{type:'text'}))}q.removeAttr('name').data({'placeholder-password':true,'placeholder-id':o}).bind('focus.placeholder',b);p.data({'placeholder-textinput':q,'placeholder-id':o}).before(q)}p=p.removeAttr('id').hide().prev().attr('id',o).show()}p.addClass('placeholder');p[0].value=p.attr('placeholder')}else{p.removeClass('placeholder')}}}(this,document,jQuery)); \ No newline at end of file From 0f093aafc0db5b69a06730e2378351a808517705 Mon Sep 17 00:00:00 2001 From: Niko Date: Fri, 28 Dec 2012 10:59:07 +0100 Subject: [PATCH 03/56] [IMP] change placeholder inclusion logic bzr revid: nwi@openerp.com-20121228095907-3mgkosm1os8w3nzz --- addons/web/controllers/main.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index cfe25bb9e97..59722d12372 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -530,8 +530,9 @@ html_template = """ From f690a9310c6b78848a7caf95e87abfdce478baba Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 13 Feb 2013 08:25:57 +0100 Subject: [PATCH 04/56] [IMP] prefix searchview tests for easy filtering bzr revid: xmo@openerp.com-20130213072557-er5xl9xcj17mhuqe --- addons/web/static/test/search.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/addons/web/static/test/search.js b/addons/web/static/test/search.js index 292954b1992..09d135ca203 100644 --- a/addons/web/static/test/search.js +++ b/addons/web/static/test/search.js @@ -1,4 +1,4 @@ -openerp.testing.section('query', { +openerp.testing.section('search.query', { dependencies: ['web.search'] }, function (test) { test('Adding a facet to the query creates a facet and a value', function (instance) { @@ -180,7 +180,7 @@ var makeSearchView = function (instance, dummy_widget_attributes, defaults) { }); return view; }; -openerp.testing.section('defaults', { +openerp.testing.section('search.defaults', { dependencies: ['web.search'], rpc: 'mock', templates: true, @@ -331,7 +331,7 @@ openerp.testing.section('defaults', { }); }); }); -openerp.testing.section('completions', { +openerp.testing.section('search.completions', { dependencies: ['web.search'], rpc: 'mock', templates: true @@ -564,7 +564,7 @@ openerp.testing.section('completions', { }); }); }); -openerp.testing.section('search-serialization', { +openerp.testing.section('search.serialization', { dependencies: ['web.search'], rpc: 'mock', templates: true @@ -871,7 +871,7 @@ openerp.testing.section('search-serialization', { return $.when(t1, t2); }); }); -openerp.testing.section('removal', { +openerp.testing.section('search.removal', { dependencies: ['web.search'], rpc: 'mock', templates: true @@ -894,7 +894,7 @@ openerp.testing.section('removal', { }); }); }); -openerp.testing.section('drawer', { +openerp.testing.section('search.drawer', { dependencies: ['web.search'], rpc: 'mock', templates: true @@ -910,7 +910,7 @@ openerp.testing.section('drawer', { }); }); }); -openerp.testing.section('filters', { +openerp.testing.section('search.filters', { dependencies: ['web.search'], rpc: 'mock', templates: true, @@ -995,7 +995,7 @@ openerp.testing.section('filters', { }); }); }); -openerp.testing.section('saved_filters', { +openerp.testing.section('search.filters.saved', { dependencies: ['web.search'], rpc: 'mock', templates: true @@ -1077,7 +1077,7 @@ openerp.testing.section('saved_filters', { }); }); }); -openerp.testing.section('advanced', { +openerp.testing.section('search.advanced', { dependencies: ['web.search'], rpc: 'mock', templates: true From dfb7493034f2b102d647967654f5c99893450e2b Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 13 Feb 2013 10:01:08 +0100 Subject: [PATCH 05/56] [FIX] add support for invisibility to fields (don't complete an invisible field) bzr revid: xmo@openerp.com-20130213090108-h38emnwscgb5v1pu --- addons/web/doc/search_view.rst | 11 +++++++ addons/web/static/src/js/search.js | 23 ++++++++------ addons/web/static/test/search.js | 49 ++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 9 deletions(-) diff --git a/addons/web/doc/search_view.rst b/addons/web/doc/search_view.rst index 16a91c5e9ac..12b0eaf3104 100644 --- a/addons/web/doc/search_view.rst +++ b/addons/web/doc/search_view.rst @@ -107,6 +107,12 @@ formatted differently). If an input *may* fetch multiple completion items, it *should* prefix those with a section title using its own name. This has no technical consequence but is clearer for users. +.. note:: + + If a field is :js:func:`invisible + `, its completion function will + *not* be called. + Providing drawer/supplementary UI +++++++++++++++++++++++++++++++++ @@ -145,6 +151,11 @@ started only once (per view). dynamically collects, lays out and renders filters? => exercises drawer thingies +.. note:: + + An :js:func:`invisible ` input + will not be inserted into the drawer. + Converting from facet objects +++++++++++++++++++++++++++++ diff --git a/addons/web/static/src/js/search.js b/addons/web/static/src/js/search.js index 8391f7a8ca5..8422f4e55fe 100644 --- a/addons/web/static/src/js/search.js +++ b/addons/web/static/src/js/search.js @@ -499,6 +499,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea */ complete_global_search: function (req, resp) { $.when.apply(null, _(this.inputs).chain() + .filter(function (input) { return input.visible(); }) .invoke('complete', req.term) .value()).then(function () { resp(_(_(arguments).compact()).flatten(true)); @@ -903,8 +904,8 @@ instance.web.search.Input = instance.web.search.Widget.extend( /** @lends instan */ init: function (view) { this._super(view); + this.load_attrs({}); this.view.inputs.push(this); - this.style = undefined; }, /** * Fetch auto-completion values for the widget. @@ -952,15 +953,19 @@ instance.web.search.Input = instance.web.search.Widget.extend( /** @lends instan "get_domain not implemented for widget " + this.attrs.type); }, load_attrs: function (attrs) { - if (attrs.modifiers) { - attrs.modifiers = JSON.parse(attrs.modifiers); - attrs.invisible = attrs.modifiers.invisible || false; - if (attrs.invisible) { - this.style = 'display: none;' - } - } + attrs.modifiers = attrs.modifiers ? JSON.parse(attrs.modifiers) : {}; this.attrs = attrs; - } + }, + /** + * Returns whether the input is "visible". The default behavior is to + * query the ``modifiers.invisible`` flag on the input's description or + * view node. + * + * @returns {Boolean} + */ + visible: function () { + return !this.attrs.modifiers.invisible; + }, }); instance.web.search.FilterGroup = instance.web.search.Input.extend(/** @lends instance.web.search.FilterGroup# */{ template: 'SearchView.filters', diff --git a/addons/web/static/test/search.js b/addons/web/static/test/search.js index 09d135ca203..340784e657d 100644 --- a/addons/web/static/test/search.js +++ b/addons/web/static/test/search.js @@ -1158,3 +1158,52 @@ openerp.testing.section('search.advanced', { }); // TODO: UI tests? }); +openerp.testing.section('search.invisible', { + dependencies: ['web.search'], + rpc: 'mock', + templates: true, +}, function (test) { + // Invisible fields should not auto-complete + test('invisible-no-autocomplete', {asserts: 1}, function (instance, $fix, mock) { + instance.web.search.fields.add('test', 'instance.test.TestWidget'); + instance.test = { + TestWidget: instance.web.search.Field.extend({ + complete: function () { + return $.when([{label: this.attrs.string}]); + }, + }), + }; + var fields = { + field0: {type: 'test', string: 'Field 0'}, + field1: {type: 'test', string: 'Field 1'}, + }; + mock('ir.filters:get_filters', function () { return []; }); + mock('test.model:fields_get', function () { return fields; }); + mock('test.model:fields_view_get', function () { + return { + type: 'search', + fields: fields, + arch: '' + + '' + + '' + + '' + }; + }); + var ds = new instance.web.DataSet(null, 'test.model'); + var view = new instance.web.SearchView(null, ds, false); + return view.appendTo($fix) + .then(function () { + var done = $.Deferred(); + view.complete_global_search({term: 'test'}, function (comps) { + done.resolve(comps); + }); + return done; + }).then(function (completions) { + deepEqual(completions, [{label: 'Field 0'}], + "should only complete the visible field"); + }); + }); + // Invisible filters should not appear in the drawer + // Invisible filter groups should not appear in the drawer + // Group invisibility should be inherited by children +}); From 2d87d908e244d6f0fc7621038e90f29621a6f8ea Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 13 Feb 2013 13:56:06 +0100 Subject: [PATCH 06/56] [IMP] do not add empty filter groups to the drawer columns bzr revid: xmo@openerp.com-20130213125606-ykbb2kg9wvfbs2ho --- addons/web/static/src/js/search.js | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/addons/web/static/src/js/search.js b/addons/web/static/src/js/search.js index 8422f4e55fe..50065b300b4 100644 --- a/addons/web/static/src/js/search.js +++ b/addons/web/static/src/js/search.js @@ -1715,15 +1715,22 @@ instance.web.search.Filters = instance.web.search.Input.extend({ .sum() .value(); - var col1 = [], col2 = _(this.view.controls).map(function (inputs, group) { - var filters = _(inputs).filter(is_group); - return { - name: group === 'null' ? "q " + _t("Filters") : "w " + group, - filters: filters, - length: _(filters).chain().map(function (i) { - return i.filters.length; }).sum().value() - }; - }); + var col1 = [], col2 = _(this.view.controls).chain() + .map(function (inputs, group) { + return {group: group, inputs: inputs}; + }).reject(function (item) { + return _(item.inputs).isEmpty(); + }).map(function (item) { + var filters = _(item.inputs).filter(is_group); + return { + name: item.group === 'null' + ? "q " + _t("Filters") + : "w " + item.group, + filters: filters, + length: _(filters).chain().map(function (i) { + return i.filters.length; }).sum().value() + }; + }).value(); while (col2.length) { // col1 + group should be smaller than col2 + group From 9852c6ff1e212df4e944b61cc9d1ad586b5f08ef Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 14 Feb 2013 08:43:02 +0100 Subject: [PATCH 07/56] [FIX] missing support for invisible fields and groups in new search view lp bug: https://launchpad.net/bugs/1122183 fixed bzr revid: xmo@openerp.com-20130214074302-rwm2hcmv9mpvp9dv --- addons/web/static/src/js/search.js | 141 ++++++++++++++++----------- addons/web/static/src/xml/base.xml | 11 +-- addons/web/static/test/search.js | 151 ++++++++++++++++++++++++----- 3 files changed, 215 insertions(+), 88 deletions(-) diff --git a/addons/web/static/src/js/search.js b/addons/web/static/src/js/search.js index 50065b300b4..c57dec67229 100644 --- a/addons/web/static/src/js/search.js +++ b/addons/web/static/src/js/search.js @@ -359,7 +359,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea this.has_defaults = !_.isEmpty(this.defaults); this.inputs = []; - this.controls = {}; + this.controls = []; this.headless = this.options.hidden && !this.has_defaults; @@ -588,18 +588,18 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea * * @param {Array} items a list of nodes to convert to widgets * @param {Object} fields a mapping of field names to (ORM) field attributes - * @param {String} [group_name] name of the group to put the new controls in + * @param {Object} [group] group to put the new controls in */ - make_widgets: function (items, fields, group_name) { - group_name = group_name || null; - if (!(group_name in this.controls)) { - this.controls[group_name] = []; + make_widgets: function (items, fields, group) { + if (!group) { + group = new instance.web.search.Group( + this, 'q', {attrs: {string: _t("Filters")}}); } - var self = this, group = this.controls[group_name]; + var self = this; var filters = []; _.each(items, function (item) { if (filters.length && item.tag !== 'filter') { - group.push(new instance.web.search.FilterGroup(filters, this)); + group.push(new instance.web.search.FilterGroup(filters, group)); filters = []; } @@ -607,15 +607,18 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea case 'separator': case 'newline': break; case 'filter': - filters.push(new instance.web.search.Filter(item, this)); + filters.push(new instance.web.search.Filter(item, group)); break; case 'group': - self.make_widgets(item.children, fields, item.attrs.string); + self.make_widgets(item.children, fields, + new instance.web.search.Group(group, 'w', item)); break; case 'field': - group.push(this.make_field(item, fields[item['attrs'].name])); + var field = this.make_field( + item, fields[item['attrs'].name], group); + group.push(field); // filters - self.make_widgets(item.children, fields, group_name); + self.make_widgets(item.children, fields, group); break; } }, this); @@ -630,12 +633,13 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea * * @param {Object} item fields_view_get node for the field * @param {Object} field fields_get result for the field + * @param {Object} [parent] * @returns instance.web.search.Field */ - make_field: function (item, field) { + make_field: function (item, field, parent) { var obj = instance.web.search.fields.get_any( [item.attrs.widget, field.type]); if(obj) { - return new (obj) (item, field, this); + return new (obj) (item, field, parent || this); } else { console.group('Unknown field type ' + field.type); console.error('View node', item); @@ -870,13 +874,18 @@ instance.web.search.Widget = instance.web.Widget.extend( /** @lends instance.web * @constructs instance.web.search.Widget * @extends instance.web.Widget * - * @param view the ancestor view of this widget + * @param parent parent of this widget */ - init: function (view) { - this._super(view); - this.view = view; + init: function (parent) { + this._super(parent); + var ancestor = parent; + do { + this.view = ancestor; + } while (!(ancestor instanceof instance.web.SearchView) + && (ancestor = (ancestor.getParent && ancestor.getParent()))); } }); + instance.web.search.add_expand_listener = function($root) { $root.find('a.searchview_group_string').click(function (e) { $root.toggleClass('folded expanded'); @@ -885,13 +894,24 @@ instance.web.search.add_expand_listener = function($root) { }); }; instance.web.search.Group = instance.web.search.Widget.extend({ - template: 'SearchView.group', - init: function (view_section, view, fields) { - this._super(view); - this.attrs = view_section.attrs; - this.lines = view.make_widgets( - view_section.children, fields); - } + init: function (parent, icon, node) { + this._super(parent); + var attrs = node.attrs; + this.modifiers = attrs.modifiers = + attrs.modifiers ? JSON.parse(attrs.modifiers) : {}; + this.attrs = attrs; + this.icon = icon; + this.name = attrs.string; + this.children = []; + + this.view.controls.push(this); + }, + push: function (input) { + this.children.push(input); + }, + visible: function () { + return !this.modifiers.invisible; + }, }); instance.web.search.Input = instance.web.search.Widget.extend( /** @lends instance.web.search.Input# */{ @@ -900,10 +920,10 @@ instance.web.search.Input = instance.web.search.Widget.extend( /** @lends instan * @constructs instance.web.search.Input * @extends instance.web.search.Widget * - * @param view + * @param parent */ - init: function (view) { - this._super(view); + init: function (parent) { + this._super(parent); this.load_attrs({}); this.view.inputs.push(this); }, @@ -964,7 +984,18 @@ instance.web.search.Input = instance.web.search.Widget.extend( /** @lends instan * @returns {Boolean} */ visible: function () { - return !this.attrs.modifiers.invisible; + if (this.attrs.modifiers.invisible) { + return false; + } + var parent = this; + while ((parent = parent.getParent()) && + ( (parent instanceof instance.web.search.Group) + || (parent instanceof instance.web.search.Input))) { + if (!parent.visible()) { + return false; + } + } + return true; }, }); instance.web.search.FilterGroup = instance.web.search.Input.extend(/** @lends instance.web.search.FilterGroup# */{ @@ -979,17 +1010,17 @@ instance.web.search.FilterGroup = instance.web.search.Input.extend(/** @lends in * @extends instance.web.search.Input * * @param {Array} filters elements of the group - * @param {instance.web.SearchView} view view in which the filters are contained + * @param {instance.web.SearchView} parent parent in which the filters are contained */ - init: function (filters, view) { + init: function (filters, parent) { // If all filters are group_by and we're not initializing a GroupbyGroup, // create a GroupbyGroup instead of the current FilterGroup if (!(this instanceof instance.web.search.GroupbyGroup) && _(filters).all(function (f) { return f.attrs.context && f.attrs.context.group_by; })) { - return new instance.web.search.GroupbyGroup(filters, view); + return new instance.web.search.GroupbyGroup(filters, parent); } - this._super(view); + this._super(parent); this.filters = filters; this.view.query.on('add remove change reset', this.proxy('search_change')); }, @@ -1108,6 +1139,7 @@ instance.web.search.FilterGroup = instance.web.search.Input.extend(/** @lends in var self = this; item = item.toLowerCase(); var facet_values = _(this.filters).chain() + .filter(function (filter) { return filter.visible(); }) .filter(function (filter) { var at = { string: filter.attrs.string || '', @@ -1134,8 +1166,8 @@ instance.web.search.FilterGroup = instance.web.search.Input.extend(/** @lends in instance.web.search.GroupbyGroup = instance.web.search.FilterGroup.extend({ icon: 'w', completion_label: _lt("Group by: %s"), - init: function (filters, view) { - this._super(filters, view); + init: function (filters, parent) { + this._super(filters, parent); // Not flanders: facet unicity is handled through the // (category, field) pair of facet attributes. This is all well and // good for regular filter groups where a group matches a facet, but for @@ -1143,8 +1175,8 @@ instance.web.search.GroupbyGroup = instance.web.search.FilterGroup.extend({ // view which proxies to the first GroupbyGroup, so it can be used // for every GroupbyGroup and still provides the various methods needed // by the search view. Use weirdo name to avoid risks of conflicts - if (!this.getParent()._s_groupby) { - this.getParent()._s_groupby = { + if (!this.view._s_groupby) { + this.view._s_groupby = { help: "See GroupbyGroup#init", get_context: this.proxy('get_context'), get_domain: this.proxy('get_domain'), @@ -1153,7 +1185,7 @@ instance.web.search.GroupbyGroup = instance.web.search.FilterGroup.extend({ } }, match_facet: function (facet) { - return facet.get('field') === this.getParent()._s_groupby; + return facet.get('field') === this.view._s_groupby; }, make_facet: function (values) { return { @@ -1178,10 +1210,10 @@ instance.web.search.Filter = instance.web.search.Input.extend(/** @lends instanc * @extends instance.web.search.Input * * @param node - * @param view + * @param parent */ - init: function (node, view) { - this._super(view); + init: function (node, parent) { + this._super(parent); this.load_attrs(node.attrs); }, facet_for: function () { return $.when(null); }, @@ -1197,10 +1229,10 @@ instance.web.search.Field = instance.web.search.Input.extend( /** @lends instanc * * @param view_section * @param field - * @param view + * @param parent */ - init: function (view_section, field, view) { - this._super(view); + init: function (view_section, field, parent) { + this._super(parent); this.load_attrs(_.extend({}, field, view_section.attrs)); }, facet_for: function (value) { @@ -1240,7 +1272,7 @@ instance.web.search.Field = instance.web.search.Input.extend( /** @lends instanc * * @param {String} name the field's name * @param {String} operator the field's operator (either attribute-specified or default operator for the field - * @param {Number|String} value parsed value for the field + * @param {Number|String} facet parsed value for the field * @returns {Array} domain to include in the resulting search */ make_domain: function (name, operator, facet) { @@ -1472,8 +1504,8 @@ instance.web.search.DateTimeField = instance.web.search.DateField.extend(/** @le }); instance.web.search.ManyToOneField = instance.web.search.CharField.extend({ default_operator: {}, - init: function (view_section, field, view) { - this._super(view_section, field, view); + init: function (view_section, field, parent) { + this._super(view_section, field, parent); this.model = new instance.web.Model(this.attrs.relation); }, complete: function (needle) { @@ -1716,16 +1748,13 @@ instance.web.search.Filters = instance.web.search.Input.extend({ .value(); var col1 = [], col2 = _(this.view.controls).chain() - .map(function (inputs, group) { - return {group: group, inputs: inputs}; - }).reject(function (item) { - return _(item.inputs).isEmpty(); - }).map(function (item) { - var filters = _(item.inputs).filter(is_group); + .reject(function (group) { + return _(group.children).isEmpty() || group.modifiers.invisible; + }).map(function (group) { + var filters = _(group.children).filter(is_group); return { - name: item.group === 'null' - ? "q " + _t("Filters") - : "w " + item.group, + name: _.str.sprintf("%s %s", + group.icon, group.name), filters: filters, length: _(filters).chain().map(function (i) { return i.filters.length; }).sum().value() diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index e31dcd2a4a0..655c4ddc9e2 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -1496,7 +1496,7 @@
    -
  • @@ -1584,15 +1584,6 @@ - - - - - - - - -
    diff --git a/addons/web/static/test/search.js b/addons/web/static/test/search.js index 340784e657d..f180a97357d 100644 --- a/addons/web/static/test/search.js +++ b/addons/web/static/test/search.js @@ -1163,34 +1163,35 @@ openerp.testing.section('search.invisible', { rpc: 'mock', templates: true, }, function (test) { - // Invisible fields should not auto-complete - test('invisible-no-autocomplete', {asserts: 1}, function (instance, $fix, mock) { - instance.web.search.fields.add('test', 'instance.test.TestWidget'); - instance.test = { - TestWidget: instance.web.search.Field.extend({ - complete: function () { - return $.when([{label: this.attrs.string}]); - }, - }), - }; - var fields = { - field0: {type: 'test', string: 'Field 0'}, - field1: {type: 'test', string: 'Field 1'}, + var registerTestField = function (instance, methods) { + instance.web.search.fields.add('test', 'instance.testing.TestWidget'); + instance.testing = { + TestWidget: instance.web.search.Field.extend(methods), }; + }; + var makeView = function (instance, mock, fields, arch, defaults) { mock('ir.filters:get_filters', function () { return []; }); mock('test.model:fields_get', function () { return fields; }); mock('test.model:fields_view_get', function () { - return { - type: 'search', - fields: fields, - arch: '' + - '' + - '' + - '' - }; + return { type: 'search', fields: fields, arch: arch }; }); var ds = new instance.web.DataSet(null, 'test.model'); - var view = new instance.web.SearchView(null, ds, false); + return new instance.web.SearchView(null, ds, false, defaults); + }; + // Invisible fields should not auto-complete + test('invisible-field-no-autocomplete', {asserts: 1}, function (instance, $fix, mock) { + registerTestField(instance, { + complete: function () { + return $.when([{label: this.attrs.string}]); + }, + }); + var view = makeView(instance, mock, { + field0: {type: 'test', string: 'Field 0'}, + field1: {type: 'test', string: 'Field 1'}, + }, ['', + '', + '', + ''].join()); return view.appendTo($fix) .then(function () { var done = $.Deferred(); @@ -1204,6 +1205,112 @@ openerp.testing.section('search.invisible', { }); }); // Invisible filters should not appear in the drawer + test('invisible-filter-no-drawer', {asserts: 4}, function (instance, $fix, mock) { + var view = makeView(instance, mock, {}, [ + '', + '', + '', + ''].join()); + return view.appendTo($fix) + .then(function () { + var $fs = $fix.find('.oe_searchview_filters ul'); + strictEqual($fs.children().length, + 1, + "should only display one filter"); + strictEqual(_.str.trim($fs.children().text()), + "filter 0", + "should only display filter 0"); + var done = $.Deferred(); + view.complete_global_search({term: 'filter'}, function (comps) { + done.resolve(); + strictEqual(comps.length, 1, "should only complete visible filter"); + strictEqual(comps[0].label, "Filter on: filter 0", + "should complete filter 0"); + }); + return done; + }); + }); // Invisible filter groups should not appear in the drawer // Group invisibility should be inherited by children + test('group-invisibility', {asserts: 6}, function (instance, $fix, mock) { + registerTestField(instance, { + complete: function () { + return $.when([{label: this.attrs.string}]); + }, + }); + var view = makeView(instance, mock, { + field0: {type: 'test', string: 'Field 0'}, + field1: {type: 'test', string: 'Field 1'}, + }, [ + '', + '', + '', + '', + '', + '', + '', + '', + '', + '' + ].join('')); + return view.appendTo($fix) + .then(function () { + strictEqual($fix.find('.oe_searchview_filters h3').length, + 1, + "should only display one group"); + strictEqual($fix.find('.oe_searchview_filters h3').text(), + 'w Visibles', + "should only display the Visibles group (and its icon char)"); + + var $fs = $fix.find('.oe_searchview_filters ul'); + strictEqual($fs.children().length, 1, + "should only have one filter in the drawer"); + strictEqual(_.str.trim($fs.text()), "Filter 0", + "should have filter 0 as sole filter"); + + var done = $.Deferred(); + view.complete_global_search({term: 'filter'}, function (compls) { + done.resolve(); + strictEqual(compls.length, 2, + "should have 2 completions"); + deepEqual(_.pluck(compls, 'label'), + ['Field 0', 'Filter on: Filter 0'], + "should complete on field 0 and filter 0"); + }); + return done; + }); + }); + // Default on invisible fields should still work, for fields and filters both + test('invisible-defaults', {asserts: 1}, function (instance, $fix, mock) { + var view = makeView(instance, mock, { + field: {type: 'char', string: "Field"}, + field2: {type: 'char', string: "Field 2"}, + }, [ + '', + '', + '', + '', + '', + '', + '', + '' + ].join(''), {field: "foo", filter: true}); + + return view.appendTo($fix) + .then(function () { + deepEqual(view.build_search_data(), { + errors: [], + groupbys: [], + contexts: [], + domains: [ + // Generated from field + [['field', 'ilike', 'foo']], + // generated from filter + "[['whee', '=', '42']]" + ], + }, "should yield invisible fields selected by defaults"); + }); + }); }); From ab816b74fb007dd19c28dcb00281a2555ae7f2ee Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 14 Feb 2013 08:59:06 +0100 Subject: [PATCH 08/56] [FIX] correctly handle invisible or empty groups in total filters count computation bzr revid: xmo@openerp.com-20130214075906-kg83zi0pan61au1d --- addons/web/static/src/js/search.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/addons/web/static/src/js/search.js b/addons/web/static/src/js/search.js index c57dec67229..bb846726b93 100644 --- a/addons/web/static/src/js/search.js +++ b/addons/web/static/src/js/search.js @@ -1740,17 +1740,19 @@ instance.web.search.Filters = instance.web.search.Input.extend({ var running_count = 0; // get total filters count var is_group = function (i) { return i instanceof instance.web.search.FilterGroup; }; - var filters_count = _(this.view.controls).chain() + var visible_filters = _(this.view.controls).chain().reject(function (group) { + return _(_(group.children).filter(is_group)).isEmpty() + || group.modifiers.invisible; + }); + var filters_count = visible_filters + .pluck('children') .flatten() .filter(is_group) .map(function (i) { return i.filters.length; }) .sum() .value(); - var col1 = [], col2 = _(this.view.controls).chain() - .reject(function (group) { - return _(group.children).isEmpty() || group.modifiers.invisible; - }).map(function (group) { + var col1 = [], col2 = visible_filters.map(function (group) { var filters = _(group.children).filter(is_group); return { name: _.str.sprintf("%s %s", From 01eb26c35a9508a1579b4a9205c0d6b9685d81b5 Mon Sep 17 00:00:00 2001 From: Dhruti Shastri Date: Tue, 19 Feb 2013 14:18:56 +0530 Subject: [PATCH 09/56] [translation] : placeholders are missed out for Translation (Case:585261) bzr revid: dhs@tinyerp.com-20130219084856-ab44isuegq7w31wo --- openerp/tools/translate.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openerp/tools/translate.py b/openerp/tools/translate.py index 41113b8ccd9..da9f47dfebf 100644 --- a/openerp/tools/translate.py +++ b/openerp/tools/translate.py @@ -550,6 +550,8 @@ def trans_parse_view(de): res.append(de.get('sum').encode("utf8")) if de.get("confirm"): res.append(de.get('confirm').encode("utf8")) + if de.get("placeholder"): + res.append(de.get('confirm').encode("utf8")) for n in de: res.extend(trans_parse_view(n)) return res From c0e0110f420f194b66686340824fc1eca058a1e7 Mon Sep 17 00:00:00 2001 From: Dhruti Shastri Date: Tue, 19 Feb 2013 15:26:01 +0530 Subject: [PATCH 10/56] [translation] : placeholders are missed out for Translation (Case:585261) bzr revid: dhs@tinyerp.com-20130219095601-ncsy6tnfns1xz4ls --- openerp/tools/translate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openerp/tools/translate.py b/openerp/tools/translate.py index da9f47dfebf..f1efbc17f2d 100644 --- a/openerp/tools/translate.py +++ b/openerp/tools/translate.py @@ -551,7 +551,7 @@ def trans_parse_view(de): if de.get("confirm"): res.append(de.get('confirm').encode("utf8")) if de.get("placeholder"): - res.append(de.get('confirm').encode("utf8")) + res.append(de.get('placeholder').encode("utf8")) for n in de: res.extend(trans_parse_view(n)) return res From 8d02a23ee96211c5fc539dc9490220a58e8467e8 Mon Sep 17 00:00:00 2001 From: Mohammed Shekha Date: Tue, 19 Feb 2013 15:49:40 +0530 Subject: [PATCH 11/56] [FIX]Fixed the issue of reload record affter callign exec_workflow for one2many dataset. bzr revid: msh@openerp.com-20130219101940-uum7iqbvvhg619uk --- addons/web/static/src/js/data.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/addons/web/static/src/js/data.js b/addons/web/static/src/js/data.js index bbff633c687..137694ee347 100644 --- a/addons/web/static/src/js/data.js +++ b/addons/web/static/src/js/data.js @@ -860,9 +860,8 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({ } return completion.promise(); }, - call_button: function (method, args) { - var id = args[0][0], index; - for(var i=0, len=this.cache.length; i Date: Wed, 20 Feb 2013 15:47:21 +0530 Subject: [PATCH 12/56] [FIX]Fixed the issue of context not passed in fields_view_get method for graph view. bzr revid: msh@openerp.com-20130220101721-7rzlpdpy7p5em0g7 --- addons/web_graph/static/src/js/graph.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/web_graph/static/src/js/graph.js b/addons/web_graph/static/src/js/graph.js index 35ad0aaaea8..ce98962367c 100644 --- a/addons/web_graph/static/src/js/graph.js +++ b/addons/web_graph/static/src/js/graph.js @@ -246,7 +246,7 @@ instance.web_graph.GraphView = instance.web.View.extend({ var result = []; var ticks = {}; - return obj.call("fields_view_get", [view_id, 'graph']).then(function(tmp) { + return obj.call("fields_view_get", [view_id, 'graph', context]).then(function(tmp) { view_get = tmp; fields = view_get['fields']; var toload = _.select(group_by, function(x) { return fields[x] === undefined }); From e6dadd6b36516671d3191f71ec6ff2fd387ba72f Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Thu, 21 Feb 2013 14:47:24 +0100 Subject: [PATCH 13/56] [FIX] account: change confusing labels on Cancel buttons bzr revid: odo@openerp.com-20130221134724-72bevdo9r56gm4ic --- addons/account/account_invoice_view.xml | 6 +++--- addons/account/account_view.xml | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/addons/account/account_invoice_view.xml b/addons/account/account_invoice_view.xml index 6cca8e0ce46..e8aee50f68d 100644 --- a/addons/account/account_invoice_view.xml +++ b/addons/account/account_invoice_view.xml @@ -145,8 +145,8 @@
    @@ -1256,7 +1256,7 @@
    @@ -2258,7 +2258,7 @@
    From af065f1dfc3bfe3162844efd5e220fbf4e874504 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Thu, 21 Feb 2013 16:04:12 +0100 Subject: [PATCH 14/56] [FIX] sale: change confusing labels on Cancel buttons bzr revid: odo@openerp.com-20130221150412-m4dqjuctf980zc6p --- addons/sale/sale_view.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/addons/sale/sale_view.xml b/addons/sale/sale_view.xml index 18fdb739923..74d0b2cc669 100644 --- a/addons/sale/sale_view.xml +++ b/addons/sale/sale_view.xml @@ -140,9 +140,9 @@