outsourced all types (except for object type)

This commit is contained in:
DadaMonad
2015-02-27 18:01:21 +00:00
parent c663230c1b
commit 96ed8b0f98
33 changed files with 94032 additions and 63591 deletions

View File

@@ -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()

View File

@@ -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

View File

@@ -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

View File

@@ -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 += '&nbsp;'
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

View File

@@ -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 classs 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

View File

@@ -36,4 +36,4 @@ module.exports = createY
if window?
window.Y = createY
createY.Object = require "./Types/Object"
createY.Object = require "./ObjectType"