Multiple dynamic forms in one view MVC - c#

In my project I have one view that populates dynamically, creating either a single or multiple forms on one screen / page. I have managed to dynamically give each form a unique id, however when there are multiple forms on one page, an array of all forms is returned when only one is submitted and I do not know how to identify the actual instance that is being submitted from the array.
Here is my View;
#using Microsoft.AspNet.Identity
#model IEnumerable<Template.Models.GetQuestionViewModel>
#{
ViewBag.Title = "View question";
ViewBag.Page = Model.First().PageNumber;
}
#Html.AntiForgeryToken();
#foreach (var q in Model.OrderBy(o => o.QuestionRanking))
{
Html.BeginForm("ViewQuestion", "Question", FormMethod.Post, new { #class = "form-horizontal", id = #q.formNumber, role = "form" });
var qtype = q.QuestionTypeId;
<div>
<h1>#q.QuestionRanking. #q.Question1</h1>
</div><div class="form-group">
#switch (qtype)
{
case 1:
// Textbox
#Html.TextArea("Answer", q.Answer, new { #class = "form-control", rows="4", cols="10" })
break;
case 2:
// Dropdown
<select class="form-control" id="Answer" name="Answer">
#foreach (var item in q.QuestionOptions.OrderBy(o => o.QuestionOptionRanking))
{
if (q.Answer == item.QuestionOption1)
{
<option value="#item.QuestionOption1" selected>#item.QuestionOption1</option>
}
else
{
<option value="#item.QuestionOption1">#item.QuestionOption1</option>
}
}
</select>
break;
...
}
</div>
<input type="hidden" name="QuestionId" value="#q.Id" /
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" name="#q.formNumber" class="btn btn-primary" value="Save answer" />
</div>
</div>
<br />
<hr />
**Html.EndForm();**
}
And here is my controller;
using System.Web.Mvc;
using Template.Models;
using Microsoft.AspNet.Identity;
using System.Linq;
using System.Collections.Generic;
using System;
namespace Template.Controllers
{
public class QuestionController : Controller
{
private WebTemplateEntities db = new WebTemplateEntities();
// GET: /Questions/ViewQuestion/5
[HttpGet]
public ActionResult ViewQuestion(int page = 1)
{
// If user is not logged in, push them back to Login
var userId = User.Identity.GetUserId();
if (userId == null)
{
return RedirectToAction("Login", "Account");
}
// Get the list of Questions and Response options from the database
List<GetQuestionViewModel> quest = new List<GetQuestionViewModel>();
var i = 1; // for form array
foreach (var item in db.Questions.Where(q => q.PageNumber == page))
{
//Check if user has submitted a response
var reply = db.Responses.Where(r => r.QuestionId == item.Id && r.UserId == userId).Select(r => r.Answer).ToList();
//If no input, do not look for answers
if (reply.Count == 0)
{
quest.Add(new GetQuestionViewModel()
{
Id = item.Id,
QuestionOptions = db.QuestionOptions
.Where(k => k.QuestionId == item.Id)
.ToList(),
PageNumber = item.PageNumber,
Question1 = item.Question1,
QuestionRanking = item.QuestionRanking,
QuestionTypeId = item.QuestionTypeId,
formNumber = "form" + i // for form array
});
}
else //If user has input answers find them
{
quest.Add(new GetQuestionViewModel()
{
Id = item.Id,
QuestionOptions = db.QuestionOptions
.Where(k => k.QuestionId == item.Id)
.ToList(),
PageNumber = item.PageNumber,
Question1 = item.Question1,
QuestionRanking = item.QuestionRanking,
QuestionTypeId = item.QuestionTypeId,
formNumber = "form" + i, // for form array
Answer = reply.First()
});
}
i++; // for form array
}
return View(quest);
}
// POST: /Questions/ViewQuestion/5
[HttpPost]
public ActionResult ViewQuestion([Bind(Include = "QuestionId, Answer, UserId")] ResponseViewModel responseViewModel)
{
// Get page and question id
var page = System.Web.HttpContext.Current.Request["page"]; //NOTE: Problem with binding on multi form pages
var qId = System.Web.HttpContext.Current.Request["QuestionId"]; //NOTE: Problem with binding on multi form pages
var newAnswer = System.Web.HttpContext.Current.Request["Answer"];
int a = Convert.ToInt32(qId); //NOTE: Problem with binding on multi form pages
//Check if user has previously answerer
List<Response> reply = new List<Response>();
reply = (from r in db.Responses
where r.QuestionId == a && r.UserId == responseViewModel.UserId
select r ).ToList();
// If this is the first time the user has answered, add the response
if (reply.Count() == 0)
{
Response re = new Models.Response();
re.Answer = responseViewModel.Answer;
re.UserId = responseViewModel.UserId;
re.QuestionId = responseViewModel.QuestionId;
re.Source = "Web";
re.Status = "New";
re.DateStamp = System.DateTime.Now;
db.Responses.Add(re);
db.SaveChanges();
}
else // If the user has already answered move the original responses to AuditTrail
{
var ans = reply.Select(r => r.Id).First();
var dos = reply.Select(r => r.Answer).First();
AuditTrail au = new Models.AuditTrail();
au.ResponseId = ans;
au.OriginalData = dos;
au.DateChanged = System.DateTime.Now;
db.AuditTrails.Add(au);
db.SaveChanges();
// Update user's response
var stat = "Update, 1"; // NOTE: Work in progress; create array of updates to track number of updates
var updateAnswer = db.Responses.Single(q => q.Id == ans);
updateAnswer.Answer = newAnswer;
updateAnswer.Status = stat;
db.SaveChanges();
}
return RedirectToAction("ViewQuestion/" + page);
}
}
}
There are more question type cases but I left them out for clarity.
Where there is more than one question and response per screen the post action returns an array of all the forms on the page. From my initial investigations I cannot see how to determine which specific form is submitted therefore I do not know how to pull the correct response.
Is there a way?

There is concept called PartialView in MVC. You can use this to can render multiple forms in single page.

How utterly stupid I feel!! I didn't close the form!
For those of you struggling with the same issue, simply close with Html.EndForm();
I placed it right after the last line in my view and now it returns only the values from the submitted form.
And my boss spotted it..... Not a good day!

Related

How to keep SelectListItem value selected after submitting form in ASP.NET Core?

I'm trying to filter a table using a dropdown-list but I'm not able to keep SelectListItem value selected after submitting the form in ASP.NET Core.
The filter seems to work but whenever the page reloads the selected item is always the same
Get Controller
public ActionResult Index(int myDropDown)
{
var myItems= new List<Items>();
myItems = _myRepository.GetAllItems();
var filteredItems = myItems.Where(x => x.Color.DemoId == myDropDown);
if (myDropDown != 0 )
{
return View(filteredItems);
}
return View(myItems);
}
Index.chtml
<form asp-controller="MyController">
<div class="row">
<div class="form-group col-3">
<select class="form-control" id="exampleFormControlSelect1" name="myDropDown"
asp-items="(_myRepository.GetDemos()).Select(x => new SelectListItem()
{ Text = x.Name, Value = x.Id.ToString()})" onchange="this.form.submit()">
</select>
</div>
</form>
C# Method
public List<Demo> GetDemos()
{
var demos = _dbContext.Demos.Select(x => new Demo()
{
Id = x.Id,
Name = x.Name
}).ToList();
return demos;
}
trying to filter a table using a dropdown-list but I'm not able to keep SelectListItem value selected after submitting the form
You can try to dynamically set Selected property of SelectListItem, like below.
<select class="form-control" id="exampleFormControlSelect1" name="myDropDown"
asp-items="(_myRepository.GetDemos()).Select(x => new SelectListItem()
{ Text = x.Name, Value = x.Id.ToString(), Selected = x.Id == ViewBag.SelectedIndex ? true : false })" onchange="this.form.submit()">
</select>
In action method
public ActionResult Index(int myDropDown)
{
var myItems = new List<Items>();
myItems = _myRepository.GetAllItems();
var filteredItems = myItems.Where(x => x.Color.DemoId == myDropDown);
//pass selected value through ViewBag etc
ViewBag.SelectedIndex = myDropDown;
if (myDropDown != 0)
{
return View(filteredItems);
}
return View(myItems);
}

Using PagedList for paginating product list

I am trying to show a list of products and show pagination. So, I decided to use PagedList plugin. I read some examples of how to modify controller and view to do so in the link below.
https://github.com/TroyGoode/PagedList
But, instead of using ViewBag, I managed to send a group of products to controller by doing some modifications to controller:
public ActionResult Index(string name)
{
IEnumerable<ProductModel> productList = new HashSet<ProductModel>();
if (string.IsNullOrWhiteSpace(name))
{
}
else
{
CategoryModel category = db.Categories.Where(x => x.CategorytUrl == name).FirstOrDefault();
if (category != null)
{
int catId = category.Id;
string catName = category.Name;
ViewBag.categoryName = category.Name;
List<SpecItemsModel> options = Products.GetFilter(category.Id);
//initialize the list
//productList = Products.ProductLists(catId);
ViewBag.filters = options;
var childrenIDs = db.Categories.Where(x => x.ParentId == category.Id).Select(x => x.Id);
var grandchildrenIDs = db.Categories.Where(x => childrenIDs.Contains((int)x.ParentId)).Select(x => x.Id);
List<int> catIds = new List<int>();
catIds.Add(category.Id);
catIds.AddRange(childrenIDs);
catIds.AddRange(grandchildrenIDs);
var templates = db.ProductTemplates.Where(t => catIds.Contains(t.CategoryId)).Select(x => x.Id);
productList = db.Products.Where(p => templates.Contains(p.ProductTemplateId));
}
}
var pageNumber = page ?? 1;
//var onePageOfProducts = productList.OrderBy(i => i.AdminRate).ToPagedList(pageNumber, 2);
//ViewBag.OnePageOfProducts = onePageOfProducts;
return View(productList.OrderBy(i => i.AdminRate).ToPagedList(pageNumber, 2));
}
As the result, products are showing up in he view in 2-product groups. the only thing I don't know is how to modify html pager of the example I mentioned to show paging controls. :
#model IEnumerable<fardashahr.Models.ProductModel>
<div class="col-md-9">
#foreach (var item in Model)
{
<a href="#Url.Action("Details","Home",new { name = item.ProductUrl })">
<img class="bd-placeholder-img card-img-top mw-100" src="/Images/Uploads/Products/#item.Id/Thumbs/#item.ImageName" />
</a>
<p class="card-text">
<h3>
#item.ShortName
</h3>
</p>
}
</div>
</div>
Any suggestions?
Sorry, I didn't check the github project you posted.
In Controller
Change
public ActionResult Index(string name)
to
public ActionResult Index(int page, string name)
In View
Use
#Html.PagedListPager( (IPagedList)Model, page => Url.Action("Index", new { page = page, name = "***" }) )
to show page navigator.

Send data via ViewData multiselect in ASP.NET Core 3.0 MVC

I’m trying to create multiselect DropDownList, which should load data from the database. All fields should be selected on the first run. I'd like to send back to the controller parameter List<string> selectedStatus, do some logic there and send back to the view by ViewData["Status"]. Based on this parameter I'd like to fill multiselect DropDownList... I’m using jQuery select2.
On first run there is nothing in the DropDownList selected.
When I select more than one option and send to controller, the first value only will appear in the DropDownList.
I have the Index method with the following code
var status = new List<SelectListItem>();
if (!selectedStatus.Any())
{
status = (from t in _context.Status
select new SelectListItem()
{
Text = t.Status,
Value = t.StatusId.ToString(),
Selected = true
}).OrderBy(t => t.Text)
.ToList();
}
else
{
status = (from t in _context.Status
select new SelectListItem()
{
Text = t.Status,
Value = t.StatusId.ToString(),
Selected = false
}).OrderBy(t => t.Text)
.ToList();
foreach (var item in status)
{
if (selectedStatus.Any(s => s.Contains(item.Value)) == true)
{
item.Selected = true;
}
}
}
ViewData["Status"] = status;
In the Index view
<form asp-controller="Home" asp-action="Index" method="get">
<div class="input-group">
#Html.DropDownList("selectedStatus", new SelectList((IEnumerable<SelectListItem>)ViewData["Status"], "Value", "Text"), htmlAttributes: new { #class = "form-control", multiple = "multiple", id = "select" })
<div class="input-group-append">
<button class="btn btn-outline-secondary" type="submit">Submit</button>
<a asp-action="Index" class="btn btn-outline-secondary">Cancel</a>
</div>
</div>
</form>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");
<script>
$('select').select2({
theme: 'bootstrap4',
multiple: true,
});
</script>
}
}
What am I doing wrong? What's missing there?
Thank you.
As Fabian Kamp suggested , you should use a viewmodel to contain the Status and selectedStatus. Here is a workaround that using js to selected all fields , you could refer to:
ViewModel
public class Status
{
public int StatusId { get; set; }
public string StatusName { get; set; }
}
public class StatusVM
{
public List<string> selectedStatus { get; set; }
public List<SelectListItem> Status { get; set; }
}
Controller
public IActionResult MultiSelectDropDownList(List<string> selectedStatus)
{
var model = new StatusVM();
if (!selectedStatus.Any())
{
model.Status = (from t in _context.Status
select new SelectListItem()
{
Text = t.StatusName,
Value = t.StatusId.ToString(),
}).OrderBy(t => t.Text)
.ToList();
model.selectedStatus = _context.Status.Select(s => s.StatusId.ToString()).ToList();
}
else
{
model.Status = (from t in _context.Status
select new SelectListItem()
{
Text = t.StatusName,
Value = t.StatusId.ToString(),
}).OrderBy(t => t.Text)
.ToList();
model.selectedStatus = new List<string>();
foreach (var item in model.Status)
{
if (selectedStatus.Any(s => s.Contains(item.Value)) == true)
{
model.selectedStatus.Add(item.Value);
}
}
}
return View(model);
}
View
#model Demo1.Models.StatusVM
<form asp-controller="Home" asp-action="MultiSelectDropDownList" method="get">
<div class="input-group">
#Html.DropDownListFor(m => m.selectedStatus, new SelectList((IEnumerable<SelectListItem>)Model.Status, "Value", "Text", Model.selectedStatus), htmlAttributes: new { #class = "js-example-theme-multiple", multiple = "multiple", id = "select" })
<div class="input-group-append">
<button class="btn btn-outline-secondary" type="submit">Submit</button>
<a asp-action="Index" class="btn btn-outline-secondary">Cancel</a>
</div>
</div>
</form>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.6-rc.0/js/select2.min.js"></script>
<script>
var stringArray = #Html.Raw(Json.Serialize(Model.selectedStatus));
$(document).ready(function () {
$("select").select2({
theme: "classic",
multiple: true,
});
$("select").val(stringArray).trigger('change');
});
</script>
}
}
Result
Reference:Setting multiple values using jQuery select2
On the first run its empty because you have a not equals char infront of your selectedStatus.Any() or you have accendently changed the true false in the first if, but a easier solution for this would be
var status = (from t in _context.Status
select new SelectListItem()
{
Text = t.Status,
Value = t.StatusId.ToString(),
Selected = !selectedStatus.Any()
}).OrderBy(t => t.Text)
.ToList();
without a if query.
The second thing is you should really use a ViewModel not ViewData or some thing like this for this amount of data.

How to send selected radiobutton value together with html.actionlink in MVC 5

I did sorting by initial letters in my MVC 5 application. Now I would like to add radio buttons to determine whether it will sort by name or last name.The problem is that I do not know how to send value of a selected radiobutton together with Html.ActionLink (after the user clicks on it) to controller.
I hope it is understandable what I want to achieve.
View:
using (Html.BeginForm())
{
#Html.RadioButton("sortButton", "FirstName", true) <span>First Name</span>
#Html.RadioButton("sortButton", "LastName") <span>Last Name</span>
<div class="form-inline">
<div class="form-group">
<div class="form-horizontal">
#Html.ActionLink("ALL", "Index", new { sortLetter = "" }) |
#Html.ActionLink("A", "Index", new { sortLetter = "A" }) -
#Html.ActionLink("B", "Index", new { sortLetter = "B" }) -
#Html.ActionLink("C", "Index", new { sortLetter = "C" }) -
#Html.ActionLink("D", "Index", new { sortLetter = "D" }) -
etc...
</div>
</div>
</div>
}
Controller:
public class HomeController : Controller
{
private UsersContext ctx = new UsersContext();
public ActionResult Index(string sortLetter, string sortButton)
{
var contacts = from s in ctx.Users
select s;
// Sorting A - Z
if (sortLetter != null)
{
if (sortButton == "FirstName"
{
contacts = contacts.Where(o => o.FirstName.ToUpper().StartsWith(sortLetter));
}
else
{
contacts = contacts.Where(o => o.LastName.ToUpper().StartsWith(sortLetter));
}
}
return View(contacts.ToList());
}
}
You might need to go with workaround using jquery for this
$(function () {
$('.form-horizontal a').on('click', function () {
var url = $(this).attr('href'),
selectedSortLetter = $('input[type="radio"][name="sortButton"]:checked').val();
$(this).attr('href', url + '?sortLetter=' + selectedSortLetter);
//or directly browse to url like below.
location.href = url + '?sortLetter=' + selectedSortLetter;
});
})

better way to load 2 dropdown in mvc

This is how i am loading on page load state and city dropdown:
My Controller method:
This is the first method which is calling when page is loaded.
public ActionResult Index()
{
var states = GetStates();
var cities = Enumerable.Empty<SelectListItem>();
ViewBag.States = states;
ViewBag.Cities = cities;
}
private IEnumerable<SelectListItem> GetStates()
{
using (var db = new DataEntities())
{
return db.States.Select(d => new SelectListItem { Text = d.StateName, Value =d.Id.ToString() });
}
}
[HttpGet]
public ActionResult GetCities(int id)
{
using (var db = new DataEntities())
{
var data = db.Cities.Where(d=>d.StateId==id).Select(d => new { Text = d.CityName, Value = d.Id }).ToList();
return Json(data, JsonRequestBehavior.AllowGet);
}
}
My View:
IEnumerable<SelectListItem> States = ViewBag.States;
IEnumerable<SelectListItem> Cities = ViewBag.Cities;
#Html.DropDownList("State", States, "Select State", new { onchange="loadCities(this)"})
#Html.DropDownListFor(m => m.CityId, Cities, "Select City", new { id="ddlCity"})
function loadCities(obj) {
$.ajax({
url: "/Home/GetCities",
data: { id: $(obj).val() },
contentType:"application/json",
success:function(responce){
var html = '<option value="0">Select City</option>';
$(responce).each(function () {
html += '<option value="'+this.Value+'">'+this.Text+'</option>'
});
$("#ddlCity").html(html);
}
});
}
Any better way then this to load state and city dropdown?
public class HomeController : Controller
{
public ActionResult Index(int id=0)
{
Person model = null;
var states = GetStates().ToList();
var cities = Enumerable.Empty<SelectListItem>();
if (id > 0)
{
using (var db = new DataEntities())
{
model = db.People.Include("City").FirstOrDefault(d => d.Id == id);
if (model == null)
model = new Person();
else
{
states.First(d => d.Value == model.City.StateId.ToString()).Selected = true;
cities = db.Cities.Where(d => d.StateId == model.City.StateId).ToList().Select(d => new SelectListItem { Text = d.CityName,Value=d.Id.ToString(),Selected=d.Id==model.CityId });
}
}
}
else
{
model = new Person();
}
ViewBag.States = states;
ViewBag.Cities = cities;
ViewBag.Persons = GetPersons();
return View(model);
}
[HttpGet]
public ActionResult GetCities(int id)
{
using (var db = new DataEntities())
{
var data = db.Cities.Where(d=>d.StateId==id).Select(d => new { Text = d.CityName, Value = d.Id }).ToList();
return Json(data, JsonRequestBehavior.AllowGet);
}
}
public ActionResult SavePersonDetail([Bind(Exclude = "Id")] Person model)
{
// var employeeDal= new Emploee();
//employee.firstname=model.
if (ModelState.IsValid)
{
var Id = model.Id;
int.TryParse(Request["Id"], out Id);
using (var db = new DataEntities())
{
if (Id > 0)
{
var person = db.People.FirstOrDefault(d => d.Id == Id);
if (person != null)
{
model.Id = Id;
db.People.ApplyCurrentValues(model);
}
}
else
{
db.People.AddObject(model);
}
db.SaveChanges();
}
}
if (!Request.IsAjaxRequest())
{
ViewBag.States = GetStates();
ViewBag.Persons = GetPersons();
ViewBag.Cities = Enumerable.Empty<SelectListItem>();
return View("Index");
}
else
{
return PartialView("_personDetail",GetPersons());
}
}
public ActionResult Delete(int id)
{
using (var db = new DataEntities())
{
var model = db.People.FirstOrDefault(d => d.Id == id);
if (model != null)
{
db.People.DeleteObject(model);
db.SaveChanges();
}
}
if (Request.IsAjaxRequest())
{
return Content(id.ToString());
}
else
{
ViewBag.States = GetStates();
ViewBag.Persons = GetPersons();
ViewBag.Cities = Enumerable.Empty<SelectListItem>();
return View("Index");
}
}
private IEnumerable<SelectListItem> GetStates()
{
using (var db = new DataEntities())
{
return db.States.ToList().Select(d => new SelectListItem { Text = d.StateName, Value =d.Id.ToString() });
}
}
private IEnumerable<Person> GetPersons()
{
using (var db = new DataEntities())
{
return db.People.Include("City").Include("City.State").ToList();
}
}
public ActionResult HomeAjax()
{
ViewBag.States = GetStates();
ViewBag.Cities = Enumerable.Empty<SelectListItem>();
using (var db = new DataEntities())
{
var data = db.States.Include("Cities").Select(d => new { Id = d.Id, Name = d.StateName, Cities = d.Cities.Select(x => new { Id=x.Id,Name=x.CityName}) }).ToList();
ViewBag.CityStateJson = new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(data);
}
ViewBag.Persons = GetPersons();
return View();
}
}
#model IEnumerable<Person>
<div>
<table>
<tr>
<th>
First Name
</th>
<th>
Last Name
</th>
<th>
Email
</th>
<th>
City
</th>
<th>
State
</th>
<th>
Edit
</th>
</tr>
#if (Model.Count() == 0)
{
<tr>
<td colspan="6">
<h3>No data available</h3>
</td>
</tr>
}
else {
foreach (var item in Model) {
<tr data-id="#item.Id">
<td data-id="fn">#item.FirstName</td>
<td data-id="ln">#item.LastName</td>
<td data-id="email">#item.Email</td>
<td data-id="cn">#item.CityName<input type="hidden" value="#item.CityId" /></td>
<td>#item.StateName</td>
<td>
#if (ViewBag.Title == "Home Ajax" || Request.IsAjaxRequest())
{
Update
<span>#Ajax.ActionLink("Delete", "Delete", new { id = item.Id }, new AjaxOptions {OnSuccess="deleteSuccess",OnBegin="showLoader",OnComplete="hideLoader" })</span>
}
else {
<span>#Html.ActionLink("Update", "Index", new { id = item.Id })</span>
<span>#Html.ActionLink("Delete", "Delete", new { id = item.Id })</span>
}
</td>
</tr>
}
}
</table>
</div>
#model Person
#{
ViewBag.Title = "Home Ajax";
IEnumerable<Person> persons = ViewBag.Persons;
IEnumerable<SelectListItem> States = ViewBag.States;
IEnumerable<SelectListItem> Cities = ViewBag.Cities;
IEnumerable<State> fullStates=ViewBag.CityStates;
}
#section featured {
<section class="featured">
<div class="content-wrapper">
<hgroup class="title">
<h1>#ViewBag.Title.</h1>
</hgroup>
</div>
</section>
}
#section styles{
<style type="text/css">
td,th {
border:1px solid;
padding:5px 10px;
}
select {
padding:5px 2px;
width:310px;
font-size:16px;
}
</style>
}
#section scripts{
#Scripts.Render("~/bundles/jqueryval")
<script type="text/javascript">
var jsonArray = #Html.Raw(ViewBag.CityStateJson)
function clearValues() {
$("input[type='text'],select").val('');
$("input[type='hidden'][name='Id']").val(0);
}
function loadCities(obj) {
for (var i = 0; i < jsonArray.length; i++) {
if (jsonArray[i].Id == parseInt($(obj).val())) {
fillCity(jsonArray[i].Cities);
break;
}
}
}
function Edit(obj, Id) {
// alert("hi")
$("input[type='hidden'][name='Id']").val(Id);
var tr = $(obj).closest("tr");
$("#txtfirstName").val($("td[data-id='fn']", tr).text().trim());
$("#txtlastName").val($("td[data-id='ln']", tr).text().trim());
$("#txtemail").val($("td[data-id='email']", tr).text().trim());
var city = $("td[data-id='cn'] input[type='hidden']", tr).val();
var state;
for (var i = 0; i < jsonArray.length; i++) {
for (var j = 0; j < jsonArray[i].Cities.length; j++) {
if (jsonArray[i].Cities[j].Id == parseInt(city)) {
state = jsonArray[i].Id;
break;
}
}
if (state) {
fillCity(jsonArray[i].Cities);
break;
}
}
$("#ddlState").val(state);
$("#ddlCity").val(city);
}
function fillCity(obj) {
var html = '<option value="0">Select City</option>';
$(obj).each(function () {
html += '<option value="' + this.Id + '">' + this.Name + '</option>'
});
$("#ddlCity").html(html);
}
function deleteSuccess(responce) {
alert("record deleted successfully");
$("tr[data-id='" + responce + "']").remove();
}
function insertSuccess() {
alert("Record saved successfully");
clearValues();
}
function showLoader() {
$("#overlay").show();
}
function hideLoader() {
$("#overlay").hide();
}
</script>
}
<h3>Add Personal Detail</h3>
#using (Ajax.BeginForm("SavePersonDetail", "Home", new AjaxOptions { HttpMethod = "POST", UpdateTargetId = "personList" ,OnSuccess="insertSuccess",OnBegin="showLoader",OnComplete="hideLoader"}))
{
#Html.HiddenFor(m => m.Id);
<ol class="round">
<li>
#Html.LabelFor(m => m.FirstName)
#Html.TextBoxFor(m => m.FirstName, new { id = "txtfirstName" })
#Html.ValidationMessageFor(m => m.FirstName)
</li>
<li>
#Html.LabelFor(m => m.LastName)
#Html.TextBoxFor(m => m.LastName, new { id = "txtlastName" })
#Html.ValidationMessageFor(m => m.LastName)
</li>
<li>
#Html.LabelFor(m => m.Email)
#Html.TextBoxFor(m => m.Email, new { id = "txtemail" })
#Html.ValidationMessageFor(m => m.Email)
</li>
<li>
#Html.Label("State")
#Html.DropDownList("State", States, "Select State", new { onchange = "loadCities(this)", id = "ddlState" })
</li>
<li>
#Html.LabelFor(m => m.CityId)
#Html.DropDownListFor(m => m.CityId, Cities, "Select City", new { id = "ddlCity" })
#Html.ValidationMessageFor(m => m.CityId)
</li>
</ol>
<input type="submit" value="Save" />
<input type="button" value="Cancel" onclick="clearValues();"/>
}
<h2>
Person List
</h2>
<div style="position:fixed;text-align:center;top:0;bottom:0;left:0;right:0;z-index:10;background-color:black;opacity:0.6;display:none;" id="overlay">
<img style="position:relative;top:370px" src="~/Images/ajax-loader.gif" />
</div>
<div id="personList">
#Html.Partial("_personDetail", persons)
</div>
You approach using ajax is fine although I would recommend a few better practices including using a view model with properties for StateID, CityID StateList and CityList, and using Unobtrusive JavaScript rather than polluting you markup with behavior, and generating the first ("please select") option with a null value rather than 0 so it can be used with the [Required] attribute
HTML
#Html.DropDownList(m => m.StateID, States, "Select State") // remove the onchange
#Html.DropDownListFor(m => m.CityID, Cities, "Select City") // why change the default ID?
SCRIPT
var url = '#Url.Action("GetCities", "Home")'; // use the helper (dont hard code)
var cities = $('#CityID'); // cache the element
$('#StateID').change(function() {
$.getJSON(url, { id: $(this).val() }, function(response) {
// clear and add default (null) option
cities.empty().append($('<option></option>').val('').text('Please select'));
$.each(response, function(index, item) {
cities.append($('<option></option>').val(item.Value).text(item.Text));
});
});
});
If you were rendering multiple items (say you were asking the user to select their last 10 cities they visited), you can cache the result of the first call to avoid repeated calls where their selections may include cities from the same state.
var cache = {};
$('#StateID').change(function() {
var selectedState = $(this).val();
if (cache[selectedState]) {
// render the options from the cache
} else {
$.getJSON(url, { id: selectedState }, function(response) {
// add to cache
cache[selectedState] = response;
.....
});
}
});
Finally, in response to your comments regarding doing it without ajax, you can pass all the cities to the view and assign them to a javascript array. I would only recommend this if you have a few countries, each with a few cities. Its a matter of balancing the slight extra initial load time vs the slight delay in making the ajax call.
In the controller
model.CityList = db.Cities.Select(d => new { City = d.CountryID, Text = d.CityName, Value = d.Id }).ToList();
In the view (script)
// assign all cities to javascript array
var allCities= JSON.parse('#Html.Raw(Json.Encode(Model.CityList))');
$('#StateID').change(function() {
var selectedState = $(this).val();
var cities = $.grep(allCities, function(item, index) {
return item.CountryID == selectedState;
});
// build options based on value of cities
});
This is a correct approach, but you can simplify your javascript:
function loadCities(obj) {
$.getJSON("/Home/GetCities", function (data) {
var html = '<option value="0">Select City</option>';
$(data).each(function () {
html += '<option value="'+this.Value+'">'+this.Text+'</option>'
});
$("#ddlCity").html(html);
});
}
Further possible simplification:
Add the default item (Select City) server-side, so your javascript will be smaller.
Here's how I'd do it without the page refresh, assuming the list of cities isn't too long.
I'm assuming you can create a GetStatesAndCities method to return a Dictionary.
public ActionResult Index()
{
Dictionary<string, List<String>> statesAndCities = GetStatesAndCities();
ViewBag.StatesAndCities = Json(statesAndCities);
}
Then in the view:
var states = JSON.parse(#ViewBag.StatesAndCities);
function loadCities(obj) {
var cities = states[$(obj).val()];
var html = '<option value="0">Select City</option>';
$(cities).each(function () {
html += '<option value="'+this.Value+'">'+this.Text+'</option>'
});
$("#ddlCity").html(html);
}
This way when the state is changed the cities field with update immediately with no need for callback.
disclaimer: This is not a code answer, there are plenty other answers.
I think best way to keep yourself happy to seperate UI pages from data => turn them into API calls:
/GetCities
/GetStates
Now you can simply leave the select's empty on Razor rendering the page. And use a Jquery/Bootstrap plugin to create an AJAX select box.
This way when the user stops typing his search, this search string can than be send with the AJAX call (eg: /GetStates?search=test) and then a small result set can be send back to the website.
This gives:
Better separation in serveside code
Better User eXperience.
Smaller page loads (since you no longer send all the options to user when he requests the page, only when he opens the select box).
How about using Knockout?
Knockout is a JavaScript library that helps you to create rich, responsive display and editor user interfaces with a clean underlying data model
You have to use ajax for your cities. But with knockout you dont need to write
var html = '<option value="0">Select City</option>';
$(responce).each(function () {
html += '<option value="'+this.Value+'">'+this.Text+'</option>'});
$("#ddlCity").html(html);
in your javascript.Knockout makes it simple.
You can simply write:
function CityModel() {
var self = this; // that means this CityModel
self.cities = ko.observableArray([]);
self.getCities = function () {
$.ajax({
url: "/Home/GetCities",
data: { id: $(obj).val() },
contentType: "application/json",
success: self.cities
});
}
}
ko.applyBindings(new CityModel());
thats all. But you have to bind your data into html elements.
Instead of using :
#Html.DropDownListFor(m => m.CityId, Cities, "Select City", new { id="ddlCity"})
You can use:
<select data-bind="options:cities,optionsValue:"Id",optionsText:"CityName",optionsCaption:"Select City""></select>
or you can mix razor and knockout:
#Html.DropDownListFor(m => m.CityId, Cities, "Select City", new { id="ddlCity",data_bind:"options:cities,optionsValue:\"Id\",optionsText:\"CityName\""})
One more thing you have to call GetCities when State changes, you can :
#Html.DropDownList("State", States, "Select State", new {data_bind:"event:\"change\":\"$root.GetCities\""})
Dont be scare with \"\" things this because " is an escape character and we have to say to razor i want to use " by using \ before it.
You can find more info about knockout :Knockout
And mixing with razor: Razor and Knockout
Ps: yes using knockout is suspend us from Razor and Mvc. You have to write another ViewModel . But like this situations ko is helpful. Mixing razor and knockout is another option for you.

Categories

Resources