How to have a conditional checked, disabled, ... with the html helper? - html

For a view, I've to generate some checkbox.
I've one collection of items:
public class ItemSelection
{
public int Id { get; set; }
public String Name { get; set; }
public Boolean IsSelected { get; set; }
public Boolean IsActive { get; set; }
}
and in the view, I'm iterating on this
#foreach(ItemSelection item in Model.Items){
Html.CheckBoxFor(m=>item.IsSelected)//HERE I WOULD LIKE TO HAVE DISABLED properties if I've a IsActive=falsel
Html.HiddenFor(m=>item.Id)
}
Now I see that I can do a "if" in which I create a different HtmlAttribute array, depending of this property, but is there a way to create only one array
new {disabled=item.IsActive?"ONE_SPECIAL_VALUE_HERE":"disabled"}
I tried to put false, or some other things, nothing worked.

You can't avoid the if:
The problem is with the special nature of the disabled attribute because there is no "special" value which would make your sample work, because:
"disabled" is only possible value for this attribute. If the input
should be enabled, simply omit the attribute entirely.
So you need to omit the attribute to enable the control but all HTML helpers will serialize all the properties of the anonymous objects passed in as the htmlattributes. And there is no way to conditionally add properties to anonymous types.
However if you have multiple common attributes for the enable/disable case, and you don't want to create two anonymoues types, you can put the attributes in a dictionary with the optional disabled attribute and use the dictionary as the htmlAttributes:
var checkboxHtmlAttributes = new Dictionary<string, object>
{{"attibute1", "value1"},
{"attribute2", "value2"}};
if (!item.IsActive)
{
checkboxHtmlAttributes.Add("disabled", "disabled");
}
#Html.CheckBoxFor(m=>item.IsSelected, checkboxHtmlAttributes)

Related

Why does parsing C# classes into JSON automatically lowercases the second character of property?

I just noticed that when a class property is serialized into JSON, the second character is always lowercased if it's not a word.
Example
public class Address
{
public string ABLot { get; set; }
public string ACBasedLot { get; set; }
public string ADLot { get; set; }
public string PostalCode = "99999"; // initialize properties to generate sample data
}
When serialized into JSON is:
{
"postalCode": "99999",
"abLot": null,
"acBasedLot": null,
"adLot": null
}
It's not just the second letter, the first letter is lowercased as well. It looks like you're using the CamelCaseNamingStrategy (which according to the documentation isn't the default). It's just found the first thing that looks like a word that isn't at the start of the name (so Lot, Based, and Code) and made everything prior to that lower case.
There is some configuration available for that class, or you can write your own NamingStrategy instead (or use the default, which leaves the property names untouched).

Save DropDownListFor multiple selected values

How can I save data for a multiple DropDownListFor? I have this code but the selected items aren't reaching the controller.
My code view:
#Html.DropDownListFor(model => model.Ingredients, new SelectList(Model.ListIngredients, "Id", "Description"), "Select the ingredients", new { multiple = "multiple", })
My model:
public ICollection<Ingredient> Ingredients { get; set; }
public ICollection<Ingredient> ListIngredients{ get; set; }
Ingredient.cs:
public string Description { get; set; }
public int Id { get; set; }
I have to change the name and id of my helper for data to be saved?
You are trying to bind the selected values into a collection of Ingredients.
However if you take a look at the posted values, they will look something like this:
...Ingredients=1&Ingredients=2...
That means the model binder will not know how to convert values like 1,2 (which are the ingredient Ids) into instances of the ingredient class.
You need to change the property of your view model where you will get the ingredients selected by the user, so instead of a collection of Ingredients:
public ICollection<Ingredient> Ingredients { get; set; }
you have a collection of Ids (which is the property you are using as the value of the multi-select), for example:
public int[] SelectedIngredients { get; set; }
(I have renamed the property but that is not strictly needed, I have done it just for clarity)
As a suggestion, I would remove the default option value from that multi-select (The "Select the ingredients" option) and use a label or tooltip. Otherwise it may look like any other value from the list and you will have to deal with the scenario where the user selects that option, which is not a valid ingredient.
Even better, I would also use the #Model.ListBoxFor helper as that will let everybody know they are dealing with a multi-select. Otherwise (with the drop down helper) you may not realize it is a multi-select unless you look at the attributes. Something like:
#Html.ListBoxFor(m=>m.SelectedIngredients, new SelectList(Model.ListIngredients, "Id", "Description"))
Of course, these are just suggestions. You can happily Ignore them if they don't apply to your requirements!
As a final comment, it is possible to convert the posted values into Ingredient objects as you initially had in your view model. That would require you to write your own model binder and configure it for the Ingredient type. However you will only receive the id in the posted values, so you would have to retrieve the other properties (from the database probably) or you will have a collection of Ingredient objects where only the Id is populated. I would not go down this route unless you really need to.

MVVMCrosscore binding issue tableview, collection

Following is the Observable collection which is available in the viewmodel:
ObservableCollection<Category> productcat;
further split of Category class is as follows:
public class Category
{
public string CategoryName { get; set;}
public List<ProductData> Products
{
get;
set;
}
}
ProductData class as follows:
public class ProductData
{
public string ProductImageUri { get; set;}
public string ProductTitle { get; set;}
public float productcost { get; set;}
}
Part 1:
Now I have UIScrollView where each Scrollbar item is a button item, containing title as the CategoryName.
I want to do the binding of CategoryName from the observable collection- to each scrollbar button title
Whenever the collection changes the buttons in the UISCrollView titles must reflect the change. What would be the binding expression in this case given the above class structures. Its challenging to figure out binding syntax.
Part 2:
I have a UITableView which would contain a cell having product title, product image and product cost, which means i have list
I want to bind this UITableView to this list which is part of Observable Collection->CategoryName->ProductData list
What would be the binding expression in this case. I hope we must do custom binding here.?
Yes as you said MvxTableViewCell etc., are available.
And regarding custom binding should I do it in the minisetup?
view models/data structures
In your data structures, you seem to be binding public fields instead of public properties.
These won't work straight away - you need to use properties
So:
public string CategoryName;
public class ProductData
{
public string ProductImageUri;
public string ProductTitle;
public float productcost;
}
need to become:
public string CategoryName {get;set;}
public class ProductData
{
public string ProductImageUri {get;set;}
public string ProductTitle {get;set;}
public float productcost {get;set;}
}
I am not able to find any of these classes: MvxCollectionViewSource, MvxTableViewCell, MvxCollectionViewController etc
MvxCollectionViewSource and MvxTableViewCell should be available in MvvmCross.Binding.Touch.dll
MvxCollectionViewController is not available - it's an MvvmCross View
In this case how can I achieve binding in CrossCore environment?
This is shown in the N+1 N=39 tutorial - http://slodge.blogspot.com.au/2013/09/n39-crosslight-on-xamariniosmonotouch.html - with source in https://github.com/slodge/NPlus1DaysOfMvvmCross/tree/master/N-39-CrossLight-Touch
Also I should be able to do custom bindings?
Yes
But I am not having any setup class.. in my case. How can achieve custom binding in this case?
As shown in the N+1 N=39 tutorial, you still have a setup class - so you can put your initialization code in there. After the binding builder has been initialized, you can access the IMvxTargetBindingFactoryRegistry using Mvx.Resolve<IMvxTargetBindingFactoryRegistry>()
Because you are not using MvvmCross - because you are choosing to build your own framework - then ensuring setup is done is your own job to do.
What I am currently doing is have a view class and derive the view from IMvxBindable that's all and doing some binding in it
I have no idea what this means - please try including a working code sample in your questions.

IEnumerable in my ViewModel is not displayed with EditorForModel

ViewModel
[Validator(typeof(ProdutoCategoriaValidator))]
public class ProdutoCategoriaViewModel
{
[HiddenInput(DisplayValue = false)]
public Guid ID { get; set; }
public IEnumerable<SelectListItem> Tipos { get; set; } // <<<<------- Is not showing in my view
[AdditionalMetadata("data-bind", "event: { change: function(data) { Link(data.Nome()); }}")]
public string Nome { get; set; }
[DataType(DataType.Url)]
[AdditionalMetadata("Prefixo", "Produtos/{tipo-de-produto}#")]
public string Link { get; set; }
public int? Ordem { get; set; }
public ProdutoCategoriaViewModel()
{
ID = Guid.NewGuid();
}
}
Solution
View (_Formulario.cshtml)
#model ProdutoCategoriaViewModel
#using (Html.BeginForm(null, null, FormMethod.Post, new { id="form-produtocategoria", data_bind = "submit: salvar" }))
{
#Html.AntiForgeryToken()
<legend>#Html.MvcSiteMap().SiteMapTitle()</legend>
<fieldset>
#Html.ValidationSummary(false, "Verifique os erros abaixo:")
#Html.EditorForModel()
</fieldset>
<div class="buttons">
#Html.ActionLink("Cancelar", "Index")
<input type="submit" value="SALVAR" />
</div>
}
SelectListItem.cshtml
#model IEnumerable<SelectListItem>
#Html.DropDownListFor(m => m, Model)
<p>Test</p>
Result
Full image: http://i.imgur.com/I7HxA.png
Notes
I've tried to put the attribute "UIHint" but still nothing is displayed!
Questions
What am I doing wrong?
By default when you use Html.EditorForModel don't expect this to recurse down to complex properties such as your Tipos property which is of type IEnumerable<SelectListItem>. Brad Wilson explained this in his blog post (more specifically read the Shallow Dive vs. Deep Dive section towards the end of the post). You will need to write a custom editor template for the Object type if you want this to happen.
Another possibility is to specify the template name:
#Html.EditorFor(x => x.Tipos, "SelectListItem")
Also bear in mind that your editor template for the SelectListItem is wrong because you are binding the DropDownListFor to the model as first argument. Don't forget that the first argument of this helper must be a scalar property that will be used to hold the selected value. You need a string or integer property on your view model for this. The second argument represents the collection.
Another important aspect about editor templates is that when you have a property of type IEnumerable<T> and an editor template called T.cshtml this editor template must be strongly typed to the T class and not IEnumerable<T> as you did with your SelectListItem.cshtml template. This doesn't apply if you use UIHint or specify the template name as second argument to the EditorFor helper. n this case the template will be typed to the collection.
So to recap, you could either implement a custom object editor template as Brad Wilson suggested that will recurse down to complex properties or you could modify your _Formulario.cshtml view to specify EditorFor each individual elements.
A #foreach loop renders something that looks right, but the resulting markup will have the same id for each row's controls. It also will not post the enumerable collection back with the model instance.
There are two ways to make this work such that you have a unique id for each item in the collection, and so that the collection is hydrated on postbacks:
1. Use the default editor template rather than a named one
// editor name parameter must be omitted; default editor template's model type
// should be a single instance of the child object, not an IEnumerable. This
// convention looks wrong, but it fully works:
#Html.EditorFor(parentObject => parentObject.Items)
2. Use a #for loop, not a #foreach:
#for (int i = 0; i < parentObject.Items.Count ; i++) {
// the model binder uses the indexer to disambiguate the collection items' controls:
#Html.EditorFor(c => Model.Items[i], "MyEditorTemplate")
}
This will not work, however:
// this will error out; the model type will not match the view's at runtime:
#Html.EditorFor(parentObject => parentObject.Items, "MyEditorTemplate")
Nor will this:
#foreach(var item in parentObject.Items) {
// this will render, but won't post the collection items back with the model instance:
#Html.EditorFor(c => item, "MyEditorTemplate")
}
For a detailed answer why this is, look at this question: MVC can't override EditorTemplate name when used in EditorFor for child object.

Cannot get my html input array to serialize into a List<string> in Asp.Net mvc

I am attempting to implement a tagging system into my asp.net MVC project. When a user edits or adds a task, they can add any amount of tags they want before submitting. I am using the Jquery Tagit plugin, so when a user adds a new tag an input field is created that looks like:
<input type="hidden" style="display:none;" value="tag1" name="Tags[]">
When the user presses the submit button after adding a few tags, the browser sends the following querystring to the server (retrieved via fiddler):
IsNew=True&Id=2222&Title=Test+Title&Description=Test+Description&Tags%5B%5D=Tag1&Tags%5B%5D=blah&Tags%5B%5D=another-tag
Now my viewmodel that I am serializing this data into has the following structure:
public class KnowledgeBaseTaskViewModel
{
public int Id { get; set; }
[Required(AllowEmptyStrings=false, ErrorMessage="Task title is required")]
[StringLength(500)]
public string Title { get; set; }
[Required(AllowEmptyStrings=false, ErrorMessage="Task description is required")]
[StringLength(500)]
public string Description { get; set; }
public List<string> Tags { get; set; }
public bool IsNew { get; set; } // Needed to determine if we are inserting or not
}
Finally my receiving action has the following signature:
[HttpPost]
public ActionResult EditTask(KnowledgeBaseTaskViewModel task)
The issue is that my tag list is not serializing correctly, and my List Tags is null. I have looked at various questions on this site on how to serialize arrays but I still cannot see what I am doing wrong. Any help is greatly appreciated.
It sounds like what you've got should work, but try changing the type of Tags property from List to IList. the model binder might not be using the concrete List<> type.
also, check out this article by Phil Haack: http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx