$ gnpm install bigpipe.js
BigPipe.js
is the client side component for the BigPipe framework. It
orchestrates the arrival of pagelet's on the page, loads all assets and setup
the real-time connections. It's the glue between the server and your own client
code. While this library is highly opinionated and developed against the
features that are provided in BigPipe we made sure that every piece of code is
re-usable by the community. We've extracted various of components out of this
library and released them as separate projects:
The code is released through npm, but it doesn't work in Node.js. We merely use it for dependency management.
npm install --save bigpipe.js
We assume that this code is loaded in an environment that has primus and it's substream plugin loaded.
BigPipe instance
Pagelet instance
The code introduced to the page as an BigPipe
global. The BigPipe
constructor accepts 2 arguments:
The following options are accepted:
20
id
of the Page that we're loading.In addition to the options listed above, all options that can be used to configure primus are also supported as this options object is directly passed to the Primus constructor. The only Primus option that is forced by us is the manual option. As we need to be in control of the opening of the real-time connection.
var bigpipe = new BigPipe(undefined, {
pagelets: 20,
id: 'ADFASDF0E-2FADAF-24'
});
When a new BigPipe instance has been created it will automatically check the
documentElement
or <html>
element for the presence of a no_js
class. When
this class is found it will be automatically removed as JavaScript is obviously
active and working as intended. In addition to that we also append the class
pagelets-loading
to the element.
The created bigpipe
instance is an EventEmitter3. Events that are prefixed
with <name>
indicates that the <name>
is the name of the Pagelet that emits
this message.
Event | Receives | Description |
---|---|---|
progress |
percentage, index, Pagelet | A new Pagelet has been loaded. |
loaded |
All Pagelets have been loaded. | |
create |
Pagelet | A new Pagelet has been created. |
remove |
Pagelet | A pagelet has been removed. |
<name>:error |
Pagelet, Error | We've failed to load the Pagelet. |
<name>:loaded |
Pagelet | All assets have been loaded. |
<name>:configure |
Pagelet, Data object | Pagelet has been configured. |
<name>:initialize |
Pagelet | Pagelet has been initialized. |
<name>:render |
Pagelet, html | Rendered the HTML. |
<name>:destroy |
Pagelet | Pagelet has been destroyed. |
public, returns BigPipe.
bigpipe.arrive('pagelet name', { data object });
When a new Pagelet has been pushed from the server to the client it should be
announced using this method. If we don't have a Pagelet for this name yet we
will automatically create a new Pagelet instance and assign it this name. After
the creation of this Pagelet we emit the progress
event as new Pagelet has
been loaded.
bigpipe.on('progress', function progress(percentage, index, pagelet) {
console.log('loaded %s of %s pagelets. We are %s% loaded', this.expected, index, percentage);
});
But also an create
event:
bigpipe.on('create', function create(pagelet) {
console.log('A new pagelet has been created', pagelet.name);
});
If this was the last Pagelet that needed to be loaded we will also emit the
loaded
event:
bigpipe.on('loaded', function loaded() {
console.log('All pagelets have been loaded.');
});
But just because they have been loaded it doesn't mean that they all have been rendered as well as assets still need to be loaded.
The options that are provided will be passed in the Pagelet's configuration method so it can start fetching and rendering the newly received Pagelet. There is one property that is required to be present on the data object:
Please note that in the case of BigPipe this method call is automatically added at the bottom of the server response. So you don't need to manually invoke this.
bigpipe.arrive("packages", {
"id": "G1M3RAKQK4V7K3XR-SB00M199BR8DUNMI-9EJZKHLWHE5C23XR-LHXF7DD4SHHQ6W29",
"css": ["/4200c15db55f69d6038332b69a9099b3d178242f.css"],
"js": ["/97bdbe337bf705ff46b4476ed8a5b65b551106dd.js"],
"rpc": ["autocomplete"],
"authorized": true,
"streaming": true,
"remove": false,
"processed": 1,
"data": {}
});
public, returns boolean.
bigpipe.has('pagelet name');
Check if a Pagelet has already been loaded/received on the page.
public, returns BigPipe
bigpipe.remove('pagelet name');
Removes a pagelet from our internal Pagelet object. The remove
event is
emitted before we actually destroy the pagelet that gets removed so you could do
some additional cleanup if needed. After the event is emitted we call the
Pagelet#destroy method and remove it from our internal
reference.
bigpipe.on('remove', function (pagelet) {
console.log('removed', pagelet);
});
bigpipe.remove('pagelet name');
public, returns BigPipe
bigpipe.broadcast(event, [args]);
Broadcast will emit the given event on every single added Pagelet instance.
bigpipe.broadcast('hello', 'world');
bigpipe.broadcast('foo bar', 'multiple', 1, 'args', { no: 'problem' });
Unlike the BigPipe
class you do not need to create instances of the Pagelet
your self. This is all orchestrated by the [BigPipe.arrive] method. The reason
for this is that it needs to have a reference to the BigPipe instance as well as
one to the created Primus connection so we can create a dedicated substream
for each pagelet.
var pagelet = new Pagelet(bigpipe);
The Pagelet instances are simply allocated and returned to a pool so they can be
re-used and improve garbage collection. The options it receives are applied
every time the Pagelet.configuration
is called which again is done in the
BigPipe.arrive method.
pagelet.configure('pagelet name', { received data/options });
The following options are accepted:
id
of the Pagelet that we're loading.When the pagelet is configured it:
data-pagelet=""
attribute on HTML elements..name
and data.id
as .id
.remove
as option is set. It will call Pagelet.destroy(true)
so it
removes the placeholder elements.<form>
submit listeners so we re-route those requests over our
real-time connection.rpc
array.configured
event.css
and js
files.loaded
Pagelet.parse
and
render it in the placeholders using Pagelet.render(html)
render
.initialize
event.Congratulations you've read through the whole configuration process of a pagelet. Hopefully this makes everything a bit more clear on how they work.
The created Pagelet
instance is an EventEmitter3. The following events are
emitted by the Pagelet:
Event | Receives | Description |
---|---|---|
error |
Error | We've failed to load the Pagelet. |
loaded |
All assets have been loaded. | |
configure |
Data object | Pagelet has been configured. |
initialize |
Pagelet has been initialized. | |
render |
html | Rendered the HTML. |
destroy |
Pagelet is about to be destroyed. |
public, String
The name of the Pagelet.
public, String
The unique id of the Pagelet.
public, Array
Array of placeholders HTML elements that had data-pagelet
set to the Pagelet's
name. When the pagelet is rendering all of these pagelets will have their HTML
updated.
public, returns Pagelet.
pagelet.destroy(boolean);
Destroy the created Pagelet. If true
as argument is given it will also remove
the placeholders the Pagelet was running in. Before we start with the
destruction process we emit an destroy
event. This allows you clean up the
pagelet if needed.
pagelet.on('destroy', function () {
console.log('Pagelet', this.name, 'has been destroyed');
});
After the event is emitted we:
remove
boolean is given, the placeholder is also removed.rpc
methods were added to the Pagelet, they are deleted.public, returns Object.
pagelet.submit(document.forms[0]);
Submit the contents of the given form to Pagelet on the server using the real-time connection. We extract the input/select/textarea/button elements from the form and transform it an object. If you have selected a button/input we will filter out elements with same name so it doesn't get overridden.
When invoking this method we return the created object which was sent to the server.
<form id="foo" action="/foo" method="POST">
<input name="foo" value="bar">
</form>
var data = pagelet.submit(document.getElementById('foo'));
console.log(data.foo) // "bar"
public, returns Pagelet.
pagelet.get();
Re-render the HTML which is retrieved from the server.
public, returns Pagelet.
pagelet.broadcast('eventname', [ optional arguments ]);
Broadcast an event to this Pagelet instance as well as the BigPipe instance
that created the Pagelet. Before emitting the event on the BigPipe instance it
prefixes the event with the name of the Pagelet and :
. If the name of your
Pagelet is foo
and you emit event bar
the BigPipe instance will emit
foo:bar
as event.
pagelet.broadcast('foo', 'bar');
public, returns Array.
pagelet.$('data-pagelet', 'foo');
Find elements in the DOM based on the attribute name and it's value. If
querySelectorAll
is not supported in the browser we will fall back to a full
DOM scan in order to get the correct elements. All items are added to an array.
If no matching elements are found the Array will be empty.
public, returns Boolean.
pagelet.render('<strong>bigpipe ftw</strong>');
Insert the given HTML in the placeholders. If there are elements in the
placeholder they will be removed first as it might be a good idea to display a
loading message while we are still rendering or loading the resource from the
server. Once all HTML has been added to the placeholders we emit the render
event.
pagelet.render('foo'); // true
pagelet.placeholders.length = 0;
pagelet.render('foo'); // false
private, returns String.
var prerendered = pagelet.parse();
Parse the pre-rendered HTML template from the comment node that got injected in
to our Page when the fragment was written. It's wrapped in a HTML comment so the
browser doesn't spend any time parsing the contents of it. It searches for the
comment node based on the name
property and searches for an element with an
data-pagelet-fragment
attribute.
private, returns Pagelet.
pagelet.listen();
This private method attaches an submit
listener to the placeholder so it
can intercept the POST/PUT/GET requests from a <form>
and re-route them over the
real-time connection. If a data-pagelet-async="false"
property is set on the
form it will simply append _pagelet=<name>
to the action as query string so the
server knows which pagelet has submitted this form.
When the Pagelet emits destroy
we will automatically remove the attached event
listener.
private, returns Pagelet.
pagelet.processor({ packet object });
This private method processes the incoming messages from our substream. It handles all the RPC calls, Event Emitting, HTML rendering and much more. There are many different types of packets that it can process. There a couple requirements in order for us to process the data.
type
property that indicates the type of package.pagelet.processor({ type: 'event', args: ['eventname', 'arg', 'arg' ]});
MIT
Copyright 2013 - present © cnpmjs.org | Home |