CheckBoxList multiple selections: how to model bind back and get all selections? - templates

This code:
Html.CheckBoxList(ViewData.TemplateInfo.HtmlFieldPrefix, myList)
Produces this mark-up:
<ul><li><input name="Header.h_dist_cd" type="checkbox" value="BD" />
<span>BD - Dist BD Name</span></li>
<li><input name="Header.h_dist_cd" type="checkbox" value="SS" />
<span>SS - Dist SS Name</span></li>
<li><input name="Header.h_dist_cd" type="checkbox" value="DS" />
<span>DS - Dist DS Name</span></li>
<li><input name="Header.h_dist_cd" type="checkbox" value="SW" />
<span>SW - Dist SW Name </span></li>
</ul>
You can check multiple selections. The return string parameter Header.h_dist_cd only contains the first value selected. What do I need to do to get the other checked values?
The post method parameter looks like this:
public ActionResult Edit(Header header)

I'm assuming that Html.CheckBoxList is your extension and that's markup that you generated.
Based on what you're showing, two things to check:
The model binder is going to look for an object named Header with string property h_dist_cd to bind to. Your action method looks like Header is the root view model and not a child object of your model.
I don't know how you are handling the case where the checkboxes are cleared. The normal trick is to render a hidden field with the same name.
Also a nit, but you want to use 'label for="..."' so they can click the text to check/uncheck and for accessibility.
I've found that using extensions for this problem is error prone. You might want to consider a child view model instead. It fits in better with the EditorFor template system of MVC2.
Here's an example from our system...
In the view model, embed a reusable child model...
[AtLeastOneRequired(ErrorMessage = "(required)")]
public MultiSelectModel Cofamilies { get; set; }
You can initialize it with a standard list of SelectListItem...
MyViewModel(...)
{
List<SelectListItem> initialSelections = ...from controller or domain layer...;
Cofamilies = new MultiSelectModel(initialSelections);
...
The MultiSelectModel child model. Note the setter override on Value...
public class MultiSelectModel : ICountable
{
public MultiSelectModel(IEnumerable<SelectListItem> items)
{
Items = new List<SelectListItem>(items);
_value = new List<string>(Items.Count);
}
public int Count { get { return Items.Count(x => x.Selected); } }
public List<SelectListItem> Items { get; private set; }
private void _Select()
{
for (int i = 0; i < Items.Count; i++)
Items[i].Selected = Value[i] != "false";
}
public List<SelectListItem> SelectedItems
{
get { return Items.Where(x => x.Selected).ToList(); }
}
private void _SetSelectedValues(IEnumerable<string> values)
{
foreach (var item in Items)
{
var tmp = item;
item.Selected = values.Any(x => x == tmp.Value);
}
}
public List<string> SelectedValues
{
get { return SelectedItems.Select(x => x.Value).ToList(); }
set { _SetSelectedValues(value); }
}
public List<string> Value
{
get { return _value; }
set { _value = value; _Select(); }
}
private List<string> _value;
}
Now you can place your editor template in Views/Shared/MultiSelectModel.ascx...
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<WebUI.Cofamilies.Models.Shared.MultiSelectModel>" %>
<div class="set">
<%=Html.LabelFor(model => model)%>
<ul>
<% for (int i = 0; i < Model.Items.Count; i++)
{
var item = Model.Items[i];
string name = ViewData.ModelMetadata.PropertyName + ".Value[" + i + "]";
string id = ViewData.ModelMetadata.PropertyName + "_Value[" + i + "]";
string selected = item.Selected ? "checked=\"checked\"" : "";
%>
<li>
<input type="checkbox" name="<%= name %>" id="<%= id %>" <%= selected %> value="true" />
<label for="<%= id %>"><%= item.Text %></label>
<input type="hidden" name="<%= name %>" value="false" />
</li>
<% } %>
</ul>
<%= Html.ValidationMessageFor(model => model) %>
Two advantages to this approach:
You don't have to treat the list of items separate from the selection value. You can put attributes on the single property (e.g., AtLeastOneRequired is a custom attribute in our system)
you separate model and view (editor template). We have a horizontal and a vertical layout of checkboxes for example. You could also render "multiple selection" as two listboxes with back and forth buttons, multi-select list box, etc.

I think what you need is how gather selected values from CheckBoxList that user selected and here is my solution for that:
1- Download Jquery.json.js and add it to your view as reference:
2- I've added a ".cssMyClass" to all checkboxlist items so I grab the values by their css class:
<script type="text/javascript" >
$(document).ready(function () {
$("#btnSubmit").click(sendValues);
});
function populateValues()
{
var data = new Array();
$('.myCssClas').each(function () {
if ($(this).attr('checked')) {
var x = $(this).attr("value");
data.push(x);
}
});
return data;
}
function sendValues() {
var data = populateValues();
$.ajax({
type: 'POST',
url: '#Url.Content("~/Home/Save")',
data: $.json.encode(data),
dataType: 'json',
contentType: 'application/json; charset=utf-8',
success: function () { alert("1"); }
});
}
</script>
3- As you can see I've added all selected values to an Array and I've passed it to "Save" action of "Home" controller by ajax 4- in Controller you can receive the values by adding an array as argument:
[HttpPost]
public ActionResult Save(int[] val)
{
I've searched too much but apparently this is the only solution. Please let me know if you find a better solution for it.

when you have multiple items with the same name you will get their values separated with coma

Related

Why listing of items is empty in calling method of the component?

with livewire 2 I have listing of items ($itemDataRows var) and I need for any item show checkbox ($selectedItems var) and
"Select all" button and clicking on this button all items must be selected. I do :
class CrudItems extends Component
{
private $itemDataRows = [];
public $selectedItems = [];
...
public function render()
{
...
$this->itemDataRows = Item
::orderBy('created_at', 'desc')
...
->paginate($backend_per_page);
return view('livewire.admin.items.crud-items', [
'itemDataRows' => $this->itemDataRows,
'item_rows_count' => $this->item_rows_count
])->layout('layouts.admin');
}
}
public function calcSelectedItemsCount()
{
$ret= 0;
foreach( $this->selectedItems as $next_key=>$next_value ) {
if($next_value) {
$ret++;
}
}
return $ret;
}
public function selectAllItems()
{
$this->selectedItems= [];
\Log::info( dump($this->itemDataRows, ' -0 $this->itemDataRows selectAllItems::') );
// INL OG FILE_I SEE THAT ARRAY ABOVE IS EMPTY!!!
foreach( $this->itemDataRows as $nextItemDataRow ) {
$this->selectedItems[$nextItemDataRow->id] = true;
\Log::info( dump($this->selectedItems, ' -$this->selectedItems INSIDE selectAllItems::') );
}
\Log::info( dump($this->selectedItems, ' -$this->selectedItems selectAllItems::') );
}
and in template :
$selectedItems::{{ var_dump($selectedItems) }}<hr>
$itemDataRows::{{ $itemDataRows }}
/* $selectedItems is filled ok when I click on checkboxes , but $itemDataRows shows empty var, though I filled items listing below */
#foreach ($itemDataRows as $item)
<tr>
<td class=" whitespace-nowrap ">
<x-jet-checkbox id="is_reopen" type="checkbox" class="editor_checkbox_field ml-4" title="On saving editor will be opened"
wire:model="selectedItems.{{ $item->id }}"/>
</td>
Is something wrong with definition of $itemDataRows ? Why $itemDataRows is empty in selectAllItems method, but on my template all items are visible ok....
Thanks in advance!
In Livewire you can pass the data via the class variables. And in the mount function you can fill the variable. For Example.
Important Note: The Class Vars must be public!
public $selectedItems = [];
public function mount(): void
{
$this->selectedItems = ['data' => 'Hello World'];
}
public function render()
{
return view('livewire.admin.items.crud-items')->layout('layouts.admin');
}
Update
This must have something to do with the Livewire Lifecyle. Every Livewire component goes through a lifecycle. Lifecycle hooks allow you to run code at any stage of the component's lifecycle or before updating certain properties. In your case, use the mount hook.
You initialise the variable itemDataRows in the render function. A request then calls the method selectAllItems. There you have to initialise itemDataRows again, because the state is no longer there during render or mount.
Solution: create a method getItemDataRows()
private getItemDataRows()
{
$this->itemDataRows => Item::orderBy('created_at', 'desc')
...
->paginate($backend_per_page);
}
then you can call those in the render method and in the selectAllItems method too.
public function selectAllItems()
{
$this->selectedItems= [];
$this->itemDataRows => $this->getItemDataRows();
...
// your code
}

dictionary requires a model item of type 'Projectname.Sp name'

i am doing a sample where i require stored procedure result have to be displayed in view(created strongly typed view by choosing details in dropdown).from service i am getting result in this format
[{"studentID":1,"studentName":"Suja","StudentDepaertment":"Economics"}]
but i get error
The model item passed into the dictionary is of type 'System.Collections.Generic.List`1[StudentForm.sp_join_details_Result]', but this dictionary requires a model item of type 'StudentForm.sp_join_details_Result'.
Homecontroller.cs
public ActionResult Details(string name)
{
studentEntities data = new studentEntities();
List<sp_join_details_Result> model = null;
var client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:99991/");
var response = client.GetAsync(string.Format("api/Details?name={0}", name)).Result;
var responseBody = response.Content.ReadAsStringAsync();
responseBody.Wait();
var productString = Newtonsoft.Json.JsonConvert.DeserializeObject<List<sp_join_details_Result>>(responseBody.Result);
return View(productString);
}
Details.cshtml
#model EmployAppraisalForm.sp_join_details_Result
#{
ViewBag.Title = "Details";
}
<h2>Details</h2>
<fieldset>
<legend>sp_join_details_Result</legend>
<div class="display-label">
#Html.DisplayNameFor(model => model.studentID)
</div>
<div class="display-field">
#Html.DisplayFor(model => model.studentID)
</div>
<div class="display-label">
#Html.DisplayNameFor(model => model.studentName)
</div>
<div class="display-field">
#Html.DisplayFor(model => model.studentName)
</div>
</fieldset>
I know i have to covert the response of service to result that could be displayed in view,to which i should convert ?Please help me in this.....
Your Details() method is returning List<sp_join_details_Result> to a view which expects a single sp_join_details_Result object.
If you want to display a single object, then change the return statement to
return View(productString.FirstOrDefault());
Alternatively, if your wanting to display a collection of objects, change the view to
#model List<sp_join_details_Result>
#foreach(var item in Model)
{
....

MVC 4 - sorting with LINQ doesn't work with Ajax.BeginForm and my For loop

I writing some code with C# and MVC and I have button for sorting a list of data by asc and desc. The logic works in my controller, I am able to call the method that sorts the list and in the breakpoint I can see that it has been sorted.
But it's weird because when I loop through my list in the partial view it never works. I use a breakpoint in my view to make sure it's the same order of items which it is. But it's like the new values don't render to the screen.
TeamManagement.cshtml
#model Website.Models.modelTeamSelect
#{
ViewBag.Title = "Football App";
}
#section featured {
}
#using (Ajax.BeginForm("_PartialTeams",
new
{
model = this.Model
},
new AjaxOptions
{
HttpMethod = "POST",
UpdateTargetId = "divCreatedTeams",
InsertionMode = InsertionMode.Replace
}))
{
<div id="divTeams" style="float: left; padding: 10px;">
<h3>Create a new team:</h3>
#Html.LabelFor(m => m.team.TeamName)
#Html.TextBoxFor(m => m.team.TeamName)
<input type="submit" value="Add Team" name="btnSubmit" />
</div>
Html.RenderPartial("~/Views/Partials/_PartialTeams.cshtml");
}
_PartialTeams.cshtml
#model Website.Models.modelTeamSelect
<div id="divCreatedTeams" style="float: left; padding: 10px;">
<h3>Your created teams:</h3>
<input type="submit" value="Asc" name="btnSubmit" />
<input type="submit" value="Desc" name="btnSubmit" />
<br />
#if (Model.teams.Count > 0)
{
for (int i = 0; i < Model.teams.Count; i++)
{
#Html.EditorFor(m => m.teams[i].TeamName)
<input type="button" value="Update team name" name="btnSubmit"/>
<input type="button" value="Remove team" name="btnSubmit"/>
<br />
}
}
</div>
Sorting logic in my controller
[HttpPost]
public PartialViewResult _PartialTeams(string BtnSubmit, modelTeamSelect modelTeamSelect)
{
switch (BtnSubmit)
{
case "Add Team":
modelTeamSelect.teams.Add(modelTeamSelect.team);
break;
case "Asc":
FootballRepository = new Repository.FootballRepository();
modelTeamSelect.teams = FootballRepository.Sort(modelTeamSelect, BtnSubmit);
break;
case "Desc":
FootballRepository = new Repository.FootballRepository();
modelTeamSelect.teams = FootballRepository.Sort(modelTeamSelect, BtnSubmit);
break;
}
return PartialView("~/Views/Partials/_PartialTeams.cshtml", modelTeamSelect);
}
public List<Models.modelTeam> Sort(Models.modelTeamSelect modelTeamSelect, string sort)
{
switch (sort)
{
case "Asc":
modelTeamSelect.teams = modelTeamSelect.teams.OrderBy(t => t.TeamName).ToList();
break;
case "Desc":
modelTeamSelect.teams = modelTeamSelect.teams.OrderByDescending(t => t.TeamName).ToList();
break;
}
return modelTeamSelect.teams;
}
My main model with team collection
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace Website.Models
{
public class modelTeamSelect
{
public modelTeamSelect()
{
teams = new List<modelTeam>();
team = new modelTeam();
}
public List<modelTeam> teams { get; set; }
public modelTeam team { get; set; }
}
}
My method Sort does it's job but in the view it never displays correctly. e.g. always wrong order.
Anyone have any ideas because I am stuck.
Screenshots
In the screenshots I click sort by Asc and you can see it says Newcastle as the first item in the list. But when the page renders it will say West Ham first even though it is iterating using the for loop.
All the Html helpers are preferring to use the ModelState values over the actual model values.
So even you have sorted in place your modelTeamSelect.teams in your action in the view #Html.EditorFor(m => m.teams[i].TeamName) call will use the original (before sorting) values form the ModelState.
The solution: if you are updating your action parameters in-place then just clear the ModelState before returning the View/PartialView:
[HttpPost]
public PartialViewResult _PartialTeams(string BtnSubmit,
modelTeamSelect modelTeamSelect)
{
// ... Do the sorting, etc.
ModelState.Clear();
return PartialView("~/Views/Partials/_PartialTeams.cshtml", modelTeamSelect);
}
You can read more about why the helpers are working like this in this article: ASP.NET MVC Postbacks and HtmlHelper Controls ignoring Model Changes

Rails or Ember object model breaking browser's pushState functionality?

** I'm using Ember.Object instead of Ember Data, to pull data from an api and I believe that might be causing the issue.**
I have my resources nested as so:
Mdm.Router.map ->
#resource "groups", ->
#resource "group", path: ':group_id'
'/groups/ loads a list of all groups on the left side of the browser. Each group is linking to its specific group_id. When clicked, the 'group' template renders on the right side of the screen, showing details of one group while the list remains to the left.
However, when you click the back button, or manually enter the group_id into the url the individual groups dont render. The url will update in the browser window, but the content wont change to match it.
I have the singular 'group' template rendering inside of the 'groups' template with the {{outlet}}.
My groups_route.js.coffee looks like this:
Mdm.GroupsRoute = Ember.Route.extend(model: ->
Mdm.Group.all()
)
application.hbs:
<div class="container">
<div class="nav-bar">
<img src="assets/logo_loginbox.png" class="logo">
<ul class="nav-menu">
<li>GROUPS</li>
<li>USERS</li>
</ul>
</div><!-- nav-bar -->
<hr>
{{outlet}}
</div><!-- container -->
groups.hbs:
<h1>Groups</h1>
{{ partial groupsList }}
<div class="group">
{{outlet}}
</div><!-- group -->
group.hbs:
<h1>{{name}}</h1>
I'm getting the following error in the console when I use the back button or try to load the page with the group_id present:
Uncaught TypeError: Object function () {
if (!wasApplied) {
Class.proto(); // prepare prototype...
}
o_defineProperty(this, GUID_KEY, undefinedDescriptor);
o_defineProperty(this, '_super', undefinedDescriptor);
var m = meta(this);
m.proto = this;
if (initMixins) {
// capture locally so we can clear the closed over variable
var mixins = initMixins;
initMixins = null;
this.reopen.apply(this, mixins);
}
if (initProperties) {
// capture locally so we can clear the closed over variable
var props = initProperties;
initProperties = null;
var concatenatedProperties = this.concatenatedProperties;
for (var i = 0, l = props.length; i < l; i++) {
var properties = props[i];
Ember.assert("Ember.Object.create no longer supports mixing in other definitions, use createWithMixins instead.", !(properties instanceof Ember.Mixin));
for (var keyName in properties) {
if (!properties.hasOwnProperty(keyName)) { continue; }
var value = properties[keyName],
IS_BINDING = Ember.IS_BINDING;
if (IS_BINDING.test(keyName)) {
var bindings = m.bindings;
if (!bindings) {
bindings = m.bindings = {};
} else if (!m.hasOwnProperty('bindings')) {
bindings = m.bindings = o_create(m.bindings);
}
bindings[keyName] = value;
}
var desc = m.descs[keyName];
Ember.assert("Ember.Object.create no longer supports defining computed properties.", !(value instanceof Ember.ComputedProperty));
Ember.assert("Ember.Object.create no longer supports defining methods that call _super.", !(typeof value === 'function' && value.toString().indexOf('._super') !== -1));
if (concatenatedProperties && indexOf(concatenatedProperties, keyName) >= 0) {
var baseValue = this[keyName];
if (baseValue) {
if ('function' === typeof baseValue.concat) {
value = baseValue.concat(value);
} else {
value = Ember.makeArray(baseValue).concat(value);
}
} else {
value = Ember.makeArray(value);
}
}
if (desc) {
desc.set(this, keyName, value);
} else {
if (typeof this.setUnknownProperty === 'function' && !(keyName in this)) {
this.setUnknownProperty(keyName, value);
} else if (MANDATORY_SETTER) {
Ember.defineProperty(this, keyName, null, value); // setup mandatory setter
} else {
this[keyName] = value;
}
}
}
}
}
finishPartial(this, m);
delete m.proto;
finishChains(this);
this.init.apply(this, arguments);
} has no method 'find' application.js:51233
Mdm.GroupRoute.Ember.Route.extend.model application.js:51233
superWrapper application.js:12849
Ember.Route.Ember.Object.extend.deserialize application.js:36503
collectObjects application.js:35614
proceed application.js:35638
(anonymous function) application.js:1193
fire application.js:1038
self.fireWith application.js:1149
(anonymous function) application.js:1200
fire application.js:1038
self.fireWith application.js:1149
done application.js:8075
script.onload.script.onreadystatechange

MVC Templated Helper - DropDown

Using the templated helpers in MVC2.0 I ran into a dillema, how to get the items to fill a dropdownlist.
I am using a [UIHint(BadgesDropDown)] attribute, but how will i get the list items without violating the MVC Pattern, should the controller place them in the ViewData? Should the BadgesDropDown.ascx invoke a Helper to get them ?
Right now i am going for:
BadgesDropDown.ascx
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<%= Html.DropDownList("", ViewData["Badges"] as IEnumerable<SelectListItem>)%>
Controller
ViewData["Badges"] = new SelectList(SiteRepository.GetBadges(), "RowKey", "BadgeName");
Is this the way to go ?
There's been a lot of discussion about this topic recently. Similar roadblocks are encountered with dates, date ranges and multi-select checkbox lists. Anywhere you might want to use a rich set of html controls. I've been experimenting with the concept of child ViewModels and I think the solution is cleaner than other approaches I've tried.
The basic concept is that you define a small view model that's closely coupled to a custom EditorTemplate.
In your example, we would start with a (child) ViewModel that's specific to a single select list:
public class SelectModel
{
#region SelectModel(string value, IEnumerable<SelectListItem> items)
public SelectModel(string value, IEnumerable<SelectListItem> items)
{
_value = value;
Items = new List<SelectListItem>(items);
_Select();
}
#endregion
// Properties
public List<SelectListItem> Items { get; private set; }
public string Value
{
get { return _value; }
set { _value = value; _Select();}
}
private string _value;
// Methods
private void _Select()
{
Items.ForEach(x => x.Selected = (Value != null && x.Value == Value));
}
}
In the view model that wants to use the dropdown you compose the select model (we're all using view models, right?):
public class EmailModel
{
// Constructors
public EmailModel()
{
Priority = new SelectModel("normal", _ToPrioritySelectItems());
}
// Properties
public SelectModel Priority { get; set; }
// Methods
private IEnumerable<SelectListItem> _ToPrioritySelectItems()
{
List<SelectListItem> result = new List<SelectListItem>();
result.Add(new SelectListItem() { Text = "High", Value = "high" });
...
}
Note this is a simple example with a fixed set of dropdown items. If they are coming from the domain layer, the controller passes them into the ViewModel.
Then add an editor template SelectModel.ascx in Shared/EditorTemplates
<%# Control Inherits="System.Web.Mvc.ViewUserControl<SelectModel>" %>
<div class="set">
<%= Html.LabelFor(model => model) %>
<select id="<%= ViewData.ModelMetadata.PropertyName %>_Value" name="<%=ViewData.ModelMetadata.PropertyName %>.Value">
<% foreach (var item in Model.Items) { %>
<%= Html.OptionFor(item) %>
<% } %>
</select>
</div>
Note: OptionFor is a custom extension that does the obvious
The trick here is that the id and name are set using the compound format that the default ModelBinder expects. In our example "Priority.Value". So the string based Value property that is defined as part of SelectModel is set directly. The setter takes care of updating the list of Items to set the default select option if we need to redisplay the form.
Where this "child view model" approach really shines is more complex "control snippets of markup". I now have child view models that follow a similar approach for MultiSelect lists, Start/End date ranges, and Date + time combinations.
As soon as you go down this path, the next obvious question becomes validation.
I ended up having all of my child ViewModel's implement a standard interface:
public interface IValidatable
{
bool HasValue { get; }
bool IsValid { get; }
}
Then, I have a custom ValidationAttribute:
public class IsValidAttribute : ValidationAttribute
{
// Constructors
public IsValidAttribute()
{
ErrorMessage = "(not valid)";
}
// Properties
public bool IsRequired { get; set; }
// Methods
private bool Is(object value)
{
return value != null && !"".Equals(value);
}
public override bool IsValid(object value)
{
if (!Is(value) && !IsRequired)
return true;
if (!(value is IValidatable))
throw new InvalidOperationException("IsValidAttribute requires underlying property to implement IValidatable");
IValidatable validatable = value as IValidatable;
return validatable.IsValid;
}
}
Now you can just put attributes on properties that are child ViewModel based like any scalar property:
[IsValid(ErrorMessage = "Please enter a valid start date/time")]
public DateAndTimeModel Start { get; set; }
In the MVC 2 a great new method... which if used relies on all the attribute data.
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master"
Inherits="System.Web.Mvc.ViewPage<glossaryDB.EntityClasses.AssociationEntity>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Association: Edit
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderID="MainContent" runat="server">
<h3>Association: Edit</h3>
<% using (Html.BeginForm()) { %>
<fieldset style="padding: 1em; margin: 0; border: solid 1px #999;">
<%= Html.ValidationSummary("Edit was unsuccessful. Please correct the errors and try again.") %>
<%= Html.EditorForModel() %>
<input type="submit" value=" Submit " />
</fieldset>
<% } %>
<p><%= Html.ActionLink("Details", "Index") %></p>
</asp:Content>
For this to work there is 2 options. Either the UIHint has to provide the source of the data or the controller must. If the UIHint does then the data provided to thhe dropdown is fixed. The other option is the controller, which allows us to switch out the dropdown data with a different set of data as reqired.
There is some related examples I found:
Nerd Dinner
[1]: searcch for codeclimber.net.nz and how-to-create-a-dropdownlist-with-asp.net-mvc
[2]: bradwilson.typepad.com and templates-part-5-master-page-templates
I implemented the solution as the above example. One thing to be noted is that Helpers should only work with the data supplied to them, see View dependency
The best practice is to write Html
helpers unaware of controllers and
contexts. They should do their job
only based on what data is supplied by
the caller.
I agree on the above statement. It's just that a lot of work needs to be done when compared to the regular ASP.Net development.