Creating custom UI elements en MVC 3 - asp.net-mvc-3

In my project I have the requirement where certain fields depending of a certain condition should be editable or just readonly.
So I though that doing this for every field was an overkill
#if (Model.CanEdit)
{
#Html.TextBoxFor(model => Model.Foo, new { width = "100px" })
}
else
{
#Html.TextBox(model => Model.Foo, new { width = "100px", #readonly = "readonly"})
}
I decided to use an editor template, but then I realised that the width cannot be fixed, so I was wondering what's the best way to send parameters to an Editor template? Also it should handle the scenario that width may not be defined and not use the width property at all. I found that ViewData may help with this, but having a code that looks like this makes me feel I'm doing something wrong.
#inherits System.Web.Mvc.WebViewPage<string>
#if (Model.CanEdit)
{
#if(ViewData["width"] == null)
{
#Html.TextBox("", Model, new { width = ViewData["width"].ToString() })
}
else
{
#Html.TextBox("", Model)
}
}
else
{
#if(ViewData["width"] == null)
{
#Html.TextBox("", Model, new { width = ViewData["width"].ToString() , #readonly = "readonly"})
}
else
{
#Html.TextBox("", Model, new {#readonly = "readonly"})
}
}
I wonder if there could be a way to create a helper so I could make something like:
#MyTextBoxFor(model => Model.Foo, true) #* true would be or readonly *#
#MyTextBoxFor(model => Model.Foo, true, 100) #* 100 would be the width length *#

kapsi's answer is great, but if you want to use your helper in a strongly typed view, here's the basic syntax. It's a little sloppy, but you can add overloads as you see fit.
public static class MyHelpers
{
public static MvcHtmlString MyTextBoxFor<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
bool flagReadonly,
int? widthProperty) where TModel : class
{
MemberExpression memberExpression = expression.Body as MemberExpression;
string parameterName = memberExpression.Member.Name;
return new MvcHtmlString(
string.Format("<input id=\"{0}\" name=\"{0}\" {1} {2} />",
parameterName, // name and if of parameter
flagReadonly ? "readonly=\"readonly\"" : string.Empty,
widthProperty.HasValue ?
string.Format("width=\"{0}px\"", (int)widthProperty) :
string.Empty));
}
}
Of course, this way, you're afforded the ability to strongly type your view elements
#Html.MyTextBoxFor(model => model.Foo, true)
and
#Html.MyTextBoxFor(model => model.Foo, true, 100)
etc

A simple example:
public static class MyHelpers
{
public static MvcHtmlString MyTextBox(this HtmlHelper htmlHelper, string name, string value = "", bool canEdit = false, int width = 100)
{
if (canEdit)
{
return htmlHelper.TextBox(name, value, new { width = width.ToString() + "px" });
}
else
{
return htmlHelper.TextBox(name, value, new { #readonly = "readonly", width = width.ToString() + "px" });
}
}
}
Then register the Class in web.config or use a using statement
#using test.Helpers
#Html.MyTextBox("test")
#Html.MyTextBox("test", "asdf")
#Html.MyTextBox("test", "asdf", true, 500)
Result:
<input id="test" name="test" readonly="readonly" value="" type="text" width="100px">
<input id="test" name="test" readonly="readonly" value="asdf" type="text" width="100px">
<input id="test" name="test" value="asdf" type="text" width="500px">

Related

DropDownList doesn't pass additional parameter

I would like to pass a number selected from DropDownlist to a GET Create method in other Controller. It looks like:
[HttpGet]
public ActionResult Details(int? id, string error)
{
{...}
var numbers = Enumerable.Range(1, 100);
ViewBag.Quantity = numbers.Select(i => new SelectListItem { Value = i.ToString(), Text = i + "%" });
return View(viewModel);
}
public ActionResult Create(int ID, int quantity)
{
{...}
}
Details View looks like:
<div>
#if (Model.ItemRent.Zatwierdzony == false)
{
using (Html.BeginForm("Create", "ItemRentLists", new { ID = #Model.ItemRent.ItemRentID }, FormMethod.Get))
{
#Html.DropDownList("quantity", new SelectList(ViewBag.Quantity, "Text", "Value"))
<input type="submit" value="Dodaj przedmioty"/>
}
}
</div>
DropDownList doesn't pass a Value to "quantity" parameter in Create method, what is wrong here?
OK I changed #Html.DropDownList("quantity", ViewBag.Quantity as SelectList) and now it works as it should work.

how to register onclick event to #Html.RadioButtonForSelectList in asp.net mvc 3.0

My Model
public class IndexViewModel
{
public IEnumerable<SelectListItem> TestRadioList { get; set; }
[Required(ErrorMessage = "You must select an option for TestRadio")]
public String TestRadio { get; set; }
[Required(ErrorMessage = "You must select an option for TestRadio2")]
public String TestRadio2 { get; set; }
}
public class aTest
{
public Int32 ID { get; set; }
public String Name { get; set; }
}
My Controller
public ActionResult Index()
{
List<aTest> list = new List<aTest>();
list.Add(new aTest() { ID = 1, Name = "Yes" });
list.Add(new aTest() { ID = 2, Name = "No" });
list.Add(new aTest() { ID = 3, Name = "Not applicable" });
list.Add(new aTest() { ID = 3, Name = "Muttu" });
SelectList sl = new SelectList(list, "ID", "Name");
var model = new IndexViewModel();
model.TestRadioList = sl;
return View(model);
}
My View
#using (Html.BeginForm()) {
<div>
#Html.RadioButtonForSelectList(m => m.TestRadio, Model.TestRadioList)
</div>
}
Helper method
public static class HtmlExtensions
{
public static MvcHtmlString RadioButtonForSelectList<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
IEnumerable<SelectListItem> listOfValues)
{
var metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
var sb = new StringBuilder();
if (listOfValues != null)
{
// Create a radio button for each item in the list
foreach (SelectListItem item in listOfValues)
{
// Generate an id to be given to the radio button field
var id = string.Format("{0}_{1}", metaData.PropertyName, item.Value);
// Create and populate a radio button using the existing html helpers
var label = htmlHelper.Label(id, HttpUtility.HtmlEncode(item.Text));
var radio = htmlHelper.RadioButtonFor(expression, item.Value, new { id = id }).ToHtmlString();
// Create the html string that will be returned to the client
// e.g. <input data-val="true" data-val-required="You must select an option" id="TestRadio_1" name="TestRadio" type="radio" value="1" /><label for="TestRadio_1">Line1</label>
sb.AppendFormat("<div class=\"RadioButton\">{0}{1}</div>", radio, label);
}
}
return MvcHtmlString.Create(sb.ToString());
}
}
Here is the code i'm using... not sure how to give a onclick event for the control. In the helper method i could not find any appropriate htmlattributes parameter. as per my requirement. on click of any radiobutton in the list i need to call a js function with few parameters. which i'm not able to do. Someonce please help. Thanks in advance.
I haven't got the means to test this at the moment, but a rough idea is adding IDictionary htmlAttributes to the method and passing it in there. If you dont have the required onClick code in the view, then you could omit the parameter and do it in the extension method
public static class HtmlExtensions
{
public static MvcHtmlString RadioButtonForSelectList<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
IEnumerable<SelectListItem> listOfValues,
IDictionary<string, Object> htmlAttributes)
{
var metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
var sb = new StringBuilder();
if (listOfValues != null)
{
// Create a radio button for each item in the list
foreach (SelectListItem item in listOfValues)
{
// Generate an id to be given to the radio button field
var id = string.Format("{0}_{1}", metaData.PropertyName, item.Value);
// Create and populate a radio button using the existing html helpers
var label = htmlHelper.Label(id, HttpUtility.HtmlEncode(item.Text));
htmlAttributes["id"] = id;
var radio = htmlHelper.RadioButtonFor(expression, item.Value, htmlAttributes).ToHtmlString();
// Create the html string that will be returned to the client
// e.g. <input data-val="true" data-val-required="You must select an option" id="TestRadio_1" name="TestRadio" type="radio" value="1" /><label for="TestRadio_1">Line1</label>
sb.AppendFormat("<div class=\"RadioButton\">{0}{1}</div>", radio, label);
}
}
return MvcHtmlString.Create(sb.ToString());
}
}
and then call it using something like:
#Html.RadioButtonForSelectList(m => m.TestRadio, Model.TestRadioList, new { onclick="someFunction();" })
alternatively you could set a css class and bind to the click event. for example,
<script type="text/javascript>
$('.someClassName').click( function() {
alert('clicked');
});
</script>
#Html.RadioButtonForSelectList(m => m.TestRadio, Model.TestRadioList, new { #class="someClassName" })

MultiSelectList does not highlight previously selected items

In a ASP.NET MVC (Razor) project, I'm using a ListBox with Multi Select option in a Edit View,
CONTROLLER
public ActionResult Edit(int id)
{
Post post = db.Posts.Find(id);
string selectedValues = post.Tags; //This contains Selected values list (Eg: "AA,BB")
ViewBag.Tagslist = GetTags(selectedValues.Split(','));
return View(post);
}
private MultiSelectList GetTags(string[] selectedValues)
{
var tagsQuery = from d in db.Tags
orderby d.Name
select d;
return new MultiSelectList(tagsQuery, "Name", "Name", selectedValues);
}
HTML
<div class="editor-field">
#Html.ListBox("Tags", ViewBag.Tagslist as MultiSelectList)
</div>
This loads the items (Tag List) in to ListBox, but does not highlight the items in the Selected Values list.
How to fix this issue?
Thanks in advance.
I suspect that your Post class (to which your view is strongly typed) has a property called Tags. You also use Tags as first argument of the ListBox helper. This means that the helper will look into this property first and ignore the selected values you passed to the MultiSelectList. So the correct way to set a selected value is the following:
public ActionResult Edit(int id)
{
Post post = db.Posts.Find(id);
ViewBag.Tagslist = GetTags();
return View(post);
}
private MultiSelectList GetTags()
{
var tagsQuery = from d in db.Tags
orderby d.Name
select d;
return new MultiSelectList(tagsQuery, "Name", "Name");
}
and in the view:
<div class="editor-field">
#Html.ListBoxFor(x => x.Tags, ViewBag.Tagslist as MultiSelectList)
</div>
And here's a full example that should illustrate:
public class HomeController : Controller
{
public ActionResult Index()
{
var post = new Post
{
Tags = new[] { "AA", "BB" }
};
var allTags = new[]
{
new { Name = "AA" }, new { Name = "BB" }, new { Name = "CC" },
};
ViewBag.Tagslist = new MultiSelectList(allTags, "Name", "Name");
return View(post);
}
}
Also I would recommend you using view models instead of passing your domain entities to the view. So in your PostViewModel you will have a property called AllTags of type MultiSelectList. This way you will be able to get rid of the weak typed ViewBag.

Additional parameters for TextBoxFor() in ASP.net MVC3

I was using a textbox to do the following:
<input style="width:50px;" type="text" id="blah" value="#model.value1" #if(model.id!=1){ <text>disabled</text>}/>
This basically shows a textbox which is disabled under specific circumstances.
I decided to replace this with a more "mvc-friendly" way.
#Html.TextBoxFor(m => model.value1, new { id = "blah" })
But not sure how to add in the disabled attribute (Dynamically) I can get it to do it statically easilly by adding a disabled=truevalue into the new{}.
I did try using this:
#if (<condition>) { var disable = true; }
#Html.TextBoxFor(m => model.value1, new { id = "blah", disabled = disable })
But this also didn't work. Am i taking the right approach here?
#{
var attributes = new Dictionary<string, object>
{
{ "id", "blah" }
};
if (<condition>)
{
attributes["disabled"] = "disabled";
}
}
#Html.TextBoxFor(m => model.value1, attributes)
Obviously that's ugly as hell and you should never even be thinking of polluting your view like that. I would simply write a custom reusable HTML helper:
public static class HtmlExtensions
{
public static IHtmlString MyTextBoxFor<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> ex,
object htmlAttributes,
bool disabled
)
{
var attributes = new RouteValueDictionary(htmlAttributes);
if (disabled)
{
attributes["disabled"] = "disabled";
}
return htmlHelper.TextBoxFor(ex, attributes);
}
}
that you could use in the view as simply as:
#Html.MyTextBoxFor(m => model.value1, new { id = "blah" }, <condition>)
You have a scope issue with the above disable doesn't exist outside the scope of the if statement,
my recommendation is this:
#Html.TextBoxFor(m => model.value1, new { id = "blah", disabled = (<condition>) })
EDIT:
You can use
#Html.TextBoxFor(m => model.value1, new { id = "blah", disabled = (<condition>) ? "disabled" : "" })
If you want to insert the word disabled rather than a bool, from memory this is kinda a browser specific setting some are happy with "true" others with "disabled"

MVC3- Radion Button Grouping and Binding from database

How to get the radio button options from database using in MVC3 Razor. I have 4 options for each question, the options should be populated from the database and should be grouped.
#Html.RadioButtonFor(model => Model.AAAa, 'Y', new { title = "Please check this if AAA has been updated" })
Yes
This gives me hard coded value as Yes but text needs to be populated with DB Table.
How would I bind it back the selected value back to the database?. An Example would be more helpful.
Thank you
As always you start by defining a view model that will represent the information you will be dealing with in the view:
public class MyViewModel
{
public string SelectedValue { get; set; }
// In your question you seem to be dealing with a title attribute as well
// If this is the case you could define a custom view model ItemViewModel
// which will contain the Value, the Text and the Title properties for
// each radio button because the built-in SelectListItem class that I am using here
// has only Value and Text properties
public SelectListItem[] Items { get; set; }
}
then you write a controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new MyViewModel
{
// Obviously those will come from your database or something
Items = new[]
{
new SelectListItem { Value = "Y", Text = "Yes" },
new SelectListItem { Value = "N", Text = "No" },
new SelectListItem { Value = "D", Text = "Dunno" }
}
};
return View(model);
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
return View(model);
}
}
then a corresponding view:
#model MyViewModel
#using (Html.BeginForm())
{
for (int i = 0; i < Model.Items.Count; i++)
{
#Html.RadioButtonFor(x => x.SelectedValue, Model.Items[i].Value, new { id = "item_" + i })
#Html.Label("item_" + i, Model.Items[i].Text)
<br/>
}
<button type="submit">OK</button>
}
or to make the view less messy you could write a custom HTML helper that will render those radio buttons for you:
public static class HtmlExtensions
{
public static IHtmlString RadioButtonListFor<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
IEnumerable<SelectListItem> items
)
{
var sb = new StringBuilder();
var i = 0;
foreach (var item in items)
{
var id = string.Format("item{0}", i++);
var radio = htmlHelper.RadioButtonFor(expression, item.Value, new { id = id });
var label = htmlHelper.Label(id, item.Text);
sb.AppendFormat("{0}{1}<br/>", radio, label);
}
return new HtmlString(sb.ToString());
}
}
and now your view will simply become:
#model MyViewModel
#using (Html.BeginForm())
{
#Html.RadioButtonListFor(x => x.SelectedValue, Model.Items)
<button type="submit">OK</button>
}
which obviously is nicer to look.

Resources