Thursday, May 28, 2009 10:44 PM
Kazi Manzur Rashid
ASP.NET MVC UI Components (Continued)
In my last post, two important issues are raised
- The justification of having server side components for jQuery UI.
- The style of syntax.
The intension of my last post was to get the feedback of the type of syntax the ASP.NET MVC developer prefers, so I did not mention anything on the server side side integration, this might be the reason why few people were unable to find the benefits of this server side support. In this post, I will try to show few simple examples of the server side integration, lets say that you are creating a Task submit form, you can use the Slider as completed percent field instead of regular input field, like the following:
View:
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<Task>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Slider Value Form Submit Example
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<% using(Html.BeginForm()){%>
<fieldset>
<legend>Submit Task</legend>
<span style="color:Blue;font-weight:bold">
<%= ViewData.Get<string>("successMessage") %>
</span>
<%= Html.ValidationSummary("Please correct the following errors:") %>
<div>
<label for="name">Name:</label>
</div>
<div style="margin-top:5px">
<%= Html.TextBox("task.Name", null, new { style = "width:200px" })%>
</div>
<div style="margin-top:5px">
<%= Html.ValidationMessage("task.Name")%>
</div>
<div style="margin-top:10px">
Completed: <span id="completedPercent" style="color:Black"></span>%
</div>
<div style="margin-top:5px;width:200px">
<% Html.jQuery().Slider()
.Name("task.Completed")
.UpdateElements("#completedPercent")
.Render(); %>
</div>
<div style="margin-top:5px">
<%= Html.ValidationMessage("task.Completed")%>
</div>
<div style="margin-top:10px">
<input type="submit" value="Submit"/>
</div>
</fieldset>
<% }%>
</asp:Content>
Controller:
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult FormSubmitWithValue()
{
return View(new Task());
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult FormSubmitWithValue([Bind]Task task)
{
if (string.IsNullOrEmpty(task.Name))
{
ModelState.AddModelError("task.Name", "Name cannot be blank.");
}
if (task.Completed <= 0)
{
ModelState.AddModelError("task.Completed", "Invalid task complete percent.");
}
if (ModelState.IsValid)
{
//Save here;
ViewData.Set("successMessage", "Task saved successfully.");
}
return View(task);
}
[Live Version]
Check that (line 26- 29 in the above View) you are using the same kind of naming as you do in strongly typed view for regular input fields, you can use the slider for non-strongly typed view too.
In case of Slider is ranged, we can use the following:
View:
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Slider Values Form Submit Example
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<% using(Html.BeginForm()){%>
<fieldset>
<legend>Submit Department</legend>
<span style="color:Blue;font-weight:bold">
<%= ViewData.Get<string>("successMessage") %>
</span>
<%= Html.ValidationSummary("Please correct the following errors:") %>
<div>
<label for="name">Name:</label>
</div>
<div style="margin-top:5px">
<%= Html.TextBox("name", null, new { style = "width:200px" })%>
</div>
<div style="margin-top:5px">
<%= Html.ValidationMessage("name") %>
</div>
<div style="margin-top:10px">
Salary: $<span id="rangeFrom" style="color:Black"></span> - $<span id="rangeTo" style="color:Black"></span>
</div>
<div style="margin-top:5px;width:600px">
<% Html.jQuery().Slider()
.Name("salary")
.Range(jQuerySliderRange.True)
.Values(1000, 2500) //Initial value
.UpdateElements("#rangeFrom", "#rangeTo")
.Minimum(1000)
.Maximum(10000)
.Steps(500)
.Render(); %>
</div>
<div style="margin-top:5px">
<%= Html.ValidationMessage("salary")%>
</div>
<div style="margin-top:10px">
<input type="submit" value="Submit"/>
</div>
</fieldset>
<% }%>
</asp:Content>
Controller:
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult FormSubmitWithValues()
{
return View();
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult FormSubmitWithValues(string name, IList<int> salary)
{
if (string.IsNullOrEmpty(name))
{
ModelState.AddModelError("name", "Name cannot be blank.");
}
if (salary.Count != 2)
{
ModelState.AddModelError("salary", "Invalid salary range.");
}
if (salary.Count == 2)
{
if (salary[0] <= 1000)
{
ModelState.AddModelError("salary", "Salary minimum range should be greater than 1000.");
}
if (salary[1] <= 2500)
{
ModelState.AddModelError("salary", "Salary maximum range should be greater than 2500.");
}
}
if (ModelState.IsValid)
{
//Save here;
ViewData.Set("successMessage", "Department saved successfully.");
}
return View();
}
[Live Version]
Since the slider is a range slider, it will have an array of values, that is why the controllers accepts IList<int> for the slider. This will also work for strongly typed view.
You can also use the ProgressBar to auto retrieve the value directly from ViewData, for example,
View:
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
ProgressBar Auto Retrieve Value Example
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<% Html.jQuery().ProgressBar()
.Name("myProgressBar")
.Render(); %>
</asp:Content>
Controller:
public ActionResult AutoRetrieve()
{
ViewData["myProgressBar"] = 40;
return View();
}
[Live Version]
As mentioned in the past that the goal of this component is to add some RAD support for the ASP.NET MVC Developers. For example, in the original jQuery UI both Slider and ProgressBar does not have any built-in support to show the value(s) in an associated html element(s), but it does have this support to attach any html elements to show the numeric value(s), check the UpdateElements method in the above (works on jQuery Selector), behind the scene it generates the necessary javascript codes to hook the events and update the elements, the same is true for the above slider form submit examples, it generates the required hidden input(s) to support form submit. It will not discourage you from writing javascript codes, instead it automates some common repetitive tasks. The next thing of my previous blog post was the benefits of server side generating the necessary html and javascript codes even when there is no integration of server side like the above slider or progressbar, to demonstrate it, I will first create a dynamic jQuery UI tab with raw html and javascripts, lets assume the tab items are disabled and selected based upon some condition.
Raw Html and JavaScript:
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<IList<DynamicTabContent>>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Tab Dynamic Item Example
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<% var disabledIndexes = new List<string>(); %>
<% var selectedIndex = 0; %>
<div id="tabs">
<ul>
<% for (var i = 0; i < Model.Count; i++) {%>
<li><a href="#section-<%= i %>"><%= Html.Encode(Model[i].Header)%></a></li>
<% }%>
</ul>
<% for (var i = 0; i < Model.Count; i++) {%>
<% var dynamicContent = Model[i]; %>
<div id="section-<%= i %>">
<p><%= Html.Encode(dynamicContent.Content) %></p>
</div>
<% if (dynamicContent.IsDisabled)
{
disabledIndexes.Add(i.ToString());
}
//The last selected will win
if (dynamicContent.IsSelected && (i > selectedIndex))
{
selectedIndex = i;
}
%>
<% }%>
</div>
<script type="text/javascript">
$(document).ready(function(){
$('#tabs').tabs({
selected : <%= selectedIndex %>,
disabled : [<%= string.Join(", ", disabledIndexes.ToArray()) %>]
});
});
</script>
</asp:Content>
In the above, we are first iterating the Model to generate the tab headers, next we are again iterating it to generate the content panes and this time we are also populating the disabled index list and selecting the selected index. At last, we are dumping the selected index and converting the disabled indexes to a comma delimited string to create the tab in javascript.
Now lets see how this can simplify the above situation, we can do the exact same thing with the following codes.
Simple Fluent:
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<IList<DynamicTabContent>>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Tab Dynamic Item Example
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<% Html.jQuery().Tab().Name("tabs")
.Items(item =>
{
for(var i = 0; i < Model.Count; i++)
{
var dynamicContent = Model[i];
item.Create()
.HeaderText(dynamicContent.Header)
.Content(() =>
{%>
<p><%= Html.Encode(dynamicContent.Content) %></p>
<%}
)
.Selected(dynamicContent.IsSelected)
.Disabled(dynamicContent.IsDisabled);
}
}
)
.Render(); %>
</asp:Content>
[Live Version]
As you can see it is much more simpler and concise and with the above, we do not have to iterate the model again and again, no need to maintain the selected index and disabled index list, the component takes these responsibility and generates the Tab. I hope this clarifies the issue.
The second issue is the style of syntax, first, let me post the result of my previous Poll (total 48 votes):
- Regular Methods (9 votes, 19%)
- Simple Fluent (14 votes, 29%)
- Progressive Fluent(25 votes, 52%)
Though the progressive has more votes, but I think once you are familiar with the syntax, the verboseness of the progressive fluent will start to irritate you and this is the reason why I have changed it to simple fluent from progressive fluent. You can download the latest version from the bottom of the post. Another interesting syntax proposed my Chad Myer. The difference between mine with this is that it first creates the component, but instead of returning the component it provides an Action to configure the component. Let us do a side by side comparison of this syntax with the simple fluent, here we will see the basic tab:
Factory + Action Syntax:
<% Html.jQuery<jQueryTab>(tab =>
{
tab.Name = "myTab";
tab.Items(items =>
{
items.Create(item =>
{
item.HeaderText = "Nunc tincidunt";
item.Content = () =>
{%>
<p>
Proin elit arcu, rutrum commodo, vehicula tempus,
commodo a, risus. Curabitur nec arcu. Donec
sollicitudin mi sit amet mauris. Nam elementum
quam ullamcorper ante. Etiam aliquet massa et
lorem. Mauris dapibus lacus auctor risus. Aenean
tempor ullamcorper leo. Vivamus sed magna quis
ligula eleifend adipiscing. Duis orci. Aliquam
sodales tortor vitae ipsum. Aliquam nulla. Duis
aliquam molestie erat. Ut et mauris vel pede
varius sollicitudin. Sed ut dolor nec orci
tincidunt interdum. Phasellus ipsum. Nunc
tristique tempus lectus.
</p>
<%};
}
);
items.Create(item =>
{
item.HeaderText = "Proin dolor";
item.Content = () =>
{%>
<p>
Morbi tincidunt, dui sit amet facilisis feugiat,
odio metus gravida ante, ut pharetra massa metus
id nunc. Duis scelerisque molestie turpis. Sed
fringilla, massa eget luctus malesuada, metus eros
molestie lectus, ut tempus eros massa ut dolor.
Aenean aliquet fringilla sem. Suspendisse sed
ligula in ligula suscipit aliquam. Praesent in
eros vestibulum mi adipiscing adipiscing. Morbi
facilisis. Curabitur ornare consequat nunc. Aenean
vel metus. Ut posuere viverra nulla. Aliquam erat
volutpat. Pellentesque convallis. Maecenas feugiat,
tellus pellentesque pretium posuere, felis lorem
euismod felis, eu ornare leo nisi vel felis.
Mauris consectetur tortor et purus.
</p>
<%};
}
);
items.Create(item =>
{
item.HeaderText = "Aenean lacinia";
item.Content = () =>
{%>
<p>
Mauris eleifend est et turpis. Duis id erat.
Suspendisse potenti. Aliquam vulputate, pede vel
vehicula accumsan, mi neque rutrum erat, eu congue
orci lorem eget lorem. Vestibulum non ante. Class
aptent taciti sociosqu ad litora torquent per
conubia nostra, per inceptos himenaeos. Fusce
sodales. Quisque eu urna vel enim commodo
pellentesque. Praesent eu risus hendrerit ligula
tempus pretium. Curabitur lorem enim, pretium nec,
feugiat nec, luctus a, lacus.
</p>
<p>
Duis cursus. Maecenas ligula eros, blandit nec,
pharetra at, semper at, magna. Nullam ac lacus.
Nulla facilisi. Praesent viverra justo vitae neque.
Praesent blandit adipiscing velit. Suspendisse
potenti. Donec mattis, pede vel pharetra blandit,
magna ligula faucibus eros, id euismod lacus dolor
eget odio. Nam scelerisque. Donec non libero sed
nulla mattis commodo. Ut sagittis. Donec nisi
lectus, feugiat porttitor, tempor ac, tempor vitae,
pede. Aenean vehicula velit eu tellus interdum
rutrum. Maecenas commodo. Pellentesque nec elit.
Fusce in lacus. Vivamus a libero vitae lectus
hendrerit hendrerit.
</p>
<%};
}
);
}
);
}
); %>
Simple Fluent:
<% Html.jQuery().Tab()
.Name("myTab")
.Items(items =>
{
items.Create()
.HeaderText("Nunc tincidunt")
.Content(() =>
{%>
<p>
Proin elit arcu, rutrum commodo, vehicula tempus,
commodo a, risus. Curabitur nec arcu. Donec
sollicitudin mi sit amet mauris. Nam elementum
quam ullamcorper ante. Etiam aliquet massa et
lorem. Mauris dapibus lacus auctor risus. Aenean
tempor ullamcorper leo. Vivamus sed magna quis
ligula eleifend adipiscing. Duis orci. Aliquam
sodales tortor vitae ipsum. Aliquam nulla. Duis
aliquam molestie erat. Ut et mauris vel pede
varius sollicitudin. Sed ut dolor nec orci
tincidunt interdum. Phasellus ipsum. Nunc
tristique tempus lectus.
</p>
<%}
);
items.Create()
.HeaderText("Proin dolor")
.Content(() =>
{%>
<p>
Morbi tincidunt, dui sit amet facilisis feugiat,
odio metus gravida ante, ut pharetra massa metus
id nunc. Duis scelerisque molestie turpis. Sed
fringilla, massa eget luctus malesuada, metus eros
molestie lectus, ut tempus eros massa ut dolor.
Aenean aliquet fringilla sem. Suspendisse sed
ligula in ligula suscipit aliquam. Praesent in
eros vestibulum mi adipiscing adipiscing. Morbi
facilisis. Curabitur ornare consequat nunc. Aenean
vel metus. Ut posuere viverra nulla. Aliquam erat
volutpat. Pellentesque convallis. Maecenas feugiat,
tellus pellentesque pretium posuere, felis lorem
euismod felis, eu ornare leo nisi vel felis.
Mauris consectetur tortor et purus.
</p>
<%}
);
items.Create()
.HeaderText("Aenean lacinia")
.Content(() =>
{%>
<p>
Mauris eleifend est et turpis. Duis id erat.
Suspendisse potenti. Aliquam vulputate, pede vel
vehicula accumsan, mi neque rutrum erat, eu congue
orci lorem eget lorem. Vestibulum non ante. Class
aptent taciti sociosqu ad litora torquent per
conubia nostra, per inceptos himenaeos. Fusce
sodales. Quisque eu urna vel enim commodo
pellentesque. Praesent eu risus hendrerit ligula
tempus pretium. Curabitur lorem enim, pretium nec,
feugiat nec, luctus a, lacus.
</p>
<p>
Duis cursus. Maecenas ligula eros, blandit nec,
pharetra at, semper at, magna. Nullam ac lacus.
Nulla facilisi. Praesent viverra justo vitae neque.
Praesent blandit adipiscing velit. Suspendisse
potenti. Donec mattis, pede vel pharetra blandit,
magna ligula faucibus eros, id euismod lacus dolor
eget odio. Nam scelerisque. Donec non libero sed
nulla mattis commodo. Ut sagittis. Donec nisi
lectus, feugiat porttitor, tempor ac, tempor vitae,
pede. Aenean vehicula velit eu tellus interdum
rutrum. Maecenas commodo. Pellentesque nec elit.
Fusce in lacus. Vivamus a libero vitae lectus
hendrerit hendrerit.
</p>
<%}
);
}
)
.Render(); %>
To me the simple fluent is much simpler than Chad’s Factory + Action. But I think, it completely depends upon the personal preference. If you think, Factory + Action is much preferable than the simple fluent or even you want to revert back to the initial progressive fluent, do let me know I will change it based upon your feedback.
[Live Version]
[Download Sample]
Filed under: Asp.net, MVC, ASPNETMVC, ASP.NET MVC, jQuery UI, jQuery