❯~/pixelastic

Faire communiquer PHP et Javascript

Pour un projet sur lequel je suis en ce moment, je dois faire une interface d'administration assez complexe, le tout en AJAX.

Je dois manipuler des données sorties de ma base de donnée, venant de trois modèles différents, mais liés les uns avec les autres. Autant dire que j'ai de gros tableaux imbriqués de données. La page travaille vraiment beaucoup et manipule ces données constamment.

Pour éviter d'avoir à faire une requete sur ma base à chaque fois que j'ai besoin d'une information, j'ai décidé de tout charger une première fois au chargement de la page, puis de modifier coté client les variables sur lesquelles je travaille, pour ne faire des requetes au serveur qu'en upload (update/add) et ainsi éviter au maximum les requetes en download inutiles.

Pour ça, j'utilise tout d'abord le base64 de php pour passer des objets complexes (au format JSON) à mes scripts. Muhammad Hussein Fattahizadeh a pour cela mis à disposition une retranscription de l'encodage/decodage en base64 pour jQuery.

Je peux stocker ainsi mes objets de façon "dormante" dans ma page, et les décoder pour récupérer/modifier les valeurs dont j'ai besoin avant de les réencoder et de les re-ranger dans mon DOM. L'avantage de stocker ces informations en base64 c'est que je ne risque pas de causer d'erreur de syntaxe, de balise non fermée, de caractère interdit, même si je manipule des objets contenant du code HTML et que j'ai besoin de le stocker dans mon propre DOM.

J'utilise aussi à coté de ça le parseur officiel JSON pour parser mes éléments JSON. Il ne me reste plus qu'à passer mon élément JSON en paramètre aux fonction AJAX de jQuery pour mettre à jour mes données.

Créer plusieurs formulaires sur la même page avec FormHelper::create

CakePHP permet de créer très facilement des formulaires dont l'action (add/edit) ainsi que les champs sont remplis automatiquement grâce à FormHelper::create et FormHelper::end.

Il prends pour cela les variables contenues dans le $data du controller, qui sont alors passées au $data de la vue.

Si on veut changer le modèle et/ou les valeurs utilisées par le formulaire, il suffit de faire nos modifications dans le $this->data directement dans la vue avant d'appeller $form->create().

Par contre, si sur une même page on possède plusieurs formulaires, tous d'un modele différent, l'astuce ne fonctionne plus. FormHelper s'est initialisé lors de son premier appel et utilisera le même set de model et valeurs pour les autres.

Même modifier $this->data avant chaque appel à $form->create() ne changera rien.

Il faut donc ruser et modifier la variable utilisée par le Helper, c'est à dire modifier $form->data à partir du second formulaire.

Caracole - Edition directe

En plus de son interface d'administration complète, Caracole permet un mode d'édition rapide directement accessible depuis le coté front-end des sites, pour corriger rapidement un titre ou un coquille qui se serait glissée quelque part.

Grâce à jQuery et cakePHP, cette fonctionnalité est très simple à utiliser. Il suffit pour cela d'avoir les droits d'administration sur le site que l'on veut modifier. Il suffit ensuite d'y naviguer avec un navigateur assez récent (cela signifie que IE6 n'y est pas convié) et s'affichera alors automatiquement en haut à gauche de l'écran un petit switch qui permet d'activer ou de désactiver en un clic le mode édition.

Lorsque le mode d'édition est activé, il suffit de cliquer sur une zone que l'on veut modifier pour la transformer automatiquement en champ de formulaire modifiable (avec intégration de tinyMCE pour les grands textes). Il suffit alors de modifier le texte exactement comme on peut le faire dans l'admin et de valider. La modification est prise en compte automatiquement.

Coté développeur, il suffit d'ajouter une class="editable" ainsi qu'un id formé de la forme "controller-champ-id". Ainsi id="posts-text-12" identifiera le champ "text" du 12e post.

C'est extremement pratique pour modifier des fautes de frappe, ou faire des tas de petites modifications, je m'en sers énormément et c'est un pas de plus pour faciliter l'administration à l'utilisateur final.

Caracole - Référencement

Le référencement est une des préocupations principales de nos clients, tous veulent savoir si avec le produit qu'on leur propose ils seront en mesure d'être les premiers dans les résultats Google.

Bien qu'il n'existe pas de recette miracle pour parvenir à ce résultat, certaines sociétés sont spécialisés dans ce business et déploient des trésors d'ingéniosité pour réussir à faire grimper les pages de leurs clients dans les index des moteurs.

Mais c'est un boulot à part entière, et ce n'est pas le nôtre. Nous faisons des sites web, à chacun sa spécialité.

Malgré tout, il existe plusieurs bonnes pratiques qui permettent d'obtenir de bons résultats sans trop d'efforts, et Caracole s'en sort pas trop mal de ce coté là.

Tout d'abord il tire parti de la façon dont les urls sont gérées par cakePHP, et donc aucune url ne contient de paramètre de la forme ?foo=bar&action=foobar qui sont très mal indexées. Chaque page possède donc sa propre url unique. Les méthodes d'url rewriting proposées de base par cakePHP sont très puissantes et font déjà une très grosse partie du travail.

Dans la même veine, il est possible de définir des 'slugs' pour chaque élément, un slug permet de donner un identifiant à chacune des pages, ce qui signifie qu'au lieu de retrouver un élément par son id dans l'url (un simple nombre, qui ne donne pas d'indication sur le contenu), on peut à la place le remplacer par un slug (du genre 'services', 'nous-contacter', etc). Cela donne des urls du genre /services.html, /posts/1:mon-premier-post, etc

Cela facilite le référencement des pages car des mots clés se rapportant au contenu de la page peuvent déjà être mis dans l'url, qui est une place privilégiée.

Ici encore, il suffit d'ajouter un champ 'slug' dans la table correspondante au modèle pour que celui-ci soit automatiquement généré à partir du nom (il est bien sur possible de le modifier si besoin).

Caracole est aussi doté d'un système de génération automatique de sitemap (le plan du site). Celui-ci est généré en deux versions, une version html pour vos visiteurs, et une version XML (qui indique les fréquences de réactualisations ainsi que les dates de dernière modification) pour les moteurs.

Et pour finir, je vais passer rapidement sur les essentiels : balise et meta description différents pour chaque page, fichier robots.txt, texte alternatif sur les images et accessibilité du markup HTML mais bien sur, Caracole se charge de tout cela aussi.</p> </div> <div class="mt-[38px] flex flex-wrap gap-2"> <a href="/tags/cakephp/" class="border border-tag-line bg-tag-bg px-2 py-[5px] font-mono text-[11px] text-tag hover:border-tag hover:text-tag">#cakephp</a> <a href="/tags/caracole/" class="border border-tag-line bg-tag-bg px-2 py-[5px] font-mono text-[11px] text-tag hover:border-tag hover:text-tag">#caracole</a> </div> </article> <article class="py-[48px] md:py-[60px]"> <h1 class="mb-3 mt-0 max-w-[800px] break-words font-mono text-[26px] font-semibold leading-[1.2] tracking-[-0.05em] text-ink md:text-[clamp(28px,3.6vw,36px)]"> <a href="/2009/02/17/caracole-corbeille/" class="hover:text-accent"><span data-text>Caracole - Corbeille</span></a> </h1> <div class="mb-8 font-mono text-[11px] text-date"> <time datetime="2009-02-17">2009-02-17</time> </div> <div class="prose max-w-[800px] break-words font-sans text-base leading-[1.85] md:text-[17px] [--tw-prose-body:var(--color-body)] [--tw-prose-bold:var(--color-body)] [--tw-prose-headings:var(--color-h2)] [--tw-prose-links:var(--color-accent)] [--tw-prose-bullets:var(--color-muted)] [--tw-prose-counters:var(--color-muted)] [--tw-prose-quotes:var(--color-muted)] [--tw-prose-quote-borders:var(--color-line)] [--tw-prose-hr:var(--color-line)] [--tw-prose-code:var(--color-code)] [--tw-prose-pre-code:var(--color-body)] [--tw-prose-pre-bg:var(--color-panel)] [--tw-prose-th-borders:var(--color-line)] [--tw-prose-td-borders:var(--color-line)] [--tw-prose-captions:var(--color-muted)] prose-headings:mb-[18px] prose-headings:mt-[52px] prose-headings:font-mono prose-headings:font-semibold prose-headings:tracking-tight prose-h1:text-h1 prose-h1:before:content-['\#_'] prose-h2:text-h2 prose-h2:text-2xl prose-h2:before:content-['\#\#_'] prose-h3:text-h3 prose-h3:before:content-['\#\#\#_'] prose-h4:text-h4 prose-h4:before:content-['\#\#\#\#_'] prose-a:underline prose-a:underline-offset-4 prose-a:hover:text-accent-bright prose-code:font-mono prose-code:text-[0.95em] prose-code:font-normal prose-code:before:content-none prose-code:after:content-none prose-pre:rounded-none prose-pre:border prose-pre:border-line prose-pre:leading-[1.7] prose-img:mx-auto prose-img:block prose-img:max-w-full prose-img:rounded-lg prose-img:border prose-img:cursor-zoom-in prose-img:border-image/50 prose-img:shadow-[0_0_36px_color-mix(in_srgb,var(--color-image)_25%,transparent)] prose-img:transition-[border-color,box-shadow] prose-img:duration-200 prose-img:hover:border-image/70 prose-img:hover:shadow-[0_0_36px_color-mix(in_srgb,var(--color-image)_40%,transparent)] prose-figcaption:font-mono prose-figcaption:text-[11px]"> <p>Dans la lignée du principe de brouillon j'ai aussi ajouté une fonction de "Corbeille". Ca fonctionne exactement comme celle du bureau de Windows qu'on est bien content d'avoir quand on a eu la main un peu généreuse dans les fichiers à supprimer.</p> <p>Elle fait office de sécurité et les éléments soit-disant supprimés ne sont pas réellement effacés instantanément, ils passent dans une corbeille intermédiaire. A partir de là, si on se rends compte qu'on a fait une bétise, on peut toujours les restaurer à leur état d'origine. Ou alors vider la corbeille de temps en temps pour faire de la place.</p> <p>Exactement de la même façon que pour les brouillons, il suffit ici d'ajouter un champ 'is_deleted' à la table du modèle correspondant pour lui ajouter la fonction de corbeille, Caracole se charge du reste.</p> <p>Les éléments supprimés ne sont plus retournés par les fonctions de recherche habituelle, à moins de préciser l'option 'includeDeleted'.</p> </div> <div class="mt-[38px] flex flex-wrap gap-2"> <a href="/tags/caracole/" class="border border-tag-line bg-tag-bg px-2 py-[5px] font-mono text-[11px] text-tag hover:border-tag hover:text-tag">#caracole</a> <a href="/tags/cakephp/" class="border border-tag-line bg-tag-bg px-2 py-[5px] font-mono text-[11px] text-tag hover:border-tag hover:text-tag">#cakephp</a> </div> </article> </section> <nav class="mx-auto flex max-w-[800px] items-center justify-between pb-20 pt-[30px] font-mono text-[11px] text-muted" aria-label="Pagination"> <a href="/page65/" class="text-accent hover:text-accent-bright">← Older</a> <span>Page 64 of 65</span> <a href="/page63/" class="text-accent hover:text-accent-bright">Newer →</a> </nav> <footer class="mx-auto flex max-w-[800px] flex-col justify-between gap-3 border-t border-line pb-8 pt-[22px] font-mono text-[11px] text-muted sm:flex-row"> <div class="flex flex-col gap-1"> <span>Personal blog of Tim Carry. Posting mostly on tech-related stuff.</span> <span>EOF — thanks for reading.</span> </div> <div class="flex gap-[18px]"> <a href="https://github.com/pixelastic" class="hover:text-accent">↗ source on GitHub</a> <a href="/atom.xml" class="hover:text-accent">RSS</a> </div> </footer> </div> <script> // Open article images full screen in a native dialog, closed by a click or Escape (() => { const images = document.querySelectorAll(".prose img"); if (!images.length) return; const dialog = document.createElement("dialog"); dialog.className = "m-auto max-h-[96vh] max-w-[96vw] cursor-zoom-out bg-transparent p-0 backdrop:bg-paper/90"; const full = document.createElement("img"); full.className = "max-h-[96vh] max-w-[96vw] rounded-lg border border-image/70"; dialog.append(full); document.body.append(dialog); dialog.addEventListener("click", () => dialog.close()); const open = (image) => { full.src = image.currentSrc || image.src; full.alt = image.alt; dialog.showModal(); }; for (const image of images) { image.tabIndex = 0; image.addEventListener("click", () => open(image)); image.addEventListener("keydown", (event) => { if (event.key === "Enter") open(image); }); } })(); </script> </body> </html>