Une BlockAction peut directement écrire la réponse sans passer par une vue en utilisant la méthode write()
de l'objet response. Cela pourra notamment être utilisé lorsque le flux XHTML est fourni par un service externe. Dans ce cas, execute()
ne doit rien retourner (null
).
class mymodule_MyBlockAction extends website_BlockAction { /** * @see f_mvc_Action::execute() * * @param f_mvc_Request $request * @param f_mvc_Response $response * @return String */ function execute($request, $response) { $html = ...; $response->write($html); return null; } }
A la fin de son execution, un bloc peut rediriger vers un autre bloc. Typiquement, l'action prenant en compte l'insertion d'un nouveau document (ou plus généralement recevant un POST), aura intérêt à rediriger vers l'action affichant le résultat plutôt que de se contenter d'afficher la vue “résultat”. Ainsi, on évitera le symptôme “rejouer l'action” lorsque l'utilisateur rafraîchira sa page de résultat.
La redirection repose sur le fait que la page contenant le bloc visé soit taguée avec le tag contextuel du bloc (contextual_website_website_<NOM_MODULE>_<NOM_BLOC>
). Si aucune page n'est trouvée, redirect()
est équivalente à forward()
La méthode website_BlockAction::redirect()
prend 4 paramètres dont deux optionnels :
/** * @see f_mvc_Action::redirect() * * @param String $moduleName * @param String $actionName * @param Array<String, String> $moduleParams * @param Array<String, String> $absParams */ public final function redirect($moduleName, $actionName, $moduleParams = null, $absParams = null);
Ainsi, pour rediriger vers le bloc Mydocument du module “mymodule” (autrement dit la classe mymodule_BlockMydocumentAction
), un bloc pourra en fin de sa méthode execute() procéder comme suit :
$this->redirect("mymodule", "mydocument", array("documentId" => 123), array("otherParam" => "toto"));
Si URL_PAGE.html
est l'URL de la page tagguée contextual_website_website_mymodule_mydocument
dans le site courant, l'URL vers laquelle le client sera redirigé sera : URL_PAGE.html?mymoduleParam[“documentId”]=123&otherParam=toto
Le paramètre $actionName
de redirect()
permet de spécifier la méthode execute du bloc à exécuter.
Ainsi la ligne suivante redirigera vers la page taguée contextual_website_website_rbs_componenttest
et la méthode executeRun()
du bloc rbs_BlockComponenttestAction
sera exécutée :
$this->redirect("rbs", "componenttest.run", array("componenttestId" => 123));
Le paramètre $actionName
de redirect()
permet de spécifier une ancre :
$this->redirect("rbs", "componenttest.run#feature456", array("componenttestId" => 123));
L'URL générée sera complétée de #feature456
, permettant ainsi au navigateur client de positionner le viewport
à l'ancre considérée.
N.B.: l'ancre doit toujours être le dernier élément du paramètre $actionName.
Un bloc peut mettre à disposition des métadonnées que le rédacteur utilise pour renseigner les propriétés “titre de page”, “description” et “mots-clefs” de la page le contenant.
Ainsi le bloc “post” (ou “Détail d'un billet”) propose des métadonnées comme “titre du billet”, “description du billet” ou “mots-clefs du billet” :
Deux étapes sont nécessaires pour qu'un bloc propose des métadonnées :
getMetas()
dans l'action, qui doit retourner un tableau des valeurs des métadonnées,
Exemple : déclaration des métadonnées proposées par le bloc “billet” du module “blog” (dans config/blocks.xml
) :
<block type="modules_blog_post" ...> <parameters> ... </parameters> <metas> <meta name="postLabel" allow="title,description" /> <meta name="postDate" allow="title,description" /> <meta name="postSummary" allow="description" /> <meta name="blogLabel" allow="title,description" /> <meta name="blogDescription" allow="description" /> <meta name="postKeywords" allow="keywords" /> <meta name="postCategories" allow="keywords" /> </metas> </block>
Le tableau suivant présente les attributs de l'élément <meta/>
, tous obligatoires :
Nom | Valeurs possibles | Description |
---|---|---|
name | chaîne de caractères libre | identifiant de la métadonnée, unique au sein du bloc |
allow | combinaisons de “title”, “description” et “keywords”, séparés par des virgules | liste des propriétés de la page où la métadonnée est utilisable |
A chaque métadonnée de nom “name” correspond une clef de localisation “modules.<moduleName>.bo.blocks.<blockName>.metas.<name>”.
Et du côté de l'action :
class blog_BlockPostAction extends website_BlockAction { function getMetas() { ... return array("postLabel" => $postLabel, "blogLabel" => $blogLabel, ...); } }
Un bloc peut déléguer l'affichage à un autre bloc en utilisant la méthode forward()
. Le “sous-bloc” est alors exécuté avec une requête qui partage l'ensemble des paramètres et attributs de la requête courante.
La méthode forward()
prend deux arguments :
/** * @see website_BlockAction::execute() * * @param f_mvc_Request $request * @param f_mvc_Response $response * @return String */ function execute($request, $response) { // ... some code $this->forward("mymodule", "mydocument"); }
N.B. : forward()
ne retourne rien : le résultat de l'exécution du bloc (affichage de la vue compris !) est directement affiché comme si le bloc “hôte” avait écrit le résultat de l'exécution sur la réponse.
La déléguation à un autre bloc peut se faire depuis un gabarit en utilisant l'extension PHPTal change:block
Avant d'exécuter executeXXX()
, le controlleur (website_BlockController
) interroge (si elle existe) la méthode validateXXXInput()
du bloc :
true
, alors seulement executeXXX()
sera exécutéegetXXXInputViewName()
est appelée pour déterminer le nom de la vue à afficher et celle-ci est rendue.
Optionnellement, le bloc peut implémenter validateInput()
pour conditionner l'appel à execute()
: si validateInput()
retourne false
, execute()
n'est pas exécutée et la méthode getInputViewName()
détermine la vue à afficher. L'implémentation par défaut de validateInput() retourne true
.
La validation des données peut souvent se régler en utilisant des validateurs du framework.
L'implémentation par défaut de validateXXXInput()
utilise la méthode getXXXInputValidationRules()
dont le rôle est de retourner des règles de validation, du type “<parameterName>{<validatorName>:<validatorParam>}”, comme dans la définition XML d'un document.
Voici un exemple qui valide les données d'entrée de la méthode executeSave()
en forçant :
/** * @param f_mvc_Request $request * @return String[] */ function getSaveInputValidationRules($request) { return array("label{blank:false;maxSize:255}", "status{maxSize:10}", "email{email:true}); }
Il est possible de récupérer tout ou partie des contraintes déclarées sur un document donné en utilisant BeanUtils::getBeanValidationRules()
. Le tableau retourné est directement utilisable comme retour de getXXXInputValidationRules()
La méthode BeanUtils::getBeanValidationRules()
prend trois arguments dont deux optionnels :
$beanClassName
: le nom de la classe du document$include
: le tableau optionnel des noms de propriété desquelles les contraintes doivent être importées$exclude
: le tableau optionnel des noms de propriété desquelles les contraintes ne doivent pas être importées/** * @param String $beanClassName * @param String[] $include * @param String[] $exclude * @return String[] */ static function getBeanValidationRules($beanClassName, $include = null, $exclude = null)
Ainsi, pour récupérer les contraintes déclarées et du document mymodule_persistentdocument_mydoc
, sauf pour la propriété project
, on procédera comme suit :
function getSaveInputValidationRules($request) { return BeanUtils::getBeanValidationRules("mymodule_persistentdocument_mydoc", null, array("project")); }
Les méthode getXXXInputValidationRules()
reçoivent en argument la requête, ce qui leur permet selon la valeur de certains attributs de valider ou non telle ou telle partie des données.
function getSaveInputValidationRules($request, rbs_persistentdocument_component $component) { if ($component->isNew()) { // project property will be initialized later. return BeanUtils::getBeanValidationRules("rbs_persistentdocument_component", null, array("project")); } return BeanUtils::getBeanValidationRules("rbs_persistentdocument_component"); }
Lorsque la validation déclarative ne suffit pas pour exprimer l'ensemble des contraintes, il faut ré-implémenter la méthode validateXXXInput()
et retourner true
ou false
selon les cas.
Si certaines validation peuvent s'exprimer déclarativement, on peut alors utiliser la méthode website_BlockAction::processValidationRules()
:
function validateSaveInput($request, mymodule_mybean $component) { // declarative validation $rules = array("label{blank:false;maxSize:255}", "status{maxSize:10}"); $isOk = $this->processValidationRules($rules, $request, $component); // non-declarative validation $myCustomOk = $component->isSomethingMethod($request); return $isOk && $myCustomOk; }
N.B. : on peut aussi, si cela a du sens, déclarer son propre validateur et l'utiliser dans une règle.
Transmettre des messages utilisateurs comme de simples messages de confirmation ou des messages d'erreur se fait avec les méthodes suivantes :
website_BlockAction::addMessage($msg)
,website_BlockAction::addError($msg)
.
Ces méthodes définissent des attributs de l'objet Page (website_BlockAction::getContext() : website_Page
) qui sont notamment utilisés par les extensions change:messages
et change:errors
pour la restitution des messages dans la vue.
Les messages transmis ne sont pas traduits : si vos messages sont internationalisés, il faudra utiliser f_Locale::translate()
en amont de l'appel à addMessage()
ou addError()
.
$this->addMessage(f_Locale::translate("&mymodule.bo.someMessage;")); $this->addMessage(f_Locale::translate("&mymodule.bo.someOtherMessage;"));
N.B.: Lors de la validation déclarative des paramètres d'entrée de la méthode execute()
, les éventuels messages d'erreurs sont automatiquement ajoutés en suivant le même mécanisme.
...
<ul change:messages="" />
...
va générer
... <ul class="messages"> <li>un certain message</li> <li>un certain autre message</li> </ul> ...
Dans le cas où votre bloc affiche plusieurs formulaires et que vous souhaitez segmenter les messages utilisateurs, il faudra utiliser le second argument optionnel $relKey
:
Côté bloc :
$this->addMessage(f_Locale::translate("&mymodule.bo.someMessage;"), "form1"); $this->addMessage(f_Locale::translate("&mymodule.bo.someOtherMessage;"), "form2");
Côté gabarit :
<form change:form id="form1"> <!-- affiche someMessage traduit --> <ul change:messages="relKey form1" /> </form> <form change:form id="form2"> <!-- affiche someOtherMessage traduit --> <ul change:messages="relKey form2" /> </form>
Un bloc peut éventuellement être responsable de plusieurs traitement distincts comme l'affichage du détail, l'ajout, l'édition, la suppression ou encore le listing de documents d'un modèle donné.
Pour simplifier le code, éviter la multiplication des classes et permettre plus facilement la mutualisation de portions de code, un bloc peut déclarer plusieurs méthodes de type “execute”. Il pourra ainsi implémenter les méthodes execute()
, executeList()
, executeSave()
, executeDelete()
, ou tout autre méthode nommée execute<Action>()
.
Le contrôleur de blocs prendra la main pour décider quelle méthode devra être appelée (économisant ainsi une série de if
ou de switch
dans la méthode execute du bloc).
A chaque méthode executeXXX() sont associées les méthodes :
Chaque “sous action” a donc son cycle d'exécution séparé des autres.
La sélection de la méthode du bloc à exécuter se fait en fonction du paramètre de requête website_BlockAction::SUBMIT_PARAMETER_NAME
: si ce paramètre est présent et défini pour un bloc une valeur alors cette valeur donne le nom de la méthode à exécuter.
En général, le développeur ne renseigne pas directement ce paramètre : les extensions PHPTal change:submit
et change:actionlink
le définissent correctement en fonction de leurs paramètres :
change:submit
, le paramètre name
permet de spécifier la “sous-action” cible,change:actionlink
, le paramètre action
permet spécifier la “sous-action” cible.
Le bloc rbs_BlockProjectAction
est responsable de :
generic_DetailBlockAction
)executeAddForm
)executeEditForm
)executeDelete
)Ci-dessous la classe du bloc :
class rbs_BlockProjectAction extends generic_DetailBlockAction { /** * @see website_BlockAction::execute() * * @param f_mvc_Request $request * @param f_mvc_Response $response * @return String */ function executeAddForm($request, $response, rbs_persistentdocument_project $project) { return $this->getSaveInputViewName(); } /** * @see website_BlockAction::execute() * * @param f_mvc_Request $request * @param f_mvc_Response $response * @return String */ function executeEditForm($request, $response, rbs_persistentdocument_project $project) { return $this->getSaveInputViewName(); } function getSaveInputViewName() { return "Form"; } function getSaveInputValidationRules($request) { return BeanUtils::getBeanValidationRules("rbs_persistentdocument_project"); } /** * @see website_BlockAction::execute() * * @param f_mvc_Request $request * @param f_mvc_Response $response * @return String */ function executeSave($request, $response, rbs_persistentdocument_project $project) { $isNew = $project->isNew(); $project->save(); if ($isNew) { return $this->redirect("rbs", "projectlist"); } return $this->redirect("rbs", "project", array("projectId" => $project->getId())); } /** * @see website_BlockAction::execute() * * @param f_mvc_Request $request * @param f_mvc_Response $response * @return String */ function executeDelete($request, $response) { $project = $this->getDocumentParameter("projectId"); if ($project !== null) { $project->delete(); } return $this->redirect("rbs", "projectlist"); } }
Le gabarit utilisé pour le détail d'un projet (Rbs-Block-Project-Success.all.all.html
). Celui-ci affiche les propriétés du projet et présente des liens pour :
rbs_BlockProjectlistAction
),<tal:block change:loadhandler="generic_DocumentLoadHandler" args="project, projectId" /> <h2 change:h="">${project/getLabel}</h2> <p> <a change:actionlink="" block="rbs_projectlist" class="link">Liste des projets</a> </p> <dl> <tal:block tal:condition="project/getLabel"> <dt><strong change:translate="&modules.rbs.document.project.Label;" /></dt> <dd>${project/getLabel}</dd> </tal:block> ... <tal:block tal:condition="project/getAttachmentsArray"> <dt><strong>Pièces jointes</strong></dt> <dd> <ul class="normal"> <li tal:repeat="attachement project/getAttachmentsArray"> <a change:link="document attachement">${attachement/getLabel}</a> </li> </ul> </dd> </tal:block> </dl> <ul class="actionmenu"> <li> <a change:actionlink="" action="editForm" projectId="${project/getId}">Editer <em>${project/getLabel}</em></a> </li> <li> <a change:actionlink="" action="delete" projectId="${project/getId}" change:i18attr="onclick &modules.rbs.document.project.confirm-delete-js;">Effacer <em>${project/getLabel}</em></a> </li> </ul> ...
Le gabarit utilisé pour le formulaire de création/édition (Rbs-Block-Project-Form.all.all.html
). Du fait du nom de l'élément change:submit
(“save”), l'action du formulaire est la méthode executeSave
du bloc :
<tal:block change:loadhandler="generic_DocumentLoadHandler" args="project, projectId" /> <tal:block tal:define="global isNew php:!project or project.isNew()" /> <h2 change:h="" tal:condition="isNew">Créer un projet</h2> <h2 change:h="" tal:condition="not: isNew"><a change:link="document project">${project/getLabel}</a> : édition</h2> <p> <a change:actionlink="" block="rbs_projectlist" class="link">Liste des projets</a> </p> <form change:form="beanClass rbs_persistentdocument_project; beanName project"> <div change:errors=""></div> <tal change:field="name beanId" hidden="true" /> <ol> <li><input change:field="name label" /></li> <li><input change:field="name name" /></li> <li><input change:field="name description" /></li> <li><input change:field="name version" /></li> <li class="last"><input change:uploadfield="name attachments" label="Pièce(s) jointe(s) : " class="button" action="${php: isNew ? 'addForm' : 'editForm'}" /></li> </ol> <p> <input change:submit="" name="save" value="Enregistrer" class="button" /> </p> </form>
L'extension PHPTal change:actionlink
permet la création de liens vers une page contenant un bloc donné, en lui transmettant des paramètres. La page contenant le bloc est supposée être taguée avec le tag associé au bloc (contextual_website_website_modules_<NOM_BLOC>
).
Exemple : lien vers le bloc rbs_BlockProjectAction
, méthode executeEditForm
. Un paramètre supplémentaire, projectId
, est transmis avec.
<a change:actionlink="" block="rbs_project" action="editForm" projectId="${project/getId}">Editer <em>${project/getLabel}</em></a>
Le paramètre block
contextual_website_website_modules_<NOM_BLOC>
),Le paramètre action :
execute
appelée.Paramètres supplémentaires
<nomModule>Param[<param>]
) pour ne transmettre qu'au module.interface website_PageBlock
/** * Called when the block is inserted into a page content * @param website_persistentdocument_Page $page */ function onPageInsertion($page);
interface website_PageBlock
/** * Called when the block is removed from a page content * @param website_persistentdocument_Page $page */ function onPageRemoval($page);