Introduction
Client side work has been very important because of large amounts of data in database systems. In classic
ASP.NET with code-behind, the natural approach is coding most of the work in code-behind files using
ASP.NET components. Moreover, rendering ASP.NET components and viewstate mechanism are thought as weak abilities of classic
ASP.NET anymore.
So a new paradigm occured. According to this new paradigm a developer can work with pure HTML and
JavaScript with less rendering and without viewstate that decreases performance.
Although MVC is known as an old design or architectural pattern, it has been popular especially after the classic
ASP.NET bottlenecks mentioned above. High-performance applications can be developed where jQuery and
JSON are used effectively in MVC.
Background
This article may be useful after reading some beginner-level articles related to
MVC, jQuery, and JSON. The following links may be useful for beginners:
Using the code
In this article, a "how to series" is aimed. So all scripts are given from the attached source code. In fact, each "how to" could be a separate article. Anyway, all possible answers are collected in a single example. In this article, there are practical approaches more than theoretical knowledge. For simplicity of the example, some static lists are used instead of a database.
The following questions are answered:
- How to make CRUD with good performance in MVC?
- How to use jQuery dialog instead of JavaScript confirm or alert?
- How to make paging in a MVC list?
- How to make "show more" link using jQuery in
MVC?
- How to use attributes with link?
- How to make a AJAX call in jQuery?
- How to use the Form collection in MVC?
- How to delete multiple records at one shot?
- How to use partial action in MVC?
- How to use JSON format in an MVC application?
- How to fill a master-detail combobox?
- How to use the jQuery datepicker?
- How to upload an image in MVC with a jQuery dialog?
- How to create a table row at client side?
- How to customize a maproute in Global.asax?
- How to make checkALLand UncheckALL rows of a table
- How to make "Loading Data"
- How to make master-detail grids with jQuery
1) How to make CRUD with good performance in
MVC?
When thought roughly, all business solutions have Create-Read-Update-Delete characteristic. If it is possible, using same "save form" for both "insert" and "update" may be cost effective from the view of developer. Using same form
is possible with managing parameters sent to action.
Suppose that you have a grid-like list, in fact it is html table, and a separate "new" button on form. In each line of table you have an "edit"
and "delete" button related to that row.
After clicking "New", redirecting to a new page - here it is called view - is not an efficient way. Because after saving data on a redirected page,
the user must click "show list" to see data inserted to the database. This means redirecting the list view and selecting data from the database with changeable select cost!
Instead of the "clicking new and listing again" scenario expressed above, a better way can be performed.
Create:
- On the list view, after clicking "New", a "jQuery Save dialog" can appear.
- Create view is rendered into jQuery dialog.
- Create form is filled and "Save" is pressed.
- At the time of pressing save button, data can be sent to related controller with
AJAX post.
- Ajax form has
onSuccess
JavaScript function.
- In the "
onSuccess
" JavaScript method the added new line, that is
JSON in parameter to "onSuccess
", is prepended to list without refreshing all of list.
Read:
It is a list operation. If it is possible, only needed data should be shown on list form. The following techniques could be used:
- paging with combobox or numbers,
- implementing "show more"
- listing with filtering.
Update:
- On the list view after clicking "Edit" in any row of list, same "jQuery Save dialog" can appear with data of selected line.
- Because all steps are same as "Create" only "
onSuccess
" method is modified according to "update" operation.
- In this case, only data in edited line is updated on view after database update. By this way there is no need to refresh all list to see last edited record.
Delete:
- After confirmation with a good looking jQuery dialog, only selected line is removed from list after database delete. Again, there is no need to refresh all list.
Same dialog is used for both insert and edit.
Links are in PersonList.cshtml view as below:
@Html.ActionLink("New", "Save",
new { personNo = 0 }, new { @class = "newLink" })
...
@Html.ActionLink("Edit", "Save", new { personNo = item.PersonNo }, new { @class = "editLink" })
- New: Text to show.
- Save: Action in controller.
- personNo : in parameter to action "Save".
If it is 0, dialog will be opened empty, else if it is greater than 0, data of that id will be shown on dialog.
- newLink: Fictitious className which will be used in jQuery below.
<div id="saveDialog" title="Person Information"></div>
<script type="text/javascript">
var linkObj;
$(document).ready(function () {
$('#saveDialog').dialog({
autoOpen: false,
width: 400,
resizable: false,
modal: true,
buttons: {
"Save": function () {
$("#update-message").html('');
$("#savePersonForm").submit();
},
"Cancel": function () {
$(this).dialog("close");
}
}
});
setLinks();
});
function setLinks()
{
$(".editLink, .newLink, uploadPicLink").unbind('click');
$(".editLink, .newLink").click
(
function ()
{
linkObj = $(this);
var dialogDiv = $('#saveDialog');
var viewUrl = linkObj.attr('href');
$.get(viewUrl, function (data)
{
dialogDiv.html(data);
var $form = $("#savePersonForm");
$form.unbind();
$form.data("validator", null);
$.validator.unobtrusive.parse(document);
$form.validate($form.data("unobtrusiveValidation").options);
dialogDiv.dialog('open');
});
return false;
}
);
} </script>
Save action in PersonController
is as below:
[HttpGet]
public ActionResult Save(int personNo)
{
Person person= new Person();
person.BirthDate = DateTime.Today;
person.PersonNo = 0;
if (personNo > 0)
{
person = Repository.GetPersonList().Where(c => c.PersonNo == personNo).FirstOrDefault();
}
return PartialView(person);
}
[HttpPost]
public JsonResult Save(Person p)
{
}
2) How to use
a jQuery dialog instead of JavaScript confirm or alert?
Customized messagebox is possible in a Windows application. For web it is possible when using third party components library. It is also possible using jQuery dialog instead of
JavaScript boxes as follows:
Delete link in PersonList
view is as below:
@Html.ActionLink("Delete", "DeletePerson", new { personNo =
item.PersonNo }, new { @class = "deleteLink", @pkNo = item.PersonNo })
The HTML and jQuery code are as below:
<div id="confirmDialog" title="Warning"></div>
<script type="text/javascript">
$(".deleteLink").live("click", function (e) {
e.preventDefault();
$("#confirmDialog").html('<br/><br/>sure?');
$("#confirmDialog").dialog({
resizable: false,
height: 200,
width: 300,
modal: true,
buttons: {
"Yes": function () {
}, "No": function () {
$(this).dialog("close");
}
} }); });
</script>
3) How to make paging in a MVC list?
Paging is one of the good approaches to listing data and minimizing cost of data transfer. Many ways to display data could be performed. In this "how to" a combobox is used for paging. But, if it is desired, numbering at the bottom of page is possible. It depends on application and developer. Paging with combobox could be developed as follows.
Firstly, the place for paging meta data is arranged as below:
@model AddressBook_mvc3_jQuery.Models.Paginginfo
...
<div id="paginginfo">
<hr />
<select id="PageSelect"></select>
<span class="pagingPersonNo" style="visibility:hidden">@Model.id</span>
<span class="pagingTotalCount" style="visibility:hidden">@Model.TotalCount</span>
<span class="pagingPageSize" style="visibility:hidden">@Model.PageSize</span>
<span class="pagingSummary">aaa</span>
<hr/>
</div>
<div id="content"></div>
...
On the first load of page the div with "paginginfo
" id is filled and first page of records is shown using following scripts.
<script type="text/javascript">
function initializePaging()
{
var PersonNo = $("#paginginfo .pagingPersonNo").text();
var TotalCount = $("#paginginfo .pagingTotalCount").text();
var PageSize = $("#paginginfo .pagingPageSize").text();
var PageSelect = $("#PageSelect");
if (TotalCount==0)
{
PageSelect.html("");
$("#paginginfo").hide();
}
else
{
PageSelect.html("");
var num = Math.ceil(TotalCount/PageSize);
for (var i = 1; i <= num; i++)
{
if (i==1)
PageSelect.append($("<option selected></option>").val(i).text(i));
else
PageSelect.append($("<option></option>").val(i).text(i));
}
}
fillData(PersonNo, 1);
}
function fillData(parPersonNo, parPageNo)
{
if (parPageNo)
{
$.ajax({
type: "POST",
url: "@Url.Action("GetAddressList", "Address")",
data: { personNo: parPersonNo, pageNo: parPageNo },
cache: false,
dataType: "json",
success: function (data)
{
if (data.Html)
{
$("#content").html(data.Html);
buttonizeALL();
setLinkAbilites();
setPagingSummary(parPageNo);
}
else
{
alert('opps!');
}
},
error: function(exp)
{
alert('Error address : ' + exp.responseText);
}
}); }
}
</script>
The action code in controller is as below. As it is seen, the result list is partially rendered and put in
JSON object using RenderPartialView
method.
public class AddressController : Controller
{
public JsonResult GetAddressList(int personNo, int pageNo)
{
int pageSize = 5; int skipCnt = ((pageNo - 1) * pageSize);
List<Address> list = (from x in Repository.GetAddressList() where x.PersonNo ==
personNo orderby x.AddressNo descending select x).Skip(skipCnt).Take(pageSize).ToList();
JsonResult jr = Json(new
{
Html = this.RenderPartialView("AddressList", list),
Message = "OK"
}, JsonRequestBehavior.AllowGet);
return jr;
}
}
When selecteditem
of combo with "PageSelect
" id is changed the following
jQuery script is running.
$("#PageSelect").change(function ()
{
var $this = $(this);
var parPageNo = $this.val();
var parPersonNo = $("#paginginfo .pagingPersonNo").text();
fillData(parPersonNo,parPageNo);
});
4) How to make "show more" link using
jQuery in MVC?
This technique is used in many popular web sites. It should be performed on large lists. "Show more" could be performed as follows:
The list view has a link for "more".
//
<table id="NoteTable"></table>
<br />
<a href="#" style="display:none" id="more">more</a>
<div id="saveDialog" title="Notes Information"></div>
<div id="confirmDialog" title="Warning"></div>
//
When "more" is clicked the following jQuery script is running.
$(function ()
{
$("#more").click(function (e)
{
e.preventDefault();
var lastNoteNo = $("#NoteTable tr:last .noteNo").text();
if (lastNoteNo)
{
var PersonNo = $("#paginginfo .pagingPersonNo").text();
fillData(PersonNo, lastNoteNo);
}
var $target = $('html,body');
$target.animate({scrollTop: $target.height()}, "slow");
return false;
});
});
function fillData(parPersonNo, parLastNoteNo)
{
if (parPersonNo)
{
$.ajax({
type: "POST",
url: "@Url.Action("GetNoteList", "Note")",
data: { personNo: parPersonNo, lastNoteNo: parLastNoteNo} ,
cache: false,
dataType: "json",
success: function (data)
{
if (data.Html)
{
$("#NoteTable").append(data.Html);
buttonizeALL();
setLinkAbilites();
if (data.HasMore)
$("#more").show();
else
$("#more").hide();
}
else
{
alert('opps!');
}
},
error: function(exp)
{
alert('Error address : ' + exp.responseText);
}
}); } }
The following action in controller is returning the JSON result.
public class NoteController : Controller
{
public JsonResult GetNoteList(int personNo, int lastNoteNo)
{
int pageSize = 5; bool hasMore = false;
List<Note> list = null;
if (lastNoteNo == 0)
{
list = (from x in Repository.GetNoteList() where x.PersonNo == personNo
orderby x.NoteNo descending select x).Take(pageSize).ToList();
hasMore = (from x in Repository.GetNoteList() where x.PersonNo ==
personNo select x.NoteNo).Take(pageSize + 1).Count() - pageSize > 0;
}
else
{
list = (from x in Repository.GetNoteList() where x.NoteNo < lastNoteNo &&
x.PersonNo == personNo orderby x.NoteNo descending select x).Take(pageSize).ToList();
hasMore = (from x in Repository.GetNoteList() where x.NoteNo < lastNoteNo &&
x.PersonNo == personNo select x.NoteNo).Take(pageSize + 1).Count() - pageSize > 0;
}
JsonResult jr = Json(new
{
Html = this.RenderPartialView("_NoteList", list),
Message = "OK",
HasMore = hasMore
}, JsonRequestBehavior.AllowGet);
return jr;
}
}
5) How to use attributes with link?
This is good ability to use especially for buttons, such as edit, delete, show detail, etc.
During creation of list, related key is attached to link as an attribute. On click event of link, that key is used and operation is done easily.
For example, suppose that you have a delete link in each line of a list. hen clicking delete link in line, it is possible to use the key as parameter
for "delete action" in controller.
@Html.ActionLink("Delete", "DeletePerson", new { personNo = item.PersonNo },
new { @class = "deleteLink", @pkNo = item.PersonNo })
On client when source is checked, the following line is seen.
<a role="button" class="deleteLink ui-button ui-widget ui-state-default ui-corner-all ui-button-text-only"
href="http://www.codeproject.com/Person/DeletePerson/1" pkno="1"><span class="ui-button-text">Delete</span></a>
In jQuery script the attributes are used as below. For example pkno is 1 and used.
$(".deleteLink").live("click", function (e)
{
e.preventDefault();
var pkNo = $(this).attr("pkNo");
});
6) How to make an AJAX call in jQuery?
An AJAX call is a very good ability to make an application faster. In some applications with large amount of data in database, developer must care low amount of data transfer in two data lines. The first line is between database and application, the second one is between application and client browser. For such requirements
AJAX call is very useful.
$.ajax({
type: "POST",
url: "/Person/DeletePerson",
data: { personNo: pkNo },
cache: false,
dataType: "json",
success: function ()
{
},
error: function (jqXHR, exception)
{
alert('Uncaught Error.\n' + jqXHR.responseText);
}
});
The URL can be also used as below.
url: "@Url.Action("DeletePerson", "Person")",
7) How to use the Form collection in MVC?
When posting a form, all form elements are sent as a collection to the related action in controller.
In controller each key-value pair can be used.
Supoose that you hava a save form as below:
@model AddressBook_mvc3_jQuery.Models.Address
@{ViewBag.Title = "Address"; }
@using (Ajax.BeginForm("Save", "Address", new AjaxOptions
{
InsertionMode = InsertionMode.Replace,
HttpMethod = "POST",
OnSuccess = "saveSuccess"
}, new { @id = "saveForm" }))
{
@Html.ValidationSummary(true)
<input style="visibility:hidden" type="text" name="TBPersonNo"
id="idTBPersonNo" value="@Model.PersonNo"/>
<input style="visibility:hidden" type="text" name="TBAddressNo"
id="idTBAddressNo" value="@Model.AddressNo"/>
<br />
<fieldset>
<table>
<tr>
<td>Address Type</td>
<td>
<select name="CBAddressType" id="idCBAddressType" style="width:120px">
</select>
</td>
</tr>
<tr>
<td>Country</td>
<td>
<select name="CBcountry" id="idCBcountry" style="width:120px">
</select>
</td>
</tr>
<tr>
<td>City</td>
<td>
<select name="CBcity" id="idCBcity" style="width:120px">
</select>
</td>
</tr>
<tr>
<td>AddressText</td>
<td>
<textarea rows="4" cols="25" name="TBAddressText"
id="idTBAddressText">@Model.AddressText</textarea>
</td>
</tr>
</table>
</fieldset>
}
The data on view could be submitted to action in controller as model object or FormCollection as shown below:
[HttpPost]
public JsonResult Save(FormCollection fc)
{
object obj = null;
Address addrTmp = new Address();
addrTmp.AddressNo = Convert.ToInt32(fc["TBAddressNo"].ToString());
addrTmp.AddressTypeNo = Convert.ToInt32(fc["CBAddressType"].ToString());
addrTmp.AddressText = fc["TBAddressText"].ToString();
addrTmp.CityNo = Convert.ToInt32(fc["CBcity"].ToString()); ;
addrTmp.PersonNo = Convert.ToInt32(fc["TBPersonNo"].ToString());
if (ModelState.IsValid)
{
if (addrTmp.AddressNo == 0)
{
addrTmp.AddressNo = Data.Repository.GetAddressList().OrderBy(x => x.AddressNo).Last().AddressNo + 1;
Data.Repository.GetAddressList().Add(addrTmp);
obj = new { Success = true,
Message = "Added successfully",
Object = addrTmp,
operationType = "INSERT",
Html = this.RenderPartialView("_addressLine", addrTmp )
};
}
else
{
Address addr = Repository.GetAddressList().Where(c => c.AddressNo == addrTmp.AddressNo).FirstOrDefault();
addr.AddressTypeNo = addrTmp.AddressTypeNo;
addr.AddressText = addrTmp.AddressText;
addr.CityNo = addrTmp.CityNo;
addr.PersonNo = addrTmp.PersonNo;
obj = new { Success = true,
Message = "Updated successfully",
Object = addr,
operationType = "UPDATE",
Html = this.RenderPartialView("_addressLine", addr )
};
}
}
else
{
obj = new { Success = false, Message = "Please check form" };
}
return Json(obj, JsonRequestBehavior.DenyGet);
}
8) How to delete multiple records at one shot?
On some pages sometimes deletion of many records at one shot makes the business easy. Deleting multiple records is possible collecting keys of all selected records.
After sending all keys to controller deletion can be performed as follows.
Firstly "Delete Selected" button with "deleteALL" id is clicked. After pressing Yes the following jQuery script could be used.
Of course alternative scripts could be developed.
$("#deleteALL").live("click", function (e)
{
e.preventDefault();
var len = $("#NoteTable tr").length;
$("#confirmDialog").html('<br/><br/>deleting all selecteds.. sure?');
$("#confirmDialog").dialog({
resizable: false,
height: 200,
width: 300,
modal: true,
buttons:
{
"Yes": function ()
{
$(this).dialog("close");
var strSelecteds = '';
var rows = $("#NoteTable tr");
for(var i=0; i< rows.length; i++)
{
var row = $(rows).eq(i);
var span = row.find('span#cboxSpan');
var cb = row.find('span#cboxSpan').find('input.cboxDELclass');
var checked=(cb.is(':checked'));
var pkno = cb.attr("pkno");
if (checked)
{
strSelecteds = strSelecteds + pkno + ',';
}
}
if (strSelecteds.length>0)
{
strSelecteds = strSelecteds.substring(0,strSelecteds.length-1);
}
if (strSelecteds.length>0)
{
$.ajax({
type: "POST",
url: "/Note/DeleteALL",
data: { noteNOs: strSelecteds },
cache: false,
dataType: "json",
success: function (data)
{
var strSelectedsArr = strSelecteds.split(',');
for (var i = 0; i < strSelectedsArr.length; i++)
{
var rowNo = '#row-' + strSelectedsArr[i];
$(rowNo).remove();
}
$('#saveDialog').dialog('close');
$('#Message').html(data.Message);
$('#Message').delay(300).slideDown(300).delay(1000).slideUp(300);
},
error: function(jqXHR, exception)
{
alert('Uncaught Error.\n' + jqXHR.responseText);
}
}); }
else
alert('No row selected');
}, "No": function ()
{
$(this).dialog("close");
}
} });
});
As it is seen above, deletion of many records is done with ajax call to "DeleteALL" action in "Note" controller.
9) How to use partial action in MVC?
In some cases, a component is needed to use on many forms. For example, you may need a "person information box" on some separate forms as shown below.
_personinfo view can be as below.
@model AddressBook_mvc3_jQuery.Models.Person
@{ ViewBag.Title = "_personinfo"; }
<fieldset>
<legend>Person info</legend>
<table>
<tr><td><b><span> @Model.FirstName @Model.LastName </span></b>(@String.Format("{0:dd.MM.yyyy}",
Model.BirthDate))</td><td>(@Model.CategoryName)</td></tr>
</table>
</fieldset>
To use partial action in any view following line of code can be used.
//..
<h2>Address List</h2>
<div>
@Html.Action("_personinfo", "Common")
</div>
//..
10) How to use JSON format in MVC application?
JSON format can be used when sending parameter to an action in controller and getting result from an action.
As it is shown below, DeleteNote action in Note controller has parameter noteNo. Here, pkNo is a call-by-value parameter.
$(".deleteLink").live("click", function (e)
{
e.preventDefault();
var pkNo = $(this).attr("pkNo");
$.ajax({
type: "POST",
url: "/Note/DeleteNote",
data: { noteNo: pkNo },
cache: false,
dataType: "json",
success: function ()
{
$(rowNo).remove();
},
error: function(jqXHR, exception)
{
alert('Uncaught Error.\n' + jqXHR.responseText);
}
});
});
It is possible to get JSON result from action in controller. The following script returns result as json object.
[HttpPost]
public JsonResult DeleteNote(int noteNo)
{
string message = string.Empty;
try
{
Note n = Data.Repository.GetNoteList().Where(c => c.NoteNo == noteNo).FirstOrDefault();
if (n != null)
{
Data.Repository.GetNoteList().Remove(n);
message = "Deleted";
}
else
{
message = "Note not found!";
}
}
catch (Exception ex)
{
message = ex.Message;
}
return Json(new { Message = message }, JsonRequestBehavior.AllowGet);
}
11) How to fill a master-detail combobox?
Some forms require to fill a combobox on changing another one. For example, for a country-city couple, country can be thought as master and city as detail.
The html in view of "save form" is as below.
@model AddressBook_mvc3_jQuery.Models.Address
@{ViewBag.Title = "Address"; }
@using (Ajax.BeginForm("Save", "Address", new AjaxOptions
{
InsertionMode = InsertionMode.Replace,
HttpMethod = "POST",
OnSuccess = "saveSuccess"
}, new { @id = "saveForm" }))
{
@Html.ValidationSummary(true)
<input style="visibility:hidden" type="text" name="TBPersonNo"
id="idTBPersonNo" value="@Model.PersonNo"/>
<input style="visibility:hidden" type="text" name="TBAddressNo"
id="idTBAddressNo" value="@Model.AddressNo"/>
<br />
<fieldset>
<table>
<tr>
<td>Address Type</td>
<td>
<select name="CBAddressType" id="idCBAddressType" style="width:120px">
</select>
</td>
</tr>
<tr>
<td>Country</td>
<td>
<select name="CBcountry" id="idCBcountry" style="width:120px">
</select>
</td>
</tr>
<tr>
<td>City</td>
<td>
<select name="CBcity" id="idCBcity" style="width:120px">
</select>
</td>
</tr>
<tr>
<td>AddressText</td>
<td>
<textarea rows="4" cols="25" name="TBAddressText"
id="idTBAddressText">@Model.AddressText</textarea>
</td>
</tr>
</table>
</fieldset>
}
On first load, idCBCountry combobox is filled using "GetCountryList" action in Address controller. Then on changing country combobox, city combobox is filled as below.
<script type="text/javascript">
$(document).ready(function () {
$.ajax({
type: "POST",
url: "@Url.Action("GetCountryList", "Address")",
data: {},
cache: false,
dataType: "json",
success: function (data)
{
var idCBcountry = $("#idCBcountry");
idCBcountry.html("");
if (@Model.AddressNo>0)
{
for (var i = 0; i < data.List.length; i++)
{
var item = data.List[i];
if (item.CountryNo == @Model.CountryNo)
{
idCBcountry.append($("<option selected></option>").val(item.CountryNo).text(item.CountryName));
fillCity(item.CountryNo);
}
else
{
idCBcountry.append($("<option />").val(item.CountryNo).text(item.CountryName));
}
} }
else
{
for (var i = 0; i < data.List.length; i++)
{
var item = data.List[i];
if (i==0)
{
idCBcountry.append($("<option selected></option>").val(item.CountryNo).text(item.CountryName));
fillCity(item.CountryNo);
}
else
{
idCBcountry.append($("<option />").val(item.CountryNo).text(item.CountryName));
}
} } },
error: function(exp)
{
alert('ErrorCountry : ' + exp.responseText);
}
});
$("#idCBcountry").change(function () {
var $this = $(this);
var CountryNo = $this.val();
if (CountryNo)
{
fillCity(CountryNo);
} });
});
function fillCity(parCountryNo)
{
$.ajax({
type: "POST",
url: "@Url.Action("GetCityList", "Address")",
data: {CountryNo: parCountryNo},
cache: false,
dataType: "json",
success: function (data)
{
var idCBcity = $("#idCBcity");
idCBcity.html("");
for (var i = 0; i < data.List.length; i++)
{
var item = data.List[i];
if (item.CityNo == @Model.CityNo)
{
idCBcity.append($("<option selected></option>").val(item.CityNo).text(item.CityName));
}
else
{
idCBcity.append($("<option />").val(item.CityNo).text(item.CityName));
}
}
},
error: function(exp)
{
alert('ErrorCity : ' + exp.responseText);
}
});
}
</script>
The actions code are as below. The lists are embedded into json object. Then, in jQuery script shown above, list elements are used with index.
public class AddressController : Controller
{
public JsonResult GetCountryList()
{
object obj = null;
List<Country> list = Repository.GetCountryList();
obj = new { Success = true, Message = "OK", List = list };
return Json(obj, JsonRequestBehavior.AllowGet);
}
public JsonResult GetCityList(int CountryNo)
{
object obj = null;
List<City> list = Repository.GetCityList().Where(c => c.CountryNo == CountryNo).ToList(); ;
obj = new { Success = true, Message = "OK", List = list };
return Json(obj, JsonRequestBehavior.AllowGet);
}
}
This technique can be used for any html elements on the view form.
12) How to use jQuery datepicker?
Date type is almost used in all business applications. Because of differentitation of cultures, sometimes using this type means trouble for developer.
However, jQuery datepicker makes using date type easy.
To show date in desired format in list, the following line can be used.
<span class="BirthDate"> @String.Format("{0:dd.MM.yyyy}", item.BirthDate) </span>
In save form the following html script is used.
...
<div class="editor-label">
@Html.LabelFor(model => model.BirthDate)
</div>
<div class="editor-field">
@Html.TextBoxFor(model => model.BirthDate,
new { @class = "BirthDateSave",
@id = "TBBirthDate",
@Value = Model.BirthDate.ToString("dd.MM.yyyy")
})
</div>
...
The jQuery Datepicker can work with @Html.TextboxFor with the following script on the same page.
<script language="javascript" type="text/javascript">
$(document).ready(function () {
$(".BirthDateSave").datepicker({
changeMonth: true,
changeYear: true,
dateFormat: 'dd.mm.yy',
showOn: 'both'
});
});
</script>
At the beginning of appropriate .cshtml the following lines must be included.
..
<link href="@Url.Content("~/Content/themes/base/jquery.ui.all.css")" rel="stylesheet" type="text/css" />
<script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery-ui-1.8.11.min.js")" type="text/javascript"></script>
..
13) How to upload image in MVC with jQuery dialog?
An image can be uploaded easily in a separate view. But redirecting to a seperate view for only uploading an image, and then redirecting list view may be ineffective
from the view of cost. So, instead, on list view, a jquery dialog can be opened by clicking upload link in each line, an image can be browsed and uploaded.
When "Upload Pic" is clicked the following script is running.
$(".uploadPicLink").click
(
function ()
{
linkObj = $(this);
var dialogDiv = $('#savePicDialog');
var viewUrl = linkObj.attr('href');
$.get(viewUrl, function (data) {
dialogDiv.html(data);
var $form = $("#savePersonPicForm");
$form.unbind();
$form.data("validator", null);
$.validator.unobtrusive.parse(document);
$form.validate($form.data("unobtrusiveValidation").options);
dialogDiv.dialog('open');
});
return false;
}
);
SavePersonPic form that is loaded into jQuery dialog is as below. iframe is used to upload file in jQuery dialog.
@model AddressBook_mvc3_jQuery.Models.Person
@{ViewBag.Title = "Save image";}
@using (Html.BeginForm("SavePersonPic", "Person", FormMethod.Post,
new
{
enctype = "multipart/form-data",
id = "savePersonPicForm",
name = "savePersonPicForm",
target = "UploadTarget"
}))
{
@Html.ValidationSummary(true)
<div id="update-message" class="error invisible"></div>
<fieldset>
<legend>Person Picture</legend>
<div class="editor-label">
<label for="file">Upload Image:</label>
</div>
<div class="editor-field">
<input type="file" name="file" id="file"/>
</div>
</fieldset>
}
<iframe id="UploadTarget"
name="UploadTarget" onload="UploadImage_Complete();"
style="position: absolute; left: -999em; top: -999em;">
</iframe>
The action to upload file in controller is below:
public class PersonController : Controller
{
[HttpGet]
public ActionResult SavePersonPic(int personNo)
{
Person person = new Person();
if (personNo > 0)
{
person = Repository.GetPersonList().Where(c => c.PersonNo == personNo).FirstOrDefault();
}
return PartialView(person);
}
[HttpPost]
public JsonResult SavePersonPic(HttpPostedFileBase file, int personNo)
{
string message = string.Empty;
bool success = false;
string imgPath = "";
string fileName = "";
try
{
string path = System.IO.Path.Combine(Server.MapPath("~/Content/images"),
System.IO.Path.GetFileName(file.FileName));
file.SaveAs(path);
Person p = Data.Repository.GetPersonList().Where(r => r.PersonNo == personNo).FirstOrDefault();
p.imgFileName = file.FileName;
ViewBag.Message = "File uploaded successfully";
message = ViewBag.Message;
fileName = file.FileName;
imgPath = Url.Content(String.Format("~/Content/images/{0}", fileName));
success = true;
}
catch (Exception ex)
{
message = ex.Message;
success = true;
imgPath = "";
fileName = "";
}
return Json(
new { Success = success,
Message = message,
PersonNo=personNo,
ImagePath = imgPath,
FileName = fileName
},
JsonRequestBehavior.AllowGet
);
}
}
The JavaScript "onload" function of iframe is below. The uploaded image is shown in related line of list without refreshing line.
function UploadImage_Complete()
{
if (isFirstLoad == true)
{
isFirstLoad = false;
return;
}
try
{
document.getElementById("savePersonPicForm").reset();
var jsonTxt = ($('#UploadTarget').contents()).text();
var jsonObj = JSON.parse(jsonTxt);
var rowid = '#row-' + jsonObj.PersonNo;
var row = $('#personTable ' + rowid);
var imgid = "#img-" + jsonObj.PersonNo;
var img = row.find(imgid);
$(img).attr("src", jsonObj.ImagePath);
$('#Message').html(jsonObj.Message);
$('#Message').delay(300).slideDown(300).delay(1000).slideUp(300)
$('#savePicDialog').dialog('close');
}
catch (err)
{
alert(err.get_Message());
}
}
14) How to create a table row at client side?
A record added to database should be shown in list at client side. It can be done by many ways. After adding a record, list can be refreshed totally from database
but it will result in heavy operation. However, using javascript or jquery, a new line could be added to list on view without refreshing all elements on view. Here, two ways are mentioned.
The first one, in which row and cells of row are created by javascript one by one, is below. As it is seen in the following scripts, after submit of this form saveSuccess
JavaScript function is used for such a scenario.
..
@model AddressBook_mvc3_jQuery.Models.Person
@{ ViewBag.Title = "Save Person"; }
..
@using (Ajax.BeginForm("Save", "Person", new AjaxOptions
{
InsertionMode = InsertionMode.Replace,
HttpMethod = "POST",
OnSuccess = "saveSuccess"
}, new { @id = "savePersonForm" }))
{
@Html.ValidationSummary(true)
..
}
..
The saveSuccess javascript method is as below. The action returns a json result including operation type. According to operation, that is INSERT
or UPDATE, the table on view is modified. When a new record is added to database, a new row is prepended to table. When an existing record is updated in database,
the only related row in table is modified.
..
function saveSuccess(data)
{
if (data.Success == true)
{
if (data.operationType == 'UPDATE')
{
var parent = linkObj.closest("tr");
$(parent).animate({ opacity: 0.3 }, 200, function () {;});
parent.find(".FirstName").html(data.Object.FirstName);
parent.find(".LastName").html(data.Object.LastName);
parent.find(".CategoryName").html(data.Object.CategoryName);
var date = new Date(parseInt(data.Object.BirthDate.substr(6)));
var dateStr = FormatDate(date);
parent.find(".BirthDate").html(dateStr);
$(parent).animate({ opacity: 1.0 }, 200, function () {;});
}
else
{
try
{
var personTable = document.getElementById("personTable");
var row = personTable.insertRow(1); row.setAttribute("id", 'row-' + data.Object.PersonNo.toString());
var buttonsLinks =
'<a role="button" class="editLink ui-button ui-widget ui-state-default ui-corner-all ' +
'ui-button-text-only ui-state-hover ui-state-focus" href="http://www.codeproject.com/Person/Save/' +
data.Object.PersonNo.toString() + '"><span class="ui-button-text">Edit</span></a> ' +
'<a role="button" class="adressLink ui-button ui-widget ui-state-default ui-corner-all ' +
'ui-button-text-only" href="http://www.codeproject.com/Address/Index/' + data.Object.PersonNo.toString() +
'"><span class="ui-button-text">Addresses</span></a> ' +
'<a role="button" class="noteLink ui-button ui-widget ui-state-default ui-corner-all ' +
'ui-button-text-only" href="http://www.codeproject.com/Note/Index/' + data.Object.PersonNo.toString() +
'"><span class="ui-button-text">Notes</span></a> ' +
'<a role="button" class="deleteLink ui-button ui-widget ui-state-default ' +
'ui-corner-all ui-button-text-only" href="http://www.codeproject.com/Person/Delete/' +
data.Object.PersonNo.toString() + '" pkno="' + data.Object.PersonNo.toString() +
'"><span class="ui-button-text">Delete</span></a>';
var cellButtons = row.insertCell(0);
cellButtons.innerHTML = buttonsLinks;
var cellPersonNo = row.insertCell(1);
cellPersonNo.innerHTML = "<span class=\"PersonNo\">" +
data.Object.PersonNo + "</span>";
var cellCategoryName = row.insertCell(2);
cellCategoryName.innerHTML = "<span class=\"CategoryName\">" +
data.Object.CategoryName + "</span>";
var cellFN = row.insertCell(3);
cellFN.innerHTML = "<span class=\"FirstName\">" +
data.Object.FirstName + "</span>";
var cellLN= row.insertCell(4);
cellLN.innerHTML = "<span class=\"LastName\">" +
data.Object.LastName + "</span>";
var cellBirthDate = row.insertCell(5);
var date = new Date(parseInt(data.Object.BirthDate.substr(6)));
var dateStr = FormatDate(date);
cellBirthDate.innerHTML = "<span class=\"BirthDate\">" +
dateStr + "</span>";
var cellimgFileName = row.insertCell(6);
cellimgFileName.innerHTML =
"<img id=\"img-" + data.Object.PersonNo.toString() + "\" alt=\"" +
data.Object.ImgFileName + "\" src=\"/content/images/" + "noimg.jpg" +
"\" height=\"35px\" width=\"50px\"><br><a " +
"class=\"uploadPicLink\" href=\"/Person/SavePersonPic/" + data.Object.PersonNo.toString() +
"\" pkno=\"" + data.Object.PersonNo.toString() +
"\" style=\"font-size:9px;\">Upload Pic</a>";
setLinks();
}
catch (err) {
alert(err.Message);
}
}
$('#saveDialog').dialog('close');
$('#Message').html(data.Message);
$('#Message').delay(300).slideDown(300).delay(1000).slideUp(300);
}
else {
$("#update-message").html(data.ErrorMessage);
$("#update-message").show();
}
}
..
The table is as below:
//..
<table id="personTable">
<tr>
<th></th>
<th>
#
</th>
<th>
Category
</th>
<th>
FirstName
</th>
<th>
SecondName
</th>
<th>
BirthDate
</th>
<th>
image
</th>
</tr>
@foreach (var item in Model) {
<tr id="row-@item.PersonNo">
<td>
@Html.ActionLink("Edit", "Save",
new { personNo = item.PersonNo }, new { @class = "editLink" })
@Html.ActionLink("Addresses", "Index",
"Address", new { personNo = item.PersonNo }, new { @class = "adressLink" })
@Html.ActionLink("Notes", "Index",
"Note", new { personNo = item.PersonNo }, new { @class = "noteLink" })
@Html.ActionLink("Delete", "DeletePerson",
new { personNo = item.PersonNo }, new { @class = "deleteLink", @pkNo = item.PersonNo })
</td>
<td>
<span class="PersonNo">@item.PersonNo</span>
</td>
<td>
<span class="CategoryName">@item.CategoryName</span>
</td>
<td>
<span class="FirstName">@item.FirstName</span>
</td>
<td>
<span class="LastName">@item.LastName</span>
</td>
<td>
<span class="BirthDate"> @String.Format("{0:dd.MM.yyyy}", item.BirthDate) </span>
</td>
<td>
<img id="img-@item.PersonNo" height="35px" width="50px"
alt="@item.ImgFileName " src="http://www.codeproject.com/content/images/@item.ImgFileName" />
<br />
@Html.ActionLink("Upload Pic", "SavePersonPic", new { personNo = item.PersonNo },
new { @class = "uploadPicLink", @pkNo = item.PersonNo, style = "font-size:9px;" })
</td>
</tr>
}
</table>
//..
The first way mentioned above might be looking as an old approach. So, the following second way may be more feasible.
In this way, to add or updata a row of table, rendered html is used. Suppose that there is a table as below.
@model IEnumerable<AddressBook_mvc3_jQuery.Models.Address>
<table id="AddressTable">
<tr>
<th></th>
<th>
#
</th>
<th>
AddressType
</th>
<th>
City/Country
</th>
<th>
Address Text
</th>
</tr>
@foreach (var item in Model)
{
<tr id="row-@item.AddressNo">
<td>
@Html.ActionLink("Edit", "Save",
new { addressNo = item.AddressNo, personNo = item.PersonNo }, new { @class = "editLink" })
@Html.ActionLink("Delete", "DeleteAddress",
new { addressNo = item.AddressNo }, new { @class = "deleteLink", @pkNo = item.AddressNo })
</td>
<td>
<span class="AddressNo">@item.AddressNo</span>
</td>
<td>
<span class="AddressTypeName">@item.AddressTypeName</span>
</td>
<td>
<span class="CityName">@item.CityName/@item.CountryName</span>
</td>
<td>
<span class="AddressText">@item.AddressText</span>
</td>
</tr>
}
</table>
When clicking "new" the following script is loaded in a jQuery dialog.
@model AddressBook_mvc3_jQuery.Models.Address
@{ViewBag.Title = "Address"; }
@using (Ajax.BeginForm("Save", "Address", new AjaxOptions
{
InsertionMode = InsertionMode.Replace,
HttpMethod = "POST",
OnSuccess = "saveSuccess"
}, new { @id = "saveForm" }))
{
@Html.ValidationSummary(true)
<input style="visibility:hidden" type="text"
name="TBPersonNo" id="idTBPersonNo" value="@Model.PersonNo"/>
<input style="visibility:hidden" type="text"
name="TBAddressNo" id="idTBAddressNo" value="@Model.AddressNo"/>
<br />
<fieldset>
<table>
<tr>
<td>Address Type</td>
<td>
<select name="CBAddressType" id="idCBAddressType" style="width:120px">
</select>
</td>
</tr>
<tr>
<td>Country</td>
<td>
<select name="CBcountry" id="idCBcountry" style="width:120px">
</select>
</td>
</tr>
<tr>
<td>City</td>
<td>
<select name="CBcity" id="idCBcity" style="width:120px">
</select>
</td>
</tr>
<tr>
<td>AddressText</td>
<td>
<textarea rows="4" cols="25" name="TBAddressText"
id="idTBAddressText">@Model.AddressText</textarea>
</td>
</tr>
</table>
</fieldset>
}
When the form is submitted, the following action in controller runs.
public class AddressController : Controller
{
[HttpPost]
public JsonResult Save(FormCollection fc)
{
object obj = null;
Address addrTmp = new Address();
addrTmp.AddressNo = Convert.ToInt32(fc["TBAddressNo"].ToString());
addrTmp.AddressTypeNo = Convert.ToInt32(fc["CBAddressType"].ToString());
addrTmp.AddressText = fc["TBAddressText"].ToString();
addrTmp.CityNo = Convert.ToInt32(fc["CBcity"].ToString()); ;
addrTmp.PersonNo = Convert.ToInt32(fc["TBPersonNo"].ToString());
if (ModelState.IsValid)
{
if (addrTmp.AddressNo == 0)
{
addrTmp.AddressNo = Data.Repository.GetAddressList().OrderBy(x => x.AddressNo).Last().AddressNo + 1;
Data.Repository.GetAddressList().Add(addrTmp);
obj = new { Success = true,
Message = "Added successfully",
Object = addrTmp,
operationType = "INSERT",
Html = this.RenderPartialView("_addressLine", addrTmp )
};
}
else
{
Address addr = Repository.GetAddressList().Where(c => c.AddressNo == addrTmp.AddressNo).FirstOrDefault();
addr.AddressTypeNo = addrTmp.AddressTypeNo;
addr.AddressText = addrTmp.AddressText;
addr.CityNo = addrTmp.CityNo;
addr.PersonNo = addrTmp.PersonNo;
obj = new { Success = true,
Message = "Updated successfully",
Object = addr,
operationType = "UPDATE",
Html = this.RenderPartialView("_addressLine", addr )
};
}
}
else
{
obj = new { Success = false, Message = "Please check form" };
}
return Json(obj, JsonRequestBehavior.DenyGet);
}
}
The RenderPartialView method is used to get desired html script.
public static string RenderPartialView(this Controller controller, string viewName, object model)
{
if (string.IsNullOrEmpty(viewName))
viewName = controller.ControllerContext.RouteData.GetRequiredString("action");
controller.ViewData.Model = model;
using (var sw = new StringWriter())
{
ViewEngineResult viewResult = ViewEngines.Engines.FindPartialView(controller.ControllerContext, viewName);
var viewContext = new ViewContext(controller.ControllerContext,
viewResult.View, controller.ViewData, controller.TempData, sw);
viewResult.View.Render(viewContext, sw);
return sw.GetStringBuilder().ToString();
}
}
The saveSuccess function of ajax post in saveAddress form is as below:
<script type="text/javascript">
function saveSuccess(data)
{
if (data.Success == true)
{
$("#paginginfo").show();
if (data.operationType == 'UPDATE')
{
var row = linkObj.closest("tr");
row.replaceWith(data.Html);
}
else
{
try
{
$("#AddressTable tr:first").after(data.Html);
}
catch (err)
{
alert(err.Message);
}
}
}
else
{
}
}
</script>
Any one of the two techniques explained above can be used.
15) How to customize maproute in Global.asax?
In classic asp.net application, urlRewrite operations were performed with some third party assemblies easily. In MVC application, customizing maproutes is possible using Global.asax.
The following maproute is default in Global.asax:
public class MvcApplication : System.Web.HttpApplication
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute(
"Default", "{controller}/{action}/{id}", new { controller = "Home", action = "Index", id = UrlParameter.Optional } );
}
}
According to the following script /Save/{addressNo}/{personNo} is used as link.
@Html.ActionLink("Edit", "Save",
new { addressNo = item.AddressNo, personNo = item.PersonNo }, new { @class = "editLink" })
The snapshot of the link above is as below.
So, adding a customized maproute rule to Gloabal.asax is possible as below.
routes.MapRoute(
"AddressSave",
"Address/Save/{addressNo}/{personNo}",
new { controller = "Address", action = "Save",
addressNo = UrlParameter.Optional, personNo = UrlParameter.Optional }
);
Any one of the two techniques explained above can be used.
16) How to make checkALL and unCheckALL rows of a table?
In many applications, all checks in a table can be desired to check or uncheck at one shot as following.
The following script can be used for such a function.
..
<br />
| <a href="#" class="checkALLRecords" id="checkALL">Check ALL</a> | <a href="#" class="unCheckALLRecords" id="unCheckALL">Uncheck ALL</a> |
<br />
..
<table id="NoteTable"></table>
..
$("#checkALL").live("click", function (e)
{
e.preventDefault();
CheckALL(true);
});
$("#unCheckALL").live("click", function (e)
{
e.preventDefault();
CheckALL(false);
});
function CheckALL(state)
{
var rows = $("#NoteTable tr");
for(var i=0; i< rows.length; i++)
{
var row = $(rows).eq(i);
var span = row.find('span#cboxSpan');
var cb = row.find('span#cboxSpan').find('input.cboxDELclass');
if (state==true)
cb.attr('checked',true);
else
cb.attr('checked',false);
}
}
Any one of the two techniques explained above can be used.
17) How to make "Loading Data"
During loading mutliple rows of data "loading data" message should be shown to users.
The following div can be customized according to needed message.
..
<div id="loadMessage"></div>
..
The following javascript function can be used to customize div area.
..
function showLoader(root, txt) {
$("#loadMessage").html("");
$("#loadMessage").show();
var loader = '<img src="' + root + '/ajax-loader.gif" align="absmiddle"> <span><br/>' + txt + '...</span>';
$("#loadMessage").fadeIn(100).html(loader);
}
function hideLoader() {
$("#loadMessage").hide();
}
..
Any one of the two techniques explained above can be used.
18) How to make master-detail grids with jQuery
When clicking master line, detail lines are shown under master grid as below.
The following tables are used as grids.
..
<table id="CountryTable" class="hovertable2"></table>
<br />
<br />
<table id="CityTable" class="hovertable"></table>
..
The following javascript functions are used to simulate master-detail approach.
..
function setTableRowClick()
{
$("#CountryTable tr td.clickable").unbind('click');
$('#CountryTable tr td.clickable').click(function ()
{
var row = $(this).parent();
setRow(row);
});
}
function setRow(row)
{
var rowid = row.attr('id');
var higlightedCountryTableRowid = $("#pageinfo .higlightedCountryTableRowid").text();
$("#pageinfo .higlightedCountryTableRowid").html(rowid.toString());
if ((rowid==0) || (rowid!=higlightedCountryTableRowid))
{
row.siblings().removeClass('diffColor');
row.addClass("diffColor");
fillCityData(rowid);
}
}
..
function fillCountryData()
{
$.ajax({
type: "POST",
url: "@Url.Action("GetCountryList", "Country")",
data: {},
cache: false,
dataType: "json",
success: function (data)
{
if (data.Html)
{
$("#CountryTable").html(data.Html);
buttonizeALL();
setLinkAbilitesCountry();
setLinkAbilitesCity();
setTableRowClick();
}
else
{
alert('opps-- country list error!');
}
},
error: function(exp)
{
alert('Error address : ' + exp.responseText);
}
});
}
function fillCityData(parCountryNo)
{
$.ajax({
type: "POST",
url: "@Url.Action("GetCityList", "Country")",
data: { countryNo: parCountryNo},
cache: false,
dataType: "json",
success: function (data)
{
if (data.Html)
{
$("#CityTable").html(data.Html);
buttonizeALL();
setLinkAbilitesCity();
setTableRowClick();
}
else
{
alert('opps-- city list error!');
}
},
error: function(exp)
{
alert('Error address : ' + exp.responseText);
}
});
}
..
Conclusion
I hope it has been a useful article.