osh-page
v0.0.5
Published
openscihub.org: abstract webpage for isomorphic webdev
Downloads
4
Readme
Page
A Page is a javascript abstraction of a webpage meant to help you build isomorphic web apps. You can think of it as the model layer. It was built with ReactJS in mind as the view/rendering layer but is generic enough for use with any templating/view system (e.g. Handlebars).
The abstraction helps you with:
- "isomorphism"
- progressive enhancement
- AJAX on the client
- API interaction
with the following client/server interaction flow in mind (forward in time is down):
................ .................. .......
| Browser | | Page server | | API |
| | Request page | | | |
| |--------------->| | | |
| | | | GET data | |
| | | |<------------>| |
| | | 1. Render HTML | | |
| | | 2. Stash data | | |
| | Send HTML | | | |
| |<---------------| | | |
| 1. See HTML! | | | | |
| | GET js | | | |
| |<-------------->| | | |
| 1. Get stash | | | | |
| 2. Enhance | | | | |
| HTML w js | | | | |
| 3. Navigate | | | | |
| | GET js? | | | |
| |<-------------->| | | |
| | | | | |
| | |................| | |
| | | |
| | GET data? | |
| |<---------------------------------------------->| |
| 1. Render | | |
| HTML | | |
| . | | . |
| . | (AJAX from here down) | . |
| . | | . |
v v v v
The design of Page closely ties data with URIs. That is, requests for more/different data should be accompanied by navigation/URI-changes and vice versa. This is a necessary design choice if your app promises to serve static pages; otherwise, you will be serving the same dataset under two different URIs (maybe not so bad) or different datasets under the same URI (bad for linking).
URL hash fragments should be used for different views of the same page, since the hash fragment is not sent to the server when requesting a page. This means Page supports AJAX through the HTML5 history API.
Installation
npm install osh-page
Example
Here is a minimal yet complete example. There is no dynamic js on the client
yet; this is a simple static page server. Run this in Node.js (script is at
example/simple.js
).
var Page = require('..');
var express = require('express');
var request = require('superagent');
var html = require('html');
var http = require('http');
var page = Page({
title: 'My page',
path: {
pattern: '/home'
},
body: function(props) {
return '<h1>Welcome!</h1>';
}
});
var app = express();
page.serve(app);
var server = http.createServer(app);
server.listen(3333, function() {
request.get('localhost:3333/home')
.end(function(res) {
console.log(
html.prettyPrint(res.text)
);
server.close();
});
});
A small mess of html should print to the console that looks something like:
<!DOCTYPE html>
<html>
<head>
<title>My page</title>
</head>
<body>
<div id="__mount">
<h1>Welcome!</h1>
</div><span id="__stash" data-stash="{"data":{},"params":{},"query":{},"id":"/home","uri":"/home"}"></span>
</body>
</html>
Documentation
Configuration
Create a new Page instance by calling Page(...)
with a config object holding
the following properties.
title (String|Function)
The title can be a simple string or a function that accepts a
props
object as its single argument.
path (Object|Path)
A Path instance or config
object. The page will be served at this url after a call to
Path.serve()
.
body (Function)
The body function takes the props
object and
returns a string. This string is injected into the <div id="__mount">
tag as shown above.
ui (Function)
This function is called after a Page has been rendered to the DOM. On an initial page load, the
data (Object)
The data object is more complex and is at the heart of Page's functionality. This object is a set of namespaces of the developer's choosing, each equipped with the following properties.
var page = Page({
// ...
data: {
user: {
stash: function(user) {
// Return only a subset of user if you don't want to stash everything.
return user;
},
find: function(user) {
var username = document.getElementById('username').textContent;
return extend(user, {username: username});
},
get: function(props, callback) {
superagent.get('https://api.github.com/...')
.end(function(err, res) {
callback(err, res.body);
});
}
},
post: { /* e.g. a blog post */ }
},
// ...
});
data.{namespace}.get (Function)
Signature
get(Object props, Function callback)
Make a request for some data (asynchronously) and pass the data
to a callback when finished. The returned data will be assigned to
props.data.{namespace}
(see the props
object).
Note, the props object passed as first argument to this function does not include the data property.
data.{namespace}.path (Path)
This must be a Path instance, and will be queried (via GET request) automatically by Page when it is gathering its data prior to rendering. It is a convenience for data.{namespace}.get.
Note that the path and query parameters given to a data path GET request are those given to the various methods that request a Page (e.g. visit()). If a translation is required between page parameters and data path parameters, you must use data.{namespace}.get instead.
data.{namespace}.stash (Function|Boolean)
Signature
stash(* namespace)
This function is called on the server and determines what data is stashed in
the <span id="__stash">
element. If not a function, its truthiness determines
whether to stash all or none of this namespace's data (default is undefined
which is falsey).
On initial page load in the browser, Page automatically
loads data from <span id="__stash">
, passing it to ui() for progressive
enhancement (and/or body() on subsequent navigation).
If the data namespace does not exist in the stash, it is loaded via an
API request.
The purpose of the stash option is to prevent duplicate requests to APIs on initial page load if raw data is needed by your page's javascript; the API requests are made once on the server, and the resulting data is stashed in the DOM for immediate use on the client.
The design of the callback allows selective stashing for when
only some of the data under a namespace is large. Simply return from the
stash function the data you would like saved to <span id="__stash">
.
Use the find callback to recover
the unstashed data from the DOM.
data.{namespace}.find (Function)
Signature
find(* namespace)
Recover unstashed data for this namespace from the DOM in the browser. This
function is given the namespace data recovered from <span id="__stash">
and
should return the fully recovered namespace data (this often means calling
something like extend() on the data
recovered from <span id="__stash">
and returning the result).
The props
object
After a Page has finished gathering data, it will pass a props
object to
various rendering callbacks defined on the Page config object. The
props
object will have the following properties:
id (String)
An id describing which Page instance this props belongs to.
uri (String)
The Page uri built from the given path and query parameters. parameters.
data (Object)
The object of data namespaces.
params (Object)
Page path params.
query (Object)
Page query params.
License
MIT