I'm trying to populate a pie chart with some JSON-data. My datasource is restcountries.eu/rest/v2/all. I retrieve the data with a $.getJSON and I create a temporary array which in turn is used as a datasource. I then bind the source to the pie chart. That what I think I'm doing atleast..
The error I'm getting is following
my-piechart-viewModel.js:25 Uncaught (in promise) ReferenceError: data is not
defined
at new myPieChartModel (my-piechart-viewModel.js:25)
at Object.CreateComponent (ojcomposite.js:1808)
at ojcustomelement.js:385
My code looks like this
HTML
<oj-chart id="pieChart1" aria-label= 'TestPieChart'
type="pie"
series='[[datasource]]'
style="max-width:500px;width:100%;height:350px;">
</oj-chart>
JS
function myPieChartModel() {
var self = this;
self.data = ko.observableArray();
$.getJSON("https://restcountries.eu/rest/v2/all").
then(function(countries) {
var tempArray = [];
$.each(countries, function() {
tempArray.push({
name: this.name,
population: this.population
});
});
self.data(tempArray);
});
self.datasource = ko.observableArray(data);
}
return myPieChartModel;
What am I doing wrong? I'm very new to Oracle's JET, and I have very little experience with JSON overall.
If you defined something as self.data you cannot later access it by calling just data. So you need to change your last line to:
self.datasource = ko.observableArray(self.data);
Even if you do do that, you'll get this error:
The argument passed when initializing an observable array must be an array, or null, or undefined.
That is, you cannot pass an observableArray into an observableArray. self.data should just be a normal JS array.
self.data = [];
But a normal JS array does not fire any events when its values are changed, so you'll need to update the observableArray datasource again. Your full code can be like this:
function myPieChartModel() {
var self = this;
self.data = [];
self.datasource = ko.observableArray(self.data);
$.getJSON("https://restcountries.eu/rest/v2/all").
then(function(countries) {
$.each(countries, function() {
self.data.push({
name: this.name,
population: this.population
});
});
self.datasource(self.data);
});
}
return myPieChartModel;
Let me know if it works. I have a feeling that your JSON data will also need to be modified like this:
self.data.push({name: this.name,
items: [this.population]
});
Why? Because that's how Oracle JET expects it. Here's the documentation.
Related
I'm trying to implement nested Collections exactly like the example I found here: https://stackoverflow.com/a/17453870/295133
The only difference being is that I'm trying to store the data locally using the localStorage plugin.
Here, my Lists would be the Hotels in the example above:
var app = app || {};
(function (){
'use strict';
// List Collection - list of words
//---------------------
var listCollection = Backbone.Collection.extend({
//referebce to this collection's model
model: app.ListModel,
localStorage: new Backbone.LocalStorage('translate-lists')
});
app.listCollection = new listCollection();
})();
(function (){
'use strict';
app.ListModel = Backbone.Model.extend({
initialize: function() {
// because initialize is called after parse
_.defaults(this, {
words: new app.wordCollection
});
},
parse: function(response) {
if (_.has(response, "words")) {
this.words = new app.wordCollection(response.words, {
parse: true
});
delete response.words;
}
return response;
}
});
})();
What the localStorage does is stores the ListModels, but if I add anything to the words collection it soon disappears after I refresh.
Any ideas how I should be saving the entire nested collection?
So got this working and it came down to something in parse but also if you want to ensure you just get the attributes out of your nested collection you should override the toJSON otherwise you get the full collection in what this returns.
Backbone.Model.prototype.toJSON = function() {
var json = _.clone(this.attributes);
for (var attr in json) {
if ((json[attr] instanceof Backbone.Model) || (json[attr] instanceof Backbone.Collection)) {
json[attr] = json[attr].toJSON();
}
}
return json;
};
The main thing that was breaking is in the parse. Is assigns words directly to the model,
this.words = new app.wordCollection(response.words, {
parse: true
});
but this means that it will not show up when toJSON is called as it is not in the attributes (it also means you can't access it via model.get)
so this should be changed to
initialize: function () {
// because initialize is called after parse
_.defaults(this.attributes, {
words: new app.WordCollection()
});
},
parse: function (response) {
if (_.has(response, "words")) {
this.attributes.words = new app.WordCollection(response.words, {
parse: true
});
delete response.words;
}
return response;
}
this way it is added to the attributes of the model on not directly on the model. If you look at this fiddle http://jsfiddle.net/leighking2/t2qcc7my/ and keep hitting run it will create a new model in the collection, save it in local storage then print the results to the console. each time you hit run you should see it grow by 1 (as it gets the previous results local storage) and contain the full information that you want.
I have a rather simple question. I have a simple controller and its $scope.coords = []; renders JSON in HTML:
[24.43359375, 54.6611237221]
[25.2905273438, 54.6738309659]
[25.3344726562, 54.6102549816]
[25.2685546875, 54.6801830971]
[25.2960205078, 54.6611237221]
How can I render that JSON not in html, but in my controller itself ? The code looks like that. Please see the comment in code:
propertyModule.controller('propertyController', ['$scope', 'Property', function ($scope, Property) {
// Query returns an array of objects, MyModel.objects.all() by default
$scope.properties = Property.query();
// Getting a single object
$scope.property = Property.get({pk: 1});
$scope.coords = [];
$scope.properties = Property.query({}, function(data){
console.log(data);
angular.forEach(data , function(value){
$scope.coords.push(value.coordinates);
});
});
$scope.positions = //$Resource('realestate.property').items();
[
[54.6833, 25.2833], [54.67833, 25.3033] // those coordinates are hardcoded now, I want them to be rendered here by $scope.coords
];
}]);
First off, you're showing us a bunch of arrays, not a JSON document. But since your code seems to be working, I'll assume you do have a valid JSON to work with.
You need to consider the fact that you are making an asynchronous request here :
$scope.properties = Property.query({}, function(data) {
console.log(data);
angular.forEach(data , function(value){
$scope.coords.push(value.coordinates);
});
});
This means you won't be able to fetch data from $scope.coords before anything has arrived.
There are several ways to solve that :
You could simply fetch data while you're still in the loop :
angular.forEach(data , function(value) {
$scope.coords.push(value.coordinates);
if('your condition') {
$scope.positions.push(value.coordinates);
}
});
You could use a promise, see the angular doc.
Or you could watch over $scope.coords with $scope.$watch.
I'm trying to convert my basic crud operations into an API that multiple components of my application can use.
I have successfully converted all methods, except the update one because it calls for each property on the object to be declared before the put request can be executed.
controller
$scope.update = function(testimonial, id) {
var data = {
name: testimonial.name,
message: testimonial.message
};
dataService.update(uri, data, $scope.id).then(function(response) {
console.log('Successfully updated!');
},
function(error) {
console.log('Error updating.');
});
}
dataService
dataService.update = function(uri, data, id) {
var rest = Restangular.one(uri, id);
angular.forEach(data, function(value, key) {
// needs to be in the format below
// rest.key = data.key
});
// needs to output something like this, depending on what the data is passed
// rest.name = data.name;
// rest.message = data.message;
return rest.put();
}
I tried to describe the problem in the codes comments, but to reiterate I cannot figure out how to generate something like rest.name = data.name; without specifying the name property because the update function shouldn't need to know the object properties.
Here is what the update method looked like before I started trying to make it usable by any of my components (this works)
Testimonial.update = function(testimonial, id) {
var rest = Restangular.one('testimonials', id);
rest.name = testimonial.name;
rest.message = testimonial.message;
return rest.put();
}
How can I recreate this without any specific properties parameters hard-coded in?
Also, my project has included lo-dash, if that helps, I don't know where to start with this problem. Thanks a ton for any advice!
Try like
angular.extend(rest,testimonial)
https://docs.angularjs.org/api/ng/function/angular.extend
I'm using web services to load data to client side. For binding purposes I need to expand on data that I get. I.e. I don't want to massage all data on server side.
For example, object Trip { Id: "123", Status: "P" }
In HTML I bind table to observableArray and want to display "Pending" instead of "P".
I'm coming from Silverlight/MVVM and usually you would use converter or just add new R/O property to object.
Not sure how this scenario should be handled in knockout.js
You may find here all you need :
http://net.tutsplus.com/sessions/knockout-succinctly/
Have a good read.
If you are just looking for a converter, computed observables are a good candidate.
var Tip = function(data) {
var self = this;
self.id = data.id;
self.status = ko.observable(data.status);
//You may prefer fullStatus, or statusName
self.statusConverter = ko.computed(function() {
return self.statusMap[self.status()];
});
};
Tip.prototype.statusMap = {
P: "Pending",
O: "Open",
C: "Closed"
};
which you can bind to like this:
<td data-bind="text: statusConverter"></td>
You can see it in this fiddle
In my app, I have to fetch some JSON data and assign it to an array before the page is loaded. This is my code for fetching the JSON using the CardService service:
cards = [];
var cs = {
...
fetchCards: function() {
var d = $q.defer();
$http.get("data/cards.php").success(function(data) {
cards = data;
d.resolve();
}).error(function(data, status) {
d.reject(status);
});
return d.promise;
},
getCards: function() { return cards; };
...
}
In the controller's resolve block, I have the following:
WalletController.resolve = {
getCards: function(CardService) {
CardService.fetchCards().then(loadView, showError);
}
}
And in the actual controller, I have the following:
function WalletController($scope, CardService) {
$scope.cards = CardService.getCards();
}
The problem is, the fetchCards function in the service seems to resolve the promise before the JSON data is assigned to the cards variable. This leads to my view loading with blank data until I refresh a couple times and get lucky.
I can confirm the late loading as when I log the cards variable in the console, I get an empty array at line 122 (when my view is loaded) and a full array at line 57 (when the JSON call is successful). Line 57's code somehow executes after the view is loaded.
How do I fix this?
I haven't used resolve but I'm throwing this out there just in case the issue you are having is related to binding to an array returned from a service.
If you are returning your cards array from a service and binding to it in the UI you may want to try to populate that same array instead of setting cards = data; (which will overwrite the local cards with a new array which is not bound to the UI).
Something like:
fetchCards: function() {
var d = $q.defer();
$http.get("data/cards.php").success(function(data) {
cards.length = 0;
for(var i = 0; i < data.length; i++){
cards.push(data[i]);
}
d.resolve();
}).error(function(data, status) {
d.reject(status);
});
return d.promise;
},
See this fiddle for a working example of what I'm trying to describe. Clicking the first button multiple times will update the view but once you click on the second button the binding will be broken.
The main difference between the two is:
First button uses data.length = 0 and data.push() to retain the original array's reference
Second button overwrites the original data array reference with a new one using data = newArray
Update: Also, as Mark Rajcok, mentioned below you can use angular.copy to retain the original array's reference by emptying it out and adding new ones from the source like this:
fetchCards: function() {
var d = $q.defer();
$http.get("data/cards.php").success(function(data) {
angular.copy(data, cards);
d.resolve();
}).error(function(data, status) {
d.reject(status);
});
return d.promise;
},