Subversion Repositories eFlore/Applications.cel

Rev

Rev 3877 | Blame | Compare with Previous | Last modification | View Log | RSS feed

import {WidgetsSaisiesCommun,utils} from './WidgetsSaisiesCommun.js';
import {valOk,tryParseJson} from './Utils.js';

/**
 * Constructeur WidgetSaisie par défaut
 */
function WidgetSaisie(  ) {
        if  ( valOk(widgetProp) ) {
                this.urlWidgets                        = widgetProp.urlWidgets;
                this.projet                            = widgetProp.projet;
                this.idProjet                          = widgetProp.idProjet;
                this.tagsMotsCles                      = widgetProp.tagsMotsCles;
                this.mode                              = widgetProp.mode;
                this.langue                            = widgetProp.langue;
                this.serviceAnnuaireIdUrl              = widgetProp.serviceAnnuaireIdUrl;
                this.serviceNomCommuneUrl              = widgetProp.serviceNomCommuneUrl;
                this.serviceNomCommuneUrlAlt           = widgetProp.serviceNomCommuneUrlAlt;
                this.debug                             = widgetProp.debug;
                this.html5                             = widgetProp.html5;
                this.serviceSaisieUrl                  = widgetProp.serviceSaisieUrl;
                this.serviceObsUrl                     = widgetProp.serviceObsUrl;
                this.chargementImageIconeUrl           = widgetProp.chargementImageIconeUrl;
                this.pasDePhotoIconeUrl                = widgetProp.pasDePhotoIconeUrl;
                this.autocompletionElementsNbre        = widgetProp.autocompletionElementsNbre;
                this.serviceAutocompletionNomSciUrl    = widgetProp.serviceAutocompletionNomSciUrl;
                this.serviceAutocompletionNomSciUrlTpl = widgetProp.serviceAutocompletionNomSciUrlTpl;
                this.dureeMessage                      = widgetProp.dureeMessage;
                this.obsMaxNbre                        = widgetProp.obsMaxNbre;
                this.tagImg                            = widgetProp.tagImg;
                this.tagObs                            = widgetProp.tagObs;
                this.obsId                             = widgetProp.obsId;
                this.nomSciReferentiel                 = widgetProp.nomSciReferentiel;
                this.especeImposee                     = widgetProp.especeImposee;
                this.infosEspeceImposee                = widgetProp.infosEspeceImposee;
                this.referentielImpose                 = widgetProp.referentielImpose;
                this.isTaxonListe                      = widgetProp.isTaxonListe;
                this.photoObligatoire                  = widgetProp.photoObligatoire;
        }
        this.urlRacine            = window.location.origin;
        this.obsNbre              = 0;
        this.nbObsEnCours         = 1;
        this.totalObsATransmettre = 0;
        this.nbObsTransmises      = 0;
        this.observer             = null;
        this.isASL                = false;
        this.geoloc               = {};
}
WidgetSaisie.prototype = new WidgetsSaisiesCommun();

/**
 * Initialise le formulaire, les validateurs, les listes de complétion...
 */
WidgetSaisie.prototype.initForm = function() {
        this.initFormConnection();
        if ( valOk( this.obsId ) ) {
                this.chargerInfoObs();
        }
        if( this.isTaxonListe ) {
                this.initFormTaxonListe();
        } else {
                this.ajouterAutocompletionNoms();
        }
        // au rafraichissement de la page,
        // les input date semblent conserver la valeur entrée précedemment
        // c'est voulu après la création d'une obs mais pas quand la page est actualisée
        // Déjà tenté: onbeforeunload avec un location.reload(true) n'a pas permis de le faire
        $( 'input[type=date]' ).each( function () {
                ( valOk( $( this ).data( 'default' ) ) ) ? $( this ).val( $( this ).data( 'default' ) ) : $( this ).val( '' );
        });
        this.configurerFormValidator();
        this.definirReglesFormValidator();

        if( this.especeImposee ) {
                $( '#taxon' ).attr( 'disabled', 'disabled' );
                $( '#taxon-input-groupe' ).attr( 'title', '' );
                // Bricolage cracra pour avoir le nom retenu avec auteur (nom_retenu.libelle ne le mentionne pas)
                const infosEspeceImposee = $.parseJSON( this.infosEspeceImposee );
                let nomRetenuComplet   = infosEspeceImposee.nom_retenu_complet;
                const debutAnneRefBiblio = nomRetenuComplet.indexOf( ' [' );

                if ( -1 !== debutAnneRefBiblio ) {
                        nomRetenuComplet = nomRetenuComplet.substr( 0, debutAnneRefBiblio );
                }
                // fin bricolage cracra
                const infosAssociee = {
                        label : infosEspeceImposee.nom_sci_complet,
                        value : infosEspeceImposee.nom_sci_complet,
                        nt : infosEspeceImposee.num_taxonomique,
                        nomSel : infosEspeceImposee.nom_sci,
                        nomSelComplet : infosEspeceImposee.nom_sci_complet,
                        numNomSel : infosEspeceImposee.id,
                        nomRet : nomRetenuComplet,
                        numNomRet : infosEspeceImposee['nom_retenu.id'],
                        famille : infosEspeceImposee.famille,
                        retenu : ( 'false' === infosEspeceImposee.retenu ) ? false : true
                };
                $( '#taxon' ).data( infosAssociee );
        }
};

/**
 * Initialise les écouteurs d'événements
 */
WidgetSaisie.prototype.initEvts = function() {
        // identité
        this.initEvtsConnection();
        // on location, initialisation de la géoloc
        this.initEvtsGeoloc();
        // Sur téléchargement image
        this.initEvtsFichier();

        $( '#referentiel' ).on( 'change', this.surChangementReferentiel.bind( this ) );
        // Création / Suppression / Transmission des obs
        // Défilement des miniatures dans le résumé obs
        this.initEvtsObs();
        // Alertes et aides
        this.initEvtsAlertes();
        // message avant de quitter le formulaire
        this.confirmerSortie();
};

// Identité Observateur par courriel
WidgetSaisie.prototype.requeterIdentiteCourriel = function() {
        const lthis = this,
                courriel    = $( '#courriel' ).val(),
                urlAnnuaire = this.serviceAnnuaireIdUrl + courriel;

        if ( valOk( courriel ) ) {
                $.ajax({
                        url : urlAnnuaire,
                        type : 'GET',
                        success : function( data, textStatus, jqXHR ) {
                                if ( lthis.debug ) {
                                        console.log( 'SUCCESS: ' + textStatus );
                                }
                                if ( valOk( data ) && valOk( data[courriel] ) ) {
                                        const infos = data[courriel];
                                        lthis.surSuccesCompletionCourriel( infos, courriel );
                                } else {
                                        lthis.surErreurCompletionCourriel();
                                }
                        },
                        error : function( jqXHR, textStatus, errorThrown ) {
                                if ( lthis.debug ) {
                                        console.log( 'ERREUR: '+ textStatus );
                                }
                                lthis.surErreurCompletionCourriel();
                        },
                        complete : function( jqXHR, textStatus ) {
                                if ( lthis.debug ) {
                                        console.log( 'COMPLETE: '+ textStatus );
                                }
                        }
                });
        }
};

// se déclanche quand on choisit "Observation sans inscription" mais que le mail entré est incrit à Tela
WidgetSaisie.prototype.surSuccesCompletionCourriel = function( infos, courriel ) {
        if ( $( '#utilisateur-connecte' ).hasClass( 'hidden' ) ) {// si quelque chose a foiré après actualisation
                if ( !valOk( $( '#warning-identite' ) ) ) {
                        $( '#zone-courriel' ).before( '<p id="warning-identite" class="warning"><i class="fas fa-exclamation-triangle"></i> ' + this.msgTraduction( 'courriel-connu' ) + '</p>' );
                }
                $( '#inscription, #zone-prenom-nom, #zone-courriel-confirmation' ).addClass( 'hidden' );
                $( '#prenom, #nom, #courriel_confirmation' ).attr( 'disabled', 'disabled' );
                $( '.nav.control-group' ).addClass( 'error' );
        }
};

// se déclanche quand on choisit "Observation sans inscription" et qu'effectivement le mail n'est pas connu de Tela
WidgetSaisie.prototype.surErreurCompletionCourriel = function() {
        $( '#creation-compte, #zone-prenom-nom, #zone-courriel-confirmation' ).removeClass( 'hidden' );
        $( '#warning-identite' ).remove();
        $( '.nav.control-group' ).removeClass( 'error' );
        $( '#prenom, #nom, #courriel_confirmation' ).val( '' ).removeAttr( 'disabled' );
};

WidgetSaisie.prototype.testerLancementRequeteIdentite = function( event ) {
        if ( valOk( event.which, true, 13 ) ) {
                this.requeterIdentiteCourriel();
                event.preventDefault();
                event.stopPropagation();
        }
};

WidgetSaisie.prototype.reduireVoletIdentite = function() {
        if ( $( '#form-observateur' ).valid() && $( '#courriel' ).valid() && $( '#courriel_confirmation' ).valid() ) {
                $( '#bouton-connexion, #creation-compte' ).addClass( 'hidden' );
                $( '#bienvenue').removeClass( 'hidden' );
                $( '#inscription, #zone-courriel' ).addClass( 'hidden' );
                if ( valOk( $( '#nom' ).val() ) && valOk( $( '#prenom' ).val() ) ) {
                        $( '#zone-prenom-nom' ).addClass( 'hidden' );
                        $( '#bienvenue-prenom' ).text( ' ' + $( '#prenom' ).val() );
                        $( '#bienvenue-nom' ).text( ' ' + $( '#nom' ).val() );
                } else {
                        $( '#zone-prenom-nom' ).removeClass( 'hidden' );
                        $( '#bienvenue-prenom,#bienvenue-nom' ).text( '' );
                }
        } else {
                $( '#bouton-connexion, #creation-compte' ).removeClass( 'hidden' );
                $( '#bienvenue').addClass( 'hidden' );
        }
};


WidgetSaisie.prototype.formaterNom = function() {
        $( '#nom' ).val( $( '#nom' ).val().toUpperCase() );
};

WidgetSaisie.prototype.formaterPrenom = function() {
        const prenom   = [],
                mots       = $( '#prenom' ).val().split( ' ' ),
                motsLength = mots.length;

        for ( let i = 0; i < motsLength; i++ ) {
                let mot          = mots[i],
                        motMajuscule = '';

                if ( 0 <= mot.indexOf( '-' ) ) {
                        const prenomCompose    = new Array(),
                                motsComposes       = mot.split( '-' ),
                                motsComposesLength = motsComposes.length;

                        for ( let j = 0; j < motsComposesLength; j++ ) {
                                const motSimple    = motsComposes[j];

                                motMajuscule = motSimple.charAt(0).toUpperCase() + motSimple.slice(1);
                                prenomCompose.push( motMajuscule );
                        }
                        prenom.push( prenomCompose.join( '-' ) );
                } else {
                        motMajuscule = mot.charAt(0).toUpperCase() + mot.slice(1);
                        prenom.push( motMajuscule );
                }
        }
        $( '#prenom' ).val( prenom.join( ' ' ) );
};

WidgetSaisie.prototype.bloquerCopierCollerCourriel = function() {
        this.afficherPanneau( '#dialogue-bloquer-copier-coller' );

        return false;
};

// Préchargement des infos-obs ************************************************/
WidgetSaisie.prototype.chargerInfoObs = function() {
        const lthis = this,
                urlObs  = this.serviceObsUrl + '/' + this.obsId;

        $.ajax({
                url: urlObs,
                type: 'GET',
                success: function( data, textStatus, jqXHR ) {
                        if ( valOk( data ) ) {
                                lthis.prechargerForm( data );
                        } else {
                                lthis.surErreurChargementInfosObs.bind( lthis );
                        }
                },
                error: function( jqXHR, textStatus, errorThrown ) {
                        lthis.surErreurChargementInfosObs();
                }
        });
};

// @TODO faire mieux que ça !
WidgetSaisie.prototype.surErreurChargementInfosObs = function() {
        utils.activerModale( this.msgTraduction( 'erreur-chargement' ) );
};

WidgetSaisie.prototype.prechargerForm = function( data ) {
        $( '#milieu' ).val( data.milieu );
        $( '#commune-nom' ).text( data.zoneGeo );
        if( data.hasOwnProperty( 'codeZoneGeo' ) ) {
          // TODO: trouver un moyen qui fonctionne lorsqu'on aura d'autres référentiels que INSEE
          $( '#commune-insee' ).text( data.codeZoneGeo.replace( 'INSEE-C:', '' ) );
        }

        if( data.hasOwnProperty( 'latitude' ) && data.hasOwnProperty( 'longitude' ) ) {
                // $cartoRemplacee = $( '#tb-geolocation' ),
                // suffixe = '',
                // layer = 'osm',
                // zoomInit = 18
                const typeLocalisation = $( '#top' ).data( 'type-loc' ),
                        donnesResetCarto   = {
                        latitude         : data.latitude,
                        longitude        : data.longitude,
                        typeLocalisation : typeLocalisation,
                        zoom             : 18
                };
                this.transfererCarto( donnesResetCarto );
        }
};

// Ajouter Obs ****************************************************************/
/**
 * Retourne un Array contenant les valeurs des champs étendus
 */
WidgetSaisie.prototype.getObsChpSpecifiques = function() {
        const lthis = this,
                champs    = [],
                $thisForm = $( '#form-supp' ),
                elements  =
                        'input[type=text]:not(.collect-other),'+
                        'input[type=checkbox]:checked,'+
                        'input[type=radio]:checked,'+
                        'input[type=email],'+
                        'input[type=number],'+
                        'input[type=range],'+
                        'input[type=date],'+
                        'textarea,'+
                        '.select',
                retour    = [];

        $( elements, $thisForm ).each( function() {
                if ( valOk( $( this ).val() ) && ( valOk( $( this ).attr( 'name' ) ) || valOk( $( this ).data( 'name' ) ) ) ) {
                        const valeur = $( this ).val(),
                                cle    = ( valOk( $( this ).attr( 'name' ) ) ) ? $( this ).attr( 'name' ) : $( this ).data( 'name' );
                        if ( cle in champs ) {
                                champs[cle] += ';' + valeur;
                        } else {
                                champs[cle] = valeur;
                        }
                }
        });
        for ( let key in champs ) {
                retour.push({ 'cle' : key , 'valeur' : champs[key] });
        }
        if ( valOk( $( '#coord-lineaire' ).val() ) ) {
                retour.push({ 'cle' : 'coordonnees-rue-ou-lineaire' , 'valeur' : $( '#coord-lineaire' ).val() });
        }
        return retour;
};

WidgetSaisie.prototype.reinitialiserForm = function() {
        this.supprimerMiniatures();
        if( !this.especeImposee ) {
                $( '#taxon' ).val( '' );
                $( '#taxon' ).data( 'numNomSel', '' )
                        .data( 'nomRet','' )
                        .data( 'numNomRet', '' )
                        .data( 'nt', '' )
                        .data( 'famille', '' );
                if( this.isTaxonListe ) {
                        $( '#taxon-liste' ).find( 'option' ).each( function() {
                                if ( $( this ).hasClass( 'choisir' ) ) {
                                        $( this ).attr( 'selected', true );
                                } else {
                                        $( this ).attr( 'selected', false );
                                }
                        });
                        $( '#taxon-input-groupe' ).addClass( 'hidden' );
                        $('#taxon-autre').val('');
                }
        }
        if ( valOk( $( '#form-supp' ) ) ) {
                $( '#form-supp' ).validate().resetForm();
        }
};

WidgetSaisie.prototype.validateGeometry = function( geometry ) {
        const isLineString = !!geometry && 'LineString' === geometry.type,
                validateTypeOfCoordinates = coordinates => isLineString ? Array.isArray( coordinates ) : ['number','string'].includes( typeof coordinates );

        if ( !valOk( geometry.coordinates ) ) {
                return false;
        }

        let isValid = true;

        $.each(geometry.coordinates, (i, coordinates) => {
                if ( !validateTypeOfCoordinates( coordinates ) ) {
                        isValid = false;
                }
        });

        const isValidLength = isLineString ? ( geometry.coordinates.length >= 2 ) : ( geometry.coordinates.length === 2 );

        return isValid && isValidLength;
}

// Géolocalisation *************************************************************/
/**
 * Fonction handler de l'évenement location du module tb-geoloc
 */
WidgetSaisie.prototype.locationHandler = function( location ) {
        const locDatas          = location.originalEvent.detail,
                $geolocControlGroup = $( '#geoloc' ).closest( '.control-group' );

        if ( !valOk( locDatas ) ) {
                console.warn( 'Error location' );
        } else {
                if ( !this.validateGeometry( locDatas.geometry ) ) {
                        $geolocControlGroup.addClass( 'error' );
                        $( '#geometry' ).val( '' );
                } else {
                        console.log( locDatas );

                        const geometry = JSON.stringify( locDatas.geometry ),
                                altitude   = ( valOk( locDatas.elevation ) ) ? locDatas.elevation : '',
                                pays       = ( valOk( locDatas.osmCountryCode ) ) ? locDatas.osmCountryCode.toUpperCase() : 'FR',
                                rue        = ( valOk( locDatas.osmRoad ) ) ? locDatas.osmRoad : '';
                        let latitude      = '',
                                longitude     = '',
                                coordLineaire = '',
                                nomCommune    = '',
                                communeInsee  = '';

                        if ( valOk( locDatas.geometry.coordinates ) &&
                                valOk( locDatas.centroid.coordinates ) &&
                                valOk( locDatas.centroid.coordinates[0] ) &&
                                valOk( locDatas.centroid.coordinates[1] )
                        ) {
                                longitude = locDatas.centroid.coordinates[0];
                                latitude = locDatas.centroid.coordinates[1];
                        }
                        if ( valOk( locDatas.inseeData ) ) {
                                nomCommune = locDatas.inseeData.nom;
                                communeInsee = ( valOk( locDatas.inseeData.code ) ) ? locDatas.inseeData.code : '';
                        } else if ( valOk( locDatas.locality ) ) {
                                nomCommune = locDatas.locality;
                        } else if ( valOk( locDatas.locality ) ) {
                                nomCommune = locDatas.osmCounty;
                        }
                        $( '#geometry' ).val( geometry );
                        $( '#coord-lineaire' ).val( coordLineaire );
                        $( '#latitude' ).val( latitude );
                        $( '#longitude' ).val( longitude );
                        $( '#commune-nom' ).val( nomCommune );
                        $( '#commune-insee' ).val( communeInsee );
                        $( '#altitude' ).val( altitude );
                        $( '#pays' ).val( pays );
                        $( '#station' ).val( rue );
                        $( '#latitude, #longitude' ).valid();
                        $geolocControlGroup.toggleClass(
                                        'error',
                                        !valOk( $( '#latitude' ).val() ) || !valOk( $( '#longitude' ).val() )
                                );
                }
        }
}

// Form Validator *************************************************************/
WidgetSaisie.prototype.chpEtendusValidation = function() {
        const lthis = this,
                $thisForm = $( '#form-supp' ),
                elements  =
                        '.checkbox,'+
                        '.radio,'+
                        '.checkboxes,'+
                        '.select,'+
                        'textarea,'+
                        'input[type=text]:not(.collect-other),'+
                        'input[type=email],'+
                        'input[type=number],'+
                        'input[type=range],'+
                        'input[type=date]',
                speFields = ['checkbox','radio','checkboxes','select'],
                spefieldsCount = speFields.length,
                chpSuppValidation = {
                        rules : {},
                        messages : {},
                        minmax : []
                },
                errors = {},
                namesListFields = [];
        let picked = '';

        $( elements, $thisForm ).each( function() {
                for( let fieldsClass = 0; spefieldsCount > fieldsClass; fieldsClass++ ) {
                                const dataName = $( this ).data( 'name' );

                        if ( valOk( $( this ).attr( 'required' ) ) && $( this ).hasClass( speFields[fieldsClass] ) && !valOk( chpSuppValidation.rules[ dataName ] ) ) {
                                namesListFields.push( dataName );
                                chpSuppValidation.rules[ dataName ] = { required : true };
                                if ( valOk( $( '.other', $( this ) ) ) ) {
                                        picked = ( 'select' === speFields[fieldsClass] ) ? ':selected' : ':checked';
                                        chpSuppValidation.rules[ 'collect-other-' + dataName.replace( '[]', '' ) ] = {
                                                required : '#other-' + dataName.replace( '[]', '' ) + picked,
                                                minlength: 1
                                        };
                                        chpSuppValidation.messages[ 'collect-other-' + dataName.replace( '[]', '' ) ] = false;
                                }
                                chpSuppValidation.rules[ dataName ]['listFields'] = true;
                                chpSuppValidation.messages[ dataName ] = 'Ce champ est requis :\nVeuillez choisir une option, ou entrer une valeur autre valide.';
                                errors[dataName] = '.' + speFields[fieldsClass];
                        }
                }
                if ( valOk( $( this ).attr( 'name' ) ) && valOk ( $( this ).attr( 'required' ) ) && 0 > $.inArray( $( this ).attr( 'name' ) , namesListFields ) ) {
                        chpSuppValidation.rules[ $( this ).attr( 'name' ) ] = { required : true, minlength: 1 };
                        if(
                                ( valOk( $( this ).attr( 'type' ), true, 'number' ) || valOk( $( this ).attr( 'type' ), true, 'range' ) ) &&
                                ( valOk( $( this )[0].min ) || valOk( $( this )[0].max ) )
                        ) {
                                chpSuppValidation.rules[ $( this ).attr('name') ]['minMaxOk'] = true;
                                chpSuppValidation.messages[ $( this ).attr('name') ] = lthis.validerMinMax( $( this )[0] ).message;
                        }
                }
        });
        if ( valOk( chpSuppValidation.rules ) ) {
                $.each( chpSuppValidation.rules, function( key ) {
                        if ( !valOk( chpSuppValidation.messages[key] ) ) {
                                chpSuppValidation.messages[key] = 'Ce champ est requis :\nVeuillez entrer une valeur valide.';
                        }
                });
                if ( 0 < Object.keys( errors ).length ) {
                        chpSuppValidation['errors'] = errors;
                }
        }
        return chpSuppValidation;
};

WidgetSaisie.prototype.validerMinMax = function( element ) {
        const minCond  = parseFloat( element.value ) >= parseFloat( element.min ),
                maxCond    = parseFloat( element.value ) <= parseFloat( element.max ),
                returnMnMx = { cond : true , message : '' };
        let mnMxCond    = new Boolean(),
                messageMnMx = 'La valeur entrée doit être';

        if(
                ( valOk( element.type, true, 'number' ) || valOk( element.type, true, 'range' ) ) &&
                ( valOk( element.min ) || valOk( element.max ) )
        ) {
                if ( element.min && element.max ) {
                        messageMnMx += ' comprise entre ' + element.min + ' et ' + element.max;
                        mnMxCond     = ( minCond && maxCond );
                } else if ( element.min ) {
                        messageMnMx += ' supérieure à ' + element.min;
                        mnMxCond     = minCond;
                } else {
                        messageMnMx += ' inférieure à ' + element.max;
                        mnMxCond     = maxCond;
                }
                returnMnMx.cond    = mnMxCond;
                returnMnMx.message = messageMnMx;
        }
        return returnMnMx;

};

WidgetSaisie.prototype.definirReglesFormValidator = function() {
        const lthis = this,
                formSuppValidation = this.chpEtendusValidation();

        $( '#form-supp' ).validate({
                onclick : function( element ) {
                        if (
                                (
                                        valOk( element.type, true, 'checkbox' ) ||
                                        valOk( element.type, true, 'radio' )
                                ) &&
                                (
                                        !valOk( $( '.' + $( element ).attr( 'name' ).replace( '[]', '' ) + ':checked' ) ) ||
                                        valOk( $( '.' + $( element ).attr( 'name' ).replace( '[]', '' ) + ':not(.other):checked' ) ) ||
                                        !valOk( $( '#other-' + $( element ).attr( 'name' ).replace( '[]', '' ) ) ) ||
                                        $( '#other-' + $( element ).attr( 'name' ).replace( '[]', '' ) ).is( ':checked' ) ||
                                        (
                                                $( '#other-' + $( element ).attr( 'name' ).replace( '[]', '' ) ).is( ':checked' ) &&
                                                $( element ).closest( '.control-group' ).hasClass('error')
                                        )
                                )
                        ) {
                                $( element ).valid();
                                if ( $( element ).valid() ) {
                                        $( element ).closest( '.control-group' ).removeClass( 'error' );
                                        $( element ).next( $( 'span.error' ) ).remove();
                                } else {
                                        $( element ).closest( '.control-group' ).addClass( 'error' );
                                }
                        }
                        return false;
                },
                rules : formSuppValidation.rules,
                messages : formSuppValidation.messages,
                errorPlacement : function( error , element ) {
                        if ( 0 < Object.keys( formSuppValidation.errors ).length ) {
                                const errorsKeys = Object.keys( formSuppValidation.errors );
                                let thisKey    = '',
                                        errorsFlag = true;

                                for ( let i = 0 ; i < errorsKeys.length ; i++ ) {
                                        thisKey = errorsKeys[i];
                                        if( $( element ).attr( 'name' ) === thisKey ) {
                                                $( formSuppValidation.errors[thisKey] ).append( error );
                                                errorsFlag = false;
                                        }
                                }
                                if ( errorsFlag ) {
                                        error.insertAfter( element );
                                }
                        } else {
                                error.insertAfter( element );
                        }
                }
        });
        $( '#form-supp .select' ).change( function() {
                $( this ).valid();
        });
        $( 'input[type=date]' ).on( 'input', function() {
                $( this ).valid();
        });
        // Validation taxon
        // et gestion des messages d'erreur taxon et images en fonction de la certitude
        $( '#taxon, #certitude' ).on( 'change', function() {
                lthis.validerTaxonRequis( valOk( $( '#taxon' ).val() ) );
        });
        // Validation miniatures avec MutationObserver
        this.surPresenceAbsenceMiniature();
        $( '#form-observation' ).validate({
                rules : {
                        date_releve : {
                                required : true,
                                'dateCel' : true
                        },
                        latitude : {
                                required : true,
                                minlength : 1,
                                range : [-90, 90]
                        },
                        longitude : {
                                required : true,
                                minlength : 1,
                                range : [-180, 180]
                        }
                }
        });
        $( '#form-observateur' ).validate({
                rules : {
                        courriel : {
                                required : true,
                                email : true,
                                'userEmailOk' : true
                        },
                        courriel_confirmation : {
                                required : true,
                                equalTo : '#courriel'
                        }
                }
        });
        $( '#connexion,#inscription,#bouton-anonyme' ).on( 'click', function( event ) {
                $( '.nav.control-group' ).removeClass( 'error' );
        });
};


WidgetSaisie.prototype.validerCertitudeTaxonImage = function( hasTaxon = false, hasImages = false ) {
        const isCertain = 'certain' === $( '#certitude' ).val();
        let isvalid = true ;

        if ( this.photoObligatoire || !isCertain ) {
                isvalid = this.validerImageRequise( hasImages );
        }
        if ( isCertain ) {
                isvalid &= this.validerTaxonRequis( hasTaxon );
        }

        return isvalid;
};

WidgetSaisie.prototype.validerTaxonRequis = function( hasTaxon = false ) {
        const taxonEstRequis = 'certain' === $( '#certitude' ).val();

        if ( !this.photoObligatoire ) {
                $( '#photos-conteneur').removeClass( 'error' )
                        .find( 'span.error' ).hide();
        }

        if ( !hasTaxon && taxonEstRequis ) {
                this.afficherPanneau( '#dialogue-taxon-or-image' );
                $( '#bloc-taxon' ).addClass( 'error' )
                        .find( 'span.error' ).show();
        } else {
                this.masquerPanneau( '#dialogue-taxon-or-image' );
                $( '#bloc-taxon' ).removeClass( 'error' )
                        .find( 'span.error' ).hide();
        }

        if ( taxonEstRequis ) {
                return hasTaxon;
        }
};

WidgetSaisie.prototype.validerImageRequise = function( hasImages = false ) {
        $( '#bloc-taxon' ).removeClass( 'error' )
                        .find( 'span.error' ).hide();

        if ( hasImages ) {
                this.masquerPanneau( '#dialogue-taxon-or-image' );
                this.masquerPanneau( '#dialogue-image-requise' );
                $( '#fichier' ).parent( 'label.label-file' ).removeClass( 'error' );
                $( '#photos-conteneur').removeClass( 'error' ).find( 'span.error' ).hide();
        } else {
                if ( this.photoObligatoire ) {
                        this.afficherPanneau( '#dialogue-image-requise' );
                } else {
                        this.afficherPanneau( '#dialogue-taxon-or-image' );
                }
                $( '#fichier' ).parent( 'label.label-file' ).addClass( 'error' );
                $( '#photos-conteneur').addClass( 'error' ).find( 'span.error' ).show();
        }
        return hasImages;
};

WidgetSaisie.prototype.surPresenceAbsenceMiniature = function() {
        const lthis = this;
        // voir : https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver/disconnect
        // Selectionne le noeud dont les mutations seront observées
        const targetNode = document.getElementById( 'miniatures' );
        // Fonction callback à éxécuter quand une mutation est observée
        const callback = mutationsList => {
                for( let mutation of mutationsList ) {
                        lthis.validerCertitudeTaxonImage(
                                valOk( $( '#taxon' ).val() ),
                                0 < mutation.target.childElementCount
                        );
                }
        };
        // Créé une instance de l'observateur lié à la fonction de callback
        this.observer = new MutationObserver( callback );
        // Commence à observer le noeud cible pour les mutations précédemment configurées
        this.observer.observe( targetNode, { childList: true } );
};

WidgetSaisie.prototype.validerForm = function() {
        const observateur  = ( $( '#form-observateur' ).valid() && $( '#courriel' ).valid() && $( '#courriel_confirmation' ).valid() ),
                obs            = $( '#form-observation' ).valid(),
                parsedGeometry = tryParseJson( $( '#geometry' ).val() ),
                geoloc         = this.validateGeometry( parsedGeometry ) && ( valOk( $( '#latitude' ).val() ) && valOk( $( '#longitude' ).val() ) ) ,
                // validation et panneau taxon/images
                certitudeTaxonImage  = this.validerCertitudeTaxonImage(
                        valOk( $( '#taxon' ).val() ),
                        valOk( $( '#miniatures .miniature' ) )
                );
        let chpsSupp = true;

        if ( valOk( $( '#form-supp' ) ) ) {
                chpsSupp = ( function () {
                        let otherFlag = $( '#form-supp' ).valid();

                        if( valOk( $( '.other', $( '#form-supp' ) ) ) ) {
                                $( '.other', $( '#form-supp' ) ).each( function() {
                                        const picked = ( $( this ).data( 'element' ) !== 'select' ) ? ':checked' : ':selected';

                                                if ( $( this ).is( picked ) && valOk( $( this ).val(), true, 'other' ) ) {
                                                        otherFlag = false;
                                                }
                                });
                        }

                        return otherFlag;
                })();
        }
        // panneau geoloc
        if ( geoloc ) {
                this.masquerPanneau( '#dialogue-geoloc-ko' );
                $( '#geoloc-datas' ).closest( '.control-group' ).removeClass( 'error' );
        } else{
                this.afficherPanneau( '#dialogue-geoloc-ko' );
                $( '#geoloc-datas' ).closest( '.control-group' ).addClass( 'error' );
        }
        // panneau observateur
        if ( observateur ) {
                this.masquerPanneau( '#dialogue-utilisateur-non-identifie' );
                $( '.nav.control-group' ).removeClass( 'error' );
        } else {
                this.afficherPanneau( '#dialogue-utilisateur-non-identifie' );
                $( '.nav.control-group' ).addClass( 'error' );
        }
        return ( observateur && obs && geoloc && certitudeTaxonImage && chpsSupp );
};

// Referentiel ****************************************************************/
// N'est pas utilisé en cas de taxon-liste
WidgetSaisie.prototype.surChangementReferentiel = function() {
        this.nomSciReferentiel = $( '#referentiel' ).val();
        //réinitialise taxon.val
        $( '#taxon' ).val( '' );
        $( '#taxon' ).data( 'numNomSel', '' );
};


$( document ).ready( function() {
        const widget = new WidgetSaisie();
        widget.init();
        // Fonctions de Style et Affichage des éléments "spéciaux"
        utils.init();
});