D-Nada Vos développements
d'application et site web
humain et sur-mesure
contact@d-nada.com

Plume

Plume est un framework Javascript/PHP orienté objet et OpenSource, léger et permettant une facilité et souplesse dans le développement de vos applications Web.
Une création SASU DNada.
Mise à jour le 11/05/2020.

Sommaire

Présentation

Dans les grandes lignes, Plume permet de : Pour bénéficier de ces fonctions (et d'autres), il suffit de faire unrequire("plume.php"); en PHP et un<script src="/plume.js"></script> en HTML.

Exemple typique d'une page HTML :
<!DOCTYPE html>
<html lang="fr">
<head>

<link href="/lib/plume.css" rel="stylesheet"/>
<script src="/lib/plume.js"></script>

</head>
<body>

{.mon_template() … .}

</body>
</html>
D'un script JS :
$.start(function() {
$('mon_id').on('clic', function(event, tag) {

})
$.ajax.ma_methode(42, [1, 2, 3, 5, 7]).then(function(resultat) {

})
});
Et côté serveur PHP :
<?php
// permet d'ajouter des fonctionnalités de debugging au poste indiqué par son IP
define('PLUME_TRACE_IP', '77.0.1.2');
// spécifie une adresse où le développeur pourra recevoir des données de débugging.
define('PLUME_MAIL_REPORT', "report@ma−societe.com");
// ajoute plume au PHP
require('plume.php');
// ouvre la base de données
$base = new Base("mon−client");
// gère les méthodes asynchrones ajax (voir JS : RPC)
Template::ajax(function() use($base) { $base−>close(); });
// affiche la page HTML et les templates (voir HTML : Template)
Template::display();

// exemple de fonction template (dans l'exemple HTML plus haut)
function template_mon_template($pattern) {

}
// exemple de méthode ajax (dans l'exemple JS plus haut)
function ajax_ma_methode($premier = 'default', $deuxieme = false, $etc = null) {

}
?>

HTML

Plume apporte des fonctionnalités au niveau du HTML.

HTML : Template

Le mécanisme de template permet de relier des parties à l'intérieur de page.html, à une fonction PHP, pour générer du HTML dynamique, qui viendra remplacer cette partie statique HTML.

Template : Définition

La syntaxe d'un template est délimité par des accolades, unifié par un caractère non-alphabétique et nommé avec les mêmes règles de nommage des fonctions PHP (alphabétique et espace souligné), suivi d'éventuel paramètres, encadré de parenthèse.
{?nom_de_fonction()   …  ?}
{*nomDeFonction(un, deux, trois) … *}
Les templates peuvent contenir des champs, modifiable depuis la fonction PHP, dont le format est un nom de variable PHP (sans le dollar) encadré d'accolade.
{+fonction()
texte statique {variable_dynamique} texte statique
+}

Template : Principe

Exemple{*xxx() html *} etfunction template_xxx() { /* php */ }.
Pour arriver dynamiquement à ce résultat :
<h1>Liste des satellites de Jupiter</h1>
<ol>
<li>Io</li>
<li>Europe</li>
<li>Ganymède</li>
<li>Callisto</li>
</ol>
On créé une page HTML (exemple page.html), contenant :
<h1>Liste des satellites de </h1>
<ol>
{*satellite()
<li>{nom}</li>
*}
</ol>
On code en PHP, les deux templates "planete" et "satellite" :
<?php
require("plume.php");

function template_planete() {
return "Jupiter";
}

function template_satellite($pattern) {
foreach (['Io', 'Europe', 'Ganymède', 'Callisto'] as $satellite)
$html .= $pattern−>html( [ 'nom' => $satellite ] );
return $html;
}
Et on termine en demandant d'afficher ces templates :
Template::display("page.html");
?>

Template : Imbrication

Les templates peuvent s'imbriquer.
En reprenant l'exemple précédent, on peut afficher plus d'information par satellite.
<h1>Liste des satellites de Jupiter</h1>
<ol>
<li>
Io
<ul>
<li>Diamètre: 3 643Km</li>
<li>Période: 1,769d</li>
<li>Gravité: 1,79m/s2</li>
</ul>
</li>
<li>
Europe
<ul>
<li>…</li>
</ul>
</li>
<li>
Ganymède
<ul>
<li>…</li>
</ul>
</li>
<li>
Callisto
<ul>
<li>…</li>
</ul>
</li>
</ol>
On modifie la page HTML, avec :
{*planete()
<h1>Liste des satellites de {planete}</h1>
<ol>
{+satellite()
<li>
{nom}
<ul>
{.information()
<li>{nom}: {valeur}</li>
.}
</ul>
</li>
+}
</ol>
*}
Et on code en PHP, le template « planete », son template imbriqué « satellite », et le sous-template imbriqué « information » :
<?php
require("plume.php");

$planetes = [
"Jupiter" => [
[
"Io" => [
"Diamètre" => "3 643Km",
"Période" => "1,769d",
"Gravité" => "1,79m/s2",
],
// autre satellite
],
// autre planète
],
];

function template_planete($pattern) {
global $planetes;

foreach ($planetes as $planete => $satellites) {
$html .= $pattern−>html([
'planete' => $planete,
'satellite' => $satellites
]);
}
return $html;
}

function template_satellite($pattern, $satellites) {
foreach ($satellites as $nom => $information) {
$html .= $pattern−>html([
'nom' => $nom,
'information' => $information
]);
}
return $html;
}

function template_information($pattern, $information) {
foreach ($information as $nom => $valeur) {
$html .= $pattern−>html([
'nom' => $nom,
'valeur' => $valeur
]);
}
return $html;
}

Template::display("page.html");
?>

Template : Contrôle

Il est possible de faire quelques tests côté template.
{?tests()
{var_ok?test ok}{var_ko?test ko}
{var_ok!sinon ok}{var_ko!sinon ko}
{var_ok|ou alors ok} {var_ko|ou alors ko}
{{var_ko! <span> {var_ok} </span>}}
?}
<?php
function template_tests($pattern) {
return $pattern−>html([
'var_ok' => 'x',
'var_ko' => '', // ou false ou null
]);
}
?>
Affichera :
test ok
sinon ko
x ou alors ko
<span> x </span>

Template : Paramètrage

On peut passer des paramètres aux fonctions template :
<span>{+parametrage(I, II, III, IV) 1:{un}, 2:{deux}, 3:{trois}, 4:{quatre} +}</span>
<?php
function template_parametrage($pattern) {
return [
'un' => $pattern−>parameter(1),
'deux' => $pattern−>parameter(2),
'trois' => $pattern−>parameter(3),
'quatre' => $pattern−>parameter(4)
]; // affichera : <span>1:I, 2:II, 3:III, 4:IV</span>
}
?>
Voir parameter() pour plus d'information.

Template : relecture en RPC

Il est possible d'utiliser la notion de template, en l'appelant depuis une URL, et en plus, depuis le JavaScript, en utilisant le mécanisme RPC.
<ul id="ma_liste">
{*ma_liste(5)
<li>{num}</li>
*}
</ul>
<script>
// fonction déclenché sur un clic
window.on('click, function() {
$.ajax.ma_liste(42).then(function(html) {
$('#ma_liste').innerHTML = html;
});
});
</script>
<?php
// fonction qui renvoi un contenu HTML
// appelé lors de la demande vers le serveur, avec le nombre 5 figé dans la page
function template_ma_liste($pattern, $valeur_ajax = 0) {
$max = $valeur_ajax ?: $pattern−>parameter(1);
for (number = 1; number < $max; ++ number)
$html .= $pattern−>html([
'num' => $number]);
return $html;
}
// fonction appelé lors d'une demande ajax, et qui va simuler un RPC pour appeler la fonction template, mais avec le nombre 42
function ajax_ma_liste($valeur_ajax) {
return Template::render(template_ma_liste(Template::extract("ma_liste"), $valeur_ajax));
}
?>
Cet exemple utilise les fonctions asynchrone RPC expliquées plus bas et la méthode extract et render expliquées dans le chapitre suivant.

Template : méthodes

L'API template comprend :
Méthode display:
Permet de transformer les templates puis d'afficher la page HTML ainsi générée.
<?php
if ($il_y_a_une_erreur])
Template::display("error.html");
else if ($il_y_a_quelque_chose])
Template::display("//library/other.html");
else
Template::display();
?>
Attention : suite à l'appel de cette méthode, les trace() ne sont plus actif, puisque cette méthode s'occupe de les insérer dans la page HTML.
Méthode html:
Permet de générer dynamique du HTML, en passant des variables pouvant contenir des balises HTML
<html>
{?foo()
Balise {exemple}.
?}
</html>
<?php
function template_foo($pattern) {
return $pattern−>text([ 'exemple' => "<u>prise en compte</u>" ]);
}
?>
Rendra :
Balise prise en compte
Méthode text:
Permet de générer dynamique du HTML, en protégeant les balises HTML.
<html>
{?foo()
Balise {exemple}.
?}
</html>
<?php
function template_foo($pattern) {
return $pattern−>text([ 'exemple' => "<u>non−prise en compte</u>" ]);
}
?>
Rendra :
Balise <u>non-prise en compte</u>
Méthode parameter:
Permet de récupérer les paramètres :
Méthode first:
Renvoi le premier paramètre.
<html>	{?foo(one, two, three)?}	</html>
<?php
function template_foo($pattern) {
return $pattern−>first(); // remplace le template par "one"
}
?>
Méthode second:
Renvoi le second paramètre.
<html>	{?foo(one, two, three)?}	</html>
<?php
function template_foo($pattern) {
return $pattern−>second(); // remplace le template par "two"
}
?>
Méthode third:
Renvoi le troisième paramètre.
<html>	{?foo(one, two, three)?}	</html>
<?php
function template_foo($pattern) {
return $pattern−>third(); // remplace le template par "three"
}
?>
Méthode extract:
Permet d'extraire un template ou la totalité d'un fichier de templates.
  1. Nom du template : si absent, prend la totalité du fichier
  2. Nom du fichier :
    • Si vide ou absent, prend le fichier de même nom que le dossier où se trouve le PHP, suffixé de ".html"
    • Si commence par //, par de la racine du serveur
  3. Render : si vrai, execute la fonction template suivi d'un render sur le Template et renvoi directement la chaîne de caractère HTML.
    <html>
    Partie non−extraite
    {?foo()
    text {field}
    {+bar()
    and {field}
    +}
    ?}
    Partie non−extraite
    </html>
    <?php
    // récupère le pattern "foo" de la page "page.html"
    $pattern = Template::extract("/page.html", "foo");
    // parse simple
    echo $pattern−>html([ 'field' => "test and" ]);
    echo "<br/>";
    // ou parse en profondeur dans les imbrications
    $foo = Template::render(template_foo($pattern));
    echo $foo;

    function template_foo($pattern) {
    return $pattern−>html([ 'field' => "foo" ]);
    }
    function template_bar($pattern) {
    return $pattern−>html([ 'field' => "bar" ]);
    }
    ?>
Rendra :
text test and
text foo and bar
Si vous n'utilisez pas de paramètre "Extra", il est possible de raccourcir cette présentation :
return Template::render(template_models(Template::extract("models", '')));
Par :
return Template::extract("models", '', true);
Méthode render:
Même principe que pour display, mais en lui passant la chaîne de caractère.
<?php
$foo = Template::render("
{+foo()
and {field}
+}
");
function template_foo($pattern) {
return $pattern−>html([ 'field' => "foo" ]);
}
?>
Méthode ajax:
Cette méthode recherche si une fonction RPC est appelé, et si c'est le cas, lance la méthode associée et sort du PHP.
Elle peut prendre un paramètre optionnel qui sera la fonction à appeler en fin de traitement de la méthode, pour fermer un fichier, une base, …
<?php
Template::ajax();
Template::display();

// ou avec action post ajax passé en paramètre
$base = new Base("ma_base");
Template::ajax(function() use($base) { $base−>close(); });

Template::display();
$base−>close();
?>

Define:

Des fonctionnalités plus poussées mais non obligatoire peuvent être mis en fonctionnement.
PLUME_FILE_TRIGGER:
SiPLUME_FILE_TRIGGER est défini comme fonction, elle sera appelée avant toutes transformations, afin de modifier le chemin d'accès au fichier templates.
  1. Paramètre: le chemin du fichier
Cela sert pour modifier le chemin pour d'autre média par exemple :
define("PLUMEFILETRIGGER", function($path) {
global $mobile;
if ($mobile)
return preg_replace('/.(?=w+)$/', '-mobile.', $path);
return $path;
});
PLUME_TEMPLATE_TRIGGER:
SiPLUME_TEMPLATE_TRIGGER est défini comme fonction, elle sera appalée après toutes transformations, afin de préparer cette transformation du template.
  1. Paramètre: la page transformé, mais contenant encore les templates non reconnus du genre :{*un_template_inconnu_dans_php()*}
PLUME_PUR:
SiPLUME_PUR est défini afalse, (par défaut correspond àtrue), Plume ne cherchera pas à protéger les adresses (téléphone, mail, …).

HTML : $event

Ajout l'attribut $event au balise HTML, identique à l'attribut onevent, mais permet d'appeler la fonction JS, en passant les mêmes paramètres et contexte que la $.on
<div onclick="monClick()"></div>
Est identique à :
<div $click="monClick(this)"></div>
De plus, lors de l'appel de la méthode JS :

HTML : calendar

Ajout un nouveau type d'input pour la saisie de jour, avec popup-window associé : de à
<input type="calendar">

<!−−− interval −−−>
<input type="calendar" end="+">
<input type="calendar" begin="">

HTML : slide(s)

Ajout un nouveau type d'input de valeur sous forme d'une glissière.
Accepte les attributs suivants :

slide

Glissière à une seule valeur :
<input type="slide" min="0" max="9" rule="2" for="#slide_value_id" value="1" style="width: 200px;">
<span id="slide_value_id"></span>
valeur :

slides

La différence est au niveau des attributs : Double glissière à deux valeurs :
<input type="slides" min="1" max="9" rule="[1, 4, 7, 9]" for="#slides_value_id" value="2,3" style="width: 300px;">
<span id="slides_value_id1"></span>
<span id="slides_value_id2"></span>
valeur minimum : et maximum :

HTML : strip

Ajout d'un nouveau type d'input de sélection d'énumérés. L'attributenum peut recevoir soit une chaîne de mots, soit une liste de chaînes, soit un objet d'attribut/valeur.
<input type="strip" enum="lundi mardi mercredi jeudi vendredi" value="mardi"/>
<input type="strip" enum="['lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi']" value=""/>
<input type="strip" enum="{lun: 'lundi', mar: 'mardi', mer: 'mercredi', jeu: 'jeudi', ven: 'vendredi'}"/>
Voici la liste des attributs pouvant être utilisés : Exemples :
<html>
<input type="strip" template="rank" name="ranked" value=""/>
<template id="rank" class="creator rank">
<span value="flat" title="Classe de haut en bas"><svg width="24" height="20"><path d="M8,2v18l2,−6h−4l2,6"/></svg></span>
<span value="deep" title="Classe par imbrication"><svg width="24" height="20"><path d="M4,6h18l−6,2v−4l6,2"/></span>
<span value="both" title="Classe par imbrication et de haut en bas"><svg width="24" height="20"><path d="M4,6h18l−6,2v−4l6,2M8,2v18l2,−6h−4l2,6"/></span>
</template>

<input id="format" type="strip" enum="B I U S" multiple="true"/>
</html>
<script>
$.start(function() {
$('format').on('change', function() {
console.log(this.id, this.show);
});
});
</script>

HTML : Segmented

Ajout d'un nouveau type d'input de sélection d'énumérés. L'attributenum peut recevoir soit une chaîne de mots, soit une liste de chaînes, soit un objet d'attribut/valeur.
<input type="segmented" enum="lundi mardi mercredi jeudi vendredi" value="mardi"/>
<input type="segmented" enum="['lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi']" value=""/>
<input type="segmented" enum="{lun: 'lundi', mar: 'mardi', mer: 'mercredi', jeu: 'jeudi', ven: 'vendredi'}"/>
Voir les attributs et exemple de strip pour plus d'explication.

HTML : Menu

Il est possible d'assigner un menu à un élément HTML :

Menu : HTML statique

Directement depuis le HTML, avec l'ajout de l'attribut menu :
<html>
<span menu='["item1", "item2", ]'>Liste des items</span>
<input menu='{i: "Un", ii: "Deux", iii: "Trois", iv: "Quatre", v: "Cinq", vi: "Six", vii: "Sept", viii: "Huit", ix: "Neuf", x: "Dix"}'/>
</html>

Menu : HTML ajax

En spécifiant un nom de fonction PHP RPC suivi de parenthèse encadrant d'éventuel paramètres :
<html>
<span menu='mon_menu(un, deux)' name="hello" id="hola">Bonjour</span>
</html>

Le menu va essayer d'appeler cette fonction, de signature "ajax_mon_menu($attributs, $parametres, …)" pour alimenter son menu.
Le 1er paramètre de cette fonction, est un tableau de tous les attributs de l'élément HTML rattaché au menu, et l'attribut "menu" contient la chaîne des paramètres éventuels passés.
Les autres paramètres correspondent aux paramètres éventuels passés.
<?php
function ajax_mon_menu($attributs, $arg1 = null, $arg2 = null) {
return [$arg1, $arg2];
}
?>
Dans l'exemple :
$attributs = [
'menu' => "un, deux",
'name' => "hello",
'id' => "hola",
];

Menu : HTML JS

L'attribut menu peut contenir le nom d'une variable global, permettant de gérer plus finement le comportement du menu.
Sa structure se compose des méthodes suivantes, toutes optionnelles, qui seront appelées à certain moment la vie du menu : Enfin, les options permettent d'affiner des comportements bien précis.
<script>
window.mon_menu = {
// appelé pour initialiser le menu au démarrage ou suite à un reloadMenu()
load: [ // sous forme d'un tableau indexé
"texte", // le texte de l'item
{ // ou sa version plus complète
text: "texte", // le texte de l'item
html: "<span>texte</span>", // ou sa forme HTML
key: 'clé', // la clé unique associée
alias: '±K', // le raccourci clavier précédé eventuellement de + (alt) et/ou − (shift) (voir plus bas)
disabled: false, // si true, l'item n'est pas sélectionnable
enabled: true, // si === false, l'item n'est pas sélectionnable, si true prioritaire sur le disabled
},
"−−−", // séparateur
false, // séparateur
{ sep: true }, // séparateur
],
load: { // sous forme d'un tableau associatif
key1: "texte",
key2: {
text: "texte",
html: "<span>texte</span>",
alias: 'K',
disabled: false,
enabled: true,
},
key3: "−−−", // séparateur
key4: false, // séparateur
. key5: { sep: true }, // séparateur
},
load: function(menu, update, reload) { // ou sous forme d'une méthode renvoyant un tableau
// renvoi directement un tableau
return { };

// renvoi en asynchrone
$.ajax.rpc().then(function(data) {
update(data);
});
},
// appelé lors d'une demande d'ouverture de menu ou juste avant l'appel du select lors d'un raccourci clavier
open: function(menu, event, update) {
// si le menu a déjà été récupéré, le renvoi
if (this.cache)
return this.cache;
// sinon, le demande au serveur, le stock puis le renvoi
if (update) { // test que l'on peut supprimer si pas de raccourci clavier dans ce menu
$.ajax.rpc(menu.trigger.value).then(data => update(this.cache = data));
}

return true; // autorise l'ouverture
return { }; // ouvre avec de nouvelle donnée (même principe que pour le load)
return false; // interdit l'ouverture
return; // interdit l'ouverture
},
// appelé lors d'une sélection d'un item du menu
select: function(menu, item, index, event) {
switch (item.key) {
case 'clé':
// …
break;
}
return true; // valide et referme le menu
return "valeur"; // valide sur la valeur renvoyée et ferme le menu
return false; // ne valide pas la sélection et ferme le menu
return; // ne valide pas la sélection et laisse ouvert
},
// si présent, sera appelé sur un raccourci clavier à la place d'un open() + select()
alias: function(menu, item, index, event) {
this.open(menu);
if (item.enabled || item.enabled === undefined && ! item.disabled)
return this.select(menu, item);
return true; // arrêt et supprime la propagation du raccourci
return false; // continue à chercher sur d'éventuel autre menu et supprime la propagation du raccourci
return; // propage le raccourci
},
// appelé sur le survol d'un item
over: function(menu, item, index, event) {
return false; // interdit le survol de l'élément
},
// appelé lors d'une demande de fermeture du menu
close: function(menu) {
},
// option supplémentaire sur le comportement du menu
option: {
right: undefined, // le menu apparait sur le clic gauche (par défaut), le clic droit affichant le menu−contextuel du navigateur
right: false, // le menu apparait sur le clic gauche et le clic droit ne permet plus d'afficher le menu−contextuel du navigateur
right: true, // le menu apparait que sur le clic droit
delay: 1000, // permet de retarder l'affichage du menu de 'delay' millisecondes
unique: true, // si vrai, désélectionne automatiquement tous les autres éléments
cursor: false, // si faux, n'affiche pas de curseur "menu" au survol de l'élément
},
event: null // est mis à jour par PlumeMenu
};
</script>
Les paramètres passés à ces méthodes, sont : Pour raison de temps de réaction, le update du open() est égale à undefined et ne peut donc être appelé.
La méthode alias (ou select si absente) sera appelée qu'une seule fois, même si ce menu est rattaché à plusieurs éléments.

Menu : Methodes.js

Il est possible de contrôler le menu depuis JS.
Un espace de fonction, nommé "menu", contenant un ensemble de fonctions, est ajouté à l'élément HTML déclencheur auquel est rattaché le menu.
Méthode : PlumeMenu()
Il est possible de rattacher un menu à un élément HTML depuis JS :
<script>
// version simple
PlumeMenu.attach($('SPAN'), ["item1", "item2", "itemx"]);

// ou version plus complète
PlumeMenu.attach($('SPAN'), menu_conf, 'mon_menu');
</script>
Cette méthode statique permet de rattacher un menu nommé "mon_menu" sur cet élément "SPAN", menu défini par la variable "menu_conf".
Si un seul menu dans le HTML, le nom est optionnel.
Le nom "mon_menu" ne peut contenir que des lettres, chiffres et ".", "_", "-".
Si l'élément est éditable (input, …), celui-ci interagirera avec le menu.

Méthode : menu.reloadMenu()
Une méthode element.reloadMenu() permet de provoquer une modification du menu, depuis js :
input.menu.reloadMenu("nouvelle valeur");
Elle passe cette nouvelle valeur, en appelant le mécanisme deload puis deselect.
Un appel à cette fonction sans paramètre, ne fera que redéclencher le mécanisme deload, sans appelerselect.
Méthode : menu.activeMenu()
Cette méthode permet de désactiver ou réactiver un menu, en lui passant respectivementfalse outrue.
Par défaut, le menu est bien entendu activé.
input.menu.activeMenu(false);

Menu : Raccourcis clavier

Les raccourcies claviers appel uniquement la méthodeshortcut sans gérer le contexte, c'est à la méthode de vérifier si l'action est possible et sur quoi elle est possible.
Ils sont toujours associés à la touche "⌃ Contrôle" sur un Windows ou un Linux, ou la touche "⌘ Commande" sur un Macintosh, et désactivé sur un mobile.
Il est possible d'associé la touche "Majuscule" en précédent le raccourci par le signe "-" et/ou la touche "Alt/Option" par le signe "+".
Certains raccourcis sont à éviter s'ils entrent en conflit avec les raccourcis du navigateur, voir carrément inactivé (raccourci "N" par exemple).
Voici le nom des touches particulières (et leurs caractères d'affichage utilisés) :

Menu : Exemples

Exemple simple statique :
<input type="text" menu="[Monsieur Madame Mademoiselle]"/>

Exemple avec clé différente de la valeur
<html>
<body>
<button search="monsieur">Cliquer ici pour afficher les messieurs</button>
<button search="madame">Cliquer ici pour afficher les dames</button>
<span>Ceci est mon menu</span>
</body>

</html>

<script>
$.start(function() {
// rattache le menu depuis js (on aurait pu faire <span menu="menu_conf"> aussi)
const menu = $('SPAN');
PlumeMenu.attach(menu, menu_conf, 'mon_menu');

// active les déclencheurs
$$('BUTTON').on('click', button => menu.reloadMenu(button.att('search')));

// gestion du menu
window.menu_conf = {
caches: {},

load: function(menu, update, reload) {
if (! reload) // ne rien faire au démarrage, uniquement lors d'un déclenchement
return;

// gère une notion de cache supplémentaire
const cache = caches[reload];
if (cache)
return cache;

// appel le serveur si pas en cache
$.ajax.mon_menu(reload).then(function(data) {
caches[reload] = data;
update(data);
});
},

select: function(menu, item, index, event) {
trace('la clé choisie', item.key);
return item.value;
}
}
});
</script>

<?php
// init plume, base et ajax
require('plume.php');
$base = new Base("ma_base");
Template::ajax(function() use($base) { $base−>close(); });

// appelé lors d'un déclenchement d'un load n'ayant pas le cache
function ajax_mon_menu($civilite) {
global $base;
return $base−>select("select id, texte from ma_table where civilite = ", $civilite);
}
?>
Voir la gestion des bases de données pour plus d'explication sur$base et la gestion des RPC pour plus d'explication sur ajax.

HTML : modal

Exemples :

Propose l'affichage d'une boite d'information de type modal (bloquant temporairement ou jusqu'à l'action de l'utilisateur par validation du message)
Cette méthode est toujours en mode asynchrone, en d'autre terme, elle revient immédiatement alors que le modal n'est peut être pas encore affiché.
La réponse (le bouton appuyé par l'utilisateur) se faisant par fonction callback asynchrone passé en dernier paramètre ou par une promesse JS.
Le bouton le plus à droite est actionné par la touche retour-chariot⏎, et celle la plus à gauche par la touche d'échappement␛.

Les paramètres sont :
En dehors du premier et dernier bouton, les éventuels raccourcis clavier sont toujours couplés à la touche méta (commande⌘ sous MacOS, Contrôle sous Windows/Linux), qui peut apparaitre à côté des boutons associés si maintien prolongé de la touche méta.
<script>
// temporaire (sans gestion du retour de délai)
$.modal("Merci beaucoup.");

// information (sans gestion du bouton OK)
$.modal("Un erreur est survenue: " . error.message, "OK");

// choix à deux boutons avec retour asynchrone
$.modal("Merci de valider cette action", {cancel: "Annuler", ok: "D'accord"}, function(action) {
if (action == "ok")

});

// choix à deux boutons avec retour en promesse
$.modal("Merci de valider cette action", {cancel: "Annuler", ok: "D'accord"}).then(function(action) {
switch (action) {
case "ok":

}
});

// choix à trois boutons avec raccourci clavier (escape est automatiquement attribué au 1er "cancel" et retour−chariot au dernier "D'accord")
$.modal("Merci de valider cette action", {cancel: "Annuler", other: {"J'hésite", key: "J"}, ok: "D'accord"}).then(function(action) {
switch (action) {
case "ok":

}
});

// fenêtre avec icone d'alerte
$.modal("<span style='font−size:36px'>⚠️</span> Une exception est survenue.\n\n" + error, "OK");
</script>
À noter que le callback permet de passer le this avec cette syntaxe :
window.quarantedeux = 'XXXXII';
var ma_structure = {
quarantedeux: 42,
une_methode: function() {

$.modal("Callback avec le problème de this", "OK", function(action) {
trace('callback sans this', this.quarantedeux); // affichera le 42 global à window, donc en chiffre romain
});

$.modal.call(this, "Callback avec le problème de this", "OK", function(action) {
trace('callback avec this', this.quarantedeux); // affichera le 42 local à ma_structure, donc en chiffre numérique
});
}
}

JS

Plume apporte des fonctionnalités supplémentaires au Javascript et facilite le développement.
La principale est la classe$ qui est un querySelector évolué.
Mais implémente également d'autres méthodes, tel que :

JS : trace()

trace() permet d'utiliser la fonction console.log, uniquement en mode développement (voir PLUME_TRACE_IP).
Avec la possibilité de formater la sortie avec un formatage de type (format)[#### String.format()], les paramètres n'ont utilisé par le format, seront affichés après le formatage.
Exemples :
trace("hello");
trace("valeur[", input.value, ']', window); // affiche sur la console : valeur[ (tabulation) 42 (tabulation) ] (tabulation) <Window>
trace("valeur[%d]", input.value, window); // affiche sur la console : valeur[42] (tabulation) <Window>

JS : $

Permet de rechercher un élément dans le Document de la page HTML ou depuis un autre élément parent :
ele = $('abc');			// recherche un élément "abc" dans tout le Document
ele = parent.$('abc'); // recherche un élément "abc" depuis l'élément "parent"
C'est une version évolué dequerySelector().

Pour une recherche uniquement sur le body, il faut employer la syntaxe$() :
ele = $().$('SCRIPT');	// recherche une balide <script> uniquement dans le <body>
ele = $('SCRIPT'); // recherche une balide <script> dans tout le Document, donc dans le <head>, le <body>, après le <body>, …
Le deuxième paramètre optionnel peut être un numérique indiquant qu'il doit renvoyer le xème élément trouvé :
<html>
<ul>
<li>un</li>
<li>deux</li>
<li>trois</li>
</ul>
</html>
<script>
ele = $('LI', 2); // renverra <li>deux</li>
</script>
Si la recherche n'abouti pas, la méthode renvoiundefined.

La recherche se fait sur les paramètres décrits dans les chapitres suivants.

$ : id

Recherche une balise HTML par son ID, qui doit être constitué uniquement de lettre, chiffre et des caractères "-" et "_".
Attention : cette syntaxe est la seule à faire une distinction entre les majuscules et minuscules : Cette exception "tout majuscule" uniquement présente dans la syntaxe$() est une facilité pour le développeur.
ele = $('div');			// renverra <… id="div">
ele = $('DIv'); // renverra <… id="DIv">
ele = $('DIV); // attention : dû aux majuscules, ne renverra pas <… id="DIV"> mais plutôt le 1er DIV trouvé dans Document
Attention également à la syntaxeélément.$('DIV') (vu plus bas) qui recherche normalement sans l'exception du "tout majuscule" de la syntaxe précédente :
ele = bar.$('div');		// attention : dû à une recherche depuis un élément, ne renverra pas <… id="div"> mais plutôt le 1er DIV enfant de bar

$ : #ID

Si vous souhaitez rechercher un ID qui est tout en majuscule, ou par méthodologie de travail, il faut précédé l'ID d'un dièse :
ele = $('#div');		// renverra <… id="div">
ele = $('#DIv'); // renverra <… id="DIv">
ele = $('#DIV'); // renverra <… id="DIV">
ele = $('# div'); // renverra une erreur de syntaxe
ele = bar.$('#div'); // sera identique à $('#div')

$ : Nom de l'élément

Par son tagName (qui doit être tout en majuscule si recherche dans tout le Document) :
ele = $('DIV');			// renverra le 1er <div> trouvé dans le Document
ele = bar.$('div'); // renverra le 1er <div> enfant de bar
ele = $('DIv'); // attention : dû à une minuscule, ne renverra pas le 1er <DIV> dans le Document, mais plutôt un id égale à "DIv" <… id="DIv">
Attention : il est recommandé de mettre en minuscule le nom des balises, certaines versions de navigateurs (tel que FireFox 86.0 ou Safari 14.0), ne trouvent pas certaines balises tel que div.$('SVG'), mais n'auront aucune problème avec div.$('svg').

$ : Nom de la classe

Par son attribut de classe :
ele = $('.foo');		// renverra le 1er élément contenant la classe "foo" <… class="… foo …">
ele = bar.$('.foo'); // renverra le 1er <… class="… foo …"> enfant de bar

$ : Type

Par son attribut type :
ele = $('?foo');			// renverra le 1er <… type="foo">
ele = bar.$('?foo'); // renverra le 1er <… type="foo"> enfant de bar

$ : Nom

Par son attribut name, avec des variantes (^$*) possibles :
ele = $('@foo');			// renverra le 1er élément dont le name égale à "foo" <… name="foo">
ele = bar.$('@foo'); // renverra le 1er <… name="foo"> enfant de bar
ele = $('@^foo'); // renverra le 1er élément dont le name commence par "foo", exemple: <… name="foobar">
ele = $('@$foo'); // renverra le 1er élément dont le name fini par "foo", exemple: <… name="barfoo">
ele = $('@*foo'); // renverra le 1er élément dont le name fini par "foo", exemple: <… name="barfoobar">

$ : Attribut

  1. [attribut]: sur un attribut précis (avec les variantes traditionnelles) :
    ele = $('[hidden]');			// recherche tous les éléments dans le Document ayant un attribut hidden
    ele = $('[hidden=true]) // recherche tous les éléments dans le Document ayant un attribut hidden à true

$ : Hierarchique

En remontant à son parent :
ele = tag.$('^');				// renverra le parent de tag
ele = tag.$('^5'); // renverra le 5ème parent de tag
ele = tag.$('^div'); // renverra le 1er parent DIV de tag
ele = tag.$('^=div'); // idem mais en incluant en 1ère recherche, le tag lui−même
ele = tag.$('^=main.foo@bar'); // idem mais en cherchant un parent de genre <MAIN class="abc foo def" name="bar">
ele = tag.$('^ div'); // attention: dû à l'espace, la recherche est tag.$('^').$('DIV')

$ : À côté

En cherchant l'élément de même niveau situé après lui :
ele = tag.$('+');				// renverra le 1er élément à la suite et au même niveau que tag
ele = tag.$('+5'); // renverra le 5ème élément à la suite et au même niveau que tag
ele = tag.$('+div'); // renverra le 1er DIV à la suite et au même niveau que tag
ele = tag.$('+div.foo?bar'); // idem mais recherche un élément de genre <DIV class="abc foo def" type="bar">
ele = tag.$('+ div'); // attention, dû à l'espace, la recherche est tag.$('+').('DIV')
Ou en cherchant l'élément de même niveau situé avant lui :
ele = tag.$('');				// renverra le 1er élément en amont et au même niveau que tag
ele = tag.$('−5'); // renverra le 5ème élément en amont et au même niveau que tag
ele = tag.$('−div'); // renverra le 1er DIV en amont et au même niveau que tag
ele = tag.$('−div.foo@bar'); // idem mais recherche un élément de genre <DIV class="abc foo def" name="bar">
ele = tag.$('− div'); // attention, dû à l'espace, la recherche est tag.$('−').('DIV')

$ : $$

La recherche peut renvoyer un tableau, avec la méthode$$ :
elements = tag.$$('div');		// renverra un tableau de tous les DIV contenu dans tag
elements = $$('INPUT'); // renverra tous les INPUT contenu dans le Document
elements = $$('DNADA'); // renverra un tableau vide, puisque le W3C n'a pas encore accepté la balise HTML <dnada> :)
Le deuxième paramètre optionnel peut être : Renverra :
[
0: <li name="un">,
1: <li name="deux">,
2: <li name="trois">,
3: <li name="quatre">,
4: <li name="cinq">,
5: <li name="six">,
6: <li name="sept">,
length: 7,
un: <li name="un">,
deux: <li name="deux">,
trois: <li name="trois">,
quatre: <li name="quatre">,
cinq: <li name="cinq">,
six: <li name="six">,
sept: <li name="sept">,
]
Une particularité concernant les balises partageant les mêmes noms, tel que les inputs de type radio, où l'APPOINT renverra si besoin, un tableau associatif contenant toutes les balises de même nom.
<html>
<form>
<input name="un" type="checkbox"/>
<input name="deux" type="radio" value="2.1"/>
<input name="deux" type="radio" value="2.2"/>
<input name="deux" type="radio" value="2.3"/>
<input name="trois" type="checkbox"/>
<input name="quatre" type="checkbox"/>
</form>
</html>
<script>
trace( $$('INPUT', $.APPOINT) );
</script>
Renverra :
[
0: <input name="un">,
1: <input name="deux" value="2.1">,
2: <input name="deux" value="2.2">,
3: <input name="deux" value="2.3">,
4: <input name="trois">,
5: <input name="quatre">,
length: 6,
un: <input name="un">,
deux: [
<input name="deux" value="2.1">,
<input name="deux" value="2.2">,
<input name="deux" value="2.3">,
],
trois: <input name="trois">,
quatre: <input name="quatre">,
]

$ : tableau

Et inversement, la recherche peut s'effectuer sur un tableau indexé :
$element1 = [element1, element2].$('abc');
$elements = $$('TABLE').$$('CAPTION');

$ : groupe

Il est possible de faire une recherche groupée :
<html>
<main>
<dl>
<dt id="10">Titre 1</dt>
<dd>
<ul>
<li id="11">Titre 1.1</li>
<li id="12">Titre 1.2</li>
</ul>
</dd>
<dt id="20">Titre 2</dt>
<dd>
<ul>
<li id="21">Titre 2.1</li>
<li id="22">Titre 2.2</li>
</ul>
</dd>
</dl>
</main>
</html>
<script>
elemnts = $('MAIN').$$('LI|DT'); // renverra tous les LI puis les DT : <li id="11">, <li id="12">, <li id="21">, <li id="22">, <dt id="10">, <dt id="20">
elemnts = $('MAIN').$$('LI,DT'); // renverra dans l'ordre trouvé : <dt id="10">, <li id="11">, <li id="12">, <dt id="20">, <li id="21">, <li id="22">
</script>

$ : chaînage

Enfin, on peut chaîner les recherches dans la chaîne de recherche :
$$('#marqueur ^4 + DIV INPUT?text.valide@personnel[nom][]')
ou chaîner par appel successif :
$('marqueur').$('^4').$('+').$$('DIV').$$('INPUT?text.valide@personnel[nom][]')
Les deux cas renvoyant un tableau identique de 3 éléments HTML, d'id = « un », « trois » et « sept », pour l'HTML suivant :
<body>
<article>
<section>
<div>
<fieldset>
<ul>
<li id="marqueur">
</li>
</ul>
</fieldset>
</div>
</section>
<section>
<div>
<fieldset>
<input class="valide" name="personnel[nom][]" id="un"/>
<input class="invalide" name="personnel[nom][]" id="deux"/>
<input class="important valide" name="personnel[nom][]" id="trois"/>
<input class="valide" name="personnel[prenom][]" id="quatre"/>
</fieldset>
</div>
<input type="text" class="valide" name="personnel[nom][]" id="cinq"/>
<div>
<fieldset>
<input class="invalide" name="personnel[nom][]" id="six"/>
<input class="valide" name="personnel[nom][]" id="sept"/>
<input class="valide" name="personnel[prenom][]" id="huit"/>
</fieldset>
</div>
</section>
</article>
</body>

JS : $.for()

$.for() permet d'appliquer une boucle intelligente et protégée. Les paramètres sont :
  1. Objet : sur lequel s'appliquera la boucle.
  2. Fonction : qui sera appelée, avec les paramètres classiques :
    1. La valeur
    2. La clé ou index
    3. L'objet
  3. This : optionnel qui sera affecté au this de la fonction appelée. L'object sera passé si ce This est omis.
    $.for([], function(valeur, index) {}, this);	// itère comme un [].forEach, avec passage du this optionnel
    $.for({}, function(valeur, index) {}); // itère sur les attributs d'un objet (pas les function)
    $.for(NodeListe, function(valeur, index) {}); // itère même si non itérable
    $.for(42, function(valeur, index) {}); // itère 42 fois (et renvoi un [] comme si c'était un map())

JS : $.create()

Cette méthode permet de créer un nouvel élement, et renvoi cet élément :
$.create();				// dans le document (donc pas visible, à vous de faire un appendChild insertBefore)
$().create(); // dans le <body>
$('DIV').create(); // dans un DIV existant
On lui indique le type d'élément à créer, en premier paramètre :
element.create('TAGNAME);			// son tagName, exemple "DIV" (non sensible à la case)
element.create("<tag>"); // à partir de balisage HTML
element.create("texte simple"); // créer simplement du texte
element.create(element); // clôner un élément existant, exemple $("mon_id")
element.create($(TEMPLATE)); // crée un nouvel ensemble d'élément HTML en copiant le <template> dans <element>
element.create("DIV").create("SPAN").innerText = "texte"; // permet d'ajouter un <div><span>texte</span></div> à <element>
On peut lui attribuer en 2ème paramètres soit le nom d'une classe CSS, le name, le id, soit des attributs lors de sa création :
element.create(tag, {id: "xxx", style: {backgroundColor: 'yellow'}});
element.create(tag, "ma_classe"); // équivalent à element.create(tag, {class: "ma_classe"});
element.create(tag, "#mon−id"); // équivalent à element.create(tag, {id: "mon−id"});
element.create(tag, "@le_nom_de_cet_element"); // équivalent à element.create(tag, {name: "le_nom_de_cet_element"});
Puisque cette méthode utilise $.att() pour gérer les attributs, on peut utiliser un "inner*", directement sous forme d'attribut, afin d'enchaîne avec le this :
const mon_element = $().create('DIV', {innerText: "Ceci est un exemple de texte"});
// est identique à
const mon_element = $().create('DIV');
mon_element.innerText = "Ceci est un exemple de texte";
On peut spécifier en 3ème paramètre, sa position dans l'élément :
element.create(tag, false);				// soit en dernier élément si omis ou false.
element.create(tag, false, element); // avant un élément
element.create(tag, false, true); // en 1er
element.create(tag, false, x); // en xème élément
Si pas besoin d'attribut, passer soit{}, soitnull, soit false.

JS : $.att()

Permet de lire ou d'assigner une valeur à un attribut.
Si le 1er paramètre est : Plusieurs attributs spécifiques virtuels ont été ajoutés :

JS : $.design()

Permet d'ajouter, enlever ou vérifier une classe CSS à un élément.
Le premier paramètre représente la ou les classes visées.
Le deuxième paramètre, l'action :

JS : $.css()

Permet de lire le style CSS calculé ou d'affecter de nouveaux styles CSS.
var css = element.css("border");				// renvoi le border calculé
var css = element.css("border margin"); // renvoi un tableau contenant le border et la margin calculé
var css = element.css(["border" , …]); // renvoi un tableau de style
var tag = element.css("border: 1px ; …"); // affecte le bording et d'autre valeur, …
var tag = element.css({border: '1px' , …}); // même principe mais passé sous forme d'un object.
var tag = element.css(["border"], source); // clone les css de la source
var tag = element.css({border: 0}, source); // clone avec des valeurs par défaut
Le nommage accepte autant la syntaxe conventionnelle kebab-case]() que la syntaxe condensée [camelCase que
Certains styles peuvent être considéré en affectation ou en lecture (en ajoutant le suffixe "i") comme des valeurs numériques.
Cela s'applique sur les styles : margin, border, padding, mais aussi tous les suffixes : top, right, bottom, left, height et width.
Idem avec le suffixe w (pour width) et h (pour height) permettant d'avoir respectivement la somme du left+right ou la somme du top+bottom, que l'on pourra appliquer en lecture à margin, border et padding.
Même principe pour la lecture en mode hexadécimal, des couleurs qui finissent par "color", en ajoutant le suffixe "x" (background-colorx, borderColorx, colorx, …).
var css = element.css("border");				// renverrait (par exemple) la chaîne "6px 12px 6px 12px"
var css = element.css("borderi"); // renverrait plutôt un tableau de valeur numérique [6, 12, 6, 12]
var css = element.css("padding−top"); // renverrait (par exemple) la chaîne "8px"
var css = element.css("paddingTopi"); // renverrait plutôt la valeur numérique 8
var css = element.css("borderh"); // renverrait une valeur numérique correspondant à borderTop + borderBottom
var css = element.css("marginw"); // renverrait une valeur numérique correspondant à marginLeft + marginRight
element.css("border", 12); // affecterait 12px sur tous les bords
element.css("margin", [12, 6, 10, 8]); // affecterait une marge de top:12px, right:6px, bottom:10px et left:8px
var css = element.css("backgroundColor"); // renverrait (par exemple) la chaîne "rgb(1, 128, 255)"
var css = element.css("backgroundColorx"); // renverrait plutôt la chaîne 0180ff00

JS : $.setPos(), $.setSize(), $.setRect()

Permet de positionner (avecstyle.left etstyle.top) et dimensionner (avecstyle.width etstyle.height) l'élément.
tag.setPos(point);			// positionnement
tag.setSize(point); // dimensionne
tag.setSize(rect); // dimensionne en utilisant la taille du rect
tag.setRect(rect); // positionnement et dimensionne
Ces méthodes utilisent des notions de Point et de Rect.

JS : $.position()

Permet de lire les dimensions et positions absolues d'un élément, avec les paramètres suivants : Renvoi l'objet (si le type de retour n'est pas spécifié) :
{
inner: {
topLeft, // Point haut gauche (voir plus bas l'objet Point)
botRight, // Point bas droit
size, // Point représentant la taille
},
padding: {
topLeft, // …
botRight, // …
size, // …
top, // valeur entière représentant la taille du padding−top
left, // …
bottom,
right
},
border: {
topLeft,
botRight,
size,
top,
left,
bottom,
right
},
margin: {
topLeft,
botRight,
size,
top,
left,
bottom,
right
}
}
Cette méthode utilise des notions de Point et de Rect.
liste des valeurs renvoyées par position()

Voir le chapitre sur Rect() pour une utilisation plus poussée de cette méthode.

JS : $.on()

Appel une fonction lorsqu'un évenement se déclenche sur un élément HTML.
On peut associer un ou plusieurs événements à un ou plusieurs éléments HTML.
Cette méthode accepte les paramètres suivants :

JS : $.off()

Permet de détacher un évenement :
// ajoute
$('main').on("mousedown", trigger);

// retire
$('main').off("mousedown", trigger);

JS : Point

Cet objet représente un point, avec les attributs{x, y}

Point : Création

La création d'un point se fait par new Point(), en lui passant le x,y ou un élément DOM (event ou tag).
let point = new Point();							// point de coordonnée (0,0)
let point = new Point(x, y); // point en utilisant 2 valeurs numériques
let point = new Point([x, y]); // fonctionne avec un tableau
let point = new Point({x: 1, y: 2}); // fonctionne avec une simple structure
let point = new Point({topLeft: {x: 1, y: 2}}); // fonctionne avec une structure topLeft
let point = new Point({left: 1, top: 2}); // fonctionne avec une simple structure
let point = new Point(event); // point à partir du clientX et clientY
let point = new Point(element); // point à partir du offsetLeft et offsetTop
let point = new Point(point); // duplique
let point = Point(x, y); // sucre du new Point()
let point = point1.copy(); // duplique le point

Point : Modification

Il est possible de "deplacer" un point.
La méthodeadd applique un addition (this + paramètre), alors quesub applique une soustraction (this - paramètre).
La méthodecut applique une soustraction inverse (paramètre - this).
D'autres méthodes existent permettant de minimiser (min), maximiser (max), appliquer un produit (prod) ou arrondir (round).
Ces méthodes sont doublées :
  1. Tout en minuscule : qui applique le résultat sur le this, avant de le renvoyer.
  2. Commence par une lettre Majuscule : même mécanisme, mais en clônant le this avant, afin de ne pas modifier le this d'origine.
    point.add(point1).add(x, y);				// ajout point1 à point et le renvoi. On peut dire qu'il déplace le point.
    let p = point.Add(point1, …); // Même principe que add() mais crée d'abord un nouveau Point afin de ne pas toucher à point

    point.sub(point1).sub(x, y); // renvoi un nouveau point, enlevé de la liste de points passés en paramètre
    let p = point.Sub(point1, …).back(x, y); // Même principe que sub() mais crée d'abord un nouveau Point afin de ne pas toucher à point

    point.cut(point).cut(x, y); // renvoi un nouveau point, correspondant au point du paramètre − point (this)
    let p = point.Cut(point); // Même principe que cut() mais crée d'abord un nouveau Point afin de ne pas toucher à point

    point.min(point1).min(x, y); // affecte à point le point contenant les coordonnées minimum entre lui et point1 (ou x,y)
    let p = point.Min(point1.min(x, y); // Même principe que min() mais crée d'abord un nouveau Point afin de ne pas toucher à point

    point.max(point1).max(x, y); // affecte à point le point contenant les coordonnées maximum entre lui et point1 (ou x,y)
    let p = point.Max(point1.min(x, y); // Même principe que max() mais crée d'abord un nouveau Point afin de ne pas toucher à point

    point.prod(facteur); // Modifie le point en le multipliant par le facteur puis le renvoi
    let p = point.Prod(facteur); // Même principe que prod() mais crée d'abord un nouveau Point afin de ne pas toucher à point

    point.round(round); // Modifie et renvoi le point arrondi à l'entier suivant round : −1: floor, 0: trunc, 1: round, 2: ceil
    let p = point.Round(round); // Même principe que round() mais crée d'abord un nouveau Point afin de ne pas toucher à point

    point.x = 0;
    point.y = 6.56;

Point : Comparaison

Un ensemble de méthode permettant de comparer les points entre eux.
let t = point.compare(point2);				// renvoi true si point a les mêmes coordonnées que point2
let t = point.angle(point2); // renvoi false si point2 n'est pas viable, 0 s'il est égal à point, valeur en radian > 0 indiquant la direction, si différent
let t = point.includes(rect); // renvoi true si point est à l'intérieur de rect −> strictement identique à Rect.includes(point) mais partant du point
valeurs renvoyé par point.angle()

Point : Utilisation

Il est possible de récupérer et modifier les attributs x et y directement.
IL est également possible d'utiliser le point pour positionner ou dimensionner un élément HTML :
let x = point.x;
point.y = y;
element_html.setPos(point); // positionne le top et left de l'élément
element_html.setSize(point); // positionne le height et width de l'élément
element_html.setSize(tag); // positionne le height et width de l'élément par rapport au dimension de tag
point.trace(1); // permet d'afficher visuellement un point en trace dans la fenêtre. Le paramètre permet d'unifier les traces entre eux, un false efface tout.
// exemple en positionnant un canvas
$('CANVAS').setPos(point);

Enfin, il est possible de tracer visuellement le point pour le développeur, par une représentation sous forme de croix, en utilisant la méthodetrace.
Si le paramètre est vrai, il affiche une trace, sinon, il efface la trace.
Si le paramètre est une chaîne de caractère ou un numérique, il "nomme" la trace afin de la rendre unique parmis toutes les traces des points.
Si le 2è paramètre est présent, il indique en milliseconde, le temps que cette trace reste visible.
point.trace(true);							// affiche la trace
point.trace(true); // supprime la trace
point.trace('une trace précise'); // une trace précise et unique sur l'écran
point.trace(1, 500); // une trace nommé "1" et qui disparait au bout d'une demie seconde
point.trace.delay(point, 500, true, 1000); // une trace qui s'affichera au bout d'une demie seconde et disparaitra au bout d'une seconde

JS : Rect

Cet objet représente un rectangle, avec 3 attributs Point{topLeft, botRight, size} Attention: Le point topLeft sera toujours au dessus et sur la gauche du point botRight.

Rect : Création

La création d'un rectangle se fait par new Rect(), en lui passant les 2 x et les 2 y ou un élément DOM (events ou tag).
let rect = new Rect();											// rectangle vide
let rect = new Rect(x1, y1, x2, y2); // point en utilisant 4 valeurs numériques
let rect = new Rect([x1, y1, x2, y2]); // fonctionne avec un tableau de 2 points
let rect = new Rect({x: 1, y: 2}, {x: 3, y: 4}); // fonctionne avec de simples structures
let rect = new Rect(topBot); // fonctionne avec une structure topLeft, botRight
let rect = new Rect(topSize); // fonctionne avec une structure topLeft, size
let rect = new Rect({left: 1, top: 2, bottom: 3, right: 4}); // fonctionne avec une simple structure
let rect = new Rect(event1, event2); // fonctionne à partir de clientX et clientY
let rect = new Rect(rect); // duplique
let rect = Rect(); // sucre du new Rect()

Rect : Modification

Il est possible de "deplacer" ou redimensionner les rectangles.
La méthodeadd applique un déplacement-addition, alors quesub applique une soustraction (déplacement dans l'autre sens).
D'autres méthodes existent permettant de inclure (include), redimensionner (resize ou scale), élargir (intersect) ou arrondir les coorodonnées (round).
Ces méthodes sont doublées :
  1. Tout en minuscule : qui applique le résultat sur le this, avant de le renvoyer.
  2. Commence par une lettre Majuscule : même mécanisme, mais en clônant le this avant, afin de ne pas modifier le this d'origine.
    rect.add(rect1).add(point).add(x, y);			// Ajoute rect1 (ou point ou x,y) à rect et le renvoi. On peut dire qu'il déplace (et déforme) le rect.
    rect.add({x: 1, y: −2, w: 3, h: −4}); // Transforme le rect en le poussant de 1 sur la droite, remontant de 2, elargissant de 3 et rétrécissant de 4 en hauteur.
    let r = rect.Add(rect1); // Même principe que add() mais crée d'abord un nouveau Rect afin de ne pas toucher à rect.

    rect.sub(rect1).sub(point).sub(x,y); // Renvoie un nouveau rect, enlevé de rect1, …
    rect.sub({x: 1, y: −2, w: 3, h: −4}); // Transforme le rect en le poussant de 1 sur la gauche, descendant de 2, maigrissant de 3 et elargissant de 4 en hauteur.
    let r = rect.sub(rect1).sub(point); // Même principe que sub() mais crée d'abord un nouveau Rect afin de ne pas toucher à rect.

    rect.include(rect1).include(point); // Se transforme en rect englobant son rect et le rect1 ou point
    let r = rect.Include(rect1).include(point); // Même principe que include() mais crée d'abord un nouveau Rect afin de ne pas toucher à rect.

    rect.resize(width, height); // Ajoute ou enlève à botRight
    let r = rect.Resize(point); // Même principe que resize() mais crée d'abord un nouveau Rect afin de ne pas toucher à rect.

    rect.scale(width, height); // Si positif redimensionne right ou bottom, si négatif redimensionne left ou top
    let r = rect.Scale(width, height); // Même principe que scale() mais crée d'abord un nouveau Rect afin de ne pas toucher à rect.

    rect.intersect(rect2); // Redimensionne rect pour correspondre au rectancle formé par l'intersection des deux rectangles
    let r = rect.Intersect(rect2); // Même principe que intersect() mais crée d'abord un nouveau Rect afin de ne pas toucher à rect.

    rect.round(round); // Renvoi le rect arrondi à l'entier suivant round : −1: floor, 0: trunc, 1: round, 2: ceil
    let r = point.Round(round); // Même principe que round() mais crée d'abord un nouveau Rect afin de ne pas toucher à rect.

    rect.size = point;

Rect : Comparaison

Un ensemble de méthode permettant de comparer les rectangles entre eux.
let t = rect.includes(rect1);		// renvoi true si rect1 est totalement à l'intérieur
let t = rect.contains(rect2); // renvoi true si rect1 est en partie ou totalement à l'intérieur
let t = rect.excludes(rect1); // renvoi true si rect1 est totalement à l'extérieur
let t = rect.includes(point); // renvoi true si point est à l'intérieur de rect −> strictement identique à Point.includes(rect)

Rect : Utilisation

Il est possible de récupérer et modifier les attributs topLeft et botRight directement, l'attribut size étant uniquement en lecture.
Puisque topLeft, botRight et size (en lecture seul pour ce dernier) sont des points, il est possible d'appliquer les méthodes des points sur ces 3 attributs.
Et d'utiliser le rectangle pour positionner et dimensionner un élément :
let point = rect.topLeft;			// Point de l'angle le plus haut à gauche
rect.botRight = point; // Point de l'angle le plus bas à droite
let size = rect.size; // Point représentant la taille du rectangle
let x1 = rect.topLeft.x;
let y1 = rect.topLeft.y;
let x2 = rect.botRight.x;
let y2 = rect.botRight.y;
let width = rect.size.x;
let height = rect.size.y;
element_html.setRect(rect); // positionne et dimensionne
rect.setRect(element); // idem
rect.trace(1); // permet d'afficher visuellement un rectangle en trace, le paramètre servant à unifier les traces, un false supprimant tous les traces.
let r = rect.tolebori(); // Ajoute à rect les attributs suivants : {top, left, bottom, right} contenant {topLeft.y, topLeft.x, botRight.y, botRight.x}
let r = rect.Tolebori(); // Même principe que tolebori() mais crée d'abord un nouveau Rect afin de ne pas toucher à rect.
let struct = rect.Tolebori(true); // Ne renvoie que {top, left, bottom, right} sans toucher à rect

Enfin, il est possible de tracer visuellement le rectangle pour le développeur, en utilisant la méthodetrace.
Si le paramètre est vrai, il affiche une trace, sinon, il efface la trace.
Si le paramètre est une chaîne de caractère ou un numérique, il "nomme" la trace afin de la rendre unique parmis toutes les traces des rectangles.
Si le 2è paramètre est présent, il indique en milliseconde, le temps que cette trace reste visible.
rect.trace(true);							// affiche la trace
rect.trace(true); // supprime la trace
rect.trace('une trace précise'); // une trace précise et unique sur l'écran
rect.trace(1, 500); // une trace nommé "1" et qui disparait au bout d'une demie seconde
rect.trace.delay(rect, 500, true, 1000); // une trace qui s'affichera au bout d'une demie seconde et disparaitra au bout d'une seconde

JS : $.start()

Plume lance les fonctions passées dans$.start(); lorsque la page est chargée et prête.
// Exemple d'utilisation
function demarrage() {
// les instructions qui seront executées uniquement lorsque la page sera prête.
}
$.start(demarrage);

// ou plus simplement en employant une fonction anonyme intégrée
$.start(function() {
// les instructions qui seront executées uniquement lorsque la page sera prête.
});
Il est possible de bloquer les starts, en attente de données qui pourrait venir après le load de la page, tel que les requêtes asynchrones ajax, les promesses, les database, …
Le principe est de préciser en premier paramètre le ou les flags qui sont indispensables à la bonne exécution du contenu de la fonction; puis lorsque ces flags sont opérationnels, de l'indiqueer en appelant$.start avec uniquement le flag concerné.
(Cet exemple utilise des fonctions asynchrone RPC expliquées plus bas)
// exécute une fonction en asynchrone, suite à un retour du serveur PHP
ajax.differe_ready().then(function() {
// traitement
$.start("ready"); // déverouille ce flag permettant de débloquer les starts ayant besoin de ce traitement
});
// fonction qui ne sera lancée que lorsque la page sera prête ET que le flag "ready" sera déverouillé,
// donc après le retour de l'ajax "differe_ready"
$.start("ready", function() {
});
// fonction qui ne sera lancée que lorsque la page sera prête ET que les 2 flags "ready" et "impossible" seront déverouillés,
// donc jamais, puisque le flag "impossible" n'est pas déverouillé dans cet exemple.
$.start("ready impossible", function() {
});
// alors que cette fonction sera appelée dès que la page sera prête, sans tenir compte d'aucun flag
$.start(function() {
});
Les flags sont de type chaîne de caractères, mais l'on peut également rattacher une valeur à ces flags, qui sera récupéré dans le start :
// demande d'information supplémentaire au serveur
ajax.reading().then(function(result) {
if (result.ressource)
$.start({ressource: result.ressource});
});
$.start(function() {
// attend que l'utilisateur clic sur son bouton
$.$('button#goUser').on('click', function() {
$.start({ready: true});
});
});
// lorsque ces deux évenements seront réunis, affiche le résultat dans la console
$.start("ready ressource", function(event, flags) {
console.log(flags.ready, flags.ressource);
});

JS : $.database()

Permet d'accéder à la base locale du navigateur
// connexion sur le database database1 en v1, contenant les store1, …, créés s'il le faut
var database = $.database("database1", ['store1', …], 1, function(db) {database = db; $.start('database');});
// attention : ouverture en asynchrone, il est important d'utiliser la variable qu'après appel du succes, d'où l'appel du start avec le flag 'database'

$.start('database', function() {
var record = {id: 42, nom: "Dupond"};

// enregistrement
database.write(store, record);

// lecture
database.read('store1', record.id).then(function(record) {
$('nom').value = record.nom;
}).catch(function (err) {
$.modal("Erreur sur la lecture de cet enregistrement d'id : " . record.id)
});

// ou lecture avec valeur par défaut si non trouvé
database.read('store1', record.id, {nom: ''}).then(function(record) {
$('nom').value = record.nom;
});

// lecture d'un store
database.select(store, []).then(function(list) {
$.for(list, function(record) {
// …
});
});
});
Cet exemple utilise l'itération $.for et les dialogues $.modal.

JS : extension

Plume ajoute des fonctionnalités aux objets existants.

Function.delay()

Cette méthode lance en asynchrone différé la fonction spécifiée, en lui passant le timer et éventuellement unthis, et renvoi le minuteur, pour utilisation d'unclearTimeout eventuel.
Exemple :
function ma_fonction(parametre) {
console.log(this, parametre);
}
ma_fonction.delay($('BODY'), 3000, 42); // affichera dans 3s : <body> 42
ma_fonction.delay(500, 42); // affichera dans une demie−seconde : <window> 42

String.format()

Cette méthode renvoi une chaîne formatée en utilisant les paramètres passés.
Le format est composé de caractères (qui seront affichés tel quel) et d'insertion de paramètres.
La syntaxe de ces insertions est : % position complement flag signe largeur precision type. d : affiche le paramètre sous forme d'un nombre entier. f : affiche le paramètre sous forme d'un nombre flottant. s : affiche le paramètre sous forme d'une chaîne de caractère. % : forçant d'insérer non pas un paramètre, mais le caractère pourcentage (%).
Exemples :
console.log( "(%1u, %7u, %7u, %7u)".format(42, −42.24, " +42", "a 42" ) );			// affichera (42,      42,      42,       0)
console.log( "(%1d, %7d, %7d, %7d)".format(42, −42.24, " +42", "a 42" ) ); // affichera (42, −42, 42, 0)
console.log( "(%1f, %7f, %7f, %7f)".format(42, −42.24, " +.42", "a 42.24" ) ); // affichera (42, −42.24, 0.42, 0)
console.log( "(%1s, %7s, %7s, %7s)".format(42, −42.24, " +.42", "a 42" ) ); // affichera (42, −42.24, +.42, a 42)
console.log( "(%%)".format(42, −42.24, " +.42", "a 42" ) ); // affichera (%)
console.log( "(%2$x7.1f)".format(42, 42.24, " +.42", "a 42" ) ); // affichera (xxx42.2)
console.log( "(%2$x−7.3f)".format(42, 42.24, " +.42", "a 42" ) ); // affichera (42.240x)
console.log( "(%.3f)".format(4224.2442 ) ); // affichera (4224.244)
console.log( "(%10.3s)".format("abcdefghijklmnopqrstuvwxyz" ) ); // affichera ( abc)
console.log( "(%−10.3s)".format("abcdefghijklmnopqrstuvwxyz" ) ); // affichera (abc )
console.log( "(%−−10.3s)".format("abcdefghijklmnopqrstuvwxyz" ) ); // affichera (abc−−−−−−−)
console.log( "(%.+10.3s)".format("abcdefghijklmnopqrstuvwxyz" ) ); // affichera (.......abc)
console.log( "(%.10.3s)".format("abcdefghijklmnopqrstuvwxyz" ) ); // affichera (.......abc)
console.log( "(%.10.0s)".format("abcd" ) ); // affichera (......abcd)
console.log( "(%.4u, %+.4u, %.4u, %+.4u)".format(42, 42, −42, −42) ); // affichera (0042, +042, 0042, +042)
console.log( "(%.4d, %+.4d, %.4d, %+.4d)".format(42, 42, −42, −42) ); // affichera (0042, +042, −042, −042)

Array.first()

Cette méthode renvoi le premier élément, ounull si tableau vide.

Array.second()

Cette méthode renvoi le second élément, ounull si inexistant.

Array.last()

Cette méthode renvoi le dernier élément, ounull si tableau vide.

Array.delete()

Cette méthode efface l'élément passé en paramètre, puis renvoi l'index (à partir de 0) de l'élément effacé, sinon ne touche pas au tableau et renvoi -1.
let tab = ['a', 'b', 'c'];
tab.delete('b'); // renvoi 1
console.log(tab); // renvoi ['a', 'c']

Date.addDate()

Cette méthode ajoute ou enlève un nombre de jour à la date.
Exemple :
var date = new Date(2020, 0, 1, 12);
date.addDate(1);
console.log(date);
Renverra :

Date.copy()

Cette méthode renvoi une copie de la date.
Exemple :
var origine = new Date(2020, 0, 1, 12);
var reference = origine;
reference.addDate(1);
var copie = origine.copy();
reference.addDate(3);
console.log(reference, copie);
Renverra :

window.location.args

Renvoi un tableau en lecture seul, contenant la liste des variables passées au GET et en attribut, le couple variable = valeur.
Exemple avec une url de type https://www.d-nada.com/rubrique/action/index.php?nom=Dupon&prenom=André,window.location.args renverra :
{
0: "nom",
1: "prenom",
length: 2,
nom: "Dupon",
prenom: "André"
}

// affichera dans la console : "Nom renseigné"
trace('Nom', window.location.args.nom ? "renseigné" : "non−renseigné");

// affichera dans la console : "nom Dupon", "prenom André"
window.location.args.forEach(arg => trace(arg, location.args[arg]));

window.location.folders()

Est une méthode renvoyant un tableau indexé contenant la liste des dossiers de l'URL.
Exemple avec une url de type https://www.d-nada.com/rubrique/action/index.php?nom=Dupon&prenom=André,window.location.folders renverra :
[ 'rubrique', 'action' ]

window.navigator.name

Renvoi une chaîne contenant le nom du navigateur dans l'énuméré suivant : chrome, firefox, opera, safari, samsung, …
Exemple avec votre navigateurwindow.navigator.name renverra :

window.navigator.mobile

Renvoi unboolean indiquant si le navigateur est sur un smartphone ou tablette.
Exemple avec votre navigateurwindow.navigator.mobile renverra :

window.navigator.version

Renvoi la version
Exemple avec votre navigateurwindow.navigator.version renverra :

JS : RPC

Plume intégre des mécanismes de RPC, permettant de communiquer facilement et en asynchrone entre le navigateur et le serveur.

RPC : Fonctions

Il est possible d'appeler des fonctions serveurs (uniquement en PHP actuellement) en asynchrone depuis le JS.
Fonctions PHP, que le développeur autorise, avec passage de paramètres (passés par valeur).
Pour cela, la fonction doit être appelée à travers la classe$.ajax, et cette fonction doit être préfixée parajax_ côté PHP.
Le retour de donnée se fait par un return de valeurs côté PHP, qui sera (sérialisé/désérialisé puis) transmis à la fonction promesse de JS.
Il est donc inutile, voir proscrit de faire desecho.
Exemple avec la fonction "ma_methode", avec côté client JS :
$.ajax.ma_methode(premier, deuxieme, etc).then(function(resultat) {
alert("Valeur de retour correct :", Array.isArray(resultat.ok) && resultat.ok.length == 1 && resultat.ok[0] === 42);
});
Et côté serveur PHP :
function ajax_ma_methode($premier = 'default', $deuxieme = false, $etc = null) {
return [ 'ok' => [42] ];
}
Template::ajax(); // à placer avant tout envoi (tel que echo ou Template::display())
Pour plus d'information, voir la fonction Template::ajax().

RPC : Fichier

Il est possible de lui faire passer un ou plusieurs fichiers (via un<input type="file"/>) :
<script>
// le choix d'une nouvelle image faite par l'utilisateur déclenche cette méthode
$$('INPUT?file').on('change', function() {
if (! this.files.length)
return;
// on récupère la 1ère image et on vérifie sa taille
let file = this.files[0];
if (file.size > 100*1024*1024)
window.alert("Fichier trop volumineux.");
else {
// on transmet ce fichier au serveur
$.ajax.download(42, file).then(function(resultat) {
if (resultat === true)
window.alert("Téléchargement réussi")
else
window.alert("Problème lors du téléchargement: " + resultat);
});
}
});
</script>
<?php
// on récupère le nouveau fichier sur le serveur
function ajax_download($quarante_deux) {
$files = $_FILES['files'];
if (! $files || count($files["tmp_name"]) == 0)
return "Fichier manquant.";
foreach ($files["tmp_name"] as $idx => $tmp) {
$name = $files["name"][$idx];
$type = $files["type"][$idx];
$size = $files["size"][$idx] / (1024*1024);
if (! preg_match('{^text/(w+)$}', $type, $type))
return "Type '$type' du fichier '$name' non pris en charge.";
if ($size > 100)
return "Taille ({$size}Mo) du fichier '$name' trop volumineux.";

if (! $file = @fopen($files["tmp_name"][$idx], 'r'))
return "Ouverture du fichier '$name' impossible.";
// fread…
fclose($file);

// ou
$name = "/image/" . preg_replace(['/^W+|W+$/', '/[^w .−]+/'], ['', '_'], $files['name'][$idx]);
if (@move_uploaded_file($files["tmp_name"][$idx], $_SERVER['DOCUMENT_ROOT'] . $name))
// …
}
return true;
}
?>

RPC : Options

Il existe des options, qu'il est possible de passer en paramètre, en retour de la méthode$.ajax.option().
Ces options sont :
  1. timeout: une valeur entière positive exprimant le nombre de milliseconde avant que la requête aboutisse à un échec (ce qui déclenchera lecatch de la promesse).
  2. progress: une fonction qui sera appelée régulièrement durant l'envoi et la réception.
Avec en paramètre :
  1. event contenant :
    • total: indiquant le nombre total de caractères à envoyer ou recevoir.
    • loaded: indiquant le nombre d'octets déjà envoyé ou reçu.
    • lengthComputable : booléan indiquant que total est valide (donc durant l'envoi).
    • target: le XMLHttpRequest, permettant d'accéder aux éléments tel quereadyState,response,status,getResponseHeader(), …
    • timeStamp: rattaché à l'évenement.
  2. download : àtrue si le download est en cours (undefined si c'est le upload).
Exemples :
// si la requête prend plus d'une demie−seconde, la fonction catch sera appelée
$.ajax.ma_fonction1(parametres, $.ajax.option({timeout: 500})).then(function(success) {…}).catch(function(error) {…});

// affiche visuellement la durée restante du transfert, durant l'envoi et réception de l'appel de cette méthode PHP
let progress = $('PROGRESS');
progress.value = 0; // part de zéro
progress.display = 'inherit'; // et affiche la jauge
$.ajax.ma_fonction2(file, $.ajax.option({progress: function(event, download) {
if (! download)
if (event.loaded < event.total)
progress.value = event.loaded / event.total; // augmente la jauge durant l'envoi
else
progress.value = undefined; // met en attente la jauge pendant le traitement PHP
else
if (event.loaded < event.total)
progress.value = (event.total − event.loaded) / event.total; // diminue la jauge durant la réception
else
progress.display = 'none'; // ferme la jauge lorsque terminé
}})).then(succes);

RPC : Manuel

Il est enfin possible d'employer la classe$.ajax pour lancer juste des requêtes simples asynchrones (donc sans créer de fonction PHP particulière), en interceptant manuellement les demandes et en renvoyant une promesse en retour :
<script>
// appel index.php avec le paramètre par1 = 42, puis appel la fonction "traitement()" avec la réponse de la requête.
$.ajax({par1: 42}).then(traitement);

// appel request.php à la racine du serveur avec post1 à 42 en POST, avec 1s maxi
$.ajax('post', "/request.php", {post1: 42}, 1000).then(function(response) {
// traitement de la réponse
});
<script>
<?php
if (Template::request('post1') == 42) {
Template::send([ 'ok' => "yes" ]);
exit;
}
?>

$.async()

Il est possible de lancer une méthode en asynchrone, qui se répetera si besoin.
Elle prend en paramètres : Elle renvoi une promesse avec le résultat de la méthode.
La méthode appelée, devra renvoyer quelque chose (même null) qui sera passé comme paramètre à la promesse réussie.
Si cette méthode ne renvoi rien (ou undefined ou return), elle sera relancée en aysnchrone, jusqu'à quelle renvoie quelque chose, mécanisme permettant de générer une boucle.
Exemple :
const source = [1, 2, 3, 5, 7, 11, 15 ];
trace('début);
$.async(
// this, // je ne passe pas le this puisque je ne l'utilise pas à l'intérieur de cette méthode
function(premiers, pas) {
const traces = premiers.splice(0, pas).map(function(premier) { // on retire les x éléments, que l'on traite
return
' & ' + premier;
});
trace(
'−>', traces);
if (! premiers.length)
return "fini";
},
Array.from(source), // fait une copie de la source, qui sera passé au paramètre "premiers"
2 // le nombre d'élément que l'on traitera, qui sera passé au paramètre "pas"
).then(function(resultat) {
trace(resultat +
'.');
});
trace(
'suite);

Affichera :
début
suite

−> 1 & 2
−> 3 & 5
−> 7 & 11
−> 15
fini.

PHP

Plume ajoute également des fonctionnalités au PHP, afin de faciliter la vie du développeur.

PHP : constante

Ces constantes permettent de personnaliser Plume :

PLUME_TRACE_IP

PLUME_TRACE_IP permet d'ajouter des fonctionnalités de debugging au poste indiqué par son IP.
Il accepte : Exemple :
define('PLUME_TRACE_IP', "77.0.1.2");
define('PLUME_TRACE_IP', "77.0.1.*");
define('PLUME_TRACE_IP', "77.0.[0−3].*");
define('PLUME_TRACE_IP', ["77.0.1.2", "168.0.1.2"]);
define('PLUME_TRACE_IP', true);
define('PLUME_TRACE_IP', false);

PLUME_LOG

PLUME_LOG peut spécifier un dossier, où Plume cherchera à enregistrer le journal des activités SQL et d'autres journaux, tel que les Fault.
S'il est égale à faux: il interdit ce comportement.
S'il correspond à un nom de dossier viable (qui sera créé si besoin), c'est ce dossier qui sera utilisé.
Exemple :
define('PLUME_LOG', "/www/data/log");
Dans les autres cas, s'il est égale à vrai ou s'il n'est pas défini, Plume cherchera à créer un dossier avec cet algorithme :
Le nom du dossier sera :
  1. "log"
  2. Ou se terminant par ".data/log".
  3. Ou se terminant par ".log".
En partant du dossier où se trouve la librairieplume.php, puis en remontant jusqu'à la racine du disque.
Si aucun dossier n'est trouvé, le mécanisme de log n'est pas activé.
Exemple avec /www/mon-site/sous-dossier/plume.php :
  1. /www/mon-site/sous-dossier/log/ ➽ logs propre au site, et situé au même niveau que plume.php, dans son dossier "log"
  2. /www/mon-site/sous-dossier.data/log/ ➽ logs propre au site, et situé dans un dossier "data" partagé à d'autre information
  3. /www/mon-site/sous-dossier.log/ ➽ logs propre au site, et situé à la racine du site
  4. /www/mon-site/log/ ➽ logs propre au site, et situé à la racine du site, dans son dossier "log"
  5. /www/mon-site.data/log/ ➽ logs propre au site mais situé en dehors du site, dans un dossier "data" partagé à d'autre information
  6. /www/mon-site.log/ ➽ logs propre au site mais situé en dehors du site, dans son dossier "log"
  7. /www/log/ ➽ logs commun à l'ensemble des sites, mais situé au même niveau que les sites
  8. /www.data/log/ ➽ logs commun à l'ensemble des sites, et situé dans un dossier partagé à d'autre information
  9. /www.log/ ➽ logs commun à l'ensemble des sites, et situé à la racine du disque

Les fichiers de log ont un nommage de type : prefixe_YYYY-MM-DD.log
Si un fichier log est trop volumineux (> 50Mo), le reste des informations seront enregistrées dans des fichiers nommés prefixe_YYYY-MM-DD_Z.log, ou Z est l'index commençant à 1.

Liste des logs actuellement gérés par Plume :

PLUME_MAIL_REPORT

Une synthèse des journaux d'erreurs et warnings, peut être envoyé automatiquement par mail journalièrement.PLUME_MAIL_REPORT peut spécifier une adresse mail où le développeur pourra recevoir des informations de débugging, de type report.
Exemple :
define('PLUME_MAIL_REPORT', "contact−technique@ma−societe.com");

PLUME_SESSION

Plume lance automatiquement unsession_start().
Si vous souhaitez interdire à Plume ce mécanisme, par exemple pour le faire en amont afin de mettre à jour une variable de$_SESSION avant de charger Plume, il suffit de déclarer à faux cette constante :
<?php
define('PLUME_SESSION', false);
// avec par exemple…
session_cache_expire(366 * 24 * 60);
session_start();
$_SESSION['authentification'] = false;
require('plume.php');
?>

PHP : Trace

Plume intégre au niveau PHP (comme au niveau JS)) un mécanisme simple et efficace de trace pour les développeurs.

Attention : suite à l'appel de la méthode statique Template::display, les trace() ne sont plus actif, puisque cette méthode s'occupe de les insérer dans la page HTML.

Trace : trace()

Procédure PHP trace() permet d'afficher des données PHP directement sur la console du navigateur, uniquement en mode développement (voir PLUME_TRACE_IP).
Le premier paramètre est de préférence une chaîne de caractère, titrant la trace ou un formatage éventuel s'il contient des insertions de type (sprintf)[https://www.php.net/manual/fr/function.sprintf.php].
<?php
define('PLUME_TRACE_IP', "192.168.0.1"); // l'adresse du développeur
require('plume.php');
trace('texte'); // envoi le texte sur la console
trace('titre', $var1, $var2, …); // envoi le texte et les variables
trace('nom=%s, %dsecondes', 'enzo', 9, −1); // formate avant d'envoyer "nom=enzo, 9secondes" suivi de "−1"

// les traces ne fonctionne que …
trace('exemple', $_REQUEST);

// si l'on appelle un display (pour afficher une page template)
Template::display();

// ou si l'on renvoit manuellement un retour
if (Template::request('post1') == 42) {
Template::send();
exit;
}

// ou encore si une fonction ajax est appelée
function ajax_ma_fonction() {
return;
}
?>

Si appelé sans paramètre, elle ne fera que renvoyer un booléen indiquant si la trace est activée ou non.
if (trace())
; // execution d'une partie PHP uniquement pour les développeurs

Trace : PLUME_TRACE_FILE

Il est possible d'orienter tous les flux des trace() (PHP comme JS) vers un fichier hébergé côté serveur PHP.
Et de lire ce fichier.log en direct.
Il faut pour cela juste définir la constantePLUME_TRACE_FILE avec une valeur "canal" numérique unique.
Exemple :
define('PLUME_TRACE_FILE', ! navigator('mobile') && trace() ? 42 : false);
Cette valeur servira à filtrer la lecture sur ce canal, avec juste une page comme /trace.html
Ce mécanisme permet d'avoir des traces lorsque la console du navigateur n'est pas accessible (développement mobile, navigateur client, …).

Trace : Warning et Error PHP


Les erreurs PHP provenant d'un appel ajax sont redirigés vers la console du navigateur, permettant de ne pas perturber les éventuelles informations retournées et le fonctionnement du script JS.

PHP : Base

Classe PHP simplifiant la vie du développeur, par sa facilité d'utilisation, sa lisibilité et ses fonctionnalités poussées.
Tous les résultats suivants prendront cette table en exemple :
id nom ville age
42 Enzo Annecy 35
74 Patricia La Paz 28
500 Yvan Annecy 35
501 Yannis Annecy 20

Base : open

Ouverture d'une base :
$base = new Base("signature");
Base va ouvrir une base, en recherchant les informations de connexion, rattachées à la signature, dans un annuaire.
L'annuaire est un fichier qui peut être présent sur le serveur aux endroits suivants : Pour des raisons de sécurité, ce fichier "annuaire" est préfixé .php et donc de syntaxe PHP :
<?php
/*
open:signature mysql://login:password@server[/base]
sql:clé select … …
value:clé constante
data:clé data
*/
?>
Ce fichier annuaire peut contenir plusieurs commandes. La commande quenew Base() recherche estopen.
open:signature	mysql://login:password@server[/base]
Pour le moment, seul MySQL est porté, les autres bases tel que Oracle ou PostgreSQL, peuvent être ajouté facilement.
Les autres commandes servent à :

Base : read

On peut lire un enregistrement avecbase−>read().
read(= 1 champ)
Si un seul champ demandé, renvoi une chaîne correspondant à ce champ.
$record = $base−>read("select nom from table where id = 42");
Renvoi :
"Enzo"
Si besoin, il est possible néanmoins de forcer le renvoi d'un tableau plutôt qu'une valeur, en indiquant une constante supplémentaire dans la requête :
$record = $base−>read("select 0, nom from table where id = 42");
Renvoi :
[
0 => 0,
"nom" => "Enzo"
]
read(> 1 champ)
Si plusieurs champs demandés, renvoi un tableau associatif :
$record = $base−>read("select id, * from table where id = 42");
Renvoi :
[
"id" => "42",
"nom" => "Enzo",
"ville" => "Annecy",
"age" => 35
]
Attention aux homographes de champs identiques sur deux tables différentes, la dernière valeurs écrasant toujours les autres.
Exemple fréquent avec les "id" :
$base = [
'table1' => [
[ 'id' => 1, 'lien' => 2 ],
],
'table2' => [
[ 'id' => 2 ]
]
];
// avec juste une étoile, le table2.id viendra écraser la valeur du table1.id
trace($base−>read("select * from table1, table2 where table1.id = 1 && table1.lien = table.2.id"));
// renverra [ 'id' => 2, 'lien' => 2 ]

// si l'on veut conserver uniquement le table1.id, il faut ajouter cette dernière en fin du select
trace($base−>read("select *, table1.id from table1, table2 where table1.id = 1 && table1.lien = table.2.id"));
// renverra [ 'id' => 1, 'lien' => 2 ]

// et si l'on veut conserver les id, il faut les nommer
trace($base−>read("select *, table1.id id1, table2.id id2 from table1, table2 where table1.id = 1 && table1.lien = table.2.id"));
// renverra [ 'id' => 2, 'lien' => 2, 'id1' => 1, 'id2' => 2 ]

Enfin, il est possible d'ajouter une trace pour le développeur, en la plaçant en premier paramètre voir plus d'information trace().

Base : select

On peut lire une liste d'enregistrement avecbase−>select().
$liste = $base−>select("select * from ma_table where id = 42 order by nom");
$liste = $base−>select("show table status like 'ESSAI%'");
select(1 champ)
Si un seul champ demandé, renvoi une liste indexée, de la valeur de ce champ.
$liste = $base−>select("select nom from table");
Renvoi :
[
"Enzo",
"Patricia",
"Yvan",
"Yannis"
]
select(2 champs)
Si deux champs demandés, renvoi une liste indexée, de la valeur du premier champ (généralement l'id), avec la valeur du 2ème champ.
$liste = $base−>select("select id, nom from table");
Renvoi :
[
42 => "Enzo",
74 => "Patricia",
500 => "Yvan",
501 => "Yannis"
]
select(>= 3 champs)
Si plus de 3 champs demandés, renvoi un tableau associatif dont la clé correspond au premier champ, et la valeur, un tableau associatif de ces champs :
$liste = $base−>select("select id, nom, age from table where 1");
Renvoi :
[
"42" => [ "id" => "42", "nom" => "Enzo", "age" => "35" ],
"74" => [ "id" => "74", "nom" => "Patricia", "age" => "28" ],
"500" => [ "id" => "500", "nom" => "Yvan", "age" => "35" ],
"501" => [ "id" => "501", "nom" => "Yannis", "age" => "20" ]
]
Ce champ clé devant être unique, le champ "id" est généralement utilisé.
Exemple destructif à ne pas utiliser dans ce cas :
$liste = $base−>select("select ville, age, nom, id from table");
Renvoi :
[
"Annecy" => [ "ville" => "Annecy", "age" => "20", "nom" => "Yannis", "id" => "501" ],
"La Paz" => [ "ville" => "La Paz", "age" => "28", "nom" => "Patricia", "id" => "74" ],
]
select(profondeur)
Si le premier paramètre, précédent la requête, est une valeur numérique, elle indique une notion de groupe sur le résultat renvoyé.
La valeur entière indique sur combien de champ du select, le regroupement se fera.
Si la valeur entière est inférieur à 2, elle reproduit le même principe que s'il n'y avait pas de profondeur.
// regroupement sur la ville et le nom
$liste = $base−>select(2, "select ville, nom, age, id from table");
Renvoi :
[
"Annecy" => [
"Enzo" => [ "ville" => "Annecy", "nom" => "Enzo", "age" => "35", "id" => "42" ],
"Yvan" => [ "ville" => "Annecy", "nom" => "Yvan", "age" => "35", "id" => "500" ],
"Yannis" => [ "ville" => "Annecy", "nom" => "Yannis", "age" => "20", "id" => "501" ],
],
"La Paz" => [
"Patricia" => [ "ville" => "La Paz", "nom" => "Patricia", "age" => "28", "id" => "74" ]
]
]
// regroupement sur la ville, puis l'âge, puis le nom
$liste = $base−>select(3, "select ville, age, nom, id from table");
Renvoi :
[
"Annecy" => [
"35" => [
"Enzo" => [ "ville" => "Annecy", "age" => "35", "nom" => "Enzo", "id" => "42" ],
"Yvan" => [ "ville" => "Annecy", "age" => "35", "nom" => "Yvan", "id" => "500" ],
],
"20" => [
"Yannis" => [ "ville" => "Annecy", "age" => "20", "nom" => "Yannis", "id" => "501" ],
],
],
"La Paz" => [
"28" => [
"Patricia" => [ "ville" => "La Paz", "age" => "28", "nom" => "Patricia", "id" => "74" ]
]
]
]

Enfin, comme pour read(), il est possible d'ajouter une trace pour le développeur, en la plaçant en premier paramètre voir plus d'information trace().

Base : list

Même principe que pour le$base−>select mais sans notion de nombre de champ : renvoi toujours une liste indexée (donc pas de risque d'écrasement des doublons).
$liste = $base−>list("select id, nom from table where 1");
Renvoi :
[
0 => [ "id" => "42", "nom" => "Enzo" ],
1 => [ "id" => "74", "nom" => "Patricia" ],
2 => [ "id" => "500", "nom" => "Yvan" ]
]
list(profondeur)
Même principe de regroupement que pour le$base−>select, mais avec la profondeur pouvant partir de 1 :
// regroupement sur la ville
$liste = $base−>list(1, "select ville, age, nom, id from table");
Renvoi :
[
"Annecy" => [
0 => [ "ville" => "Annecy", "age" => "35", "nom" => "Enzo", "id" => "42" ],
1 => [ "ville" => "Annecy", "age" => "35", "nom" => "Yvan", "id" => "500" ],
2 => [ "ville" => "Annecy", "age" => "20", "nom" => "Yannis", "id" => "501" ],
],
"La Paz" => [
0 => [ "ville" => "La Paz", "age" => "28", "nom" => "Patricia", "id" => "74" ],
],
]
// regroupement sur la ville et l'âge
$liste = $base−>list(2, "select ville, age, nom, id from table");
Renvoi :
[
"Annecy" => [
"35" => [
0 => [ "ville" => "Annecy", "age" => "35", "nom" => "Enzo", "id" => "42" ],
1 => [ "ville" => "Annecy", "age" => "35", "nom" => "Yvan", "id" => "500" ],
],
"20" => [
0 => [ "ville" => "Annecy", "age" => "20", "nom" => "Yannis", "id" => "501" ],
],
],
"La Paz" => [
"28" => [
0 => [ "ville" => "La Paz", "age" => "28", "nom" => "Patricia", "id" => "74" ],
],
],
]
// regroupement sur la ville, puis l'âge, puis le nom
$liste = $base−>list(3, "select ville, age, nom, id from table");
Renvoi :
[
"Annecy" => [
"35" => [
"Enzo" => [
0 => [ "ville" => "Annecy", "age" => "35", "nom" => "Enzo", "id" => "42" ],
],
"Yvan" => [
1 => [ "ville" => "Annecy", "age" => "35", "nom" => "Yvan", "id" => "500" ],
],
],
"20" => [
"Yannis" => [
0 => [ "ville" => "Annecy", "age" => "20", "nom" => "Yannis", "id" => "501" ],
]
],
],
"La Paz" => [
"28" => [
"Patricia" => [
0 => [ "ville" => "La Paz", "age" => "28", "nom" => "Patricia", "id" => "74" ],
],
],
],
]

Enfin, comme pour read(), il est possible d'ajouter une trace pour le développeur, en la plaçant en premier paramètre voir plus d'information trace().

Base : Enregistrement

On peut enregistrer des données, en deux étapes :
  1. Préparation des données, avec la méthode set.
  2. Enregistrement des données, avec les méthodes insert, update ou modify.
set()
// on prépare l'enregistrement, champ par champ
$base−>set("champ", "valeur");
// ou en bloc
$base−>set( [ "champ" => "valeur" ] );
Il est possible de passer le nom (ou l'alias) de la table dans le nom du champ.
$base−>set("table.champ", $valeur);
On peut spécifier un 3ème paramètre, pour formater la valeur avant enregistrement :
$base−>set($champ, $valeur, "i");	// formate une valeur numérique entière
$base−>set($champ, $valeur, "f"); // formate un flottant
$base−>set($champ, $valeur, "d"); // formate une date
$base−>set($champ, $valeur, "t"); // formate une heure
$base−>set($champ, $valeur, "dt"); // formate une date et heure
$base−>set($champ, $valeur, "n"); // met le mot−valeur "null" si $valeur est indéfini
Le paramètre "n" peut être combiné avec les autres types (ni, ndt, …).

Enfin, comme pour read(), il est possible d'ajouter une trace pour le développeur, en la plaçant en premier paramètre voir plus d'information trace().
insert()
En forçant l'ajout d'un nouvel enregistrement.
Un seul paramètre : le nom de la table.
Si l'ajout est réussi, le retour correspond auid du nouvel enregistrement (toujours positif non-nul), sinon à false.
$id = $base−>insert("table");

Comme pour read(), il est possible d'ajouter une trace pour le développeur, en la plaçant en premier paramètre voir plus d'information trace().
update()
En modifiant un enregistrement déjà présent.
Paramètrage :
  1. Le nom de la table.
  2. La clausewhere ou le "id".
Renvoi le nombre d'enregistrement modifié, ou false si erreur
Attention, ce nombre peut être égale à zéro, dans le cas ou le where n'est pas trouvé ou si les colonnes sont identiques.
$base−>set('titre', "titre 1");
$nombre = $base−>update("table", 'champ = "valeur"');
if ($nombre === false)
trace('Erreur', $base−>error());
else
trace('Nombre d'enregistrement mis à jour', $nombre);
Si la clausewhere se résume à faire unid = , il suffit de passer cet "id" à la place de la clausewhere :
$ok = $base−>update("table", $id);

Comme pour insert(), il est possible d'ajouter une trace pour le développeur, en la plaçant en premier paramètre voir plus d'information trace().
modify()
En modifiant un enregistrement déjà présent ou en ajoutant un nouvel enregistrement.
Paramètrage :
  1. Le nom de la table.
  2. Le "id" (pas de clausewhere ).
Renvoi le ID si la modification est réussie, sinon false.
Attention, à la différence d'un update(), cette méthode ne renvoi pas le nombre d'enregistrement, mais bien le ID de l'enregistrement modifié (ou créé) ou false si erreur.
$id = $base−>modify("table", $id);
if (! $id)
trace("Erreur lors de l'enregistrement", $base−>error());
Cela correspond plus ou moins à faire unif (read("id")) update("table", "id"); else insert("table sans le id"); Remarque : Les set() précédant peuvent contenir un champ 'id', qui ne sera pas utilisé par cette fonction, afin de ne pas rentrer en conflit avec la colonne 'id' de la base.

Comme pour insert(), il est possible d'ajouter une trace pour le développeur, en la plaçant en premier paramètre voir plus d'information trace().
delete()
Effacement d'un enregistrement.
Paramètrage :
  1. Le nom de la table
  2. La clausewhere ou le "id".
Renvoi le nombre d'enregistrement effacé, ou false si erreur
Attention, ce nombre peut être égale à zéro, dans le cas ou le where n'est pas trouvé.
$ok = $base−>delete("table", "where");
if ($nombre === false)
trace('Erreur', $base−>error());
else
trace('Nombre d'enregistrement effacé', $nombre);
Si l'on veut effacer toute la table, il est préférable de faire undeletes Paramètrage :
  1. Le nom de la table
    $ok = $base−>deletes("table");

Comme pour insert(), il est possible d'ajouter une trace pour le développeur, en la plaçant en premier paramètre voir plus d'information trace().

Base : Requêtes

On peut également faire des requêtes particulières.
desc()
Permet d'avoir la description des tables et champs, avec les signatures possibles suivantes :
  1. paramètre représentant le nom de la table :
    • Si absent ou vide : renvoi l'ensemble des tables
    • Si la chaîne contient '%' : renvoi uniquement les tables commençant ou finissant par cette chaîne.
    • Si la chaîne ne contient pas '%' : renvoi les informations sur cette table.
  2. paramètre permettant d'affiner le retour d'information :
    • false (par défaut) : juste le nom des tables ou des colonnes.
    • true : renvoi un tableau d'information sur les tables ou colonnes.
    • chaine : renvoi la liste des énumérés (enum ou set) d'un champs.

Voici en détails les différentes signatures :
desc()
Renvoi la liste des tables.
$tables = $base−>desc('');
Renvoi (par exemple) :
[ "table 1", "table 2", "tablex" ]
desc('', true)
Renvoi la description des tables.
$tables = $base−>desc('', true);
Renvoi (par exemple) :
[
"table 1" => ['Name'=> "table1", 'Engine'=> "InnoDB", 'Auto_increment'=> 1, 'Comment'=> "Commentaire", 'Collation'=> "utf8mb4_unicode_ci", 'Data_length'=> 16384, …],
"table 2" => […],
"table" => […]
]
desc('filtre%')
Renvoi la liste des tables correspondant au filtre.
$tables = $base−>desc('table %');
Renvoi (par exemple) :
[ "table 1", "table 2" ]
desc(table, false)
Renvoi la liste des champs d'une table précise.
$champs = $base−>desc('table');
Renvoi (par exemple) :
[ "id", "nom", "ville" ]
desc(table, true)
Renvoi la description des champs d'une table précise.
$champs = $base−>desc('table', true);
Renvoi (par exemple) :
[
"id" => ['Field' => "id", 'Type' => "int(11)", 'Key' => "PRI", 'Null' => "NO", 'Default' => null, 'Collation' => null, 'Comment' => "", 'Privileges' => "select,insert,update,references", 'Extra' => "auto_increment"],
"nom" => ['Field' => "nom", 'Type' => "varchar(100)", 'Key' => "", 'Null' => "NO", 'Default' => null, 'Collation' => null, 'Comment' => "", 'Privileges' => "select,insert,update,references", 'Extra' => ""],
"ville" => ['Field' => "ville", 'Type' => "varchar(100)", 'Key' => "", 'Null' => "NO", 'Default' => null, 'Collation' => null, 'Comment' => "", 'Privileges' => "select,insert,update,references", 'Extra' => ""]
]
desc(table, enum)
Renvoi l'énuméré d'un champ de typeenum ouset.
$champs = $base−>desc('table', "ville");
Renvoi :
[ "Annecy", "La Paz" ]
foreignKey()
Renvoi la liste des clés étrangères des tables demanadées, regroupé par table.
Il accepte en paramètre : Les tableaux renvoyés contiennent : Exemples :
$foreign1 = $base−>foreignKey('table');
$foreign2 = $base−>foreignKey(['table', 'personnels']);
$foreigns = $base−>foreignKey();
Pourrait renvoyer :
$foreign1 = [
'nom' => [ 'name' => "table", 'source' => "nom", 'foreign' => "personnels", 'key' => "nom_personne" ],
'ville' => [ 'name' => "table", 'source' => "ville", 'foreign' => "villes", 'key' => "identifiant" ],
];
$foreign2 = [
'table' => [
'nom' => [ 'name' => "table", 'source' => "nom", 'foreign' => "personnels", 'key' => "nom_personne" ],
'ville' => [ 'name' => "table", 'source' => "ville", 'foreign' => "villes", 'key' => "identifiant" ],
],
'personnels' => [
'telephone' => [ 'name' => "personnels", 'source' => "telephone", 'foreign' => "annuaire", 'key' => "id" ],
],
];
$foreigns = [
'table' => [
'nom' => [ 'name' => "table", 'source' => "nom", 'foreign' => "personnels", 'key' => "nom_personne" ],
'ville' => [ 'name' => "table", 'source' => "ville", 'foreign' => "villes", 'key' => "identifiant" ],
],
'personnels' => [
'telephone' => [ 'name' => "personnels", 'source' => "telephone", 'foreign' => "annuaire", 'key' => "id" ],
],
'societes' => [
'directeur' => [ 'name' => "societes", 'source' => "directeur", 'foreign' => "personnels", 'key' => "id" ],
],
];
sql()
Il est possible de produire n'importe quelles requêtes SQL spécifiques.
$base−>sql('ma requete sql');
$base−>sql("alter table comment ", $ma_table, $mon_commentaire);

Comme pour la plupart des méthodes précédentes, il est possible d'ajouter une trace pour le développeur, en la plaçant en premier paramètre voir plus d'information trace().
requested()
Renvoi la dernière requête (suite à un select, read, update, …, sql()).
host()
Renvoi le type de connexion utilisé (identique à mysql->host_info).
error()
Il est possible de tester l'erreur suite à une requête :
$base−>error();			// renverra "numéro: message" ou '' si pas d'erreur
$base−>error(true); // renverra "message" ou '' si pas d'erreur
$base−>error(false); // renverra le numéro d'erreur ou 0 si pas d'erreur
close()
Il est recommander de fermer les accès aux bases avant de terminer le programme PHP.
$base−>close();

Base : Insertions


Les requêtes peuvent contenir des insertions, permettant le formattage (et dû coup la protection de données extérieurs à la requête).
Les insertions reprennent le mécanisme des "printf", mais avec un marquage sous forme de lettre encadrés d'accolades, qui sécurisera formatera et placera les paramètres passées à la fonction, dans la requête.
$record = $base−>read("select * from table where id =  and nom =  and ville in ", "42", "Enzo", ["Annecy"]);
Les insertions possibles sont : Exemple :
// Avec cette valeur dans l'annuaire
value:client ma_table

// ce format SQL contenant des insertions
$base−>read(
"select * from {:client} where 1 {and validate = 42} {?and name like } and in ",
false,
"En",
"level",
[−1, null, "etc", "('"%_)", ""]
);

// est identique à celle−là
$base−>read('select * from ma_table where 1 and name like
"En%" and `level` in ("−1", "", "etc", "('"%_)", "")');
Attention : Si le nombre de paramètre passé est inférieur au nombre d'insertion, les insertions en plus ne seront pas traité et resteront sous forme de lettre encadré d'accolades (ce qui peut être utile pour stocker par exemple du JSon en base de donnée) :
// cette requête renverra tous les enregistrements dont la colonne "value" commence par : France
$list = $base−>list("select * from like , 'value', "France");

// alors que celle−ci renverra tous les enregistrements dont la colonne "value" égale à la constante :
$list = $base−>list(
"select * from like ''", 'value');

Base : Requête statique


Les requêtes SQL utilisé l'ensemble de ces méthodes, peuvent être stoquées dans l'annuaire.
Exemple :
// Avec cette valeur dans l'annuaire
sql:requete_42 select * from table where 1;

// la requête :
$base−>read("requete_42");

// sera remplacé par :
$base−>read("select * from table where 1");





Base : Traces

Il existe un mécanisme de trace, proposant au développeur de suivre et surveiller l'ensemble de ses requêtes SQL, via ces 3 méthodes :
trace()
Cette méthode permet de générer des traces, méthode que l'on peut employer de deux façons différentes :
  1. $base->trace(titre) : envoyé directement sur la console du navigateur.
  2. $base->trace() : sans paramètre, permet de renvoyer une structure contenant la trace (pour enregistrer l'information par exemple).
    $trace = $base−>trace();	// renvoi ['sql','result','error','time'] sur la dernière requête
    $base−>trace('title'); // envoi sur la console.log ['sql','result','error','time'] sur la dernière requête
trace_log()
Permet d'indiquer un temps maximum de temps passé sur les requêtes, avant de déclencher un log d'alerte, sous forme d'un fichier.log et d'un mail de synthèse journalier.
Ceci permettant au développeur d'optimiser, voir corriger, certaines de ses requêtes SQL qui mettraient trop de temps à s'exécuter.
Il est possible de passer les paramètres suivants : Par défaut, le log est activé à une demie-seconde, sans masquage.
$base−>trace_log(0.500);					// 1/2 seconde maximum avant de déclencher les logs
$base−>trace_log(false); // désactive le log
$base−>trace_log("password = '(.*?)'"); // masque les rubriques de base de nom password
trace_auto()
Il est également possible de placer des traces directement dans la plupart des méthodes précédentes (select, read, modify, …), en plaçant la trace en premier paramètre de ces méthodes, précédé du caractère : Cette méthode peut prendre le paramètre suivant : Renvoi dans tous les cas l'état de la trace.
$record = $base−>read("select * from table");				// n'affiche aucune trace
$record = $base−>select('!trace1', "select * from table"); // affiche trace1
$record = $base−>list('?trace2', "select * from table"); // n'affiche pas trace2
$base−>trace_auto(true); // active la trace pour les ?traces
$record = $base−>sql('?trace3', "select * from table"); // affiche trace3
$base−>trace_auto(false); // désactive la trace pour les ?traces
$record = $base−>read('?trace4', "select * from table"); // n'affiche plus trace4
$base−>trace_auto("trace5"); // active la trace forçée
$record = $base−>select('!trace6', "select * from table"); // affiche trace6
$record = $base−>list('?trace7', "select * from table"); // n'affiche pas trace7
$record = $base−>sql("select * from table"); // affiche cette trace avec le titre trace5
$base−>trace_auto(""); // desactive l'ajout de la trace forcée
$trace_actif = _$base−>trace_auto(); // renvoi un booléen de l'état d'activation
Log

Suivant les appels des méthodes de trace précédentes, les erreurs, warning et requête trop lentes vont s'afficher sur la console du navigateur et également être journalisé sur le serveur et synthétisé par mail journalier.
Le fichier.log est enregistré dans le dossier des log sur le serveur, avec le nom : base_AAAA-MM-JJ.log
Le format du fichier est une ligne = une trace, avec les colonnes suivantes, découpées par des tabulations :
  1. Date du jour : sous forme YYMMDD.
  2. Moment : sous forme HHMMSS.
  3. Utilisateur : sous forme d'un nom (extrait de$_SESSION['PHP_AUTH_USER']) suivi du caractère arobas (@) suivi de l'adresse IP de l'utilisateur.
  4. Temps : valeur flottante représentant le temps pris par la requête, en seconde précis à la microseconde.
  5. Mémoire : valeur flottante représentant la mémoire utilisés actuellement par PHP en giga octet (voirmemory_get_usage()).
  6. Résultat :
    • Soit le résultat, si un seul champ.
    • Soit le caractère dièse (#), suivi du nombre de champ ou enregistrement retourné.
    • Soit une erreur, sous forme d'un nombre positif, suivi du symbole égale (=), suivi du texte de l'erreur.
    • Soit un warning, sous forme d'un nombre négatif, suivi du symbole égale (=), suivi du texte du warning.
  7. SQL : la reqête SQL.
  8. URL : la requête URL côté navigateur.
  9. Programme : la pile de fonctions PHP, séparée par des tabulations, décomposée en différentes valeurs, séparée elles-mêmes par deux-petits-points (:) :
    • Le niveau de la pile, le 1 étant la première a avoir été lancée.
    • Le nom du fichier PHP.
    • Le numéro de la ligne dans ce fichier.
    • Le nom de la fonction PHP.
    • Les paramètres, entre parenthèse, passées à cette fonction.
Exemple :
yymmdd hhmmss	user@ip.address	time	memory	result|#lines|number=error	request	url	sub:php:line:function(args)
201231 123059 enzo@78.233.207.42 0.0006 0.0010 #18 select * from ma_table where id = 2 && enabled = 1 https://mon−site.com/rubrique/?event=235346 3:/library.php:1041:Base.select($sql, 2) 2:/lib/plume.php:688:ajax_ma_fonction('texte', 2942, 42) 1:/index.php:1485:Template.ajax()
Dans cet exemple, une requête a été faite avec succès, ce 31/12/2020 à 12h 30m 59s par "enzo" d'IP 78.233.207.42, qui a pris 0.0006s, pour 1Mo, et a renvoyé 18 enregistrements, avec la requête SQL : https://mon-site.com/rubrique/?event=235346.
Il a été lancé par la méthodeselect de la classeBase depuis la ligne 1041 du programme/library.php, lui-même, appelé parajax_ma_fonction en ligne 688 de/lib/plume.php, lui-même parTemplate.ajax appelé en ligne 1485 de/index.php.


PHP : mail

Classe PHP permettant de préparer et envoyer des mails.
// préparation du mail
$mail = new Mail();

// entête
$mail−>from("emetteur@mail.com");
$mail−>to("destinataire@mail.com");
$mail−>to(["destinataire@mail.com"]);
$mail−>cc("copie@mail.com");
$mail−>cc(["copie@mail.com"]);
$mail−>bcc("copie−caché@mail.com");
$mail−>bcc(["copie−caché@mail.com"]);
$mail−>returnPath("emetteur@mail.com");

$mail−>subject("titre du mail");

// corps du mail
$pattern = Template::extract("//message/page.html", "mail_template");
$message = Template::render($pattern−>html(['mail' => $message, 'signer' => $signature]));
$mail−>html(
"texte ou <html>");

if (! $mail−>attachment(
"/fichier.ext", "nom du fichier"))
echo
"fichier inexistant";

// envoi
$mail−>trace(
"mail"); // trace sur la console
if ($mail−>send()) // envoi le mail
return ['success' =>
"Mail anvoyé avec succès."];
return ['error' =>
"Problème lors de l'envoi du mail."];
Le constructeur accepte d'initialiser directement certains paramètres :
//	$mail = new Mail($from = '', $to = '', $subject = '', $body = '');
Et avec cette forme simplifiée et raccourcie, un dernier paramètre "send" mis à "true", envoye immédiatement le mail (sans avoir besoin de stocker l'instance Mail) :
new Mail("emetteur@dnada.fr", "client@client.com", "Mail de DNADA", "Merci pour votre réponse.\nCordialement", true);
Attention : à bien mettre une majuscule sur l'objet Mail, sinon PHP utilisera la fonction native mail().

PHP : Markdown

Fonction PHP permettant de réaliser des markdowns (tel que la plus grosse partie des pages de ce site ou simplement cette documentation que vous lisez actuellement).

À implémenter depuis PHP :
function template_produit($pattern) {
return markdown("mon_fichier_markdown.md");
}
Où depuis un template :
<html>
{+markdown(mon_fichier_markdown)+}
</html>
L'implémentation Markdown respecte la syntaxe suivante :

Markdown : Texte

Une fin de ligne provoquera un retour à la ligne HTML<br/> automatiquement.
Sauf si la ligne finie ou commence par une balise en ligne, tel que<span>, <code>, dans ce cas, pour forcer le retour à la ligne, il faut ajouter un espace en fin de ligne.
Un ensemble de caractères permettent des mises en page (tel que# * −), pour afficher ce caractère, il suffit de placer un anti-slash avant "\" (exemple\# \* \−).
Les caractères< > et & utilisés en HTML, sont naturellement protégés, mais rien n'interdit d'insérer des balises HTML. Exemple .
Les {templates} sont gérés, en dehors des blocs de code.

Markdown : Titre

Syntaxe : le caractère dièse# en début de ligne
Formatage : encadre avec les balises<h1>, <h2>, … Exemple :
# Titre 1
## Titre 2
### Titre 3
#### Titre 4
##### Titre 5
###### Titre 6
\# Ceci n'est pas un titre
Donnera :

Titre 1

Titre 2

Titre 3

Titre 4

Titre 5
Titre 6
# Ceci n'est pas un titre

Markdown : Emphase gras

Syntaxe : le caractère astérisque* encadrant un bloc de mots
Formatage : encadre avec la balise<strong> Exemple :
Exemple de texte *en gras sur une ligne* ou
sur plusieurs *lignes
blabla blabla blabla blabla blabla
en finissant* à tout instant.
(Ceci n'est pas \*du texte en gras mais juste encadré d'astérisques\*)
Donnera :
Exemple de texte en gras sur une ligne ou
sur plusieurs lignes
blabla blabla blabla blabla blabla
en finissant
à tout instant.
(Ceci n'est pas *du texte en gras mais juste encadré d'astérisques*)

Markdown : Emphase souligné

Syntaxe : le caractère espace-souligné_ encadrant un bloc de mots
Formatage : encadre avec la balise<u> Exemple :
Exemple de texte _souligné sur une ligne_ ou
sur plusieurs _lignes
blabla blabla blabla blabla blabla
en finissant_ à tout instant.
(Ceci n'est pas \_du texte souligné mais juste encadré d'un espace souligné\_)
Donnera :
Exemple de texte souligné sur une ligne ou
sur plusieurs lignes
blabla blabla blabla blabla blabla
en finissant
à tout instant.
(Ceci n'est pas _du texte souligné mais juste encadré d'un espace souligné_)

Markdown : Emphase barré

Syntaxe : deux fois le moins encadrant un bloc de mots
Formatage : encadre avec la balise<s> Exemple :
Exemple de texte −−barré sur une ligne−− ou
sur plusieurs −−lignes
blabla blabla blabla blabla blabla
en finissant−− à tout instant.
(Ceci n'est pas \−−du texte barré mais juste encadré de 2 moins−−)
Donnera :
Exemple de texte barré sur une ligne ou
sur plusieurs lignes
blabla blabla blabla blabla blabla
en finissant
à tout instant.
(Ceci n'est pas --du texte barré mais juste encadré de 2 moins--)

Markdown : Ligne

Syntaxe : Trois caractères moins minimum en début de ligne
Formatage : transformer en balise<hr/> Exemple :
−−−
Donnera :

Markdown : Lien

Syntaxe : [texte descriptif](url)
Formatage : encadre de la balise<a>
Exemple :
Lien externe [Site de DNADA](https://www.d−nada.com) et lien interne à la page [1er chapitre](# Présentation).
Donnera :
Lien externe Site de DNADA et lien interne à la page 1er chapitre.

Les adresses mails présentes dans la partie texte (et non dans un bloc de code) sont automatiquement protégées et transformées en adresse mail HTML.
Exemple : contact@d-nada.com
Donnera :

Markdown : Image

Syntaxe : !texte alternatif Formatage : transforme avec la balise<img>
Exemple :
Image : ![image représentant une plume](exemple image.png)
Donnera :
Image :
image représentant une plume
Il est possible de définir la taille (en hauteur et/ou largeur) et de justifier la position de l'image dans le texte, avec la syntaxe suivante en fin de texte explicatif (qui ne sera supprimé de ce texte) : largeur/hauteur/centrage.
Avec :
Mais je dois vous expliquer comment est née toute cette idée erronée de dénoncer le plaisir et louer la douleur et je vais vous donner un compte rendu complet du système, et exposer les enseignements réels du grand explorateur de la vérité, le maître-constructeur de l'homme bonheur. Personne ne rejette, n'aime pas ou évite le plaisir lui-même, parce que c'est du plaisir, mais parce que ceux qui ne savent pas poursuivre le plaisir rencontrent rationnellement des conséquences extrêmement douloureuses.
image représentant une petite plume
Il n'y a pas non plus de personne qui aime ou poursuit ou désire obtenir la douleur d'elle-même, parce que c'est de la douleur, mais parce que des circonstances se produisent parfois dans lesquelles le travail et la douleur peuvent lui procurer un grand plaisir. Pour prendre un exemple trivial, lequel d'entre nous entreprend jamais un exercice physique laborieux, sauf pour en tirer un avantage ? Mais qui a le droit de trouver à redire à un homme qui choisit de jouir d'un plaisir qui n'a pas de conséquences gênantes, ou qui évite une douleur qui ne produit aucun plaisir résultant ?
Mais je dois vous expliquer comment est née toute cette idée erronée de dénoncer le plaisir et louer la douleur et je vais vous donner un compte rendu complet du système, et exposer les enseignements réels du grand explorateur de la vérité, le maître-constructeur de l'homme bonheur. Personne ne rejette, n'aime pas ou évite le plaisir lui-même, parce que c'est du plaisir, mais parce que ceux qui ne savent pas poursuivre le plaisir rencontrent rationnellement des conséquences extrêmement douloureuses.
image représentant une plume à gauche
Il n'y a pas non plus de personne qui aime ou poursuit ou désire obtenir la douleur d'elle-même, parce que c'est de la douleur, mais parce que des circonstances se produisent parfois dans lesquelles le travail et la douleur peuvent lui procurer un grand plaisir. Pour prendre un exemple trivial, lequel d'entre nous entreprend jamais un exercice physique laborieux, sauf pour en tirer un avantage ? Mais qui a le droit de trouver à redire à un homme qui choisit de jouir d'un plaisir qui n'a pas de conséquences gênantes, ou qui évite une douleur qui ne produit aucun plaisir résultant ?
Mais je dois vous expliquer comment est née toute cette idée erronée de dénoncer le plaisir et louer la douleur et je vais vous donner un compte rendu complet du système, et exposer les enseignements réels du grand explorateur de la vérité, le maître-constructeur de l'homme bonheur. Personne ne rejette, n'aime pas ou évite le plaisir lui-même, parce que c'est du plaisir, mais parce que ceux qui ne savent pas poursuivre le plaisir rencontrent rationnellement des conséquences extrêmement douloureuses.
image représentant une plume centrée
Il n'y a pas non plus de personne qui aime ou poursuit ou désire obtenir la douleur d'elle-même, parce que c'est de la douleur, mais parce que des circonstances se produisent parfois dans lesquelles le travail et la douleur peuvent lui procurer un grand plaisir. Pour prendre un exemple trivial, lequel d'entre nous entreprend jamais un exercice physique laborieux, sauf pour en tirer un avantage ? Mais qui a le droit de trouver à redire à un homme qui choisit de jouir d'un plaisir qui n'a pas de conséquences gênantes, ou qui évite une douleur qui ne produit aucun plaisir résultant ?
Mais je dois vous expliquer comment est née toute cette idée erronée de dénoncer le plaisir et louer la douleur et je vais vous donner un compte rendu complet du système, et exposer les enseignements réels du grand explorateur de la vérité.
image représentant une plume à droite
Le maître-constructeur de l'homme bonheur. Personne ne rejette, n'aime pas ou évite le plaisir lui-même, parce que c'est du plaisir, mais parce que ceux qui ne savent pas poursuivre le plaisir rencontrent rationnellement des conséquences extrêmement douloureuses. Il n'y a pas non plus de personne qui aime ou poursuit ou désire obtenir la douleur d'elle-même, parce que c'est de la douleur, mais parce que des circonstances se produisent parfois dans lesquelles le travail et la douleur peuvent lui procurer un grand plaisir. Pour prendre un exemple trivial, lequel d'entre nous entreprend jamais un exercice physique laborieux, sauf pour en tirer un avantage ? Mais qui a le droit de trouver à redire à un homme qui choisit de jouir d'un plaisir qui n'a pas de conséquences gênantes, ou qui évite une douleur qui ne produit aucun plaisir résultant ?
Mais je dois vous expliquer comment est née toute cette idée erronée de dénoncer le plaisir et louer la douleur et je vais vous donner un compte rendu complet du système, et exposer les enseignements réels du grand explorateur de la vérité, le maître-constructeur de l'homme bonheur. Personne ne rejette, n'aime pas ou évite le plaisir lui-même, parce que c'est du plaisir, mais parce que ceux qui ne savent pas poursuivre le plaisir rencontrent rationnellement des conséquences extrêmement douloureuses. Il n'y a pas non plus de personne qui aime ou poursuit ou désire obtenir la douleur d'elle-même, parce que c'est de la douleur, mais parce que des circonstances se produisent parfois dans lesquelles le travail et la douleur peuvent lui procurer un grand plaisir. Pour prendre un exemple trivial, lequel d'entre nous entreprend jamais un exercice physique laborieux, sauf pour en tirer un avantage ? Mais qui a le droit de trouver à redire à un homme qui choisit de jouir d'un plaisir qui n'a pas de conséquences gênantes, ou qui évite une douleur qui ne produit aucun plaisir résultant ?
image représentant une plume à babord
Mais je dois vous expliquer comment est née toute cette idée erronée de dénoncer le plaisir et louer la douleur et je vais vous donner un compte rendu complet du système, et exposer les enseignements réels du grand explorateur de la vérité, le maître-constructeur de l'homme bonheur. Personne ne rejette, n'aime pas ou évite le plaisir lui-même, parce que c'est du plaisir, mais parce que ceux qui ne savent pas poursuivre le plaisir rencontrent rationnellement des conséquences extrêmement douloureuses. Il n'y a pas non plus de personne qui aime ou poursuit ou désire obtenir la douleur d'elle-même, parce que c'est de la douleur, mais parce que des circonstances se produisent parfois dans lesquelles le travail et la douleur peuvent lui procurer un grand plaisir. Pour prendre un exemple trivial, lequel d'entre nous entreprend jamais un exercice physique laborieux, sauf pour en tirer un avantage ? Mais qui a le droit de trouver à redire à un homme qui choisit de jouir d'un plaisir qui n'a pas de conséquences gênantes, ou qui évite une douleur qui ne produit aucun plaisir résultant ?
Mais je dois vous expliquer comment est née toute cette idée erronée de dénoncer le plaisir et louer la douleur et je vais vous donner un compte rendu complet du système, et exposer les enseignements réels du grand explorateur de la vérité, le maître-constructeur de l'homme bonheur. Personne ne rejette, n'aime pas ou évite le plaisir lui-même, parce que c'est du plaisir, mais parce que ceux qui ne savent pas poursuivre le plaisir rencontrent rationnellement des conséquences extrêmement douloureuses. Il n'y a pas non plus de personne qui aime ou poursuit ou désire obtenir la douleur d'elle-même, parce que c'est de la douleur, mais parce que des circonstances se produisent parfois dans lesquelles le travail et la douleur peuvent lui procurer un grand plaisir. Pour prendre un exemple trivial, lequel d'entre nous entreprend jamais un exercice physique laborieux, sauf pour en tirer un avantage ? Mais qui a le droit de trouver à redire à un homme qui choisit de jouir d'un plaisir qui n'a pas de conséquences gênantes, ou qui évite une douleur qui ne produit aucun plaisir résultant ?
Mais je dois vous expliquer comment est née toute cette idée erronée de dénoncer le plaisir et louer la douleur et je vais vous donner un compte rendu complet du système, et exposer les enseignements réels du grand explorateur de la vérité, le maître-constructeur de l'homme bonheur. Personne ne rejette, n'aime pas ou évite le plaisir lui-même, parce que c'est du plaisir, mais parce que ceux qui ne savent pas poursuivre le plaisir rencontrent rationnellement des conséquences extrêmement douloureuses. Il n'y a pas non plus de personne qui aime ou poursuit ou désire obtenir la douleur d'elle-même, parce que c'est de la douleur, mais parce que des circonstances se produisent parfois dans lesquelles le travail et la douleur peuvent lui procurer un grand plaisir. Pour prendre un exemple trivial, lequel d'entre nous entreprend jamais un exercice physique laborieux, sauf pour en tirer un avantage ? Mais qui a le droit de trouver à redire à un homme qui choisit de jouir d'un plaisir qui n'a pas de conséquences gênantes, ou qui évite une douleur qui ne produit aucun plaisir résultant ?
Mais je dois vous expliquer comment est née toute cette idée erronée de dénoncer le plaisir et louer la douleur et je vais vous donner un compte rendu complet du système, et exposer les enseignements réels du grand explorateur de la vérité, le maître-constructeur de l'homme bonheur. Personne ne rejette, n'aime pas ou évite le plaisir lui-même, parce que c'est du plaisir, mais parce que ceux qui ne savent pas poursuivre le plaisir rencontrent rationnellement des conséquences extrêmement douloureuses. Il n'y a pas non plus de personne qui aime ou poursuit ou désire obtenir la douleur d'elle-même, parce que c'est de la douleur, mais parce que des circonstances se produisent parfois dans lesquelles le travail et la douleur peuvent lui procurer un grand plaisir. Pour prendre un exemple trivial, lequel d'entre nous entreprend jamais un exercice physique laborieux, sauf pour en tirer un avantage ? Mais qui a le droit de trouver à redire à un homme qui choisit de jouir d'un plaisir qui n'a pas de conséquences gênantes, ou qui évite une douleur qui ne produit aucun plaisir résultant ?
image représentant une plume à tribord

Mais je dois vous expliquer comment est née toute cette idée erronée de dénoncer le plaisir et louer la douleur et je vais vous donner un compte rendu complet du système, et exposer les enseignements réels du grand explorateur de la vérité, le maître-constructeur de l'homme bonheur. Personne ne rejette, n'aime pas ou évite le plaisir lui-même, parce que c'est du plaisir, mais parce que ceux qui ne savent pas poursuivre le plaisir rencontrent rationnellement des conséquences extrêmement douloureuses. Il n'y a pas non plus de personne qui aime ou poursuit ou désire obtenir la douleur d'elle-même, parce que c'est de la douleur, mais parce que des circonstances se produisent parfois dans lesquelles le travail et la douleur peuvent lui procurer un grand plaisir. Pour prendre un exemple trivial, lequel d'entre nous entreprend jamais un exercice physique laborieux, sauf pour en tirer un avantage ? Mais qui a le droit de trouver à redire à un homme qui choisit de jouir d'un plaisir qui n'a pas de conséquences gênantes, ou qui évite une douleur qui ne produit aucun plaisir résultant ?
Mais je dois vous expliquer comment est née toute cette idée erronée de dénoncer le plaisir et louer la douleur et je vais vous donner un compte rendu complet du système, et exposer les enseignements réels du grand explorateur de la vérité, le maître-constructeur de l'homme bonheur. Personne ne rejette, n'aime pas ou évite le plaisir lui-même, parce que c'est du plaisir, mais parce que ceux qui ne savent pas poursuivre le plaisir rencontrent rationnellement des conséquences extrêmement douloureuses. Il n'y a pas non plus de personne qui aime ou poursuit ou désire obtenir la douleur d'elle-même, parce que c'est de la douleur, mais parce que des circonstances se produisent parfois dans lesquelles le travail et la douleur peuvent lui procurer un grand plaisir. Pour prendre un exemple trivial, lequel d'entre nous entreprend jamais un exercice physique laborieux, sauf pour en tirer un avantage ? Mais qui a le droit de trouver à redire à un homme qui choisit de jouir d'un plaisir qui n'a pas de conséquences gênantes, ou qui évite une douleur qui ne produit aucun plaisir résultant ?
Mais je dois vous expliquer comment est née toute cette idée erronée de dénoncer le plaisir et louer la douleur et je vais vous donner un compte rendu complet du système, et exposer les enseignements réels du grand explorateur de la vérité, le maître-constructeur de l'homme bonheur. Personne ne rejette, n'aime pas ou évite le plaisir lui-même, parce que c'est du plaisir, mais parce que ceux qui ne savent pas poursuivre le plaisir rencontrent rationnellement des conséquences extrêmement douloureuses. Il n'y a pas non plus de personne qui aime ou poursuit ou désire obtenir la douleur d'elle-même, parce que c'est de la douleur, mais parce que des circonstances se produisent parfois dans lesquelles le travail et la douleur peuvent lui procurer un grand plaisir. Pour prendre un exemple trivial, lequel d'entre nous entreprend jamais un exercice physique laborieux, sauf pour en tirer un avantage ? Mais qui a le droit de trouver à redire à un homme qui choisit de jouir d'un plaisir qui n'a pas de conséquences gênantes, ou qui évite une douleur qui ne produit aucun plaisir résultant ?

Markdown : Liste

Syntaxe : le caractère plus pour une liste non-ordonnée (•, •, •), ou moins+ pour une liste ordonnée (1, 2, 3), en début de ligne, suivi d'un eventuel "titre :"
Formatage : encadre avec la balise<ul> ou<ol>, et encadre de<span> l'eventuel titre.
Exemple :
− titre : texte

− Action
− Aventure
− Fantastique
−− Bande−dessinée
−− Roman
−−− Stephen King
−−− H.P. Lovecarft
−−− Edgar Allan Poe
−−− J.R.R Tolkien
−− Film
−− Série
− Policier
− Suspense
+ Mercure
+ Vénus
+ Terre
++ Lune
+ Mars
++ Phobos
++ Déimos
+ Jupiter
++ Io
++ Europe
++ Ganymède
++ Callisto
+− 79 satellites
+ Saturne
+− 200 satellites
+ Uranus
+− 27 satellites
+ Neptune
+− 14 satellites
Donnera :
  • titre : texte
  • Action
  • Aventure
  • Fantastique
    • Bande-dessinée
    • Roman
  • Stephen King
- H.P. Lovecarft
  • Edgar Allan Poe
- J.R.R Tolkien
    • Film
    • Série
  • Policier
  • Suspense
  1. Mercure
  2. Vénus
  3. Terre
    1. Lune
  4. Mars
    1. Phobos
    2. Déimos
  5. Jupiter
    1. Io
    2. Europe
    3. Ganymède
    4. Callisto
    • 79 satellites
  6. Saturne
    • 200 satellites
  7. Uranus
    • 27 satellites
  8. Neptune
    • 14 satellites

Markdown : Code

Syntaxe : un caractère guillemet oblique` Formatage : encadre soit de balise<code> s'il tient sur une ligne, soit<pre> s'il tient sur plusieurs lignes
Exemple :
Exemple de `code` sur une seule ligne et
`
Exemple d'un bloc de code
sur plusieurs lignes.
`
Donnera :
Exemple decode sur une seule ligne et
Exemple d'un bloc de code
sur plusieurs lignes.
Les blocs de code ont leurs propres formatage de couleur, pour une meilleur lisibilité :
* des balises HTML (<cite><var></var></cite>).
* des mot-clés tel que if, return (<cite>)
* des $variables et Classe:: (<var>)
* des "constantes" (<samp>)
* des // commentaires (<abbr>)
Exemple :
<button name="ok"> Action </button>

// commentaire
function fonction($parametre) {
while ($parametre > 0) {
if ($parametre)
$parametre −= 1;
Template::render("<div>texte constant</div>");
}
return;
}

Markdown : Citation

Syntaxe : un ou plusieurs caractères> en début de ligne
Formatage : encadre avec la balise<blockquote> Exemple :
> Exemple de citation
>> imbriqué sur
>>> plusieurs niveaux
>> et sur
>> plusieurs lignes
> fin de la première citation
\> ceci n'est pas une citation
Donnera :
Exemple de citation
imbriqué sur
plusieurs niveaux
et sur
plusieurs lignes
fin de la première citation
> ceci n'est pas une citation

Markdown : Sommaire

Syntaxe : !{Table of Contents} ou !{Table of Contents(de 1 à 6)}
Formatage : construit dynamiquement un sommaire, encadré de<nav>.
Exemple :
!{Table of Contents(5)}
Donnera le sommaire de cette page en affichant que les 4 premiers niveaux.

Markdown : Social

Syntaxe : ?[social](lien facebook)
Formatage : remplace par un lien rattaché à un icône propre au réseau social spécifié.
Exemple :
?[facebook](mon facebook)	?[skype](mon skype)	?[blog](url vers mon blog).
Donnera :
Facebook
mon skype
Blog
.


PHP : Fault

Plume inclus un mécanisme simple d'exception pour les développeurs : Fault

Lorsque le script PHP rencontre un problème bloquant, grâce à ce mécanisme, le développeur peut ajouter une instruction afin de signaler ce problème et provoquera la sortie du programme.
Dans le but d'unifier et retrouver plus facilement le "fault", il est conseillé de créer un "fault" unique par erreur (dans l'exemple plus bas, le mot "UneErreurBienPrecise").
Syntaxe "Fault::" + le nom interne de l'erreur :
if ($unCasQuiNeDevraitPasSurvenir)
Fault::UneErreurBienPrecise(['session' => $_SESSION, 'conf' => $conf, 'file' => $file], "qurante−deux");
Cette exception doit être initialisée en début de programme, avec les autres exceptions :
Fault::setFault([
'UneErreurBienPrecise' => "Un cas %s qui ne devrait pas survenir",
// …
]);
Ces instructions d'exception acceptent les paramètres suivants :
  1. Un tableau qui ne sera affiché sur la console et dans le log, que pour le développeur. Informations privées.
  2. Une suite de paramètres permettant de personnaliser le message d'erreur, via le mécanisme de printf("%d, %s, …").

L'exception générée dans une méthode ajax ou template sera renvoyée sous forme de trace sur la console du navigateur.
Il sera également sauvegardé dans un fichier log journalier, présent sur le serveur de nommage error_AAAA-MM-JJ.log et dont chaque ligne aura les champs suivants séparés d'une tabulation :
Si une erreur PHP survient lors d'un appel AJAX, cette erreur est également enregistrée dans ce journal, avec la syntaxe suivante :
Exemple fictif :
201231 124907	fault	76.200.201.202	https://www.d−nada.com/test/	appendUnauthorized	Ajout non−autorisé	{"id":"7","client":"abc","login":"laurent","pref":{"entitled":true,"emplacement":123}}
201231 125142 error 76.200.201.202 https://www.d−nada.com/test/ Warning: Invalid argument supplied for foreach() in /home/server/www/test/dev.php on line 170

PHP : Divers


Plusieurs fonctions existent en plus :

utfsort($array, $column, $sep = '')

uni8encoder($str)

arraysubstr($array, $start, $length = NULL)

navigator($type = '')

(voir window.navigator)
Gère le template inconnu

getdirlog($path = '')

jsondecodes($json, &$strict = false, &$pos = 0)

Cette fonction remplace la fonction native jsondecode mais sans les restrictions (et bug).

jsonencodes($data, $strict = false)

Cette fonction remplace la fonction native jsonencode mais sans les restrictions (et bug).

curl($url, $post = false, $file = false)



Installation

Suivre les 4 étapes suivantes :
  1. Télécharger les fichiers : com.dnada.plume.zip (44Ko).
  2. Les placer sur votre serveur
  3. Les appeler côté page HTML (de préférence dans un fichier entete.html qui sera appelé dans toutes vos pages, par exemple, via un template de type {header}) :
    <!DOCTYPE html>
    <html lang="fr">
    <head>

    <link href="/lib/plume.css" rel="stylesheet"/>
    <script src="/lib/plume.js"></script>

    </head>
    <body>

    {.mon_template() .}

    </body>
    </html>
  4. et dans vos sources PHP (de préférence dans un fichier.php, qui sera inclus via un require())) :
    <?php
    define('PLUME_MAIL_REPORT', "report@ma−societe.com");
    define('PLUME_TRACE_IP', '77.0.1.2');
    require('plume.php');
    $base = new Base("mon−client");
    Template::ajax(function() use($base) { $base−>close(); });
    Template::display();

    function template_mon_template($pattern) {

    }
    // fonction appelé en JS : $.ajax.ma_methode(par1).then(function(resultat) { }
    function ajax_ma_methode($par1 = 'default') {

    }
Et c'est tout !
Ah Si ! Dites nous ce que vous pensez de cette librairie, faites nous remontez vos remarques, bug (pas possible), améliorations (possible), ou simplement un petit merci, c'est par ici :

Copyright

Cette librairie Plume est l'œuvre et la propriété de DNada.
Même si gratuite, cette librairie ne peut être utilisée partiellement ou intégralement dans des développements rémunérées, sauf par consentement écrite de la société DNada. (Mais bon, aucune crainte à avoir, aucun refus n'a été fait jusqu'à présent).
Pour toutes demandes ou questions, merci de nous contacter :