From 06f9e425c9b8f59f6a19f1fb65f82719b67791a7 Mon Sep 17 00:00:00 2001 From: Jim Cowart Date: Fri, 19 Jun 2015 20:38:50 -0400 Subject: [PATCH] Adding Examples from Repo --- example/atm/README.md | 12 + example/atm/index.html | 40 ++ example/atm/js/AtmFsm.js | 60 +++ example/atm/js/NavFsm.js | 33 ++ example/atm/js/Repository.js | 78 +++ example/atm/js/amplify.diagnostics.js | 11 + example/atm/js/appConfig.js | 17 + example/atm/js/main.js | 80 +++ example/atm/js/models/AccountInfo.js | 12 + example/atm/js/models/AccountModel.js | 11 + example/atm/js/models/AppModel.js | 3 + example/atm/js/models/DepositModel.js | 6 + example/atm/js/models/LoginModel.js | 7 + example/atm/js/models/ResultModel.js | 5 + example/atm/js/models/WithdrawalModel.js | 6 + example/atm/js/router.js | 93 ++++ example/atm/js/views/AccountView.js | 22 + example/atm/js/views/AppView.js | 25 + example/atm/js/views/DepositView.js | 52 ++ example/atm/js/views/LoginView.js | 53 ++ example/atm/js/views/NavView.js | 70 +++ example/atm/js/views/ResultView.js | 21 + example/atm/js/views/WithdrawalView.js | 52 ++ example/atm/style.css | 133 +++++ example/atm/templates/account.html | 10 + example/atm/templates/app.html | 5 + example/atm/templates/current-client.html | 1 + example/atm/templates/deposit.html | 15 + example/atm/templates/login.html | 16 + example/atm/templates/nav.html | 18 + example/atm/templates/result.html | 17 + example/atm/templates/withdrawal.html | 17 + example/connectivity/README.md | 15 + example/connectivity/css/normalize.css | 375 +++++++++++++++ example/connectivity/css/style.css | 454 ++++++++++++++++++ .../font/DIGITALDREAMNARROW-webfont.eot | Bin 0 -> 33204 bytes .../font/DIGITALDREAMNARROW-webfont.svg | 172 +++++++ .../font/DIGITALDREAMNARROW-webfont.ttf | Bin 0 -> 32992 bytes .../font/DIGITALDREAMNARROW-webfont.woff | Bin 0 -> 8716 bytes .../font/pizzadude.dk License.txt | 8 + .../connectivity/img/equipment-front-2x.png | Bin 0 -> 9354 bytes example/connectivity/img/equipment-front.png | Bin 0 -> 4052 bytes example/connectivity/index.html | 12 + example/connectivity/js/app.js | 26 + example/connectivity/js/bus.js | 15 + example/connectivity/js/connectivityFsm.js | 75 +++ example/connectivity/js/main.js | 52 ++ example/connectivity/js/mainView.js | 147 ++++++ example/connectivity/js/stethoscope.js | 28 ++ .../connectivity/js/template/mainView.html | 18 + example/hierarchical/index.html | 16 + example/hierarchical/js/main.js | 179 +++++++ example/load/README.md | 9 + example/load/ajax-loader.gif | Bin 0 -> 8238 bytes example/load/index.html | 23 + example/load/js/appCfg.js | 17 + example/load/js/main.js | 109 +++++ example/load/js/views.js | 74 +++ example/load/style.css | 74 +++ example/load/templates/failure.html | 5 + example/load/templates/headlines.html | 15 + example/load/templates/main.html | 11 + ext/infuser_all.js | 227 +++++++++ ext/livereloadhack.js | 9 + index.html | 35 +- 65 files changed, 3190 insertions(+), 11 deletions(-) create mode 100644 example/atm/README.md create mode 100644 example/atm/index.html create mode 100644 example/atm/js/AtmFsm.js create mode 100644 example/atm/js/NavFsm.js create mode 100644 example/atm/js/Repository.js create mode 100644 example/atm/js/amplify.diagnostics.js create mode 100644 example/atm/js/appConfig.js create mode 100644 example/atm/js/main.js create mode 100644 example/atm/js/models/AccountInfo.js create mode 100644 example/atm/js/models/AccountModel.js create mode 100644 example/atm/js/models/AppModel.js create mode 100644 example/atm/js/models/DepositModel.js create mode 100644 example/atm/js/models/LoginModel.js create mode 100644 example/atm/js/models/ResultModel.js create mode 100644 example/atm/js/models/WithdrawalModel.js create mode 100644 example/atm/js/router.js create mode 100644 example/atm/js/views/AccountView.js create mode 100644 example/atm/js/views/AppView.js create mode 100644 example/atm/js/views/DepositView.js create mode 100644 example/atm/js/views/LoginView.js create mode 100644 example/atm/js/views/NavView.js create mode 100644 example/atm/js/views/ResultView.js create mode 100644 example/atm/js/views/WithdrawalView.js create mode 100644 example/atm/style.css create mode 100644 example/atm/templates/account.html create mode 100644 example/atm/templates/app.html create mode 100644 example/atm/templates/current-client.html create mode 100644 example/atm/templates/deposit.html create mode 100644 example/atm/templates/login.html create mode 100644 example/atm/templates/nav.html create mode 100644 example/atm/templates/result.html create mode 100644 example/atm/templates/withdrawal.html create mode 100644 example/connectivity/README.md create mode 100644 example/connectivity/css/normalize.css create mode 100644 example/connectivity/css/style.css create mode 100644 example/connectivity/font/DIGITALDREAMNARROW-webfont.eot create mode 100644 example/connectivity/font/DIGITALDREAMNARROW-webfont.svg create mode 100644 example/connectivity/font/DIGITALDREAMNARROW-webfont.ttf create mode 100644 example/connectivity/font/DIGITALDREAMNARROW-webfont.woff create mode 100644 example/connectivity/font/pizzadude.dk License.txt create mode 100644 example/connectivity/img/equipment-front-2x.png create mode 100644 example/connectivity/img/equipment-front.png create mode 100644 example/connectivity/index.html create mode 100644 example/connectivity/js/app.js create mode 100644 example/connectivity/js/bus.js create mode 100644 example/connectivity/js/connectivityFsm.js create mode 100644 example/connectivity/js/main.js create mode 100644 example/connectivity/js/mainView.js create mode 100644 example/connectivity/js/stethoscope.js create mode 100644 example/connectivity/js/template/mainView.html create mode 100644 example/hierarchical/index.html create mode 100644 example/hierarchical/js/main.js create mode 100644 example/load/README.md create mode 100644 example/load/ajax-loader.gif create mode 100644 example/load/index.html create mode 100644 example/load/js/appCfg.js create mode 100644 example/load/js/main.js create mode 100644 example/load/js/views.js create mode 100644 example/load/style.css create mode 100644 example/load/templates/failure.html create mode 100644 example/load/templates/headlines.html create mode 100644 example/load/templates/main.html create mode 100644 ext/infuser_all.js create mode 100644 ext/livereloadhack.js diff --git a/example/atm/README.md b/example/atm/README.md new file mode 100644 index 0000000..525b9ce --- /dev/null +++ b/example/atm/README.md @@ -0,0 +1,12 @@ +# ATM Example + +An example application where one FSM acts as the "brains" for an ATM machine (AtmFsm.js), and another controls how the nav bar is displayed (NavFsm.js), depending on the state of the application. + +There are two sets of credentials that will work with this sample app: + +* acct 123456789, pin 8675 +* acct 987654321, pin 3090 + +This example application was intentionally written without message-bus interaction in order to demonstrate direct API usage. For a message-bus-integrated example, please see the "load" sample app. + +Even though messaging is not actively used in this example, it *does* demonstrate the auto-wireup to amplify.js's pub/sub. The auto-wireup to amplify provides a wiretap, which causes any FSM events to be printed to the console. diff --git a/example/atm/index.html b/example/atm/index.html new file mode 100644 index 0000000..d25a0a9 --- /dev/null +++ b/example/atm/index.html @@ -0,0 +1,40 @@ + + + + + ATM State Machine Example + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + diff --git a/example/atm/js/AtmFsm.js b/example/atm/js/AtmFsm.js new file mode 100644 index 0000000..9304760 --- /dev/null +++ b/example/atm/js/AtmFsm.js @@ -0,0 +1,60 @@ +/* + This FSM generates the following custom events: + "Initialized", + "Authorized", + "UnAuthorized", + "Deposit", + "Withdrawal", + "OverLimit", + "Result" + */ +var Atm = function() { + var fsm; + fsm = new machina.Fsm( { + initialState: "uninitialized", + states: { + "uninitialized": { + "initialize": function() { + // TODO: any other init work here... + this.emit( "Initialized" ); + this.transition( "unauthorized" ); + } + }, + "unauthorized": { + _onEnter: function() { + this.emit( "UnAuthorized", { msg: "Please enter your account and PIN." } ); + }, + "*": function() { + this.emit( "UnAuthorized", { msg: "You must authenticate first." } ); + }, + authorize: function( credentials ) { + if ( authRepository.authorize( credentials.acct, credentials.pin ) ) { + this.acct = credentials.acct; + this.transition( "authorized" ); + return; + } + this.emit( "UnAuthorized", { msg: "Invalid Account and/or PIN." } ); + } + }, + "authorized": { + _onEnter: function() { + this.emit( "Authorized", { acct: this.acct } ); + }, + deposit: function( amount ) { + var result = clientRepository.deposit( this.acct, amount ); + this.emit( "Result", result ); + }, + withdrawal: function( amount ) { + var result = clientRepository.withdrawal( this.acct, amount ); + this.emit( "Result", result ); + }, + deauthorize: function() { + authRepository.deauthorize( this.acct ); + delete this.acct; + this.transition( "unauthorized" ); + } + } + } + } ); + return fsm; +}; diff --git a/example/atm/js/NavFsm.js b/example/atm/js/NavFsm.js new file mode 100644 index 0000000..8e42ba1 --- /dev/null +++ b/example/atm/js/NavFsm.js @@ -0,0 +1,33 @@ +var NavFsm = function( appFsm, navView ) { + var fsm = new machina.Fsm( { + initialState: "unauthorized", + states: { + "unauthorized": { + _onEnter: function() { + navView.unAuthLayout(); + }, + "*": function() { + navView.unAuthLayout(); + } + }, + "authorized": { + "*": function() { + navView.authLayout(); + }, + "deposit": function() { + navView.depositLayout(); + }, + "withdrawal": function() { + navView.withdrawalLayout(); + } + } + } + } ); + appFsm.on( "Authorized", function() { + fsm.transition( "authorized" ); + } ); + appFsm.on( "UnAuthorized", function() { + fsm.transition( "unauthorized" ); + } ); + return fsm; +}; diff --git a/example/atm/js/Repository.js b/example/atm/js/Repository.js new file mode 100644 index 0000000..207b313 --- /dev/null +++ b/example/atm/js/Repository.js @@ -0,0 +1,78 @@ +var clientRepository, authRepository, resources, accounts, currentAcct; +( function( _ ) { + var results = { }; + accounts = { + "123456789": { + pin: "8675", + name: "Elwin Ransom", + limit: 20000, + balance: 100000 + }, + "987654321": { + pin: "3090", + name: "Edward Weston", + limit: 400, + balance: 300 + } + }; + authRepository = { + authorize: function( acct, pin ) { + var authed = accounts[acct] && accounts[acct].pin === pin; + if ( authed ) { + currentAcct = acct; + return true; + } + return false; + }, + + deauthorize: function( acct ) { + if ( currentAcct === acct ) { + currentAcct = undefined; + return true; + } + return false; + } + }; + clientRepository = { + withdrawal: function( acct, amount ) { + var current = accounts[currentAcct], + newBal = current.balance - amount, + result = { + status: ( amount > current.limit ) ? "OverLimit" : ( newBal < 0 ) ? "InsufficientFunds" : "Successful", + transactionAmount: amount + }; + if ( result.status === "Successful" ) + current.balance = newBal; + if ( !results[acct] ) { + results[acct] = []; + } + results[acct].push( $.extend( true, { transactionType: "Withdrawal" }, accounts[ currentAcct ], result ) ); + return { acct: acct, resultId: results[acct].length - 1 }; + }, + deposit: function( acct, amount ) { + accounts[ currentAcct ].balance += amount; + if ( !results[acct] ) { + results[acct] = []; + } + results[acct].push( $.extend( true, { transactionType: "Deposit" }, accounts[ currentAcct ], { status: "Successful", transactionAmount: amount } ) ); + return { acct: acct, resultId: results[acct].length - 1 }; + } + }; + resources = { + result: { + read: function( model, options ) { + options.success( results[model.acct][model.id] ); + } + }, + accountInfo: { + read: function( model, options ) { + options.success( accounts[model.id] ); + } + } + }; + + Backbone.sync = function( method, model, options ) { + var modelType = model.get( "modelType" ); + resources[modelType][method]( model.toJSON(), options ); + }; +} )( _ ); diff --git a/example/atm/js/amplify.diagnostics.js b/example/atm/js/amplify.diagnostics.js new file mode 100644 index 0000000..385f6ad --- /dev/null +++ b/example/atm/js/amplify.diagnostics.js @@ -0,0 +1,11 @@ +var orig = amplify.publish; + +amplify.publish = function( topic, message ) { + try { + console.log( topic + " " + JSON.stringify( message ) ); + } + catch ( exception ) { + console.log( topic + " (unable to serialize payload)" ); + } + orig.call( this, topic, message ); +} diff --git a/example/atm/js/appConfig.js b/example/atm/js/appConfig.js new file mode 100644 index 0000000..f7781ef --- /dev/null +++ b/example/atm/js/appConfig.js @@ -0,0 +1,17 @@ +( function( $, _, infuser, undefined ) { + var infuserDefault = infuser.defaults; + + infuser.defaults = $.extend( true, infuserDefault, { + templateUrl: "/example/atm/templates", + bindingInstruction: function( template, model ) { + return template( model ); + }, + render: function( target, template ) { + $( target ).html( template ); + }, + useLoadingTemplate: false, + templatePreProcessor: function( template ) { + return _.template( template ); + } + } ); +} )( jQuery, _, infuser ); diff --git a/example/atm/js/main.js b/example/atm/js/main.js new file mode 100644 index 0000000..788c5ba --- /dev/null +++ b/example/atm/js/main.js @@ -0,0 +1,80 @@ +var AtmApplication = function( target ) { + //---------------------------------------------------------------------------- + // + // The top level application object + // + //---------------------------------------------------------------------------- + var app = { + atm: new Atm(), + models: { + accountInfo: undefined + }, + views: { + main: new AppView( { target: target } ), + nav: new NavView(), + login: new LoginView( { target: "#screen" } ), + account: new AccountView( { target: "#screen" } ), + deposit: new DepositView( { target: "#screen" } ), + result: new ResultView( { target: "#screen" } ), + withdrawal: new WithdrawalView( { target: "#screen" } ) + }, + start: function() { + this.atm.handle( "initialize" ); + } + }; + + app.navFsm = new NavFsm( app.atm, app.views.nav ); + + //---------------------------------------------------------------------------- + // + // Handlers for View Events + // + //---------------------------------------------------------------------------- + app.views.main.on( "AppRendered", function() { + app.views.nav.render( app.views.nav.unAuthLayout ); + } ); + + app.views.login.on( "Authenticate", function( model ) { + app.atm.handle( "authorize", model ); + } ); + + app.views.deposit.on( "Deposit", function( amount ) { + app.atm.handle( "deposit", amount ); + } ); + + app.views.withdrawal.on( "Withdrawal", function( amount ) { + app.atm.handle( "withdrawal", amount ); + } ); + + //---------------------------------------------------------------------------- + // + // Handlers for App FSM Events + // + //---------------------------------------------------------------------------- + app.atm.on( "Initialized", function() { + app.views.main.render(); + app.router = new Router( app.atm, app.navFsm, app.views, app.models ); + Backbone.history.start( { root: "/example/atm/" } ); + } ); + + app.atm.on( "Authorized", function( data ) { + app.models.accountInfo = new AccountInfo( { id: data.acct } ); + app.views.main.model.set( "currentAccount", data.acct ); //TODO = needed? + window.location.hash = "account"; + } ); + + app.atm.on( "UnAuthorized", function() { + window.location.hash = "unauthorized"; + } ); + app.atm.on( "Result", function( data ) { + app.models.accountInfo.fetch(); + window.location.hash = "result/" + data.resultId; + } ); + + return app; +}; + +$( function() { + window.atmApp = new AtmApplication( '#content' ); + window.atmApp.start(); +} ); diff --git a/example/atm/js/models/AccountInfo.js b/example/atm/js/models/AccountInfo.js new file mode 100644 index 0000000..429f883 --- /dev/null +++ b/example/atm/js/models/AccountInfo.js @@ -0,0 +1,12 @@ +var AccountInfo = Backbone.Model.extend( { + defaults : { + modelType : "accountInfo", + name : "", + balance : 0, + limit : 0 + }, + + initialize : function () { + this.fetch(); + } +} ); diff --git a/example/atm/js/models/AccountModel.js b/example/atm/js/models/AccountModel.js new file mode 100644 index 0000000..6cc9bba --- /dev/null +++ b/example/atm/js/models/AccountModel.js @@ -0,0 +1,11 @@ +var AccountModel = Backbone.Model.extend( { + defaults : { + modelType : "account", + name : "", + balance : 0, + limit : 0 + }, + initialize : function () { + + } +} ); \ No newline at end of file diff --git a/example/atm/js/models/AppModel.js b/example/atm/js/models/AppModel.js new file mode 100644 index 0000000..7fc4ba3 --- /dev/null +++ b/example/atm/js/models/AppModel.js @@ -0,0 +1,3 @@ +var AppModel = Backbone.Model.extend( { + title : "Contrived ATM Example" +} ); \ No newline at end of file diff --git a/example/atm/js/models/DepositModel.js b/example/atm/js/models/DepositModel.js new file mode 100644 index 0000000..bc848bf --- /dev/null +++ b/example/atm/js/models/DepositModel.js @@ -0,0 +1,6 @@ +var DepositModel = Backbone.Model.extend( { + defaults : { + amount : 0, + error : "" + } +} ); \ No newline at end of file diff --git a/example/atm/js/models/LoginModel.js b/example/atm/js/models/LoginModel.js new file mode 100644 index 0000000..a693535 --- /dev/null +++ b/example/atm/js/models/LoginModel.js @@ -0,0 +1,7 @@ +var LoginModel = Backbone.Model.extend( { + defaults : { + acct : "", + pin : "", + error : "" + } +} ); \ No newline at end of file diff --git a/example/atm/js/models/ResultModel.js b/example/atm/js/models/ResultModel.js new file mode 100644 index 0000000..e51e379 --- /dev/null +++ b/example/atm/js/models/ResultModel.js @@ -0,0 +1,5 @@ +var ResultModel = Backbone.Model.extend( { + defaults : { + modelType : "result" + } +} ); \ No newline at end of file diff --git a/example/atm/js/models/WithdrawalModel.js b/example/atm/js/models/WithdrawalModel.js new file mode 100644 index 0000000..fe79eaf --- /dev/null +++ b/example/atm/js/models/WithdrawalModel.js @@ -0,0 +1,6 @@ +var WithdrawalModel = Backbone.Model.extend( { + defaults : { + amount : 0, + error : "" + } +} ); \ No newline at end of file diff --git a/example/atm/js/router.js b/example/atm/js/router.js new file mode 100644 index 0000000..9e46352 --- /dev/null +++ b/example/atm/js/router.js @@ -0,0 +1,93 @@ +var Router = function( atm, nav, views, models ) { + var delegateSubmit = function( handler ) { + $( document ).undelegate( "#submit", "click" ); + if ( handler ) { + $( document ).delegate( "#submit", "click", handler ); + } + }, + _Router = Backbone.Router.extend( { + routes: { + "deauthorize": "deauthorize", + "unauthorized": "unauthorized", + "account": "account", + "deposit": "deposit", + "withdrawal": "withdrawal", + "result/:id": "result", + "*other": "root" + }, + + initialize: function() { + _.bindAll( this, "deauthorize", "unauthorized", "account", "deposit", "root", "withdrawal" ); + }, + + deauthorize: function() { + models.accountInfo = undefined; + atm.handle( "deauthorize" ); + window.location.hash = "/" + }, + + unauthorized: function() { + nav.handle( "unauthorized" ); + views.login.render(); + }, + + account: function() { + if ( !models.accountInfo ) { + redirectUnAuth(); + } else { + views.account.model = models.accountInfo; + views.account.render(); + nav.handle( "account" ); + } + }, + + deposit: function() { + if ( !models.accountInfo ) { + redirectUnAuth(); + } else { + views.deposit.model.set( models.accountInfo.toJSON() ); + views.deposit.render(); + nav.handle( "deposit" ); + delegateSubmit( function() { + views.deposit.handleSubmit(); + } ); + } + }, + + result: function( id ) { + if ( !models.accountInfo ) { + redirectUnAuth(); + } else { + views.result.model.set( { id: id, acct: models.accountInfo.toJSON().id } ); + views.result.model.fetch(); + views.result.render(); + nav.handle( "result" ); + } + }, + + root: function() { + if ( !models.accountInfo ) { + redirectUnAuth(); + } + }, + + withdrawal: function() { + if ( !models.accountInfo ) { + redirectUnAuth(); + } else { + views.withdrawal.model.set( models.accountInfo.toJSON() ); + views.withdrawal.render(); + nav.handle( "withdrawal" ); + delegateSubmit( function() { + views.withdrawal.handleSubmit(); + } ); + } + } + } ), + router = new _Router(), + redirectUnAuth = function() { + router.navigate( "unauthorized", { trigger: true } ); + }; + + return router; +}; diff --git a/example/atm/js/views/AccountView.js b/example/atm/js/views/AccountView.js new file mode 100644 index 0000000..7187fdf --- /dev/null +++ b/example/atm/js/views/AccountView.js @@ -0,0 +1,22 @@ +var AccountView; +(function ( $, Backbone, infuser, undefined ) { + AccountView = Backbone.View.extend( { + tagName : "div", + + initialize : function () { + _.bindAll( this, "render" ); + }, + + render : function () { + var self = this; + infuser.infuse( "account", { + target : self.$el, + model : self.model.toJSON(), + preRender : function () { + infuser.defaults.preRender.apply( this, arguments ); + $( self.options.target ).html( self.$el ); + } + } ); + } + } ); +})( jQuery, Backbone, infuser ); diff --git a/example/atm/js/views/AppView.js b/example/atm/js/views/AppView.js new file mode 100644 index 0000000..2f547e4 --- /dev/null +++ b/example/atm/js/views/AppView.js @@ -0,0 +1,25 @@ +var AppView; +(function ( $, Backbone, infuser, undefined ) { + AppView = Backbone.View.extend( { + model : new AppModel(), + + initialize : function () { + _.bindAll( this, "render" ); + }, + + render : function () { + var postRender = infuser.defaults.postRender, + self = this; + infuser.infuse( "app", { + model : this.model, + target : self.options.target, + postRender : function () { + postRender.apply( this ); + _.defer( function () { + self.trigger( "AppRendered" ); + } ); + } + } ); + } + } ); +})( jQuery, Backbone, infuser ); \ No newline at end of file diff --git a/example/atm/js/views/DepositView.js b/example/atm/js/views/DepositView.js new file mode 100644 index 0000000..49a8dbf --- /dev/null +++ b/example/atm/js/views/DepositView.js @@ -0,0 +1,52 @@ +var DepositView; +(function ( $, Backbone, infuser, undefined ) { + DepositView = Backbone.View.extend( { + tagName : "div", + + model : new DepositModel(), + + events : { + "change #amount" : "handleAmountChange" + }, + + initialize : function () { + _.bindAll( this, "render", "handleSubmit", "handleAmountChange", "handleModelChange" ); + this.model.on( "change", this.handleModelChange ); + }, + + render : function () { + var self = this; + infuser.infuse( "deposit", { + target : self.$el, + model : self.model.toJSON(), + preRender : function () { + infuser.defaults.preRender.apply( this, arguments ); + $( self.options.target ).html( self.$el ); + }, + postRender : function () { + infuser.defaults.postRender.apply( this, arguments ); + self.delegateEvents( self.events ); + } + } ); + }, + + handleModelChange : function () { + this.render(); + }, + + handleSubmit : function () { + var amount = this.model.get( "amount" ); + if ( amount > 0 ) { + this.trigger( "Deposit", amount ); + this.model.set( "amount", 0 ); + return; + } + this.model.set( "error", "Deposit amount must be greater than $0." ); + }, + + handleAmountChange : function ( evnt ) { + this.model.set( { error : ""}, { silent : true } ); + this.model.set( { amount : parseFloat( $( evnt.target ).val() ) } ); + } + } ); +})( jQuery, Backbone, infuser ); \ No newline at end of file diff --git a/example/atm/js/views/LoginView.js b/example/atm/js/views/LoginView.js new file mode 100644 index 0000000..414dadc --- /dev/null +++ b/example/atm/js/views/LoginView.js @@ -0,0 +1,53 @@ +var LoginView; +(function ( $, Backbone, infuser, undefined ) { + LoginView = Backbone.View.extend( { + tagName : "div", + + model : new LoginModel(), + + events : { + "change #account" : "handleAccountChange", + "change #pin" : "handlePinChange", + "click #auth" : "authenticate" + }, + + initialize : function () { + _.bindAll( this, "render", "authenticate", "handleAccountChange", "handlePinChange" ); + }, + + handleAccountChange : function ( evnt ) { + this.model.set( { acct : $( evnt.target ).val() } ); + }, + + handlePinChange : function ( evnt ) { + this.model.set( { pin : $( evnt.target ).val() } ); + }, + + render : function () { + var self = this; + infuser.infuse( "login", { + target : self.$el, + model : self.model.toJSON(), + preRender : function () { + infuser.defaults.preRender.apply( this, arguments ); + $( self.options.target ).html( self.$el ); + }, + postRender : function () { + infuser.defaults.postRender.apply( this, arguments ); + self.delegateEvents( self.events ); + } + } ); + }, + + authenticate : function () { + this.model.set( "error", "" ); + var model = this.model.toJSON(); + if ( !model.acct || !model.pin ) { + this.model.set( { error : "You must enter an account number and a pin first." } ); + this.render(); + return; + } + this.trigger( "Authenticate", model ); + } + } ); +})( jQuery, Backbone, infuser ); \ No newline at end of file diff --git a/example/atm/js/views/NavView.js b/example/atm/js/views/NavView.js new file mode 100644 index 0000000..35fa329 --- /dev/null +++ b/example/atm/js/views/NavView.js @@ -0,0 +1,70 @@ +var NavView; +(function ( $, Backbone, infuser, undefined ) { + NavView = Backbone.View.extend( { + tagName : "div", + + events : { + "click #deposit" : "makeDeposit", + "click #withdrawal" : "makeWithdrawal", + "click #logout" : "logout" + }, + + initialize : function () { + _.bindAll( this, "render", "unAuthLayout", "authLayout", "makeDeposit", "makeWithdrawal", "logout", "depositLayout", "withdrawalLayout" ); + }, + + render : function ( postRender ) { + var self = this; + infuser.infuse( "nav", { + target : self.$el, + preRender : function () { + infuser.defaults.preRender.apply( this, arguments ); + $( "#nav" ).html( self.$el ); + $( document ).delegate( "#cancel", "click", function () { + self.cancelAction(); + } ); + }, + postRender : function () { + infuser.defaults.postRender.apply( this, arguments ); + postRender.apply( self ); + } + } ); + }, + + unAuthLayout : function () { + this.$el.find( "#deposit, #withdrawal, #logout, #submit, #cancel, #atm-spacer-row" ).parent().hide(); + this.$el.find( "#auth" ).parent().show(); + }, + + authLayout : function () { + this.$el.find( "#deposit, #withdrawal, #logout" ).parent().show(); + this.$el.find( "#auth, #submit, #cancel, #atm-spacer-row" ).parent().hide(); + }, + + depositLayout : function () { + this.$el.find( "#withdrawal, #logout, #submit, #cancel, #atm-spacer-row" ).parent().show(); + this.$el.find( "#deposit, #auth" ).parent().hide(); + }, + + withdrawalLayout : function () { + this.$el.find( "#deposit, #logout, #submit, #cancel, #atm-spacer-row" ).parent().show(); + this.$el.find( "#withdrawal, #auth" ).parent().hide(); + }, + + makeDeposit : function () { + window.location.hash = "deposit"; + }, + + makeWithdrawal : function () { + window.location.hash = "withdrawal"; + }, + + logout : function () { + window.location.hash = "deauthorize"; + }, + + cancelAction : function () { + window.location.hash = "account"; + } + } ); +})( jQuery, Backbone, infuser ); \ No newline at end of file diff --git a/example/atm/js/views/ResultView.js b/example/atm/js/views/ResultView.js new file mode 100644 index 0000000..07d6831 --- /dev/null +++ b/example/atm/js/views/ResultView.js @@ -0,0 +1,21 @@ +var ResultView = Backbone.View.extend( { + tagName : "div", + + model : new ResultModel(), + + initialize : function () { + _.bindAll( this, "render" ); + }, + + render : function () { + var self = this; + infuser.infuse( "result", { + target : self.$el, + model : self.model.toJSON(), + preRender : function () { + infuser.defaults.preRender.apply( this, arguments ); + $( self.options.target ).html( self.$el ); + } + } ); + } +} ); \ No newline at end of file diff --git a/example/atm/js/views/WithdrawalView.js b/example/atm/js/views/WithdrawalView.js new file mode 100644 index 0000000..4f2ab3b --- /dev/null +++ b/example/atm/js/views/WithdrawalView.js @@ -0,0 +1,52 @@ +var WithdrawalView; +(function ( $, Backbone, infuser, undefined ) { + WithdrawalView = Backbone.View.extend( { + tagName : "div", + + model : new WithdrawalModel(), + + events : { + "change #amount" : "handleAmountChange" + }, + + initialize : function () { + _.bindAll( this, "render", "handleSubmit", "handleAmountChange", "handleModelChange" ); + this.model.on( "change", this.handleModelChange ); + }, + + render : function () { + var self = this; + infuser.infuse( "withdrawal", { + target : self.$el, + model : self.model.toJSON(), + preRender : function () { + infuser.defaults.preRender.apply( this, arguments ); + $( self.options.target ).html( self.$el ); + }, + postRender : function () { + infuser.defaults.postRender.apply( this, arguments ); + self.delegateEvents( self.events ); + } + } ); + }, + + handleModelChange : function () { + this.render(); + }, + + handleSubmit : function () { + var amount = this.model.get( "amount" ); + if ( amount > 0 ) { + this.trigger( "Withdrawal", amount ); + this.model.set( "amount", 0 ); + return; + } + this.model.set( "error", "Withdrawal amount must be greater than $0." ); + }, + + handleAmountChange : function ( evnt ) { + this.model.set( { error : ""}, { silent : true } ); + this.model.set( { amount : parseFloat( $( evnt.target ).val() ) } ); + } + } ); +})( jQuery, Backbone, infuser ); \ No newline at end of file diff --git a/example/atm/style.css b/example/atm/style.css new file mode 100644 index 0000000..9ebb9de --- /dev/null +++ b/example/atm/style.css @@ -0,0 +1,133 @@ +body { + font-family: monospace, prestige; + background-color: black; + color:#5fba3d; +} + +div { + font-family: monospace, prestige; +} + +input { + font-family: monospace, prestige; +} + +input[type="text"], .atm-login input[type="password"] { + background-color: silver; + border-style: none; + cursor: cell; + height: 1.5em; + margin-top: .45em; +} + +#content { + width:800px; + height: 500px; +} + +.atm-nav { + float: left; + margin-right: .5em; +} + +.atm-nav-btn { + background-color:#0034dc; + color:white; + width: 6em; + text-align: right; + padding-right:.25em; + padding-top: .6em; + height:2em; + margin-bottom: .25em; + cursor:pointer; + font-variant: small-caps; + font-size: 1.45em; + border: .05em solid #0034dc; +} + +.atm-nav-btn:hover { + border: .05em solid white; +} + +.atm-screen { + float: right; +} + +.atm-header { + clear:both; + font-size: 1.5em; + margin-bottom: .5em; +} + +#screen .atm-header { + color: black; + background-color: #5fba3d; + text-align: center; + margin-top:.25em; +} + +.atm-wrapper { + float: left; + display: block; +} + +.atm-login { + width: 75%; + margin: 0px auto; +} + +.atm-row { + clear:both; + margin-top: .25em; +} + +.atm-info { + color: white; +} + +.atm-login label { + float:left; + text-align: right; + width:5em; + margin-right: .25em; +} + +.atm-login input { + float:right; +} + +.atm-login .atm-ctrl { + margin-top: .5em; + text-align: right; +} + +.atm-error { + clear:both; + background-color:#d5381a; + color:#080808; + width: 22em; + text-align: center; +} + +#auth { + width: 7em; + background-color:#e5df43; + color:#d5381a; + border-style: none; + margin: 1em .25em 1em 0; +} + +#amount[type="text"] { + width: 7em; + text-align: right; +} + +.atm-result-label { + float:left; +} + +.atm-result-value { + float:right; + color: white; + margin-left: .5em; +} \ No newline at end of file diff --git a/example/atm/templates/account.html b/example/atm/templates/account.html new file mode 100644 index 0000000..89a6a93 --- /dev/null +++ b/example/atm/templates/account.html @@ -0,0 +1,10 @@ +
Welcome, <%= name %>
+ +
+
Current Account Balance:
+ <%= accounting.formatMoney(balance) %> +
+
+
Transaction Limit:
+ <%= accounting.formatMoney(limit) %> +
\ No newline at end of file diff --git a/example/atm/templates/app.html b/example/atm/templates/app.html new file mode 100644 index 0000000..843abdd --- /dev/null +++ b/example/atm/templates/app.html @@ -0,0 +1,5 @@ +
<%= title %>
+
+ +
+
\ No newline at end of file diff --git a/example/atm/templates/current-client.html b/example/atm/templates/current-client.html new file mode 100644 index 0000000..7f407f7 --- /dev/null +++ b/example/atm/templates/current-client.html @@ -0,0 +1 @@ +
current client
\ No newline at end of file diff --git a/example/atm/templates/deposit.html b/example/atm/templates/deposit.html new file mode 100644 index 0000000..31cf5c8 --- /dev/null +++ b/example/atm/templates/deposit.html @@ -0,0 +1,15 @@ +
*** Deposit ***
+ +
Current Balance: <%= accounting.formatMoney(balance) %>
+
+ + +
+<% if(!error) { %> +
+
Your balance after this transaction: <%= accounting.formatMoney(parseFloat(balance) + parseFloat(amount)) %>
+
+<% } %> +
+ <%= error%> +
\ No newline at end of file diff --git a/example/atm/templates/login.html b/example/atm/templates/login.html new file mode 100644 index 0000000..d04ecb8 --- /dev/null +++ b/example/atm/templates/login.html @@ -0,0 +1,16 @@ +
+
+ + +
+
+ + +
+
+ +
+
+
+
<%= error%>
+
\ No newline at end of file diff --git a/example/atm/templates/nav.html b/example/atm/templates/nav.html new file mode 100644 index 0000000..9e9df42 --- /dev/null +++ b/example/atm/templates/nav.html @@ -0,0 +1,18 @@ +
+
Submit
+
+
+
Cancel
+
+
+
 
+
+
+
Deposit
+
+
+
Withdrawal
+
+
+
Logout
+
\ No newline at end of file diff --git a/example/atm/templates/result.html b/example/atm/templates/result.html new file mode 100644 index 0000000..470955f --- /dev/null +++ b/example/atm/templates/result.html @@ -0,0 +1,17 @@ +
*** <%= transactionType %> ***
+
+
+
Transaction Status:
+
<%= status %>
+
+
+
Transaction Amount:
+
<%= accounting.formatMoney(transactionAmount) %>
+
+ <% if(status === "Successful") { %> +
+
New Account Balance:
+
<%= accounting.formatMoney(balance) %>
+
+ <% } %> +
\ No newline at end of file diff --git a/example/atm/templates/withdrawal.html b/example/atm/templates/withdrawal.html new file mode 100644 index 0000000..b626326 --- /dev/null +++ b/example/atm/templates/withdrawal.html @@ -0,0 +1,17 @@ +
*** Withdrawal ***
+ +
+
Current Balance: <%= accounting.formatMoney(balance) %>
+
+ + +
+ <% if(!error) { %> +
+
Your balance after this transaction: <%= accounting.formatMoney(parseFloat(balance) - parseFloat(amount)) %>
+
+ <% } %> +
+
+ <%= error%> +
\ No newline at end of file diff --git a/example/connectivity/README.md b/example/connectivity/README.md new file mode 100644 index 0000000..3d1c9c5 --- /dev/null +++ b/example/connectivity/README.md @@ -0,0 +1,15 @@ +# Connectivity FSM Example + +The Connectivity FSM (under `js/connectivityFsm.js`) can be in one of the following states: + +* `probing` - the FSM will attempt to perform a heartbeat check via HTTP (we're currently using mockjax to mock the endpoint) + * this state has an entry action, triggered by the `_onEnter` handler - which issues the command to check for a heartbeat. +* `online` - the FSM is online, and will monitor for events to signal it should `go.offline` or into probing again. +* `offline` - the FSM has been told to go offline intentionally and will listen for a `go.online` event +* `disconnected` - the FSM has detected the connection has been lost, and will listen for events the signal it should `go.offline` or into probing. + +### The UI + +* clicking the switch to the left of the center box will send `go.online` and `go.offline` commands to the FSM +* clicking the cables that run down from the top of the page to the box will simulate the `window.offline` and `window.online` events, to which the FSM is listening. +* clicking the box itself will expand it to display a LED readout, and will cause the UI to run in slow motion (2 second delay) between each message \ No newline at end of file diff --git a/example/connectivity/css/normalize.css b/example/connectivity/css/normalize.css new file mode 100644 index 0000000..57b5d26 --- /dev/null +++ b/example/connectivity/css/normalize.css @@ -0,0 +1,375 @@ +/*! normalize.css v2.0.1 | MIT License | git.io/normalize */ + +/* ========================================================================== + HTML5 display definitions + ========================================================================== */ + +/* + * Corrects `block` display not defined in IE 8/9. + */ + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +nav, +section, +summary { + display: block; +} + +/* + * Corrects `inline-block` display not defined in IE 8/9. + */ + +audio, +canvas, +video { + display: inline-block; +} + +/* + * Prevents modern browsers from displaying `audio` without controls. + * Remove excess height in iOS 5 devices. + */ + +audio:not([controls]) { + display: none; + height: 0; +} + +/* + * Addresses styling for `hidden` attribute not present in IE 8/9. + */ + +[hidden] { + display: none; +} + +/* ========================================================================== + Base + ========================================================================== */ + +/* + * 1. Sets default font family to sans-serif. + * 2. Prevents iOS text size adjust after orientation change, without disabling + * user zoom. + */ + +html { + font-family: sans-serif; /* 1 */ + -webkit-text-size-adjust: 100%; /* 2 */ + -ms-text-size-adjust: 100%; /* 2 */ +} + +/* + * Removes default margin. + */ + +body { + margin: 0; +} + +/* ========================================================================== + Links + ========================================================================== */ + +/* + * Addresses `outline` inconsistency between Chrome and other browsers. + */ + +a:focus { + outline: thin dotted; +} + +/* + * Improves readability when focused and also mouse hovered in all browsers. + */ + +a:active, +a:hover { + outline: 0; +} + +/* ========================================================================== + Typography + ========================================================================== */ + +/* + * Addresses `h1` font sizes within `section` and `article` in Firefox 4+, + * Safari 5, and Chrome. + */ + +h1 { + font-size: 2em; +} + +/* + * Addresses styling not present in IE 8/9, Safari 5, and Chrome. + */ + +abbr[title] { + border-bottom: 1px dotted; +} + +/* + * Addresses style set to `bolder` in Firefox 4+, Safari 5, and Chrome. + */ + +b, +strong { + font-weight: bold; +} + +/* + * Addresses styling not present in Safari 5 and Chrome. + */ + +dfn { + font-style: italic; +} + +/* + * Addresses styling not present in IE 8/9. + */ + +mark { + background: #ff0; + color: #000; +} + + +/* + * Corrects font family set oddly in Safari 5 and Chrome. + */ + +code, +kbd, +pre, +samp { + font-family: monospace, serif; + font-size: 1em; +} + +/* + * Improves readability of pre-formatted text in all browsers. + */ + +pre { + white-space: pre; + white-space: pre-wrap; + word-wrap: break-word; +} + +/* + * Sets consistent quote types. + */ + +q { + quotes: "\201C" "\201D" "\2018" "\2019"; +} + +/* + * Addresses inconsistent and variable font size in all browsers. + */ + +small { + font-size: 80%; +} + +/* + * Prevents `sub` and `sup` affecting `line-height` in all browsers. + */ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sup { + top: -0.5em; +} + +sub { + bottom: -0.25em; +} + +/* ========================================================================== + Embedded content + ========================================================================== */ + +/* + * Removes border when inside `a` element in IE 8/9. + */ + +img { + border: 0; +} + +/* + * Corrects overflow displayed oddly in IE 9. + */ + +svg:not(:root) { + overflow: hidden; +} + +/* ========================================================================== + Figures + ========================================================================== */ + +/* + * Addresses margin not present in IE 8/9 and Safari 5. + */ + +figure { + margin: 0; +} + +/* ========================================================================== + Forms + ========================================================================== */ + +/* + * Define consistent border, margin, and padding. + */ + +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} + +/* + * 1. Corrects color not being inherited in IE 8/9. + * 2. Remove padding so people aren't caught out if they zero out fieldsets. + */ + +legend { + border: 0; /* 1 */ + padding: 0; /* 2 */ +} + +/* + * 1. Corrects font family not being inherited in all browsers. + * 2. Corrects font size not being inherited in all browsers. + * 3. Addresses margins set differently in Firefox 4+, Safari 5, and Chrome + */ + +button, +input, +select, +textarea { + font-family: inherit; /* 1 */ + font-size: 100%; /* 2 */ + margin: 0; /* 3 */ +} + +/* + * Addresses Firefox 4+ setting `line-height` on `input` using `!important` in + * the UA stylesheet. + */ + +button, +input { + line-height: normal; +} + +/* + * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` + * and `video` controls. + * 2. Corrects inability to style clickable `input` types in iOS. + * 3. Improves usability and consistency of cursor style between image-type + * `input` and others. + */ + +button, +html input[type="button"], /* 1 */ +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; /* 2 */ + cursor: pointer; /* 3 */ +} + +/* + * Re-set default cursor for disabled elements. + */ + +button[disabled], +input[disabled] { + cursor: default; +} + +/* + * 1. Addresses box sizing set to `content-box` in IE 8/9. + * 2. Removes excess padding in IE 8/9. + */ + +input[type="checkbox"], +input[type="radio"] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ +} + +/* + * 1. Addresses `appearance` set to `searchfield` in Safari 5 and Chrome. + * 2. Addresses `box-sizing` set to `border-box` in Safari 5 and Chrome + * (include `-moz` to future-proof). + */ + +input[type="search"] { + -webkit-appearance: textfield; /* 1 */ + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; /* 2 */ + box-sizing: content-box; +} + +/* + * Removes inner padding and search cancel button in Safari 5 and Chrome + * on OS X. + */ + +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +/* + * Removes inner padding and border in Firefox 4+. + */ + +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; +} + +/* + * 1. Removes default vertical scrollbar in IE 8/9. + * 2. Improves readability and alignment in all browsers. + */ + +textarea { + overflow: auto; /* 1 */ + vertical-align: top; /* 2 */ +} + +/* ========================================================================== + Tables + ========================================================================== */ + +/* + * Remove most spacing between table cells. + */ + +table { + border-collapse: collapse; + border-spacing: 0; +} diff --git a/example/connectivity/css/style.css b/example/connectivity/css/style.css new file mode 100644 index 0000000..90f431b --- /dev/null +++ b/example/connectivity/css/style.css @@ -0,0 +1,454 @@ +@import url( "normalize.css" ); + +@font-face { + font-family: 'DigitaldreamNarrowRegular'; + src: url('../font/DIGITALDREAMNARROW-webfont.eot'); + src: url('../font/DIGITALDREAMNARROW-webfont.eot?#iefix') format('embedded-opentype'), + url('../font/DIGITALDREAMNARROW-webfont.woff') format('woff'), + url('../font/DIGITALDREAMNARROW-webfont.ttf') format('truetype'), + url('../font/DIGITALDREAMNARROW-webfont.svg#DigitaldreamNarrowRegular') format('svg'); + font-weight: normal; + font-style: normal; +} + +html, body { + height: 100%; +} + +body { + background-color: #aaa; + background: -moz-radial-gradient(center, ellipse cover, #aaa 0%, #999 100%); /* FF3.6+ */ + background: -webkit-gradient(radial, center center, 0px, center center, 100%, color-stop(0%,#aaa), color-stop(100%,#999)); /* Chrome,Safari4+ */ + background: -webkit-radial-gradient(center, ellipse cover, #aaa 0%,#999 100%); /* Chrome10+,Safari5.1+ */ + background: -o-radial-gradient(center, ellipse cover, #aaa 0%,#999 100%); /* Opera 12+ */ + background: -ms-radial-gradient(center, ellipse cover, #aaa 0%,#999 100%); /* IE10+ */ + background: radial-gradient(ellipse at center, #aaa 0%,#999 100%); /* W3C */ + -webkit-tap-highlight-color: rgba(0,0,0,0); +} + +.internet { + width: 66px; + position: absolute; + left: 50%; + margin-left: -33px; + top: 0; + height: 50%; + cursor: pointer; + z-index: 6; +} + +.wires, +.wires:before, +.wires:after { + position: absolute; + width: 6px; + border-style: solid; + border-color: #bbb; + border-width: 0 6px; +} + +.wires:before, +.wires:after { + content: ""; + top: 0; + bottom: 0; +} + +.wires { + left: 50%; + margin-left: -9px; +} + +.wires:before { + left: -30px; +} + +.wires:after { + right: -30px; +} + +.internet-outgoing { + top: 0; + height: 25%; +} + +.internet-connector { + margin-bottom: -1px; + bottom: 50%; + height: 25.4%; + -webkit-transform-origin: left bottom; + -moz-transform-origin: left bottom; + -ms-transform-origin: left bottom; + -o-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transition: all linear 300ms; + -moz-transition: all linear 300ms; + -o-transition: all linear 300ms; + transition: all linear 300ms; +} + +.internet-incoming { + top: 50%; + bottom: 0; +} + +.internet-disconnected .internet-connector { + -webkit-transform: skewX(-25deg); + -moz-transform: skewX(-25deg); + -ms-transform: skewX(-25deg); + -o-transform: skewX(-25deg); + transform: skewX(-25deg); + height: 15%; +} + +.offline-overlay { + /*display: none;*/ + opacity: 0.0; + position: fixed; + pointer-events: none; + z-index: 1000; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: -moz-radial-gradient(center, ellipse cover, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 100%); /* FF3.6+ */ + background: -webkit-gradient(radial, center center, 0px, center center, 100%, color-stop(0%,rgba(0,0,0,0)), color-stop(100%,rgba(0,0,0,1))); /* Chrome,Safari4+ */ + background: -webkit-radial-gradient(center, ellipse cover, rgba(0,0,0,0) 0%,rgba(0,0,0,1) 100%); /* Chrome10+,Safari5.1+ */ + background: -o-radial-gradient(center, ellipse cover, rgba(0,0,0,0) 0%,rgba(0,0,0,1) 100%); /* Opera 12+ */ + background: -ms-radial-gradient(center, ellipse cover, rgba(0,0,0,0) 0%,rgba(0,0,0,1) 100%); /* IE10+ */ + background: radial-gradient(ellipse at center, rgba(0,0,0,0) 0%,rgba(0,0,0,1) 100%); /* W3C */ + -webkit-transition: opacity ease-in-out 500ms; + -moz-transition: opacity ease-in-out 500ms; + -o-transition: opacity ease-in-out 500ms; + transition: opacity ease-in-out 500ms; + -webkit-transition-delay: 1s; + -moz-transition-delay: 1s; + -o-transition-delay: 1s; + transition-delay: 1s; +} + +.no-pointerevents .offline-overlay { + z-index: 1; +} + +.offline .offline-overlay { + opacity: 1.0; +} + +.switch-plate { + position: absolute; + top: 50%; + height: 80px; + margin-top: -40px; + left: 50%; + margin-left: -240px; + width: 40px; + background: #888; + border-radius: 4px; + cursor: pointer; + z-index: 10; +} + +.switch-plate:before { + content: ""; + position: absolute; + background: #444; + width: 6px; + left: 50%; + margin-left: -3px; + top: 10px; + bottom: 10px; +} + +.switch-plate .switch { + position: absolute; + height: 10px; + width: 60px; + left: 50%; + margin-left: -30px; + top: 60px; + border-radius: 10px; + background: rgb(246,248,249); /* Old browsers */ + background: -moz-linear-gradient(top, rgba(246,248,249,1) 0%, rgba(229,235,238,1) 50%, rgba(215,222,227,1) 51%, rgba(245,247,249,1) 100%); /* FF3.6+ */ + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(246,248,249,1)), color-stop(50%,rgba(229,235,238,1)), color-stop(51%,rgba(215,222,227,1)), color-stop(100%,rgba(245,247,249,1))); /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(top, rgba(246,248,249,1) 0%,rgba(229,235,238,1) 50%,rgba(215,222,227,1) 51%,rgba(245,247,249,1) 100%); /* Chrome10+,Safari5.1+ */ + background: -o-linear-gradient(top, rgba(246,248,249,1) 0%,rgba(229,235,238,1) 50%,rgba(215,222,227,1) 51%,rgba(245,247,249,1) 100%); /* Opera 11.10+ */ + background: -ms-linear-gradient(top, rgba(246,248,249,1) 0%,rgba(229,235,238,1) 50%,rgba(215,222,227,1) 51%,rgba(245,247,249,1) 100%); /* IE10+ */ + background: linear-gradient(to bottom, rgba(246,248,249,1) 0%,rgba(229,235,238,1) 50%,rgba(215,222,227,1) 51%,rgba(245,247,249,1) 100%); /* W3C */ + -webkit-transition: all ease-in-out 200ms; + -moz-transition: all ease-in-out 200ms; + -o-transition: all ease-in-out 200ms; + transition: all ease-in-out 200ms; + -webkit-box-shadow: rgba(0,0,0,0.2) 2px 2px 5px; + box-shadow: rgba(0,0,0,0.2) 2px 2px 5px; +} + +.switch-on .switch { + top: 10px; +} + +.equipment { + z-index: 10; + cursor: pointer; + width: 325px; + height: 181px; + position: absolute; + left: 50%; + top: 50%; + margin-left: -163px; + margin-top: -91px; + background: #777; + border-radius: 10px; + -webkit-box-shadow: rgba(0,0,0,0.2) 5px 5px 10px; + box-shadow: rgba(0,0,0,0.2) 5px 5px 10px; +} + +.equipment:before { + content: ""; + position: absolute; + z-index: 6; + top: 0; + left: 0; + right: 0; + height: 72px; + border-radius: 10px 10px 0 0; + -webkit-box-shadow: inset rgba(0,0,0,0.2) 0 5px 10px; + box-shadow: inset rgba(0,0,0,0.2) 0 5px 10px; +} + +.equipment:before, +.equipment-bottom { + background-image: url(../img/equipment-front.png); + background-repeat: no-repeat; +} + +@media only screen and (-webkit-min-device-pixel-ratio: 2 ), + only screen and (-moz-min-device-pixel-ratio: 2 ), + only screen and (-o-min-device-pixel-ratio: 2/1 ), + only screen and (min-device-pixel-ratio ) { + .equipment:before, + .equipment-bottom { + background-image: url(../img/equipment-front-2x.png); + background-repeat: no-repeat; + -webkit-background-size: 325px 181px; + background-size: 325px 181px; + } +} + +.equipment:after { + content: ""; + position: absolute; + top: 8px; + left: 8px; + bottom: 8px; + right: 8px; + border-radius: 8px; + background-color: #555; + z-index: 2; +} + +.equipment-bottom { + position: absolute; + top: 72px; + height: 109px; + right: 0; + left: 0; + z-index: 5; + background-position: left bottom; + border-radius: 0 0 10px 10px; + -webkit-box-shadow: inset rgba(0,0,0,0.2) 0 -5px 10px; + box-shadow: inset rgba(0,0,0,0.2) 0 -5px 10px; + -webkit-transition: all ease-in-out 500ms; + -moz-transition: all ease-in-out 500ms; + -o-transition: all ease-in-out 500ms; + transition: all ease-in-out 500ms; +} + +.csstransforms3d .equipment-open .equipment-bottom { + -webkit-transform: translate3d(0,85px,0); + -moz-transform: translate3d(0,85px,0); + -ms-transform: translate3d(0,85px,0); + -o-transform: translate3d(0,85px,0); + transform: translate3d(0,85px,0); +} + +.no-csstransforms3d.csstransforms .equipment-open .equipment-bottom { + -webkit-transform: translateY(85px); + -moz-transform: translateY(85px); + -ms-transform: translateY(85px); + -o-transform: translateY(85px); + transform: translateY(85px); +} + +.no-csstransforms3d.no-csstransforms .equipment-open .equipment-bottom { + top: 157px; +} + +.equipment-open .equipment-bottom { + -webkit-box-shadow: inset rgba(0,0,0,0.2) 0 -5px 10px, rgba(0,0,0,0.2) 0 5px 15px; + box-shadow: inset rgba(0,0,0,0.2) 0 -5px 10px, rgba(0,0,0,0.2) 0 5px 15px; +} + +.equipment-led { + position: absolute; + border: solid 2px #000; + left: 20px; + bottom: 20px; + right: 20px; + height: 40px; + background: rgb(11,12,12); /* Old browsers */ + background: -moz-linear-gradient(top, rgba(11,12,12,1) 0%, rgba(30,33,32,1) 50%, rgba(10,14,10,1) 51%, rgba(10,8,9,1) 100%); /* FF3.6+ */ + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(11,12,12,1)), color-stop(50%,rgba(30,33,32,1)), color-stop(51%,rgba(10,14,10,1)), color-stop(100%,rgba(10,8,9,1))); /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(top, rgba(11,12,12,1) 0%,rgba(30,33,32,1) 50%,rgba(10,14,10,1) 51%,rgba(10,8,9,1) 100%); /* Chrome10+,Safari5.1+ */ + background: -o-linear-gradient(top, rgba(11,12,12,1) 0%,rgba(30,33,32,1) 50%,rgba(10,14,10,1) 51%,rgba(10,8,9,1) 100%); /* Opera 11.10+ */ + background: -ms-linear-gradient(top, rgba(11,12,12,1) 0%,rgba(30,33,32,1) 50%,rgba(10,14,10,1) 51%,rgba(10,8,9,1) 100%); /* IE10+ */ + background: linear-gradient(to bottom, rgba(11,12,12,1) 0%,rgba(30,33,32,1) 50%,rgba(10,14,10,1) 51%,rgba(10,8,9,1) 100%); /* W3C */ + + color: #F7931E; + line-height: 40px; + font-size: 16px; + text-align: center; + letter-spacing: 1px; + font-family: DigitaldreamNarrowRegular, sans-serif; + z-index: 4; + white-space: nowrap; +} + +.equipment-led:before { + content: ""; + background: #000; + width: 67px; + height: 67px; + border-radius: 35px; + position: absolute; + top: -80px; + left: 50%; + margin-left: -33px; +} + +.equipment-led:after { + content: ""; + position: absolute; + left: 50%; + margin-left: -12px; + top: -20px; + height: 25px; + width: 4px; + border-style: double; + border-color: #000; + border-width: 0 10px; +} + +.equipment-light, +.equipment-light-back { + background-color: #777; + -webkit-background-clip: border-box; + -moz-background-clip: border-box; + background-clip: border-box; + position: absolute; + top: 43px; + left: 50%; + margin-left: -28px; + width: 57px; + height: 57px; + border-radius: 30px; + -webkit-box-shadow: inset rgba(0,0,0,0.8) 0 0 30px, inset #444 0 0 2px; + box-shadow: inset rgba(0,0,0,0.8) 0 0 30px, inset #444 0 0 2px; + z-index: 9; +} + +.equipment-light { + -webkit-transition: all ease-in-out 300ms; + -moz-transition: all ease-in-out 300ms; + -o-transition: all ease-in-out 300ms; + transition: all ease-in-out 300ms; + + -webkit-animation: 1s pulse infinite alternate; + -moz-animation: 1s pulse infinite alternate; + -o-animation: 1s pulse infinite alternate; + animation: 1s pulse infinite alternate; + + opacity: 0.2; + z-index: 10; +} + +.no-borderradius .equipment-light-back, +.no-borderradius .equipment-light { + width: 59px; + height: 59px; + top: 41px; + margin-left: -29px; +} + +.no-borderradius .equipment-light-back { + z-index: 4; +} + +.no-borderradius .equipment-light { + z-index: 5; +} + +.no-cssanimations .equipment-light { + opacity: 1.0; +} + +.equipment-light:after, +.equipment-light-back:after { + content: ""; + position: absolute; + width: 39px; + height: 39px; + left: 9px; + top: 9px; + border-radius: 20px; + background: -moz-linear-gradient(-45deg, rgba(255,255,255,0.5) 0%, rgba(255,255,255,0.5) 49%, rgba(255,255,255,0.3) 50%, rgba(255,255,255,0.2) 100%); /* FF3.6+ */ + background: -webkit-gradient(linear, left top, right bottom, color-stop(0%,rgba(255,255,255,0.5)), color-stop(49%,rgba(255,255,255,0.5)), color-stop(50%,rgba(255,255,255,0.3)), color-stop(100%,rgba(255,255,255,0.2))); /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(-45deg, rgba(255,255,255,0.5) 0%,rgba(255,255,255,0.5) 49%,rgba(255,255,255,0.3) 50%,rgba(255,255,255,0.2) 100%); /* Chrome10+,Safari5.1+ */ + background: -o-linear-gradient(-45deg, rgba(255,255,255,0.5) 0%,rgba(255,255,255,0.5) 49%,rgba(255,255,255,0.3) 50%,rgba(255,255,255,0.2) 100%); /* Opera 11.10+ */ + background: -ms-linear-gradient(-45deg, rgba(255,255,255,0.5) 0%,rgba(255,255,255,0.5) 49%,rgba(255,255,255,0.3) 50%,rgba(255,255,255,0.2) 100%); /* IE10+ */ + background: linear-gradient(135deg, rgba(255,255,255,0.5) 0%,rgba(255,255,255,0.5) 49%,rgba(255,255,255,0.3) 50%,rgba(255,255,255,0.2) 100%); /* W3C */ + opacity: 0.5; +} + + +.online .equipment-light { + background-color: #52ff12; + -webkit-box-shadow: inset rgba(0,0,0,0.8) 0 0 30px, inset #318210 0 0 2px, rgba(82,255,18,1.0) 0 0 40px; + -moz-box-shadow: inset rgba(0,0,0,0.8) 0 0 30px, inset #318210 0 0 2px, rgba(82,255,18,1.0) 0 0 40px; + box-shadow: inset rgba(0,0,0,0.8) 0 0 30px, inset #318210 0 0 2px, rgba(82,255,18,1.0) 0 0 40px; +} + +.disconnected .equipment-light { + background-color: #F7931E; + -webkit-box-shadow: inset rgba(0,0,0,0.8) 0 0 30px, inset #492403 0 0 2px, rgba(247,147,30,1.0) 0 0 40px; + -moz-box-shadow: inset rgba(0,0,0,0.8) 0 0 30px, inset #492403 0 0 2px, rgba(247,147,30,1.0) 0 0 40px; + box-shadow: inset rgba(0,0,0,0.8) 0 0 30px, inset #492403 0 0 2px, rgba(247,147,30,1.0) 0 0 40px; +} + +.offline .equipment-light { + background-color: #ED1C24; + -webkit-box-shadow: inset rgba(0,0,0,0.8) 0 0 30px, inset #72030B 0 0 2px, rgba(237,28,36,1.0) 0 0 40px; + -moz-box-shadow: inset rgba(0,0,0,0.8) 0 0 30px, inset #72030B 0 0 2px, rgba(237,28,36,1.0) 0 0 40px; + box-shadow: inset rgba(0,0,0,0.8) 0 0 30px, inset #72030B 0 0 2px, rgba(237,28,36,1.0) 0 0 40px; +} + + +@-webkit-keyframes pulse { + from { opacity: 0.2; } + to { opacity: 1.0; } +} + +@-moz-keyframes pulse { + from { opacity: 0.2; } + to { opacity: 1.0; } +} + +@-o-keyframes pulse { + from { opacity: 0.2; } + to { opacity: 1.0; } +} + +@keyframes pulse { + from { opacity: 0.2; } + to { opacity: 1.0; } +} \ No newline at end of file diff --git a/example/connectivity/font/DIGITALDREAMNARROW-webfont.eot b/example/connectivity/font/DIGITALDREAMNARROW-webfont.eot new file mode 100644 index 0000000000000000000000000000000000000000..69acca5d8bbcbc7c2e4b8160959a55f6c4d4f14d GIT binary patch literal 33204 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 literal 0 HcmV?d00001 diff --git a/example/connectivity/font/DIGITALDREAMNARROW-webfont.svg b/example/connectivity/font/DIGITALDREAMNARROW-webfont.svg new file mode 100644 index 0000000..0f610c6 --- /dev/null +++ b/example/connectivity/font/DIGITALDREAMNARROW-webfont.svg @@ -0,0 +1,172 @@ + + + + +This is a custom SVG webfont generated by Font Squirrel. +Copyright : c Jakob Fischer at wwwpizzadudedk DO NOT DISTRIBUTE WITHOUT AUTHORS PERMISSION + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/example/connectivity/font/DIGITALDREAMNARROW-webfont.ttf b/example/connectivity/font/DIGITALDREAMNARROW-webfont.ttf new file mode 100644 index 0000000000000000000000000000000000000000..b60a6affe8b9f8952d9c42647828f5764b45289f GIT binary patch literal 32992 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 ZpaZA}Qc2arvPi2bxI&(tdj>wVPxvd2a5^WGj_ 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 o_6KyMGfZue1^F&KRk&z4IKLcBg5C8xG literal 0 HcmV?d00001 diff --git a/example/connectivity/index.html b/example/connectivity/index.html new file mode 100644 index 0000000..2711013 --- /dev/null +++ b/example/connectivity/index.html @@ -0,0 +1,12 @@ + + + + Connectivity FSM Example + + + + + + + + diff --git a/example/connectivity/js/app.js b/example/connectivity/js/app.js new file mode 100644 index 0000000..5456816 --- /dev/null +++ b/example/connectivity/js/app.js @@ -0,0 +1,26 @@ +define( [ + 'jquery', + 'bus', + 'connectivityFsm', + 'stethoscope', + 'mainView' +], function( $, bus, ConnectivityFsm, Stethoscope, MainView ) { + var stethoscope = new Stethoscope( { url: "heartbeat" } ); + stethoscope.on( "checking-heartbeat", function() { + bus.heartbeat.publish( { topic: "checking", data: {} } ); + } ); + + var app = { + simulateDisconnect: false, + toggleDisconnectSimulation: function() { + this.simulateDisconnect = !this.simulateDisconnect; + $( window ).trigger( this.simulateDisconnect ? "offline" : "online" ); + }, + view: new MainView(), + monitor: new ConnectivityFsm( { stethoscope: stethoscope } ) + }; + + app.view.render(); + + return app; +} ); diff --git a/example/connectivity/js/bus.js b/example/connectivity/js/bus.js new file mode 100644 index 0000000..fa8a97d --- /dev/null +++ b/example/connectivity/js/bus.js @@ -0,0 +1,15 @@ +define( [ + 'postal', + 'postal.diags' +], function( postal, DiagnosticsWireTap ) { + return { + _wiretaps: { + firehose: new DiagnosticsWireTap( "firehose", function( x ) { + console.log( x ); + } ) + }, + connectivityOutput: postal.channel( "connectivity.events", "#" ), + connectivityInput: postal.channel( "connectivity", "#" ), + heartbeat: postal.channel( "heartbeat", "#" ) + }; +} ); diff --git a/example/connectivity/js/connectivityFsm.js b/example/connectivity/js/connectivityFsm.js new file mode 100644 index 0000000..8b63964 --- /dev/null +++ b/example/connectivity/js/connectivityFsm.js @@ -0,0 +1,75 @@ +define( [ + 'jquery', + 'machina', + 'lodash' + ], function( $, machina, _ ) { + var useStethoscope = function( fsm, steth ) { + _.each( [ 'heartbeat', 'no-heartbeat' ], function( eventName ) { + steth.on( eventName, function() { + fsm.handle( eventName ); + } ); + } ); + }; + + return machina.Fsm.extend( { + + namespace: 'connectivity', + + initialState: "offline", + + initialize: function() { + var self = this; + $( window ).bind( "online", function() { + self.handle( "window.online" ); + } ); + + $( window ).bind( "offline", function() { + self.handle( "window.offline" ); + } ); + + $( window.applicationCache ).bind( "error", function() { + self.handle( "appCache.error" ); + } ); + + $( window.applicationCache ).bind( "downloading", function() { + self.handle( "appCache.downloading" ); + } ); + }, + + states: { + probing: { + _onEnter: function() { + if ( !this.wiredUp ) { + useStethoscope( this, this.stethoscope ); + this.wiredUp = true; + } + this.stethoscope.checkHeartbeat(); + }, + heartbeat: "online", + "no-heartbeat": "disconnected", + "go.offline": "offline", + "*": function() { + this.deferUntilTransition(); + } + }, + + online: { + "window.offline": "probing", + "appCache.error": "probing", + "request.timeout": "probing", + "go.offline": "offline" + }, + + disconnected: { + "window.online": "probing", + "appCache.downloading": "probing", + "go.online": "probing", + "go.offline": "offline" + }, + + offline: { + "go.online": "probing" + } + } + } ); +} ); diff --git a/example/connectivity/js/main.js b/example/connectivity/js/main.js new file mode 100644 index 0000000..ad49faa --- /dev/null +++ b/example/connectivity/js/main.js @@ -0,0 +1,52 @@ +require.config( { + paths: { + text: "../../../bower/requirejs-text/text", + backbone: '../../../bower/backbone/backbone', + lodash: '../../../bower/lodash/dist/lodash', + underscore: '../../../bower/lodash/dist/lodash.underscore', + mockjax: '../../../bower/jquery-mockjax/jquery.mockjax', + machina: 'http://cdnjs.cloudflare.com/ajax/libs/machina.js/1.1.0/machina', + 'machina.postal': '../../../bower/machina.postal/lib/machina.postal', + postal: '../../../bower/postal.js/lib/postal', + 'postal.diags': '../../../bower/postal.diagnostics/lib/postal.diagnostics.min', + jquery: '../../../bower/jquery/jquery', + conduitjs: '../../../bower/conduitjs/lib/conduit' + }, + shim: { + mockjax: [ 'jquery' ], + backbone: { + deps: [ 'lodash', 'jquery' ], + exports: 'Backbone' + } + } +} ); + +// This first require statement is pulling in foundational libraries +require( [ + 'jquery', + 'mockjax', + 'machina.postal', + 'postal.diags' + ], + function( $ ) { + require( [ 'app' ], function( app ) { + // mockjax setup + // Mocked response for the heartbeat check + $.mockjax( { + url: "heartbeat", + type: "GET", + response: function( settings ) { + if ( app.simulateDisconnect ) { + this.isTimeout = true; + } else { + this.responseText = { + canYouHearMeNow: "good" + }; + } + } + } ); + // more for convenience, our app gets a global namespace + window.app = app; + } ); + } +); diff --git a/example/connectivity/js/mainView.js b/example/connectivity/js/mainView.js new file mode 100644 index 0000000..f3b241f --- /dev/null +++ b/example/connectivity/js/mainView.js @@ -0,0 +1,147 @@ +define( [ + 'backbone', + 'jquery', + 'lodash', + 'bus', + 'text!template/mainView.html' + ], function( Backbone, $, _, bus, template ) { + return Backbone.View.extend( { + el: 'body', + + slowMotion: false, + slowMotionDelay: 2000, + online: false, + animating: false, + + events: { + 'click .equipment': 'toggleSlowMotion', + 'click .toggle-online': 'toggleOnline', + 'click .toggle-disconnect': 'toggleWindowDisconnect' + }, + + initialize: function() { + _.bindAll( this, "render", "dequeue", "resetQueue", "updateLed", "updateClass", "routeEvents", "transition", "handling", "handled", "checking", "toggleSlowMotion", "toggleOnline", "toggleWindowDisconnect" ); + this.template = _.template( template ); + bus.connectivityOutput.subscribe( "#", this.routeEvents ).withContext( this ); + bus.heartbeat.subscribe( "#", this.routeEvents ).withContext( this ); + }, + + render: function() { + this.$el.html( this.template( {} ) ); + this.$switchPlate = this.$( '.switch-plate' ); + this.$internet = this.$( '.internet' ); + this.$led = this.$( '.equipment-led' ); + }, + + dequeue: function() { + this.animating = false; + this.$el.dequeue( "slow-motion" ); + }, + + queueUpdate: function( callback, immediate ) { + var self = this; + + this.$el.queue( "slow-motion", function() { + self.animating = true; + callback(); + + if ( immediate === true ) { + self.dequeue(); + return; + } + + window.setTimeout( self.dequeue, self.slowMotionDelay ); + } ); + + if ( !this.animating ) { + this.$el.dequeue( "slow-motion" ); + } + }, + + resetQueue: function() { + this.$el.stop( "slow-motion", true, false ); + }, + + updateLed: function( message ) { + var self = this; + + if ( this.slowMotion ) { + this.queueUpdate( function() { + self.$led.html( message ); + } ); + } else { + self.$led.html( message ); + } + }, + + updateClass: function( toState ) { + var self = this, + updateInternet = function() { + if ( toState === "online" ) { + self.$internet.removeClass( "internet-disconnected" ); + } else { + self.$internet.toggleClass( "internet-disconnected", app.simulateDisconnect || toState === "disconnected" ); + } + }; + + if ( this.slowMotion ) { + this.queueUpdate( function() { + self.$el + .removeClass( "online offline disconnected probing" ) + .addClass( toState ); + + updateInternet(); + }, true ); + } else { + if ( toState !== "probing" ) { + this.$el + .removeClass( "online offline disconnected probing" ) + .addClass( toState ); + + updateInternet(); + } + } + }, + + // LISTENING TO THE FSM OUTPUT (& STETHOSCOPE) + routeEvents: function( data, envelope ) { + var handler = envelope.topic.toLowerCase(); + if ( this[ handler ] ) { + this[ handler ]( data ); + } + }, + transition: function( data ) { + this.updateLed( data.fromState + " → " + data.toState ); + this.updateClass( data.toState ); + this.updateLed( data.toState ); + }, + handling: function( data ) { + this.updateLed( data.inputType ); + }, + handled: function( data ) { + }, + checking: function() { + this.updateClass( "probing" ); + this.updateLed( "Heartbeat Check" ); + }, + + // Reacting to events origination from DOM + toggleSlowMotion: function() { + this.slowMotion = !this.slowMotion; + this.resetQueue(); + this.$el.toggleClass( "equipment-open", this.slowMotion ); + }, + toggleOnline: function() { + this.online = !this.online; + this.$switchPlate.toggleClass( "switch-on", this.online ); + bus.connectivityInput.publish( { + topic: this.online ? "go.online" : "go.offline", + data: {} + } ); + }, + toggleWindowDisconnect: function() { + app.toggleDisconnectSimulation(); + this.$internet.toggleClass( "internet-disconnected", app.simulateDisconnect ); + } + } ); +} ); diff --git a/example/connectivity/js/stethoscope.js b/example/connectivity/js/stethoscope.js new file mode 100644 index 0000000..9a433b4 --- /dev/null +++ b/example/connectivity/js/stethoscope.js @@ -0,0 +1,28 @@ +define( [ + 'backbone', + 'jquery' +], function( Backbone, $ ) { + var Stethoscope = function( heartbeatDef ) { + this.settings = $.extend( { + type: "GET", + dataType: "json", + timeout: 5000 + }, heartbeatDef ); + }; + + $.extend( Stethoscope.prototype, Backbone.Events, { + checkHeartbeat: function() { + var self = this; + self.trigger( 'checking-heartbeat' ); + $.ajax( self.settings ) + .done( function() { + self.trigger( 'heartbeat' ); + } ) + .fail( function() { + self.trigger( 'no-heartbeat' ); + } ); + } + } ); + + return Stethoscope; +} ); diff --git a/example/connectivity/js/template/mainView.html b/example/connectivity/js/template/mainView.html new file mode 100644 index 0000000..3a386e1 --- /dev/null +++ b/example/connectivity/js/template/mainView.html @@ -0,0 +1,18 @@ +
+
+
+
+
+ +
+ +
+
+
+
Offline
+
+
+ +
+
+
\ No newline at end of file diff --git a/example/hierarchical/index.html b/example/hierarchical/index.html new file mode 100644 index 0000000..3f398c6 --- /dev/null +++ b/example/hierarchical/index.html @@ -0,0 +1,16 @@ + + + + Hierarchical FSM Example + + +
Alas, one day I will have a real example app for this....
+
+ +
+ + + + + + diff --git a/example/hierarchical/js/main.js b/example/hierarchical/js/main.js new file mode 100644 index 0000000..dd7ab89 --- /dev/null +++ b/example/hierarchical/js/main.js @@ -0,0 +1,179 @@ +( function( global, $ ) { + var DO_NOT_WALK = "Do Not Walk"; + var WALK = "Walk"; + var RED = "red"; + var YELLOW = "yellow"; + var GREEN = "green"; + var $content = $( "#content" ); + + function writeToDom( eventName, data ) { + $content.prepend( "

" + eventName + "

" + JSON.stringify( data, null, 4 ) + "
" ); + } + + // Child FSM + var vehicleSignal = new machina.Fsm( { + namespace: "vehicle-signal", + initialState: "uninitialized", + reset: function() { + this.transition( "green" ); + }, + states: { + uninitialized: { + "*": function() { + this.deferUntilTransition(); + this.transition( "green" ); + } + }, + green: { + _onEnter: function() { + this.timer = setTimeout( function() { + this.handle( "timeout" ); + }.bind( this ), 30000 ); + this.emit( "vehicles", { status: GREEN } ); + }, + timeout: "green-interruptible", + pedestrianWaiting: function() { + this.deferUntilTransition( "green-interruptible" ); + }, + _onExit: function() { + clearTimeout( this.timer ); + } + }, + "green-interruptible": { + pedestrianWaiting: "yellow" + }, + yellow: { + _onEnter: function() { + this.timer = setTimeout( function() { + this.handle( "timeout" ); + }.bind( this ), 5000 ); + this.emit( "vehicles", { status: YELLOW } ); + }, + timeout: "red", + _onExit: function() { + clearTimeout( this.timer ); + } + }, + red: { + _onEnter: function() { + this.timer = setTimeout( function() { + this.handle( "timeout" ); + }.bind( this ), 1000 ); + }, + _reset: "green", + _onExit: function() { + clearTimeout( this.timer ); + } + } + } + } ); + + // // Child FSM + var pedestrianSignal = new machina.Fsm( { + namespace: "pedestrian-signal", + initialState: "uninitialized", + reset: function() { + this.transition( "walking" ); + }, + states: { + uninitialized: { + "*": function() { + this.deferUntilTransition(); + this.transition( "walking" ); + } + }, + walking: { + _onEnter: function() { + this.timer = setTimeout( function() { + this.handle( "timeout" ); + }.bind( this ), 30000 ); + this.emit( "pedestrians", { status: WALK } ); + }, + timeout: "flashing", + _onExit: function() { + clearTimeout( this.timer ); + } + }, + flashing: { + _onEnter: function() { + this.timer = setTimeout( function() { + this.handle( "timeout" ); + }.bind( this ), 5000 ); + this.emit( "pedestrians", { status: DO_NOT_WALK, flashing: true } ); + }, + timeout: "dontwalk", + _onExit: function() { + clearTimeout( this.timer ); + } + }, + dontwalk: { + _onEnter: function() { + this.timer = setTimeout( function() { + this.handle( "timeout" ); + }.bind( this ), 1000 ); + }, + _reset: "walking", + _onExit: function() { + clearTimeout( this.timer ); + } + } + } + } ); + + // // Parent FSM + var crosswalk = new machina.Fsm( { + namespace: "crosswalk", + initialState: "vehiclesEnabled", + eventListeners: { + "*": [ function( eventName, data ) { + switch ( eventName ) { + case "transition" : + writeToDom( eventName, data ); + console.log( data.namespace, data.fromState, "->", data.toState ); + break; + case "vehicles" : + writeToDom( eventName, data ); + console.log( "vehicles", data.status ); + break; + case "pedestrians": + writeToDom( eventName, data ); + if ( data.flashing ) { + console.log( "pedestrians", data.status, "(flashing)" ); + } else { + console.log( "pedestrians", data.status ); + } + break; + default: + + break; + } + } + ] + }, + states: { + vehiclesEnabled: { + // after _onEnter execs, send "reset" input down the hierarchy + _onEnter: function() { + this.emit( "pedestrians", { status: DO_NOT_WALK } ); + }, + timeout: "pedestriansEnabled", + _child: vehicleSignal, + }, + pedestriansEnabled: { + _onEnter: function() { + this.emit( "vehicles", { status: RED } ); + }, + timeout: "vehiclesEnabled", + _child: pedestrianSignal + } + } + } ); + + global.app = { + vehicleSignal: vehicleSignal, + pedestrianSignal: pedestrianSignal, + crosswalk: crosswalk + }; + + crosswalk.handle( "pedestrianWaiting" ); +}( window, jQuery ) ); diff --git a/example/load/README.md b/example/load/README.md new file mode 100644 index 0000000..614e14f --- /dev/null +++ b/example/load/README.md @@ -0,0 +1,9 @@ +# "Load" Sample Application + +The FSM in this sample application handles the "initialization" (or "load") process of the app. Although it's fairly oversimplified, it demonstrates an alternative approach to wrapping the series of async events with deferreds. + +To demonstrate how the FSM would respond to a failed call to Hacker News, change the url in the main.js file to something that will break. + +This example application is using postal.js to integrate the FSM with a local message bus. Communication between the FSM and other components in the application is happening almost entirely through message-based means. + +A wiretap (provided by the postal.diagnostics.js file) causes all published messages to be printed to the console as well. \ No newline at end of file diff --git a/example/load/ajax-loader.gif b/example/load/ajax-loader.gif new file mode 100644 index 0000000000000000000000000000000000000000..cc70a7a8b3d426c30e76686fac70c0dcd4c70125 GIT binary patch literal 8238 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 literal 0 HcmV?d00001 diff --git a/example/load/index.html b/example/load/index.html new file mode 100644 index 0000000..e6132e7 --- /dev/null +++ b/example/load/index.html @@ -0,0 +1,23 @@ + + + + FSM App Load Example + + + + + + + + + + + + + + +
+ +
+ + diff --git a/example/load/js/appCfg.js b/example/load/js/appCfg.js new file mode 100644 index 0000000..de7f9cb --- /dev/null +++ b/example/load/js/appCfg.js @@ -0,0 +1,17 @@ +( function( $, _, infuser, undefined ) { + var infuserDefault = infuser.defaults; + + infuser.defaults = $.extend( true, infuserDefault, { + templateUrl: "/example/load/templates", + bindingInstruction: function( template, model ) { + return template( model ); + }, + render: function( target, template ) { + $( target ).html( template ); + }, + useLoadingTemplate: false, + templatePreProcessor: function( template ) { + return _.template( template ); + } + } ); +} )( jQuery, _, infuser ); diff --git a/example/load/js/main.js b/example/load/js/main.js new file mode 100644 index 0000000..abcd9cb --- /dev/null +++ b/example/load/js/main.js @@ -0,0 +1,109 @@ +/*machina.bus.config = { + handlerChannelSuffix: "", + eventChannelSuffix: "" + };*/ + +var resourceGetter = { + getNews: function() { + $.ajax( { + url: "http://api.ihackernews.com/page?format=jsonp", + dataType: "jsonp", + success: function( data ) { + postal.publish( { channel: "application", topic: "itemData.retrieved", data: data } ); + } + } ); + return setTimeout( function() { + postal.publish( { channel: "application", topic: "itemData.getFailed", data: {} } ); + }, 4000 ); + } + }, + appFsm = new machina.Fsm( { + checkIfReady: function() { + if ( _.all( this.constraints[this.state].checkList, function( constraint ) { + return constraint; + } ) ) { + this.transition( this.constraints[this.state].nextState ); + } + }, + + namespace: "application", + + constraints: { + waitingOnTemplates: { + nextState: "waitingOnData", + checkList: { + haveMainTemplate: false, + haveItemTemplate: false, + haveErrorTemplate: false + } + }, + waitingOnData: { + nextState: "ready", + attempts: 0, + checkList: { + haveItemData: false + } + } + }, + + states: { + uninitialized: { + initialize: "waitingOnTemplates", + "*": function() { + this.deferUntilTransition(); + } + }, + waitingOnTemplates: { + "mainTemplate.retrieved": function() { + this.constraints.waitingOnTemplates.checkList.haveMainTemplate = true; + this.checkIfReady(); + }, + "itemTemplate.retrieved": function() { + this.constraints.waitingOnTemplates.checkList.haveItemTemplate = true; + this.checkIfReady(); + }, + "errorTemplate.retrieved": function() { + this.constraints.waitingOnTemplates.checkList.haveErrorTemplate = true; + this.checkIfReady(); + }, + "*": function() { + this.deferUntilTransition(); + } + }, + waitingOnData: { + _onEnter: function() { + this.constraints.waitingOnData.timeoutFn = resourceGetter.getNews(); + }, + "itemData.retrieved": function() { + clearTimeout( this.constraints.waitingOnData.timeoutFn ); + this.constraints.waitingOnData.checkList.haveItemData = true; + this.checkIfReady(); + }, + "itemData.getFailed": function() { + this.emit( "dataGetFail", { attempts: ++this.constraints.waitingOnData.attempts } ); + resourceGetter.getNews(); + } + }, + "ready": { + _onEnter: function() { + this.emit( "appReady" ); + }, + refresh: "waitingOnData" + } + } + } ), + mainView = new MainView( "#content" ), + itemView = new ItemView( "#items" ); + +var app = window.loadApp = { + fsm: appFsm, + views: { + main: mainView, + items: itemView + }, + start: function() { + postal.publish( { channel: "application", topic: "initialize", data: {} } ); + } +}; + +app.start(); diff --git a/example/load/js/views.js b/example/load/js/views.js new file mode 100644 index 0000000..a16b25d --- /dev/null +++ b/example/load/js/views.js @@ -0,0 +1,74 @@ +var MainView = function( target ) { + var self = this; + self.model = { + title: "Contrived News Reader" + }; + + self.render = function() { + var self = this; + infuser.infuse( "main", { + target: target, + model: self.model, + postRender: function() { + $( '#refresh' ).on( "click", function() { + postal.publish( { channel: "application", topic: "refresh" } ); + } ) + } + } ); + }; + + infuser.get( "main", function( template ) { + postal.publish( { channel: "application", topic: "mainTemplate.retrieved" } ); + self.render(); + } ); + }, + ErrorView = function( target ) { + this.model = {}; + + this.render = function( model ) { + var self = this; + self.model = model || self.model; + infuser.infuse( "failure", { + target: target, + model: self.model + } ); + }; + + infuser.get( "failure", function( template ) { + postal.publish( { channel: "application", topic: "errorTemplate.retrieved" } ); + } ); + }, + ItemView = function( target ) { + var self = this, + errorNotice = new ErrorView( target ); + self.model = {}; + + self.render = function() { + var self = this; + infuser.infuse( "headlines", { + target: target, + model: self.model + } ); + }; + + postal.subscribe( { + channel: "application.events", + topic: "dataGetFail", + callback: function( data, env ) { + errorNotice.render( data[0] ); + } + } ); + + postal.subscribe( { + channel: "application", + topic: "itemData.retrieved", + callback: function( data, env ) { + self.model = data; + self.render(); + } + } ); + + infuser.get( "headlines", function( template ) { + postal.publish( { channel: "application", topic: "itemTemplate.retrieved" } ); + } ); + }; diff --git a/example/load/style.css b/example/load/style.css new file mode 100644 index 0000000..389d7b7 --- /dev/null +++ b/example/load/style.css @@ -0,0 +1,74 @@ +* { + font-family: Arial, Helvetica, sans-serif; + font-size: 10pt; +} + +.header { + width: 570px; +} + +.title { + float: left; + font-size: 14pt; + font-variant: small-caps; + margin-bottom: 15px; +} + +.ctrl { + float: right; +} + +#items { + clear:both; + height: 600px; + overflow: scroll; +} + +li { + clear: both; + width: 570px; + border-top: 1pt solid gainsboro; +} + +ul { + list-style-type: none; + padding: 0; + margin-left: 0; +} + +.left { + float: left; + margin-top: 10px; + margin-bottom: 10px +} + +.right { + float: right; + margin-bottom: 20px; +} + +.right { + color: #708090; +} + +.right div { + font-family: Tahoma; + font-size: 24pt; + text-align:center; +} + +a { + text-decoration: none; +} + +.onoes { + width: 570px; + color: #708090; + text-align: center; +} + +.onoes div { + margin-bottom: 10px; + font-size: 14pt; + font-variant: small-caps; +} \ No newline at end of file diff --git a/example/load/templates/failure.html b/example/load/templates/failure.html new file mode 100644 index 0000000..52dbb5e --- /dev/null +++ b/example/load/templates/failure.html @@ -0,0 +1,5 @@ +
+
OHSNAP! We're having trouble reaching hacker news.
+
That last attempt was attempt # <%= attempts %>.
+
We will try again in a few seconds....
+
\ No newline at end of file diff --git a/example/load/templates/headlines.html b/example/load/templates/headlines.html new file mode 100644 index 0000000..5b2ab96 --- /dev/null +++ b/example/load/templates/headlines.html @@ -0,0 +1,15 @@ +
\ No newline at end of file diff --git a/example/load/templates/main.html b/example/load/templates/main.html new file mode 100644 index 0000000..31ac723 --- /dev/null +++ b/example/load/templates/main.html @@ -0,0 +1,11 @@ +
+
<%= title %>
+
+ +
+
+ + +
+ +
\ No newline at end of file diff --git a/ext/infuser_all.js b/ext/infuser_all.js new file mode 100644 index 0000000..78cbe0e --- /dev/null +++ b/ext/infuser_all.js @@ -0,0 +1,227 @@ +(function($, undefined) { +/* + TrafficCop + Author: Jim Cowart + License: Dual licensed MIT (http://www.opensource.org/licenses/mit-license) & GPL (http://www.opensource.org/licenses/gpl-license) + Version 0.3.0 +*/ + +var inProgress = {}; + +$.trafficCop = function(url, options) { + var reqOptions = url, key; + if(arguments.length === 2) { + reqOptions = $.extend(true, options, { url: url }); + } + key = JSON.stringify(reqOptions); + if (key in inProgress) { + for (var i in {success: 1, error: 1, complete: 1}) { + inProgress[key][i](reqOptions[i]); + } + } else { + inProgress[key] = $.ajax(reqOptions).always(function () { delete inProgress[key]; }); + } + return inProgress[key]; +}; + + +if (typeof define === "function" && define.amd) { + define(function() { + return $.trafficCop; //superfluous, since it's already on the jQuery object + }); +} +})(jQuery); +/* + infuser.js + Author: Jim Cowart + License: Dual licensed MIT (http://www.opensource.org/licenses/mit-license) & GPL (http://www.opensource.org/licenses/gpl-license) + Version 0.2.0 +*/ +var hashStorage = { + templates: {}, + + storeTemplate: function(templateId, templateContent) { + this.templates[templateId] = templateContent; + }, + + getTemplate: function(templateId) { + return this.templates[templateId]; + }, + + purge: function() { + this.templates = {}; + } +}; +var scriptStorage = { + templateIds: [], + storeTemplate: function(templateId, templateContent) { + var node = document.getElementById(templateId); + if(node === null) { + this.templateIds.push(templateId); + node = document.createElement("script"); + node.type = "text/html"; + node.id = templateId; + document.body.appendChild(node); + } + node.text = templateContent; + }, + + getTemplate: function(templateId) { + return document.getElementById(templateId); + }, + + purge: function() { + for(var i = 0; i < this.templateIds.length; i++) { + document.body.removeChild(document.getElementById(this.templateIds[i])); + } + this.templateIds = []; + } +}; +var errorHtml = "
The template {TEMPLATEID} could not be loaded. {STATUS}
", + returnErrorTemplate = function(status, templateId, templatePath) { + return errorHtml.replace('{STATUS}', status).replace('{TEMPLATEID}', templateId).replace('{TEMPLATEURL}', templatePath); + }, + errors = []; +var helpers = { + getTemplatePath: function(templateOptions) { + var templateFile = templateOptions.templatePrefix + templateOptions.templateId + templateOptions.templateSuffix; + return templateOptions.templateUrl === undefined || templateOptions.templateUrl === "" ? + templateFile : templateOptions.templateUrl + "/" + templateFile; + }, + templateGetSuccess: function(templateId, callback) { + return function(response) { + var _response = infuser.defaults.templatePreProcessor(response); + infuser.store.storeTemplate(templateId, _response); + callback(infuser.store.getTemplate(templateId)); + }; + }, + templateGetError: function(templateId, templatePath, callback) { + return function(exception) { + if($.inArray(templateId, errors) === -1) { + errors.push(templateId); + } + var templateHtml = returnErrorTemplate("HTTP Status code: " + exception.status, templateId, templatePath); + infuser.store.storeTemplate(templateId, templateHtml); + callback(infuser.store.getTemplate(templateId)); + }; + }, + getAjaxOptions: function(templateOptions) { + + } +}, +infuserOptions = ['target','loadingTemplate','postRender','preRender','render','bindingInstruction','useLoadingTemplate','model','templateUrl','templateSuffix','templatePrefix','']; +var infuser = { + storageOptions: { + hash: hashStorage, + script: scriptStorage + }, + + store: hashStorage, + + defaults: { + // Template name conventions + templateUrl: "", + templateSuffix: ".html", + templatePrefix: "", + templatePreProcessor: function(x) { return x }, + // AJAX Options + ajax: { + "async": true, + "dataType": "html", + "type": "GET" + }, + // infuse() specific options - NOT used for "get" or "getSync" + target: function(templateId) { return "#" + templateId }, // DEFAULT MAPPING + loadingTemplate: { + content: '
Loading...
', + transitionIn: function(target, content) { + var tgt = $(target); + tgt.hide(); + tgt.html(content); + tgt.fadeIn(); + }, + transitionOut: function(target) { + $(target).html(""); + } + }, + postRender: function(targetElement) { }, // NO_OP effectively by default + preRender: function(targetElement, template) { }, // NO_OP effectively by default + render: function(target, template) { + var tgt = $(target); + if(tgt.children().length === 0) { + tgt.append($(template)); + } + else { + tgt.children().replaceWith($(template)); + } + }, + bindingInstruction: function(template, model) { return template; }, // NO_OP effectively by default + useLoadingTemplate: true // true/false + }, + + get: function(options, callback) { + var templateOptions = $.extend({}, infuser.defaults, (typeof options === "object" ? options : { templateId: options })), + template; + templateOptions.ajax.url = helpers.getTemplatePath(templateOptions); + template = infuser.store.getTemplate(templateOptions.ajax.url); + if(!template || $.inArray(templateOptions.ajax.url, errors) !== -1) { + templateOptions.ajax.success = helpers.templateGetSuccess(templateOptions.ajax.url, callback); + templateOptions.ajax.error = helpers.templateGetError(templateOptions.templateId, templateOptions.ajax.url, callback); + $.trafficCop(templateOptions.ajax); + } + else { + callback(template); + } + }, + + getSync: function(options) { + var templateOptions = $.extend({}, infuser.defaults, (typeof options === "object" ? options : { templateId: options }), { ajax: { async: false } }), + template, + templateHtml; + templateOptions.ajax.url = helpers.getTemplatePath(templateOptions); + template = infuser.store.getTemplate(templateOptions.ajax.url); + if(!template || $.inArray(templateOptions.ajax.url, errors) !== -1) { + templateHtml = null; + templateOptions.ajax.success = function(response) { templateHtml = response; }; + templateOptions.ajax.error = function(exception) { + if($.inArray(templateOptions.ajax.url) === -1) { + errors.push(templateOptions.ajax.url); + } + templateHtml = returnErrorTemplate("HTTP Status code: exception.status", templateOptions.templateId, templateOptions.ajax.url); + }; + $.ajax(templateOptions.ajax); + if(templateHtml === null) { + templateHtml = returnErrorTemplate("An unknown error occurred.", templateOptions.templateId, templateOptions.ajax.url); + } + else { + infuser.store.storeTemplate(templateOptions.ajax.url, templateHtml); + template = infuser.store.getTemplate(templateOptions.ajax.url); + } + } + return template; + }, + + infuse: function(templateId, renderOptions) { + var templateOptions = $.extend({}, infuser.defaults, (typeof templateId === "object" ? templateId : renderOptions), (typeof templateId === "string" ? { templateId: templateId } : undefined )), + targetElement = typeof templateOptions.target === 'function' ? templateOptions.target(templateId) : templateOptions.target; + if(templateOptions.useLoadingTemplate) { + templateOptions.loadingTemplate.transitionIn(targetElement, templateOptions.loadingTemplate.content); + } + infuser.get(templateOptions, function(template) { + var _template = template; + templateOptions.preRender(targetElement, _template); + _template = templateOptions.bindingInstruction(_template, templateOptions.model); + if(templateOptions.useLoadingTemplate) { + templateOptions.loadingTemplate.transitionOut(targetElement); + } + templateOptions.render(targetElement, _template); + templateOptions.postRender(targetElement); + }); + } +}; + +if (typeof define === "function" && define.amd) { + define(["jquery"], function( $ ) { + return infuser; + }); +} \ No newline at end of file diff --git a/ext/livereloadhack.js b/ext/livereloadhack.js new file mode 100644 index 0000000..7ba8695 --- /dev/null +++ b/ext/livereloadhack.js @@ -0,0 +1,9 @@ +(function() { + try { + var liveReload = document.createElement('script'); + liveReload.src = "http://" + (location.host || "localhost").split(":")[0] + ":35729/livereload.js?snipver=1"; + document.body.appendChild(liveReload); + } catch(e) { + console.log("LiveReload not running, skipping script injection...."); + } +}()); diff --git a/index.html b/index.html index 6d83f4b..3773e71 100644 --- a/index.html +++ b/index.html @@ -20,12 +20,12 @@

Machina.js

js ex machina - finite state machines in JavaScript

-

This project is maintained by ifandelse

+

This project is maintained by ifandelse

@@ -390,7 +390,7 @@

// remove ALL subscribers, period: trafficLight.off();

-

You can emit your own custom events in addition to the built-in events machina emits. To read more about these events, see the wiki.

+

You can emit your own custom events in addition to the built-in events machina emits. To read more about these events, see the wiki.

Things Suddenly Got Hierarchical!

@@ -489,7 +489,7 @@

  • As the parent state transitions into any of its states, it will tell the child FSM to handle a _reset input. This gives you a hook to move the child FSM to the correct state before handling any further input. For example, you'll notice our pedestrianSignal FSM has a _reset input handler in the dontwalk state, which transitions the FSM to the walking state.
  • -

    In v1.1.0, machina added the compositeState() method to the BehavioralFsm and Fsm prototypes. This means you can get the current state of the FSM hierarchy. For example:

    +

    In v1.1.1, machina added the compositeState() method to the BehavioralFsm and Fsm prototypes. This means you can get the current state of the FSM hierarchy. For example:

    // calling compositeState on Fsm instances
     console.log( crosswalk.compositeState() ); // vehiclesEnabled.green
    @@ -533,17 +533,17 @@ 

    Build, Tests & Examples

    -

    machina.js uses gulp.js to build.

    +

    machina.js uses gulp.js to build.

      -
    • Install node.js (and consider using nvm to manage your node versions)
    • +
    • Install node.js (and consider using nvm to manage your node versions)
    • run npm install & bower install to install all dependencies
    • To build, run npm run build - then check the lib folder for the output
    • To run the examples:
    • @@ -563,12 +563,25 @@

      Release Notes

      -

      Go here to see the changelog.

      +

      Go here to see the changelog.

      Have More Questions?

      -

      Read the wiki and the source – you might find your answer and more! Check out the issue opened by @burin - a great example of how to use github issues to ask questions, provide sample code, etc. I only ask that if you open an issue, that it be focused on a specific problem or bug (not wide-open ambiguity, please).

      +

      Read the wiki and the source – you might find your answer and more! Check out the issue opened by @burin - a great example of how to use github issues to ask questions, provide sample code, etc. I only ask that if you open an issue, that it be focused on a specific problem or bug (not wide-open ambiguity, please).

      + +

      +Examples

      + +

      There are a few examples included in the repo that you can run right here in your browser. Just be aware, some of them are rough :-)!

      + + +