Bonjour,

Voila je rencontre un petit problème avec mon code.
Je suis débutante en CakePHP mais également en javascript... Ca n'arrange donc pas mon problème ^^'

J'explique ce que je souhaite faire.
Sur mon site, j'ai un tableau recensant mes utilisateurs.

Tableau que voici (dans mon template Users/index.ctp) :

<table cellpadding="0" cellspacing="0">
    <thead>
        <tr id="titreCol">
            <th><?= $this->Paginator->sort('username', 'Identifiant') ?></th>
            <th><?= $this->Paginator->sort('name', 'Nom') ?></th>
            <th><?= $this->Paginator->sort('first_name', 'Prénom') ?></th>
            <th class="emailUser"><?= $this->Paginator->sort('email', 'Email') ?></th>
            <th><?= $this->Paginator->sort('profil_id', 'Profil') ?></th>
            <th><?= $this->Paginator->sort('status', 'Statut') ?></th>
            <th class="actions"><?= __('Actions') ?></th>
        </tr>
    </thead>
    <tbody>
        <?php $i = 0; ?>
        <?php foreach ($users as $user): ?>
        <tr id="<?= $i ?>">
            <td class="username"><?= h($user->username) ?></td>
            <td class="name"><?= h($user->name) ?></td>
            <td class="first_name"><?= h($user->first_name) ?></td>
            <td class="email"><?= h($user->email) ?></td>
            <td class="profil_id"><?= h($user->profil_id) ?></td>
            <?php    
                if($user['status'] == 0)
                {
                    $user->status = "Actif";
                }
                else
                {
                    $user->status = "Inactif";
                }
            ?>
            <td class="status"><?= h($user->status) ?></td>
            <td class="actions">
                <span>
                    <button id="buttonUpdate"><img src="/Intranet/img/update.gif" /></button>
                                        <?php /*echo $this->Html->link(
                                        $this->Html->image('update.gif', array('alt' =>'Modifier')),
                                        array('controller' => 'users', 'action' => 'edit', $user->id),
                                        array('escape' => false))*/ ?>
                 </span>
                <span>
                    <?= $this->Form->postLink(
                        $this->Html->image('delete.png', array('alt' => 'Supprimer')),
                        array('controller' => 'users', 'action' => 'delete', $user->id), 
                        $options = ['confirm' => __('Etes-vous sûr de vouloir supprimer l\'utilisateur {0} {1}?', $user->first_name, $user->name), 'escape' => false]);
                    ?>
                </span>
            </td>
        </tr>
        <?php $i++; ?>
        <?php endforeach; ?>
    </tbody>
</table>

Comme vous pouvez le voir, en face de chaque ligne, j'ai un bouton "modifier". J'aimerai que lorsque je clique sur ce bouton, les champs de la ligne concernée se transforment en input, pour pouvoir modifier l'utilisateur directement dans le tableau.

Pour le moment, j'ai réussi à créer une action javascript en cliquant sur le bouton modifier de la première ligne.

$(document).ready(function()
{
        $("#buttonUpdate").click(function()
        {
            alert('bonjour!');
        });

});

Un morceau de code tout basique quoi. Néanmoins, premier soucis, seul le bouton de la première ligne fonctionne, les autres non.

Second soucis, et le plus important, comment modifier mes champs en input et select ?
J'ai vu qu'il existait une fonction replaceWith qui permet de remplacer une div par une autre, mais là ce serait plutôt des td à la place des div. Est-ce possible ? Et comment lier tout ça à CakePHP ? J'ai déjà une fonction edit, mais je ne sais pas comment la lier à ce que je veux...

Merci par avance pour votre aide, en espérant que mon message soit assez clair ^^'

Ju'

11 réponses


Ferias Quarante
Réponse acceptée

Bonjour,
Alors tout d'abord concernant le bouton update : un id est unique sur une page -> tu ne peux pas mettre sur chaque ligne du tableau un id avec le même nom -> la solution : utiliser les classes par exemple :

// dans ton tableau
<button class="user-update"><img src="/Intranet/img/update.gif" /></button>
// et du coup pour le javascript, par exemple
$('table').find('button.user-update').each(function()
{
     alert('bonjour!');
});

Ensuite pour les inputs tu as 2 solutions je pense :
1 - modifier les td de chaque cellule pour remplacer les informations par des input

$('table').find('.user-update').each(function()
{
    var row = $(this).parents('tr');
    var td_username = row.find('td.username');
    td_username.html('<input type="text" name="username" value="'+td_username.text()+'">');
    // ... ainsi de suite pour le reste des td
});

2 - créer de base avec Cake les inputs avec les données mais les cacher par défaut, et les afficher quand tu cliques sur le bouton update

// dans Cake lors de la création de ton tableau
<tbody>
        <?php $i = 0; ?>
        <?php foreach ($users as $user): ?>
        <tr id="<?= $i ?>">
            <td class="username"><span class="value"><?= h($user->username) ?><input type="text" name="username" value="<?= h($user->username) ?>" class="hide"></td>
            // ...
         </tr>
</tbody>
// dans ton javascript (avec jQuery)
$('table').find('.user-update').each(function()
{
    var row = $(this).parents('tr');
    var td_username = row.find('td.username');
    td_username.find('span.value').addClass('hide');
    td_username.find('input').removeClassClass('hide');
    // ... ainsi de suite pour le reste des td
});

Enfin pour l'enregistrement direct depuis le tableau, le mieux serait de passer par de l'ajax pour ne pas rechager la page : du coup il faudra faire l'operation inverse de la précédente à savoir 1 - enlever les input pour mettre les données ou 2- cacher les inputs et ré-afficher les données modifiées

Ferias Quarante
Réponse acceptée

Bonjour,

Oula il était un peu tard pour répondre hier soir .. j'ai fais quelque erreur de code JS.

$('table').find('.user-update').click(function()
{ ... });

Il faut mettre click et pas each, (ou alors on('click', function() ..) mais cela ca reste d u JS donc tu devrait mieux publier dans le forum javascript.

Ensuite pour l'input qui n'as pas la bonne valeur, je te conseille soit de debogguer le code avec l'inspecteur de ton navigateur / soit de faire du console.log(...) à l'ancienne sur les variables row et td_username du JS pour voir pas à pas où est le problème dans la récupération de la valeur du td de l'username.

Anju
Auteur

Bonjour Ferias Quarante !
Merci beaucoup pour tes réponses :)

Voici mes retours :
Pour le bouton update, ça fonctionne bien, tous les boutons fonctionnent. Néanmoins, lorsque je clique sur un bouton, une fenêtre "bonjour" s'ouvre bien, mais quand je clique sur ok, une autre s'ouvre (au total 9 fois, ce qui représente le nombre de lignes dans le tableau).

Pour les inputs :

  • Première solution :
    Lorsque je clique sur n'importe quel bouton, tous les username se transforment en input vide. Comme je souhaiterai que seul l'username de la ligne se modifie, ne faut-il pas que j'utilise l'id du tr ? :o Et dans ce cas comment ? Et pourquoi les input sont-ils vides alors que si je comprends bien le code JS que tu m'as montré, on fait appel au contenu du champ ?

  • Deuxième solution :
    Peu importe le bouton sur lequel je clique, l'username de la première ligne disparait totalement, sans se transformer en input :(

En réfléchissant, la première solution a quand même l'air plus simple, non ? Si j'utilise des input cachés, cela ne risque-t-il pas de surcharger mon code ? :o

Encore merci pour ton aide !

Ju'

Anju
Auteur

Ah oui ça fonctionne bien là ^^ Merci !

Pour l'input sans valeur, j'avais juste bêtement oublié les "égal" derrière les value... ^^' Donc ça fonctionne bien là !
Par contre, mes deux derniers champs sont des select, et reste vides. Comment faire pour que la liste déroulante se remplisse avec toutes mes options, mais que le select reste sur l'option qui concerne l'utilisateur ?

De plus, je voudrais qu'en plus des champs qui se transforment, je souhaiterai que les boutons de fin de ligne changent également. Il faut le faire sur le même principe que pour les input ? Sachant qu'il faut aussi charger de nouvelles images :o

Désolée de t'embêter ^^'

EDIT :
Pour la modification des boutons du coup je suis partie sur le même principe.

// Dans le javascript
var button_update = row.find('button.user-update');
var button_delete = row.find('button.user-delete');
button_update.html('<button class="user-save"><img src="/Intranet/img/save.gif" /></button>');
button_delete.html('<button class="user-retour"><img src="/Intranet/img/retour.gif" /></button>');

// Dans le tableau
<td class="actions">
                <span>
                    <button class="user-update"><img src="/Intranet/img/update.gif" /></button>
                                        <?php /*echo $this->Html->link(
                                        $this->Html->image('update.gif', array('alt' =>'Modifier', 'class' => 'user-update')),
                                        array('controller' => 'users', 'action' => 'edit', $user->id),
                                        array('escape' => false))*/ ?>
                 </span>
                <span>
                    <?= $this->Form->postLink(
                        $this->Html->image('delete.png', array('alt' => 'Supprimer', 'class' => 'user-delete')),
                        array('controller' => 'users', 'action' => 'delete', $user->id), 
                        $options = ['confirm' => __('Etes-vous sûr de vouloir supprimer l\'utilisateur {0} {1}?', $user->first_name, $user->name), 'escape' => false]);
                    ?>
                </span>

</td>

Toutefois, le bouton delete qui devrait devenir le bouton retour, ne change pas... Je suppose que c'est à cause de ma syntaxe, puisque la seule différence avec l'autre bouton, c'est que le bouton delete est écrit en php. Peut-être que j'intègre mal la classe ?

De même comment faire pour que mon bouton save (qui remplace l'update), soit lié à mon controller "user" et mon action "edit" ?

Pour les boutons se serait plus simple de créer les 4 boutons (update, save, delete et back) et de cacher par défault save et back ; quand tu cliques tu updates tu affiches les input et tu masques update et delete et tu affiches save et back.
Quand tu cliques sur back tu reviens de base (sans les inputs), quand tu cliques sur save tu sauvegardes le tout en ajax et tu reviens aussi de base (sans les inputs).

Pour les select, ce que tu peux faire c'est créer le select que tu veux de base avant le tableau et le cacher, comme ça au lieu de créer le code dans le JS tu prend l'élément cacher et tu l'ajoute au td correspondant.
Sinon la soltuion 2 (qui surcharge le code html certes) aurait était plus simple, avec pour chaque td par exemple

<td class="username">
    <span class="value">XXX</span>
    <span class="input">le code de l'input ou du select</span>
</td>

Comme ca tu n'aurais qu' afficher ou masquer tel ou tel élément selon si tu édite ou pas, le code JS serait plus "lite" et lisible.

Après tout cela n'est pas propre à Cake mais plutôt au JS, tu devrait pulbier dans le forum JS

Anju
Auteur

J'ai donc rajouté 4 boutons comme tu me l'as conseillé. Bon, après ce sont des problèmes de JS que j'ai, donc je vais pas t'embêter avec ça ^^'

Par contre, je pense que j'ai quand même un problème côté CakePHP.
En effet, mon bouton delete est écrit en PHP :

<?= $this->Form->postLink(
                        $this->Html->image('delete.png', array('alt' => 'Supprimer', 'class' => 'user-delete')),
                        array('controller' => 'users', 'action' => 'delete', $user->id), 
                        $options = ['confirm' => __('Etes-vous sûr de vouloir supprimer l\'utilisateur {0} {1}?', $user->first_name, $user->name), 'escape' => false]);
                    ?>

Le problème c'est que du coup, la classe se met sur l'image, alors que moi j'ai besoin d'avoir un bouton avec une classe. En fait, il faudrait que mon icône delete soit écrit de la même façon que le bouton "update" :

<button class="user-update"><img src="/Intranet/img/update.gif" /></button>

Mais je n'arrive pas à faire l'équivalence en HTML... Comme il fait appel à un controller, je ne comprends pas comment faire :(

C'est normal qu'il te mette la classe sur l'image et pas sur le bouton vu que c'est ce que tu as écris ... :)

<?= $this->Form->postLink($this->Html->image('delete.png', array('alt' => 'Supprimer')),
                          array('controller' => 'users', 'action' => 'delete', $user->id), 
                          ['class' => 'user-delete', 'confirm' => __('Etes-vous sûr de vouloir supprimer l\'utilisateur {0} {1}?', $user->first_name, $user->name), 'escape' => false]);
?>
Anju
Auteur

Effectivement... ^^'
Mais du coup, dans mon code JS, faut que je mette la classe hide sur le "a" et non sur "button" pour que ça fonctionne !

Bon, maintenant mes autres boutons ne s'affichent pas, et faut que je trouve le moyen de sauvegarder tout ça...

Le fait de sauvegarder ce serait plutôt du CakePHP, non?
Parce que j'ai ma fonction edit dans mon UsersController, mais comment la lier au bouton save ?

Ma fonction edit :

 public function edit($id = null)
    {
        $user = $this->Users->get($id, [
            'contain' => []
        ]);
        if ($this->request->is(['patch', 'post', 'put'])) {
            $user = $this->Users->patchEntity($user, $this->request->data);
            if ($this->Users->save($user)) {
                $this->Flash->success(__('L\'utilisateur a bien été sauvegardé.'));
                return $this->redirect(['action' => 'index']);
            } else {
                $this->Flash->error(__('L\'utilisateur n\'a pas été sauvegardé. Veuillez réessayer.'));
            }
        }
        $profiles = $this->Users->Profiles->find('list', ['limit' => 200]);
        $this->set(compact('user', 'profiles'));
        $this->set('_serialize', ['user']);
    }

Poour la sauvegarde tu peux par exemple sur le bouton save ajoute dans les options

[
    'data-url' => $this->Url->Build(['controller' => 'Users', 'action' => 'edit', $user->id])
]

Et dans ton JS tu fais un appel ajax, en tutilisant jquery ca fait qqe chose comme ca

$('.bouton-save').click(function(e) {
    e.preventDefault();
    var url = $(this).data('url');
    $.post({
        url : url,
        // liste des parametres 
    });
});
Anju
Auteur

Donc du coup j'ai modifié mon bouton "save", ce qui donne ceci :

<?= $this->Form->postLink(
                        $this->Html->image('save.gif', array('alt' => 'Save')),
                        ['data-url' => $this->Url->Build(['controller' => 'users', 'action' => 'edit', $user->id])],
                        ['class' => 'user-save hide', 'escape' => false])  ?>

Du coup, y'a bien une action sur mon bouton, quand je clique dessus, il semble bien valider. Toutefois, la modification ne s'enregistre pas encore !
Pour l'ajax, je ne vois pas quoi mettre dans la liste de paramètre. Après quelques recherches, j'ai fais quelque chose comme ça, mais j'ai de forts doutes sur la justesse du truc ^^' :

$('.user-save').click(function(e) {
        e.preventDefault();
        var url = $(this).data('url');
        $.post({
            url : url,
            data: data,
              success: success,
              dataType: dataType
        });
    });

Je ne sias pas commetntu fais tes recherches mais le pramètre data doit contenir tout les champs et valeurs comme pour un formulaire, qqe chose comme çà. Après côté controller, il faut que seriazlize le retour de la vue, il y a des exemples sur ce forum et sur internet

$('.user-save').click(function(e) {
        e.preventDefault();
        var url = $(this).data('url');
        var row = $(this)..parents('tr');
        $.post({
            url : url,
            data: { 
                username: row.find('td.username').find('input').val(), 
                name : ...
                // etc pour chaque champs
             }
        }).done(function(data) {
            // mettre les valeurs des inputs dans les champs valeurs et cacher les inputs
        }).fail(function(data) {
            // afficher un message
        });
    });