Implicit Bookmarking Example Use Javascript to personalize experience


Bookmarking in Action

Imagine you are reading a book online using the website Project Gutenberg. After a while you stop reading and maybe close the browser tab. When you'll come back to reading (after a few hours or few days), you'll have to reenter the URL for your book and the HTML page will load at the beginning. It is up to you to remember where you left reading last time and scroll to that position. But, wouldn't it be nice if your browser would remember this for you and actually open the page directly at the desired position. This would be similar to what you do with a physical book, when you insert a bookmark on the page you stopped reading.

The example explained in these notes does exactly that: it remembers for you the last position on the page that you were reading and the next time you open the page, it goes directly to that position, without you having to take any action.

We will first show how to test this feature and then will explain how this Javascript application was built.

  1. Visit the implicit bookmarking example page (it will open a new tab). Because this is the first time you visit the page, on the right corner you should see the following message:
  2. 1) initial page message
  3. Click on the yellow box and see how it expands to show the following:
  4. 1.a) dialog message
  5. Type your name in the input box and click on the button "Save". After clicking the button you should see the following message, but with your name:
  6. 1.b) personalized message
  7. Start scrolling on the page and find a location further down that you can remember easily (such as a chapter number). At this point close the browser tab and do something else. Write down the time you're closing the tab.
  8. After a few minutes, go back to the page. You should be able to see a message similar to the one below, but with your name, the timestamp of your last visit, as well as the page position where you stopped reading.
  9. 2) bookmarked position

    What is different?

    We are used to personalized experiences on the Web, mostly related to websites such as Facebook, Google, Amazon, etc. However, in all these cases we need to create an account with the service and login every now and then. The solution that we showed is different from a server-based approach in that it doesn't require us to create an account, and no login is necessary. No data is stored on the server. This is because the example uses the local storage of your browser to store and retrieve the information.

    Advantage

    This approach is faster and preserves your privacy.

    Disadvantage

    This approach is device-bound. If you open the book page on another device (phone, tablet, other computer, etc.) you don't have access to your history. Only a server-based solution can maintain cross-device states.

    Writing the HTML and CSS

    Let us start explaining this example by quickly showing the HTML and CSS that is relevant to us. You can follow along to try to replicate this example.

    In the HTML page, there will be two containers aside and main. In the latter container will be the long text of the book, we are not interested in it (in fact we copied that from the Guttenberg page). The aside block contains everything we need for our example. Below, we show the HTML and CSS for the element aside.

    <aside>
      <span>Personalize Page</span>
      <div id="getInfo">
        <input placeholder="your name" size=20> 
        <button>Save</button>
      </div>
      <div id="lastVisit">
      </div>
    </aside>
    
    aside {
      position: fixed;
      top: 5px;
      right: 5px;
      width: 200px;
      cursor: pointer;
      /* more styling follows */
    }
    
    #getInfo{
      display: none;
    }
    

    The span element has the text "Personalize Page", which we will see as long as we haven't undertaken steps to do the personalization. The element <div id="getInfo"> that contains the input text field and the button is made invisible by setting in the CSS file the property display: none;. Notice also that the aside element has a fixed position on the top right corner of the page and we are changing the appearance of the cursor to pointer to give feedback that the box can be clicked.

    There is a second div element, <div id="lastVisit">, which doesn't have any content for the moment and will be dynamically updated with Javascript.

    Breaking Down the Problem

    If you review the screenshots shown at the top, you will notice that when we visit the page, there are two states in which the aside element can be:

    1. The user has not personalized the page, so it will see the message about personalization (Figure 1.)
    2. The user has already entered her name, so the aside box displays it together with the datetime of last visit (Figure 2.). Additionally, the page is scrolled down to the last remembered position.

    This suggests that by storing the name of the user in the local storage and checking when the page is loaded whether the key username has a value or not, we can decide in which of the two states the element should be.

    In fact, if you look at the Javascript code, the event handler for the onload event has exactly an if-else block to achieve this, as shown below:

    function onDocumentReady() {
      
      if (localStorage.getItem("username") != null){
        // some code here to show the welcome user message and scroll to last position
      }
    
      else {
        // some code here that binds the two clicking events (for box and button) to
        // their event handler functions.
      }
    

    null versus undefined

    If you declare a variable, but never assign it a value, trying to access such value will give you back undefined, which is a special Javascript datatype (similar to booleans or numbers).

    If you have never stored a pair of key/values in the localStorage, this means that neither the key nor the value exist, and in this case, trying to access such a key will return the value null. This is why we are checking that (localStorage.getItem("username") != null) in the code snippet above.

    Scenario 1:Suppose we are in the situation of Figure 1. (the user has not yet personalized the page). What we want to happen here is:

    • When the user clicks on the message, the box expands to reveal the hidden text field and button (Figure 1.a). Thus, we need to have a function that deals with the onclick event on the box.
    • If the user enters a name and clicks on the "Save" button, we want to have an event handler function that reads the input and stores it in the localStorage, but that also updates the box to show the welcome message (Figure 1.b) and make the button disappear.

    Scenario 2:Suppose we are in the situation of Figure 2. (the user has personalized the page). In this case, we don't want the user to be performing any action, everything should happen automatically when the HTML page is loaded (the window.onload event):

    • The welcome message shows the stored username.
    • The last visited date is shown.
    • The page is scrolled down to the last known location.

    The information for these three steps was stored previously in the localStorage. Here is a screenshot that shows the key/pair values:

    local storage
    Screenshot from the local storage content for Scenario 2.

    We explained what will happen in the different scenarios, and the only thing we didn't mention is in which moment the key/value pairs for last visit and last scroll position are stored in the storage. We will discuss that in the next section.

    Event Binding

    If you look at the HTML page for our example, you will not find there any references to Javascript functions. The only thing you'll see is the tag: <script src="storageEx.js"> </script> that indicates where our Javascript code is stored. As we'll discuss this again this semester, we are using one of the cornerstone of modularity, separation of concerns to keep HTML and Javascript separate, so that different developers can work on different parts of a website.

    There are two kinds of events that happen in this example:

    1. User-generated events:
      1. User clicks on the yellow box to expand it.
      2. User clicks on the "Save" button.
    2. Browser-generated events:
      1. The page is loaded.
      2. The page is about to be unloaded (e.g. the browser tab will be closed).

    Below we show the Javascript statements that will bind to every of these four events the event handler function that will deal with it.

    // 1.1 User clicks on the yellow box (aside element)
    var aside = document.querySelector("aside");
    aside.onclick = processAsideClick;
    
    // 1.2 User clicks on the "Save" button
    var button = document.querySelector("aside button");
    button.onclick = processButtonClick;
    
    // 2.1 The page is loaded
    window.onload = onDocumentReady;
    
    // 2.2 The page is about to be unloaded
    window.onunload = onLeavingPage;
    

    As always, remember that when we bind an event to the event handler, we don't use the ( ) because that would mean invoking the function, but that is not what we want to do at this moment. We are only saying that once such an event is triggered (fired), we want the broswer to go and execute the function with this name.

    Inspect the Code

    The complete Javascript code for this application can be found in this file. We strongly recommend that you inspect the code to undersand the different component such as event binding, function the perform event handling, storing and retrieving values from the localStorage as well as using helper functions. The code is heavily commented to make it easy for you to understand it.