Pull to refresh

IML TODO

Reading time16 min
Views6.2K
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. Как всегда рад критики и замечаниям )))
Tags:
Hubs:
Total votes 23: ↑10 and ↓13-3
Comments5

Articles