Bind knockoutjs to javascript object property - javascript

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

Related

Trigger function in moment column is sorted in GridX

I use SingleSort module with Gridx. I can't find (I already checked SingleSort documentation and found nothing) a way to react on sort event. I need info about which (and how) column is sorted. I know how get info about sorting (getSortData method) but i don't know how make react in moment sort is made. I can't made onRender-event function because after sorting i will send that info to webapi get new data and again render Grid so event will be triggered again.
define([
"dojo/Evented",
"dojo/_base/declare",
"dojo/store/Memory",
"gridx/core/model/cache/Sync",
"gridx/Grid",
"gridx/modules/ColumnResizer",
'gridx/modules/Focus',
'gridx/modules/RowHeader',
'gridx/modules/select/Row',
'gridx/modules/select/Column',
'gridx/modules/select/Cell',
"gridx/modules/SingleSort"
], function (Evented, declare, Memory, Cache, Grid, ColumnResizer, Focus, RowHeader, SelectRow, SelectColumn, SelectCell, Sort) {
return declare([Evented], {
_counter: 1,
_topOffset: 100,
constructor: function () {
},
initialize: function () {
this._initGrid();
this._resizeGridContainer();
},
clear: function () {
var store = new Memory({ data: [] });
this._grid.setStore(store);
this._counter = 1;
},
_initGrid: function () {
var _this = this;
var store = new Memory({
data: []
});
var structure = [
{
id: 'operationType', field: 'operationType', name: dojo.byId(this._operationTypeId).value
},
{
id: 'transportationType', field: 'transportationType', name: dojo.byId(this._transportationUnitTypeId).value
},
{
id: 'transportationTypeLength', field: 'transportationTypeLength', name: dojo.byId(this._transportationLengthId).value
}
]
this._grid = Grid({
id: this._gridId,
cacheClass: Cache,
store: store,
structure: structure,
modules: [
ColumnResizer,
Sort
],
selectRowTriggerOnCell: true
});
this._grid.placeAt(this._gridPlaceholderId);
this._grid.startup();
},
_resizeGridContainer: function () {
var _this = this;
var container = dojo.byId(this._gridContainerId);
var height = container.parentElement.clientHeight - this._topOffset;
require(["dojo/dom-style"], function (domStyle) {
domStyle.set(_this._gridContainerId, "height", height + "px");
})
},
});
});
you need to tap on to the gridx SingleSort module sort method like this
this._grid.connect( this._grid.Sort, 'sort', function(colId, isDescending, skipUpdateBody){
alert(colId+" "+isDescending+" "+skipUpdateBody);
});
this will trigger the event whenever a grid column is sorted. Hope this helps.

ExtJS 5: Initial ViewModel data not firing formula

I have a ViewModel that contains some initial data... this initial data is based off of a global variable that I have created. In the ViewModel, I have a formula that does some logic based on the data set from the global variable. The interesting thing is, this formula does not fire when the ViewModel is created. I'm assuming this is because the Something.Test property does not exist, so the ViewModel internals have some smarts to not fire the method if that property does not exist.
If the property doesn't exist, how do I fire the formula anyway? I know I could look for Something check to see if it has the property Test, but I'm curious why this example wouldn't work. Here's the example:
Ext.application({
name : 'Fiddle',
launch : function() {
// Define global var Something
Ext.define('Something', {
singleton: true
});
Ext.define('MyViewModel', {
extend: 'Ext.app.ViewModel',
alias: 'viewmodel.myView',
data: {
Something: window.Something
},
formulas: {
testSomething: function(getter) {
console.log('here', getter('Something.Test'));
return getter('Something.Test');
},
myTitle: function(getter) {
return 'My Title';
}
}
});
Ext.define('MyView', {
extend: 'Ext.panel.Panel',
bind: {
title: '{myTitle}'
},
viewModel: {
type: 'myView'
}
});
var view = Ext.create('MyView', {
renderTo: Ext.getBody()
});
// This will fire the ViewModel formula
//view.getViewModel().set('Something', window.Something);
console.log(Something, window.Something)
}
});
You can workout some logic to handle when Something.Test is not available, something like:
data: {
Something: window.Something && window.Something.Test || {Test: null}
},
formulas: {
testSomething: function(get) {
var val = get('Something.Test');
console.log('Test');
return val;
},
myTitle: function(getter) {
return 'My Title';
}
}

kendo ui - create a binding within another binding?

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);
});

an array of ArrayControllers

I'm doing something wrong here, but can't figure out what. I'm new to both Ember and Javascript in general, so feel free to point out any mistakes. I would appreciate an additional pair of eyes.
I basically have a google map with multiple datasets. In the controller that goes with the view I get the datasets and create an dataSetController(ArrayController) for each dataset. I then let the dataSetController load the data and add it to it's content, and an additional marker array.
When the process is done however, both dataSetControllers contain all points, instead of just the points for the particular dataset.
Below is the controller that goes with the view:
App.MapviewShowController = Ember.ObjectController.extend({
dataSets: [],
createDataSets: function() {
'use strict';
var self = this;
// clean previous data
this.get('dataSets').length = 0;
$.ajax({
url: '/active_data_sets.json',
type: 'GET',
data: {'project_id': this.get('id')},
success: function(data) {
data.active_data_sets.forEach(function(entry) {
// create a new controller for this dataset
var newds = App.AddressRecordController.create();
self.get('dataSets').pushObject(newds);
});
},
error: function() {
}
});
}
});
And the dataSetController itself:
App.AddressRecordController = Ember.ArrayController.extend({
content: [],
isActive: true,
dataSetId: 0,
markerColor: '',
datasetName: '',
map: null,
map_nelat: null,
map_nelng: null,
map_swlat: null,
map_swlng: null,
markerIcon: null,
markers: [],
mapBinding: 'App.MapData.map',
map_nelatBinding: 'App.MapData.ne_lat',
map_nelngBinding: 'App.MapData.ne_lng',
map_swlatBinding: 'App.MapData.sw_lat',
map_swlngBinding: 'App.MapData.sw_lng',
getAddresses: function(ne_lat, ne_lng, sw_lat, sw_lng) {
"use strict";
var self = this;
$.ajax({
url: '/address_records.json',
type: 'GET',
data: {'dataset_id': this.get('dataSetId'), 'ne_lat': ne_lat, 'ne_lng': ne_lng, 'sw_lat': sw_lat, 'sw_lng': sw_lng},
success: function(data) {
data.address_records.forEach(function(new_address) {
if (!self.findProperty('id', new_address.id)) {
// add to the content
self.content.addObject(App.AddressRecord.create(new_address));
// add the marker
var marker = new google.maps.Marker({
position: new google.maps.LatLng(new_address.lat, new_address.long),
map: self.get('map'),
animation: google.maps.Animation.DROP,
title: 'marker',
id: new_address.id
});
// add the marker for later reference
self.markers.push(marker);
}
});
},
error: function() {
}
});
},
newBounds: function() {
"use strict";
this.getAddresses(this.map_nelat, this.map_nelng, this.map_swlat, this.map_swlng);
}.observes('map_swlng'),
clean: function() {
'use strict';
// clean the objects in arracycontroller
this.forEach(function(el) {
el.destroy();
});
// clean the markers
this.markers.length = 0;
},
showMarkers: function() {
'use strict';
var self = this;
if(this.get('isActive')) {
this.markers.forEach(function(mkr) {
mkr.setMap(self.get('map'));
});
} else {
this.markers.forEach(function(mkr) {
mkr.setMap(null);
});
}
}.observes('isActive')
});
Update
AFter further debugging I found out that multiple AddressRecordControllers share nothing except the markers array. To circumvent the issue I now store the markers as content and that works fine. Still not clear about why the markers array is shared over different controllers.
I believe the create methods is more like a singleton so it either creates the object or returns a pointer to the object. So you are just adding to the same controller. you might try instead. Ember also has a Mixin thing that I am not sure how it works yet either.
var newds = App.AddressRecordController.extend();

knockout js. foreach children of a child not working

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).

Categories

Resources