Is it possible with either Bootstrap, HTML5 or jQuery to have a currency prefix within the input field as a display element and not part of the actual input value?
For example I wish to display £550.00 in the Amount field, but the data as 550.00
I have tried using w2ui, jQuery Price Format and jQuery maskMoney, all 3 do the same job basically, they can indeed prefix or suffix, but they don't actually store a value in the input, so posting data returns a null value.
Model.cs
[DisplayFormat(DataFormatString = "{0:F2}", ApplyFormatInEditMode = true)]
public double Amount { get; set; }
HTML
<div class="col-sm-3">
<div class="input-group">
<span class="input-group-addon">Amount</span>
#Html.TextBoxFor(m => m.Amount, new { #class = "form-control", #Value = #ViewBag.Amount, placeholder = "Amount", #Id = "Amount" })
</div>
</div>
// Dynamically setting value of field
$('#Amount').val(data.Amount);
// w2ui example
$('#Amount').val(data.Amount).w2field('money', { moneySymbol: '£' });
// jQuery Price Format example
$('#Amount').val(data.Amount).priceFormat({
prefix: '£'
});
I realise I can use another field to store the numeric value, but I have quite a few fields I need to display which are initially dynamically populated, but can be overridden by the user. So duplicating fields, one for display input and one hidden to store inputted data seems over kill.
Any assistance would be much appreciated :-)
I recommend wrapping your input with a span:
<span class="currency">£ #Html.TextBoxFor()</span>
And modifying that with css to make it format nicely for yourself. Such as this.
Or just add it as a label:
<label for="currencyInput">Amount in £</label>
Otherwise, you'll be stuck trying to remove / add the prefix everywhere. Keep your input as the actual value.
The issue wasn't with any of the jQuery libraries but the fact that the form was posting a string, whereas the model was expecting a double.
So I have implemented a ModelBinder to allow the post of string data containing the '£' pound sign and convert to a double.
MyModelBinder.cs
namespace WebApplication1.Models
{
public class MyModelBinder : DefaultModelBinder
{
protected override object GetPropertyValue(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor, IModelBinder propertyBinder)
{
if (propertyDescriptor.ComponentType == typeof(MyModel))
{
if (propertyDescriptor.Name == "Amount")
{
var obj = bindingContext.ValueProvider.GetValue("Amount");
return Convert.ToDouble(obj.AttemptedValue.Replace("£", ""));
}
}
return base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder);
}
}
}
HomeController.cs
[HttpPost]
public ActionResult Index([ModelBinder(typeof(MyModelBinder))]MyModel model)
HTML
$('#Amount').val(data.Amount).priceFormat({
prefix: '£'
});
I hope this proves useful to others.
Related
In an MVC application, I have a list of exam questions and I want to present a small number of them to the user on the same page but where each answer can be submitted separately.
So my page looks like this ....
The view code is ....
#model List<QuestionResponseVM>
#for (int i = 0; i < Model.Count(); i++)
{
using (Html.BeginForm("CheckQuestions", "Checks", FormMethod.Post, new {questResponses = Model[i] }))
{
#Html.AntiForgeryToken()
#Html.HiddenFor(model => model[i].QuestionID)
<tr>
<td width="35%">
#Html.Raw(Model[i].QuestionText)
#Html.HiddenFor(model => model[i].QuestionText)
</td>
<td>
#Html.TextAreaFor(model => model[i].Response, new { #name = "DisplayTextEdit", #id = "DisplayTextEdit", #rows = 1, #cols = 80 })
</td>
<td width="30%">
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</div>
</td>
</tr>
}
}
My problem is I can only get data returned to the POST method for Question 1.
Here is the Controller Code ....
public class ChecksController : Controller
{
public ActionResult CheckQuestions()
{
return View(LoadQuestions());
}
// POST: Checks
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult CheckQuestions(List<QuestionResponseVM> questResponses)
{
List<QuestionResponseVM> testList = new List<QuestionResponseVM>();
if (ModelState.IsValid)
{
testList = LoadQuestions(questResponses[0].QuestionID, questResponses[0].Response);
}
return View(testList);
}
private List<QuestionResponseVM> LoadQuestions (int _QuestionID = -1, string _Response = "")
{
List<QuestionResponseVM> thisList = new List<QuestionResponseVM>();
thisList.Add(new QuestionResponseVM()
{
QuestionID = 1,
QuestionText = "Question 1",
Response = (_QuestionID == 1 ? _Response : "")
});
thisList.Add(new QuestionResponseVM()
{
QuestionID = 2,
QuestionText = "Question 2",
Response = (_QuestionID == 2 ? _Response : "")
});
thisList.Add(new QuestionResponseVM()
{
QuestionID = 3,
QuestionText = "Question 3",
Response = (_QuestionID == 3 ? _Response : "")
});
return thisList;
}
}
If the Controller POST method has a parameter of QuestionResponseVM questResponses which is what I was expecting (hoping for) then null is returned from the view no matter which "Save" button is clicked.
However, if I change the parameter to a list (i.e. List<QuestionResponseVM> questResponses) then the "Save" button for Question 1 returns a list with a single item and correct data. But, any other "Save" button (e.g. Question 2 or Question 3) returns a null list.
The behaviour for scenario 1. seems counter-intuitive to me since the "Begin Form" is set to return a single model item (instance of the model) i.e. "Model[i]".
And in scenario 2., I just don't understand why it works for the first form ("Save" button) but not for the others.
I don't believe I should need to use JScript or AJAX to do this.
But clearly, I am not "connecting some dots" here.
Can someone please explain my observed behaviour and maybe give me a push in the right direction to meet this requirement.?
I would greatly appreciate any help.
Before going through your questions, I don't get what new {questResponses = Model[i] })) is doing in your forms:
using (Html.BeginForm("CheckQuestions", "Checks", FormMethod.Post, new {questResponses = Model[i] }))
{
...
}
Model[i] is a complex object. All you got there was the name of the object:
Q1: If the Controller POST method has just a single parameter
Since you're using a for loop to generate each form and inputs within the form, the name of those inputs will be in the forms of [INDEX].NAME:
By default, the model binding will bind those inputs (QuestionId, QuestionText and Response) to a matching object. QuestionResponseViewModel indeed matches that. The problem is [INDEX]. prefix.
In order for the default model binding to work, the parameter name you declare in the POST method has to be called [INDEX], i.e., [0] for the first form, [1] for the second form and so on:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult CheckQuestions(QuestionResponseVM [0])
{
...
}
But you know we can't declare anything like that in C#.
The Fix for Q1
Instead of using the regular for loop, you can use foreach to generate each form. In that way, you get rid of the need for naming a parameter that's changing for each form.
Another "GOTYOU" here is that the parameter in the controller has to match the variable you declared in the for loop for each QuestionResponseViewModel:
#foreach (var qrVM in Model)
{
using(Html.BeginForm("..."))
{
#Html.AntiForgeryToken()
#Html.HiddenFor(x => qrVM.QuestionId)
<tr>
<td>
#Html.DisplayFor(x => qrVM.QuestionId)
#Html.HiddenFor(x => qrVM.QuestionId)
</td>
...
</tr>
}
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult CheckQuestions(QuestionResponseVM qrVM)
{
// If you name the parameter something else, it won't bind!
...
}
If you think about it, that makes sense, because you know the form will post data with keys like qrVM.QuestionId, qrVM.QuestionText back to the server. The default model binding will search for a model that has those properties and is named qrVM.
Q2: Change parameter to a list
When the first form posts back to the server, the form data in the request body will look like:
[0].RequestionId: 1
[0].RequestionText: Question 1
[0].Response: xxx
MVC model binding is still smart enough and thinks you're posting the first item of the list you declared. Hence you will see List<QuestionResponseVM> questResponses capture the correct data for the first form.
Well what about the second and third form? If you submit the data on the second form for example, the form data in the request body will look like:
[1].RequestionId: 2
[1].RequestionText: Question 2
[1].Response: xxx
MVC model binding sees it as the 2nd item of the list, but where's the 1st item? And it got confused so it couldn't bind the data to the parameter. Hence you will see NULL from the parameter List<QuestionResponseVM> questResponses.
My 2 cents
You actually cannot put a form inside a table or between table rows like this. It's considered as invalid HTML structure. It's never a good idea to use tables as structures to display data on the page anyway. Instead, You can use Bootstrap's row and columns.
I don't know why or what made you think you shouldn't need AJAX. Your case is like the best scenario to go with an AJAX approach! For example, with AJAX, the user can save each question's response individually. The page doesn't have to be refreshed.
Your save button on Question 1 is submitting the form to the controller. You will need to either have one Save/Submit button at the end of a set of questions and utilize the FormCollection object or spend time setting up JQuery/Ajax for click events on each button and removing the form element. You could have a bit of both if the button at the bottom becomes a 'Next' and then submits to a controller to get the next set of related questions.
Having some trouble creating a time-only input field with unobtrusive validation, mapping to a non-nullable DateTime field.
I am working with a database-first approach. The database has datetime fields used to store both dates and times. By that I mean some datetimes have real date data and 00:00:00 for time while others have meaningless date data with real times. I understand this came about due to limitations with EF5 and C# datatypes. Now using EF6, but not particularly intending to change the database.
Here is the view model
[UIHint("Date")]
public DateTime MatchDate { get; set; }
[UIHint("Time")]
public DateTime StartTime { get; set; }
The date-only field, MatchDate, is working. It uses an EditorFor template, Date.cshtml (below). It allows text input, correctly attaches a jQuery datepicker and correctly validates server side and client-side with unobtrusive.
#model DateTime
#if (Model == DateTime.MinValue)
{
#Html.TextBox("", "", new { #class = "datepicker" })
}
else
{
#Html.TextBox("", String.Format("{0:d/M/yyyy}", Model), new { #class = "datepicker" })
}
So I created a new EditorFor template for Time.cshtml
#model DateTime
#if (Model == DateTime.MinValue)
{
#Html.TextBox("", "", new { #class = "timepicker" })
}
else
{
#Html.TextBox("", String.Format("{0:h\\:mm tt}", Model), new { #class = "timepicker" })
}
At this point the Html input element gets an unwanted data-val-date attribute, causing unobtrusive date validation. I found that by adding [DataType(DataType.Time)], the data-val-date is no longer added. While this is a good thing, I'm not if that is exactly (and only) what the datatype attribute is supposed to do. So this is the first part of the question.
Next, I have created custom validation rules (server & client-side) for a 12-hr time format.
Annotation:
public class TimeTwelveAttribute : ValidationAttribute, IClientValidatable
{
private const string _ErrorMessage = "Invalid time format. Please use h:mm am/pm (TimeAttribute)";
public TimeTwelveAttribute()
{
this.ErrorMessage = _ErrorMessage;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
yield return new ModelClientValidationRule
{
ErrorMessage = _ErrorMessage,
ValidationType = "timetwelve"
};
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
DateTime time;
if (value == null || !DateTime.TryParse(value.ToString(), out time))
{
return new ValidationResult(_ErrorMessage);
}
return ValidationResult.Success;
}
}
javascript
$.validator.unobtrusive.adapters.addSingleVal("timetwelve");
$.validator.addMethod(
"timetwelve",
function (value, element) {
return this.optional(element) || /^((0?[1-9]|1[012])(:[0-5]\d){1,2}(\ ?[AP]M))$/i.test(value);
},
"Invalid time format. Please use h:mm am/pm"
);
However, adding the [TimeTwelve] data annotation to StartTime has no effect:
Server-side validation is occurring, but it's creating a different error message that I can't override: "The value 'asdf' is not valid for Start Time."
There is no client-side validation message popping up if I type 'asdf' in to the time field.
I gather this is caused by the postback values being mapped in to C# DateTimes. Whatever mechanism does that takes a higher priority and overrides all other validation. As an exercise, I rewrote StartTime as a string (and updated the templates etc.) and sure enough [TimeTwelve] works, client-side and server-side as expected. I feel like this is aspect has not been covered in any of the related questions here on stackoverflow.
So the second part of my question, given that I would really prefer to use DateTimes in my view model, how can I get validation to work?
This is the closest question I could find, but is from 2013.
First, to get your client side validation working, change your scripts to
$.validator.unobtrusive.adapters.add('timetwelve', function (options) {
options.rules['timetwelve'] = {};
options.messages['timetwelve'] = options.message;
});
// no need to hard code the message - it will use the one you have defined in the validation atribute
$.validator.addMethod('timetwelve', function (value, element) {
return this.optional(element) || /^((0?[1-9]|1[012])(:[0-5]\d){1,2}(\ ?[AP]M))$/i.test(value);
});
or you can use the .addBool() method
$.validator.unobtrusive.adapters.addBool("timetwelve");
As far as for receiving the The value 'asdf' is not valid for Start Time if you your enter "asdf" in the textbox, that is the default behavior. The DefaultModelBinder uses ValueConverters to parse the values in the Request to the property type. Since asdf cannot be converted to DateTime, a ModelState error is added and no further validation is done (because its already invalid and there would be no point). Of course, once client side validation is working, you will never get to that point anyway (unless the user has disabled javascript or its a malicious user).
Note also your IsValid() method can simply be
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
return ValidationResult.Success;
}
By the time you reach here, the DefaultModelBinder has already set the DateTime value (by combining today's date with the time entered in the form), so there is no need to try and parse it again.
I want to give my TextAreaFor a default value from my database (a certain comment which they can edit). I use the #Value and I can see it in the html code (inspect element), but in the textarea itself it isn't visible.
My code:
#Html.TextAreaFor(a => a.description, new { Value = ViewBag.Description }
ViewBag.Description = adver.description;
textarea works differently then other input fields in HTML and because of this the value attribute doesn't actually do anything that you'd expect.
<textarea value="you won't see this">you will see this</textarea>
Versus a text field:
<input type="text" value="you will see this" />
Because of this you need to assign the text that you want to be shown in the textarea to the variable before creating the textarea.
Edit
Here's a more complete example for you:
public ActionResult MyAction()
{
var myDefaultDescription = ""; //Replace with whatever you initialize ViewBag.Description with
return View(new AdverModel{ description = myDefaultDescription });
}
This will cause the model to be initialized in the controller. The #Html.TextAreaFor() only uses fields and values from the model object that's passed into the view. The Model variable is read only in the view so you must initialize it in the controller and pass it to the view during the return from the controller.
This will cause that field to auto populate with the default value.
If a view is ment to allow editing of only one property but all other properties are being displayed (DisplayFor, non-editable) as well, what is a good way to design the handing-over of changed value to the controller?
Right now I have hidden-input-fields for all properties that are displayed with DisplayFor and the controller gets the full object passed.
This is pretty much ineffecient and I know it would suffice to post only the ID of that object and the changed value.
The user can input the value to be changed like this:
#Html.EditorFor(model => model.Verkaufspreis)
#Html.ValidationMessageFor(model => model.Verkaufspreis)
I could pass the ID of the object like this
#Html.ActionLink("Edit", "Edit", new { id=Model.ID })
But how would I pass the value that was changed? Thank you for your input.
if you want to get a value and you do not want to return the model knowing the name of the value you can use FormCollection
[HttpPost]
public ActionResult (FormCollectio collection)
{
string Verkaufspreis1=collection["Verkaufspreis"].ToString();
}
MVC allows all kinds of binding, for instance you could go
[HttpPost]
public ActionResult (int ID, String Verkaufspreis)
//Have to be the same propery name as your model
{
//Get original object with the ID
//change the "Sell of Stock" field
}
This would dynamically pass the ID and Verkaufspreis as parameters.
This would allow you to only have the ID and the value needing to be changed, as you would be getting the rest from your database(or wherever) on postback, only updating the value that is necessary.
You could do the entire model as a parameter, although this would mean you would have alot of empty values if you're not passing them to the client.
Instead of putting a lot of hidden inputs in your form, you can do this.
Simply post the changed values and the id to the action method. Read the full entity from your data source and update the new values and save it back.
[HttpPost]
public ActionResult Update(CustomerViewModel model)
{
Customer customer=repositary.GetCustomerFromID(model.ID)
customer.DisplayName=model.DisplayName;
repositary.SaveCustomer(customer);
return RedirectToAction("ProfileUpdated");
}
In this case, you need to post only the ID and DisplayName properties from the form
#model CustomerViewModel
<h2>Update Customer details</h2>
#using(Html.Beginform())
{
Display Name : #Html.TextBoxFor(x=>x.DisplayName)
#Html.HiddenFor(x=>x.ID)
<input type="submit" value="Save" />
}
I am trying to submit when a dropdownlist changes by doing this
#using (Html.BeginForm("testAction", "FishingTrip"))
{
#Html.DropDownListFor(x => x.Day, Model.Days, new { onchange="this.form.submit();" })
}
This works fine but I am having problems (in other words don't know how) to get the option value on the server, can anybody help me with this ?
cheers
sushiBite
You could simply have your POST controller action take it as parameter and leave the default model binder to the binding:
[HttpPost]
public ActionResult TestAction(string day)
{
// The day parameter will contain the selected value
...
}
Or directly use the view model you used in the view:
[HttpPost]
public ActionResult TestAction(MyViewModel model)
{
// The model.Day parameter will contain the selected value
...
}
Just use the Day property of the model parameter, like any other property / editor.