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