banner



How To Create A Single Page Website Using Jquery

Front-end frameworks are great. They abstract away much of the complexity of building a single-page application (SPA) and help you organize your code in an intelligible manner as your project grows.

However, there's a flip side: these frameworks come with a degree overhead and can introduce complexity of their own.

That's why, in this tutorial, we're going to learn how to build an SPA from scratch, without using a client-side JavaScript framework. This will help you evaluate what these frameworks actually do for you and at what point it makes sense to use one. It will also give you an understanding of the pieces that make up a typical SPA and how they're wired together.

Let's get started …

Prerequisites

For this tutorial, you'll need a fundamental knowledge of modern JavaScript and jQuery. Some experience using Handlebars, Express and Axios will come handy, though it's not strictly necessary. You'll also need to have the following setup in your environment:

  • Node.js
  • Git or Git Bash for Window users.

You can find the completed project on our GitHub repository.

Building the Project

We're going to build a simple currency application that will provide the following features:

  • display the latest currency rates
  • convert from one currency to another
  • display past currency rates based on a specified date.

We'll make use of the following free online REST APIs to implement these features:

  • fixer.io API
  • Free Currency Converter API.

Fixer is a well-built API that provides a foreign exchange and currency conversion JSON API. Unfortunately, it's a commercial service and the free plan doesn't allow currency conversion. So we'll also need to use the Free Currency Converter API. The conversion API has a few limitations, which luckily won't affect the functionality of our application. It can be accessed directly without requiring an API key. However, Fixer requires an API key to perform any request. Simply sign up on their website to get an access key for the free plan.

Ideally, we should be able to build the entire single-page application on the client side. However, since we'll be dealing with sensitive information (our API key) it won't be possible to store this in our client code. Doing so will leave our app vulnerable and open to any junior hacker to bypass the app and access data directly from our API endpoints. To protect such sensitive information, we need to put it in server code. So, we'll set up an Express server to act as a proxy between the client code and the cloud services. By using a proxy, we can safely access this key, since server code is never exposed to the browser. Below is a diagram illustrating how our completed project will work.

Project plan

Take note of the npm packages that will be used by each environment — i.e. browser (client) and server. Now that you know what we'll be building, head over to the next section to start creating the project.

Project Directories and Dependencies

Head over to your workspace directory and create the folder single-page-application. Open the folder in VSCode or your favorite editor and create the following files and folders using the terminal:

                          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 lines:

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

Next, create the package.json file by executing the following command inside the terminal:

                          npm              init -y                      

You should get the following content 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 has been generated based on the project structure. Let's now install the core dependencies needed by our project. Execute the following command in your terminal:

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

After the packages have finished installing, head over to the next section to start building the base of the application.

Application Base

Before we start writing our front-end code, we need to implement a server–client base to work from. That means a basic HTML view being served from an Express server. For performance and reliability reasons, we'll inject front-end dependencies straight from the node_modules folder. We'll have to set up our Express server in a special way to make this work. 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 gives us a basic Express server. I've commented the code, so hopefully this gives you a fairly good idea of what's going on. 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're using Semantic UI for styling. Please refer to the Semantic UI Menu documentation to understand the code used for our navigation bar. Go to your terminal and start the server:

                          npm              start                      

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

Navigation bar

Let's now write some view templates for our app.

Front-end Skeleton Templates

We'll use Handlebars to write our templates. JavaScript will be used to render the templates based on the current URL. The first template we'll create will be for displaying error messages such as 404 or server errors. Place this code in public/index.html right after the the 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 that will represent a view for each URL path we specified 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 theses templates in public/js/app.js. After compilation, we'll render the 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)              ;              }              )              ;                      

Take note that we're wrapping all JavaScript client code inside a load event. This is just to make sure that all dependencies have been loaded and that the DOM has completed loading. Refresh the page and see what we have:

Currency rates blank

We're making progress. Now, if you click the other links, except Currency Rates, the browser will try to fetch a new page and end up with a message like this: Cannot GET /exchange.

We're a building a single page application, which means all the action should happen in one page. We need a way to tell the browser to stop fetching new pages whenever the URL changes.

Client-side Routing

To control routing within the browser environment, we need to implement client-side routing. There are many client-side routing libraries that can help out with this. For our project, we'll use vanilla router, which is a very easy-to-use routing package.

If you recall, we had earlier included all the JavaScript libraries we need in index.html. Hence we can call the Router class right away. Remove the last two statements you added to app.js and replace them with this 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 go through the code. I've added comments in various sections to explain what's happening. You'll notice that, in the router's declaration, we've specified the page404 property to use the error template. Let's now test the links:

Historical navigation blank

The links should now work. But we have a problem. Click either the /exchange or historical link, then refresh the browser. We get the same error as before — Cannot GET /exchange. To fix this, head over to server.js and add this statement right before the listen code:

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

You'll have to restart the the server using Ctrl + C and executing npm start. Go back to the browser and try to refresh. You should now see the page render correctly. Now, let's try entering a non-existent path in the URL like /exchanges. The app should display a 404 error message:

404 error

We've now implemented the necessary code to create our single-page-app skeleton. Let's now start working on listing the latest currency rates.

Latest Currency Rates

For this task, we'll make use of the Fixer Latest Rates Endpoint. Open the .env file and add your API key. We'll also specify the timeout period and the symbols we'll list on our page. Feel free to increase the timeout value if you have a slower internet connection:

            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. This is where we'll write helper code for our Express server to easily request information from 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                `                            )              ,              }              ;                      

Again, take some time to go through the code to understand what's happening. If you're unsure, you can also check out the documentation for dotenv, axios and read up on module exports. Let's now do a quick test to confirm the getRates() function is working.

Open server.js and add this 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, it means the code is working. The values will of course be different, since the rates change every day. Now comment out the test block and insert this code right before the statement that redirects all traffic to index.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's a custom error handler function that's designed to handle different error scenarios, which can occur during execution of server code. When an error occurs, an error message is constructed and sent back to the client.

Let's confirm this bit of code is working. Restart the Express server and navigate your browser to this URL: localhost:3000/api/rates. You should see the same JSON result that was displayed in the console. We can now implement a view that will display this information in a neat, elegant table.

Open public/index.html and replace the rates-template with this 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're using Semantic UI to provide us with styling. I'd like you to pay close attention to the Segment loading component. This will be an indication to let users know that something is happening as the app fetches the data. We're also using the Table UI to display the rates. Please go though the linked documentation if you're new to Semantic.

Now let's update our code in public/js/app.js to make use of this new template. Replace the the first route.add('/') function with this code:

                          // 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 for communicating with our proxy server. The second block is a global function for handling errors. Its work is simply to display an error banner in case something goes wrong on the server side. The third block is where we get rates data from the localhost:3000/api/rates endpoint and pass it to the rates-template to display the information.

Simply refresh the browser. You should now have the following view:

Currency rates

Next we'll build an interface for converting currencies.

Exchange Conversion

For the currency conversion, we'll use two endpoints:

  • Fixer's Symbols Endpoint
  • Free Currency Converter Endpoint.

We need the symbols endpoint to get a list of supported currency codes. We'll use this data to populate the dropdowns that the users will use to select which currencies to convert. Open lib/fixer-service.js and add this line right after the getRates() function:

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

Create another helper file, lib/free-currency-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 conversion rate from one currency to another for free. In the client code, we'll have to calculate the conversion amount by multiplying amount by 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 get symbols and conversion rates. Take note that /api/convert is a POST method. We'll use a form on the client side to build the currency conversion UI. Feel free to use the test function to confirm both endpoints are working. Here's 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'll have to restart the server for each test. Remember to comment out the tests once you've confirmed the code is working so far. Let's now work on our currency conversion UI. Open public/index.html and update the exchange-template by replacing the existing code with this:

                                                            <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 your time to go through the script and understand what's happening. We're using Semantic UI Form to build the interface. We're also using Handlebars notation to populate the dropdown boxes. Below is the JSON format used by Fixer's Symbols endpoint:

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

Take note that the symbols data is in map format. That means the information is stored as key {{@key}} and value {{this}} pairs. Let's now update public/js/app.js and make it work with the new template. Open the file and replace the existing route code for /exchange with 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 view:

Exchange UI

Select some currencies of your choosing and enter an amount. Then hit the Convert button:

Exchange error

Oops! We just hit an error scenario. At least we know our error handling code is working. To figure out why the error is occurring, go back to the server code and look at the /api/convert function. Specifically, look at the line that says const { from, to } = req.body;.

It seems Express is unable to read properties from the request object. To fix this, we need to install middleware that can help out with this:

                          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 doing another conversion. It should now work.

Exchange conversion

Let's now focus on the final bit — historical currency rates. Let's start with the views.

Historical Currency Rates

Implementing this feature will be like combining the tasks from the first and second pages. We're going to build a tiny form where the user will be expected to input a date. When the user clicks submit, the currency rates for the specified date will be displayed in table format. We'll use the Historical Rates Endpoint from Fixer API to achieve this. The API request looks like this:

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

And the response will look like this:

                          {              "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.js and the Historical Rates Endpoint like this:

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

Open server.js and add this 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're in any doubt as to how the code is arranged, please refer to the complete server.js file on GitHub. Feel free to write a quick test to confirm the historical endpoint is working:

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

Do remember to comment out the test block once you confirm everything's working. Now let's now work on the client code.

Open index.html. Delete the existing historical-template we used 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                >                                    

Take a look at the form first. One thing I'd like to point out is that Semantic UI doesn't officially have a date input. However, thanks to Michael de Hoog's contribution, we have the Semantic-UI-Calendar module available to us. Simply install it using npm:

                          npm              install              semantic-ui-calendar                      

Go back to public/index.html and include it within the scripts section:

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

To display the historical rates, we'll simply reuse the rates-template. Next open public/js/app.js and update the existing route code for /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)              ;              }              )              ;                      

Once again, take 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. Pick any date before the year 1999 then click Fetch Rates. You should have something like this:

Todo historical rates

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

Todo historical rates error

Summary

Now that we've come to the end of the tutorial, you should see that it's not that difficult to build a single-page application powered by REST APIs without using a framework. But there are a few things we should be concerned with:

  • DOM Performance. In our client-side code, we're directly manipulating the DOM. This can soon get out of hand as the project grows, causing the UI to become sluggish.

  • Browser Performance. There are quite a number of front-end libraries that we've loaded as scripts in index.html, which is okay for development purposes. For production deployment, we need a system for bundling all scripts such that the browsers use a single request for loading the necessary JavaScript resources.

  • Monolithic Code. For the server code, it's easier to break down code into modular parts since it runs within a Node environment. However, for client-side code, it's not easy to organize in modules unless you use a bundler like webpack.

  • Testing. So far we've been doing manual testing. For a production-ready application, we need to set up a testing framework like Jasmine, Mocha or Chai to automate this work. This will help prevent recurring errors.

These are just a few of the many issues you'll face when you approach project development without using a framework. Using something such as Angular, React or Vue will help you alleviate a lot of these concerns. I hope this tutorial has been helpful and that it will aid you in your journey to becoming a professional JavaScript developer.

I write clean, readable and modular code. I love learning new technologies that bring efficiencies and increased productivity to my workflow.

How To Create A Single Page Website Using Jquery

Source: https://www.sitepoint.com/single-page-app-without-framework/

Posted by: simontonwitedingued.blogspot.com

0 Response to "How To Create A Single Page Website Using Jquery"

Post a Comment

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel