├── .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