UNPKG

@danielkalen/simplybind

Version:

Magically simple, framework-less one-way/two-way data binding for frontend/backend in ~5kb.

232 lines (148 loc) 9.39 kB
do ()-> import parts/closure-helpers.coffee import parts/closure-errorsAndWarnings.coffee boundInstances = {} globalOptions = silent: false liveProps: true dispatchEvents: false updateEvenIfSame: false updateOnBind: true mutateInherited: false trackArrayChildren: false simpleSelector: false promiseTransforms: false placeholder: ['{{', '}}'] setPholderRegEx() SimplyBind = (subject, options, isProxiedFunc)-> if (!subject and subject isnt 0) or (!checkIf.isString(subject) and !checkIf.isNumber(subject) and !checkIf.isFunction(subject) and subject not instanceof Array) throwError('invalidParamName') unless checkIf.isBindingInterface(subject) if checkIf.isObject(subject) and subject not instanceof Array # Indicates it's a Binding instance object due to the above check return new BindingInterface subject._, 1#, subject.placeholder else return new BindingInterface null, 0, null, subject, isProxiedFunc, options Binding = (object, type, state, isProxiedFunc)-> extendState(@, state) @type = type # ObjectProp | Array | Func | Event | DOMAttr | DOMValue | DOMCheckbox | DOMRadio @object = object # The subject object of this binding, i.e. function, array, {}, DOM el, etc. @ID = genID() # Assigned only after passing a valid object to .of() @deps = [] # Dependents array listing all of the objects that will be updated upon value update @depsMap = {1:genObj(), 2:genObj()} # Map dependents by ID on the '1' object if is one-way binding and '2' as wel if is two-way (deps that have this object as a dep as well) @depsPholders = genObj() # DependentID:dep-placeholder mapping @myPholders = genObj() # DependentID:self-placeholder mapping indicating which deps will receive a value of a specific placeholder from this binding (if applicable) @transforms = genObj() # DependentID:transformFn mapping @conditions = genObj() # DependentID:coniditionFn mapping @attachedEvents = [] # Array listing all of the events currently listened on @object # ==== Properties declared later or inherited from binding interface ================================================================================= # @options = options # @valueOriginal = undefined # Value on init # @value = undefined # Will represent the actual current value of the binding/object # @property = property # The property name or array index or event callback argument # @selector = selector # The property name or array index or event callback argument # @customEventMethod = {} # Names of the event emitter/trigger methods (if applicable) # @pholderValues = {} # Placeholder value mapping # @pholderContexts = {} # Placeholder surroundings (original binding value split by the placeholder regEx) # @pholderIndexMap = {} # Placeholder occurence mapping, i.e. the placeholder name for each placeholder occurence # @placeholder = "" # The last specified placeholder to bind the value to # @descriptor = "" # Describes the type of property, i.e. 'attr:data-name' to indicate a DOMAttr type binding # @isLiveProp = Boolean # Indicates whether or not the Object/Object's propety have been modified to be a live property # @isDom = Boolean # Indicates whether or not the binding's object is a DOM object # @pollInterval = ID # The interval ID of the timer that manually polls the object's value at a set interval # @arrayBinding = Binding # Reference to the parent array binding (if exists) for an index-of-array binding (i.e. SimplyBind(array)) # @trackedChildren = [] # For Array type bindings - indicates which indicies of the array are tracked for changes (applicable when @options.trackArrayChildren) is true # @eventName = "" # The name of the event this binding is listening to (for Event type bindings) # @isEmitter = Boolean # Tracker to let us know we shouldn't handle the event update we received as it is the event this binding just emitted # @eventHandler = Function # The callback that gets triggered upon an event emittance (for Even type bindings) # @eventObject = Event # The dispatched event object (for Event type bindings) # @selfTransform = Function # The transform function that new values being set to this binding are being passed through during @setValue (if applicable) # @throttleRate = milliseconds # The rate in which the binding's dependents will be updated only once in # @throttleTimeout = ID # The timeout ID of the delay timer to update a throttled binding # @lastUpdate = epoch timestamp # Last time the deps have been updated; used for throttle functions # @hasTransforms = Boolean # Indicates whether or not at least 1 dependent has a transform (to refrain from executing useless code) # @hasConditions = Boolean # Indicates whether or not at least 1 dependent has a condition (to refrain from executing useless code) # @isAsync = Boolean # Indicates if this is an async binding (currently only used for Event bindings) ### ========================================================================== ### if @type is 'Event' or @type is 'Func' @options.updateOnBind = false @options.updateEvenIfSame = true import [browserOnly] parts/Binding-DOMChoice_group.coffee unless @type is 'Event' or isProxiedFunc @value = @valueOriginal = subjectValue = @fetchDirectValue() if @type is 'ObjectProp' and not checkIf.isDefined(subjectValue) @object[@property] = subjectValue # Define the prop on the object if it non-existent if @placeholder and not @pholderValues @scanForPholders() @makePropertyLive() @attachEvents() # IF this is a binding to a specific index of an array then we must make this binding update the array on change if the array binding is tracking its children if @object instanceof Array and @type isnt 'Array' @arrayBinding = arrayBinding = cache.get(@object, true) if arrayBinding and arrayBinding.options.trackArrayChildren and not arrayIncludes(arrayBinding.trackedChildren, @property) arrayBinding.trackedChildren.push(@property) SimplyBind(@property).of(@object).to arrayBinding.updateSelf return boundInstances[@ID] = @ ###* * Stage definitions: * * 0: Selection: Got selector, awaiting object. * 1: Indication: Got object, awaiting proxied property / function / Binding-object. * 2: Binding Selection: Got proxied selector, awaiting proxied object. * 3: Binding Complete: Complete, awaiting additional (optional) bindings/mutations. ### BindingInterface = (binding, stage, inheritedState, subject, isProxiedFunc, options)-> extendState(@, inheritedState) if inheritedState @stage = stage or 0 @proxies ?= [] @state ?= {} # @state.hasTransform # @state.hasMultiTransforms # @state.initialBinding # @state.hasEventName switch @stage when 0 @optionsPassed = options ||= {} @options = {} for key of globalOptions @options[key] = if options[key]? then options[key] else globalOptions[key] if checkIf.isFunction(subject) @stage = 1 binding = @createBinding(subject, 'Func', true, isProxiedFunc) else if subject instanceof Array @stage = 1 binding = @createBinding(subject, 'Array', true) else subject = subject.toString() if checkIf.isNumber(subject) @selector = @property = subject unless @options.simpleSelector if arrayIncludes(@selector, ':') split = @property.split(':') @descriptor = split[0] # An addl. string in the selector name defining the scope of the selection (i.e. SimplyBind('attr:class')) @property = split[1] if arrayIncludes(@selector, '.') # Placeholder extraction split = @property.split('.') @property = split[0] @placeholder = split.slice(1).join('.') @selector = @property when 1 unless binding # is defined import [browserOnly] parts/BindingInterface-parseDOMObject.coffee newObjectType = switch when @state.hasEventName then 'Event' import [browserOnly] parts/BindingInterface-setType.DOM.coffee else 'ObjectProp' if @descriptor is 'multi' binding = new BindingMulti(subject, newObjectType, @) else binding = @createBinding(subject, newObjectType) @defineMainProps(binding) # ==== Binding Prototype ================================================================================= import parts/Binding-prototype.coffee # ==== BindingInterface Prototype ================================================================================= import parts/BindingInterface-private_prototype.coffee import parts/BindingInterface-prototype.coffee # ==== BindingInterface Multi Proxy ================================================================================= import parts/BindingMulti.coffee # ==== SimplyBind props/methods ================================================================================= import parts/SimplyBind-methods.coffee # ==== Exports ================================================================================= import [browserOnly] parts/SimplyBind-export.window.coffee import [nodeOnly] parts/SimplyBind-export.module.coffee