Что такое чистый шаблон для хранения всего JavaScript в нижней части моей страницы? - PullRequest
18 голосов
/ 15 февраля 2012

У нас есть вложенный макет для наших различных страниц. Например:

Master.cshtml

<!DOCTYPE html>
<html>
   <head>...</head>
   <body>@RenderBody()<body>
</html>

Question.cshtml

<div>
  ... lot of stuff ...
  @Html.Partial("Voting", Model.Votes)
</div>
<script type="text/javascript">
  ... some javascript ..
</script>

Voting.cshtml

<div>
  ... lot of stuff ...
</div>
<script type="text/javascript">
  ... some javascript ..
</script>

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

Есть ли способ, которым я могу определить магическую директиву во вложенных партиалах, которая может привести к тому, что различные теги сценария будут отображаться в порядке внизу страницы?

Например, могу ли я создать магический помощник, который захватывает все блоки js, а затем получить макет верхнего уровня для его рендеринга:

Voting.cshtml

<div>
  ... lot of stuff ...
</div>
@appendJSToFooter{
   <script type="text/javascript">
     ... some javascript ..
   </script>
}

Ответы [ 11 ]

3 голосов
/ 17 февраля 2012

Примерно год назад я придумал относительно простое решение этой проблемы, создав помощника для регистрации скриптов в ViewContext.TempData. Моя первоначальная реализация (которую я переосмысливаю) просто выводит ссылки на различные ссылочные скрипты. Не идеально, но вот пошаговое описание моей текущей реализации.

На части я регистрирую связанный файл скрипта по имени:

@Html.RegisterScript("BnjMatchyMatchy")

На главной странице я затем вызываю метод для повторения зарегистрированных сценариев:

@Html.RenderRegisteredScripts()

Это текущий помощник:

public static class JavaScriptHelper
{
    private const string JAVASCRIPTKEY = "js";

    public static void RegisterScript(this HtmlHelper helper, string script)
    {
        var jScripts = helper.ViewContext.TempData[JAVASCRIPTKEY]
            as IList<string>; // TODO should probably be an IOrderedEnumerable

        if (jScripts == null)
        {
            jScripts = new List<string>();
        }

        if (!jScripts.Contains(script))
        {
            jScripts.Add(script);
        }

        helper.ViewContext.TempData[JAVASCRIPTKEY] = jScripts;
    }

    public static MvcHtmlString RenderRegisteredScripts(this HtmlHelper helper)
    {
        var jScripts = helper.ViewContext.TempData[JAVASCRIPTKEY]
            as IEnumerable<string>;

        var result = String.Empty;

        if (jScripts != null)
        {
            var root = UrlHelper.GenerateContentUrl("~/scripts/partials/",
                    helper.ViewContext.HttpContext);

            result = jScripts.Aggregate("", (acc, fileName) =>
                String.Format("<script src=\"{0}{1}.js\" " +
                    "type=\"text/javascript\"></script>\r\n", root, fileName));
        }

        return MvcHtmlString.Create(result);
    }
}

Как указано моим TODO (я должен обойти это), вы можете легко изменить это, чтобы использовать IOrderedEnumerable для гарантии заказа.

Как я уже сказал, не идеально, и вывод набора тегов script src определенно создает некоторые проблемы. Я скрывался, когда твоя дискуссия о налоге на jQuery закончилась с Стивом Соудерсом , Переполнением стека , Twitter и твоим блогом, В любом случае, это вдохновило меня на то, чтобы переработать этого помощника, чтобы он читал содержимое файлов сценариев и затем помещал их на визуализированную страницу в собственных тегах script, а не в тегах ссылок. Это изменение должно помочь ускорить рендеринг страницы.

1 голос
/ 02 июля 2012

Я понимаю, что этот корабль в значительной степени отплыл, но, поскольку у меня есть какое-то решение (хотя и не идеальное), я решил поделиться им.

Я написал сообщение в блоге о подходах к отрисовке скриптов.что я использую.В этом я упомянул библиотеку, написанную Майклом Дж. Райаном, которую я настроил для своих собственных целей.

Используя это, можно создать скрипт в любом месте, используя что-то вроде этого:

@Html.AddClientScriptBlock("A script", @"

    $(function() {
        alert('Here');
    });

")

А затем активируйте его вывод на странице макета, используя этот вызов непосредственно перед закрывающим тегом body:

@Html.ClientScriptBlocks()

При использовании этого подхода в Visual Studio нет смысла осмысления.Если честно, я не советую использовать эту технику;Я бы предпочел иметь отдельные JS-файлы для точек отладки и использовать атрибуты данных HTML для управления любым динамическим поведением.Но если это будет полезно, я подумаю, что поделюсь этим.Caveat emptor и т. Д.

Вот список соответствующих ссылок:

  1. Мой пост в блоге
  2. Сообщение в блоге Майкла Дж. Райана
  3. Мой помощник на GitHub
1 голос
/ 28 февраля 2012

Если у вас есть ситуация, когда вы не можете улучшить существующую структуру (т. Е. У вас есть и MVC, и старые страницы WebForms, теги javascript включены повсюду и т. Д.), Вы можете взглянуть на некоторые Я работаю над модулем HTTP, который выполняет постобработку на выходе, так же, как mod_pagespeed в Apache. Все еще первые дни, хотя.

https://github.com/Teun/ResourceCombinator

Если вы по-прежнему можете включать сценарии заранее определенным образом, это, вероятно, лучше.

Редактировать: проект, упомянутый Сэмом (см. Комментарии), действительно был в значительной степени частично совпадающим и более зрелым. Я удалил проект ResourceCombinator из GitHub.

1 голос
/ 20 февраля 2012

Это немного странно, но если ваша цель состоит в том, чтобы повлиять на минимальные изменения в существующих представлениях (помимо перемещения визуализированных сценариев), то, как я делал это в прошлом (в частности, в веб-формах, но также применимо к MVC).) должен был переопределить TextWriter с тем, который толкает сценарии к основанию.

По сути, вы просто пишете реализацию TextWriter и подключаете ее к базовой странице MVC, которая ищет <script src=" и записывает имя файла во внутренний Queue, а затем, когда он начинает получать Write требует </body>, он отображает все, что встроено в Queue.Это можно сделать с помощью регулярных выражений, но это, вероятно, довольно медленно.В этой статье показан пример TextWriter для перемещения ViewState, но тот же принцип должен применяться.

Чтобы переопределить зависимости, я затем определил файлы сценариев и зависимые файлы сценариев в моей сети..config, аналогично этому в ситуациях, когда мне нужно было переопределить порядок:

<scriptWriter>
    <add file="~/scripts/jquery.ui.js">
        <add dependency="~/scripts/jquery.js" />
    </add>
</scriptWriter>

Отказ от ответственности Как я уже сказал, это хакерство, лучшее решение было бы использовать какой-то CommonJS /AMD, как синтаксис в ваших частичных (@Script.Require("~/scripts/jquery-ui.js")), в основном вы могли бы написать функцию, которая, если главная страница / страница макета указывает свою регистрацию сценария захвата, она может прослушивать все дочерние регистрации, в противном случае она может просто выводить inline, так что не будетбольно просто использовать его везде.Конечно, это может нарушить intellisense.

Предполагая, что некоторый код в вашем мастере, например:

@using(Script.Capture()) {
    @RenderBody()

    @Html.Partial("Partial")
}

И часть:

@Script.Require("~/scripts/jquery-ui.js")

Тогда вы можете просто закодироватьчто-то вроде этого, чтобы справиться с этим:

public class ScriptHelper : IDisposable
{
    bool _capturing = false;
    Queue<string> _list = new Queue<string>();
    readonly ViewContext _ctx;


    public ScriptHelper Capture()
    {
        _capturing = true;
        return this;
    }

    public IHtmlString Require(string scriptFile)
    {
        _list.Enqueue(scriptFile);

        if (!_capturing)
        {
            return Render();
        }

        return new HtmlString(String.Empty);
    }

    public IHtmlString Render()
    {
        IHtmlString scriptTags;

        //TODO: handle dependencies, order scripts, remove duplicates

        _list.Clear();

        return scriptTags;
    }

    public void Dispose()
    {
        _capturing = false;

        _ctx.Writer.Write(Render().ToHtmlString());
    }
}

Вам может понадобиться сделать Queue ThreadStatic или использовать HttpContext или что-то еще, но я думаю, что это дает общую идею.

1 голос
/ 15 февраля 2012

Вместо того чтобы строить какую-либо серверную инфраструктуру для решения проблем на стороне клиента, посмотрите на мой ответ на другой вопрос: https://stackoverflow.com/a/9198526/144604.

С RequireJS, http://requirejs.org, ваши сценарии не будутобязательно находиться внизу страницы, но они будут загружаться асинхронно, что очень поможет с производительностью.Рендеринг страницы не будет остановлен во время выполнения скриптов.Он также способствует написанию Javascript в виде множества небольших модулей, а затем предоставляет инструмент развертывания для объединения и минимизации их при публикации сайта.

1 голос
/ 15 февраля 2012

Можете ли вы определить раздел внизу элемента body в вашем файле Master.cshtml следующим образом:

@RenderSection("Footer", required: false)

Затем в отдельных .cshtml файлах вы можете использовать следующее:

@section Footer {
    <script type="text/javascript">
    ...
    </script>
}
0 голосов
/ 25 июля 2015

А как насчет HTML-фильтра ответов, который изменяет ваш окончательный HTML-файл? Найдите все теги сценария перед определенным местом и вставьте их в конец тела. Он также работает для улучшения рендеринга таблиц данных, помечая их изначально скрытыми и заставляя js отображать их.

Никакого вмешательства или изменений в реальном коде не требуется.

0 голосов
/ 24 марта 2012

Сэм,

Я написал Portal по этой самой причине: http://nuget.org/packages/Portal Вы в основном помещаете

@Html.PortalOut() 

в свой мастер / макет и добавляете HTML-код изпредставления или частичные представления, подобные этому

@Html.PortalIn(@<text> $(function() { alert('Hi'); }); </text>)

Вы можете использовать несколько «порталов», указав клавишу ввода и вывода.Прямо сейчас он добавляет код в том порядке, в котором он поступает (сначала отображаются частичные, сверху вниз).Если вы находитесь в одном и том же виде, вам приходится иметь дело с ограничением сверху вниз, то есть вы не можете иметь «выходной» портал перед «входным», но это не проблема при отправке из представлений в макет.В файле Portal.cs есть и другие примеры.

0 голосов
/ 28 февраля 2012

Мой скромный вариант - оставить такие вещи профессионалам :-) Взгляните на ControlJS , библиотеку для загрузки ваших внешних файлов javascript без прерывания обработки страницы (async, defer, on спрос и т. д.). С ControlJS не имеет значения, куда вы поместите код загрузки, все они будут загружаться в конце или по требованию.

Эта презентация Стива Соудерса, автора библиотеки, дает хороший обзор проблемы (и решения).

0 голосов
/ 17 февраля 2012

Мы используем метод ScriptRegistrar из библиотеки Telep's (GPL с открытым исходным кодом) Asp.Net MVC.

Просто включите ваш javascript на любые страницы просмотра / частичного использования и т. Д., Как показано ниже. Библиотека Telerik обрабатывает все это в нижней части конечной страницы.

<% 
Html.Telerik().ScriptRegistrar()
             .Scripts(scripts => scripts.AddSharedGroup('lightbox')
             .OnDocumentReady(() => { %>
   <%-- LIGHTBOX  --%>
   $('.images a').lightBox();
<% });%>

Он также выглядит после группировки нескольких css и js вместе в один запрос.

http://www.telerik.com/products/aspnet-mvc.aspx

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...