outsourced all types (except for object type)
This commit is contained in:
@@ -369,7 +369,7 @@ module.exports = ()->
|
||||
# (e.g. the following operation order must be invertible :
|
||||
# Insert refers to content, then the content is deleted)
|
||||
# Therefore, we have to do this in the cleanup
|
||||
if @content instanceof ops.Operation and not deleted_earlyer
|
||||
if @content instanceof ops.Operation
|
||||
@content.referenced_by--
|
||||
if @content.referenced_by <= 0 and not @content.is_deleted
|
||||
@content.applyDelete()
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
|
||||
json_types_uninitialized = require "./JsonTypes"
|
||||
|
||||
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)->
|
||||
|
||||
|
||||
|
||||
setXmlProxy: ()->
|
||||
|
||||
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
|
||||
@@ -1,80 +0,0 @@
|
||||
class YList
|
||||
|
||||
#
|
||||
# @private
|
||||
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
|
||||
#
|
||||
constructor: (list)->
|
||||
if not list?
|
||||
@_list = []
|
||||
else if list.constructor is Array
|
||||
@_list = list
|
||||
else
|
||||
throw new Error "Y.List expects an Array as a parameter"
|
||||
|
||||
_name: "List"
|
||||
|
||||
_getModel: (types, ops)->
|
||||
if not @_model?
|
||||
@_model = new ops.ListManager(@).execute()
|
||||
@_model.insert 0, @_list
|
||||
delete @_list
|
||||
@_model
|
||||
|
||||
_setModel: (@_model)->
|
||||
delete @_list
|
||||
|
||||
val: ()->
|
||||
@_model.val.apply @_model, arguments
|
||||
|
||||
observe: ()->
|
||||
@_model.observe.apply @_model, arguments
|
||||
@
|
||||
|
||||
unobserve: ()->
|
||||
@_model.unobserve.apply @_model, arguments
|
||||
@
|
||||
|
||||
#
|
||||
# Inserts an Object into the list.
|
||||
#
|
||||
# @return {ListManager Type} This String object.
|
||||
#
|
||||
insert: (position, content)->
|
||||
if typeof position isnt "number"
|
||||
throw new Error "Y.List.insert expects a Number as the first parameter!"
|
||||
@_model.insert position, [content]
|
||||
@
|
||||
|
||||
insertContents: (position, contents)->
|
||||
if typeof position isnt "number"
|
||||
throw new Error "Y.List.insert expects a Number as the first parameter!"
|
||||
@_model.insert position, contents
|
||||
@
|
||||
|
||||
delete: (position, length)->
|
||||
@_model.delete position, length
|
||||
@
|
||||
|
||||
push: (content)->
|
||||
@_model.push content
|
||||
@
|
||||
|
||||
if window?
|
||||
if window.Y?
|
||||
window.Y.List = YList
|
||||
else
|
||||
throw new Error "You must first import Y!"
|
||||
|
||||
if module?
|
||||
module.exports = YList
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,305 +0,0 @@
|
||||
#
|
||||
# Handles a String-like data structures with support for insert/delete at a word-position.
|
||||
#
|
||||
class YText
|
||||
|
||||
#
|
||||
# @private
|
||||
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
|
||||
#
|
||||
constructor: (text)->
|
||||
@textfields = []
|
||||
if not text?
|
||||
@_text = ""
|
||||
else if text.constructor is String
|
||||
@_text = text
|
||||
else
|
||||
throw new Error "Y.Text expects a String as a constructor"
|
||||
|
||||
_name: "Text"
|
||||
|
||||
_getModel: (types, ops)->
|
||||
if not @_model?
|
||||
@_model = new ops.ListManager(@).execute()
|
||||
@insert 0, @_text
|
||||
delete @_text
|
||||
@_model
|
||||
|
||||
_setModel: (@_model)->
|
||||
delete @_text
|
||||
|
||||
#
|
||||
# Get the String-representation of this word.
|
||||
# @return {String} The String-representation of this object.
|
||||
#
|
||||
val: ()->
|
||||
@_model.fold "", (left, o)->
|
||||
left + o.val()
|
||||
|
||||
observe: ()->
|
||||
@_model.observe.apply @_model, arguments
|
||||
|
||||
unobserve: ()->
|
||||
@_model.unobserve.apply @_model, arguments
|
||||
|
||||
#
|
||||
# Same as String.val
|
||||
# @see String.val
|
||||
#
|
||||
toString: ()->
|
||||
@val()
|
||||
|
||||
#
|
||||
# Inserts a string into the word.
|
||||
#
|
||||
# @return {ListManager Type} This String object.
|
||||
#
|
||||
insert: (position, content)->
|
||||
if content.constructor isnt String
|
||||
throw new Error "Y.String.insert expects a String as the second parameter!"
|
||||
if typeof position isnt "number"
|
||||
throw new Error "Y.String.insert expects a Number as the first parameter!"
|
||||
if content.length > 0
|
||||
ith = @_model.getOperationByPosition position
|
||||
# the (i-1)th character. e.g. "abc" the 1th character is "a"
|
||||
# the 0th character is the left Delimiter
|
||||
@_model.insertAfter ith, content
|
||||
|
||||
delete: (position, length)->
|
||||
@_model.delete position, length
|
||||
|
||||
#
|
||||
# Bind this String to a textfield or input field.
|
||||
#
|
||||
# @example
|
||||
# var textbox = document.getElementById("textfield");
|
||||
# y.bind(textbox);
|
||||
#
|
||||
bind: (textfield, dom_root)->
|
||||
dom_root ?= window
|
||||
if (not dom_root.getSelection?)
|
||||
dom_root = window
|
||||
|
||||
# don't duplicate!
|
||||
for t in @textfields
|
||||
if t is textfield
|
||||
return
|
||||
creator_token = false;
|
||||
|
||||
word = @
|
||||
textfield.value = @val()
|
||||
@textfields.push textfield
|
||||
|
||||
if textfield.selectionStart? and textfield.setSelectionRange?
|
||||
createRange = (fix)->
|
||||
left = textfield.selectionStart
|
||||
right = textfield.selectionEnd
|
||||
if fix?
|
||||
left = fix left
|
||||
right = fix right
|
||||
{
|
||||
left: left
|
||||
right: right
|
||||
}
|
||||
|
||||
writeRange = (range)->
|
||||
writeContent word.val()
|
||||
textfield.setSelectionRange range.left, range.right
|
||||
|
||||
writeContent = (content)->
|
||||
textfield.value = content
|
||||
else
|
||||
createRange = (fix)->
|
||||
range = {}
|
||||
s = dom_root.getSelection()
|
||||
clength = textfield.textContent.length
|
||||
range.left = Math.min s.anchorOffset, clength
|
||||
range.right = Math.min s.focusOffset, clength
|
||||
if fix?
|
||||
range.left = fix range.left
|
||||
range.right = fix range.right
|
||||
|
||||
edited_element = s.focusNode
|
||||
if edited_element is textfield or edited_element is textfield.childNodes[0]
|
||||
range.isReal = true
|
||||
else
|
||||
range.isReal = false
|
||||
range
|
||||
|
||||
writeRange = (range)->
|
||||
writeContent word.val()
|
||||
textnode = textfield.childNodes[0]
|
||||
if range.isReal and textnode?
|
||||
if range.left < 0
|
||||
range.left = 0
|
||||
range.right = Math.max range.left, range.right
|
||||
if range.right > textnode.length
|
||||
range.right = textnode.length
|
||||
range.left = Math.min range.left, range.right
|
||||
r = document.createRange()
|
||||
r.setStart(textnode, range.left)
|
||||
r.setEnd(textnode, range.right)
|
||||
s = window.getSelection()
|
||||
s.removeAllRanges()
|
||||
s.addRange(r)
|
||||
writeContent = (content)->
|
||||
content_array = content.replace(new RegExp("\n",'g')," ").split(" ")
|
||||
textfield.innerText = ""
|
||||
for c, i in content_array
|
||||
textfield.innerText += c
|
||||
if i isnt content_array.length-1
|
||||
textfield.innerHTML += ' '
|
||||
|
||||
writeContent this.val()
|
||||
|
||||
@observe (events)->
|
||||
for event in events
|
||||
if not creator_token
|
||||
if event.type is "insert"
|
||||
o_pos = event.position
|
||||
fix = (cursor)->
|
||||
if cursor <= o_pos
|
||||
cursor
|
||||
else
|
||||
cursor += 1
|
||||
cursor
|
||||
r = createRange fix
|
||||
writeRange r
|
||||
|
||||
else if event.type is "delete"
|
||||
o_pos = event.position
|
||||
fix = (cursor)->
|
||||
if cursor < o_pos
|
||||
cursor
|
||||
else
|
||||
cursor -= 1
|
||||
cursor
|
||||
r = createRange fix
|
||||
writeRange r
|
||||
|
||||
# consume all text-insert changes.
|
||||
textfield.onkeypress = (event)->
|
||||
if word.is_deleted
|
||||
# if word is deleted, do not do anything ever again
|
||||
textfield.onkeypress = null
|
||||
return true
|
||||
creator_token = true
|
||||
char = null
|
||||
if event.keyCode is 13
|
||||
char = '\n'
|
||||
else if event.key?
|
||||
if event.charCode is 32
|
||||
char = " "
|
||||
else
|
||||
char = event.key
|
||||
else
|
||||
char = window.String.fromCharCode event.keyCode
|
||||
if char.length > 1
|
||||
return true
|
||||
else if char.length > 0
|
||||
r = createRange()
|
||||
pos = Math.min r.left, r.right
|
||||
diff = Math.abs(r.right - r.left)
|
||||
word.delete pos, diff
|
||||
word.insert pos, char
|
||||
r.left = pos + char.length
|
||||
r.right = r.left
|
||||
writeRange r
|
||||
|
||||
event.preventDefault()
|
||||
creator_token = false
|
||||
false
|
||||
|
||||
textfield.onpaste = (event)->
|
||||
if word.is_deleted
|
||||
# if word is deleted, do not do anything ever again
|
||||
textfield.onpaste = null
|
||||
return true
|
||||
event.preventDefault()
|
||||
textfield.oncut = (event)->
|
||||
if word.is_deleted
|
||||
# if word is deleted, do not do anything ever again
|
||||
textfield.oncut = null
|
||||
return true
|
||||
event.preventDefault()
|
||||
|
||||
#
|
||||
# consume deletes. Note that
|
||||
# chrome: won't consume deletions on keypress event.
|
||||
# keyCode is deprecated. BUT: I don't see another way.
|
||||
# since event.key is not implemented in the current version of chrome.
|
||||
# Every browser supports keyCode. Let's stick with it for now..
|
||||
#
|
||||
textfield.onkeydown = (event)->
|
||||
creator_token = true
|
||||
if word.is_deleted
|
||||
# if word is deleted, do not do anything ever again
|
||||
textfield.onkeydown = null
|
||||
return true
|
||||
r = createRange()
|
||||
pos = Math.min(r.left, r.right, word.val().length)
|
||||
diff = Math.abs(r.left - r.right)
|
||||
if event.keyCode? and event.keyCode is 8 # Backspace
|
||||
if diff > 0
|
||||
word.delete pos, diff
|
||||
r.left = pos
|
||||
r.right = pos
|
||||
writeRange r
|
||||
else
|
||||
if event.ctrlKey? and event.ctrlKey
|
||||
val = word.val()
|
||||
new_pos = pos
|
||||
del_length = 0
|
||||
if pos > 0
|
||||
new_pos--
|
||||
del_length++
|
||||
while new_pos > 0 and val[new_pos] isnt " " and val[new_pos] isnt '\n'
|
||||
new_pos--
|
||||
del_length++
|
||||
word.delete new_pos, (pos-new_pos)
|
||||
r.left = new_pos
|
||||
r.right = new_pos
|
||||
writeRange r
|
||||
else
|
||||
if pos > 0
|
||||
word.delete (pos-1), 1
|
||||
r.left = pos-1
|
||||
r.right = pos-1
|
||||
writeRange r
|
||||
event.preventDefault()
|
||||
creator_token = false
|
||||
return false
|
||||
else if event.keyCode? and event.keyCode is 46 # Delete
|
||||
if diff > 0
|
||||
word.delete pos, diff
|
||||
r.left = pos
|
||||
r.right = pos
|
||||
writeRange r
|
||||
else
|
||||
word.delete pos, 1
|
||||
r.left = pos
|
||||
r.right = pos
|
||||
writeRange r
|
||||
event.preventDefault()
|
||||
creator_token = false
|
||||
return false
|
||||
else
|
||||
creator_token = false
|
||||
true
|
||||
|
||||
|
||||
if window?
|
||||
if window.Y?
|
||||
window.Y.Text = YText
|
||||
else
|
||||
throw new Error "You must first import Y!"
|
||||
|
||||
if module?
|
||||
module.exports = YText
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,492 +0,0 @@
|
||||
class YXml
|
||||
|
||||
constructor: (tag_or_dom, attributes = {})->
|
||||
if not tag_or_dom?
|
||||
# nop
|
||||
else if tag_or_dom.constructor is String
|
||||
tagname = tag_or_dom
|
||||
@_xml = {}
|
||||
@_xml.children = []
|
||||
#TODO: How to force the user to specify parameters?
|
||||
#if not tagname?
|
||||
# throw new Error "You must specify a tagname"
|
||||
@_xml.tagname = tagname
|
||||
if attributes.constructor isnt Object
|
||||
throw new Error "The attributes must be specified as a Object"
|
||||
for a_name, a of attributes
|
||||
if a.constructor isnt String
|
||||
throw new Error "The attributes must be of type String!"
|
||||
@_xml.attributes = attributes
|
||||
@_xml.classes = {}
|
||||
_classes = @_xml.attributes.class
|
||||
delete @_xml.attributes.class
|
||||
if _classes?
|
||||
for c_name, c in _classes.split(" ")
|
||||
if c.length > 0
|
||||
@_xml.classes[c_name] = c
|
||||
undefined
|
||||
else if tag_or_dom instanceof Element
|
||||
@_dom = tag_or_dom
|
||||
@_xml = {}
|
||||
|
||||
|
||||
|
||||
_name: "Xml"
|
||||
|
||||
_getModel: (Y, ops)->
|
||||
if not @_model?
|
||||
if @_dom?
|
||||
@_xml.tagname = @_dom.tagName.toLowerCase()
|
||||
@_xml.attributes = {}
|
||||
@_xml.classes = {}
|
||||
for attribute in @_dom.attributes
|
||||
if attribute.name is "class"
|
||||
for c in attribute.value.split(" ")
|
||||
@_xml.classes[c] = true
|
||||
else
|
||||
@_xml.attributes[attribute.name] = attribute.value
|
||||
@_xml.children = []
|
||||
for child in @_dom.childNodes
|
||||
if child.nodeType is child.TEXT_NODE
|
||||
@_xml.children.push child.textContent
|
||||
else
|
||||
@_xml.children.push(new YXml(child))
|
||||
@_model = new ops.MapManager(@).execute()
|
||||
@_model.val("attributes", new Y.Object(@_xml.attributes))
|
||||
@_model.val("classes", new Y.Object(@_xml.classes))
|
||||
@_model.val("tagname", @_xml.tagname)
|
||||
@_model.val("children", new Y.List(@_xml.children))
|
||||
if @_xml.parent?
|
||||
@_model.val("parent", @_xml.parent)
|
||||
|
||||
if @_dom?
|
||||
@getDom() # two way bind dom to this xml type
|
||||
|
||||
@_setModel @_model
|
||||
|
||||
@_model
|
||||
|
||||
_setModel: (@_model)->
|
||||
@_model.observe (events)->
|
||||
for event in events
|
||||
if event.name is "parent" and event.type isnt "add"
|
||||
parent = event.oldValue
|
||||
children = parent._model.val("children")?.val()
|
||||
if children?
|
||||
for c,i in children
|
||||
if c is @
|
||||
parent._model.val("children").delete i
|
||||
break
|
||||
delete @_xml
|
||||
|
||||
_setParent: (parent)->
|
||||
if parent instanceof YXml
|
||||
if @_model?
|
||||
@remove()
|
||||
@_model.val("parent", parent)
|
||||
else
|
||||
@_xml.parent = parent
|
||||
else
|
||||
throw new Error "parent must be of type Y.Xml!"
|
||||
|
||||
toString: ()->
|
||||
xml = "<"+@_model.val("tagname")
|
||||
for name, value of @attr()
|
||||
xml += " "+name+'="'+value+'"'
|
||||
xml += ">"
|
||||
for child in @_model.val("children").val()
|
||||
xml += child.toString()
|
||||
xml += '</'+@_model.val("tagname")+'>'
|
||||
xml
|
||||
|
||||
#
|
||||
# Get/set the attribute(s) of this element.
|
||||
# .attr()
|
||||
# .attr(name)
|
||||
# .attr(name, value)
|
||||
#
|
||||
attr: (name, value)->
|
||||
if arguments.length > 1
|
||||
if value.constructor isnt String
|
||||
throw new Error "The attributes must be of type String!"
|
||||
if name is "class"
|
||||
classes = value.split(" ")
|
||||
cs = {}
|
||||
for c in classes
|
||||
cs[c] = true
|
||||
|
||||
@_model.val("classes", new @_model.custom_types.Object(cs))
|
||||
else
|
||||
@_model.val("attributes").val(name, value)
|
||||
@
|
||||
else if arguments.length > 0
|
||||
if name is "class"
|
||||
Object.keys(@_model.val("classes").val()).join(" ")
|
||||
else
|
||||
@_model.val("attributes").val(name)
|
||||
else
|
||||
attrs = @_model.val("attributes").val()
|
||||
classes = Object.keys(@_model.val("classes").val()).join(" ")
|
||||
if classes.length > 0
|
||||
attrs["class"] = classes
|
||||
attrs
|
||||
|
||||
#
|
||||
# Adds the specified class(es) to this element
|
||||
#
|
||||
addClass: (names)->
|
||||
for name in names.split(" ")
|
||||
@_model.val("classes").val(name, true)
|
||||
@
|
||||
|
||||
#
|
||||
# Insert content, specified by the parameter, after this element
|
||||
# .after(content [, content])
|
||||
#
|
||||
after: ()->
|
||||
parent = @_model.val("parent")
|
||||
if not parent?
|
||||
throw new Error "This Xml Element must not have siblings! (for it does not have a parent)"
|
||||
|
||||
# find the position of this element
|
||||
for c,position in parent.getChildren()
|
||||
if c is @
|
||||
break
|
||||
|
||||
contents = []
|
||||
for content in arguments
|
||||
if content instanceof YXml
|
||||
content._setParent(@_model.val("parent"))
|
||||
else if content.constructor isnt String
|
||||
throw new Error "Y.Xml.after expects instances of YXml or String as a parameter"
|
||||
contents.push content
|
||||
|
||||
parent._model.val("children").insertContents(position+1, contents)
|
||||
|
||||
#
|
||||
# Insert content, specified by the parameter, to the end of this element
|
||||
# .append(content [, content])
|
||||
#
|
||||
append: ()->
|
||||
for content in arguments
|
||||
if content instanceof YXml
|
||||
content._setParent(@)
|
||||
else if content.constructor isnt String
|
||||
throw new Error "Y.Xml.after expects instances of YXml or String as a parameter"
|
||||
@_model.val("children").push(content)
|
||||
@
|
||||
|
||||
#
|
||||
# Insert content, specified by the parameter, after this element
|
||||
# .after(content [, content])
|
||||
#
|
||||
before: ()->
|
||||
parent = @_model.val("parent")
|
||||
if not parent?
|
||||
throw new Error "This Xml Element must not have siblings! (for it does not have a parent)"
|
||||
|
||||
# find the position of this element
|
||||
for c,position in parent.getChildren()
|
||||
if c is @
|
||||
break
|
||||
|
||||
contents = []
|
||||
for content in arguments
|
||||
if content instanceof YXml
|
||||
content._setParent(@_model.val("parent"))
|
||||
else if content.constructor isnt String
|
||||
throw new Error "Y.Xml.after expects instances of YXml or String as a parameter"
|
||||
contents.push content
|
||||
|
||||
parent._model.val("children").insertContents(position, contents)
|
||||
|
||||
#
|
||||
# Remove all child nodes of the set of matched elements from the DOM.
|
||||
# .empty()
|
||||
#
|
||||
empty: ()->
|
||||
# TODO: do it like this : @_model.val("children", new Y.List())
|
||||
children = @_model.val("children")
|
||||
for child in children.val()
|
||||
if child.constructor is String
|
||||
children.delete(0)
|
||||
else
|
||||
child.remove()
|
||||
|
||||
#
|
||||
# Determine whether any of the matched elements are assigned the given class.
|
||||
# .hasClass(className)
|
||||
#
|
||||
hasClass: (className)->
|
||||
if @_model.val("classes").val(className)?
|
||||
true
|
||||
else
|
||||
false
|
||||
|
||||
#
|
||||
# Insert content, specified by the parameter, to the beginning of this element.
|
||||
# .prepend(content [, content])
|
||||
#
|
||||
prepend: ()->
|
||||
for content in arguments
|
||||
if content instanceof YXml
|
||||
content._setParent(@)
|
||||
else if content.constructor isnt String
|
||||
throw new Error "Y.Xml.after expects instances of YXml or String as a parameter"
|
||||
@_model.val("children").insert(0, content)
|
||||
@
|
||||
|
||||
#
|
||||
# Remove this element from the DOM
|
||||
# .remove()
|
||||
#
|
||||
remove: ()->
|
||||
parent = @_model.delete("parent")
|
||||
@
|
||||
|
||||
#
|
||||
# Remove an attribute from this element
|
||||
# .removeAttr(attrName)
|
||||
#
|
||||
removeAttr: (attrName)->
|
||||
if attrName is "class"
|
||||
@_model.val("classes", new @_model.custom_types.Object())
|
||||
else
|
||||
@_model.val("attributes").delete(attrName)
|
||||
@
|
||||
|
||||
#
|
||||
# Remove a single class, multiple classes, or all classes from this element
|
||||
# .removeClass([className])
|
||||
#
|
||||
removeClass: ()->
|
||||
if arguments.length is 0
|
||||
@_model.val("classes", new @_model.custom_types.Object())
|
||||
else
|
||||
for className in arguments
|
||||
@_model.val("classes").delete(className)
|
||||
@
|
||||
|
||||
#
|
||||
# Add or remove one or more classes from this element,
|
||||
# depending on either the class’s presence or the value of the state argument.
|
||||
# .toggleClass([className])
|
||||
#
|
||||
toggleClass: ()->
|
||||
for className in arguments
|
||||
classes = @_model.val("classes")
|
||||
if classes.val(className)?
|
||||
classes.delete(className)
|
||||
else
|
||||
classes.val(className, true)
|
||||
@
|
||||
|
||||
#
|
||||
# Get the parent of this Element
|
||||
# @Note: Every XML element can only have one parent
|
||||
# .getParent()
|
||||
#
|
||||
getParent: ()->
|
||||
@_model.val("parent")
|
||||
|
||||
#
|
||||
# Get all the children of this XML Element as an Array
|
||||
# @Note: The children are either of type Y.Xml or String
|
||||
# .getChildren()
|
||||
#
|
||||
getChildren: ()->
|
||||
@_model.val("children").val()
|
||||
|
||||
getPosition: ()->
|
||||
parent = @_model.val("parent")
|
||||
if parent?
|
||||
for c,i in parent._model.val("children").val()
|
||||
if c is @
|
||||
return i
|
||||
throw new Error "This is not a child of its parent (should not happen in Y.Xml!)"
|
||||
else
|
||||
null
|
||||
|
||||
|
||||
getDom: ()->
|
||||
if not @_dom?
|
||||
@_dom = document.createElement(@_model.val("tagname"))
|
||||
|
||||
# set the attributes _and_ the classes (@see .attr())
|
||||
for attr_name, attr_value of @attr()
|
||||
@_dom.setAttribute attr_name, attr_value
|
||||
for child,i in @getChildren()
|
||||
if child.constructor is String
|
||||
dom = document.createTextNode child
|
||||
else
|
||||
dom = child.getDom()
|
||||
@_dom.insertBefore dom
|
||||
|
||||
that = @
|
||||
|
||||
if (not @_dom._y_xml?)
|
||||
@_dom._y_xml = @
|
||||
initialize_proxies.call @
|
||||
|
||||
@_model.val("children").observe (events)->
|
||||
for event in events
|
||||
if event.type is "insert"
|
||||
if event.value.constructor is String
|
||||
newNode = document.createTextNode(event.value)
|
||||
else
|
||||
newNode = event.value.getDom()
|
||||
event.value._setParent that
|
||||
children = that._dom.childNodes
|
||||
if children.length is event.position
|
||||
rightNode = null
|
||||
else
|
||||
rightNode = children[event.position]
|
||||
|
||||
dont_proxy ()->
|
||||
that._dom.insertBefore newNode, rightNode
|
||||
else if event.type is "delete"
|
||||
deleted = event.oldValue.getDom()
|
||||
dont_proxy ()->
|
||||
that._dom.removeChild deleted
|
||||
@_model.val("attributes").observe (events)->
|
||||
for event in events
|
||||
if event.type is "add" or event.type is "update"
|
||||
newval = event.object.val(event.name)
|
||||
dont_proxy ()->
|
||||
that._dom.setAttribute event.name, newval
|
||||
else if event.type is "delete"
|
||||
dont_proxy ()->
|
||||
that._dom.removeAttribute event.name
|
||||
setClasses = ()->
|
||||
that._model.val("classes").observe (events)->
|
||||
for event in events
|
||||
if event.type is "add" or event.type is "update"
|
||||
dont_proxy ()->
|
||||
that._dom.classList.add event.name # classes are stored as the keys
|
||||
else if event.type is "delete"
|
||||
dont_proxy ()->
|
||||
that._dom.classList.remove event.name
|
||||
setClasses()
|
||||
@_model.observe (events)->
|
||||
for event in events
|
||||
if event.type is "add" or event.type is "update"
|
||||
dont_proxy ()->
|
||||
classes = that.attr("class")
|
||||
if (not classes?) or classes is ""
|
||||
that._dom.removeAttribute "class"
|
||||
else
|
||||
that._dom.setAttribute "class", that.attr("class")
|
||||
setClasses()
|
||||
|
||||
@_dom
|
||||
|
||||
proxies_are_initialized = false
|
||||
# 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
|
||||
|
||||
initialize_proxies = ()->
|
||||
|
||||
_proxy = (f_name, f, source = Element.prototype, y)->
|
||||
old_f = source[f_name]
|
||||
source[f_name] = ()->
|
||||
if (not (y? or @_y_xml?)) or proxy_token
|
||||
old_f.apply this, arguments
|
||||
else if @_y_xml?
|
||||
f.apply @_y_xml, arguments
|
||||
else
|
||||
f.apply y, arguments
|
||||
|
||||
that = this
|
||||
f_add = (c)->
|
||||
that.addClass c
|
||||
_proxy "add", f_add, @_dom.classList, @
|
||||
|
||||
f_remove = (c)->
|
||||
that.removeClass c
|
||||
|
||||
_proxy "remove", f_remove, @_dom.classList, @
|
||||
|
||||
@_dom.__defineSetter__ 'className', (val)->
|
||||
that.attr('class', val)
|
||||
@_dom.__defineGetter__ 'className', ()->
|
||||
that.attr('class')
|
||||
@_dom.__defineSetter__ 'textContent', (val)->
|
||||
# remove all nodes
|
||||
that.empty()
|
||||
|
||||
# insert word content
|
||||
if val isnt ""
|
||||
that.append val
|
||||
|
||||
|
||||
if proxies_are_initialized
|
||||
return
|
||||
proxies_are_initialized = true
|
||||
|
||||
# the following methods are initialized on prototypes and therefore they need to be written only once!
|
||||
|
||||
insertBefore = (insertedNode_s, adjacentNode)->
|
||||
if adjacentNode?
|
||||
pos = adjacentNode._y_xml.getPosition()
|
||||
else
|
||||
pos = @getChildren().length
|
||||
|
||||
new_childs = []
|
||||
if insertedNode_s.nodeType is insertedNode_s.DOCUMENT_FRAGMENT_NODE
|
||||
child = insertedNode_s.firstChild
|
||||
while child?
|
||||
new_childs.push child
|
||||
child = child.nextSibling
|
||||
else
|
||||
new_childs.push insertedNode_s
|
||||
new_childs = new_childs.map (child)->
|
||||
if child._y_xml?
|
||||
child._y_xml
|
||||
else if child.nodeType == child.TEXT_NODE
|
||||
child.textContent
|
||||
else
|
||||
new YXml(child)
|
||||
@_model.val("children").insertContents pos, new_childs
|
||||
|
||||
_proxy 'insertBefore', insertBefore
|
||||
_proxy 'appendChild', insertBefore
|
||||
_proxy 'removeAttribute', (name)->
|
||||
@removeAttr name
|
||||
_proxy 'setAttribute', (name, value)->
|
||||
@attr name, value
|
||||
|
||||
removeChild = (node)->
|
||||
node._y_xml.remove()
|
||||
_proxy 'removeChild', removeChild
|
||||
replaceChild = (insertedNode, replacedNode)-> # TODO: handle replace with replace behavior...
|
||||
insertBefore.call this, insertedNode, replacedNode
|
||||
removeChild.call this, replacedNode
|
||||
_proxy 'replaceChild', replaceChild
|
||||
|
||||
if window?
|
||||
if window.Y?
|
||||
window.Y.Xml = YXml
|
||||
else
|
||||
throw new Error "You must first import Y!"
|
||||
|
||||
if module?
|
||||
module.exports = YXml
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -36,4 +36,4 @@ module.exports = createY
|
||||
if window?
|
||||
window.Y = createY
|
||||
|
||||
createY.Object = require "./Types/Object"
|
||||
createY.Object = require "./ObjectType"
|
||||
|
||||
Reference in New Issue
Block a user