Commit fa97f342 authored by Romain Courteaud's avatar Romain Courteaud

[erp5_web_renderjs_ui] Reimplement list field

Try to reduce number of DOM modifications.
Display value even if not present in the item list.
parent 3a2a70f2
...@@ -220,6 +220,8 @@ gadget_html5_input.html\n ...@@ -220,6 +220,8 @@ gadget_html5_input.html\n
gadget_html5_input.js\n gadget_html5_input.js\n
gadget_html5_textarea.html\n gadget_html5_textarea.html\n
gadget_html5_textarea.js\n gadget_html5_textarea.js\n
gadget_html5_select.html\n
gadget_html5_select.js\n
gadget_erp5_global.js\n gadget_erp5_global.js\n
gadget_jio.html\n gadget_jio.html\n
gadget_jio.js\n gadget_jio.js\n
...@@ -363,7 +365,7 @@ NETWORK:\n ...@@ -363,7 +365,7 @@ NETWORK:\n
</item> </item>
<item> <item>
<key> <string>serial</string> </key> <key> <string>serial</string> </key>
<value> <string>954.27350.45081.38075</string> </value> <value> <string>954.38476.56174.42734</string> </value>
</item> </item>
<item> <item>
<key> <string>state</string> </key> <key> <string>state</string> </key>
...@@ -381,7 +383,7 @@ NETWORK:\n ...@@ -381,7 +383,7 @@ NETWORK:\n
</tuple> </tuple>
<state> <state>
<tuple> <tuple>
<float>1475763290.3</float> <float>1476437355.22</float>
<string>UTC</string> <string>UTC</string>
</tuple> </tuple>
</state> </state>
......
...@@ -8,20 +8,10 @@ ...@@ -8,20 +8,10 @@
<!-- renderjs --> <!-- renderjs -->
<script src="rsvp.js" type="text/javascript"></script> <script src="rsvp.js" type="text/javascript"></script>
<script src="renderjs.js" type="text/javascript"></script> <script src="renderjs.js" type="text/javascript"></script>
<script src="handlebars.js" type="text/javascript"></script>
<!-- custom script --> <!-- custom script -->
<script src="gadget_erp5_field_list.js" type="text/javascript"></script> <script src="gadget_erp5_field_list.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> </head>
<body> <body>
<select />
</body> </body>
</html> </html>
\ No newline at end of file
...@@ -220,7 +220,7 @@ ...@@ -220,7 +220,7 @@
</item> </item>
<item> <item>
<key> <string>actor</string> </key> <key> <string>actor</string> </key>
<value> <string>super_sven</string> </value> <value> <string>zope</string> </value>
</item> </item>
<item> <item>
<key> <string>comment</string> </key> <key> <string>comment</string> </key>
...@@ -234,7 +234,7 @@ ...@@ -234,7 +234,7 @@
</item> </item>
<item> <item>
<key> <string>serial</string> </key> <key> <string>serial</string> </key>
<value> <string>939.43978.9403.31744</string> </value> <value> <string>954.37505.27238.51626</string> </value>
</item> </item>
<item> <item>
<key> <string>state</string> </key> <key> <string>state</string> </key>
...@@ -252,8 +252,8 @@ ...@@ -252,8 +252,8 @@
</tuple> </tuple>
<state> <state>
<tuple> <tuple>
<float>1419418540.22</float> <float>1476431000.28</float>
<string>GMT</string> <string>UTC</string>
</tuple> </tuple>
</state> </state>
</object> </object>
......
/*global window, rJS, Handlebars, document, RSVP, loopEventListener*/ /*global window, rJS, loopEventListener*/
/*jslint nomen: true, indent: 2, maxerr: 3 */ /*jslint nomen: true, indent: 2, maxerr: 3, maxlen: 80 */
(function (window, rJS, Handlebars, document, RSVP) { (function (window, rJS) {
"use strict"; "use strict";
///////////////////////////////////////////////////////////////// rJS(window)
// Handlebars .setState({
///////////////////////////////////////////////////////////////// tag: 'p'
// 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) {
return g.getElement()
.push(function (element) {
g.element = element;
});
}) })
//////////////////////////////////////////////
// acquired method
//////////////////////////////////////////////
.declareAcquiredMethod("translateHtml", "translateHtml")
.declareAcquiredMethod("notifyValid", "notifyValid")
.declareAcquiredMethod("notifyInvalid", "notifyInvalid")
.declareAcquiredMethod("notifyChange", "notifyChange")
.declareMethod('getTextContent', function () {
var select = this.element.querySelector('select');
return select.options[select.selectedIndex || 0].text;
})
.declareMethod('render', function (options) { .declareMethod('render', function (options) {
var i, var field_json = options.field_json || {},
template, state_dict = {
gadget = this, value: field_json.value || field_json.default || "",
select = this.element.querySelector('select'), item_list: JSON.stringify(field_json.items),
field_json = options.field_json, editable: field_json.editable,
tmp = "", required: field_json.required,
wrap = document.createElement("select"); name: field_json.key,
title: field_json.title
};
return this.changeState(state_dict);
})
select.setAttribute('name', field_json.key); .onStateChange(function (modification_dict) {
for (i = 0; i < field_json.items.length; i += 1) { var element = this.element,
if (field_json.items[i][1] === field_json.default) { url,
template = selected_option_template; result,
} else { i,
template = option_template; text_content,
item_list,
state = {};
for (i in this.state) {
if (this.state.hasOwnProperty(i)) {
state[i] = this.state[i];
} }
tmp += template({
value: field_json.items[i][1],
text: field_json.items[i][0]
});
} }
state.item_list = JSON.parse(this.state.item_list);
// need a <select> for transport if (modification_dict.hasOwnProperty('editable')) {
wrap.innerHTML = tmp; if (this.state.editable) {
url = 'gadget_html5_select.html';
return new RSVP.Queue() } else {
.push(function () { url = 'gadget_html5_element.html';
return gadget.translateHtml(wrap.outerHTML);
})
.push(function (my_translated_html) {
// XXX: no fan...
var select_div,
div = document.createElement("div");
div.innerHTML = my_translated_html;
select_div = div.querySelector("select");
select.innerHTML = select_div.innerHTML;
if (field_json.required === 1) { item_list = state.item_list;
select.setAttribute('required', 'required'); for (i = 0; i < item_list.length; i += 1) {
if (item_list[i][1] === this.state.value) {
text_content = item_list[i][0];
} }
if (field_json.editable !== 1) { }
select.setAttribute('readonly', 'readonly'); if (text_content === undefined) {
select.setAttribute('data-wrapper-class', 'ui-state-readonly'); text_content = '??? (' + this.state.value + ')';
// select.setAttribute('disabled', 'disabled'); }
state.text_content = text_content;
} }
result = this.declareGadget(url, {scope: 'sub'})
.push(function (input) {
// Clear first to DOM, append after to reduce flickering/manip
while (element.firstChild) {
element.removeChild(element.firstChild);
}
element.appendChild(input.element);
return input;
}); });
}) } else {
.declareMethod('checkValidity', function () { result = this.getDeclaredGadget('sub');
var result;
result = this.element.querySelector('select').checkValidity();
if (result) {
return this.notifyValid()
.push(function () {
return result;
});
} }
return result; return result
}) .push(function (input) {
.declareMethod('getContent', function () { return input.render(state);
var input = this.element.querySelector('select'), });
result = {};
result[input.getAttribute('name')] = input.options[input.selectedIndex].value;
return result;
}) })
.declareService(function () { .declareMethod('getContent', function () {
//////////////////////////////////// if (this.state.editable) {
// Check field validity when the value changes return this.getDeclaredGadget('sub')
//////////////////////////////////// .push(function (gadget) {
var field_gadget = this; return gadget.getContent();
});
function notifyChange() {
return RSVP.all([
field_gadget.checkValidity(),
field_gadget.notifyChange()
]);
} }
return {};
// Listen to input change
return loopEventListener(
field_gadget.element.querySelector('select'),
'change',
false,
notifyChange
);
}) })
.declareService(function () { .declareMethod('getTextContent', function () {
//////////////////////////////////// return this.getDeclaredGadget('sub')
// Inform when the field input is invalid .push(function (gadget) {
//////////////////////////////////// return gadget.getTextContent();
var field_gadget = this; });
})
function notifyInvalid(evt) { .declareMethod('checkValidity', function () {
return field_gadget.notifyInvalid(evt.target.validationMessage); if (this.state.editable) {
return this.getDeclaredGadget('sub')
.push(function (gadget) {
return gadget.checkValidity();
});
} }
return true;
// Listen to input change
return loopEventListener(
field_gadget.element.querySelector('select'),
'invalid',
false,
notifyInvalid
);
}); });
}(window, rJS, Handlebars, document, RSVP)); }(window, rJS));
\ No newline at end of file \ No newline at end of file
...@@ -230,7 +230,7 @@ ...@@ -230,7 +230,7 @@
</item> </item>
<item> <item>
<key> <string>serial</string> </key> <key> <string>serial</string> </key>
<value> <string>952.64761.25287.18397</string> </value> <value> <string>954.38571.58992.44731</string> </value>
</item> </item>
<item> <item>
<key> <string>state</string> </key> <key> <string>state</string> </key>
...@@ -248,7 +248,7 @@ ...@@ -248,7 +248,7 @@
</tuple> </tuple>
<state> <state>
<tuple> <tuple>
<float>1470231990.11</float> <float>1476437096.88</float>
<string>UTC</string> <string>UTC</string>
</tuple> </tuple>
</state> </state>
......
<!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>
<!-- 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 src="gadget_html5_select.js" type="text/javascript"></script>
</head>
<body><select /></body>
</html>
\ No newline at end of file
/*global window, rJS, RSVP, Handlebars */
/*jslint indent: 2, maxerr: 3, maxlen: 80, nomen: true */
(function (window, rJS, RSVP, Handlebars) {
"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);
gadget_klass
.setState({
editable: false,
value: undefined,
checked: undefined,
title: '',
item_list: [],
required: false
})
.declareMethod('render', function (options) {
var state_dict = {
value: options.value || "",
item_list: JSON.stringify(options.item_list),
editable: options.editable,
required: options.required,
name: options.name,
title: options.title
};
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.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 (modification_dict.hasOwnProperty('value') ||
modification_dict.hasOwnProperty('item_list')) {
for (i = 0; i < item_list.length; i += 1) {
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;
}
return result;
})
.declareMethod('getTextContent', function () {
var select = this.element.querySelector('select');
return select.options[select.selectedIndex].text;
})
.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));
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment