dna-engine

An uncomplicated user interface library for cloning semantic templates

Fork me on GitHub

Tutorial to Build a REST-driven Search Component

Follow the steps below to build an interactive book search tool on top of the Google Books API.

View a sample of the JSON results you will be using: https://www.googleapis.com/books/v1/volumes?q=spacex


Steps:

  1. Create baseline HTML
  2. Add search component
  3. Add search results component
  4. Style search results
  5. Define book template
  6. Load dna-engine
  7. Wire up search callback
  8. Make REST call
  9. Smarten up the search field
  10. Conclusion

Step 1: Create baseline HTML

Using your favorite code editor, create an HTML file named book-finder.html with the content below.

Initial contents for book-finder.html

            <!doctype html>
            <html>
            <head>
               <meta charset=utf-8>
               <title>Book Finder</title>
            </head>
            <body>

            <main>
               <h1>Book Finder</h1>
            </main>

            </body>
            </html>
         

Open book-finder.html in your web browser to verify it displays properly.

Step 2: Add search component

Just after the h1 header line, insert the HTML for the search input box and the submit button.

Example HTML

            <main>
               <h1>Book Finder</h1>
               <label>
                  Search:
                  <input placeholder="Enter terms" autofocus>
               </label>
               <button>Find</button>
            </main>
         

Now we need some HTML to display the search results.

Step 3: Add search results component

Just after the search component you added in the previous step, insert a section tag to hold a list of books.  Then add the static HTML for a single book.

HTML for books

            <section class=books>
               <div class=book>
                  <img src=https://dna-engine.org/graphics/sample-book-cover.jpg alt=cover>
                  <div>
                     <h2>Title</h2>
                     <p>Publisher</p>
                     <i>Price</i>
                  </div>
               </div>
            </section>
         

If you go to your browser and refresh the book-finder.html page, you'll see the sample book desperately needs some styling.

Step 4: Style search results

Add the following lines into the <head> section:

Example HTML

            <style>
               body      { font-family: system-ui, sans-serif; margin: 30px; }
               input     { font-size: 1.2rem; background-color: aliceblue; }
               .book     { display: flex; align-items: flex-start; max-width: 400px; background-color: skyblue; padding: 10px; margin: 10px 0px; }
               .book img { width: 100px; margin-right: 10px; }
               .book h2  { margin: 0px; }
            </style>
         

The book-finder.html page looks better with a little CSS, but it's still completely static.  Next we'll turn the static book HTML into a template that can take JSON data.

Step 5: Define book template

Before we convert the static book HTML into a data-driven template, we need to know the structure of the data.  Do this by manually examining the results of a sample search.

Below are the pertinent fields:

Sample Google Books API response

            {
               "totalItems": 1306,
               "items": [
                  {
                     "id": "5VVDAAAAQBAJ",
                     "volumeInfo": {
                        "title": "SpaceX",
                        "publisher": "Springer Science & Business Media",
                        "imageLinks": {
                           "thumbnail": "https://books.google.com/books/...",
                        },
                     },
                     "saleInfo": {
                        "listPrice": {
                           "amount": 44.99,
                        },
                     },
                  },
               ],
            },
         

The API returns a single object.  The data we want is the list of books, which is the items array.

The desired fields from each book are:

Convert the book HTML into a template by changing the class book to an ID (name of the template) and adding the class dna-template.  Also insert the data fields surrounded by double tildes (~~) into the desired spots within the template.

Book template

            <div id=book class=dna-template>
               <img src=~~volumeInfo.imageLinks.thumbnail~~ alt=cover>
               <div>
                  <h2>~~volumeInfo.title~~</h2>
                  <p>~~volumeInfo.publisher~~</p>
                  <i>~~saleInfo.listPrice.amount~~</i>
               </div>
            </div>
         

Note: For a production website, set the image src attribute to # and specify the data field in the data-attr-src attribute.  This approach maintains valid HTML and a build tool can automatically convert the links to data URLs.

Now it's time to load some libraries that will help us build dynamic features.

Step 6: Load dna-engine and fetch-json

The easiest way to get the necessary CSS and JavaScript libraries is the pull them from a CDN.

Add the following line into the <head> section:

CSS and JS links in head section

            <link rel=stylesheet href=https://cdn.jsdelivr.net/npm/dna-engine@3.1/dist/dna-engine.css>
            <script src=https://cdn.jsdelivr.net/npm/dna-engine@3.1/dist/dna-engine.min.js></script>
            <script src=https://cdn.jsdelivr.net/npm/fetch-json@3.3/dist/fetch-json.min.js></script>
         

Go to your web browser and reload the book-finder.html page.  Verify dna-engine loaded properly by going into the JavaScript console and entering the command: dna.info();

Step 7: Wire up search callback

Start by declaring the url variable and writing the shell of a function called findBooks().

Put the function just before the closing </head> tag:

Callback event for button

            <script>
               const url = 'https://www.googleapis.com/books/v1/volumes';
               const findBooks = (elem) => {
                  const terms = globalThis.document.querySelector('input').value;
                  console.log(terms);
                  };
            </script>
         

To wire up the function to the "Find" button, we'll use the dna-click attribute.  The attribute tells dna-engine which function to call when the user clicks the element.

Set the data-on-click attribute to findBooks on the search component button:

Callback event for button

            <button data-on-click=findBooks>Find</button>
         

Verify the click events are firing the callback by viewing the JavaScript console while clicking the "Find" button.

Step 8: Make REST call

Immediately after the console.log line, insert the following code:

REST

            const handleResults = (data) =>
               dna.clone('book', data.items, { empty: true, fade: true });
            fetchJson.get(url, { q: terms }).then(handleResults);
         
The code:
  1. Builds the REST URL
  2. Invokes fetchJson.get() function to call the Google Books API
  3. Passes the search results data to the templating engine: dna.clone()

The options { empty: true, fade: true } tell dna-engine to delete any previous results before displaying the new results and to smoothly fade in the new results.

Go to your web browser and give it a whirl by searching for "laser".

Step 9: Smarten up the search field

Clicking the "Find" button may be simple, but it makes for a cumbersome user experience.  We can leverage the smart update feature in dna-engine to replace the button with automatic searching.

Delete the line of HTML for the <button>, and update the <input> tag to be:

Smart update

            <input data-on-smart-update=findBooks
               placeholder="Enter terms" autofocus>
         

dna-engine passes the event target element into the callback.  The callback event now happens on the <input> element, so we can delete the old const terms = globalThis.document.querySelector('input').value; line and replace terms with elem.value.

Smart update

            { q: elem.value }
         

Hop back to your browser and verify that search results are returned continuously as you type in search terms.

Conclusion

That's a wrap!

Finished version:

Code

book-finder.html

            <!doctype html>
            <html>
            <head>
               <meta charset=utf-8>
               <title>Book Finder</title>
               <link rel=stylesheet href=https://cdn.jsdelivr.net/npm/dna-engine@3.1/dist/dna-engine.css>
               <script src=https://cdn.jsdelivr.net/npm/dna-engine@3.1/dist/dna-engine.min.js></script>
               <script src=https://cdn.jsdelivr.net/npm/fetch-json@3.3/dist/fetch-json.min.js></script>
               <style>
                  body      { font-family: system-ui; margin: 30px; }
                  input     { font-size: 1.2rem; background-color: aliceblue; }
                  .book     { display: flex; align-items: flex-start; max-width: 400px; background-color: skyblue; padding: 10px; margin: 10px 0px; }
                  .book img { width: 100px; margin-right: 10px; }
                  .book h2  { margin: 0px; }
               </style>
               <script>
                  const url = 'https://www.googleapis.com/books/v1/volumes';
                  const findBooks = (elem) => {
                     const handleResults = (data) =>
                        dna.clone('book', data.items, { empty: true, fade: true });
                     fetchJson.get(url, { q: elem.value }).then(handleResults);
                     };
               </script>
            </head>
            <body>

            <main>
               <h1>Book Finder</h1>
               <label>
                  Search:
                  <input data-on-smart-update=findBooks placeholder="Enter terms" autofocus>
               </label>
               <section class=books>
                  <div id=book class=dna-template>
                     <img src=~~volumeInfo.imageLinks.thumbnail~~ alt=cover>
                     <div>
                        <h2>~~volumeInfo.title~~</h2>
                        <p>~~volumeInfo.publisher~~</p>
                        <i>~~saleInfo.listPrice.amount~~</i>
                     </div>
                  </div>
               </section>
            </main>

            </body>
            </html>
         

Keep going

Hungry for more?  Try building something using the Google Maps APIs Web Services.

For example, here's the JSON data for the geographic coordinates of Paris: maps.googleapis.com/maps/api/geocode/json?address=Paris

Questions and comments

Tweet your question or comment with #dna-engine or submit an issue on GitHub.