📜 ⬆️ ⬇️

IML TODO

image
disclaimer : the article is a response to criticism (which collapsed in Habré ), revealing the potential of IML in the example of the popular ToDo MVC application.

Got cuffs


Criticism is an extremely mild word in relation to the discussion that arose in my previous article, because it was more like a beating in which there were extremely unpleasant comments (in the photo of the top delusional), but also objective ones:


Why ToDo?


In the comments, we suggested trying to implement “Todo MVC” as evidence of the IML capabilities, and now we will see what happens. First of all, the demo version, which has one thing different from those presented for the js framework, is that the storage is not local storage, but the database, as well as the source code , which we will analyze further in the post. In the process of implementation, I built all the logic (basement calculations, hiding elements, etc.) on the client, although on real tasks it is easier (sometimes necessary) to update the “point-like” elements that, having IML code, know how to calculate and display .
')

Code review


The storytelling style this time will not be a comparison of one solution with another (otherwise the volume of material will be large), but an overview of the code that will be obtained when implementing the todo application. I mentioned above that in the IML implementation there is also a server part, but in order to equalize the tasks to be solved for a more objective comparison, we will focus only on the client side.


What it consists of


The code was divided into 3 view

image

Index (consists of three elements)

Add form 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 = "" }) } 

note: I immediately expect phrases in the style of “Yes, well, this is not serious, the code is much more, you need to copy everywhere, see how people do !!!” - to which I have an argument in the face of C # extensions, which allows wrapping IML constructs. Later in the article, alternative solutions to problems will be given (also repository on GibHub with revised code) using C # extensions
What is what?


Add form TODO (alternative)

 @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 = "" }) } 

note: the code has become less and most importantly, now you can expand (validation, redirect after submit, etc.) method, to the needs of a specific project.
Under the hood
 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); } 

note: the code is familiar to most asp.net mvc developers, but it is worth noting that instead of the “usual” parameters, we pass an anonymous method that accepts the settings class.


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()) 

What is what?


Container (alternative way)

 @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; }) 

note: if in the future it will be necessary to add block ui or other actions, now it can be done centrally
Under the hood
 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()) 



Todo list tmpl

Markup template to build todo list
 @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> } 

note: the source code is larger (remote element logic) than is presented in the example, but this is done for ease of explanation template

What is what?



Other elements


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("")) 

What is what ?



Button Del (alternative)

 @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" }; }) 

note: OnBegin takes Action, which makes it easy to scale your extensions by embedding IML into it. (more examples will follow)

Under the hood
 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); } 

note: since Verb uses in several scenarios, you can easily do optional parameters, check them for null, and also set default values


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); 

note: within the razor page, you can use anonymous C # functions or Razor helper, which allows you to aggregate tasks of the same type.
What is what?



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> 

note: another example of the implementation of anonymous functions within the razor view
What is what?



Filter by type todo (alternative method)

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


Unconditional advantages!


What are the advantages of IML, I tried to reveal in the last article, but it was not convincing, so I will try again:

Source: https://habr.com/ru/post/214963/


All Articles