In my pursuit to get a binding for an associative array to work, I've made significant progress, but am still blocked by one particular problem.
I do not understand how to create a binding from strictly javascript
Here is a jsFiddle that shows more details than I have posted here:
jsFiddle
Basically, I want to do a new binding within the shown $.each function that would be equivalent to this...
<div data-template="display-associative-many" data-bind="repeat: Root.Items"></div>
Gets turned into this ...
<div data-template="display-associative-single" data-bind="source: Root['Items']['One']"></div>
<div data-template="display-associative-single" data-bind="source: Root['Items']['Two']"></div>
<div data-template="display-associative-single" data-bind="source: Root['Items']['Three']"></div>
And I am using the repeat binding to create that.
Since I cannot bind to an associative array, I just want to use a binding to write all of the bindings to the objects in it.
We start again with an associative array.
var input = {
"One" : { Name: "One", Id: "id/one" },
"Two" : { Name: "Two", Id: "id/two" },
"Three" : { Name: "Three", Id: "id/three" }
};
Now, we create a viewModel that will contain that associative array.
var viewModel = kendo.observable({
Name: "View Model",
Root: {
Items: input
}
});
kendo.bind('#example', viewModel);
Alarmingly, finding the items to bind was pretty easy, here is my binding so far;
$(function(){
kendo.data.binders.repeat = kendo.data.Binder.extend({
init: function(element, bindings, options) {
// detailed more in the jsFiddle
$.each(source, function (idx, elem) {
if (elem instanceof kendo.data.ObservableObject) {
// !---- THIS IS WHERE I AM HAVING TROUBLE -----! //
// we want to get a kendo template
var template = {};// ...... this would be $('#individual-item')
var result = {}; // perhaps the result of a template?
// now I need to basically "bind" "elem", which is
// basically source[key], as if it were a normal HTML binding
$(element).append(result); // "result" should be a binding, basically
}
});
// detailed more in the jsFiddle
},
refresh: function() {
// detailed more in the jsFiddle
},
change: function() {
// detailed more in the jsFiddle
}
});
});
I realize that I could just write out the HTML, but that would not perform the actual "binding" for kendo to track it.
I'm not really sure what you are attempting to do, but it seemed to me that the custom "repeat" binding was unnecessary. Here's what I came up with. Is this on track with what you are trying to do?
Here is a working jsFiddle example.
HTML
<div id="example">
<div data-template="display-associative-many" data-bind="source: Root.Items"></div>
</div>
<script type="text/x-kendo-template" id="display-associative-many">
#for (var prop in data) {#
# if (data.hasOwnProperty(prop)) {#
# if (data[prop].Id) {#
<div><span>${data[prop].Id}</span> : <span>${data[prop].Name}</span></div>
# }#
# }#
#}#
</script>
JavaScript
$(function () {
var input = {
"One" : { Name: "One", Id: "id/one" },
"Two" : { Name: "Two", Id: "id/two" },
"Three" : { Name: "Three", Id: "id/three" }
};
var viewModel = new kendo.data.ObservableObject({
Id: "test/id",
Root: {
Items: input
}
});
kendo.bind('#example', viewModel);
});
Related
I'm new to Knockoutjs, so please bear with me.
I want to knocoutjs bind a DxForm (DevExpress) to an javascript object property, but I get an error ... "Cannot read property 'items' of undefined".
I am uncertain if this is a knockout problem, DevExpress problem or just incufficient coding skills from my part.
Here's my code...
HTML:
<div data-bind="dxForm: frm.options"></div>
Javascript:
var viewModel = function() {
var that = this;
// -----------------------------------------------------------------
// Faste...
// -----------------------------------------------------------------
that.frm = {
items: ko.observable(undefined),
data: ko.observable(undefined),
instance: ko.observable({}),
options: {
items: that.frm.items,
formData: that.frm.data,
onInitialized: function(e) {
that.frm.instance(e.component);
},
},
};
return {
frm: that.frm,
};
};
var vm = new viewModel();
ko.applyBindings(vm);
var items = [{
"dataField": "test",
"editorOptions": {
"type": "date",
"pickerType": "calendar",
},
"editorType": "dxDateBox",
"name": "Test",
"visible": true
}];
var data = {
test: 10,
};
vm.frm.data(data);
vm.frm.items(items);
JSFiddle: https://jsfiddle.net/MojoDK/ke395v2c/3/
I want to bind to objects since I'm going to use several DxForm objects and I like to keep the code to each DxForm in an object (easier to read).
Any idea why it fails?
Thanks.
You just have a problem with closure in your frm.
The that property in frm object do not exist you should use this...
But in your onInitialized function, this and that will not target your viewModel object...
So this way, the easiest is to declare options object later :
that.frm = {
items: ko.observable(undefined),
data: ko.observable(undefined),
instance: ko.observable({})
};
that.frm.options = {
items: that.frm.items,
formData: that.frm.data,
onInitialized: function(e) {
that.frm.instance(e.component);
},
};
Updated jsfiddle
I am trying to understand how I can bind data from the view-model to the view. The REST request to the back-end is working fine and I get a JSON array with several items. The existing documentation doesn't give me enough help.
How can I bind the timeline component ojtimeline to the view-model data array?
Edit: No errors now, since the view recognize the view-model array. But the ojtimeline doesn't display the data, only a working empty view component.
View
<div id="tline"
data-bind='ojComponent: {
component: "ojTimeline",
minorAxis: {
scale: "hours",
zoomOrder: ["hours", "days", "weeks"]
},
majorAxis: {
scale: "weeks"
},
start: new Date("Jan 1, 2016").toISOString(),
end: new Date("Jun 31, 2016").toISOString(),
referenceObjects: [{value: new Date("Feb 1, 2010").toISOString()}],
series: [{
id: "id",
emptyText: "No Data.",
items: statusArray,
label: "Oracle Events"
}],
overview: {
rendered: "off"
}
}' style="width: '100%';height: 350px"></div>
View-model
define(['ojs/ojcore', 'knockout', 'jquery', 'ojs/ojknockout', 'ojs/ojtimeline'],
function (oj, ko) {
/**
* The view model for the main content view template
*/
function timelineContentViewModel() {
var self = this;
this.statusArray = ko.observableArray([]);
self.addData = function () {
$.ajax({
url: "http://localhost:8080/myproject/rest/status/v1/findAll",
type: 'GET',
dataType: 'json',
success: function (data, textStatus, jqXHR) {
var x = data;
for (i = 0; i < x.length; i++) {
statusArray.push({
id: data[i].id,
description: data[i].text,
title: data[i].user.screenName,
start: data[i].createdAt});
}
//$("#tline").ojTimeline("refresh"); Doesn't have ant affect
}
});
};
self.addData();
}
return timelineContentViewModel;
});
The ReferenceError is caused by
var statusArray = ko.observableArray([]);
it should be
this.statusArray = ko.observableArray([])
You will also (probably) need to refresh the timeline when the observable array has changed, e.g. after the for-loop in success callback:
...
success: function (data, textStatus, jqXHR) {
var x = data;
for (i = 0; i < x.length; i++) {
self.statusArray.push({
id: data[i].id,
description: data[i].text,
title: data[i].user.screenName,
start: data[i].createdAt});
}
$("#tline").ojTimeline("refresh");
}
...
I have loaded ojTimeline from Ajax data and have never needed to use refresh. Worst case, you can wrap the ojTimeline in a <!-- ko if ... --> so that the timeline doesn't appear until you have an Ajax response.
For the ojTimeline items attribute, instead of referencing the observable, I had to unwrap the observable like this: items: ko.toJS(statusArray).
Another thing to consider is pushing into an ko.observableArray inside a for loop. Each push using the ko.observableArray push() method invokes subscriptions. If your array is bound to the UI, then each push will trigger a DOM change. Instead, it is often better to push into the underlying array (unwrap the array) and then invoke self.statusArray.valueHasMutated. You may also want to keep an eye on your use of this, self, and nothing. Consistency will help avoid bugs like the one ladar identified.
What do you think about rewriting your for loop like this (code untested)?
ko.utils.arrayPushAll(
self.statusArray(),
ko.utils.arrayMap(data, function(item) {
return {
id: item.id,
description: item.text,
title: item.user.screenName,
start: item.createdAt;
};
});
);
self.statusArray.valueHasMutated();
Or, if you can get away with it (some OJ components don't like this approach), you can skip the push and just replace the entire array inside the observable:
self.statusArray(
ko.utils.arrayMap(data, function(item) {
return {
id: item.id,
description: item.text,
title: item.user.screenName,
start: item.createdAt;
};
});
);
I have this code in a Backbone application that I need to debug. (rough idea)
window.TableView = Backbone.View.extend({
initialize: function() {...
..
..
...
});
},
selectRow: function() {
...
...
..
},
render: function() { // this renders my models fields in a table
var editableColumns = [
//{ name: "display", type: "combobox", combobox: comboboxOptions, validate: validateText },
{ name: "display" },
{ name: "submitDate", type: "datepicker", datepicker: datepickerOptions },
{ name: "displayDate", type: "datepicker", datepicker: datepickerOptions },
{ name: "name"},
...
...
Now my problem is, how can I add a function to this field: { name: "display" }
like onclick, or after focus function, etc.? For example can I have,
{ name: "display", onclick: setMyText(); } or something like this? Also is this part of backbone.js or one of its components? Where can I read more about this?
In Backbone you have events hash for a View where you can specify the events for respective View. Events are specified in following format:
{"event selector": "callback"}
So in your case for all the editableColumns you also need a selector for each one or may you can specify by using the name property. Try specifying the events hash like this :
events: {
'click .columnSelector[name="display"]' : "setMyText"
}
where .columnSelector is the class applied to element that is to be edited.
For more details on events check this.
I have the following javascript:
function RandomViewModel() {
var self = this;
self.RnadomSquaresK = ko.observableArray([
randomSquare(),
randomSquare(),
]);
}
var randomSquare = function() { return ko.observable({
innate: ko.observableArray([
{ star: "test1", Class: "starList", Style: {} },
{ star: "test2", Class: "starList", Style: {display:'inline'} },
{ star: "test3", Class: "starList", Style: {} },
{ star: "test4", Class: "starList", Style: {} }
])
})};
ko.applyBindings(new RandomViewModel());
It's basically creating an observableArray of two observableArrays each with 4 elements.
I have the following html:
<script id="starTemplate" type="text/html">
<!-- ko if: ($index >= 0) -->
<div data-bind="text: $index, attr: { class: Class }, style: Style"></div>
<!-- /ko -->
</script>
<div class="starList" data-bind="template: { name: 'starTemplate', foreach: RnadomSquaresK()[0]().innate }"></div>
I'm expecting divs to be created with each of their indexes printed according to the observableArray they're bound to. (in this case: 4 divs, since all indexes should be equal or larger than 0)
What I actually get: blank.
Here's the JSFiddle link: http://jsfiddle.net/qrwBE/
If I change the if statement to ex: if: $index != 0, I'd print all 4 elements bound to the array, but I don't quite understand why in this case the first element (index 0) is being printed alongside the other 3 elements.
Am I using the if statement incorrectly?
Explanations on what's happening, and any other comments regarding javascript, etc. are much welcome.
So there are a number of items to examine here:
Why isn't your foreach loop displaying divs with index values in them?
The answer is because you $index() is an observable, which are functions. So you must use it like I demonstrated above. The reason why if: $index != 0 worked is because in javascript, things that evaluate to 0 are false any non-zero evaluates to true (just like in c/c++ if you are familiar with those languages.
So this means when you wrote if: $index != 0 you where really saying, is the function $index not null or undefined? which was true so it would continue to output divs from your template.
Refactor Recommendation:
A foreach binding will automatically take care of not rendering the div tags in the event that your observableArray is empty. This means you can just remove the if check altogether ... which also would have fixed your issue.
<script id="starTemplate"a type="text/html">
<div data-bind="text: $index, attr: { class: Class }, style: Style"></div>
</script>
Model changes:
Let's examine this function:
var randomSquare = function() { return ko.observable({
innate: ko.observableArray([
{ star: "test1", Class: "starList", Style: {} },
{ star: "test2", Class: "starList", Style: {display:'inline'} },
{ star: "test3", Class: "starList", Style: {} },
{ star: "test4", Class: "starList", Style: {} }
])
})};
Here you are returning an observable that contains a property called innate which is an observable array. Why not just remove this being contained in a observable because it only makes the syntax for accessing the array more funky RnadomSquaresK()[0]().innate
Instead of that, how about the following:
var randomSquare = function() { return {
innate: ko.observableArray([
{ star: "test1", Class: "starList", Style: {} },
{ star: "test2", Class: "starList", Style: {display:'inline'} },
{ star: "test3", Class: "starList", Style: {} },
{ star: "test4", Class: "starList", Style: {} }
]});
})};
which would simply accessing it to the following: RnadomSquaresK()[0].innate
When to make something Observable
Just remember that you only ever need to make something an observable ... if you intend for the UI or a subscriber function in js to be alerted when a change in the observable's state occurs. Otherwise, just make it a copied value (plain old js variable).
The dangers of mixing function declarations with function expressions (Hoisting concerns):
This is purely a js note.
This is a function declaration function RandomViewModel() { }
This is a function expression var randomSquare = function() { };
These are slightly different in how they are handled by the interpretters. The first one (declaration) will be hoisted to the top of it's parent scope. Therefore in subsequent code calling RandomViewModel() will work flawlessly because it is defined before everything else thanks to the interpreter.
However, the second one (expression) will only have the variable name hoisted to the top, but it's assignment will stay where it is, which means your code would be equivalent to this:
function RandomViewModel() { }
var randomSquare;
randomSquare = function() { };
This will work fine in your case. However, as your models become more complex and more tightly coupled with one another, you may end up with issues like this scenario:
function RandomViewModel() {
var x = randomSquare(); //<-- would throw an undefined exception here
alert(x);
}
var model = new RandomViewModel();
var randomSquare = function() { return 5; };
This would happen because the interpretter hoisted your var randomSquare to the top of the scope but it isn't assigned a function until after it's attemped use in RandomViewModel
Safe bet, do it a consistent, don't mix and match these to appraoches to writing functions.
When doing javascript expressions in a binding, you need to get the value of the property:
http://jsfiddle.net/wiredprairie/EWrsF/
if: $index() > 0
In use:
<script id="starTemplate" type="text/html">
<!-- ko if: $index() > 0 -->
<div data-bind="text: star, attr: { class: Class }, style: Style"></div>
<!-- /ko -->
</script>
I admittedly don't understand the rest of your code (as the variable names and pattern you're using is challenging to understand).
I am having trouble iterating over my json data with knockout.
My view model looks like :
var ViewModel = function () {
var self = this;
self.Summary = ko.observableArray();
$.getJSON('some api url', function(result) {
ko.mapping.fromJS(result, {}, self);
});
}
ko.applyBindings(new ViewModel());
My JSON data looks like :
{
Summary: {
Details: [
{
Name: "Foo",
Id: 1,
Detail: "Some Data"
},
{
Name: "Bar",
Id: 2,
Detail: "Another Data"
}
],
SummaryOverview: "BlahBlah",
AnotherObject: [
{
Name: "My Name"
AnotherChildObject: [
{
name:"some name"
}
]
}
]
}
}
My question is do I iterate thru my data this way:
<div data-bind="foreach: Summary">
<div data-bind="text: Details.Detail"></div>
</div>
OR
<div data-bind: "foreach: Summary.Details">
<div data-bind="text: Detail"></div>
</div>
How do I display the Detail? The HTML above is not working for me.
Thank you very much!!
The problem with ko.mapping is that your observables will be replaced with new observables. To clarify, the Summary, which is an observableArray, will be replaced by a new observableArray by ko.mapping.
There are two ways to remedy this. The first alternative is to wait with the applyBindings until the real array has been created:
var ViewModel = function () {
var self = this;
// no need to set the array, it will be overwritten anyway
// self.Summary = ko.observableArray();
}
var vm = new ViewModel();
$.getJSON('some api url', function(result) {
ko.mapping.fromJS(result, {}, vm);
ko.applyBindings(vm);
});
Alternative 2 is to bootstrap the viewmodel with initial (empty) data. If you apply ko.mapping on an empty array, the next call to ko.mapping will update the existing array rather than overwrite it. Like so:
var ViewModel = function () {
var self = this;
var init = { Summary: [] };
ko.mapping.fromJS(init, {}, self);
$.getJSON('some api url', function(result) {
ko.mapping.fromJS(result, {}, self);
});
}
ko.applyBindings(new ViewModel());
I usually go with alternative 2. Alternative 1 will cause a delay before ko.applyBindings is called, which might cause some UI flicker (and unwanted elements may be visible, etc).