Клиентские template 1

Article-8_Small

disclaimer: статья делает обзор  преобразования JSON данных в html на клиентской стороне ( browser ) и раскрывает детали  работы template в Incoding Framework ( поиск, формирования, local storage и подключение своих engine ). Примеры из статьи доступны на GitHub

Чем плох серверный ?

Перед тем как ответить на этот вопрос, надо изучить что же предлагает asp.net mvc. Razor  – это серверный template, который идет по умолчанию в asp.net mvc, он обладает огромным функционалом и умеет выполнять C# код, который объявлен в рамках cshtml файла, поэтому в нем нет проблем, так в чем же дело? Когда возвращаемый результат для Action строится с помощью формирования ( View or PartialView ) html на сервере, то будут не “чистые” данные, а уже готовый контент, что ведет к следующим проблемам:

  • Нельзя повторно использовать Action при разработке мобильного приложения или стороннего клиента ( API )
  • Объем трафика, который используется для передачи данных, увеличивается из-за громоздкого html контента по сравнению с компактными JSON данными
  • На формирование расходуются ресурсы сервера, что можно избежать делегировав клиенту ( их больше, так что не жалко )

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

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

Решение есть !

Как бы не был хорош Razor, но современные приложения требуют, чтобы данные возвращались в виде JSON или xml, поэтому построения html на сервере подходит не для всех сценариев. После  использования клиентские template engine в “чистом”  виде,  был составлен список требований, которые  реализованы в нашей “обертке”.

  • Типизация
  • Интеграция с IML
  • Тонкий template
  • Замена на  “горячую”
  • Что-то своё

Типизация

Magic string

Преимущество razorв том, что есть поддержка intellisense и инструменты для refactor ( Rename, Delete ), чтобы увидеть разницу по сравнению с клиентским,  рассмотрим пример

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

 примечание: пример template для handlerbars, где используется обычные строки и Visual Studio не может заранее вычислить какие поля будут. 
 важно: handlerbars и многие другие engine просто игнорируют поля которых нет, поэтому обнаружить проблему сложно из-за отсутствия ошибок

Если переименовать поле FirstName в Name через специальные инструменты для refactor, такие как R#, то это отразится и на View, но  handlerbars такое не поддерживает, потому что нет связи между кодом и template.

Для решение задачи был разработан builder ( построитель ), который  формирует разметку для выбранного engine ( handlebars, mustaches и т.д. )

 

ScriptTemplate vs Template


Incoding Framework имеет два способа создания template

  • ScriptTemplate – результирующая разметка помещается в тэг script с заданным Id

 примечание:  в качестве type надо указывать любой тип кроме javascript, чтобы браузер не исполнял вложенный код

  • Template – “чистая” результирующая разметка

примечание: данный способ нужен, когда template возвращает в качестве результата из Action ( в разделе интеграция с IML будет подробный обзор )

Синтаксис

Класс ITemplateSyntax содержит методы, которые используются для построение template, чтобы зарегистрировать его, надо добавить запись в IoC ( Bootstarpper.cs )

примечание: на данный момент из “коробки” идет Handlebars ( default by nuget  ) и Mustaches  реализации, но по их примерам можно написать своих TemplateSyntax. 

  • ForEach  – цикл по коллекции ( razor аналог @foreach(var item in Model) {} )
    • по основной ( данные которые пришли в response )

    • по вложенной

  • NotEach –выводит содержимое, если нет данных ( razor аналог @if(Model.Count == 0) { } )

примечание: можно использовать совместно с ForEach

  • For  – выводит содержимое указанного поля ( razor аналог @Model.Title )

  •  ForRaw – выводит содержимое указанного поля без кодировки HTML ( razor аналог Html.Raw(Model.Title) )

  • Is – отображать, если поле True или NOT NULL ( razor  аналог @if(Is != null || Is){} )

  • Not – отображать, если поле False или NULL ( razor аналог @if(Is == null || !Is) {} )

  • Inline – в зависимости от значения поля, отображает true content ( True или NOT NULL ) или false content ( False или NULL ) ( razor аналог @(Is ? “true-class” : “false-class”) )

примечание:  Is и Not используются для построения больших блоков, а Inline возвращает сразу контент

примечание: Inline имеет аналоги IsInline, NotInLine, которые покрывают только одну часть условия.

Примеры

    • Установить класс red, если true

    •   Скрыть элемент, если true, иначе показать

    •  Вывести заголовок, если true

 

  • Up – подняться по иерархии вверх

примечание: метод необходим когда надо получить значение ( условие ) поля, но в рамках внутреннего ForEach.

  • А теперь все вместе

 примечание: пример доступен для скачивания с GitHub

 

Интеграция с IML

IML имеет методы ( AjaxGet, Submit, AjaxPost ), чтобы получить данные, которые потом можно вставить через Insert. Данные могут быть html контентом или json объекты. Для того, чтобы вставить json объекты используется template, путь к которому указывается с помощью Selector

примечание: начиная с версии 1.2, лучше использовать методы WithTemplateById или WithTemplateByUrl

  • WithTemplateById – найти dom элемент по Id, который содержит разметку template

важно: для построения template в данном случае надо использовать ScriptTemplate(tmplId)

 

sample



  • WithTemplateByUrl – загрузить разметку по ajax

Sample

Код controller

Код View Template

 важно: при построение template для ajax надо использовать метод Template в место ScriptTemplate

Код View IML

Каждому template свой  Action ?

Чтобы не дублировать код в action есть 2 решения:

  • Общий action

 примечание: можно построить extensions для Url, чтобы добавить проверку пути через ReSharper annotation 

 примечание: изначально MVD позиционировался в качестве универсального загрузчика template

Id vs Url

Изначально мы использовали  dom элемент ( script ) в качестве хранения разметки template, но постепенно перешли на загрузку по ajax, которая имеет следующие плюсы:

  • Повторно использовать template на разных View

примечание: для Id это реализовалось вынося template в Layout ( иной master page )

  • Разметка вынесена из View

примечание:  для Id это реализовалось через partial view

  • Lazy load ( template подгружается по требованию )

примечание: особенно актуально при использовании табов

Тонкий template

Мы выбирали engine, которые поддерживают  logic less template , такие как Mustaches, Handlerbars, потому что этот подход позволяет упросить View, переместив сложную логику на сервер, где проще “бороться со сложностью”.

Для наглядности задача, которую решим “обычным” способом и при помощи logic less

  • Обычный

  • Logic Less

В первом случае мы вычисляем значение во View, а в logic less мы заранее вычисляем выражение на сервере, что дает следующие преимущества:

  • Повторно использовать в других сценариях
  • Можно покрыть Unit Test
  • Меньше кода в разметки View

Преимущества logic less проявляются  при увеличении сложности задач, потому что расширять и поддерживать условия проще на серверной стороне, чем во View

Замена на “горячую”

Задача появилась, после того, как были обнаружены проблемы с mustaches, который притормаживал в ie 8 и ниже, а также имел проблемы при вставки больших объемов данных ( свыше 30 записей ). Поскольку реализация mustaches использовалась в ряде проектов, то выбор engine должен быть простым, чтобы можно было использовать любой.

примечание: код описанный ниже, доступен на GitHub

Код JavaScript

 примечание: поскольку не все engine поддерживают pre compile, то можно вернуть tmpl без изменений

Код Layout

 примечание: код находится в layout, потому что должен быть до первого вызова Insert.WithTemplate

Код Template

 примечание: template построен на “чистом” jquery tmpl, без использования ITemplateSyntax<T>, но можно написать реализацию и зарегистрировать её в IoC

Код IMl

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

 

Что-то своё

Быстрее, ещё быстрее !!

Для первых версий framework, мы хранили template в dom элементах ( script ), но такой способ не позволял построить lazy load, поэтому далее мы перешли на ajax загрузку, которая имела другие проблемы:

  • Если на одной странице загружаются сразу много элементов, то заполняется пул запросов браузера

примечание: это частично решается с помощью cache, но запрос все равно будет, хоть и со статусом 304

  • Отсутствие pre-compile для  engine

примечание: особенно актуально при  большом количестве вставляемых данных ( 30 и более объектов ) 

Раз и на всегда

Решение было найдено в Local Storage, который позволяет сохранять template в browser и далее все время использовать его. Всегда будет один template ? – чтобы ответить на этот вопрос, давайте посмотрим на код (псевдокод) работы этого механизма.

  • Строка 1 – проверяем наличие template в local storage

примечание: в качестве ключа выступает selector и версия ( о версии подробней ниже )

  • Строка 2 –  возвращаем  template из local storage
  • Строка 4 –  получаем значение selector
  • Строка 5 – добавляем template в local storage

примечание: перед добавлением делаем pre compile

  • Строка 6 – возвращаем template из local storage

А если я изменю разметку template, но ключ останется тем же  ? – для того, чтобы решить эту проблему, мы добавили версионность на глобальном уровне. Чтобы указать текущий версию всех template надо установить через JavaScript поле TemplateFactory.Version

примечание: код надо выполнить до вызова Insert.WithTemplate, поэтому лучше всего размещать в Layout

На примере я устанавливаю Guid, что гарантирует новую версию после полной ( F5 ) перегрузки страницы, но такое поведение актуально только для процесса разработки, а когда код будет выложен в production, надо зафиксировать версию.

Наша политика версионность

  • Debug    – в процессе разработки частота изменения разметки в template высокая, поэтому версия обновляется максимально часто ( мы используем Guid, чтобы поддерживать уникальность )
  • Release – после того, как проект выкладывается на сервер, надо установить фиксированную версию
Layout

Current Version

 примечание: в качестве фиксированной версии, мы используем TeamCity build.numberВ следующих статья мы подробно рассмотрим наш подход к continuous integration ( TeamCity, rake ) 

Один на всех и все на одного

При построение template, надо указывать только тип модели, но при этом не учитывается коллекция  это или один объект. Некоторые template engine требуют указывать, что конкретно будет – коллекция или один объект ( например handlebars  {{#with data}} или {{#each autors}}  ), поэтому мы решили убрать данное условие и при построение template не учитывать сколько будет элементов. Благодаря этой особенности нам не потребовалось добавлять метод With ( который поддерживают не все engine ).

пример

Коллекция объектов

 Один объект

Общий template

 

Клиент это не сервер или наши грабли

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

GUID – на view его используют для того, чтобы задать уникальное имя элементу в рамках страниц, но в случае с клиентским template это имеет подводные камни.

Условие: каждый тэг li должен иметь уникальный Id

Код содержит ошибку, потому что переменная uniqueId будет вычислена один раз на сервере, а обход коллекции будет проходить на клиенте. Для того, чтобы проверить это, надо посмотреть результирующую разметку.

Чтобы код правильно работал, надо перенести логику вычисления guid в Model

 Заключение

Мы постоянно развиваем каждый компонент Incoding Framework, привнося в него с каждой версией новые возможности и “полируем” старые и конечно template не исключение, чтобы в этом убедиться, можно посмотреть наш BugTracker. Все что я описал в статье – это проверенные на личном опыте конструкции и практики, которые позволяют разрабатывать кросс-платформенные приложения изначально, а не перестраивать архитектуру, когда это будет стоит очень дорого.

Vlad Kopachinsky

I am a Senior developer at Incoding Software in Russian Federation. I created the Incoding Framework for rapid development

More Posts - Website - Twitter - Facebook - LinkedIn

One comment on “Клиентские template

  1. Pingback: Derrick

Leave a Reply