Как правильно отображать частичные виды и загружать файлы JavaScript в AJAX с помощью Express/Jade?
Резюме
Я использую Express + Jade для своего веб-приложения, и я изо всех сил пытаюсь отобразить частичные представления для моей навигации AJAX.
У меня есть два разных вопроса, но они полностью связаны, поэтому я включил их в один пост.
Я думаю, это будет длинный пост, но я гарантирую, что это интересно, если вы уже боролись с теми же проблемами. Я был бы очень признателен, если бы кто-то нашел время, чтобы прочитать и предложить решение.
TL; DR: 2 вопроса
- Что такое самый чистый и самый быстрый способ визуализации фрагментов просмотров для навигации AJAX с помощью Express + Jade?
- Как загружать файлы JavaScript по отношению к каждому представлению?
Требования
- Мое веб-приложение должно быть совместимо с пользователями, отключенными
JavaScript
- Если JavaScript включен, только собственный контент страницы (а не
весь макет) должны быть отправлены с сервера на клиент
- Приложение должно быть быстрым и загружать как можно меньше байтов
Проблема №1: что я пробовал
Решение 1: наличие разных файлов для запросов AJAX & non-AJAX
Мой layout.jade:
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")
Мой page_full.jade:
extends layout.jade
block content
h1 Hey Welcome !
Мой page_ajax:
h1 Hey Welcome
И наконец, в router.js (Экспресс):
app.get("/page",function(req,res,next){
if (req.xhr) res.render("page_ajax.jade");
else res.render("page_full.jade");
});
Недостатки:
- Как вы, наверное, догадались, мне приходится каждый раз редактировать свои взгляды каждый раз, когда я
нужно что-то изменить. Довольно неприятно.
Решение 2: тот же метод с `include`
Мой layout.jade остается без изменений:
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")
Теперь мой page_full.jade:
extends layout.jade
block content
include page.jade
My page.jade содержит фактическое содержимое без какого-либо макета/блока/расширения/включения:
h1 Hey Welcome
И теперь у меня есть router.js (Экспресс):
app.get("/page",function(req,res,next){
if (req.xhr) res.render("page.jade");
else res.render("page_full.jade");
});
Преимущества:
- Теперь мой контент определяется только один раз, что лучше.
Недостатки:
- Мне еще нужны два файла для одной страницы.
- Я должен использовать ту же технику в Express на каждом маршруте. я
просто переместил мою проблему повторения кода из Jade в Express. Привязать.
Решение 3: то же, что и решение 2, но исправление проблемы повторения кода.
Используя технику Alex Ford, я мог бы определить свою собственную функцию render
в middleware.js:
app.use(function (req, res, next) {
res.renderView = function (viewName, opts) {
res.render(viewName + req.xhr ? null : '_full', opts);
next();
};
});
И затем измените router.js (Экспресс) на:
app.get("/page",function(req,res,next){
res.renderView("/page");
});
оставление остальных файлов без изменений.
<сильные > Преимущества
- Он решил проблему повторения кода
Недостатки
- Мне еще нужны два файла для одной страницы.
- Определение моего собственного метода
renderView
кажется грязным. В конце концов, я
ожидайте, что мой механизм шаблонов/фреймворк обработает это для меня.
Решение 4: Перемещение логики в Jade
Мне не нравится использовать два файла для одной страницы, так что, если я позволю Джейд решить, что делать вместо Express? На первый взгляд мне кажется очень неудобно, потому что я думаю, что механизм шаблонов должен не обрабатывать любую логику вообще. Но попробуй.
Во-первых, мне нужно передать переменную Jade, которая сообщит ей, какой именно запрос:
В middleware.js (экспресс)
app.use(function (req, res, next) {
res.locals.xhr = req.xhr;
});
Итак, теперь мой layout.jade будет таким же, как и раньше:
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")
И мой page.jade будет:
if (!locals.xhr)
extends layout.jade
block content
h1 Hey Welcome !
Великий, да? За исключением того, что это не сработает, потому что условные расширения невозможны в Джейд. Поэтому я могу перенести тест с page.jade на 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
и page.jade вернется к:
extends layout.jade
block content
h1 Hey Welcome !
Преимущества:
- Теперь у меня есть только один файл на странице
- Мне не нужно повторять тест
req.xhr
на каждом маршруте или в каждом
вид
Недостатки:
- В моем шаблоне есть логика. Не хорошо
Резюме
Это все методы, о которых я думал и пытался, но никто из них не убедил меня. Я делаю что-то неправильно? Существуют ли более чистые методы? Или я должен использовать другой механизм/структуру шаблона?
Проблема № 2
Что происходит (с любым из этих решений), если представление имеет свои собственные файлы JavaScript?
Например, используя решение # 4, если у меня есть две страницы, page_a.jade и page_b.jade, у которых есть свои собственные клиентские файлы JavaScript js/page_a.js и js/page_b.js, что происходит с ними при загрузке страниц в AJAX?
Во-первых, мне нужно определить блок extraJS
в 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
а затем page_a.jade будет:
extends layout.jade
block content
h1 Hey Welcome !
block extraJS
script(src="js/page_a.js")
Если я набрал localhost/page_a
в моей строке URL (не-AJAX-запрос), я бы получил скомпилированную версию:
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")
Это выглядит хорошо. Но что произойдет, если я перейду к page_b
с помощью моей навигации AJAX? Моя страница будет скомпилированной версией:
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 и js/page_b.js загружаются на одну страницу. Что произойдет, если есть конфликт (такое же имя переменной и т.д.)?
Кроме того, если я вернусь к localhost/page_a с помощью AJAX, у меня будет следующее:
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")
Тот же самый файл JavaScript (page_a.js) загружается дважды на одной странице! Будет ли это причиной конфликтов, двойного обжига каждого события?
Независимо от того, действительно ли это дело, я не считаю его чистым кодом.
Итак, вы можете сказать, что определенные JS файлы должны быть в моем block content
, чтобы они удалялись, когда я перехожу на другую страницу. Таким образом, мой 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
block extraJS
// Shared JS files go here
script(src="js/jquery.min.js")
else
block content
// Specific JS files go there
block extraJS
Правильно? Err.... Если я перейду к localhost/page_a
, я получу скомпилированную версию:
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")
Как вы могли заметить, js/page_a.js фактически загружается перед jQuery, поэтому он не будет работать, потому что jQuery еще не определен...
Поэтому я не знаю , что делать для этой проблемы. Я думал о обработке запросов script на стороне клиента, используя (например) jQuery.getScript()
, но клиенту нужно было бы узнать имя файла скриптов, посмотреть, загружены ли они, возможно, удалить их. Я не думаю, что это должно быть сделано на стороне клиента.
Как мне обрабатывать файлы JavaScript, загружаемые через AJAX? На стороне сервера используется другой механизм стратегии/шаблона? Клиентская сторона?
Если вы сделали это так далеко, вы настоящий герой, и я благодарен, но я был бы еще более благодарен, если бы вы могли дать мне несколько советов:)
Ответы
Ответ 1
Отличный вопрос. У меня нет идеального варианта, но я предложу вариант вашего решения №3, который мне нравится. Та же идея, что и решение №3, но переместите шаблон jade для _full файла в ваш код, так как он является шаблоном, а javascript может генерировать его, когда это необходимо для полной страницы. отказ от ответственности: непроверенный, но я смиренно предлагаю:
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();
};
});
И вы можете стать более умными с этой идеей, так как ваши сценарии усложняются, например, сохранение этого шаблона в файл с заполнителями для имен файлов.
Конечно, это еще не идеальное решение. Вы реализуете функции, которые действительно должны обрабатываться вашим механизмом шаблонов, так же как и ваше первоначальное возражение против решения №3. Если вы в конечном итоге написали более двух десятков строк кода для этого, попробуйте найти способ вставить эту функцию в Jade и отправить им запрос на перенос. Например, если ключевое слово jend "extends" приняло аргумент, который мог бы отключить расширение макета для запросов xhr...
Для вашей второй проблемы я не уверен, что какой-либо механизм шаблонов может вам помочь. Если вы используете навигацию ajax, вы не можете "разгрузить" page_a.js, когда навигация происходит с помощью магии шаблона задней части. Я думаю, вам нужно использовать традиционные методы изоляции javascript для этого (на стороне клиента). К вашим конкретным проблемам: Внедрите логику (и переменные), специфичную для страницы, в закрытие, для стартера и разумное сотрудничество между ними, когда это необходимо. Во-вторых, вам не нужно слишком беспокоиться о подключении обработчиков двойных событий. Предполагая, что основной контент очищается на ajax-навигации, а также те элементы, к которым привязаны обработчики событий, которые будут называть get reset (конечно), когда новый контент ajax загружается в DOM.
Ответ 2
Не уверен, действительно ли это поможет вам, но я решил ту же проблему с шаблоном Express и ejs.
моя папка:
- app.js
- просмотр /header.ejs
- просмотр /page.ejs
- просмотр /footer.ejs
my app.js
app.get('/page', function(req, res) {
if (req.xhr) {
res.render('page',{ajax:1});
} else {
res.render('page',{ajax:0});
}
});
my page.ejs
<% if (ajax == 0) { %> <% include header %> <% } %>
content
<% if (ajax == 0) { %> <% include footer %><% } %>
очень просто, но отлично работает.
Ответ 3
Существует несколько способов сделать то, что вы просили, но все они плохо архитектурны. Вы можете не заметить этого сейчас, но со временем это будет хуже и хуже.
Звучит странно на данный момент, но вы не хотите, чтобы передавал вам JS, CSS через AJAX.
Что вы хотите, так это получить разметку через AJAX и после этого сделать Javascript. Кроме того, вы хотите быть гибким и рациональным, делая это. Масштабируемость также важна.
Общий способ:
-
Предварительная загрузка всех файлов js
может использоваться на конкретной странице и обработчиках запросов AJAX. Используйте browserify, если вы боитесь конфликтов потенциальных имен или побочных эффектов script. Загрузите их асинхронно (script async), чтобы пользователь не ожидал.
-
Разработайте архитектуру, которая позволит вам в основном выполнить это на клиенте: loadPageAjax('page_a').then(...).catch(...)
. Дело в том, что, поскольку вы предварительно загрузили все модули JS, для запуска кода, связанного с AJAX-страницей, в вызывающем запросе, а не в вызываемом.
Итак, ваше Решение № 4 почти хорошо. Просто используйте его для извлечения HTML, но не скриптов.
Этот метод позволяет сделать вашу цель без создания какой-то неясной архитектуры, но с использованием единого надежного целевого ограничения.
Будем рады ответить на любые другие вопросы и убедить вас не делать то, что вы собираетесь делать.