From f0fd38412a9167f5917f6586dc514d0e3ad11b8d Mon Sep 17 00:00:00 2001
From: Romain Courteaud <romain@nexedi.com>
Date: Fri, 14 Oct 2016 15:33:14 +0000
Subject: [PATCH] [erp5_web_renderjs_ui] Reimplement multilistfield

Reuse listfield gadget for the rendering.

Dynamically add/remove listfield to improve usage.
---
 .../rjs_gadget_erp5_multilistfield_html.html  |  11 -
 .../rjs_gadget_erp5_multilistfield_html.xml   |   6 +-
 .../rjs_gadget_erp5_multilistfield_js.js      | 265 +++++++++++-------
 .../rjs_gadget_erp5_multilistfield_js.xml     |   8 +-
 4 files changed, 177 insertions(+), 113 deletions(-)

diff --git a/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_multilistfield_html.html b/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_multilistfield_html.html
index b63ac3314a..98e4d93523 100644
--- a/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_multilistfield_html.html
+++ b/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_multilistfield_html.html
@@ -8,21 +8,10 @@
     <!-- renderjs -->
     <script src="rsvp.js" type="text/javascript"></script>
     <script src="renderjs.js" type="text/javascript"></script>
-    <script src="handlebars.js" type="text/javascript"></script>
     <!-- custom script -->
     <script src="gadget_erp5_field_multilist.js" type="text/javascript"></script>
-    <script id="option-template" type="text/x-handlebars-template">
-      <option value="{{value}}" data-i18n="{{text}}">{{text}}</option>
-    </script>
 
-    <script id="selected-option-template" type="text/x-handlebars-template">
-      <option selected="selected" data-i18n="{{text}}" value="{{value}}">{{text}}</option>
-    </script>
   </head>
   <body>
-   <fieldset class="ui-controlgroup ui-corner-all ui-controlgroup-vertical">
-     <div class="ui-controlgroup-controls">
-     </div>
-  </fieldset>
   </body>
 </html>
\ No newline at end of file
diff --git a/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_multilistfield_html.xml b/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_multilistfield_html.xml
index 43501be269..64c5160945 100644
--- a/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_multilistfield_html.xml
+++ b/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_multilistfield_html.xml
@@ -234,7 +234,7 @@
             </item>
             <item>
                 <key> <string>serial</string> </key>
-                <value> <string>940.61935.4199.24456</string> </value>
+                <value> <string>954.38753.57848.4522</string> </value>
             </item>
             <item>
                 <key> <string>state</string> </key>
@@ -252,8 +252,8 @@
                     </tuple>
                     <state>
                       <tuple>
-                        <float>1424105340.28</float>
-                        <string>GMT</string>
+                        <float>1476452909.56</float>
+                        <string>UTC</string>
                       </tuple>
                     </state>
                   </object>
diff --git a/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_multilistfield_js.js b/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_multilistfield_js.js
index 90cfcab93d..28289ab2c4 100644
--- a/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_multilistfield_js.js
+++ b/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_multilistfield_js.js
@@ -1,110 +1,185 @@
-/*global window, rJS, Handlebars, document, RSVP, loopEventListener*/
-/*jslint nomen: true, indent: 2, maxerr: 3 */
-(function (window, rJS, Handlebars, document, RSVP) {
+/*global window, rJS, document, RSVP*/
+/*jslint nomen: true, indent: 2, maxerr: 3, maxlen: 80, unparam: true */
+(function (window, rJS, document, RSVP) {
   'use strict';
-  /////////////////////////////////////////////////////////////////
-  // Handlebars
-  /////////////////////////////////////////////////////////////////
-  // Precompile the templates while loading the first gadget instance
-  var gadget_klass = rJS(window),
-    option_source = gadget_klass.__template_element
-                      .getElementById("option-template")
-                      .innerHTML,
-    option_template = Handlebars.compile(option_source),
-    selected_option_source = gadget_klass.__template_element
-                               .getElementById("selected-option-template")
-                               .innerHTML,
-    selected_option_template = Handlebars.compile(selected_option_source);
-  gadget_klass
-    .ready(function (g) {
-      g.props = {};
+
+  function appendListField(gadget, value, item_list) {
+    var div = document.createElement('div');
+    gadget.element.appendChild(div);
+    return new RSVP.Queue()
+      .push(function () {
+        return gadget.declareGadget('gadget_erp5_field_list.html',
+                                    {element: div});
+      })
+      .push(function (result) {
+        var state = {
+            value: value,
+            items: item_list,
+            editable: gadget.state.editable,
+            // Single listfield is never mandatory.
+            // Check requirement globally instead
+            required: 0,
+            key: 'sub',
+            title: gadget.state.title
+          };
+        return result.render({field_json: state});
+      });
+  }
+
+  rJS(window)
+    .declareMethod('render', function (options) {
+      var field_json = options.field_json || {},
+        state_dict = {
+          value_list: JSON.stringify(field_json.value ||
+                                     field_json.default || []),
+          item_list: JSON.stringify(field_json.items),
+          editable: field_json.editable,
+          required: field_json.required,
+          name: field_json.key,
+          title: field_json.title,
+          sub_select_key: field_json.sub_select_key,
+          sub_input_key: field_json.sub_input_key
+        };
+      return this.changeState(state_dict);
     })
-    // Assign the element to a variable
-    .ready(function (g) {
-      return g.getElement()
-        .push(function (element) {
-          g.props.element = element;
-        });
+
+    .onStateChange(function () {
+      var i,
+        value_list = JSON.parse(this.state.value_list),
+        item_list = JSON.parse(this.state.item_list),
+        queue = new RSVP.Queue(),
+        element = this.element,
+        gadget = this;
+
+      // Always display an empty value at the end
+      value_list.push("");
+
+      // Clear first to DOM, append after to reduce flickering/manip
+      while (element.firstChild) {
+        element.removeChild(element.firstChild);
+      }
+
+      function enQueue() {
+        var argument_list = arguments;
+        queue
+          .push(function () {
+            return appendListField.apply(this, argument_list);
+          });
+      }
+
+      for (i = 0; i < value_list.length; i += 1) {
+        enQueue(gadget, value_list[i], item_list);
+      }
+      return queue;
     })
-    .declareAcquiredMethod("translateHtml", "translateHtml")
-    .declareMethod('render', function (options) {
-      var gadget = this,
-        selects = [],
-        tmp,
-        template,
-        container,
-        field_json = options.field_json,
-        i,
-        j;
-      gadget.props.field_json = field_json;
-      container = gadget.props.element.querySelector(".ui-controlgroup-controls");
-      field_json.default[field_json.default.length] = "";
-      for (i = 0; i < field_json.default.length; i += 1) {
-        tmp = "";
-        selects[i] = document.createElement("select");
-        container.appendChild(selects[i]);
-        for (j = 0; j < field_json.items.length; j += 1) {
-          if (field_json.items[j][1] === field_json.default[i]) {
-            template = selected_option_template;
-          } else {
-            template = option_template;
-          }
-          tmp += template({
-            value: field_json.items[j][1],
-            text: field_json.items[j][0]
+
+    .declareMethod('getContent', function () {
+      var i,
+        element = this.element,
+        queue = new RSVP.Queue(),
+        final_result = {},
+        result_list = [],
+        gadget = this;
+
+      function calculateSubContent(node) {
+        queue
+          .push(function () {
+            var scope = node.getAttribute('data-gadget-scope');
+            if (scope !== null) {
+              return gadget.getDeclaredGadget(
+                node.getAttribute('data-gadget-scope')
+              )
+                .push(function (result) {
+                  return result.getContent();
+                })
+                .push(function (result) {
+                  result_list.push(result.sub);
+                });
+            }
           });
+      }
+
+      if (this.state.editable) {
+        for (i = 0; i < element.childNodes.length; i += 1) {
+          calculateSubContent(element.childNodes[i]);
         }
-        selects[i].innerHTML = tmp;
+        return queue
+          .push(function () {
+            final_result[gadget.state.sub_select_key] = result_list;
+            final_result[gadget.state.sub_input_key] = 0;
+            return final_result;
+          });
       }
-      return new RSVP.Queue()
-        .push(function () {
-          var list = [];
-          for (i = 0; i < selects.length; i += 1) {
-            list.push(gadget.translateHtml(selects[i].outerHTML));
-          }
-          return RSVP.all(list);
+      return final_result;
+    })
+
+    /*
+    .declareMethod('getTextContent', function () {
+      // I don't know if a multilistfield was ever used in a listbox
+      // Skip for now
+      throw new Error('not implemented');
+    })
+    */
+
+    .allowPublicAcquisition('notifyValid', function () {
+      return;
+    })
+
+    .declareAcquiredMethod("notifyValid", "notifyValid")
+    .declareAcquiredMethod("notifyInvalid", "notifyInvalid")
+    .declareAcquiredMethod("notifyChange", "notifyChange")
+    .allowPublicAcquisition('notifyChange', function (argument_list, scope) {
+      // An empty listfield should be created when the last one is modified
+      // An empty listfield should be removed
+
+      var gadget = this,
+        sub_gadget;
+      return gadget.getDeclaredGadget(scope)
+        .push(function (result) {
+          sub_gadget = result;
+          return sub_gadget.getContent();
         })
-        .push(function (translated_htmls) {
-          var select_div,
-            wrapper_class_string,
-            div = document.createElement("div");
-          for (i = 0; i < translated_htmls.length; i += 1) {
-            div.innerHTML = translated_htmls[i];
-            select_div = div.querySelector("select");
-            selects[i].innerHTML = select_div.innerHTML;
-            if (field_json.editable !== 1) {
-              selects[i].setAttribute('readonly', 'readonly');
-              wrapper_class_string = wrapper_class_string || "";
-              wrapper_class_string += 'ui-state-readonly ';
-            }
-            // XXX add first + last class, needs to be improved
-            if (i === 0) {
-              wrapper_class_string = wrapper_class_string || "";
-              wrapper_class_string += 'ui-first-child';
+        .push(function (result) {
+          var value = result.sub;
+          if (sub_gadget.element === gadget.element.lastChild) {
+            if (value) {
+              return appendListField(gadget, "",
+                                     JSON.parse(gadget.state.item_list));
             }
-            if (i === translated_htmls.length - 1) {
-              wrapper_class_string = wrapper_class_string || "";
-              wrapper_class_string += 'ui-last-child';
-            }
-            if (wrapper_class_string) {
-              selects[i].setAttribute('data-wrapper-class', wrapper_class_string);
-              wrapper_class_string = undefined;
+          } else {
+            if (!value) {
+              gadget.element.removeChild(sub_gadget.element);
             }
           }
+        })
+        .push(function () {
+          return gadget.notifyChange();
         });
     })
-    .declareMethod('getContent', function () {
-      var gadget = this,
-        result = {},
-        tmp = [],
-        selects = this.props.element.querySelectorAll('select'),
-        i;
 
-      for (i = 0; i < selects.length; i += 1) {
-        tmp.push(selects[i].options[selects[i].selectedIndex].value);
+    .declareMethod('checkValidity', function () {
+      var gadget = this,
+        empty = true;
+      if (this.state.editable && this.state.required) {
+        return this.getContent()
+          .push(function (result) {
+            var value_list = result[gadget.state.sub_select_key],
+              i;
+            for (i = 0; i < value_list.length; i += 1) {
+              if (value_list[i]) {
+                empty = false;
+              }
+            }
+            if (empty) {
+              return gadget.notifyInvalid("Please fill out this field.");
+            }
+            return gadget.notifyValid();
+          })
+          .push(function () {
+            return !empty;
+          });
       }
-      result[gadget.props.field_json.sub_select_key] = tmp;
-      result[gadget.props.field_json.sub_input_key] = 0;
-      return result;
+      return true;
     });
-}(window, rJS, Handlebars, document, RSVP));
\ No newline at end of file
+
+}(window, rJS, document, RSVP));
\ No newline at end of file
diff --git a/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_multilistfield_js.xml b/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_multilistfield_js.xml
index 9e0b6bf651..a10946a4ae 100644
--- a/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_multilistfield_js.xml
+++ b/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_multilistfield_js.xml
@@ -216,7 +216,7 @@
             </item>
             <item>
                 <key> <string>actor</string> </key>
-                <value> <string>xiaowu</string> </value>
+                <value> <string>zope</string> </value>
             </item>
             <item>
                 <key> <string>comment</string> </key>
@@ -230,7 +230,7 @@
             </item>
             <item>
                 <key> <string>serial</string> </key>
-                <value> <string>944.12751.13272.54476</string> </value>
+                <value> <string>954.38943.62606.2730</string> </value>
             </item>
             <item>
                 <key> <string>state</string> </key>
@@ -248,8 +248,8 @@
                     </tuple>
                     <state>
                       <tuple>
-                        <float>1436170826.41</float>
-                        <string>GMT+2</string>
+                        <float>1476458904.36</float>
+                        <string>UTC</string>
                       </tuple>
                     </state>
                   </object>
-- 
2.30.9