Forcing ko.mapping to create an empty array with mapped properties - asp.net-mvc

I am creating a view model in Knockout from a model in mvc4. I am using the mapping plugin. Current code looks like this.
First: I merge the different MVC4 models I need into a single ko viewmodel.
var mergedData = $.extend(true, {}, initialEventData,
{ "Tickets": initialTicketData }, { "TimeZones": timeZones }
);
Second: I add some mapping to add a computed function to my viewmodel.
var mapping = {
'Tickets': {
create: function (options) {
return new updatedTicket(options.data);
}
}
}
var updatedTicket = function (data) {
ko.mapping.fromJS(data, {}, this);
this.formattedPrice = ko.computed(function () {
return "$" + parseFloat(this.Price()).toFixed(2);
}, this);
}
Finlly: I apply the bindings.
var eventViewModel = ko.mapping.fromJS(mergedData, mapping);
However: Sometimes the Tickets model may come back empty. When this happens, the mapping plugin does not create the observable array (obviously). I need to have an empty array with mapped properties created so that I can push new tickets.

I would just add a check to the mapping configuration. Check if the data is there in the updatedTicket function and if it's not, manually create an observable array

just make a container viewmodel where you can create your default tickets array...
also in your callback functions, make sure you use a variable referring to "this"
var mapping = {
'Tickets': {
create: function (options) {
return new updatedTicket(options.data);
}
}
}
var updatedTicket = function (data) {
var self = this;
ko.mapping.fromJS(data, {}, this);
this.formattedPrice = ko.computed(function () {
return "$" + parseFloat(self.Price()).toFixed(2);
}, this);
}
var viewModel = function ( ) {
this.Tickets = ko.observableArray([]);
ko.mapping.fromJS(mergedData, mapping, this);
}
var eventViewModel = new viewModel();

Related

How to filter data from oDataModel in sapui5 after navigation to another view

I've tried different demo application of SAPUI5 like shoping cart, manage product. But I"m unable to solve my problem. I've two views. on home view I've set my model globally like
var model = this.getView().getModel('product');
var oModel = new sap.ui.model.json.JSONModel(model);
sap.ui.getCore().setModel(oModel, "product");
and then I'm navigating to product page. where i'm accessing my product name and trying to access my specific product to bind with my current view.
_routePatternMatched: function(oEvent) {
var name= oEvent.getParameter("arguments").name,
oView = this.getView(),
sPath = "/Product('" + sId + "')";
console.log(sPath);
var oModel = sap.ui.getCore().getModel("product");
var oData = oModel.getData(sPath);
console.log(oData);
oView.bindElement({
path: sPath,
events: {
dataRequested: function() {
oView.setBusy(true);
},
dataReceived: function() {
oView.setBusy(false);
}
}
});
//if there is no data the model has to request new data
if (!oData) {
oView.setBusyIndicatorDelay(0);
oView.getElementBinding().attachEventOnce("dataReceived", function() {
// reset to default
oView.setBusyIndicatorDelay(null);
this._checkIfCourseAvailable(sPath, name);
}.bind(this));
}
},
_checkIfCourseAvailable: function(sPath) {
var oModel = sap.ui.getCore().getModel("product");
var oData = oModel.getData(sPath);
// show not found page
if (!oData) {
this._router.getTargets().display("notFound");
}
},
I got the right result by filtering by id. now after giving path to var oData = oModel.getData(sPath);
console.log(oData); It have the right result but i'm unable to it do not show data on view i'm trying as {pruduct>/name}
pas model name in bindElement and then access via model name ..
oView.bindElement({
path: sPath,
model:modelName,
events: {
dataRequested: function() {
oView.setBusy(true);
},
dataReceived: function() {
oView.setBusy(false);
}
}
});
In view use {modelName>name}

Knockout bind array of complex type with ajax

I have a bootstrap nav-tab and I want to display dynamically content when I select a tab. Ajax returns an array of Results. Each result has Price,Logo,Companyname and an array of Covers. Each cover has Price,MaxCover,Optional and Description.
Rest of html code is here link but now I want to return a more complex type.
<script type='text/javascript'>
var cover = new
{
Price: ko.observable(),
MaxCover: ko.observable(),
Optional: ko.observable(),
Description: ko.observable(),
}
var result = new
{
Price: ko.observable(),
InsLogo: ko.observable(),
CompanyName: ko.observable(),
Covers: ko.observableArray()
};
var tab = function (Id, name, selected) {
this.Id = Id;
this.name = name;
this.Results = ko.observableArray();
this.isSelected = ko.computed(function () {
return this === selected();
}, this);
}
var ViewModel = function () {
var self = this;
self.selectedTab = ko.observable();
self.tabs = ko.observableArray([
new tab(0, 'Tab1', self.selectedTab),
new tab(1, 'Tab2', self.selectedTab),
new tab(2, 'Tab3', self.selectedTab)
]);
self.selectedTab(self.tabs()[0]);
self.selectedTab.subscribe(function () {
$.ajax({
url: '#Url.Action("GetSection")',
data: { Id: self.selectedTab().Id },
type: 'GET',
success: function (data) {
self.selectedTab().Results(data.Results); //Here I want to fill the results!!!!!!
}
});
});
}
ko.applyBindings(new ViewModel());
Your approach is okay with a few small glitches. Some suggestions to improve it:
Make your viewmodels so that they initialize themselves from a parameters object.
Don't introduce dependencies between viewmodels when you don't have have to. I'm thinking of the isSelected property here, this can be taken care of in the view. For example, when inside a foreach: tabs: data-bind="css: {selected: $data === $parent.selectedTab()}"
You have a timing issue: Subscribe to selectedTab first, then initialize it with self.selectedTab(self.tabs()[0]);. It should be obvious why. (Generally it's useful to split viewmodel creation into a "setup" and an "init" phase.)
Only send an Ajax request for tab details when tab details are still empty.
Subscribtions receive the new value of the observable as an argument, use it.
Observables are functions:
If you want to store an Ajax response in them you can use them as a callback directly.
In the same way you can use them as an event handler:
data-bind="click: $parent.selectedTab".
JS convention is to use PascalCase for constructor names (like in viewmodels) and camelCase for all other identifiers.
With all that, we get:
function Tab(data) {
this.Id = data.Id;
this.name = data.name;
this.Results = ko.observableArray();
}
function ViewModel(data) {
var self = this;
// setup
self.selectedTab = ko.observable();
self.selectedTab.subscribe(function (selectedTab) {
if (selectedTab.Results()) return;
$.get('#Url.Action("GetSection")', {Id: selectedTab.Id}).done(selectedTab.Results);
});
// init
self.tabs = ko.observableArray(ko.utils.arrayMap(data.tabs, function (tabData) {
return new Tab(tabData);
}));
self.selectedTab(self.tabs()[0]);
}
ko.applyBindings(new ViewModel({
tabs: [
{Id: 0, name: 'Tab1'},
{Id: 1, name: 'Tab2'},
{Id: 2, name: 'Tab3'}
]
}));
To convert plain data structures that come from the server (like your array results and covers) into a structure of viewmodels, observables and observable arrays I recommend looking into the mapping plugin.

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.

Knockout Custom Binding and Running a ViewModel Function

I found a custom binding that makes an observable update in an editable div.
I'm not able to run a function when an event occurs with it.
Does anyone know what I can do to my custom binding "editableText" run a function in my ViewModel?
I would like the function "nameChange" to run when text is changed.
HTML:
<div contenteditable="true" data-bind="event: { change: nameChange }, editableText: firstName"></div>
Javascript:
//Editable Text Custom Binding
ko.bindingHandlers.editableText = {
init: function (element, valueAccessor) {
$(element).on('blur', function () {
var observable = valueAccessor();
observable($(this).text());
});
},
update: function (element, valueAccessor, allBindingsAccessor, data) {
var value = ko.utils.unwrapObservable(valueAccessor());
$(element).text(value);
}
};
//Knockout ViewModel
function viewModel(){
var self = this;
self.firstName = ko.observable();
self.status = ko.observable();
self.nameChange = function(){
console.log("Name has been updated");
ko.mapping.fromJS("Name has been updated", {}, self.status)
}
self.loadName = function(){
ko.mapping.fromJS("hey", {}, self.firstName)
}
}
var vm = new viewModel();
ko.applyBindings(vm);
vm.loadName();
JSFIDDLE:
http://jsfiddle.net/madscientist1882/urLd2/
How about subscribing to changes on the observable? (Look at explicitly subscribing to observables)
self.firstNameSubscription = self.firstName.subscribe(function (newValue){
//do something
});
If you do this you need to dispose of the subscription when your view model goes down
self.firstNameSubscription.dispose();
If you want the observable to be updated every time a key is entered have a look here
My personal opinon is that using the variable name 'self' is probably a bad idea...

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

Resources