Pagination de checkbox complétée depuis la BDD sous codeigniter

logo codeigniter

Je fais une petite fixette sur codeigniter dans le cadre de l’écriture d’une application en ce moment. Ce billet est un aide mémoire (ou un tuto de bout en bout pour ceux que ça intéresse).

Contexte :

L’application a comme groupes de rôles l’administrateur puis quelques milliers d’autres groupes destinés à restreindre en visibilité les utilisateurs (environ 10000) dès la connexion sur leurs groupes utilisateurs respectifs. Pour schématiser, c’est un peu le fonctionnement de consultation de N comptes bancaires qui seraient consultés chacun par X utilisateurs.

Problème :

Pour initier le projet, j’ai pris le parti de ne pas me casser la tête à créer la librairie d’authentification de toute pièce. Mon choix s’est donc porté sur Codeigniter ion_auth library qui est complète et simple d’utilisation MAIS qui présente un très gros inconvénient : il n’y a pas de système de pagination des groupes utilisateurs.
Dans son fonctionnement, la librairie ion auth propose d’assigner les groupes aux utilisateurs (ce qui m’arrangeait). C’est pratique quand on a 4 ou 5 rôles à distribuer mais avec plus de 3000 rôles (correspondant à des groupes de consultation), ça devient totalement ingérable. Il est impensable de devoir chercher, depuis une fiche utilisateur, quelles sont les cases à cocher parmi 3000 groupes portant approximativement le même nom.

Solution :

Pour résoudre ce désagrément sans tout exploser, j’ai sorti le combo JSON + jQuery + datatables pour résoudre ce problème de pagination de checkboxes en utilisant ce que j’ai trouvé sur internet. Le moteur de recherche m’a guidé vers :

C’est parti !

Etape 1 : Création du fichier JSON

Ce fichier est utilisé par le script de génération des groupes. Il est utilisé pour afficher l’intégralité des groupes utilisateurs de ion auth sur lesquels ont va jouer avec les cases à cocher.

Il est formé de cette manière :
{« data »:[[« 1″, »admin »],[« 2″, »members »],[« 6″, »GROUPE1 »],[« 7″, »GROUPE2 »],[« 8″, »GROUPE3 »]]}

Comme ce n’est pas la construction par défaut de ce que ion auth propose pour la création de notre json sous codeigniter, on va exploiter une méthode du modèle Ion_auth_model.php

$this->data['groups'] = $this->ion_auth->groups()->result();

// CREATION DU JSON
$longueur = count($this->data['groups']);
$contenu_json = '{"data":[';
for ($i=0; $i < $longueur; $i++)
{
  $contenu_json .= '["'.$this->data['groups'][$i]->id.'","'.$this->data['groups'][$i]->name.'"]';
  if ($i != $longueur-1)
  {
    $contenu_json .= ',';
  }
}
$contenu_json .= ']}';
// FIN CREATION JSON

    //echo '{"data":[["'.$this->data['groups'][0]->id.'","'.$this->data['groups'][0]->name.'"]]}';
//$contenu_json = json_encode($this->data['groups']);
//var_dump($contenu_json);

// Nom du fichier à créer
$nom_du_fichier = 'fichier.json';

// Ouverture du fichier
$fichier = fopen($nom_du_fichier, 'w+');

// Ecriture dans le fichier
fwrite($fichier, $contenu_json);

// Fermeture du fichier
fclose($fichier);

C’est fait. Notre fichier fichier.json est dorénavant à la racine -> http://localhost/fichier.json (qu’il faudra placer ailleurs en situation réelle, bien sûr).

On peut du coup passer à la suite.

Etape n°2 : Préparation du tableau de cases à cocher datatables

Dans la vue users/edit_user_view.php virer

  <div class="form-group">
    <?php
    if(isset($groups))
    {
      echo form_label('Groupes','groups[]');
      foreach($groups as $group)
      {
        echo '<div class="checkbox">';
        echo '<label>';
        echo form_checkbox('groups[]', $group->id, set_checkbox('groups[]', $group->id, in_array($group->id,$usergroups)));
        echo ' '.$group->name;
        echo '</label>';
        echo '</div>';
      }
    }
    ?>
  </div>
  <?php echo form_hidden('user_id',$user->id);?>
  <?php echo form_submit('submit', 'Modifier l\'utilisateur', 'class="btn btn-primary btn-lg btn-block"');?>
<?php echo form_close();?>

et remplacer par  :

<div class="form-group">
<table id="example" class="display select" cellspacing="0" width="100%">
   <thead>
      <tr>
         <th><input name="select_all" value="1" type="checkbox"></th>
         <th>Noms Groupes</th>
      </tr>
   </thead>
</table>
<?php echo form_hidden('user_id',$user->id);?>
<p><button class="btn btn-primary btn-lg btn-block">Modifier les données</button></p>
</form>

L’absence de balise <td></td> est normale. Elle vont apparaître à la lecture du JSON.

Etape 3 : JQuery de génération des cases à cocher datatables, coche des cases de la BDD et soumission du formulaire

a/ Traiter le contrôleur pour que la vue repeuple le formulaire des cases déjà cochées (en base)

Dans Users.php qui envoie les données à la vue, il faut charger $this->data['votrevariable'] avec les groupes déjà assignés à l’utilisateur pour recocher les cases :

$this->data['groupes_utilisateur'] = $usergroups = $this->ion_auth->get_users_groups($user->id)->result();

b/ Positionner le script Jquery de génération / lecture des cases à cocher

Et $groupes_utilisateur est exploité par le javascript (cf. ci-dessous « boucle de récupération des groupes ») :

<script type="text/javascript">
// DEBUT SCRIPT JSON
function updateDataTableSelectAllCtrl(table){
   var $table             = table.table().node();
   var $chkbox_all        = $('tbody input[type="checkbox"]', $table);
   var $chkbox_checked    = $('tbody input[type="checkbox"]:checked', $table);
   var chkbox_select_all  = $('thead input[name="select_all"]', $table).get(0);

   // Si aucune des checkbox n'est cochée
   if($chkbox_checked.length === 0){
      chkbox_select_all.checked = false;
      if('indeterminate' in chkbox_select_all){
         chkbox_select_all.indeterminate = false;
      }

   // Si toutes les checkbox sont cochées
   } else if ($chkbox_checked.length === $chkbox_all.length){
      chkbox_select_all.checked = true;
      if('indeterminate' in chkbox_select_all){
         chkbox_select_all.indeterminate = false;
      }

   // Si quelques checkbox sont cochées
   } else {
      chkbox_select_all.checked = true;
      if('indeterminate' in chkbox_select_all){
         chkbox_select_all.indeterminate = true;
      }
   }
}

$(document).ready(function (){
   
   <?php 
   // boucle de récupération des groupes de l'utilisateur pour 
   // repeupler le tableau
   $lignes_a_cocher = "";
   foreach ($groupes_utilisateur as $guser) {
      $lignes_a_cocher .= '"'.$guser->id.'",';
   } 
   $lignes_a_cocher = rtrim($lignes_a_cocher,',');


   ?>
   // Tableau contenant les ID sélectionnés
   var rows_selected = [<?php echo $lignes_a_cocher?>];
   var table = $('#example').DataTable({
      'ajax': 'http://localhost/fichier.json',
      'columnDefs': [{
         'targets': 0,
         'searchable':false,
         'orderable':false,
         'width':'1%',
         'className': 'dt-body-center',
         'render': function (data, type, full, meta){
             return '<input type="checkbox">';
         }
      }],
      'order': [1, 'asc'],
      'rowCallback': function(row, data, dataIndex){
         // Récupération du row ID
         var rowId = data[0];

         // SI le row ID est dans la liste des row ID sélectionnés
         if($.inArray(rowId, rows_selected) !== -1){
            $(row).find('input[type="checkbox"]').prop('checked', true);
            $(row).addClass('selected');
         }
      }
   });

   // Tient le clic sur la checkbox
   $('#example tbody').on('click', 'input[type="checkbox"]', function(e){
      var $row = $(this).closest('tr');

      // Récupère le row data
      var data = table.row($row).data();

      // Récupère le row ID
      var rowId = data[0];

      // Determine whether row ID is in the list of selected row IDs 
      var index = $.inArray(rowId, rows_selected);

      // Si la checkbox est cochée et que le row ID n'est pas
      // dans la liste des row ID
      if(this.checked && index === -1){
         rows_selected.push(rowId);

      // Sinon, si la checkbox n'est pas cochée et 
      // que le rowID est dans la liste des row ID sélectionnés
      } else if (!this.checked && index !== -1){
         rows_selected.splice(index, 1);
      }

      if(this.checked){
         $row.addClass('selected');
      } else {
         $row.removeClass('selected');
      }

      // Met à jour le contrôle SELECT ALL
      updateDataTableSelectAllCtrl(table);

      // Evite que le clic se propage au parent
      e.stopPropagation();
   });

   // Gère le clic sur les cellules en lien avec les checkboxes
   $('#example').on('click', 'tbody td, thead th:first-child', function(e){
      $(this).parent().find('input[type="checkbox"]').trigger('click');
   });

   // Gère le clic select all
   $('thead input[name="select_all"]', table.table().container()).on('click', function(e){
      if(this.checked){
         $('#example tbody input[type="checkbox"]:not(:checked)').trigger('click');
      } else {
         $('#example tbody input[type="checkbox"]:checked').trigger('click');
      }

      // Evite que le clic se propage au parent
      e.stopPropagation();
   });

   // Récupère les évènements du draw de la table
   table.on('draw', function(){
      // Mets à jour en cas de "select all"
      updateDataTableSelectAllCtrl(table);
   });
    
   // Récupère l'évènement de soumission de formulaire 
    $('#frm-example').submit(function(e) {
      var form = this;

      // Itère en fonction des checkboxes cochées
      $.each(rows_selected, function(index, rowId){
         // Constitution du formulaire
         $(form).append(
             $('<input>')
                .attr('type', 'hidden')
                .attr('name', 'groups[]')
                .val(rowId)
         );
      });

   });
});
// FIN SCRIPT JSON

Youhou ! Ca marche !

 

PS : A ajouter dans le header (en plus de jquery)

<script src="https://cdn.datatables.net/select/1.2.7/js/dataTables.select.min.js"></script>
<link type="text/css" href="https://cdn.datatables.net/select/1.2.7/css/select.dataTables.min.css" rel="stylesheet" >

 

 

Codeigniter : faire un where sur un champ vide ou NULL

logo codeigniter

Encore un pense-bête qui peut servir à d’autres.

Dans une table dont les valeurs vides ne sont pas nulles mais vides, c’est à dire de cette nature ->  » (rien entre deux quotes), je souhaite poser comme condition de ne pas prendre en compte ces valeurs.

La manoeuvre est simple, il faut juste échapper les quotes :

->where('champ != \'\'')

pour que ça fonctionne. Si vous tentez les guillemets, vous vous collerez une erreur.

Egalement, et c’est mon cas, la valeur champ est passée en paramètre pour utiliser plusieurs fois la même méthode d’appel à la requête. Même procédé, en positionnant le champ comme variable :

->where($champ.' != \'\'')

Enfin, si votre champ n’est pas vide mais NULL, il faut ruser tout doucement l’active record qui ne prend pas ISNULL en faisant

->where($champ.' !=', null, false)

Créer une sous-requête avec condition sous Codeigniter

logo codeigniter

Je mets ça en aide-mémoire vu que j’ai un peu ramé pour créer une sous-requête avec condition sous Codeigniter 3.1.6, l’active record ne prenant pas en charge cette fonctionnalité.

Le but de la manoeuvre est de renvoyer 1 pour vrai si un utilisateur est présent dans la table users_groups et si le nom du groupe passé également en paramètre de la fonction est un groupe valide dans la table groups.

Pour réaliser cela, il faut donc sélectionner 1 quand l’id de l’utilisateur est égal à l’id de session utilisateur passé en paramètre et quand l’id du groupe est présent dans la table des groupes d’après son nom complet.

Active record ne permettant pas cela directement, la requête doit être lancée puis son résultat stocké dans une chaîne renvoyée par la méthode get_compiled_insert([$table =  »[$reset = TRUE]]) chargée dans la variable $query que l’on jouera après, ce qui donne (faites super gaffe aux quotes et guillemets) :

$query = $this->db->select("(SELECT 1 as ok FROM users_groups WHERE user_id = '".$user_id."' and group_id = (select id from groups where name = '".$nom_complet_du_groupe."'))", FALSE)->get_compiled_select();

La chaine $query est chargée du contenu de la requête. On peut alors la lancer avec :

$query = $this->db->query($query);

A ce niveau, on serait tenté de lancer un simple return $query->result(); récupéré par le contrôleur. Malheureusement, il sera compliqué de récupérer simplement la valeur unitaire dans une variable à cause de l’erreur Array to string conversion.

Puisque l’on sait qu’on ne renvoie qu’une valeur, on utilise la méthode row() qui ne renvoie… qu’une valeur :

$dac = $query->row();

$dac est maintenant chargé. Comment le renvoyer le nom du champ ? Facilement en fait grâce au select 1 as ok…

Un simple :

return $dac->ok;

vous en convaincra.

C’en est fini. Nous avons créé notre requête avec un sous-select sous condition puis renvoyé un champ unique réglant ainsi le problème des erreurs Array to string conversion ou stdClass object.

Tout rassemblé, l’obtention du résultat se fait avec :

$query = $this->db->select("(SELECT 1 as ok FROM users_groups WHERE user_id = '".$user_id."' and group_id = (select id from groups where name = '".$nom_complet_du_groupe."'))", FALSE)->get_compiled_select();
$query = $this->db->query($query);
$dac = $query->row();
return $dac->ok;