Using PagedList for paginating product list - c#

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.

Related

How to add a List<string> to other strings that filters results in ASP.NET MVC C#?

I have been stuck on this for over a day now and it's driving me nuts. I don't know what I'm doing wrong or what I'm missing. Anyways, I have a Users List page that is using ASP Identity framework. I have some filters at the top of the page that will filter the users by: UserName(txtbox), CompanyName(dropdown), and by Role(dropdown). My current code is filtering it, but it won't filter it with the other criteria's if they use multiple filters (like CompanyName and Role). I had to query a List string to populate my model to display in the view. So my problem I'm having trouble figuring out is how to add the "Roles" (which is the List string) to my controller code that incorporates 3 strings from the filters together. But since Roles is already a string, if I put .ToString() after it, the filter will not show any records when triggered. If I take the .ToString() off of Roles, it works only if I were to filter the Roles by itself, but it will not work with the other filters. Any suggestions on what I can do would be greatly appreciated. Here is my model:
public class UserList
{
public UserList() { }
public ApplicationUser User { set; get; }
public List<string> Roles { get; set; }
}
Here is my view:
#using (Html.BeginForm("ListUsers", "Administrator", FormMethod.Get))
{
<div class="form-inline title">
<h4>Users</h4>
<button class="create-btn"><span class="fa-solid fa-plus"></span> #Html.ActionLink("Create New User", "Register", "Account", new { #class = "CreateLink" })</button>
</div>
<p class="filter-section">
<label class="filter-lbl1">By Company:</label><label class="filter-ddl">#Html.DropDownList("strComp", Extensions.GetCompanyList(), "All", new { #class = "comp-ctl" })</label>
<label class="filter-lbl2">By Role:</label><label class="filter-ddl">#Html.DropDownList("strRole", Extensions.GetRolesList(), "All", new { #class = "role-ctn" })</label><br />
<label class="filter-lbl3">By User Name:</label><label class="filter-txtbox">#Html.TextBox("strName", "", new { #class = "name-ctl" })</label>
<button type="button" class="red" onclick="location.href='#Url.Action("ListUsers", new { })'"><i class="fa solid fa-trash-o"></i> Reset</button>
<button type="submit" class="filter-btn"><i class="fa solid fa-filter"></i> Filter</button>
</p>
}
<table class="table">
<tr>
<th>
Email
</th>
<th>
Company
</th>
<th>
Role
</th>
<th>
Last Login
</th>
<th></th>
</tr>
#foreach (var user in Model)
{
var appUser = user.User;
var roles = user.Roles;
<tr>
<td class="email">
#Html.HiddenFor(modelitem => user.User.Id)
#Html.DisplayFor(modelitem => user.User.Email)
</td>
<td>
#Html.DisplayFor(modelitem => user.User.CompanyName)
</td>
<td>
#Html.DisplayFor(modelitem => user.Roles, string.Join(",", roles))
</td>
<td>
</td>
<td>
#Html.ActionLink("Edit", "EditUser", new { id = appUser.Id }, new { #class = "edit-link" }) |
#Html.ActionLink("Delete", "DeleteUser", new { id = appUser.Id }, new { #class = "delete-link" })
</td>
</tr>
}
</table>
<link href="~/Content/PagedList.css" rel="stylesheet" />
<div></div>
Page #(Model.PageCount < Model.PageNumber ? 0: Model.PageNumber)/#Model.PageCount
#Html.PagedListPager(Model, page => Url.Action("ListUsers", new { page, strComp = ViewData["strFilter1"], strRole = ViewData["strFilter2"], strName = ViewData["strFilter3"] }))
And here is my code for my controller part:
public ActionResult ListUsers(string strFilter1, string strFilter2, string strFilter3, string strComp, string strName, string strRole, int? page)
{
if (strComp != null && strRole != null && strName != null)
{
strFilter1 = strComp;
strFilter2 = strRole;
strFilter3 = strName;
}
ViewBag.strFilter1 = strComp;
ViewBag.strFilter2 = strRole;
ViewBag.strFilter3 = strName;
var user = (from u in context.Users
let query = from ur in context.Set<IdentityUserRole>()
where ur.UserId.Equals(u.Id)
join r in context.Roles on ur.RoleId equals r.Id
select r.Name
select new UserList() { User = u, Roles = query.ToList<string>() })
.ToList();
if (!String.IsNullOrEmpty(strComp))
{
//Filter results based on company selected.
var pageNumber = page ?? 1;
var pageSize = 25;
if (strRole == null && strName == null)
{
var comp = user.Where(u => u.User.CompanyName.ToString().Contains(strComp));
return View(comp.OrderBy(u => u.User.Id).ToPagedList(pageNumber, pageSize));
}
else
{
var compuser = user.Where(u => u.User.CompanyName.ToString().Contains(strComp) &&
u.Roles.ToString().Contains(strRole) &&
u.User.UserName.ToString().Contains(strName));
return View(compuser.OrderBy(u => u.User.Id).ToPagedList(pageNumber, pageSize));
}
}
if (!String.IsNullOrEmpty(strRole))
{
//Filter results based on role selected.
var pageNumber = page ?? 1;
var pageSize = 25;
if (strComp == null && strName == null)
{
var roll = user.Where(u => u.Roles.Contains(strRole));
return View(roll.OrderBy(u => u.User.Id).ToPagedList(pageNumber, pageSize));
}
else
{
var rolluser = user.Where(u => u.Roles.Contains(strRole) &&
u.User.CompanyName.ToString().Contains(strComp) &&
u.User.UserName.ToString().Contains(strName));
return View(rolluser.OrderBy(u => u.User.Id).ToPagedList(pageNumber, pageSize));
}
}
if (!String.IsNullOrEmpty(strName))
{
//Filter results based on the username typed in.
var pageNumber = page ?? 1;
var pageSize = 25;
if (strComp == null && strRole == null)
{
var uname = user.Where(u => u.User.UserName.ToString().Contains(strName));
return View(uname.OrderBy(u => u.User.Id).ToPagedList(pageNumber, pageSize));
}
else
{
var nameuser = user.Where(u => u.User.UserName.ToString().Contains(strName) &&
u.User.CompanyName.ToString().Contains(strComp) &&
u.Roles.ToString().Contains(strRole));
return View(nameuser.OrderBy(u => u.User.Id).ToPagedList(pageNumber, pageSize));
}
}
//var userList = user.OrderBy(u => u.User.Id).ToList();
{
var pageNumber = page ?? 1;
var pageSize = 25;
var userList = user.OrderBy(u => u.User.CompanyName).ToList();
return View(userList.ToPagedList(pageNumber, pageSize));
}
}
The controller is where I believe the issue is. If I leave the .ToString() off of the Roles, its works perfect if I am just filtering by the role. But if I try to filter with anything else, it filters it to empty results. I'm thinking since the Roles is a list string, for some reason it's not able to properly add to the other strings. What's weird is with my current code, it will filter records by UserName and by Role at the same time, but the CompanyName and Role do not work in sync with each other.
I think the bit of logic you want is Enumerable.Any.
e.g. your Where clause for roles can be:
u.Roles.Any(role => role.Contains(strRole)))
this will return true if any of the u.Roles contain the specified strRole string.
Edit: Sidenote, if I've read this correctly, all of your code after the user declaration can be simplified to just:
public ActionResult ListUsers(/*...*/)
{
var user = /*...*/;
var pageNumber = page ?? 1;
var pageSize = 25;
IEnumerable<ApplicationUser> filteredUsers = user;
if (!String.IsNullOrEmpty(strComp)) {
filteredUsers = filteredUsers.Where(u => u.User.CompanyName.ToString().Contains(strComp));
}
if (!String.IsNullOrEmpty(strName)) {
filteredUsers = filteredUsers.Where(u => u.User.UserName.ToString().Contains(strName));
}
if (!String.IsNullOrEmpty(strRole)) {
filteredUsers = filteredUsers.Where(u => u.Roles.Any(role => role.Contains(strRole)));
}
return View(filteredUsers.OrderBy(u => u.User.Id).ToPagedList(pageNumber, pageSize));
}

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

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.

Multiple dynamic forms in one view MVC

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!

ASP.NET MVC dropdown filter clear

I've created a dropdown to filter results. This works great the 1st time, but if the user selects another option it filters on the filtered results. Is there a way to clear the results before it filters again?
RAZOR
#using (Html.BeginForm("Index", "Admin", FormMethod.Get))
{
#Html.DropDownList("category", (SelectList)ViewBag.Categories, "--Select One--")
<input type="submit" value="Filter" />
}
Server code
public ViewResult Index(string category, int page = 1)
{
var categoryList = categoriesRepository.Categories.Select(c => c.CategoryName).Distinct();
ViewBag.Categories = new SelectList(categoryList.AsEnumerable());
var viewModel = new ProductsListViewModel
{
Products = repository.Products
.Where(p => category == null || p.Category == category)
.OrderBy(p => p.ProductID)
.Skip((page - 1) * PageSize)
.Take(PageSize),
PagingInfo = new PagingInfo
{
CurrentPage = page,
ItemsPerPage = PageSize,
TotalItems = category == null
? repository.Products.Count()
: repository.Products.Where(e => e.Category == category).Count()
},
CurrentCategory = category
};
return View(viewModel);
}

Categories

Resources