How to get one of observable field's validation result in the computed field using Knockout Validation framework - knockout-validation

i have a view model defined following:
var ViewModel = function() {
var self = this;
self.name = ko.observable().extend({ required: true });
self.identityCode = ko.observable().extend({ required: true, maxLength: 18, minLength: 15 });
self.gender = ko.computed(function() {
// get gender information from the identiy code here
});
self.birthdate = ko.computed(function() {
// get birthdate information from the identity code here
});
self.form_onsubmit = function (form) {
if (!self.isValid()) {
self.errors.showAllMessages();
return false;
} else {
return true;
}
};
};
as you can see the code above, the gender field and brithdate field are computed field which be got from the identity code. i just want to know how to get the validation result of identity code before doing it. thanks!

The validated observables are extended with an computed isValid. Therefore you can check the result with:
self.gender = ko.computed(function() {
// get gender information from the identiy code here
if(self.identityCode.isValid()) {
// do something with the code
}
});

Related

Kendo DropDownList FilterText always null

I have a Kendo DropdownList that I can't get to filter - on looking at the data received in the controller to the following function I notice that the "string text" is always null:
[OutputCache(NoStore = true, Duration = 0)]
public JsonResult GetAssemblys(string text, long site)
{
return Json(CreateFilteredList(text, site, (long)AssetTypeEnum.Assembly), JsonRequestBehavior.AllowGet);
}
This is the code for the DropDownList:
<div>#(Html.Kendo().DropDownList()
.Name("AssemblySelector")
.DataTextField("AssetName")
.DataValueField("AssetId")
.HtmlAttributes(new { style = "width: 570px;" })
.OptionLabel("Select assembly...")
.DataSource(s =>
{
s.Read(r => r.Action("GetAssemblys", "Form707B").Data("getsite"));
s.ServerFiltering(true);
})
.Filter(FilterType.Contains)
.Height(300)
.SelectedIndex(0))
This worked before I added the .Data("getsite")) part to to the read method. getsite() returns a long called site (this is successfully received in the controller).
What is often not known by people using the MVC Builder is that fluent builder puts a default read data handler to send the filter text to their controller.
if you override the data handler you need to send the text filter yourself or call the method it usually calls which is the following
if (DataSource.ServerFiltering && !DataSource.Transport.Read.Data.HasValue() && DataSource.Type != DataSourceType.Custom) {
DataSource.Transport.Read.Data = new ClientHandlerDescriptor {
HandlerName = "function() { return kendo.ui.DropDownList.requestData(jQuery(\"" + EscapeRegex.Replace(Selector, #"\\$1") + "\")); }"
};
}
to do so your getsite function should look like this.
function getsite() {
// drop down element
var $dd = $('#AssemblySelector');
// widget
var dd = dd.data('kendoDropDownList');
var filterText = dd.input.text();
var site = null; // do your logic for this.
return {
site: site,
text: filterText
};
}
or
function getsite() {
// drop down element
var $dd = $('#AssemblySelector');
// widget
var dd = dd.data('kendoDropDownList');
var ret = kendo.ui.DropDownList.requestData($dd);
ret['site'] = site;
return ret;
}

Best way to cast server viewmodel to client's - Knockout

I'm looking the best way to get my server model mapped into the knockout viewmodel
for this purpose here I'm using the mapping plugin,
Id, Title, People were mapped to something similar to the groupViewModel I have here,
How can I be sure and force this mapping to always be casted exactly to a new groupViewModel, the real issue here is Id, Title, People are bound to the view, not the addPerson method,
Please share the best workaround on this, or any better way you know to make it thiis mapping precise and yet simple and clean, thanks.
here our viewModel contains Groups Array, how to cast this Groups array into GroupViewModel items , maybe the question could be answered this way.
var model = #Html.Raw(Json.Encode(Model));
$(function() {
var root = this;
var viewModel = ko.mapping.fromJS(model, root);
// there are Groups as expected but not the addPerson
// will cause undefined exception
var groupViewModel = function (item) {
var self = this;
self.Id = ko.observable(item.Id);
self.Title = ko.observable(item.Title);
self.People = ko.observableArray([]);
self.addPerson = function (name) {
console.log('addPerson Clicked');
}; // .bind(self)
}
ko.mapping.fromJS(viewModel);
ko.applyBindings(viewModel);
});
Edit - Updated based to the answer :
Server ViewModel
In Server we have this :
class ServerVm {
public int Id { get; set; }
public IList<GroupMap> Groups { get; set; } // groupViewModel
}
In the client we have :
var model = #Html.Raw(Json.Encode(Model));
$(function() {
var root = this;
var viewModel = ko.mapping.fromJS(model, root);
//viewModel.Groups = ko.observableArray([]);
// No need because it's same as server model
viewModel.addGroup = function (teamName) {
console.log('addGroup Clicked');
}; // .bind(self)
// addGroup is fine
// it's defined here and working fine, no special mapping needed here
// "model" contains something
var groupViewModel = function (item) {
var self = this;
self.Id = ko.observable(item.Id);
self.Title = ko.observable(item.Title);
self.People = ko.observableArray([]);
// self.addPerson - this is hidden in the "model"
self.addPerson = function (name) {
console.log('addPerson Clicked');
}; // .bind(self)
}
ko.mapping.fromJS(viewModel);
ko.applyBindings(viewModel);
});
Our problem is the self.addPerson located inside our nested collection which because the container(groupViewModel) isn't bound automatically to the GroupMap. everytime I create groupViewModel by hand it's ok cause I'm casting it myself, but this is not the real mapping solution, what's yours, thanks
You could use different overload of ko.mapping.fromJS method that takes 3 parameters, from documentation:
Specifying the update target
...The third parameter to ko.mapping.fromJS indicates the target.
ko.mapping.fromJS(data, {}, someObject);
So in your case you could update your view model definition as follows:
function ViewModel() {
var self = this;
this.addPerson = function(data) {
$.ajax({
url: ...+ self.Id,
contentType: 'application/json;charset=utf-8',
dataType: 'JSON',
type: "POST",
success: function (result) // result
{
console.log('Success');
var avm = new childViewModel(result,self); // another defined vm
self.People.push(avm);
}
});
}
}
ViewModel.prototype.init = function(data) {
var self = this;
ko.mapping.fromJS(data, {}, self);
}
And to initialize it:
...
var model = #Html.Raw(Json.Encode(Model));
...
var vm = new ViewModel();
vm.init(model);
ko.applyBindings(vm);
See working demo.
Another approach, a bit shorter, is to map your model first and then add methods to it, like this:
var vm = ko.mapping.fromJS(model);
vm.addPerson = function(data) {
...
See another demo.
I like first approach more since function is defined inside view model and not added later.
Update:
So, after some clarification in comments and after question update, here is what should be done:
You should use mentioned ko.mapping.fromJS method inside that child object to map it's properties automatically.
You should use mapping object to tell the mapping plugin how to map your child object.
The child object view model:
function groupViewModel(data) {
var self = this;
ko.mapping.fromJS(data, {}, self);
self.addPerson = function(personData) {
// here should be your ajax request, I'll use dummy data again
};
}
The mapping object:
var mapping = {
"Groups" : {
'create': function(options) {
return new groupViewModel(options.data);
}
}
};
And initialization:
var vm = ko.mapping.fromJS(model, mapping);
Here is updated demo.

Returning List as Json and viewbag from same controller action

I am working on asp.net MVC 3 applciation. I have a jquery ui dialog. On Ok button of this dialog, I am opening another jquery ui dialogue. In order to populate the newly opened popup, I am using jquery ajax call which returns a collection. I am using this collection to create table rows. Code is here:
$("#Prices").dialog({
autoOpen: false,
autoResize: true, buttons: {
"OK": function () {
var PirceCurrencies = $('#PirceCurrencies').val();
jQuery("#hdCurrencyId").val(PirceCurrencies);
jQuery(this).dialog('close');
$.ajax({
type: "POST",
dataType: "json",
url: "/Home/GetRecordingRates",
data: { Id: $("#hdCurrencyId").val() },
success: function (data) {
$("#results").find("tr:gt(0)").remove();
var messages = data.Result;
$.each(messages, function(k, v) {
var row = $('<tr>');
row.append($('<td>').html(v.DialPrefix));
row.append($('<td>').html(v.Rate));
$('#results').append(row);
});
jQuery('#RecordingRates').dialog({ closeOnEscape: false });
$(".ui-dialog-titlebar").hide();
$("#RecordingRates").dialog({ dialogClass: 'transparent' });
$('#RecordingRates').dialog('open');
}
});
}
},
open: function () {
$('.ui-dialog-buttonset').find('button:contains("OK")').focus();
$('.ui-dialog-buttonset').find('button:contains("OK")').addClass('customokbutton');
}
});
and controller action is:
public JsonResult GetRecordingRates(int Id)
{
List<DefaultRateChart> defaultRateCharts = new List<DefaultRateChart>();
Currency currency = new Currency();
using (IDefaultRateChartManager defaultRateChartManager = new ManagerFactory().GetDefaultRateChartManager())
{
defaultRateCharts = defaultRateChartManager.GetAll().Where(rc => rc.Currency.Id == Id
&& (!rc.NumberPrefix.StartsWith("#") && !rc.NumberPrefix.Equals("Subscription")
&& !rc.NumberPrefix.Equals("Default")) && rc.AccountCredit == "Credit").ToList();
}
using (ICurrencyManager currencyManager = new ManagerFactory().GetCurrencyManager())
{
currency = currencyManager.GetById(Id);
ViewBag.currecycode = currency.CurrencyCode;
ViewBag.countrycode = currency.CountryCode;
}
return this.Json( new {
Result = ( from obj
in defaultRateCharts
select new {
Id = obj.Id,
DialPrefix = obj.NumberPrefix,
Rate = obj.PurchaseRates
}
)
}, JsonRequestBehavior.AllowGet);
}
All this works fine but I need to show some other data on newly opened popup other than the collection which populates/create html table rows. Fort that do I need to make another ajax call to another controller action which will return the data ?
Please suggest
Look at what you return now in your controller:
new {
Result = ( ... )
}
You are returning an object with 1 property named Result. In your javascript code you get that object returned named data and you retrieve the Result property as your list.
What stops you from adding more properties to that list?
new {
result = ( ... ),
currencyCode = currency.CurrencyCode,
countryCode = currency.CountryCode
}
In javascript you can then use data.currencyCode and data.countryCode
From Controller Action Method you can Return Dictionary like below.
Sample Code - C#
var dic = new List<KeyValuePair<short, object>>
{
new KeyValuePair<Int16, object>(1, SomeObj),
new KeyValuePair<Int16, object>(2, SomeObj),
new KeyValuePair<short, object>(3, SomeObj),
new KeyValuePair<Int16, object>(4, SomeObj)
};
return Json(dic, JsonRequestBehavior.AllowGet);
Sample Code - JQuery- Access Dictionary objects
var obj1; //Global Variables
var obj2; //Global Variables
var obj3; //Global Variables
var obj4; //Global Variables
$.ajax({
url: url,
async: true,
type: 'GET',
data: JSON.stringify({ Parameter: Value }),
beforeSend: function (xhr, opts) {
},
contentType: 'application/json; charset=utf-8',
complete: function () { },
success: function (data) {
DataSources(data);
}
});
function DataSources(dataSet) {
obj1 = dataSet[0].Value; //Access Object 1
obj2 = dataSet[1].Value; //Access Object 2
obj3 = dataSet[2].Value; //Access Object 3
obj4 = dataSet[3].Value; //Access Object 4
}
return a Dictionary from your controller.
convert your collection to string and other object to string and return
dictionary<int, string>
in your javascript sucess function,
JSON.parse(data[0].key) will give you your collection
This will give you an idea
bool inCart = false;
Cart MyCart = default(Cart);
Dictionary<string, string> Result = new Dictionary<string, string>();
Result.Add("inCart", inCart.ToString().ToLower());
Result.Add("cartText", MyCart.CartText());
string ResultString = new JavaScriptSerializer().Serialize(Result);
return ResultString;
Here I am adding two types to a dictionary and returning my serialized dictionary

ASP.NET MVC3 Second custom validator method not triggered

I am trying to implement a custom validation on my ASP.NET MVC3 form.
The first custom validation is only validating if a file has been selected in the file upload input.
It worked fine when I had only one client validation method. When I tried to add a second one. The second validation method is never triggered.
The GetValidationRules method in my attribute class
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ValidationType = "file",
ErrorMessage = "ResumeEmptyError".Translate("fordia_subform")
};
var rule2 = new ModelClientValidationRule
{
ValidationType = "extension",
ErrorMessage = "ResumeFileFormatError".Translate("fordia_subform")
};
var list = new List<ModelClientValidationRule>();
list.Add(rule2);
list.Add(rule);
return list;
}
My javascript code in my view
<script type="text/javascript">
jQuery.validator.addMethod("file", function (value, element) {
return $('#ResumeFileName').val() != '';
});
jQuery.validator.addMethod("extension", function (value, element) {
return $('#ResumeFileName').val() == 'a';
});
jQuery.validator.unobtrusive.adapters.add("file", function (options) {
options.rules["file"] = options.params.param;
if (options.message) {
options.messages['file'] = options.message;
}
});
jQuery.validator.unobtrusive.adapters.add("extension", function (options) {
options.rules["extension"] = options.params.param;
if (options.message) {
options.messages["extension"] = options.message;
}
});
</script>
When I look at my HTML source, I have the following HTML attributes on my input element :
<input data-val="true" data-val-extension="Erreur: format error" data-val-file="Required" id="Resume" name="Resume" type="file" value="" class="input-validation-error">
What am I missing to have multiple client validation methods on this form?
In the script you have shown you seem to be using some options.params.param parameter which is never declared nor passed from your validation attribute. So at its present form your script won't work even with a single rule. You said it was working but I guess you must have changed something in the code because what you have shown has no chance of working.
So if you don't have parameters here's what you could do (notice the empty array passed as second argument to the add adapter method):
jQuery.validator.addMethod("file", function (value, element) {
return $('#ResumeFileName').val() != '';
});
jQuery.validator.unobtrusive.adapters.add("file", [], function (options) {
options.rules["file"] = options.params;
if (options.message) {
options.messages['file'] = options.message;
}
});
jQuery.validator.addMethod("extension", function (value, element) {
return $('#ResumeFileName').val() == 'a';
});
jQuery.validator.unobtrusive.adapters.add("extension", [], function (options) {
options.rules["extension"] = options.params;
if (options.message) {
options.messages["extension"] = options.message;
}
});
and if you have parameters you will need to declare them first on the validation rules returned by the attribute and then use them in the adapter as shown in this post.

Send list/array as parameter with jQuery getJson

I have the following where I'm trying to send list/array to MVC controller method:
var id = [];
var inStock = [];
$table.find('tbody>tr').each(function() {
id.push($(this).find('.id').text());
inStock.push($(this).find('.stocked').attr('checked'));
});
var params = {};
params.ids = id;
params.stocked = inStock;
$.getJSON('MyApp/UpdateStockList', params, function() {
alert('finished');
});
in my contoller:
public JsonResult UpdateStockList(int[] ids, bool[] stocked) { }
both paramaters are null.
Note that if I change the params to single items
params.ids = 1;
params.stocked = true;
public JsonResult UpdateStockList(int ids, bool stocked) { }
then it works ok, so I don't think it's a routing issue.
Try setting the traditional flag:
$.ajax({
url: '/home/UpdateStockList',
data: { ids: [1, 2, 3], stocked: [true, false] },
traditional: true,
success: function(result) {
alert(result.status);
}
});
works fine with:
public ActionResult UpdateStockList(int[] ids, bool[] stocked)
{
return Json(new { status = "OK" }, JsonRequestBehavior.AllowGet);
}
Besides calling .ajax() instead of .getJSON() as Darin suggests or setting the global jQuery.ajaxSettings.traditional to true as jrduncans suggests, you can also pass the result of calling the jQuery .param() function on your params object:
var id = [];
var inStock = [];
$table.find('tbody>tr').each(function() {
id.push($(this).find('.id').text());
inStock.push($(this).find('.stocked').attr('checked'));
});
var params = {};
params.ids = id;
params.stocked = inStock;
$.getJSON('MyApp/UpdateStockList', $.param(params, true), function() {
alert('finished');
});
Unfortunately, while it seems that jquery provides a "traditional" flag to toggle this behavior on jQuery.ajax, it does not on jQuery.getJSON. One way to get around this would to be set the flag globally:
jQuery.ajaxSettings.traditional = true;
See the documentation for jQuery.param: http://api.jquery.com/jQuery.param/
Also see the release notes for this change: http://jquery14.com/day-01/jquery-14 (search for 'traditional')
In the view, generate multiple named fields (not id, as id should be unique per field), noting the use of Name not name:
#foreach (var item in Model.SomeDictionary)
{
#Html.TextBoxFor(modelItem => item.Value.SomeString, new { Name = "someString[]" })
}
Then retrieve the input field values using jQuery, so:
var myArrayValues = $('input[name="someString[]"]').map(function () { return $(this).val(); }).get();
You can use this directly in jQuery / AJAX as follows:
$.ajax({
type: "POST",
url: "/MyController/MyAction",
dataType: 'json',
data: {
someStrings: $('input[name="someString[]"]').map(function () { return $(this).val(); }).get(),
someDates: $('input[name="someDate[]"]').map(function () { return $(this).val(); }).get(),
Then in the controller action in MVC:
[HttpPost]
public JsonResult MyAction(string[] someStrings, DateTime[] someDates...

Resources