Skip to content

Instantly share code, notes, and snippets.

@necoco
Last active July 30, 2017 05:39
Show Gist options
  • Save necoco/b9752cc5444ebc776c75 to your computer and use it in GitHub Desktop.
Save necoco/b9752cc5444ebc776c75 to your computer and use it in GitHub Desktop.
painless FRP library Flor prototype
class FlorContinue
constructor: (key)->
@key = key
class FlorEnd
class Flor
@_id = 1
@generateId = ()->
@_id++;
constructor: (values)->
defineProp = ()->
target = ()->
fn = arguments[arguments.length-1]
for value in arguments
if typeof value == 'string'
target[value] = fn
@on = defineProp()
@after = defineProp()
@before = defineProp()
@progress = defineProp()
@error = defineProp()
@_values = {}
@_on = {}
@_pendings = {}
@_progress = {}
@_keys = []
for own key, value of values
if value instanceof FlorBehavior
@_defineValue key, value
@_defineOnValue key
value.register @, key
unregister: ()->
for own _, value of @_values
value.unregister()
update: ()->
for own key, value of @_values
if value.update()
@_invoke key
cancel: (key)->
@_progress[key]?._clear()
delete @_progress[key]
_defineValue: (key, content)->
@_values[key] = content
@_keys.push key
Object.defineProperty @, key,
set: (newValue)->
@_values[key].set(newValue)
@_invoke key
_invoke: (key, cont)->
@_recalc key
if @_pendings[key]?
@_on[@_pendings[key].key] @_pendings[key].value, false
@_on[key]? @_values[key].peek(), true, false
_invokeContinue: (key)->
@_on[key]? @_values[key].peek(), false, true
_recalc: (key)->
for _, pending of @_pendings
pending.accessor._checkAndClear key
_defineOnValue: (key)->
Object.defineProperty @on, key,
set: (newValue)=>
@_on[key] = @_wrapOn key, newValue
_wrapOn: (key, fn)->
accessor = @_newAccessor key
cleanUp = ()=>
accessor._clear()
accessor._clearValues()
delete @_pendings[key]
delete @_progress[key]
(value, isNewValue, isContinue)=>
@before[key]?(value)
try
@cancel key if isNewValue and @_progress[key]?
@_progress[key] = accessor
result = fn accessor, value
cleanUp()
@after[key]?(result)
catch err
if err instanceof FlorContinue
@progress[key]? accessor.progress
@_pendings[err.key] = {key, value, accessor}
else
unless @error[key]?(err)
cleanUp()
throw err
_newAccessor: (key)->
accessor =
key: key
versions: {}
storage: {}
sideEffects: {}
sideEffectsAsync: {}
recalc: {}
watch: false
asyncVersion: {}
o = (fn)=>
unless fn.__flor_id__?
fn.__flor_id__ = Flor.generateId()
unless accessor.sideEffects[fn.__flor_id__]?
accessor.watch = fn.__flor_id__
()->
try
accessor.sideEffects[fn.__flor_id__] =
(fn.apply null, arguments) || true
finally
accessor.watch = false
else
()-> accessor.sideEffects[fn.__flor_id__]
for key in @_keys
@_defineAccessorKey o, key, accessor
getOrThrow = (fn)->
{
get: ()->
if accessor.sideEffects[fn.__flor_id__]?
accessor.sideEffects[fn.__flor_id__]
else
throw new FlorContinue(accessor.key)
_ready: ()->
accessor.sideEffects[fn.__flor_id__]?
}
createAsync = (fn, wrapper)=>
unless accessor.sideEffectsAsync[fn.__flor_id__]?
accessor.watch = fn.__flor_id__
wrapper
else
()-> getOrThrow(fn)
o.async = (fn)=>
unless fn.__flor_id__?
fn.__flor_id__ = Flor.generateId()
id = fn.__flor_id__
accessor.asyncVersion[id] = accessor.asyncVersion[id] or 1
version = accessor.asyncVersion[id]
((id,version)=> createAsync fn, ()=>
args = Array.prototype.slice.call arguments, 0
args.push (value)=>
if accessor.asyncVersion[id] == version
accessor.asyncVersion[id]++
accessor.sideEffects[id] = value
if accessor.sideEffectsAsync[id]
@_invokeContinue accessor.key
try
fn.apply(null, args) unless accessor.sideEffectsAsync[id]
accessor.sideEffectsAsync[id] = true
finally
accessor.watch = false
getOrThrow(fn)
)(id, version)
o.or = (asyncValues)=>
for value in arguments
if typeof value == 'string'
return @_values[value].get() if @_values[value].ready()
else
return value.get() if value._ready()
throw new FlorContinue accessor.key
o._checkAndClear = (key)=>
if accessor.recalc[key]?
id = accessor.recalc[key]
delete accessor.recalc[key]
delete accessor.sideEffectsAsync[id] if accessor.sideEffects[id]
delete accessor.sideEffects[id]
o._clearValues = ()=>
for key of accessor.storage
@_values[key].clear()
o._clear = ()=>
accessor.recalc = {}
accessor.versions = {}
accessor.storage = {}
accessor.sideEffects = {}
accessor.sideEffectsAsync = {}
#must not clear accessor.asyncVersion
o
_defineAccessorKey: (o, key, accessor)->
Object.defineProperty o, key,
get: ()=>
value = @_values[key]
storage = accessor.storage
versions = accessor.versions
unless versions[key]?
storage[key] = value.get(key)
versions[key] = value.version()
else
version = @_values[key].version()
if version != versions[key]
storage[key] = value.get(key)
versions[key] = version
if accessor.watch
accessor.recalc[key] = accessor.watch
storage[key]
class FlorBehavior
constructor: (value)->
@value = value
@oldVersion = 0
@newVersion = 0
get: (key)->
@value
peek: ()->
@value
set: (newValue)->
@value = newValue
@versionUp()
version: ()->
@newVersion
versionUp: ()->
@newVersion++
clear: ()->
@value = null
register: ()->
unregister: ()->
update: ()-> false
ready: ()->
@oldVersion < @newVersion
class FlorEvent extends FlorBehavior
constructor: (value)->
super(value)
get: (key)->
throw new FlorContinue(key) unless @ready()
@oldVersion = @newVersion
@value
class FlorHandler extends FlorEvent
constructor: (receiver, name)->
super()
@receiver = receiver
@name = name
register: (flor, key)->
@old = @receiver[@name]
@receiver[@name] = (ev)->
flor[key] = ev
unregister: ()->
@receiver[@name] = @old
class FlorCallback extends FlorEvent
setCallback: (thisArg, register, unregister, args...)->
@thisArg = thisArg
@register = register
@unregister = unregister
@args = args
@
register: (flor, key)->
@cb = (value)->
flor[key] = value
@args.push @cb
@fn.apply @thisArg, @args
unregister: ()->
@unregister?.apply @thisArg, @args
class FlorWatch extends FlorEvent
constructor: (target, name)->
super()
@target = target
@name = name
@oldValue = @value = target[name]
update: ()->
if @oldValue != @target[@name]
@oldValue = @target[@name]
@set @oldValue
return true
return false
flor =
bloom: (values)->
new Flor values
create: (values)->
new Flor values
Behavior: (value)->
new FlorBehavior value
Event: ()->
new FlorEvent()
Handler: (receiver, name)->
new FlorHandler receiver, name
Callback: (thisArg, register, unregister, args)->
callback = new FlorCallback()
callback.setCallback.apply callback, arguments
Watcher: (target, prop)->
new FlorWatch(target, prop)
On: (thisArg, handler, args)->
callback = new FlorCallback()
args = Array.prototype.slice 1
args.unshift 'off'
args.unshift 'on'
args.unsfhit thiArg
callback.setCallback.apply callback, args
form = flor.bloom
keydown: flor.Event()
register: flor.Event()
username: flor.Behavior()
fullname: flor.Behavior()
checkResult: flor.Behavior(false)
showIndicator = ()-> console.log 'processing...'
checkAvailableAjax = (name, cb)-> cb(name != 'dame')
hideIndicator = ()-> console.log 'ok!'
registerAjax = (user, full, cb) -> cb(true)
disableButton = ()-> console.log 'button:disable'
enableButton = ()-> console.log 'button:enable'
feedbackUnavailableUserName = ()-> console.log 'unavailable username'
feedbackRegistration = ()-> console.log 'registered!!'
available = ($)->
$(showIndicator)()
result = $.async(checkAvailableAjax)($.username).get()
$(hideIndicator)()
result
processRegistration = ($)->
$(showIndicator)()
$.async(registerAjax)($.username, $.fullname).get()
$(hideIndicator)()
form.on.keydown = ($)->
$(disableButton)()
unless available($)
$(feedbackUnavailableUserName)()
else if $.username and $.fullname
$(enableButton)()
return true
return false
form.after.keydown = (value)->
form.checkResult = value
form.on.register = ($)->
unless $.checkResult
return false
$(disableButton)()
processRegistration($)
$(feedbackRegistration)()
return true
form.fullname = "tshinsay"
form.username = "hoge"
#form.username = "dame"
form.keydown = true
form.register = true
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment