Programming Single Page Applications

Craig Austgen, Brian Capouch, Nathan Samano

Saint Joseph's College

Follow along live:

http://slides.com/capouch/deck-1/live

What, Why, How

What:

Instead of a steady flow of page request/response interaction, there is a one-time transfer of a control program (and its data) from the server to the browser.

 

The program then takes over the browser window and directs all communication with both the enduser and the backend server.

That program is typically called "the shell"

What is a SPA?

A different way of thinking

  • Browser is a virtual app container
  • Apps are intended to be long-lived
  • URL routing on both client and server

Why:

"A SPA isn't like an application; a SPA is an application"

-- Michael Mikowski

Advantages of SPA

  • App-like responsiveness ("fluidity")
  • Less network-latency delay after page load
  • Only one full-page render
  • Offline capabilities
  • Takes advantage of HTML5 features

Challenges

  • Good ones are intensive, expensive
  • Nav buttons are problematic
  • What's a page view?
  • Analytics, SEO, etc.
  • Memory and namespace management

Frameworks abound

How:

Our sample repo

  • Architecture review
  • Websockets interface
  • SEO concerns

Basic SPA Components

  • Executive (shell)
  • Feature modules render views in regions
  • Aware back-end
  • Model (data)

https://github.com/capouch/spa.git

git clone https://github.com/capouch/spa.git

cd spa

npm install

node app.js

 

Notes on repo

  • Simplicity first
  • No data-saving ability
  • Cartoonishly simple
  • Demonstrates basic parts

What is an SPA?

  • Single Page Load
  • Long-lived
  • Like an application
  • Last bullet
<!-- Let's get started -->                                             
<script>                                                               
    // Calling the toplevel initModule starts up the SPA               
    $(function () { spa.initModule( $('#spa') ); });
</script>

</head>
<body>
<main id="spa"></main>
</body>
</html>

index.html

The URL

The SPA takes full ownership of routing requests within its namespace, i.e. it owns all of the URL past the hostname

The shell

The shell is a command and control interpreter, named to suggest its analogy to the well-known OS shell

SPA controls all routing

  • Server-side is done by server package (e.g. Express)
  • #Hash-based client routing is common
  • HTML5 History API pushstate() is the ascendant

app.js

// Watch a file and let clients know if/when one changes
setWatch = function ( url_path, file_type ) {
  console.log( 'setWatch called on ' + url_path );
  //
  if ( ! watchMap[ url_path ] ) {
    console.log( 'setting watch on ' + url_path );
    // Part of Node's native 'fs' package
    fsHandle.watchFile(
      url_path.slice(1),
      function ( current, previous ) {
        console.log( 'file accessed' );
        // Send notice 
        if ( current.mtime !== previous.mtime ) {
          console.log( 'file changed: ' + url_path);
          io.sockets.emit( file_type, url_path );
        }
      }
    );
    watchMap[ url_path ] = true;
  }
};

app.js

// Process all traffic looking for "magic names"
app.use( function ( request, response, next ) {
  // Watch for changes to /js/data.js or /css/sockstyle.css file
  if ( request.url.indexOf( '/js/data.js'  ) >= 0 ) {
    setWatch( request.url, 'script' );
  }
  else if ( request.url.indexOf( '/css/sockstyle.css' ) >= 0 ) {
    setWatch( request.url, 'stylesheet' );
  }
  next();

  app.use( express.static( __dirname + '/' ) );
 
});

data.js

var dummyPage = '<b>Name: </b>Javier Flores'
		+ '<br><b> Address: </b>101 Calle Mayor'
		+ '<br><b> Age: </b>52';

spa.socket.js

+ '<script id="sock_js" src="/js/data.js"></script>'

spa.socket.js initModule()

// Display the value of var "dummyPage" from the data.js file
$(function () {
  jqueryMap.$socketIO.html( dummyPage );
});

// Set event handler to react to "script" message 
io.connect(serverURL).on('script', function (path) {
  $( '#sock_js' ).remove();
  // Replace contents of element with freshly-loaded value
  jqueryMap.$container.append(
    '<script id="sock_js"  src="'
      + path +
      '"></script>'
  );
  // Redisplay HTML with new JavaScript
  jqueryMap.$socketIO.html( dummyPage );
});

// Set event handler to react to "stylesheet" message
io.connect(serverURL).on('stylesheet', function (path) {
  // Get rid of current style
  $( '#sock_css' ).remove();
  // Replace contents of stylesheet with file from websocket
  // Note which container you append to here is crucial
  jqueryMap.$container.append(
    '<link id="sock_css" rel="stylesheet" href="'
      + path +
      '"/>'
  );
  // Redisplay HTML with new styling
  jqueryMap.$socketIO.html( dummyPage );
});

toSpanish.sh

#!/bin/bash

cp js/intl/data.js-es js/data.js
cp css/intl/sockstyle.css-es css/sockstyle.css

data.js-es

// Spanish "version" of our SPA view
var dummyPage = '<b>Nombre: </b>Javier Flores'
		+ '<br><b> Dirección: </b>101 Calle Mayor'
		+ '<br><b> Edad: </b>52<br><b> Estado: </b> D.F.';

SEO and SPAs

  • Issue
  • Follow SEO guidelines
  • Our Solution

Traditional Pages

  • Multiple HTML pages
  • Static HTML

SPA

  • Single HTML Page
  • Small amount of HTML in Body

Solution

 

  • Prerender.io
curl http://localhost:8000

Traditional crawlers' view

of index.html

curl http://localhost:8000/?_escaped_fragment_=

SPA->Desktop App

Full App w/Electron