I have a field called ReclaimTotalAmount that displays a value from a c# model.
<div class="container-left">
Reclaim Total: <span data-bind='text: model.ReclaimTotalAmount'></span>
</div>
And I also have a field that displays the value of the Sum of fields:
<div class="container-left">
Countered Total:<span data-bind='text: model.CounteredTotalAmount'></span>
</div>
To get the CounteredTotalAmount I use the following
self.model.CounteredTotalAmount = ko.computed(function () {
var SumCounterTotals = 0;
for (var i = 0; i < self.model.CounterReclaimViewModels().length; i++) {
SumCounterTotals += (
parseFloat(self.model.CounterReclaimViewModels()[i].CounteredTimeAmount())
+ parseFloat(self.model.CounterReclaimViewModels()[i].CounteredMileAmount())
+ parseFloat(self.model.CounterReclaimViewModels()[i].CounteredAppurtenanceAmount())
)
}
So I need to check weather the Countered total is greater than the ReclaimTotal. I tried this:
I created an extension
self.model.CounteredTotalAmount.extend({
greaterThan: { params: self.model.ReclaimTotalAmount, message: "Car number high must be greater than the low." }
});
then this is the function
ko.validation.rules['greaterThan'] = {
validator: function (val, other) {
if (val != null && val != "" && other != null) {
var first = parseInt(val);
var second = parseInt(ko.unwrap(other));
if (!isNaN(first) && !isNaN(second)) {
return first > second;
}
}
return true;
},
message: 'Must be greater than or equal to the other value'
};
everything works except the validation. I am not able to generate an error message if the Countered Total is greater than the Reclaim total...
Thanks
Multiple things could go wrong, but because you haven't' posted a complete example only code fragments you need to check the following things:
Because you are creating a custom validator you need to call ko.validation.registerExtenders(); before you want to use it for the first time.
the KO .extend({ }) returns the extended observable so you need to override the existing property with the result:
self.CounteredTotalAmount = self.CounteredTotalAmount.extend({
greaterThan: {
params: self.ReclaimTotalAmount,
message: "Car number high must be greater than the low."
}
});
Because KO validation only overrides the value and checked binding to automatically display the error message. So you need to use the validationMessage binding to display your error because you are using the text binding here:
<div class="container-left">
Countered Total:<span data-bind='text: CounteredTotalAmount'></span>
<span data-bind="validationMessage: CounteredTotalAmount"></span>
</div>
Here is a working JSFiddle with the simplified version of your code.
Related
Following Post method in my ASP.NET MVC Core 1.1 app adds a record to database. The method successfully adds the record as long as user selects exactly 3 order types from a multi-select dropdown. User is supposed to select at most 3 order types from the dropdown. So, if a user selects less than 3 order types it, as expected, throws the well-know error: Index was outside the bounds of the array. Question: How can I avoid the above error if user selects less than 3 order types. I guess I can place the entire var oOrder = new Order{...} statement below inside each block of an if...else.. to avoid the error. But in real scenario there are lots more model properties and hence repeating them 3 times in if...else... blocks would make the code look a more complicated than it really is. Are there any better ways of doing it?
[HttpPost]
public IActionResult AddOrder(OrderViewModel model)
{
if (ModelState.IsValid)
{
var oOrder = new Order
{
orderName = model.orderName,
StartDate = model.StartDate,
EndDate = model.EndDate,
....
....
lkupType_1_ID = model.SelectedTypeIDs[0],
lkupType_2_ID = model.SelectedTypeIDs[1],
lkupType_3_ID = model.SelectedTypeIDs[2],
....
};
_context.Add(oOrder);
}
return RedirectToAction(....);
}
UPDATE:
snapshot of the View
....
<div>....</div>
....
<div class="form-group">
<label asp-for="SelectedOrderTypeIDs"></label>
<div class="col-md-10">
<select asp-for="SelectedOrderTypeIDs" asp-items="Model.lstOrderTypes"></select>
</div>
</div>
<button type="submit" name="submit">Add Order</button>
NOTE: I'm using ASP.NET MVC tag helpers and often use this post from #Shyju for good example of multi-select tag helper.
You can try below, an if as a tenary operator:
[HttpPost]
public IActionResult AddOrder(OrderViewModel model)
{
if (ModelState.IsValid)
{
var oOrder = new Order
{
orderName = model.orderName,
StartDate = model.StartDate,
EndDate = model.EndDate,
....
....
lkupType_1_ID = (model.SelectedTypeIDs.Length > 0) ? model.SelectedTypeIDs[0] : 0, // You can default it to null if it is Int?
lkupType_2_ID = (model.SelectedTypeIDs.Length > 1) ? model.SelectedTypeIDs[1] : 0,
lkupType_3_ID = (model.SelectedTypeIDs.Length > 2) ? model.SelectedTypeIDs[2] : 0,
....
};
_context.Add(oOrder);
}
return RedirectToAction(....);
}
You can use the Length property in order to check the number of items in SelectedTypeIDs list.
if(model.SelectedTypeIDs.Length>3){
//code
}
If the condition is false you can use ModelState.AddModelError method in order to show the error in the View.
if(model.SelectedTypeIDs.Length>3){
ModelState.AddModelError("Dropdown", "Error! You must have maximum of 3 options");
return View();
}
UPDATE
You can create a generic function which returns 0, if the index is out of bound or the list item instead.
public static TValue GetSafe<TItem>(this IList<TItem> list,
int index, TValue defaultValue)
{
if (index < 0 || index >= list.Count)
{
return defaultValue;
}
return list[index];
}
Now you can use this function to implement you functionality.
var oOrder = new Order
{
orderName = model.orderName,
StartDate = model.StartDate,
EndDate = model.EndDate,
....
....
lkupType_1_ID =model.SelectedTypeIDs.GetSafe(0, 0) ,
lkupType_2_ID =model.SelectedTypeIDs.GetSafe(1, 0) ,
lkupType_3_ID =model.SelectedTypeIDs.GetSafe(2, 0) ,
....
};
I am trying to limit my textbox to include max 500 character, for example i want to receive a question from the user but it the max length is 500 and how can i show the number of how many character left while writing ?
this is my code in the view, but i don't know its not working, i have tried various ways!
<li>
#Html.LabelFor(m => m.TeacherQuestion, new { maxlength = 500 })
#Html.TextBoxFor(m => m.TeacherQuestion , new { maxlength = 500})
</li>
There is no built-in element or attribute for this - you need to use Javascript.
Assign an ID to your input and add a new element to hold the remaining character count
#Html.TextBoxFor(m =>
m.TeacherQuestion, new { id = "questionInput", maxlength = 500 })
<span id="charsLeft"></span>
Next add a reference to jQuery and add the following Javascript to your layout view:
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script>
$(function () {
$("#questionInput").keyup(function () {
var charsLeft = $(this).attr("maxlength") - $(this).val().length;
$("#charsLeft").text(charsLeft + " characters left");
});
});
</script>
(If you need to include the script in a normal view, please refer to this answer for guidance.)
The result should look something like this:
I used the following tutorial:
http://msdn.microsoft.com/en-us/library/gg508808%28VS.98%29.aspx
And everything seemed fine, but in my case, string Username always comes back null. After tonnes of research, I found everyone discovered BIND Prefixes. That would be great in many circumstances, but not this one. I should note all properties and names line up, however in my for loop, the EditorFor creates a [i].Username field and this doesn't map to any model property.
QUESTION: I think I want to map [i].Username to Username where i is any number from 0-infinity, so when it GETS, the value is passed to the Action properly. How do I do this? If this is wrong, what do I do validate this for a specific row in a table?
#for (var i = 0; i < Model.Count; i++)
{
BLAH BLAH BLAH CODE FOR BUILDING TABLE ROWS
<td>
#Html.EditorFor(modelItem => Model[i].Username)
</td>
}
Since I could technically have HUNDREDS if not THOUSANDS of records, I would rather not had a binding PREFIX for all 1000. Am I fundamentally missing something here? I am new to ASP.NET MVC and am used to WebForms so I feel like sometimes I am mixing concepts and mashing up something that is entirely wrong.
EDIT:
I fixed it by doing the following, but not sure if this is the best idea. I set the parameter equal to the FieldName without [i] prefix, but still retrieve the element with the [i] prefix. Javascript isn't my Forte so please let me know if it is horrible.
adapters.add("remote", ["url", "type", "additionalfields"], function (options) {
var value = {
url: options.params.url,
type: options.params.type || "GET",
data: {}
},
prefix = getModelPrefix(options.element.name);
$.each(splitAndTrim(options.params.additionalfields || options.element.name), function (i, fieldName) {
var paramName = fieldName.substr(fieldName.lastIndexOf(".") + 1);
var actualFieldName = appendModelPrefix(fieldName, prefix)
value.data[paramName] = function () {
return $(options.form).find(":input").filter("[name='" + escapeAttributeValue(actualFieldName) + "']").val();
};
});
setValidationValues(options, "remote", value);
});
You have not posted your code for the model or controller, but assuming you have a RemoteAttribute applied to property Username, for example
public class MyModel
{
[Remote("IsValidUserName", "Person")]
public string Username { get; set; }
}
with a method in PersonController
public JsonResult IsValidUserName(string Username)
{
....
}
and the view
#model List<Person>
...
#for (var i = 0; i < Model.Count; i++)
{
#Html.EditorFor(m => m[i].Username)
}
This will generate html such as
<input name="[0].UserName" ... />
<input name="[1].UserName" ... />
Unfortunately the remote method in jquery-validate posts back the name and value of the element so that the ajax call looks like
$.ajax({
url: '/Person/IsValidUserName',
data: { [0].UserName: 'someone#somewhere.com' },
...
which will not bind.
I have reported this as an issue at Codeplex with a possible solution. In the meantime you can modify the remote method in jquery-validate.js file as follows
remote: function(value, element, param) {
....
var data = {};
// data[element.name] = value;
data[element.name.substr(element.name.lastIndexOf(".") + 1)] = value; // add this
This will strip the prefix so that the posted data is
data: { UserName: 'someone#somewhere.com' },
and will correctly bind to the method.
Assuming the code is formatted in the following way:
View:
#for(var i = 0; i<Model.Count; i++) {
<div class="row">
#Html.EditorFor(modelItem => Model[i].Username)
</div>
}
<style>
.valid{
background: lime;
}
</style>
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
Model:
public class MyModel {
[Remote("IsValidUserName", "Validation", HttpMethod = "POST")]
public string Username { get; set; }
}
It is possible to use the automatic modelbinding to bind to the remote validation. If you were to use a list or array for this, the binding would fail while a Dictionary can catch this error.
Be aware however that the Key in the dictionary will be consistent with the id in the view (e.g. [5].Username will map to {Key: 5, Value: MyModel{Username:...}}) and won't be a default 0, hence the use of a Linq query.
Controller:
[HttpPost]
public JsonResult IsValidUserName(Dictionary<int,MyModel> Users) {
return Json(Users.First().Value.Username.Contains("User"));
}
I am trying to dynamically add a text box based on the selection of dropbox. When the user selects 'other' a text box gets generated asking them to explain the other. The user can dynamically add mutiple dropboxes, resulting in multiple text boxes if other is selected value in dropbox. Every generated dropbox has a unique name which gets read and placed in id of dynamically generated textbox.
The problem i am facing is that when there are multiple dropboxes and the first dropbox selection is something other that 'Other' and second dropbox value is other, the text box generated is placed in front of the first dropbox where it should be placed in front of the dropbox relevant to it.
The html code is as follows for the dropbox:
<div id="container">
<label id="rightlabel"for="dropbox1"></label>
<span>
<select id="frequency" onclick="getData(this, name)" name="dropbox[4fb103e3-06e7-4c88-8836-73b855968478]"></select>
<span class="field-validation-valid" data-valmsg-replace="true" data-valmsg-for="dropbox[4fb103e3-06e7-4c88-8836-73b855968478]"></span>
</span>
<div id="hiddenothertexbox"></div>
</div>
Javascript is as follows:
function getData(title, i) {
var value = title.options[title.selectedIndex].value;
var y = i.replace(/-/g, '')
$('#hiddenother').attr('id', y);
if (value == 'Other') {
str = '<label id="leftlabel">if other, please specify</label><span><input id="textboxid" class="text-box single-line" type="text" value="" name="textbox ></input></span>';
$('#'+y).html(str);
}
else {
str = '';
$('#'+y).html(str);
}
}
teh javascript gets the names of the dropbox and replaces the id of 'hiddenanothertextbox' with that id so its unique. I have an idea of the problem, i think its because when the user does not click teh first dropbox the id of 'hiddenanothertextbox' does not change for the first dropbox and when another dropbox is added and the value is changed, the hiddenanothertextbox for first dropox value changes adding it in front of first not second. I am struggling to achieve teh required result.
UPDATED JAVASCRIPt
function getData(title, i) {
var value = $(title).val();
var y = i.replace(/-/g, '');
$('#hiddenother').attr('id', y);
if (value == 'Other') {
str = '<label id="leftlabel">if other, please specify</label><span><input id="textboxid" class="text-box single-line" type="text" value="" name="textbox"" ></input></span>';
$(title).after(str);
}
else {
$(title).nextUntil('#textboxid').remove();
}
}
Working Demo
Use this code snippet.
var flag = 0;
function getData(title, i) {
var value = $(title).val();
var y = i.replace(/-/g, '');
$('#hiddenother').attr('id', y);
if (value == 'Other' ) {
if(flag == 0){
flag=1;
str = '<label id="leftlabel">if other, please specify</label><span><input id="textboxid" class="text-box single-line" type="text" value="" name="textbox"></input></span>';
$(title).after(str);
}
}
else {
flag=0;
$(title).nextUntil('#textboxid').remove();
}
}
Html :
<select id="frequency" onchange="getData(this, name)" name="dropbox[4fb103e3-06e7-4c88-8836-73b855968478]">
Changes :
var value = $(title).val() gives you the correct value
Use onchange="getData(this, name)" instead of onclick=".."
In variable str : name="textbox" did not have ending "
use .after() or .append() instead of .html()
I'm trying to take a HTML form and serialize the fields so that it can be stored as attributes against an element in JavaScript (you can use jQuery). This can later be parsed in C# and converted to a .NET type. This must work for any type as the form is generated via an Ajax call to the server.
For example given the following form:
<form>
<input type="text" name="Assembly" value="MyAssembly.dll" />
<input type="text" name="Class" value="MyClass" />
<input type="text" name="Parameters.Parameter1" value="5" />
<input type="text" name="Parameters.Parameter2" value="10" />
</form>
It would produce something like:
<widget assembly="MyAssembly.dll" class="MyClass" parameters="???"></widget>
Note: ??? would be replaced with either JSON or XML (depending on your which you think is best).
Now say I stored this string in the database I need to parse it on the server to convert it to a .NET type. I can do a regular expression to get the appropriate attributes leaving me with the following variables:
var assembly = "MyAssembly.dll";
var #class = "MyClass";
var parameters = "???";
Now finally I need to serialize this to a .NET type.
I'd appreciate it if someone could help. Thanks
I've come up with something that works. My solution is slightly more complicated but I'll try to post the key bits in case anyone is interested.
First I created the following plugins:
$.fn.serializeObject = function(prefix) {
var o = {};
// Serialize the form as an array
var a = this.serializeArray()
.filter($.proxy(function(element) {
// Make sure the prefix matches and it is not a checkbox (this is needed since ASP.NET MVC renders a hidden checkbox for the false value)
return element.name.indexOf(prefix || '') == 0 && $('[name=\'' + element.name + '\'][type=\'checkbox\']', this).length == 0;
}, this));
// Now append the checkbox values (this makes sure we only have one element in the array with the correct value whether it is selected or not)
a = a.concat($('input[type=\'checkbox\']', this).map(function() {
return { 'name': this.name, 'value': $(this).is(':checked') ? 'true' : 'false' }
}).get());
$.each(a, function() {
var n = this.name.substr((prefix || '').length);
if (o[n] !== undefined) {
if (!o[n].push)
o[n] = [o[n]];
o[n].push(this.value || '');
} else
o[n] = this.value || '';
});
return o;
};
Now for my application I actually have a WYSIWYG plugin which embeds my custom widget tag within the editor. Here's an example of what you could do to create the widget tag from the form when it is submitted (this would then be stored in the database):
$('form').submit(function(e) {
var parameters = JSON.stringify($('form').serializeObject('Parameters.'));
var widget = '<widget assembly="' + $('[name=\'Assembly\']').val() + '" class="' + $('[name=\'Class\']').val() + '" parameters="' + parameters + '"></widget>';
...
});
Finally on the server you need to replace the widget on display by doing something like:
output = Regex.Replace(output, #"<widget assembly=""(.*?)"" class=""(.*?)"" parameters=""(.*?)""></widget>", m => ReplaceWidget(m, helper));
Here's the ReplaceWidget method:
private static string ReplaceWidget(Match match, HtmlHelper helper) {
var assembly = match.Groups[1].Value;
var #class = match.Groups[2].Value;
var serializedParameters = match.Groups[3].Value;
// Get the type
var type = Assembly.LoadFrom(HttpContext.Current.Server.MapPath("~/bin/" + assembly)).GetType(#class);
// Deserialize the parameters
var parameters = JsonConvert.DeserializeObject(HttpUtility.HtmlDecode(serializedParameters), type);
// Return the widget
return helper.Action("Widget", "Widgets", new { parameters = parameters }).ToString();
}
Hope this helps.