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 += '' 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