IML TODO

от автора

image
disclaimer: статья является ответом на критику ( которая обрушилась на хабре ), раскрывая потенциал IML на примере популярного приложения ToDo MVC.

Получил тумаков

Критика – это слово крайне мягкое по отношению к дискуссии, которая возникла к моей предыдущей статье, потому что это было больше похоже на избиение в котором были крайне не приятные комментарии ( на фото топ бредовых ), но также и объективные:

  • Код на AngularJs не фонтан — сложно опровергнуть, хотя все они были с официального сайта и популярного руководства
  • Слабые примеры – упор был на задачи, а не на сценарии, но соглашусь что более комплексное решение более полно раскрывает потенциал ( я предложил некоторые наши проекты, которые открыты на open source, но они остались без внимания )
  • Не знаешь AngularJs ? – по понятным причинам это очень сильно задело разработчиков AngularJs
  • Топик JS – это серьезная ошибка, потому что не используя asp.net mvc, сложно понять прелести типизированных TextBoxFor и других расширений.

Почему ToDo ?

В комментариях предложили попробовать реализовать “Todo MVC” в качестве доказательства возможностей IML и сейчас мы посмотрим что из этого получилось. Во первых demo версия, которая имеет одно отличите от тех, что представлены для js framework, в том, что в качестве хранилища используется не local storage, а база данных, а также исходный код, который мы будем разбирать далее в посте. В процессе реализации я строил всю логику ( расчеты подвала, скрытие элементов и т.д. ) на клиенте, хотя на реальных задачах, проще ( иногда необходимо ) обновлять “точечно” элементы, которые имея IML код, знают, как себя вычислить и отобразить.

Code review

Стиль повествования в этот раз будет не сравнение одного решения с другим (иначе объем материала будет большим ), а обзор кода, который получится при реализации приложения todo. Я упоминал выше о том, что в реализации IML присутствует и серверная часть, но в целях уравнивания решаемых задач для более объективного сравнения, сфокусируемся только на клиентской части.

Из чего состоит

Код был разделен на 3 View

  • Index — основная страница ( и по факту единственная для браузера )
  • Todo_List_Tmpl – шаблон для построения центрального списка
  • Todo_Footer_Tmpl – шаблон для построения подвала с показателями

image

Index ( состоит из трех элементов )

Форма для добавления TODO

@using (Html.When(JqueryBind.Submit)             .DoWithPreventDefault()             .Submit(options =>                    {        options.Url = Url.Dispatcher()                      .Push(new AddTodoCommand {  ClientId = Selector.Incoding.Cookie(CookieManager.ClientId) });                    })             .OnSuccess(dsl =>                            {                                dsl.WithId(containerId).Core().Trigger.Incoding();                                dsl.Self().Core().Form.Reset();                            })             .AsHtmlAttributes()             .ToBeginTag(Html, HtmlTag.Form)) {     @Html.TextBoxFor(r => r.Title, new { placeholder = "What needs to be done?", autofocus = "" }) } 

примечание: сразу ожидаю фразы в стиле “Да, ну это не серьезно, кода в разы больше, надо везде копировать, посмотри как люди делают !!!” — на что у меня есть аргумент в лице C# extensions, который позволяет оборачивать конструкции IML. Далее в статье будет приведены альтернативные варианты решения задач ( также repository на GibHub с переработанным кодом ) с применением C# extensions

Что к чему ?

  • When(JqueryBind.Submit) — указываем целевое событие
  • DoWithPreventDefault — поведение события ( отменяем обработчик браузера )
  • Submit — отправляем форму через ajax
    примечание: пару замечаний по представленной реализации:
    • Url куда отправляется форма задается в опциях ( а не через атрибут action у form )
    • ClientId можно вынести форму, как Hidden, который по InitIncoding проставит значение из Cookies, что бы вызывать Submit без параметров

  • OnSuccess — выполняем после удачного завершения submit
    • Trigger Incoding to containerId – запускаем весь IML код для элемента Container ( описание ниже )
      примечание: можно применять более одного When, что позволяет подвязаться на разные события ( с разным IML кодом ), поэтому trigger Incoding запускает все цепочки.
    • Form reset – сбрасываем значение элементов формы

  • AsHtmlAttributes — собираем IML код в удобный для asp.net mvc формат ( RouteValueDictionary )
  • ToBeginTag — упаковываем полученные атрибуты в тэг form ( принцип работы как Html.BeginForm )
    примечание: можно использовать Html.BeginForm(“action”,”controller”,Post,iml.AsHtmlAttributes())

Форма для добавления TODO ( альтернативный вариант )

@using (Html.Todo().BeginForm(setting =>                              {                     setting.TargetId = containerId;                     setting.Routes = new { ClientId = Selector.Incoding.Cookie(CookieManager.ClientId) };                              })) {     @Html.TextBoxFor(r => r.Title, new { placeholder = "What needs to be done?", autofocus = "" }) } 

примечание: кода стало меньше и что самое главное, теперь можно расширять ( валидация, redirect после submit и т.д. ) метод, под нужды конкретного проекта.

Под капотом

public class BeginFormSetting {     public string TargetId { get; set; }       public object Routes { get; set; } }   public BeginTag BeginForm(Action configure) {     var setting = new BeginFormSetting();     configure(setting);       var url = new UrlHelper(HttpContext.Current.Request.RequestContext);     return this.helper.When(JqueryBind.Submit)                .DoWithPreventDefault()                .Submit(options =>                            {                                options.Url = url.Dispatcher()                                                 .Push(setting.Routes);                            })                .OnSuccess(dsl =>                               {                                   dsl.WithId(setting.TargetId).Core().Trigger.Incoding();                                   dsl.Self().Core().Form.Reset();                               })                .AsHtmlAttributes()                .ToBeginTag(this.helper, HtmlTag.Form); } 

примечание: код знаком большинству asp.net mvc разработчиков, но стоит отметить то, что вместо “обычных” параметров, мы передаем анонимный метод, который принимает класс настроек.

Container

@(Html.When(JqueryBind.InitIncoding | JqueryBind.IncChangeUrl)       .Do()       .AjaxGet(Url.Dispatcher()                   .Query(new                          {                    ClientId = Selector.Incoding.Cookie(CookieManager.ClientId),                    Type = Selector.Incoding.HashQueryString(r => r.Type)                          })                   .AsJson())       .OnSuccess(dsl =>                      {                          string urlTmpl = Url.Dispatcher()                                              .Model(new GetTodoByClientQuery.Tmpl { FooterId = footerId })                                              .AsView("~/Views/Home/Todo_List_Tmpl.cshtml");                          dsl.Self().Core().Insert.WithTemplateByUrl(urlTmpl).Html();                          dsl.WithId(footerId).Core().Trigger.Incoding();                      })       .AsHtmlAttributes(new { id = containerId })       .ToDiv()) 

Что к чему ?

  • When(JqueryBind.InitIncoding | IncChangeUrl) — указываем целевые события
    • InitIncoding – срабатывает при первом появлении элемента на странице ( не важно ajax или обычно )
    • IncChangeUrl – срабатывает при изменение hash

  • Do — поведение события
  • AjaxGet — указываем url, на который будет выполнен ajax запрос
    • ClientId – получаем значение из cookies
    • Type – получаем значение из Hash Query String

  • OnSuccess — выполняем после удачного завершения AjaxGet
    • Insert data to self by template – вставляем полученные данные из запроса ( json ) через template ( Todo_List_Tmpl ниже ) в текущий элемент.
      примечание: template можно получить через любой доступный Selector, например раньше основным был Jquery.Id, но загрузка по ajax предпочтительней
    • Trigger incoding to footerId – запускаем весь IML код для элемента footer ( описание ниже )

  • AsHtmlAttributes — собираем IML код и задаем значение containerId ( guid ) атрибуту Id
    примечание: использование guid в качестве Id гарантирует уникальность элемента на странице, особенно актуально для single page application
  • ToDiv — упаковываем полученные атрибуты в тэг div
    примечание: ToDiv это C# extensions над RouteValueDictionary, поэтому без труда можно написать свой нужный вариант

Container ( альтернативный способ )

@Html.Todo().Container(setting =>              {                  setting.Id = containerId;                  setting.Url = Url.Dispatcher()                                   .Query(new                                                 {                                                         ClientId = Selector.Incoding.Cookie(CookieManager.ClientId),                                                         Type = Selector.Incoding.HashQueryString(r => r.Type)                                                 })                                   .AsJson();                  setting.Tmpl = Url.Dispatcher()                                    .Model(new GetTodoByClientQuery.Tmpl { FooterId = footerId })                                    .AsView("~/Views/Home/Todo_List_Tmpl.cshtml");                  setting.DependencyId = footerId;              }) 

примечание: если в будущем надо будет добавить block ui или другие действия, то теперь это можно делать централизованно

Под капотом

public class ContainerSetting {     public string Id { get; set; }       public string Url { get; set; }       public string Tmpl { get; set; }       public string DependencyId { get; set; } }   public MvcHtmlString Container(Action configure) {     var setting = new ContainerSetting();     configure(setting);       return helper.When(JqueryBind.InitIncoding | JqueryBind.IncChangeUrl)                  .Do()                  .AjaxGet(setting.Url)                  .OnSuccess(dsl =>                                 {                                     dsl.Self().Core().Insert.WithTemplateByUrl(setting.Tmpl).Html();                                     dsl.WithId(setting.DependencyId).Core().Trigger.Incoding();                                 })                  .AsHtmlAttributes(new { id = setting.Id })                  .ToDiv(); } 

Footer

@(Html.When(JqueryBind.None)       .Do()       .Direct(new FooterVm                         {                          AllCount = Selector.Jquery.Class("toggle").Length(),                          IsCompleted = Selector.Jquery.Class("toggle").Is(JqueryExpression.Checked),                          CompletedCount = Selector.Jquery.Class("toggle")                                                          .Expression(JqueryExpression.Checked)                                                          .Length(),                         }))       .OnSuccess(dsl =>                      {                          string urlTmpl = Url.Dispatcher()                                              .Model(new TodoFooterTmplVm                                                         {                                                                 ContainerId = containerId                                                         })                                              .AsView("~/Views/Home/Todo_Footer_Tmpl.cshtml");                          dsl.Self().Core().Insert.Prepare().WithTemplateByUrl(urlTmpl).Html();                      })       .AsHtmlAttributes(new { id = footerId })       .ToDiv()) 

  • When(JqueryBind.None) — указываем целевые события
    • None – When позволяет указать любое пользовательское событие, как строку “MySpecialEvent”, но практика показал, что для многих сценариев хватает одного.

  • Do — поведение события
  • Direct — можно рассматривать как action заглушка, который не выполняет действий, но может работать с данными
    • AllCount – получаем кол-во объектов с классом “toggle”
      примечание: можно воспользоваться расширением Method ( вместо Length ), чтобы вызвать любой jquery метод, а также написать C# extensions над JquerySelectorExtend
    • IsCompleted — проверяем на наличие отмеченных объектов с классом “toggle”
      примечание: кому не хватит возможностей готовых jquery selector, то можно воспользоватся Selector.Jquery.Custom(“your jquery selector”)
    • CompletedCount – получаем количество отмеченных объектов с классом “toggle”

  • OnSuccess — выполняем после удачного завершения AjaxGet
    • Insert prepare data to self by template — вставляем подготовленные ( prepare ) данные из Direct через template ( Todo_Footer_Tmpl ниже ) в текущий элемент

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

  • AsHtmlAttributes — собираем IML код
  • ToDiv — упаковываем полученные атрибуты в тэг div
Todo List Tmpl

Разметка шаблона для построения списка todo

@using (var template = Html.Incoding().Template()) {  <ul>   @using (var each = template.ForEach())     {     @using (each.Is(r => r.Active))      { @createCheckBox(true) }     @using (each.Not(r => r.Active))     { @createCheckBox(false) }       <li class="@each.IsInline(r=>r.Active,"completed")">       <label>@each.For(r=>r.Title)</label>     </li> </ul> } 

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

Что к чему ?

  • Html.Incoding().Template() – открываем контекст ( в рамках using ) построения template
  • template.ForEach() — начинаем перебор ( в рамках using ) элементов
  • using(each.Is(r=>r.Active)) — предыдущий вариант условий был в “одну линию”, но часто бывает что надо выполнить более сложные действия.
  • createCheckBox — анонимная C# функция для создания checkbox ( описание ниже )
  • each.IsInline(r=>r.Active,”completed”) — если поле Active true, тогда возвращаем “completed”
    примечание: также имеются IsNotLine и IsLine.
  • each.For(r => r.Title) – выводим значение поля Title
    примечание: все обращения к полям происходят на основе указанной модели ( да, я опять о типизации )

Другие элементы

Button del

@(Html.When(JqueryBind.Click)       .Do()       .AjaxPost(Url.Dispatcher().Push(new DeleteEntityByIdCommand                                  {                                          Id = each.For(r => r.Id),                                          AssemblyQualifiedName = typeof(Todo).AssemblyQualifiedName                                  }))       .OnBegin(r =>                    {                        r.WithSelf(s => s.Closest(HtmlTag.Li)).Core().JQuery.Manipulation.Remove();                        r.WithId(Model.FooterId).Core().Trigger.Incoding();                        r.WithId(toggleAllId).Core().Trigger.None();                    })       .AsHtmlAttributes(new { @class = "destroy" })       .ToButton("")) 

Что к чему ?

  • When(JqueryBind.Click) — указываем целевое событие
  • Do — поведение события
  • AjaxPost — указываем Url, на который делаем ajax запрос
    • Id — значение из Todo
    • AssemblyQualifiedName – получаем имя типа элемента ( или иной другой C# код )

  • OnBegin — выполняем до начала действия ( AjaxPost )
    примечание: конечно правильней будет использовать OnSuccess, потому что может произойти ошибка на сервере ( timeout или что-то другое ) и транзакция будет не полная из-за того, что OnBegin сработает до вызова AjaxPost, но примеры TodoMVC на js framework используют local storage ( который быстрее чем ajax ) и поэтому я не много схитрил, да бы не проигрывать в быстродействие
    • Remove closest LI — удаляем ближайший LI
    • Trigger incoding to footer id – запускаем весь IML код для элемента footer ( описание выше )
    • Trigger none to toggle all – запускаем IML код ( только цепочку None ) для элемента Toggle All ( описание ниже )

    примечание: если бы для обоих ( footer и toggle all ) нужно было вызвать одинаковый trigger, то можно использовать следующий вариант

    dsl.WithId(Model.FooterId, toggleAllId).Core().Trigger.Incoding(); 

  • AsHtmlAttributes — собираем IML код
  • ToButton — упаковываем полученные атрибуты в тэг button
    примечание: ToButton позволяет указать содержимое, но в данном случаи это не надо, потому что картинка устанавливает через CSS
Button Del ( альтернативный вариант )

@Html.Todo().Verb(setting =>        {            setting.Url = Url.Dispatcher().Push(new DeleteEntityByIdCommand                                                    {                                                            Id = each.For(r => r.Id),                                                            AssemblyQualifiedName = typeof(Todo).AssemblyQualifiedName                                                    });            setting.OnBegin = dsl =>                                  {                                      dsl.WithSelf(s => s.Closest(HtmlTag.Li)).Core().JQuery.Manipulation.Remove();                                      dsl.WithId(Model.FooterId).Core().Trigger.Incoding();                                      dsl.WithId(toggleAllId).Core().Trigger.None();                                  };            setting.Attr = new { @class = "destroy" };        }) 

примечание: OnBegin принимает Action, что позволяет легко масштабировать ваш extensions внедряя в него IML. ( далее будут ещё примеры )

Под капотом

 public class VerbSetting {     public string Url { get; set; }       public Action<IIncodingMetaLanguageCallbackBodyDsl> OnBegin { get; set; }       public Action<IIncodingMetaLanguageCallbackBodyDsl> OnSuccess { get; set; }       public object Attr { get; set; }       public string Content { get; set; } }  public MvcHtmlString Verb(Action<VerbSetting> configure) {     var setting = new VerbSetting();     configure(setting);       return this.helper.When(JqueryBind.Click)                .Do()                .AjaxPost(setting.Url)                .OnBegin(dsl =>                             {                                 if (setting.OnBegin != null)                                     setting.OnBegin(dsl);                             })                .OnSuccess(dsl =>                               {                                   if (setting.OnSuccess != null)                                       setting.OnSuccess(dsl);                               })                .AsHtmlAttributes(setting.Attr)                .ToButton(setting.Content); } 

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

Checkbox Completed

var createCheckBox = isValue => Html.When(JqueryBind.Change)                                     .Do()                                     .AjaxPost(Url.Dispatcher().Push(new ToggleTodoCommand                                                                  {                                                                      Id = each.For(r => r.Id)                                                                  }))                                     .OnBegin(dsl =>                                                   {                                                    dsl.WithSelf(r => r.Closest(HtmlTag.Li))                                                       .Behaviors(inDsl =>                                                                      {                       inDsl.Core().JQuery.Attributes.RemoveClass("completed");                       inDsl.Core().JQuery.Attributes.AddClass("completed")                                  .If(builder => builder.Is(() => Selector.Jquery.Self()));                                                                      });                                                       dsl.WithId(Model.FooterId).Core().Trigger.Incoding();                                                     dsl.WithId(toggleAllId).Core().Trigger.None();                                                    })                                      .AsHtmlAttributes(new {@class="toggle" })                                      .ToCheckBox(isValue); 

примечание: в рамках razor страницы можно использовать анонимные C# функции или Razor helper, что позволяет агрегировать однотипные задачи.

Что к чему ?

  • When(JqueryBind.Change) — указываем целевое событие
  • Do — поведение события
  • AjaxPost — указываем Url, на который делаем ajax запрос
    примечание: AjaxPost и AjaxGet это “именованная” версия Ajax, который имеет много дополнительных настроек
  • OnBegin — выполняем до начала действия ( AjaxPost )
    • Remove class on closest LI – удаляем класс “completed” у ближайшего LI
    • Add class on closest LI if self is true — добавляем класс “completed”

    примечание: пока в IML не реализована возможность else, но в версии 2.0 планируется

  • AsHtmlAttributes — собираем IML код, а также устанавливаем значение “toggle” атрибуту class
  • ToCheckBox — упаковываем полученные атрибуты в тэг input[type=checkbox]
Filter by type todo

@{     const string classSelected = "selected";     var createLi = (typeOfTodo,isFirst) => Html.When(JqueryBind.InitIncoding)                               .Do()                               .Direct()                               .OnSuccess(dsl =>                                                            {       var type = Selector.Incoding.HashQueryString(r => r.Type);         if (isFirst)       dsl.Self().Core().JQuery.Attributes.AddClass(classSelected).If(s => s.Is(() => type == ""));         dsl.Self().Core().JQuery.Attributes.AddClass(classSelected).If(s => s.Is(() => type == typeOfTodo.ToString()));                                                             })                                .When(JqueryBind.Click)                                .Do()                                .Direct()                                .OnSuccess(dsl =>                                                            {     dsl.WithSelf(r => r.Closest(HtmlTag.Ul).Find(HtmlTag.A)).Core().JQuery.Attributes.RemoveClass(classSelected);     dsl.Self().Core().JQuery.Attributes.AddClass(classSelected);                                                                                                               })                                 .AsHtmlAttributes(new { href = "#!".AppendToHashQueryString(new { Type = typeOfTodo }) })                                 .ToLink(typeOfTodo.ToString()); }    <li>  @createLi(GetTodoByClientQuery.TypeOfTodo.All,true)        </li>    <li>  @createLi(GetTodoByClientQuery.TypeOfTodo.Active,false)    </li>  <li>  @createLi(GetTodoByClientQuery.TypeOfTodo.Completed,false) </li> 

примечание: ещё один пример реализации анонимных функций в рамках razor view

Что к чему ?

  • When(JqueryBind.InitIncoding) — указываем целевое событие
  • Do — поведение события
  • Direct – ничего не выполняем
  • OnSuccess — выполняем после удачного завершения
    примечание: для Direct нет отличия между OnBegin или OnSuccess, но OnError и OnBreak работают, так же как и для остальных
    • var type – объявляем переменную, которую потом будем использовать в выражениях
    • add class to self if IsFirst is true And type is Empty – добавляем класс, если текущий элемент является первым и в type пустой
    • add class to self if type is current type – добавляем класс к текущему элементу если type равен аргументу typeOfTodo

  • When(JqueryBind.Click) — указываем целевое событие
  • Do — поведение события
    примечание: мы не отменяем поведение ссылки, потому что нам нужно, чтобы браузер обновил location
  • Direct — ничего не выполняем
    • remove class – удаляем класс selected у всех A, которые находятся в ближайшем UL
    • add class to self — добавляем класс selected текущему элементу

  • AsHtmlAttributes — собираем IML код, а также устанавливаем атрибут href
Filter by type todo ( альтернативный способ )

<li>     @Html.Todo().LiHash(setting =>                             {                          setting.IsFirst = true;                          setting.SelectedClass = classSelected;                           setting.Type = GetTodoByClientQuery.TypeOfTodo.All;                             })                     </li> 

Безусловные плюсы !

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

  • Типизация – конечно каждый смотрит через свою призму на типизацию, кто-то считает что приходится писать больше кода ( это верно ), другим не хватает гибкости, которая присуща не типизированным языкам, но IML это прежде всего C#, поэтому те разработчики, которые его выбрали, я думаю по достоинству оценят этот плюс.
  • Мощные extensions – в статье я привел несколько, но на практике их на много больше, чтобы подкрепить свои слова приведу ещё пару:
    • Drop down
      @Html.For(r=>r.HcsId).DropDown(control =>                                 {                  control.Url = Url.Action("HealthCareSystems", "Shared");                  control.OnInit = dsl => dsl.Self().Core().Rap().DropDown();                  control.Attr(new { @class = "selectInput", style = "width:375px" });                                 }) 

      примечание: OnInit принимает Action, что позволяет легко масштабировать ваш extensions внедряя в него IML.

      Dialog

      @Html.ProjectName().OpenDialog(setting =>                          {                             setting.Url = Url.Dispatcher()                                              .Model<GroupEditProviderOrderCommand>()                                              .AsView("~/Views/ProviderOrder/Edit.cshtml");                             setting.Content = "Edit";                             setting.Options = options => { options.Title = "Edit Order"; };                          }) 

      примечание: для большей гибкости можно использовать Action в качестве поля, например setting.Options это Action.
      Список можно продолжать бесконечно, но основная идея в том, что IML позволяет выполнять любые задачи, а html extensions решает проблему с повторным использованием.

    • Ещё мощнее extensions
      • Grid — полностью построенный на IML ( в ближайшие время будет документация )
        @(Html.ProjectName()       .Grid<CTRPrintLogModel>()       .Columns(dsl =>                {           dsl.Template(@<text>                            <span>@item.For(r=>r.Comment)</span>                                  </text>)                                       .Title("Comment");            const string classVerticalTop = "vertical_top";          dsl.Bound(r => r.Time).Title("Time").HtmlAttributes(new { @class = classVerticalTop });          dsl.Bound(r => r.Version).Title("Type").HtmlAttributes(new { @class = classVerticalTop });                             dsl.Bound(r => r.PrintDate).Title("Date");          dsl.Bound(r => r.Comment).Raw();                })       .AjaxGet(Url.RootAction("GetCTRPrintLogModel", "CTR"))) 

      • Tabs
        @(Html.Rap()       .Tabs<Enums.CarePlanTabs>()       .Items(dsl =>              {                          dsl.AddTab(Url.Action("RedFlags", "PatientRedFlag"), Enums.CarePlanTabs.RedFlags);                  dsl.AddTab(Url.Action("Goals", "IncGoal"), Enums.CarePlanTabs.SelfCareGoals);                  dsl.AddTab(Url.Action("Index", "IncAppointment"), Enums.CarePlanTabs.Appointments);                      })) 

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

    • Работа с hash – в этой статье была рассмотрена только на уровне IncChangeUrl, но у нас есть:
      • Hash.Fetch – проставляет значения из hash в элементы ( sandbox )
      • Hash.Insert/Update — проставляет значения в hash из элементов
      • Hash.Manipulate – позволяет тонко ( set/ remove by key ) настроить текущий hash
      • AjaxHash — это аналог Submit, но не для form, а для Hash.

    • Работа с Insert – для реализации TODO мне не пришлось применять, но в реальных проектах повсеместно
      • Insert Generic– все примеры выше были построенные на одной модели, но часто бывают сценарии, когда полученные данные являются “контейнером”, для этих целей в Insert есть возможность указывать с какой частью модели мы работаем через For, а также template для каждой свой.
        Html.When(JqueryBind.InitIncoding)     .Do()     .AjaxGet(Url.Action("FetchComplex", "Data"))     .OnSuccess(dsl =>                    {  dsl.WithId(newsDivId).Core().Insert.For<ComplexVm>(r => r.News).WithTemplateByUrl(urlNewsTmpl).Html();  dsl.WithId(contactDivId).Core().Insert.For<ComplexVm>(r => r.Contacts).WithTemplateByUrl(urlContactsTmpl).Html();                    })     .AsHtmlAttributes()     .ToDiv() 

    • Работа с validation ( сервер, как клиент ) — в многих js framework есть инструменты для валидации, но IML, как упоминалось не однократно имеет интеграцию с сервером и поддержка любых validation engine ( FluentValidation, стандартный MVC) без написания дополнительного кода.
      • Код command
        if (device != null)    throw IncWebException.For<AddDeviceCommand>(r => r.Pin, "Device with same pin is already exist"); 

      • Код view
        .OnError(dsl => dsl.Self().Core().Form.Validation.Refresh()) 

      примечание: обработчик OnError должен быть прикреплен к элементу, который вызывает action ( submit, ajaxpost or etc )

    • Меньше скриптов – с увлечением проекта js framework требует написания множества js файлов, но IML имеет фиксированный ( плагины не в счет ) набор библиотек
    • Типизированные template – я о типизации в целом, но для построения шаблонов это особенно важно
    • Замена template engine — выбирайте любой, а синтаксис тот же
    • Готовая инфраструктура – IML это часть Incoding Framework и в отличии от js framework у нас полная ( сервер / клиент / unit testing ) инфраструктура для разработки проектов, которая тесно интегрирована между собой.

    Заключение

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

    Невозможны ( по причине производительности ):

    • Paginated – если в базе сотни тысяч записей, то такой объем не правильно передавать на клиента
    • Order – та же причина
    • Where — та же причина

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

    Опять IML ))

    В рамках IML вычисления можно решить следующими способами:

    • Одиночное значение
      var val = Selector.Incoding.AjaxGet(url); dsl.WithId(yourId).Core().JQuery.Attributes.Val(val); 

    • Набор данных
      dsl.With(r => r.Name(s => s.Last)).Core().Insert.For<ContactVm>(r => r.Last).Val(); dsl.With(r => r.Name(s => s.First)).Core().Insert.For<ContactVm>(r => r.First).Val();  dsl.With(r => r.Name(s => s.City)).Core().Insert.For<ContactVm>(r => r.City).Val(); 

    Можно долго рассказывать про возможности IML ( и Incoding Framework ), но статья и так получилось большой, поэтому, те кто захочет продолжить изучать наш инструмент смогут найти материалы в сети. Я понимаю, доказать то, что IML способен решать задачи не хуже популярных js framework крайне сложно, но в следующих статьях будет обзор реализаций autocomplete, Tree View, grid и других сложных задач, которые продемонстрируют ещё больше возможностей.

    P.S. Как всегда рад критики и замечаниям )))

ссылка на оригинал статьи http://habrahabr.ru/post/214963/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *