← Back to team overview

openerp-dev-web team mailing list archive

[Merge] lp:~openerp-dev/openobject-client-web/m2m-dialogs into lp:openobject-client-web

 

Xavier (Open ERP) has proposed merging lp:~openerp-dev/openobject-client-web/m2m-dialogs into lp:openobject-client-web.

Requested reviews:
  Aline (OpenERP) (apr-tinyerp): functional


Fixed the management of m2m fields to bring their opening of new content areas in line with the wizards and m2os: as dialogs instead of popups.

Should be able to handle recursive openings correctly, as well as creation of new items from within the dialog.
-- 
https://code.launchpad.net/~openerp-dev/openobject-client-web/m2m-dialogs/+merge/43352
Your team OpenERP R&D Team is subscribed to branch lp:~openerp-dev/openobject-client-web/m2m-dialogs.
=== added file 'addons/openerp/controllers/templates/openm2.mako'
--- addons/openerp/controllers/templates/openm2.mako	1970-01-01 00:00:00 +0000
+++ addons/openerp/controllers/templates/openm2.mako	2010-12-10 13:41:23 +0000
@@ -0,0 +1,65 @@
+<%inherit file="/openerp/controllers/templates/base_dispatch.mako"/>
+
+<%def name="header()">
+    <title>${form.screen.string} </title>
+
+    <script type="text/javascript">
+        var form_controller = '/openerp/open${self.relation()}';
+    </script>
+
+    <script type="text/javascript">
+
+        function do_select(id, src) {
+            viewRecord(id, src);
+        }
+
+        jQuery(document).ready(function() {
+
+            var id = parseInt(openobject.dom.get('_terp_id').value, 10) || null;
+            var lc = parseInt(openobject.dom.get('_terp_load_counter').value, 10) || ${self.default_load_counter()};
+
+            if(lc <= 1) {
+                return;
+            }
+            jQuery.${self.relation()}('close', ${self.token_to_close('id')});
+        });
+    </script>
+</%def>
+
+<%def name="content()">
+    <table class="view" cellspacing="5" border="0" width="100%" style="border: none;">
+        <tr>
+            <td>
+                <input type="hidden" id="_terp_load_counter" value="${params.load_counter or 0}"/>
+                <table width="100%" class="titlebar" style="border: none;">
+                    <tr>
+                        <td width="100%"><h1>${form.screen.string}</h1></td>
+                    </tr>
+                </table>
+            </td>
+        </tr>
+        <tr>
+            <td>
+                <div class="footer_tool_box">
+                    <table border="0" cellpadding="0" cellspacing="0" width="100%" style="border: none;">
+                        <tr>
+                            % if form.screen.editable:
+                                <td class="save_close">
+                                    <a class="button-a" onclick="submit_form('save')" href="javascript: void(0)">${_("Save")}</a>
+                                </td>
+                            % endif
+                            <td class="save_close">
+                            	<a class="button-a" onclick="jQuery.${self.relation()}('close');" href="javascript: void(0)">${_("Close")}</a>
+                            </td>
+                            <td width="100%">
+                            </td>
+                        </tr>
+                    </table>
+                </div>
+            </td>
+        </tr>
+        <tr>
+            <td>${form.display()}</td>
+        </tr>
+    </table>
+</%def>

=== modified file 'addons/openerp/controllers/templates/openm2m.mako'
--- addons/openerp/controllers/templates/openm2m.mako	2010-09-15 12:55:07 +0000
+++ addons/openerp/controllers/templates/openm2m.mako	2010-12-10 13:41:23 +0000
@@ -1,77 +1,4 @@
-<%inherit file="/openerp/controllers/templates/base_dispatch.mako"/>
-
-<%def name="header()">
-    <title>${form.screen.string} </title>
-
-    <script type="text/javascript">
-        var form_controller = '/openerp/openm2m';
-    </script>
-
-    <script type="text/javascript">
-
-        function do_select(id, src) {
-            viewRecord(id, src);
-        }
-
-        jQuery(document).ready(function() {
-
-            var id = parseInt(openobject.dom.get('_terp_id').value) || null;
-            var lc = parseInt(openobject.dom.get('_terp_load_counter').value) || 0;
-
-            if (lc > 0 && id) {
-
-                with(window.opener) {
-
-                    var m2m = Many2Many('${params.m2m}');
-                    var ids = m2m.getValue();
-                    
-                    if (MochiKit.Base.findIdentical(ids, id) == -1)
-                        ids.push(id);
-
-                    m2m.setValue(ids);
-                }
-            }
-
-            if (lc > 1) {
-                window.close();
-            }
-        });
-
-    </script>
-</%def>
-
-<%def name="content()">
-    <table class="view" cellspacing="5" border="0" width="100%" style="border: none;">
-        <tr>
-            <td>
-                <input type="hidden" id="_terp_load_counter" value="${params.load_counter or 0}"/>
-                <table width="100%" class="titlebar" style="border: none;">
-                    <tr>
-                        <td width="100%"><h1>${form.screen.string}</h1></td>
-                    </tr>
-                </table>
-            </td>
-        </tr>
-        <tr>
-            <td>
-                <div class="footer_tool_box">
-                    <table border="0" cellpadding="0" cellspacing="0" width="100%" style="border: none;">
-                        <tr>
-                            <td class="save_close">
-                            	<a class="button-a" href="javascript: void(0)" onclick="submit_form('save')">${_("Save")}</a>
-                            </td>
-                            <td class="save_close">
-                            	<a class="button-a" href="javascript: void(0)" onclick="window.close()">${_("Close")}</a>
-                            </td>
-                            <td width="100%">
-                            </td>
-                        </tr>
-                    </table>
-                </div>
-            </td>
-        </tr>
-        <tr>
-            <td>${form.display()}</td>
-        </tr>
-    </table>
-</%def>
+<%inherit file="/openerp/controllers/templates/openm2.mako"/>
+<%def name="relation()">m2m</%def>
+<%def name="default_load_counter()">0</%def>
+<%def name="token_to_close(token)">${token} && [${token}]</%def>

=== modified file 'addons/openerp/controllers/templates/openm2o.mako'
--- addons/openerp/controllers/templates/openm2o.mako	2010-12-02 07:53:05 +0000
+++ addons/openerp/controllers/templates/openm2o.mako	2010-12-10 13:41:23 +0000
@@ -1,65 +1,4 @@
-<%inherit file="/openerp/controllers/templates/base_dispatch.mako"/>
-
-<%def name="header()">
-    <title>${form.screen.string} </title>
-
-    <script type="text/javascript">
-        var form_controller = '/openerp/openm2o';
-    </script>
-
-    <script type="text/javascript">
-        
-        function do_select(id, src) {
-            viewRecord(id, src);
-        }
-        
-        jQuery(document).ready(function() {
-        
-            var id = parseInt(openobject.dom.get('_terp_id').value, 10) || null;
-            var lc = parseInt(openobject.dom.get('_terp_load_counter', 10).value) || 1;
-
-            if(lc <= 1) {
-                return;
-            }
-            $.m2o('close', id);
-        });
-    </script>
-</%def>
-
-<%def name="content()">
-    <table class="view" cellspacing="5" border="0" width="100%" style="border: none;">
-        <tr>
-            <td style="padding: 0pt 2px 2px 5px;">
-                <input type="hidden" id="_terp_load_counter" value="${params.load_counter or 0}"/>
-                <table width="100%" class="titlebar" style="border: none;">
-                    <tr>
-                        <td width="100%"><h1>${form.screen.string}</h1></td>
-                    </tr>
-                </table>
-            </td>
-        </tr>
-        <tr>
-            <td>
-                <div class="footer_tool_box">
-                    <table border="0" cellpadding="0" cellspacing="0" width="100%" style="border: none;">
-                        <tr>
-                        	% if form.screen.editable:
-	                            <td class="save_close">
-	                            	<a class="button-a" onclick="submit_form('save')" href="javascript: void(0)">${_("Save")}</a>
-	                            </td>
-                            % endif
-                            <td class="save_close">
-                            	<a class="button-a" onclick="$.m2o('close');" href="javascript: void(0)">${_("Close")}</a>
-                            </td>
-                            <td width="100%">
-                            </td>
-                        </tr>
-                    </table>
-                </div>
-            </td>
-        </tr>
-        <tr>
-            <td style="padding: 5px;">${form.display()}</td>
-        </tr>
-    </table>
-</%def>
+<%inherit file="/openerp/controllers/templates/openm2.mako"/>
+<%def name="relation()">m2o</%def>
+<%def name="default_load_counter()">1</%def>
+<%def name="token_to_close(token)">${token}</%def>

=== modified file 'addons/openerp/controllers/templates/search.mako'
--- addons/openerp/controllers/templates/search.mako	2010-12-01 16:43:33 +0000
+++ addons/openerp/controllers/templates/search.mako	2010-12-10 13:41:23 +0000
@@ -24,7 +24,7 @@
     % if params.selectable == KINDS['M2O']:
     <script type="text/javascript">
         function close_dialog() {
-            $.m2o('close');
+            jQuery.m2o('close');
         }
         function do_select(selected_id){
             if (!selected_id) {
@@ -35,7 +35,7 @@
 
                 selected_id = ids[0];
             }
-            $.m2o('close', selected_id);
+            jQuery.m2o('close', selected_id);
         }
     </script>
     % elif params.selectable == KINDS['M2M']:
@@ -67,35 +67,12 @@
             </script>
         % else:
 		    <script type="text/javascript">
-
 		        function do_select(id) {
-
-		            var source = "${params.source}";
-		            var list_this = new ListView('_terp_list');
-
-		            with(window.opener) {
-
-		                var m2m = Many2Many('${params.source}');
-		                var ids = m2m.getValue();
-
-		                if (id){
-		                    if (findValue(ids, id) == -1) ids.push(id);
-		                } else {
-		                    var boxes = list_this.getSelectedItems();
-
-		                    if(boxes.length == 0) {
-		                        error_display(_("No record selected..."));
-		                        return;
-		                    }
-
-		                    forEach(boxes, function(b){
-		                        if (findValue(ids, b.value) == -1) ids.push(b.value);
-		                    });
-		                }
-
-		                m2m.setValue(ids);
-		            }
-		            close_dialog();
+                    jQuery.m2m('close',
+                        id ? [id]
+                           : ListView('_terp_list').$getSelectedItems().map(function () {
+                                return parseInt(this.value, 10); }).get()
+                    );
 		        }
 		    </script>
         % endif
@@ -171,26 +148,19 @@
             </tr>
         </table>
         <script type="text/javascript">
-            if(jQuery('#${form_name} tr.pagerbar:first td.pager-cell-button')) {
-                jQuery('#${form_name} tr.pagerbar:first td.pager-cell-button:first a').click(function() {
-                    openLink(openobject.http.getURL('/openerp/openm2m/new', {
-                        _terp_model: '${params.model}',
-                        _terp_source: '${params.source}',
-                        _terp_m2m: '${params.source}',
-                        _terp_domain: openobject.dom.get('_terp_domain').value,
-                        _terp_context: openobject.dom.get('_terp_context').value}));
-                });
-            }
             jQuery('table.search_table input:text').eq(0).focus();
+            /*
             % if params.selectable == KINDS['M2M']:
+            */
                 var $select_link = jQuery('a.select-link').hide();
-                jQuery('form#search_form').click(function(event) {
-                    if ($(event.target).is("input[type=checkbox]")) {
-                        $select_link.show();
-                        $(this).unbind('click');
-                    }
+                jQuery('#search_form').click(function(event) {
+                    if (!jQuery(event.target).is(".grid-record-selector")) { return; }
+                    $select_link.toggle(
+                        !!jQuery('#search_form .grid-record-selector:checked').length)
                 });
+            /*
             % endif
+            */
         </script>
     </form>
 </div>

=== modified file 'addons/openerp/static/javascript/form.js'
--- addons/openerp/static/javascript/form.js	2010-12-09 12:47:08 +0000
+++ addons/openerp/static/javascript/form.js	2010-12-10 13:41:23 +0000
@@ -737,27 +737,21 @@
         'domain': domain,
         'context': context
     }).addCallback(function(obj){
-        var dialog_url = openobject.http.getURL('/openerp/search/new', {
+        var options = {
             'model': relation,
             'domain': obj.domain,
             'context': obj.context,
             'source': source,
             'kind': kind,
             'text': text
-        });
+        };
         switch(kind) {
             case KIND_M2O:
-                jQuery.m2o({
-                    'model': relation,
-                    'domain': obj.domain,
-                    'context': obj.context,
-                    'source': source,
-                    'kind': kind,
-                    'text': text
-                });
-                break;
-            default:
-                openobject.tools.openWindow(dialog_url);
+                jQuery.m2o(options);
+                break;
+            case KIND_M2M:
+                jQuery.m2m(options);
+                break;
         }
     });
 }

=== modified file 'addons/openerp/static/javascript/listgrid.js'
--- addons/openerp/static/javascript/listgrid.js	2010-12-08 15:38:44 +0000
+++ addons/openerp/static/javascript/listgrid.js	2010-12-10 13:41:23 +0000
@@ -124,7 +124,7 @@
     },
 
     getSelectedRecords: function() {
-        return jQuery(this.getSelectedItems()).map(function() {
+        return this.$getSelectedItems().map(function() {
             if(this.value) {
                 return this.value
             } else {
@@ -134,10 +134,16 @@
         }).get();
     },
 
+    $getSelectedItems: function () {
+       return jQuery(idSelector(this.name))
+                .find('input.grid-record-selector')
+                .filter(function() {
+            return this.id && this.checked;
+        })
+    },
+
     getSelectedItems: function() {
-        return filter(function(box) {
-            return box.id && box.checked;
-        }, openobject.dom.select('input.grid-record-selector', this.name));
+        return this.$getSelectedItems().get();
     },
 
     onBooleanClicked: function() {
@@ -463,11 +469,14 @@
             _terp_button_type : btype
         };
 
-        var req = eval_domain_context_request({source: this.name, context : context || '{}',active_id: id, active_ids: openobject.dom.get(prefix + '_terp_ids').value});
-        req.addCallback(function(res) {
+        eval_domain_context_request({
+            source: this.name,
+            context : context || '{}',
+            active_id: id,
+            active_ids: openobject.dom.get(prefix + '_terp_ids').value
+        }).addCallback(function(res) {
             params['_terp_context'] = res.context;
-            var req = openobject.http.postJSON('/openerp/listgrid/button_action', params);
-            req.addCallback(function(obj) {
+            openobject.http.postJSON('/openerp/listgrid/button_action', params).addCallback(function(obj) {
                 if (obj.error) {
                     return error_display(obj.error);
                 }

=== modified file 'addons/openerp/static/javascript/m2m.js'
--- addons/openerp/static/javascript/m2m.js	2010-10-14 12:19:33 +0000
+++ addons/openerp/static/javascript/m2m.js	2010-12-10 13:41:23 +0000
@@ -51,32 +51,30 @@
         this.id = openobject.dom.get(name + '_id');
         this.text = openobject.dom.get(name + '_set');
 
-        this.btnAdd = openobject.dom.get(name + '_button1');
-//        this.btnDel = openobject.dom.get('_' + name + '_button2');
-
         this.terp_ids = openobject.dom.get(name + '/_terp_ids');
-        this.model = getNodeAttribute(this.id, 'relation');
-
-        this.hasList = openobject.dom.get(name + '_container') ? true : false;
-
-        MochiKit.Signal.connect(this.id, 'onchange', this, this.onChange);
-        MochiKit.Signal.connect(this.text, 'onchange', this, this.onChange);
+
+        this.hasList = !!openobject.dom.get(name + '_container');
+
+        jQuery([this.id, this.text]).change(jQuery.proxy(this, 'onChange'));
+
+        jQuery(idSelector('_m2m_' + this.name)).delegate(
+            idSelector(this.name + '_add_records'), 'click', jQuery.proxy(this, 'addRecords')
+        ).delegate(
+            idSelector(this.name + '_delete_record'), 'click', jQuery.proxy(this, 'remove')
+        );
 
         if (!this.hasList) {
-            MochiKit.Signal.connect(this.text, 'onkeydown', bind(function(evt) {
-                var key = evt.event().keyCode;
-
-                if (key == 8 || key == 46) {
-                    evt.stop();
-                    this.id.value = '';
-                    this.onChange();
-                }
-
-                if (key == 113) {
-                    evt.stop();
-                    this.onClick();
-                }
-
+            jQuery(this.text).keydown(jQuery.proxy(function(evt) {
+                switch (evt.which) {
+                    case 8:
+                    case 46:
+                        this.id.value = '';
+                        this.onChange();
+                        return false;
+                    case 113:
+                        this.addRecords();
+                        return false;
+                }
             }, this));
         }
 
@@ -84,10 +82,6 @@
         openobject.dom.get(name)._m2m = this;
     },
 
-    onClick: function() {
-        this.btnAdd.onclick();
-    },
-
     onChange: function() {
         this.setValue(this.id.value);
     },
@@ -99,24 +93,19 @@
     },
 
     setValue: function(ids) {
-
         ids = /^\[.*\]/.test(ids) ? ids : '[' + ids + ']';
         ids = eval(ids);
 
+        var $id = jQuery(this.id).val('[' + ids.join(',') + ']');
         if (this.hasList) {
-
-            this.terp_ids.value = '[' + ids.join(',') + ']';
-            this.id.value = '[' + ids.join(',') + ']';
-
+            jQuery(this.terp_ids).val('[' + ids.join(',') + ']');
             ListView(this.name).reload();
-
         } else {
-            this.text.value = '(' + ids.length + ')';
-            this.id.value = '[' + ids.join(',') + ']';
-            openobject.dom.get(this.name).value = ids;
+            jQuery(this.text).val('(' + ids.length + ')');
+            jQuery(idSelector(this.name)).val(ids);
         }
 
-        if (getNodeAttribute(this.id, 'callback')) {
+        if ($id.attr('callback')) {
             onChange(this.id);
         }
     },
@@ -124,7 +113,7 @@
     getValue: function() {
         var ids = this.hasList ? this.terp_ids.value : this.id.value;
         try {
-            res = eval(ids);
+            var res = eval(ids);
             if (res.length)
                 return res;
         } catch(e) {
@@ -134,39 +123,146 @@
     },
 
     remove: function(remove_id) {
-
         var ids = eval(this.terp_ids.value) || [];
-        var boxes = ListView(this.name).getSelectedItems();
+        var $selectedItems = ListView(this.name).$getSelectedItems();
 
-        if (boxes.length <= 0 && !remove_id)
+        if (!(remove_id || $selectedItems.length)) {
             return;
-
-        boxes = MochiKit.Base.map(function(box) {
-            return parseInt(box.value);
-        }, boxes);
-
-        var removed_ids = remove_id ? [remove_id] : boxes;
-        ids = MochiKit.Base.filter(function(id) {
-            return MochiKit.Base.findIdentical(removed_ids, id) == -1;
-        }, ids);
-
-        this.id.value = this.terp_ids.value = '[' + ids.join(',') + ']';
+        }
+
+        var ids_to_remove = remove_id ? [remove_id]
+                                      : $selectedItems.map(function() {return parseInt(this.value, 10);}).get();
+
+        ids = jQuery.grep(ids, function(id) {
+            return jQuery.inArray(id, ids_to_remove) == -1;
+        });
+
+        jQuery([this.id, this.terp_ids]).val('[' + ids.join(',') + ']');
         this.onChange();
+        return false;
     },
 
     setReadonly: function(readonly) {
-
-        var field = jQuery('[id="'+this.name +'"]') || this.id
-        field.attr('readOnly', readonly)
-        this.text.readOnly = readonly;
-
-        if (readonly) {
-            jQuery(field).addClass('readonlyfield');
-            jQuery(this.text).addClass('readonlyfield');
-
-        } else {
-            jQuery(field).removeClass('readonlyfield')
-            jQuery(this.text).removeClass('readonlyfield');
-        }
+        var $field = jQuery(idSelector(this.name));
+        if(!$field.length) $field = jQuery(this.id);
+        $field.add(this.text)
+                .attr('readOnly', readonly)
+                .toggleClass('readonlyfield', readonly);
+    },
+
+    addRecords: function () {
+        var $this = jQuery(idSelector('_m2m_' + this.name));
+        open_search_window(
+                $this.attr('relation'),
+                $this.attr('domain'),
+                $this.attr('context'),
+                this.name, 2,
+                jQuery(idSelector(this.name + '_set')).val());
+        return false;
     }
 };
+
+(function ($) {
+    /**
+     * Opens an m2m dialog linked to the provided <code>$this</code> window,
+     * with the selected options.
+     *
+     * @param $this the parent window of the opened dialog, contains the
+     * input to fill with the selected m2m value if any
+     * @param options A map of options to provide to the xhr call.
+     * The <code>source</code> key is also used for the id of the element
+     * (in <code>$this</code>) on which any selected m2o value should be set.
+     * The <code>record</code> key indicates whether a record should be opened
+     * instead of a search view
+     */
+    function open($this, options) {
+        var url;
+        if(options.record) {
+            url = '/openerp/openm2m/create'
+        } else {
+            url = '/openerp/search/new';
+        }
+        return $('<iframe>', {
+            src: openobject.http.getURL(url, options),
+            frameborder: 0
+        }).data('source_window', $this[0])
+          .data('source_id', options.source || null)
+          .appendTo(document.documentElement)
+          .dialog({
+              modal: true,
+              width: 640,
+              height: 480,
+              close: function () {
+                  jQuery(this).dialog('destroy').remove();
+              }
+          });
+    }
+
+    /**
+     * Closes the m2m dialog it was called from (represented by
+     * <code>$this</code>, setting the related m2m input to the provided
+     * <code>value</code>, if any.
+     *
+     * @param $this the window of the dialog to close
+     * @param values optional, the values to add to the m2m
+     */
+    function close($this, values) {
+        var $frame = $($this.attr('frameElement'));
+
+        if(values && values.length) {
+            var original_window = $frame.data('source_window');
+            // the m2m input to set is in the source_window, which is set as
+            // a `data` of the dialog iframe
+            var Many2Many = original_window.Many2Many;
+            var source_id = $frame.data('source_id');
+
+            var m2m = Many2Many(source_id);
+            var ids = m2m.getValue();
+            jQuery.each(values, function (_, value) {
+                if(jQuery.inArray(value, ids) == -1) {
+                    ids.push(value);
+                }
+            });
+            m2m.setValue(ids);
+        }
+
+        $frame.dialog('close');
+        return null;
+    }
+
+    /**
+     * Manage m2m dialogs for this scope
+     * <ul>
+     *  <li><p>Called with only options, opens a new m2m dialog linking to the
+     *         current scope.</p></li>
+     *  <li><p>Called with the <code>"close"</code> command, closes the m2m
+     *         dialog it was invoked from and focuses its parent scope.
+     *  </p></li>
+     *  <li><p>Called with the <code>"close"</code> command and an argument,
+     *         sets that argument as the m2m value of the parent widget and
+     *         closes the m2m dialog it was invoked from as above.
+     *  </p></li>
+     * </ul>
+     *
+     * @returns the m2m container (iframe) if one was created
+     */
+    $.m2m = function () {
+        // $this should be the holder for the window from which $.m2m was
+        // originally called, even if $.m2m() was bubbled to the top of
+        // the window stack.
+        var $this;
+        if(this == $) $this = $(window);
+        else $this = $(this);
+        if(window != window.top) {
+            return window.top.jQuery.m2m.apply($this[0], arguments);
+        }
+        // We're at the top-level window, $this is the window from which the
+        // original $.m2m call was performed, window being the current window
+        // level.
+        if(arguments[0] === "close") {
+            return close($this, arguments[1]);
+        } else {
+            return open($this, arguments[0]);
+        }
+    };
+})(jQuery);

=== modified file 'addons/openerp/widgets/form/_m2m.py'
--- addons/openerp/widgets/form/_m2m.py	2010-11-18 14:11:28 +0000
+++ addons/openerp/widgets/form/_m2m.py	2010-12-10 13:41:23 +0000
@@ -26,17 +26,12 @@
 # You can see the MPL licence at: http://www.mozilla.org/MPL/MPL-1.1.html
 #
 ###############################################################################
-
-import time
-
 import cherrypy
 
 from openerp.utils import rpc
-from openerp.utils import cache
 from openerp.utils import TinyDict
 from openerp.utils import expr_eval
 
-from openerp.widgets.listgrid import List
 from openerp.widgets.screen import Screen
 
 from openerp.widgets import TinyInputWidget

=== modified file 'addons/openerp/widgets/form/templates/one2many.mako'
--- addons/openerp/widgets/form/templates/one2many.mako	2010-11-04 08:46:58 +0000
+++ addons/openerp/widgets/form/templates/one2many.mako	2010-12-10 13:41:23 +0000
@@ -14,7 +14,7 @@
                 <tr class="pagerbar">
 	               	<td class="pagerbar-cell" align="left" width="${pager_width}">
 	               		<div class="pagerbar-header">
-	               			<strong>${screen.string}</strong><nobr>
+	               			<strong>${screen.string}</strong>
 	               			<a class="button-a" href="javascript: void(0)" title="${_('Create new record...')}" onclick="new One2Many('${name}', ${(screen.view_type == 'tree' or 0) and len(screen.widget.editors)}).create(); return false;">${_('New')}</a>
 	               			%if id:
 	               				<a class="button-a" href="javascript: void(0);" title="${_('Delete record...')}" onclick="new One2Many('${name}', ${(screen.view_type == 'tree' or 0) and len(screen.widget.editors)}).remove(${id}); return false;">${_("Delete")}</a>
@@ -34,15 +34,17 @@
     </tr>
     % endif
     % if pager_info:
-             <td width="65%" style="text-align: left" align="left">
-                 <div class="pager">
-                     <p id="_${name}_link_span" class="paging">
-                         <a class="prev nav" title="${_('Previous record...')}" href="javascript: void(0)" onclick="submit_form('previous', '${name}');"></a>
-                         <span>${pager_info}</span>
-                         <a class="next nav" title="${_('Next record...')}" href="javascript: void(0)" onclick="submit_form('next', '${name}');"></a>
-                     </p>
-                 </div>
-             </td>
+    <tr>
+         <td width="65%" style="text-align: left" align="left">
+             <div class="pager">
+                 <p id="_${name}_link_span" class="paging">
+                     <a class="prev nav" title="${_('Previous record...')}" href="javascript: void(0)" onclick="submit_form('previous', '${name}');"></a>
+                     <span>${pager_info}</span>
+                     <a class="next nav" title="${_('Next record...')}" href="javascript: void(0)" onclick="submit_form('next', '${name}');"></a>
+                 </p>
+             </div>
+         </td>
+    </tr>
 	% endif
     <tr>
         % if screen:

=== modified file 'addons/openerp/widgets/listgrid.py'
--- addons/openerp/widgets/listgrid.py	2010-12-03 10:33:14 +0000
+++ addons/openerp/widgets/listgrid.py	2010-12-10 13:41:23 +0000
@@ -223,20 +223,14 @@
 
             for f, fa in self.headers:
                 if not isinstance(fa, int):
-                    k = fa.get('type', 'char')
-                    if not get_widget(k):
-                        k = 'char'
-
                     fa['prefix'] = '_terp_listfields' + ((self.name != '_terp_list' or '') and '/' + self.name)
                     fa['inline'] = True
-                    self.editors[f] = get_widget(k)(**fa)
+                    
+                    Widget = get_widget(fa.get('type', 'char')) or get_widget('char')
+                    self.editors[f] = Widget(**fa)
 
             # generate hidden fields
             for f, fa in self.hiddens:
-                k = fa.get('type', 'char')
-                if not get_widget(k):
-                    k = 'char'
-
                 fa['prefix'] = '_terp_listfields' + ((self.name != '_terp_list' or '') and '/' + self.name)
                 self.editors[f] = form.Hidden(**fa)
 

=== modified file 'addons/openerp/widgets/templates/listgrid/listgrid.mako'
--- addons/openerp/widgets/templates/listgrid/listgrid.mako	2010-12-03 13:18:53 +0000
+++ addons/openerp/widgets/templates/listgrid/listgrid.mako	2010-12-10 13:41:23 +0000
@@ -130,8 +130,7 @@
                                 % if editable:
                                     <td class="pager-cell-button">
                                         % if m2m:
-                                            <button title="${_('Add records...')}" id="${name}_button1"
-                                                onclick="open_search_window(jQuery('[id=_m2m_${name}]').attr('relation'), jQuery('[id=_m2m_${name}]').attr('domain'), jQuery('#_m2m_${name}').attr('context'),'${name}', 2, jQuery('[id=${name}_set]').val()); return false;">
+                                            <button title="${_('Add records...')}" id="${name}_add_records">
                                                     ${_('Add')}
                                             </button>
                                         % elif o2m:
@@ -169,9 +168,8 @@
                                     </td>
                                     <td class="pager-cell-button" style="display: none;">
                                         % if m2m:
-                                            <button id="${name}_delete_record" title="${_('Delete record(s).')}"
-                                                onclick="new Many2Many('${name}').remove(); return false;">
-                                                    ${_('Delete')}
+                                            <button id="${name}_delete_record" title="${_('Delete record(s).')}">
+                                                ${_('Delete')}
                                             </button>
                                         % else:
                                             <button id="${name}_delete_record" title="${_('Delete record(s).')}"

=== modified file 'openobject/static/javascript/openobject/openobject.dom.js'
--- openobject/static/javascript/openobject/openobject.dom.js	2010-11-21 19:45:54 +0000
+++ openobject/static/javascript/openobject/openobject.dom.js	2010-12-10 13:41:23 +0000
@@ -36,7 +36,7 @@
  * @param id the DOM id
  */
 function escapeId(id) {
-    return id.replace(/[^\r\n\f0-9a-f]/ig, "\\$&");
+    return id.replace(/[^\r\n\f0-9a-z_-]/ig, "\\$&");
 }
 /**
  * Transforms a node id in the corresponding CSS selector: escapes the id and prefixes with '#'.


Follow ups