Commit ea219b74 authored by Gabriel Monnerat's avatar Gabriel Monnerat Committed by Romain Courteaud

erp5_web_renderjs_ui: Improve fields to propagate error_text

* change the label field to not display the error text by default
* change all html5 fields (input, select, textarea) to add a custom *invalid* class when the field is invalid
* change all html5 fields to listen to the `focus` and `blur` event (with the renderjs onEvent method)
  Then, acquire (with `declareAcquiredMethod`) and call `notifyFocus` / `notifyBlur` methods respectively.
* change the label field to handle `notifyFocus` / `notifyBlur` (with `allowPublicAcquisition`).
  Change the validation error text display state depending on the field status
* Trigger onStateChange to set invalid class. If error comes from input field, we need to change state locally to set invalid class
* Regenerate gadget_erp5_nojqm.css from  erp5less.css using http://lesscss.org/less-preview/
* Textarea gadget should not prevent default when invalid event is triggered
* Support notifyFocus and notifyBlur in panel
parent 27ac35c6
......@@ -24,6 +24,7 @@
id: field_json.key,
name: field_json.key,
title: field_json.title,
error_text: field_json.error_text,
hidden: field_json.hidden,
// Force calling subfield render
// as user may have modified the input value
......
......@@ -234,7 +234,7 @@
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>982.26919.14719.24132</string> </value>
<value> <string>987.16243.63478.16486</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -252,7 +252,7 @@
</tuple>
<state>
<tuple>
<float>1583764359.91</float>
<float>1602527335.51</float>
<string>UTC</string>
</tuple>
</state>
......
......@@ -65,6 +65,7 @@
name: field_json.key,
key: field_json.key,
title: field_json.title,
error_text: field_json.error_text,
timezone_style: field_json.timezone_style,
date_only: field_json.date_only,
hide_day: field_json.hide_day,
......@@ -112,7 +113,8 @@
editable: gadget.state.editable,
required: gadget.state.required,
type: gadget.state.date_only ? "date" : "datetime-local",
hidden: gadget.state.hidden
hidden: gadget.state.hidden,
error_text: modification_dict.error_text
},
select_state = {
name: gadget.state.key + '_select',
......@@ -120,7 +122,8 @@
item_list: ZONE_LIST,
editable: gadget.state.editable,
required: gadget.state.required,
hidden: gadget.state.hidden
hidden: gadget.state.hidden,
error_text: modification_dict.error_text
// name: field_json.key,
// title: field_json.title
},
......
......@@ -228,7 +228,7 @@
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>983.56055.11203.16827</string> </value>
<value> <string>986.51894.21525.38502</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -246,8 +246,8 @@
</tuple>
<state>
<tuple>
<float>1589358221.58</float>
<string>GMT+0</string>
<float>1600894302.31</float>
<string>UTC</string>
</tuple>
</state>
</object>
......
......@@ -20,8 +20,7 @@
.declareAcquiredMethod("jio_getAttachment", "jio_getAttachment")
.declareMethod('render', function (options) {
var element = this.element,
gadget = this,
var gadget = this,
field_json = options.field_json || {},
new_state = {
value: field_json.value || field_json['default'] || "",
......@@ -44,7 +43,7 @@
return gadget.changeState(new_state);
})
.onStateChange(function (modification_dict) {
.onStateChange(function () {
var gadget = this,
erp5_document_uri = new URI(gadget.state.erp5_embedded_document._view._links.traversed_document.href),
form_options = {
......
......@@ -220,7 +220,7 @@
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>superkato</string> </value>
<value> <string>zope</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
......@@ -234,7 +234,7 @@
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>967.35221.49309.22852</string> </value>
<value> <string>986.61483.49376.1638</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -252,7 +252,7 @@
</tuple>
<state>
<tuple>
<float>1526316882.68</float>
<float>1601393999.02</float>
<string>UTC</string>
</tuple>
</state>
......
......@@ -35,6 +35,7 @@
name: field_json.key,
title: field_json.title,
precision: window.parseFloat(field_json.precision),
error_text: field_json.error_text,
// erp5 always put value into "default" (never "value")
value: window.parseFloat(field_json.default),
text_content: '',
......
......@@ -234,7 +234,7 @@
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>983.35869.13162.60928</string> </value>
<value> <string>986.14570.51196.18756</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -252,8 +252,8 @@
</tuple>
<state>
<tuple>
<float>1599097800.89</float>
<string>GMT+2</string>
<float>1598579261.92</float>
<string>UTC</string>
</tuple>
</state>
</object>
......
......@@ -17,6 +17,7 @@
editable: field_json.editable,
required: field_json.required,
id: field_json.key,
error_text: options.field_json.error_text || "",
name: field_json.key,
title: field_json.title,
hidden: field_json.hidden,
......
......@@ -234,7 +234,7 @@
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>965.12118.35525.1655</string> </value>
<value> <string>986.11537.50394.34935</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -252,7 +252,7 @@
</tuple>
<state>
<tuple>
<float>1517930232.43</float>
<float>1598397556.71</float>
<string>UTC</string>
</tuple>
</state>
......
/*global window, document, rJS */
/*global window, document, rJS*/
/*jslint indent: 2, maxerr: 3 */
/**
* Label gadget takes care of displaying validation errors and label.
......@@ -80,6 +80,7 @@
error_text: '',
label: true, // the label element is already present in the HTML template
css_class: '',
display_error_text: false,
first_call: false
})
......@@ -101,6 +102,7 @@
state_dict.label = true;
}
return this.changeState(state_dict);
})
.onStateChange(function onStateChange(modification_dict) {
......@@ -110,15 +112,13 @@
i,
queue,
new_div;
if (modification_dict.hasOwnProperty('first_call')) {
gadget.props = {
container_element: gadget.element.querySelector('div'),
label_element: gadget.element.querySelector('label')
};
}
if (gadget.state.hidden && !modification_dict.error_text) {
if (gadget.state.hidden && !gadget.state.error_text) {
this.element.hidden = true;
} else {
this.element.hidden = false;
......@@ -136,14 +136,31 @@
}
}
if (modification_dict.hasOwnProperty('error_text')) {
// Remove/add label_element from DOM
if (modification_dict.hasOwnProperty('label')) {
if (this.state.label === true) {
this.props.container_element.insertBefore(this.props.label_element, this.props.container_element.firstChild);
} else {
this.props.container_element.removeChild(this.props.label_element);
}
}
if (this.state.error_text && this.props.label_element &&
!this.props.label_element.classList.contains("is-invalid")) {
this.props.label_element.classList.add("is-invalid");
} else if (!this.state.error_text &&
this.props.label_element.classList.contains("is-invalid")) {
this.props.label_element.classList.remove("is-invalid");
}
if (modification_dict.hasOwnProperty('display_error_text') || modification_dict.hasOwnProperty('error_text')) {
// first remove old errors
span = this.props.container_element.lastElementChild;
if ((span !== null) && (span.tagName.toLowerCase() !== 'span')) {
span = null;
}
// display new error if present
if (this.state.error_text) {
if (this.state.error_text && this.state.display_error_text) {
if (span === null) {
span = document.createElement('span');
span.textContent = this.state.error_text;
......@@ -151,18 +168,11 @@
} else {
span.textContent = this.state.error_text;
}
} else if (span !== null) {
} else {
if (span !== null) {
this.props.container_element.removeChild(span);
}
}
// Remove/add label_element from DOM
if (modification_dict.hasOwnProperty('label')) {
if (this.state.label === true) {
this.props.container_element.insertBefore(this.props.label_element, this.props.container_element.firstChild);
} else {
this.props.container_element.removeChild(this.props.label_element);
}
}
if (modification_dict.hasOwnProperty('options')) {
......@@ -199,6 +209,7 @@
});
}
}
})
.declareMethod("checkValidity", function checkValidity() {
......@@ -231,6 +242,14 @@
});
}, {mutex: 'changestate'})
.allowPublicAcquisition("notifyFocus", function notifyFocus() {
return this.changeState({display_error_text: true});
})
.allowPublicAcquisition("notifyBlur", function notifyBlur() {
return this.changeState({display_error_text: false});
})
.allowPublicAcquisition("notifyInvalid", function notifyInvalid(param_list) {
// Label doesn't know when a subgadget calls notifyInvalid
// Prevent mutex dead lock by defering the changeState call
......
......@@ -234,7 +234,7 @@
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>984.41193.2075.29252</string> </value>
<value> <string>987.16240.14459.45960</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -252,7 +252,7 @@
</tuple>
<state>
<tuple>
<float>1596116391.77</float>
<float>1602525354.74</float>
<string>UTC</string>
</tuple>
</state>
......
......@@ -23,6 +23,7 @@
title: field_json.title,
first_item: field_json.first_item,
hidden: field_json.hidden,
error_text: field_json.error_text,
// Force calling subfield render
// as user may have modified the input value
render_timestamp: new Date().getTime()
......
......@@ -234,7 +234,7 @@
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>979.41914.53909.44561</string> </value>
<value> <string>986.42821.5680.17442</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -252,7 +252,7 @@
</tuple>
<state>
<tuple>
<float>1573126833.72</float>
<float>1600275350.99</float>
<string>UTC</string>
</tuple>
</state>
......
......@@ -3,7 +3,7 @@
(function (window, rJS, RSVP, document) {
'use strict';
function appendCheckboxField(gadget, item, checked) {
function appendCheckboxField(gadget, item, checked, error_text) {
var input_gadget,
label_gadget;
if (!gadget.state.editable) {
......@@ -13,7 +13,8 @@
label_gadget = result;
return result.render({
tag: 'p',
text_content: item[0]
text_content: item[0],
error_text: error_text
});
})
.push(function () {
......@@ -35,6 +36,7 @@
value: item[1],
checked: checked,
editable: true,
error_text: error_text,
hidden: gadget.state.hidden
};
......@@ -47,19 +49,24 @@
label.appendChild(input_gadget.element);
label.appendChild(text_node);
div.appendChild(label);
if (error_text && !label.classList.contains("is-invalid")) {
label.classList.add("is-invalid");
} else if (!error_text && label.classList.contains("is-invalid")) {
label.classList.remove("is-invalid");
}
gadget.element.appendChild(div);
});
}
rJS(window)
.declareMethod('render', function (options) {
var field_json = options.field_json || {},
state_dict = {
editable: field_json.editable,
name: field_json.key,
item_list: field_json.items,
value_list: field_json.value || field_json.default,
value_list: field_json.value || field_json["default"],
error_text: field_json.error_text,
hidden: field_json.hidden,
// Force calling subfield render
// as user may have modified the input value
......@@ -94,11 +101,13 @@
return appendCheckboxField.apply(this, argument_list);
});
}
queue = new RSVP.Queue();
for (i = 0; i < item_list.length; i += 1) {
enQueue(gadget, item_list[i], value_dict.hasOwnProperty(item_list[i][1]));
enQueue(gadget,
item_list[i],
value_dict.hasOwnProperty(item_list[i][1]),
this.state.error_text);
}
return queue;
......@@ -149,13 +158,6 @@
}, {mutex: 'changestate'})
.declareMethod('checkValidity', function () {
var name = this.state.name;
if (this.state.editable && this.state.required) {
return this.getContent()
.push(function (result) {
return result[name].length !== 0;
});
}
return true;
});
......
......@@ -234,7 +234,7 @@
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>971.50596.31143.27050</string> </value>
<value> <string>987.29075.27892.56422</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -252,7 +252,7 @@
</tuple>
<state>
<tuple>
<float>1542818919.2</float>
<float>1603296350.21</float>
<string>UTC</string>
</tuple>
</state>
......
......@@ -329,14 +329,23 @@ select:focus {
}
input:not([type=submit]):not([type=file]):not([type=checkbox]):not([type=radio]):not([type=color]):invalid,
textarea:invalid,
select:invalid {
select:invalid,
input:not([type=submit]):not([type=file]):not([type=checkbox]):not([type=radio]):not([type=color]).is-invalid,
textarea.is-invalid,
select.is-invalid {
border: 1px solid #FF6600;
}
input:not([type=submit]):not([type=file]):not([type=checkbox]):not([type=radio]):not([type=color]):invalid:focus,
textarea:invalid:focus,
select:invalid:focus {
select:invalid:focus,
input:not([type=submit]):not([type=file]):not([type=checkbox]):not([type=radio]):not([type=color]).is-invalid:focus,
textarea.is-invalid:focus,
select.is-invalid:focus {
box-shadow: 0 0 12pt #FF6600;
}
label.is-invalid {
color: #FF6600;
}
input[type="search"] {
-webkit-appearance: textfield;
}
......@@ -1174,7 +1183,7 @@ div[data-gadget-scope='header'] .ui-header ul {
padding-left: 24pt;
}
}
.gadget-content .ui-field-contain > label {
.gadget-content .ui-field-contain > label:not(:is-invalid) {
color: hsl(0, 0%, 42%);
}
.gadget-content .required > .ui-field-contain > label {
......
......@@ -244,7 +244,7 @@
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>988.2056.8848.16469</string> </value>
<value> <string>987.16008.2576.23808</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -262,7 +262,7 @@
</tuple>
<state>
<tuple>
<float>1605606512.05</float>
<float>1602511207.11</float>
<string>UTC</string>
</tuple>
</state>
......
......@@ -347,6 +347,19 @@
}
}, false, false)
.allowPublicAcquisition("notifyFocus", function notifyFocus() {
// All html5 fields in ERP5JS triggers this method when focus
// is triggered. This is usefull to display error text.
// But, in the case of panel, we don't need to handle anything.
return;
})
.allowPublicAcquisition("notifyBlur", function notifyFocus() {
// All html5 fields in ERP5JS triggers this method when blur
// is triggered now. This is usefull to display error text.
// But, in the case of panel, we don't need to handle anything.
return;
})
.allowPublicAcquisition('notifyChange', function notifyChange(
argument_list,
scope
......
......@@ -234,7 +234,7 @@
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>986.45400.41413.45892</string> </value>
<value> <string>987.27464.21813.55483</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -252,7 +252,7 @@
</tuple>
<state>
<tuple>
<float>1600429015.37</float>
<float>1604411452.55</float>
<string>UTC</string>
</tuple>
</state>
......
......@@ -3,7 +3,7 @@
(function (window, rJS, RSVP) {
"use strict";
function appendCheckboxField(gadget, item) {
function appendCheckboxField(gadget, item, error_text) {
var input_gadget;
return gadget.declareGadget('gadget_html5_input.html', {scope: item[1], element: 'span'})
.push(function (result) {
......@@ -13,6 +13,7 @@
name: gadget.state.name,
value: item[1],
editable: true,
error_text: error_text,
hidden: gadget.state.hidden
};
if (item[1] === gadget.state.value) {
......@@ -24,6 +25,11 @@
var label = document.createElement("label");
label.textContent = item[0];
label.insertBefore(input_gadget.element, label.firstChild);
if (error_text && !label.classList.contains("is-invalid")) {
label.classList.add("is-invalid");
} else if (!error_text && label.classList.contains("is-invalid")) {
label.classList.remove("is-invalid");
}
gadget.element.appendChild(label);
});
}
......@@ -39,6 +45,7 @@
select_first_item: field_json.select_first_item,
required: field_json.required,
editable: field_json.editable,
error_text: field_json.error_text,
name: field_json.key,
title: field_json.title,
item_list: field_json.items,
......@@ -82,7 +89,7 @@
queue = new RSVP.Queue();
for (i = 0; i < item_list.length; i += 1) {
enQueue(gadget, item_list[i]);
enQueue(gadget, item_list[i], this.state.error_text);
}
} else {
......@@ -152,10 +159,18 @@
return final_result;
}, {mutex: 'changestate'})
.declareJob('deferErrorText', function deferErrorText(error_text) {
var input = this.element.querySelector("input");
return this.changeState({
error_text: error_text
});
})
.declareMethod('checkValidity', function () {
var name = this.state.name,
gadget = this,
empty;
if (this.state.editable && this.state.required) {
return new RSVP.Queue()
.push(function () {
......@@ -169,7 +184,10 @@
error_message = all_result[1];
empty = !content[name];
if (empty) {
return gadget.notifyInvalid(error_message);
return RSVP.all([
gadget.deferErrorText(error_message),
gadget.notifyInvalid(error_message)
]);
}
return gadget.notifyValid();
})
......@@ -179,4 +197,5 @@
}
return true;
});
}(window, rJS, RSVP));
\ No newline at end of file
......@@ -220,7 +220,7 @@
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>valentin</string> </value>
<value> <string>zope</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
......@@ -234,7 +234,7 @@
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>976.61321.38056.58231</string> </value>
<value> <string>987.16084.46356.54801</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -252,7 +252,7 @@
</tuple>
<state>
<tuple>
<float>1562581079.03</float>
<float>1602516004.43</float>
<string>UTC</string>
</tuple>
</state>
......
......@@ -149,6 +149,7 @@
key: options.key,
view: options.view,
search_view: options.search_view,
error_text: options.error_text || "",
url: options.url,
allow_creation: options.allow_creation,
portal_types: JSON.stringify(options.portal_types),
......@@ -231,8 +232,19 @@
// First display of the input
buildEditableInputHTML(gadget);
}
input = gadget.element.querySelector("input");
if (gadget.state.error_text &&
!input.classList.contains("is-invalid")) {
input.classList.add("is-invalid");
} else if (
// value_portal_type not empty means that user
// wants to create a Document
(!gadget.state.error_text || gadget.state.value_portal_type) &&
input.classList.contains("is-invalid")
) {
input.classList.remove("is-invalid");
}
if (modification_dict.hasOwnProperty("value_text")) {
input.value = gadget.state.value_text;
}
......@@ -417,6 +429,14 @@
"Invalid Search Criteria"
])
.push(function (translation_list) {
if (gadget.state.error_text !== translation_list[0]) {
// Avoid call deferErrorText if error_text is already set
// Otherwise, onStateChange will run forever
return RSVP.all([
gadget.deferErrorText(translation_list[0]),
gadget.notifyInvalid(translation_list[0])
]);
}
return gadget.notifyInvalid(translation_list[0]);
});
}
......@@ -429,9 +449,25 @@
])
.push(function (translation_list) {
if (error.target.status === 0) {
if (gadget.state.error_text !== translation_list[0]) {
// Avoid call deferErrorText if error_text is already
// set. Otherwise, onStateChange will run forever
return RSVP.all([
gadget.deferErrorText(translation_list[0]),
gadget.notifyInvalid(translation_list[0])
]);
}
return gadget.notifyInvalid(translation_list[0]);
}
if (error.target.status >= 500) {
if (gadget.state.error_text !== translation_list[0]) {
// Avoid call deferErrorText if error_text is already
// set. Otherwise, onStateChange will run forever
return RSVP.all([
gadget.deferErrorText(translation_list[1]),
gadget.notifyInvalid(translation_list[1])
]);
}
return gadget.notifyInvalid(translation_list[1]);
}
throw error;
......@@ -513,6 +549,7 @@
}
}, false, false)
.declareAcquiredMethod("notifyBlur", "notifyBlur")
.onEvent('blur', function (evt) {
var gadget = this;
if (evt.target.tagName.toLowerCase() === 'input') {
......@@ -525,22 +562,27 @@
return gadget.changeState({
has_focus: false
});
})
.push(function () {
return gadget.notifyBlur();
});
}
}, true, false)
.declareAcquiredMethod("notifyFocus", "notifyFocus")
.onEvent('focus', function (evt) {
var gadget = this;
if (evt.target.tagName.toLowerCase() === 'input') {
return gadget.changeState({
has_focus: true
});
return RSVP.all([
gadget.notifyFocus(),
gadget.changeState({has_focus: true})
]);
}
}, true, false)
.declareAcquiredMethod("notifyValid", "notifyValid")
.declareMethod('checkValidity', function () {
var gadget = this;
if ((this.state.value_text) && (
(this.state.value_relative_url === null) &&
(this.state.value_uid === null) &&
......@@ -548,19 +590,34 @@
)) {
return gadget.translate("No such document was found")
.push(function (error_message) {
return gadget.notifyInvalid(error_message);
return RSVP.all([
gadget.deferErrorText(error_message),
gadget.notifyInvalid(error_message)
]);
})
.push(function () {
return false;
});
}
return gadget.notifyValid()
.push(function () {
return true;
});
}, {mutex: 'changestate'})
.declareJob('deferErrorText', function deferErrorText(error_text) {
return this.changeState({
error_text: error_text
});
})
// XXX Use html5 input
.onEvent('invalid', function (evt) {
// invalid event does not bubble
return this.notifyInvalid(evt.target.validationMessage);
return RSVP.all([
this.deferErrorText(evt.target.validationMessage),
this.notifyInvalid(evt.target.validationMessage)
]);
}, true, false)
.onEvent('change', function () {
......@@ -569,20 +626,23 @@
.onEvent('input', function (event) {
var gadget = this;
if (!this.state.editable) {
return;
}
var context = this;
return this.changeState({
value_text: event.target.value,
value_relative_url: null,
value_uid: null,
value_portal_type: null,
has_focus: true
has_focus: true,
error_text: ""
})
.push(function () {
return context.notifyChange();
return RSVP.all([
gadget.notifyValid(),
gadget.notifyChange()
]);
});
}, true, false);
......
......@@ -69,9 +69,7 @@
</item>
<item>
<key> <string>content_type</string> </key>
<value>
<none/>
</value>
<value> <string>text/javascript</string> </value>
</item>
<item>
<key> <string>default_reference</string> </key>
......@@ -240,7 +238,7 @@
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>983.6083.54777.37051</string> </value>
<value> <string>987.36268.43767.1126</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -258,7 +256,7 @@
</tuple>
<state>
<tuple>
<float>1586446542.77</float>
<float>1603726868.97</float>
<string>UTC</string>
</tuple>
</state>
......
......@@ -22,6 +22,7 @@
title: field_json.title,
key: field_json.key,
view: field_json.view,
error_text: field_json.error_text,
search_view: field_json.search_view,
url: field_json.url,
allow_creation: field_json.allow_creation,
......
......@@ -234,7 +234,7 @@
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>970.13790.51027.29644</string> </value>
<value> <string>986.41646.43198.36846</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -252,7 +252,7 @@
</tuple>
<state>
<tuple>
<float>1539872919.1</float>
<float>1600685092.05</float>
<string>UTC</string>
</tuple>
</state>
......
......@@ -210,6 +210,19 @@
});
}, {mutex: 'changestate'})
.allowPublicAcquisition("notifyFocus", function notifyFocus() {
// All html5 fields in ERP5JS triggers this method when focus
// is triggered. This is usefull to display error text.
// But, in the case of panel, we don't need to handle anything.
return;
})
.allowPublicAcquisition("notifyBlur", function notifyFocus() {
// All html5 fields in ERP5JS triggers this method when blur
// is triggered now. This is usefull to display error text.
// But, in the case of panel, we don't need to handle anything.
return;
})
.declareAcquiredMethod("triggerSubmit", "triggerSubmit")
.onEvent('click', function (evt) {
if ((evt.target.nodeType === Node.ELEMENT_NODE) &&
......
......@@ -234,7 +234,7 @@
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>969.37531.32072.23688</string> </value>
<value> <string>987.27464.21813.55483</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -252,7 +252,7 @@
</tuple>
<state>
<tuple>
<float>1536936754.19</float>
<float>1604428536.71</float>
<string>UTC</string>
</tuple>
</state>
......
/*global window, rJS */
/*global window, document, rJS */
/*jslint indent: 2, maxerr: 3 */
(function (window, rJS) {
(function (window, document, rJS) {
"use strict";
rJS(window)
......@@ -16,6 +16,7 @@
required: field_json.required,
id: field_json.key,
name: field_json.key,
error_text: field_json.error_text,
title: field_json.title,
hidden: field_json.hidden,
trim: true,
......@@ -72,4 +73,4 @@
return true;
}, {mutex: 'changestate'});
}(window, rJS));
\ No newline at end of file
}(window, document, rJS));
\ No newline at end of file
......@@ -234,7 +234,7 @@
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>965.41976.11439.18449</string> </value>
<value> <string>986.42689.22481.46592</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -252,7 +252,7 @@
</tuple>
<state>
<tuple>
<float>1526396915.47</float>
<float>1600380471.64</float>
<string>UTC</string>
</tuple>
</state>
......
......@@ -14,6 +14,7 @@
editable: field_json.editable,
name: field_json.key,
id: field_json.key,
error_text: field_json.error_text,
title: field_json.title,
hidden: field_json.hidden,
// Force calling subfield render
......
......@@ -240,7 +240,7 @@
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>965.12118.35525.1655</string> </value>
<value> <string>986.42841.34859.34594</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -258,7 +258,7 @@
</tuple>
<state>
<tuple>
<float>1517927278.74</float>
<float>1600277966.0</float>
<string>UTC</string>
</tuple>
</state>
......
......@@ -96,6 +96,17 @@
}
return data;
})
.declareAcquiredMethod("notifyFocus", "notifyFocus")
.onEvent('focus', function focus() {
return this.notifyFocus();
}, true, false)
.declareAcquiredMethod("notifyBlur", "notifyBlur")
.onEvent('blur', function blur() {
return this.notifyBlur();
}, true, false)
.declareMethod("checkValidity", function checkValidity() {
return true;
});
......
......@@ -228,7 +228,7 @@
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>982.42532.14902.56951</string> </value>
<value> <string>986.41601.40161.17629</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -246,7 +246,7 @@
</tuple>
<state>
<tuple>
<float>1584701796.37</float>
<float>1601393849.37</float>
<string>UTC</string>
</tuple>
</state>
......
......@@ -29,6 +29,7 @@
type: options.type || 'text',
title: options.title,
focus: options.focus,
error_text: options.error_text || "",
step: options.step,
hidden: options.hidden,
trim: options.trim || false,
......@@ -43,13 +44,13 @@
.onStateChange(function onStateChange(modification_dict) {
var textarea = this.element.querySelector('input'),
tmp; // general use short-scope variable
if (this.state.type === 'checkbox') {
textarea.checked = this.state.checked;
} else {
textarea.setAttribute('value', this.state.value);
textarea.value = this.state.value;
}
if (this.state.type === 'radio') {
textarea.checked = this.state.checked;
}
......@@ -86,7 +87,7 @@
textarea.readonly = false;
}
if (this.state.hidden) {
if (this.state.hidden && !modification_dict.error_text) {
textarea.hidden = true;
} else {
textarea.hidden = false;
......@@ -117,6 +118,15 @@
this.element.insertBefore(tmp, textarea);
tmp = undefined;
}
if (this.state.error_text &&
!textarea.classList.contains("is-invalid")) {
textarea.classList.add("is-invalid");
} else if (!this.state.error_text &&
textarea.classList.contains("is-invalid")) {
textarea.classList.remove("is-invalid");
}
})
.declareService(function focus() {
......@@ -176,15 +186,24 @@
.declareAcquiredMethod("notifyValid", "notifyValid")
.declareMethod('checkValidity', function checkValidity() {
var result = this.element.querySelector('input').checkValidity(),
var input = this.element.querySelector('input'),
result = input.checkValidity(),
gadget = this;
if (gadget.state.type === "radio") {
result = result && !gadget.state.error_text;
}
if (result) {
return this.notifyValid()
return gadget.notifyValid()
.push(function () {
var date,
value;
if (!result) {
if ((gadget.state.type === 'checkbox') && gadget.state.error_text) {
return gadget.notifyInvalid(gadget.state.error_text)
.push(function () {
return result;
});
}
if ((gadget.state.type === 'date') ||
(gadget.state.type === 'datetime-local')) {
......@@ -194,7 +213,10 @@
if (isNaN(date)) {
return gadget.translate("Invalid DateTime")
.push(function (error_message) {
return gadget.notifyInvalid(error_message);
return RSVP.all([
gadget.deferErrorText(error_message),
gadget.notifyInvalid(error_message)
]);
})
.push(function () {
return false;
......@@ -205,9 +227,24 @@
return result;
});
}
if (gadget.state.error_text) {
return gadget.notifyInvalid(gadget.state.error_text)
.push(function () {
return result;
});
}
return result;
}, {mutex: 'changestate'})
.declareJob('deferErrorText', function deferErrorText(error_text) {
var input = this.element.querySelector("input");
return this.changeState({
value: input.value,
error_text: error_text
});
})
.declareAcquiredMethod("notifyChange", "notifyChange")
.onEvent('change', function change() {
return RSVP.all([
......@@ -215,6 +252,7 @@
this.notifyChange("change")
]);
}, false, false)
.onEvent('input', function input() {
return RSVP.all([
this.checkValidity(),
......@@ -222,10 +260,23 @@
]);
}, false, false)
.declareAcquiredMethod("notifyFocus", "notifyFocus")
.onEvent('focus', function focus() {
return this.notifyFocus();
}, true, false)
.declareAcquiredMethod("notifyBlur", "notifyBlur")
.onEvent('blur', function blur() {
return this.notifyBlur();
}, true, false)
.declareAcquiredMethod("notifyInvalid", "notifyInvalid")
.onEvent('invalid', function invalid(evt) {
// invalid event does not bubble
return this.notifyInvalid(evt.target.validationMessage);
return RSVP.all([
this.deferErrorText(evt.target.validationMessage),
this.notifyInvalid(evt.target.validationMessage)
]);
}, true, false);
}(window, document, rJS, RSVP, jIO, getFirstNonEmpty));
\ No newline at end of file
......@@ -234,7 +234,7 @@
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>967.40700.16743.2833</string> </value>
<value> <string>987.27756.45244.12953</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -252,7 +252,7 @@
</tuple>
<state>
<tuple>
<float>1526653024.9</float>
<float>1603216380.65</float>
<string>UTC</string>
</tuple>
</state>
......
......@@ -22,6 +22,7 @@
item_list: JSON.stringify(options.item_list),
editable: options.editable,
required: options.required,
error_text: options.error_text || "",
id: options.id,
name: options.name,
title: options.title,
......@@ -41,6 +42,13 @@
select.id = this.state.id || this.state.name;
select.setAttribute('name', this.state.name);
if (modification_dict.error_text &&
!select.classList.contains("is-invalid")) {
select.classList.add("is-invalid");
} else if (select.classList.contains("is-invalid")) {
select.classList.remove("is-invalid");
}
if (this.state.title) {
select.setAttribute('title', this.state.title);
}
......@@ -118,7 +126,8 @@
.declareAcquiredMethod("notifyValid", "notifyValid")
.declareMethod('checkValidity', function checkValidity() {
var result = this.element.querySelector('select').checkValidity();
var select = this.element.querySelector('select'),
result = select.checkValidity() || this.state.error_text === "";
if (result) {
return this.notifyValid()
.push(function () {
......@@ -142,6 +151,16 @@
]);
}, false, false)
.declareAcquiredMethod("notifyFocus", "notifyFocus")
.onEvent('focus', function focus() {
return this.notifyFocus();
}, true, false)
.declareAcquiredMethod("notifyBlur", "notifyBlur")
.onEvent('blur', function blur() {
return this.notifyBlur();
}, true, false)
.declareAcquiredMethod("notifyInvalid", "notifyInvalid")
.onEvent('invalid', function invalid(evt) {
// invalid event does not bubble
......
......@@ -234,7 +234,7 @@
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>978.42847.9438.64699</string> </value>
<value> <string>986.42836.14245.5444</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -252,7 +252,7 @@
</tuple>
<state>
<tuple>
<float>1569589427.71</float>
<float>1600275334.01</float>
<string>UTC</string>
</tuple>
</state>
......
......@@ -24,6 +24,14 @@
.onStateChange(function onStateChange(modification_dict) {
var textarea = this.element.querySelector('textarea');
if (this.state.error_text &&
!textarea.classList.contains("is-invalid")) {
textarea.classList.add("is-invalid");
} else if (!this.state.error_text &&
textarea.classList.contains("is-invalid")) {
textarea.classList.remove("is-invalid");
}
if (modification_dict.hasOwnProperty("value")) {
textarea.value = modification_dict.value;
}
......@@ -74,7 +82,8 @@
.declareAcquiredMethod("notifyValid", "notifyValid")
.declareMethod('checkValidity', function checkValidity() {
var result = this.element.querySelector('textarea').checkValidity();
var textarea = this.element.querySelector('textarea'),
result = textarea.checkValidity();
if (result) {
return this.notifyValid()
.push(function () {
......@@ -92,7 +101,17 @@
.onEvent('invalid', function invalid(evt) {
// invalid event does not bubble
return this.notifyInvalid(evt.target.validationMessage);
}, true, true)
}, true, false)
.declareAcquiredMethod("notifyFocus", "notifyFocus")
.onEvent('focus', function focus() {
return this.notifyFocus();
}, true, false)
.declareAcquiredMethod("notifyBlur", "notifyBlur")
.onEvent('blur', function blur() {
return this.notifyBlur();
}, true, false)
.declareAcquiredMethod("notifySubmit", "notifySubmit")
.onEvent('keydown', function keydown(evt) {
......
......@@ -226,7 +226,7 @@
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>zope</string> </value>
<value> <string>ERP5TypeTestCase</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
......@@ -240,7 +240,7 @@
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>966.49875.42877.4590</string> </value>
<value> <string>987.741.47228.29815</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -258,7 +258,7 @@
</tuple>
<state>
<tuple>
<float>1526653198.19</float>
<float>1601596591.47</float>
<string>UTC</string>
</tuple>
</state>
......
......@@ -434,13 +434,16 @@ input:not([type=submit]):not([type=file]):not([type=checkbox]):not([type=radio])
border: @focus-border;
box-shadow: @focus-box-shadow;
}
&:invalid {
&:invalid, &.is-invalid {
border: @invalid-border;
&:focus {
box-shadow: @invalid-box-shadow;
}
}
}
label.is-invalid {
color: @txtorange;
}
// Force safari to render search input as text
// https://stackoverflow.com/questions/34802552/safari-input-type-search-reset-normalize
......@@ -1360,7 +1363,7 @@ div[data-gadget-scope='header'] .ui-header {
//Label
.ui-field-contain {
& > label {
& > label:not(:is-invalid) {
color: @colorlabel;
}
......
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