text_types_uninitialized = require "./TextTypes.coffee" module.exports = (HB)-> text_types = text_types_uninitialized HB types = text_types.types parser = text_types.parser createJsonWrapper = (_jsonType)-> # # A JsonWrapper was intended to be a convenient wrapper for the JsonType. # But it can make things more difficult than they are. # @see JsonType # # @example create a JsonWrapper # # You get a JsonWrapper from a JsonType by calling # w = yatta.value # # It creates Javascripts -getter and -setter methods for each property that JsonType maintains. # @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty # # @example Getter Example # # you can access the x property of yatta by calling # w.x # # instead of # yatta.val('x') # # @note You can only overwrite existing values! Setting a new property won't have any effect! # # @example Setter Example # # you can set an existing x property of yatta by calling # w.x = "text" # # instead of # yatta.val('x', "text") # # In order to set a new property you have to overwrite an existing property. # Therefore the JsonWrapper supports a special feature that should make things more convenient # (we can argue about that, use the JsonType if you don't like it ;). # If you overwrite an object property of the JsonWrapper with a new object, it will result in a merged version of the objects. # Let w.p the property that is to be overwritten and o the new value. E.g. w.p = o # * The result has all properties of o # * The result has all properties of w.p if they don't occur under the same property-name in o. # # @example Conflict Example # yatta.value = {a : "string"} # w = yatta.value # console.log(w) # {a : "string"} # w.a = {a : {b : "string"}} # console.log(w) # {a : {b : "String"}} # w.a = {a : {c : 4}} # console.log(w) # {a : {b : "String", c : 4}} # # @example Common Pitfalls # w = yatta.value # # Setting a new property # w.newProperty = "Awesome" # console.log(w.newProperty == "Awesome") # false, w.newProperty is undefined # # overwrite the w object # w = {newProperty : "Awesome"} # console.log(w.newProperty == "Awesome") # true!, but .. # console.log(yatta.value.newProperty == "Awesome") # false, you are only allowed to set properties! # # The solution # yatta.value = {newProperty : "Awesome"} # console.log(w.newProperty == "Awesome") # true! # class JsonWrapper constructor: (jsonType)-> for name, obj of jsonType.map do (name, obj)-> Object.defineProperty JsonWrapper.prototype, name, get : -> x = obj.val() if x instanceof JsonType createJsonWrapper x else if x instanceof types.ImmutableObject x.val() else x set : (o)-> if o.constructor is {}.constructor overwrite = jsonType.val(name) for o_name,o_obj of o overwrite.val(o_name, o_obj, 'immutable') else jsonType.val(name, o, 'immutable') enumerable: true configurable: false new JsonWrapper _jsonType # # Manages Object-like values. # class JsonType extends types.MapManager # # @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created. # @param {Object} initial_value Create this operation with an initial value. # @param {String|Boolean} Whether the initial_value should be created as mutable. (Optional - see setMutableDefault) # constructor: (uid, initial_value, mutable)-> super uid if initial_value? if typeof initial_value isnt "object" throw new Error "The initial value of JsonTypes must be of type Object! (current type: #{typeof initial_value})" for name,o of initial_value @val name, o, mutable mutable_default: true setMutableDefault: (mutable)-> if mutable is true or mutable is 'mutable' JsonType.prototype.mutable_default = true else if mutable is false or mutable is 'immutable' JsonType.prototype.mutable_default = false else throw new Error 'Set mutable either "mutable" or "immutable"!' 'OK' # # @overload val() # Get this as a Json object. # @return [Json] # # @overload val(name) # Get value of a property. # @param {String} name Name of the object property. # @return [JsonType|Word|String|Object] Depending on the value of the property. If mutable it will return a Operation-type object, if immutable it will return String/Object. # # @overload val(name, content) # Set a new property. # @param {String} name Name of the object property. # @param {Object|String} content Content of the object property. # @return [JsonType] This object. (supports chaining) # val: (name, content, mutable)-> if typeof name is 'object' # Special case. First argument is an object. Then the second arg is mutable. # Keep that in mind when reading the following.. for o_name,o of name @val(o_name,o,content) @ else if name? and content? if mutable? if mutable is true or mutable is 'mutable' mutable = true else mutable = false else mutable = @mutable_default if typeof content is 'function' @ # Just do nothing else if ((not mutable) or typeof content is 'number') and content.constructor isnt Object obj = HB.addOperation(new types.ImmutableObject undefined, content).execute() super name, obj else if typeof content is 'string' word = HB.addOperation(new types.Word HB.getNextOperationIdentifier(), content).execute() super name, word else if content.constructor is Object json = HB.addOperation(new JsonType HB.getNextOperationIdentifier(), content, mutable).execute() super name, json else throw new Error "You must not set #{typeof content}-types in collaborative Json-objects!" else super name, content Object.defineProperty JsonType.prototype, 'value', get : -> createJsonWrapper @ set : (o)-> if o.constructor is {}.constructor for o_name,o_obj of o @val(o_name, o_obj, 'immutable') else throw new Error "You must only set Object values!" # # @private # _encode: ()-> { 'type' : "JsonType" 'uid' : @getUid() } parser['JsonType'] = (json)-> { 'uid' : uid } = json new JsonType uid types['JsonType'] = JsonType text_types