← Back to team overview

openerp-dev-web team mailing list archive

[Merge] lp:~openerp-dev/openobject-client-web/proto61-searchview-niv into lp:~openerp-dev/openobject-client-web/trunk-proto61

 

Nicolas Vanhoren (OpenERP) has proposed merging lp:~openerp-dev/openobject-client-web/proto61-searchview-niv into lp:~openerp-dev/openobject-client-web/trunk-proto61 with lp:~openerp-dev/openobject-client-web/proto61-searchview-xmo as a prerequisite.

Requested reviews:
  Antony Lesuisse (al-openerp)

For more details, see:
https://code.launchpad.net/~openerp-dev/openobject-client-web/proto61-searchview-niv/+merge/55352

Added custom filters (still needs improvements but the current state is consistent)
-- 
https://code.launchpad.net/~openerp-dev/openobject-client-web/proto61-searchview-niv/+merge/55352
Your team OpenERP R&D Team is subscribed to branch lp:~openerp-dev/openobject-client-web/proto61-searchview-xmo.
=== modified file 'addons/base/static/openerp/base.html'
--- addons/base/static/openerp/base.html	2011-03-28 09:03:39 +0000
+++ addons/base/static/openerp/base.html	2011-03-29 14:42:53 +0000
@@ -13,7 +13,11 @@
     <script type="text/javascript" src="/base/static/LABjs/LAB.js"></script>
     <script>
         $LAB
+<<<<<<< TREE
         .setOptions({UsePreloading:false}) 
+=======
+        .setOptions({UsePreloading:false})
+>>>>>>> MERGE-SOURCE
         .script('/base/static/openerp/js/base.js')
         .script('/base/static/qweb/qweb.js')
         .script('/base/static/underscore/underscore.js')

=== modified file 'addons/base/static/openerp/base.xml'
--- addons/base/static/openerp/base.xml	2011-03-29 12:50:42 +0000
+++ addons/base/static/openerp/base.xml	2011-03-29 14:42:53 +0000
@@ -341,15 +341,67 @@
         <t t-if="filters.length" t-raw="filters.render(defaults)"/>
     </div>
 </t>
-<t t-name="SearchView.group">
-    <div t-att-class="'searchview_group ' + (attrs.expand == '0' ? 'folded' : 'expanded')"
+<t t-name="SearchView.util.expand">
+	<div t-att-class="'searchview_group ' + (expand == '0' ? 'folded' : 'expanded')"
          t-att-id="element_id">
-        <a t-if="attrs.string" class="searchview_group_string" href="#">
-            <t t-esc="attrs.string"/>
+        <a t-if="label" class="searchview_group_string" href="#">
+            <t t-esc="label"/>
         </a>
         <div class="searchview_group_content">
-            <t t-call="SearchView.render_lines"/>
+            <t t-raw="content"/>
         </div>
     </div>
 </t>
+<t t-name="SearchView.group">
+	<t t-call="SearchView.util.expand">
+		<t t-set="expand" t-value="attrs.expand"/>
+		<t t-set="label" t-value="attrs.string"/>
+		<t t-set="content">
+			<t t-call="SearchView.render_lines"/>
+		</t>
+	</t>
+</t>
+<t t-name="SearchView.extended_search">
+	<t t-call="SearchView.util.expand">
+		<t t-set="expand" t-value="false"/>
+		<t t-set="label" t-value="'Extended Filters'"/>
+		<t t-set="content">
+			<div class="searchview_extended_groups_list"></div>
+			<button class="searchview_extended_add_group">Add group of conditions</button>
+		</t>
+	</t>
+</t>
+<t t-name="SearchView.extended_search.group">
+	<div t-att-id="element_id">
+		<select class="searchview_extended_group_choice">
+			<option value="all">All</option>
+			<option value="any">Any</option>
+			<option value="none">None</option>
+		</select>
+		<div class="searchview_extended_propositions_list">
+		</div>
+		<button class="searchview_extended_add_proposition">Add condition</button>
+		<button class="searchview_extended_delete_group">Delete this group</button>
+	</div>
+</t>
+<t t-name="SearchView.extended_search.proposition">
+	<div t-att-id="element_id">
+		<select class="searchview_extended_prop_field">
+			<t t-foreach="attrs.fields" t-as="field">
+				<option t-att="{'selected': field === attrs.selected ? 'selected' : null}"
+						t-att-value="field.name">
+					<t t-esc="field.obj.string"/>
+				</option>
+			</t>
+		</select>
+		<select class="searchview_extended_prop_op">
+		</select>
+		<span class="searchview_extended_prop_value">
+		</span>
+		<button class="searchview_extended_delete_prop">Delete this condition</button>
+	</div>
+</t>
+<t t-name="SearchView.extended_search.proposition.char">
+	<input t-att-id="element_id"></input>
+</t>
 </templates>

=== modified file 'addons/base/static/openerp/js/base_views.js'
--- addons/base/static/openerp/js/base_views.js	2011-03-29 12:50:42 +0000
+++ addons/base/static/openerp/js/base_views.js	2011-03-29 14:42:53 +0000
@@ -393,6 +393,94 @@
     }
 });
 
+/**
+ * Base class for widgets. Handle rendering (based on a QWeb template), identifier
+ * generation, parenting and destruction of the widget.
+ */
+openerp.base.BaseWidget = openerp.base.Controller.extend({
+	/**
+	 * The name of the QWeb template that will be used for rendering. Must be redifined
+	 * in subclasses or the render() method can not be used.
+	 * 
+	 * @type string
+	 */
+    template: null,
+    /**
+     * The prefix used to generate an id automatically. Should be redifined in subclasses.
+     * If it is not defined, the make_id() method must be explicitly called.
+     * 
+     * @type string
+     */
+    identifier_prefix: null,
+    /**
+     * Contructor.
+     * 
+     * @params {openerp.base.search.BaseWidget} parent The parent widget.
+     */
+    init: function (parent) {
+		this.children = [];
+        this.parent = parent;
+        if(parent != null) {
+        	parent.children.push(this);
+        }
+        if(this.identifier_prefix != null) {
+        	this.make_id(this.identifier_prefix);
+        }
+    },
+    /**
+     * Sets and returns a globally unique identifier for the widget.
+     *
+     * If a prefix is appended, the identifier will be appended to it.
+     *
+     * @params sections prefix sections, empty/falsy sections will be removed
+     */
+    make_id: function () {
+        this.element_id = _.uniqueId(_.toArray(arguments).join('_'));
+        return this.element_id;
+    },
+    /**
+     * "Starts" the widgets. Called at the end of the rendering, this allows
+     * to get a jQuery object referring to the DOM ($element attribute).
+     */
+    start: function () {
+        this._super();
+        if (this.element_id) {
+            this.$element = $(document.getElementById(
+                this.element_id));
+        }
+    },
+    /**
+     * "Stops" the widgets. Called when the view destroys itself, this
+     * lets the widgets clean up after themselves.
+     */
+    stop: function () {
+    	var tmp_children = this.children;
+    	this.children = [];
+    	_.each(tmp_children, function(x) {
+    		x.stop();
+    	});
+    	if(this.$element != null) {
+    		this.$element.remove();
+    	}
+    	if(this.parent != null) {
+    		var _this = this;
+    		this.parent.children = _.reject(this.parent.children, function(x) { return x === _this;});
+            this.parent = null;
+    	}
+        this._super();
+    },
+    /**
+     * Render the widget. This.template must be defined.
+     * The content of the current object is passed as context to the template.
+     * 
+     * @param {object} additional Additional context arguments to pass to the template.
+     */
+    render: function (additional) {
+        return QWeb.render(this.template, _.extend({}, this,
+        		additional != null ? additional : {}));
+    }
+});
+
 openerp.base.SearchView = openerp.base.Controller.extend({
     init: function(session, element_id, dataset, view_id, defaults) {
         this._super(session, element_id);
@@ -514,6 +602,11 @@
             data.fields_view['arch'].children,
             data.fields_view.fields);
 
+        // for extended search view
+        var ext = new openerp.base.search.ExtendedSearch(null, data.fields_view.fields);
+        lines.push([ext]);
+        this.inputs.push(ext);
+        
         var render = QWeb.render("SearchView", {
             'view': data.fields_view['arch'],
             'lines': lines,
@@ -714,6 +807,13 @@
         });
     }
 });
+openerp.base.search.add_expand_listener = function($root) {
+	$root.find('a.searchview_group_string').click(function (e) {
+        $root.toggleClass('folded expanded');
+        e.stopPropagation();
+        e.preventDefault();
+    });
+};
 openerp.base.search.Group = openerp.base.search.Widget.extend({
     template: 'SearchView.group',
     // TODO: contain stuff
@@ -731,14 +831,185 @@
             .chain()
             .flatten()
             .each(function (widget) { widget.start(); });
-        var $root = this.$element;
-        $root.find('a.searchview_group_string').click(function (e) {
-            $root.toggleClass('folded expanded');
-            e.stopPropagation();
-            e.preventDefault();
-        });
+        openerp.base.search.add_expand_listener(this.$element);
     }
 });
+
+openerp.base.search.ExtendedSearch = openerp.base.BaseWidget.extend({
+    template: 'SearchView.extended_search',
+    identifier_prefix: 'extended-search',
+    init: function (parent, fields) {
+	    this._super(parent);
+        this.fields = fields;
+	},
+	add_group: function(group) {
+    	var group = new openerp.base.search.ExtendedSearchGroup(this, this.fields);
+        var render = group.render({});
+        this.$element.find('.searchview_extended_groups_list').append(render);
+        group.start();
+	},
+    start: function () {
+        this._super();
+        var _this = this;
+        openerp.base.search.add_expand_listener(this.$element);
+        this.add_group();
+        this.$element.find('.searchview_extended_add_group').click(function (e) {
+            _this.add_group();
+            e.stopPropagation();
+            e.preventDefault();
+        });
+	},
+	get_context: function() {
+		return null;
+	},
+	get_domain: function() {
+		if(this.$element.hasClass("folded")) {
+			return null;
+		}
+		var domain = _.reduce(this.children,
+				function(mem, x) { return mem.concat(x.get_domain());}, []);
+		return domain;
+	}
+});
+
+openerp.base.search.ExtendedSearchGroup = openerp.base.BaseWidget.extend({
+    template: 'SearchView.extended_search.group',
+    identifier_prefix: 'extended-search-group',
+    init: function (parent, fields) {
+	    this._super(parent);
+	    this.fields = fields;
+	},
+	add_prop: function() {
+		var prop = new openerp.base.search.ExtendedSearchProposition(this, this.fields);
+        var render = prop.render({});
+        this.$element.find('.searchview_extended_propositions_list').append(render);
+        prop.start();
+	},
+    start: function () {
+        this._super();
+        var _this = this;
+        this.add_prop();
+        this.$element.find('.searchview_extended_add_proposition').click(function (e) {
+        	_this.add_prop();
+            e.stopPropagation();
+            e.preventDefault();
+        });
+        var delete_btn = this.$element.find('.searchview_extended_delete_group');
+        delete_btn.click(function (e) {
+        	_this.stop();
+            e.stopPropagation();
+            e.preventDefault();
+        });
+	},
+	get_domain: function() {
+		var props = _(this.children).chain().map(function(x) {
+			return x.get_proposition();
+		}).compact().value();
+		var choice = this.$element.find(".searchview_extended_group_choice").val();
+		var op = choice == "all" ? "&" : "|";
+		var domain = [].concat(choice == "none" ? ['!'] : [],
+				_.map(_.range(_.max([0,props.length - 1])), function(x) { return op; }),
+				props);
+		return domain;
+	}
+});
+
+openerp.base.search.extended_filters_types = {
+	char: {
+		operators: [
+		            {value: "ilike", text: "contains"},
+		            {value: "not like", text: "doesn't contain"},
+		            {value: "=", text: "is equal to"},
+		            {value: "!=", text: "is not equal to"},
+		            {value: ">", text: "greater than"},
+		            {value: "<", text: "less than"},
+		            {value: ">=", text: "greater or equal than"},
+		            {value: "<=", text: "less or equal than"},
+		],
+		build_component: function(parent) {
+			return new openerp.base.search.ExtendedSearchProposition.Char(parent);
+		}
+	}
+};
+
+openerp.base.search.ExtendedSearchProposition = openerp.base.BaseWidget.extend({
+    template: 'SearchView.extended_search.proposition',
+    identifier_prefix: 'extended-search-proposition',
+    init: function (parent, fields) {
+	    this._super(parent);
+	    this.fields = _(fields).chain()
+	    	.map(function(val,key) {return {name:key, obj:val};})
+	    	.sortBy(function(x) {return x.obj.string;}).value();
+	    this.attrs = {_: _, fields: this.fields, selected: null};
+	    this.value_component = null;
+	},
+    start: function () {
+        this._super();
+	    this.set_selected(this.fields.length > 0 ? this.fields[0] : null);
+	    var _this = this;
+	    this.$element.find(".searchview_extended_prop_field").change(function(e) {
+	    	_this.changed();
+            e.stopPropagation();
+            e.preventDefault();
+	    });
+	    var delete_btn = this.$element.find('.searchview_extended_delete_prop');
+	    delete_btn.click(function (e) {
+        	_this.stop();
+            e.stopPropagation();
+            e.preventDefault();
+        });
+	},
+	changed: function() {
+		var nval = this.$element.find(".searchview_extended_prop_field").val();
+		if(this.attrs.selected == null || nval != this.attrs.selected.name) {
+			this.set_selected(_.detect(this.fields, function(x) {return x.name == nval;}));
+		}
+	},
+	set_selected: function(selected) {
+		var _this = this;
+		if(this.attrs.selected != null) {
+			this.value_component.stop();
+			this.value_component = null;
+			this.$element.find('.searchview_extended_prop_op').html('');
+		}
+		this.attrs.selected = selected;
+		if(selected == null) {
+			return;
+		}
+		var type = selected.obj.type;
+		var extended_filters_types = openerp.base.search.extended_filters_types;
+		type = type in extended_filters_types ? type : "char";
+		_.each(extended_filters_types[type].operators, function(operator) {
+			option = jQuery('<option/>');
+			option.attr('value', operator.value);
+			option.text(operator.text);
+			option.appendTo(_this.$element.find('.searchview_extended_prop_op'));
+		});
+		this.value_component = extended_filters_types[type].build_component(this);
+		var render = this.value_component.render({});
+		this.$element.find('.searchview_extended_prop_value').html(render);
+		this.value_component.start();
+	},
+	get_proposition: function() {
+		if ( this.attrs.selected == null)
+			return null;
+		var field = this.attrs.selected.name;
+		var op =  this.$element.find('.searchview_extended_prop_op').val();
+		var value = this.value_component.get_value();
+		return [field, op, value];
+	}
+});
+
+openerp.base.search.ExtendedSearchProposition.Char = openerp.base.BaseWidget.extend({
+    template: 'SearchView.extended_search.proposition.char',
+    identifier_prefix: 'extended-search-proposition-char',
+    
+    get_value: function() {
+		var val = this.$element.val();
+		return val;
+	}
+});
+
 openerp.base.search.Input = openerp.base.search.Widget.extend({
     init: function (view) {
         this._super(view);
@@ -1073,6 +1344,8 @@
 openerp.base.TreeView = openerp.base.Controller.extend({
 });
 
+
+
 openerp.base.Widget = openerp.base.Controller.extend({
     // TODO Change this to init: function(view, node) { and use view.session and a new element_id for the super
     // it means that widgets are special controllers

=== modified file 'addons/base/static/qweb/qweb.js'
--- addons/base/static/qweb/qweb.js	2011-03-24 12:42:21 +0000
+++ addons/base/static/qweb/qweb.js	2011-03-29 14:42:53 +0000
@@ -182,7 +182,9 @@
                     g_att[o[0]] = new String(o[1]);
                 } else if (o.constructor == Object) {
                     for (var i in o) {
-                        g_att[i] = new String(o[i]);
+                    	if(o[i]!=null) {
+                    		g_att[i] = new String(o[i]);
+                    	}
                     }
                 }
             }


Follow ups