Reputation: 35
I have a small application that asks the user for their city and returns current weather data. When the user clicks the "Get Weather" function it pulls the Json from the OpenWeatherMap api and returns it to the user. As of right now it refreshes the page and shows the data. What do I need to change so that instead of a page refresh it loads asynchronously whenever you change the city?
Server.js (Express routing)
const express = require('express');
const bodyParser = require('body-parser');
const weatherFunctions = require('./functions/weatherFunctions.js')
const PORT = process.env.PORT || 5000
const app = express()
app.use(express.static('public'));
app.use(bodyParser.urlencoded({ extended: true }));
app.set('view engine', 'ejs')
app.get('/', (req, res) => {
res.render('index', {weather: null, error: null});
})
app.post('/', weatherFunctions.getWeather)
app.listen(PORT, () => console.log(`Listening on ${ PORT }`))
weatherFunctions.js
const request = require('request');
const apiKey = '28af81603ac21f0fe4c75478dad21818';
function currentWeather(req, res) {
let city = req.body.city;
let url = `http://api.openweathermap.org/data/2.5/weather?q=${city}&units=imperial&appid=${apiKey}`
request(url, function (err, response, body) {
if (err) {
res.render('index', {
weather: null,
error: 'Error, please try again'
});
} else {
let weather = JSON.parse(body)
if (weather.main == undefined) {
res.render('index', {
weather: null,
error: 'Error, please try again'
});
} else {
let weatherText = `It's ${weather.main.temp} degrees in ${weather.name}! `;
weatherText += `The low for today will be ${weather.main.temp_min} degrees with a high of ${weather.main.temp_max}`;
res.render('index', {
weather: weatherText,
error: null
}); //passes parameters for ejs to read
}
}
});
}
module.exports = {
getWeather: currentWeather
};
index.ejs
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Simple Weather</title>
<link rel="stylesheet" type="text/css" href="/css/style.css">
<link href='https://fonts.googleapis.com/css?family=Open+Sans:300' rel='stylesheet' type='text/css'>
</head>
<body>
<div class="container">
<fieldset>
<form action="/" method="post">
<input name="city" type="text" class="ghost-input" placeholder="Enter a City" required>
<input type="submit" class="ghost-button" value="Get Weather">
</form>
<% if(weather !== null){ %>
<p><%= weather %></p>
<% } %>
<% if(error !== null){ %>
<p><%= error %></p>
<% } %>
</fieldset>
</div>
</body>
</html>
Upvotes: 3
Views: 1044
Reputation: 8528
The reason it is refreshing for you is because you are rendering HTML server-side. You will need to take advantage of XMLHttpRequest (aka XHR). You can use an XHR wrapper called fetch
which makes working with XHR a lot easier.
Getting API data using a browser, then modifying the DOM using that data, is known as 'client side rendering'..
For example, if you open up your browsers console, and paste the following code into it, you will see how you can send requests from your browser and get data back (this is the foundation of client side rendering):
fetch(`https://api.openweathermap.org/data/2.5/weather?q=New York&units=imperial&appid=28af81603ac21f0fe4c75478dad21818`).then(res => res.json()).then(data => console.log(data));
See the following example:
document
.getElementById("getWeather")
.addEventListener("click", () => {
handleGetWeather();
});
async function handleGetWeather() {
const apiKey = "28af81603ac21f0fe4c75478dad21818";
const locationEl = document.getElementById("location");
const weatherDataEl = document.getElementById("weatherData");
const results = await currentWeather(locationEl.value, apiKey);
weatherDataEl.innerHTML = results;
}
async function currentWeather(location, apiKey) {
const city = location.replace(/\s\s+/g, ' '); // Replace multiple spaces with single space
if (city === "" || city === " ") return `<pre>Please enter a location!</pre>`;
let url = `https://api.openweathermap.org/data/2.5/weather?q=${city}&units=imperial&appid=${apiKey}`;
try {
const res = await fetch(url);
if (res.ok === false) {
return `<pre><i>Location '${encodeURIComponent(city)}' not found!</i></pre><br/><pre>Full Error: ${res.statusText}</pre>`;
}
const weather = await res.json();
const main = weather.main;
const t = Number(main.temp) >= 71.00 ? 'hot' : 'cold';
const min = Number(main.temp_min) >= 71.00 ? 'hot' : 'cold';
const max = Number(main.temp_max) >= 71.00 ? 'hot' : 'cold';
return `
<h1>
It's <span class="${t}">${main.temp}</span> degrees in ${weather.name}!
</h1>
<h1>
The low for today will be
<span class="${min}">${main.temp_min}</span>
degrees with a high of
<span class="${max}">${main.temp_max}</span>
</h1>
`;
} catch {
return `<pre><i>Error, please try again.</i></pre>`;
}
}
pre {
color: red;
margin-bottom: -15px;
}
.hot {
color: red;
}
.cold {
color: blue;
}
<input id="location" />
<button id="getWeather">Get Weather</button>
<div id="weatherData"></div>
Upvotes: 2
Reputation: 16515
You need to consume the api.openweathermap endpoint directly from your page using javascript and ajax (frontrend), rather than through nodejs in your (backend)
In web development there are two approaches to deliver content to end users, called Server rendering and client side rendering
Server side rendering (SSR) — the traditional rendering method, basically all of your page’s resources are housed on the server. Then, when the page is requested (commonly from web browsers), the Html, JS and CSS are downloaded. Also frameworks can dynamically can create the html based on backend logic and finally download it.
Technologies : java, c#, python, nodejs, etc
Client side rendering (CSR) — a more recent kind of rendering method, this relies on JS executed on the client side (browser) via a JavaScript framework. So, when page is requested, a minimal , little or empty index.html, css and js were downloaded. Here javascript is responsible to send or receive data and update a minimal section of the page without an entire page refresh.. Finally when user click or trigger some event, javascript will send or receive the data commonly to an api rest (json) using an async call (ajax).
Technologies : react, angular, vue, aurelia, jquery, pure javascript, etc
In your case you are using nodejs which is a backend language and ejs framework which create dynamically the html in your backend, in other words : Server Rendering
Finally if you don't want the entire page refresh effect, you could add a javascript in your nodejs app as simple asset to download in the of your index.ejs and or at the bottom of your
This javascript must detect the on page completely loaded to load data of bind a javascript function to your . You could use:
Upvotes: 1
Reputation: 707328
To load a new city without refreshing the page, here's an outline of what you'd do:
Hook up client-side Javascript in your page so it intercepts the form submission rather than letting the browser submit the form.
Send the form to your server via an Ajax call from your page Javascript (probably using the client-side fetch()
interface.
Change the server POST handler so that it renders a piece of your page (the piece that changes), not the entire page.
Get the results of the ajax call in your Javascript and replace a portion of the content of the page with the newly rendered portion using DOM manipulations (probably setting the .innerHTML
of a particular parent object in your page).
You have a couple of approaches for how the Ajax call works. You can either a get a piece of rendered HTML from the server and then insert that HTML into the page (replacing some previous HTML) or you can fetch JSON data with the Ajax call and then render that into HTML in the browser and then insert that HTML. You would need a client-side version of your template rendering in order to do the second option.
FYI, it's not clear why you made the word "asynchronous" such a big part of your question. Pretty much everything here is already asynchronous. Even the posting of your form in the current version of your page is already asynchronous and if you change it as above, posting it via Javascript will also be asynchronous.
Upvotes: 0