better way to load 2 dropdown in mvc - c#

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.

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

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.

C# mvc saving deviceconfig as new config id in foreign table

I am trying to create a system where it is possible to add/ change device configurations. The user should be able to add / delete rows at the same time.
Everytime a config is saved, a new config_id is created and VersionNr is increased.
I am 100% sure I am doing something wrong, and what is going wrong. But I dont know how to fix it or improve it.
Here is my code:
public ActionResult Edit(int? Id)
{
//create new list
var Device_Pricelist = new List<Device_Pricelist>(db.Device_Pricelist.Where(r => r.DeviceConfig.Device_config_id == Id));
//create new SelectList in Device_Pricelist
var SelectedCMI = (from d in db.pricelist
select new { d.Price_id, Value = d.bas_art_nr }).Distinct();
//call viewbag based on SelectedCMI query
ViewBag.SelectedCMI = new SelectList(SelectedCMI.Distinct(), "Price_id", "Value");
return View(Device_Pricelist);
}
public ActionResult Edit([Bind(Include = "id,Device_config_id,Price_id,amount,assembly_order")]DeviceConfig deviceConfig, List<Device_Pricelist> device_Pricelists)
{
if (ModelState.IsValid)
{
try
{
try
{
db.DeviceConfig.Add(deviceConfig).Device_config_id = deviceConfig.Device_config_id++;
db.DeviceConfig.Add(deviceConfig).device_type_id = deviceConfig.device_type_id = 13;
db.DeviceConfig.Add(deviceConfig).Active = true;
//Needs to be based on current VersionNr in deviceConfig
db.DeviceConfig.Add(deviceConfig).VersionNr = deviceConfig.VersionNr + 1;
db.DeviceConfig.Add(deviceConfig).Date = deviceConfig.Date = DateTime.Now;
}
finally
{
foreach (var item in device_Pricelists)
{
db.Entry(item).State = EntityState.Added;
}
}
db.SaveChanges();
TempData["SuccesMessage"] = "Data is Succesfully saved";
return RedirectToAction("Index");
}
catch
{
TempData["AlertMessage"] = "Saving Data Failed, " + "Try Again";
}
}
return View(device_Pricelists);
}
view:
#model List<ConcremoteDeviceManagment.Models.Device_Pricelist>
#{
Layout = "~/Views/Shared/_Layout.cshtml";
}
#using (Html.BeginForm("Edit", "Home", FormMethod.Post))
{
<h1>
#Html.DisplayName("Edit Configuration")
</h1>
<h2>
#* #Html.DisplayFor(model => model.DeviceType.name)<br />*#dammit
</h2>
if (TempData["AlertMessage"] != null)
{
<p class="alert alert-danger" id="FailMessage">#TempData["AlertMessage"]</p>
}
#Html.ValidationSummary(true)
#Html.AntiForgeryToken()
<div>
#* Add New*#
</div>
<table class="table table-hover" id="dataTable">
<tr>
<th class="table-row">
#Html.DisplayName("BAS artikelnummer")
</th>
<th class="table-row">
#Html.DisplayName("Beschrijving")
</th>
<th class="table-row">
#Html.DisplayName("Aantal")
</th>
<th class="table-row">
#Html.DisplayName("Bouw Volgorde")
</th>
<th class="table-row">
Add New Row
</th>
</tr>
#if (Model != null && Model.Count > 0)
{
int j = 0;
foreach (var item in Model)
{
<tr>
#Html.HiddenFor(a => a[j].id)
#Html.HiddenFor(a => a[j].Device_config_id)
#* #Html.HiddenFor(a => a[j].Price_id)*#
<td class="table-row">
#Html.DropDownListFor(a => a[j].Price_id, (IEnumerable<SelectListItem>)ViewBag.SelectedCMI, null, new { #class = "form-control" })
</td>
<td class="table-row">
#Html.DisplayFor(a => a[j].Pricelist.description, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(a => a[j].Pricelist.description, "", new { #class = "text-danger" })
</td>
<td class="table-row">
#Html.EditorFor(a => a[j].amount, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(a => a[j].amount, "", new { #class = "text-danger" })
</td>
<td class="table-row">
#Html.EditorFor(a => a[j].assembly_order, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(a => a[j].assembly_order, "", new { #class = "text-danger" })
</td>
#*<td class="table-row">
#Html.DisplayFor(a => a[j].DeviceConfig.Date, new { htmlAttributes = new { #class = "form-control" } })
</td>*#
<td>
#if (j > 0)
{
Remove
}
</td>
</tr>
j++;
}
}
</table>
<input type="submit" value="Save Device Data" class="btn btn-primary" />
<button onclick="location.href='#Url.Action("Index", "Home")';return false; " class="btn btn-primary">Back to list</button>
}
#section Scripts{
#*#Scripts.Render("~/bundles/jqueryval")*#
<script language="javascript">
$(document).ready(function () {
//1. Add new row
$("#addNew").click(function (e) {
e.preventDefault();
var $tableBody = $("#dataTable");
var $trLast = $tableBody.find("tr:last");
var $trNew = $trLast.clone();
var suffix = $trNew.find(':input:first').attr('name').match(/\j+/);
$trNew.find("td:last").html('Remove');
$.each($trNew.find(':input'), function (i, val) {
// Replaced Name
var oldN = $(this).attr('name');
var newN = oldN.replace('[' + suffix + ']', '[' + (parseInt(suffix) + 1) + ']', '[' + suffix + ']', '[' + (parseInt(suffix) + 1) + ']', '[' + suffix + ']', '[' + (parseInt(suffix) + 1) + ']');
$(this).attr('name', newN);
//Replaced value
var type = $(this).attr('type');
//if (type.toLowerCase() == "text") {
// $(this).attr('value', '');
//}
// If you have another Type then replace with default value
$(this).removeClass("input-validation-error");
});
$trLast.after($trNew);
// Re-assign Validation
//var form = $("form")
// .removeData("validator")
// .removeData("unobtrusiveValidation");
//$.validator.unobtrusive.parse(form);
});
// 2. Remove
//$('a.remove').live("click", function (e) { --> this is for old jquery library
$('body').on("click", '#remove', function (e) {
e.preventDefault();
$(this).parent().parent().remove();
});
});
</script>
<script type="text/javascript">
function SelectedIndexChanged() {
//Form post
document.demoForm.submit();
}
</script>
I hope somebody is able to help me.
More information will be given when needed.
Your method accepts 2 parameters but you are only posting 1 object - List<Device_PriceList>. Let's assume you want to construct a new DeviceConfig based on the existing one, you should change your Action method to the following:
public ActionResult Edit(List<Device_Pricelist> device_Pricelists)
{
if (ModelState.IsValid)
{
try
{
var deviceConfig = db.DeviceConfig.Find(device_Pricelists.First().Device_config_id);
deviceConfig.device_type_id = 13;
deviceConfig.Active = true;
deviceConfig.VersionNr++;
deviceConfig.Date = DateTime.Now;
db.DeviceConfig.Add(deviceConfig);
foreach(var item in device_Pricelists)
{
item.Device_config_id = deviceConfig.Device_config_id;
}
db.Device_Pricelists.AddRange(device_Pricelists);
db.SaveChanges();
TempData["SuccesMessage"] = "Data is Succesfully saved";
return RedirectToAction("Index");
}
catch
{
TempData["AlertMessage"] = "Saving Data Failed, Try Again";
}
}
return View(device_Pricelists);
}
Remember when posting a list, you need to post complete sets of the object you're trying to map to. That is for example [0].Id, [0].Device_config_id (etc), [1].Id, [1].Device_config_id (etc).
Your JS doesn't seem correct, particularly the .replace(..) call with 6 parameters, so you're probably seeing lots of [0]s when you clone the rows. Some simpler JS that will get you the same result is:
var oldN = $(this).attr('name');
var idx = parseInt(oldN.substr(1, oldN.indexOf(']')));
var newN = oldN.replace('[' + idx + ']', '[' + (idx + 1) + ']');

Adding check boxes to each row on MVCcontrib Grid

How can I add a checkbox to each row of a MVCcontrib grid. then when the form is posted find out which records were selected? I Am not finding much when searching for this.
Thank you
Here's how you could proceed:
Model:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public bool IsInStock { get; set; }
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var products = new[]
{
new Product { Id = 1, Name = "product 1", IsInStock = false },
new Product { Id = 2, Name = "product 2", IsInStock = true },
new Product { Id = 3, Name = "product 3", IsInStock = false },
new Product { Id = 4, Name = "product 4", IsInStock = true },
};
return View(products);
}
[HttpPost]
public ActionResult Index(int[] isInStock)
{
// The isInStock array will contain the ids of selected products
// TODO: Process selected products
return RedirectToAction("Index");
}
}
View:
<% using (Html.BeginForm()) { %>
<%= Html.Grid<Product>(Model)
.Columns(column => {
column.For(x => x.Id);
column.For(x => x.Name);
column.For(x => x.IsInStock)
.Partial("~/Views/Home/IsInStock.ascx");
})
%>
<input type="submit" value="OK" />
<% } %>
Partial:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<MyNamespace.Models.Product>" %>
<!--
TODO: Handle the checked="checked" attribute based on the IsInStock
model property. Ideally write a helper method for this
-->
<td><input type="checkbox" name="isInStock" value="<%= Model.Id %>" /></td>
And finally here's a helper method you could use to generate this checkbox:
using System.Web.Mvc;
using Microsoft.Web.Mvc;
public static class HtmlExtensions
{
public static MvcHtmlString EditorForIsInStock(this HtmlHelper<Product> htmlHelper)
{
var tagBuilder = new TagBuilder("input");
tagBuilder.MergeAttribute("type", "checkbox");
tagBuilder.MergeAttribute("name", htmlHelper.NameFor(x => x.IsInStock).ToHtmlString());
if (htmlHelper.ViewData.Model.IsInStock)
{
tagBuilder.MergeAttribute("checked", "checked");
}
return MvcHtmlString.Create(tagBuilder.ToString());
}
}
Which simplifies the partial:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<MyNamespace.Models.Product>" %>
<td><%: Html.EditorForIsInStock() %></td>
Dont know if this helps but I did a similar thing using the code below:
#Html.Grid(Model.PagedModel).AutoGenerateColumns().Columns(column => {
column.For(a => Html.ActionLink("Edit", "Edit", new { a.ID })).InsertAt(0).Encode(false);
column.Custom(a => Html.Raw("<input type='checkbox' name='resubmit' value='" + a.ID + "'/>" ) );
})
My controller was then able to receive the selected checkboxlist items:
[HttpPost]
public ViewResult List(string[] resubmit)
For resolving this in every get request I'm appending checked boxes value as comma separated string. Then retrieve the values from query string.
$("#pageLayout a").click(function () {
//Check for the click event insed area pageLayout
//get the href of link
var link = $(this).attr('href');
var apps = '';
//for all the checkbox get the checked status
$("input:checkbox").each(
function () {
if ($(this).attr('name') != 'IsChecked') {
if (apps != '') {
apps = apps + ',' + $(this).attr('name') + '=' + $(this).is(':checked');
}
else {
apps = $(this).attr('name') + '=' + $(this).is(':checked');
}
}
}
)
//Used to check if request has came from the paging, filter or sorting. For the filter anchor href doesnt haave
//any query string parameter. So for appending the parameter first ? mark is used followed by list of query string
//parameters.
var index = link.lastIndexOf('?');
//If there is no question mark in the string value return is -1
if (index == -1) {
//url for the filter anchor tag
//appList hold the comma sep pair of applicationcode=checked status
link = link + '?appList=' + apps + '&profileName=' + $('#ProfileName').val();
}
else {
//url for sorting and paging anchor tag
link = link + '&appList=' + apps + '&profileName=' + $('#ProfileName').val();
}
//Alter the url of link
$(this).attr('href', link);
});
In your view (say ReportList.cshtml) include your grid in a form and define an action for the form
<html>
<form action="/SubmitReportList">
#{Html.Grid((List<NetSheet.Models.Report>)ViewData["ReportList"])
.Sort((GridSortOptions)ViewData["sort"])
.Attributes(id => "grid", #class => "grid")
.Columns(column =>
{
column.For(c => Html.CheckBox("chkSelected", new { #class = "gridCheck", #value = c.ReportId }))
.Named("").Encode(false)
.Attributes(#class => "grid-column");
column.For(c => c.ReportName)
.Named("Buyer Close Sheet Name")
.SortColumnName("ReportName")
.Attributes(#class => "grid-column");
})
.Render();}
<input type="submit" name=" DeleteReports" id=" DeleteReports" value="Delete" class="btnSubmit"/>
</form>
</html>
Then in your controller implement your action method
public ActionResult SubmitReportList (string ReportListSubmit, IList<string> chkSelected){
// The IList chkSelected will give you a list of values which includes report ids for the selected reports and “false” for the non-selected reports. You can implement your logic accordingly.
return View("ReportList");
}

Categories

Resources