UNPKG

@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
defineProperty = Object.defineProperty getDescriptor = Object.getOwnPropertyDescriptor import [browserOnly] helpers-changeEvent.coffee import [browserOnly] helpers-requiresDomDescriptorFix.coffee import [browserOnly] helpers-windowPropsToIgnore.coffee setValueNoop = (v, publisher)-> @updateAllSubs(publisher or @) 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')