Cleaning up. No more bubbling events. All tests run fine. Following the Object.observe pattern (untested).

This commit is contained in:
DadaMonad
2014-12-17 22:50:08 +00:00
parent 584964153c
commit 21f7350c4d
35 changed files with 263 additions and 387 deletions

View File

@@ -19,12 +19,13 @@ module.exports = (HB)->
class Operation
#
# @param {Object} uid A unique identifier.
# @param {Object} uid A unique identifier.
# If uid is undefined, a new uid will be created before at the end of the execution sequence
#
constructor: (uid)->
@is_deleted = false
@garbage_collected = false
@event_listeners = [] # TODO: rename to observers or sth like that
if uid?
@uid = uid
@@ -32,41 +33,27 @@ module.exports = (HB)->
#
# Add an event listener. It depends on the operation which events are supported.
# @param {String} event Name of the event.
# @param {Function} f f is executed in case the event fires.
#
on: (events, f)->
@event_listeners ?= {}
if events.constructor isnt [].constructor
events = [events]
for e in events
@event_listeners[e] ?= []
@event_listeners[e].push f
observe: (f)->
@event_listeners.push f
#
# Deletes a function from an event / list of events.
# @see Operation.on
# Deletes function from the observer list
# @see Operation.observe
#
# @overload deleteListener(event, f)
# @param event {String} An event name
# @param f {Function} The function that you want to delete from these events
# @overload deleteListener(events, f)
# @param events {Array<String>} A list of event names
# @param f {Function} The function that you want to delete from these events.
deleteListener: (events, f)->
if events.constructor isnt [].constructor
events = [events]
for e in events
if @event_listeners?[e]?
@event_listeners[e] = @event_listeners[e].filter (g)->
f isnt g
#
# Deletes all subscribed event listeners.
# This should be called, e.g. after this has been replaced.
# @overload unobserve(event, f)
# @param f {Function} The function that you want to delete
unobserve: (f)->
@event_listeners.filter (g)->
f isnt g
#
# Deletes all subscribed event listeners.
# This should be called, e.g. after this has been replaced.
# (Then only one replace event should fire. )
# This is also called in the cleanup method.
deleteAllListeners: ()->
# This is also called in the cleanup method.
deleteAllObservers: ()->
@event_listeners = []
#
@@ -78,11 +65,10 @@ module.exports = (HB)->
#
# Fire an event and specify in which context the listener is called (set 'this').
#
forwardEvent: (op, event, args...)->
if @event_listeners?[event]?
for f in @event_listeners[event]
f.call op, event, args...
# TODO: do you need this ?
forwardEvent: (op, args...)->
for f in @event_listeners
f.call op, args...
isDeleted: ()->
@is_deleted
@@ -98,7 +84,7 @@ module.exports = (HB)->
cleanup: ()->
#console.log "cleanup: #{@type}"
HB.removeOperation @
@deleteAllListeners()
@deleteAllObservers()
#
# Set the parent of this operation.
@@ -128,15 +114,15 @@ module.exports = (HB)->
#
execute: ()->
@is_executed = true
if not @uid?
# When this operation was created without a uid, then set it here.
# There is only one other place, where this can be done - before an Insertion
if not @uid?
# When this operation was created without a uid, then set it here.
# There is only one other place, where this can be done - before an Insertion
# is executed (because we need the creator_id)
@uid = HB.getNextOperationIdentifier()
@uid = HB.getNextOperationIdentifier()
HB.addOperation @
for l in execution_listener
l @_encode()
@
@
#
# @private
@@ -388,7 +374,7 @@ module.exports = (HB)->
if parent? and fire_event
@setParent parent
@parent.callEvent "insert", @
@
@
#
# Compute the position of this operation.
@@ -501,7 +487,7 @@ module.exports = (HB)->
delete @prev_cl.unchecked.next_cl
@prev_cl.next_cl = @
super
else if @prev_cl? or @next_cl? or true # TODO: are you sure? This can happen right?
else if @prev_cl? or @next_cl? or true # TODO: are you sure? This can happen right?
super
#else
# throw new Error "Delimiter is unsufficient defined!"

View File

@@ -115,12 +115,16 @@ module.exports = (HB)->
cleanup: ()->
super()
#
# Transform this to a Json. If your browser supports Object.observe it will be transformed automatically when a change arrives.
# Transform this to a Json. If your browser supports Object.observe it will be transformed automatically when a change arrives.
# Otherwise you will loose all the sharing-abilities (the new object will be a deep clone)!
# @return {Json}
#
# TODO: at the moment you don't consider changing of properties.
# E.g.: let x = {a:[]}. Then x.a.push 1 wouldn't change anything
#
toJson: ()->
if not @bound_json? or not Object.observe? or true # TODO: currently, you are not watching mutable strings for changes, and, therefore, the @bound_json is not updated. TODO TODO wuawuawua easy
val = @val()
@@ -137,14 +141,14 @@ module.exports = (HB)->
else
json[name] = o
@bound_json = json
if Object.observe?
if Object.observe?
that = @
Object.observe @bound_json, (events)->
for event in events
if not event.changed_by? and (event.type is "add" or event.type = "update")
# this event is not created by Yatta.
that.val(event.name, event.object[event.name])
that.on 'change', (event_name, property_name, op)->
that.observe (event_name, property_name, op)->
if this is that and op.uid.creator isnt HB.getUserId()
notifier = Object.getNotifier(that.bound_json)
oldVal = that.bound_json[property_name]
@@ -152,17 +156,17 @@ module.exports = (HB)->
notifier.performChange 'update', ()->
that.bound_json[property_name] = that.val(property_name)
, that.bound_json
notifier.notify
notifier.notify
object: that.bound_json
type: 'update'
name: property_name
oldValue: oldVal
changed_by: op.uid.creator
else
else
notifier.performChange 'add', ()->
that.bound_json[property_name] = that.val(property_name)
, that.bound_json
notifier.notify
notifier.notify
object: that.bound_json
type: 'add'
name: property_name
@@ -170,23 +174,6 @@ module.exports = (HB)->
changed_by: op.uid.creator
@bound_json
#
# @see WordType.setReplaceManager
# Sets the parent of this JsonType object.
#
setReplaceManager: (replace_manager)->
@replace_manager = replace_manager
@on ['change','addProperty'], ()->
if replace_manager.parent?
replace_manager.parent.forwardEvent this, arguments...
#
# Get the parent of this JsonType.
# @return {JsonType}
#
getParent: ()->
@replace_manager.parent
#
# Whether the default is 'mutable' (true) or 'immutable' (false)
#
@@ -222,16 +209,7 @@ module.exports = (HB)->
# @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.
# (I refer to var name and content here)
# Keep that in mind when reading the following..
jt = new JsonType()
@replace_manager.replace jt.execute()
for n,o of name
jt.val n, o, mutable
@
else if name? and arguments.length > 1
if name? and arguments.length > 1
if mutable?
if mutable is true or mutable is 'mutable'
mutable = true

View File

@@ -91,10 +91,10 @@ module.exports = (HB)->
# always have the same result (ReplaceManager, and its beginning and end are the same)
#
execute: ()->
if not @validateSavedOperations()
if not @validateSavedOperations()
return false
else
# helper for cloning an object
# helper for cloning an object
clone = (o)->
p = {}
for name,value of o
@@ -110,7 +110,7 @@ module.exports = (HB)->
uid_end.op_number = "#{uid_r.op_number}_end"
beg = (new types.Delimiter uid_beg, undefined, uid_end).execute()
end = (new types.Delimiter uid_end, beg, undefined).execute()
@map_manager.map[@name] = new ReplaceManager undefined, uid_r, beg, end
@map_manager.map[@name] = new ReplaceManager uid_r, beg, end
@map_manager.map[@name].setParent @map_manager, @name
(@map_manager.map[@name].add_name_ops ?= []).push @
@map_manager.map[@name].execute()
@@ -224,10 +224,8 @@ module.exports = (HB)->
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
# @param {Delimiter} beginning Reference or Object.
# @param {Delimiter} end Reference or Object.
constructor: (initial_content, uid, beginning, end, prev, next, origin)->
super uid, beginning, end, prev, next, origin
if initial_content?
@replace initial_content
# constructor: (uid, beginning, end, prev, next, origin)->
# super uid, beginning, end, prev, next, origin
type: "ReplaceManager"
@@ -256,24 +254,6 @@ module.exports = (HB)->
(new Replaceable content, @, replaceable_uid, o, o.next_cl).execute()
undefined
#
# Add change listeners for parent.
#
setParent: (parent, property_name)->
repl_manager = this
@on 'insert', (event, op)->
if op.next_cl instanceof types.Delimiter
repl_manager.parent.callEvent 'change', property_name, op
@on 'change', (event, op)->
if repl_manager isnt this
repl_manager.parent.callEvent 'change', property_name, op
# Call this, when the first element is inserted. Then delete the listener.
addPropertyListener = (event, op)->
repl_manager.deleteListener 'insert', addPropertyListener
repl_manager.parent.callEvent 'addProperty', property_name, op
@on 'insert', addPropertyListener
super parent
#
# Get the value of this WordType
# @return {String}
@@ -304,7 +284,6 @@ module.exports = (HB)->
parser["ReplaceManager"] = (json)->
{
'content' : content
'uid' : uid
'prev': prev
'next': next
@@ -312,7 +291,7 @@ module.exports = (HB)->
'beginning' : beginning
'end' : end
} = json
new ReplaceManager content, uid, beginning, end, prev, next, origin
new ReplaceManager uid, beginning, end, prev, next, origin
#
@@ -342,16 +321,10 @@ module.exports = (HB)->
val: ()->
@content
#
# Replace the content of this replaceable with new content.
#
replace: (content)->
@parent.replace content
applyDelete: ()->
if @content?
if @next_cl.type isnt "Delimiter"
@content.deleteAllListeners()
@content.deleteAllObservers()
@content.applyDelete()
@content.dontSync()
@content = null
@@ -361,16 +334,14 @@ module.exports = (HB)->
super
#
# If possible set the replace manager in the content.
# @see WordType.setReplaceManager
#
execute: ()->
if not @validateSavedOperations()
return false
else
@content?.setReplaceManager?(@parent)
# only fire 'insert-event' (which will result in addProperty and change events),
# when content is added. In case of Json, empty content means that this is not the last update,
# when content is added.
# In case of Json, empty content means that this is not the last update,
# since content is deleted when 'applyDelete' was exectuted.
ins_result = super(@content?) # @content? whether to fire or not
if ins_result

View File

@@ -176,23 +176,6 @@ module.exports = (HB)->
delete_ops.push d._encode()
@
#
# Replace the content of this word with another one. Concurrent replacements are not merged!
# Only one of the replacements will be used.
#
# @return {WordType} Returns the new WordType object.
#
replaceText: (text)->
# Can only be used if the ReplaceManager was set!
# @see WordType.setReplaceManager
if @replace_manager?
word = (new WordType undefined).execute()
word.insertText 0, text
@replace_manager.replace(word)
word
else
throw new Error "This type is currently not maintained by a ReplaceManager!"
#
# Get the String-representation of this word.
# @return {String} The String-representation of this object.
@@ -213,18 +196,6 @@ module.exports = (HB)->
@val()
#
# @private
# In most cases you would embed a WordType in a Replaceable, wich is handled by the ReplaceManager in order
# to provide replace functionality.
#
setReplaceManager: (op)->
@saveOperation 'replace_manager', op
@validateSavedOperations()
@on 'insert', (event, ins)=>
@replace_manager?.forwardEvent @, 'change', ins
@on 'delete', (event, ins, del)=>
@replace_manager?.forwardEvent @, 'change', del
#
# Bind this WordType to a textfield or input field.
#
# @example

View File

@@ -135,7 +135,7 @@ module.exports = (HB)->
dont_proxy ()->
that.xml.removeChild deleted
@attributes.on ['addProperty', 'change'], (event, property_name, op)->
@attributes.on ['add', 'update'], (event, property_name, op)->
if op.creator isnt HB.getUserId() and this is that.attributes
dont_proxy ()->
newval = op.val().val()