SPA Bootcamp
OSCON
Brian Capouch, Ben Davisson, Danilo Zekovic
Saint Joseph's College
github.com/capouch/oscon16
My background
- Just retired from 34 years of teaching CS
- C, Java, Perl, PHP; networking, systems programming
- Eighteen months of JavaScript experience
YOU?
Target audience
- Coding experience
- New-ish to recent (ES6) JavaScript
- Want to make web apps
Goals
- Introduce fundamental SPA concepts
- Demonstrate typical development workflow
- Review significant concepts, patterns, etc.
- Analyze a sample app that can serve as a learning tool
Workflow
git clone; cd <repo-name>
npm install
npm start
What is a SPA?
A client application loads instead of a traditional page
Views are generated on the client, instead of server trips
By analogy, control belongs to a client-side "shell"
The page context theoretically never changes
Routing is done on both client and server
Navigation and SEO semantics are fraught
<body>
<main id="main"/>
<script src="/js/bundle.js"></script>
</body>
public/index.html:15
The entire body of index.html
ReactDOM.render((
<Router history={ browserHistory }>
<Route path="/" component={ osconSPA } history={ browserHistory }>
<IndexRoute name="home" component={ Home }/>
<Route path="browse" component= { Browse }/>
<Route path="zoomer" component= { Zoom }/>
<Route path="zoomer/:imageId" component={ Zoom }/>
<Route path="upload" component={ Upload }/>
<Route path="slides" component={ SlideShow } />
<Route path="slides/:viewSet" component={ SlideShow } />
<Route path="*" component={ Home }/>
</Route>
</Router>
), document.getElementById("main"))
js/Shell.jsx:39
The top-level code that fills the main div
Node.js is key
- JavaScript end-to-end
- Single execution thread (mostly)
- The Github explosion
Our sample app
Scene:History
A system to assist in archiving, cataloging, finding, and rendering high-definition scanned images
It has five views
- Launch
- Upload/Data entry
- Browser
- Deep Tile Zoomer
- Slideshow
Each of the view modules exports a single default
Awesome syntactic feature: the name you assign to the default export DOESN'T MATTER!!!!
export default class UniqueNewYork extends React.Component {
constructor(props) {
super(props)
}
render() {
// This is the default case, i.e. Zoomer with no parameters
let sendParms = "../tiles/bremer"
// This fires if properties are sent explictly
if ( this.props.params.imageId ) {
sendParms = '/tiles/' + this.props.params.imageId
}
return (
<Section>
<center><ZoomBox image={sendParms}/></center>
</Section>
)
}
}
/js/Zoom.js:51
No authentication
Minimal error checking
Often simplistic
No test scaffolding
oscon.saintjoe-cs.org:2016
This ain't your former self's JavaScript
- ES6 brings a lot to the table
- Transpilation makes it easy to do
- Functional capabilities add expressive power
ES6
- Babel makes it trivially easy
- Scoping and modules make for cleaner code
- Is there a case for "classic" JavaScript anymore?
// Render composite component
export default React.createClass ( {
render() {
return (
<div>
<InfoTable
url={ assetBase + queryTarget}/>
</div>
)
}
})
js/Browse.js:198
// fieldValues provide a form's default input--in this case empty
let fieldValues = {
title : null,
description : null,
source : null,
taglist : null
}
// We blank fields after each data entry event
const blankFieldValues = {
title: null,
description: null,
source: null,
taglist: null
}
js/Upload.js:74
fieldValues = Object.assign({}, fieldValues, fields)
js/Upload.js:103
export default {
...image,
...geopoint
};
graphql/queries/index.js:4
Think async
At the moment I worked this out I thought, "WUT?"
this.setState({fetchURL: searchURL}, function(){
this.loadRecordsFromServer()
sessionStorage.setItem('browse', JSON.stringify(this.state))
}.bind(this))
js/Browse.js:148
Let's desconstruct:
This is the basic operation we want, to set a React state variable
this.setState({fetchURL: searchURL})
But the operation is asynchronous - i.e. we have to use a callback when it finishes
// Once we know our change is in place, reload current working set and cache component state
function(){
this.loadRecordsFromServer()
sessionStorage.setItem('browse', JSON.stringify(this.state))
}
Shell.jsx
- Brazenly similar to the neal-react demo page
Browse.js
- react-griddle
- react-search-bar
- wrapped HTML buttons
SlideShow.js
Upload.js
- Dropzone.js wrapped in React
- Generic HTML form wrapped in React
- Images are post-processed via Sharp
Zoom.js
Modules
- Maintainability
- Namespace
- Re-usability
exports.operation = (function() {
var num1 = 4; // this is a local variable
function addNumbers (num2) {
var sum = num1 + num2;
return sum;
}
return {
addNumbers : addNumbers
};
})();
Module as function
ES6 solves many problems from past
let, const and var
let num1 = 4
function addNumbers (num2) {
return num1 + num2
}
export default addNumbers
ES6 modules
import addNumbers from './addNumbers
CommonJS
- Server-first approach
- Synchronous loading
- Not a good solution for the client side
- Returns only objects
function moduleName() {
// do something
...
}
module.exports = moduleName;
CommonJS
var moduleName = require("moduleName");
AMD
- Asynchronous Module Definition
- Browser-first approach
- Exports more than just objects (strings, functions, constructors, JSON)
define([], function) {
var foo = "some value"; // local variable
return {
bar : function(){
// some code to be returned
}
}
}
AMD
require(['someFile'], function ( bar ) {
// your code
bar.doSomething();
});
Routing
Client
Server
HTTP request
HTML page
Server
Client
Router
HTTP request
View
Client Side
Server Side
Server Side Routing
- Syntax did not change compared to traditional
- It always returns the same file, regarding the route given
router.get('/', function(req, res) {
res.sendFile('index.html', options)
})
router.get('/upload', function(req, res) {
res.sendFile('index.html', options)
})
Server Side (Express)
Client Side Routing
ON MATCH "route/example"
FUNCTION execute functionality
VIEW render requested page
AngularJS
.when("/routes/example", {
templateUrl : "views/example.html",
controller : "exampleController"
})
Third party routing library (page.js)
page('/', index);
page('/upload', upload);
page('/browse', browse);
...
htmlElement.hide()
htmlElemetn.show()
jQuery
render((
<Router history={hashHistory}>
<Route path="/" component={App}>
<Route path="/about" component={About}/>
<Route path="/contact" component={Contact}/>
</Route>
</Router>
), document.getElementById('app'))
React Router
<NavItem><NavLink to="/about" className="nav-link">About</NavLink></NavItem>
<NavItem><NavLink to="/contact" className="nav-link">Contact</NavLink></NavItem>
Default router
Parameters (:someoption)
History (HTML5)
Four Variants
Cloud
Master
Two variations:
- uses online storage
- uses local storage
Switched by Source Code
Desktop
Electron-dist
- storage on cloud
Electron-local
- storage on local file system
Switched by Branches
Electron's Promise
New Concept:
Single Codebase serves as Web App & Desktop App
Electron
Electron is a framework for creating native applications with web technologies like JavaScript, HTML, and CSS
Increased Security
Offline Access
Personal Use
Use for Today
- Web App(cloud)
- Master Branch
Electron is Complex
-
Large Files on GitHub
-
Assets must be installed Locally
Switching Variants
Requires change in the source code
Browse.js
SlideShow.js
Zoom.js
Changes in Browse.js
Line 20
// 1.
// Select one of the two to configure for local/cloud access
// Local assets
// const assetBase = '/oscon-test?'
//
// Cloud assets
const assetBase = 'http://oscon.saintjoe-cs.org:2016/oscon-test?'
// 2.
// Local assets
// let searchURL = assetBase + queryTarget
// Cloud assets
let searchURL = 'http://oscon.saintjoe-cs.org:2016/oscon-test?' + queryTarget
Line 141
Changes in Slideshow.js
Line 14
// 2.
// cloud assets:
const urlBase = 'http://oscon.saintjoe-cs.org:2016/'
// local assets:
// const urlBase = '/'
Line 67
// 1.
// Cloud assets
const assetBase = 'http://oscon.saintjoe-cs.org:2016/oscon-test?'
//
// Local assets
// const assetBase = '/oscon-test?'
Changes in Zoom.js
Line 15
// 1.
// Use cloud-based image assets
const assetBase ="http://oscon.saintjoe-cs.org:2016/"
//
// Use local assets
// const assetBase = ''
Deep Zoom Image Tiles
SPA Bootcamp
By capouch
SPA Bootcamp
- 1,798