Résumé
J'utilise Express + Jade pour mon application Web et j'ai du mal à rendre des vues partielles pour ma navigation AJAX.
J'ai en quelque sorte deux questions différentes, mais elles sont totalement liées, je les ai donc incluses dans le même message. Je suppose que ce sera un long post, mais je vous garantis que c'est intéressant si vous avez déjà eu du mal avec les mêmes problèmes. J'apprécierais beaucoup que quelqu'un prenne le temps de lire et de proposer une solution.
TL; DR: 2 questions
Exigences
Mon layout.jade est:
doctype html
html(lang="fr")
head
// Shared CSS files go here
link(type="text/css",rel="stylesheet",href="css/bootstrap.min.css")
body
div#main_content
block content
// Shared JS files go here
script(src="js/jquery.min.js")
Ma page_full.jade est:
extends layout.jade
block content
h1 Hey Welcome !
Ma page_ajax est:
h1 Hey Welcome
Et enfin dans router.js (Express):
app.get("/page",function(req,res,next){
if (req.xhr) res.render("page_ajax.jade");
else res.render("page_full.jade");
});
Inconvénients :
Mon layout.jade reste inchangé:
doctype html
html(lang="fr")
head
// Shared CSS files go here
link(type="text/css",rel="stylesheet",href="css/bootstrap.min.css")
body
div#main_content
block content
// Shared JS files go here
script(src="js/jquery.min.js")
Ma page_full.jade est maintenant:
extends layout.jade
block content
include page.jade
Ma page.jade contient le contenu réel sans aucune mise en page/bloc/étendre/inclure:
h1 Hey Welcome
Et j'ai maintenant dans router.js (Express):
app.get("/page",function(req,res,next){
if (req.xhr) res.render("page.jade");
else res.render("page_full.jade");
});
Avantages :
Inconvénients :
En utilisant la technique d'Alex Ford , je pouvais définir ma propre fonction render
dans middleware.js :
app.use(function (req, res, next) {
res.renderView = function (viewName, opts) {
res.render(viewName + req.xhr ? null : '_full', opts);
next();
};
});
Et puis changez router.js (Express) en:
app.get("/page",function(req,res,next){
res.renderView("/page");
});
laissant les autres fichiers inchangés.
Avantages
Inconvénients
renderView
semble un peu sale. Après tout, je m'attends à ce que mon moteur/framework de modèle gère cela pour moi.Je n'aime pas utiliser les fichiers deux pour la page un, alors que se passe-t-il si je laisse Jade décider quoi rendre au lieu d'Express? À première vue, cela me semble très inconfortable, car je pense que le moteur de modèle ne devrait pas gérer la logique du tout. Mais essayons.
Tout d'abord, je dois passer une variable à Jade qui lui dira de quel type de demande il s'agit:
Dans middleware.js (Express)
app.use(function (req, res, next) {
res.locals.xhr = req.xhr;
});
Alors maintenant, mon layout.jade serait le même qu'avant:
doctype html
html(lang="fr")
head
// Shared CSS files go here
link(type="text/css",rel="stylesheet",href="css/bootstrap.min.css")
body
div#main_content
block content
// Shared JS files go here
script(src="js/jquery.min.js")
Et ma page.jade serait:
if (!locals.xhr)
extends layout.jade
block content
h1 Hey Welcome !
Super hein? Sauf que cela ne fonctionnera pas car les extensions conditionnelles sont impossibles dans Jade. J'ai donc pu déplacer le test de page.jade vers layout.jade :
if (!locals.xhr)
doctype html
html(lang="fr")
head
// Shared CSS files go here
link(type="text/css",rel="stylesheet",href="css/bootstrap.min.css")
body
div#main_content
block content
// Shared JS files go here
script(src="js/jquery.min.js")
else
block content
et page.jade reviendrait à:
extends layout.jade
block content
h1 Hey Welcome !
Avantages :
req.xhr
Dans chaque itinéraire ou dans chaque vueInconvénients :
Ce sont toutes des techniques auxquelles j'ai pensé et essayé, mais aucune ne m'a vraiment convaincu. Est-ce que je fais quelque chose de mal ? Existe-t-il des techniques plus propres? Ou dois-je utiliser un autre moteur/framework de modèle?
Que se passe-t-il (avec l'une de ces solutions) si une vue possède ses propres fichiers JavaScript?
Par exemple, en utilisant la solution # 4, si j'ai deux pages, page_a.jade et page_b.jade qui ont tous deux leurs propres fichiers JavaScript côté client js/page_a.js et js/page_b.js , que leur arrive-t-il lorsque les pages sont chargées en AJAX?
Tout d'abord, je dois définir un bloc extraJS
dans layout.jade :
if (!locals.xhr)
doctype html
html(lang="fr")
head
// Shared CSS files go here
link(type="text/css",rel="stylesheet",href="css/bootstrap.min.css")
body
div#main_content
block content
// Shared JS files go here
script(src="js/jquery.min.js")
// Specific JS files go there
block extraJS
else
block content
// Specific JS files go there
block extraJS
puis page_a.jade serait:
extends layout.jade
block content
h1 Hey Welcome !
block extraJS
script(src="js/page_a.js")
Si je tapais localhost/page_a
Dans ma barre d'URL (demande non AJAX), j'obtiendrais une version compilée de:
doctype html
html(lang="fr")
head
link(type="text/css",rel="stylesheet",href="css/bootstrap.min.css")
body
div#main_content
h1 Hey Welcome A !
script(src="js/jquery.min.js")
script(src="js/page_a.js")
Ça à l'air bon. Mais que se passerait-il si je passais maintenant à page_b
En utilisant ma navigation AJAX? Ma page serait une version compilée de:
doctype html
html(lang="fr")
head
link(type="text/css",rel="stylesheet",href="css/bootstrap.min.css")
body
div#main_content
h1 Hey Welcome B !
script(src="js/page_b.js")
script(src="js/jquery.min.js")
script(src="js/page_a.js")
js/page_a.js et js/page_b.js sont tous deux chargés sur la même page. Que se passe-t-il en cas de conflit (même nom de variable etc ...)? De plus, si je reviens à localhost/page_a en utilisant AJAX, j'aurais ceci:
doctype html
html(lang="fr")
head
link(type="text/css",rel="stylesheet",href="css/bootstrap.min.css")
body
div#main_content
h1 Hey Welcome B !
script(src="js/page_a.js")
script(src="js/jquery.min.js")
script(src="js/page_a.js")
Le même fichier JavaScript ( page_a.js ) est chargé deux fois sur la même page! Va-t-il provoquer des conflits, un double déclenchement de chaque événement? Que ce soit le cas ou non, je ne pense pas que ce soit du code propre.
Vous pourriez donc dire que des fichiers JS spécifiques devraient être dans mon block content
Afin qu'ils soient supprimés lorsque je vais sur une autre page. Ainsi, mon layout.jade devrait être:
if (!locals.xhr)
doctype html
html(lang="fr")
head
// Shared CSS files go here
link(type="text/css",rel="stylesheet",href="css/bootstrap.min.css")
body
div#main_content
block content
block extraJS
// Shared JS files go here
script(src="js/jquery.min.js")
else
block content
// Specific JS files go there
block extraJS
Droite ? Euh .... Si je vais à localhost/page_a
, Je vais obtenir une version compilée de:
doctype html
html(lang="fr")
head
link(type="text/css",rel="stylesheet",href="css/bootstrap.min.css")
body
div#main_content
h1 Hey Welcome A !
script(src="js/page_a.js")
script(src="js/jquery.min.js")
Comme vous l'avez peut-être remarqué, js/page_a.js est en fait chargé avant jQuery, donc ça ne marchera pas , car jQuery n'est pas encore défini ... Donc je ne sais pas quoi faire pour ce problème . J'ai pensé à gérer les requêtes de script côté client, en utilisant (par exemple) jQuery.getScript()
, mais le client devrait connaître le nom de fichier des scripts, voir s'ils sont déjà chargés, peut-être les supprimer. Je ne pense pas que cela devrait être fait côté client.
Comment dois-je gérer les fichiers JavaScript chargés via AJAX? Côté serveur en utilisant un moteur de stratégie/modèle différent? Côté client?
Si vous êtes arrivé jusqu'ici, vous êtes un vrai héros, et je vous en suis reconnaissant, mais je vous serais encore plus reconnaissant si vous pouviez me donner quelques conseils :)
Grande question. Je n'ai pas une option parfaite, mais je vous proposerai une variante de votre solution # 3 que j'aime. Même idée que la solution # 3 mais déplacez le modèle jade pour le fichier _full dans votre code, car il est passe-partout et javascript peut le générer en cas de besoin pour une page complète. Avertissement: non testé, mais je suggère humblement:
app.use(function (req, res, next) {
var template = "extends layout.jade\n\nblock content\n include ";
res.renderView = function (viewName, opts) {
if (req.xhr) {
var renderResult = jade.compile(template + viewName + ".jade\n", opts);
res.send(renderResult);
} else {
res.render(viewName, opts);
}
next();
};
});
Et vous pouvez devenir plus intelligent avec cette idée à mesure que vos scénarios deviennent plus compliqués, par exemple en enregistrant ce modèle dans un fichier avec des espaces réservés pour les noms de fichiers.
Bien sûr, ce n'est pas encore une solution parfaite. Vous implémentez des fonctionnalités qui devraient vraiment être gérées par votre moteur de modèle, de la même manière que votre objection d'origine à la solution # 3. Si vous finissez par écrire plus de quelques dizaines de lignes de code pour cela, essayez de trouver un moyen d'intégrer la fonctionnalité dans Jade et de leur envoyer une demande d'extraction. Par exemple, si le mot clé jade "extend" a pris un argument qui pourrait désactiver l'extension de la mise en page pour les requêtes xhr ...
Pour votre deuxième problème, je ne suis pas sûr qu'un moteur de modèle puisse vous aider. Si vous utilisez la navigation ajax, vous ne pouvez pas très bien "décharger" page_a.js lorsque la navigation s'effectue via une magie de modèle d'arrière-plan. Je pense que vous devez utiliser des techniques d'isolation javascript traditionnelles pour cela (côté client). À vos préoccupations spécifiques: implémentez la logique (et les variables) spécifiques à la page dans les fermetures, pour commencer, et établissez une coopération judicieuse entre elles si nécessaire. Deuxièmement, vous n'avez pas à vous soucier trop de connecter des gestionnaires d'événements doubles. En supposant que le contenu principal est effacé lors de la navigation ajax et que ce sont également les éléments auxquels les gestionnaires d'événements ont été attachés qui appellent get reset (bien sûr) lorsque le nouveau contenu ajax est chargé dans le DOM.
Je ne sais pas si cela va vraiment vous aider, mais j'ai résolu le même problème avec Express et le modèle ejs.
mon dossier:
mon app.js
app.get('/page', function(req, res) {
if (req.xhr) {
res.render('page',{ajax:1});
} else {
res.render('page',{ajax:0});
}
});
ma page.ejs
<% if (ajax == 0) { %> <% include header %> <% } %>
content
<% if (ajax == 0) { %> <% include footer %><% } %>
très simple mais fonctionne parfaitement.
Il existe plusieurs façons de faire ce que vous avez demandé, mais elles sont toutes mauvaises sur le plan architectural. Vous ne le remarquerez peut-être pas maintenant, mais ce sera de pire en pire avec le temps.
Cela semble bizarre à ce stade, mais vous ne voulez pas vraiment vous transmettre JS, CSS via AJAX.
Ce que vous voulez, c'est pouvoir obtenir le balisage via AJAX et faire du Javascript après cela. De plus, vous voulez être flexible et rationnel en faisant cela. L'évolutivité est également importante.
La voie commune est:
Préchargez tous les fichiers js
pourraient être utilisés sur la page particulière et ses gestionnaires de requêtes AJAX. Utilisez browserify si vous avez peur des conflits de noms potentiels ou du script Chargez-les de manière asynchrone ( script async ) pour ne pas faire attendre l'utilisateur.
Développez une architecture qui vous permet de faire, fondamentalement, ceci chez le client: loadPageAjax('page_a').then(...).catch(...)
. Le point est, puisque vous avez pré-téléchargé tous les modules JS, de vous exécuter le code lié à la page AJAX à l'appelant de la demande et non à l'appelé .
Donc, votre Solution # 4 est presque bonne. Utilisez-le simplement pour récupérer le code HTML, mais pas les scripts.
Cette technique permet d'atteindre votre objectif sans construire une architecture obscure, mais en utilisant la seule méthode robuste à objectif limité.
Sera heureux de répondre à toutes vos questions et de vous convaincre de ne pas faire ce que vous allez faire.