Databinding with Knockout JS not working - javascript

I'm trying to bind my JSON object to a HTML div but none of the bindings seem to work. This is my current try on the subject. But I have tried using the template binding already. That resulted in an undefined error , but the object is correctly loaded because i always get it in the console.
$(document).ready(function () {
var submissionViewModel = new SubmissionModel();
submissionViewModel.getSubmission().done(function () {
ko.applyBindings(submissionViewModel, document.getElementById("submission"));
})
});
var SubmissionModel = function () {
var self = this;
//self.loading = ko.observableArray();
self.Submission = ko.observable(null);
self.getSubmission = function () {
// Let loading indicator know that there's a new loading task that ought to complete
//self.loading.push(true);
return $.getJSON('/Submission/GetSubmission',
function (data) {
console.log("submission loading")
console.dir(data);
self.Submission = ko.mapping.fromJSON(JSON.stringify(data));
}
);
}
}
HTML
<div id="submission" data-bind="with: Submission">
<span data-bind="text: SubmissionTitle"></span>
</div>
JSON
"{"
SubmissionID":"1be87a85-6d95-43aa-ad3c-ffa047b759a5",
"SubmissionTitle":"nog wat sliden",
"SubmissionDescription":"////",
"SubmissionFile":"TESTFILE ",
"CreatedOn":"2015-09-02T21:10:54.913",
"SubmissionPoolID":"5af408f5-515c-4994-88dd-dbb2e4a242a2",
"SubmissionTypeID":1,
"CreatedBy":"a028a47d-3104-4ea4-8fa6-7abbb2d69bbd
"}"
I have been chewing on this problem for a few days now an I can't seem to get it to work. Could any of you point me in the right direction ?

In java-script to decode object inside string you need to use JSON.parse and make sure your object is not structured in such way double quote inside double quote .
viewModel:
var json = '{"SubmissionID":"1be87a85-6d95-43aa-ad3c-ffa047b759a5","SubmissionTitle":"nogwatsliden","SubmissionDescription":"--","SubmissionFile":"TESTFILE ","CreatedOn":"2015-09-02T21:10:54.913","SubmissionPoolID":"5af408f5-515c-4994-88dd-dbb2e4a242a2","SubmissionTypeID":1,"CreatedBy":"a028a47d-3104-4ea48fa67abbb2d69bbd"}'
var ViewModel = function () {
this.Submission = ko.observable();
this.Submission(ko.mapping.fromJS(JSON.parse(json)));
//you can also use ko.mapping.fromJSON(json) as jeroen pointed out
};
ko.applyBindings(new ViewModel());
working sample here

Related

Mapping a nested object as an observable from a complex JSON using the create callback

I've got a complex object in a JSON format. I'm using Knockout Mapping, customizing the create callback, and trying to make sure that every object that should be an observable - would actually be mapped as such.
The following code is an example of what I've got:
It enables the user to add cartItems, save them (as a JSON), empty the cart, and then load the saved items.
The loading part fails: It doesn't display the loaded option (i.e., the loaded cartItemName). I guess it's related to some mismatch between the objects in the options list and the object bounded as the cartItemName (see this post), but I can't figure it out.
Code (fiddle):
var cartItemsAsJson = "";
var handlerVM = function () {
var self = this;
self.cartItems = ko.observableArray([]);
self.availableProducts = ko.observableArray([]);
self.language = ko.observable();
self.init = function () {
self.initProducts();
self.language("english");
}
self.initProducts = function () {
self.availableProducts.push(
new productVM("Shelf", ['White', 'Brown']),
new productVM("Door", ['Green', 'Blue', 'Pink']),
new productVM("Window", ['Red', 'Orange'])
);
}
self.getProducts = function () {
return self.availableProducts;
}
self.getProductName = function (product) {
if (product) {
return self.language() == "english" ?
product.productName().english : product.productName().french;
}
}
self.getProductValue = function (selectedProduct) {
// if not caption
if (selectedProduct) {
var matched = ko.utils.arrayFirst(self.availableProducts(), function (product) {
return product.productName().english == selectedProduct.productName().english;
});
return matched;
}
}
self.getProductColours = function (selectedProduct) {
selectedProduct = selectedProduct();
if (selectedProduct) {
return selectedProduct.availableColours();
}
}
self.addCartItem = function () {
self.cartItems.push(new cartItemVM());
}
self.emptyCart = function () {
self.cartItems([]);
}
self.saveCart = function () {
cartItemsAsJson = ko.toJSON(self.cartItems);
console.log(cartItemsAsJson);
}
self.loadCart = function () {
var loadedCartItems = ko.mapping.fromJSON(cartItemsAsJson, {
create: function(options) {
return new cartItemVM(options.data);
}
});
self.cartItems(loadedCartItems());
}
}
var productVM = function (name, availableColours, data) {
var self = this;
self.productName = ko.observable({ english: name, french: name + "eux" });
self.availableColours = ko.observableArray(availableColours);
}
var cartItemVM = function (data) {
var self = this;
self.cartItemName = data ?
ko.observable(ko.mapping.fromJS(data.cartItemName)) :
ko.observable();
self.cartItemColour = data ?
ko.observable(data.cartItemColour) :
ko.observable();
}
var handler = new handlerVM();
handler.init();
ko.applyBindings(handler);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://rawgit.com/SteveSanderson/knockout.mapping/master/build/output/knockout.mapping-latest.js
"></script>
<div>
<div data-bind="foreach: cartItems">
<div>
<select data-bind="options: $parent.getProducts(),
optionsText: function (item) { return $parent.getProductName(item); },
optionsValue: function (item) { return $parent.getProductValue(item); },
optionsCaption: 'Choose a product',
value: cartItemName"
>
</select>
</div>
<div>
<select data-bind="options: $parent.getProductColours(cartItemName),
optionsText: $data,
optionsCaption: 'Choose a colour',
value: cartItemColour,
visible: cartItemName() != undefined"
>
</select>
</div>
</div>
<div>
<button data-bind="text: 'add cart item', click: addCartItem" />
<button data-bind="text: 'empty cart', click: emptyCart" />
<button data-bind="text: 'save cart', click: saveCart" />
<button data-bind="text: 'load cart', click: loadCart" />
</div>
</div>
What needs to be changed to fix it?
P.S.: I've got another piece of code (see it here) that demonstrates a persistance of the selected value even after changing the options - though there optionsValue is a simple string, while here it's an object.
EDIT:
I figured out the problem: the call ko.mapping.fromJS(data.cartItemName) creates a new productVM object, which is not one of the objects inside availableProducts array. As a result, none of the options corresponds to the productVM contained in the loaded cartItemName, so Knockout thereby clears the value altogether and passes undefined.
But the question remains: how can this be fixed?
In the transition from ViewModel -> plain object -> ViewModel you loose the relation between the products in your cart and the ones in your handlerVM.
A common solution is to, when loading a plain object, manually search for the existing viewmodels and reference those instead. I.e.:
We create a new cartItemVM from the plain object
Inside its cartItemName, there's an object that does not exist in handlerVM.
We look in handlerVM for a product that resembles this object, and replace the object by the one we find.
In code, inside loadCart, before setting the new viewmodels:
loadedCartItems().forEach(
ci => {
// Find out which product we have:
const newProduct = ci.cartItemName().productName;
const linkedProduct = self.availableProducts()
.find(p => p.productName().english === newProduct.english());
// Replace the newProduct by the one that is in `handlerVM`
ci.cartItemName(linkedProduct)
}
)
Fiddle: https://jsfiddle.net/7z6010jz/
As you can see, the equality comparison is kind of ugly. We look for the english product name and use it to determine the match. You can also see there's a difference in what is observable and what isn't.
My advice would be to use unique id properties for your product, and start using those. You can create a simpler optionsValue binding and matching new and old values happens automatically. If you like, I can show you an example of this refactor as well. Let me know if that'd help.

How to Use JSON Object to populate Knockout Form

I have a form that i would like to populate using knockout data-binding from a JSON object. I have hard coded the values using knockout. What i am really after is since i have a JSON object how do i turn that into data that knockout can use to populate my form dynamically? Here is my FIDDLE
<p>Hero Name: <span data-bind="text : HeroName" /></p>
<p>Agent Writing Number: <span data-bind="text : HeroCode" /></p>
<p>Contact Phone: <span data-bind="text : ContactPhone" /></p>
var HeroDetails = $.get("GetHeroDetailsForVillianView", {
HeroRequestIdForvillianDetailsView: HeroRequestIdForVillianDetailsView
}, function (returnDataForDetails) {
var results = JSON.parse(returnDataForDetails);
$('#json').text(JSON.stringify({HeroName:'PeterParker', ContactPhone: '123456', HeroCode: 3 }, null, 4));
function ViewModel() {
var self = this;
self.HeroName = ko.observable("Peter Parker");
self.ContactPhone = ko.observable("123456858");
self.HeroCode = ko.observable("2455654");
}
ko.applyBindings(new ViewModel());
Check out this page of the KO documentation for more details. You might also be interested in the mapping plugin.
Here is the pattern I use:
function ViewModel(data) {
var self = this;
self.HeroName = ko.observable(data.HeroName);
self.ContactPhone = ko.observable(data.PhoneNumber);
self.HeroCode = ko.observable(data.HeroCode);
}
var viewModel;
$.getJSON("/some/url", function(data) {
viewModel = new ViewModel(data);
})
ko.applyBindings(viewModel);
There are a few ways to do this, but pretty simple with jQuery.
https://jsfiddle.net/2bwegmsv/4/
I added ids to each of the divs where you want the data and then added this to your javascript:
var myJson = {HeroName:'PeterParker', ContactPhone: '123456', HeroCode: 3 };
$('#hname').append(myJson.HeroName);
$('#agent').append(myJson.ContactPhone);
$('#contact').append(myJson.HeroCode);
May not work precisely as you want, but should point you in the right direction.
Dynamically: Just binding JSON object.
var jsonDataViewModel = {HeroName:'Peter Parker', ContactPhone: '123456', HeroCode: 3};
ko.applyBindings(jsonDataViewModel);
http://knockoutjs.com/documentation/observables.html

DropDownList Change() doesn't seem to fire

So, I have been bashing my head against the desk for a day now. I know this may be a simple question, but the answer is eluding me. Help?
I have a DropDownList on a modal that is built from a partial view. I need to handle the .Change() on the DropDownList, pass the selected text from the DropDownList to a method in the controller that will then give me data to use in a ListBox. Below are the code snippets that my research led me to.
all other controls on the modal function perfectly.
Can anyone see where I am going wrong or maybe point me in the right direction?
ProcessController
// I have tried with [HttpGet], [HttpPost], and no attribute
public ActionResult RegionFilter(string regionName)
{
// Breakpoint here is never hit
var data = new List<object>();
var result = new JsonResult();
var vm = new PropertyModel();
vm.getProperties();
var propFilter = (from p in vm.Properties
where p.Region == regionName && p.Class == "Comparable"
select p).ToList();
var listItems = propFilter.ToDictionary(prop => prop.Id, prop => prop.Name);
data.Add(listItems);
result.Data = data;
return result;
}
Razor View
#section scripts{
#Scripts.Render("~/Scripts/ui_PropertyList.js")
}
...
<div id="wrapper1">
#using (Html.BeginForm())
{
...
<div id="fancyboxproperties" class="content">
#Html.Partial("PropertyList", Model)
</div>
...
<input type="submit" name="bt_Submit" value="#ViewBag.Title" class="button" />
}
</div>
Razor (Partial View "PropertyList.cshtml")
...
#{ var regions = (from r in Model.Properties
select r.Region).Distinct(); }
<div>
<label>Region Filter: </label>
<select id="ddl_Region" name="ddl_Region">
#foreach (var region in regions)
{
<option value=#region>#region</option>
}
</select>
</div>
// ListBox that needs to update after region is selected
<div>
#Html.ListBoxFor(x => x.Properties, Model.Properties.Where(p => p.Class == "Comparable")
.Select(p => new SelectListItem { Text = p.Name, Value = p.Id }),
new { Multiple = "multiple", Id = "lb_C" })
</div>
...
JavaScript (ui_PropertyList.js)
$(function () {
// other events that work perfectly
...
$("#ddl_Region").change(function () {
$.getJSON("/Process/RegionFilter/" + $("#ddl_Region > option:selected").attr("text"), updateProperties(data));
});
});
function updateProperties(data, status) {
$("#lb_C").html("");
for (var d in data) {
var addOption = new Option(data[d].Value, data[d].Name);
addOption.appendTo("#lb_C");
}
}
The callback function passed to your $.getJSON method is wrong. You need to pass a reference to the function, not to invoke it.
Try this:
$.getJSON("/Process/RegionFilter/" + $("#ddl_Region > option:selected").text(), updateProperties);
Also, in order to get the text of the selected drop-down option, you need to use the text() function:
$("#ddl_Region > option:selected").text()
See Documentation

Knockout Nested Bindings--Visible in DOM but won't display

I've got an issue where my viewmodel has an observable object that contains observable properties. When I try to access those properties they don't display. I can, however, see that all the properties with values are visible in the DOM using the Knockout chrome extension.
My code looks like:
viewmodel:
self.device=ko.observable();
self.device(querydevice.query({"url": self.url, "ref":self.ref}));
query code:
define(['jquery','knockout','hsd'], function ($,ko, device) {
return{
query:function (params) {
var hsdevice=ko.observable();
self.url=params.url;
self.ref=params.ref;
var controlData = $.getJSON(self.url + "/JSON?request=getcontrol&ref=" + self.ref);
var statusData = $.getJSON(self.url + "/JSON?request=getstatus&ref=" + self.ref);
$.when(controlData, statusData).done(function (_cdata, _sdata) {
var data = $.extend(_cdata[0], _sdata[0]);
hsdevice(new device(data));
});
return hsdevice;
}};
});
device object:
define(['knockout'], function (ko) {
return function device (data){
var self=this;
self.deviceName = ko.observable(data.Devices[0].name);
self.value = ko.observable(data.Devices[0].value);
self.status =ko.observable(data.Devices[0].status);
self.controlPairs = ko.observableArray();
ko.utils.arrayPushAll(self.controlPairs, data.ControlPairs);
};
});
This is what I see being returned:
" device": Object
controlPairs: Array[2]
deviceName: "Garage Hall Light"
status: "Off"
value: 0
In my HTML I have this:
<span class="tile-title align-" data-bind="with: device.deviceName"></span>
I've also tried using data-bind:"text: device().deviceName", but that doesn't work either. Nothing displays. I can however access over observable properties that are on the viewmodel. The only difference is that they're single level properties with no sub-binding. So I am able to see something like self.test("test") in my html but not my self.device with the nested databinds.
Any ideas what I'm doing wrong?
Thanks!
It looks like you are using jquery promises. what you need to do is return the $.when
something like
define(['jquery','knockout','hsd'], function ($,ko, device) {
return{
query:function (params) {
self.url=params.url;
self.ref=params.ref;
var controlData = $.getJSON(self.url + "/JSON?request=getcontrol&ref=" + self.ref);
var statusData = $.getJSON(self.url + "/JSON?request=getstatus&ref=" + self.ref);
return $.when(controlData, statusData).done(function (_cdata, _sdata) {
var data = $.extend(_cdata[0], _sdata[0]);
return new device(data);
});
}};
});
then you end up with something like this.
querydevice.query({"url": self.url, "ref":self.ref})
.when(function(data){
self.device(data);
return true;
});
Thanks to Nathan for his code contribution. I was finally able to access my nested properties in the html by using:
<!-- ko with: device -->
<!-- /ko -->
and THEN data-bind to the property I needed.

Knockout.js - Data binding outputting function text when not using parens

I am new to Knockout and have been trying to follow code examples and the documentation, but keep running into an issue. My data bindings printing the Knockout observable function, not the actual values held by my observable fields. I can get the value if I evaluate the field using (), but if you do this you do not get any live data-binding / updates.
Below are some code snippets from my project that are directly related to the issue I am describing:
HTML
<div class="col-xs-6">
<div data-bind="foreach: leftColSocialAPIs">
<div class="social-metric">
<img data-bind="attr: { src: iconPath }" />
<strong data-bind="text: name"></strong>:
<span data-bind="text: totalCount"></span>
</div>
</div>
</div>
Note: leftColSocialAPIs contains an array of SocialAPIs. I can show that code too if needed.
Initializing the totalcount attribute
var SocialAPI = (function (_super) {
__extends(SocialAPI, _super);
function SocialAPI(json) {
_super.call(this, json);
this.totalCount = ko.observable(0);
this.templateName = "social-template";
}
SocialAPI.prototype.querySuccess = function () {
this.isLoaded(true);
appManager.increaseBadgeCount(this.totalCount());
ga('send', 'event', 'API Load', 'API Load - ' + this.name, appManager.getRedactedURL());
};
SocialAPI.prototype.toJSON = function () {
var self = this;
return {
name: self.name,
isActive: self.isActive(),
type: "social"
};
};
return SocialAPI;
})(API);
Updating totalcount attribute for LinkedIn
var LinkedIn = (function (_super) {
__extends(LinkedIn, _super);
function LinkedIn(json) {
json.name = "LinkedIn";
json.iconPath = "/images/icons/linkedin-16x16.png";
_super.call(this, json);
}
LinkedIn.prototype.queryData = function () {
this.isLoaded(false);
this.totalCount(0);
$.get("http://www.linkedin.com/countserv/count/share", { "url": appManager.getURL(), "format": "json" }, this.queryCallback.bind(this), "json").fail(this.queryFail.bind(this));
};
LinkedIn.prototype.queryCallback = function (results) {
if (results != undefined) {
results.count = parseInt(results.count);
this.totalCount(isNaN(results.count) ? 0 : results.count);
}
this.querySuccess();
};
return LinkedIn;
})(SocialAPI);
In the <span data-bind="text: totalCount"></span>, I expect to see a number ranging from 0-Integer.MAX. Instead I see the following:
As you can see, its outputting the knockout function itself, not the value of the function. Every code example I've seen, including those in the official documentation, says that I should be seeing the value, not the function. What am I doing wrong here? I can provide the full application code if needed.
Not sure, but KO view models obviously tend to bind own (not inherited through prototypes) observable properties only. So you should rewrite your code to supply totalCount observable for every social network separately.

Categories