Merge lp:~openerp-dev/openerp-web/7.0-searchview-invisibles-xmo into lp:openerp-web/7.0
- 7.0-searchview-invisibles-xmo
- Merge into 7.0
Proposed by
Xavier (Open ERP)
Status: | Merged | ||||
---|---|---|---|---|---|
Merged at revision: | 3803 | ||||
Proposed branch: | lp:~openerp-dev/openerp-web/7.0-searchview-invisibles-xmo | ||||
Merge into: | lp:openerp-web/7.0 | ||||
Diff against target: |
643 lines (+284/-83) 4 files modified
addons/web/doc/search_view.rst (+11/-0) addons/web/static/src/js/search.js (+107/-64) addons/web/static/src/xml/base.xml (+1/-10) addons/web/static/test/search.js (+165/-9) |
||||
To merge this branch: | bzr merge lp:~openerp-dev/openerp-web/7.0-searchview-invisibles-xmo | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Yannick Vaucher @ Camptocamp (community) | test | Approve | |
OpenERP Core Team | Pending | ||
Review via email:
|
Commit message
Description of the change
Re-introduces handling of invisible fields and groups in the search view, forgotten during the reimplementation of the unified search view for 7.0
This handling is important as it's how @groups is handled (invisible fields and filters can't be removed altogether as e.g. defaults still need to work correctly).
To post a comment you must log in.
Revision history for this message
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Yannick Vaucher @ Camptocamp (yvaucher-c2c) wrote : | # |
review:
Approve
(test)
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'addons/web/doc/search_view.rst' | |||
2 | --- addons/web/doc/search_view.rst 2012-11-14 23:00:42 +0000 | |||
3 | +++ addons/web/doc/search_view.rst 2013-02-14 08:53:20 +0000 | |||
4 | @@ -107,6 +107,12 @@ | |||
5 | 107 | items, it *should* prefix those with a section title using its own | 107 | items, it *should* prefix those with a section title using its own |
6 | 108 | name. This has no technical consequence but is clearer for users. | 108 | name. This has no technical consequence but is clearer for users. |
7 | 109 | 109 | ||
8 | 110 | .. note:: | ||
9 | 111 | |||
10 | 112 | If a field is :js:func:`invisible | ||
11 | 113 | <openerp.web.search.Input.visible>`, its completion function will | ||
12 | 114 | *not* be called. | ||
13 | 115 | |||
14 | 110 | Providing drawer/supplementary UI | 116 | Providing drawer/supplementary UI |
15 | 111 | +++++++++++++++++++++++++++++++++ | 117 | +++++++++++++++++++++++++++++++++ |
16 | 112 | 118 | ||
17 | @@ -145,6 +151,11 @@ | |||
18 | 145 | dynamically collects, lays out and renders filters? => | 151 | dynamically collects, lays out and renders filters? => |
19 | 146 | exercises drawer thingies | 152 | exercises drawer thingies |
20 | 147 | 153 | ||
21 | 154 | .. note:: | ||
22 | 155 | |||
23 | 156 | An :js:func:`invisible <openerp.web.search.Input.visible>` input | ||
24 | 157 | will not be inserted into the drawer. | ||
25 | 158 | |||
26 | 148 | Converting from facet objects | 159 | Converting from facet objects |
27 | 149 | +++++++++++++++++++++++++++++ | 160 | +++++++++++++++++++++++++++++ |
28 | 150 | 161 | ||
29 | 151 | 162 | ||
30 | === modified file 'addons/web/static/src/js/search.js' | |||
31 | --- addons/web/static/src/js/search.js 2013-01-31 13:51:25 +0000 | |||
32 | +++ addons/web/static/src/js/search.js 2013-02-14 08:53:20 +0000 | |||
33 | @@ -359,7 +359,7 @@ | |||
34 | 359 | this.has_defaults = !_.isEmpty(this.defaults); | 359 | this.has_defaults = !_.isEmpty(this.defaults); |
35 | 360 | 360 | ||
36 | 361 | this.inputs = []; | 361 | this.inputs = []; |
38 | 362 | this.controls = {}; | 362 | this.controls = []; |
39 | 363 | 363 | ||
40 | 364 | this.headless = this.options.hidden && !this.has_defaults; | 364 | this.headless = this.options.hidden && !this.has_defaults; |
41 | 365 | 365 | ||
42 | @@ -499,6 +499,7 @@ | |||
43 | 499 | */ | 499 | */ |
44 | 500 | complete_global_search: function (req, resp) { | 500 | complete_global_search: function (req, resp) { |
45 | 501 | $.when.apply(null, _(this.inputs).chain() | 501 | $.when.apply(null, _(this.inputs).chain() |
46 | 502 | .filter(function (input) { return input.visible(); }) | ||
47 | 502 | .invoke('complete', req.term) | 503 | .invoke('complete', req.term) |
48 | 503 | .value()).then(function () { | 504 | .value()).then(function () { |
49 | 504 | resp(_(_(arguments).compact()).flatten(true)); | 505 | resp(_(_(arguments).compact()).flatten(true)); |
50 | @@ -587,18 +588,18 @@ | |||
51 | 587 | * | 588 | * |
52 | 588 | * @param {Array} items a list of nodes to convert to widgets | 589 | * @param {Array} items a list of nodes to convert to widgets |
53 | 589 | * @param {Object} fields a mapping of field names to (ORM) field attributes | 590 | * @param {Object} fields a mapping of field names to (ORM) field attributes |
55 | 590 | * @param {String} [group_name] name of the group to put the new controls in | 591 | * @param {Object} [group] group to put the new controls in |
56 | 591 | */ | 592 | */ |
61 | 592 | make_widgets: function (items, fields, group_name) { | 593 | make_widgets: function (items, fields, group) { |
62 | 593 | group_name = group_name || null; | 594 | if (!group) { |
63 | 594 | if (!(group_name in this.controls)) { | 595 | group = new instance.web.search.Group( |
64 | 595 | this.controls[group_name] = []; | 596 | this, 'q', {attrs: {string: _t("Filters")}}); |
65 | 596 | } | 597 | } |
67 | 597 | var self = this, group = this.controls[group_name]; | 598 | var self = this; |
68 | 598 | var filters = []; | 599 | var filters = []; |
69 | 599 | _.each(items, function (item) { | 600 | _.each(items, function (item) { |
70 | 600 | if (filters.length && item.tag !== 'filter') { | 601 | if (filters.length && item.tag !== 'filter') { |
72 | 601 | group.push(new instance.web.search.FilterGroup(filters, this)); | 602 | group.push(new instance.web.search.FilterGroup(filters, group)); |
73 | 602 | filters = []; | 603 | filters = []; |
74 | 603 | } | 604 | } |
75 | 604 | 605 | ||
76 | @@ -606,15 +607,18 @@ | |||
77 | 606 | case 'separator': case 'newline': | 607 | case 'separator': case 'newline': |
78 | 607 | break; | 608 | break; |
79 | 608 | case 'filter': | 609 | case 'filter': |
81 | 609 | filters.push(new instance.web.search.Filter(item, this)); | 610 | filters.push(new instance.web.search.Filter(item, group)); |
82 | 610 | break; | 611 | break; |
83 | 611 | case 'group': | 612 | case 'group': |
85 | 612 | self.make_widgets(item.children, fields, item.attrs.string); | 613 | self.make_widgets(item.children, fields, |
86 | 614 | new instance.web.search.Group(group, 'w', item)); | ||
87 | 613 | break; | 615 | break; |
88 | 614 | case 'field': | 616 | case 'field': |
90 | 615 | group.push(this.make_field(item, fields[item['attrs'].name])); | 617 | var field = this.make_field( |
91 | 618 | item, fields[item['attrs'].name], group); | ||
92 | 619 | group.push(field); | ||
93 | 616 | // filters | 620 | // filters |
95 | 617 | self.make_widgets(item.children, fields, group_name); | 621 | self.make_widgets(item.children, fields, group); |
96 | 618 | break; | 622 | break; |
97 | 619 | } | 623 | } |
98 | 620 | }, this); | 624 | }, this); |
99 | @@ -629,12 +633,13 @@ | |||
100 | 629 | * | 633 | * |
101 | 630 | * @param {Object} item fields_view_get node for the field | 634 | * @param {Object} item fields_view_get node for the field |
102 | 631 | * @param {Object} field fields_get result for the field | 635 | * @param {Object} field fields_get result for the field |
103 | 636 | * @param {Object} [parent] | ||
104 | 632 | * @returns instance.web.search.Field | 637 | * @returns instance.web.search.Field |
105 | 633 | */ | 638 | */ |
107 | 634 | make_field: function (item, field) { | 639 | make_field: function (item, field, parent) { |
108 | 635 | var obj = instance.web.search.fields.get_any( [item.attrs.widget, field.type]); | 640 | var obj = instance.web.search.fields.get_any( [item.attrs.widget, field.type]); |
109 | 636 | if(obj) { | 641 | if(obj) { |
111 | 637 | return new (obj) (item, field, this); | 642 | return new (obj) (item, field, parent || this); |
112 | 638 | } else { | 643 | } else { |
113 | 639 | console.group('Unknown field type ' + field.type); | 644 | console.group('Unknown field type ' + field.type); |
114 | 640 | console.error('View node', item); | 645 | console.error('View node', item); |
115 | @@ -869,13 +874,18 @@ | |||
116 | 869 | * @constructs instance.web.search.Widget | 874 | * @constructs instance.web.search.Widget |
117 | 870 | * @extends instance.web.Widget | 875 | * @extends instance.web.Widget |
118 | 871 | * | 876 | * |
120 | 872 | * @param view the ancestor view of this widget | 877 | * @param parent parent of this widget |
121 | 873 | */ | 878 | */ |
125 | 874 | init: function (view) { | 879 | init: function (parent) { |
126 | 875 | this._super(view); | 880 | this._super(parent); |
127 | 876 | this.view = view; | 881 | var ancestor = parent; |
128 | 882 | do { | ||
129 | 883 | this.view = ancestor; | ||
130 | 884 | } while (!(ancestor instanceof instance.web.SearchView) | ||
131 | 885 | && (ancestor = (ancestor.getParent && ancestor.getParent()))); | ||
132 | 877 | } | 886 | } |
133 | 878 | }); | 887 | }); |
134 | 888 | |||
135 | 879 | instance.web.search.add_expand_listener = function($root) { | 889 | instance.web.search.add_expand_listener = function($root) { |
136 | 880 | $root.find('a.searchview_group_string').click(function (e) { | 890 | $root.find('a.searchview_group_string').click(function (e) { |
137 | 881 | $root.toggleClass('folded expanded'); | 891 | $root.toggleClass('folded expanded'); |
138 | @@ -884,13 +894,24 @@ | |||
139 | 884 | }); | 894 | }); |
140 | 885 | }; | 895 | }; |
141 | 886 | instance.web.search.Group = instance.web.search.Widget.extend({ | 896 | instance.web.search.Group = instance.web.search.Widget.extend({ |
149 | 887 | template: 'SearchView.group', | 897 | init: function (parent, icon, node) { |
150 | 888 | init: function (view_section, view, fields) { | 898 | this._super(parent); |
151 | 889 | this._super(view); | 899 | var attrs = node.attrs; |
152 | 890 | this.attrs = view_section.attrs; | 900 | this.modifiers = attrs.modifiers = |
153 | 891 | this.lines = view.make_widgets( | 901 | attrs.modifiers ? JSON.parse(attrs.modifiers) : {}; |
154 | 892 | view_section.children, fields); | 902 | this.attrs = attrs; |
155 | 893 | } | 903 | this.icon = icon; |
156 | 904 | this.name = attrs.string; | ||
157 | 905 | this.children = []; | ||
158 | 906 | |||
159 | 907 | this.view.controls.push(this); | ||
160 | 908 | }, | ||
161 | 909 | push: function (input) { | ||
162 | 910 | this.children.push(input); | ||
163 | 911 | }, | ||
164 | 912 | visible: function () { | ||
165 | 913 | return !this.modifiers.invisible; | ||
166 | 914 | }, | ||
167 | 894 | }); | 915 | }); |
168 | 895 | 916 | ||
169 | 896 | instance.web.search.Input = instance.web.search.Widget.extend( /** @lends instance.web.search.Input# */{ | 917 | instance.web.search.Input = instance.web.search.Widget.extend( /** @lends instance.web.search.Input# */{ |
170 | @@ -899,12 +920,12 @@ | |||
171 | 899 | * @constructs instance.web.search.Input | 920 | * @constructs instance.web.search.Input |
172 | 900 | * @extends instance.web.search.Widget | 921 | * @extends instance.web.search.Widget |
173 | 901 | * | 922 | * |
175 | 902 | * @param view | 923 | * @param parent |
176 | 903 | */ | 924 | */ |
179 | 904 | init: function (view) { | 925 | init: function (parent) { |
180 | 905 | this._super(view); | 926 | this._super(parent); |
181 | 927 | this.load_attrs({}); | ||
182 | 906 | this.view.inputs.push(this); | 928 | this.view.inputs.push(this); |
183 | 907 | this.style = undefined; | ||
184 | 908 | }, | 929 | }, |
185 | 909 | /** | 930 | /** |
186 | 910 | * Fetch auto-completion values for the widget. | 931 | * Fetch auto-completion values for the widget. |
187 | @@ -952,15 +973,30 @@ | |||
188 | 952 | "get_domain not implemented for widget " + this.attrs.type); | 973 | "get_domain not implemented for widget " + this.attrs.type); |
189 | 953 | }, | 974 | }, |
190 | 954 | load_attrs: function (attrs) { | 975 | load_attrs: function (attrs) { |
196 | 955 | if (attrs.modifiers) { | 976 | attrs.modifiers = attrs.modifiers ? JSON.parse(attrs.modifiers) : {}; |
197 | 956 | attrs.modifiers = JSON.parse(attrs.modifiers); | 977 | this.attrs = attrs; |
198 | 957 | attrs.invisible = attrs.modifiers.invisible || false; | 978 | }, |
199 | 958 | if (attrs.invisible) { | 979 | /** |
200 | 959 | this.style = 'display: none;' | 980 | * Returns whether the input is "visible". The default behavior is to |
201 | 981 | * query the ``modifiers.invisible`` flag on the input's description or | ||
202 | 982 | * view node. | ||
203 | 983 | * | ||
204 | 984 | * @returns {Boolean} | ||
205 | 985 | */ | ||
206 | 986 | visible: function () { | ||
207 | 987 | if (this.attrs.modifiers.invisible) { | ||
208 | 988 | return false; | ||
209 | 989 | } | ||
210 | 990 | var parent = this; | ||
211 | 991 | while ((parent = parent.getParent()) && | ||
212 | 992 | ( (parent instanceof instance.web.search.Group) | ||
213 | 993 | || (parent instanceof instance.web.search.Input))) { | ||
214 | 994 | if (!parent.visible()) { | ||
215 | 995 | return false; | ||
216 | 960 | } | 996 | } |
217 | 961 | } | 997 | } |
220 | 962 | this.attrs = attrs; | 998 | return true; |
221 | 963 | } | 999 | }, |
222 | 964 | }); | 1000 | }); |
223 | 965 | instance.web.search.FilterGroup = instance.web.search.Input.extend(/** @lends instance.web.search.FilterGroup# */{ | 1001 | instance.web.search.FilterGroup = instance.web.search.Input.extend(/** @lends instance.web.search.FilterGroup# */{ |
224 | 966 | template: 'SearchView.filters', | 1002 | template: 'SearchView.filters', |
225 | @@ -974,17 +1010,17 @@ | |||
226 | 974 | * @extends instance.web.search.Input | 1010 | * @extends instance.web.search.Input |
227 | 975 | * | 1011 | * |
228 | 976 | * @param {Array<instance.web.search.Filter>} filters elements of the group | 1012 | * @param {Array<instance.web.search.Filter>} filters elements of the group |
230 | 977 | * @param {instance.web.SearchView} view view in which the filters are contained | 1013 | * @param {instance.web.SearchView} parent parent in which the filters are contained |
231 | 978 | */ | 1014 | */ |
233 | 979 | init: function (filters, view) { | 1015 | init: function (filters, parent) { |
234 | 980 | // If all filters are group_by and we're not initializing a GroupbyGroup, | 1016 | // If all filters are group_by and we're not initializing a GroupbyGroup, |
235 | 981 | // create a GroupbyGroup instead of the current FilterGroup | 1017 | // create a GroupbyGroup instead of the current FilterGroup |
236 | 982 | if (!(this instanceof instance.web.search.GroupbyGroup) && | 1018 | if (!(this instanceof instance.web.search.GroupbyGroup) && |
237 | 983 | _(filters).all(function (f) { | 1019 | _(filters).all(function (f) { |
238 | 984 | return f.attrs.context && f.attrs.context.group_by; })) { | 1020 | return f.attrs.context && f.attrs.context.group_by; })) { |
240 | 985 | return new instance.web.search.GroupbyGroup(filters, view); | 1021 | return new instance.web.search.GroupbyGroup(filters, parent); |
241 | 986 | } | 1022 | } |
243 | 987 | this._super(view); | 1023 | this._super(parent); |
244 | 988 | this.filters = filters; | 1024 | this.filters = filters; |
245 | 989 | this.view.query.on('add remove change reset', this.proxy('search_change')); | 1025 | this.view.query.on('add remove change reset', this.proxy('search_change')); |
246 | 990 | }, | 1026 | }, |
247 | @@ -1103,6 +1139,7 @@ | |||
248 | 1103 | var self = this; | 1139 | var self = this; |
249 | 1104 | item = item.toLowerCase(); | 1140 | item = item.toLowerCase(); |
250 | 1105 | var facet_values = _(this.filters).chain() | 1141 | var facet_values = _(this.filters).chain() |
251 | 1142 | .filter(function (filter) { return filter.visible(); }) | ||
252 | 1106 | .filter(function (filter) { | 1143 | .filter(function (filter) { |
253 | 1107 | var at = { | 1144 | var at = { |
254 | 1108 | string: filter.attrs.string || '', | 1145 | string: filter.attrs.string || '', |
255 | @@ -1129,8 +1166,8 @@ | |||
256 | 1129 | instance.web.search.GroupbyGroup = instance.web.search.FilterGroup.extend({ | 1166 | instance.web.search.GroupbyGroup = instance.web.search.FilterGroup.extend({ |
257 | 1130 | icon: 'w', | 1167 | icon: 'w', |
258 | 1131 | completion_label: _lt("Group by: %s"), | 1168 | completion_label: _lt("Group by: %s"), |
261 | 1132 | init: function (filters, view) { | 1169 | init: function (filters, parent) { |
262 | 1133 | this._super(filters, view); | 1170 | this._super(filters, parent); |
263 | 1134 | // Not flanders: facet unicity is handled through the | 1171 | // Not flanders: facet unicity is handled through the |
264 | 1135 | // (category, field) pair of facet attributes. This is all well and | 1172 | // (category, field) pair of facet attributes. This is all well and |
265 | 1136 | // good for regular filter groups where a group matches a facet, but for | 1173 | // good for regular filter groups where a group matches a facet, but for |
266 | @@ -1138,8 +1175,8 @@ | |||
267 | 1138 | // view which proxies to the first GroupbyGroup, so it can be used | 1175 | // view which proxies to the first GroupbyGroup, so it can be used |
268 | 1139 | // for every GroupbyGroup and still provides the various methods needed | 1176 | // for every GroupbyGroup and still provides the various methods needed |
269 | 1140 | // by the search view. Use weirdo name to avoid risks of conflicts | 1177 | // by the search view. Use weirdo name to avoid risks of conflicts |
272 | 1141 | if (!this.getParent()._s_groupby) { | 1178 | if (!this.view._s_groupby) { |
273 | 1142 | this.getParent()._s_groupby = { | 1179 | this.view._s_groupby = { |
274 | 1143 | help: "See GroupbyGroup#init", | 1180 | help: "See GroupbyGroup#init", |
275 | 1144 | get_context: this.proxy('get_context'), | 1181 | get_context: this.proxy('get_context'), |
276 | 1145 | get_domain: this.proxy('get_domain'), | 1182 | get_domain: this.proxy('get_domain'), |
277 | @@ -1148,7 +1185,7 @@ | |||
278 | 1148 | } | 1185 | } |
279 | 1149 | }, | 1186 | }, |
280 | 1150 | match_facet: function (facet) { | 1187 | match_facet: function (facet) { |
282 | 1151 | return facet.get('field') === this.getParent()._s_groupby; | 1188 | return facet.get('field') === this.view._s_groupby; |
283 | 1152 | }, | 1189 | }, |
284 | 1153 | make_facet: function (values) { | 1190 | make_facet: function (values) { |
285 | 1154 | return { | 1191 | return { |
286 | @@ -1173,10 +1210,10 @@ | |||
287 | 1173 | * @extends instance.web.search.Input | 1210 | * @extends instance.web.search.Input |
288 | 1174 | * | 1211 | * |
289 | 1175 | * @param node | 1212 | * @param node |
291 | 1176 | * @param view | 1213 | * @param parent |
292 | 1177 | */ | 1214 | */ |
295 | 1178 | init: function (node, view) { | 1215 | init: function (node, parent) { |
296 | 1179 | this._super(view); | 1216 | this._super(parent); |
297 | 1180 | this.load_attrs(node.attrs); | 1217 | this.load_attrs(node.attrs); |
298 | 1181 | }, | 1218 | }, |
299 | 1182 | facet_for: function () { return $.when(null); }, | 1219 | facet_for: function () { return $.when(null); }, |
300 | @@ -1192,10 +1229,10 @@ | |||
301 | 1192 | * | 1229 | * |
302 | 1193 | * @param view_section | 1230 | * @param view_section |
303 | 1194 | * @param field | 1231 | * @param field |
305 | 1195 | * @param view | 1232 | * @param parent |
306 | 1196 | */ | 1233 | */ |
309 | 1197 | init: function (view_section, field, view) { | 1234 | init: function (view_section, field, parent) { |
310 | 1198 | this._super(view); | 1235 | this._super(parent); |
311 | 1199 | this.load_attrs(_.extend({}, field, view_section.attrs)); | 1236 | this.load_attrs(_.extend({}, field, view_section.attrs)); |
312 | 1200 | }, | 1237 | }, |
313 | 1201 | facet_for: function (value) { | 1238 | facet_for: function (value) { |
314 | @@ -1235,7 +1272,7 @@ | |||
315 | 1235 | * | 1272 | * |
316 | 1236 | * @param {String} name the field's name | 1273 | * @param {String} name the field's name |
317 | 1237 | * @param {String} operator the field's operator (either attribute-specified or default operator for the field | 1274 | * @param {String} operator the field's operator (either attribute-specified or default operator for the field |
319 | 1238 | * @param {Number|String} value parsed value for the field | 1275 | * @param {Number|String} facet parsed value for the field |
320 | 1239 | * @returns {Array<Array>} domain to include in the resulting search | 1276 | * @returns {Array<Array>} domain to include in the resulting search |
321 | 1240 | */ | 1277 | */ |
322 | 1241 | make_domain: function (name, operator, facet) { | 1278 | make_domain: function (name, operator, facet) { |
323 | @@ -1467,8 +1504,8 @@ | |||
324 | 1467 | }); | 1504 | }); |
325 | 1468 | instance.web.search.ManyToOneField = instance.web.search.CharField.extend({ | 1505 | instance.web.search.ManyToOneField = instance.web.search.CharField.extend({ |
326 | 1469 | default_operator: {}, | 1506 | default_operator: {}, |
329 | 1470 | init: function (view_section, field, view) { | 1507 | init: function (view_section, field, parent) { |
330 | 1471 | this._super(view_section, field, view); | 1508 | this._super(view_section, field, parent); |
331 | 1472 | this.model = new instance.web.Model(this.attrs.relation); | 1509 | this.model = new instance.web.Model(this.attrs.relation); |
332 | 1473 | }, | 1510 | }, |
333 | 1474 | complete: function (needle) { | 1511 | complete: function (needle) { |
334 | @@ -1703,22 +1740,28 @@ | |||
335 | 1703 | var running_count = 0; | 1740 | var running_count = 0; |
336 | 1704 | // get total filters count | 1741 | // get total filters count |
337 | 1705 | var is_group = function (i) { return i instanceof instance.web.search.FilterGroup; }; | 1742 | var is_group = function (i) { return i instanceof instance.web.search.FilterGroup; }; |
339 | 1706 | var filters_count = _(this.view.controls).chain() | 1743 | var visible_filters = _(this.view.controls).chain().reject(function (group) { |
340 | 1744 | return _(_(group.children).filter(is_group)).isEmpty() | ||
341 | 1745 | || group.modifiers.invisible; | ||
342 | 1746 | }); | ||
343 | 1747 | var filters_count = visible_filters | ||
344 | 1748 | .pluck('children') | ||
345 | 1707 | .flatten() | 1749 | .flatten() |
346 | 1708 | .filter(is_group) | 1750 | .filter(is_group) |
347 | 1709 | .map(function (i) { return i.filters.length; }) | 1751 | .map(function (i) { return i.filters.length; }) |
348 | 1710 | .sum() | 1752 | .sum() |
349 | 1711 | .value(); | 1753 | .value(); |
350 | 1712 | 1754 | ||
360 | 1713 | var col1 = [], col2 = _(this.view.controls).map(function (inputs, group) { | 1755 | var col1 = [], col2 = visible_filters.map(function (group) { |
361 | 1714 | var filters = _(inputs).filter(is_group); | 1756 | var filters = _(group.children).filter(is_group); |
362 | 1715 | return { | 1757 | return { |
363 | 1716 | name: group === 'null' ? "<span class='oe_i'>q</span> " + _t("Filters") : "<span class='oe_i'>w</span> " + group, | 1758 | name: _.str.sprintf("<span class='oe_i'>%s</span> %s", |
364 | 1717 | filters: filters, | 1759 | group.icon, group.name), |
365 | 1718 | length: _(filters).chain().map(function (i) { | 1760 | filters: filters, |
366 | 1719 | return i.filters.length; }).sum().value() | 1761 | length: _(filters).chain().map(function (i) { |
367 | 1720 | }; | 1762 | return i.filters.length; }).sum().value() |
368 | 1721 | }); | 1763 | }; |
369 | 1764 | }).value(); | ||
370 | 1722 | 1765 | ||
371 | 1723 | while (col2.length) { | 1766 | while (col2.length) { |
372 | 1724 | // col1 + group should be smaller than col2 + group | 1767 | // col1 + group should be smaller than col2 + group |
373 | 1725 | 1768 | ||
374 | === modified file 'addons/web/static/src/xml/base.xml' | |||
375 | --- addons/web/static/src/xml/base.xml 2013-02-13 15:21:58 +0000 | |||
376 | +++ addons/web/static/src/xml/base.xml 2013-02-14 08:53:20 +0000 | |||
377 | @@ -1496,7 +1496,7 @@ | |||
378 | 1496 | <t t-esc="attrs.string"/> | 1496 | <t t-esc="attrs.string"/> |
379 | 1497 | </button> | 1497 | </button> |
380 | 1498 | <ul t-name="SearchView.filters"> | 1498 | <ul t-name="SearchView.filters"> |
382 | 1499 | <li t-foreach="widget.filters" t-as="filter" | 1499 | <li t-foreach="widget.filters" t-as="filter" t-if="filter.visible()" |
383 | 1500 | t-att-title="filter.attrs.string ? filter.attrs.help : undefined"> | 1500 | t-att-title="filter.attrs.string ? filter.attrs.help : undefined"> |
384 | 1501 | <t t-esc="filter.attrs.string or filter.attrs.help or filter.attrs.name or 'Ω'"/> | 1501 | <t t-esc="filter.attrs.string or filter.attrs.help or filter.attrs.name or 'Ω'"/> |
385 | 1502 | </li> | 1502 | </li> |
386 | @@ -1584,15 +1584,6 @@ | |||
387 | 1584 | </div> | 1584 | </div> |
388 | 1585 | </div> | 1585 | </div> |
389 | 1586 | </t> | 1586 | </t> |
390 | 1587 | <t t-name="SearchView.group"> | ||
391 | 1588 | <t t-call="SearchView.util.expand"> | ||
392 | 1589 | <t t-set="expand" t-value="attrs.expand"/> | ||
393 | 1590 | <t t-set="label" t-value="attrs.string"/> | ||
394 | 1591 | <t t-set="content"> | ||
395 | 1592 | <t t-call="SearchView.render_lines"/> | ||
396 | 1593 | </t> | ||
397 | 1594 | </t> | ||
398 | 1595 | </t> | ||
399 | 1596 | <div t-name="SearchView.Filters" class="oe_searchview_filters oe_searchview_section"> | 1587 | <div t-name="SearchView.Filters" class="oe_searchview_filters oe_searchview_section"> |
400 | 1597 | 1588 | ||
401 | 1598 | </div> | 1589 | </div> |
402 | 1599 | 1590 | ||
403 | === modified file 'addons/web/static/test/search.js' | |||
404 | --- addons/web/static/test/search.js 2013-01-31 11:26:17 +0000 | |||
405 | +++ addons/web/static/test/search.js 2013-02-14 08:53:20 +0000 | |||
406 | @@ -1,4 +1,4 @@ | |||
408 | 1 | openerp.testing.section('query', { | 1 | openerp.testing.section('search.query', { |
409 | 2 | dependencies: ['web.search'] | 2 | dependencies: ['web.search'] |
410 | 3 | }, function (test) { | 3 | }, function (test) { |
411 | 4 | test('Adding a facet to the query creates a facet and a value', function (instance) { | 4 | test('Adding a facet to the query creates a facet and a value', function (instance) { |
412 | @@ -180,7 +180,7 @@ | |||
413 | 180 | }); | 180 | }); |
414 | 181 | return view; | 181 | return view; |
415 | 182 | }; | 182 | }; |
417 | 183 | openerp.testing.section('defaults', { | 183 | openerp.testing.section('search.defaults', { |
418 | 184 | dependencies: ['web.search'], | 184 | dependencies: ['web.search'], |
419 | 185 | rpc: 'mock', | 185 | rpc: 'mock', |
420 | 186 | templates: true, | 186 | templates: true, |
421 | @@ -331,7 +331,7 @@ | |||
422 | 331 | }); | 331 | }); |
423 | 332 | }); | 332 | }); |
424 | 333 | }); | 333 | }); |
426 | 334 | openerp.testing.section('completions', { | 334 | openerp.testing.section('search.completions', { |
427 | 335 | dependencies: ['web.search'], | 335 | dependencies: ['web.search'], |
428 | 336 | rpc: 'mock', | 336 | rpc: 'mock', |
429 | 337 | templates: true | 337 | templates: true |
430 | @@ -564,7 +564,7 @@ | |||
431 | 564 | }); | 564 | }); |
432 | 565 | }); | 565 | }); |
433 | 566 | }); | 566 | }); |
435 | 567 | openerp.testing.section('search-serialization', { | 567 | openerp.testing.section('search.serialization', { |
436 | 568 | dependencies: ['web.search'], | 568 | dependencies: ['web.search'], |
437 | 569 | rpc: 'mock', | 569 | rpc: 'mock', |
438 | 570 | templates: true | 570 | templates: true |
439 | @@ -871,7 +871,7 @@ | |||
440 | 871 | return $.when(t1, t2); | 871 | return $.when(t1, t2); |
441 | 872 | }); | 872 | }); |
442 | 873 | }); | 873 | }); |
444 | 874 | openerp.testing.section('removal', { | 874 | openerp.testing.section('search.removal', { |
445 | 875 | dependencies: ['web.search'], | 875 | dependencies: ['web.search'], |
446 | 876 | rpc: 'mock', | 876 | rpc: 'mock', |
447 | 877 | templates: true | 877 | templates: true |
448 | @@ -894,7 +894,7 @@ | |||
449 | 894 | }); | 894 | }); |
450 | 895 | }); | 895 | }); |
451 | 896 | }); | 896 | }); |
453 | 897 | openerp.testing.section('drawer', { | 897 | openerp.testing.section('search.drawer', { |
454 | 898 | dependencies: ['web.search'], | 898 | dependencies: ['web.search'], |
455 | 899 | rpc: 'mock', | 899 | rpc: 'mock', |
456 | 900 | templates: true | 900 | templates: true |
457 | @@ -910,7 +910,7 @@ | |||
458 | 910 | }); | 910 | }); |
459 | 911 | }); | 911 | }); |
460 | 912 | }); | 912 | }); |
462 | 913 | openerp.testing.section('filters', { | 913 | openerp.testing.section('search.filters', { |
463 | 914 | dependencies: ['web.search'], | 914 | dependencies: ['web.search'], |
464 | 915 | rpc: 'mock', | 915 | rpc: 'mock', |
465 | 916 | templates: true, | 916 | templates: true, |
466 | @@ -995,7 +995,7 @@ | |||
467 | 995 | }); | 995 | }); |
468 | 996 | }); | 996 | }); |
469 | 997 | }); | 997 | }); |
471 | 998 | openerp.testing.section('saved_filters', { | 998 | openerp.testing.section('search.filters.saved', { |
472 | 999 | dependencies: ['web.search'], | 999 | dependencies: ['web.search'], |
473 | 1000 | rpc: 'mock', | 1000 | rpc: 'mock', |
474 | 1001 | templates: true | 1001 | templates: true |
475 | @@ -1077,7 +1077,7 @@ | |||
476 | 1077 | }); | 1077 | }); |
477 | 1078 | }); | 1078 | }); |
478 | 1079 | }); | 1079 | }); |
480 | 1080 | openerp.testing.section('advanced', { | 1080 | openerp.testing.section('search.advanced', { |
481 | 1081 | dependencies: ['web.search'], | 1081 | dependencies: ['web.search'], |
482 | 1082 | rpc: 'mock', | 1082 | rpc: 'mock', |
483 | 1083 | templates: true | 1083 | templates: true |
484 | @@ -1158,3 +1158,159 @@ | |||
485 | 1158 | }); | 1158 | }); |
486 | 1159 | // TODO: UI tests? | 1159 | // TODO: UI tests? |
487 | 1160 | }); | 1160 | }); |
488 | 1161 | openerp.testing.section('search.invisible', { | ||
489 | 1162 | dependencies: ['web.search'], | ||
490 | 1163 | rpc: 'mock', | ||
491 | 1164 | templates: true, | ||
492 | 1165 | }, function (test) { | ||
493 | 1166 | var registerTestField = function (instance, methods) { | ||
494 | 1167 | instance.web.search.fields.add('test', 'instance.testing.TestWidget'); | ||
495 | 1168 | instance.testing = { | ||
496 | 1169 | TestWidget: instance.web.search.Field.extend(methods), | ||
497 | 1170 | }; | ||
498 | 1171 | }; | ||
499 | 1172 | var makeView = function (instance, mock, fields, arch, defaults) { | ||
500 | 1173 | mock('ir.filters:get_filters', function () { return []; }); | ||
501 | 1174 | mock('test.model:fields_get', function () { return fields; }); | ||
502 | 1175 | mock('test.model:fields_view_get', function () { | ||
503 | 1176 | return { type: 'search', fields: fields, arch: arch }; | ||
504 | 1177 | }); | ||
505 | 1178 | var ds = new instance.web.DataSet(null, 'test.model'); | ||
506 | 1179 | return new instance.web.SearchView(null, ds, false, defaults); | ||
507 | 1180 | }; | ||
508 | 1181 | // Invisible fields should not auto-complete | ||
509 | 1182 | test('invisible-field-no-autocomplete', {asserts: 1}, function (instance, $fix, mock) { | ||
510 | 1183 | registerTestField(instance, { | ||
511 | 1184 | complete: function () { | ||
512 | 1185 | return $.when([{label: this.attrs.string}]); | ||
513 | 1186 | }, | ||
514 | 1187 | }); | ||
515 | 1188 | var view = makeView(instance, mock, { | ||
516 | 1189 | field0: {type: 'test', string: 'Field 0'}, | ||
517 | 1190 | field1: {type: 'test', string: 'Field 1'}, | ||
518 | 1191 | }, ['<search>', | ||
519 | 1192 | '<field name="field0"/>', | ||
520 | 1193 | '<field name="field1" modifiers="{"invisible": true}"/>', | ||
521 | 1194 | '</search>'].join()); | ||
522 | 1195 | return view.appendTo($fix) | ||
523 | 1196 | .then(function () { | ||
524 | 1197 | var done = $.Deferred(); | ||
525 | 1198 | view.complete_global_search({term: 'test'}, function (comps) { | ||
526 | 1199 | done.resolve(comps); | ||
527 | 1200 | }); | ||
528 | 1201 | return done; | ||
529 | 1202 | }).then(function (completions) { | ||
530 | 1203 | deepEqual(completions, [{label: 'Field 0'}], | ||
531 | 1204 | "should only complete the visible field"); | ||
532 | 1205 | }); | ||
533 | 1206 | }); | ||
534 | 1207 | // Invisible filters should not appear in the drawer | ||
535 | 1208 | test('invisible-filter-no-drawer', {asserts: 4}, function (instance, $fix, mock) { | ||
536 | 1209 | var view = makeView(instance, mock, {}, [ | ||
537 | 1210 | '<search>', | ||
538 | 1211 | '<filter string="filter 0"/>', | ||
539 | 1212 | '<filter string="filter 1" modifiers="{"invisible": true}"/>', | ||
540 | 1213 | '</search>'].join()); | ||
541 | 1214 | return view.appendTo($fix) | ||
542 | 1215 | .then(function () { | ||
543 | 1216 | var $fs = $fix.find('.oe_searchview_filters ul'); | ||
544 | 1217 | strictEqual($fs.children().length, | ||
545 | 1218 | 1, | ||
546 | 1219 | "should only display one filter"); | ||
547 | 1220 | strictEqual(_.str.trim($fs.children().text()), | ||
548 | 1221 | "filter 0", | ||
549 | 1222 | "should only display filter 0"); | ||
550 | 1223 | var done = $.Deferred(); | ||
551 | 1224 | view.complete_global_search({term: 'filter'}, function (comps) { | ||
552 | 1225 | done.resolve(); | ||
553 | 1226 | strictEqual(comps.length, 1, "should only complete visible filter"); | ||
554 | 1227 | strictEqual(comps[0].label, "Filter on: filter 0", | ||
555 | 1228 | "should complete filter 0"); | ||
556 | 1229 | }); | ||
557 | 1230 | return done; | ||
558 | 1231 | }); | ||
559 | 1232 | }); | ||
560 | 1233 | // Invisible filter groups should not appear in the drawer | ||
561 | 1234 | // Group invisibility should be inherited by children | ||
562 | 1235 | test('group-invisibility', {asserts: 6}, function (instance, $fix, mock) { | ||
563 | 1236 | registerTestField(instance, { | ||
564 | 1237 | complete: function () { | ||
565 | 1238 | return $.when([{label: this.attrs.string}]); | ||
566 | 1239 | }, | ||
567 | 1240 | }); | ||
568 | 1241 | var view = makeView(instance, mock, { | ||
569 | 1242 | field0: {type: 'test', string: 'Field 0'}, | ||
570 | 1243 | field1: {type: 'test', string: 'Field 1'}, | ||
571 | 1244 | }, [ | ||
572 | 1245 | '<search>', | ||
573 | 1246 | '<group string="Visibles">', | ||
574 | 1247 | '<field name="field0"/>', | ||
575 | 1248 | '<filter string="Filter 0"/>', | ||
576 | 1249 | '</group>', | ||
577 | 1250 | '<group string="Invisibles" modifiers="{"invisible": true}">', | ||
578 | 1251 | '<field name="field1"/>', | ||
579 | 1252 | '<filter string="Filter 1"/>', | ||
580 | 1253 | '</group>', | ||
581 | 1254 | '</search>' | ||
582 | 1255 | ].join('')); | ||
583 | 1256 | return view.appendTo($fix) | ||
584 | 1257 | .then(function () { | ||
585 | 1258 | strictEqual($fix.find('.oe_searchview_filters h3').length, | ||
586 | 1259 | 1, | ||
587 | 1260 | "should only display one group"); | ||
588 | 1261 | strictEqual($fix.find('.oe_searchview_filters h3').text(), | ||
589 | 1262 | 'w Visibles', | ||
590 | 1263 | "should only display the Visibles group (and its icon char)"); | ||
591 | 1264 | |||
592 | 1265 | var $fs = $fix.find('.oe_searchview_filters ul'); | ||
593 | 1266 | strictEqual($fs.children().length, 1, | ||
594 | 1267 | "should only have one filter in the drawer"); | ||
595 | 1268 | strictEqual(_.str.trim($fs.text()), "Filter 0", | ||
596 | 1269 | "should have filter 0 as sole filter"); | ||
597 | 1270 | |||
598 | 1271 | var done = $.Deferred(); | ||
599 | 1272 | view.complete_global_search({term: 'filter'}, function (compls) { | ||
600 | 1273 | done.resolve(); | ||
601 | 1274 | strictEqual(compls.length, 2, | ||
602 | 1275 | "should have 2 completions"); | ||
603 | 1276 | deepEqual(_.pluck(compls, 'label'), | ||
604 | 1277 | ['Field 0', 'Filter on: Filter 0'], | ||
605 | 1278 | "should complete on field 0 and filter 0"); | ||
606 | 1279 | }); | ||
607 | 1280 | return done; | ||
608 | 1281 | }); | ||
609 | 1282 | }); | ||
610 | 1283 | // Default on invisible fields should still work, for fields and filters both | ||
611 | 1284 | test('invisible-defaults', {asserts: 1}, function (instance, $fix, mock) { | ||
612 | 1285 | var view = makeView(instance, mock, { | ||
613 | 1286 | field: {type: 'char', string: "Field"}, | ||
614 | 1287 | field2: {type: 'char', string: "Field 2"}, | ||
615 | 1288 | }, [ | ||
616 | 1289 | '<search>', | ||
617 | 1290 | '<field name="field2"/>', | ||
618 | 1291 | '<filter name="filter2" string="Filter"', | ||
619 | 1292 | ' domain="[[\'qwa\', \'=\', 42]]"/>', | ||
620 | 1293 | '<group string="Invisibles" modifiers="{"invisible": true}">', | ||
621 | 1294 | '<field name="field"/>', | ||
622 | 1295 | '<filter name="filter" string="Filter"', | ||
623 | 1296 | ' domain="[[\'whee\', \'=\', \'42\']]"/>', | ||
624 | 1297 | '</group>', | ||
625 | 1298 | '</search>' | ||
626 | 1299 | ].join(''), {field: "foo", filter: true}); | ||
627 | 1300 | |||
628 | 1301 | return view.appendTo($fix) | ||
629 | 1302 | .then(function () { | ||
630 | 1303 | deepEqual(view.build_search_data(), { | ||
631 | 1304 | errors: [], | ||
632 | 1305 | groupbys: [], | ||
633 | 1306 | contexts: [], | ||
634 | 1307 | domains: [ | ||
635 | 1308 | // Generated from field | ||
636 | 1309 | [['field', 'ilike', 'foo']], | ||
637 | 1310 | // generated from filter | ||
638 | 1311 | "[['whee', '=', '42']]" | ||
639 | 1312 | ], | ||
640 | 1313 | }, "should yield invisible fields selected by defaults"); | ||
641 | 1314 | }); | ||
642 | 1315 | }); | ||
643 | 1316 | }); |
It works
Test done: "base.group_ no_one"
- Install crm.
- Add a user1 with group Sales / User: Own Leads Only
- Add a user2 with groups Sales / User: Own Leads Only and Technical Features
- Edit the view search to set Customers filter under Technical Features group.
groups=
Expected:
user1 doesn't see the filter in partner search view OK
user2 see the filter in partner search view OK