@danielkalen/simplybind
Version:
Magically simple, framework-less one-way/two-way data binding for frontend/backend in ~5kb.
259 lines (160 loc) • 7.03 kB
text/coffeescript
defineProperty = Object.defineProperty
getDescriptor = Object.getOwnPropertyDescriptor
import [browserOnly] helpers-changeEvent.coffee
import [browserOnly] helpers-requiresDomDescriptorFix.coffee
import [browserOnly] helpers-windowPropsToIgnore.coffee
setValueNoop = (v, publisher)->
genID = ()-> ''+(++currentID)
genObj = ()-> Object.create(null)
genProxiedInterface = (isSub, completeCallback)-> (subject, customOptions, saveOptions)->
SimplyBind(subject, customOptions, saveOptions, isSub, completeCallback)
genSelfUpdater = (binding, fetchValue)->
binding.selfUpdater or
binding.selfUpdater = new Binding ()->
if fetchValue then binding.setValue(binding.fetchDirectValue(), binding, true) else binding.updateAllSubs(binding)
, 'Func', {}
# ==== Checks =================================================================================
targetIncludes = (target, item)-> target and target.indexOf(item) isnt -1
checkIf =
isDefined: (subject)-> subject isnt undefined
isArray: (subject)-> subject instanceof Array
isObject: (subject)-> typeof subject is 'object' and subject # 2nd check is to test against 'null' values
isString: (subject)-> typeof subject is 'string'
isNumber: (subject)-> typeof subject is 'number'
isFunction: (subject)-> typeof subject is 'function'
isBindingInterface: (subject)-> subject instanceof BindingInterface
import [browserOnly] helpers-checkIf.DOM.coffee
# ==== Descriptor Modification =================================================================================
fetchDescriptor = (object, property, isProto)->
descriptor = getDescriptor(object, property)
if descriptor
descriptor.configurable = true if isProto
return descriptor
else if objectProto=Object.getPrototypeOf(object)
return fetchDescriptor(objectProto, property, true)
convertToLive = (bindingInstance, object, onlyArrayMethods)->
_ = bindingInstance
_.origDescriptor = fetchDescriptor(object, _.property) if not _.origDescriptor
if onlyArrayMethods
arrayMutatorMethods.forEach (method)-> # Using forEach because we need a closure here
defineProperty object, method,
configurable: true
value: ()->
result = Array::[method].apply object, arguments
_.updateAllSubs(_)
return result
else
if _.type is 'Proxy'
origFn = _.origFn = _.value
context = object
_.value = result:null, args:null
if checkIf.isFunction(origFn)
slice = [].slice
getterValue = proxyFn = ()->
args = slice.call(arguments)
_.value.args = args = if _.selfTransform then _.selfTransform(args) else args
_.value.result = result = origFn.apply(context, args)
_.updateAllSubs(_)
return result
defineProperty object, _.property,
configurable: _.isLiveProp = true
get: ()-> getterValue
set: (newValue)->
if not checkIf.isFunction(newValue)
getterValue = newValue
else if newValue isnt origFn
origFn = _.origFn = newValue if newValue isnt proxyFn
getterValue = proxyFn if getterValue isnt proxyFn
return
# 'ObjectProp' or 'Array' type bindings
else import [browserOnly] helpers-convertToLive-avoidDomTypes.coffee
propertyDescriptor = _.origDescriptor or dummyPropertyDescriptor
_.origGetter = propertyDescriptor.get.bind(object) if propertyDescriptor.get
_.origSetter = propertyDescriptor.set.bind(object) if propertyDescriptor.set
shouldWriteLiveProp = propertyDescriptor.configurable
import [browserOnly] helpers-convertToLive-StylingConstructorCheck.coffee
import [browserOnly] helpers-convertToLive-WebkitDomDescriptorFix.coffee
if shouldWriteLiveProp
typeIsArray = _.type is 'Array'
shouldIndicateUpdateIsFromSelf = not _.origSetter and not typeIsArray
defineProperty object, _.property,
configurable: _.isLiveProp = true
enumerable: propertyDescriptor.enumerable
get: _.origGetter or ()-> _.value
set: (newValue)-> _.setValue(newValue, _, shouldIndicateUpdateIsFromSelf); return
if typeIsArray
convertToLive(_, object[_.property], true)
return
convertToReg = (bindingInstance, object, onlyArrayMethods)->
if onlyArrayMethods
delete object[method] for method in arrayMutatorMethods
else
_ = bindingInstance
newDescriptor = _.origDescriptor
newDescriptor.value = (_.origFn or _.value) unless newDescriptor.set or newDescriptor.get
defineProperty object, _.property, newDescriptor
# ==== Object cloning =================================================================================
cloneObject = (object)->
clone = genObj()
clone[key] = object[key] for key of object
return clone
extendState = (base, stateToInherit)->
stateMapping = Object.keys(stateToInherit)
base[key] = stateToInherit[key] for key in stateMapping
return
# ==== Binding Cache =================================================================================
cache =
get: (object, isFunction, selector, isMultiChoice)->
if isFunction
return boundInstances[object._sb_ID]
else
import [browserOnly] helpers-cache.get.DOMChoice_group.coffee
if object._sb_map and object._sb_map[selector]
return boundInstances[ object._sb_map[selector] ]
set: (B, isFunction)-> # B ==== Binding Object
if isFunction
defineProperty B.object, '_sb_ID', {'configurable':true, 'value':B.ID}
else
selector = B.selector
if B.object._sb_map
B.object._sb_map[selector] = B.ID
else
propsMap = {}
propsMap[selector] = B.ID
defineProperty B.object, '_sb_map', {'configurable':true, 'value':propsMap}
return
# ==== Placeholders =================================================================================
escapeRegEx = /[.*+?^${}()|[\]\\]/g
pholderRegEx = pholderRegExSplit = null
setPholderRegEx = ()->
start = settings.placeholder[0].replace(escapeRegEx, '\\$&')
end = settings.placeholder[1].replace(escapeRegEx, '\\$&')
middle = "[^#{end}]+"
pholderRegEx = new RegExp("#{start}(#{middle})#{end}", 'g')
pholderRegExSplit = new RegExp("#{start}#{middle}#{end}", 'g')
return
setPholderRegEx() # Create the regEx on init
applyPlaceholders = (contexts, values, indexMap)->
output = ''
for contextPart,index in contexts
output += contextPart
output += values[indexMap[index]] if indexMap[index]
return output
import [browserOnly] helpers-scanTextNodesPlaceholders.coffee
# ==== Errors + Warnings =================================================================================
throwError = (errorName)->
throw new Error 'SimplyBind: '+(errors[errorName] or errorName)
throwWarning = (warningName, depth)-> unless settings.silent
errSource = getErrSource(depth)
warn = errors[warningName]
warn += "\n\n"+errSource
console.warn('SimplyBind: '+warn)
return
throwErrorBadArg = (arg)->
throwError "Invalid argument/s (#{arg})", true
return
getErrSource = (depth)->
((new Error).stack or '')
.split('\n')
.slice(depth+3)
.join('\n')