openscihub.org: abstract webpage for isomorphic webdev
npm install osh-pageA 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.
``
npm install osh-page
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).
`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 '
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:
`html
`
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
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.
`js
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
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
, 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
.
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
and
should return the fully recovered namespace data (this often means calling
something like extend() on the data
recovered from and returning the result).
$3
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.
MIT