Building a JavaScript single page application without a framework

Build a JavaScript Single Page App Without a Framework - SitePoint

The front frame is great. As your projects evolve, they can be structured in a way that allows you to understand most of the complexity of your application and help you organize it in a single page.

However, there is also a downside: these frameworks incur a certain degree of overhead and may introduce their own complexity.

That's why in this tutorial, we'll learn how to build SPA from scratch without using the client-side JavaScript framework. This will help you assess what these frameworks actually do for you and when it makes sense to use them. It will also let you know the parts that make up a typical SPA and how they are connected.

Let's start

precondition

For this tutorial, you need to have Modern JavaScript and jQuery Basic knowledge of. Some use Handlebars,Express and Axios Experience will come in handy, although it is not absolutely necessary. You also need to make the following settings in your environment:

You can find it in our GitHub repository Found completed items in.

Build project

We will build a simple currency application that will provide the following functions:

  • Display the latest exchange rate
  • Conversion from one currency to another
  • Displays the past currency exchange rate based on the specified date.

We will use the following free online rest APIs to implement these functions:

Fixer is a perfect API that provides JSON API for foreign exchange and currency conversion. Unfortunately, this is a commercial service, and the free program does not allow currency exchange. So we also need to use the free currency converter API. The transformation API has some limitations, and fortunately it will not affect the functionality of our application. Direct access without API key. However, the fixer requires an API key to perform any request. Just Register on their website to get the access key of the free program.

Ideally, we should be able to build an entire single page application on the client side. However, since we will handle sensitive information (our API key), we cannot store it in our client code. Doing so will make our application vulnerable, and any junior hacker can bypass the application and access data directly from our API endpoint. In order to protect such sensitive information, we need to put it into the server code. Therefore, we will set up a Express The server acts as a proxy between the client code and the cloud service. By using a proxy, we can safely access this key because the server code will never be exposed to the browser. The figure below illustrates how our completed project will work.

Note the npm packages that each environment will use -- that is, browsers (clients) and servers. Now that you know what we're going to build, go to the next section and start creating the project.

Project catalogs and dependencies

Go to your workspace directory and create the folder single page application. Open the folder in VSCode or your favorite editor, and then use the terminal to create the following files and folders:

touch .env .gitignore README.md server.js
mkdir public lib
mkdir public/js
touch public/index.html
touch public/js/app.js

Open gitignore and add these lines:

node_modules
.env

Open readme MD and add these rows:

# Single Page Application

This is a project demo that uses Vanilla JS to build a Single Page Application.

Next, package JSON creates the file by executing the following command in the terminal:

npm init -y

You should get the following generated for you:

{
  "name": "single-page-application",
  "version": "1.0.0",
  "description": "This is a project demo that uses Vanilla JS to build a Single Page Application.",
  "main": "server.js",
  "directories": {
    "lib": "lib"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node server.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

See how convenient the npm command is? The content is generated according to the project structure. Now let's install the core dependencies required for the project. Execute the following commands in the terminal:

npm install jquery semantic-ui-css handlebars vanilla-router express dotenv axios

After the package installation is complete, go to the next section and start building the foundation of the application.

Application basis

Before we start writing front-end code, we need to implement a server-client foundation to work. This means the basic HTML view provided from the Express server. node_modules for performance and reliability reasons, we will inject front-end dependencies directly from the folder. We must set up our Express server in a special way to do this. Open server JS and add the following:

require('dotenv').config(); // read .env files
const express = require('express');

const app = express();
const port = process.env.PORT || 3000;

// Set public folder as root
app.use(express.static('public'));

// Allow front-end access to node_modules folder
app.use('/scripts', express.static(`${__dirname}/node_modules/`));

// Listen for HTTP requests on port 3000
app.listen(port, () => {
  console.log('listening on %d', port);
});

This provides us with a basic Express server. I've reviewed the code, so I hope it gives you a fairly good understanding of what's happening. Next, open public / index HTML and enter:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <link rel="stylesheet" href="scripts/semantic-ui-css/semantic.min.css">
  <title>SPA Demo</title>
</head>
<body>
  <div class="ui container">
    <!-- Navigation Menu -->
    <div class="ui four item inverted orange menu">
      <div class="header item">
        <i class="money bill alternate outline icon"></i>
        Single Page App
      </div>
      <a class="item" href="/">
        Currency Rates
      </a>
      <a class="item" href="/exchange">
        Exchange Rates
      </a>
      <a class="item" href="/historical">
        Historical Rates
      </a>
    </div>

    <!-- Application Root -->
    <div id="app"></div>
  </div>

  <!-- JS Library Dependencies -->
  <script src="scripts/jquery/dist/jquery.min.js"></script>
  <script src="scripts/semantic-ui-css/semantic.min.js"></script>
  <script src="scripts/axios/dist/axios.min.js"></script>
  <script src="scripts/handlebars/dist/handlebars.min.js"></script>
  <script src="scripts/vanilla-router/dist/vanilla-router.min.js"></script>
  <script src="js/app.js"></script>
</body>
</html>

We use semantic UI for style setting. see also Semantic UI menu Documentation for the code for our navigation bar. Go to your terminal and start the server:

npm start

Open in browser localhost:3000 . You should have a blank page with only the navigation bar:

Now let's write some view templates for our application.

Front end skeleton template

We will use Handlebars To write our template. JavaScript will be used to render the template based on the current URL. The first template we will create will be used to display error messages, such as 404 or server errors. Put this code in public / index After the HTML navigation section:

<!-- Error Template -->
<script id="error-template" type="text/x-handlebars-template">
  <div class="ui {{color}} inverted segment" style="height:250px;">
    <br>
    <h2 class="ui center aligned icon header">
      <i class="exclamation triangle icon"></i>
      <div class="content">
        {{title}}
        <div class="sub header">{{message}}</div>
      </div>
    </h2>
  </div>
</script>

Next, add the following templates, which will represent the view of each URL path we specify in the navigation bar:

<!-- Currency Rates Template -->
<script id="rates-template" type="text/x-handlebars-template">
  <h1 class="ui header">Currency Rates</h1>
  <hr>
</script>

<!-- Exchange Conversion Template -->
<script id="exchange-template" type="text/x-handlebars-template">
  <h1 class="ui header">Exchange Conversion</h1>
  <hr>
</script>

<!-- Historical Rates Template -->
<script id="historical-template" type="text/x-handlebars-template">
  <h1 class="ui header">Historical Rates</h1>
  <hr>
</script>

Next, let's compile all these templates into public / JS / APP js. After compiling, we will render rates template and see what it looks like:

window.addEventListener('load', () => {
  const el = $('#app');

  // Compile Handlebar Templates
  const errorTemplate = Handlebars.compile($('#error-template').html());
  const ratesTemplate = Handlebars.compile($('#rates-template').html());
  const exchangeTemplate = Handlebars.compile($('#exchange-template').html());
  const historicalTemplate = Handlebars.compile($('#historical-template').html());

  const html = ratesTemplate();
  el.html(html);
});

Note that we wrap all JavaScript client code in a load event. This is just to ensure that all dependencies have been loaded and that the DOM has finished loading. Refresh the page to see what we have:

We are making progress. Now, if you click a link other than Currency Rates, the browser will try to get a new page and finally display the following message Cannot GET /exchange:

We are building a single page application, which means that all operations should occur on one page. We need a way to tell the browser to stop getting new pages when the URL changes.

Client routing

In order to control the routing in the browser environment, we need to implement client-side routing. There are many client routing libraries that can help solve this problem. For our project, we will use vanilla router , this is a very easy to use routing packet.

If you remember, we were already in index html. Therefore, we can call the Router class immediately. Delete the last two statements you added app JS and replace them with the following code:

// Router Declaration
const router = new Router({
  mode: 'history',
  page404: (path) => {
    const html = errorTemplate({
      color: 'yellow',
      title: 'Error 404 - Page NOT Found!',
      message: `The path '/${path}' does not exist on this site`,
    });
    el.html(html);
  },
});

router.add('/', () => {
  let html = ratesTemplate();
  el.html(html);
});

router.add('/exchange', () => {
  let html = exchangeTemplate();
  el.html(html);
});

router.add('/historical', () => {
  let html = historicalTemplate();
  el.html(html);
});

// Navigate app to current url
router.navigateTo(window.location.pathname);

 // Highlight Active Menu on Refresh/Page Reload
const link = $(`a[href$='${window.location.pathname}']`);
link.addClass('active');

$('a').on('click', (event) => {
  // Block browser page load
  event.preventDefault();

  // Highlight Active Menu on Click
  const target = $(event.target);
  $('.item').removeClass('active');
  target.addClass('active');

  // Navigate to clicked url
  const href = target.attr('href');
  const path = href.substr(href.lastIndexOf('/'));
  router.navigateTo(path);
});

Take some time to browse the code. I added comments in various sections to explain what is happening. You will notice that in the declaration of the router, we specified the attribute that page404 uses the wrong template. Let's test the link now:

Links should now work. But we have a problem. Click the / exchange or historical link and refresh the browser. We get the same error as before - Cannot GET /exchange. To fix this problem, go to server JS and add this statement before listening Code:

// Redirect all traffic to index.html
app.use((req, res) => res.sendFile(`${__dirname}/public/index.html`));

You must use Ctrl+C and execute restart server npm start. Return to the browser and try refreshing. You should now see that the page is rendered correctly. Now, let's try to enter a non-existent path in the URL, such as / exchanges The application should display a 404 error message:

We have now implemented the necessary code to create our single page application framework. Now let's start listing the latest currency exchange rates.

Latest currency exchange rate

For this task, we will use Fixer Latest Rates Endpoint . Open env file and add your API key. We will also specify the timeout period and the symbols we will list on the page. If your Internet connection is slow, please feel free to increase the timeout value:

API_KEY=<paste key here>
PORT=3000
TIMEOUT=5000
SYMBOLS=EUR,USD,GBP,AUD,BTC,KES,JPY,CNY

Next, create the file lib / Fixer service js. Here, we will write help code for our Express server to easily request information from the Fixer. Copy the following code:

require('dotenv').config();
const axios = require('axios');

const symbols = process.env.SYMBOLS || 'EUR,USD,GBP';

// Axios Client declaration
const api = axios.create({
  baseURL: 'http://data.fixer.io/api',
  params: {
    access_key: process.env.API_KEY,
  },
  timeout: process.env.TIMEOUT || 5000,
});

// Generic GET request function
const get = async (url) => {
  const response = await api.get(url);
  const { data } = response;
  if (data.success) {
    return data;
  }
  throw new Error(data.error.type);
};

module.exports = {
  getRates: () => get(`/latest&symbols=${symbols}&base=EUR`),
};

Also, take some time to browse the code to see what's happening. If you are not sure, you can also view dotenv,axios Document and read Module export . Now let's do a quick test to confirm that the getRates() function works properly.

Open server JS and add the following code:

const { getRates } = require('./lib/fixer-service');

...
// Place this block at the bottom
const test = async() => {
  const data = await getRates();
  console.log(data);
}

test();

Run npm start or node server. After a few seconds, you should get the following output:

{
  success: true,
  timestamp: 1523871848,
  base: 'EUR',
  date: '2018-04-16',
  rates: {
    EUR: 1,
    USD: 1.23732,
    GBP: 0.865158,
    AUD: 1.59169,
    BTC: 0.000153,
    KES: 124.226892,
    JPY: 132.608498,
    CNY: 7.775567
  }
}

If you get something similar to the above, the code is running. These values will of course vary, as rates change from day to day. Now comment out the test block and insert this code before the statement that redirects all traffic to html:

// Express Error handler
const errorHandler = (err, req, res) => {
  if (err.response) {
    // The request was made and the server responded with a status code
    // that falls out of the range of 2xx
    res.status(403).send({ title: 'Server responded with an error', message: err.message });
  } else if (err.request) {
    // The request was made but no response was received
    res.status(503).send({ title: 'Unable to communicate with server', message: err.message });
  } else {
    // Something happened in setting up the request that triggered an Error
    res.status(500).send({ title: 'An unexpected error occurred', message: err.message });
  }
};

// Fetch Latest Currency Rates
app.get('/api/rates', async (req, res) => {
  try {
    const data = await getRates();
    res.setHeader('Content-Type', 'application/json');
    res.send(data);
  } catch (error) {
    errorHandler(error, req, res);
  }
});

As we can see, there is a custom error handling function designed to handle different error scenarios that may occur during server code execution. When an error occurs, an error message is constructed and sent back to the client.

Let's confirm whether this code is valid. Restart the Express server and navigate the browser to this URL: localhost:3000/api/rates . You should see the same JSON result displayed in the console. We can now implement a view that displays this information in a neat and elegant table.

Open public / index HTML and replace rates template with the following code:

<!-- Currency Rates Template -->
<script id="rates-template" type="text/x-handlebars-template">
  <h1 class="ui header">Currency Rates</h1>
  <hr>
  <div class="ui loading basic segment">
    <div class="ui horizontal list">
      <div class="item">
        <i class="calendar alternate outline icon"></i>
        <div class="content">
          <div class="ui sub header">Date</div>
          <span>{{date}}</span>
        </div>
      </div>
      <div class="item">
        <i class="money bill alternate outline icon"></i>
        <div class="content">
          <div class="ui sub header">Base</div>
          <span>{{base}}</span>
        </div>
      </div>
    </div>

    <table class="ui celled striped selectable inverted table">
      <thead>
        <tr>
          <th>Code</th>
          <th>Rate</th>
        </tr>
      </thead>
      <tbody>
        {{#each rates}}
        <tr>
          <td>{{@key}}</td>
          <td>{{this}}</td>
        </tr>
        {{/each}}
      </tbody>
    </table>
  </div>
</script>

Remember, we use the semantic UI to provide us with styles. I hope you will pay close attention Segment loading Components. This will indicate that the user knows something is happening when the application gets the data. We also use Table UI To display the rate. If you are new to Semantic, please read the link document.

Now let's update our code public / JS / APP JS to use this new template. Replace the first route with the following code Add ('/') function:

// Instantiate api handler
const api = axios.create({
  baseURL: 'http://localhost:3000/api',
  timeout: 5000,
});

// Display Error Banner
const showError = (error) => {
  const { title, message } = error.response.data;
  const html = errorTemplate({ color: 'red', title, message });
  el.html(html);
};

// Display Latest Currency Rates
router.add('/', async () => {
  // Display loader first
  let html = ratesTemplate();
  el.html(html);
  try {
    // Load Currency Rates
    const response = await api.get('/rates');
    const { base, date, rates } = response.data;
    // Display Rates Table
    html = ratesTemplate({ base, date, rates });
    el.html(html);
  } catch (error) {
    showError(error);
  } finally {
    // Remove loader status
    $('.loading').removeClass('loading');
  }
});

The first code block instantiates an API client to communicate with our proxy server. The second block is the global function used to handle errors. Its job is to display error banners when there is a problem on the server side. The third block is where we get the rate data from the localhost:3000/api/rates endpoint and pass it to the rates template to display the information.

Just refresh the browser. You should now have the following views:

Next, we will build an interface for converting currencies.

Exchange exchange

For currency conversion, we will use two endpoints:

We need symbolic endpoints to get a list of supported currency codes. We will use this data to populate the drop-down list that the user will use to select the currency to convert. After the function, open lib / fixer service JS and add this line getRates():

getSymbols: () => get('/symbols'),

Create another help file, lib / free current service JS and add the following code:

require('dotenv').config();
const axios = require('axios');

const api = axios.create({
  baseURL: 'https://free.currencyconverterapi.com/api/v5',
  timeout: process.env.TIMEOUT || 5000,
});

module.exports = {
  convertCurrency: async (from, to) => {
    const response = await api.get(`/convert?q=${from}_${to}&compact=y`);
    const key = Object.keys(response.data)[0];
    const { val } = response.data[key];
    return { rate: val };
  },
};

This will help us get the exchange rate from one currency to another for free. In the client code, we have to calculate the conversion amount by multiplying the amount by the rate. Now let's add these two service methods to our Express server code. Open server JS and update accordingly:

const { getRates, getSymbols, } = require('./lib/fixer-service');
const { convertCurrency } = require('./lib/free-currency-service');
...
// Insert right after get '/api/rates', just before the redirect statement

// Fetch Symbols
app.get('/api/symbols', async (req, res) => {
  try {
    const data = await getSymbols();
    res.setHeader('Content-Type', 'application/json');
    res.send(data);
  } catch (error) {
    errorHandler(error, req, res);
  }
});

// Convert Currency
app.post('/api/convert', async (req, res) => {
  try {
    const { from, to } = req.body;
    const data = await convertCurrency(from, to);
    res.setHeader('Content-Type', 'application/json');
    res.send(data);
  } catch (error) {
    errorHandler(error, req, res);
  }
});

Now our proxy server should be able to obtain symbols and conversion rates. Note that this / api/convert is a POST method. We will use a form on the client side to build the currency conversion UI. Feel free to use the test function to confirm that both endpoints are working. Here is an example:

// Test Symbols Endpoint
const test = async() => {
  const data = await getSymbols();
  console.log(data);
}

// Test Currency Conversion Endpoint
const test = async() => {
  const data = await convertCurrency('USD', 'KES');
  console.log(data);
}

You must restart the server for each test. Once you confirm that the code has worked so far, remember to comment out the tests. Now let's deal with our currency conversion UI. Open public / index. By replacing the existing code with the following code Exchange HTML

<script id="exchange-template" type="text/x-handlebars-template">
  <h1 class="ui header">Exchange Rate</h1>
  <hr>
  <div class="ui basic loading segment">
    <form class="ui form">
      <div class="three fields">
        <div class="field">
          <label>From</label>
          <select class="ui dropdown" name="from" id="from">
            <option value="">Select Currency</option>
            {{#each symbols}}
              <option value="{{@key}}">{{this}}</option>
            {{/each}}
          </select>
        </div>
        <div class="field">
          <label>To</label>
          <select class="ui dropdown" name="to" id="to">
            <option value="">Select Currency</option>
            {{#each symbols}}
              <option value="{{@key}}">{{this}}</option>
            {{/each}}
          </select>
        </div>
        <div class="field">
          <label>Amount</label>
          <input type="number" name="amount" id="amount" placeholder="Enter amount">
        </div>
      </div>
      <div class="ui primary submit button">Convert</div>
      <div class="ui error message"></div>
    </form>
    <br>
    <div id="result-segment" class="ui center aligned segment">
      <h2 id="result" class="ui header">
        0.00
      </h2>
    </div>
  </div>
</script>

Take time to read the script and understand what's happening. We are using Semantic UI form To build the interface. We also use the Handlebars symbol to populate the drop-down box. The following is the JSON format used by the Symbols endpoint of the Fixer:

{
  "success": true,
  "symbols": {
    "AED": "United Arab Emirates Dirham",
    "AFN": "Afghan Afghani",
    "ALL": "Albanian Lek",
    "AMD": "Armenian Dram",
  }
}

Note that the symbol data is in map format. This means that the information is stored as a key value {{@ key}} pair {{this}}. Now let's update public / JS / APP JS and use it with the new template. Open the file and replace the existing routing code with / exchange the following:

// Perform POST request, calculate and display conversion results
const getConversionResults = async () => {
  // Extract form data
  const from = $('#from').val();
  const to = $('#to').val();
  const amount = $('#amount').val();
  // Send post data to Express(proxy) server
  try {
    const response = await api.post('/convert', { from, to });
    const { rate } = response.data;
    const result = rate * amount;
    $('#result').html(`${to} ${result}`);
  } catch (error) {
    showError(error);
  } finally {
    $('#result-segment').removeClass('loading');
  }
};

// Handle Convert Button Click Event
const convertRatesHandler = () => {
  if ($('.ui.form').form('is valid')) {
    // hide error message
    $('.ui.error.message').hide();
    // Post to Express server
    $('#result-segment').addClass('loading');
    getConversionResults();
    // Prevent page from submitting to server
    return false;
  }
  return true;
};

router.add('/exchange', async () => {
  // Display loader first
  let html = exchangeTemplate();
  el.html(html);
  try {
    // Load Symbols
    const response = await api.get('/symbols');
    const { symbols } = response.data;
    html = exchangeTemplate({ symbols });
    el.html(html);
    $('.loading').removeClass('loading');
    // Validate Form Inputs
    $('.ui.form').form({
      fields: {
        from: 'empty',
        to: 'empty',
        amount: 'decimal',
      },
    });
    // Specify Submit Handler
    $('.submit').click(convertRatesHandler);
  } catch (error) {
    showError(error);
  }
});

Refresh the page. You should now have the following views:

Select some of the currencies you choose and enter the amount. Then click the conversion button:

oh dear! We have just encountered a wrong scenario. At least we know that our error handling code is valid. To find out why the error occurred, return to the server code and look at the / api/convert function. Specifically, see {const {from, to} = req body;.

Express does not appear to be able to read properties from the request object. To solve this problem, we need to install middleware that can help solve this problem:

npm install body-parser

Next, update the server code as follows:

const bodyParser = require('body-parser');
...

/** Place this code right before the error handler function **/

// Parse POST data as URL encoded data
app.use(bodyParser.urlencoded({
  extended: true,
}));

// Parse POST data as JSON
app.use(bodyParser.json());

Start the server again and refresh the browser. Try another conversion. It should be working now.

Now let's focus on the last point - the historical currency exchange rate. Let's start with the view.

Historical currency exchange rate

Implementing this function is like combining tasks on page 1 and page 2. We will build a small form in which the user needs to enter a date. When the user clicks submit, the exchange rate of the specified date will be displayed in tabular form. We will use the from the Fixer API Historical exchange rate endpoint To achieve this. API requests are as follows:

https://data.fixer.io/api/2013-12-24
    ? access_key = API_KEY
    & base = GBP
    & symbols = USD,CAD,EUR

The response will be as follows:

{
  "success": true,
  "historical": true,
  "date": "2013-12-24",
  "timestamp": 1387929599,
  "base": "GBP",
  "rates": {
    "USD": 1.636492,
    "EUR": 1.196476,
    "CAD": 1.739516
  }
}

Open lib / fixer service like this JS and historical exchange rate endpoint:

...
  /** Place right after getSymbols **/
  getHistoricalRate: date => get(`/${date}&symbols=${symbols}&base=EUR`),
...

Open server JS and add the following code:

...
const { getRates, getSymbols, getHistoricalRate } = require('./lib/fixer-service');
...
/** Place this after '/api/convert' post function **/

// Fetch Currency Rates by date
app.post('/api/historical', async (req, res) => {
  try {
    const { date } = req.body;
    const data = await getHistoricalRate(date);
    res.setHeader('Content-Type', 'application/json');
    res.send(data);
  } catch (error) {
    errorHandler(error, req, res);
  }
});
...

If you have any questions about how the code is arranged, see On GitHub server.js complete file. Write a quick test randomly to confirm whether the historical endpoint works normally:

const test = async() => {
  const data = await getHistoricalRate('2012-07-14');
  console.log(data);
}

test();

After confirming that everything is normal, please remember to comment out the test block. Now let's deal with the client code.

Open index html. Delete the existing content of the historical template that we use as a placeholder and replace it with the following:

<script id="historical-template" type="text/x-handlebars-template">
  <h1 class="ui header">Historical Rates</h1>
  <hr>
  <form class="ui form">
    <div class="field">
      <label>Pick Date</label>
      <div class="ui calendar" id="calendar">
        <div class="ui input left icon">
          <i class="calendar icon"></i>
          <input type="text" placeholder="Date" id="date">
        </div>
      </div>
    </div>
    <div class="ui primary submit button">Fetch Rates</div>
    <div class="ui error message"></div>
  </form>

  <div class="ui basic segment">
    <div id="historical-table"></div>
  </div>
</script>

Look at the form first. One thing I want to point out is that there is no formal date input in the semantic UI. However, thank you Michael de Hoog Contribution that we can use Semantic-UI-Calendar modular. Just install it using npm:

npm install semantic-ui-calendar

Return to public / index HTML and include it in the script section:

...
<script src="scripts/semantic-ui-css/semantic.min.js"></script>
<script src="scripts/semantic-ui-calendar/dist/calendar.min.js"></script>
....

To display historical exchange rates, we will simply reuse rates-template Next, open public / JS / APP JS and update the existing route code / historical:

const getHistoricalRates = async () => {
  const date = $('#date').val();
  try {
    const response = await api.post('/historical', { date });
    const { base, rates } = response.data;
    const html = ratesTemplate({ base, date, rates });
    $('#historical-table').html(html);
  } catch (error) {
    showError(error);
  } finally {
    $('.segment').removeClass('loading');
  }
};

const historicalRatesHandler = () => {
  if ($('.ui.form').form('is valid')) {
    // hide error message
    $('.ui.error.message').hide();
    // Indicate loading status
    $('.segment').addClass('loading');
    getHistoricalRates();
    // Prevent page from submitting to server
    return false;
  }
  return true;
};

router.add('/historical', () => {
  // Display form
  const html = historicalTemplate();
  el.html(html);
  // Activate Date Picker
  $('#calendar').calendar({
    type: 'date',
    formatter: { //format date to yyyy-mm-dd
      date: date => new Date(date).toISOString().split('T')[0],
    },
  });
  // Validate Date input
  $('.ui.form').form({
    fields: {
      date: 'empty',
    },
  });
  $('.submit').click(historicalRatesHandler);
});

Again, take the time to read the comments and understand the code and what it's doing. Then restart the server, refresh the browser and navigate to the / historical path. Select any date before 1999 and click Fetch Rates. You should have something like this:

If you choose a date before 1999 or a future date, an error banner will be displayed when you submit the form.

generalization

Now that we have finished this tutorial, you should see that it is not difficult to build a single page application supported by the REST API without using the framework. However, there are several points we should pay attention to:

  • DOM performance. In our client code, we operate directly on the dom. As the project progresses, this can quickly get out of control, causing the UI to become sluggish.

  • Browser performance. We have loaded many front-end libraries into index. Com as scripts HTML, which is OK for development purposes. For production deployment, we need a system that bundles all scripts so that the browser can use a single request to load the necessary JavaScript resources.

  • Monolithic code. For server code, it is easier to break down the code into modular parts because it runs in a Node environment. However, for client code, unless you use something like webpack Such a packer, otherwise it is not easy to organize in the module.

  • Testing. So far, we have been doing manual testing. For a production ready application, we need to establish a test framework like Jasmine, Mocha or Chai to automate this work. This will help prevent repeated errors.

These are just a few of the many problems you will face when you develop a project without using a framework. Using something like Angular, React, or Vue will help you alleviate many of these concerns. I hope this tutorial will help you and help you become a professional JavaScript developer.

Keywords: Javascript Front-end

Added by eddedwards on Mon, 14 Feb 2022 14:31:39 +0200