Yahoo Weather API throws 'forEach' error - node.js

i decided to go ahead and use the yahoo weather api, in an attempt for it to show the location and then the weather.
app.js code:
** note i replaced the api url location with the term 'query'**
var express = require("express");
var app = express();
var request = require("request");
app.set("view engine", "ejs");
app.get("/", function(req, res){
res.render("weatherSearch");
});
app.get("/results", function(req, res){
var query = req.query.searchTerm;
var url = "https://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20weather.forecast%20where%20woeid%20in%20(select%20woeid%20from%20geo.places(1)%20where%20text%3D%22"+query+"%22)&format=json&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys"
request(url, function(error, response, body){
if(!error && response.statusCode == 200){
var data = JSON.parse(body);
res.render("results", {data: data});
}
});
});
app.listen(process.env.PORT, process.env.IP, function(){
console.log("Server Connected");
});
weatherSearch.ejs :
<h1>Where would you like to check the weather?<h1>
<form action ="/results" method="GET">
<input type="text" placeholder="Enter the city here!" name="searchTerm">
<input type="submit">
</form>
results.ejs
<h1>The Weather is:</h1>
<ul>
<% data["query"].forEach(function(weather){ %>
<li>
<strong>
<% weather["location"] %>
</strong>
</li>
<% }) %>
</ul>
Search Again!
The error i receive from the console is:
Server Connected
TypeError: /home/ubuntu/workspace/WeatherSearchAPP/views/results.ejs:4
2|
3| <ul>
>> 4| <% data["query"].forEach(function(weather){ %>
5| <li>
6| <strong>
7| <% weather["location"] %>
data.query.forEach is not a function
at eval (eval at compile (/home/ubuntu/workspace/WeatherSearchAPP/node_modules/ejs/lib/ejs.js:618:12), <anonymous>:11:22)
at returnedFn (/home/ubuntu/workspace/WeatherSearchAPP/node_modules/ejs/lib/ejs.js:653:17)
at tryHandleCache (/home/ubuntu/workspace/WeatherSearchAPP/node_modules/ejs/lib/ejs.js:251:36)
at View.exports.renderFile [as engine] (/home/ubuntu/workspace/WeatherSearchAPP/node_modules/ejs/lib/ejs.js:482:10)
at View.render (/home/ubuntu/workspace/WeatherSearchAPP/node_modules/express/lib/view.js:135:8)
at tryRender (/home/ubuntu/workspace/WeatherSearchAPP/node_modules/express/lib/application.js:640:10)
at EventEmitter.render (/home/ubuntu/workspace/WeatherSearchAPP/node_modules/express/lib/application.js:592:3)
at ServerResponse.render (/home/ubuntu/workspace/WeatherSearchAPP/node_modules/express/lib/response.js:1008:7)
at Request._callback (/home/ubuntu/workspace/WeatherSearchAPP/app.js:19:21)
at Request.self.callback (/home/ubuntu/workspace/WeatherSearchAPP/node_modules/request/request.js:185:22)
yahoo weather api: (This currently gives the weather in 'london'), so if you copy this in to your web browser you'll see a vast amount of info pertaining to the weather in London.
https://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20weather.forecast%20where%20woeid%20in%20(select%20woeid%20from%20geo.places(1)%20where%20text%3D%22london%22)&format=json&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys
if anyone has any suggestions on what i have done incorrectly that would greatly be appreciated!!

The API returns data in the format of { "query": { ...} }, translate this into a JSON object and you end up with a query object, not an array. Looking at the query object itself it does seem to have count and results properties (and the results property also looks like an object). However, your particular query only yields a single result, so assuming this yields an array in scenarios where you have more than one I presume you'll need to use a combination both of these properties to parse the data accurately e.g.
const data = JSON.parse(body);
const { count, results } = data.query;
res.render("results", {
results: count > 1 ? results : [results] // wrap in an array of only single result
});
Then in your view
<% results.forEach(...) %>

'forEach' is an array method and the Yahoo Weather API doesn't return an array under the key "query". Each API is different, and each request can yield different structured JSON data.
You can check how Yahoo Weather API response is structured by opening this link, as you mentioned. For example, if you want to access the city name, you would use
<%= data.query.results.channel.location.city %>
You get a forecast array returned for the upcoming days, on which you could use a forEach loop because it's an array: query.results.channel.item.forecast
In a scenario where you would want to loop over objects in JS, you can use the for...in loop: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...in

Related

Send Data from Span in EJS to Server Nodejs

Using Nodejs, Express, EJS
I see a lot of 'Send Data from Server to Client' but not from Client to Server AND sending the data from a tag not from a form input.
I would like to send the content of a tag from my ejs page/client to my nodejs/server.
What I'm trying...
page.ejs
<div>
<form action="/brewery" method="GET">
<div class="form-group">
<input type="text" class="form-control"
placeholder="search brewery name"
name="search">
</div>
<input class="button" type="submit" value="Submit">
</form>
</div>
<div>
<ul>
<% searchBreweryList.forEach(function(searchBrewery){ %>
<div>
<li>
Brewery Name: <span><%= searchBrewery.brewery.brewery_name %></span>
Brewery ID: <span name="brewID" id="shareBreweryID"><%= searchBrewery.brewery.brewery_id %></span>
</li>
</div>
<% }) %>
</ul>
</div>
Then on my server side...
server.js
app.get('/brewery', async (req, res) => {
var searchBreweryFound = req.query.search;
var isIndie = req.params.brewID
//console.log(isIndie)
//console.log(searchBreweryFound)
try {
request("https://api.com/v4/search/brewery?access_token=abc123&limit=1&q=" + searchBreweryFound, function (error, response, body)
{
if (error) throw new Error(error);
const searchBrewery = JSON.parse(body);
const searchBreweryList = searchBrewery.response.brewery.items.map(item => item );
res.render('brewery.ejs', {searchBreweryList});
//console.log(searchBreweryList);
});
} catch(e) {
console.log("Something went wrong", e)
}
});
So the above Get call is just an example where I'm trying to take the result in the client side span that looks like <span name="brewID">. Then I'm trying to give that ID number to the server in the var seen as var isIndie = req.params.brewID.
But this does not seem to be a method that allows me to pass content from a span in client to server.
What approach should I be taking? Thanks!
It's not clear what you mean by "sending data from client to server". Standard method of sending data from client to server is using XMLHttpRequest (XHR) inside *.js file(s), but based on your code you want to redirect browser window to URL with some parameters. When browser hit your endpoint, for example /brewery, server will render *.ejs file and respond to browser with HTML code.
Redirecting browser with EJS
Below code is based on code you posted.
<div>
<ul>
<% searchBreweryList.forEach(function(searchBrewery){ %>
<div>
<li>
Brewery Name: <span>
<%= searchBrewery.brewery.brewery_name %>
</span>
Brewery ID: <span name="brewID" id="shareBreweryID">
<%= searchBrewery.brewery.brewery_id %>
</span>
<!-- Here we create link using parameter of brewery (version with param) -->
Go to brewery
<!-- Version with query param -->
Go to brewery
</li>
</div>
<% }) %>
</ul>
</div>
After clicking Go to brewery browser will hit /brewery endpoint with param brewery_id. I also noticed that in posted example var isIndie = req.params.brewID may not work.
Bonus: req.query vs req.params
Both req.query and req.params are used to get some informations from endpoint URL. However req.query and req.params are not the same.
Using req.query
For endpoint: /brewery?id=5&q=test
const id = req.query.id // 5
const q = req.query.q // 'test'
Using req.params
To use req params you must place param name somewhere in url of express endpoint.
app.get('/brewery/:id', async (req, res) => {
var id = req.params.id
})
So to make your example with req.params working:
app.get('/brewery/:brewID', async (req, res) => {
var searchBreweryFound = req.query.search;
var isIndie = req.params.brewID
// Rest of your code
})

Rendering elements classname in client-side without JS framework? (EJS)

Let's say I have an app which shows to the users a list of existing hobbies.
Each hobby has a category, stored in the db.
I want every hobby element to have its background color - dependent on its category.
I want to implement this with appending specific class to each element.
Basic example code:
Server
app.get("/hobbies", (req, res) => {
const hobbies = Hobby.getAllHobbies();
res.render("hobbies", hobbies);
});
Client (EJS)
<% hobbies.forEach(hobby => { %>
<div class=""><%= hobby.name %></div>
<% }); %>
What is the best way to append to each div a class depending of hobby.category?
I know its easily possible in React, but I don't want to use any framework for now.
If your classname is not the same as the category but is based on it, then you just need to pass a lookup object to your template.
Server
const categories_classnames = {
lookup: {
swimming: 'div-swim',
biking: 'div-bike',
painting: 'div-paint',
// ...
}
};
app.get("/hobbies", (req, res) => {
const hobbies = Hobby.getAllHobbies();
// Alternatively, `locals = { ...hobbies, ...categories_classnames }`
const locals = Object.assign({}, hobbies, categories_classnames);
res.render("hobbies", locals);
});
Client
<% hobbies.forEach(hobby => { %>
<div class="<%= lookup[hobby.category] %>"><%= hobby.name %></div>
<% }); %>

how can i retrieve objectId of parse server through req.params.id

I am trying to retrieve a parse object with objectId in the show route on nodeJS. Below is my code to help you understand better.
//SHOW route
app.get("/books/:id", function(req, res) {
var Books = Parse.Object.extend("Books");
var query = new Parse.Query(Books);
query.equalTo("objectId", req.params.id);
query.find().then(function(foundBook){
res.render("show", {book: foundBook});
}, function(error) {
res.send("Error: " + error.code + " " + error.message);
});
});
Basically, The req.params.id does not return the objectID. when i try console.log(req.params.id), it returns the Title of the book stored in the database instead of the objectId which is important for linking to the /books/:id page.
Even when i try to retrieve all the objects from the database in the index route, i noticed that <%= book.get('objectId') %> is not displayed on the ejs page.
Please help me out of this. i am a beginner MEAN stack web developer but i am using parse server because the android and web applications would be sharing the same database on parse.com.
Thank You.
<% books.forEach(function(book) { %>
<div class="col-md-3 col-sm-6">
<div class="thumbnail">
<!-- this line of code gets the image content of the array and puts it in the img tag -->
<img src="<%= book.get('coverPictureLink') %>">
<div class="caption">
<h4><%= book.get('Title') %></h4>
</div>
<p>
<!-- This code adds the button and links it to the ID of the campground that was clicked on!-->
More Info
</p>
</div>
</div>
<% }); %>
</div>
Above is sample of the html page for displaying details of a particular book
I finally figured it out. The right way to retrieve object id on the html is book.id not book.get("objectId").
app.get("/books/:id", function(req, res) {
//find the book with provided ID
var Books = Parse.Object.extend("Books");
var query = new Parse.Query(Books);
query.get(req.params.id).then(function(book) {
console.log('retrieved! ' + book.id);
res.render('show', {book: book});
}, function(error) {
console.log('error occured');
res.send('could not be retrieved');
});
});
On the html file,
<p>
More Info
</p>
This is also the same if you are using node.js. with the parse server framework. Using .get('objectId') returns undefined values. Therefore you have to use.
for (i = 0; i < result.length; i++){
console.log('ID:' + result[i].id)
}

Passing data from one page to another using Node, Express & EJS

I've got the following routes set up:
router.get('/', function(req, res) {
res.render('index', {});
});
router.post('/application', function(req, res) {
res.render('application', {twitchLink : req.query.twitchLink});
});
I've got the two views set up properly.
This is what I've got in the 'index' view:
<form class="form-horizontal" action="/application", method="post", role="form">
<input type="url" name="twitchLink" required>
<button class="btn btn-success">Submit</button>
</form>
Submitting this form does take me to the application view.
<script>var twitchLink = <%- JSON.stringify(twitchLink) %></script>
<script>console.log(twitchLink)</script>
This should log out the link that I submitted, right?
However, I get these two lines:
Uncaught SyntaxError: Unexpected end of input
Uncaught ReferenceError: twitchLink is not defined
I think you need to put quotes around <%- JSON.stringify(twitchLink) %>, like this:
var twitchLink = '<%- JSON.stringify(twitchLink) %>'
In your example, it will come out as:
var twitchLink = foo.bar.com
What you want is:
var twitchLink = 'foo.bar.com'

Create jqueryMobile page dynamically based on the url

I am creating an application to get some experience in jQuery Mobile and backbone. I have made a "restful" API with node.js that handles the data I need. It works fine with all my static pages I made in index.html. But when I need to create a page with data from a certain id I am a bit lost.
For example when I want to display all items(/items) I have a data-role=page with id items that list all items, but when I need to go to a detailed page for each item (/items/1) i want to create that details page whenever a user wants details on an item, in other words when a user visit the url spots#3 for example.
Is this possible?
my router: the model gives me all data i want
Spoter.Router = Backbone.Router.extend({
routes: {
"": "",
"spot#:id": "spotDetails"
},
//Details on a certain spot with id
spotDetails: function(id) {
var spotDetailsContentDiv = Spoter.spotDetailsContent;
spotDetailsContentDiv.empty();
var spot = new Spoter.spotModel({id: id});
spot.fetch({
successCallback: function(data) {
var spotDetailsView = new Spoter.spotDetailsView({
model: data
});
spotDetailsContentDiv.html(spotDetailsView.render().el);
}
});
}
});
View:
Spoter.spotDetailsView = Backbone.View.extend({
render:function () {
this.$el.html(this.template(this.model));
return this;
}
});
Template with underscore
<ul data-role="listview" data-theme="c" data-inset="true">
<li>
<a href="#">
<h1><%= this.model.name %></h1>
<p><%= this.model.description %></p>
</a>
</li>
</ul>

Resources