Comparing version 0.1.1 to 0.2.0
188
index.js
@@ -7,4 +7,3 @@ 'use strict'; | ||
, fuse = require('fusing') | ||
, path = require('path') | ||
, fs = require('fs'); | ||
, path = require('path'); | ||
@@ -19,3 +18,3 @@ // | ||
* A pagelet is the representation of an item, section, column, widget on the | ||
* page. It's basically a small sandboxed application within your page. | ||
* page. It's basically a small sand boxed application within your page. | ||
* | ||
@@ -26,8 +25,15 @@ * @constructor | ||
function Pagelet() { | ||
var writable = Pagelet.predefine(this, Pagelet.predefine.WRITABLE) | ||
, readable = Pagelet.predefine(this); | ||
this.fuse(); | ||
readable('temper', temper); // Template parser. | ||
writable('id', null); // Custom ID of the pagelet. | ||
this.configure(); // Prepare the instance. | ||
this.readable('temper', temper); // Template parser. | ||
this.writable('substream', null); // Substream from Primus. | ||
this.writable('id', null); // Custom ID of the pagelet. | ||
// | ||
// Add an correctly namespaced debug method so it easier to see which pagelet | ||
// is called by just checking the name of it. | ||
// | ||
this.readable('debug', require('debug')('bigpipe:pagelet:'+ this.name)); | ||
this.configure(); // Prepare the instance. | ||
} | ||
@@ -38,2 +44,20 @@ | ||
/** | ||
* Reset the instance to it's original state. | ||
* | ||
* @returns {Pagelet} | ||
* @api private | ||
*/ | ||
Pagelet.readable('configure', function configure() { | ||
// | ||
// Set a new id. | ||
// | ||
this.id = [1, 1, 1, 1].map(function generator() { | ||
return Math.random().toString(36).substring(2).toUpperCase(); | ||
}).join('-'); | ||
this.debug('configuring %s/%s', this.name, this.id); | ||
return this.removeAllListeners(); | ||
}); | ||
/** | ||
* The name of this pagelet so it can checked to see if's enabled. In addition | ||
@@ -91,2 +115,10 @@ * to that, it can be injected in to placeholders using this name. | ||
/** | ||
* A pagelet has been initialised. | ||
* | ||
* @type {Function} | ||
* @public | ||
*/ | ||
Pagelet.writable('initialize', null); | ||
/** | ||
* The actual chunk of the response that is written for each pagelet. | ||
@@ -97,3 +129,3 @@ * | ||
*/ | ||
Pagelet.writable('fragment', fs.readFileSync(__dirname +'/pagelet.fragment', 'utf-8') | ||
Pagelet.writable('fragment', require('fs').readFileSync(__dirname +'/pagelet.fragment', 'utf-8') | ||
.split('\n') | ||
@@ -185,3 +217,3 @@ .join('') | ||
* @type {String} | ||
* @public | ||
* @private | ||
*/ | ||
@@ -194,7 +226,7 @@ Pagelet.writable('directory', ''); | ||
* | ||
* @type {Function} | ||
* @param {Function} done Completion callback when we've received data to render | ||
* @api public | ||
*/ | ||
Pagelet.writable('get', function get(done) { | ||
setImmediate(done); | ||
(global.setImmediate || global.setTimeout)(done); | ||
}); | ||
@@ -222,2 +254,3 @@ | ||
* @param {Function} done Completion callback. | ||
* @returns {Pagelet} | ||
* @api private | ||
@@ -259,3 +292,3 @@ */ | ||
if (err) { | ||
debug('render %s/%s resulted in a error', pagelet.name, pagelet.id, err); | ||
pagelet.debug('render %s/%s resulted in a error', pagelet.name, pagelet.id, err); | ||
throw err; | ||
@@ -287,3 +320,3 @@ } | ||
.replace(/\{pagelet::name\}/g, pagelet.name) | ||
.replace(/\{pagelet::template\}/g, content.replace('-->', '')) | ||
.replace(/\{pagelet::template\}/g, content.replace(/<!--(.|\s)*?-->/, '')) | ||
.replace(/\{pagelet::data\}/g, options.data); | ||
@@ -301,68 +334,85 @@ | ||
}); | ||
return this; | ||
}); | ||
/** | ||
* Reset the instance to it's original state. | ||
* Connect with a Primus substream. | ||
* | ||
* @param {Spark} spark The Primus connection. | ||
* @param {Function} next The completion callback | ||
* @returns {Pagelet} | ||
* @api private | ||
*/ | ||
Pagelet.readable('configure', function configure() { | ||
// | ||
// Set a new id. | ||
// | ||
this.id = [1, 1, 1, 1].map(function generator() { | ||
return Math.random().toString(36).substring(2).toUpperCase(); | ||
}).join('-'); | ||
Pagelet.readable('connect', function connect(spark, next) { | ||
var pagelet = this; | ||
debug('configuring %s/%s', this.name, this.id); | ||
return this.removeAllListeners(); | ||
/** | ||
* Create a new substream. | ||
* | ||
* @param {Boolean} authorized Allowed to use this pagelet. | ||
* @returns {Pagelet} | ||
* @api private | ||
*/ | ||
function substream(authorized) { | ||
if (!authorized) return next(new Error('Unauthorized to access this pagelet')); | ||
var stream = pagelet.substream = spark.substream(pagelet.name); | ||
stream.once('end', pagelet.emits('end')); | ||
stream.on('data', function streamed(data) { | ||
switch (data.type) { | ||
case 'rpc': | ||
// pagelet.trigger(data.method, data.args, data.id); | ||
pagelet.call(data); | ||
break; | ||
case 'emit': | ||
pagelet.emit.apply(pagelet, [data.name].concat(data.args)); | ||
break; | ||
// @TODO handle get/post/put | ||
} | ||
}); | ||
next(undefined, pagelet); | ||
return pagelet; | ||
} | ||
if ('function' !== this.authorize) return substream(true); | ||
this.authorize(spark.request, substream); | ||
return this; | ||
}); | ||
/** | ||
* Trigger a RPC function. | ||
* Call an rpc method. | ||
* | ||
* @param {String} method The name of the method. | ||
* @param {Array} args The function arguments. | ||
* @param {String} id The RPC id. | ||
* @param {SubStream} substream The substream that does RPC. | ||
* @returns {Boolean} The event was triggered. | ||
* @param {Object} data The RPC call information. | ||
* @api private | ||
*/ | ||
Pagelet.readable('trigger', function trigger(method, args, id, substream) { | ||
var index = this.RPC.indexOf(method) | ||
Pagelet.readable('call', function calls(data) { | ||
var index = this.RPC.indexOf(data.method) | ||
, fn = this[data.method] | ||
, pagelet = this | ||
, err; | ||
if (!~index) { | ||
debug('%s/%s received an unknown method `%s`, ignorning rpc', this.name, this.id, method); | ||
return substream.write({ | ||
args: [new Error('The given method is not allowed as RPC function.')], | ||
type: 'rpc', | ||
id: id | ||
}); | ||
} | ||
if (!~index || 'function' !== typeof fn) return this.substream.write({ | ||
args: [new Error('RPC method is not known')], | ||
type: 'rpc', | ||
id: data.id | ||
}); | ||
var fn = this[this.RPC[index]] | ||
, pagelet = this; | ||
if ('function' !== typeof fn) { | ||
debug('%s/%s method `%s` is not a function, ignoring rpc', this.name, this.id, method); | ||
return substream.write({ | ||
args: [new Error('The called method is not an RPC function.')], | ||
// | ||
// Our RPC pattern is a callback first pattern, where the callback is the | ||
// first argument that a function receives. This makes it a lot easier to add | ||
// a variable length of arguments to a function call. | ||
// | ||
fn.apply(pagelet, [function reply() { | ||
pagelet.substream.write({ | ||
args: Array.prototype.slice.call(arguments, 0), | ||
type: 'rpc', | ||
id: id | ||
id: data.id | ||
}); | ||
} | ||
// | ||
// We've found a working function, assume that function is RPC compatible | ||
// where it accepts a `returns` function that receives the arguments. | ||
// | ||
fn.apply(this, [function returns() { | ||
var args = Array.prototype.slice.call(arguments, 0) | ||
, success = substream.write({ type: 'rpc', args: args, id: id }); | ||
return success; | ||
}].concat(args)); | ||
return true; | ||
}].concat(data.args)); | ||
}); | ||
@@ -381,2 +431,3 @@ | ||
* @param {Module} module The reference to the module object. | ||
* @returns {Pagelet} | ||
* @api public | ||
@@ -396,2 +447,3 @@ */ | ||
* @param {Function} hook Hook into optimize, function will be called with Pagelet. | ||
* @returns {Pagelet} | ||
* @api private | ||
@@ -410,2 +462,3 @@ */ | ||
debug('Optimizing pagelet %s for FreeList', prototype.name); | ||
if (prototype.view) { | ||
@@ -469,3 +522,10 @@ Pagelet.prototype.view = path.resolve(dir, prototype.view); | ||
Pagelet.freelist = new FreeList('pagelet', Pagelet.prototype.freelist || 1000, function allocate() { | ||
return new Pagelet; | ||
var pagelet = new Pagelet(); | ||
pagelet.once('free', function free() { | ||
Pagelet.freelist.free(pagelet); | ||
pagelet = null; | ||
}); | ||
return pagelet; | ||
}); | ||
@@ -479,2 +539,2 @@ | ||
// | ||
module.exports = Pagelet; | ||
module.exports = Pagelet; |
{ | ||
"name": "pagelet", | ||
"version": "0.1.1", | ||
"version": "0.2.0", | ||
"description": "pagelet", | ||
@@ -25,3 +25,3 @@ "main": "index.js", | ||
"debug": "~0.7.x", | ||
"fusing": "~0.0.x", | ||
"fusing": "~0.2.x", | ||
"temper": "~0.1.4" | ||
@@ -28,0 +28,0 @@ }, |
107
README.md
# Pagelet | ||
## Getting Started | ||
## Installation | ||
@@ -15,5 +15,28 @@ In all of the following code examples we assume that the `Pagelet` variable is | ||
```js | ||
var Pagelet = require('bigpipe').Pagelet. | ||
var Pagelet = require('bigpipe').Pagelet; | ||
``` | ||
## Table of Contents | ||
**Pagelet function** | ||
- [Pagelet.extend](#pageletextend) | ||
- [Pagelet.on](#pageleton) | ||
**Pagelet instance** | ||
- [Pagelet: name](#pagelet-name) | ||
- [Pagelet: RPC](#pagelet-rpc) | ||
- [Pagelet: fragment](#pagelet-fragment) | ||
- [Pagelet: get](#pagelet-get) | ||
- [Pagelet: authorize](#pagelet-authorize) | ||
- [Pagelet: initialize](#pagelet-initialize) | ||
- [Pagelet: remove](#pagelet-remove) | ||
- [Pagelet: view](#pagelet-view) | ||
- [Pagelet: error](#pagelet-error) | ||
- [Pagelet: engine](#pagelet-engine) | ||
- [Pagelet: css](#pagelet-css) | ||
- [Pagelet: js](#pagelet-js) | ||
- [Pagelet: dependencies](#pagelet-dependencies) | ||
- [Pagelet: id](#pagelet-id) | ||
- [Pagelet: substream](#pagelet-substream) | ||
### Pagelet.extend | ||
@@ -58,2 +81,4 @@ | ||
_required:_ **writable, string** | ||
Every pagelet should have a name, it's one of the ways that [BigPipe] uses to | ||
@@ -87,2 +112,4 @@ identify which pagelet and where it should be loaded on the page. The name | ||
_optional:_ **writable, array** | ||
The `RPC` array specifies the methods that can be remotely called from the | ||
@@ -109,2 +136,4 @@ client/browser. Please note that they are not actually send to the client as | ||
_optional:_ **writable, string** | ||
A default fragment is provided via `Pagelet.fragment`, however it is | ||
@@ -125,2 +154,4 @@ possible to overwrite this default fragment with a custom fragment. This fragment | ||
_required:_ **writable, function** | ||
Get provides the data that is used for rendering the output of the Pagelet. | ||
@@ -146,2 +177,4 @@ | ||
_optional:_ **writable, function** | ||
There is the possibility to create private pagelets. These pagelets could require | ||
@@ -170,4 +203,24 @@ special permissions in your application in order to be used. An example of this | ||
### Pagelet: initialize | ||
_optional:_ **writable, function** | ||
The pagelet has been initialised. If you have an authorization function this | ||
function will only be called **after** a successful authorization. If no | ||
authorization hook is provided it should be called instantly. | ||
```js | ||
Pagelet.extend({ | ||
initialize: function () { | ||
this.once('event', function () { | ||
doStuff(); | ||
}); | ||
} | ||
}); | ||
``` | ||
### Pagelet: remove | ||
_optional:_ **writable, boolean** | ||
This instructs our render engine to remove the pagelet placeholders from the DOM | ||
@@ -188,2 +241,4 @@ structure if we're unauthorized. This makes it easier to create conditional | ||
_required:_ **writable, string** | ||
The view is a reference to the template that we render inside the | ||
@@ -196,2 +251,4 @@ `data-pagelet="<name>"` placeholders. Please make sure that your template can be | ||
_optional:_ **writable, string** | ||
Just like the `Pagelet.view` this is a reference to a template that we will | ||
@@ -202,4 +259,4 @@ render in your `data-pagelet="<name>"` placeholders but this template is only | ||
1. We receive an `Error` argument in our callback that we supply to the | ||
`Pagelet#render` method. | ||
2. Your `Pagelet.view` throws an errow when we're rendering the template. | ||
`Pagelet#get` method. | ||
2. Your `Pagelet.view` throws an error when we're rendering the template. | ||
@@ -212,2 +269,4 @@ If this property is not set we will default to a template that ships with this | ||
_optional:_ **writable, string** | ||
We attempt to detect the correct template engine based on filename as well as | ||
@@ -230,2 +289,4 @@ the template engine's that we can require. It is possible that we make the wrong | ||
_optional:_ **writable, string** | ||
The location of the styling for **only this** pagelet. You should assume that | ||
@@ -248,2 +309,4 @@ you bundle all the CSS that is required to fully render this pagelet. By | ||
_optional:_ **writable, string** | ||
As you might have guessed, this is the location of the JavaScript that you want | ||
@@ -271,2 +334,36 @@ to have loaded for your pagelet. We use [fortress] to sandbox this JavaScript in | ||
_optional:_ **writable, array** | ||
An array of dependencies that your pagelet depends on which should be loaded in | ||
advance and available on the page before any CSS or JavaScript is executed. The | ||
files listed in this array can either a be CSS or JavaScript resource. | ||
```js | ||
pagelet.extend({ | ||
dependencies: [ | ||
'https://google.com/ga.js' | ||
] | ||
}).on(module); | ||
``` | ||
### Pagelet: id | ||
**read only** | ||
The unique id of a given pagelet instance. Please note that this is not a | ||
persistent id and will differ between every single initialised instance. | ||
### Pagelet: substream | ||
**read only** | ||
The pagelet can also be initialised through [Primus] so it can be used for | ||
real-time communication (and make things like [RPC](#pagelet-rpc) work). The | ||
communication is done over a [substream] which allows primus multiplex the | ||
connection between various of endpoints. | ||
## License | ||
MIT | ||
[Backbone]: http://backbonejs.com | ||
@@ -279,1 +376,3 @@ [BigPipe]: http://bigpipe.io | ||
[frag]: https://github.com/bigpipe/pagelet/blob/master/pagelet.fragment | ||
[Primus]: https://github.com/primus/primus | ||
[substream]: https://github.com/primus/substream |
26407
479
364
+ Addedextendible@0.1.1(transitive)
+ Addedfusing@0.2.3(transitive)
+ Addedpredefine@0.1.3(transitive)
- Removedextendable@0.0.6(transitive)
- Removedfusing@0.0.3(transitive)
- Removedpredefine@0.0.6(transitive)
Updatedfusing@~0.2.x