WinJS Databind to property get/set - javascript

In WinJS can I bind a property getter in a listView? Say I have an object defined like this:
var MyLib = MyLib || {};
MyLib.ToDoItem = function() {
this.name = '';
this.description = '';
Object.defineProperty(this, "completed", {
get : function() {
return false;
}
});
}
MyLib.ToDoList = [];
//MyLib.ToDoList.push....add todo items
I am declaring a WinJS.Binding.Template where all of the properties are binding except the one that is defined with a property getter:
<div id="myItemTemplate" data-win-control="WinJS.Binding.Template">
<div class="titleTile">
<h4 class="item-title" data-win-bind="textContent: name"></h4>
<p data-win-bind="textContent: description"></p>
<div data-win-bind="textContent: completed"></div> <-- Renders as undefined
</div>
</div>
The "completed" property renders as undefined. If I put a breakpoint in the javascript console where I am loading the data, I can get to the completed property, but the databinding doesn't seem to like it...any ideas?

You missed one line after your getter.
get : function() {
return false;
}
, enumerable: true
By setting enumerable to true, you can make data binding works on this property.

Related

Use object returned from helper in Handlebars

I am trying to use a object property returned from a helper in handlebars
handlebars.registerHelper("calcPercentage", (newVal, oldVal) => {
const computed = round((newVal - oldVal) / oldVal, 1);
return {
computed,
className: computed.toString().startsWith("-") ? "red" : "green"
};
});
<p>{{calcPercentage this.totalOrders this.totalPreviousOrders}}</p>
Here I'd like to use the computed property, how can I achieve this?
Fixed using
<p style="display: inline-block; padding-bottom: 0;">
{{#with (calcPercentage this.totalOrders this.totalPreviousOrders)}}
{{computed}}
{{/with}}%
</p>
Try below, This one is working.
var data = {
yes: "Great Final got it"
};
hbs.handlebars.registerHelper("count", function(price) {
return data[price];
});
Hbs look like below,
<p>
{{count 'yes'}}
</p>
Check the demo link below ClickHere

How do I add an attribute to an object within an observable array in knockout and trigger a notification?

With Knockout.js I have an observable array in my view model.
function MyViewModel() {
var self = this;
this.getMoreInfo = function(thing){
var updatedSport = jQuery.extend(true, {}, thing);
updatedThing.expanded = true;
self.aThing.theThings.replace(thing,updatedThing);
});
}
this.aThing = {
theThings : ko.observableArray([{
id:1, expanded:false, anotherAttribute "someValue"
}])
}
}
I then have some html that will change depending on the value of an attribute called "expanded". It has a clickable icon that should toggle the value of expanded from false to true (effectively updating the icon)
<div data-bind="foreach: aThing.theThings">
<div class="row">
<div class="col-md-12">
<!-- ko ifnot: $data.expanded -->
<i class="expander fa fa-plus-circle" data-bind="click: $parent.getMoreInfo"></i>
<!-- /ko -->
<!-- ko if: $data.expanded -->
<span data-bind="text: $data.expanded"/>
<i class="expander fa fa-minus-circle" data-bind="click: $parent.getLessInfo"></i>
<!-- /ko -->
<span data-bind="text: id"></span>
(<span data-bind="text: name"></span>)
</div>
</div>
</div>
Look at the monstrosity I wrote in the getMoreInfo() function in order to get the html to update. I am making use of the replace() function on observableArrays in knockout, which will force a notify to all subscribed objects. replace() will only work if the two parameters are not the same object. So I use a jQuery deep clone to copy my object and update the attribute, then this reflects onto the markup. My question is ... is there a simpler way to achieve this?
I simplified my snippets somewhat for the purpose of this question. The "expanded" attribute actually does not exist until a user performs a certain action on the app. It is dynamically added and is not an observable attribute in itself. I tried to cal ko.observable() on this attribute alone, but it did not prevent the need for calling replace() on the observable array to make the UI refresh.
Knockout best suits an architecture in which models that have dynamic properties and event handlers are backed by a view model.
By constructing a view model Thing, you can greatly improve the quality and readability of your code. Here's an example. Note how much clearer the template (= view) has become.
function Thing(id, expanded, name) {
// Props that don't change are mapped
// to the instance
this.id = id;
this.name = name;
// You can define default props in your constructor
// as well
this.anotherAttribute = "someValue";
// Props that will change are made observable
this.expanded = ko.observable(expanded);
// Props that rely on another property are made
// computed
this.iconClass = ko.pureComputed(function() {
return this.expanded()
? "fa-minus-circle"
: "fa-plus-circle";
}, this);
};
// This is our click handler
Thing.prototype.toggleExpanded = function() {
this.expanded(!this.expanded());
};
// This makes it easy to construct VMs from an array of data
Thing.fromData = function(opts) {
return new Thing(opts.id, opts.expanded, "Some name");
}
function MyViewModel() {
this.things = ko.observableArray(
[{
id: 1,
expanded: false,
anotherAttribute: "someValue"
}].map(Thing.fromData)
);
};
MyViewModel.prototype.addThing = function(opts) {
this.things.push(Thing.fromData(opts));
}
MyViewModel.prototype.removeThing = function(opts) {
var toRemove = this.things().find(function(thing) {
return thing.id === opts.id;
});
if (toRemove) this.things.remove(toRemove);
}
var app = new MyViewModel();
ko.applyBindings(app);
// Add stuff later:
setTimeout(function() {
app.addThing({ id: 2, expanded: true });
app.addThing({ id: 3, expanded: false });
}, 2000);
setTimeout(function() {
app.removeThing({ id: 2, expanded: false });
}, 4000);
.fa { width: 15px; height: 15px; display: inline-block; border-radius: 50%; background: green; }
.fa-minus-circle::after { content: "-" }
.fa-plus-circle::after { content: "+" }
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<div data-bind="foreach: things">
<div class="row">
<div class="col-md-12">
<i data-bind="click: toggleExpanded, css: iconClass" class="expander fa"></i>
<span data-bind="text: id"></span> (
<span data-bind="text: name"></span>)
</div>
</div>
</div>

Cannot read property 'push' of undefined - knockout observableArray

When I click this button, an object is created and added to the array which is reflected in UI. Next when I click this button again I get an error "Cannot read property 'push' of undefined". What could possibly be the issue? Since the context seems fine otherwise it wouldn't have called the appropriate function in Rule class.
html code
<div data-bind="with: formatterVM">
<div data-bind="with: currentRule">
<ul data-bind="foreach: conditions">
<li data-bind="html: x"></li>
<li data-bind="html: y"></li>
</ul>
<button data-bind="click: addCondition">Click</button>
</div>
</div>
JS/Knockout code
function ReportsVM() {
self = this
self.formatterVM = new FormatterVM()
}
function FormatterVM(){
self = this
self.rules = ko.observableArray()
self.currentRule = ko.observable(new Rule())
}
function Rule(){
self = this
self.conditions = ko.observableArray()
self.addCondition = function(){
self.conditions.push(new Condition(2,3))
}
}
function Condition(x,y){
self = this
self.x = ko.observable(x)
self.y = ko.observable(y)
}
// Activates knockout.js
ko.applyBindings(new ReportsVM());
Working JSFiddle
https://jsfiddle.net/etppe937/
Update
It was an issue with the scope of the self variable. We need to use var keyword in order to not let the variable have a global scope. JSFiddle has been updated.

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