367 lines
11 KiB
Plaintext
367 lines
11 KiB
Plaintext
###
|
|
json_types_uninitialized = require "./JsonTypes"
|
|
|
|
# some dom implementations may call another dom.method that simulates the behavior of another.
|
|
# For example xml.insertChild(dom) , wich inserts an element at the end, and xml.insertAfter(dom,null) wich does the same
|
|
# But Y's proxy may be called only once!
|
|
proxy_token = false
|
|
dont_proxy = (f)->
|
|
proxy_token = true
|
|
try
|
|
f()
|
|
catch e
|
|
proxy_token = false
|
|
throw new Error e
|
|
proxy_token = false
|
|
|
|
_proxy = (f_name, f)->
|
|
old_f = @[f_name]
|
|
if old_f?
|
|
@[f_name] = ()->
|
|
if not proxy_token and not @_y?.isDeleted()
|
|
that = this
|
|
args = arguments
|
|
dont_proxy ()->
|
|
f.apply that, args
|
|
old_f.apply that, args
|
|
else
|
|
old_f.apply this, arguments
|
|
#else
|
|
# @[f_name] = f
|
|
Element?.prototype._proxy = _proxy
|
|
|
|
|
|
module.exports = (HB)->
|
|
json_types = json_types_uninitialized HB
|
|
types = json_types.types
|
|
parser = json_types.parser
|
|
|
|
#
|
|
# Manages XML types
|
|
# Not supported:
|
|
# * Attribute nodes
|
|
# * Real replace of child elements (to much overhead). Currently, the new element is inserted after the 'replaced' element, and then it is deleted.
|
|
# * Namespaces (*NS)
|
|
# * Browser specific methods (webkit-* operations)
|
|
class XmlType extends types.Insert
|
|
|
|
constructor: (uid, @tagname, attributes, elements, @xml)->
|
|
### In case you make this instanceof Insert again
|
|
if prev? and (not next?) and prev.type?
|
|
# adjust what you actually mean. you want to insert after prev, then
|
|
# next is not defined. but we only insert after non-deleted elements.
|
|
# This is also handled in TextInsert.
|
|
while prev.isDeleted()
|
|
prev = prev.prev_cl
|
|
next = prev.next_cl
|
|
###
|
|
|
|
super(uid)
|
|
|
|
|
|
if @xml?._y?
|
|
d = new types.Delete undefined, @xml._y
|
|
HB.addOperation(d).execute()
|
|
@xml._y = null
|
|
|
|
if attributes? and elements?
|
|
@saveOperation 'attributes', attributes
|
|
@saveOperation 'elements', elements
|
|
else if (not attributes?) and (not elements?)
|
|
@attributes = new types.JsonType()
|
|
@attributes.setMutableDefault 'immutable'
|
|
HB.addOperation(@attributes).execute()
|
|
@elements = new types.WordType()
|
|
@elements.parent = @
|
|
HB.addOperation(@elements).execute()
|
|
else
|
|
throw new Error "Either define attribute and elements both, or none of them"
|
|
|
|
if @xml?
|
|
@tagname = @xml.tagName
|
|
for i in [0...@xml.attributes.length]
|
|
attr = xml.attributes[i]
|
|
@attributes.val(attr.name, attr.value)
|
|
for n in @xml.childNodes
|
|
if n.nodeType is n.TEXT_NODE
|
|
word = new TextNodeType(undefined, n)
|
|
HB.addOperation(word).execute()
|
|
@elements.push word
|
|
else if n.nodeType is n.ELEMENT_NODE
|
|
element = new XmlType undefined, undefined, undefined, undefined, n
|
|
HB.addOperation(element).execute()
|
|
@elements.push element
|
|
else
|
|
throw new Error "I don't know Node-type #{n.nodeType}!!"
|
|
@setXmlProxy()
|
|
undefined
|
|
|
|
#
|
|
# Identifies this class.
|
|
# Use it in order to check whether this is an xml-type or something else.
|
|
#
|
|
type: "XmlType"
|
|
|
|
applyDelete: (op)->
|
|
if @insert_parent? and not @insert_parent.isDeleted()
|
|
@insert_parent.applyDelete op
|
|
else
|
|
@attributes.applyDelete()
|
|
@elements.applyDelete()
|
|
super
|
|
|
|
cleanup: ()->
|
|
super()
|
|
|
|
setXmlProxy: ()->
|
|
@xml._y = @
|
|
that = @
|
|
|
|
@elements.on 'insert', (event, op)->
|
|
if op.creator isnt HB.getUserId() and this is that.elements
|
|
newNode = op.content.val()
|
|
right = op.next_cl
|
|
while right? and right.isDeleted()
|
|
right = right.next_cl
|
|
rightNode = null
|
|
if right.type isnt 'Delimiter'
|
|
rightNode = right.val().val()
|
|
dont_proxy ()->
|
|
that.xml.insertBefore newNode, rightNode
|
|
@elements.on 'delete', (event, op)->
|
|
del_op = op.deleted_by[0]
|
|
if del_op? and del_op.creator isnt HB.getUserId() and this is that.elements
|
|
deleted = op.content.val()
|
|
dont_proxy ()->
|
|
that.xml.removeChild deleted
|
|
|
|
@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()
|
|
if newval?
|
|
that.xml.setAttribute(property_name, op.val().val())
|
|
else
|
|
that.xml.removeAttribute(property_name)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Here are all methods that proxy the behavior of the xml
|
|
|
|
# you want to find a specific child element. Since they are carried by an Insert-Type, you want to find that Insert-Operation.
|
|
# @param child {DomElement} Dom element.
|
|
# @return {InsertType} This carries the XmlType that represents the DomElement (child). false if i couldn't find it.
|
|
#
|
|
findNode = (child)->
|
|
if not child?
|
|
throw new Error "you must specify a parameter!"
|
|
child = child._y
|
|
elem = that.elements.beginning.next_cl
|
|
while elem.type isnt 'Delimiter' and elem.content isnt child
|
|
elem = elem.next_cl
|
|
if elem.type is 'Delimiter'
|
|
false
|
|
else
|
|
elem
|
|
|
|
insertBefore = (insertedNode_s, adjacentNode)->
|
|
next = null
|
|
if adjacentNode?
|
|
next = findNode adjacentNode
|
|
prev = null
|
|
if next
|
|
prev = next.prev_cl
|
|
else
|
|
prev = @_y.elements.end.prev_cl
|
|
while prev.isDeleted()
|
|
prev = prev.prev_cl
|
|
inserted_nodes = null
|
|
if insertedNode_s.nodeType is insertedNode_s.DOCUMENT_FRAGMENT_NODE
|
|
child = insertedNode_s.lastChild
|
|
while child?
|
|
element = new XmlType undefined, undefined, undefined, undefined, child
|
|
HB.addOperation(element).execute()
|
|
that.elements.insertAfter prev, element
|
|
child = child.previousSibling
|
|
else
|
|
element = new XmlType undefined, undefined, undefined, undefined, insertedNode_s
|
|
HB.addOperation(element).execute()
|
|
that.elements.insertAfter prev, element
|
|
|
|
@xml._proxy 'insertBefore', insertBefore
|
|
@xml._proxy 'appendChild', insertBefore
|
|
@xml._proxy 'removeAttribute', (name)->
|
|
that.attributes.val(name, undefined)
|
|
@xml._proxy 'setAttribute', (name, value)->
|
|
that.attributes.val name, value
|
|
|
|
renewClassList = (newclass)->
|
|
dont_do_it = false
|
|
if newclass?
|
|
for elem in this
|
|
if newclass is elem
|
|
dont_do_it = true
|
|
value = Array.prototype.join.call this, " "
|
|
if newclass? and not dont_do_it
|
|
value += " "+newclass
|
|
that.attributes.val('class', value )
|
|
_proxy.call @xml.classList, 'add', renewClassList
|
|
_proxy.call @xml.classList, 'remove', renewClassList
|
|
@xml.__defineSetter__ 'className', (val)->
|
|
@setAttribute('class', val)
|
|
@xml.__defineGetter__ 'className', ()->
|
|
that.attributes.val('class')
|
|
@xml.__defineSetter__ 'textContent', (val)->
|
|
# remove all nodes
|
|
elem = that.xml.firstChild
|
|
while elem?
|
|
remove = elem
|
|
elem = elem.nextSibling
|
|
that.xml.removeChild remove
|
|
|
|
# insert word content
|
|
if val isnt ""
|
|
text_node = document.createTextNode val
|
|
that.xml.appendChild text_node
|
|
|
|
removeChild = (node)->
|
|
elem = findNode node
|
|
if not elem
|
|
throw new Error "You are only allowed to delete existing (direct) child elements!"
|
|
d = new types.Delete undefined, elem
|
|
HB.addOperation(d).execute()
|
|
node._y = null
|
|
@xml._proxy 'removeChild', removeChild
|
|
@xml._proxy 'replaceChild', (insertedNode, replacedNode)->
|
|
insertBefore.call this, insertedNode, replacedNode
|
|
removeChild.call this, replacedNode
|
|
|
|
|
|
|
|
val: (enforce = false)->
|
|
if document?
|
|
if (not @xml?) or enforce
|
|
@xml = document.createElement @tagname
|
|
|
|
attr = @attributes.val()
|
|
for attr_name, value of attr
|
|
if value?
|
|
a = document.createAttribute attr_name
|
|
a.value = value
|
|
@xml.setAttributeNode a
|
|
|
|
e = @elements.beginning.next_cl
|
|
while e.type isnt "Delimiter"
|
|
n = e.content
|
|
if not e.isDeleted() and e.content? # TODO: how can this happen? Probably because listeners
|
|
if n.type is "XmlType"
|
|
@xml.appendChild n.val(enforce)
|
|
else if n.type is "TextNodeType"
|
|
text_node = n.val()
|
|
@xml.appendChild text_node
|
|
else
|
|
throw new Error "Internal structure cannot be transformed to dom"
|
|
e = e.next_cl
|
|
@setXmlProxy()
|
|
@xml
|
|
|
|
|
|
execute: ()->
|
|
super()
|
|
###
|
|
if not @validateSavedOperations()
|
|
return false
|
|
else
|
|
|
|
return true
|
|
###
|
|
|
|
#
|
|
# Get the parent of this JsonType.
|
|
# @return {XmlType}
|
|
#
|
|
getParent: ()->
|
|
@parent
|
|
|
|
#
|
|
# @private
|
|
#
|
|
# Convert all relevant information of this operation to the json-format.
|
|
# This result can be send to other clients.
|
|
#
|
|
_encode: ()->
|
|
json =
|
|
{
|
|
'type' : @type
|
|
'attributes' : @attributes.getUid()
|
|
'elements' : @elements.getUid()
|
|
'tagname' : @tagname
|
|
'uid' : @getUid()
|
|
}
|
|
json
|
|
|
|
parser['XmlType'] = (json)->
|
|
{
|
|
'uid' : uid
|
|
'attributes' : attributes
|
|
'elements' : elements
|
|
'tagname' : tagname
|
|
} = json
|
|
|
|
new XmlType uid, tagname, attributes, elements, undefined
|
|
|
|
#
|
|
# @nodoc
|
|
# Defines an object that is cannot be changed. You can use this to set an immutable string, or a number.
|
|
#
|
|
class TextNodeType extends types.ImmutableObject
|
|
|
|
#
|
|
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
|
|
# @param {Object} content
|
|
#
|
|
constructor: (uid, content)->
|
|
if content._y?
|
|
d = new types.Delete undefined, content._y
|
|
HB.addOperation(d).execute()
|
|
content._y = null
|
|
content._y = @
|
|
super uid, content
|
|
|
|
applyDelete: (op)->
|
|
if @insert_parent? and not @insert_parent.isDeleted()
|
|
@insert_parent.applyDelete op
|
|
else
|
|
super
|
|
|
|
|
|
type: "TextNodeType"
|
|
|
|
#
|
|
# Encode this operation in such a way that it can be parsed by remote peers.
|
|
#
|
|
_encode: ()->
|
|
json = {
|
|
'type': @type
|
|
'uid' : @getUid()
|
|
'content' : @content.textContent
|
|
}
|
|
json
|
|
|
|
parser['TextNodeType'] = (json)->
|
|
{
|
|
'uid' : uid
|
|
'content' : content
|
|
} = json
|
|
textnode = document.createTextNode content
|
|
new TextNodeType uid, textnode
|
|
|
|
types['XmlType'] = XmlType
|
|
|
|
json_types
|
|
### |