talio
Version:
a smaller, less confuse, mercury
133 lines (118 loc) • 4.07 kB
text/coffeescript
Delegator = require 'dom-delegator'
extend = require 'xtend'
immupdate = require 'immupdate'
# ~ export virtual-dom and h, just because it is cheap
module.exports['virtual-dom'] = require 'virtual-dom'
module.exports.h = require 'virtual-dom/h'
# /
# ~ event handlers
module.exports.sendEvent = require 'value-event/event'
module.exports.sendValue = require 'value-event/value'
module.exports.sendClick = require 'value-event/click'
module.exports.sendSubmit = require 'value-event/submit'
module.exports.sendChange = require 'value-event/change'
module.exports.sendKey = require 'value-event/key'
module.exports.sendDetail = (require 'value-event/base-event') (ev, broadcast) ->
detail = ev._rawEvent.detail
data = extend detail, this.data
broadcast data
# /
# ~ state factory
class TalioState
type: 'TalioState'
constructor: (@state) ->
silentlyUpdate: ->
u = immupdate.bind @, @state
@state = u.apply @, arguments
change: ->
@silentlyUpdate.apply @, arguments
@cb(@state) if @cb
subscribe: (cb) -> @cb = cb
itself: -> @state
get: (prop) ->
ret = @state
try
for degree in prop.split '.'
ret = ret[degree]
catch e
return ret
module.exports.StateFactory = (dict) -> new TalioState dict
# / state factory
# ~ mainloop
raf = require('raf')
TypedError = require('error/typed')
InvalidUpdateInRender = TypedError(
type: 'talio.invalid.update.in-render'
message: 'talio: Unexpected update occurred in loop.\n' + 'We are currently rendering a view, ' + 'you can\'t change state right now.\n' + 'The diff is: {stringDiff}.\n' + 'SUGGESTED FIX: find the state mutation in your view ' + 'or rendering function and remove it.\n' + 'The view should not have any side effects.\n'
diff: null
stringDiff: null)
mainloop = (initialState, view, channels, opts) ->
opts = opts or {}
currentState = initialState
create = opts.create
diff = opts.diff
patch = opts.patch
redrawScheduled = false
tree = opts.initialTree or view(currentState, channels)
target = opts.target or create(tree, opts)
inRenderingTransaction = false
currentState = null
update = (state) ->
if inRenderingTransaction
throw InvalidUpdateInRender(
diff: state._diff
stringDiff: JSON.stringify(state._diff))
if currentState == null and !redrawScheduled
redrawScheduled = true
raf redraw
currentState = state
return
redraw = ->
redrawScheduled = false
return if currentState == null
inRenderingTransaction = true
try
newTree = view(currentState, channels)
catch e
console.error "We had a problem while rendering the tree with the following state:", currentState
console.debug "Aborting the render."
inRenderingTransaction = false
newTree = tree
console.debug e.stack
if opts.createOnly
inRenderingTransaction = false
create newTree, opts
else
patches = diff(tree, newTree, opts)
inRenderingTransaction = false
target = patch(target, patches, opts)
tree = newTree
currentState = null
return
return {
target: target
update: update
}
# / mainloop
# ~ the function that starts everything
module.exports.Delegator = Delegator
module.exports.delegator = Delegator()
module.exports.run = (domnode, vrender, handlers, BaseState) ->
# create a blank state if none provided
if not BaseState or BaseState.type != 'TalioState'
BaseState = new TalioState {}
# allocate handlers in the dom-delegator for the supplied channels
createChannel = (acc, name) ->
acc[name] = Delegator.allocateHandle(
handlers[name].bind(handlers, BaseState)
)
return acc
channels = Object.keys(handlers).reduce createChannel, {}
theloop = mainloop(BaseState.itself(), vrender, channels,
diff: require 'virtual-dom/vtree/diff'
patch: require 'virtual-dom/vdom/patch'
create: require 'virtual-dom/vdom/create-element'
)
domnode.appendChild theloop.target
BaseState.subscribe (state) -> theloop.update state
# /