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

253 lines (204 loc) 8.41 kB
'use strict'; var _ = require("lodash"); var Preconditions = require("../utils/preconditions"); var Immutables = require("../utils/immutables"); var ArgumentsOrArray = require("../utils/argumentsOrArray"); var AtomUtils = require("./atomUtils"); var AtomAsyncUtils = require("./atomAsyncUtils"); function ensureIsArray(maybeArray,message) { if ( !(maybeArray instanceof Array) ) { throw new Error("Not an array: " + maybeArray + " -> " + message); } } //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // CURSOR PRIMITIVES /////////////////////// var AtomCursor = function AtomCursor(atom,atomPath, options) { var options = options || Immutables.EmptyObject; this.atom = atom; this.atomPath = atomPath; // A dynamic cursor never precomputes or memoize a value, it always use up to date data this.dynamic = !!options.dynamic; this.memoized = !!options.memoized; Preconditions.checkCondition( !(this.dynamic && this.memoized) ,"A cursor can't be dynamic and memoized at the same time!"); if ( this.dynamic ) { // Nothing to do } else { // The value of the cursor when it was created. // It can be forced as an optimization if the cursor creator knows the creation time value this.creationTimeValue = options.hasOwnProperty("creationTimeValue") ? options.creationTimeValue : this.getFreshValue(); if ( this.memoized ) { this.memoizedValue = this.creationTimeValue; } } }; AtomCursor.prototype.memoize = function() { Preconditions.checkCondition(!this.dynamic,"You can't memoize a dynamic cursor"); var value = this.value(); this.memoized = true; this.memoizedValue = value; return this; }; AtomCursor.prototype.memoizeToCreationTimeValue = function() { Preconditions.checkCondition(!this.dynamic,"You can't memoize a dynamic cursor"); this.memoized = true; this.memoizedValue = this.creationTimeValue; return this; }; AtomCursor.prototype.unmemoize = function() { this.memoized = false; this.memoizedValue = undefined; return this; }; // TODO this should be removed AtomCursor.prototype.transact = function(tasks) { this.atom.transact(tasks); }; AtomCursor.prototype.getFreshValue = function() { return this.atom.getPathValue(this.atomPath); }; AtomCursor.prototype.value = function() { return this.memoized ? this.memoizedValue : this.getFreshValue(); }; AtomCursor.prototype.follow = function() { var pathToFollow = ArgumentsOrArray(arguments); var newPath = this.atomPath.concat(pathToFollow); return new AtomCursor(this.atom,newPath, { // If current cursor is memoized or dynamic, that configuration will propagate... dynamic: this.dynamic, memoized: this.memoized, // Minor optimization creationTimeValue: this.dynamic ? undefined : AtomUtils.getPathValue(this.value(),pathToFollow) }); }; //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // CURSOR API /////////////////////// AtomCursor.prototype.getCreationTimeValue = function() { Preconditions.checkCondition(!this.dynamic,"A dynamic cursor does not have any creationTimeValue available"); return this.creationTimeValue; }; AtomCursor.prototype.exists = function() { return Preconditions.hasValue(this.value()); }; AtomCursor.prototype.get = function() { var value = this.value(); Preconditions.checkHasValue(value,"No value for path " + this.atomPath + " -> maybe you want to use " + "cursor.getOrElse(undefined) instead if the value you look for may be absent when the cursor is created "); return value; }; AtomCursor.prototype.getOrElse = function(fallback) { var value = this.value(); return Preconditions.hasValue(value) ? value : fallback; }; AtomCursor.prototype.getOrEmptyArray = function() { return this.getOrElse(Immutables.EmptyArray); }; AtomCursor.prototype.getOrEmptyObject = function() { return this.getOrElse(Immutables.EmptyObject); }; // Will return false if the set/unset operation did not change anything AtomCursor.prototype.set = function(value) { var stateBefore = this.atom.get(); this.atom.setPathValue(this.atomPath,value); var stateAfter = this.atom.get(); return (stateBefore !== stateAfter); }; AtomCursor.prototype.unset = function() { var stateBefore = this.atom.get(); this.atom.unsetPathValue(this.atomPath); var stateAfter = this.atom.get(); return (stateBefore !== stateAfter); }; AtomCursor.prototype.push = function(value) { var list = this.getOrEmptyArray(); ensureIsArray(list,"can only call push on an array. "+this.atomPath); var newList = list.concat([value]); this.atom.setPathValue(this.atomPath,newList); }; AtomCursor.prototype.unshift = function(value) { var list = this.getOrEmptyArray(); ensureIsArray(list,"can only call unshift on an array. "+this.atomPath); var newList = [value].concat(list); this.atom.setPathValue(this.atomPath,newList); }; AtomCursor.prototype.without = function(value) { var list = this.getOrEmptyArray(); ensureIsArray(list,"can only call without on an array. "+this.atomPath); var newList = list.filter(function(v) { return v !== value; }); this.atom.setPathValue(this.atomPath,newList); }; AtomCursor.prototype.filter = function(value) { var list = this.getOrEmptyArray(); ensureIsArray(list,"can only call filter on an array. "+this.atomPath); var newList = list.filter(function(v) { return v === value }); this.atom.setPathValue(this.atomPath,newList); }; AtomCursor.prototype.reduce = function(reducer,event) { this.update(function(value) { return reducer(value,event); }); }; AtomCursor.prototype.update = function(updateFunction,initialValueFallback) { var value = this.value() || initialValueFallback; var valueToSet = updateFunction(value); this.atom.setPathValue(this.atomPath,valueToSet); }; AtomCursor.prototype.plus = function(number) { this.update(function(value) { return value+number },0); }; AtomCursor.prototype.minus = function(number) { this.update(function(value) { return value-number },0); }; AtomCursor.prototype.toggle = function(initialValueFallback) { this.update(function(value) { return !value },!!initialValueFallback); }; AtomCursor.prototype.isInstanceOf = function(clazz) { return this.exists() && this.get() instanceof clazz; }; AtomCursor.prototype.list = function() { var list = this.getOrEmptyArray(); ensureIsArray(list,"can only call list on an array. "+this.atomPath); return list.map(function(item,index) { return this.follow(index); }.bind(this)); }; AtomCursor.prototype.asyncSuccess = function() { var value = this.value(); if ( Preconditions.hasValue(value) && value.isSuccess() ) { return this.asyncSuccessUnsafe(); } else { throw new Error("You can't follow an async value that is not successfully loaded. Path="+this.atomPath); } }; // Will follow the async value even if there is no async value or a loading/error async value AtomCursor.prototype.asyncSuccessUnsafe = function() { return this.follow("value"); }; AtomCursor.prototype.asyncList = function() { return AtomAsyncUtils.getPathAsyncValueListCursors(this); }; // Useful to manage a single promise AtomCursor.prototype.setAsyncValue = function(promise,logCompletion) { return AtomAsyncUtils.setPathAsyncValue(this.atom,this.atomPath,promise,logCompletion); }; // Useful to load multiple promises in an array, mostly to handle pagination of long lists... AtomCursor.prototype.pushAsyncValue = function(promise,logCompletion) { return AtomAsyncUtils.pushPathAsyncValue(this.atom,this.atomPath,promise,logCompletion); }; AtomCursor.prototype.setAsyncSuccess = function(success) { var asyncSuccess = new AtomAsyncUtils.AtomAsyncValue().toSuccess(success); this.set(asyncSuccess); }; AtomCursor.prototype.setAsyncError = function(error) { var asyncError = new AtomAsyncUtils.AtomAsyncValue().toError(error); this.set(asyncError); }; module.exports = AtomCursor;