Kendo UI: One data source, two widgets - javascript

UPDATE: Here is a link to reproduce the problem
RELATED: This is another question of mine where similar problems are happening with Kendo UI Map, maybe it could help someone figure this one out! It has one failing and one working version.
I use Kendo UI's DataSource, DropDownList and Map in an Angular single-page application.
I want to use the same DataSource object for both the DropDownList and the Map. However, the Map behaves in a very unpredictable manner.
When I put the DropDownList before the Map in the template, only the DropDownList gets populated. Inspecting the network traffic reveals that indeed only one request is being made. When I put the Map first, both of them get populated and two requests are made.
When I don't use any promises in transport.read, but just call options.success immediately with a static value, everything works as expected. Two calls are being made.
I've been pulling my hair over this the entire work day, so any help is highly appreciated.
The data source service:
m.factory('ourDataSource', function(foo, bar, baz) {
return new kendo.data.DataSource({
transport: {
read: function(options) {
foo().then(function (result) {
return bar(result);
}).then(function (result) {
return baz(result);
}).then(function (result) {
options.success(result);
}).catch(function (err) {
options.error(err);
});
}
}
});
});
The controller:
m.controller('ourController', ['ourDataSource', function(ourDataSource) {
// set the data source of the dropdownlist
this.ourDataSource = ourDataSource;
// set up the map layers
this.mapLayers = [{
type: 'tile',
urlTemplate: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/#= zoom #/#= y #/#= x #',
}, {
type: 'marker',
dataSource: ourDataSource, // the same data source as before
locationField: 'Position',
titleField: 'Title'
}];
}]);
The view:
<div ng-controller="ourController as ctrl">
<select kendo-drop-down-list
k-data-text-field="'Title'"
k-data-value-field="'Title'"
k-data-source="ctrl.ourDataSource"></select>
<div kendo-map
k-zoom="2"
k-center="[1, 1]"
k-layers="ctrl.mapLayers">
</div>
</div>
What am I missing here?

I believe that this might be a bug in the Kendo UI Map widget, since the behavior occurring here isn't at all what one would expect. However, I do have a workaround solution. Rather than return the data source as a singleton object, return it as a function. This is probably not ideal, but it works.
angular.module('ourModule', ['kendo.directives'])
.factory('getDataSource', function ($q) {
return function() { // return a function that creates a new data source
return new kendo.data.DataSource({
transport: {
read: function (options) {
$q.when([
{Position: [1, 1], Title: 'First place'},
{Position: [10, 10], Title: 'Second place'}
]).then(function (result) {
options.success(result);
});
}
}
});
};
})
.controller('ourController', function (getDataSource) {
this.ourDataSource = getDataSource();
this.mapLayers = [{
type: 'tile',
urlTemplate: '...removed for brevity...'
}, {
type: 'marker',
dataSource: getDataSource(),
locationField: 'Position',
titleField: 'Title'
}];
});

Factory mostly used to create instances on demand. See this example
var app = angular.module('ourModule', ['kendo.directives']);
app.factory('dataSourceFactory', function($q) {
function dataSourceFactory() {}
dataSourceFactory.prototype = {
contentTypes: function() {
return new kendo.data.DataSource({
transport: {
read: function(options) {
$q.when(
[{
Position: [1, 1],
Title: 'First place'
}, {
Position: [10, 10],
Title: 'Second place'
}])
.then(function(result) {
options.success(result);
});
}
}
})
}
};
return dataSourceFactory;
});
app.controller('ourController', ['$scope', 'dataSourceFactory',
function($scope, dataSourceFactory) {
var dataSourceFactory = new dataSourceFactory();
$scope.mapLayers = [{
type: 'tile',
urlTemplate: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/#= zoom #/#= y #/#= x #',
}, {
type: 'marker',
dataSource: dataSourceFactory.contentTypes(), // the same data source as before
locationField: 'Position',
titleField: 'Title'
}];
$scope.ourDataSource = dataSourceFactory.contentTypes();
}
]);
<link rel="stylesheet" href="http://kendo.cdn.telerik.com/2015.3.930/styles/kendo.common.min.css">
<link rel="stylesheet" href="http://kendo.cdn.telerik.com/2015.3.930/styles/kendo.rtl.min.css">
<link rel="stylesheet" href="http://kendo.cdn.telerik.com/2015.3.930/styles/kendo.default.min.css">
<link rel="stylesheet" href="http://kendo.cdn.telerik.com/2015.3.930/styles/kendo.mobile.all.min.css">
<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<script src="http://kendo.cdn.telerik.com/2015.3.930/js/angular.min.js"></script>
<script src="http://kendo.cdn.telerik.com/2015.3.930/js/jszip.min.js"></script>
<script src="http://kendo.cdn.telerik.com/2015.3.930/js/kendo.all.min.js"></script>
<div ng-app="ourModule">
<div ng-controller="ourController">
<kendo-drop-down-list k-data-source="ourDataSource"
k-data-text-field="'Title'"
k-data-value-field="'Title'">
</kendo-drop-down-list>
<kendo-map k-zoom="2"
k-layers="mapLayers">
</kendo-map>
</div>
</div>
See this JSFiddle demo

Related

Is there a way to use NumberFormat() formatter (Google Charts) in vue-google-charts vue.js wrapper

I have been tasked with formatting some columns in charts using vue-google-charts, a vue.js wrapper for Google Charts and I am not sure that 'NumberFormat()' is even supported in vue-google-charts.
First, if somebody knows if it is or isn't, I would like to know so I don't waste much time pursuing something that isn't possible. But if it is, I sure would love an example of how to do it.
What we are doing is returning our chart data from the database and passing it into this vue.js wrapper. We are creating several charts but there are columns that have commas in them we want to remove.
Please review the existing code. I am trying to implement this using #ready as documented in the docs for vue-google-charts.
vue-google-charts docs -> https://www.npmjs.com/package/vue-google-charts
Here is our existing code with a little framework of the onChartReady method already in place.
<GChart
v-if="chart.data"
id="gchart"
:key="index"
:options="{
pieSliceText: chart.dropDownPie,
allowHtml: true
}"
:type="chart.ChartType"
:data="filtered(chart.data, chart.query, chart.query_type)"
:class="[
{'pieChart': chart.ChartType == 'PieChart'},
{'tableChart': chart.ChartType == 'Table'}
]"
#ready = "onChartReady"
/>
And then ...
<script>
import { GChart } from 'vue-google-charts';
import fuzzy from 'fuzzy';
import 'vue-awesome/icons';
import Icon from 'vue-awesome/components/Icon';
export default {
components: {
GChart,
Icon
},
props: {
},
data() {
return {
charts: window.template_data,
selected: 'null',
selects: [],
chartToSearch: false,
directDownloads: {
'Inactive Phones' : {
'slug' : 'phones_by_status',
'search_by' : 2,
'search' : '/Inactive/'
},
'Active Phones' : {
'slug' : 'phones_by_status',
'search_by' : 2,
'search' : '/Active/'
},
}
}
},
created(){
for (let i in this.charts){
if( !this.charts[i].slug ) continue;
$.post(ajaxurl, {
action: 'insights_' + this.charts[i].slug,
}, (res) => {
console.log(res.data);
if (res.success) {
this.$set(this.charts[i], 'data', res.data);
}
});
}
// console.log(this.charts);
},
methods: {
onChartReady(chart,google) {
let formatter = new.target.visualization.NumberFormat({
pattern: '0'
});
formatter.format(data, 0);
chart.draw(data)
},
toggleChart(chart) {
jQuery.post(ajaxurl, {
'action': 'update_insight_chart_type',
'chartType': chart.ChartType,
'chartSlug': chart.slug
}, (res) => {
chart.ChartType = res.data
})
},
csvHREF(chart) {
return window.location.href + '&rr_download_csv=' + chart.slug + '&rr_download_csv_search_by=' + chart.query_type + '&rr_download_csv_search=' + chart.query.trim()
},
filtered(data, query, column) {
query = query.trim();
if (query){
let localData = JSON.parse(JSON.stringify(data));
let column_Headers = localData.shift();
localData = localData.filter((row)=>{
if( query.endsWith('/') && query.startsWith('/') ){
return new RegExp(query.replace(/\//g, '')).test(String(row[column]));
}
return String(row[column]).toLowerCase().indexOf(query.toLowerCase()) > -1;
});
localData.unshift(column_Headers);
return localData;
}
return data;
},
filterIcon(chart) {
chart.searchVisible = !chart.searchVisible;
chart.query = "";
setTimeout(()=>{
document.querySelector(`#chart-${chart.slug} .insightSearch`).focus();
}, 1);
}
}
}
document.getElementsByClassName('google-visualization-table')
If anybody can help in ANY way, I am all ears.
Thanks!
not familiar with vue or the wrapper,
but in google charts, you can use object notation in your data,
to provide the formatted values.
all chart types will display the formatted values by default.
google's formatters just simply do this for you.
so, in your data, replace your number values with objects,
where v: is the value and f: is the formatted value...
{v: 2000, f: '$2,000.00'}
see following working snippet...
google.charts.load('current', {
packages: ['table']
}).then(function () {
var chartData = [
['col 0', 'col 1'],
['test', {v: 2000, f: '$2,000.00'}],
];
var dataTable = google.visualization.arrayToDataTable(chartData);
var table = new google.visualization.Table(document.getElementById('chart_div'));
table.draw(dataTable);
});
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div id="chart_div"></div>

Bind OracleJet ojtimeline component to viewModel

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

Angular JS selectize error

This is driving me nuts, as I can find no real reason for it.
I have an Angular 1.3.2 project. On one view, with one controller, one factory, and 2 json files as sources. I added 2 selectize menus, both work perfectly.
I did a save as to create a new file, new controller, new factory, new json file.
The html I kept the same but adjusted the selectize to match the new data:
<input type="text" selectize="partnerSelectMenu.options" options="partnerList" ng-model="partner.selected" />
The factory is
app.factory('DatagroupsFactory', ['$http',
function($http) {
var datagroupsData;
return {
getDataGroups: function() {
if (!datagroupsData) {
datagroupsData = $http.get('scripts/data/datagroups.json').then(function(response) {
return response.data;
});
}
return datagroupsData;
}
};
}
]);
the controller is
app.controller('AddDatagroupCtrl', function($scope, $log, PartnerFactory) {
var partnerList = [];
PartnerFactory.getPartners().then(function(response) {
$scope.partnerList = response.partners;
});
$scope.partnerSelectMenu = {
options: {
valueField: 'name',
labelField: 'name',
searchField: ['name'],
plugins: ['remove_button']
}
};
});
And a fragment of the json:
{
"partners" : [
{
"value" : "CPPRT0002706",
"name" : "Axis Promotions"
},
{
"value" : "CPPRT0005006",
"name" : "Band of Outsiders"
}
]
}
Here's the kicker: the menu works fine, as expected, and displays my data. The problem is I also get this error:
I have tried commenting out every part of this path of files. Nothing. Log debug statements show the data is correctly being passed; heck, the menu even works.
The other pages on my site with selectize menus -- with the same options -- do not throw this error. I am at a loss on how to track down.
I think you need to change:
var partnerList = [];
to
$scope.partnerList = [];
in your controller.
app.controller('AddDatagroupCtrl', function($scope, $log, PartnerFactory) {
$scope.partnerList = [];
PartnerFactory.getPartners().then(function(response) {
$scope.partnerList = response.partners;
});
$scope.partnerSelectMenu = {
options: {
valueField: 'name',
labelField: 'name',
searchField: ['name'],
plugins: ['remove_button']
}
};
});
I think before your promise returns, partnerList is undefined and your selectize is trying to get a name property from that.

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

kendoui: How to display foreign key from remote datasource in grid

i have a kendoui grid which list claims. one of the columns is lenders which is a foreign key reference to the lenders table. what i want is to be able to display the lender name in the grid instead of its id reference.
ive setup the lenders datasource as follows
var dsLenders = new kendo.data.DataSource({
transport: {
read: {
url: "../data/lenders/",
dataType: "jsonp"
},
parameterMap: function(options, operation) {
if (operation === "read") {
return options;
}
}
}
});
and the grid looks like this
$("#gridClaims").kendoGrid({
dataSource: claimData,
autoSync:true,
batch: true,
pageable: {
refresh: true,
pageSizes: true
},
filterable: true,
sortable: true,
selectable: "true",
editable: {
mode: "popup",
confirmation: "Are you sure you want to delete this record?",
template: $("#claimFormPopup").html()
},
navigable: true, // enables keyboard navigation in the grid
toolbar: ["create"], // adds insert buttons
columns: [
{ field:"id_clm", title:"Ref", width: "80px;" },
{ field:"status_clm", title:"Status", width: "80px;" },
{ field:"idldr_clm", title:"Lender", values: dsLenders },
{ field:"type_clm", title:"Claim Type"},
{ field:"value_clm", title:"Value", width: "80px;", format:"{0:c2}", attributes:{style:"text-align:right;"}},
{ field:"created", title:"Created", width: "80px;", format: "{0:dd/MM/yyyy}"},
{ field:"updated", title:"Updated", width: "80px;", format: "{0:dd/MM/yyyy}"},
{ field:"user", title:"User" , width: "100px;"},
{ command: [
{text: "Details", className: "claim-details"},
"destroy"
],
title: " ",
width: "160px"
}
]
});
however its still displaying the id in the lenders column. Ive tried creating a local datasource and that works fine so i now is something to do with me using a remote datasource.
any help would be great
thanks
Short answer is that you can't. Not directly anyway. See here and here.
You can (as the response in the above linked post mentions) pre-load the data into a var, which can then be used as data for the column definition.
I use something like this:-
function getLookupData(type, callback) {
return $.ajax({
dataType: 'json',
url: '/lookup/' + type,
success: function (data) {
callback(data);
}
});
}
Which I then use like this:-
var countryLookupData;
getLookupData('country', function (data) { countryLookupData = data; });
I use it in a JQuery deferred to ensure that all my lookups are loaded before I bind to the grid:-
$.when(
getLookupData('country', function (data) { countryLookupData = data; }),
getLookupData('state', function (data) { stateLookupData = data; }),
getLookupData('company', function (data) { companyLookupData = data; })
)
.then(function () {
bindGrid();
}).fail(function () {
alert('Error loading lookup data');
});
You can then use countryLookupData for your values.
You could also use a custom grid editor, however you'll probably find that you still need to load the data into a var (as opposed to using a datasource with a DropDownList) and ensure that the data is loaded before the grid, because you'll most likely need to have a lookup for a column template so that you're newly selected value is displayed in the grid.
I couldn't quite get ForeignKey working in any useful way, so I ended up using custom editors as you have much more control over them.
One more gotcha: make sure you have loaded your lookup data BEFORE you define the column. I was using a column array that was defined in a variable I was then attaching to the grid definition... even if the lookup data is loaded before you use the grid, if it's defined after the column definition it will not work.
Although this post past 2 years, I still share my solution
1) Assume the api url (http://localhost/api/term) will return:
{
"odata.metadata":"http://localhost/api/$metadata#term","value":[
{
"value":2,"text":"2016-2020"
},{
"value":1,"text":"2012-2016"
}
]
}
please note that the attribute name must be "text" and "value"
2) show term name (text) from the foreign table instead of term_id (value).
See the grid column "term_id", the dropdownlist will be created if added "values: data_term"
<script>
$.when($.getJSON("http://localhost/api/term")).then(function () {
bind_grid(arguments[0].value);
});
function bind_grid(data_term) {
$("#grid").kendoGrid({
dataSource: ds_proposer,
filterable: true,
sortable: true,
pageable: true,
selectable: "row",
columns: [
{ field: "user_type", title: "User type" },
{ field: "user_name", title: "User name" },
{ field: "term_id", title: "Term", values: data_term }
],
editable: {
mode: "popup",
}
});
}
</script>
For those stumbling across this now, this functionality is supported:
https://demos.telerik.com/aspnet-mvc/grid/foreignkeycolumnbinding

Categories

Resources