@fto-consult/expo-ui
Version:
Bibliothèque de composants UI Expo,react-native
1,161 lines (1,143 loc) • 50.8 kB
JavaScript
import PropTypes from "prop-types";
import KeyboardEventHandler from "../KeyboardEventHandler";
import { addMediaQueryUpdateStyeSubscription } from "$econtext/hooks";
import Dimensions from "$cdimensions";
const {getActions,getFormFields,Forms} = require("../utils")
import TextField,{parseDecimal} from "$ecomponents/TextField";
import Icon from "$ecomponents/Icon";
import {extendObj,isBool,isUndefined,uniqid,isValidDataFileName,isValidEmail,defaultObj,isObj,defaultFunc,isFunction,isNumber,arrayValueExists,defaultVal,defaultStr,isNonNullString,defaultBool,defaultDecimal} from "$cutils";
import {Component as AppComponent} from "$react";
import {observable,addObserver} from "$observable";
import {isValidPhoneNumber} from "$ecomponents/PhoneInput";
import {Validator} from "$validator";
import theme,{grid} from "$theme";
import React from "$react";
import {StyleSheet} from "react-native";
import {isDevEnv} from "$cplatform";
import {isMobileMedia} from "$cplatform/dimensions";
import APP from "$capp/instance";
import { prepareState as preparePhoneInput } from "$ecomponents/PhoneInput";
///la combinaison à appliquer pour modifier le contenu textuel de la valeur
let sanitizeKeyEvent = 'ctrl+m' //le type hashtag
import defaultKeyboardEvents from "../utils/keyboardEvents";
import sprintf from "./sprintf";
import ErrorMessage from "$ecomponents/ErrorBoundary/ErrorMessage";
import { UPPER_CASE, LOWER_CASE} from "$common/lib/validator";
import Label from "$ecomponents/Label";
import stableHash from "stable-hash";
export default class Field extends AppComponent {
constructor(props) {
super(props);
observable(this);
addObserver(this);
extendObj(this._events,{
validatorBeforeValidate : this.validatorBeforeValidate.bind(this),
validatorValid : this.onValidate.bind(this),
validatorNoValid : this.onNoValidate.bind(this)
})
let {
name,
field,
formName,
beforeValidate,
onValidate,
onNoValidate,
validType,
validRule,
length,
minLength,
required,
renderfilter,
render_filter,
maxLength,
validParams,
jsType,
type,
} = props;
Object.defineProperties(this,{
type : {value : defaultStr(jsType,type,"text").trim().toLowerCase()},
isEditableSymbol : {value : Symbol('isEditableSymbol'),override:false,writable:false},
///pour rendre le champ enabled à partir du symbol
forceEnableSymbol : {value : Symbol('enableSymbol'),override:false,writable:false},
isEnabled : {
value : ()=>{
return !this.isDisabled();
},override : false, writable : false
},
isFilter : {
value : ()=>defaultVal(renderfilter,render_filter,this.props.renderfilter,this.props._render_filter) ? true : false,override : false,writable : false,
},
isEditableBySymbol : {
value : ()=>{
return typeof this[this.isEditableSymbol] ==='boolean'? this[this.isEditableSymbol] : true;
}
},
isEnabledBySymbol : {
value : ()=>{
return !!this[this.forceEnableSymbol];
}
},
isEditable : {
value : ()=>{
return this.isEditableBySymbol() && this.state.isFieldEditable && !this.state.isReadOnlyOrDisabled && true || false;
}
},
forceEnableBySymbol : {
value : (toggle)=>{
if(typeof toggle =='boolean'){
this[this.forceEnableSymbol] = toggle;
}
}
},
isReadOnly : {
value : ()=>{
return !!this.state.isReadOnlyOrDisabled || !this.isEditableBySymbol();
},override : false, writable : false
},
isDisabled : {
value : ()=>{
return !!this.state.isReadOnlyOrDisabled || !this.isEditableBySymbol();
}, override : false,writable : false
}
/*** désactive le champ */
,disable : {
value : ()=>{
this.setState({isReadOnlyOrDisabled:true,sk:!this.state.sk})
}, override : false, writable : false
}
/**** active le champ */
,enable : {
value : () => {
this.forceEnableBySymbol(true);
this.setState({isReadOnlyOrDisabled:false,sk:!this.state.sk});
}, override : false, writable : false
},
show : {
value : () => {
this.setState({isFieldVisible:true,sk:!this.state.sk})
}, override : false, writable : false
},
hide : {
value : () => {
this.setState({isFieldVisible:false,sk:!this.state.sk})
}, override : false, writable : false
},
isVisible : {
value : () => {
return this.state.isFieldVisible;
}, override : false, writable : false
},
isHidden : {
value : () => {
return !this.state.isFieldVisible;
}, override : false, writable : false
},
/** si la valeur valide à retourner par le field est de type decimal */
canValueBeDecimal : {
value : this.isTextField() && arrayValueExists(['number','decimal'],this.type)
,override : false, writable : false
},
wrapperRef : {
value : React.createRef(null),
},
})
name = defaultStr(name,field,uniqid("form-data-field-name"));
Object.defineProperties(this,{
INITIAL_STATE : {
value : {formName},
override : false,
writable : false
}
})
validType = defaultVal(validType,validRule);
if(required && (isNonNullString(validType) || validType == null || validType ==undefined || validType === "")){
validType = isNonNullString(validType)?validType : '';
validType = validType.contains("required")? validType : ('required|'+validType)
}
if(isNonNullString(validType) || validType == "" || validType == null || validType ==undefined){
if(!isNonNullString(validType)) validType = "";
if(isNumber(length) && length>0){
if(validType.indexOf('length') === -1){
validType="length["+length+"]"+"|"+validType.trim().ltrim("|");
}
maxLength = length;
}
if(isNumber(minLength) && minLength > 0){
if(validType.indexOf('minLength') === -1){
validType+="|minLength["+minLength+"]"
}
}
if(isNumber(maxLength) && maxLength>0){
if(validType.indexOf('maxLength') === -1){
validType+="|maxLength["+maxLength+"]"
}
}
if(this.props.upper === true || this.props.upperCase === true){
validType +="|"+UPPER_CASE;
} else if(this.props.lower === true || this.props.lowerCase === true){
validType +="|"+LOWER_CASE;
}
}
this.INITIAL_STATE.validType = this.INITIAL_STATE.validRule = validType;
this.INITIAL_STATE.validParams = validParams;
if(!this.isValidRuleDynamic()){
Object.defineProperties(this.INITIAL_STATE,{
validType : {value:validType,override:false},
validRule : {value:validType,override:false},
validParams : {value:validParams,override:false},
})
}
Object.defineProperties(this,{
name : {value:name,override:false,writable:false},
formName : {value:formName,override:false,writable : false},
})
if(this.canRegisterField()){
Forms.trigger("registerField",name,formName,this);
this.initObservers();
}
this.state.errorText = null;
this.state.error = false;
this.autobind();
this.keybaordEvents = [sanitizeKeyEvent];
Object.map(this.props.keyboardEvents,(k,i)=>{
if(!defaultKeyboardEvents[k]){
this.keybaordEvents.push(k);
}
})
this.state.validatingValue = this.validatingValue = defaultVal(this.props.defaultValue);
this.keybaordEvents = [...Object.keys(defaultKeyboardEvents),...this.keybaordEvents]
this.state.isMobile = isMobileMedia();
this.state.isReadOnlyOrDisabled = false;
this.state.isFieldEditable = true;
this.state.isFieldVisible = typeof this.props.visible =='boolean'? this.props.visible : true;
this.state.wrapperStyle = this.getMediaQueryUpdateStyle();
this.state.currentMedia = Dimensions.getDimensionsProps().currentMedia;
}
canRegisterField(){
return !this.isFilter() && this.canValidate();
}
canValidate(){
return this.props.validate !== false;
}
validatorBeforeValidate({value,validRule,validParams,event,...rest}){
let _result = undefined;
this.trigger.call(this,"beforeValidate",{...defaultObj(rest),value,name:this.name,field:this.name,formName:this.formName,context:this,event,validRule,validParams},(result)=>{
for(var r in result){
if(result[r] === false) {
_result = false;
return context;
}
}
typeof this.props.beforeValidate=="function" && this.props.beforeValidate.call(this,{...defaultObj(rest),value,name:this.name,field:this.name,formName:this.formName,context:this,event,validRule,validParams});
});
return _result;
}
/*** si le composant devra prendre en compte les props width et height */
canHandleWidthOrHeightProps(){
return false;
}
callOnChange(args){
args.isFilter = this.isFilter();
if(typeof this.props.onChange === "function" && this.hasValueChanged(args.value)){
this.props.onChange(args);
}
}
onValidate ({value,event,...rest}){
this.trigger.call(this,"validate",{...defaultObj(rest),value,event},(result)=>{
for(var j in result){
if(result[j] === false) return this;
}
this.validatingValue = value;
this.INITIAL_STATE._lastValidatingValue = value;
this.setState ({validValue:value,previousValidatedValue:this.state.validValue,validatingValue:value,previousValue:this.state.validValue,errorText:"",error:false},()=>{
const fields = getFormFields(this.formName);
let canEnable = true;
for(var k in fields){
if(k === this.getName()) continue;
const fK = fields[k];
const matchF = fK.getMatchField();
if(matchF && matchF.getName() === this.getName()){
fK.validate({value:fK.getValue()});
}
if(!fK.isValid()){
canEnable = false;
break;
}
}
const actions = getActions(this.formName);
const action = canEnable?"enable":"disable";
for(var k in actions){
actions[k][action]();
}
const form = Forms.getForm(this.formName);
typeof this.props.onValidate=="function" && this.props.onValidate.call(this,{...defaultObj(rest),isFilter:this.isFilter(),props:this.props,formName:this.formName,form,name:this.name,field:this.name,value,event,context:this});
this.callOnChange({value,event,isValid:true,...rest});
if(form && form.props){
if(canEnable){
const vOpts = {...defaultObj(rest),formName:this.formName,data:form.getData(),context:form,fieldInstance:this,field:this.name,name:this.name,value,event,form};
form.onValidate.call(form,vOpts);
if(isFunction(form.props.onValidate)){
form.props.onValidate.call(form,vOpts)
}
}
if(isFunction(form.props.onValidateField)){
form.props.onValidateField.call(form,{...defaultObj(rest),formName:this.formName,context:this,form,name:this.name,field:this.name,value,form,event})
}
}
});
});
}
getMatchField (){
const matchField = defaultStr(this.props.matchField).trim();
return matchField && this.getField(matchField) || null;
}
validateMatchField (value){
if(this.isFilter()) return true;
const matchedField = this.getMatchField();
if(matchedField){
value = value !== undefined ? value : this.getValue();
const matchedValue = matchedField.getValue();
if(!React.isEquals(matchedValue,value)){
return `le champ [${matchedField?.getLabel()}] doit avoir la même valeur que celle du champ [${this.getLabel()}].`;
}
}
return true;
}
onValidatorValid(args){
const valMatchField = this.validateMatchField();
if(isNonNullString(valMatchField)){
return valMatchField;
}
if(!this.isFilter()){
const vRule =defaultStr(this.getValidRule()).toLowerCase();
const value = typeof args.value == "undefined" || args.value == null ? "" : String(args.value).replaceAll("/","").replaceAll("\\",'');
if(value){
if(this.type ==='email' || vRule.contains('email')){
if(!isValidEmail(value)){
return "Veuillez saisir une addresse email valide";
}
} else if(this.type ==="tel" || this.type =="phone"){
const nState = preparePhoneInput({...args,defaultValue:args.displayValue||args.value});
if(!isValidPhoneNumber(nState?.displayValue)){
return "Merci d'entrer un numéro de téléphone valide";
}
} else if(((this.props.allowWhiteSpaces === false) || ((this.type ==='id' || this.type =='piece') && this.props.allowWhiteSpaces !== true))){
if((value.contains(" ") || !isValidDataFileName(value.replaceAll("@","").replaceAll(".","")))){
return "Veuillez renseigner une valeur ne contenant pas d'espace ou de caractère accentués";
}
}
}
}
if(isFunction(this.props.onValidatorValid)){
return this.props.onValidatorValid(args);
}
return true;
}
onNoValidate({msg,message,value,context,validRule,validParams,event,...rest}){
this.validatingValue = value;
msg = msg || message;
this.trigger.call(this,"noValidate",{...defaultObj(rest),props:this.props,context:this,msg,value,event,validRule,validParams},(result)=>{
this.setState({
errorText : msg,
invalidValue : value,
validatingValue : value,
error : true
},()=>{
const actions = getActions(this.formName);
for(var k in actions){
actions[k].disable();
}
typeof this.props.onNoValidate=="function" && this.props.onNoValidate.call(this,{...defaultObj(rest),isFilter:this.isFilter(),props:this.props,msg,value,formName:this.formName,field:this.name,name:this.name,context:this,event,validRule,validType:validRule,validParams,context:this});
let form = Forms.getForm(this.formName);
this.callOnChange({value,validRule,validParams,event,isValid:false,...rest});
if(form){
const vOpts = {...defaultObj(rest),formName:this.formName,fieldInstance:this,name:this.name,field:this.name,value,msg,validRule,validParams,event,context:form};
if(typeof form?.onNoValidate ==="function"){
form.onNoValidate.call(form,vOpts);
}
if(form.props && isFunction(form.props.onNoValidate)){
form.props.onNoValidate.call(form,vOpts);
}
}
})
});
}
canRenderInlineIndicator(){
return true;
}
initObservers (){
for(let i in this._events){
this.on(i,this._events[i]);
}
}
/*** retourne l'instance de la form */
getForm(){
return Forms.getForm(this.formName);
}
///retourne l'instance d'un champ autre que celui actuel
/// si le fieldName n'est pas définit alors on retourne l'instance du field actuel
getField (fieldName){
if(!isNonNullString(fieldName)) return this;
let form = this.getForm();
if(form && isFunction(form.getField)){
return form.getField(fieldName);
}
return null;
}
setRef (el){
if(el) this._fieldRef = el;
}
isFocused(){
const field = this.getFieldRef();
if(isObj(field) && typeof field.isFocused =="function"){
return field.isFocused();
}
return false;
}
getFieldRef (){
return this.___formattedField || this._fieldRef;
}
onRegister(field,name){}
getName (){
return this.name;
}
getLabel (){
return defaultStr(this.props.label,this.props.text,this.props.title,this.name);
}
getFormName(){
return this.formName;
}
getOldValue () {
return this.state.previousValidatedValue;
}
/*** la valeur qui a été validée
* @param : l'ensemble des données du formulaire à retourner
* Ce paramètre est utile pour étendre les données validées du formulaire avec certaines données qui ne
* sont pas par défaut définies comme champ de formulaire
*/
parseDecimal(v){
return parseDecimal(v,this.type);
}
getInvalidValue(){
return this.state.invalidValue;
}
getValidValue (data){
let v = this.state.validValue;
if(isFunction(this.props.getValidValue)){
let v1 = this.props.getValidValue.call(this,{data,context:this,value:v,validValue:v,state:this.state,props:this.props});
if(v1 !== undefined){
v = v1;
}
}
return this.parseDecimal(v);
}
canFocus(){
return true;
}
getComponentProps (props){
return defaultObj(props,this.props);
}
isFocused(){
let f = this.getFieldRef();
if(!f) return undefined;
if(typeof f.isFocused ==='function') return f.isFocused();
return undefined;
}
/*** pour focus le champ
* Ajout de la méthode focus, aux FormField pour focus le champ en cas de changement
* @return : vrai si l'élément a été focus et faux au cas contraire
*/
focus (activate){
if(this.isDisabled() || this.isReadOnly() || !this.canFocus()){
return false;
}
let f = this.getFieldRef();
if(f && activate !== false){
if(typeof f.isFocused ==='function' && f.isFocused()) return true;
if(typeof f.focus ==='function'){
try {
f.focus();
return true;
} catch(e){
if(isDevEnv() )console.log(e," focusing field ",this.name)
}
}
return false;
}
return false;
}
/*** retourne le prochain élement et le suivant au field dans l'ensemble des fields de la form
* si la valeur du prochainFormField est égale au précédent
* @return ({keys,count:keys.length,fields,first,last,next,previous})
*/
getPrev2NextFields(){
let form = this.getForm();
let next = null,nextIndex=-1,index=-1,previousIndex=-1,first,last = null,previous = null,keys=[],fields={};
if(form && isFunction(form.getFields) ){
fields = form.getFields();
if(isObj(fields)){
keys = Object.keys(fields);
first = fields[keys[0]];
last = fields[keys[keys.length-1]];
for(let f in fields){
index++;
if(fields[f] === this){
break;
}
}
if(index>= 0){
previousIndex = index-1
nextIndex = index+1;
previous = fields[keys[previousIndex]];
next = fields[keys[nextIndex]]
}
}
}
return {index,keys,count:keys.length,fields,nextIndex,previousIndex,first,last,next,previous}
}
setValue (val,focus,rest){
if(isObj(focus)){
let t = rest;
rest = focus;
focus = t;
}
if(typeof focus !== 'boolean'){
focus = false;
}
if(val == undefined || val =='undefined'){
val = '';
}
this.validate({...defaultObj(rest),value:val});
return val;
}
getValue (){
return this.validatingValue;
}
getDefaultValue (){
return defaultVal(this.getComponentProps(this.props).defaultValue,this.props.defaultValue);
}
isSelectField(){
return false;
}
getPreviousValue (){
return this.state.previousValidatedValue;
}
hasValueChanged(value){
return (stableHash(this.state.validatingValue) === stableHash(value))? false : true;
}
isValidRuleDynamic(){
return false;
}
setValidRule(validRule){
if(this.isValidRuleDynamic()){
this.INITIAL_STATE.validRule = this.INITIAL_STATE.validType = validRule;
}
}
getValidRule(){
return this.INITIAL_STATE.validRule;
}
validate ({value,event,...rest}){
value = this.parseDecimal(value);
if(!this.hasValueChanged(value) && (this.isFilter()? true : this.__hasAlreadyValidated)){
return;
}
this.__hasAlreadyValidated = true;
this.validatingValue = value;
if(((!this.canValidate()) && !this.isSelectField()) || this.isFilter()){
this.trigger("validate",{...defaultObj(rest),context:this,value,event,oldValue:this.INITIAL_STATE._lastValidatingValue},(results)=>{
this.setState({validValue:value,previousValidatedValue:this.state.validValue,validatingValue:value,sk:!this.state.sk,previousValue:this.state.validatingValue},()=>{
if(isFunction(this.props.onValidate)){
this.props.onValidate({...defaultObj(rest),props:this.props,name:this.name,field:this.name,value,event,context:this,oldValue:this.INITIAL_STATE._lastValidatingValue});
}
if(isFunction(this.props.onChange)){
this.props.onChange({value,previousValue:this.INITIAL_STATE._lastValidatingValue,event});
}
this.INITIAL_STATE._lastValidatingValue = value;
})
});
return;
}
const validRule = this.getValidRule();
if(this.isValidRuleDynamic()){
this.INITIAL_STATE.validRule = this.INITIAL_STATE.validType = validRule;
}
Validator.validate({...rest,onValidatorValid:this.onValidatorValid.bind(this),context:this,value,validRule,validType:validRule,validParams:this.INITIAL_STATE.validParams,event})
}
/**** met le focus sur l'élément précédent */
focusPrevField (){
return;
let ob = this.getPrev2NextFields();
let {previous} = ob;
let focus = false;
let canGoToNext = true;
while(canGoToNext && previous){
focus = previous.focus();
canGoToNext = previous && !focus;
if(canGoToNext){
ob = previous.getPrev2NextFields();
previous = ob.previous;
}
if(focus){
this.trigger("blur");
}
}
return ob;
}
sprintf(){
if(this.isDisabled() || this.isReadOnly()) return this;
sprintf({context:this,value:this.getValue(),formatter:this.props.sprintfResultFormatter}).then((val)=>{
this.setValue(APP.sprintf(val),true);
});
}
/**** met le focus sur le prochain cham */
focusNextField (){
return;
let ob = this.getPrev2NextFields();
let {next} = ob;
let focus = false;
let canGoToNext = true;
while(canGoToNext && next){
focus = next.focus();
canGoToNext = next && !focus;
if(canGoToNext){
ob = next.getPrev2NextFields();
next = ob.next;
}
if(focus){
this.trigger("blur");
}
}
return ob;
}
componentDidMount (validate){
super.componentDidMount();
this.mediaQueryUpdateStyleSubscription = addMediaQueryUpdateStyeSubscription(this.doUpdateMediaQueryStyle.bind(this));
if(this.canRegisterField()){
Forms.trigger("registerField",this.getName(),this.getFormName(),this);
}
if(!this.isFilter() && defaultVal(validate,true) && this.props.validateOnMount !== false) {
this.validate({context:this,value:defaultVal(this.props.defaultValue)})
}
}
componentWillUnmount() {
super.componentWillUnmount();
this._fieldRef = undefined;
this.offAll();
this.clearEvents();
if(this.mediaQueryUpdateStyleSubscription && this.mediaQueryUpdateStyleSubscription?.remove){
this.mediaQueryUpdateStyleSubscription.remove();
}
if(this.canRegisterField()){
Forms.trigger("unregisterField",this.getName(),this.getFormName());
}
}
isHtml(){
return false;
}
canHandleMediaQueryUpdate(){
return !this.isFilter() && this.props.responsive !== false;
}
doUpdateMediaQueryStyle(args){
if(!this.canHandleMediaQueryUpdate()) return;
const wrapperStyle = this.getMediaQueryUpdateStyle(args);
this.setState({isMobile:args.isMobile,wrapperStyle});
}
getMediaQueryUpdateStyle(args){
if(!this.canHandleMediaQueryUpdate()) return null;
if(!isObj(args)){
args = Dimensions.getDimensionsProps();
}
const style2 = typeof this.props.mediaQueryUpdateStyle =='function'? this.props.mediaQueryUpdateStyle(args) : null;
return StyleSheet.flatten([grid.col(this.props.windowWidth),style2]);
}
onKeyEvent(key,event){
let form = this.getForm();
if(!this.canValidate()) return;
let {keyboardEvents,onKeyEvent} = this.props;
const formInstance = this.getForm();
const data = formInstance ? formInstance.getData() : {};
const arg = {key,event,formInstance,form:formInstance,field:this.name,formName:this.getFormName(),value:this.getValidRule(),validValue:this.getValidValue(data),data,context:this,isFormField:true,formInstance};
let handler = undefined;
if(isObj(keyboardEvents)){
handler = keyboardEvents[key];
if(isFunction(handler) && handler.call(this,arg) === false) return;
}
if(!isFunction(handler)){
if(isFunction(keyboardEvents)){
handler = keyboardEvents;
} else if(isFunction(onKeyEvent)){
handler = onKeyEvent;
}
if(isFunction(handler) && handler.call(this,arg) === false) return;
}
handler = undefined;
if(formInstance && isObj(formInstance.props)){
const formInstanceProps = formInstance.props;
if(key =='enter' && isFunction(formInstanceProps.onEnterKeyPress) && formInstance.isValid()){
if(formInstanceProps.onEnterKeyPress.call(this,arg) === false) return;
}
if(isObj(formInstanceProps.keyboardEvents)){
handler = formInstanceProps.keyboardEvents[key];
if(isFunction(handler)) handler.call(this,arg);
}
if(!isFunction(handler)){
if(isFunction(formInstanceProps.keyboardEvents)){
handler = formInstanceProps.keyboardEvents;
} if(isFunction(formInstanceProps.onKeyEvent)){
handler = formInstanceProps.onKeyEvent;
}
if(isFunction(handler)) handler.call(this,arg);
}
}
switch(key){
case 'down':
this.focusNextField();
break;
case 'left':
this.focusPrevField();
break;
case 'right':
this.focusNextField();
break;
case 'up':
this.focusPrevField();
break;
case 'enter' :
if(!form) return this;
break;
let {next,nextIndex,count} = this.focusNextField();
if(!next){
/**** on a atteind le dernier elément, l'on doit demander à l'enregistrement du formulaire */
if(nextIndex >= count){
let fields = form.getFields();
let canSubmit = true;
if(isObj(fields)){
for(var j in fields){
let field = fields[j];
if(field && !field.isValid()){
canSubmit = false;
///on met le focus sur le prochain élément non valide
if(field.focus()){
break;
}
}
}
}
if(canSubmit && form.props && form.props.onSubmit) {
//le formulaire peut être envoyé maintenant
form.props.onSubmit.call(form,{data:form.getData(),context:form,currentField:this})
}
}
}
break;
default :
if(key === sanitizeKeyEvent){
this.sprintf();
return false;
}
break;
}
}
canBindResizeEvent(){
return true;
}
/*** appelée pour la mise à jour du layout state */
getLayoutState(){}
updateLayout(event){
if(!this.canBindResizeEvent()) return;
}
isValid (){
return !this.state.error ? true : false;
}
/*** cette méthode a été ajouté pour qu'à partir de certains composant Field, l'on puisse apporter des modification
* aux props immédiatement avant qu'elles soient passé au composant requis
*/
removeNotAllowedProps(props){
return props;
}
isTextField(){
return true;
}
onBlurField(event){
if(isFunction(this.props.onBlur) && this.props.onBlur({event,value:this.getValue(),context:this}) === false){
return;
}
if(this.isFilter()) return
if(this.getMatchField())
if(isNonNullString(this.props.fieldToPopulateOnBlur) && isNonNullString(value)){
const context = this.getField(this.props.fieldToPopulateOnBlur.trim());
if(context && context.getValue){
const cVal = defaultStr(context.getValue());
if(cVal.length < value.length){
context.setValue(value);
}
}
}
}
onFocusField(event){
if(isFunction(this.props.onFocus)){
this.props.onFocus({event,context:this})
}
}
_render (props){
const {defaultValue} = props;
return <TextField
{...props}
value = {defaultValue}
/>
}
getCallFuncArgs(){
let data = defaultObj(this.props.data);
return {
context:this,
windowResized:APP.windowHasResized,
field:this.name,
name:this.name,
...this.props,
data,
props:this.props
}
}
UNSAFE_componentWillReceiveProps(nextProps,b,c){
if(super.UNSAFE_componentWillReceiveProps && super.UNSAFE_componentWillReceiveProps(nextProps,b,c) === false) return;
if("defaultValue" in nextProps){
const value = this.parseDecimal(nextProps.defaultValue);
if(this.isValidRuleDynamic()){
this.setState({sk:!this.state.sk},()=>{
this.validate({value})
})
} else {
this.validate({value});
}
}
}
///si la props right peut être une fonction
canRightPropsBeFunction(){
//typeof customRight =='function' || _type =='date' || _type =='time' || _type =='text' || _type=='number' || _type =='decimal' || _type.contains('select')
return true;
}
getErrorText(includeLabel){
const label = defaultStr(this.label,this.name);
if(!isNonNullString(this.state.errorText)) return "";
if(label){
return this.state.errorText+(includeLabel !== false?(" ["+label+"]"):"");
} else {
return this.state.errorText;
}
}
getHashTagTooltip(){
return "Cliquer insérer un hashtag.\nle motif &sdate& permet de sélectionner une date; le motif &date& insère la date actuelle au format : jj/mm/aaaa; Le motif &heure& insère l'heure actuelle; Le motif &dateheure& insère l'heure actuelle au format jj/mm/aaaa hh:mm:ss.";
}
render (){
let {
data,onKeyEvent,
keyboardEvents,
required,
validRule,
validType,value,selected,label,text,
getProps,
formName,
datagrid,
form,
format,
validParams,
//renderfilter, //pour spécifier que c'est un rendu de type filtre
getValidValue,
validate,
onValidate,
onValidatorValid,///il s'agit de la fonction de rappel appelée immédiatement après que le validateur ait réuissie la validation
onValidateField,
onNoValidate,
disabled,
visible,
readOnly,
beforeValidate,
footer,
archived,
windowWidth,
dataFilesInterest,
title,
tooltip,
right : customRight,
responsive,
responsiveProps,
usePlaceholderWhenEmpty,
width,
height,
jsType,
fieldToPopulateOnBlur,
...rest
} = this.getComponentProps(this.props);
if(this.state.caughtAnError){
return <ErrorMessage
error={this.state.caughtError}
info={this.state.caughtInfo}
resetError={this.resetCatchedError.bind(this)}
/>
}
const isFilter = this.isFilter();
if(isFilter){
responsive = false;
}
rest = defaultObj(rest);
data = defaultObj(data);
this.label = label = defaultVal(label,text);
rest.name = this.name;
rest.label = label;
rest.data = data;
rest.validRule = rest.validType = this.INITIAL_STATE.validType;
rest.validParams = this.INITIAL_STATE.validParams;
if(this.state.isReadOnlyOrDisabled){
disabled = true;
readOnly = true;
rest.disabled = rest.readOnly = true;
rest.readOnly = true;
} else if(this.isEnabledBySymbol()){
disabled = readOnly = false;
readOnly = false;
rest.disabled = rest.readOnly = false;
rest.readOnly = false;
} else {
const callArgs = {context:this,field:this.name,name:this.name,value:this.validatingValue,validValue:this.state.validValue,...rest,data,props:this.props};
readOnly = defaultVal(readOnly);
if(isFunction(readOnly)){
readOnly = readOnly.call(this,callArgs);
}
if(!isUndefined(readOnly)) readOnly = readOnly?true:false;
else rest.readOnly = readOnly ? true : false;
if(isFunction(disabled)){
disabled = disabled.call(this,callArgs);
}
if(!isBool(disabled)) disabled = disabled ? true : false;
if(isFunction(archived)){
archived = archived.call(this,callArgs);
}
if(archived === true){
readOnly = false;
disabled = true;
}
rest.disabled = disabled;
rest.readOnly = readOnly;
if(disabled || readOnly){
this[this.isEditableSymbol] = false;
} else delete this[this.isEditableSymbol];
}
if(this.state.isFieldVisible){
if(isFunction(visible)){
visible = visible({field:this.name,name:this.name,...rest,value:this.validatingValue,data});
}
visible = typeof visible =='boolean'? visible : true;
} else{
visible = false;
}
rest.defaultValue = this.validatingValue;
rest.style = [{backgroundColor:'transparent'},rest.style,!visible && theme.styles.hidden,!visible?{display:'none',opacity:0}:undefined];
/**** si ce n'est pas un composant de type dropdown*/
if(!isFunction(rest.filter)){
delete rest.filter;
}
delete rest.export;
delete rest.colIndex;
delete rest.accordion;
delete rest._id;
delete rest.import;
delete rest.dataFilesInterest;
delete rest.archivable;
delete rest.archivable;
this.___formattedField = undefined;
let _type = this.type;
format = defaultStr(format);
tooltip = defaultVal(tooltip,title);
const isEditable = rest.disabled !== true && rest.readOnly !== true ? true : false;
const hasDefaultValue = isNonNullString(rest.defaultValue) || typeof rest.defaultValue =='number'? true : false;
const canChangeRight = this.isTextField() && !isEditable && hasDefaultValue;
if(canChangeRight){
rest.contentContainerProps = Object.assign({},rest.contentContainerProps);
rest.contentContainerProps.pointerEvents = defaultStr(rest.contentContainerProps.pointerEvents,"auto");
}
if(!isFilter && this.canRenderInlineIndicator()){
const canRenderHashtag = format === 'hashtag' && isEditable && (!isNonNullString(_type) || _type ==='text');
const renderRigth = (props)=>{
let right = null;
let cRight = typeof customRight ==='function'? customRight(props) : customRight;
if(typeof cRight ==='number' || typeof cRight =="string"){
cRight = <Label {...props}>{cRight}</Label>
}
if(!React.isValidElement(cRight)){
cRight = null;
}
if(tooltip){
right = <Icon color={theme.colors.primary} {...props} tooltip = {tooltip} style={[styles.icon,props.style]} icon="information"/>;
}
if(canRenderHashtag && !rest.disabled && !rest.readOnly){
const nRight = <Icon {...props} icon={"format-header-pound"} style={[right||cRight?{marginLeft:-5}:null,props.style]} title={this.getHashTagTooltip()} onPress={this.sprintf.bind(this)}/>;
right = right ? <>{right}{nRight}</> : nRight;
}
return (cRight)? <>{cRight}{right}</> : right;
};
rest.right = this.canRightPropsBeFunction() ? renderRigth : renderRigth({});
} else {
rest.right = customRight;
}
const hasWrapper = !isFilter ? true : false;
const wrapperProps = hasWrapper ? Object.assign({},responsiveProps) : {};
const visibleStyle = !visible && {display:"none",opacity:0};
if(hasWrapper){
const rP = {};
if(_type =='switch' || _type =='checkbox' || _type =='image'){
rP.justifyContent = 'center';
}
if(_type =='image'){
if(rest.displayLabel === false){
delete rest.label;
rP.alignItems = 'center';
rP.width = "100%";
}
}
wrapperProps.style = [{marginVertical:10},responsive !== false?grid.col(windowWidth):{width:'100%'},rP,wrapperProps.style,visibleStyle];
}
if(isFunction(getProps)){
rest = {...rest,...defaultObj(getProps.call(this,{...rest,context:this}))};
}
rest.context = this;
this.removeNotAllowedProps(rest,{formName:this.formName,context:this});
rest.setRef = this.setRef.bind(this);
rest.isFilter = isFilter;
rest.type = this.type;
if(format){
rest.format = format;
}
rest.onBlur = this.onBlurField.bind(this);
rest.autoComplete = "off";
rest.errorText = this.state.errorText;
rest.helperText = this.state.errorText;
rest.error = this.state.error;
rest.onFocus = this.onFocusField.bind(this);
rest.testID = defaultStr(rest.testID,"RN_FormField_"+this.getName());
rest.tooltip = tooltip;
if(this.canHandleWidthOrHeightProps()){
if(width){
rest.width = width;
}
if(height){
rest.height = height;
}
}
return <KeyboardEventHandler formFieldName={this.getName()} testID={'RN_FormFieldContainer_'+this.getName()} innerRef={this.wrapperRef}
{...wrapperProps}
handleKeys={this.keybaordEvents}
onKeyEvent = {this.onKeyEvent.bind(this)}
isDisabled = {rest.disabled}
mediaQueryUpdateStyle={null}
style = {[this.state.wrapperStyle,wrapperProps.style,visibleStyle]}
>
{(kProps)=>{
return this._render({...rest,...kProps},this.setRef.bind(this))
}}
</KeyboardEventHandler>
}
}
Field.propTypes = {
validateOnMount : PropTypes.oneOfType([
PropTypes.bool,//si la formField sera validée immédiatement au montage du composant
]),
left : PropTypes.oneOfType([
PropTypes.node,
PropTypes.func,
]),
right : PropTypes.oneOfType([
PropTypes.node,
PropTypes.func,
]),
/**** la fonction utilisée pour le formattage des valeurs de type sprintf */
sprintfResultFormatter : PropTypes.func,
jsType : PropTypes.string,
usePlaceholderWhenEmpty : PropTypes.bool,//si la valeur du placeholder sera utilée, lorsque la valeur du champ de type formatable est nulle ou égale à la valeur vide
responsive : PropTypes.bool,
responsiveProps : PropTypes.object,
///la valeur à retourner au cas où defaultValue est non définie pour la formField
_defaultValue : PropTypes.any,
/*** fonction de rappel, appélée immédiatement que la validation a réussie par le validateur.
* Si cette fonction retourne un message non null ou un objet contenant la prop msg ou message non null, alors celui-ci est considéré comme une erreur
*/
onValidatorValid : PropTypes.func,
/*** le format dans lequel sera rendu le contenu du textfield */
format : PropTypes.string,
//la liste d'évènements du clavier à écouter
/*** objet de la forme :
* {
* eventName : handler
avec eventName le nom de l'évènement et hanbler la fonction de rappel à appeler
* }
*/
keyboardEvents : PropTypes.oneOfType([
PropTypes.object,
PropTypes.func,
]),
onKeyEvent : PropTypes.func, /*** l'évènement global appelé lorsqu'on clique sur une touche */
onKeyEvent : PropTypes.func/*** l'évènement global appelé lorsqu'on clique sur une champ de la form */,
id: PropTypes.string,
/**** l'on peut décider que la field ait la possibilité d'exploiter le gestion de validateur ou pas
* dans ce cas, il suffit de renseigner la props validate à true ou à false
*/
validate : PropTypes.bool,
/***function utile pour définir dynamiquement
certaines props de la field;
* fonction appelée pour retourner les props à passer au composant
Elle prend en paramètre : les props passés au composant de la field, ainsi que l'objet data
et doit retourner un objet props final qui sera passé à la field
* cette fonction prend en paramètre :
* -1. l'objet props, représentant les props courant de la field
* -2. l'objet data, représentant les données actuelle passées à la field
* la valeur des props retournés doit être une fusions avec les props initiaux
*/
/*cette
*/
getProps : PropTypes.func, //cette fonction est utile pour permettre de retourner les props par défaut de la field
disabled : PropTypes.oneOfType([PropTypes.func,PropTypes.bool,PropTypes.string]),
readOnly : PropTypes.oneOfType([PropTypes.func,PropTypes.bool,PropTypes.string]),
visible : PropTypes.oneOfType([PropTypes.func,PropTypes.bool]),
text : PropTypes.string,
label : PropTypes.string,//le libelé du champ, idem à text
value : PropTypes.any,
/*** tous les champs doivent avoir un nom définit dans la prop name */
//name : PropTypes.string.isRequired,
defaultValue : PropTypes.any,
selected : PropTypes.any,
formName : PropTypes.string.isRequired,
name : PropTypes.oneOfType([PropTypes.string]).isRequired,
required : PropTypes.bool,
/*** cette fonction est appelée lorsque la field est validée */
onValidate : PropTypes.func,
/*** la propriété onChange prend en paramètre un callback, qui est prend en paramètre
la nouvelle valeur, et l'évènement qui est à l'origine
*/
onChange : PropTypes.func,
/*** le noeud de la text field à utiliser */
onNoValidate : PropTypes.func,
validParams : PropTypes.oneOfType([PropTypes.object,PropTypes.array]),
validType : PropTypes.oneOfType([
PropTypes.func,
PropTypes.string,
]),
validRule : PropTypes.oneOfType([
PropTypes.func,
PropTypes.string,
]),
required : PropTypes.oneOfType([
PropTypes.bool, //si c'est un boolean
]),
/*** cette props est définie par le composant Filter :
* Elle permet tout simplement de spécifier que le rendu sera un champ de filtre
* Lorsqu'elle est définie alors le rendu lors du composant doit être de type filter
*/
renderfilter : PropTypes.string,
matchField : PropTypes.string,//si cette valeur est définie, alors le champ figurant dans la valeur doit avoir la même valeur que le champ courant
/**** il s'agit d'un champ du même formulaire que la formField actuel, qui sera populated avec la valeur par défaut
* de la formField cournat loreque la valeur du champ en question a une longueur très inférieure à celle de la valeur de la form courante.
*/
fieldToPopulateOnBlur : PropTypes.string,
/*** cette fonction doit retourner l'instance de la field
elle doit toujours être définie dans la classe qui hérite directement au composant Field
*/
/////get