Single-Page Application

Junghoo Cho

Single-Page Application (SPA)

  • Example: Gmail
  • Web app where everything happens on a single page
  • No page reload and wait
    • Even when the browser needs to obtain data from server
    • Desktop-app like experience

SPA Example

  • Google Suggest Example
  • Q: What events does the app monitor?
  • Q: What does it do when a typing event is detected?
  • Q: How can the app let users keep typing while waiting for data from the server?
  • A: Need for an asynchronous HTTP request/response API

Fetch API

  • Syntax: fetch(URL)
    • Asynchronous API to issue an HTTP request to a server and process it
  • New “promisified” version of old XMLHttpRequest
    • Sends a request to URL
    • Returns a promise that will be resolved to Response from server
  • Obtaining response body
    • response.text() or response.json() returns a promise that will be resolved to body text or json, respectively

Coding Google Suggest (1)

  • Initial setup
    <html>
    <head><title>Google Suggest</title></head>
    <body>
    <form action="https://www.google.com/search">
        <input type="text" name="q">
        <input type="submit"><br>
    </form>
    <b>Suggestion</b>: <div id="suggestion"></div>
    </body>
    </html>
    
  • Intercepting input event
    • Add oninput="onInput(event);" attribute to the text input box

Coding Google Suggest (2)

  • Inside the callback: send request and process response
  • function onInput(event)
    {
        let query = event.target.value;
        if (query.length > 0) {
            fetch("google-suggest.php?q="+encodeURI(query))  
            .then(res => res.text())
            .then(text => { 
                let display = document.getElementById("suggestion");
                display.textContent = text; 
             });
        }
    }
    
  • Demo of current code

More on Fetch API: Cutomizing Request

  • HTTP request can be customized by adding options to fetch()
    fetch("http://a.com", 
        {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify(data)
        }
    )
    

More on Fetch API: Error Handling

  • Returned promise is rejected only if network error
    • 4xx or 5xx status code resolves normally
    • response.ok is set to false for non-2xx status code
    • response.status, response.statusText, response.url
  • Common error handling pattern
    fetch(url)
    .then(res => {
        if (!res.ok) throw new Error(res.status);
        return res.json();
    })  ...
    .catch(
        ...error handling here...
    );
    

Same-Origin Policy

  • Q: Where was the request sent to? Google server?
    fetch("google-suggest.php?q="+encodeURI(query))        
    
  • fetch() can send a request only to the same host of the page
    • fetch() cannot send a request to a third-party site
    • Same-origin policy
  • Workarounds
    • Run a “proxy” on the same host, which takes a request and forwards it to the third-party Web site
    • Cross-Origin Resource Sharing (CORS)
    • JSONP

Cross-Origin Resource Sharing (CORS)

  • Basic idea: Browser get an explicit approval from the third-party server before returning response to the JS code
    • The browser asks “can I show your response to JS code running here?”
    • The server either “approves” or “denies” the request

CORS HTTP Headers

  • Origin and Access-Control-Allow-Origin
  • This allows JS code running on http://a.com to see the response from http://b.com

How to Use CORS

  • Modern browsers take care of CORS automatically
    • Nothing to do in the browser-side JavaScript code
  • Server should be configured to respond with Access-Control-Allow-Origin header
  • By default, third-party cookies are not sent to the cross-origin server for added security
    • Add the option {credential: include} option to fetch() call to send cookies
    • Configure the server to respond with Access-Control-Allow-Credentials: true header

XML

  • HTML was hugely successful due to
    • Simplicity: can be learned easily
    • Text based: can be easily edited using any text editor
  • But, HTML is mainly for human consumption
    • HTML tags are for document structure, not for semantic meaning
    • e.g., <table>, <ul>, etc.
  • XML: eXtensible Markup Language
    • Data representation standard with “semantic” tag
    • Any tag name can be used to represent the data

XML: Google Suggest Example

<?xml version="1.0"?>
<toplevel>
    <CompleteSuggestion>
        <suggestion data="ucla"/>
    </CompleteSuggestion>
    <CompleteSuggestion>
        <suggestion data="ucla basketball"/>
    </CompleteSuggestion>
    ...
</toplevel>
  • XML is still popular, but JSON’s popularity is growing

XML String ⇆ XML DOM

  • Parsing: XML string → XML DOM
    parser = new DOMParser();
    xml_dom = parser.parseFromString(xml_string,"text/xml");
    
  • Once DOM tree is obtained, we can use JavaScript DOM functions
    • e.g., getElementsByTagName()
  • Serialization: XML DOM → XML string
    serializer = new XMLSerializer();
    xml_string = serializer.serializeToString(xml_dom);
    

Handling Google Suggest Response (1)

  • Response from Google Suggest
    <?xml version="1.0"?>
    <toplevel>
        <CompleteSuggestion>
            <suggestion data="ucla"/>
        </CompleteSuggestion>
        <CompleteSuggestion>
            <suggestion data="ucla basketball"/>
        </CompleteSuggestion>
        ...
    </toplevel>
    

Handling Google Suggest Response (2)

  • Our code for XML → HTML
    function xml2html(response_text) {
        // get the suggestion elements from the response
        let parser = new DOMParser();
        let xml = parser.parseFromString(response_text,"text/xml");
        let s = xml.getElementsByTagName('suggestion');
    
        // construct a bullet list from the suggestions
        let htmlCode = "<ul>";
        for (let i = 0; i < s.length; i++) {
            let text = s[i].getAttribute("data");
            htmlCode += "<li><b>" + text + "</b></li>";
        }
        htmlCode += "</ul>";
    
        return htmlCode;
    }
    
  • Final Google Suggest Code

Back Button

  • Example: http://mail.google.com
    • Q: Open an email. When a user presses back button, what will the user expect?
    • Q: Knowing what we know, what is likely to happen?
  • Browser’s back button may cause a serious usability issue
    • User expects the previous app state within the SPA
    • Browser may unload the app and go to the previous page

Deep Link

  • Q: When a user “saves” a URL, what does the user expect to see when the user visits the URL later?
  • Q: Within a SPA, how can we go back to a particular state of the app if all states are the “same” page?
  • Q: Any way to address the back-button and deep-link issue?

Pre-HTML5: URL Fragment Identifier

  • URL: http://hostname/path#fragment_identifier
  • Change in URL fragment identifier does not reload a page
    • Navigation within the same page
  • Associate each “state” of the app with a unique URL fragment identifier
    • Back button will change the URL by changing the fragment identifier, but browser says in the same page
  • Intercept “fragment identifier change event” to handle “state change event”

location.hash and onhashchange

  • window.location.hash: JavaScript API to URL fragment identifier
  • When the app state changes, Update window.location.hash to change fragment identifier
    • The updated URL is appended to the browser history
  • Set window.onhashchange to a custom handler to take an action on fragment identifer change

HTML5: Session History API

  • history.pushState(object, title, url) and history.replaceState(object, title, url)
    • Allows saving an “object” as part of browser
    • Appends (pushState) or replaces (replaceState) browser history
  • When users navigate history through the back button, “pop state” event is triggered
  • Set window.onpopstate to a custom handler function to update the app using the “popped object” as a result of history navigation

Web Storage API (1)

  • Q: Can our app store data persistently in the client (not in the server)?
    • Allows SPA work using saved data even with no network
  • Q: What about cookie?

Web Storage API (2)

  • localStorage: Persistent client-side data storage API
    • Essentially an “associative array” (or key-value store)
    // store data
    localStorage["username"] = "John";
    localStorage["object"] = JSON.stringify(obj);

    // get data
    let name = localStorage["username"];
    // iterate over all stored keys
    for(let key in localStorage) { 
        let value = localStorage[key];
    }

    // remove data
    localStorage.removeItem("username"); 
    localStorage.clear();  // delete everything

Web Storage API (3)

  • localStorage and sessionStorage
    • localStorage persists over multiple browser sessions
      • Separate storage is allocated per each server
    • sessionStorage persists only within the current browser tab
      • Data disappears once the browser tab is closed
      • If two tabs from the same server is opened, they get separate storage
  • Standard allows storing any object, but most browsers support only string

IndexedDB

What We Learned

  • Single page application (SPA)
  • Fetch: Asynchronous request API
  • Same-origin policy
    • Cross Origin Resource Sharing (CORS)
  • XML
  • Session History
  • Web Storage

References