How do I call the placeholder watermark in a class? - asp.net-mvc-3

I do this in an EditorTemplate View:
#Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue,
new { placeholder = ViewData.ModelMetadata.Watermark })
I now want to do something in a class file in which I am using TagBuilder and MergeAttribute. I use code, for example, to display an image for "required", and tried to modify it to do the same thing with placeholder. But I can't do ViewData. So this isn't working:
public static MvcHtmlString PlaceholderFor<T, TValue>
(this HtmlHelper<T> html, Expression<Func<T, TValue>> expression)
{
var placeholder = new ModelMetadata([CAN'T FIGURE OUT IN HERE]);
placeholder.MergeAttribute([ALSO CAN'T FIGURE OUT IN HERE]);
return MvcHtmlString.Create(placeholder.ToString());
}
I don't even know if the above would be appropriate so I could do something like this:
input.MergeAttribute += html.PlaceholderFor(expression);
or whether that is also the incorrect approach.
For context I am building an "input" using:
input.InnerHtml += html.EditorFor(expression);
Can any one provide a code example that would work in this context? Sorry if I am not explaining it correctly.

Your question is not very clear about what you are trying exactly to achieve. But if you want to get the contents generated by the TextBox helper inside another custom helper you could simply call this TextBox helper:
public static IHtmlString MyHelperFor<T, TValue>(
this HtmlHelper<T> html,
Expression<Func<T, TValue>> expression,
object htmlAttributes
)
{
var div = new TagBuilder("div");
var metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
var attributes = new RouteValueDictionary(htmlAttributes);
attributes["placeholder"] = metadata.Watermark;
div.InnerHtml = html.TextBoxFor(expression, attributes).ToHtmlString();
return new HtmlString(div.ToString());
}
and then:
#Html.MyHelperFor(x => x.Test, new { #class = "foo" })

Related

MVC3 Set Default value for Textbox from model

I have a text box created using
#Html.TextBoxFor(m => m.Model1.field1, new { #class = "login-input", #name="Name", #Value = "test" })
I want to change the default value of this textbox from "text" to a value stored in a model field. How do i set a model field as the the value attribute? Say the Name of the model to be called is Model2 and the attribute is field2. How do I set the value of the textbox to field2?
You must first write an Extension Method like this:
public class ObjectExtensions
{
public static string Item<TItem, TMember>(this TItem obj, Expression<Func<TItem, TMember>> expression)
{
if (expression.Body is MemberExpression)
{
return ((MemberExpression)(expression.Body)).Member.Name;
}
if (expression.Body is UnaryExpression)
{
return ((MemberExpression)((UnaryExpression)(expression.Body)).Operand).Member.Name;
}
if (expression.Body is ParameterExpression)
{
return expression.Body.Type.Name;
}
throw new InvalidOperationException();
}
}
It will extract the Name of property when you write sth like this: #Html.TextBoxFor(m => m.Model1.field1)
then you can use it like this:
Html.TextBoxFor(m => m.Model1.field1,
new { #class = "login-input",
#name="Name",
#value = Model.Item(m => m.Model1.field1) })
If you don't want to call m => m.Model1.field1 again, you must declare your version of TextBoxFor method which is more complicate, but If you want I can provide you the details.
This is a sample from my code base on Github:
public static class HtmlHelperExtensionForEditorForDateTime
{
public static MvcHtmlString Editor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression)
{
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
string propname = html.ViewData.Model.Item(expression);
string incomingValue = null;
var httpCookie = html.ViewContext.RequestContext.HttpContext.Request.Cookies["lang"];
if (metadata.Model is DateTime && (httpCookie.IsNull() || httpCookie.Value == Cultures.Persian))
incomingValue = PersianCalendarUtility.ConvertToPersian(((DateTime)metadata.Model).ToShortDateString());
if (string.IsNullOrEmpty(incomingValue))
return html.TextBox(propname, null, new { #class = "datepicker TextField" });
return html.TextBox(propname, incomingValue, new { #class = "datepicker TextField"});
}
}
In your controller, set the value of field1 before passing it to the view... and that will set the value automatically. If you have the value in another field, just do:
model.field1 = model.field2;
in your controller... and that way the models have consistent data.
If you don't need/want that the default value being actually the value of the textbox, you could also use a PlaceHolder... and that way the user could see a value as a hint, but it will be not count as textbox content once you post the form.
#Html.TextBoxFor(m => m.Model1.field1, new { #class = "login-input", #name="Name", placeholder= "test" })
Remember that not all fieldnames in the HtmlAttributes need a "#"... #class is correct, but the others I don't think are needed.
You can set the default value inside your controller action just before passing the model to the view:
model.field1 = "Test"
return View(model)

Passing parameter from view to controller when button is clicked

I have created my own extension as:
public static MvcHtmlString hSearch(this HtmlHelper helper, string labelName, string labelCaption, string textName, string textValue, string tableName, string buttonId,
string actionName, string controllerName, object routeValues, object htmlAttributes)
{
var textbuilder = new TagBuilder("input");
textbuilder.MergeAttribute("id", textName);
textbuilder.MergeAttribute("name", textName);
textbuilder.MergeAttribute("value", textValue);
textbuilder.MergeAttributes(new RouteValueDictionary(htmlAttributes));
ModelMetadata metadata = ModelMetadata.FromStringExpression(labelName, helper.ViewData);
String innerText = labelCaption ?? (metadata.DisplayName ?? (metadata.PropertyName ?? labelName.Split('.').Last()));
if (String.IsNullOrEmpty(innerText))
{
return MvcHtmlString.Empty;
}
TagBuilder labelbuilder = new TagBuilder("label");
labelbuilder.Attributes.Add("for", TagBuilder.CreateSanitizedId(helper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(labelName)));
labelbuilder.MergeAttributes(new RouteValueDictionary(htmlAttributes));
labelbuilder.SetInnerText(innerText);
//return new MvcHtmlString(textbuilder.ToString());
var buttonBuilder = new TagBuilder("button");
buttonBuilder.MergeAttribute("id", buttonId);
buttonBuilder.SetInnerText(buttonId);
var formBuilder = new TagBuilder("form");
var urlHelper = new UrlHelper(helper.ViewContext.RequestContext);
formBuilder.Attributes.Add("action", urlHelper.Action(actionName, controllerName, routeValues));
formBuilder.Attributes.Add("method", "Post");
formBuilder.MergeAttributes(new RouteValueDictionary(htmlAttributes));
formBuilder.InnerHtml = labelbuilder.ToString() + textbuilder.ToString() + buttonBuilder.ToString();
return new MvcHtmlString(formBuilder.ToString());
}
I used the extensions in view as:
#Html.hSearch("lblSrch", "Company", "companyName", (string)TempData["cName"], "CHComp", "Search", "Fetch", "Home", null, null)
Now I want to pass tableName when I click the button to the controller.. my controller looks like this:
public ActionResult Fetch(string search, string tablename)
{
var c = cbo.fetchData(search, tablename);
return PartialView(c.ToList());
}
Waiting for reply.. Thanks..
You haven't given us the code for your helper, but at a guess it writes out a label, a text field (textName), and a button. If this is the case, it will post / get companyName=someValue via HTTP back to your controller.
You would typically need to add a FormCollection to your controller if the fields are dynamically sent from the view. Alternatively, why not keep the name of the text search input static, e.g. name="search", which will bind to your controller's parameter of the same name.
Edit
You can pass tableName back to the controller in a hidden field (<input type='hidden' name='tableName' value='{tableNameGoesHere}')
But as per above, your search string will have different names- the model binder isn't going to recognise it as string search.

How can I set the width of a textbox in asp.net mvc 3?

How can I set the width of a textbox in asp.net mvc 3? this does not work:
#Html.TextBox("Name", new { style = "width:500px" })
try this, this should work..
#Html.TextBox("Name", new { #class= "mySize" })
.mySize
{
width: 500px;
}
also in your code,try adding a semicolon and see if that works, something like this
#Html.TextBox("Name", new { style = "width:500px;" })
I'm surprised the answer given by #Yasser works. Since these extension methods are overloaded with functions that can take several anonymous objects, it's easy inadvertently use the wrong one.
From MSDN, it looks like you're calling this method:
public static MvcHtmlString TextBox(
this HtmlHelper htmlHelper,
string name,
Object value)
where value is:
The value of the text input element. If this value is null, the value of the element is retrieved from the ViewDataDictionary object. If no value exists there, the value is retrieved from the ModelStateDictionary object.
So value is used to populate the input. Instead, I think you want this extension:
public static MvcHtmlString TextBox(
this HtmlHelper htmlHelper,
string name,
Object value,
Object htmlAttributes)
Then, use it like this (pass null for value) to add inline styles to the markup:
#Html.TextBox("Name", null, new { style = "width:500px;" })
Or:
#Html.TextBox("Name", null, new { #class = "myStyle" })

MVC3 Conditionally disable Html.TextBoxFor()

I have a C# .Net web app. In that app I need to conditionally disable Html.TextBoxFor controls (also Html.DropDownListFor controls) based on who is logged into the system. I tried using
#Html.TextBoxFor(model => model.ProposalName, new { #ViewBag.IsDisabled })
Where #ViewBag.IsDisabled is set to either String.Empty or "disabled" in the Controller. However, this renders as IsDisabled='disabled' or IsDisabled="" so the control is not disabled. When I tried
#Html.TextBoxFor(model => model.ProposalName, new { #ViewBag.Disabled })
The control was always disabled even if ViewBag.Disabled contained no text. How can I conditionally disable the Html.TextBoxFor() controls?
Try
#Html.TextBoxFor(model => model.ProposalName, ViewBag.Disabled ? (object)new { disabled="disabled" } : new {})
The solution posted by #epignosisx works, but it may be a problem if you want to add some other attribute because you will have to add it it both objects (the one with disabled and the one now its empty).
Even worse if you have some other bool property because you will have four different objects, each one for each combination.
The best solution here (with a little more code) is to build an extension method for HtmlHelper to receive your boolean property as a parameter.
public static MvcHtmlString TextBoxDisabledFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, bool disabled, object htmlAttributes = null)
{
return TextBoxDisabledFor(htmlHelper, expression, disabled, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
}
public static MvcHtmlString TextBoxDisabledFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, bool disabled, IDictionary<string, object> htmlAttributes)
{
if (htmlAttributes == null)
htmlAttributes = new Dictionary<string, object>();
if (disabled)
htmlAttributes["disabled"] = "disabled";
return htmlHelper.TextBoxFor(expression, htmlAttributes);
}
Here there is another example
I had this same problem and decided to write my own HtmlHelper extension method.
public static MvcHtmlString Disable(this MvcHtmlString helper, bool disabled)
{
if (helper == null)
throw new ArgumentNullException();
if (disabled)
{
string html = helper.ToString();
int startIndex = html.IndexOf('>');
html = html.Insert(startIndex, " disabled=\"disabled\"");
return MvcHtmlString.Create(html);
}
return helper;
}
This will accept a boolean to indicate if the control should be disabled or not. It just appends disabled="disabled" just inside the first > it comes across in a string.
You can use it like below.
#Html.TextBoxFor(model => model.ProposalName).Disable(true)
Here is the method I use, which doesn't require extensions, and doesn't limit you to only one HTML attribute. It assumes there is a boolean property named "Disabled" in your model, but you could put whatever you wanted in there, as long as it evaluates to boolean for the ternary operator:
#Html.TextBoxFor(model => model.Whatever,
new Dictionary<string, object>() {
{ "size", "5" },
{ "class", "someclasshere" },
{ model.Disabled ? "disabled" : "data-notdisabled", "disabled" }
})
The limitation with the standard shortcut notation is that the name of the attribute cannot be dynamic. By creating a dictionary of the correct type, you can then make the attribute name dynamic, and you then pass that dictionary to the textbox as the dictionary of attributes. When the field is not to be disabled, it passes an attribute named "data-notdisabled" instead of one named "disabled", which the browser will then ignore.
Extending #James's answer, I wrote this HtmlHelper extension that updates/removes the disabled attribute if it's already present, or adds it if not:
public static MvcHtmlString Disable(this MvcHtmlString helper, bool disabled) {
string html = helper.ToString();
var regex = new Regex("(disabled(?:=\".*\")?)");
if (regex.IsMatch(html)) {
html = regex.Replace(html, disabled ? "disabled=\"disabled\"" : "", 1);
} else {
regex = new Regex(#"(\/?>)");
html = regex.Replace(html, disabled ? "disabled=\"disabled\"$1" : "$1", 1);
}
return MvcHtmlString.Create(html);
}
It also plays nicely with self-closing tags (like <input />).
Usage is the same:
#Html.TextBoxFor(model => model.PropertyName).Disable(true)
Tested on both #Html.DropDownListFor() and #Html.TextBoxFor().
Using a dictionary for the attributes and conditionally adding the disabled attrib.
<div>
#{
var attribs = new Dictionary<String, object>
{
{ "class", "form-control" }
,{ "style", "display:inline; width:100px;"}
};
if (Model.IsFieldDisabled)
{
attribs.Add("disabled", true);
}
}
#Html.TextBoxFor(m => m.MinValue, attribs)
to
#Html.TextBoxFor(m => m.MaxValue, attribs)
</div>
Adding this really only to add a more literal version of #ITFlyer's answer above. His method of conditionally changing the attribute name rather than the value is crafty and still allows for inlining. However, when I first looked at it I mistakenly thought that it wasn't conditional so I thought a more verbose version was still of value even if it largely just redirects to that answer.
An additional benefit is that a separate variable also allows for reuse over multiple controls (which I needed in my case

ASP.NET MVC - Extending TextBoxFor without re-writing the method

Is there any possible way to extend the basic html helpers (TextBoxFor, TextAreaFor, etc) using extension methods on their output, instead of just re-writing the entire methods completely? For instance, adding in ...
#Html.TextBoxFor( model => model.Name ).Identity("idName")
I know I can achieve this using the following, already..
#Html.TextBoxFor( model => model.Name, new { #id = "idName" })
But that gets clunky and frustrating to manage when you have to start adding a lot of properties. Is there any way to add extensions to these inherently without just passing in htmlAttributes for every little detail?
As #AaronShockley says, because TextBoxFor() returns an MvcHtmlString, your only option for developing a 'fluid API' style of amending the output would be to operate on the MvcHtmlStrings returned by the helper methods. A slightly different way of doing this which I think approaches what you're after would be to use a 'property builder' object, like this:
public class MvcInputBuilder
{
public int Id { get; set; }
public string Class { get; set; }
}
...and to set up extension methods like this:
public static MvcHtmlString TextBoxFor<TModel, TProp>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProp>> expression,
params Action<MvcInputBuilder>[] propertySetters)
{
MvcInputBuilder builder = new MvcInputBuilder();
foreach (var propertySetter in propertySetters)
{
propertySetter.Invoke(builder);
}
var properties = new RouteValueDictionary(builder)
.Select(kvp => kvp)
.Where(kvp => kvp.Value != null)
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
return htmlHelper.TextBoxFor(expression, properties);
}
You can then do stuff like this in your View:
#this.Html.TextBoxFor(
model => model.Name,
p => p.Id = 7,
p => p.Class = "my-class")
This gives you strong typing and intellisense for input properties, which you could customise for each extension method by adding properties to an appropriate MvcInputBuilder subclass.
All of the basic html helpers return an object of type System.Web.Mvc.MvcHtmlString. You can set up extension methods for that class. Here is an example:
public static class MvcHtmlStringExtensions
{
public static MvcHtmlString If(this MvcHtmlString value, bool check)
{
if (check)
{
return value;
}
return null;
}
public static MvcHtmlString Else(this MvcHtmlString value, MvcHtmlString alternate)
{
if (value == null)
{
return alternate;
}
return value;
}
}
Then you can use these in a view like:
#Html.TextBoxFor(model => model.Name)
.If(Model.Name.StartsWith("A"))
.Else(Html.TextBoxFor(model => model.LastName)
To make extension methods that modify attributes on the rendered HTML tag, you'll have to convert the result to a string, and find and replace the value you're looking for.
using System.Text.RegularExpressions;
public static MvcHtmlString Identity(this MvcHtmlString value, string id)
{
string input = value.ToString();
string pattern = #"(?<=\bid=")[^"]*";
string newValue = Regex.Replace(input, pattern, id);
return new MvcHtmlString(newValue);
}
public static MvcHtmlString Name(this MvcHtmlString value, string id)
{
string input = value.ToString();
string pattern = #"(?<=\bname=")[^"]*";
string newValue = Regex.Replace(input, pattern, id);
return new MvcHtmlString(newValue);
}
The id and name attributes are always added by the html helpers, but if you want to work with attributes that may not be there (and you'll have to add them instead of just replacing them), you'll need to modify the code.

Resources