Get needed attribute working

Will cancel submit, shows validation message
This commit is contained in:
nathan 2022-02-24 15:52:45 -07:00
parent ce84dd753a
commit d9e95dae87
7 changed files with 133 additions and 25 deletions

View File

@ -10,7 +10,7 @@
import {css, html} from "@lion/core"; import {css, html} from "@lion/core";
import {FormControlMixin} from "@lion/form-core"; import {FormControlMixin, ValidateMixin} from "@lion/form-core";
import 'lit-flatpickr'; import 'lit-flatpickr';
import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget"; import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget";
import {dateStyles} from "./DateStyles"; import {dateStyles} from "./DateStyles";
@ -270,7 +270,7 @@ export function formatDateTime(date : Date, options = {dateFormat: "", timeForma
return formatDate(date, options) + " " + formatTime(date, options); return formatDate(date, options) + " " + formatTime(date, options);
} }
export class Et2Date extends Et2InputWidget(FormControlMixin(LitFlatpickr)) export class Et2Date extends Et2InputWidget(FormControlMixin(ValidateMixin(LitFlatpickr)))
{ {
static get styles() static get styles()
{ {

View File

@ -1,7 +1,8 @@
import {et2_IInput, et2_IInputNode} from "../et2_core_interfaces"; import {et2_IInput, et2_IInputNode, et2_ISubmitListener} from "../et2_core_interfaces";
import {Et2Widget} from "../Et2Widget/Et2Widget"; import {Et2Widget} from "../Et2Widget/Et2Widget";
import {dedupeMixin} from "@lion/core"; import {dedupeMixin, PropertyValues} from "@lion/core";
import {ManualMessage} from "./ManualMessage"; import {ManualMessage} from "../Validators/ManualMessage";
import {Required} from "../Validators/Required";
/** /**
* This mixin will allow any LitElement to become an Et2InputWidget * This mixin will allow any LitElement to become an Et2InputWidget
@ -37,7 +38,7 @@ export declare class Et2InputWidgetInterface
const Et2InputWidgetMixin = (superclass) => const Et2InputWidgetMixin = (superclass) =>
{ {
class Et2InputWidgetClass extends Et2Widget(superclass) implements et2_IInput, et2_IInputNode class Et2InputWidgetClass extends Et2Widget(superclass) implements et2_IInput, et2_IInputNode, et2_ISubmitListener
{ {
protected _oldValue : string | number | Object; protected _oldValue : string | number | Object;
protected node : HTMLElement; protected node : HTMLElement;
@ -66,6 +67,11 @@ const Et2InputWidgetMixin = (superclass) =>
type: Boolean type: Boolean
}, },
needed: {
type: Boolean,
reflect: true
},
onchange: { onchange: {
type: Function type: Function
}, },
@ -83,6 +89,32 @@ const Et2InputWidgetMixin = (superclass) =>
this.node = this.getInputNode(); this.node = this.getInputNode();
} }
/**
* A property has changed, and we want to make adjustments to other things
* based on that
*
* @param {import('@lion/core').PropertyValues } changedProperties
*/
updated(changedProperties : PropertyValues)
{
super.updated(changedProperties);
// Needed changed, add / remove validator
if(changedProperties.has('needed'))
{
// Remove class
this.classList.remove("et2_required")
// Remove all existing Required validators (avoids duplicates)
this.validators = (this.validators || []).filter((validator) => validator instanceof Required)
this.setAttribute("required", this.needed);
if(this.needed)
{
this.validators.push(new Required());
this.classList.add("et2_required");
}
}
}
/** /**
* Change handler calling custom handler set via onchange attribute * Change handler calling custom handler set via onchange attribute
* *
@ -169,12 +201,19 @@ const Et2InputWidgetMixin = (superclass) =>
this._oldValue = this.getValue(); this._oldValue = this.getValue();
} }
/**
* Used by etemplate2 to determine if we can submit or not
*
* @param messages
* @returns {boolean}
*/
isValid(messages) isValid(messages)
{ {
var ok = true; var ok = true;
debugger;
// Check for required // Check for required
if(this.options && this.options.needed && !this.options.readonly && !this.disabled && if(this.needed && !this.readonly && !this.disabled &&
(this.getValue() == null || this.getValue().valueOf() == '')) (this.getValue() == null || this.getValue().valueOf() == ''))
{ {
messages.push(this.egw().lang('Field must not be empty !!!')); messages.push(this.egw().lang('Field must not be empty !!!'));
@ -215,6 +254,29 @@ const Et2InputWidgetMixin = (superclass) =>
// it won't show up without validate() // it won't show up without validate()
this.validate(); this.validate();
} }
/**
* Called whenever the template gets submitted. We return false if the widget
* is not valid, which cancels the submission.
*
* @param _values contains the values which will be sent to the server.
* Listeners may change these values before they get submitted.
*/
async submit(_values) : Promise<boolean>
{
this.submitted = true;
// If using Lion validators, run them now
if(this.validate)
{
// Force update now
this.validate(true);
await this.validateComplete;
return (this.hasFeedbackFor || []).indexOf("error") == -1;
}
return true;
}
} }
return Et2InputWidgetClass; return Et2InputWidgetClass;

View File

@ -1156,6 +1156,17 @@ export function loadWebComponent(_nodeName : string, _template_node, parent : Et
return widget; return widget;
} }
/**
* Take attributes from a node in a .xet file and apply those to a WebComponent widget
*
* Any attributes provided that match a property (or attribute) on the widget will be adjusted according to
* the passed arrayManager, coerced into the proper type, and set.
* It is here that we find values or set attributes that should come from content.
*
* @param widget
* @param {et2_arrayMgr} mgr
* @param attributes
*/
function transformAttributes(widget, mgr : et2_arrayMgr, attributes) function transformAttributes(widget, mgr : et2_arrayMgr, attributes)
{ {

View File

@ -2,8 +2,7 @@ import {ResultValidator} from "@lion/form-core";
/** /**
* Manual validator for server-side validation messages passed * Manual validator for server-side validation messages passed
* from Etemplate. It is always "activated", and just gives whatever * from Etemplate. It just gives whatever message is passed in when created.
* message is passed in when created.
* *
*/ */
export class ManualMessage extends ResultValidator export class ManualMessage extends ResultValidator

View File

@ -0,0 +1,14 @@
import {Required as LionRequired} from "@lion/form-core";
export class Required extends LionRequired
{
/**
* Give a message about this field being required. Could be customised according to MessageData.
* @param {MessageData | undefined} data
* @returns {Promise<string>}
*/
static async getMessage(data)
{
return data.formControl.egw().lang("Field must not be empty !!!");
}
}

View File

@ -158,7 +158,7 @@ export interface et2_ISubmitListener
* @param _values contains the values which will be sent to the server. * @param _values contains the values which will be sent to the server.
* Listeners may change these values before they get submitted. * Listeners may change these values before they get submitted.
*/ */
submit(_values) : void submit(_values) : boolean | Promise<boolean>
} }
export const et2_ISubmitListener = "et2_ISubmitListener"; export const et2_ISubmitListener = "et2_ISubmitListener";
et2_implements_registry.et2_ISubmitListener = function(obj : et2_widget) et2_implements_registry.et2_ISubmitListener = function(obj : et2_widget)

View File

@ -905,9 +905,9 @@ export class etemplate2
* *
* @param container * @param container
* @param values * @param values
* @return et2_widget|null first invalid widget or null, if all are valid * @return Promise<et2_widget>|Promise<Et2Widget>|null
*/ */
isInvalid(container : et2_container | undefined, values : object | undefined) : et2_widget | null async isInvalid(container : et2_container | undefined, values : object | undefined) : Promise<et2_ISubmitListener> | null
{ {
if(typeof container === 'undefined') if(typeof container === 'undefined')
{ {
@ -917,19 +917,27 @@ export class etemplate2
{ {
values = this.getValues(container); values = this.getValues(container);
} }
let invalid = null; let invalid = [];
container.iterateOver(function(_widget) container.iterateOver(function(_widget)
{ {
if(_widget.submit(values) === false) let submit = _widget.submit(values);
if(submit === false)
{ {
if(!invalid && !_widget.isValid([])) if(!invalid && !_widget.isValid([]))
{ {
invalid = _widget; invalid.push(_widget);
} }
} }
else if(submit instanceof Promise)
{
invalid.push(submit.then(function(sub)
{
return sub ? false : this;
}.bind(_widget)));
}
}, this, et2_ISubmitListener); }, this, et2_ISubmitListener);
return invalid; return Promise.all(invalid);
} }
/** /**
@ -962,8 +970,27 @@ export class etemplate2
{ {
canSubmit = !(invalid = this.isInvalid(container, values)); canSubmit = !(invalid = this.isInvalid(container, values));
} }
invalid.then((widgets) =>
{
let invalid_widgets = widgets.filter((widget) => widget);
if(canSubmit) if(invalid_widgets.length)
{
// Show the first invalid widget, not the last
if(invalid_widgets[0] && invalid_widgets[0] instanceof et2_widget)
{
let messages = [];
let valid = invalid.isValid(messages);
invalid.set_validation_error(messages);
}
}
else
{
doSubmit();
}
});
let doSubmit = function()
{ {
if(typeof async == 'undefined' || typeof async == 'string') if(typeof async == 'undefined' || typeof async == 'string')
{ {
@ -996,14 +1023,9 @@ export class etemplate2
{ {
this._widgetContainer.egw().debug("warn", "Missing menuaction for submit. Values: ", values); this._widgetContainer.egw().debug("warn", "Missing menuaction for submit. Values: ", values);
} }
} }.bind(this);
else if(invalid !== null)
{
// Show the first invalid widget, not the last
let messages = [];
let valid = invalid.isValid(messages);
invalid.set_validation_error(messages);
}
return canSubmit; return canSubmit;
} }