Подтвердить что ты не робот

Как правильно отображать частичные виды и загружать файлы 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? На стороне сервера используется другой механизм стратегии/шаблона? Клиентская сторона?

Если вы сделали это так далеко, вы настоящий герой, и я благодарен, но я был бы еще более благодарен, если бы вы могли дать мне несколько советов:)

4b9b3361

Ответ 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, но не скриптов.

Этот метод позволяет сделать вашу цель без создания какой-то неясной архитектуры, но с использованием единого надежного целевого ограничения.

Будем рады ответить на любые другие вопросы и убедить вас не делать то, что вы собираетесь делать.