Icon

Partager Envoyer

(Document)

Bibliothèque pour la génération de formulaires CRUD et la manipulation de données SQL externes

Il peut arriver que la structure existante des données de l'application (collections/contenus) soit inappropriée à la gestion d'un type particulier de données, par exemple si vous devez importer de nombreux enregistrements d'une base de données existante qui possède sa propre structure complexe.

Dans un tel cas, la bibliothèque de génération de formulaires CRUD (pour Create, Read, Update, Delete) fournie avec chora permet le plus souvent de développer rapidement les contrôleurs et vues adaptés à vos données externes.

(Notez qu'il vous faudra cependant importer ces données dans la base de données MySQL de l'application, soit à l'aide d'un import SQL, soit d'un import CSV, d'un import ODBC ou via l'API RESTful (par exemple à l'aide de la bibliothèque client d'API RESTful).
 

Généralités

La bibliothèque de génération de formulaires CRUD de chora est dérivée du populaire GroceryCRUD de John Skoumbourdis (en version 1.6.1 modifiée) : cette application est disponible sous licence MIT. Malgré les modifications apportées pour son intégration à chora (elle est utilisée pour construire de nombreux formulaires de l'interface de gestion), son fonctionnement demeure 100% compatible avec l'application d'origine.

Vous trouverez la documentation complète de GroceryCRUD à l'adresse suivante : https://www.grocerycrud.com/documentation (veuillez noter que cette documentation est considérablement enrichie par les informations disponibles sur les forums d'utilisateurs de GroceryCRUD : https://www.grocerycrud.com/forums/ ; aussi, nous vous conseillons vivement de les consulter).
 

Fichiers de la bibliothèque

Le générateur de formulaires CRUD est une des bibliothèques les plus complètes qui sont fournies avec chora. il convient d'en présenter rapidement les fichiers importants.
 

Fichiers principaux

Le fichier principal de la bibliothèque est /application/libraries/Grocery_CRUD.php
C'est le seul fichier à charger pour pouvoir utiliser les fonctions de génération de formulaires CRUD :

$this->load->library('grocery_CRUD');

La configuration basique de la bibliothèque se fait dans le fichier /application/config/grocery_crud.php
(En principe, vous ne devez pas modifier celui-ci, car certaines options sont nécessaires au fonctionnement de chora).

Enfin, une grande partie du code de la bibliothèque est constituée par les fichiers de contenu qui se trouvent dans le dossier /assets/grocery_crud

Nous ne présentons ici que les spécificités de la version livrée avec chora.
 

Fichiers de l'interface (thème)

Une partie importante des formulaires CRUD est leur interface qui fonctionne avec AJAX. Celle-ci est créée à partir des fichiers du thème fourni avec chora : /assets/grocery_crud/themes/AEdatatables/

AEdatatables s'appuie sur les versions spécifiques des bibliothèques Javascript Datatables, jQuery et Bootstrap 4 fournies avec chora pour proposer des formulaires et des tables homogènes avec le reste de l'application. Ses fonctions comprenent notamment le support de la taille d'écran d'utilisateur (design adaptatif), des messages non intrusifs affichés avec CSS/Javascript via la bibliothèque humane.js, des filtres des colonnes, du tri des colonnes (corrigé), de la recherche dans less tableaux et des boutons copier, imprimer et exporter au format CSV.

Pour permettre l'intégration dans chora, quelques modifications ont été apportées au chargement des fichiers Javascript et aux code Javascript de la bibliothèque.
 

Modèle

Le fichier modèle fourni avec chora (/application/models/Grocery_crud_model.php) inclut le support d'une fonction supplémentaire écrite par Nagara Jupuli Mamadi (http://www.nagarajupulimamidi.com/grocery-crud-where_in/).
Cette fonction est semblable à la méthode where_in() de ActiveRecord (qui n'est pas supportée sinon dans GroceryCRUD) et fournit la possiblité d'utiliser des clauses IN WHERE dans les formulaires CRUD :

$crud->in_where(<liste de valeurs clés>)

 

Utilisation

Une fois la bibliothèque chargée, vous pouvez l'utiliser dans un contrôleur pour générer en quelques lignes les formulaires permettant de manipuler les données d'une table de la base de données de l'application :

                        //Chargement de la bibliothèque CRUD
                        $this->load->library('grocery_CRUD');


                        //Construction des formulaires
                        $crud = new grocery_CRUD(); //Initialisation
                        $crud->set_theme('AEdatatables'); //Définition du thème
                        $crud->set_language($this->session->userdata('language')); //Définition de la langue
                        $crud->set_table('my_table'); //Table de la base de données
                        $crud->set_subject('My Data'); //Libellé des données
                        //Clause WHERE :
                        //$crud->where("cle=$valeur"); //Filtre optionnel (clause WHERE)
                        //$crud->in_where($valeur1,$valeur2,$valeur3...); //Filtre optionnel (clause WHERE IN)

                        //...

                        //Sortie HTML (à transmettre à la vue)
                        $output = $crud->render();

Notez que dans le cadre d'une utilisation avec chora, il vous faudra généralement ajouter vos propres contrôles pour gérer les droits de l'utilisateur à l'aide la bibliothèque Auth :
 

                        $this->load->library('Auth');

                        if (!$this->auth->is_allowed(0,'view')) {
                              $crud->unset_list();  //Supprime la fonction lister
                              $crud->unset_read();  //Supprime la fonction voir
                        }

                        if (!$this->auth->is_allowed(0,'edit')) {
                              $crud->unset_edit();  //Supprime la fonction éditer
                              $crud->unset_clone();  //Supprime la fonction cloner
                        }
                        if (!$this->auth->is_allowed(0,'delete')) {
                              $crud->unset_edit();  //Supprime la fonction effacer
                        }

Deux fonctions particulièrement utiles de la bibliothèque permettent la gestion des relations entre deux tables de la base de données MySQL :
 

Relation 1-n

Soit une relation 1-n entre la valeur du champ this_table.data_id et les données de la table data dont la clé primaire est définie :

$crud->set_relation('data_id','data','{label_data} ({publish_data})',array('status' => 1,'label_data ASC'));

affichera une chaîne de caractères constituée par la valeur du champ label_data, suivie de la valeur du champ publish_data entre parenthèses, toutes deux prises dans la table data, à la place du champ data_id de la table manipulée dans les formulaires CRUD (this_table dans notre exemple).
Le quatrième paramètre (optionnel) ajoute une clause where supplémentaire : seuls les contenus actifs sont listés.
Le cinquième paramètre (optionnel) indique que les résultats seront triés par ordre croissant de la valeur du champ label_data
 

Relation n-n

Nous reprenons ici l'exemple fourni dans la documentation de GroceryCRUD pour traduire la relation n-n suivante :

table manipulée dans le formulaire CRUD (this_table) -> table de jointure (relation_table) -> table mise en relation (selection_table)

On utilisera :
 

$crud->set_relation_n_n(
$field_name, 
$relation_table, 
$selection_table, 
$primary_key_alias_to_this_table, 
$primary_key_alias_to_selection_table , 
$title_field_selection_table 
[ , $priority_field_relation [, $where_clause]]
);

Avec les paramètres suivants :

  1. $field_name : nom du champ de formulaire CRUD à créer (ce champ n'existe pas dans la base de données et vous ne devriez pas avoir à vous en soucier).
  2. $relation_table : nom de la table de jointure qui met en relation les deux tables this_table et selection_table.
  3. $selection_table : nom de la table jointe à travers la table de jointure (indique dans quelle table la valeur de la clé primaire stockée dans la table de jointure devra être obtenue : la clé primaire de cette table sera obtenue automatiquement ou son nom devra être spécifié le cas échéant, par exemple s'il s'agit d'une vue dépourvue de clé).
  4. $primary_key_alias_to_this_table : Nom du champ qui pointe vers la clé primaire de la table manipulée par le formulaire CRUD dans la table de jointure (relation_table.this_table_key_alias).
  5. $primary_key_alias_to_selection_table : Nom du champ qui pointe vers la clé primaire de la table en relation dans la table de jointure (relation_table.selection_table_key_alias).
  6. $title_field_selection_table : Nom du champ lisible par un être humain de la table en relation qui sera affiché dans le formulaire CRUD (selection_table.title)
  7. $priority_field_relation : (optionnel) champ à utiliser pour le tri et ordre de tri des données dans la table à joindre (modifie l'interface. Par exemple, 'selection_table.title ASC')
  8. $where_clause : (optionnel) Clause where aditionnelle pour la relation (par exemple, array('selection_table.status'=>1)).

Liste de valeurs dans un champ unique (multiselect)

Bien que cela ne soit pas recommandé, il arrive fréquemment de rencontrer des listes de valeurs, séparées par une virgule, qui ont été insérées dans un seul champ d'une table : un tel procédé peut permettre de simuler une relation n->n qui aurait été exprimée à l'aide d'une table intermédiaire (voir ci-dessus), mais en faisant l'économie de cette dernière.

Un champ de type multiselect permet de gérer une telle liste de valeurs dans le CRUD.

Dans le cas où les données de la liste sont issues d'une table de la base de données, le tableau stockant les valeurs possibles pour la sélection doit en sus être construit à l'aide d'une requête spécifique : dans un tel cas, il faut prendre soin de réinitialiser le constructeur de requête avant et après la construction de cette requête (pour éviter l'agrégation des clauses), ce que montre l'exemple suivant :

$this->db->reset_query();
$this->db->select('id, nom');
$results = $this->db->get('groupe')->result();
$groupes_multiselect = array();
$this->db->reset_query();
                       

foreach ($results as $result) {
       $groupes_multiselect[$result->id] = $result->nom;
}

$crud->field_type('liste_groupes', 'multiselect', $groupes_multiselect);

Recettes pour l'utilisation de la bibliothèque CRUD

Vous trouverez ci-après quelques recettes utiles, regroupées pour plus de simplicité.
En règle générale, veuillez vous référer à la documentation disponible sur GroceryCRUD 1.6.x pour développer avec cette bibliothèque.

Transmettre à une vue des paramètres personnalisés avec la sortie des fonctions CRUD

Il faut d'abord définir le tableau des paramètres personnalisés à transmettre à la vue (en principe $this->view_data), puis initialiser la bibliothèque CRUD et en récupérer la sortie dans une variable (de type tableau : $output). Pour terminer, on fusionne les deux tableaux en un seul à l'aide de la fonction array_merge(), puis on charge la vue avec le résultat de la fusion (dans la variable $crud_output).

Par exemple, pour ajouter des fichiers Javascript/CSS aditionnels et une variable $page_title à la vue, on utilisera :

//Définition des paramètres de la vue 
$css_files=$this->view_data['css_files']=array(
base_url().'assets/css/content.css',
base_url().'assets/css/vendor/aurorae/dashboard.css');
$js_files=$this->view_data['js_files']=array(base_url().'assets/js/content.js');
$this->view_data['page_title']='Ma page...';

//Initialisation de la bibliothèque CRUD et construction des formulaires
$crud = new grocery_CRUD();
$crud->set_theme('AEdatatables');
$crud->set_language($this->session->userdata('language'));
$crud->set_table('my_table');
//Filtre :
//$crud->where("cle=$valeur");
//$crud->in_where("valeur1, valeur2, valeur3, ...");
//...

//Sortie HTML de la bibliothèque CRUD (à transmettre à la vue)
$output = $crud->render();
// Fusion des paramètres personnalisés de la vue et de la sortie HTML de la bibliothèque CRUD
$crud_output = array_merge($this->view_data,(array)$output);
//Chargement de la vue finale avec le résultat de la fusion
$this->load->view('formulation/index.php',$crud_output);

Veuillez noter que l'en-tête et le pied de page devront être chargés normalement dans la vue, qui contiendra a minima le code suivant :

<?php
$view_data['css_files']=$css_files;
$view_data['js_files']=$js_files;
$view_data['page_title']=$page_title;
$this->load->view('Head',$view_data);
echo $output; //Sortie HTML de la bibliothèque CRUD
//...
?>

<!-- Messages non intrusifs avec humane.js -->
<?php if($this->session->flashdata('message')){ ?>
        <script>
        humane.log("<?=$this->session->flashdata('message');?>");
        </script>
<?php } ?>
<!-- Conteneur div des redirections JS à partir des formulaires CRUD -->
<div id="crud_redirect"></div>

<?php $this->load->view('Footer'); ?>

 

Effectuer une redirection en cas de succès

Les formulaires CRUD utilisant AJAX, il est impossible d'effectuer une redirection personnalisée à partir d'une fonction callback, sauf en utilisant Javascript pour écrire dans le formulaire avant un ajout ou une mise à jour de données.

Cependant, dans la plupart des cas vous souhaiterez plutôt rediriger l'utilisateur après l'ajout ou la mise à jour.
Pour le faire, il est possible de modifier le message de succès/d'erreur normalement affiché en y ajoutant une balise <script></script> contenant le code de redirection :

//Redirect after successful insert/update:
$crud->set_lang_string('update_success_message',
    'Redirection vers DuckDuckGo... <script language="text/javascript">window.location.replace("http://www.duckduckgo.com");</script>');
$crud->set_lang_string('insert_success_message',
    'Redirection vers DuckDuckGo... <script language="text/javascript">window.location.replace("http://www.duckduckgo.com");</script>');


Cependant, cela ne suffit pas, car la bibliothèque humane.js affichant les messages de succès/d'erreur n'exécute pas de code javascript par mesure de sécurité.

Pour que la redirection Javascript fonctionne, il faut également ajouter dans la vue une balise spéciale : <div id="crud_redirect"></div> : c'est finalement celle-ci qui accueillera le code javascript redirigeant l'utilisateur.
Idéalement, placez-la en fin de fichier, juste avant le chargement du pied de page (consultez le fichier qui contient les fonctions Javascript d'affichage des messages de succès/d'erreur CRUD pour voir comment cela fonctionne : /webapp/assets/grocery_crud/js/notify.js).

Dans le cas d'une redirection automatique vers la liste des enregistrements (state='list'), par exemple lorsque le bouton Sauver et revenir à la liste d'un formulaire est cliqué, il peut être suffisant de placer le code suivant en tête de la fonction du contrôleur :

//Redirect in case of success when saving data
if (strstr(current_url(),'success')) {
    redirect(<URL de redirection>,true);
}

Initialiser un champ caché avec une valeur par défaut

Important ! La création d'un champ caché est incompatible avec les relations.
Voici comment ajouter un champ caché à un formulaire avec une valeur par défaut :

$crud->field_type('my_field', 'hidden',$this->session->userdata('default_value'));

Note : une valeur par défaut peut être affectée de la même manière à un champ de type enum.

Attribuer une valeur par défaut à un champ texte ou dans une liste d'options

Le CRUD ne supporte les valeurs par défaut que pour les champs cachés (de type hidden) ou enum.

Un moyen rapide d'affecter une valeur par défaut à un champ texte ou dropdown du CRUD est de le faire avec Javascript.

Note : les champs de type dropdown sont générés avec le plugin jquery chosen : lorsqu'une nouvelle option est sélectionnée, un traitement spécifique doit être effectué pour que le changement soit pris en compte en appelant la méthode javascript $('#id_du_champ').trigger('chosen:updated');

Voici ce qu'il faut ajouter au contrôleur qui génère le CRUD pour définir la valeur 1 comme valeur par défaut du champ de type dropdown nommé champ1 :

$view_data['selectDefault']=array("field-champ1"=>'1');

Dans le cas d'un champ texte, le code serait le suivant :

$view_data['textDefault']=array("field-champ1"=>'1');

Dans la vue qui affiche le CRUD, le code PHP suivant générera le code javascript requis pour mettre automatiquement à jour la valeur de l'élément champ1 du formulaire :

<?php
echo <<<EOT
<script>$(document).ready(function() {
EOT;
if(isset($selectDefault)){
foreach ($selectDefault as $key => $value) {
echo <<<EOT
$("#$key").val('$value');
$('#$key').trigger('chosen:updated');
EOT;
}
}
if(isset($textDefault)){
foreach ($textDefault as $key => $value) {
echo <<<EOT
document.getElementById("$key").value = '$value';
EOT;
}
}
echo "});";
echo "</script>";

?>

Désactiver certaines fonctions CRUD

Pour désactiver les fonctions CRUD de votre choix, utiliser les fonctions suivantes :

$crud->unset_list(); //Supprime la liste des enregistrements
$crud->unset_read(); //Supprime la lecture d'un enregistrement
$crud->unset_clone();  //Supprimer la fonction cloner
$crud->unset_edit();  //Supprime la fonction éditer
$crud->unset_delete();  //Supprimer la fonction effacer


Dans le cas ou la liste est supprimée, il faudra également probablement rediriger les utilisateurs en cas de succès (voir ci-dessus).

Modifier les messages de succès/d'erreur
Les messages de succès ou d'erreur sont définis dans le fichier /assets/grocery_crud/languages/english.php (remplacez english par la langue souhaitée, par ex. french).

Si vous souhaitez modifier les messages de succès pour un seul formulaire, vous pouvez utiliser les fonctions suivantes dans le contrôleur :

$crud->set_lang_string('update_success_message',
    "Mon message en cas de mise à jour réussie");
$crud->set_lang_string('insert_success_message',
    "Mon message en cas d'ajout réussi");

Exécuter un code sous condition en fonction de l'état des formulaires CRUD

Vous pouvez utiliser la fonction getState() :

$state=$crud->getState();
$this->view_data['state']=$state;
if ($state=='add' || $state=='edit') {
    //Code à exécuter uniquement en cas d'ajout/d'édition
}
if ($state=='list' || $state=='read') {
    //Code à exécuter uniquement en cas d'affichage/d'affichage de la liste
}

Validation personnalisée des données d'un champ de formulaire (à l'aide d'une fonction de callback)

Dans la fonction CRUD du contrôleur :
                        $crud->set_rules('mon_champ',$this->lang->line('lib_mon_champ'),'callback_capitals_dash_spaces');

Ensuite, définissez la fonction callback de validation correspondante (sans le préfixe spécial callback_ !) :

public function capitals_dash_spaces($str)
{
   if ((bool) preg_match('/^[A-Z0-9 _-]+$/', $str)) {
        return true;
    } else {
        $this->form_validation->set_message('capitals_dash_spaces',
        sprintf(
                $this->lang->line('msg_x_field_can_have_only_capitals_dash_spaces'),
                $this->lang->line('lib_content')
               )
        ); //Le champ %s ne peut contenir que des lettres capitales, -, _ et des espaces.
        return false;
    }
}

Listes de valeurs dépendantes ou listes liées     complexité élevée !

Voici comment inclure des listes de valeurs dépendantes ou liées (la sélection d'une valeur dans la liste 1 entraîne le chargement de la liste de valeurs 2 avec des valeurs filtrées issues de la base de données).
Notez que cette opération est une des plus complexes à réaliser avec la bibliothèque CRUD.

TODO

Masquer le bouton Sauver et revenir à la liste

Vous pouvez utiliser la fonction suivante dans le contrôleur :
$crud->unset_back_to_list();
 

Utiliser une vue de la base de données ou une table dépourvue de clé primaire dans une relation

Pour définir une relation avec une vue MySQL ou avec une table dépourvue de clé primaire, il faut indiquer préalablement quelle est la clé à utiliser à la bibliothèque CRUD, sous peine de générer une erreur SQL :
//Voir http://www.nagarajupulimamidi.com/set-relation-table-view-grocery-crud/
$crud->set_primary_key('table_key','table_name');

Attribuer une liste de valeurs statiques à un champ

Vous pouvez soit créer un champ de type enum dans la base de données, ou bien utiliser la fonction suivante :

$crud->field_type('agreement','dropdown',array('0' => 'No', '1' => 'Yes'),1);

Supprimer avant insertion un champ de formulaire qui n'existe pas dans la base de données

Cela est nécessaire lorsque vous avez ajouté des champs, par exemple pour effectuer des traitements dans des fonctions callback : si des champs qui n'existent pas dans la base de données sont transmis, une erreur SQL sera générée au moment de l'insertion/modification de vos données. Il faut donc les supprimer au préalable, par exemple dans une fonction callback appelée avant l'insertion :

$crud->callback_before_insert(array($this,'unset_some_field'));

function unset_some_field($post_array)
{
     unset($post_array['some_field']);
     return $post_array;
}

Liste de référence des fonctions CRUD

Voici la liste de référence des fonctions supportées pour la génération de formulaires CRUD :

nom de la fonction Description
add_action Ajout d'une action personnalisée (bouton)
add_fields Champs visibles dans le formulaire d'ajout
callback_add_field This callback escapes the default auto field output of the field name at the add form.
callback_after_delete The callback runs when the operation delete completed successfully
callback_after_insert This is a callback after the auto insert of the CRUD.
callback_after_update This is a callback that is used after the automatic update of the CRUD.
callback_after_upload A callback that triggered after the upload functionality.
callback_before_delete This callback runs before the auto delete of the crud.
callback_before_insert This callback runs before the auto insert of the crud.
callback_before_update This callback runs before the auto update of the crud.
callback_before_upload A callback that triggered before the upload functionality. This callback is suggested for validation checks.
callback_column This callback runs on each row. It escapes the auto column value and runs the callback.
callback_delete This callback escapes the auto delete of the CRUD , and runs only the callback.
callback_edit_field This callback escapes the default auto field output of the field name at the edit form.
callback_field This callback escapes the default auto field output of the field name for the add and edit form.
callback_insert This callback escapes the auto insert of the CRUD, and runs only the inserted callback.
callback_read_field This callback escapes the default auto field output of the field name at the read form.
callback_update This callback escapes the auto update of the CRUD , and runs only the callback.
callback_upload Callback qui remplace le champ d'envoi de fichier par défaut
change_field_type Modifie le type de champ par défaut
columns Champs visibles pour l'affichage d'une donnée/l'affichage de la liste des données
display_as Modifie le libellé d'un champ
edit_fields Champs visibles dans le formulaire d'édition
fields Champs visibles dans les formulaires d'ajout/d'édition
field_type Alias de la fonction change_field_type.
getState Obtention de l'état sous forme de chaîne de caractères (selon la documentation)
getStateInfo Obtention de l'information d'état de l'opération qui vient d'être effectuée
get_primary_key Obtention de la clé primaire
Note : le résultat des fonctions get est disponible après l'appel à render(). 
in_where* Semblable à la fonction in_where de CodeIgniter pour construire la liste
like Semblable à la fonction like de CodeIgniter pour construire la liste
limit Semblable à la fonction limit de CodeIgniter pour construire la liste
order_by A quick first order_by (same as codeigniter) to our list
or_like Semblable à la fonction or_like de CodeIgniter pour construire la liste
or_where Semblable à la fonction or_where de CodeIgniter pour construire la liste
render Fonction qui génère la sortie HTML de la bibliothèque CRUD : présente à l'utilisateur un formulaire d'ajout, d'édition, une liste de données (tableau) ou l'affichage d'un enregistrement, selon l'état courant
required_fields Définit les champs obligatoires pour les formulaires d'ajout/d'édition
set_crud_url_path Permet de corriger la construction des URLs lorsque le chemin n'est pas spécifié correctement, notamment lorsque des routes personnalisées sont utilisées
set_field_upload Transforme un champ en envoi de fichier
set_language Définit la langue de l'interface
set_lang_string Définit directement la chaîne de caractères pour une langue
set_model Définit le modèle que le CRUD doit utiliser (ce modèle doit toujours étendre grocery_Model)
set_primary_key Définit la clé primaire à utiliser pour une table
set_relation Définit une relation 1-n dans la base de données
set_relation_n_n Définit une relation n-n dans la base de données
set_rules Définit des règles de validation (identique à la fonction CodeIgniter set_rules)
set_subject Définit un libellé de sujet pour indiquer quelles données sont manipulées
set_table Définit la table de la base de données dont les données principales seront extraites
set_theme Définit le thème de la bibliothèque CRUD. Le thème par défaut de chora est AEdatatables ; un autre thème plus dépouillé est disponible (flexigrid).
unique_fields Définit les champs qui stockent des valeurs uniques dans la base de données
unset_add Supprime l'action d'ajout
unset_add_fields Supprime les champs du formulaire d'ajout
unset_back_to_list Supprime les boutons et messages "Revenir à la liste"
   
unset_clone Supprime l'action de clonage
unset_columns Supprime les colonnes de la liste
unset_delete Supprime l'action de suppression
unset_edit Supprime l'action d'édition
unset_edit_fields Supprime les champs du formulaire d'édition
unset_export Supprime la fonction d'export
unset_fields Supprime les champs des formulaires d'ajout et d'édition
   
   
unset_list Supprime la liste des données
unset_operations Supprime toutes les actions : seule la liste des données demeure visible
unset_print Supprime la fonction d'impression
unset_read Supprime la fonction d'affichage des données
unset_texteditor Supprime l'éditeur de texte enrichi (RTF) pour les champs spécifiés
where Semblable à la fonction where de CodeIgniter pour construire la liste

(*) fonction propre à chora.
Les fonctions qui empêchement le chargement de JQuery, JQueryUI et Bootstrap ont été supprimées car elle n'ont pas de sens dans le cas de chora.
 


Ce document a été publié le 2019-01-13 22:39:54. (Dernière mise à jour : 2020-10-01 09:50:58.)




This website uses 'cookies' to enhance user experience and provide authentification. You may change which cookies are set at any time by clicking on more info. Accept
x