src/define.coffee in luca-0.9.65 vs src/define.coffee in luca-0.9.76

- old
+ new

@@ -1,37 +1,76 @@ # Component Definition Helpers # -# # We have customized the core Backbone.extend process to use a slightly # different syntax, which allows us to intercept the component definition at # various points, and maintain information about classes being defined, and # the relationships between inherited classes, etc. +# +# Under the hood it isn't much more than Backbone.View.extend(@proto) +# +# Luca provides a self-documenting component generation language which +# allows you to build the @proto property in a way which captures the intent +# of the interface being described. +# +# Example: +# myForm = MyApp.register 'MyForm' +# +# myForm.extends 'Luca.components.FormView' +# +# myForm.triggers 'some:custom:hook' +# +# myForm.publicMethods +# publicMethod: ()-> ... +# +# myForm.classMethods +# classMethod: ()-> ... +# +# This gives us the ability to inspect our component registry at run time, +# auto-generate nice documentation, build development tools, etc. -# _.def, or Luca.define returns a chainable object which allows you to define -# your components with a readable syntax. For example: - -# _.def("Luca.View").extends("Backbone.View").with the_good:"shit" -# _.def("MyView").extends("Luca.View").with the_custom:"shit" -_.mixin - def: Luca.component = Luca.define = Luca.register = (componentName)-> new DefineProxy(componentName) - -# The define proxy chain sets up a call to Luca.extend, which is a wrapper around Luca and Backbone component class' extend function. -class DefineProxy - constructor:(componentName)-> +class ComponentDefinition + constructor:(componentName, @autoRegister=true)-> @namespace = Luca.util.namespace() @componentId = @componentName = componentName @superClassName = 'Luca.View' + @properties ||= {} + @_classProperties ||= {} if componentName.match(/\./) @namespaced = true parts = componentName.split('.') @componentId = parts.pop() @namespace = parts.join('.') # automatically add the namespace to the namespace registry Luca.registry.addNamespace( parts.join('.') ) + Luca.define.__definitions.push(@) + + @create: (componentName, autoRegister=Luca.config.autoRegister)-> + new ComponentDefinition(componentName, autoRegister) + + isValid: ()-> + return false unless _.isObject(@properties) + return false unless Luca.util.resolve(@superClassName)? + return false unless @componentName? + true + + isDefined: ()-> + @defined is true + + isOpen: ()-> + !!(@isValid() and not @isDefined()) + + meta: (key, value)-> + metaKey = @namespace + '.' + @componentId + metaKey = metaKey.replace(/^\./,'') + data = Luca.registry.addMetaData(metaKey, key, value) + + @properties.componentMetaData = ()-> + Luca.registry.getMetaDataFor(metaKey) + # allow for specifying the namespace in: (@namespace)-> @ # allow for multiple ways of saying the same thing for readability purposes from: (@superClassName)-> @ @@ -41,87 +80,181 @@ triggers: (hooks...)-> _.defaults(@properties ||= {}, hooks: []) for hook in hooks @properties.hooks.push(hook) @properties.hooks = _.uniq(@properties.hooks) + @meta("hooks", @properties.hooks) @ includes: (includes...)-> _.defaults(@properties ||= {}, include: []) for include in includes @properties.include.push(include) @properties.include = _.uniq(@properties.include) + @meta("includes", @properties.include) @ - mixesIn: (mixins...)-> - _.defaults(@properties ||= {}, mixins: []) - for mixin in mixins - @properties.mixins.push(mixin) - @properties.mixins = _.uniq(@properties.mixins) + mixesIn: (concerns...)-> + _.defaults(@properties ||= {}, concerns: []) + for concern in concerns + @properties.concerns.push(concern) + @properties.concerns = _.uniq(@properties.concerns) + + @meta("concerns", @properties.concerns) @ - defaultProperties: (properties={})-> + contains: (components...)-> + _.defaults(@properties, components: []) + @properties.components = components + @ + + validatesConfigurationWith:(validationConfiguration={})-> + @meta "configuration validations", validationConfiguration + @properties.validatable = true + @ + + beforeDefinition: (callback)-> + @_classProperties.beforeDefinition = callback + @ + + afterDefinition: (callback)-> + @_classProperties.afterDefinition = callback + @ + + classConfiguration: (properties={})-> + @meta("class configuration", _.keys(properties)) + _.defaults((@_classProperties||={}), properties) + @ + + publicConfiguration: (properties={})-> + @meta("public configuration", _.keys(properties) ) _.defaults((@properties||={}), properties) + @ + privateConfiguration: (properties={})-> + @meta("private configuration", _.keys(properties) ) + _.defaults((@properties||={}), properties) + @ + + classInterface: (properties={})-> + @meta("class interface", _.keys(properties)) + _.defaults((@_classProperties||={}), properties) + @ + + publicInterface: (properties={})-> + @meta("public interface", _.keys(properties) ) + _.defaults((@properties||={}), properties) + @ + + privateInterface: (properties={})-> + @meta("private interface", _.keys(properties) ) + _.defaults((@properties||={}), properties) + @ + + # This is the end of the chain. It MUST be called + # in order for the component definition to be complete. + definePrototype: (properties={})-> + _.defaults((@properties||={}), properties) + at = if @namespaced Luca.util.resolve(@namespace, (window || global)) else (window||global) # automatically create the namespace if @namespaced and not at? eval("(window||global).#{ @namespace } = {}") at = Luca.util.resolve(@namespace,(window || global)) - at[@componentId] = Luca.extend(@superClassName,@componentName, @properties) + @meta("super class name", @superClassName ) + @meta("display name", @componentName) - if Luca.autoRegister is true - componentType = "view" if Luca.isViewPrototype( at[@componentId] ) + @properties.displayName = @componentName + + @properties.componentMetaData = ()-> + Luca.registry.getMetaDataFor(@displayName) - if Luca.isCollectionPrototype( at[@componentId] ) + @_classProperties?.beforeDefinition?(@) + + definition = at[@componentId] = Luca.extend(@superClassName,@componentName, @properties) + + if @autoRegister is true + componentType = "view" if Luca.isViewPrototype( definition ) + + if Luca.isCollectionPrototype( definition ) Luca.Collection.namespaces ||= [] Luca.Collection.namespaces.push( @namespace ) componentType = "collection" - componentType = "model" if Luca.isModelPrototype( at[@componentId] ) + componentType = "model" if Luca.isModelPrototype( definition ) # automatically register this with the component registry Luca.registerComponent( _.string.underscored(@componentId), @componentName, componentType) - at[@componentId] + @defined = true + unless _.isEmpty(@_classProperties) + _.extend(definition, @_classProperties) + + definition?.afterDefinition?.call(definition, @) + + definition + + # Aliases for the mixin definition -DefineProxy::behavesAs = DefineProxy::uses = DefineProxy::mixesIn +cd = ComponentDefinition:: +cd.concerns = cd.behavesAs = cd.uses = cd.mixesIn + # Aliases for the final call on the define proxy -DefineProxy::defines = DefineProxy::defaults = DefineProxy::exports = DefineProxy::defaultProperties -DefineProxy::defaultsTo = DefineProxy::enhance = DefineProxy::with = DefineProxy::defaultProperties +cd.register = cd.defines = cd.defaults = cd.exports = cd.defaultProperties = cd.definePrototype -# The last method of the DefineProxy chain is always going to result in +cd.defaultsTo = cd.enhance = cd.with = cd.definePrototype + +cd.publicMethods = cd.publicInterface +cd.privateMethods = cd.privateInterface +cd.classMethods = cd.classInterface + +_.extend (Luca.define = ComponentDefinition.create), + __definitions: [] + incomplete: ()-> + _( Luca.define.__definitions ).select (definition)-> + definition.isOpen() + close: ()-> + for open in Luca.define.incomplete() + open.register() if open.isValid() + Luca.define.__definitions.length = 0 + findDefinition: (componentName)-> + _( Luca.define.__definitions ).detect (definition)-> + definition.componentName is componentName + +Luca.register = (componentName)-> + new ComponentDefinition(componentName, true) + +_.mixin def: Luca.define + +# The last method of the ComponentDefinition chain is always going to result in # a call to Luca.extend. Luca.extend wraps the call to Luca.View.extend, # or Backbone.Collection.extend, and accepts the names of the extending, # and extended classes as strings. This allows us to maintain information # and references to the classes and their prototypes, mainly for the purposes # of introspection and development tools Luca.extend = (superClassName, childName, properties={})-> superClass = Luca.util.resolve( superClassName, (window || global) ) - superClass.__initializers ||= [] - unless _.isFunction(superClass?.extend) throw "Error defining #{ childName }. #{ superClassName } is not a valid component to extend from" properties.displayName = childName properties._superClass = ()-> superClass.displayName ||= superClassName superClass properties._super = (method, context=@, args=[])-> - # protect against a stack too deep error in weird cases # TODO: debug this better - + # protect against a stack too deep error in weird cases @_superClass().prototype[method]?.apply(context, args) definition = superClass.extend(properties) # _.def("MyView").extends("View").with @@ -131,52 +264,5 @@ include = Luca.util.resolve(include) if _.isString(include) _.extend(definition::, include) definition - -Luca.mixin = (mixinName)-> - namespace = _( Luca.mixin.namespaces ).detect (space)-> - Luca.util.resolve(space)?[ mixinName ]? - - namespace ||= "Luca.modules" - - resolved = Luca.util.resolve(namespace)[ mixinName ] - - console.log "Could not find #{ mixinName } in ", Luca.mixin.namespaces unless resolved? - - resolved - -Luca.mixin.namespaces = [ - "Luca.modules" -] - -Luca.mixin.namespace = (namespace)-> - Luca.mixin.namespaces.push(namespace) - Luca.mixin.namespaces = _( Luca.mixin.namespaces ).uniq() - -# Luca.decorate('Luca.View').with('Luca.modules.MyCustomMixin') -Luca.decorate = (componentPrototype)-> - componentPrototype = Luca.util.resolve(componentPrototype).prototype if _.isString(componentPrototype) - - return with: (mixin)-> - mixinDefinition = Luca.mixin(mixin) - - mixinPrivates = _( mixinDefinition ).chain().keys().select (key)-> - "#{ key }".match(/^__/) - - sanitized = _( mixinDefinition ).omit( mixinPrivates.value() ) - - _.extend(componentPrototype, sanitized) - - # When a mixin is included, we may want to do things - mixinDefinition?.__included?.call(mixinDefinition, mixin) - - superclassMixins = componentPrototype._superClass()::mixins - - componentPrototype.mixins ||= [] - componentPrototype.mixins.push( mixin ) - componentPrototype.mixins = componentPrototype.mixins.concat( superclassMixins ) - - componentPrototype.mixins = _( componentPrototype.mixins ).chain().uniq().compact().value() - - componentPrototype