UNPKG

atom-react

Version:

An opiniated way to use ReactJS in a functional way in plain old Javascript, inspired by popular Clojurescript wrappers like Om

190 lines (157 loc) 4.95 kB
'use strict'; var React = require("react"); var DeepFreeze = require("../utils/deepFreeze"); var AtomUtils = require("./atomUtils"); var AtomCursor = require("./atomCursor"); var NOOP = function noop() { } // Convenient but probably not performant: TODO ? /** * Creates an Atom * It contains an immutable state that is never modified directly, but can be swapped to a new immutable state * @param options * @constructor */ var Atom = function Atom(options) { this.state = options.initialState || {}; this.beforeTransactionCommit = options.beforeTransactionCommit || NOOP; this.afterTransactionCommit = options.afterTransactionCommit || NOOP; this.currentTransactionState = undefined; if ( process.env.NODE_ENV !== "production" ) { DeepFreeze(this.state); } }; /** * Change the state reference hold in this Atom * @param newState */ Atom.prototype.swap = function(newState) { if ( !this.isInTransaction() ) { throw new Error("It is forbidden to swap the atom outside of a transaction"); } if ( this.locked ) { throw new Error("Atom is locked because: "+this.lockReason); } if ( process.env.NODE_ENV !== "production" ) { DeepFreeze(newState); } this.currentTransactionState = newState; }; Atom.prototype.isInTransaction = function() { return !!this.currentTransactionState; }; Atom.prototype.openTransaction = function() { this.currentTransactionState = this.state; this.currentTransactionDate = Date.now(); }; Atom.prototype.commitTransaction = function() { var transactionState = this.currentTransactionState; this.currentTransactionState = undefined this.state = transactionState; var duration = Date.now() - this.currentTransactionDate; this.currentTransactionDate = undefined; var transactionData = { duration: duration }; return transactionData; }; Atom.prototype.rollbackTransaction = function() { this.currentTransactionState = undefined }; Atom.prototype.lock = function(lockReason) { this.locked = true; this.lockReason = lockReason }; Atom.prototype.unlock = function() { this.locked = false; this.lockReason = undefined }; Atom.prototype.doWithLock = function(lockReason,task) { try { this.lock(lockReason); task(); } finally { this.unlock(); } }; Atom.prototype.transact = function(tasks) { // TODO do we need to implement more complex transaction propagation rules than joining the existing transaction? if ( this.isInTransaction() ) { tasks(); } else { this.openTransaction(); try { tasks(); // "lock" these values before calling the callbacks var previousState = this.state; this.beforeTransactionCommit(this.currentTransactionState,previousState); var transactionData = this.commitTransaction(); try { this.afterTransactionCommit(this.state,previousState,transactionData); } catch (error) { console.error("Error in 'afterTransactionCommit' callback. The transaction will still be commited -> ",error); } } catch (error) { this.rollbackTransaction(); throw error; } } }; /** * Get the current state of the Atom * @return the Atom state */ Atom.prototype.get = function() { // If we are inside a transaction, we can read the transaction state (read your writes) return this.currentTransactionState || this.state; }; /** * Get a cursor,, that permits to focus on a given path of the Atom * @param options * @return {AtomCursor} */ Atom.prototype.cursor = function(options) { return new AtomCursor(this,[],options); }; /** * Change the value at a given path of the atom * @param path * @param value */ Atom.prototype.setPathValue = function(path,value) { var self = this; this.transact(function() { var newState = AtomUtils.setPathValue(self.get(),path,value); self.swap(newState); }); }; Atom.prototype.unsetPathValue = function(path) { var self = this; this.transact(function() { var newState = AtomUtils.setPathValue(self.get(),path,undefined); self.swap(newState); }) }; /** * Get the value at a given path of the atom * @param path * @return value */ Atom.prototype.getPathValue = function(path) { return AtomUtils.getPathValue(this.get(),path); }; /** * Compare and swap a value at a given path of the atom * @param path * @param expectedValue * @param newValue * @return true if the CAS operation was successful */ Atom.prototype.compareAndSwapPathValue = function(path,expectedValue,newValue) { var actualValue = this.getPathValue(path); if ( actualValue === expectedValue ) { this.setPathValue(path,newValue); return true; } return false; }; module.exports = Atom;