properly clear element cache

properly clear element cache

# Element - cascading element tree

## Class Element

    debug = require('debug')('yang:element')
    delegate = require 'delegates'
    Emitter = require('events').EventEmitter
    Emitter.defaultMaxListeners = 100

    kIndex = Symbol.for('element:index');
    kCache = Symbol.for('element:cache');

    class Element

      @property: (prop, desc) ->
        Object.defineProperty @prototype, prop, desc

      @use: ->
        res = [].concat(arguments...)
          .filter (x) -> x?
          .map (elem) =>
            exists = Element::match.call this, elem.kind, elem.tag
            if exists?
              debug "use: using previously loaded '#{elem.kind}:#{elem.tag}'"
              return exists
            try Element::merge.call this, elem
            catch e
              throw @error "use: unable to merge '#{elem.kind}:#{elem.tag}'", e
        return switch
          when res.length > 1  then res
          when res.length is 1 then res[0]
          else undefined

      @debug: -> debug @uri, arguments...

      @error: (err, ctx=this) ->
        err = new Error err unless err instanceof Error
        err.uri = @uri
        err.src = this
        err.ctx = ctx
        return err

      constructor: (@kind, @tag, scope) ->
        unless @kind?
          throw @error "must supply 'kind' to create a new Element"

        @scope = scope if scope?

        Object.defineProperties this,
          parent: value: null, writable: true
          origin: value: null, writable: true
          state:  value: {}, writable: true
          [kIndex]: value: 0, writable: true
          emitter: value: new Emitter

      error: @error
      debug: @debug

      delegate @prototype, 'emitter'
        .method 'emit'
        .method 'once'
        .method 'on'
        .method 'off'

### Computed Properties

      @property 'datakey',
        get: -> @tag ? @kind

      @property 'uri',
        get: -> switch
          when @parent instanceof Element
            mark = @kind
            mark += "(#{@tag})" if @tag? and @parent.scope?[@kind] in [ '0..n', '1..n', '*' ]
            "#{@parent.uri}/#{mark}"
          when @tag?
            "#{@kind}(#{@tag})"
          else
            @kind

      @property 'root',
        get: -> switch
          when @parent instanceof Element then @parent.root
          when @origin instanceof Element then @origin.root
          else this

      @property 'children',
        get: ->
          unless this[kCache]?
            elements = (v for own k, v of this when k not in [ 'parent', 'origin', 'tag', kCache, 'source' ])
              .reduce ((a,b) -> switch
                when b instanceof Element then a.concat b
                when b instanceof Array
                  a.concat b.filter (x) -> x instanceof Element
                else a
              ), []
            this[kCache] = elements.sort (a,b) -> a[kIndex] - b[kIndex]
          return this[kCache];

      @property '*',  get: -> @children
      @property '..', get: -> @parent

## Instance-level methods

### clone

      clone: (opts={}) ->
        { origin = @origin, relative = true } = opts
        @debug "cloning #{@kind}:#{@tag} with #{@children.length} elements"
        copy = (new @constructor @kind, @tag, @source).extends @children.map (x) -> x.clone opts
        copy.state = Object.create(@state)
        copy.state.relative = relative
        copy.origin = origin ? this
        return copy

### extends (elements...)

This is the primary mechanism for defining sub-elements to become part
of the element tree

      extends: ->
        elems = ([].concat arguments...).filter (x) -> x? and !!x
        return this unless elems.length > 0
        elems.forEach (expr) => @merge expr
        @emit 'change', elems...
        return this

### merge (element)

This helper method merges a specific Element into current Element
while performing `@scope` validations.

      merge: (elem, opts={}) ->
        unless elem instanceof Element
          throw @error "cannot merge invalid element into Element", elem

        elem.parent = this
        elem[kIndex] = @children.length if @children?
        this[kCache] = null

        _merge = (item) ->
          if not item.node or opts.append or item.datakey not in (@keys ? [])
            @push item
            true
          else if opts.replace is true
            for x, i in this when x.datakey is item.datakey
              @splice i, 1, item
              break
            true
          else false

        unless @scope?
          unless @hasOwnProperty elem.kind
            @[elem.kind] = elem
            return elem

          unless Array.isArray @[elem.kind]
            exists = @[elem.kind]
            @[elem.kind] = [ exists ]
            Object.defineProperty @[elem.kind], 'keys',
              get: (-> @map (x) -> x.datakey ).bind @[elem.kind]

          unless _merge.call @[elem.kind], elem
            throw @error "constraint violation for '#{elem.kind} #{elem.datakey}' - cannot define more than once"

          return elem

        unless elem.kind of @scope
          if elem.scope? and (not elem.source.state.unbound and not @source.state.unbound)
            @debug @scope
            throw @error "scope violation - invalid '#{elem.kind}' extension found"
          else
            @scope[elem.kind] = '*' # this is hackish...

        switch @scope[elem.kind]
          when '0..n', '1..n', '*'
            unless @hasOwnProperty elem.kind
              @[elem.kind] = []
              Object.defineProperty @[elem.kind], 'keys',
                get: (-> @map (x) -> x.datakey ).bind @[elem.kind]
            unless _merge.call @[elem.kind], elem
              throw @error "constraint violation for '#{elem.kind} #{elem.datakey}' - already defined"
          when '0..1', '1'
            unless @hasOwnProperty elem.kind
              @[elem.kind] = elem
            else if opts.replace is true
              @debug "replacing pre-existing #{elem.kind}"
              @[elem.kind] = elem
            else
              throw @error "constraint violation for '#{elem.kind}' - cannot define more than once"
          else
            throw @error "unrecognized scope constraint defined for '#{elem.kind}' with #{@scope[elem.kind]}"

        return elem

      removes: ->
        elems = ([].concat arguments...).filter (x) -> x? and !!x
        return this unless elems.length > 0
        elems.forEach (expr) => @remove expr
        @emit 'change', elems...
        return this

      remove: (elem) ->
        unless elem instanceof Element
          throw @error "cannot remove a non-Element from an Element", elem

        exists = Element::match.call this, elem.kind, elem.datakey
        return this unless exists?

        if Array.isArray @[elem.kind]
          @[elem.kind] = @[elem.kind].filter (x) -> x.datakey isnt elem.datakey
          delete @[elem.kind] unless @[elem.kind].length
        else
          delete @[elem.kind]

        this[kCache] = null
        return this

### update (element)

This alternative form of [merge](#merge-element) performs conditional
merge based on existence check. It is considered *safer* alternative
to direct [merge](#merge-element) call.

      # performs conditional merge based on existence
      update: (elem) ->
        unless elem instanceof Element
          throw @error "cannot update a non-Element into an Element", elem

        #@debug "update with #{elem.kind}/#{elem.tag}"
        exists = switch
          when elem.tag? then Element::match.call this, elem.kind, elem.datakey
          else Element::match.call this, elem.kind
        return @merge elem unless exists?

        #@debug "update #{exists.kind} in-place for #{elem.children.length} elements"
        exists.update target for target in elem.children
        return exists

      # Looks for matching Elements using kind and tag
      # Direction: up the hierarchy (towards root)
      lookup: (kind, tag) ->
        #@debug "lookup: #{kind}(#{tag})..."
        res = switch
          when this not instanceof Object then undefined
          when this instanceof Element then @match kind, tag
          else Element::match.call this, kind, tag
        res ?= switch
          when @origin? then Element::lookup.apply @origin, arguments
          when @parent? then Element::lookup.apply @parent, arguments
          else Element::match.call @constructor, kind, tag
        #@debug "lookup: #{kind}(#{tag}) got result: #{res?}"
        return res

      # Looks for matching Elements using YPATH notation
      # Direction: down the hierarchy (away from root)
      at: -> @locate arguments...
      locate: (ypath) ->
        return unless ypath?
        if typeof ypath is 'string'
          @debug "locate: #{ypath}"
          ypath = ypath.replace /\s/g, ''
          if (/^\//.test ypath) and this isnt @root
            return @root.locate ypath
          [ key, rest... ] = ypath.split('/').filter (e) -> !!e
        else
          @debug "locate: #{ypath.join('/')}"
          [ key, rest... ] = ypath
        return this unless key?

        match = switch
          when key is '..' then @match key
          else @match '*', key

        match ?= @match key if @scope[key] in ['0..1', '1']

        return switch
          when rest.length > 0 then match?.locate rest
          else match

      # Looks for a matching Element(s) in immediate sub-elements
      match: (kind, tag) ->
        return unless kind? and @[kind]?
        return @[kind] unless tag?

        match = @[kind]
        match = [ match ] unless match instanceof Array
        return match if tag is '*'

        for elem in match when elem instanceof Element
          return elem if tag is elem.datakey or tag is elem.tag
        return undefined

### toJSON

Converts the Element into a JS object

      toJSON: (opts={ tag: true, extended: false }) ->
        #@debug "converting #{@kind} toJSON with #{@children.length}"
        sub =
          @children
            .filter (x) => opts.extended or x.parent is this
            .reduce ((a,b) ->
              for k, v of b.toJSON()
                if a[k] instanceof Object
                  a[k][kk] = vv for kk, vv of v if v instanceof Object
                else
                  a[k] = v
              return a
            ), {}
        if opts.tag
          "#{@kind}": switch
            when Object.keys(sub).length > 0
              if @tag? then "#{@tag}": sub else sub
            when @tag instanceof Object then "#{@tag}"
            else @tag
        else sub

## Export Element Class

    module.exports = Element

# Element - cascading element tree

## Class Element

    debug = require('debug')('yang:element')
    delegate = require 'delegates'
    Emitter = require('events').EventEmitter
    Emitter.defaultMaxListeners = 100

    kIndex = Symbol.for('element:index');
    kCache = Symbol.for('element:cache');

    class Element

      @property: (prop, desc) ->
        Object.defineProperty @prototype, prop, desc

      @use: ->
        res = [].concat(arguments...)
          .filter (x) -> x?
          .map (elem) =>
            exists = Element::match.call this, elem.kind, elem.tag
            if exists?
              debug "use: using previously loaded '#{elem.kind}:#{elem.tag}'"
              return exists
            try Element::merge.call this, elem
            catch e
              throw @error "use: unable to merge '#{elem.kind}:#{elem.tag}'", e
        return switch
          when res.length > 1  then res
          when res.length is 1 then res[0]
          else undefined

      @debug: -> debug @uri, arguments...

      @error: (err, ctx=this) ->
        err = new Error err unless err instanceof Error
        err.uri = @uri
        err.src = this
        err.ctx = ctx
        return err

      constructor: (@kind, @tag, scope) ->
        unless @kind?
          throw @error "must supply 'kind' to create a new Element"

        @scope = scope if scope?

        Object.defineProperties this,
          parent: value: null, writable: true
          origin: value: null, writable: true
          state:  value: {}, writable: true
          [kIndex]: value: 0, writable: true
          emitter: value: new Emitter

      error: @error
      debug: @debug

      delegate @prototype, 'emitter'
        .method 'emit'
        .method 'once'
        .method 'on'
        .method 'off'

### Computed Properties

      @property 'datakey',
        get: -> @tag ? @kind

      @property 'uri',
        get: -> switch
          when @parent instanceof Element
            mark = @kind
            mark += "(#{@tag})" if @tag? and @parent.scope?[@kind] in [ '0..n', '1..n', '*' ]
            "#{@parent.uri}/#{mark}"
          when @tag?
            "#{@kind}(#{@tag})"
          else
            @kind

      @property 'root',
        get: -> switch
          when @parent instanceof Element then @parent.root
          when @origin instanceof Element then @origin.root
          else this

      @property 'children',
        get: ->
          unless this[kCache]?
            elements = (v for own k, v of this when k not in [ 'parent', 'origin', 'tag', kCache, 'source' ])
              .reduce ((a,b) -> switch
                when b instanceof Element then a.concat b
                when b instanceof Array
                  a.concat b.filter (x) -> x instanceof Element
                else a
              ), []
            this[kCache] = elements.sort (a,b) -> a[kIndex] - b[kIndex]
          return this[kCache];

      @property '*',  get: -> @children
      @property '..', get: -> @parent

## Instance-level methods

### clone

      clone: (opts={}) ->
        { origin = @origin, relative = true } = opts
        @debug "cloning #{@kind}:#{@tag} with #{@children.length} elements"
        copy = (new @constructor @kind, @tag, @source).extends @children.map (x) -> x.clone opts
        copy.state = Object.create(@state)
        copy.state.relative = relative
        copy.origin = origin ? this
        return copy

### extends (elements...)

This is the primary mechanism for defining sub-elements to become part
of the element tree

      extends: ->
        elems = ([].concat arguments...).filter (x) -> x? and !!x
        return this unless elems.length > 0
        elems.forEach (expr) => @merge expr
        @emit 'change', elems...
        return this

### merge (element)

This helper method merges a specific Element into current Element
while performing `@scope` validations.

      merge: (elem, opts={}) ->
        unless elem instanceof Element
          throw @error "cannot merge invalid element into Element", elem

        elem.parent = this
        elem[kIndex] = @children.length if @children?
        this[kCache] = null

        _merge = (item) ->
          if not item.node or opts.append or item.datakey not in (@keys ? [])
            @push item
            true
          else if opts.replace is true
            for x, i in this when x.datakey is item.datakey
              @splice i, 1, item
              break
            true
          else false

        unless @scope?
          unless @hasOwnProperty elem.kind
            @[elem.kind] = elem
            return elem

          unless Array.isArray @[elem.kind]
            exists = @[elem.kind]
            @[elem.kind] = [ exists ]
            Object.defineProperty @[elem.kind], 'keys',
              get: (-> @map (x) -> x.datakey ).bind @[elem.kind]

          unless _merge.call @[elem.kind], elem
            throw @error "constraint violation for '#{elem.kind} #{elem.datakey}' - cannot define more than once"

          return elem

        unless elem.kind of @scope
          if elem.scope? and (not elem.source.state.unbound and not @source.state.unbound)
            @debug @scope
            throw @error "scope violation - invalid '#{elem.kind}' extension found"
          else
            @scope[elem.kind] = '*' # this is hackish...

        switch @scope[elem.kind]
          when '0..n', '1..n', '*'
            unless @hasOwnProperty elem.kind
              @[elem.kind] = []
              Object.defineProperty @[elem.kind], 'keys',
                get: (-> @map (x) -> x.datakey ).bind @[elem.kind]
            unless _merge.call @[elem.kind], elem
              throw @error "constraint violation for '#{elem.kind} #{elem.datakey}' - already defined"
          when '0..1', '1'
            unless @hasOwnProperty elem.kind
              @[elem.kind] = elem
            else if opts.replace is true
              @debug "replacing pre-existing #{elem.kind}"
              @[elem.kind] = elem
            else
              throw @error "constraint violation for '#{elem.kind}' - cannot define more than once"
          else
            throw @error "unrecognized scope constraint defined for '#{elem.kind}' with #{@scope[elem.kind]}"

        return elem

      removes: ->
        elems = ([].concat arguments...).filter (x) -> x? and !!x
        return this unless elems.length > 0
        elems.forEach (expr) => @remove expr
        @emit 'change', elems...
        return this

      remove: (elem) ->
        unless elem instanceof Element
          throw @error "cannot remove a non-Element from an Element", elem

        exists = Element::match.call this, elem.kind, elem.datakey
        return this unless exists?

        if Array.isArray @[elem.kind]
          @[elem.kind] = @[elem.kind].filter (x) -> x.datakey isnt elem.datakey
          delete @[elem.kind] unless @[elem.kind].length
        else
          delete @[elem.kind]

        this._cache = null
        return this

### update (element)

This alternative form of [merge](#merge-element) performs conditional
merge based on existence check. It is considered *safer* alternative
to direct [merge](#merge-element) call.

      # performs conditional merge based on existence
      update: (elem) ->
        unless elem instanceof Element
          throw @error "cannot update a non-Element into an Element", elem

        #@debug "update with #{elem.kind}/#{elem.tag}"
        exists = switch
          when elem.tag? then Element::match.call this, elem.kind, elem.datakey
          else Element::match.call this, elem.kind
        return @merge elem unless exists?

        #@debug "update #{exists.kind} in-place for #{elem.children.length} elements"
        exists.update target for target in elem.children
        return exists

      # Looks for matching Elements using kind and tag
      # Direction: up the hierarchy (towards root)
      lookup: (kind, tag) ->
        #@debug "lookup: #{kind}(#{tag})..."
        res = switch
          when this not instanceof Object then undefined
          when this instanceof Element then @match kind, tag
          else Element::match.call this, kind, tag
        res ?= switch
          when @origin? then Element::lookup.apply @origin, arguments
          when @parent? then Element::lookup.apply @parent, arguments
          else Element::match.call @constructor, kind, tag
        #@debug "lookup: #{kind}(#{tag}) got result: #{res?}"
        return res

      # Looks for matching Elements using YPATH notation
      # Direction: down the hierarchy (away from root)
      at: -> @locate arguments...
      locate: (ypath) ->
        return unless ypath?
        if typeof ypath is 'string'
          @debug "locate: #{ypath}"
          ypath = ypath.replace /\s/g, ''
          if (/^\//.test ypath) and this isnt @root
            return @root.locate ypath
          [ key, rest... ] = ypath.split('/').filter (e) -> !!e
        else
          @debug "locate: #{ypath.join('/')}"
          [ key, rest... ] = ypath
        return this unless key?

        match = switch
          when key is '..' then @match key
          else @match '*', key

        match ?= @match key if @scope[key] in ['0..1', '1']

        return switch
          when rest.length > 0 then match?.locate rest
          else match

      # Looks for a matching Element(s) in immediate sub-elements
      match: (kind, tag) ->
        return unless kind? and @[kind]?
        return @[kind] unless tag?

        match = @[kind]
        match = [ match ] unless match instanceof Array
        return match if tag is '*'

        for elem in match when elem instanceof Element
          return elem if tag is elem.datakey or tag is elem.tag
        return undefined

### toJSON

Converts the Element into a JS object

      toJSON: (opts={ tag: true, extended: false }) ->
        #@debug "converting #{@kind} toJSON with #{@children.length}"
        sub =
          @children
            .filter (x) => opts.extended or x.parent is this
            .reduce ((a,b) ->
              for k, v of b.toJSON()
                if a[k] instanceof Object
                  a[k][kk] = vv for kk, vv of v if v instanceof Object
                else
                  a[k] = v
              return a
            ), {}
        if opts.tag
          "#{@kind}": switch
            when Object.keys(sub).length > 0
              if @tag? then "#{@tag}": sub else sub
            when @tag instanceof Object then "#{@tag}"
            else @tag
        else sub

## Export Element Class

    module.exports = Element


models base

models base

Copyright 2014 - 2015
Author: Varuna Jayasiri http://blog.varunajayasiri.com

#List

    Mod.require 'Weya',
     'Models.Properties'
     'Models.Util'
     (Weya, PROPERTIES, UTIL) ->

Class

      class Model
       _initialize: []
       _properties: {}
       _requiredFunctions: {}
       on: {}

       @extend: ->
        @::_initialize = @::_initialize.slice()
        @::_properties = UTIL.extend {}, @::_properties
        @::_requiredFunctions = UTIL.extend {}, @::_requiredFunctions
        @::on = UTIL.extend {}, @::on

       @include: (obj) ->
        if not obj?
         throw new Error 'Cannot include a null object'
        for k, v of obj
         switch k
          when 'initialize'
           @::_initialize.push v
          when 'property'
           for name, obj of v
            @_addProperty name, obj
          when 'requireFunction'
           for name, desc of v
            @::_requiredFunctions[name] = desc
          when 'on'
           for event, listener of v
            if not @::on[event]?
             @::on[event] = listener
          else
           @prototype[k] = v

        return null

 Initialize variables.

       @initialize: (func) ->
        @::_initialize.push func

Add listener

       @listen: (name, func) ->
        @::on[name] = func

Register a property

       @property: (name, obj) ->
        if (typeof name) is 'string'
         @_addProperty name, obj
        else
         objs = name
         for name, obj of objs
          @_addProperty name, obj

       @_addProperty: (name, obj) ->
        if (typeof name) isnt 'string'
         throw new Error "Property name not a string: #{name}"
        if (typeof obj) isnt 'object'
         throw new Error "Property not an object: #{name}"

        @::_properties[name] = PROPERTIES.create obj

       @requireFunction: (name, desc) ->
        if (typeof name) is 'string'
         @::_requiredFunctions[name] = desc
        else
         obj = name
         for name, desc of obj
          @::_requiredFunctions[name] = desc

Check properties

       @check: ->
        if not @::type?
         throw new Error "Object type not defined"
        type = @::type

        mentioned =
         type: true
        for func, desc of @::_requiredFunctions
         mentioned[func] = true
         if not @::[func]?
          throw new Error "#{type}: Function not defined: #{func} - #{desc}"
         if (typeof @::[func]) isnt 'function'
          throw new Error "#{type}: Not a function: #{func} - #{desc}"

        extra = []
        for k of this.prototype
         continue if k is 'on'
         if not mentioned[k]?
          if k[0] isnt '_'
           extra.push k

        if extra.length > 0
         console.error type, extra

        @::_checked = true

       constructor: ->
        @initialize.apply this, arguments

##Override

##Initialize

       initialize: (options) ->
        if @_checked isnt on
         throw new Error "Object #{@type} wasnt checked"

        @_bindEvents()

        for init in @_initialize
         init.call this

       @requireFunction
        'initialize': 'Initialize'
        'parse': 'Parse'
        'render': 'Render element'
        'weya': 'Render weya template'
        'html': 'Render html'
        'template': 'Render template'
        'toJSON': 'To JSON'
        'toJSONFull': 'To JSON Full'
        'isDefault': 'Is default value'
        'edit': 'Render editor'
        'valueChanged': 'A property value change event'

##Public functions

       parse: (data) ->
        excess = 0
        @_errors = []
        fields = 0
        @_score = 0
        @_values = {}

        for name of data when not @_properties[name]?
         @_errors.push ": #{name} excess"
         excess++

        for name, prop of @_properties
         fields++
         r = prop.parse data[name]
         @_values[name] = r.value
         if not prop.isDefault r.value
          @_score += r.score
         for e in r.errors
          @_errors.push "#{name}(#{@type})/#{e}"

        if fields is 0
         @_score = (if excess > 0 then 0 else 1)
        else
         @_score = (@_score - excess) / fields
        @_score = 0 if @_score < 0
        if @_score > 1
         throw new Error "Score greater than 1: #{score}"

        return {
         score: @_score,
         value: this
         errors: @_errors
        }

       weya: (weya) ->
        @template.call weya, this

       render: (elem) ->
        self = this
        Weya elem: elem, ->
         self.weya this

       html: ->
        self = this
        Weya.markup {}, ->
         self.weya this

       toJSON: ->
        data = {}
        for name, prop of @_properties
         v = prop.toJSON @_values[name]
         if not prop.isDefault v
          data[name] = v

        return data

       toJSONFull: ->
        data = {}
        for name, prop of @_properties
         data[name] = prop.toJSONFull @_values[name]

        return data

       isDefault: ->
        for name, prop of @_properties
         v = prop.toJSON @_values[name]
         if not prop.isDefault v
          return false

        return true

       edit: (elem, changed) ->
        @onChanged = changed
        @_editElems = {}
        Weya elem: elem, context: this, ->
         for name, prop of @$._properties
          @div ".property", ->
           @span ".property-name", name
           @$._editElems[name] = @div ".property-value", ""

        for name, prop of @_properties
         prop.edit @_editElems[name], @_values[name],
          @valueChanged.bind self: this, name: name

       valueChanged: (value, changed) ->
        @self._values[@name] = value
        @self.onChanged @self, false

##Private functions

Bind

       _bindEvents: ->
        events = @on
        @on = {}
        for k, v of events
         @on[k] = v.bind this

      Mod.set 'Models.Model.Base', Model

Copyright 2014 - 2015
Author: Varuna Jayasiri http://blog.varunajayasiri.com

#List

    Mod.require 'Weya',
     'Models.Properties'
     'Models.Util'
     (Weya, PROPERTIES, UTIL) ->

Class

      class Model
       _initialize: []
       _properties: {}
       _requiredFunctions: {}
       on: {}

       @extend: ->
        @::_initialize = @::_initialize.slice()
        @::_properties = UTIL.extend {}, @::_properties
        @::_requiredFunctions = UTIL.extend {}, @::_requiredFunctions
        @::on = UTIL.extend {}, @::on

       @include: (obj) ->
        if not obj?
         throw new Error 'Cannot include a null object'
        for k, v of obj
         switch k
          when 'initialize'
           @::_initialize.push v
          when 'property'
           for name, obj of v
            @_addProperty name, obj
          when 'requireFunction'
           for name, desc of v
            @::_requiredFunctions[name] = desc
          when 'on'
           for event, listener of v
            if not @::on[event]?
             @::on[event] = listener
          else
           @prototype[k] = v

        return null

 Initialize variables.

       @initialize: (func) ->
        @::_initialize.push func

Add listener

       @listen: (name, func) ->
        @::on[name] = func

Register a property

       @property: (name, obj) ->
        if (typeof name) is 'string'
         @_addProperty name, obj
        else
         objs = name
         for name, obj of objs
          @_addProperty name, obj

       @_addProperty: (name, obj) ->
        if (typeof name) isnt 'string'
         throw new Error "Property name not a string: #{name}"
        if (typeof obj) isnt 'object'
         throw new Error "Property not an object: #{name}"

        @::_properties[name] = PROPERTIES.create obj

       @requireFunction: (name, desc) ->
        if (typeof name) is 'string'
         @::_requiredFunctions[name] = desc
        else
         obj = name
         for name, desc of obj
          @::_requiredFunctions[name] = desc

Check properties

       @check: ->
        if not @::type?
         throw new Error "Object type not defined"
        type = @::type

        mentioned =
         type: true
        for func, desc of @::_requiredFunctions
         mentioned[func] = true
         if not @::[func]?
          throw new Error "#{type}: Function not defined: #{func} - #{desc}"
         if (typeof @::[func]) isnt 'function'
          throw new Error "#{type}: Not a function: #{func} - #{desc}"

        extra = []
        for k of this.prototype
         continue if k is 'on'
         if not mentioned[k]?
          if k[0] isnt '_'
           extra.push k

        if extra.length > 0
         console.error type, extra

        @::_checked = true

       constructor: ->
        @initialize.apply this, arguments

##Override

##Initialize

       initialize: (options) ->
        if @_checked isnt on
         throw new Error "Object #{@type} wasnt checked"

        @_bindEvents()

        for init in @_initialize
         init.call this

       @requireFunction
        'initialize': 'Initialize'
        'parse': 'Parse'
        'render': 'Render element'
        'weya': 'Render weya template'
        'html': 'Render html'
        'template': 'Render template'
        'toJSON': 'To JSON'
        'edit': 'Render editor'
        'valueChanged': 'A property value change event'

##Public functions

       parse: (data) ->
        excess = 0
        @_errors = []
        fields = 0
        @_score = 0
        @_values = {}

        for name of data when not @_properties[name]?
         @_errors.push ": #{name} excess"
         excess++

        for name, prop of @_properties
         fields++
         r = prop.parse data[name]
         @_values[name] = r.value
         if not prop.isDefault r.value
          @_score += r.score
         for e in r.errors
          @_errors.push "#{name}(#{@type})/#{e}"

        if fields is 0
         @_score = (if excess > 0 then 0 else 1)
        else
         @_score = (@_score - excess) / fields
        @_score = 0 if @_score < 0
        if @_score > 1
         throw new Error "Score greater than 1: #{score}"

        return {
         score: @_score,
         value: this
         errors: @_errors
        }

       weya: (weya) ->
        @template.call weya, this

       render: (elem) ->
        self = this
        Weya elem: elem, ->
         self.weya this

       html: ->
        self = this
        Weya.markup {}, ->
         self.weya this

       toJSON: ->
        data = {}
        for name, prop of @_properties
         v = prop.toJSON @_values[name]
         if not prop.isDefault v
          data[name] = v

        return data

       toJSONFull: ->
        data = {}
        for name, prop of @_properties
         data[name] = prop.toJSONFull @_values[name]

        return data

       isDefault: ->
        for name, prop of @_properties
         v = prop.toJSON @_values[name]
         if not prop.isDefault v
          return false

        return true

       edit: (elem, changed) ->
        @onChanged = changed
        @_editElems = {}
        Weya elem: elem, context: this, ->
         for name, prop of @$._properties
          @div ".property", ->
           @span ".property-name", name
           @$._editElems[name] = @div ".property-value", ""

        for name, prop of @_properties
         prop.edit @_editElems[name], @_values[name],
          @valueChanged.bind self: this, name: name

       valueChanged: (value, changed) ->
        @self._values[@name] = value
        @self.onChanged @self, false

##Private functions

Bind

       _bindEvents: ->
        events = @on
        @on = {}
        for k, v of events
         @on[k] = v.bind this

      Mod.set 'Models.Model.Base', Model


Tweak wording in PathStencil.

Tweak wording in PathStencil.

### PathStencil
This class is responsible for rendering Links, currently by drawing a
Paper.js Path.

    define [
      'paper'
      'viewmodels/drawings/stencils/stencil'
      'viewmodels/drawings/stencils/stencil_specification'
    ], (paper, Stencil, StencilSpecification) ->

      class PathStencil extends Stencil

The `color` and `style` properties are used when rendering a Path.
We first set some sensible defaults in case the LinkShape omits values
for any of these properties.

        defaultSpecification: =>
          new StencilSpecification(color: 'black', style: 'solid')

When a PathStencil is used to render a Link, we create a Paper.js Path
that comprises the segments stored in the Link. The segments property
will be an array of Paper.js segments. We then set the stroke color
and dash array of the Path, according to the `color` and `style` properties.

        draw: (link) =>
          path = new paper.Path(link.segments)
          path.strokeColor = @resolve(link, 'color')
          path.dashArray = [4, 4] if @resolve(link, 'style') is 'dash'
          path

### PathStencil
This class is responsible for rendering Links, currently by drawing a
Paper.js Path.

    define [
      'paper'
      'viewmodels/drawings/stencils/stencil'
      'viewmodels/drawings/stencils/stencil_specification'
    ], (paper, Stencil, StencilSpecification) ->

The `color` and `style` properties are used when rendering a Path.
We first set some sensible defaults in case the LinkShape omits values
for any of these properties.

      class PathStencil extends Stencil
        defaultSpecification: =>
          new StencilSpecification(color: 'black', style: 'solid')

When a PathStencil is used to render a Link, we create a Paper.js Path
that comprises the segments stored in the Link. The segments property
will be an array of Paper.js segments. We then set the stroke color
and dash array of the Path, according to the `color` and `style` properties.

        draw: (link) =>
          path = new paper.Path(link.segments)
          path.strokeColor = @resolve(link, 'color')
          path.dashArray = [4, 4] if @resolve(link, 'style') is 'dash'
          path


'unit' modifiers (onServer etc.) fixed

'unit' modifiers (onServer etc.) fixed

    'use strict'

    utils = require 'src/utils'

    NOP = ->

    PLATFORMS =
        onNode: 'isNode'
        onServer: 'isServer'
        onClient: 'isClient'
        onBrowser: 'isBrowser'
        onAndroid: 'isAndroid'
        onIOS: 'isIOS'

    exports.applyAll = (func) ->

isNode
--

isServer
--

isClient
--

isBrowser
--

isAndroid
--

isIOS
--

        for funcName, utilsProp of PLATFORMS
            getter = if utils[utilsProp] then -> this else NOP
            utils.defineProperty func, funcName, 0, getter, null
        return

    'use strict'

    utils = require 'src/utils'

    NOP = ->

    PLATFORMS =
        onNode: 'isNode'
        onServer: 'isServer'
        onClient: 'isClient'
        onBrowser: 'isBrowser'
        onAndroid: 'isAndroid'
        onIOS: 'isIOS'

    exports.applyAll = (func) ->

isNode
--

isServer
--

isClient
--

isBrowser
--

isAndroid
--

isIOS
--

        for funcName, utilsProp of PLATFORMS
            func[funcName] = if utils[utilsProp] then -> this else NOP
        return


Clean up documentation and links in ComponentQuery

# ComponentQuery

When we first start out, we will want to query only a single
component.  This monad is used for querying that component
and getting a list of nodes that we will filter later.

## Example

```
componentQuery.contains
              .tags("div")
              .result()
```

This will take the React component used in the constructor
of the ComponentQuery and generate a ComponentFilter with the DOM nodes
that have the tag "div".  In this particular example,
it will return a matching result if there are any such nodes.

### Requirements

The [JasmineMonad](./JasmineMonad.litcoffee#a-monad-for-jasmine-tests) is a "maybe" monad that
allows us to chain our matcher functions in a fluent way.

    JasmineMonad = require("./JasmineMonad.litcoffee")

The [ComponentFilter](./ComponentFilter.litcoffee#componentfilter) is a JasmineMonad
that allows you to filter and match collections of React components.

    ComponentFilter = require("./ComponentFilter.litcoffee")

The [DomComponentFilter](./DomComponentFilter.litcoffee#domcomponentfilter)
is a JasmineMonad that allows you to filter and match collections of DOM
components.

    DomComponentFilter = require("./DomComponentFilter.litcoffee")

## A monad for making queries of single Components

    class ComponentQuery extends JasmineMonad

      constructor: (@reactUtils, @value, @util, @testers, @messages) ->
        # FIXME: pass reactutils along so that we have a consistent api
        super(@value, @util, @testers, @messages)

### English Helpers

It is nice to have use this word to make the expectation read more easily.

        @contains = this

### Unit method for ComponentQuery

Most of the methods on `ComponentQuery` will actually want to
return a ComponentFilter so the user can filter the collection of returned
nodes.  If we were using a language with strong typing the compiler
would be able to construct the correct the correct Monad based
on the types.  However, we are using Coffeescript, so we have to
give it a helping hand by making a different `return` method.

      return: (nodes, messages) ->
        new ComponentFilter(nodes, @util, @testers, messages)

In the past DOM nodes in the React tree were treated similarly to
React components.  This will change in 0.15, so we need to make
sure to create the correct filter object.

      returnDom: (nodes, messages) ->
        new DomComponentFilter(nodes, @util, @testers, messages)

### Testing for DOM tags

One of the most common kinds of tests is to determine whether or not
a component contains a DOM "tag" (eg "h1", "div", etc). This matcher
passes if there is at least one.

      tags: (tag) ->
        @bind (component) =>
          nodes = @reactUtils.scryRenderedDOMComponentsWithTag(component, tag)
          messages = [
            "Expected to find DOM tag #{tag}, but it was not there."
            "Expected not to find DOM tag #{tag}, but there #{@was(nodes.length)}."
          ]
          if nodes.length > 0
            @returnDom(nodes, messages)
          else
            @returnDom(null, messages)

### Testing directly for DOM with class

Sometimes you don't care what kind of tag the DOM nodes have.  You just
want ensure that there is *something* with the required class.

      cssClass: (cssClass) ->
        @bind (component) =>
          nodes = @reactUtils.scryRenderedDOMComponentsWithClass(component, cssClass)
          messages = [
            "Expected to find DOM node with class #{cssClass}, but it was not there."
            "Expected not to find DOM node with class #{cssClass}, but there #{@was(nodes.length)}."
          ]
          if nodes.length > 0
            @returnDom(nodes, messages)
          else
            @returnDom(null, messages)

### Testing for React component types

Another common kind of test is to determine whether or not
a component contains a React component of a certain type. This matcher
passes if there is at least one.

FIXME: It would be nice to print the name of the component that
you want to find in the error messages, but it doesn't appear
to be available.

      type: (type) ->
        @bind (component) =>
          nodes = @reactUtils.scryRenderedComponentsWithType(component, type)
          messages = [
            "Expected to find a React component, but it was not there."
            "Expected not to find a React component, but there #{@was(nodes.length)}."
          ]
          if nodes.length > 0
            @return(nodes, messages)
          else
            @return(null, messages)

### Exported Classes

    module.exports = ComponentQuery

# ComponentQuery

When we first start out, we are likely to have only a single
component.  This monad is used for querying that component
and getting a list of nodes that we will filter later.

## Example

```
componentQuery.contains
              .tags("div")
              .result()
```

This will take a component in the componentFilter
and generate a ComponentFilter with the DOM nodes
that have the tag "div".  In this particular example,
it will return a matching result if there are any
such nodes.

## Requirements

The [JasmineMonad](./JasmineMonad.litcoffee) is a "maybe" monad that
allows us to chain our matcher functions in a fluent way.

    JasmineMonad = require("./JasmineMonad.litcoffee")

The [ComponentFilter](./ComponentFilter.litcoffee) is a JasmineMonad
that allows you to filter and match collections of React components.

    ComponentFilter = require("./ComponentFilter.litcoffee")

The [DomComponentFilter](./DomComponentFilter.litcoffee) is a JasmineMonad
that allows you to filter and match collections of DOM components.

    DomComponentFilter = require("./DomComponentFilter.litcoffee")

## A monad for making queries of single Components

    class ComponentQuery extends JasmineMonad

      constructor: (@reactUtils, @value, @util, @testers, @messages) ->
        super(@value, @util, @testers, @messages)

#### English Helpers

It is nice to have use this word to make the expectation read more easily.

        @contains = this

#### Unit method for ComponentQuery

Most of the methods on `ComponentQuery` will actually want to
return a ComponentFilter so the user can filter the collection of returned
nodes.  If we were using a language with strong typing the compiler
would be able to construct the correct the correct Monad based
on the types.  However, we are using Coffeescript, so we have to
give it a helping hand by making a different `return` method.

      return: (nodes, messages) ->
        new ComponentFilter(nodes, @util, @testers, messages)

In the past DOM nodes in the React tree were treated similarly to
React components.  This will change in 0.15, so we need to make
sure to create the correct filter object.

      returnDom: (nodes, messages) ->
        new DomComponentFilter(nodes, @util, @testers, messages)

#### Testing for DOM tags

One of the most common kinds of tests is to determine whether or not
a component contains a DOM "tag" (eg "h1", "div", etc). This matcher
passes if there is at least one.

      tags: (tag) ->
        @bind (component) =>
          nodes = @reactUtils.scryRenderedDOMComponentsWithTag(component, tag)
          messages = [
            "Expected to find DOM tag #{tag}, but it was not there."
            "Expected not to find DOM tag #{tag}, but there #{@was(nodes.length)}."
          ]
          if nodes.length > 0
            @returnDom(nodes, messages)
          else
            @returnDom(null, messages)

#### Testing directly for DOM with class

Sometimes you don't care what kind of tag the DOM nodes have.  You just
want ensure that there is *something* with the required class.

      cssClass: (cssClass) ->
        @bind (component) =>
          nodes = @reactUtils.scryRenderedDOMComponentsWithClass(component, cssClass)
          messages = [
            "Expected to find DOM node with class #{cssClass}, but it was not there."
            "Expected not to find DOM node with class #{cssClass}, but there #{@was(nodes.length)}."
          ]
          if nodes.length > 0
            @returnDom(nodes, messages)
          else
            @returnDom(null, messages)

#### Testing for React component types

Another common kind of test is to determine whether or not
a component contains a React component of a certain type. This matcher
passes if there is at least one.

FIXME: It would be nice to print the name of the component that
you want to find in the error messages, but it doesn't appear
to be available.

      type: (type) ->
        @bind (component) =>
          nodes = @reactUtils.scryRenderedComponentsWithType(component, type)
          messages = [
            "Expected to find a React component, but it was not there."
            "Expected not to find a React component, but there #{@was(nodes.length)}."
          ]
          if nodes.length > 0
            @return(nodes, messages)
          else
            @return(null, messages)

Export our matchers from this file.

    module.exports = ComponentQuery


default value invalid fix

default value invalid fix

Copyright 2014 - 2015
Author: Varuna Jayasiri http://blog.varunajayasiri.com

#List

    Mod.require 'Models.Properties',
     'Models.Property.Base'
     'Weya.Base'
     'Weya'
     (PROPERTIES, Base, WeyaBase, Weya) ->

Class

      class Value extends Base
       @extend()

       @default 'value', (str) ->
        if (typeof str) isnt 'string'
         throw new Error "Exected string: #{typeof str}, #{str}"
        return str

       @default 'string', (value) -> "#{str}"

       @default 'rows', 1
       @default 'columns', 20

       @default 'valid', (str) ->
        if (typeof str) isnt 'string'
         return false
        else
         return true

       @default 'search', (str) -> null

       @default 'default', -> ''

       @default 'isDefault', (value) -> (value is @schema.default())

       parse: (data) ->
        r = super data
        return r unless r is true
        if not @schema.valid data
         return @error 'invalid'

        res =
         score: 1
         errors: []
         value: @schema.value data

        return res

       toJSON: (value) -> value
       toJSONFull: (value) -> value

       edit: (elem, value, changed) ->
        new Edit this, elem, value, changed

Edit

      class Edit extends WeyaBase
       @extend()

       @initialize (property, elem, value, changed) ->
        @property = property
        @elems =
         parent: elem
        @value = value
        @onChanged = changed
        @render()

       render: ->
        schema = @property.schema
        width = 2 + Math.round schema.columns * 7.25
        Weya elem: @elems.parent, context: this, ->
         if schema.rows is 1
          @$.elems.input = @input '.value',
           type: 'text'
           style:
            width: "#{width}px"
         else
          @$.elems.input = @textarea '.value',
           rows: schema.rows
           columns: schema.columns
           style:
            width: "#{width}px"
            height: "#{Math.round schema.rows * 18}px"

         @$.elems.search = @div '.search',
          style:
           display: 'none'
           width: "#{width}px"

        @elems.input.value = @value
        @elems.input.addEventListener "input", @on.change
        @elems.input.addEventListener "focus", @on.focus
        @elems.input.addEventListener "blur", @on.blur
        @elems.search.addEventListener 'click', @on.searchClick
        @change()

       search: ->
        value = @elems.input.value
        results = @property.schema.search.call @property, value
        return if not results?
        return if not Array.isArray results

        if results.length is 0
         @elems.search.style.display = 'none'
         return

        @elems.search.style.display = 'block'
        @elems.search.innerHTML = ''
        Weya elem: @elems.search, ->
         for r in results
          e = @div r
          e._result = r

       @listen 'searchClick', (e) ->
        n = e.target
        result = null
        while n? and n isnt @elems.search
         if n._result
          result = n._result
          break
         n = n.parentNode

        return if not result?
        @elems.input.value = result
        @elems.search.style.display = 'none'
        @change()

       @listen 'focus', (e) -> @search()
       @listen 'blur', (e) ->
        setTimeout =>
         @elems.search.style.display = 'none'
        , 100

       @listen 'change', (e) ->
        @search()
        @change()

       change: ->
        value = @elems.input.value
        if not @property.schema.valid.call @property, value
         @elems.input.classList.add 'invalid'
        else
         @elems.input.classList.remove 'invalid'
         value = @property.schema.value.call this, value
         @onChanged value, true

      PROPERTIES.register 'value', Value

Copyright 2014 - 2015
Author: Varuna Jayasiri http://blog.varunajayasiri.com

#List

    Mod.require 'Models.Properties',
     'Models.Property.Base'
     'Weya.Base'
     'Weya'
     (PROPERTIES, Base, WeyaBase, Weya) ->

Class

      class Value extends Base
       @extend()

       @default 'value', (str) ->
        if (typeof str) isnt 'string'
         throw new Error "Exected string: #{typeof str}, #{str}"
        return str

       @default 'string', (value) -> "#{str}"

       @default 'rows', 1
       @default 'columns', 20

       @default 'valid', (str) ->
        if (typeof str) isnt 'string'
         return false
        else
         return true

       @default 'search', (str) -> null

       @default 'default', -> ''

       @default 'isDefault', (value) -> (value is @schema.default())

       parse: (data) ->
        r = super data
        return r unless r is true
        if not @schema.valid data
         return @error 'invalid'

        res =
         score: 1
         errors: []
         value: @schema.value data

        return res

       toJSON: (value) -> value
       toJSONFull: (value) -> value

       edit: (elem, value, changed) ->
        new Edit this, elem, value, changed

Edit

      class Edit extends WeyaBase
       @extend()

       @initialize (property, elem, value, changed) ->
        @property = property
        @elems =
         parent: elem
        @value = value
        @onChanged = changed
        @render()

       render: ->
        schema = @property.schema
        width = 2 + Math.round schema.columns * 7.25
        Weya elem: @elems.parent, context: this, ->
         if schema.rows is 1
          @$.elems.input = @input '.value',
           type: 'text'
           style:
            width: "#{width}px"
         else
          @$.elems.input = @textarea '.value',
           rows: schema.rows
           columns: schema.columns
           style:
            width: "#{width}px"
            height: "#{Math.round schema.rows * 18}px"

         @$.elems.search = @div '.search',
          style:
           display: 'none'
           width: "#{width}px"

        @elems.input.value = @value
        @elems.input.addEventListener "input", @on.change
        @elems.input.addEventListener "focus", @on.focus
        @elems.input.addEventListener "blur", @on.blur
        @elems.search.addEventListener 'click', @on.searchClick

       search: ->
        value = @elems.input.value
        results = @property.schema.search.call @property, value
        return if not results?
        return if not Array.isArray results

        if results.length is 0
         @elems.search.style.display = 'none'
         return

        @elems.search.style.display = 'block'
        @elems.search.innerHTML = ''
        Weya elem: @elems.search, ->
         for r in results
          e = @div r
          e._result = r

       @listen 'searchClick', (e) ->
        n = e.target
        result = null
        while n? and n isnt @elems.search
         if n._result
          result = n._result
          break
         n = n.parentNode

        return if not result?
        @elems.input.value = result
        @on.change()

       @listen 'focus', (e) -> @search()
       @listen 'blur', (e) ->
        setTimeout =>
         @elems.search.style.display = 'none'
        , 100

       @listen 'change', (e) ->
        @search()
        value = @elems.input.value
        if not @property.schema.valid.call @property, value
         @elems.input.classList.add 'invalid'
        else
         @elems.input.classList.remove 'invalid'
         value = @property.schema.value.call this, value
         @onChanged value, true

      PROPERTIES.register 'value', Value