├── .gitignore
├── Gruntfile.js
├── REAME.md
├── barrier.js
├── bower_components
└── jquery
│ ├── .bower.json
│ ├── .gitignore
│ ├── README.md
│ ├── bower.json
│ ├── component.json
│ ├── composer.json
│ ├── jquery-migrate.js
│ ├── jquery-migrate.min.js
│ ├── jquery.js
│ ├── jquery.min.js
│ ├── jquery.min.map
│ └── package.json
├── component.json
├── content
├── 404.md
├── LICENCE.md
├── TODO.md
├── api.md
├── blog
│ ├── designing-a-event-bus-for-easy-testability.md
│ └── detectingCrossOrigin.md
├── discuss.md
├── download.md
├── examples.md
├── index.md
├── listing
├── parsers.md
├── postfix.md
├── useCases.md
└── why.md
├── index.js
├── make_pdfs
├── oboe.js-demo.iml
├── package.json
├── pdf
├── _why.pdf
├── api.pdf
└── examples.pdf
├── read-content.js
├── read-pages-list.js
├── sass
├── all.scss
├── cartogram.scss
├── content.scss
├── demo.scss
├── graph.scss
├── map.scss
├── oboe.scss
└── packetColors.scss
├── sourceList.js
├── statics
├── .gitIgnore
├── css
│ └── .gitIgnore
├── favicons
│ ├── avitar-twitter.png
│ ├── blurredVis.png
│ ├── blurredVis.xcf
│ ├── favicon-16.png
│ ├── favicon-32.png
│ ├── favicon.ico
│ ├── favicon.ico.xcf
│ └── favicon.svg
├── js
│ ├── demo
│ │ ├── cssHooks.js
│ │ ├── functional.js
│ │ ├── lists.js
│ │ ├── model
│ │ │ ├── AggregatingServer.js
│ │ │ ├── Barrier.js
│ │ │ ├── Cache.js
│ │ │ ├── Client.js
│ │ │ ├── Demo.js
│ │ │ ├── Message.js
│ │ │ ├── NarrativeItem.js
│ │ │ ├── OriginServer.js
│ │ │ ├── Packet.js
│ │ │ ├── PacketHolder.js
│ │ │ ├── Parser.js
│ │ │ ├── Relay.js
│ │ │ ├── ResponseGenerator.js
│ │ │ ├── Scheduler.js
│ │ │ ├── Thing.js
│ │ │ ├── Wire.js
│ │ │ ├── announce.js
│ │ │ ├── datasets.js
│ │ │ ├── direction.js
│ │ │ ├── multiplex.js
│ │ │ └── throttle.js
│ │ ├── oop.js
│ │ ├── pubSub.js
│ │ ├── scenarioBuilder.js
│ │ ├── scenarios.js
│ │ ├── singleEventPubSub.js
│ │ ├── view
│ │ │ ├── BarrierView.js
│ │ │ ├── ClientView.js
│ │ │ ├── DemoView.js
│ │ │ ├── MessageView.js
│ │ │ ├── NarrativeView.js
│ │ │ ├── PacketView.js
│ │ │ ├── RelayView.js
│ │ │ ├── ServerView.js
│ │ │ ├── ThingView.js
│ │ │ ├── WireView.js
│ │ │ ├── payloadAttributes.js
│ │ │ └── viewUtils.js
│ │ └── wire.js
│ ├── internalNav.js
│ ├── jquery.easing.js
│ ├── jquery.inview.js
│ ├── jquery.pause.js
│ ├── jquery.sticky.js
│ └── pollyfill.js
└── type
│ ├── cmunrm.eot
│ ├── cmunrm.svg
│ ├── cmunrm.woff
│ ├── cmunti.eot
│ ├── cmunti.svg
│ ├── cmunti.ttf
│ ├── cmunti.woff
│ ├── cmuntt.eot
│ ├── cmuntt.svg
│ ├── cmuntt.ttf
│ └── cmuntt.woff
├── svgSource
├── Cartogram—2012_Electoral_Vote.svg
├── cartogram-edit.svg
├── cartogram.svg
├── drawing.svg
├── logo-github.svg
├── logo-google-groups.svg
├── logo-twitter.svg
├── mapvis.png
├── mobileMenu.inkscape.svg
└── mobileMenu.svg
└── views
├── demoTemplate.handlebars
├── page.handlebars
└── raw.handlebars
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | components
3 | .idea
4 |
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = function (grunt) {
4 |
5 | // set up config
6 | grunt.initConfig({
7 | pkg: grunt.file.readJSON('package.json')
8 | ,
9 | watch:{
10 | sources:{
11 | files:['*.js', 'sass/*.scss'],
12 | tasks:['develop:server', 'build'],
13 | options: { nospawn: true }
14 | }
15 | }
16 | ,
17 | sass: {
18 | all:{
19 | files: {'statics/css/all.css':'sass/all.scss'}
20 | }
21 | }
22 | ,
23 | develop: {
24 | server: {
25 | file: 'index.js',
26 | args: '--env=dev'
27 | }
28 | }
29 |
30 | ,
31 | uglify: {
32 |
33 | clientSideJs:{
34 | options:{
35 | wrap:'enclose'
36 | },
37 |
38 | files:{
39 | 'statics/js-concat/all.js': require('./sourceList.js').map(function(name){
40 | return 'statics' + name;
41 | })
42 | }
43 | }
44 | }
45 | ,
46 | cssmin:{
47 | minifyCss:{
48 | files:{
49 | 'statics/css/all-min.css':['statics/css/all.css']
50 | }
51 | }
52 | }
53 |
54 | });
55 |
56 |
57 | // load all grunt tasks
58 | grunt.loadNpmTasks('grunt-contrib-watch');
59 | grunt.loadNpmTasks('grunt-develop');
60 | grunt.loadNpmTasks('grunt-sass');
61 | grunt.loadNpmTasks('grunt-contrib-uglify');
62 | grunt.loadNpmTasks('grunt-contrib-cssmin');
63 | grunt.loadNpmTasks('grunt-contrib-compress');
64 |
65 | // register a few tasks
66 | grunt.registerTask('build', ['sass:all', 'uglify:clientSideJs', 'cssmin:minifyCss']);
67 | grunt.registerTask('start-dev', ['develop:server', 'sass:all', 'watch:sources']);
68 | //grunt.registerTask('start-real', ???);
69 |
70 | };
--------------------------------------------------------------------------------
/REAME.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimhigson/oboe.js-website/0d768e740a77c8e52171d3e5ceef7dc0a0680921/REAME.md
--------------------------------------------------------------------------------
/barrier.js:
--------------------------------------------------------------------------------
1 | module.exports = function barrier(whenDone) {
2 |
3 | var requiredCallbacks = 0,
4 | doneCallbacks = 0,
5 | startTime = Date.now();
6 |
7 | var instance = {
8 | add:function(fn){
9 | requiredCallbacks++;
10 |
11 | return function(){
12 | fn.apply(this,arguments);
13 | doneCallbacks++;
14 |
15 | if( requiredCallbacks === doneCallbacks ) {
16 | instance.duration = Date.now() - startTime;
17 | whenDone();
18 | }
19 | }
20 | }
21 | };
22 |
23 | return instance;
24 | };
25 |
--------------------------------------------------------------------------------
/bower_components/jquery/.bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jquery",
3 | "version": "2.0.3",
4 | "description": "jQuery component",
5 | "keywords": [
6 | "jquery",
7 | "component"
8 | ],
9 | "main": "jquery.js",
10 | "license": "MIT",
11 | "homepage": "https://github.com/components/jquery",
12 | "_release": "2.0.3",
13 | "_resolution": {
14 | "type": "version",
15 | "tag": "2.0.3",
16 | "commit": "452a56b52b8f4a032256cdb8b6838f25f0bdb3d2"
17 | },
18 | "_source": "git://github.com/components/jquery.git",
19 | "_target": "~2.0.3",
20 | "_originalSource": "jquery"
21 | }
--------------------------------------------------------------------------------
/bower_components/jquery/.gitignore:
--------------------------------------------------------------------------------
1 | build
2 |
--------------------------------------------------------------------------------
/bower_components/jquery/README.md:
--------------------------------------------------------------------------------
1 | jQuery Component
2 | ================
3 |
4 | Shim repository for the [jQuery](http://jquery.com).
5 |
6 | Package Managers
7 | ----------------
8 |
9 | * [Bower](http://bower.io/): `jquery`
10 | * [Component](https://github.com/component/component): `components/jquery`
11 | * [Composer](http://packagist.org/packages/components/jquery): `components/jquery`
12 |
--------------------------------------------------------------------------------
/bower_components/jquery/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jquery",
3 | "version": "2.0.3",
4 | "description": "jQuery component",
5 | "keywords": [
6 | "jquery",
7 | "component"
8 | ],
9 | "main": "jquery.js",
10 | "license": "MIT"
11 | }
12 |
--------------------------------------------------------------------------------
/bower_components/jquery/component.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jquery",
3 | "repo": "components/jquery",
4 | "version": "2.0.3",
5 | "description": "jQuery component",
6 | "keywords": [
7 | "jquery",
8 | "component"
9 | ],
10 | "main": "jquery.js",
11 | "scripts": [
12 | "jquery.js"
13 | ],
14 | "license": "MIT"
15 | }
16 |
--------------------------------------------------------------------------------
/bower_components/jquery/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "components/jquery",
3 | "description": "jQuery JavaScript Library",
4 | "type": "component",
5 | "homepage": "http://jquery.com",
6 | "license": "MIT",
7 | "support": {
8 | "irc": "irc://irc.freenode.org/jquery",
9 | "issues": "http://bugs.jquery.com",
10 | "forum": "http://forum.jquery.com",
11 | "wiki": "http://docs.jquery.com/",
12 | "source": "https://github.com/jquery/jquery"
13 | },
14 | "authors": [
15 | {
16 | "name": "John Resig",
17 | "email": "jeresig@gmail.com"
18 | }
19 | ],
20 | "require": {
21 | "robloach/component-installer": "*"
22 | },
23 | "extra": {
24 | "component": {
25 | "scripts": [
26 | "jquery.js"
27 | ],
28 | "files": [
29 | "jquery.min.js",
30 | "jquery-migrate.js",
31 | "jquery-migrate.min.js"
32 | ]
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/bower_components/jquery/jquery-migrate.min.js:
--------------------------------------------------------------------------------
1 | /*! jQuery Migrate v1.1.1 | (c) 2005, 2013 jQuery Foundation, Inc. and other contributors | jquery.org/license */
2 | jQuery.migrateMute===void 0&&(jQuery.migrateMute=!0),function(e,t,n){function r(n){o[n]||(o[n]=!0,e.migrateWarnings.push(n),t.console&&console.warn&&!e.migrateMute&&(console.warn("JQMIGRATE: "+n),e.migrateTrace&&console.trace&&console.trace()))}function a(t,a,o,i){if(Object.defineProperty)try{return Object.defineProperty(t,a,{configurable:!0,enumerable:!0,get:function(){return r(i),o},set:function(e){r(i),o=e}}),n}catch(s){}e._definePropertyBroken=!0,t[a]=o}var o={};e.migrateWarnings=[],!e.migrateMute&&t.console&&console.log&&console.log("JQMIGRATE: Logging is active"),e.migrateTrace===n&&(e.migrateTrace=!0),e.migrateReset=function(){o={},e.migrateWarnings.length=0},"BackCompat"===document.compatMode&&r("jQuery is not compatible with Quirks Mode");var i=e("",{size:1}).attr("size")&&e.attrFn,s=e.attr,u=e.attrHooks.value&&e.attrHooks.value.get||function(){return null},c=e.attrHooks.value&&e.attrHooks.value.set||function(){return n},l=/^(?:input|button)$/i,d=/^[238]$/,p=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,f=/^(?:checked|selected)$/i;a(e,"attrFn",i||{},"jQuery.attrFn is deprecated"),e.attr=function(t,a,o,u){var c=a.toLowerCase(),g=t&&t.nodeType;return u&&(4>s.length&&r("jQuery.fn.attr( props, pass ) is deprecated"),t&&!d.test(g)&&(i?a in i:e.isFunction(e.fn[a])))?e(t)[a](o):("type"===a&&o!==n&&l.test(t.nodeName)&&t.parentNode&&r("Can't change the 'type' of an input or button in IE 6/7/8"),!e.attrHooks[c]&&p.test(c)&&(e.attrHooks[c]={get:function(t,r){var a,o=e.prop(t,r);return o===!0||"boolean"!=typeof o&&(a=t.getAttributeNode(r))&&a.nodeValue!==!1?r.toLowerCase():n},set:function(t,n,r){var a;return n===!1?e.removeAttr(t,r):(a=e.propFix[r]||r,a in t&&(t[a]=!0),t.setAttribute(r,r.toLowerCase())),r}},f.test(c)&&r("jQuery.fn.attr('"+c+"') may use property instead of attribute")),s.call(e,t,a,o))},e.attrHooks.value={get:function(e,t){var n=(e.nodeName||"").toLowerCase();return"button"===n?u.apply(this,arguments):("input"!==n&&"option"!==n&&r("jQuery.fn.attr('value') no longer gets properties"),t in e?e.value:null)},set:function(e,t){var a=(e.nodeName||"").toLowerCase();return"button"===a?c.apply(this,arguments):("input"!==a&&"option"!==a&&r("jQuery.fn.attr('value', val) no longer sets properties"),e.value=t,n)}};var g,h,v=e.fn.init,m=e.parseJSON,y=/^(?:[^<]*(<[\w\W]+>)[^>]*|#([\w\-]*))$/;e.fn.init=function(t,n,a){var o;return t&&"string"==typeof t&&!e.isPlainObject(n)&&(o=y.exec(t))&&o[1]&&("<"!==t.charAt(0)&&r("$(html) HTML strings must start with '<' character"),n&&n.context&&(n=n.context),e.parseHTML)?v.call(this,e.parseHTML(e.trim(t),n,!0),n,a):v.apply(this,arguments)},e.fn.init.prototype=e.fn,e.parseJSON=function(e){return e||null===e?m.apply(this,arguments):(r("jQuery.parseJSON requires a valid JSON string"),null)},e.uaMatch=function(e){e=e.toLowerCase();var t=/(chrome)[ \/]([\w.]+)/.exec(e)||/(webkit)[ \/]([\w.]+)/.exec(e)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(e)||/(msie) ([\w.]+)/.exec(e)||0>e.indexOf("compatible")&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(e)||[];return{browser:t[1]||"",version:t[2]||"0"}},e.browser||(g=e.uaMatch(navigator.userAgent),h={},g.browser&&(h[g.browser]=!0,h.version=g.version),h.chrome?h.webkit=!0:h.webkit&&(h.safari=!0),e.browser=h),a(e,"browser",e.browser,"jQuery.browser is deprecated"),e.sub=function(){function t(e,n){return new t.fn.init(e,n)}e.extend(!0,t,this),t.superclass=this,t.fn=t.prototype=this(),t.fn.constructor=t,t.sub=this.sub,t.fn.init=function(r,a){return a&&a instanceof e&&!(a instanceof t)&&(a=t(a)),e.fn.init.call(this,r,a,n)},t.fn.init.prototype=t.fn;var n=t(document);return r("jQuery.sub() is deprecated"),t},e.ajaxSetup({converters:{"text json":e.parseJSON}});var b=e.fn.data;e.fn.data=function(t){var a,o,i=this[0];return!i||"events"!==t||1!==arguments.length||(a=e.data(i,t),o=e._data(i,t),a!==n&&a!==o||o===n)?b.apply(this,arguments):(r("Use of jQuery.fn.data('events') is deprecated"),o)};var j=/\/(java|ecma)script/i,w=e.fn.andSelf||e.fn.addBack;e.fn.andSelf=function(){return r("jQuery.fn.andSelf() replaced by jQuery.fn.addBack()"),w.apply(this,arguments)},e.clean||(e.clean=function(t,a,o,i){a=a||document,a=!a.nodeType&&a[0]||a,a=a.ownerDocument||a,r("jQuery.clean() is deprecated");var s,u,c,l,d=[];if(e.merge(d,e.buildFragment(t,a).childNodes),o)for(c=function(e){return!e.type||j.test(e.type)?i?i.push(e.parentNode?e.parentNode.removeChild(e):e):o.appendChild(e):n},s=0;null!=(u=d[s]);s++)e.nodeName(u,"script")&&c(u)||(o.appendChild(u),u.getElementsByTagName!==n&&(l=e.grep(e.merge([],u.getElementsByTagName("script")),c),d.splice.apply(d,[s+1,0].concat(l)),s+=l.length));return d});var Q=e.event.add,x=e.event.remove,k=e.event.trigger,N=e.fn.toggle,C=e.fn.live,S=e.fn.die,T="ajaxStart|ajaxStop|ajaxSend|ajaxComplete|ajaxError|ajaxSuccess",M=RegExp("\\b(?:"+T+")\\b"),H=/(?:^|\s)hover(\.\S+|)\b/,A=function(t){return"string"!=typeof t||e.event.special.hover?t:(H.test(t)&&r("'hover' pseudo-event is deprecated, use 'mouseenter mouseleave'"),t&&t.replace(H,"mouseenter$1 mouseleave$1"))};e.event.props&&"attrChange"!==e.event.props[0]&&e.event.props.unshift("attrChange","attrName","relatedNode","srcElement"),e.event.dispatch&&a(e.event,"handle",e.event.dispatch,"jQuery.event.handle is undocumented and deprecated"),e.event.add=function(e,t,n,a,o){e!==document&&M.test(t)&&r("AJAX events should be attached to document: "+t),Q.call(this,e,A(t||""),n,a,o)},e.event.remove=function(e,t,n,r,a){x.call(this,e,A(t)||"",n,r,a)},e.fn.error=function(){var e=Array.prototype.slice.call(arguments,0);return r("jQuery.fn.error() is deprecated"),e.splice(0,0,"error"),arguments.length?this.bind.apply(this,e):(this.triggerHandler.apply(this,e),this)},e.fn.toggle=function(t,n){if(!e.isFunction(t)||!e.isFunction(n))return N.apply(this,arguments);r("jQuery.fn.toggle(handler, handler...) is deprecated");var a=arguments,o=t.guid||e.guid++,i=0,s=function(n){var r=(e._data(this,"lastToggle"+t.guid)||0)%i;return e._data(this,"lastToggle"+t.guid,r+1),n.preventDefault(),a[r].apply(this,arguments)||!1};for(s.guid=o;a.length>i;)a[i++].guid=o;return this.click(s)},e.fn.live=function(t,n,a){return r("jQuery.fn.live() is deprecated"),C?C.apply(this,arguments):(e(this.context).on(t,this.selector,n,a),this)},e.fn.die=function(t,n){return r("jQuery.fn.die() is deprecated"),S?S.apply(this,arguments):(e(this.context).off(t,this.selector||"**",n),this)},e.event.trigger=function(e,t,n,a){return n||M.test(e)||r("Global events are undocumented and deprecated"),k.call(this,e,t,n||document,a)},e.each(T.split("|"),function(t,n){e.event.special[n]={setup:function(){var t=this;return t!==document&&(e.event.add(document,n+"."+e.guid,function(){e.event.trigger(n,null,t,!0)}),e._data(this,n,e.guid++)),!1},teardown:function(){return this!==document&&e.event.remove(document,n+"."+e._data(this,n)),!1}}})}(jQuery,window);
3 | //@ sourceMappingURL=dist/jquery-migrate.min.map
--------------------------------------------------------------------------------
/bower_components/jquery/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "components-jquery",
3 | "version": "2.0.3",
4 | "description": "jQuery component",
5 | "keywords": ["jquery"],
6 | "main": "./jquery.js"
7 | }
8 |
--------------------------------------------------------------------------------
/component.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "oboe.js-demo",
3 | "version": "0.0.0",
4 | "main": "index.js",
5 | "dependencies": {
6 | "jquery": "~2.0.3"
7 | },
8 | "ignore": [
9 | "**/.*",
10 | "node_modules",
11 | "components"
12 | ]
13 | }
--------------------------------------------------------------------------------
/content/404.md:
--------------------------------------------------------------------------------
1 | #404
2 |
3 | Oh dear
4 | -------
5 |
6 | We don't have that page.
7 |
--------------------------------------------------------------------------------
/content/LICENCE.md:
--------------------------------------------------------------------------------
1 |
2 | Licence
3 | =======
4 |
5 | Using Oboe in your projects
6 | --------------------------
7 |
8 | Oboe.js is licenced under the [2-clause BSD licence](http://en.wikipedia.org/wiki/BSD_licenses#2-clause_license_.28.22Simplified_BSD_License.22_or_.22FreeBSD_License.22.29).
9 | This is one of the more liberal FLOSS licences and shouldn't impair integration with commercial products.
10 |
11 | BSD 2-Clause License
12 | --------------------
13 |
14 | Copyright © 2013, Jim Higson
15 | All rights reserved.
16 |
17 | Redistribution and use in source and binary forms, with or without
18 | modification, are permitted provided that the following conditions are met:
19 |
20 | 1. Redistributions of source code must retain the above copyright notice, this
21 | list of conditions and the following disclaimer.
22 | 2. Redistributions in binary form must reproduce the above copyright notice,
23 | this list of conditions and the following disclaimer in the documentation
24 | and/or other materials provided with the distribution.
25 |
26 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
27 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
28 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
29 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
30 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
31 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
32 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
33 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
34 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
35 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36 |
37 | The views and conclusions contained in the software and documentation are those
38 | of the authors and should not be interpreted as representing official policies,
39 | either expressed or implied, of the FreeBSD Project.
40 |
--------------------------------------------------------------------------------
/content/TODO.md:
--------------------------------------------------------------------------------
1 | TODO
2 | ====
3 |
4 | Promote:
5 | --------
6 |
7 | * Write blog post for [college site](http://kelloggoxford.wordpress.com/) - ~600 words:
8 | * One of the more popular subreddits is [/r/programming](http://reddit.com/r/programming/)
9 | * We should use streaming more
10 | * Write article for JS Playground (Jack F's site)
11 | * Send email, go ahead and fork the site
12 | * Forked: https://github.com/jimhigson/javascriptplayground.com
13 | * SitePoint article
14 | * Propose
15 | * Pitch
16 | - Join json.org mailing list
17 | - Post in json.org
18 | - json.org post pending moderation
19 | - *Now at: https://groups.yahoo.com/neo/groups/json/conversations/topics/1982*
20 | * Put on GSON forums
21 | * GSON post pending moderation
22 | - *Now at: https://groups.google.com/forum/#!topic/google-gson/V5x4Rh_wJzY*
23 | * Put actual URL in package.json for oboe.js repo
24 | * Not pushed to NPM yet
25 | * Did NPM page update? https://npmjs.org/package/oboe yes!
26 | * Update link on microjs
27 | * Pull request made - not merged yet https://github.com/madrobby/microjs.com/pull/562
28 | * Put on HN
29 | * *Kinda fizzled this time compared to last time*
30 |
31 | Programming (Oboe):
32 | ------------
33 |
34 | * Split out barrier
35 | * Try Clarinet without key in object event (is it faster or smaller?)
36 | * Events stored in arrays (is it faster?)
37 | * `.pipe()` style in Node
38 | * http://nodejs.org/api/stream.html
39 | * 'last of array' problem
40 | * Investigate streaming on IE8/9 http://msdn.microsoft.com/en-us/library/ie/cc288060(v=vs.85).aspx
41 | * *probably too buggy/incomplete*
42 |
43 | Website
44 | -------
45 |
46 | * Star, tweet etc:
47 | * like here: http://fabricjs.com/docs/
48 | * or here: http://cutjs.org/
49 | * or here: http://lodash.com/
50 | * Such modesty. Take note?: [Clarinet.js](https://github.com/dscape/clarinet)
51 | * Automate getting latest version number from Github
52 | * Might need to cache result for some time
53 | * Add logos to footer
54 | * Travis
55 | * Nodejitsu
56 | * Nodejitsu 404s when updating. [Try out Heroku](https://devcenter.heroku.com/articles/getting-started-with-nodejs).
57 | * Current style of menu is at limit of number of articles it can support
58 | * research other ways of doing it
59 | * Tab switch bug
60 | * Better server graphic
61 | * Use cases
62 | * Depends on new menu?
63 | * [3 client vis](why#step-outside-the-trade-off-between-big-and-small-json): graph data so far
64 | * *Could be static SVG below the vis*
65 | * Autoplay vises show on *reveal*, not on *load*
66 | * Animations on homepage cycle through two or more forever (all with Oboe)
67 | * Visualisation can turn off narrative
68 | * Download link on homepage only
69 | * Move heading off sidebar
70 | * Discuss page: header looks weird
71 | * *Tweaked a bit. Ok for now.*
72 |
73 |
74 | Writing
75 | -------
76 |
77 | * Merge [why](why) into [homepage](/)?
78 | * Browser support is a bit buried at bottom of [API](/api)
79 | * *Putting on own page might need new menu structure*
80 | * Patterns should probably have own page
81 | * *Putting on own page might need new menu structure*
82 | * Licence somewhere
83 | * Not having to decide between big and small
84 | * Vis: 3 servers, 3 clients. Big message, small message and progressive message all in one!
85 |
86 | * Compare with other JSON streaming library since the message does not have to be specially written for it
87 | * *mentioned in [why](/why)*
88 |
89 | Life etc
90 | --------
91 |
92 | * Update CV
93 | * Put updates on LinkedIn
94 | * Open bank acnt for company
95 | * Invoice SE
96 | * *Company number 8887289: JIM HIGSON SOFTWARE CONSULTANCY LTD*
97 | * Incorporate co
98 |
99 | What next?
100 | ----------
101 |
102 | * Time tracking app thing?
103 | * Work full time perm?
104 | * Contracting
105 | * Move to the countryside and keep chickens?
106 |
107 |
--------------------------------------------------------------------------------
/content/blog/designing-a-event-bus-for-easy-testability.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimhigson/oboe.js-website/0d768e740a77c8e52171d3e5ceef7dc0a0680921/content/blog/designing-a-event-bus-for-easy-testability.md
--------------------------------------------------------------------------------
/content/blog/detectingCrossOrigin.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimhigson/oboe.js-website/0d768e740a77c8e52171d3e5ceef7dc0a0680921/content/blog/detectingCrossOrigin.md
--------------------------------------------------------------------------------
/content/discuss.md:
--------------------------------------------------------------------------------
1 | Discuss
2 | =======
3 |
4 | There is a [Google Group](https://groups.google.com/forum/#!forum/oboejs) for general project discussion.
5 |
6 | Please report any bugs on the [Github issues
7 | page](https://github.com/jimhigson/oboe.js/issues?state=open).
8 |
9 | You might want to follow [@OboeJS on
10 | Twitter](https://twitter.com/OboeJs).
11 |
12 | The author of this project is currently available to hire, contracts and permanent roles considered. For enquiries or just
13 | to say hi please [drop me an email](mailto:jim.higson@gmail.com).
14 |
--------------------------------------------------------------------------------
/content/download.md:
--------------------------------------------------------------------------------
1 | Download
2 | ========
3 |
4 | Versions
5 | --------
6 |
7 | The latest stable version is *{{latestTag}}*. Older versions are also [available via Github]({{repo}}/releases).
8 |
9 | Node.js and Browserify
10 | -------
11 |
12 | Install [Oboe from NPM](http://www.npmjs.org/package/oboe):
13 |
14 | ``` bash
15 | $ npm install oboe
16 | ```
17 |
18 | Add `--save` if you want to keep Oboe as a dependency in your package.json file:
19 |
20 | ``` bash
21 | $ npm install oboe --save
22 | ```
23 |
24 | Once installed load as usual:
25 |
26 | ``` javascript
27 | var oboe = require('oboe');
28 | ```
29 |
30 | Downloading manually for the Browser
31 | -----------------------------------
32 |
33 | Save one of these files:
34 |
35 | * [oboe-browser.js]({{releasedJs}}/oboe-browser.js) for development
36 | * [oboe-browser.min.js]({{releasedJs}}/oboe-browser.min.js) - minified for production. The size after gzip is 4.9k.
37 |
38 | Using Bower package manager
39 | -----------
40 |
41 | You can fetch using [Bower](http://bower.io/) like this:
42 |
43 | ``` bash
44 | $ bower install oboe
45 | ```
46 |
47 | Using Jam package manager
48 | ---------
49 |
50 | Oboe.js is also [available](http://jamjs.org/packages/#/details/oboe) through [Jam](http://jamjs.org/):
51 |
52 | ``` bash
53 | $ jam install oboe
54 | ```
55 |
56 | Loading using AMD
57 | -----------------
58 |
59 | If there is no AMD present, once the Oboe Javascript is loaded you can start
60 | using the global `oboe` object. However, when AMD is detected Oboe `defines` itself instead
61 | of adding itself as global variable.
62 |
63 | When AMD is used Oboe can be accessed asynchronously using `require`:
64 |
65 | ``` javascript
66 | require( ['oboe'], function( oboe ) {
67 |
68 | });
69 | ```
70 |
71 | If you know Oboe has already been loaded you can also access it synchronously although this
72 | is usually not the best way:
73 |
74 | ``` javascript
75 | var oboe = require('oboe');
76 | ```
77 |
78 | Configuration for Require.js
79 | ------------------------
80 |
81 | When using with Require.js some config is needed so Require knows to load a file
82 | named `oboe-browser.js` for the `oboe` module. Alternatively, you could rename
83 | `oboe-browser.js` to `oboe.js`.
84 |
85 | ``` javascript
86 | require.config({
87 | paths: {
88 | oboe: 'oboe-browser'
89 | }
90 | });
91 | ```
92 |
93 | This is similar to the [config required to use jQuery with Require](http://requirejs.org/docs/jquery.html).
94 |
95 | Polyfills
96 | ---------
97 |
98 | If you need Oboe to work with older versions of Internet Explorer polyfils such as
99 | [ES5-shim](http://github.com/es-shims/es5-shim) are required to bring the environment
100 | up to ECMAScript 5.
101 |
--------------------------------------------------------------------------------
/content/index.md:
--------------------------------------------------------------------------------
1 | Streaming JSON loading for Node and browsers
2 | ============================================
3 |
4 | Oboe.js is an [open source](LICENCE) Javascript library
5 | for loading JSON using streaming, combining the convenience of DOM with
6 | the speed and fluidity of SAX.
7 |
8 | It can parse any JSON as a stream, is small enough to be a [micro-library](http://microjs.com/#),
9 | doesn't have dependencies, and doesn't care which other libraries you need it to speak to.
10 |
11 | We can load trees [larger than the available memory](examples#loading-json-trees-larger-than-the-available-ram).
12 | Or we can [instantiate a classical OOP models from JSON](examples#demarshalling-json-to-an-oop-model),
13 | or [completely transform your JSON](examples#transforming-json-while-it-is-streaming) while it is being read.
14 |
15 | {{demo "aggregated-progressive" "autoplay"}}
16 |
17 | What next?
18 | ----------
19 |
20 | - Visualise [faster web applications through streaming](why)
21 | - Browse [code examples](examples)
22 | - Learn the Oboe.js [API](api)
23 | - [Download](download) the library
24 | - [Discuss](discuss) Oboe.js
25 | - Compare [Oboe.js vs. SAX vs. DOM](parsers)
26 | - Visit the [project on Github](http://github.com/jimhigson/oboe.js)
27 | - If you're in for the long haul,
28 | [my MSc dissertation](https://github.com/jimhigson/oboe.js-dissertation/blob/master/main/main.pdf?raw=true)
29 | was written on this subject (PDF, 64 pages plus appendices)
30 |
31 | Other tools
32 | -----------
33 |
34 | - For writing JSON streams from a Java server to an Oboe.js front-end try
35 | [GSON](https://code.google.com/p/google-gson/)
36 | - If you need an even more lightweight JSON stream library and don't
37 | mind writing [rather more code](parsers#code-comparison-sax), try
38 | [Clarinet](http://github.com/dscape/clarinet) (In fact, Oboe is
39 | built on Clarinet)
40 | - For golang programmers there is an example of different [realtime web technologies](https://github.com/SimonWaldherr/GoRealtimeWeb), which uses Oboe.js
41 |
--------------------------------------------------------------------------------
/content/listing:
--------------------------------------------------------------------------------
1 | why.md
2 | examples.md
3 | api.md
4 | download.md
5 | discuss.md
6 |
--------------------------------------------------------------------------------
/content/parsers.md:
--------------------------------------------------------------------------------
1 | Oboe.js vs SAX vs DOM
2 | =====================
3 |
4 | Oboe.js aims to be fluid like a SAX parser but with the convenience of a DOM parser.
5 |
6 | Choosing a parser
7 | -----------------
8 |
9 | DOM pros:
10 |
11 | * It is very simple. Everybody knows how to use it.
12 | * The parsing is fast and native using `JSON.parse`
13 |
14 | DOM cons:
15 |
16 | * You can't do anything until you have the whole document.
17 | * If the connection drops while transferring you lose everything.
18 | * Perpetual documents are not possible.
19 |
20 | SAX pros:
21 |
22 | * It has lean memory requirements. It can handle documents larger than the available RAM.
23 |
24 | SAX cons:
25 |
26 | * The API is very low level. Simple tasks need quite a lot of programming.
27 |
28 | Oboe.js pros:
29 |
30 | * Finding nodes is pattern-based. Unlike SAX, you don't have to maintain state on what you have already seen
31 | in order to drill down into a JSON document.
32 | * Oboe's parser provides actual JS objects which can then be used much as you would with a DOM parser. In
33 | comparison, with SAX you must programmatically infer objects from many callbacks.
34 | * Can be used to search the JSON while streaming is flowing.
35 |
36 | Oboe.js cons:
37 |
38 | * Since it builds up the parsed result as actual JSON objects parsing a document takes about the same memory as a DOM parser.
39 |
40 | Code comparison: SAX
41 | --------------------
42 |
43 | We have JSON containing an array of people objects and we wish to extract the
44 | name of the first person. Using SAX this is quite involved:
45 |
46 | ``` js
47 | function extractNameOfFirstPerson(){
48 |
49 | var parser = clarinet.parser(),
50 |
51 | // With a SAX parser it is the developer's responsibility
52 | // to track where in the document the cursor currently is.
53 | // Several variables are required to maintain this state.
54 | inPeopleArray = false,
55 | inPersonObject = false,
56 | inNameAttribute = false,
57 | found = false;
58 |
59 | parser.onopenarray = function(){
60 | // For brevity we'll cheat by assuming there is only one
61 | // array in the document. In practice this would be overly
62 | // brittle.
63 | inPeopleArray = true;
64 | };
65 |
66 | parser.onclosearray = function(){
67 | inPeopleArray = false;
68 | };
69 |
70 | parser.onopenobject = function(){
71 | inPersonObject = inPeopleArray;
72 | };
73 |
74 | parser.oncloseobject = function(){
75 | inPersonObject = false;
76 | };
77 |
78 | parser.onkey = function(key){
79 | inNameAttribute = (inPeopleObject && key == 'name');
80 | };
81 |
82 | parser.onvalue = function(value){
83 | if( !found && inNameAttribute ) {
84 | // finally!
85 | console.log('the name is', value);
86 | found = true;
87 | }
88 | };
89 |
90 | // return the parser to be hooked up to a stream
91 | return parser;
92 | }
93 | ```
94 |
95 | Code comparison: Oboe.js
96 | ------------------------
97 |
98 | Oboe makes extracting the name very much easier, but reacts equally quickly
99 | to the streamed input as the SAX example above:
100 |
101 | ``` js
102 | oboe('people.json')
103 | .node('[0].name', function(name){
104 | console.log('the name is', name);
105 | });
106 | ```
107 |
108 |
109 |
110 |
111 |
112 |
113 |
--------------------------------------------------------------------------------
/content/postfix.md:
--------------------------------------------------------------------------------
1 | {{^home}}
2 | Improve this page
3 | -----------------
4 |
5 | Is this page clear enough? Typo-free? Could you improve it?
6 |
7 | Please fork the [Oboe.js
8 | website repo](https://github.com/jimhigson/oboe.js-website) and make a pull
9 | request. The markdown source is
10 | [here](https://github.com/jimhigson/oboe.js-website/blob/master/content/{{page}}.md).
11 | {{/home}}
12 |
--------------------------------------------------------------------------------
/content/useCases.md:
--------------------------------------------------------------------------------
1 | # Use Cases
2 |
3 | Oboe.js isn't specific to my application domain, or even to solving the big-small download compromise. Here are some
4 | more use cases that I can think of:
5 |
6 | **Sarah** is sitting on a train using her mobile phone to check her email. The phone has almost finished downloading her
7 | inbox when her train goes under a tunnel. Luckily, her webmail developers used **Oboe.js** so instead of the request failing
8 | she can still read most of her emails. When the connection comes back again later the webapp is smart enough to just
9 | re-request the part that failed.
10 |
11 | **Arnold** is using a programmable stock screener.
12 | The query is many-dimensional so screening all possible companies sometimes takes a long time. To speed things up, **Oboe.js**,
13 | means each result can be streamed and displayed as soon as it is found. Later, he revisits the same query page.
14 | Since Oboe isn't true streaming it plays nice with the browser cache so now he see the same results instantly from cache.
15 |
16 | **Janet** is working on a single-page modular webapp. When the page changes she wants to ajax in a single, aggregated json
17 | for all of her modules.
18 | Unfortunately, one of the services being aggregated is slower than the others and under traditional
19 | ajax she is forced to wait for the slowest module to load before she can show any of them.
20 | **Oboe.js** is better, the fast modules load quickly and the slow modules load later.
21 | Her users are happy because they can navigate page-to-page more fluidly and not all of them cared about the slow module anyway.
22 |
23 | **John** is developing internally on a fast network so he doesn't really care about progressive loading. Oboe.js provides
24 | a neat way to route different parts of a json response to different parts of his application. One less bit to write.
25 |
--------------------------------------------------------------------------------
/content/why.md:
--------------------------------------------------------------------------------
1 | Why Oboe.js?
2 | ============
3 |
4 | This page was written to show how streaming can speed up applications. The examples
5 | illustrated show web interfaces using AJAX to pull in new data but the same techniques
6 | would apply equally well anywhere that REST is used.
7 |
8 | Stream any JSON REST resource
9 | -----------------------------
10 |
11 | Let's start by examining the standard pattern found on most AJAX-powered sites.
12 | We have a client-side web application and a service that provides it with data.
13 | The page isn't updated until the response completes:
14 |
15 | {{demo "fast-ajax-discrete"}}
16 |
17 | Oboe is different from most streaming JSON libraries since the JSON
18 | does not have to follow a special format.
19 | On a good connection there isn't a lot of time to save but
20 | it is likely that [progressive display in itself will improve the *perception* of
21 | performance](http://www.sigchi.org/chi95/proceedings/shortppr/egd_bdy.htm):
22 |
23 | {{demo "fast-ajax-progressive"}}
24 |
25 | With a slower connection or larger data the improvement
26 | is more significant.
27 |
28 | Transmit fluently over mobile
29 | -----------------------------
30 |
31 | Mobile networks today are high-bandwidth but can also be
32 | high-latency and come with inconsistent packet delivery times.
33 | This is why buffered content like streaming HD video plays
34 | fluidly but web surfing still feels laggy. The visualisation
35 | below approximates a medium-sized download on a mobile network:
36 |
37 | {{demo "mobile-discrete"}}
38 |
39 | Oboe.js makes it easy for the programmer to use chunks from the response as soon
40 | as they arrive. This helps webapps to feel faster when running over mobile networks:
41 |
42 |
43 | {{demo "mobile-progressive"}}
44 |
45 | Handle dropped connections with grace
46 | -------------------------------------
47 |
48 | Oboe.js provides improved tolerance if a connection is lost before
49 | the response completes.
50 | Most AJAX frameworks equate a dropped connection with total failure and discard
51 | the partially transferred data, even if 90% was received correctly.
52 |
53 | We can handle this situation better by using the partially transferred data
54 | instead of throwing it away. Given an incremental approach to parsing, using partial data
55 | follows naturally without requiring any extra programming.
56 |
57 | In the next visualisation we have a mobile connection which fails when the
58 | user enters a building:
59 |
60 | {{demo "mobile-fail-discrete"}}
61 |
62 | Because Oboe.js views the HTTP response as a
63 | series of small, useful parts, when a connection is lost it is simply
64 | the case that some parts were successful and were used already,
65 | while others did not arrive. Fault tolerance comes for free.
66 |
67 | In the example below the client is smart enough so that when the network
68 | comes back it only requests the data that was missed on the first attempt:
69 |
70 | {{demo "mobile-fail-progressive"}}
71 |
72 | Streamline resource aggregation
73 | -------------------------------
74 |
75 | It is a common pattern for web clients to retrieve data through a middle tier.
76 | Nodes in the middle tier connect to multiple back-end services and
77 | create a single, aggregated response by combining their data.
78 |
79 | The visualisation below shows an example without streaming.
80 | Origin 1 is slower
81 | than
82 | Origin 2
83 | but the
84 | aggregator is forced to respond at the speed of
85 | the slowest service:
86 |
87 | {{demo "aggregated-discrete"}}
88 |
89 | We can speed this scenario up by using Oboe.js to load data in
90 | the aggregator and
91 | the client.
92 | The aggregator dispatches the data as soon as it has it and
93 | the client displays the data as soon as it arrives.
94 |
95 | {{demo "aggregated-progressive"}}
96 |
97 | Despite being a stream,
98 | the aggregator's
99 | output is 100% valid JSON so it remains compatible
100 | with standard AJAX tools. A client using a streaming parser like Oboe.js
101 | consumes the resource as a stream but a more traditional client has no
102 | problem reading it as a static resource.
103 |
104 | In a Java stack this could also be implemented by using
105 | [GSON](http://code.google.com/p/google-gson/) in the middle tier.
106 |
107 | Step outside the trade-off between big and small JSON
108 | ---------------------------------------------
109 |
110 | There is often a tradeoff using traditional REST clients:
111 |
112 | * Request too much data and the application feels unresponsive because each request
113 | takes some time to download.
114 | * Request less and, while the first data is handled earlier, more requests are needed,
115 | meaning a greater http overhead and more time overall.
116 |
117 | Oboe.js breaks out of the tradeoff by beating both.
118 | Large resources load just as responsively as smaller ones so the developer can request more
119 | and let it stream.
120 |
121 | In the visualisation below three rival clients
122 | connect to the same server. The
123 | top client requests a lot of data,
124 | the middle a little data twice, and
125 | the bottom a lot using Oboe.js:
126 |
127 | {{demo "big-small"}}
128 |
129 | Send historic and live data using the same transport
130 | -------------------------------------------------
131 |
132 | It is a common pattern in web interfaces to fetch existing data
133 | and then keep the page updated with 'live' events as they happen.
134 | We traditionally use two transports here but
135 | wouldn't our day be easier if we didn't have to program distinct cases?
136 |
137 | In the example below the message server intentionally writes a JSON response
138 | that never completes. It starts by writing out the existing messages
139 | as a chunk and then continues to write out new ones as they happen.
140 | The only difference between 'old' and 'new' data is timing:
141 |
142 | {{demo "historic-and-live"}}
143 |
144 | Publish cacheable, streamed content
145 | -----------------------------------
146 |
147 | [Above](#send-historic-and-live-data-using-the-same-transport) we had a
148 | service where the response intentionally never completes. Here we will
149 | consider a slightly different case: JSON that streams to reflect
150 | live events but which eventually ends.
151 |
152 | Most streaming HTTP techniques like Websockets intentionally avoid caches
153 | and proxies.
154 | Oboe.js is different; by taking a REST-based approach to streaming it remains
155 | compatible with HTTP intermediaries and can take advantage of caches to better
156 | distribute the content.
157 |
158 | The visualisation below is based on [a cartogram taken from
159 | Wikipedia](http://en.wikipedia.org/wiki/File:Cartogram%E2%80%942012_Electoral_Vote.svg)
160 | and simulates each state's results being announced in the [2012 United
161 | States presidential
162 | election](http://en.wikipedia.org/wiki/United_States_presidential_election,_2012).
163 | Time is sped up so that hours are condensed into seconds.
164 |
165 | {{demo "caching"}}
166 |
167 | This won't work for every use case. Websockets remains the better choice where
168 | live data after-the-fact is no longer interesting. REST-based Cacheable streaming
169 | works best for cases where the live data is not specific to a single user and remains
170 | interesting as it ages.
171 |
172 | What downsides?
173 | ----------
174 |
175 | Because it is a pure Javascript parser, Oboe.js requires more CPU time
176 | than JSON.parse. Oboe.js works marginally more
177 | slowly for small messages that load very quickly
178 | but for most real-world cases using i/o effectively beats optimising CPU time.
179 |
180 | SAX parsers require less memory than Oboe's pattern-based parsing model because
181 | they do not build up a parse tree. See [Oboe.js vs SAX vs DOM](parsers).
182 |
183 | If in doubt, benchmark, but don't forget to
184 | use the real internet, including mobile, and think about perceptual performance.
185 |
186 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 |
2 | var express = require('express'),
3 | gzippo = require('gzippo'),
4 | oboe = require('oboe'),
5 | consolidate = require('consolidate'),
6 | readContent = require('./read-content.js'),
7 | readPagesList = require('./read-pages-list.js'),
8 | barrier = require('./barrier.js'),
9 | environment = require('optimist')
10 | .demand(['env'])
11 | .argv.env,
12 |
13 | PORT = '8888',
14 |
15 | isProd = (environment == 'prod'),
16 | SCRIPTS = isProd? ['/js-concat/all.js'] : require('./sourceList.js'),
17 |
18 | CSS_STYLESHEETS = isProd? ["all-min.css"] : ["all.css"],
19 |
20 | latestTag = '',
21 | ANALYTICS_ID = 'UA-47871814-1',
22 | RAW_REPO_LOCATION = 'https://raw.github.com/jimhigson/oboe.js',
23 | REPO_LOCATION = 'https://github.com/jimhigson/oboe.js',
24 | GITHUB_TAGS_URL = 'https://api.github.com/repos/jimhigson/oboe.js/tags',
25 | USER_AGENT = 'http://github.com/jimhigson/oboe.js-website',
26 |
27 | app = express();
28 |
29 | require('colors');
30 |
31 | console.log('starting up for environment', environment.blue );
32 |
33 | // Load the latest tag from Github. This will be unavailable for a second or two
34 | // when the site starts up.
35 | var FIFTEEN_MINUTES = 1000 * 60 * 15;
36 |
37 |
38 | function getLatestTag() {
39 | oboe({url: GITHUB_TAGS_URL, headers: {'User-Agent': USER_AGENT}})
40 |
41 | .node('![0].name', function (tagName) {
42 |
43 | console.log('latest Oboe version is', tagName.blue);
44 | latestTag = tagName;
45 | this.abort();
46 | })
47 | .fail(console.error);
48 | }
49 |
50 | setInterval(getLatestTag, FIFTEEN_MINUTES);
51 | getLatestTag();
52 |
53 | app.engine('handlebars', consolidate.handlebars);
54 | app.set('view engine', 'handlebars');
55 | app.set('views', __dirname + '/views');
56 |
57 | /* create elements to send to the client side so it can
58 | * make visualisations by cloning them */
59 | function renderClientSideDemoTemplates(res, callback){
60 | var DEMO_TEMPLATE_OPTIONS = {packetRadius: 15};
61 |
62 | res.render('demoTemplate', DEMO_TEMPLATE_OPTIONS,
63 | function(err, demoContentHtml) {
64 | callback(demoContentHtml);
65 | });
66 | }
67 |
68 | function defaultOpts(opts) {
69 | opts = opts || {};
70 | opts.scripts = SCRIPTS;
71 | opts.stylesheets = CSS_STYLESHEETS;
72 | opts.latestTag = latestTag;
73 | opts.analyticsId = ANALYTICS_ID;
74 | opts.repo = REPO_LOCATION;
75 | opts.rawRepo = RAW_REPO_LOCATION;
76 | opts.logoSize = 64;
77 | opts.releasedJs = RAW_REPO_LOCATION + '/' + latestTag + '/dist';
78 | opts.production = isProd;
79 |
80 | return opts;
81 | }
82 |
83 | function respondWithMarkdown(req, res, getContentFn, opts){
84 |
85 | var view = (req.query.mode == 'raw'? 'raw' : 'page');
86 |
87 | opts = defaultOpts(opts);
88 |
89 | var bar = barrier(function(){
90 | res.render(view, opts);
91 | console.log('The HTML page for', req.url.blue, 'was created in', String(bar.duration).blue, 'ms');
92 | });
93 |
94 | readPagesList(bar.add(function(pages){
95 | // if any page in the pages list is the current, mark as such:
96 | pages.forEach(function(page){
97 | var pageUrl = '/' + page.path;
98 | page.current = ( pageUrl == req.url );
99 | });
100 |
101 | opts.pages = pages;
102 | }));
103 |
104 | getContentFn(req, opts, bar.add(function( outline ){
105 |
106 | opts.content = outline.content;
107 | opts.heading = outline.heading;
108 | opts.sections = outline.sections;
109 | opts.multipleSections = outline.multipleSections;
110 | res.status(outline.status);
111 | }));
112 |
113 | renderClientSideDemoTemplates(res, bar.add(function(templateHtml) {
114 |
115 | opts.templates = templateHtml;
116 | }));
117 | }
118 |
119 | function readMarkdownFromFile( filename ) {
120 |
121 | return function( req, opts, callback ) {
122 |
123 | readContent(filename, opts, callback);
124 | };
125 | }
126 |
127 | app
128 | .use(function(req, res, next) {
129 | // Connect middleware:
130 | // works around an issue in gzippo where requests from a load balancer for the homepage
131 | // would 404 because it tries to serve as a non-existent static, non-gzipped resource.
132 | // TODO: find something less buggy than gzippo
133 |
134 | if( !req.headers['Content-Encoding'] && req.url == '/' ) {
135 | respondWithMarkdown(req, res, readMarkdownFromFile('index'));
136 | } else {
137 | // for all other requests, go on as usual:
138 | next();
139 | }
140 | })
141 | .use(express.favicon(__dirname + '/statics/favicons/favicon.ico'))
142 | .use(gzippo.staticGzip('statics')) // gzip for static
143 | .use(gzippo.staticGzip('pdf'))
144 | .use(gzippo.staticGzip('bower_components')) // gzip for static
145 | .use(express.compress()) // gzip for dynamic
146 | .get('/', function(req, res){
147 | respondWithMarkdown(req, res, readMarkdownFromFile('index'), {
148 | home: true,
149 | twitter: true
150 | });
151 | })
152 | .get('/:page', function(req, res){
153 | respondWithMarkdown(req, res, readMarkdownFromFile(req.params.page));
154 | });
155 |
156 | // allow single demos to be viewed but only if we are in dev:
157 | if( environment == 'dev' ) {
158 | app.get('/demo/:demo', function(req, res){
159 |
160 | function generateMarkdownForSingleDemo(req, _opts, callback){
161 | var demoName = req.params.demo;
162 |
163 | callback({
164 | content:''
165 | , heading: {text:'Demo: ' + demoName}
166 | , sections:[]
167 | , status:200
168 | });
169 | }
170 |
171 | respondWithMarkdown(req, res, generateMarkdownForSingleDemo);
172 | })
173 | }
174 |
175 | // As a catch-all generate a 404.
176 | app.use(function(req,res){
177 | console.warn('Unrecognised path; catch-all serving 404:'.red, req.url);
178 |
179 | respondWithMarkdown(req, res, readMarkdownFromFile('404'));
180 | });
181 |
182 | app.listen(PORT);
183 |
184 | console.log('started on port', PORT.blue);
185 |
--------------------------------------------------------------------------------
/make_pdfs:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | pandoc -o pdf/examples.pdf content/examples.md
4 | pandoc -o pdf/api.pdf content/api.md
5 | pandoc -o pdf/_why.pdf content/why.md
6 |
--------------------------------------------------------------------------------
/oboe.js-demo.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "oboe.js-demo",
3 | "version": "0.0.0-239",
4 | "description": "A website for Oboe.js",
5 | "main": "index.js",
6 | "dependencies": {
7 | "cheerio": "~0.12.4",
8 | "colors": "~0.6.2",
9 | "consolidate": "~0.9.1",
10 | "express": "~3.4.4",
11 | "grunt": "^1.0.1",
12 | "grunt-cli": "^1.2.0",
13 | "grunt-contrib-cssmin": "^1.0.1",
14 | "grunt-contrib-uglify": "^1.0.1",
15 | "grunt-develop": "^0.4.0",
16 | "grunt-sass": "^1.2.0",
17 | "gzippo": "~0.2.0",
18 | "handlebars": "~1.1.2",
19 | "line-reader": "~0.2.3",
20 | "marked": "~0.2.10",
21 | "mustache": "~0.7.3",
22 | "oboe": "^2.0.2",
23 | "optimist": "~0.6.0",
24 | "supermarked": "~1.1.0"
25 | },
26 | "scripts": {
27 | "test": "echo \"Error: no test specified\" && exit 1",
28 | "predeploy": "grunt build",
29 | "start": "node index.js --env=prod"
30 | },
31 | "repository": {
32 | "type": "git",
33 | "url": "https://github.com/jimhigson/oboe.js-website.git"
34 | },
35 | "keywords": [
36 | "oboe",
37 | "javascript",
38 | "progressive",
39 | "json",
40 | "parse",
41 | "stream",
42 | "ajax"
43 | ],
44 | "author": "Jim Higson ",
45 | "license": "BSD-2-Clause",
46 | "bugs": {
47 | "url": "https://github.com/jimhigson/oboe.js-website/issues"
48 | },
49 | "subdomain": "oboejs",
50 | "domains": [
51 | "oboejs.com",
52 | "oboejs.org",
53 | "www.oboejs.com",
54 | "www.oboejs.org"
55 | ],
56 | "engines": {
57 | "node": "0.10.x"
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/pdf/_why.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimhigson/oboe.js-website/0d768e740a77c8e52171d3e5ceef7dc0a0680921/pdf/_why.pdf
--------------------------------------------------------------------------------
/pdf/api.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimhigson/oboe.js-website/0d768e740a77c8e52171d3e5ceef7dc0a0680921/pdf/api.pdf
--------------------------------------------------------------------------------
/pdf/examples.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimhigson/oboe.js-website/0d768e740a77c8e52171d3e5ceef7dc0a0680921/pdf/examples.pdf
--------------------------------------------------------------------------------
/read-content.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var supermarked = require('supermarked'),
4 | fs = require('fs'),
5 | cheerio = require('cheerio'),
6 | Handlebars = require('handlebars'),
7 | barrier = require('./barrier.js'),
8 | figureTemplate = Handlebars.compile(
9 | ''
10 | ),
11 | MARKDOWN_OPTS = {ignoreMath:true, smartypants:true, gfm:true, tables:true},
12 | MD_PREFIX = '{{#if pdfLink}}This page is also [available as a PDF]({{pdfLink}}).{{/if}}\n',
13 | MD_POSTFIX = fs.readFileSync('content/postfix.md');
14 |
15 | Handlebars.registerHelper("demo", function(name, mode) {
16 | var autoplay = (mode == "autoplay");
17 |
18 | return new Handlebars.SafeString( figureTemplate({name:name, autoplay:autoplay}) );
19 | });
20 |
21 | function postProcessMarkup($) {
22 | $('pre').each(function(i, ele){
23 | var code = $(ele);
24 |
25 | if( /deprecated/i.exec( code.text()) ) {
26 |
27 | var details = $('');
28 | code.replaceWith(details);
29 | details
30 | .append('Deprecated API
')
31 | .append(code);
32 | }
33 | });
34 |
35 | return $;
36 | }
37 |
38 | function outline($){
39 |
40 | var mainHeadingEle = $('h1').first(),
41 |
42 | mainHeading = {
43 | text: mainHeadingEle.text(),
44 | id: mainHeadingEle.attr('id')
45 | },
46 |
47 | sectionHeadings = [];
48 |
49 | $('h2').each(function(i, element){
50 | var jEle = $(element),
51 | text = jEle.text();
52 |
53 | sectionHeadings.push({
54 | text: text,
55 | id: jEle.attr('id')
56 | });
57 | });
58 |
59 | mainHeadingEle.remove();
60 |
61 | return {
62 | content: $.html(),
63 | heading: mainHeading,
64 | sections: sectionHeadings,
65 | multipleSections: sectionHeadings.length > 1
66 | }
67 | }
68 |
69 | function readContent(requestedPage, opts, callback) {
70 |
71 | console.log('request for page:', requestedPage.blue);
72 |
73 | function pdfFile(pageName) {
74 | return 'pdf/' + pageName + '.pdf';
75 | }
76 |
77 | function pdfUrl(pageName) {
78 | return '/' + pageName + '.pdf';
79 | }
80 |
81 | function markdownFile(pageName) {
82 | return 'content/' + pageName + '.md';
83 | }
84 |
85 | fs.exists(markdownFile(requestedPage), function(requestedMarkdownExists){
86 |
87 | var actualPage = requestedMarkdownExists? requestedPage : '404',
88 | markdownToRead = markdownFile(actualPage),
89 | markdownContent,
90 | bar = barrier(function(){
91 |
92 | var surroundedMarkdown = MD_PREFIX + markdownContent + MD_POSTFIX,
93 | filledInMarkdown = Handlebars.compile(surroundedMarkdown)(opts),
94 | html = supermarked(filledInMarkdown, MARKDOWN_OPTS),
95 | $ = postProcessMarkup(cheerio.load(html)),
96 | response = outline($);
97 |
98 | response.status = requestedMarkdownExists? 200 : 404;
99 |
100 | if( !requestedMarkdownExists ) {
101 | console.log('no such markdown file:'.red, markdownFile(requestedPage));
102 | }
103 |
104 | callback( response );
105 | });
106 |
107 | opts.page = actualPage;
108 | opts.isDownloadPage = (actualPage == 'download');
109 |
110 | fs.exists(pdfFile(requestedPage), bar.add(function(exists){
111 | if( exists ) {
112 | opts.pdfLink = pdfUrl(requestedPage);
113 | }
114 | }));
115 |
116 | // fileToRead should point to legit page by now (possibly 404)
117 | fs.readFile(markdownToRead, bar.add(function(err, markdownBuffer){
118 |
119 | markdownContent = markdownBuffer.toString();
120 | }));
121 | });
122 | }
123 |
124 | module.exports = readContent;
125 |
126 |
127 |
128 |
--------------------------------------------------------------------------------
/read-pages-list.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs'),
2 | lineReader = require('line-reader'),
3 | CONTENT_DIR = 'content',
4 | MARKDOWN_FILENAME_PATTERN = /(.*)\.md/,
5 | barrier = require('./barrier');
6 |
7 | function readFirstLine(file, callback){
8 |
9 | lineReader.eachLine(CONTENT_DIR + '/' + file, function(line, last) {
10 | callback(line);
11 | return false; // stop reading the file
12 | });
13 |
14 | }
15 |
16 | function markdownTitleContent(markdownLine) {
17 | return markdownLine.replace(/#+ +(.*)/, '$1');;
18 | }
19 |
20 | module.exports = function readPagesList(callback) {
21 |
22 | fs.readFile(CONTENT_DIR + '/listing', 'utf8', function(err, content){
23 |
24 | var files = content.split('\n');
25 |
26 | readFileList(files);
27 | });
28 |
29 | function readFileList(markdownFiles){
30 |
31 | var result = [];
32 |
33 | var bar = barrier(function(){
34 | callback(result);
35 | });
36 |
37 | markdownFiles.forEach(function(file, i){
38 | if( !file )
39 | return // skip blank lines
40 |
41 | var obj = {path:file.match(MARKDOWN_FILENAME_PATTERN)[1]};
42 |
43 | readFirstLine(file, bar.add(function(firstLine){
44 | // de-markdown the title from the file:
45 | obj.title = markdownTitleContent(firstLine);
46 |
47 | result[i] = obj;
48 | }));
49 | });
50 | }
51 | }
52 |
53 |
--------------------------------------------------------------------------------
/sass/all.scss:
--------------------------------------------------------------------------------
1 |
2 |
3 | $maxPageWidth: 1100px;
4 |
5 | $backgroundColor: rgb(254, 254, 252);
6 | $shadedBackgroundColor: #f5f3ee;
7 | $shadow: #C8C5C1;
8 | $underline: #c1bcb7;
9 | $midTone: #827e7a;
10 | $nearBlack: #0f0a07;
11 |
12 | $highlight: #74b58c;
13 | $highlightHover: #4a765a;
14 | $highlightPressed: #569a6e;
15 |
16 | $linkColor: $highlight;
17 | $linkHoverColor: $highlightHover;
18 |
19 | $minSizeForTwoCol: 950px;
20 | $sizeRequiringPhoneLayout: 600px;
21 |
22 | $menuMarginRight: 20px;
23 |
24 | /* categorical colours from https://github.com/mbostock/d3/wiki/Ordinal-Scales */
25 | $categoricalBlue:#1f77b4;
26 | $categoricalOrange: #ff7f0e;
27 | $categoricalGreen: #2ca02c;
28 | $categoricalRed: #d62728;
29 | $categoricalPurple: #9467bd;
30 | $categoricalBrown: #8c564b;
31 | $categoricalPink: #e377c2;
32 | $categoricalGrey: #7f7f7f;
33 | $categoricalYellow: #bcbd22;
34 | $categoricalLightBlue: #17becf;
35 |
36 | $categoricalColors: $categoricalBlue $categoricalOrange $categoricalGreen $categoricalPurple $categoricalRed $categoricalBrown $categoricalPink $categoricalGrey $categoricalYellow $categoricalLightBlue;
37 |
38 | @mixin linkTiming {
39 | transition: all 0.5s;
40 | }
41 | @mixin linkTimingHover {
42 | transition: all 0.2s;
43 | }
44 |
45 | @mixin link {
46 | color: $linkColor;
47 | fill: $linkColor;
48 | text-decoration: none;
49 | @include linkTiming;
50 | }
51 | @mixin hovered-link {
52 | color: $highlightHover;
53 | fill: $highlightHover;
54 | text-decoration: underline;
55 | @include linkTimingHover;
56 | cursor: pointer;
57 | }
58 | @mixin active-link {
59 | color: $nearBlack;
60 | fill: $nearBlack;
61 | transition: all 0s;
62 | }
63 |
64 | $themeFont: "Computer Modern";
65 | $themeMonoFont: "Computer Modern mono";
66 | $themeItalicFont: "Computer Modern italic";
67 |
68 |
69 | @font-face {
70 | font-family: $themeMonoFont;
71 | src: url("/type/cmuntt.eot");
72 | src: url("/type/cmuntt.ttf");
73 | src: url("/type/cmuntt.svg");
74 | src: url("/type/cmuntt.woff");
75 | }
76 |
77 | @font-face {
78 | font-family: $themeFont;
79 | src: url("/type/cmunrm.eot");
80 | src: url("/type/cmunrm.ttf");
81 | src: url("/type/cmunrm.svg");
82 | src: url("/type/cmunrm.woff");
83 | }
84 |
85 | @font-face {
86 | font-family: $themeItalicFont;
87 | src: url("/type/cmunti.eot");
88 | src: url("/type/cmunti.ttf");
89 | src: url("/type/cmunti.svg");
90 | src: url("/type/cmunti.woff");
91 | }
92 |
93 | @import 'oboe';
94 | @import 'content';
95 | @import 'demo';
96 | @import 'map';
97 | @import 'graph';
98 | @import 'cartogram';
99 | @import 'packetColors';
100 |
101 |
--------------------------------------------------------------------------------
/sass/cartogram.scss:
--------------------------------------------------------------------------------
1 | .cartogram {
2 |
3 | path[data-state] {
4 | shape-rendering: crispEdges;
5 | }
6 |
7 | path {
8 | transition:fill 0.5s;
9 | }
10 |
11 | path, circle {
12 | stroke: $backgroundColor;
13 | stroke-width:0.9;
14 | vector-effect: non-scaling-stroke;
15 | fill:$shadow;
16 | }
17 |
18 | .pie line {
19 | vector-effect: non-scaling-stroke;
20 | stroke: $backgroundColor;
21 | stroke-width:1;
22 | }
23 |
24 | }
25 |
26 | @media only screen and (max-width: $sizeRequiringPhoneLayout) {
27 | .cartogram path, circle {
28 | stroke-width:0.5;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/sass/content.scss:
--------------------------------------------------------------------------------
1 |
2 |
3 | .marked {
4 | position:relative;
5 | }
6 |
7 | .marked,
8 | .marked pre {
9 | max-width: 672px;
10 | }
11 |
12 | .marked pre {
13 | background-color:$shadedBackgroundColor;
14 | overflow:auto;
15 | padding:1em;
16 | line-height: 1.2em;
17 | margin:0em 0 2em 0;
18 | }
19 |
20 | code {
21 | font-family: $themeMonoFont, monospace;
22 | color: #3d3935;
23 | font-size:80%;
24 |
25 | .comment {
26 | color: $highlight;
27 | }
28 | .string {
29 | color: #d8615a;
30 | }
31 | .keyword {
32 | color: #50c3db;
33 | }
34 | }
35 |
36 | /* inline code examples */
37 | :not(pre) > code {
38 | display:inline-block;
39 | background-color: $shadedBackgroundColor;
40 | border-radius: 2px;
41 | padding: 2px 5px;
42 | }
43 |
44 | .marked ul, ol {
45 | margin:0 2em 2em 2em;
46 | }
47 |
48 | .marked ul {
49 | list-style: none;
50 | }
51 |
52 | /* use bullets from the font and otherwise make them look
53 | like LaTeX. */
54 | .marked li {
55 | margin:0.5em 0;
56 | padding-left:0;
57 | line-height: 1.5;
58 | }
59 |
60 | .marked ul li:before {
61 | content: "•";
62 | padding-right: 0;
63 | position:absolute;
64 | left:1em;
65 | }
66 |
67 | @media only screen and (max-width: $minSizeForTwoCol) {
68 | .marked,
69 | .marked pre {
70 | max-width: 100%;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/sass/demo.scss:
--------------------------------------------------------------------------------
1 | $demoBackground: $backgroundColor;
2 | $labelColor: $shadow;
3 |
4 |
5 | @mixin overlayText {
6 | /* outline effect */
7 | text-shadow:
8 | -1px -1px 0 $demoBackground,
9 | 1px -1px 0 $demoBackground,
10 | -1px 1px 0 $demoBackground,
11 | 1px 1px 0 $demoBackground;
12 | }
13 |
14 | template {
15 | /* browsers that don't understand template elements fail to hide them */
16 | display:none;
17 | }
18 |
19 | #demoTemplateHolder {
20 | display:none;
21 | }
22 |
23 | [data-demo] {
24 | -webkit-touch-callout: none;
25 | -webkit-user-select: none;
26 | -khtml-user-select: none;
27 | -moz-user-select: none;
28 | -ms-user-select: none;
29 | user-select: none;
30 |
31 | margin: 1em 0 2em 0;
32 | background-color: $demoBackground;
33 | position:relative;
34 |
35 | -webkit-backface-visibility: hidden;
36 | -webkit-perspective: 1000;
37 | }
38 |
39 | div.demoCaption {
40 | position: absolute;
41 | width:14em;
42 | text-align: right;
43 | bottom: 2em;
44 | cursor: pointer;
45 |
46 | p {
47 | margin-bottom: 0.3em;
48 | margin-top: 0;
49 | }
50 |
51 | p, a {
52 | @include overlayText();
53 | }
54 | }
55 |
56 | div.demoCaption:hover a {
57 | @include hovered-link;
58 | }
59 | div.demoCaption:active a {
60 | @include active-link;
61 | }
62 |
63 | rect.fade,
64 | .lightbox {
65 | fill: $demoBackground;
66 | opacity: 0.9;
67 | }
68 |
69 | .lightbox {
70 | stroke: $highlight;
71 | stroke-width:2;
72 | vector-effect: non-scaling-stroke;
73 | }
74 |
75 | .lightbox div {
76 | text-align: right;
77 | }
78 | .lightbox p {
79 | line-height: 100%;
80 | margin-bottom: 0.15em;
81 | }
82 |
83 | @mixin svg-text-centered {
84 | text-anchor: middle;
85 | alignment-baseline:middle;
86 | }
87 |
88 |
89 | .packet text {
90 | fill:$demoBackground;
91 | text-anchor: middle;
92 | font-size: 60%;
93 | }
94 |
95 | .controls text {
96 | @include link;
97 | }
98 | .controls text.play {
99 | @include svg-text-centered;
100 | font-size: 125%;
101 | }
102 |
103 | .controls:hover {
104 | cursor: pointer;
105 | }
106 | .controls:hover text.play,
107 | .controls .reset:hover text {
108 | @include hovered-link;
109 | }
110 | .controls:active text.play,
111 | .controls .reset:active text{
112 | @include active-link;
113 | }
114 | .clickableArea {
115 | opacity:0;
116 | }
117 |
118 | .paused .controls .reset:not(:hover) text {
119 | fill:$shadow;
120 | }
121 |
122 |
123 | .pie-divide {
124 | fill:none;
125 | stroke:$backgroundColor;
126 | stroke-width:1;
127 | }
128 |
129 | .aerial {
130 | fill: $highlight;
131 | }
132 |
133 | $i: 1;
134 | @while $i <= length($categoricalColors) {
135 |
136 | g.client.received-#{$i - 1} .part.unit-#{$i - 1} {
137 | display:inline;
138 | }
139 |
140 | .unit-#{$i - 1} .packet.airwave {
141 | fill:none;
142 | stroke:black;
143 | }
144 |
145 | $i: $i + 1;
146 | }
147 | .packet.airwave {
148 |
149 | stroke-width:5px;
150 | }
151 |
152 | line.wire.cable {
153 | stroke:$highlight;
154 | stroke-width:4;
155 | stroke-linecap: round;
156 | }
157 | line.messageOuter {
158 | stroke:$highlight;
159 | stroke-width:41;
160 | stroke-linecap: round;
161 | }
162 | line.messageInner {
163 | stroke:white;
164 | stroke-width:35;
165 | stroke-linecap: round;
166 | }
167 |
168 | g.client .frame,
169 | .server .box,
170 | .graph .bars,
171 | .graph .axes {
172 | fill: $midTone;
173 | shape-rendering: crispEdges;
174 | }
175 |
176 | .aerial circle,
177 | circle.aerial,
178 | .browser .frame,
179 | .tower .area,
180 | .server .box {
181 | stroke:$demoBackground;
182 | stroke-width:3;
183 | stroke-linejoin: round;
184 | }
185 |
186 | .playing text.label {
187 | fill: $labelColor;
188 | }
189 |
190 | text.label {
191 | text-anchor:middle;
192 | fill: $demoBackground;
193 | font-family: $themeItalicFont;
194 | transition: fill 1s;
195 | @include overlayText();
196 | }
197 |
198 | .tower .area {
199 | fill:$demoBackground;
200 | stroke:$demoBackground;
201 | stroke-width:9;
202 | }
203 | .browser .background {
204 | stroke:$demoBackground;
205 | stroke-width:4;
206 | fill:white;
207 | shape-rendering: crispEdges;
208 | }
209 |
210 | .browser .frame {
211 | stroke:$demoBackground;
212 | stroke-width:1;
213 | }
214 |
215 |
216 |
217 | .tower .structure,
218 | .barrier .wall,
219 | .barrier .shadow,
220 | .browser .progressBar .bar{
221 | fill: $midTone;
222 | }
223 |
224 | .progressBar,
225 | .tweet .shading {
226 | shape-rendering: crispEdges;
227 | }
228 |
229 | .browser .progressBar .space{
230 | fill: $shadow;
231 | opacity:0.8;
232 | }
233 | .map .progressBar .space{
234 | fill: $demoBackground;
235 | }
236 |
237 | .browser .progressBar{
238 | transition: opacity 0.5s;
239 | transition-delay: 0.5s;
240 | }
241 | .browser .progressBar.complete{
242 | opacity:0;
243 | }
244 |
245 | .barrier .shadow {
246 | fill: $shadow;
247 | }
248 |
249 | g.client .viewPort {
250 | fill: $backgroundColor;
251 | }
252 | g.client .part {
253 | display:none;
254 | }
255 |
256 | .tweet .detail {
257 | fill:$backgroundColor;
258 | opacity:0.5;
259 | }
260 |
261 |
262 |
263 |
264 | .browser .spinner {
265 | fill:$shadow;
266 | stroke:none;
267 | }
268 | .client g.spinnerContainer {
269 | opacity:1;
270 | transition:opacity 1s;
271 | transition-delay:opacity 0.5s;
272 | }
273 |
274 | .client:not(.requesting) g.spinnerContainer {
275 | opacity:0;
276 | }
277 |
278 |
--------------------------------------------------------------------------------
/sass/graph.scss:
--------------------------------------------------------------------------------
1 |
2 | .graph {
3 | .axes {
4 | fill:none;
5 | stroke: $shadedBackgroundColor;
6 | stroke-width: 3;
7 | transition:stroke 0.5s;
8 | }
9 |
10 | .points line {
11 | stroke:black;
12 | stroke-width: 2;
13 | }
14 | .points circle {
15 | stroke: $backgroundColor;
16 | }
17 | }
18 |
19 | .received-1 .graph .axes {
20 | stroke: $shadow;
21 | }
22 |
23 |
--------------------------------------------------------------------------------
/sass/map.scss:
--------------------------------------------------------------------------------
1 | .map {
2 | .water {
3 | fill:#3F8FB4;
4 | }
5 | .parks {
6 | fill:$highlight;
7 | }
8 | .roads {
9 | fill:none;
10 | stroke:$shadow;
11 | }
12 | .roads.minor {
13 | stroke-width:1;
14 | }
15 | .roads.major {
16 | stroke-width:3;
17 | }
18 | .pins .shadow {
19 | fill:$nearBlack;
20 | opacity:0.2;
21 | }
22 | .pins .pointer {
23 | stroke: $backgroundColor;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/sass/packetColors.scss:
--------------------------------------------------------------------------------
1 | .categorical {
2 | /* categorical colors for packets */
3 |
4 | $i: 1;
5 | @while $i <= length($categoricalColors) {
6 | $catColor: nth($categoricalColors, $i);
7 |
8 | .unit-#{$i - 1} {
9 | fill:$catColor;
10 | }
11 |
12 | .unit-#{$i - 1} .airwave {
13 | stroke:$catColor;
14 | }
15 |
16 | $i: $i + 1;
17 | }
18 | }
19 |
20 | $evenColor: $categoricalBlue;
21 | $evenPlace: desaturate($evenColor, 40%);
22 | $evenKey: transparentize($evenColor, 0.8);
23 |
24 | $oddColor: $categoricalOrange;
25 | $oddPlace: desaturate($oddColor, 40%);
26 | $oddKey: transparentize($oddColor, 0.8);
27 |
28 | $thirdColor: $categoricalPurple;
29 | $thirdPlace: desaturate($thirdColor, 25%);
30 | $thirdKey: transparentize($thirdColor, 0.8);
31 |
32 |
33 | .twoSeries {
34 |
35 | $i: 1;
36 | @while $i <= 10 {
37 | $isEven: ($i % 2 == 0);
38 | $isDark: ($i % 4 == 0) or ($i % 4 == 1);
39 |
40 | $seriesColor: if($isEven, $evenColor, $oddColor);
41 | $seriesColor: if($isDark, darken($seriesColor, 10%), lighten($seriesColor, 10%));
42 |
43 | .unit-#{$i - 1} {
44 | fill:$seriesColor;
45 | }
46 |
47 | line.unit-#{$i - 1} {
48 | stroke: $seriesColor;
49 | }
50 |
51 | $i: $i + 1;
52 | }
53 | }
54 |
55 | .political {
56 | [data-wonby=dem] {
57 | fill: $categoricalBlue;
58 | }
59 |
60 | [data-wonby=rep] {
61 | fill: $categoricalRed;
62 | }
63 | }
64 |
65 | .origin-slow rect.box {
66 | fill: $oddPlace;
67 | }
68 | .origin-fast rect.box {
69 | fill: $evenPlace;
70 | }
71 | .aggregator rect.box {
72 | fill: $thirdPlace;
73 | }
74 |
75 | $requestColor: lighten($categoricalBlue, 20%);
76 |
77 | .aerial.request,
78 | .packet.request {
79 | fill:$requestColor;
80 | }
81 | .request .packet.airwave {
82 | stroke:$requestColor;
83 | }
84 |
85 | span.server1 {
86 | background-color: $evenKey;
87 | }
88 | span.server2 {
89 | background-color: $oddKey;
90 | }
91 | span.aggregator {
92 | background-color: $thirdKey;
93 | }
94 |
95 |
96 | figure[data-demo=big-small] {
97 | .client1 .frame {
98 | fill: $evenPlace;
99 | }
100 |
101 | .client2 .frame {
102 | fill: $oddPlace;
103 | }
104 |
105 | .client3 .frame {
106 | fill: $thirdPlace;
107 | }
108 | }
109 | span.client1 {
110 | background-color: $evenKey;
111 | }
112 | span.client2 {
113 | background-color: $oddKey;
114 | }
115 | span.client3 {
116 | background-color: $thirdKey;
117 | }
118 |
119 | span.place {
120 | background-color: transparentize($shadow, 0.5);
121 | }
122 |
--------------------------------------------------------------------------------
/sourceList.js:
--------------------------------------------------------------------------------
1 | module.exports = [
2 |
3 | "/js/jquery.sticky.js"
4 | , "/js/jquery.easing.js"
5 | , "/js/jquery.pause.js"
6 | , "/js/internalNav.js"
7 | , "/js/pollyfill.js"
8 | , "/js/demo/cssHooks.js"
9 | , "/js/demo/functional.js"
10 | , "/js/demo/lists.js"
11 | , "/js/demo/singleEventPubSub.js"
12 | , "/js/demo/pubSub.js"
13 | , '/js/demo/scenarios.js'
14 | , '/js/demo/scenarioBuilder.js'
15 | , '/js/demo/oop.js'
16 | , '/js/demo/model/datasets.js'
17 | , '/js/demo/model/announce.js'
18 | , '/js/demo/model/direction.js'
19 | , '/js/demo/model/multiplex.js'
20 | , '/js/demo/model/throttle.js'
21 | , '/js/demo/model/Scheduler.js'
22 | , '/js/demo/model/Thing.js'
23 | , '/js/demo/model/NarrativeItem.js'
24 | , '/js/demo/model/Barrier.js'
25 | , '/js/demo/model/PacketHolder.js'
26 | , '/js/demo/model/Demo.js'
27 | , '/js/demo/model/ResponseGenerator.js'
28 | , '/js/demo/model/OriginServer.js'
29 | , '/js/demo/model/AggregatingServer.js'
30 | , '/js/demo/model/Packet.js'
31 | , '/js/demo/model/Message.js'
32 | , '/js/demo/model/Wire.js'
33 | , '/js/demo/model/Parser.js'
34 | , '/js/demo/model/Client.js'
35 | , '/js/demo/model/Relay.js'
36 | , '/js/demo/model/Cache.js'
37 | , "/js/demo/view/payloadAttributes.js"
38 | , "/js/demo/view/viewUtils.js"
39 | , "/js/demo/view/ThingView.js"
40 | , "/js/demo/view/NarrativeView.js"
41 | , "/js/demo/view/BarrierView.js"
42 | , "/js/demo/view/ClientView.js"
43 | , "/js/demo/view/DemoView.js"
44 | , "/js/demo/view/MessageView.js"
45 | , "/js/demo/view/ServerView.js"
46 | , "/js/demo/view/WireView.js"
47 | , "/js/demo/view/PacketView.js"
48 | , "/js/demo/view/RelayView.js"
49 | , "/js/demo/wire.js"
50 | ];
51 |
--------------------------------------------------------------------------------
/statics/.gitIgnore:
--------------------------------------------------------------------------------
1 | js-concat
2 |
--------------------------------------------------------------------------------
/statics/css/.gitIgnore:
--------------------------------------------------------------------------------
1 | *.css
2 |
--------------------------------------------------------------------------------
/statics/favicons/avitar-twitter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimhigson/oboe.js-website/0d768e740a77c8e52171d3e5ceef7dc0a0680921/statics/favicons/avitar-twitter.png
--------------------------------------------------------------------------------
/statics/favicons/blurredVis.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimhigson/oboe.js-website/0d768e740a77c8e52171d3e5ceef7dc0a0680921/statics/favicons/blurredVis.png
--------------------------------------------------------------------------------
/statics/favicons/blurredVis.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimhigson/oboe.js-website/0d768e740a77c8e52171d3e5ceef7dc0a0680921/statics/favicons/blurredVis.xcf
--------------------------------------------------------------------------------
/statics/favicons/favicon-16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimhigson/oboe.js-website/0d768e740a77c8e52171d3e5ceef7dc0a0680921/statics/favicons/favicon-16.png
--------------------------------------------------------------------------------
/statics/favicons/favicon-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimhigson/oboe.js-website/0d768e740a77c8e52171d3e5ceef7dc0a0680921/statics/favicons/favicon-32.png
--------------------------------------------------------------------------------
/statics/favicons/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimhigson/oboe.js-website/0d768e740a77c8e52171d3e5ceef7dc0a0680921/statics/favicons/favicon.ico
--------------------------------------------------------------------------------
/statics/favicons/favicon.ico.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimhigson/oboe.js-website/0d768e740a77c8e52171d3e5ceef7dc0a0680921/statics/favicons/favicon.ico.xcf
--------------------------------------------------------------------------------
/statics/favicons/favicon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
95 |
--------------------------------------------------------------------------------
/statics/js/demo/cssHooks.js:
--------------------------------------------------------------------------------
1 | (function(){
2 |
3 | /* allow svg properties to be set like css by jQuery. Apply a simple
4 | * idea of SVG transforms that ignores order and other transforms */
5 | var cssHooks = $.cssHooks;
6 | var cssNumber = $.cssNumber;
7 |
8 | cssHooks.translateX = {
9 | get: function( elem, computed, extra ) {
10 | var baseVal = elem.transform.baseVal;
11 | if( !baseVal.numberOfItems ) {
12 | return 0;
13 | }
14 | return baseVal.getItem(0).matrix.e;
15 | },
16 | set: function( elem, value ) {
17 | var baseVal = elem.transform.baseVal;
18 | if( !baseVal.numberOfItems ) {
19 | elem.setAttribute('transform', 'translate(0,0)')
20 | }
21 | baseVal.getItem(0).matrix.e = value;
22 | }
23 | };
24 | cssHooks.translateY = {
25 | get: function( elem, computed, extra ) {
26 | var baseVal = elem.transform.baseVal;
27 | if( !baseVal.numberOfItems ) {
28 | return 0;
29 | }
30 | return baseVal.getItem(0).matrix.f;
31 | },
32 | set: function( elem, value ) {
33 | var baseVal = elem.transform.baseVal;
34 | if( !baseVal.numberOfItems ) {
35 | elem.setAttribute('transform', 'translate(0,0)')
36 | }
37 | baseVal.getItem(0).matrix.f = value;
38 | }
39 | };
40 | function getOrSetAttributeCssHook(attributeName) {
41 | return {
42 | get: function( elem, computed, extra ) {
43 | return elem.getAttribute(attributeName);
44 | },
45 | set: function( elem, value ) {
46 | elem.setAttribute(attributeName, value);
47 | }
48 | };
49 | }
50 |
51 | jQuery.extend(
52 | cssHooks,
53 | { lineX1 : getOrSetAttributeCssHook('x1'),
54 | lineY1 : getOrSetAttributeCssHook('y1'),
55 | lineX2 : getOrSetAttributeCssHook('x2'),
56 | lineY2 : getOrSetAttributeCssHook('y2'),
57 | circleX : getOrSetAttributeCssHook('cx'),
58 | circleY : getOrSetAttributeCssHook('cy'),
59 | circleRadius : getOrSetAttributeCssHook('r')
60 | }
61 | );
62 |
63 | // Setting cssNumber.foo to true tells jquery not to put 'px' on the
64 | // end of these properties when animating them:
65 | cssNumber.translateX =
66 | cssNumber.translateY =
67 | cssNumber.lineX1 =
68 | cssNumber.lineY1 =
69 | cssNumber.lineX2 =
70 | cssNumber.lineY2 =
71 | cssNumber.circleX =
72 | cssNumber.circleY =
73 | cssNumber.circleRadius =
74 | true;
75 |
76 | }());
77 |
--------------------------------------------------------------------------------
/statics/js/demo/functional.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Partially complete a function.
3 | *
4 | * var add3 = partialComplete( function add(a,b){return a+b}, 3 );
5 | *
6 | * add3(4) // gives 7
7 | *
8 | * function wrap(left, right, cen){return left + " " + cen + " " + right;}
9 | *
10 | * var pirateGreeting = partialComplete( wrap , "I'm", ", a mighty pirate!" );
11 | *
12 | * pirateGreeting("Guybrush Threepwood");
13 | * // gives "I'm Guybrush Threepwood, a mighty pirate!"
14 | */
15 | var partialComplete = varArgs(function( fn, args ) {
16 |
17 | // this isn't the shortest way to write this but it does
18 | // avoid creating a new array each time to pass to fn.apply,
19 | // otherwise could just call boundArgs.concat(callArgs)
20 |
21 | var numBoundArgs = args.length;
22 |
23 | return varArgs(function( callArgs ) {
24 |
25 | for (var i = 0; i < callArgs.length; i++) {
26 | args[numBoundArgs + i] = callArgs[i];
27 | }
28 |
29 | args.length = numBoundArgs + callArgs.length;
30 |
31 | return fn.apply(this, args);
32 | });
33 | }),
34 |
35 | /**
36 | * Compose zero or more functions:
37 | *
38 | * compose(f1, f2, f3)(x) = f1(f2(f3(x))))
39 | *
40 | * The last (inner-most) function may take more than one parameter:
41 | *
42 | * compose(f1, f2, f3)(x,y) = f1(f2(f3(x,y))))
43 | */
44 | compose = varArgs(function(fns) {
45 |
46 | var fnsList = arrayAsList(fns);
47 |
48 | function next(params, curFn) {
49 | return [apply(params, curFn)];
50 | }
51 |
52 | return varArgs(function(startParams){
53 |
54 | return foldR(next, startParams, fnsList)[0];
55 | });
56 | });
57 |
58 | /**
59 | * A more optimised version of compose that takes exactly two functions
60 | * @param f1
61 | * @param f2
62 | */
63 | function compose2(f1, f2){
64 | return function(){
65 | return f1.call(this,f2.apply(this,arguments));
66 | }
67 | }
68 |
69 | /**
70 | * Generic form for a function to get a property from an object
71 | *
72 | * var o = {
73 | * foo:'bar'
74 | * }
75 | *
76 | * var getFoo = attr('foo')
77 | *
78 | * fetFoo(o) // returns 'bar'
79 | *
80 | * @param {String} key the property name
81 | */
82 | function attr(key) {
83 | return new Function('o', 'return o["' + key + '"]' );
84 | }
85 |
86 | /**
87 | * Call a list of functions with the same args until one returns a
88 | * truthy result. Similar to the || operator.
89 | *
90 | * So:
91 | * lazyUnion([f1,f2,f3 ... fn])( p1, p2 ... pn )
92 | *
93 | * Is equivalent to:
94 | * apply([p1, p2 ... pn], f1) ||
95 | * apply([p1, p2 ... pn], f2) ||
96 | * apply([p1, p2 ... pn], f3) ... apply(fn, [p1, p2 ... pn])
97 | *
98 | * @returns the first return value that is given that is truthy.
99 | */
100 | lazyUnion = varArgs(function(fns) {
101 |
102 | return varArgs(function(params){
103 |
104 | var maybeValue;
105 |
106 | for (var i = 0; i < len(fns); i++) {
107 |
108 | maybeValue = apply(params, fns[i]);
109 |
110 | if( maybeValue ) {
111 | return maybeValue;
112 | }
113 | }
114 | });
115 | });
116 |
117 | /**
118 | * This file declares various pieces of functional programming.
119 | *
120 | * This isn't a general purpose functional library, to keep things small it
121 | * has just the parts useful for Oboe.js.
122 | */
123 |
124 |
125 | /**
126 | * Call a single function with the given arguments array.
127 | * Basically, a functional-style version of the OO-style Function#apply for
128 | * when we don't care about the context ('this') of the call.
129 | *
130 | * The order of arguments allows partial completion of the arguments array
131 | */
132 | function apply(args, fn) {
133 | return fn.apply(undefined, args);
134 | }
135 |
136 | /**
137 | * Define variable argument functions but cut out all that tedious messing about
138 | * with the arguments object. Delivers the variable-length part of the arguments
139 | * list as an array.
140 | *
141 | * Eg:
142 | *
143 | * var myFunction = varArgs(
144 | * function( fixedArgument, otherFixedArgument, variableNumberOfArguments ){
145 | * console.log( variableNumberOfArguments );
146 | * }
147 | * )
148 | *
149 | * myFunction('a', 'b', 1, 2, 3); // logs [1,2,3]
150 | *
151 | * var myOtherFunction = varArgs(function( variableNumberOfArguments ){
152 | * console.log( variableNumberOfArguments );
153 | * })
154 | *
155 | * myFunction(1, 2, 3); // logs [1,2,3]
156 | *
157 | */
158 | function varArgs(fn){
159 |
160 | var numberOfFixedArguments = fn.length -1,
161 | slice = Array.prototype.slice;
162 |
163 |
164 | if( numberOfFixedArguments == 0 ) {
165 | // an optimised case for when there are no fixed args:
166 |
167 | return function(){
168 | return fn.call(this, slice.call(arguments));
169 | }
170 |
171 | } else if( numberOfFixedArguments == 1 ) {
172 | // an optimised case for when there are is one fixed args:
173 |
174 | return function(){
175 | return fn.call(this, arguments[0], slice.call(arguments, 1));
176 | }
177 | }
178 |
179 | // general case
180 |
181 | // we know how many arguments fn will always take. Create a
182 | // fixed-size array to hold that many, to be re-used on
183 | // every call to the returned function
184 | var argsHolder = Array(fn.length);
185 |
186 | return function(){
187 |
188 | for (var i = 0; i < numberOfFixedArguments; i++) {
189 | argsHolder[i] = arguments[i];
190 | }
191 |
192 | argsHolder[numberOfFixedArguments] =
193 | slice.call(arguments, numberOfFixedArguments);
194 |
195 | return fn.apply( this, argsHolder);
196 | }
197 | }
198 |
199 |
200 | /**
201 | * Swap the order of parameters to a binary function
202 | *
203 | * A bit like this flip: http://zvon.org/other/haskell/Outputprelude/flip_f.html
204 | */
205 | function flip(fn){
206 | return function(a, b){
207 | return fn(b,a);
208 | }
209 | }
210 |
211 |
212 | /**
213 | * Create a function which is the intersection of two other functions.
214 | *
215 | * Like the && operator, if the first is truthy, the second is never called,
216 | * otherwise the return value from the second is returned.
217 | */
218 | function lazyIntersection(fn1, fn2) {
219 |
220 | return function (param) {
221 |
222 | return fn1(param) && fn2(param);
223 | };
224 | }
225 |
226 | /**
227 | * A function which does nothing
228 | */
229 | function noop(){}
230 |
231 | /**
232 | * A function which is always happy
233 | */
234 | function always(){return true}
235 |
236 | /**
237 | * Create a function which always returns the same
238 | * value
239 | *
240 | * var return3 = functor(3);
241 | *
242 | * return3() // gives 3
243 | * return3() // still gives 3
244 | * return3() // will always give 3
245 | */
246 | function functor(val){
247 | return function(){
248 | return val;
249 | }
250 | }
251 |
--------------------------------------------------------------------------------
/statics/js/demo/lists.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Like cons in Lisp
3 | */
4 | function cons(x, xs) {
5 |
6 | /* Internally lists are linked 2-element Javascript arrays.
7 |
8 | So that lists are all immutable we Object.freeze in newer
9 | Javascript runtimes.
10 |
11 | In older engines freeze should have been polyfilled as the
12 | identity function. */
13 | return Object.freeze([x,xs]);
14 | }
15 |
16 | /**
17 | * The empty list
18 | */
19 | var emptyList = null,
20 |
21 | /**
22 | * Get the head of a list.
23 | *
24 | * Ie, head(cons(a,b)) = a
25 | */
26 | head = attr(0),
27 |
28 | /**
29 | * Get the tail of a list.
30 | *
31 | * Ie, head(cons(a,b)) = a
32 | */
33 | tail = attr(1);
34 |
35 |
36 | /**
37 | * Converts an array to a list
38 | *
39 | * asList([a,b,c])
40 | *
41 | * is equivalent to:
42 | *
43 | * cons(a, cons(b, cons(c, emptyList)))
44 | **/
45 | function arrayAsList(inputArray){
46 |
47 | return reverseList(
48 | inputArray.reduce(
49 | flip(cons),
50 | emptyList
51 | )
52 | );
53 | }
54 |
55 | /**
56 | * A varargs version of arrayAsList. Works a bit like list
57 | * in LISP.
58 | *
59 | * list(a,b,c)
60 | *
61 | * is equivalent to:
62 | *
63 | * cons(a, cons(b, cons(c, emptyList)))
64 | */
65 | var list = varArgs(arrayAsList);
66 |
67 | /**
68 | * Convert a list back to a js native array
69 | */
70 | function listAsArray(list){
71 |
72 | return foldR( function(arraySoFar, listItem){
73 |
74 | arraySoFar.unshift(listItem);
75 | return arraySoFar;
76 |
77 | }, [], list );
78 |
79 | }
80 |
81 | /**
82 | * Map a function over a list
83 | */
84 | function map(fn, list) {
85 |
86 | return list
87 | ? cons(fn(head(list)), map(fn,tail(list)))
88 | : emptyList
89 | ;
90 | }
91 |
92 | /**
93 | * foldR implementation. Reduce a list down to a single value.
94 | *
95 | * @pram {Function} fn (rightEval, curVal) -> result
96 | */
97 | function foldR(fn, startValue, list) {
98 |
99 | return list
100 | ? fn(foldR(fn, startValue, tail(list)), head(list))
101 | : startValue
102 | ;
103 | }
104 |
105 | /**
106 | * foldR implementation. Reduce a list down to a single value.
107 | *
108 | * @pram {Function} fn (rightEval, curVal) -> result
109 | */
110 | function foldR1(fn, list) {
111 |
112 | return tail(list)
113 | ? fn(foldR1(fn, tail(list)), head(list))
114 | : head(list)
115 | ;
116 | }
117 |
118 |
119 | /**
120 | * Return a list like the one given but with the first instance equal
121 | * to item removed
122 | */
123 | function without(list, test, removedFn) {
124 |
125 | return withoutInner(list, removedFn || noop);
126 |
127 | function withoutInner(subList, removedFn) {
128 | return subList
129 | ? ( test(head(subList))
130 | ? (removedFn(head(subList)), tail(subList))
131 | : cons(head(subList), withoutInner(tail(subList), removedFn))
132 | )
133 | : emptyList
134 | ;
135 | }
136 | }
137 |
138 | /**
139 | * Returns true if the given function holds for every item in
140 | * the list, false otherwise
141 | */
142 | function all(fn, list) {
143 |
144 | return !list ||
145 | ( fn(head(list)) && all(fn, tail(list)) );
146 | }
147 |
148 | /**
149 | * Call every function in a list of functions with the same arguments
150 | *
151 | * This doesn't make any sense if we're doing pure functional because
152 | * it doesn't return anything. Hence, this is only really useful if the
153 | * functions being called have side-effects.
154 | */
155 | function applyEach(fnList, arguments) {
156 |
157 | if( fnList ) {
158 | head(fnList).apply(null, arguments);
159 |
160 | applyEach(tail(fnList), arguments);
161 | }
162 | }
163 |
164 | /**
165 | * Reverse the order of a list
166 | */
167 | function reverseList(list){
168 |
169 | // js re-implementation of 3rd solution from:
170 | // http://www.haskell.org/haskellwiki/99_questions/Solutions/5
171 | function reverseInner( list, reversedAlready ) {
172 | if( !list ) {
173 | return reversedAlready;
174 | }
175 |
176 | return reverseInner(tail(list), cons(head(list), reversedAlready))
177 | }
178 |
179 | return reverseInner(list, emptyList);
180 | }
181 |
182 | function first(test, list) {
183 | return list &&
184 | (test(head(list))
185 | ? head(list)
186 | : first(test,tail(list)));
187 | }
--------------------------------------------------------------------------------
/statics/js/demo/model/AggregatingServer.js:
--------------------------------------------------------------------------------
1 | var AggregatingServer = (function(){
2 |
3 | var Super = PacketHolder;
4 |
5 | var AggregatingServer = extend(Super, function AggregatingServer(name, locations, options){
6 | Super.apply(this, arguments);
7 |
8 | this.parseStrategyName = options.parseStrategy;
9 | });
10 | AggregatingServer.newEvent = 'AggregatingServer';
11 |
12 | AggregatingServer.prototype.acceptFromDownstream = function(receivedPacket){
13 |
14 | this.propagate(receivedPacket);
15 |
16 | this.setupResponse();
17 | };
18 |
19 | AggregatingServer.prototype.acceptFromUpstream = function(receivedPacket, sender){
20 | this.parsers[sender.name].read(receivedPacket);
21 | };
22 |
23 | AggregatingServer.prototype.setupResponse = function(){
24 |
25 | this.parsers = this.createInputParsersForEachUpstreamNode(this.parseStrategyName);
26 |
27 | multiplex(
28 | this.parseStrategyName,
29 | this.parsers,
30 | this.propagate.bind(this)
31 | );
32 | };
33 |
34 | AggregatingServer.prototype.createInputParsersForEachUpstreamNode = function(parseStrategyName){
35 | var parsers = {},
36 | nextLocations = this.adjacents['upstream'],
37 | emitPacketParsed = this.events('packetParsed').emit;
38 |
39 | nextLocations.forEach(function(loc){
40 | parsers[loc.name] = Parser(parseStrategyName, emitPacketParsed);
41 | });
42 |
43 | return parsers;
44 | };
45 |
46 | return AggregatingServer;
47 | }());
48 |
--------------------------------------------------------------------------------
/statics/js/demo/model/Barrier.js:
--------------------------------------------------------------------------------
1 | var Barrier = (function(){
2 |
3 | var Barrier = extend(Thing, function Barrier(name, locations){
4 | Thing.apply(this, arguments);
5 |
6 | this.events('reset').on(function(){
7 | this.hasBeenShown = false;
8 | }.bind(this));
9 | });
10 |
11 | Barrier.newEvent = 'Barrier';
12 |
13 |
14 | Barrier.prototype.activateIfNeverShownBefore = function(){
15 |
16 | if( !this.hasBeenShown ) {
17 | this.activate();
18 | }
19 | this.hasBeenShown = true;
20 | };
21 |
22 | return Barrier;
23 | }());
24 |
--------------------------------------------------------------------------------
/statics/js/demo/model/Cache.js:
--------------------------------------------------------------------------------
1 | var Cache = (function(){
2 |
3 | var Super = PacketHolder;
4 |
5 | var Cache = extend(PacketHolder, function Relay(name, locations, options){
6 | Super.apply(this, arguments);
7 |
8 | this.timeBetweenPackets = Thing.asFunction(options.timeBetweenPackets);
9 |
10 | this.cacheContents = [];
11 | this.requestors = [];
12 | this.upstreamRequestOngoing = false;
13 |
14 | this.events('reset').on(function(){
15 | this.cacheContents = [];
16 | this.requestors = [];
17 | this.upstreamRequestOngoing = false;
18 | }.bind(this));
19 | });
20 |
21 | Cache.prototype.acceptFromDownstream = function( packetFromDownstream, source ){
22 | // got request from client heading to server
23 | this.addToScript('requestOff', source);
24 |
25 | this.requestors.push(source);
26 |
27 | this.cacheContents.forEach(function( cachedPacket ){
28 | this.propagate(cachedPacket, [source]);
29 | }.bind(this));
30 |
31 | if( !this.upstreamRequestOngoing ) {
32 |
33 | this.propagate(packetFromDownstream);
34 |
35 | this.upstreamRequestOngoing = true;
36 | } else {
37 | // already have a request ongoing, ignore and kill this packet
38 | packetFromDownstream.done();
39 | }
40 | };
41 |
42 | Cache.prototype.acceptFromUpstream = function( packetFromUpstream ){
43 | this.addToScript('accepted', packetFromUpstream);
44 |
45 | // got response from server heading to client
46 |
47 | // make a copy for the cache:
48 | var copyForCache = packetFromUpstream
49 | .replayedCopy()
50 | .done();
51 |
52 | this.cacheContents.push(copyForCache);
53 |
54 | this.propagate(packetFromUpstream, this.requestors);
55 | };
56 |
57 | Cache.newEvent = 'Cache';
58 |
59 | return Cache;
60 | }());
61 |
--------------------------------------------------------------------------------
/statics/js/demo/model/Client.js:
--------------------------------------------------------------------------------
1 | var Client = extend( PacketHolder, function Client(name, locations, options) {
2 | "use strict";
3 |
4 | PacketHolder.apply(this, arguments);
5 | this.page = options.page;
6 | this.failAfter = options.failAfter;
7 | this.retryAfter = options.retryAfter;
8 | this.aspect = options.aspect;
9 | this.showProgress = options.showProgress;
10 | this.maxRequestSize = options.maxRequestSize || Number.POSITIVE_INFINITY;
11 |
12 | this.parseStrategy = options.parseStrategy;
13 |
14 | this.attemptNumber = 0;
15 | this.receivedUpTo = -1;
16 |
17 | this.events('reset').on(function(){
18 | this.attemptNumber = 0;
19 | this.receivedUpTo = -1;
20 | }.bind(this));
21 | });
22 |
23 | Client.newEvent = 'Client';
24 |
25 | Client.prototype.makeRequest = function(){
26 |
27 | this.parser = Parser(this.parseStrategy);
28 | this.parser.events('packetParsed').on( function(packet) {
29 | this.events('gotData').emit(packet);
30 | this.receivedUpTo = packet.ordering.i;
31 | }.bind(this));
32 |
33 | this.events('request').emit();
34 | this.addToScript('requestAttempt', this.attemptNumber);
35 |
36 | var startingAt = this.receivedUpTo + 1,
37 | endingAt = startingAt + this.maxRequestSize - 1,
38 |
39 | packet = new Packet('request', 'GET', 'upstream', {isFirst:true, isLast:true, i:0})
40 | .inDemo(this.demo)
41 | .startingAt(startingAt)
42 | .endingAt(endingAt)
43 | .announce();
44 |
45 | this.scheduleFail();
46 |
47 | this.propagate(packet);
48 |
49 | this.attemptNumber++; // increment for next time
50 | };
51 |
52 | Client.prototype.scheduleFail = function() {
53 |
54 | this.giveUpTimeout = this.schedule(function(){
55 |
56 | this.addToScript('requestFail', this.attemptNumber);
57 |
58 | this.events('requestFail').emit();
59 |
60 | this.schedule(this.makeRequest.bind(this), this.retryAfter);
61 |
62 | }.bind(this), this.failAfter);
63 | };
64 |
65 |
66 | Client.prototype.acceptFromUpstream = function(packet){
67 |
68 | this.addToScript('accepted', packet);
69 |
70 | this.parser.read(packet);
71 |
72 | this.unschedule(this.giveUpTimeout);
73 |
74 | if (packet.ordering.isLast) {
75 | this.addToScript('acceptedAll');
76 | this.events('requestComplete').emit();
77 | } else {
78 | this.scheduleFail();
79 | }
80 |
81 | packet.done();
82 | };
83 |
--------------------------------------------------------------------------------
/statics/js/demo/model/Demo.js:
--------------------------------------------------------------------------------
1 | var Demo = (function(){
2 |
3 | var DELAY_AFTER_FINISH = 4500,
4 | START_DELAY = 750;
5 |
6 | var Demo = extend(Thing, function Demo(name, options){
7 | Thing.apply(this, arguments);
8 |
9 | this.height = options.height;
10 | this.width = options.width;
11 | this.script = pubSub();
12 | this.colors = options.colors;
13 | this.paused = false;
14 | this.inProgress = false;
15 |
16 | if( options.endSimulationEvent ) {
17 | this.script( options.endSimulationEvent ).on(
18 | function(){
19 | this.schedule(this.reset.bind(this), DELAY_AFTER_FINISH);
20 | }.bind(this)
21 | );
22 | }
23 |
24 | this.demo = this; // we are our own demo
25 | });
26 |
27 | Demo.newEvent = 'Demo';
28 |
29 | Demo.prototype.start = function(){
30 | if( !this.inProgress ) {
31 |
32 | this.inProgress = true;
33 | this.events('started').emit();
34 |
35 | this.schedule(function(){
36 | this.startSimulation();
37 | }.bind(this), START_DELAY)
38 | }
39 | };
40 | Demo.prototype.reset = function(){
41 | if( this.inProgress ) {
42 | this.events('reset').emit();
43 | this.inProgress = false;
44 | this.paused = false;
45 | }
46 | };
47 |
48 | Demo.prototype.pause = function(){
49 | this.paused = true;
50 | this.events('paused').emit();
51 | };
52 | Demo.prototype.unpause = function(){
53 | if( !this.paused ){
54 | throw new Error('unpausing but not paused');
55 | }
56 |
57 | this.paused = false;
58 | this.events('unpaused').emit();
59 | };
60 |
61 | return Demo;
62 |
63 | }());
64 |
--------------------------------------------------------------------------------
/statics/js/demo/model/Message.js:
--------------------------------------------------------------------------------
1 | var Message = extend(Thing, function Message() {
2 | Thing.apply(this, arguments);
3 |
4 | this.events('reset').on(this.done.bind(this));
5 | });
6 |
7 | Message.newEvent = 'Message';
8 |
9 | Message.prototype.transmittedOver = function(packetHolder){
10 | this.holder = packetHolder;
11 | return this;
12 | };
13 |
14 | Message.prototype._withRequestStart = function(firstPacket){
15 |
16 | firstPacket.events('move').on(function(){
17 |
18 | this.events('requestStartMove').emit.apply(this, arguments);
19 | }.bind(this));
20 | return this; // chaining
21 | };
22 | Message.prototype._withResponseClose = function(lastPacket){
23 |
24 | lastPacket.events('move').on(function(){
25 |
26 | this.events('responseCloseMove').emit.apply(this, arguments);
27 | }.bind(this));
28 |
29 | lastPacket.events('done').on(function(){
30 | this.done();
31 | }.bind(this));
32 |
33 | return this; // chaining
34 | };
35 | Message.prototype.includes = function(packet) {
36 |
37 | if( packet.startsRequest() ) {
38 | this._withRequestStart(packet);
39 | }
40 | if( packet.closesResponse() ) {
41 | this._withResponseClose(packet);
42 | }
43 | return this; // chaining
44 | };
45 |
--------------------------------------------------------------------------------
/statics/js/demo/model/NarrativeItem.js:
--------------------------------------------------------------------------------
1 | var NarrativeItem = (function(){
2 |
3 | var NarrativeItem = extend(Thing, function NarrativeItem(scriptEventName, _locations, options){
4 | Thing.apply(this, arguments);
5 |
6 | this.text = options.text;
7 | this.locationOnTopic = options.locationOnTopic;
8 | });
9 |
10 | NarrativeItem.prototype.with = {
11 | topic: function( topicItem ){
12 | this.topic = topicItem;
13 | }
14 | };
15 |
16 | NarrativeItem.newEvent = 'NarrativeItem';
17 |
18 | NarrativeItem.prototype.popUp = function(){
19 | this.demo.pause();
20 | this.events('activated').emit(this);
21 | };
22 |
23 | NarrativeItem.prototype.dismiss = function(){
24 | this.demo.unpause();
25 | this.events('deactivated').emit(this);
26 | };
27 |
28 | return NarrativeItem;
29 | }());
30 |
31 |
32 |
--------------------------------------------------------------------------------
/statics/js/demo/model/OriginServer.js:
--------------------------------------------------------------------------------
1 | var OriginServer = (function(){
2 |
3 | var Super = PacketHolder;
4 |
5 | var OriginServer = extend( Super, function OriginServer(name, locations, options) {
6 |
7 | Super.apply(this, arguments);
8 |
9 | this.responseGenerator = new ResponseGenerator(options);
10 |
11 | this.responseGenerator.events('packetGenerated').on(function(packet, recipient){
12 |
13 | this.addToScript('sent', packet);
14 | this.propagate(packet, [recipient]);
15 | }.bind(this));
16 | });
17 |
18 | OriginServer.newEvent = 'OriginServer';
19 |
20 | OriginServer.prototype.acceptFromDownstream = function(packet, source){
21 |
22 | this.addToScript('gotRequest');
23 | this.responseGenerator.generateResponse(
24 | packet.gotAlreadyUpTo,
25 | packet.requestingUpto,
26 | source
27 | );
28 | packet.done();
29 | };
30 |
31 | OriginServer.prototype.inDemo = function(demo){
32 | Super.prototype.inDemo.apply(this, arguments);
33 | this.responseGenerator.inDemo(demo);
34 | return this;
35 | };
36 |
37 | return OriginServer;
38 | }());
39 |
--------------------------------------------------------------------------------
/statics/js/demo/model/Packet.js:
--------------------------------------------------------------------------------
1 | var Packet = (function(){
2 | var Super = Thing;
3 |
4 | var Packet = extend(Super,
5 | /**
6 | * @param name
7 | * @param type
8 | * @param direction
9 | * @param ordering
10 | * @param mode Number -> ('live'|'historic')
11 | * @constructor
12 | */
13 | function Packet(name, type, direction, ordering, mode){
14 | Super.apply(this, arguments);
15 |
16 | this.direction = direction;
17 | this.ordering = ordering;
18 | this.type = type;
19 | this.mode = mode;
20 | this.gotAlreadyUpTo = 0;
21 |
22 | this.events('reset').on(this.done.bind(this));
23 | }
24 | );
25 |
26 | Packet.newEvent = 'Packet';
27 |
28 | Packet.prototype.copy = function() {
29 |
30 | var orderingCopy = {
31 | i: this.ordering.i,
32 | isFirst: this.ordering.isFirst,
33 | isLast: this.ordering.isLast,
34 | expectedSize: this.ordering.expectedSize
35 | };
36 |
37 | return new Packet(
38 | this.name,
39 | this.type,
40 | this.direction,
41 | orderingCopy,
42 | this.mode
43 | )
44 | .inDemo(this.demo)
45 | .startingAt(this.gotAlreadyUpTo)
46 | .endingAt(this.requestingUpto)
47 | .withPayload(this.payload);
48 | };
49 | Packet.prototype.replayedCopy = function() {
50 | var copy = this.copy();
51 | copy.mode = functor('historic');
52 | return copy;
53 | };
54 | Packet.prototype.startingAt = function( firstPacketNumber ) {
55 | this.gotAlreadyUpTo = firstPacketNumber;
56 | return this; // chaining
57 | };
58 | Packet.prototype.endingAt = function( lastPacketNumber ) {
59 | this.requestingUpto = lastPacketNumber;
60 | return this; // chaining
61 | };
62 | Packet.prototype.withPayload = function( payload ) {
63 | this.payload = payload;
64 | return this; // chaining
65 | };
66 | Packet.prototype.startsRequest = function() {
67 | return (this.direction == Direction.upstream) && this.ordering.isFirst;
68 | };
69 | Packet.prototype.closesResponse = function() {
70 | return (this.direction == Direction.downstream) && this.ordering.isLast;
71 | };
72 | Packet.prototype.move = function(fromXY, toXY, latency){
73 | this.events('move').emit(fromXY, toXY, latency);
74 | };
75 | Packet.prototype.isOn = function(holder){
76 | this.events('isOn').emit(holder);
77 | };
78 |
79 | return Packet;
80 | })();
81 |
--------------------------------------------------------------------------------
/statics/js/demo/model/PacketHolder.js:
--------------------------------------------------------------------------------
1 | var PacketHolder = (function(){
2 | "use strict";
3 |
4 | var Super = Thing;
5 |
6 | var PacketHolder = extend(Super, function PacketHolder(name, locations){
7 |
8 | if( !locations ) {
9 | throw new Error("don't know where " + name + " is");
10 | }
11 |
12 | Super.apply(this, arguments);
13 |
14 | if( !locations ) {
15 | throw new Error("don't know where " + name + " is");
16 | }
17 |
18 | this.latency = 0;
19 | this.adjacents = {
20 | downstream: []
21 | , upstream: []
22 | };
23 | });
24 |
25 | PacketHolder.newEvent = 'PacketHolder';
26 |
27 | /* things which want to handle packets arriving can override
28 | these methods */
29 | PacketHolder.prototype.accept =
30 | PacketHolder.prototype.acceptFromDownstream =
31 | PacketHolder.prototype.acceptFromUpstream = function(){};
32 |
33 | PacketHolder.prototype.withDownstream = function(downstreamLocation){
34 |
35 | this.adjacents.downstream.push(downstreamLocation);
36 | this.listenToAdjacentForPackets(downstreamLocation, 'downstream');
37 | downstreamLocation._withUpstream(this);
38 |
39 | return this;
40 | };
41 |
42 | PacketHolder.prototype._withUpstream = function(upstreamLocation){
43 | this.adjacents.upstream.push(upstreamLocation);
44 | this.listenToAdjacentForPackets(upstreamLocation, 'upstream');
45 | };
46 |
47 | PacketHolder.prototype.listenToAdjacentForPackets = function(adjacent, direction) {
48 | var directionAtSource = oppositeDirectionTo(direction),
49 |
50 | directionalHandlerMethodName =
51 | ( direction == 'upstream'
52 | ? 'acceptFromUpstream'
53 | : 'acceptFromDownstream'
54 | ),
55 | directionSpecificHandler = this[directionalHandlerMethodName],
56 |
57 | inputHandler = function( incomingPacket ){
58 |
59 | var packetCopy = incomingPacket.copy().announce();
60 |
61 | directionSpecificHandler.call(this, packetCopy, adjacent);
62 | this.accept(packetCopy, adjacent);
63 | this.events('acceptedFrom' + direction).emit(packetCopy);
64 | }.bind(this),
65 |
66 | throttledInputHandler = this.inputThrottle(inputHandler),
67 |
68 | filteredThrottledInputHandler = function( incomingPacket, intendedRecipients ){
69 | if( intendedRecipients && (intendedRecipients.indexOf(this) == -1) ) {
70 | return; // not for me, do nothing.
71 | }
72 |
73 | throttledInputHandler(incomingPacket);
74 | }.bind(this);
75 |
76 | adjacent.events(directionAtSource).on( filteredThrottledInputHandler );
77 | };
78 |
79 | PacketHolder.prototype.inputThrottle = function(handle) {
80 | return handle;
81 | };
82 |
83 | PacketHolder.prototype.propagate = function(basePacket, recipients){
84 |
85 | this.events(basePacket.direction).emit(basePacket, recipients);
86 | basePacket.done();
87 | };
88 |
89 | PacketHolder.prototype.movePacket = function(packet){
90 | var fromLocation = oppositeDirectionTo(packet.direction),
91 | toLocation = packet.direction,
92 | fromXY = this.locations[fromLocation],
93 | toXY = this.locations[toLocation];
94 |
95 | packet.move(fromXY, toXY, this.latency);
96 | };
97 |
98 | return PacketHolder;
99 | }());
100 |
--------------------------------------------------------------------------------
/statics/js/demo/model/Parser.js:
--------------------------------------------------------------------------------
1 | function Parser (strategyName){
2 |
3 | var events = pubSub(),
4 | read;
5 |
6 | switch(strategyName){
7 | case 'progressive':
8 | read = function(packet){
9 | events('packetParsed').emit(packet);
10 | };
11 | break;
12 |
13 | case 'discrete':
14 | var packetsSoFar = [];
15 | read = function(packet){
16 | packetsSoFar.push(packet);
17 |
18 | if( packet.ordering.isLast ) {
19 | packetsSoFar.forEach(function(packet){
20 | events('packetParsed').emit(packet);
21 | });
22 | }
23 | };
24 | break;
25 |
26 | default:
27 | throw Error('wtf is ' + strategyName + '?');
28 | }
29 |
30 | return {
31 | read: read,
32 | events: events
33 | };
34 | }
35 |
--------------------------------------------------------------------------------
/statics/js/demo/model/Relay.js:
--------------------------------------------------------------------------------
1 | var Relay = (function(){
2 |
3 | var Relay = extend(PacketHolder, function Relay(name, locations, options){
4 | PacketHolder.apply(this, arguments);
5 | });
6 |
7 | Relay.newEvent = 'Relay';
8 |
9 | Relay.prototype.accept = function(receivedPacket){
10 | this.addToScript('accepted', receivedPacket);
11 | this.propagate(receivedPacket);
12 | };
13 |
14 | return Relay;
15 | }());
16 |
--------------------------------------------------------------------------------
/statics/js/demo/model/ResponseGenerator.js:
--------------------------------------------------------------------------------
1 |
2 | var ResponseGenerator = (function(){
3 |
4 | var ResponseGenerator = extend(Thing, function ResponseGenerator(options) {
5 | Thing.call(this, 'responseGenerator');
6 |
7 | var packetPayloads = dataSets[options.payloads];
8 |
9 | this.timeBetweenPackets = Thing.asFunction(options.timeBetweenPackets);
10 | this.initialDelay = options.initialDelay;
11 | this.messageSize = packetPayloads? packetPayloads.length : options.messageSize;
12 | this.packetNumberAfter = options.packetSequence;
13 | this.packetMode = Thing.asFunction(options.packetMode);
14 | this.packetPayloads = packetPayloads;
15 | });
16 |
17 | ResponseGenerator.prototype.packetGenerator = function(lastPacketNumber) {
18 |
19 | var firstPacketCreated = false;
20 |
21 | return function(n) {
22 | // unannounced packet to use as a template for others
23 | var ordering = {
24 | i: n,
25 | isFirst: !firstPacketCreated,
26 | isLast: n >= (lastPacketNumber)
27 | };
28 |
29 | if( ordering.isFirst && Number.isFinite(this.messageSize) ) {
30 | ordering.expectedSize = lastPacketNumber +1;
31 | }
32 |
33 | var packet = new Packet(
34 | 'response' + n
35 | , 'JSON'
36 | , 'downstream'
37 | , ordering
38 | , this.packetMode(n)
39 | ).inDemo(this.demo);
40 |
41 | packet.payload = this.packetPayloads && this.packetPayloads[n];
42 |
43 | firstPacketCreated = true;
44 |
45 | return packet;
46 | }.bind(this)
47 | };
48 |
49 | ResponseGenerator.prototype.generateResponse = function(startingAt, endingAt, intendedRecipient) {
50 |
51 | var lastPacketNumber = Math.min(this.messageSize - 1, endingAt),
52 | packets = this.packetGenerator( lastPacketNumber );
53 |
54 | function sendNext(previousPacketNumber){
55 |
56 | var curPacketNumber = this.packetNumberAfter(previousPacketNumber),
57 | lastPacket = curPacketNumber >= lastPacketNumber;
58 |
59 | this.events('packetGenerated').emit(packets(curPacketNumber), intendedRecipient);
60 |
61 | if (!lastPacket) {
62 | var nextPacketNumber = this.packetNumberAfter(curPacketNumber);
63 | this.schedule(
64 | sendNext.bind(this, curPacketNumber, intendedRecipient)
65 | , this.timeBetweenPackets(nextPacketNumber)
66 | );
67 | }
68 | }
69 |
70 | this.schedule( sendNext.bind(this, startingAt -1), this.initialDelay );
71 | };
72 |
73 | return ResponseGenerator;
74 | }());
75 |
--------------------------------------------------------------------------------
/statics/js/demo/model/Scheduler.js:
--------------------------------------------------------------------------------
1 | var Scheduler = (function(){
2 | "use strict";
3 |
4 | var DEFAULT_SCHEDULE_DELAY = 500;
5 |
6 | function Scheduler(subject, pauseEventSource) {
7 | this.tasks = [];
8 | this.subject = subject;
9 |
10 | pauseEventSource('paused').on(this._pause.bind(this));
11 | pauseEventSource('unpaused').on(this._unpause.bind(this));
12 | }
13 |
14 | Scheduler.prototype._pause = function(){
15 | this.tasks.forEach(this._pauseTask);
16 | };
17 |
18 | Scheduler.prototype._unpause = function(){
19 |
20 | this.tasks.forEach(this._scheduleTask);
21 | };
22 |
23 | Scheduler.prototype._removeTask = function(taskToRemove){
24 |
25 | this.tasks = this.tasks.filter(function( storedTimeout ){
26 | return storedTimeout !== taskToRemove;
27 | });
28 | };
29 |
30 | Scheduler.prototype.cancelTimeouts = function(){
31 |
32 | // cancel all scheduled events:
33 | this.tasks.forEach(function(task){
34 | window.clearTimeout(task.timeout);
35 | });
36 |
37 | this.tasks = [];
38 | };
39 |
40 | Scheduler.prototype.schedule = function(fn, requestedTiming, startPaused) {
41 |
42 | if( requestedTiming == Number.POSITIVE_INFINITY ) {
43 | // Waiting forever to do something is interpreted
44 | // as never doing it. The browser would natural
45 | // schedule it straight away (silly!)
46 | return undefined;
47 | }
48 |
49 | var wait = (requestedTiming === undefined)
50 | ? DEFAULT_SCHEDULE_DELAY
51 | : requestedTiming,
52 |
53 | performTask = function(){
54 | // stop remembering this timeout, it is done now:
55 | this._removeTask(task);
56 | fn();
57 | }.bind(this),
58 |
59 | task = {
60 | performTask: performTask,
61 | wait: wait
62 | };
63 |
64 | if( !startPaused ) {
65 | this._scheduleTask(task);
66 | }
67 |
68 | this.tasks.push( task );
69 |
70 | return task;
71 | };
72 |
73 | Scheduler.prototype._pauseTask = function(task){
74 |
75 | window.clearTimeout(task.timeout);
76 | task.timeout = undefined;
77 | task.wait = task.performTime - Date.now();
78 | };
79 |
80 | Scheduler.prototype._scheduleTask = function(task){
81 | if( task.timeout ) {
82 | throw Error('task already scheduled');
83 | }
84 |
85 | task.timeout = window.setTimeout(task.performTask, task.wait);
86 | task.performTime = Date.now() + task.wait;
87 | return task;
88 | };
89 |
90 | Scheduler.prototype.unschedule = function(unscheduledTask) {
91 |
92 | if( unscheduledTask ) {
93 | window.clearTimeout(unscheduledTask.timeout);
94 | unscheduledTask.timeout = undefined;
95 | this._removeTask(unscheduledTask);
96 | }
97 | };
98 |
99 | return Scheduler;
100 | })();
101 |
--------------------------------------------------------------------------------
/statics/js/demo/model/Thing.js:
--------------------------------------------------------------------------------
1 | var Thing = (function(){
2 | "use strict";
3 |
4 | function Thing(name, locations, options){
5 |
6 | this.name = name;
7 | this.events = pubSub();
8 | this.locations = locations || {};
9 |
10 | this.scheduler = new Scheduler(this, this.events);
11 |
12 | if (options) {
13 | this.label = options.label;
14 | this.startHidden = options.startHidden;
15 | this.zoom = options.zoom;
16 | }
17 |
18 | this.events('reset').on(function(){
19 | this.scheduler.cancelTimeouts();
20 | }.bind(this));
21 | }
22 |
23 | Thing.newEvent = 'Thing';
24 |
25 | Thing.prototype.with = {};
26 |
27 | Thing.prototype.inDemo = function(demo){
28 |
29 | this.demo = demo;
30 |
31 | var thisEvents = this.events,
32 | demoEvents = demo.events;
33 |
34 | demoEvents('reset').on( thisEvents('reset').emit );
35 | demoEvents('paused').on( thisEvents('paused').emit );
36 | demoEvents('unpaused').on( thisEvents('unpaused').emit );
37 |
38 | return this; // chaining
39 | };
40 |
41 | /** for short-lived Things. Unregister listeners */
42 | Thing.prototype.done = function(){
43 |
44 | this.events('done').emit();
45 |
46 | var thisEvents = this.events,
47 | demoEvents = this.demo.events;
48 |
49 | // unsubscribe from events on the demo
50 | demoEvents('reset').un( thisEvents('reset').emit );
51 | demoEvents('paused').un( thisEvents('paused').emit );
52 | demoEvents('unpaused').un( thisEvents('unpaused').emit );
53 |
54 | return this; // chaining
55 | };
56 |
57 | Thing.prototype.followingScript = function(scriptItems){
58 |
59 | var self = this,
60 | script = this.demo.script;
61 |
62 | scriptItems.forEach(function(scriptItem){
63 |
64 | var action = scriptItem.action,
65 | wrappedAction =
66 | scriptItem.delay
67 | ? function(){
68 | self.schedule(action, scriptItem.delay);
69 | }
70 | : action.bind(self);
71 |
72 | script(scriptItem.eventName).on(wrappedAction);
73 | });
74 |
75 | return this; // chaining
76 | };
77 |
78 | Thing.prototype.announce = function() {
79 |
80 | if( !this.constructor.newEvent ) {
81 | throw new Error('cannot announce type without .newEvent set');
82 | }
83 |
84 | this.demo.events(this.constructor.newEvent).emit(this);
85 | return this;
86 | };
87 |
88 | function scriptName( firstParty, action, secondParty ) {
89 |
90 | function name(thing){
91 | return (thing && (thing.name || thing.toString()));
92 | }
93 |
94 | return [firstParty, action, secondParty]
95 | .map(name)
96 | .filter(function(a){return a !== undefined})
97 | .join('_');
98 | }
99 |
100 | Thing.prototype.addToScript = function(verb, secondParty) {
101 |
102 | var scriptEventName = scriptName(this, verb, secondParty);
103 |
104 | this.demo
105 | .script(scriptEventName)
106 | .emit();
107 | };
108 |
109 | Thing.prototype.cancelTimeouts = function(){
110 | return this.scheduler.cancelTimeouts();
111 | };
112 |
113 | Thing.prototype.schedule = function(fn, requestedTiming) {
114 | return this.scheduler.schedule(fn.bind(this), requestedTiming, this.demo.paused);
115 | };
116 |
117 | Thing.prototype.unschedule = function(unscheduledTimeout) {
118 | return this.scheduler.unschedule(unscheduledTimeout);
119 | };
120 |
121 | Thing.asFunction = function (givenValue, defaultValue) {
122 |
123 | if (typeof givenValue == 'function') {
124 | return givenValue;
125 | }
126 |
127 | var constantValue = ( givenValue !== undefined )? givenValue : defaultValue;
128 |
129 | return function(){return constantValue};
130 | };
131 |
132 | Thing.prototype.activate = function(){
133 | this.addToScript('activated');
134 | this.events('activated').emit();
135 | };
136 |
137 | Thing.prototype.deactivate = function(){
138 | this.addToScript('deactivated');
139 | this.events('deactivated').emit();
140 | };
141 |
142 | return Thing;
143 | }());
144 |
--------------------------------------------------------------------------------
/statics/js/demo/model/Wire.js:
--------------------------------------------------------------------------------
1 | var Wire = (function(){
2 |
3 | var Super = PacketHolder;
4 |
5 | var Wire = extend( Super, function Wire(name, locations, options) {
6 |
7 | Super.apply(this, arguments);
8 | this.latency = options.latency;
9 | this.bandwidth = Thing.asFunction(options.bandwidth);
10 | this.medium = options.medium;
11 |
12 | if( !options.medium ) {
13 | throw new Error('no medium for wire ' + name);
14 | }
15 |
16 | this.events('reset').on(function(){
17 | this.blockage = undefined;
18 | }.bind(this));
19 | });
20 |
21 | Wire.newEvent = 'Wire';
22 |
23 | Wire.prototype.accept = function(packet){
24 |
25 | packet.isOn(this);
26 |
27 | if( packet.startsRequest() ) {
28 |
29 | this.message = new Message()
30 | .inDemo(this.demo)
31 | .transmittedOver(this)
32 | .announce();
33 | }
34 |
35 | this.message.includes(packet);
36 |
37 | if( packet.closesResponse() ) {
38 |
39 | // end of that message, don't prevent Message
40 | // from being GC'd anymore:
41 | this.message = null;
42 | }
43 |
44 | this.events('deliveryStarted').emit(packet);
45 | this.movePacket(packet);
46 |
47 | if( !this.blockage )
48 | this.propagateAfterLatency(packet);
49 | };
50 |
51 | Wire.prototype.inputThrottle = function(handler){
52 | var t = throttle( this.bandwidth, handler, this);
53 |
54 | this.events('reset').on(t.reset);
55 |
56 | return t.read;
57 | };
58 |
59 | Wire.prototype.withDownstream = function(downstreamLocation){
60 | Super.prototype.withDownstream.call(this, downstreamLocation);
61 |
62 | var downstreamLocations = downstreamLocation.locations;
63 | this.locations.downstream = downstreamLocations.upstream || downstreamLocations.where;
64 |
65 | return this; // chaining
66 | };
67 |
68 | Wire.prototype._withUpstream = function(upstreamLocation){
69 | Super.prototype._withUpstream.call(this, upstreamLocation);
70 |
71 | var upstreamLocations = upstreamLocation.locations;
72 | this.locations.upstream = upstreamLocations.downstream || upstreamLocations.where;
73 |
74 | return this; // chaining
75 | };
76 |
77 | Wire.prototype.with = {
78 | 'blockedBy':function( barrier ){
79 |
80 | barrier.events('activated').on(function(){
81 |
82 | this.blockage = barrier;
83 | }.bind(this));
84 |
85 | barrier.events('deactivated').on(function(){
86 |
87 | this.blockage = null;
88 | }.bind(this));
89 | }
90 | };
91 | Wire.prototype.propagateAfterLatency = function(packet){
92 |
93 | this.schedule(function(){
94 |
95 | if( !this.blockage ) {
96 | this.propagate(packet);
97 | this.events('delivered').emit(packet);
98 | }
99 |
100 | }.bind(this), this.latency);
101 | };
102 |
103 | return Wire;
104 |
105 | }());
106 |
--------------------------------------------------------------------------------
/statics/js/demo/model/announce.js:
--------------------------------------------------------------------------------
1 |
2 | function announceAll(things){
3 | things.forEach(function( thing ){
4 | thing.announce();
5 | });
6 | }
--------------------------------------------------------------------------------
/statics/js/demo/model/datasets.js:
--------------------------------------------------------------------------------
1 |
2 | var dataSets = {
3 |
4 | // order from:
5 | // http://www.theguardian.com/news/datablog/2012/nov/06/time-states-election-results-us#data
6 | "2012UsElection":[
7 | {"state": "in", "wonBy": "rep", "votes": 11},
8 | {"state": "ky", "wonBy": "rep", "votes": 8},
9 | {"state": "fl", "wonBy": "dem", "votes": 29},
10 | {"state": "ga", "wonBy": "rep", "votes": 16},
11 | {"state": "nh", "wonBy": "dem", "votes": 4},
12 | {"state": "sc", "wonBy": "rep", "votes": 9},
13 | {"state": "vt", "wonBy": "dem", "votes": 3},
14 | {"state": "va", "wonBy": "dem", "votes": 13},
15 | {"state": "nc", "wonBy": "rep", "votes": 15},
16 | {"state": "oh", "wonBy": "dem", "votes": 18},
17 | {"state": "wv", "wonBy": "rep", "votes": 5},
18 | {"state": "al", "wonBy": "rep", "votes": 9},
19 | {"state": "ct", "wonBy": "dem", "votes": 7},
20 | {"state": "de", "wonBy": "dem", "votes": 3},
21 | {"state": "dc", "wonBy": "dem", "votes": 3},
22 | {"state": "il", "wonBy": "dem", "votes": 20},
23 | {"state": "ks", "wonBy": "rep", "votes": 6},
24 | {"state": "me", "wonBy": "dem", "votes": 4},
25 | {"state": "md", "wonBy": "dem", "votes": 10},
26 | {"state": "ma", "wonBy": "dem", "votes": 11},
27 | {"state": "mi", "wonBy": "dem", "votes": 16},
28 | {"state": "ms", "wonBy": "rep", "votes": 6},
29 | {"state": "mo", "wonBy": "rep", "votes": 10},
30 | {"state": "nj", "wonBy": "dem", "votes": 14},
31 | {"state": "nd", "wonBy": "rep", "votes": 3},
32 | {"state": "ok", "wonBy": "rep", "votes": 7},
33 | {"state": "pa", "wonBy": "dem", "votes": 20},
34 | {"state": "ri", "wonBy": "dem", "votes": 4},
35 | {"state": "tn", "wonBy": "rep", "votes": 11},
36 | {"state": "tx", "wonBy": "rep", "votes": 38},
37 | {"state": "ar", "wonBy": "rep", "votes": 6},
38 | {"state": "co", "wonBy": "dem", "votes": 9},
39 | {"state": "la", "wonBy": "rep", "votes": 8},
40 | {"state": "mn", "wonBy": "dem", "votes": 10},
41 | {"state": "ne", "wonBy": "rep", "votes": 5},
42 | {"state": "nm", "wonBy": "dem", "votes": 5},
43 | {"state": "ny", "wonBy": "dem", "votes": 29},
44 | {"state": "sd", "wonBy": "rep", "votes": 3},
45 | {"state": "wi", "wonBy": "dem", "votes": 10},
46 | {"state": "wy", "wonBy": "rep", "votes": 3},
47 | {"state": "az", "wonBy": "rep", "votes": 11},
48 | {"state": "ia", "wonBy": "dem", "votes": 6},
49 | {"state": "mt", "wonBy": "rep", "votes": 3},
50 | {"state": "nv", "wonBy": "dem", "votes": 6},
51 | {"state": "ut", "wonBy": "rep", "votes": 6},
52 | {"state": "ca", "wonBy": "dem", "votes": 55},
53 | {"state": "hi", "wonBy": "dem", "votes": 4},
54 | {"state": "id", "wonBy": "rep", "votes": 4},
55 | {"state": "or", "wonBy": "dem", "votes": 7},
56 | {"state": "wa", "wonBy": "dem", "votes": 12},
57 | {"state": "ak", "wonBy": "rep", "votes": 3}
58 | ]
59 | };
60 |
--------------------------------------------------------------------------------
/statics/js/demo/model/direction.js:
--------------------------------------------------------------------------------
1 |
2 | var Direction = {
3 | upstream: 'upstream',
4 | downstream: 'downstream'
5 | }
6 |
7 | function oppositeDirectionTo(dir) {
8 | switch(dir){
9 | case 'upstream':
10 | return 'downstream';
11 | case 'downstream':
12 | return 'upstream';
13 | }
14 | throw new Error('unknown direction' + dir);
15 | }
16 | function sameDirectionAs(dir) {
17 | return dir;
18 | }
19 |
--------------------------------------------------------------------------------
/statics/js/demo/model/multiplex.js:
--------------------------------------------------------------------------------
1 | /* Receive parsed packets from multiple streams, output all according to our parse strategy: either straight
2 | away or when all parsers have finished.
3 | */
4 |
5 | var multiplex = (function(){
6 |
7 | /* Packets from multiple sources will not have .isFirst and .isLast correctly set
8 | after multiplexing unless these properties are changed. Do that.
9 | */
10 | function resequence(numberOfResponsesExpected){
11 | var numberOfResponsesCompleted = 0,
12 | responsesStarted = false;
13 |
14 | /* Takes packets. Returns packets which are very similar but have had the ordering
15 | changed, so that their isFirst/isLast is set to be correct post-multiplexing
16 | (only one first, only one last, even if read from multiple streams where each
17 | stream yielded a first and last packet)
18 | */
19 | return function(incomingPacket){
20 |
21 | var outgoing = incomingPacket.copy();
22 | incomingPacket.done();
23 |
24 | if( incomingPacket.ordering.isLast ) {
25 | numberOfResponsesCompleted++;
26 | }
27 |
28 | outgoing.ordering.isFirst = !responsesStarted;
29 | outgoing.ordering.isLast = ( numberOfResponsesCompleted == numberOfResponsesExpected );
30 |
31 | responsesStarted = true;
32 |
33 | return outgoing;
34 | }
35 | }
36 |
37 | function multiplexProgressively(parsers, output) {
38 | for (var i in parsers) {
39 |
40 | parsers[i].events('packetParsed').on(output);
41 | }
42 | }
43 |
44 | function multiplexDiscretely(parsers, output) {
45 | var numberOfCompletedRequired = 0,
46 | numberCompleted = 0,
47 | buffer = [];
48 |
49 | function outputWhenAllHaveCompleted(packet) {
50 | if( packet.ordering.isLast ) {
51 | numberCompleted++;
52 | }
53 |
54 | buffer.push(packet);
55 |
56 | if( numberCompleted == numberOfCompletedRequired ) {
57 | buffer.forEach(function(packet){
58 | output(packet);
59 | });
60 | }
61 | }
62 |
63 | for (var i in parsers) {
64 | numberOfCompletedRequired++;
65 |
66 | parsers[i].events('packetParsed').on(outputWhenAllHaveCompleted);
67 | }
68 | }
69 |
70 | return function( strategyName, parsers, output ){
71 | var numberOfResponsesExpected = Object.keys(parsers).length,
72 | reseq = resequence(numberOfResponsesExpected);
73 |
74 | function renumberedOutput(packet){
75 | output(reseq(packet));
76 | }
77 |
78 | if( strategyName == 'progressive' ) {
79 | multiplexProgressively(parsers, renumberedOutput);
80 | } else {
81 | multiplexDiscretely(parsers, renumberedOutput);
82 | }
83 | };
84 |
85 | }());
86 |
--------------------------------------------------------------------------------
/statics/js/demo/model/throttle.js:
--------------------------------------------------------------------------------
1 | function throttle(timeBetweenPackets, send, scheduler){
2 |
3 | if( !timeBetweenPackets ) {
4 | throw new Error('no timing given');
5 | }
6 |
7 | var buffer = [];
8 |
9 | function read(receivedPacket){
10 |
11 | buffer.push(receivedPacket);
12 |
13 | if( receivedPacket.ordering.isFirst ) {
14 | slot(0);
15 | }
16 | }
17 |
18 | function slot(i) {
19 |
20 | var frontOfQueuePacket = buffer.shift();
21 |
22 | if( frontOfQueuePacket ) {
23 | send.call(scheduler, frontOfQueuePacket);
24 | }
25 |
26 | if( !(frontOfQueuePacket && frontOfQueuePacket.ordering.isLast) ) {
27 | var nextSlotIn = timeBetweenPackets(i);
28 |
29 | scheduler.schedule(
30 | function(){
31 | slot(i+1);
32 | },
33 | nextSlotIn
34 | );
35 | }
36 | }
37 |
38 | function reset() {
39 | buffer = [];
40 | }
41 |
42 | return {
43 | read: read,
44 | reset: reset
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/statics/js/demo/oop.js:
--------------------------------------------------------------------------------
1 | function extend(Sup, Sub) {
2 | Sub.prototype = Object.create(Sup.prototype);
3 | Sub.prototype.constructor = Sub;
4 | return Sub;
5 | }
6 | function abstract(){
7 | throw new Error('don\'t call me, I\'m abstract');
8 | }
--------------------------------------------------------------------------------
/statics/js/demo/pubSub.js:
--------------------------------------------------------------------------------
1 | /**
2 | * pubSub is a curried interface for listening to and emitting
3 | * events.
4 | *
5 | * If we get a bus:
6 | *
7 | * var bus = pubSub();
8 | *
9 | * We can listen to event 'foo' like:
10 | *
11 | * bus('foo').on(myCallback)
12 | *
13 | * And emit event foo like:
14 | *
15 | * bus('foo').emit()
16 | *
17 | * or, with a parameter:
18 | *
19 | * bus('foo').emit('bar')
20 | *
21 | * All functions can be cached and don't need to be
22 | * bound. Ie:
23 | *
24 | * var fooEmitter = bus('foo').emit
25 | * fooEmitter('bar'); // emit an event
26 | * fooEmitter('baz'); // emit another
27 | *
28 | * There's also an uncurried[1] shortcut for .emit and .on:
29 | *
30 | * bus.on('foo', callback)
31 | * bus.emit('foo', 'bar')
32 | *
33 | * [1]: http://zvon.org/other/haskell/Outputprelude/uncurry_f.html
34 | */
35 | function pubSub(){
36 |
37 | var singles = {},
38 | newListener = newSingle('newListener'),
39 | removeListener = newSingle('removeListener');
40 |
41 | function newSingle(eventName) {
42 | return singles[eventName] = singleEventPubSub(
43 | eventName,
44 | newListener,
45 | removeListener
46 | );
47 | }
48 |
49 | /** pubSub instances are functions */
50 | function pubSubInstance( eventName ){
51 |
52 | return singles[eventName] || newSingle( eventName );
53 | }
54 |
55 | // add convenience EventEmitter-style uncurried form of 'emit' and 'on'
56 | ['emit', 'on', 'un'].forEach(function(methodName){
57 |
58 | pubSubInstance[methodName] = varArgs(function(eventName, parameters){
59 | apply( parameters, pubSubInstance( eventName )[methodName]);
60 | });
61 | });
62 |
63 | return pubSubInstance;
64 | }
--------------------------------------------------------------------------------
/statics/js/demo/scenarioBuilder.js:
--------------------------------------------------------------------------------
1 | var getScenario = (function () {
2 |
3 | var DEFAULTS = {
4 | demo: {
5 | options: {
6 | colors: 'categorical',
7 | width: 500,
8 | height: 200,
9 | startSimulation: function(modelItems){
10 | modelItems.client.makeRequest();
11 | },
12 | endSimulationEvent:'client_acceptedAll'
13 | }
14 | },
15 | wire: {
16 | options:{
17 | bandwidth: 0, // by default, can accept packets as quickly as received
18 | latency: 1000,
19 | medium: 'cable'
20 | },
21 | locations: {}
22 | },
23 |
24 | relay: {
25 | options:{
26 | "timeBetweenPackets": 500,
27 | zoom: 1
28 | },
29 | locations:{
30 | where: {x: 235, y: 90}
31 | }
32 | },
33 |
34 | cache: {
35 | options:{
36 | "timeBetweenPackets": 500,
37 | zoom:1
38 | },
39 | locations:{
40 | where: {x: 235, y: 90}
41 | }
42 | },
43 |
44 | client: {
45 | options:{
46 | retryAfter: Number.POSITIVE_INFINITY,
47 | failAfter: Number.POSITIVE_INFINITY,
48 | aspect: 'portrait',
49 | "page": "singlePageSite",
50 | "deviceType":"desktop",
51 | "showProgress":true,
52 | parseStrategy: 'discrete',
53 | zoom: 1
54 | },
55 | locations:{
56 | where: {x: 430, y: 145}
57 | }
58 | },
59 |
60 | barrier: {
61 | locations:{
62 | where: {x: 410, y: 145}
63 | }
64 | },
65 |
66 | originServer: {
67 | options:{
68 | packetSequence: function(previousPacketNumber){
69 | return previousPacketNumber+1;
70 | },
71 | "timeBetweenPackets": 500,
72 | "initialDelay": 500,
73 | "messageSize": 10,
74 | packetMode:'live',
75 | zoom: 1
76 | },
77 | locations:{
78 | where: {x: 40, y: 55}
79 | }
80 | },
81 |
82 | narrativeItem: {
83 | script:[
84 | { delay:0,
85 | action:function () {
86 | this.popUp();
87 | }
88 | }
89 | ],
90 | options:{
91 | locationOnTopic:'where'
92 | }
93 | }
94 |
95 | };
96 |
97 | function setRelativePositions(item) {
98 | var locations = item.locations;
99 | var baseXy = locations.where;
100 | var zoom = (item.options && item.options.zoom) || 1;
101 |
102 | switch (item.type) {
103 | case 'relay':
104 | locations.upstream = translate(baseXy, {x:0, y:40});
105 | break;
106 | case 'client':
107 | switch( item.options.deviceType ){
108 | case 'mobile':
109 | locations.upstream = translate(baseXy, {x:zoom*30, y:zoom*-53});
110 | break;
111 | case 'desktop':
112 | default:
113 | locations.upstream = translate(baseXy, {x:-18, y:0});
114 | }
115 | }
116 | }
117 |
118 | function translate( xy, xyDelta ) {
119 | return {
120 | x: xy.x + xyDelta.x,
121 | y: xy.y + xyDelta.y
122 | };
123 | }
124 |
125 | function deepCopy(obj){
126 | return jQuery.extend(true, {}, obj);
127 | }
128 |
129 | function fillInDefaults(obj, defaults) {
130 | return superimpose(obj, defaults, false);
131 | }
132 |
133 | function extend(obj, extension) {
134 | return superimpose(obj, extension, true);
135 | }
136 |
137 | function superimpose(to, from, overwrite) {
138 |
139 | if( !to ) {
140 | to = {};
141 | }
142 |
143 | for( var k in from ) {
144 |
145 | if( from[k] instanceof Function ) {
146 |
147 | // functions - copy directly
148 | if( overwrite || (to[k] === undefined) )
149 | to[k] = from[k];
150 | }
151 | else if( from[k] instanceof Object ) {
152 |
153 | // objects, arrays - recursive case
154 | if( !to[k] ) {
155 | to[k] = new (from[k].constructor);
156 | }
157 |
158 | superimpose(to[k], from[k], overwrite);
159 | } else {
160 |
161 | // strings, numbers etc
162 | if( overwrite || (to[k] === undefined) )
163 | to[k] = from[k];
164 | }
165 | }
166 | return to;
167 | }
168 |
169 | function fillInTemplate(template, extensions) {
170 | var copy = deepCopy(template),
171 | extendedCopy = extend(copy, extensions);
172 |
173 | return extendedCopy;
174 | }
175 |
176 | function fillInFromBaseScenario( name ){
177 | var rawJson = scenarios[name],
178 | baseName = rawJson.baseOn;
179 |
180 | if(baseName) {
181 | var extended = fillInTemplate( fillInFromBaseScenario(baseName), rawJson.extensions);
182 |
183 | // narratives are never inherited:
184 | extended.narrative = rawJson.narrative;
185 |
186 | return extended;
187 | } else {
188 | return rawJson;
189 | }
190 | }
191 |
192 | function fillInScenarioDescription(name) {
193 |
194 | var rawJson = fillInFromBaseScenario(name);
195 |
196 | fillInDefaults(rawJson, DEFAULTS.demo);
197 |
198 | var itemsByName = {};
199 |
200 | rawJson.name = name;
201 |
202 | rawJson.items.forEach(function (item, i, items) {
203 | itemsByName[item.name] = item;
204 | });
205 |
206 | rawJson.items.forEach(function (rawItem, i, items) {
207 | // fill in next property if not explicitly given:
208 | if( ! rawItem.next ) {
209 | rawItem.next
210 | = (i < items.length-1)
211 | ? [items[i + 1].name]
212 | : [];
213 | }
214 |
215 | if( DEFAULTS[rawItem.type] ) {
216 | fillInDefaults(rawItem, DEFAULTS[rawItem.type]);
217 | }
218 |
219 | setRelativePositions(rawItem);
220 | });
221 |
222 | (rawJson.narrative||[]).forEach(function (rawItem) {
223 | fillInDefaults(rawItem, DEFAULTS[rawItem.type]);
224 | });
225 |
226 | return rawJson;
227 | }
228 |
229 | return function (name) {
230 | return scenarios[name] && fillInScenarioDescription( name );
231 | }
232 |
233 | })();
234 |
--------------------------------------------------------------------------------
/statics/js/demo/singleEventPubSub.js:
--------------------------------------------------------------------------------
1 | /**
2 | * A pub/sub which is responsible for a single event type. A
3 | * multi-event type event bus is created by pubSub by collecting
4 | * several of these.
5 | *
6 | * @param {String} eventType
7 | * the name of the events managed by this singleEventPubSub
8 | * @param {singleEventPubSub} [newListener]
9 | * place to notify of new listeners
10 | * @param {singleEventPubSub} [removeListener]
11 | * place to notify of when listeners are removed
12 | */
13 | function singleEventPubSub(eventType, newListener, removeListener){
14 |
15 | /** we are optimised for emitting events over firing them.
16 | * As well as the tuple list which stores event ids and
17 | * listeners there is a list with just the listeners which
18 | * can be iterated more quickly when we are emitting
19 | */
20 | var listenerTupleList,
21 | listenerList;
22 |
23 | function hasId(id){
24 | return function(tuple) {
25 | return tuple.id == id;
26 | };
27 | }
28 |
29 | return {
30 |
31 | /**
32 | * @param {Function} listener
33 | * @param {*} listenerId
34 | * an id that this listener can later by removed by.
35 | * Can be of any type, to be compared to other ids using ==
36 | */
37 | on:function( listener, listenerId ) {
38 |
39 | var tuple = {
40 | listener: listener
41 | , id: listenerId || listener // when no id is given use the
42 | // listener function as the id
43 | };
44 |
45 | if( newListener ) {
46 | newListener.emit(eventType, listener, tuple.id);
47 | }
48 |
49 | listenerTupleList = cons( tuple, listenerTupleList );
50 | listenerList = cons( listener, listenerList );
51 |
52 | return this; // chaining
53 | },
54 |
55 | emit:function () {
56 | applyEach( listenerList, arguments );
57 | },
58 |
59 | un: function( listenerId ) {
60 |
61 | var removed;
62 |
63 | listenerTupleList = without(
64 | listenerTupleList,
65 | hasId(listenerId),
66 | function(tuple){
67 | removed = tuple;
68 | }
69 | );
70 |
71 | if( removed ) {
72 | listenerList = without( listenerList, function(listener){
73 | return listener == removed.listener;
74 | });
75 |
76 | if( removeListener ) {
77 | removeListener.emit(eventType, removed.listener, removed.id);
78 | }
79 | }
80 | },
81 |
82 | listeners: function(){
83 | // differs from Node EventEmitter: returns list, not array
84 | return listenerList;
85 | },
86 |
87 | hasListener: function(listenerId){
88 | var test = listenerId? hasId(listenerId) : always;
89 |
90 | return defined(first( test, listenerTupleList));
91 | }
92 | };
93 | }
--------------------------------------------------------------------------------
/statics/js/demo/view/BarrierView.js:
--------------------------------------------------------------------------------
1 | var BarrierView = extend(ThingView, function(subject, demoView){
2 | ThingView.apply(this,arguments);
3 |
4 | this.initDomFromTemplate( 'barriers', 'barrier', subject.name);
5 | this.moveTo(subject.locations.where);
6 |
7 | var jClipPathContents = this.jDom.find('clipPath').children();
8 | this.putAtXy(jClipPathContents, 'translateX', 'translateY', subject.locations.where);
9 |
10 | subject.events('activated').on(this.fadeIn.bind(this));
11 | subject.events('deactivated').on(this.fadeOut.bind(this));
12 |
13 | this.initHiding();
14 | });
15 |
--------------------------------------------------------------------------------
/statics/js/demo/view/DemoView.js:
--------------------------------------------------------------------------------
1 | var DemoView = extend(ThingView, function(demo){
2 | ThingView.apply(this,arguments);
3 |
4 | var containerFigure = $("[data-demo=" + demo.name + "]");
5 |
6 | this.jDom = stampFromTemplate($('#demo'), demo.colors)
7 | .add( stampFromTemplate($('#demoCaption')) );
8 |
9 | containerFigure.append( this.jDom );
10 |
11 | this.baseWidth = demo.width;
12 | this.baseHeight = demo.height;
13 | this.setDimensions(this.baseHeight, this.scalingFactor());
14 |
15 | this.initSubviewCreation();
16 |
17 | this.resizeWithWindow();
18 |
19 | this.setupControls();
20 |
21 | this.showNewNarrativeItems();
22 |
23 | demo.events('started').on(function(){
24 | addClass(this.jDom, 'playing');
25 | }.bind(this));
26 |
27 | demo.events('reset').on(function(){
28 | removeClass(this.jDom, 'playing');
29 | }.bind(this));
30 |
31 | demo.events('paused').on(function(){
32 | addClass(this.jDom, 'paused');
33 | }.bind(this));
34 |
35 | demo.events('unpaused').on(function(){
36 | removeClass(this.jDom, 'paused');
37 | }.bind(this));
38 | });
39 |
40 | DemoView.prototype.withNarrativeView = function(narrativeView){
41 | this.narrativeView = narrativeView;
42 | };
43 |
44 | DemoView.prototype.resizeWithWindow = function(){
45 | $( window ).resize(function() {
46 | this.setDimensions(this.baseHeight, this.scalingFactor());
47 | }.bind(this));
48 | };
49 |
50 | DemoView.prototype.showNewNarrativeItems = function(){
51 | this.subject.events('NarrativeItem').on(function(narrativeItem){
52 |
53 | this.narrativeView.displayItem(narrativeItem);
54 | }.bind(this));
55 | };
56 |
57 | DemoView.prototype.initSubviewCreation = function(){
58 | this.createNewViewsForNewModelItems(Packet, PacketView);
59 | this.createNewViewsForNewModelItems(Message, MessageView);
60 | this.createNewViewsForNewModelItems(AggregatingServer, ServerView);
61 | this.createNewViewsForNewModelItems(OriginServer, ServerView);
62 | this.createNewViewsForNewModelItems(Wire, WireView);
63 | this.createNewViewsForNewModelItems(Client, ClientView);
64 | this.createNewViewsForNewModelItems(Relay, RelayView);
65 | this.createNewViewsForNewModelItems(Barrier, BarrierView);
66 | this.createNewViewsForNewModelItems(Cache, ServerView);
67 | };
68 |
69 | DemoView.prototype.createNewViewsForNewModelItems = function(ModelType, ViewType){
70 | if( !ModelType.newEvent ) {
71 | throw new Error('constructors must have .newEvent set to be listened to');
72 | }
73 |
74 | var demo = this.subject;
75 |
76 | demo.events(ModelType.newEvent).on(function(modelItem){
77 | ViewType.factory
78 | ? ViewType.factory(modelItem, this)
79 | : new ViewType(modelItem, this);
80 | }.bind(this));
81 | };
82 |
83 | DemoView.prototype.setupControls = function(){
84 | var jDom = this.jDom,
85 | jControls = jDom.find('.controls'),
86 | jFadeControls = jControls.find('.fadeControls'),
87 | jReset = jControls.find('.reset').hide(),
88 | demo = this.subject,
89 | demoEvents = demo.events;
90 |
91 | demoEvents('started').on(function(){
92 | jFadeControls.fadeOut();
93 | jReset.fadeIn();
94 | listenForClickOnReset();
95 | });
96 |
97 | demoEvents('reset').on(function(){
98 | jFadeControls.off();
99 | jReset.off();
100 |
101 | jFadeControls.fadeIn();
102 | jReset.fadeOut();
103 | listenForClickOnPlay();
104 | });
105 |
106 | function listenForClickOnPlay(){
107 | jFadeControls.one('click', function(){
108 | demo.start();
109 | });
110 | }
111 |
112 | function listenForClickOnReset(){
113 | jReset.one('click', function(){
114 | demo.reset();
115 | });
116 | }
117 |
118 | listenForClickOnPlay();
119 | };
120 |
121 | DemoView.prototype.scalingFactor = function(){
122 | var spaceAvailable = this.jDom.parents('main').width();
123 | return spaceAvailable / this.baseWidth;
124 | };
125 |
126 | DemoView.prototype.setDimensions = function(height, scalingFactor){
127 |
128 | var jSvg = this.jDom.filter('svg');
129 |
130 | jSvg.attr('height', height * scalingFactor);
131 | jSvg.attr('data-scale', scalingFactor);
132 |
133 | jSvg.find('.scaling').attr('transform', 'scale(' + scalingFactor + ')');
134 | jSvg.find('.fade').attr('height', height+1);
135 |
136 | // The container div should have the height set on
137 | // the server-side to avoid the page reflowing.
138 | jSvg.find('.reset').css('translateY', height);
139 | jSvg.find('.play').attr('y', height / 2);
140 | };
141 |
--------------------------------------------------------------------------------
/statics/js/demo/view/MessageView.js:
--------------------------------------------------------------------------------
1 | var MessageView = extend(ThingView, function(message, demoView){
2 | "use strict";
3 |
4 | ThingView.apply(this,arguments);
5 |
6 | var jContainer = this.find('.messages');
7 |
8 | // jDom is two-element selection of inner and outer part
9 | this.jDom = stampFromTemplate($('#message'), message.name);
10 | this.jPausibleElements = this.jDom;
11 |
12 | jContainer.find('.inners').append(this.jDom.filter('.messageInner'));
13 | jContainer.find('.outers').append(this.jDom.filter('.messageOuter'));
14 |
15 | this.jDom.hide();
16 |
17 | message.events('requestStartMove').on(function(xyFrom, xyTo, duration){
18 | this.jDom.show();
19 | this.goToXy( 'lineX1', 'lineY1', xyFrom);
20 | this.animateXy('lineX2', 'lineY2', xyFrom, xyTo, duration);
21 | }.bind(this));
22 |
23 | message.events('responseCloseMove').on(function(xyFrom, xyTo, duration){
24 |
25 | this.animateXy('lineX2', 'lineY2', xyFrom, xyTo, duration);
26 | }.bind(this));
27 |
28 | var remove = function(){
29 | this.jDom.remove();
30 | }.bind(this);
31 |
32 | message.events('done').on(remove);
33 | });
34 |
35 | MessageView.factory = function( message, demoView ){
36 |
37 | // at the moment, message visualisation is only supported on
38 | // transmissions over cables:
39 | if( message.holder.medium == 'cable' ) {
40 | return new MessageView(message, demoView);
41 | }
42 | };
43 |
--------------------------------------------------------------------------------
/statics/js/demo/view/NarrativeView.js:
--------------------------------------------------------------------------------
1 | var NarrativeView = (function () {
2 |
3 | var NarrativeView = extend(ThingView, function NarrativeView(demo, demoView) {
4 | ThingView.apply(this, arguments);
5 |
6 | this.jDom =
7 | demoView.jDom.find('.narrative')
8 | .add( demoView.jDom.filter('.narrative') )
9 | .hide();
10 | });
11 |
12 | NarrativeView.prototype.scaleAt = function( jEle ){
13 | // bit of a hack here:
14 | return jEle.parents('[data-scale]').attr('data-scale');
15 | };
16 |
17 | NarrativeView.prototype.captionPositionOnSmall = function( highlightPosition ){
18 | var demoWidth = this.subject.demo.width,
19 | halfWidth = (demoWidth * 0.5),
20 | highlightCloserToRight = (highlightPosition.x > halfWidth);
21 |
22 | return {
23 | x: 0, y: 0,
24 | verticalSide: "top",
25 | horizontalSide: (highlightCloserToRight? 'left' : 'right')
26 | }
27 | };
28 |
29 | NarrativeView.prototype.captionPosition = function( highlightPosition ){
30 |
31 | var demoWidth = this.subject.demo.width,
32 | demoHeight = this.subject.demo.height,
33 | halfWidth = (demoWidth * 0.5),
34 | HIGHLIGHT_SIZE = 100,
35 | highlightFromLeft = highlightPosition.x,
36 | highlightCloserToRight = (highlightPosition.x > halfWidth),
37 | highlightCloserToBottom = (highlightPosition.y > (demoHeight / 2)),
38 | flipVert = false,
39 | result = {},
40 | x;
41 |
42 | if( highlightCloserToRight ) {
43 |
44 | var highlightFromRight = demoWidth - highlightPosition.x;
45 | x = highlightFromRight + HIGHLIGHT_SIZE;
46 | result.horizontalSide = 'right';
47 | } else {
48 |
49 | x = highlightFromLeft + HIGHLIGHT_SIZE;
50 | result.horizontalSide = 'left';
51 | }
52 |
53 | var maxX = demoWidth * 0.45;
54 |
55 | if( x > maxX ) {
56 | x = maxX;
57 | flipVert = true;
58 | }
59 |
60 | result.x = x;
61 | result.y = '15';
62 |
63 | var relateToTop = (flipVert ? highlightCloserToBottom : !highlightCloserToBottom);
64 | result.verticalSide = relateToTop ? 'top' : 'bottom';
65 |
66 | return result;
67 | };
68 |
69 | NarrativeView.prototype.positionLightboxHighlightAt = function( topic, location ){
70 |
71 | var jLightbox = this.jDom.filter('.lightbox');
72 | this.putAtXy(jLightbox, 'translateX', 'translateY', location);
73 |
74 | var scale = this.scaleAt(jLightbox),
75 | isSmall = scale < 1,
76 | captionLocation = (isSmall ? this.captionPositionOnSmall(location)
77 | : this.captionPosition(location));
78 |
79 | this.jDom.filter('div.narrative')
80 | .css( {left:'', right:'', top:'', bottom:''} )
81 | .css( 'text-align', captionLocation.horizontalSide )
82 | .css( captionLocation.horizontalSide, captionLocation.x * scale * topic.zoom)
83 | .css( captionLocation.verticalSide, captionLocation.y * scale * topic.zoom);
84 | };
85 |
86 | NarrativeView.prototype.showText = function( text ){
87 | this.jDom.find('.label').text(text);
88 | };
89 |
90 | NarrativeView.prototype.showItem = function( narrativeItem ){
91 | var text = narrativeItem.text,
92 | topic = narrativeItem.topic,
93 | locationOnTopic = narrativeItem.locationOnTopic,
94 | location = topic.locations[locationOnTopic];
95 |
96 | this.showText(text);
97 | this.positionLightboxHighlightAt(topic, location);
98 | this.setZoom(topic.zoom);
99 | this.jDom.fadeIn();
100 |
101 | this.jDom.filter('.demoCaption').one('click', function(){
102 |
103 | narrativeItem.dismiss();
104 | return false;
105 | });
106 | };
107 |
108 | NarrativeView.prototype.hideItem = function(){
109 | // event handler might still be on the element if narrative was
110 | // dismissed in some way other than clicking the 'dismiss' link,
111 | // for example by clicking 'reset'
112 | this.jDom.filter('.demoCaption').off();
113 | this.jDom.fadeOut();
114 | };
115 |
116 | NarrativeView.prototype.displayItem = function( narrativeItem ){
117 |
118 | narrativeItem.events('activated').on(this.showItem.bind(this));
119 |
120 | narrativeItem.events('reset').on(this.hideItem.bind(this));
121 | narrativeItem.events('deactivated').on(this.hideItem.bind(this));
122 | };
123 |
124 | NarrativeView.newEvent = 'NarrativeView';
125 |
126 | return NarrativeView;
127 | }());
128 |
--------------------------------------------------------------------------------
/statics/js/demo/view/PacketView.js:
--------------------------------------------------------------------------------
1 | var PacketView = (function(){
2 | "use strict";
3 |
4 | var MOBILE_WAVE_OVERSHOOT = 1.66;
5 |
6 | var PacketView = extend(ThingView, function (subject, demoView) {
7 |
8 | ThingView.apply(this,arguments);
9 |
10 | subject.events('isOn').on(function( holder ){
11 |
12 | var ProxyConstructor = (holder.medium == 'mobile')
13 | ? PacketOnMobileView
14 | : PacketOnWireView
15 | ;
16 |
17 | new ProxyConstructor(subject, demoView, holder);
18 |
19 | }.bind(this));
20 |
21 | });
22 |
23 | //---------------------------------------------
24 |
25 | var PacketViewRenderer = extend(ThingView, function (packet, demoView, holder) {
26 | var packetEvents = packet.events;
27 |
28 | ThingView.call(this, packet, demoView);
29 |
30 | this.initDomFromTemplate(
31 | 'packets',
32 | this.templateName(packet),
33 | this.className(packet)
34 | );
35 |
36 | if( packet.payload ) {
37 | payloadAttributes(this.jDom, packet.payload);
38 | }
39 |
40 | packetEvents('move').on(this.animateMove.bind(this));
41 | packetEvents('done').on(this.done.bind(this));
42 | });
43 |
44 | PacketViewRenderer.prototype.className = function(subject){
45 | return subject.name + ' ' + unitClass(subject);
46 | };
47 |
48 | PacketViewRenderer.prototype.done = function(){
49 | this.jDom.remove();
50 | };
51 |
52 | //---------------------------------------------
53 |
54 | var PacketOnWireView = extend(PacketViewRenderer, function(subject, demoView){
55 | PacketViewRenderer.apply(this, arguments);
56 | });
57 |
58 | PacketOnWireView.prototype.animateMove = function( xyFrom, xyTo, duration ){
59 | this.animateXy('translateX', 'translateY', xyFrom, xyTo, duration)
60 | };
61 |
62 | PacketOnWireView.prototype.templateName = function(packet){
63 | switch(packet.type) {
64 | case 'GET':
65 | return 'getRequest';
66 | case 'JSON':
67 | return ( packet.ordering.isFirst
68 | ? 'firstPacket'
69 | : ( packet.ordering.isLast
70 | ? 'lastPacket'
71 | : 'packet'
72 | )
73 | );
74 | }
75 | };
76 |
77 | //---------------------------------------------
78 |
79 | var PacketOnMobileView = extend(PacketViewRenderer, function(subject, demoView, holder){
80 | PacketViewRenderer.apply(this, arguments);
81 |
82 | if( holder.blockage ) {
83 |
84 | var clipId = oppositeDirectionTo( subject.direction ) + '-clip';
85 |
86 | this.jDom[0].setAttribute('clip-path', 'url(#' + clipId + ')');
87 | }
88 | });
89 |
90 | PacketOnMobileView.prototype.animateMove = function( xyFrom, xyTo, duration ){
91 |
92 | function distance(xy1, xy2){
93 | function sq(n){
94 | return Math.pow(n, 2);
95 | }
96 |
97 | return Math.sqrt(sq(xy2.x - xy1.x) + sq(xy2.y - xy1.y));
98 | }
99 |
100 | var packet = this.subject,
101 | transmissionDistance = distance( xyFrom, xyTo),
102 | jAirbornePacketInTransit = this.jDom.find('.packet');
103 |
104 | this.jPausibleElements = jAirbornePacketInTransit;
105 |
106 | this.putAtXy( jAirbornePacketInTransit, 'circleX', 'circleY', xyFrom);
107 |
108 | jAirbornePacketInTransit.animate(
109 | { circleRadius: transmissionDistance * MOBILE_WAVE_OVERSHOOT,
110 | opacity: 0
111 | },
112 | { duration:duration * MOBILE_WAVE_OVERSHOOT,
113 | queue:false,
114 | complete:function(){
115 | this.jDom.remove();
116 | }.bind(this)
117 | }
118 | );
119 | this.pauseAnimationIfDemoPaused(jAirbornePacketInTransit);
120 | };
121 |
122 | PacketViewRenderer.prototype.done = function(){
123 | // when we get a done event, remove the packet after a short time.
124 | // this will do nothing in most cases because the animation will
125 | // already have removed it when it completes but here we allow
126 | // a little movement to continue after the wave hits the target
127 | window.setTimeout(function(){
128 | this.jDom.remove();
129 | }.bind(this), 500);
130 | };
131 |
132 | PacketOnMobileView.prototype.templateName = function(_packet) {
133 | return 'airwavePacket';
134 | };
135 |
136 | //---------------------------------------------
137 |
138 | return PacketView;
139 | })();
140 |
--------------------------------------------------------------------------------
/statics/js/demo/view/RelayView.js:
--------------------------------------------------------------------------------
1 | var RelayView = extend(ThingView, function(subject, demoView){
2 | ThingView.apply(this,arguments);
3 |
4 | this.initDomFromTemplate( 'places', 'tower', subject.name);
5 | this.moveTo(subject.locations.where);
6 | });
--------------------------------------------------------------------------------
/statics/js/demo/view/ServerView.js:
--------------------------------------------------------------------------------
1 | var ServerView = extend(ThingView, function(subject, demoView){
2 | ThingView.apply(this,arguments);
3 |
4 | this.initDomFromTemplate( 'places', 'server', subject.name);
5 |
6 | this.moveTo( subject.locations.where );
7 |
8 | this.writeLabel();
9 | });
10 |
--------------------------------------------------------------------------------
/statics/js/demo/view/ThingView.js:
--------------------------------------------------------------------------------
1 | var ThingView = (function(){
2 | "use strict";
3 |
4 | var FLASH_DURATION = 200;
5 |
6 | function ThingView(subject, demoView) {
7 | this.subject = subject;
8 | this.demoView = demoView;
9 |
10 | var demoEvents = this.subject.demo.events;
11 |
12 | demoEvents('paused').on(this.pause.bind(this));
13 | demoEvents('unpaused').on(this.unpause.bind(this));
14 | }
15 |
16 | ThingView.prototype.writeLabel = function() {
17 | if( this.subject.label )
18 | this.jDom.find('.label').text(this.subject.label);
19 | }
20 |
21 | ThingView.prototype.find = function(selector) {
22 | return this.demoView.jDom.find(selector);
23 | };
24 |
25 | ThingView.prototype.pause = function(){
26 | this.jPausibleElements && this.jPausibleElements.pause();
27 | };
28 |
29 | ThingView.prototype.unpause = function(){
30 | this.jPausibleElements && this.jPausibleElements.resume();
31 | };
32 |
33 | ThingView.prototype.setZoom = function(zoom){
34 | this.jDom.find('.zoom').attr('transform', 'scale(' + zoom + ')');
35 | };
36 |
37 | ThingView.prototype.stampContentsFromTemplate = function(containerSelector, templateName, className) {
38 |
39 | var jDom = stampFromTemplate($('#' + templateName), className),
40 | jContainer = this.find(containerSelector);
41 |
42 | if( jContainer.length != 1 ) {
43 | throw new Error('no one place to put the thing');
44 | }
45 |
46 | jContainer.append(jDom);
47 | return jDom;
48 | };
49 |
50 | ThingView.prototype.initHiding = function() {
51 | if( this.subject.startHidden ){
52 | this.hide();
53 |
54 | this.subject.events('reset').on(this.hide.bind(this));
55 | }
56 |
57 | this.subject.events('activated').on(this.fadeIn.bind(this));
58 | this.subject.events('deactivated').on(this.fadeOut.bind(this));
59 | };
60 |
61 | ThingView.prototype.initDomFromTemplate = function(containerClass, templateName, className) {
62 | this.jDom = this.stampContentsFromTemplate('.' + containerClass, templateName, className);
63 | this.jPausibleElements = this.jDom;
64 |
65 | return this.jDom;
66 | };
67 |
68 | ThingView.prototype.moveTo = function(where) {
69 | this.jDom.css({
70 | translateX: where.x
71 | , translateY: where.y
72 | });
73 |
74 | return this; // chaining
75 | };
76 |
77 | ThingView.prototype.putAtXy = function(jEle, xProperty, yProperty, xy){
78 | var cssObject = {};
79 |
80 | cssObject[xProperty] = xy.x;
81 | cssObject[yProperty] = xy.y;
82 |
83 | jEle.css(cssObject);
84 | };
85 |
86 | ThingView.flash = function( jEle, klass ) {
87 |
88 | addClass( jEle, klass );
89 |
90 | window.setTimeout(function(){
91 | removeClass( jEle, klass );
92 | }, FLASH_DURATION);
93 | };
94 | ThingView.prototype.flash = function( klass ) {
95 |
96 | ThingView.flash(this.jDom, klass);
97 | };
98 |
99 | ThingView.prototype.goToXy = function( xProperty, yProperty, xy ) {
100 | this.putAtXy(this.jDom, xProperty, yProperty, xy);
101 | };
102 |
103 | ThingView.prototype.animateXy = function( xProperty, yProperty, xyFrom, xyTo, duration ) {
104 |
105 | this.goToXy(xProperty, yProperty, xyFrom);
106 |
107 | var toCssObject = {};
108 | toCssObject[xProperty] = xyTo.x;
109 | toCssObject[yProperty] = xyTo.y;
110 |
111 | this.jDom.animate(toCssObject, {duration:duration, queue:false});
112 |
113 | this.pauseAnimationIfDemoPaused(this.jDom);
114 | };
115 |
116 | ThingView.prototype.pauseAnimationIfDemoPaused = function(jDom) {
117 | // To be used after a call to .animate() -
118 | // If the demo is paused, start the animation as paused
119 | // to be started later.
120 |
121 | if( this.subject.demo.paused ) {
122 | jDom.pause();
123 | }
124 | };
125 |
126 | ThingView.prototype.hide = function(){
127 | this.jDom.hide();
128 | };
129 |
130 | ThingView.prototype.fadeIn = function(){
131 | this.jDom.fadeIn();
132 | };
133 |
134 | ThingView.prototype.fadeOut = function(){
135 | this.jDom.fadeOut();
136 | };
137 |
138 | return ThingView;
139 | }());
140 |
--------------------------------------------------------------------------------
/statics/js/demo/view/WireView.js:
--------------------------------------------------------------------------------
1 | var WireView = extend(ThingView, function(subject, demoView){
2 | "use strict";
3 |
4 | ThingView.apply(this,arguments);
5 |
6 | this.initDomFromTemplate( 'wires', 'wire-' + subject.medium, subject.name);
7 |
8 | var positioners = {
9 | cable: function positionCable(jDom, upstreamLocation, downstreamLocation){
10 | jDom.css({
11 | 'lineX1': downstreamLocation.x,
12 | 'lineY1': downstreamLocation.y,
13 | 'lineX2': upstreamLocation.x,
14 | 'lineY2': upstreamLocation.y
15 | });
16 | },
17 | mobile: function positionMobile(jDom, upstreamLocation, downstreamLocation) {
18 | jDom.find('.upstream').css({
19 | 'translateX': upstreamLocation.x,
20 | 'translateY': upstreamLocation.y
21 | });
22 | jDom.find('.downstream').css({
23 | 'translateX': downstreamLocation.x,
24 | 'translateY': downstreamLocation.y
25 | });
26 | }
27 | },
28 | position = positioners[subject.medium];
29 |
30 | position( this.jDom, subject.locations.upstream, subject.locations.downstream);
31 |
32 |
33 | if( subject.medium == 'mobile' ){
34 | this.flashOnMessageStartAndEnd();
35 | }
36 |
37 | this.initHiding();
38 | });
39 |
40 | WireView.prototype.flashOnMessageStartAndEnd = function(){
41 |
42 | var aerials = {
43 | upstream: this.jDom.find('.upstream'),
44 | downstream: this.jDom.find('.downstream')
45 | };
46 |
47 | function flashAerial( packet, resolveDirection ){
48 | var unit = unitClass(packet),
49 | name = packet.name;
50 |
51 | ThingView.flash( aerials[ resolveDirection(packet.direction) ], unit );
52 | ThingView.flash( aerials[ resolveDirection(packet.direction) ], name );
53 | }
54 |
55 | this.subject.events('deliveryStarted').on(function(packet){
56 | flashAerial(packet, oppositeDirectionTo);
57 | });
58 | this.subject.events('delivered').on(function(packet){
59 | flashAerial(packet, sameDirectionAs);
60 | });
61 | };
62 |
--------------------------------------------------------------------------------
/statics/js/demo/view/payloadAttributes.js:
--------------------------------------------------------------------------------
1 | function payloadAttributes(jEle, payload){
2 |
3 | for( var k in payload ) {
4 | jEle.attr('data-' + k, payload[k]);
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/statics/js/demo/view/viewUtils.js:
--------------------------------------------------------------------------------
1 |
2 | /* jQuery doesn't like adding classes to SVG elements */
3 | function addClass(jEle, klass) {
4 | var ele = jEle[0],
5 | newClasses = ele.hasAttribute('class')
6 | ? ele.getAttribute('class') + ' ' + klass
7 | : klass
8 | ;
9 | ele.setAttribute('class', newClasses);
10 | }
11 | function removeClass(jEle, klass) {
12 | var ele = jEle[0];
13 | ele.setAttribute('class',
14 | ele.getAttribute('class')
15 | .split(' ')
16 | .filter(function(c){return c != klass})
17 | .join(' ')
18 | );
19 | }
20 |
21 | /**
22 | *
23 | * @param jTemplate
24 | * @param klass
25 | * @returns {jQuery}
26 | */
27 | function stampFromTemplate(jTemplate, klass) {
28 | var jCopy;
29 |
30 | if( !jTemplate.length )
31 | throw new Error('nothing in the template');
32 |
33 | if( jTemplate[0].content ) {
34 |
35 | jCopy = $( document.importNode(jTemplate[0].content.querySelector('*'), true) );
36 | } else {
37 |
38 | jCopy = jTemplate.children().clone();
39 | }
40 |
41 | if( klass )
42 | addClass(jCopy, klass);
43 |
44 | return jCopy;
45 | }
46 |
47 |
48 | function unitClass(packet) {
49 | return 'unit-' + (packet.ordering.i % 10);
50 | }
51 |
52 |
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/statics/js/demo/wire.js:
--------------------------------------------------------------------------------
1 |
2 | function loadScenario(scenarioId, autoplay) {
3 |
4 | var MODEL_TYPES = {
5 | "originServer": OriginServer,
6 | "wire": Wire,
7 | "client": Client,
8 | "aggregatingServer": AggregatingServer,
9 | "barrier": Barrier,
10 | "relay": Relay,
11 | "cache": Cache,
12 | "narrativeItem": NarrativeItem
13 | };
14 |
15 | var scenario = getScenario(scenarioId);
16 |
17 | if( !scenario ) {
18 | console.warn('no scenario for ' + scenarioId);
19 | return;
20 | }
21 |
22 | var modelItems = {};
23 |
24 | function makeModel(jsonDescription){
25 | var Type = MODEL_TYPES[jsonDescription.type];
26 |
27 | return new Type(
28 | jsonDescription.name,
29 | (jsonDescription.locations || {}),
30 | (jsonDescription.options || {})
31 | );
32 | }
33 |
34 | function wireRelationships(modelItem, json) {
35 | var links = json.relationships || {};
36 |
37 | for (var relationship in links) {
38 | var otherItemName = links[relationship],
39 | otherModelItem = modelItems[ otherItemName ];
40 |
41 | modelItem.with[relationship].call(modelItem, otherModelItem);
42 | }
43 | }
44 |
45 | function createAndWire(scenarioItem){
46 |
47 | var script = scenarioItem.script || [];
48 |
49 | return makeModel(scenarioItem)
50 | .inDemo(demo)
51 | .followingScript(script);
52 | }
53 |
54 | function createAndWireModelItem(scenarioItem){
55 | modelItems[scenarioItem.name] = createAndWire(scenarioItem);
56 | }
57 |
58 | // init the model items
59 | var demo
60 | = modelItems.demo
61 | = new Demo(scenarioId, (scenario.options || {}));
62 |
63 | var demoView = new DemoView(demo);
64 |
65 | demoView.withNarrativeView(new NarrativeView(demo, demoView));
66 |
67 | scenario.items.forEach(createAndWireModelItem);
68 |
69 | // link up- / downstream model items to each other
70 | scenario.items.forEach(function(scenarioItem){
71 | var modelItem = modelItems[scenarioItem.name];
72 |
73 | scenarioItem.next.forEach(function( nextScenarioName ){
74 |
75 | if( !modelItems[nextScenarioName] ) {
76 | throw new Error(
77 | 'no such item as ' + nextScenarioName +
78 | ' given as downstream of ' + scenarioItem.name
79 | );
80 | }
81 |
82 | modelItem.withDownstream( modelItems[nextScenarioName] );
83 | });
84 | });
85 |
86 | // link up model items which refer to each other by name:
87 | scenario.items.forEach(function(scenarioItem){
88 |
89 | var modelItem = modelItems[scenarioItem.name];
90 |
91 | wireRelationships(modelItem, scenarioItem);
92 | });
93 |
94 | // create and wire the narrative:
95 | (scenario.narrative || []).forEach(function(narrativeJson){
96 |
97 | var narrativeItem = createAndWire(narrativeJson);
98 | wireRelationships(narrativeItem, narrativeJson);
99 | narrativeItem.announce();
100 | });
101 |
102 | // announce all the new model items
103 | scenario.items.forEach(function(scenarioItem){
104 | modelItems[scenarioItem.name].announce();
105 | });
106 |
107 | demo.startSimulation = function() {
108 | scenario.options.startSimulation.call(demo, modelItems);
109 | }
110 |
111 | if( autoplay ) {
112 | demo.start();
113 | }
114 | }
115 |
116 | $(function(){
117 | $('[data-demo]').each(function( _i, element ){
118 | loadScenario( element.getAttribute('data-demo'), element.hasAttribute('data-autoplay') );
119 | })
120 | recordHeadingsPosition();
121 | updateActiveHeading();
122 | });
123 |
--------------------------------------------------------------------------------
/statics/js/internalNav.js:
--------------------------------------------------------------------------------
1 | /* this file is stuff that css doesn't do yet, or that I'm too lazy to do in css
2 | * It is not OO at all unlike the other JS. Pretty bad perhaps. */
3 |
4 | var MIN_SIZE_FOR_TWO_COL = 950,
5 | SIZE_REQUIRING_PHONE_LAYOUT = 600;
6 |
7 | var jWindow = $(window);
8 |
9 | var headings = [];
10 |
11 | var siteNavStickyOptions = {
12 | getWidthFrom: '#pageArea',
13 | topSpacing: 0
14 | };
15 |
16 | function recordHeadingsPosition(){
17 | headings = $('main h2').map(function(i, el) {
18 | return {
19 | top: $(el).offset().top,
20 | id: el.id
21 | };
22 | });
23 | }
24 |
25 | function closestHeading() {
26 | var h;
27 | var top = jWindow.scrollTop() +100;
28 | var i = headings.length;
29 | while (i--) {
30 | h = headings[i];
31 | if (top >= h.top)
32 | return h;
33 | }
34 | }
35 |
36 | var prevHeading;
37 | function updateActiveHeading() {
38 | if( headings.length == 0 ) {
39 | return;
40 | }
41 |
42 | var activeHeading = closestHeading();
43 |
44 | if (!activeHeading)
45 | activeHeading = headings.first()[0];
46 |
47 | if (prevHeading) {
48 | prevHeading.removeClass('active');
49 | }
50 |
51 | var a = $('a[href="#' + activeHeading.id + '"]');
52 | a.addClass('active');
53 |
54 | prevHeading = a;
55 | }
56 |
57 | jWindow.resize(function() {
58 | recordHeadingsPosition();
59 | updateActiveHeading();
60 | initSticky();
61 | });
62 |
63 | function initSticky(){
64 | console.log('handling stickying');
65 |
66 | var jSiteNav = $('#siteNav');
67 |
68 | // make internal nav sticky
69 | if( jWindow.width() > SIZE_REQUIRING_PHONE_LAYOUT ) {
70 | jSiteNav.sticky(siteNavStickyOptions);
71 | } else {
72 | jSiteNav.sticky('unstick');
73 | }
74 |
75 | if( jWindow.width() > MIN_SIZE_FOR_TWO_COL ) {
76 |
77 | // make internal nav sticky
78 | $('.internalNav').sticky({
79 | topSpacing:28
80 | , getWidthFrom:'.col1'
81 | });
82 | } else {
83 | $('.internalNav').sticky('unstick');
84 | }
85 | }
86 |
87 |
88 | $(function(){
89 |
90 | var jWindow = $(window),
91 | jReducedLogo = $('.reducedLogo'),
92 | jSiteNav = $('#siteNav');
93 |
94 | $('svg.menuButton').click(function() {
95 | jSiteNav.toggleClass('open')
96 |
97 | jSiteNav.sticky('restick', siteNavStickyOptions);
98 | });
99 |
100 |
101 | jWindow.scroll(function() {
102 | var pos = jWindow.scrollTop();
103 |
104 | jReducedLogo.toggleClass('show', pos > 240);
105 | });
106 |
107 | initSticky();
108 |
109 | if( jWindow.width() > MIN_SIZE_FOR_TWO_COL ) {
110 |
111 | // highlight active item on internal nav
112 | recordHeadingsPosition();
113 | updateActiveHeading();
114 |
115 | if( headings.length ) {
116 | $(document).scroll(updateActiveHeading);
117 | updateActiveHeading();
118 | }
119 |
120 | // set up smooth scrolling for internal links
121 | // http://css-tricks.com/snippets/jquery/smooth-scrolling/
122 | var jHtmlBody = $('html,body');
123 |
124 | $('ul.sections a').click(function() {
125 | if (location.pathname.replace(/^\//,'') == this.pathname.replace(/^\//,'') && location.hostname == this.hostname) {
126 | var target = $(this.hash);
127 | target = target.length ? target : $('[name=' + this.hash.slice(1) +']');
128 | if (target.length) {
129 | jHtmlBody.animate({
130 | scrollTop: (target.offset().top - 70)
131 | }, 500);
132 | return false;
133 | }
134 | }
135 | });
136 | }
137 | });
138 |
139 |
140 |
--------------------------------------------------------------------------------
/statics/js/jquery.easing.js:
--------------------------------------------------------------------------------
1 | /*
2 | * jQuery Easing v1.3 - http://gsgd.co.uk/sandbox/jquery/easing/
3 | *
4 | * Uses the built in easing capabilities added In jQuery 1.1
5 | * to offer multiple easing options
6 | *
7 | * TERMS OF USE - jQuery Easing
8 | *
9 | * Open source under the BSD License.
10 | *
11 | * Copyright © 2008 George McGinley Smith
12 | * All rights reserved.
13 | *
14 | * Redistribution and use in source and binary forms, with or without modification,
15 | * are permitted provided that the following conditions are met:
16 | *
17 | * Redistributions of source code must retain the above copyright notice, this list of
18 | * conditions and the following disclaimer.
19 | * Redistributions in binary form must reproduce the above copyright notice, this list
20 | * of conditions and the following disclaimer in the documentation and/or other materials
21 | * provided with the distribution.
22 | *
23 | * Neither the name of the author nor the names of contributors may be used to endorse
24 | * or promote products derived from this software without specific prior written permission.
25 | *
26 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
27 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
28 | * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
29 | * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
30 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
31 | * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
32 | * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
33 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
34 | * OF THE POSSIBILITY OF SUCH DAMAGE.
35 | *
36 | */
37 |
38 | // t: current time, b: begInnIng value, c: change In value, d: duration
39 | jQuery.easing.easeOutBounce = function (x, t, b, c, d) {
40 | if ((t/=d) < (1/2.75)) {
41 | return c*(7.5625*t*t) + b;
42 | } else if (t < (2/2.75)) {
43 | return c*(7.5625*(t-=(1.5/2.75))*t + .75) + b;
44 | } else if (t < (2.5/2.75)) {
45 | return c*(7.5625*(t-=(2.25/2.75))*t + .9375) + b;
46 | } else {
47 | return c*(7.5625*(t-=(2.625/2.75))*t + .984375) + b;
48 | }
49 | };
50 |
--------------------------------------------------------------------------------
/statics/js/jquery.inview.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimhigson/oboe.js-website/0d768e740a77c8e52171d3e5ceef7dc0a0680921/statics/js/jquery.inview.js
--------------------------------------------------------------------------------
/statics/js/jquery.pause.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Pause jQuery plugin v0.1
3 | *
4 | * Copyright 2010 by Tobia Conforto
5 | *
6 | * Based on Pause-resume-animation jQuery plugin by Joe Weitzel
7 | *
8 | * This program is free software; you can redistribute it and/or modify it
9 | * under the terms of the GNU General Public License as published by the Free
10 | * Software Foundation; either version 2 of the License, or(at your option)
11 | * any later version.
12 | *
13 | * This program is distributed in the hope that it will be useful, but WITHOUT
14 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
15 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
16 | * more details.
17 | *
18 | * You should have received a copy of the GNU General Public License along with
19 | * this program; if not, write to the Free Software Foundation, Inc., 51
20 | * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 | */
22 | /* Changelog:
23 | *
24 | * 0.1 2010-06-13 Initial release
25 | */
26 | (function() {
27 | var $ = jQuery,
28 | pauseId = 'jQuery.pause',
29 | uuid = 1,
30 | oldAnimate = $.fn.animate,
31 | anims = {};
32 |
33 | function now() { return new Date().getTime(); }
34 |
35 | $.fn.animate = function(prop, speed, easing, callback) {
36 | var optall = $.speed(speed, easing, callback);
37 | optall.complete = optall.old; // unwrap callback
38 | return this.each(function() {
39 | // check pauseId
40 | if (! this[pauseId])
41 | this[pauseId] = uuid++;
42 | // start animation
43 | var opt = $.extend({}, optall);
44 | oldAnimate.apply($(this), [prop, $.extend({}, opt)]);
45 | // store data
46 | anims[this[pauseId]] = {
47 | run: true,
48 | prop: prop,
49 | opt: opt,
50 | start: now(),
51 | done: 0
52 | };
53 | });
54 | };
55 |
56 | $.fn.pause = function() {
57 | return this.each(function() {
58 | // check pauseId
59 | if (! this[pauseId])
60 | this[pauseId] = uuid++;
61 | // fetch data
62 | var data = anims[this[pauseId]];
63 | if (data && data.run) {
64 | data.done += now() - data.start;
65 | if (data.done > data.opt.duration) {
66 | // remove stale entry
67 | delete anims[this[pauseId]];
68 | } else {
69 | // pause animation
70 | $(this).stop();
71 | data.run = false;
72 | }
73 | }
74 | });
75 | };
76 |
77 | $.fn.resume = function() {
78 | return this.each(function() {
79 | // check pauseId
80 | if (! this[pauseId])
81 | this[pauseId] = uuid++;
82 | // fetch data
83 | var data = anims[this[pauseId]];
84 | if (data && ! data.run) {
85 | // resume animation
86 | data.opt.duration -= data.done;
87 | data.done = 0;
88 | data.run = true;
89 | data.start = now();
90 | oldAnimate.apply($(this), [data.prop, $.extend({}, data.opt)]);
91 | }
92 | });
93 | };
94 | })();
95 |
--------------------------------------------------------------------------------
/statics/js/jquery.sticky.js:
--------------------------------------------------------------------------------
1 | // Sticky Plugin v1.0.0 for jQuery
2 | // =============
3 | // Author: Anthony Garand
4 | // Improvements by German M. Bravo (Kronuz) and Ruud Kamphuis (ruudk)
5 | // Improvements by Leonardo C. Daronco (daronco)
6 | // Created: 2/14/2011
7 | // Date: 2/12/2012
8 | // Website: http://labs.anthonygarand.com/sticky
9 | // Description: Makes an element on the page stick on the screen as you scroll
10 | // It will only set the 'top' and 'position' of your element, you
11 | // might need to adjust the width in some cases.
12 |
13 | (function($) {
14 | var defaults = {
15 | topSpacing: 0,
16 | bottomSpacing: 0,
17 | className: 'is-sticky',
18 | wrapperClassName: 'sticky-wrapper',
19 | center: false,
20 | getWidthFrom: ''
21 | },
22 | $window = $(window),
23 | $document = $(document),
24 | sticked = [],
25 | windowHeight = $window.height(),
26 | scroller = function() {
27 | var scrollTop = $window.scrollTop(),
28 | documentHeight = $document.height(),
29 | dwh = documentHeight - windowHeight,
30 | extra = (scrollTop > dwh) ? dwh - scrollTop : 0;
31 |
32 | for (var i = 0; i < sticked.length; i++) {
33 | var s = sticked[i],
34 | elementTop = s.stickyWrapper.offset().top,
35 | etse = elementTop - s.topSpacing - extra;
36 |
37 | if (scrollTop <= etse) {
38 | if (s.currentTop !== null) {
39 | s.stickyElement
40 | .css('position', '')
41 | .css('top', '');
42 | s.stickyElement.parent().removeClass(s.className);
43 | s.currentTop = null;
44 | }
45 | }
46 | else {
47 | var newTop = documentHeight - s.stickyElement.outerHeight()
48 | - s.topSpacing - s.bottomSpacing - scrollTop - extra;
49 | if (newTop < 0) {
50 | newTop = newTop + s.topSpacing;
51 | } else {
52 | newTop = s.topSpacing;
53 | }
54 | if (s.currentTop != newTop) {
55 | s.stickyElement
56 | .css('position', 'fixed')
57 | .css('top', newTop);
58 |
59 | if (typeof s.getWidthFrom !== 'undefined') {
60 | s.stickyElement.css('width', $(s.getWidthFrom).width());
61 | }
62 |
63 | s.stickyElement.parent().addClass(s.className);
64 | s.currentTop = newTop;
65 | }
66 | }
67 | }
68 | },
69 | resizer = function() {
70 | windowHeight = $window.height();
71 | },
72 | methods = {
73 | init: function(options) {
74 | var o = $.extend(defaults, options);
75 | var rtn = this.each(function() {
76 | var stickyElement = $(this),
77 |
78 | isStuckAlready = sticked.some(function( stickedItem ){
79 | return stickedItem.stickyElement[0] == stickyElement[0];
80 | });
81 |
82 | if( isStuckAlready ) {
83 | return;
84 | }
85 |
86 | var stickyId = stickyElement.attr('id');
87 | var wrapper = $('')
88 | .attr('id', stickyId + '-sticky-wrapper')
89 | .addClass(o.wrapperClassName);
90 | stickyElement.wrapAll(wrapper);
91 |
92 | if (o.center) {
93 | stickyElement.parent().css({width:stickyElement.outerWidth(),marginLeft:"auto",marginRight:"auto"});
94 | }
95 |
96 | if (stickyElement.css("float") == "right") {
97 | stickyElement.css({"float":"none"}).parent().css({"float":"right"});
98 | }
99 |
100 | var stickyWrapper = stickyElement.parent();
101 | stickyWrapper.css('height', stickyElement.outerHeight());
102 | sticked.push({
103 | topSpacing: o.topSpacing,
104 | bottomSpacing: o.bottomSpacing,
105 | stickyElement: stickyElement,
106 | currentTop: null,
107 | stickyWrapper: stickyWrapper,
108 | className: o.className,
109 | getWidthFrom: o.getWidthFrom
110 | });
111 | });
112 | scroller(); // might already need sticking
113 | return rtn;
114 | },
115 | unstick: function(){
116 | return this.each(function() {
117 |
118 | var stickyElement = $(this),
119 | wrapped = stickyElement.parent().hasClass('sticky-wrapper');
120 |
121 | if( wrapped ) {
122 | stickyElement.unwrap();
123 | }
124 |
125 | stickyElement.attr('style', '');
126 |
127 | sticked = sticked.filter(function( stickedItem ){
128 | return stickedItem.stickyElement[0] != stickyElement[0];
129 | });
130 | });
131 | },
132 | restick: function() {
133 | methods.unstick.apply(this);
134 | methods.init.apply(this, arguments);
135 | scroller();
136 | },
137 | update: scroller
138 | };
139 |
140 | // should be more efficient than using $window.scroll(scroller) and $window.resize(resizer):
141 | if (window.addEventListener) {
142 | window.addEventListener('scroll', scroller, false);
143 | window.addEventListener('resize', resizer, false);
144 | } else if (window.attachEvent) {
145 | window.attachEvent('onscroll', scroller);
146 | window.attachEvent('onresize', resizer);
147 | }
148 |
149 | $.fn.sticky = function(method) {
150 | if (methods[method]) {
151 | return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
152 | } else if (typeof method === 'object' || !method ) {
153 | return methods.init.apply( this, arguments );
154 | } else {
155 | $.error('Method ' + method + ' does not exist on jQuery.sticky');
156 | }
157 | };
158 | $(function() {
159 | setTimeout(scroller, 0);
160 | });
161 | })(jQuery);
162 |
--------------------------------------------------------------------------------
/statics/js/pollyfill.js:
--------------------------------------------------------------------------------
1 | // ECMAScript6 Number.isFiniate
2 |
3 | (function (global) {
4 | var global_isFinite = global.isFinite;
5 |
6 | Object.defineProperty(Number, 'isFinite', {
7 | value: function isFinite(value) {
8 | return typeof value === 'number' && global_isFinite(value);
9 | },
10 | configurable: true,
11 | enumerable: false,
12 | writable: true
13 | });
14 | })(this);
15 |
--------------------------------------------------------------------------------
/statics/type/cmunrm.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimhigson/oboe.js-website/0d768e740a77c8e52171d3e5ceef7dc0a0680921/statics/type/cmunrm.eot
--------------------------------------------------------------------------------
/statics/type/cmunrm.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimhigson/oboe.js-website/0d768e740a77c8e52171d3e5ceef7dc0a0680921/statics/type/cmunrm.woff
--------------------------------------------------------------------------------
/statics/type/cmunti.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimhigson/oboe.js-website/0d768e740a77c8e52171d3e5ceef7dc0a0680921/statics/type/cmunti.eot
--------------------------------------------------------------------------------
/statics/type/cmunti.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimhigson/oboe.js-website/0d768e740a77c8e52171d3e5ceef7dc0a0680921/statics/type/cmunti.ttf
--------------------------------------------------------------------------------
/statics/type/cmunti.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimhigson/oboe.js-website/0d768e740a77c8e52171d3e5ceef7dc0a0680921/statics/type/cmunti.woff
--------------------------------------------------------------------------------
/statics/type/cmuntt.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimhigson/oboe.js-website/0d768e740a77c8e52171d3e5ceef7dc0a0680921/statics/type/cmuntt.eot
--------------------------------------------------------------------------------
/statics/type/cmuntt.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimhigson/oboe.js-website/0d768e740a77c8e52171d3e5ceef7dc0a0680921/statics/type/cmuntt.ttf
--------------------------------------------------------------------------------
/statics/type/cmuntt.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimhigson/oboe.js-website/0d768e740a77c8e52171d3e5ceef7dc0a0680921/statics/type/cmuntt.woff
--------------------------------------------------------------------------------
/svgSource/cartogram.svg:
--------------------------------------------------------------------------------
1 |
2 |
61 |
--------------------------------------------------------------------------------
/svgSource/logo-github.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/svgSource/logo-google-groups.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/svgSource/logo-twitter.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/svgSource/mapvis.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimhigson/oboe.js-website/0d768e740a77c8e52171d3e5ceef7dc0a0680921/svgSource/mapvis.png
--------------------------------------------------------------------------------
/svgSource/mobileMenu.inkscape.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
93 |
--------------------------------------------------------------------------------
/svgSource/mobileMenu.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
--------------------------------------------------------------------------------
/views/raw.handlebars:
--------------------------------------------------------------------------------
1 | {{{content}}}
--------------------------------------------------------------------------------