Let’s roll
Well, actually, let’s not roll. Let’s have a quick look at what it means to build a single page web app:
- Most of the application logic will reside in the client, instead of the server, as we are used to as web developers.
- Most of the application logic will be written in JavaScript. You better get used to that.
- Your application usually persists data to a server via a web API, which ideally is based around REST principles and speaks JSON.
- Your application looks and feels more like a desktop or “native” app.
- Things will happen asynchronously and based on Events instead of synchronously and based on requests and responses. You better get used to that.
- Everything will be more complex than before. And also more fun.
Okay, now let’s really start. What are the most basic ingredients of the most basic single page app?
- Some HTML. This can be only some meta information, script tags for the code and an empty body, but delivering at least one page of handwritten or generated HTML is unavoidable.
- Some JavaScript. Usually some more JavaScript.
- There is no step three. Usually, you will, of course, write some CSS, but it’s certainly possible to write apps without that. Not very beautiful and functional apps, but still.
- No, you don’t actually need a server component. You might want to have a web server to actually host your app, but if you’re okay with your app only saving data in the browser, you don’t need an API on the server. And about that web server part? How about keeping your app in a Dropbox folder? Then you can serve the app via Dropbox’ public sharing. Amazing, right?
A kingdom for an idea!
So. Let’s build an app. What kind of app? Because nobody has done that before, let’s build a todo-list app. Well, the real reason for building this kind of app is that you can take the code we will have written through the course of this book and compare it to all of the other single page apps that Addy Osmani and his armies have collected at the excellent http://addyosmani.github.com/todomvc/[TodoMVC] project and see how you would approach this problem in one of the various libraries and frameworks for which a port of TodoMVC exists.
So, here’s a single page (literally) skeleton for our little app. No real markup so far, no code:
1 <!doctype html>
2 <html>
3 <head>
4 <title>The Single Page App - Todolist</title>
5 </head>
6 <body>
7 <!-- markup goes here -->
8 <script type="text/javascript">
9 // code goes here
10 </script>
11 </body>
12 </html>
Just take that and throw it into a folder of your choice as index.html. You can then open in in a browser of your choice (All screenshots in this book will be made with Chrome. It simply has the best debugging and inspection tools at the moment) and what you will see will be - totally unexpected of course, a blank page.
Filling in the blanks
What we are going to do now, for the course of this book, is first writing this very simple application in raw JavaScript, which will explain a lot of the concepts of single page apps on the way, without confusing you with aspects of external libraries, and then, gradually, use some libraries to make things easier and to abstract some of the core concepts.
This todo application should be as simple as possible. Here’s the feature list we’re going to implement:
- Adding a todo list item (that will just be a simple, one line string)
- Marking todo list items as done
- Editing a todo list item
- Removing a todo list item
- Marking all todo list items as done at once
- Removing all completed todo list items
- Showing only completed todo list items
- Showing only open todo list itesm
(For the sake of simplicity, we’ll skip the view filtering (last two points) for now and implement them later on, when we have some more help at hand)
So, let’s start with the first feature. It’s probably pretty safe to assume that we need an input field for that. So, let’s add that to our empty markup. I have taken the liberties of adding an id attribute to the tag, because we’ll need that pretty soon. Also, modern browsers interpret the placeholder attribute and display the text in grey as long as the input field is empty. When building a simple UI like our todo list, this frees us from adding an explicit label for the input field. Lastly, I added the autofocus attribute which gives us a nice demanding blinking cursor as soon as we load the page.
1 <input id="new-todo" placeholder="What needs to be done?" autofocus>
If you reload the page, the textfield is there, a bit forlorn, but nicely active and waiting for the user input.
Entering data
In a traditional web app, we now would add a button and then a server action that would take the input field data and build a database object with it to persist it in, say, MySQL. Now that we don’t have a server, we need to find a different way. First of all, we need to find out that the user (us!) actually finished entering the todo. There are tons of ways to do that, but since we want to keep the UI on the minimal side, the traditional way of adding a submit button and then intercepting the form submission is not an option. Instead, the todo should be added as soon as the user presses his Enter key.
So, let’s write some code:
1 window.addEventListener('load', windowLoadHandler, false);
2
3 function newTodoKeyPressHandler(event) {
4 if (event.keyCode === 13) {
5 alert("We have a new Todo!");
6 }
7 }
8 function windowLoadHandler() {
9 document.getElementById('new-todo').addEventListener(
10 'keypress',
11 newTodoKeyPressHandler,
12 false
13 );
14 }
If you reload the page, you’ll notice that as soon as you press Enter (or Return), an alert pops up. That’s of course not what should really happen, but at least something happened, right?
So, the first thing is that we want to execute our code only when the HTML is finished with loading, so that we can actually access all the elements (in our case: the input field). This is a good rule of thumb for most things, but if you really want to build super fast JavaScript apps, this might be way to late. Please ignore that for now, again, for the sake of simplicity.
To do that, we install an event listener on the window object. Since the window object is actually present at all time, you can do it at any given point in time (or, in this case “point in the file”). We subscribe to the load event, which will fire as soon as the browser has loaded the complete HTML and other resources like images or external stylesheets etc. The function windowLoadHandler gets attached to that event by using the function name in the addEventListener call.
The DOM song
In that function, we then use document.getElementById, which gives us back a so-called DOM node, at least if an element with the id exists. The DOM, or Document Object Model, is the way the browser exposes the tree of HTML tags it has rendered in its window to JavaScript. If you use the getElementById function or any other of the DOM query methods (we’ll look at some of them later on), you’ll get back such a node object and you can do all kinds of things with them, like changing the styles, adding attributes, or, as in our case, install another event handler. As we want to detect the Return Key, we’ll listen to “keypress” events. Keyboard events are pretty complicated, they are not entirely equal in the various browsers, but in our case it’s pretty straight forward.
So now we need a second function that handles the keypress event. And while we ignored the event object that came with the “load” event, we now definitely need that object. The keycode attribute of that event gives us the actual key that has been pressed. And although these key codes are not completely the same on every browser, the Return key fortunately has the same key code everywhere. It’s 13. If you are wondering, it’s the ASCII-Code for CR, or Carriage Return, which in turn kind of is an awkward reference to typewriter technology and has, unfortunately, nothing to do with horses.
Almost there
There are two important things left to do here: First of all, we need to find out what the user has entered. Second, we need to do something with it.
Getting it should be easy. The event carries a target property that should give you the input field. From there, you can grab the value property. Only, that’s probably not the best way to do it. Events are tricky beasts and sometimes .target is not accurate. This will probably never happen with our example, but to be on the safe side, let’s simply use getElementById and fetch a known-to-be-accurate version of the input field and read the value from there.
The second thing is more tricky of course, because we now need a concept for creating, storing and retrieving todo items. Let’s keep that for the next chapter, shall we?
Keeping the lid on the scope
There’s one little tiny small thing I would like to change, though: Protecting the global scope. It’s one of JavaScripts dirty little corners and it always hurts to have to do this, but let’s just get over it really quickly and I won’t mention it again but simply assume you know why and how I’m doing it.
Thing is: Everything you do in a naked script tag runs in the context of the before mentioned window object. Which also means that the functions we just wrote are now properties of the window object. If you don’t believe me, check in Chrome’s JavaScript console (Use the Menu to open it: Display > Developer > JavaScript console). Enter window.window and see that the console autocompletion already shows our windowLoadHandler.
Now think for a second what would happen if everybody would do it like this. Not only does this literally pollute the window (or “global”) scope, which might seem cosmetic, but it is also dangerous. What if another script running in your page also wants to install a windowLoadHandler?
There’s only one way around it and that’s encapsulating your code in a somewhat strange construct, the IIFE (you can call it Iffy if you must), or Immediately Invoked Function Expression.
1 (function(){
2 // your code
3 }());
Why does this help? It declares an anonymous function (it doesn’t have a name) and after that immediately calls that function, which means that the code you’ve encapsulated in that function is executed. The difference is only in scope: All your code suddenly runs within the anonymous function’s scope and you are not leaking stuff into the global scope by accident. In case you’re wondering: The outer parentheses are not strictly necessary. They are more or less a signal to say: “Look, it’s an iffy”. We will come back to this later on, as you can do some cool stuff with this encapsulation but you also still need to be careful with your code not to leak into the global scope, but for now, it’s enough to know that window is safe now.
So here’s the final JavaScript code:
1 (function() {
2 window.addEventListener('load', windowLoadHandler, false);
3 function newTodoKeyPressHandler(event) {
4 if (event.keyCode === 13) {
5 var todo = document.getElementById('new-todo').value;
6 alert("We have a new Todo: " + todo);
7 }
8 }
9 function windowLoadHandler() {
10 document.getElementById('new-todo').addEventListener(
11 'keypress',
12 newTodoKeyPressHandler,
13 false
14 );
15 }
16 }());
|
The complete example can be found at code.thesinglepageapp.com. |