diff --git a/README.md b/README.md index 39c0374..aa258c7 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,33 @@ console.log(machine.state) // } ``` +## Guard + +Sometimes you may want a machine to be guarded against moving to a specific state based on some condition. You can encapsulate this logic as part of your machine. For example, say we have a door and a lock: + +```js +const door = nanostate("closed", { + closed: { open: "open", light_push: "ajar" }, + ajar: { close: "closed", open: "open" }, + open: { close: "closed" }, +}); +const lock = nanostate("unlocked", { + unlocked: { lock: "locked" }, + locked: { unlock: "unlocked" }, +}); +``` + +If the door is closed, the door cannot be opened unless it is unlocked. Likewise, the door cannot be lightly pushed unless it is also unlocked. + +```js +door.guard("open", () => { + return lockState.state === "unlocked"; +}); +door.guard("light_push", () => { + return lockState.state === "unlocked"; +}); +``` + ## Nanocomponent Usage in combination with [nanocomponent](https://github.com/choojs/nanocomponent) to create stateful UI @@ -198,6 +225,10 @@ passed. Trigger a callback when a certain state is entered. Useful to trigger side effects upon state change. +### `machine.onchange(cb)` +Trigger a callback when any state is entered. Useful to trigger side +effects upon state change. + ### `state = machine.state` Return the current state. diff --git a/index.js b/index.js index b66a323..d6897cd 100644 --- a/index.js +++ b/index.js @@ -13,6 +13,8 @@ function Nanostate (initialState, transitions) { this.state = initialState this.submachines = {} this._submachine = null + this.guards = {} + this.onchangecb = null; Nanobus.call(this) } @@ -21,6 +23,14 @@ Nanostate.prototype = Object.create(Nanobus.prototype) Nanostate.prototype.constructor = Nanostate +Nanostate.prototype.onchange = function(cb) { + this.onchangecb = cb; +} + +Nanostate.prototype.guard = function (eventName, cb) { + this.guards[eventName] = cb +} + Nanostate.prototype.emit = function (eventName) { var nextState = this._next(eventName) assert.ok(nextState, 'nanostate.emit: invalid transition' + this.state + '->' + eventName) @@ -29,7 +39,13 @@ Nanostate.prototype.emit = function (eventName) { this._unregister() } + if (this.guards[eventName] && (this.guards[eventName]() === false)) { + return + } this.state = nextState + if (this.onchangecb !== null && typeof this.onchangecb === 'function') { + this.onchangecb(nextState); + } Nanobus.prototype.emit.call(this, nextState) } diff --git a/package.json b/package.json index 5b32b80..fadccac 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "nanostate", "description": "Small Finite State Machine implementation", "repository": "choojs/nanostate", - "version": "1.2.2", + "version": "1.2.3", "scripts": { "start": "node .", "lint": "standard", diff --git a/test/event.js b/test/event.js new file mode 100644 index 0000000..be89c7e --- /dev/null +++ b/test/event.js @@ -0,0 +1,16 @@ +var tape = require("tape"); + +var nanostate = require("../"); + +tape("event - any change", (test) => { + test.plan(1); + var machine = nanostate("green", { + green: { timer: "yellow" }, + yellow: { timer: "red" }, + red: { timer: "green" }, + }); + machine.onchange((nextState) => { + test.equal(nextState, "yellow"); + }); + machine.emit("timer"); +}); diff --git a/test/index.js b/test/index.js index ef47d6f..bb6ffc7 100644 --- a/test/index.js +++ b/test/index.js @@ -1,3 +1,4 @@ -require('./nanostate') -require('./parallel') -require('./hierarchical') +require("./nanostate"); +require("./parallel"); +require("./hierarchical"); +require("./event");