Search code examples
javascriptnode.jsexpressevent-handlinghandlebars.js

Using Javascript functions with a button in Handlebars


I have a handlebars project with the following structure:

Project
 |
 +-- server.js
 +-- package.json
 +-- package-lock.json
 |    
 +-- public
 |  |  
 |  |-- css
 |      |
 |      + -- style.css
 |
 +-- views
 |  |  
 |  |-- layouts
 |  |   |
 |  |   + -- main.handlebars
 |  +-- index.handlebars

In my server.js file, I created the app to run on localhost:3000, as well as serve the index.handlebars at /. I understand how to grab information from an api, so just to learn how to navigate displaying data from an API in a handlebars application, I created a GetInsult(); function to retrieve a random insult from an API I found online (purely for learning purposes). I grab the data with the function, and then pass the data to the res.render() function. You can see how this works in my server.js file:

//import modules
import dotenv from 'dotenv';
dotenv.config();

import http from 'http';
import path from 'path';
import { dirname } from 'path';
import express from 'express';
import { engine } from 'express-handlebars';
import axios from 'axios';


//other variables
const port = process.env.PORT;
const hostname = process.env.HOSTNAME;
const __dirname = path.resolve();

//store data in variable
var insult = await GetInsult();


const app = express();

//Create http server to run and listen on localhost:3000
var server = http.createServer(app).listen(port, hostname, () => {
    console.log(`Server is running at http://${hostname}:${port}`);
})

app.engine('handlebars', engine());
app.set('view engine', 'handlebars')
app.set('views', './views')

//Use Static Files
app.use('/public', express.static(__dirname + '/public'));
app.use('/css', express.static(path.join(__dirname, 'node_modules/bootstrap/dist/css')));
app.use('/js', express.static(path.join(__dirname, 'node_modules/bootstrap/dist/js')));
app.use('/js', express.static(path.join(__dirname, 'node_modules/jquery')));

app.get('/', (req, res) => {
    res.render('index' , {insult})
});



export async function GetInsult() {
    var response = await axios('https://insult.mattbas.org/api/insult')
    var data = response.data
    return data
}

console.log(insult)

Then in my index.handlebars file, I can successfully display the insult like so:

<main>
    <div style="text-align: center;">
        <h1 id="insult">{{insult}}</h1>
        <button id="insult-button" class="btn btn-dark">Get New Insult</button>
    </div>
</main>

I'm having trouble figuring out how to add the GetInsult(); functionality to the button I have underneath the <h1>{{insult}}</h1> tags. I've tried numerous ways and feel like I'm missing something simple. I would like the button to execute the GetInsult(); function and display the new insult that is retrieved from the API. I'm not sure if I need to house the function elsewhere and call it from another file, or if it's even possible with an asynchronous function. Any guidance would be appreciated. Thank you!


Solution

  • I would like the button to execute the GetInsult(); function and display the new insult that is retrieved from the API.

    The basic steps are as follows:

    1 - Create some client-side Javascript that makes an Ajax call (using the fetch() interface) in the browser that makes a GET request to a new route on your server. You make that route something like /api/insult if you want to distinguish routes that return JSON vs. routes meant for the browser.

    2 - Implement a route on your server for /api/insult that looks something like this:

    app.get("/api/insult", async (req, res) => {
        try {
            let insult = await GetInsult();
            res.json({insult});
        } catch(e) {
            console.log(e);
            res.sendStatus(500);
        }
    });
    

    3 - Then, in your client-side code to get the insult, take the result from that fetch() call and insert it into your current web page to replace the current insult there.

    Your client code could look like this:

    document.getElementById("insult-button").addEventListener("click", async (e) => {
        try {
            let response = await fetch("/api/insult");
            let data = await response.json();
            document.getElementById("insult").innerHTML = data.insult;
        } catch(e) {
            console.log(e);
            alert("Request for new insult failed.")
        }
    });
    

    Make sure this client-code is placed after the insult-button in a <script> tag in your index.handlebars template.


    Summary of how it works:

    So, each time the button is clicked, it will make a new API request to your server. Your server will get a new insult and return that in the API response. Then your client-side code will get the response, parse it as JSON and then insert the insult into the web page where the previous insult was.