Blazorise Datagrid inline edit for complex object - datagrid

The Blazorise Datagrid supports inline editing. In my code, the basic editing works fine but when an object is used as a property in my parent object, editing does not work as expected.
Below, the city the value not updated as expected. What is missing to allow for a proper edit?
Code
<DataGrid Data="forecasts" TItem="WeatherForecast" EditMode="Blazorise.DataGrid.DataGridEditMode.Inline">
<DataGridCommandColumn TItem="WeatherForecast" CellsEditableOnEditCommand="true"></DataGridCommandColumn>
<DataGridDateColumn TItem="WeatherForecast" Field="#nameof(WeatherForecast.Date)" Caption="Date" Editable="true"></DataGridDateColumn>
<DataGridDateColumn TItem="WeatherForecast" Field="#nameof(WeatherForecast.Temperature)" Caption="Temperature" Editable="true"></DataGridDateColumn>
<DataGridSelectColumn TItem="WeatherForecast" Field="#nameof(WeatherForecast.City)" Caption="City" Editable="true">
<DisplayTemplate>
#{
var name = (context as WeatherForecast).City?.Name;
#name
}
</DisplayTemplate>
<EditTemplate>
#{
<Select TValue="City"
SelectedValue=
"#((City)(((CellEditContext)context).CellValue))"
SelectedValueChanged=
"#( v => ((CellEditContext)context).CellValue = v)">
#foreach (var item in Cities)
{
<SelectItem TValue="City" Value="#(item)">
#item.Name
</SelectItem>
}
</Select>
}
</EditTemplate>
</DataGridSelectColumn>
</DataGrid>
Code Section
#code {
private List<WeatherForecast> forecasts;
private List<City> Cities;
protected override async Task OnInitializedAsync()
{
forecasts = await ForecastService.GetForecastAsync(DateTime.Now); // get list of forecast
Cities = await ForecastService.GetCityListAsync(); // get list of cities
}}
Model classes
public class WeatherForecast
{
public int Id { get; set; }
public DateTime Date { get; set; }
public int Temperature { get; set; }
public City City { get; set; }
}
public class City
{
public int Id { get; set; }
public string Name { get; set; }
}

Try this workaround since Select component doesn't support complex objects binding:
<EditTemplate>
<Select
TValue="int"
SelectedValue="#((int)((City)(((CellEditContext)context).CellValue)).Id)"
SelectedValueChanged=
"#( v => ((CellEditContext)context).CellValue = Cities.First(x=> x.Id == v))">
#foreach (var item in Cities)
{
<SelectItem TValue="int" Value="#(item.Id)">#item.Name</SelectItem>
}
</Select>
</EditTemplate>

Related

Viewmodel nested checkbox not binding on post

I have a subscription form that contains a matrix of options. The form can be seen in screenshot Subscription table
I am having trouble with ASP.NET MVC generating appropriate ID's and then on postback having the binder populate the model with the form selections.
The add on name is down the left side and when posted back the collection of SubscriptionInputModel.Addons get populated ok. But SubscriptionInputModel.Addons[i].SubscriptionLevelCombos is null as seen in debug screenshot
The current code is using CheckBoxFor but I've also tried manually generating ID's in format:
#Html.CheckBox("addon[" + a + "].SubscriptionLevelCombo[" + i + "].AddonSelected", addon.SubscriptionLevelCombos[i].AddonSelected)
Neither format has worked and also experimented while debugging but no luck. I would appreciate any ideas. Worst case I assume I would need to read the raw form collection?
I assume the level of nested object shouldn't matter as it is all object path notation and array indexes in html tag names?
Here are snippets of current code to help illustrate what exists.
View Models
public class SubscriptionInputModel
{
//other stuff to come
//....
//add on's, listed down left of table
public List<SubscriptionInputAddonModel> Addons;
}
public class SubscriptionInputAddonModel
{
public int Id { get; set; }
public string Name { get; set; }
public string Note { get; set; }
public List<SubscriptionInputAddonComboModel> SubscriptionLevelCombos { get; set; }
}
public class SubscriptionInputAddonComboModel
{
public int? Id { get; set; }
public decimal? AddonCost { get; set; }
public CostTimeUnitOption? CostTimeUnit { get; set; }
public bool? IsComplimentaryBySubscriptionLevel { get; set; }
public string ComboText { get; set; }
public bool AddonSelected { get; set; }
public int? AddonId { get; set; }
}
SubscriptionController
[Route("identity/subscription")]
// GET: Subscription
public ActionResult Index()
{
SubscriptionInputModel model = new SubscriptionInputModel();
ArrbOneDbContext db = new ArrbOneDbContext();
List<SubscriptionInputAddonModel> addons = Mapper.Map<Addon[], List<SubscriptionInputAddonModel>>(db.Addons.OrderBy(a => a.OrderPosition).ToArray());
model.Addons = addons;
foreach(var addon in model.Addons)
{
var addonCombos = db.Database.SqlQuery<SubscriptionInputAddonComboModel>(#"SELECT SLA.Id, AddonCost, CostTimeUnit, IsComplimentaryBySubscriptionLevel, ComboText, AddonId
FROM SubscriptionLevel L
LEFT OUTER JOIN SubscriptionLevelAddon SLA ON L.Id = SLA.SubscriptionLevelId AND SLA.AddonId = #p0
ORDER BY L.OrderPosition", addon.Id);
addon.SubscriptionLevelCombos = addonCombos.ToList();
}
return View(model);
}
[Route("identity/subscription")]
[ValidateAntiForgeryToken]
[HttpPost]
// POST: Subscription
public ActionResult Index(SubscriptionInputModel model)
{
ArrbOneDbContext db = new ArrbOneDbContext();
List<SubscriptionInputAddonModel> addons = Mapper.Map<Addon[], List<SubscriptionInputAddonModel>>(db.Addons.OrderBy(a => a.OrderPosition).ToArray());
model.Addons = addons;
//debug breakpoint to inspect returned model values
return View();
}
Index.cshtml
#model Identity_Server._Code.ViewModel.Subscription.SubscriptionInputModel
#{
ViewBag.Title = "Subscription";
}
#using (Html.BeginForm("Index", "Subscription", new { signin = Request.QueryString["signin"] }, FormMethod.Post))
{
#Html.ValidationSummary("Please correct the following errors")
#Html.AntiForgeryToken()
...
// ADD ONs ----------------------------------------------------------------------------------
#for (int a = 0; a < Model.Addons.Count; a++)
{
var addon = Model.Addons[a];
<tr>
<td class="text-left">#addon.Name
<div class="SubscriptionItemNote">#addon.Note
#Html.HiddenFor(m => m.Addons[a].Id)
</div>
</td>
#for (int i = 0; i < addon.SubscriptionLevelCombos.Count; i++)
{
<td>
#if (addon.SubscriptionLevelCombos[i].Id.HasValue)
{
if (addon.SubscriptionLevelCombos[i].AddonCost.HasValue && addon.SubscriptionLevelCombos[i].AddonCost.Value > 0)
{
#Html.Raw("<div>+ " + #addon.SubscriptionLevelCombos[i].AddonCost.Value.ToString("0.##") + " / " + #addon.SubscriptionLevelCombos[i].CostTimeUnit.Value.ToString() + "</div>")
}
else if (addon.SubscriptionLevelCombos[i].IsComplimentaryBySubscriptionLevel.HasValue && #addon.SubscriptionLevelCombos[i].IsComplimentaryBySubscriptionLevel.Value)
{
<span class="glyphicon glyphicon-ok"></span>
}
if (!string.IsNullOrEmpty(addon.SubscriptionLevelCombos[i].ComboText))
{
<div>#addon.SubscriptionLevelCombos[i].ComboText</div>
}
if (addon.SubscriptionLevelCombos[i].AddonCost.HasValue && addon.SubscriptionLevelCombos[i].AddonCost.Value > 0)
{
#Html.HiddenFor(m => m.Addons[a].SubscriptionLevelCombos[i].Id)
#Html.CheckBoxFor(m => m.Addons[a].SubscriptionLevelCombos[i].AddonSelected)
}
}
</td>
}
</tr>
}

#Html.DisplayNameFor not showing data MVC5

I have searched around and not had much luck finding a solution to my exact problem.
Model
public class PageDetailsViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Image { get; set; }
}
Controller
public ActionResult Search(int SysID)
{
var query = from r in _db.Auctions
from d in _db.Product_Details
where SysID == d.Id && r.BidStatus == "Open" && d.Id == r.Product_DetailsId
select new PageDetailsViewModel
{
Name = d.Name,
Description = d.Description,
Image = d.Image
};
return View(query);
}
View
#model IEnumerable<ProjectT.Models.PageDetailsViewModel>
#Html.DisplayNameFor(x => x.Name)
This fails to bring the name through. However, if I use a foreach
#foreach (var item in Model)
{
#item.Name
}
It brings through the name no problem.
Any help is much appreciated.
This extension method shows the value of the DisplayNameAttribute from DataAnnotations namespace. Consider this a label. Typically it is used like this:
[DisplayName("The Name")]
public string Name { get; set; }
And in the view:
#Html.DisplayNameFor(x => x.Name) <-- displays The Name
The code above will work only if the model is a single item. For the list case, as you have, you need to do some tricks, say a for loop, so you could do something like:
#Html.DisplayNameFor(x => x[i].Name): #Model[i].Name <-- displays The Name: Bill

Radio Button For multiple bools

Say I have the following properties in my model that I want to be mutually exclusive:
public bool PrintWeek1 {get; set;}
public bool PrintWeek2 {get; set;}
public bool PrintWeek3 {get; set;}
Is it possible to render these as a set of radio buttons or do I need to change them to an enum?
If I use #Html.RadioButtonFor it renders name as the name of the property so they aren't grouped correctly.
Here comes a quick solution, let you have following properties in Model -
public bool PrintWeek1 { get; set; }
public bool PrintWeek2 { get; set; }
public bool PrintWeek3 { get; set; }
public string SelectedValue { get; set; }
Then your HTML should be like this -
#Html.RadioButtonFor(Model => Model.PrintWeek1, "PrintWeek1", new { #Name = "SelectedValue" })
#Html.RadioButtonFor(Model => Model.PrintWeek2, "PrintWeek2", new { #Name = "SelectedValue" })
#Html.RadioButtonFor(Model => Model.PrintWeek3, "PrintWeek3", new { #Name = "SelectedValue" })
Then when you submit the form, you will get the selected value in SelectedValue property.
EDIT
To Address #StephenMuecke point, created the below solution -
Create a enum -
public enum PrintWeekType
{
PrintWeek1, PrintWeek2, PrintWeek3
}
Then have a model property (instead of individual properties, have single emum property) -
public PrintWeekType SelectedValue { get; set; }
HTML should be like below -
#Html.RadioButtonFor(m => m.SelectedValue, PrintWeekType.PrintWeek1)
#Html.RadioButtonFor(m => m.SelectedValue, PrintWeekType.PrintWeek2)
#Html.RadioButtonFor(m => m.SelectedValue, PrintWeekType.PrintWeek3)
Using above sample, one can pre-select a radiobutton, at the same time we can post the selected value in SelectedValue property.
Ok I abandoned the bools and just ended up using a list - this seemed to be the quickest and easiest way to do it.
Where I initialize my model:
public PrintViewModel()
{
this.PrintTypes = new List<string>() { "Print Week 1", "Print Week 2", "Print Week 3" };
}
public List<string> PrintTypes { get; set; }
public string SelectedPrintType { get; set; }
In my view (I wanted the first option selected by default):
#for(int i = 0; i < Model.PrintTypes.Count; i++)
{
<div class="row">
<div class="col-md-2">
#(i == 0 ? Html.RadioButtonFor(x => x.SelectedPrintType, Model.PrintTypes[i], new {#checked = "checked"}) : #Html.RadioButtonFor(x => x.SelectedPrintType, Model.PrintTypes[i]))
<label for="#Model.PrintTypes[i]">#Model.PrintTypes[i]</label>
</div>
</div>
}

Two CheckBoxList

Model selects from a database list of products to their categories. On the form you want to display CheckBoxList with a list of categories and another with a list of items in this one (or more) category. Immediately after selecting the category list of products to be updated. How can this be implemented?
Thanks in advance.
I normally use below approach when dealing with checkboxes check whether it helps you.
Model:
namespace GateApplication.Models
{
public class Gate
{
public string PreprationRequired { get; set; }
public List<CheckBoxes> lstPreprationRequired{ get; set; }
public string[] CategoryIds { get; set; }
}
public class CheckBoxes
{
public int ID { get; set; }
public string Value { get; set; }
public string Text { get; set; }
public bool Checked { get; set; }
}
}
Controller:
Load CheckBox Value:
public ActionResult Create()
{
List<CheckBoxes> lstchk = new List<CheckBoxes>()
{
new CheckBoxes {Text="coduit", Value="coduit" },
new CheckBoxes {Text="safety", Value="safety" },
new CheckBoxes {Text="power", Value="power" },
new CheckBoxes {Text="access", Value="access" }
};
var model = new Gate
{
lstPreprationRequired=lstchk
};
return View(model);
}
View:
#foreach (var item in Model.lstPreprationRequired)
{
<input type="checkbox" id="#item.Value" name="CategoryIds" value="#item.Text"/>
<label for="optionId">#item.Text</label>
<br />
}
Now your view shold have list of checkboxes. Now saving CheckBox values to the database.
[HttpPost]
public ActionResult Create(Gate ViewModel,FormCollection collection)
{
try
{
Gate gate = new Gate();
if (ModelState.IsValid)
{
gate.PreprationRequired = Request.Form["CategoryIds"];// here you'll get a string containing a list of checked values of the checkbox list separated by commas
if (string.IsNullOrEmpty(gate.PreprationRequired))//this is used when no checkbox is checked
gate.PreprationRequired = "None,None";
Save();//Save to database
return RedirectToAction("Index");
}
else
{
return View();
}
}
catch
{
return View();
}
}
Now you have below kind of string in your database
safety,power,access
Now fetch the selected values and display the view.
public ActionResult Edit(int id)
{
List<CheckBoxes> lstchk = new List<CheckBoxes>()
{
new CheckBoxes {Text="coduit", Value="coduit" },
new CheckBoxes {Text="safety", Value="safety" },
new CheckBoxes {Text="power", Value="power" },
new CheckBoxes {Text="access", Value="access" }
};
var model = new Gate
{
lstPreprationRequired =lstchk,
CategoryIds = "safety,power,access".Split(',')//here get your comma separated list from database and assign it to the CategoryIds string array, i have used sample text for the values
};
return View(model);
}
View:
#foreach (var item in Model.lstPreprationRequired)
{
<input type="checkbox" id="#item.Value" name="CategoryIds" value=#item.Text"
#foreach (var c in Model.CategoryIds)
{
if(c == item.Value)
{
<text> checked="checked"</text>
}
}
<label for="optionId">#item.Text></label>
}
Let me know if this does not help you.

asp net mvc3 post a list of objects to action

I created a page with aspnet mvc3. It show all users info as a list. I want to do something with this list. There are some checkboxes that belong to each items. When I click some checkboxes and press submit button, I want to post the whole list as a collection and save each items of this collection to database. There are several notes on internet but there is no exact solution. I have a UserDto. and want to use this to transfer users data in all sections.
Does anyone have any full solution about this or can they give any idea?
Thanks in advance.
Kerem
I added some of my codes. You can see the lead sentences what they are about.
this is my index view detail:
#model List<DomainModel.UserApprovalDto>
#{
ViewBag.Title = "Manage Users";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>
Manage Users</h2>
<div>#Html.Partial("_PartialManageUsers", (List<DomainModel.UserApprovalDto>)Model) </div>
this is my partial view detail:
#model List<DomainModel.UserApprovalDto>
#using (Html.BeginForm("ConfirmUsers", "ManageUsers", FormMethod.Post))
{
<table>
<tr>
<th>
Name
</th>
<th>
Is Reported
</th>
</tr>
#for (int i = 0; i < Model.Count(); i++)
{
<tr>
<td>
#Html.DisplayFor(modelItem => Model[i].FirstName)
</td>
<td>
#Html.CheckBox("IsReported", Model[i].IsReported.HasValue ? Model[i].IsReported.Value : false)
#*#Html.CheckBoxFor(modelItem => Model[i].IsReported.Value);*# #* #if (Model[i].IsReported != null)
{
#Html.CheckBoxFor(modelItem => Model[i].IsReported.Value);
}
else
{
#Html.CheckBoxFor(modelItem => Model[i].IsReported.Value);
}*#
</td>
<td>
</td>
</tr>
}
</table>
<div>
<input name="submitUsers" type="submit" value="Save" />
</div>
}
this is my controller submit method
[HttpPost]
public ActionResult ConfirmUsers(List<DomainModel.UserApprovalDto> collection)
{
if (ModelState.IsValid)
{
//TO-DO
}
return RedirectToAction("Index");
}
this last one is my DTO class detail:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace DomainModel
{
public class UserApprovalDto
{
public long UserId { get; set; }
public Guid CarUserId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string PhotoPath { get; set; }
public string PhotoSmallPath { get; set; }
public string PhotoSquarePath { get; set; }
public string PhotoBigPath { get; set; }
public bool IsBlocked { get; set; }
public bool IsDeleted { get; set; }
}
}
when I submit this code my list return null collection to my controller method.
thanks for your comments.
Assuming you are creating a screen which adds/ remove users to a course. So let's create some viewmodels
public class CourseVM
{
public string Name { set;get;}
public int CourseID { set;get;}
public List<UserVM> Users { set;get;}
public CourseVM()
{
Users=new List<UserVM>();
}
}
public class UserVM
{
public string Name { set;get;}
public int UserID{ set;get;}
public bool IsSelected { set;get;}
}
Now in your GET Action, you will fill the values of the ViewModel and sent it to the view.
public ActionResult Add()
{
var vm = new CourseVM();
//The below code is hardcoded for demo. you may replace with DB data.
vm.Users.Add(new UseVM { Name = "Jon" , UserID=1});
vm.Users.Add(new UseVM { Name = "Scott", UserID=2 });
return View(vm);
}
Now Let's create an EditorTemplate. Go to Views/YourControllerName and Crete a Folder called "EditorTemplates" and Create a new View there with the same name as of the Property Name(UserVM.cshtml)
Add this code to your new editor template.
#model ChannelViewModel
<p>
<b>#Model.Name</b> :
#Html.CheckBoxFor(x => x.IsSelected) <br />
#Html.HiddenFor(x=>x.Id)
</p>
Now in your Main View, Call your Editor template using the EditorFor Html Helper method.
#model CourseVM
#using (Html.BeginForm())
{
<div>
#Html.EditorFor(m=>m.Users)
</div>
<input type="submit" value="Submit" />
}
Now when you post the form, Your Model will have the Users Collection where the Selected Checkboxes will be having a True value for the IsSelected Property.
[HttpPost]
public ActionResult Add(CourseVM model)
{
if(ModelState.IsValid)
{
//Check for model.Users collection and Each items
// IsSelected property value.
//Save and Redirect(PRG pattern)
}
return View(model);
}

Resources