Charles Engelke’s Blog

December 6, 2011

Chrome Web App Bookshelf – Part 2

Filed under: Uncategorized — Charles Engelke @ 11:54 pm

Note: this is part 2 of the Bookshelf Project I’m working on. Part 1 was a Chrome app “Hello, World” equivalent.

Now that we can build a web page and install it as an app in Chrome it’s time to make the page do something. Ideally, something to do with Amazon Web Services. This project is going to work by incrementally adding features, and I will start small. I want a page that has a field to enter an ISBN and a button to ask it to be looked up at Amazon. Information about the matching book (or an error message if there isn’t one) will be displayed below that in the page. While I’m at it, I’ll also change the page title and add a header explaining what the page is.

The new page is still quite simple:

<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="utf-8" />
   <title>Books to Buy</title>
</head>
<body>
   <h1>Books to Buy</h1>
   <div id="dataentry">
      <input type="text" id="isbn" />
      <button id="lookup">Look Up!</button>
   </div>
   <div id="results">
   </div>
</body>
</html>

If you have already created and installed the basic web app, you can edit the main.html file to match this. When you next run or refresh the app, you should see something like the following:

Books to Buy page, first try

Of course, if you enter an ISBN and press the button nothing happens. I have to write JavaScript to respond to the button press, call an AWS API to look up the information, parse the information, and place it into the empty results div.

I don’t like to put JavaScript in my web pages directly, so I’ll create a separate file for it and load it by putting a script tag right after the title. There are people who argue for placing script tags at the very end of a page for performance reasons but I don’t see it making much difference here, and I still like them near the top. I’ll put the JavaScript code in a file called main.js, and add a line right after the title tag:

   <script src="main.js"></script>

Since this page is HTML5 (thanks to the <!DOCTYPE html> declaration at the top), I don’t need to specify that this is a JavaScript file; HTML5 assumes all script files are. I don’t use a self-closing tag because that often (maybe always) doesn’t work for reasons I don’t understand.

After saving the file and hitting refresh, nothing looks different. Because the new JavaScript file doesn’t yet exist. I brought up the Chrome Developer Tools by hitting Ctrl-Shift-J, and saw this error message in the console:

chrome-extension://jpnlfejeoenacfaonfmmdiofnheemppo/main.js Failed to load resource

By the way, from this I see that the browser refers to my app with a URL starting with chrome-extension:// followed by an apparently randomly assigned string. I don’t know how that will be useful, but it’s interesting.

I need to create a main.js file in the same folder as the main.html file, and put code in it to:

  • Attach an event handler to the button, so that when a user clicks it my code will run.
  • Have that code read the ISBN from the input text box.
  • Call the AWS service to look up the information for that ISBN.
  • If the call works, pull the necessary data out of the response and display it in the results div.
  • If the call fails, either put an error in the div or pop up an alert box.

I’m going to use jQuery to help with this work. That’s a JavaScript library that adds a lot of useful features to JavaScript, and which handles subtle variations between how different browsers implement JavaScript. That second benefit is less important with HTML5, which causes browsers to behave much more consistently than ever before, but I’m used to jQuery and want to use it. I have to download it (either the compressed or uncompressed one will work) and put a copy of it in the same directory as the main web page. I’ll call that downloaded file jquery.js and I’ll add a script tag for it just before the main.js script tag:

   <script src="jquery.js"></script>

Now, what goes in the main.js file? The first thing to do is to attach an event handler to the button’s click event. That’s easy with jQuery:

   $("#lookup").click(lookupIsbn);

The $ is actually a jQuery JavaScript function name (it’s an alias for a function named jQuery). If you give it a string with a CSS selector (which #lookup is, referring to the element with the id lookup) it will return a jQuery object referring to that element, which has added to it a lot of useful methods. One of the methods it adds is click, which takes a function as a parameter. In this case the code is passing a function called lookupIsbn, which means that function should be invoked whenever anybody clicks that button.

There are two problems with this line. The first is pretty obvious: it says to run a function called lookupIsbn but there is no such function. Not yet. I’ll write it soon. The second is more subtle. The browser will execute the JavaScript as soon as possible, which may be before the web page has been fully read and processed. So there may not be an element with id lookup when this code runs and nothing will happen. Or maybe the timing will work out okay and this will do what I want. That would actually be worse because then the code would randomly succeed or fail. I’d rather have consistent behavior, even if that’s consistent failure.

The browser builds a data structure for each page as it reads it, starting with the document element that contains everything else. When it finishes building the page it triggers an event handler on that document element. So I can set up that event to run this code, making it run once the page is ready. jQuery makes that easy by adding a ready method to the document element when we wrap it. So the code should be:

$(document).ready(attachClickHandlerToButton);

function attachClickHandlerToButton(){
   $("#lookup").click(lookupIsbn);
}

In fact, though, people rarely define a named function (like attachClickHandlerToButton) to deal with an action that will happen only once. Instead, they define an anonymous function in place, as follows:

$(document).ready(function(){
   $("#lookup").click(lookupIsbn);
});

I could use the same trick in place of lookupIsbn, but I get uncomfortable when I nest anonymous functions too deeply. The browser’s fine with it, but I’m not. So I have to write lookupIsbn now. That can be defined after the JavaScript above, but I’d rather define it inside the anonymous function, like so:

$(document).ready(function(){
   $("#lookup").click(lookupIsbn);

   function lookupIsbn(){
   // put the code here
   }
});

This prevents any JavaScript code outside of the anonymous function from seeing or using the lookupIsbn function. I don’t much care if they could use it, but if some other code (perhaps in an included third-party library) used the same function name things would get troublesome. This keeps my function definition private and avoids interference with other code.

What goes in there seems pretty straightforward. I have to read the ISBN from the input box, ask AWS for information about that ISBN, parse the result into a readable form, and then display it. That would be something like:

      var isbn = $("#isbn").attr("value");
      var message = askAwsAboutIsbn(isbn);
      $("#results").append(message);

The first line finds the element with id isbn, which is the input element, gets the contents of the value attribute (which is what the user entered), and saves it in a new variable named isbn. The second line magically asks AWS for information, presumably getting a nicely formatted chunk of text back. The last line finds the element with id results and puts an element built from the message text inside of it.

There are some things wrong here. If the message coming back from AWS has HTML inside of it this code will insert it directly into the page, which might end up even running code. I’ve got to fix that. But a bigger problem is the magic askAwsAboutIsbn function call. My code calls the function, waits for a response, then uses the result. But that’s going to involve talking to a remote web site, which is relatively slow. My web page is going to be frozen while waiting for that answer.

The way to handle this freeze is to make the request asynchronous. That is, call askAwsAboutIsbn to get the answer, and give it a function to call when it’s done. Then immediately return instead of waiting for the answer. To do that, the magic askAwsAboutIsbn has to be told not only what ISBN to look up, but also given a function to execute when it’s done. So the code should look something like this:

      var isbn = $("#isbn").attr("value");
      askAwsAboutIsbn(isbn, function(message){
         $("#results").append(message);
      });

This magic box will get the answer without the main program waiting for it. When it has it, it will call the anonymous function, passing the message it got to it, and that function will put the message in the right place on our page. So all that’s left is to write askAwsAboutIsbn. But that’s a pretty tall order, so I’m going to leave it for next time. For now, I’ll just write a stub that returns a canned response:

      function askAwsAboutIsbn(isbn, handleResult){
         handleResult("I don't know the info for "+isbn+" yet.");
      }

This stub just immediately calls the function it was given with a canned response as the parameter. Its purpose is just to see if everything is wired together right.

Putting everything together, there is now a folder called bookshelf that contains six files: main.html, main.js, jquery.js, icon_128.png, icon_16.png, and manifest.json. I haven’t changed the last three of those files and I just downloaded jquery.js. The other two files are the main.html file:

<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="utf-8" />
   <title>Books to Buy</title>
   <script src="jquery.js"></script>
   <script src="main.js"></script>
</head>
<body>
   <h1>Books to Buy</h1>
   <div id="dataentry">
      <input type="text" id="isbn" />
      <button id="lookup">Look Up!</button>
   </div>
   <div id="results">
   </div>
</body>
</html>

and the main.js file:

$(document).ready(function(){
   $("#lookup").click(lookupIsbn);

   function lookupIsbn(){
      var isbn = $("#isbn").attr("value");
      askAwsAboutIsbn(isbn, function(message){
         $("#results").append(message);
      });
   }

   function askAwsAboutIsbn(isbn, handleResult){
      handleResult("I don't know the info for "+isbn+" yet.");
   }
});

If all the files are right the application should work when I enter an ISBN and click the button. And it does:

First version of page showing the result

That’s enough for this post. Next time I’ll actually use the AWS web service to look the information up for the given ISBN, parse the result, and display it. There will be plenty to do after that, though: saving data persistently, improving the display, adding a settings page, and packaging the app. So there’s a lot more to come.

About these ads

5 Comments

  1. […] the Bookshelf Project I’m working on. Part 1 was a Chrome app “Hello, World” equivalent, and part 2 added basic functionality. This part will finally call an Amazon Web Service via […]

    Pingback by Chrome Web App Bookshelf – Part 3 « Charles Engelke’s Blog — December 11, 2011 @ 11:21 am

  2. […] the Bookshelf Project I’m working on. Part 1 was a Chrome app “Hello, World” equivalent, part 2 added basic functionality, and part 3 finally called an Amazon web service. This part will finally […]

    Pingback by Chrome Web App Bookshelf – Part 4 « Charles Engelke’s Blog — December 28, 2011 @ 4:25 pm

  3. […] the Bookshelf Project I’m working on. Part 1 was a Chrome app “Hello, World” equivalent, part 2 added basic functionality, part 3 finally called an Amazon web service, and part 4 parsed the web […]

    Pingback by Chrome Web App Bookshelf – Part 5 « Charles Engelke’s Blog — January 1, 2012 @ 6:22 pm

  4. […] the Bookshelf Project I’m working on. Part 1 was a Chrome app “Hello, World” equivalent, part 2 added basic functionality, part 3 finally called an Amazon web service, part 4 parsed the web […]

    Pingback by Chrome Web App Bookshelf – Part 6 « Charles Engelke’s Blog — January 7, 2012 @ 3:52 pm

  5. […] the Bookshelf Project I’m working on. Part 1 was a Chrome app “Hello, World” equivalent, part 2 added basic functionality, part 3 finally called an Amazon web service, part 4 parsed the web […]

    Pingback by Chrome Web App Bookshelf – Part 7 of 7 « Charles Engelke’s Blog — January 8, 2012 @ 1:36 pm


RSS feed for comments on this post.

The Rubric Theme. Create a free website or blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.

Join 48 other followers

%d bloggers like this: