UNPKG

neft

Version:

Universal Platform

381 lines (308 loc) 12.7 kB
'use strict' assert = require 'src/assert' utils = require 'src/utils' signal = require 'src/signal' log = require 'src/log' log = log.scope 'Renderer' {Emitter} = signal {emitSignal} = Emitter {isArray} = Array module.exports = (Renderer, Impl) -> NOP = -> getObjAsString = (item) -> "(#{item.constructor.__name__})#{item._component?.fileName}:#{item.id}" getObjFile = (item) -> path = '' tmp = item while tmp if path = tmp.constructor.__file__ break else tmp = tmp._parent path or '' class UtilsObject extends Emitter initObject = (component, opts) -> for prop, val of opts path = exports.splitAttribute prop prop = path[path.length - 1] obj = exports.getObjectByPath @, path if typeof val is 'function' `//<development>` if typeof obj[prop] isnt 'function' log.error "Object '#{obj}' has no function '#{prop}'" continue `//</development>` obj[prop] exports.bindSignalListener(@, val) else if Array.isArray(val) and val.length is 2 and typeof val[0] is 'function' and Array.isArray(val[1]) continue else `//<development>` unless prop of obj log.error "Object '#{obj}' has no property '#{prop}'" continue `//</development>` obj[prop] = val for prop, val of opts path = exports.splitAttribute prop prop = path[path.length - 1] obj = exports.getObjectByPath @, path if Array.isArray(val) and val.length is 2 and typeof val[0] is 'function' and Array.isArray(val[1]) obj.createBinding prop, val, component return setOpts = (component, opts) -> assert.instanceOf component, Renderer.Component if typeof opts.id is 'string' @id = opts.id if Array.isArray(opts.properties) for property in opts.properties createProperty @, property if Array.isArray(opts.signals) for signalName in opts.signals createSignal @, signalName if Array.isArray(opts.children) for child in opts.children if child instanceof Renderer.Item child.parent = @ else if child instanceof Renderer.Extension and not child._bindings?.target child.target = @ classElem = createClass component, opts classElem.target = @ return CHANGES_OMIT_ATTRIBUTES = __proto__: null id: true properties: true signals: true children: true createClass = (component, opts) -> classElem = Renderer.Class.New component classElem._priority = -1 {changes} = classElem for prop, val of opts if typeof val is 'function' changes.setFunction prop, val else if Array.isArray(val) and val.length is 2 and typeof val[0] is 'function' and Array.isArray(val[1]) changes.setBinding prop, val else if not CHANGES_OMIT_ATTRIBUTES[prop] changes.setAttribute prop, val classElem @createProperty = createProperty = (object, name) -> assert.isString name assert.notLengthOf name, 0 if name of object.$ return exports.defineProperty object: object.$ name: name namespace: '$' return createSignal = (object, name) -> assert.isString name assert.notLengthOf name, 0 signal.Emitter.createSignalOnObject object.$, name return @setOpts = (object, component, opts) -> if opts.id? object.id = opts.id if object instanceof Renderer.Class or object instanceof FixedObject initObject.call object, component, opts else setOpts.call object, component, opts return emptyComponent = null @initialize = (object, component, opts) -> component ?= (emptyComponent ?= new Renderer.Component) assert.instanceOf component, Renderer.Component Object.seal object object._component = component Impl.initializeObject object, object.constructor.__name__ if opts UtilsObject.setOpts object, component, opts constructor: -> Emitter.call @ @id = '' @_impl = null @_bindings = null @_component = null unless this instanceof Renderer.Class @_classExtensions = null @_classList = [] @_classQueue = [] @_extensions = [] Impl.createObject @, @constructor.__name__ createBinding: (prop, val, component, ctx=@) -> assert.isString prop assert.isArray val if val? assert.instanceOf component, Renderer.Component `//<development>` unless prop of @ log.warn "Binding on the '#{prop}' property can't be created, because this object (#{@toString()}) doesn't have such property" return `//</development>` if not val and (not @_bindings or not @_bindings.hasOwnProperty(prop)) return bindings = @_bindings ?= {} if bindings[prop] isnt val bindings[prop] = val Impl.setItemBinding.call @, prop, val, component, ctx return clone: (component, opts) -> clone = @constructor.New component if @id clone.id = @id if @_$ clone._$ = Object.create @_$ MutableDeepObject.call clone._$, clone if opts setOpts.call clone, component, opts clone toString: -> getObjAsString @ class FixedObject extends UtilsObject constructor: (component, opts) -> super component, opts class MutableDeepObject extends signal.Emitter constructor: (ref) -> assert.instanceOf ref, UtilsObject super() @_ref = ref @_impl = bindings: null @_component = ref._component @_bindings = null @_extensions = [] createBinding: UtilsObject::createBinding toString: -> getObjAsString @_ref class DeepObject extends MutableDeepObject constructor: (ref) -> super ref class CustomObject extends MutableDeepObject constructor: (ref) -> super ref Impl.DeepObject = DeepObject exports = Object: UtilsObject FixedObject: FixedObject DeepObject: DeepObject MutableDeepObject: MutableDeepObject CustomObject: CustomObject getPropHandlerName: getPropHandlerName = do -> cache = Object.create null (prop) -> cache[prop] ||= "on#{utils.capitalize(prop)}Change" getPropInternalName: getPropInternalName = do -> cache = Object.create null (prop) -> cache[prop] ||= "_#{prop}" getInnerPropName: do -> cache = Object.create(null) cache[''] = '' (val) -> cache[val] ?= '_' + val splitAttribute: do -> cache = Object.create null (attr) -> cache[attr] ?= attr.split '.' getObjectByPath: (item, path) -> len = path.length - 1 i = 0 while i < len unless item = item[path[i]] return null i++ item bindSignalListener: (object, func) -> (arg1, arg2) -> if comp = object._component arr = comp.objectsOrderSignalArr arr[arr.length - 2] = arg1 arr[arr.length - 1] = arg2 func.apply object, arr else func.call object, arg1, arg2 defineProperty: (opts) -> assert.isPlainObject opts {name, namespace, valueConstructor, implementation, implementationValue} = opts `//<development>` {developmentSetter} = opts `//</development>` prototype = opts.object or opts.constructor:: customGetter = opts.getter customSetter = opts.setter # signal signalName = getPropHandlerName name if opts.hasOwnProperty('constructor') signal.Emitter.createSignal opts.constructor, signalName, opts.signalInitializer else signal.Emitter.createSignalOnObject prototype, signalName, opts.signalInitializer # getter internalName = getPropInternalName name propGetter = basicGetter = Function "return this.#{internalName}" if valueConstructor propGetter = -> @[internalName] ?= new valueConstructor @ # setter if valueConstructor if developmentSetter `//<development>` propSetter = basicSetter = developmentSetter `//</development>` else propSetter = basicSetter = NOP else if namespace? namespaceSignalName = "on#{utils.capitalize(namespace)}Change" uniquePropName = namespace + utils.capitalize(name) func = do -> funcStr = "return function(val){\n" `//<development>` if developmentSetter? funcStr += "debug.call(this, val);\n" `//</development>` funcStr += "var oldVal = this.#{internalName};\n" funcStr += "if (oldVal === val) return;\n" if implementation? if implementationValue? funcStr += "impl.call(this._ref, implValue.call(this._ref, val));\n" else funcStr += "impl.call(this._ref, val);\n" funcStr += "this.#{internalName} = val;\n" funcStr += "emitSignal(this, '#{signalName}', oldVal);\n" funcStr += "emitSignal(this._ref, '#{namespaceSignalName}', '#{name}', oldVal);\n" funcStr += "};" func = new Function 'impl', 'implValue', 'emitSignal', 'debug', funcStr propSetter = basicSetter = func implementation, implementationValue, emitSignal, developmentSetter else func = do -> funcStr = "return function(val){\n" `//<development>` if developmentSetter? funcStr += "debug.call(this, val);\n" `//</development>` funcStr += "var oldVal = this.#{internalName};\n" funcStr += "if (oldVal === val) return;\n" if implementation? if implementationValue? funcStr += "impl.call(this, implValue.call(this, val));\n" else funcStr += "impl.call(this, val);\n" funcStr += "this.#{internalName} = val;\n" funcStr += "emitSignal(this, '#{signalName}', oldVal);\n" funcStr += "};" func = new Function 'impl', 'implValue', 'emitSignal', 'debug', funcStr propSetter = basicSetter = func implementation, implementationValue, emitSignal, developmentSetter # custom desc getter = if customGetter? then customGetter(propGetter) else propGetter setter = if customSetter? then customSetter(propSetter) else propSetter # override prototype[internalName] = opts.defaultValue utils.defineProperty prototype, name, null, getter, setter prototype setPropertyValue: (item, prop, val) -> assert.instanceOf item, Renderer.Item assert.isString prop internalName = getPropInternalName prop signalName = getPropHandlerName prop oldVal = item[internalName] item[internalName] = val emitSignal item, signalName, oldVal return