'use strict'; define 'aura/extensions/widget/composable', -> stamp = extend = null advisorable = (advisor) -> # TODO merge advices in the composition chain advice_names = ['before', 'after', 'around'] extract_advices = (object) -> found = [] for key of object when key.indexOf('_') isnt -1 [name, adviced] = key.split '_' if advice_names.indexOf(name) != -1 callbacks = if object[key].length then object[key] else [object[key]] delete object[key] found.push key: key name: name adviced: adviced callbacks: callbacks found advisor.advice = (widget) -> advices = extract_advices widget for advice in advices # in order to preserve declaration order, we must reverse the callbacks order advice.callbacks.reverse() if advice.name == 'before' # Advice with all callbacks widget[advice.name] advice.adviced, callback for callback in advice.callbacks widget advisor.advisable = (factory) -> original = factory.compose stamp.mixIn factory, compose: (stamps...) -> {fixed: {methods: composition_methods}} = @composition advices = extract_advices composition_methods for stamped in stamps {fixed: {methods: stamp_methods}} = stamped advices = advices.concat extract_advices stamp_methods adviced_stamp = {} for advice in advices adviced_stamp[advice.key] ||= [] adviced_stamp[advice.key] = adviced_stamp[advice.key].concat advice.callbacks # Create a ultimate stamp with all advices arrays or functions merged # TODO do not store advices definitions in the prototype chain stamps.push stamp adviced_stamp original.apply factory, stamps advice: advisor.advice advisor composerable = (compositor) -> compositor.composable = (methods, state, enclose) -> factory = (options) -> # Inherit defaults from widget options = _.defaults options, factory.composition.fixed.methods.options # Composition only will compose methods and state! instance = factory.composition options: options enclose.call instance, options # TODO check if it is needed to inherit compositions # if methods.composition # composition = methods.composition # delete methods.composition # composition = stamp.compose composition, stamp methods, state # else # composition = stamp methods, state stamp.mixIn factory, composition: stamp methods, state # Suport an extension with multiple compositions compose: -> @composition = stamp.compose @composition, arguments... extend: -> `var methods, state` {fixed} = @composition initializers = [enclose] methods = {} state = {} for definition in arguments {methods: definition_methods, state: definition_state} = definition methods = extend methods, fixed.methods, definition_methods or definition state = extend state , fixed.state , definition_state initializers.push fixed.enclose if fixed.enclose initializers.push definition.enclose if definition.enclose enclosed = (properties) -> initializers.forEach (initializer) => initializer.call @, properties @ compositor.composable methods, state, enclosed or enclose compositor version: '0.1.2' initialize: (application) -> advisable = require 'ineadvisable' {core: {Widgets, util: {extend}, stamp}} = application Widgets = composerable advisorable Widgets # THINK how to at the same time respect aura ways of instantiating # widgets and preserve widget logic composability Widgets.Default = Widgets.Base delete Widgets.Default.extend Widgets.Base = Widgets.advisable Widgets.composable advisable(Widgets.Default.prototype), null, (options) -> Object.defineProperty @, 'constructor', value: Widgets.Default, enumerable: false, configurable: false, writable: false Widgets.Default.call Widgets.advice(@), options