From 1af6ca3cc08219276847ab65930fb8c815ac902b Mon Sep 17 00:00:00 2001
From: Boris Kocherov <bk@raskon.ru>
Date: Fri, 20 Apr 2018 11:31:17 +0300
Subject: [PATCH] add gadget_html5_select.* and dependence

---
 gadget_erp5_global.js    |  76 +++++++++++++++++++
 gadget_html5_select.html |  28 +++++++
 gadget_html5_select.js   | 158 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 262 insertions(+)
 create mode 100644 gadget_erp5_global.js
 create mode 100644 gadget_html5_select.html
 create mode 100644 gadget_html5_select.js

diff --git a/gadget_erp5_global.js b/gadget_erp5_global.js
new file mode 100644
index 0000000..5bf822a
--- /dev/null
+++ b/gadget_erp5_global.js
@@ -0,0 +1,76 @@
+/*global window, RSVP, Array, isNaN */
+/*jslint indent: 2, maxerr: 3, nomen: true, unparam: true */
+(function (window, RSVP, Array, isNaN) {
+  "use strict";
+
+  window.calculatePageTitle = function (gadget, erp5_document) {
+    return new RSVP.Queue()
+      .push(function () {
+        var title = erp5_document.title,
+          portal_type = erp5_document._links.type.name;
+        if (/ Module$/.test(erp5_document._links.type.href)) {
+          return portal_type;
+        }
+        return portal_type + ': ' + title;
+      });
+  };
+
+  /** Return true if the value truly represents an empty value.
+
+  Calling isEmpty(x) is more robust than expression !x.
+  */
+  function isEmpty(value) {
+    return (value === undefined ||
+            value === null ||
+            value.length === 0 ||
+            (typeof value === "number" && isNaN(value)));
+  }
+  window.isEmpty = isEmpty;
+
+  /** Make sure that returned object is an Array instance.
+  */
+  function ensureArray(obj) {
+    if (Array.isArray(obj)) {return obj; }
+    if (isEmpty(obj)) {return []; }
+    return [obj];
+  }
+  window.ensureArray = ensureArray;
+
+  /** Return first non-empty variable or the last one.
+
+  Calling getNonEmpy(a, b, "") is more robust way of writing a || b || "".
+  Variables coercing to false (e.g 0) do not get skipped anymore.
+  */
+  function getFirstNonEmpty() {
+    var i;
+    if (arguments.length === 0) {
+      return null;
+    }
+    for (i = 0; i < arguments.length; i++) {
+      if (!isEmpty(arguments[i])) {
+        return arguments[i];
+      }
+    }
+    if (arguments.length === 1) {
+      return arguments[0];
+    }
+    return arguments[arguments.length - 1];
+  }
+  window.getFirstNonEmpty = getFirstNonEmpty;
+
+  /** Convert anything to boolean value correctly (even "false" will be false)*/
+  function asBoolean(obj) {
+    if (typeof obj === "boolean") {
+      return obj;
+    }
+    if (typeof obj === "string") {
+      return obj.toLowerCase() === "true" || obj === "1";
+    }
+    if (typeof obj === "number") {
+      return obj !== 0;
+    }
+    return Boolean(obj);
+  }
+  window.asBoolean = asBoolean;
+
+}(window, RSVP, Array, isNaN));
\ No newline at end of file
diff --git a/gadget_html5_select.html b/gadget_html5_select.html
new file mode 100644
index 0000000..5dd3aa0
--- /dev/null
+++ b/gadget_html5_select.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
+    <meta name="viewport" content="width=device-width, user-scalable=no" />
+    <title>HTML5 Select</title>
+    <!-- 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>
+    <script src="gadget_erp5_global.js" type="text/javascript"></script>
+    <!-- custom 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>
+
+    <script id="disabled-option-template" type="text/x-handlebars-template">
+      <option disabled="disabled" data-i18n="{{text}}">{{text}}</option>
+    </script>
+
+    <script src="gadget_html5_select.js" type="text/javascript"></script>
+  </head>
+  <body><select /></body>
+</html>
\ No newline at end of file
diff --git a/gadget_html5_select.js b/gadget_html5_select.js
new file mode 100644
index 0000000..f64454c
--- /dev/null
+++ b/gadget_html5_select.js
@@ -0,0 +1,158 @@
+/*global window, rJS, RSVP, Handlebars, getFirstNonEmpty */
+/*jslint indent: 2, maxerr: 3, maxlen: 80, nomen: true */
+(function (window, rJS, RSVP, Handlebars, getFirstNonEmpty) {
+  "use strict";
+
+  // How to change html selected option using JavaScript?
+  // http://stackoverflow.com/a/20662180
+
+  /////////////////////////////////////////////////////////////////
+  // 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),
+    disabled_option_source = gadget_klass.__template_element
+                               .getElementById("disabled-option-template")
+                               .innerHTML,
+    disabled_option_template = Handlebars.compile(disabled_option_source);
+
+  gadget_klass
+    .setState({
+      editable: false,
+      value: undefined,
+      checked: undefined,
+      title: '',
+      item_list: [],
+      required: false
+    })
+
+    .declareMethod('render', function (options) {
+      var state_dict = {
+          value: getFirstNonEmpty(options.value, ""),
+          item_list: JSON.stringify(options.item_list),
+          editable: options.editable,
+          required: options.required,
+          id: options.id,
+          name: options.name,
+          title: options.title,
+          hidden: options.hidden
+        };
+      return this.changeState(state_dict);
+    })
+
+    .onStateChange(function (modification_dict) {
+      var i,
+        found = false,
+        template,
+        select = this.element.querySelector('select'),
+        item_list = JSON.parse(this.state.item_list),
+        tmp = "";
+
+      select.id = this.state.id || this.state.name;
+      select.setAttribute('name', this.state.name);
+
+      if (this.state.title) {
+        select.setAttribute('title', this.state.title);
+      }
+
+      if (this.state.required) {
+        select.required = true;
+      } else {
+        select.required = false;
+      }
+
+      if (this.state.editable) {
+        select.readonly = true;
+      } else {
+        select.readonly = false;
+      }
+
+      if (this.state.hidden) {
+        select.hidden = true;
+      } else {
+        select.hidden = false;
+      }
+
+      if (modification_dict.hasOwnProperty('value') ||
+          modification_dict.hasOwnProperty('item_list')) {
+        for (i = 0; i < item_list.length; i += 1) {
+          if (item_list[i][1] === null) {
+            template = disabled_option_template;
+          } else if (item_list[i][1] === this.state.value) {
+            template = selected_option_template;
+            found = true;
+          } else {
+            template = option_template;
+          }
+          tmp += template({
+            value: item_list[i][1],
+            text: item_list[i][0]
+          });
+        }
+
+        if (!found) {
+          tmp += selected_option_template({
+            value: this.state.value,
+            text: '??? (' + this.state.value + ')'
+          });
+        }
+        select.innerHTML = tmp;
+      }
+    })
+
+    .declareMethod('getContent', function () {
+      var result = {},
+        select = this.element.querySelector('select');
+      if (this.state.editable) {
+        result[select.getAttribute('name')] =
+          select.options[select.selectedIndex].value;
+        // Change the value state in place
+        // This will prevent the gadget to be changed if
+        // its parent call render with the same value
+        // (as ERP5 does in case of formulator error)
+        this.state.value = result[select.getAttribute('name')];
+      }
+      return result;
+    })
+
+    .declareAcquiredMethod("notifyValid", "notifyValid")
+    .declareMethod('checkValidity', function () {
+      var result = this.element.querySelector('select').checkValidity();
+      if (result) {
+        return this.notifyValid()
+          .push(function () {
+            return result;
+          });
+      }
+      return result;
+    })
+
+    .declareAcquiredMethod("notifyChange", "notifyChange")
+    .onEvent('change', function () {
+      return RSVP.all([
+        this.checkValidity(),
+        this.notifyChange()
+      ]);
+    }, false, false)
+    .onEvent('input', function () {
+      return RSVP.all([
+        this.checkValidity(),
+        this.notifyChange()
+      ]);
+    }, false, false)
+
+    .declareAcquiredMethod("notifyInvalid", "notifyInvalid")
+    .onEvent('invalid', function (evt) {
+      // invalid event does not bubble
+      return this.notifyInvalid(evt.target.validationMessage);
+    }, true, false);
+
+}(window, rJS, RSVP, Handlebars, getFirstNonEmpty));
\ No newline at end of file
-- 
2.30.9