I am calling the Foursquare API from the View Model of Knockout.
I get no errors, but I see in the network tab no sign of the API getting called.
Do I need to add specific code to call the API in the View Model?
var ViewModel = function(){
console.log("View Model started")
var self = this;
// Foursquare API Call :
self.venueList = ko.observableArray([
]);
this.foursquareURL = 'https://api.foursquare.com/v2/venues/search?ll=37.8,-122.4&query=croissant&client_id=CLIENT_ID&client_secret=CLIENT_SECRET';
this.fs_ApiCall = function()
{
console.log("API called");
$.getJSON(foursquareURL, function(data){
//Add a header
$foursquareElem.text('Croissants');
var venues = data.response.venues;
self.venueList = ko.observableArray([]);
for (var i=0; i<venues.length; i++){
console.log(venues[i].name);
self.venueList.push ({
name: venues[i].name,
lat: venues[i].location.lat,
lng: venues[i].location.lng
});
console.log(self.venueList()[i].name)
}
}).error(function() {
$foursquareElem.text( "No data available" );
});
};
};
ko.applyBindings(new ViewModel());
HTML:
<div id="foursquare-venues">
<ul data-bind= "foreach:venueList">
<li id="li-name" data-bind = "text: $data.name">
</li>
As mentioned in comments
Please make sure you declare the observable /observableArray once and re-use it . Avoid duplicates declarations as you have one such in getJSON .
Related
Question, why and how do i get my json api data to display. to display my api infomation
I am new to api and am trying
json data
[{"title":"One article - API 1 - 2017-04-25 15:43:20"},{"title":"Another article - API 1 - 2017-04-25 15:43:20"},{"title":"Great article - API 1 - 2017-04-25 15:43:20"}]
I have a small js file that im using to get my api
$(document).ready(function () {
$('#get-data').click(function () {
var showData = $('#show-data');
$.getJSON('https://some api ', function (data) {
console.log(data);
var items = data.title (function (item) {
return title;
});
showData.empty();
if (items.length) {
var content = '<li>' + items.join('</li><li>') + '</li>';
var list = $('<ul />').html(content);
showData.append(list);
}
});
showData.text('Loading the JSON file.');
});
});
I then have a html part to display the api info onlick
<body>
Get JSON data
<div id="show-data"></div>
<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<script src="example.js"></script>
</body>
You need to iterate over the items in data to build your HTML and then append it to the showData div.
I also changed how you are building your <li> a little bit for security purposes. If you set the HTML of the <li> equal to each item's title property coming back from data, and the title contains malicious HTML/scripts, your application has been successfully compromised with an XSS attack.
As a general rule of thumb, never set HTML unless you absolutely have to - especially if it is coming from a third-party source.
$(document).ready(function() {
$('#get-data').click(function() {
var showData = $('#show-data');
$.getJSON('https://some api ', function(data) {
showData.empty();
var items = data.map(function(elem) {
return $("<li />", {
text: elem.title
});
});
var list = $('<ul />').append(items);
showData.append(list);
});
});
});
Get JSON data
<div id="show-data"></div>
<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<script src="example.js"></script>
There is no method data.title() you want Array#map()
Scaled down version:
$.getJSON('https://some api ', function(data) {
// map title properties into flattened array
var items = data.map(function(item) {
return item.title;
});
if (items.length) {
var content = '<li>' + items.join('</li><li>') + '</li>';
var list = $('<ul />').html(content);
showData.append(list);
}
});
DEMO
The data returned in $.getJSON is a collection of objects. That might have been clear to you while you consoled the response.
Now, loop through data and you can access each object and insert title into li.
See a sample code below:
data.map(function(obj) {
console.log(obj.title) // use this in your <li>
})
I have configured a simple backbone Model and View using an underscore template. The exact same configuration is used for two separate APIs.
API 1 works as expected.
To reproduce the problem, comment out the url for API 1 and uncomment the url for API 2.
As you can see I have normalized the response data for both apis, the exact same data structure is returned for both apis. However, the render method for API 2 is not called. To make matters even more strange, on rare occasions render does get called by API 2.
What am I missing here?
// Model
var Quote = Backbone.Model.extend({
// API 1
//urlRoot: 'http://quotes.stormconsultancy.co.uk/quotes/1.json',
// API 2
urlRoot: 'http://quotes.rest/qod.json',
parse: function (data){
try{
data = data['contents'].quotes[0];
}
catch(e){
}
var rd = {author:data.author, quote:data.quote}
console.log("parsed", typeof rd, rd);
return rd;
},
// UPDATE as suggested by cory
initialize: function() {
this.on('all', function(eventName) {
console.log('QuoteModel: ' + eventName);
});
}
});
// View
var QuoteView = Backbone.View.extend({
initialize: function() {
this.template = _.template($('#quote-template').html());
this.listenTo(this.model, 'change', this.render);
},
render: function(){
console.log("render", this.model.attributes)
this.$el.html(this.template(this.model.attributes));
}
});
var quoteM = new Quote();
quoteM.fetch();
$(document).ready(function() {
var quoteV = new QuoteView({
el: $('#quote'),
model: quoteM
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.5/handlebars.min.js"></script>
<script type="text/html" id="quote-template">
<p>The author is : <%= author %></p>
<p>The content is : <%= quote %></p>
</script>
<div id="quote"></div>
You have a race condition, where you fetch before creating the view.
So if the fetch finishes before the document is ready, the change event gets triggered before the view has started listening to the model.
The simplest solution
$(document).ready(function() {
var quoteM = new Quote();
var quoteV = new QuoteView({
el: $('#quote'),
model: quoteM
});
// fetch after
quoteM.fetch();
});
The best solution
var API_DOMAIN = "http://quotes.rest/";
// Reusable model
var Quote = Backbone.Model.extend({});
// reusable quotes collection
var QuoteCollection = Backbone.Collection.extend({
model: Quote,
// simple generic parse
parse: function(response) {
return response.contents.quotes;
},
});
// View
var QuoteView = Backbone.View.extend({
// GOOD: gets called once
template: _.template($('#quote-template').html()),
initialize: function() {
// BAD: gets called for every view
// this.template = _.template($('#quote-template').html());
this.listenTo(this.model, 'change', this.render);
},
render: function() {
console.log("render", this.model.attributes)
this.$el.html(this.template(this.model.toJSON()));
// Backbone standard for chaining
return this;
}
});
$(function() {
var quoteV,
collection = new QuoteCollection();
collection.fetch({
url: API_DOMAIN + 'qod.json',
success: function(collection, response, options) {
// only create the view when you need it
quoteV = new QuoteView({
el: $('#quote'),
model: collection.first()
});
// manually render to be 100% in control. The event now only
// serves if the model really changes.
quoteV.render();
}
});
});
<div id="quote"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.5/handlebars.min.js"></script>
<script type="text/html" id="quote-template">
<p>The author is :
<%= author %>
</p>
<p>The content is :
<%= quote %>
</p>
</script>
Add some logging for the events on your Quote model, and you should be able to track down the problem quickly.
var Quote = Backbone.Model.extend({
initialize: function() {
this.on('all', function(eventName) {
console.debug('QuoteModel: ' + eventName);
});
}
});
My application makes api calls to the census and uses that data in combination with Google Maps API v3. It works as expected much of the time, but I’m getting an intermittent error of ‘Initmap is not defined’, or ‘google is not defined’, or ‘TypeError: map.data.getFeatureById(...) is undefined’ without any discernible reason.
HTML:
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
<script src="https://maps.googleapis.com/maps/api/js?v=3&key=KEY1234&callback=initMap" async defer
></script>
<script src="js/mapfunc.js"></script>
</head>
<body>
<div id="map"></div>
<script>var map;
function initMap() {
map = new google.maps.Map(document.getElementById('map'), {
zoom: 4,
center: {lat: 35, lng: -106}
});
}</script>
</body>
</html>
JS:
function loadMapShapes() {
map.data.loadGeoJson('jsonya2.geojson', { idPropertyName: 'STATE' });
variable = 'B01003_001E,NAME';
variable2 = ',B01001F_002E';
loadCensusData(variable);
}
function loadCensusData(variable) {
// load the requested variable from the census API
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://api.census.gov/data/2014/acs5/?get=' +
variable + '&for=state:*&key=KEY123');
xhr.onload = function() {
var censusData = JSON.parse(xhr.responseText);
censusData.shift(); // the first row contains column names
censusData.forEach(function(row) {
censusMin = 0;
censusMax = 36000000;
var censusVariable = parseFloat(row[0]);
var stateName = row[1];
var stateId = row[2];
// keep track of min and max values
if (censusVariable < censusMin) {
censusMin = censusVariable;
}
if (censusVariable > censusMax) {
censusMax = censusVariable;
}
// update the existing row with the new data
coolid = map.data.getFeatureById(stateId);// <-- Here's where
//I get the error most often: "TypeError: map.data.getFeatureById(...) is undefined"
if (coolid !== undefined) {
map.data
.getFeatureById(stateId)
.setProperty('census_variable', censusVariable);
map.data
.getFeatureById(stateId)
.setProperty('census_variable1', stateName);
}
coolstate = map.data.getFeatureById(stateName);
});
Again - this code works maybe 40% of the time, and throws one of the above-described errors the rest of the time. I may notice an increase in errors during the day but can't be sure.
Thanks a lot for any thoughts, here's a link to a live version of this code with census and google maps API calls:
http://dukecitydigital.com/c1/
loadGeoJson runs asynchronously, use the callback of loadGeoJson to execute further functions which depend on the result of loadGeoJson:
function loadMapShapes() {
variable = 'B01003_001E,NAME';
variable2 = ',B01001F_002E';
map.data.loadGeoJson('jsonya2.geojson', { idPropertyName:'STATE'}, function(){
loadCensusData(variable);
});
}
Here is the code I am using to avoid 'undefined' error:
// update the existing row with the new data
if (typeof(map.data.getFeatureById(stateId)) != "undefined") {
map.data
.getFeatureById(stateId)
.setProperty('census_variable', censusVariable);
}
my searchform with backbone works.. except it always says that the item is not found so I think I always send an empty array so yes, then it's logic it won't find anything.
My searchresult view:
var ArtikelSearchResultsView = Backbone.View.extend({
el: '#searchResults',
render: function ( query_encoded ) {
var query = decodeURIComponent(query_encoded.replace(/\+/g, "%20"));
var result_artikels = _.filter(this.model.models, function (artikel_model) {
var artikel = artikel_model.attributes;
for (var key in artikel) {
if ( artikel[key].toLowerCase().indexOf( query.toLowerCase() ) >= 0 )
{
return true;
}
}
return false;
});
// Show results
var template = $("#search-results").html();
var result_html = _.template( template, { artikels: result_artikels, query: query } );
this.$el.html( result_html );
}
});
My router sends this:
searchResults: function(query){
artikelSearchView.render(query);
var artikelSearchResultsView = new ArtikelSearchResultsView({ model: Artikel });
artikelSearchResultsView.render(query);
}
Artikel is in this case:
var Artikel = Backbone.Model.extend({
urlRoot: 'api/items.json',
defaults: {
titel: 'Titel niet opgegeven',
url_titel: 'unieke sleutel urltitel',
img_path: 'geen image toegevoegd',
commentaar: 'Commentaar niet opgegeven',
categorie: 'Categorie niet opgegeven',
waardering: 0,
artikel: 'Artikel niet opgegeven'
},
initialize: function(){
if(!this.get('description')){
var lazy = 'This user was too lazy too add a description';
this.set('description', lazy);
}
}
});
Full code: http://pastebin.com/Y9zi6aGH (Awere that I use Artikel and Artikels in different ways, I know it's bad practice but that's the way I go for now) So my question is: Can someone fix me this so I get searchresult? If I press "a" that should give allmost all my objects but it gives me nothing in results.
I have made some changes to your code in order to work :
1- In your view ArtikelSearchResultsView, I have bound it's model (collection) reset event to it's render method, thus once it's model is reset from the server it call it's render method :
var ArtikelSearchResultsView = Backbone.View.extend({
el: '#searchResults',
initialize: function(){
this.model.bind('reset', this.render, this);
} ...
2- change
var artikels = new Artikels();
var artikel = new Artikels();
to
var artikels = new Artikels();
var artikel = new Artikel();
3- And finally change your router :
searchResults: function(query){
var artikelSearchResultsView = new ArtikelSearchResultsView({ model: artikels });
artikels.fetch();
}
I have also removed the filtering from ArtikelSearchResultsView render method in order to test it, now the 'this.model.models' is populated with the data you receive from the server.
I will just show the part of the code I am having problens. If you think that you need to see all the code, let me know.
My Controller:
index: function() {
var documents = new App.Collections.Documents();
documents.fetch({
success: function() {
new App.Views.Index({ collection: documents });
},
error: function() {
new Error({ message: "Error loading documents." });
}
});
},
My View:
App.Views.Index = Backbone.View.extend({
initialize: function() {
console.log(this.documents);
this.documents = this.options.documents;
this.render();
},
render: function() {
if(this.documents.length > 0) {
var out = "<h3><a href='#new'>Create New</a></h3><ul>";
_(this.documents).each(function(item) {
out += "<li><a href='#documents/" + item.id + "'>" + item.escape('title') + "</a></li>";
});
out += "</ul>";
} else {
out = "<h3>No documents! <a href='#new'>Create one</a></h3>";
}
$(this.el).html(out);
$('#app').html(this.el);
}
});
The console.log of document code is: undefined
In the controller, var document is valid.
WHYYyY? How I can Access the json sent by the Controller in the view?
Thanks,
console.log(this.documents);
this.documents = this.options.documents;
Your using console.log before this.documents is even set.
UPDATE:
new App.Views.Index({ collection: documents });
this.documents = this.options.documents;
Wouldn't it be this.documents = this.options.collection; ? Because I don't see you passing the option.documents variable anywhere.
You pass collection: documents, so in your view you access it with this.collection.
Check out this jsfiddle that shows it working: http://jsfiddle.net/dira/TZZnA/
I dont know backbone too well ... but is this.documents defined before entering the initialize function ? seems not to be - try switching around the lines :
this.documents = this.options.documents; // define the variable
console.log(this.documents); // console log it