about summary refs log tree commit diff stats
path: root/src/html/dom.nim
diff options
context:
space:
mode:
Diffstat (limited to 'src/html/dom.nim')
-rw-r--r--src/html/dom.nim195
1 files changed, 125 insertions, 70 deletions
diff --git a/src/html/dom.nim b/src/html/dom.nim
index 2a4dc921..98d1f7ea 100644
--- a/src/html/dom.nim
+++ b/src/html/dom.nim
@@ -128,8 +128,7 @@ type
     element: Element
     attrlist: seq[Attr]
 
-  Collection = ref CollectionObj
-  CollectionObj = object of RootObj
+  Collection = ref object of RootObj
     islive: bool
     childonly: bool
     root: Node
@@ -141,6 +140,10 @@ type
 
   HTMLCollection = ref object of Collection
 
+  HTMLFormControlsCollection = ref object of HTMLCollection
+
+  RadioNodeList = ref object of NodeList
+
   HTMLAllCollection = ref object of Collection
 
   DOMTokenList = ref object
@@ -207,6 +210,7 @@ type
     cachedSheets: seq[CSSStylesheet]
     cachedSheetsInvalid*: bool
     cachedChildren: HTMLCollection
+    cachedForms: HTMLCollection
     #TODO I hate this but I really don't want to put chadombuilder into dom too
     parser*: pointer
 
@@ -244,11 +248,11 @@ type
     prefix*: string
     localName*: CAtom
 
-    id*: CAtom
-    name*: CAtom
+    id* {.jsget.}: CAtom
+    name* {.jsget.}: CAtom
     classList* {.jsget.}: DOMTokenList
     attrs*: seq[AttrData] # sorted by int(qualifiedName)
-    attributesInternal: NamedNodeMap
+    internalAttributes: NamedNodeMap
     internalHover: bool
     invalid*: bool
     cachedStyle*: CSSStyleDeclaration
@@ -314,6 +318,7 @@ type
   HTMLFormElement* = ref object of HTMLElement
     constructingEntryList*: bool
     controls*: seq[FormAssociatedElement]
+    cachedElements: HTMLFormControlsCollection
     relList {.jsget.}: DOMTokenList
 
   HTMLTemplateElement* = ref object of HTMLElement
@@ -437,6 +442,8 @@ jsDestructor(HTMLAudioElement)
 jsDestructor(Node)
 jsDestructor(NodeList)
 jsDestructor(HTMLCollection)
+jsDestructor(HTMLFormControlsCollection)
+jsDestructor(RadioNodeList)
 jsDestructor(HTMLAllCollection)
 jsDestructor(Location)
 jsDestructor(Document)
@@ -466,6 +473,7 @@ proc delAttr(element: Element; i: int; keep = false)
 proc getImageId(window: Window): int
 proc parseColor(element: Element; s: string): ARGBColor
 proc reflectAttr(element: Element; name: CAtom; value: Option[string])
+proc setInvalid*(element: Element)
 
 # Forward declaration hacks
 # set in css/cascade
@@ -1066,7 +1074,7 @@ func localNameStr*(element: Element): string =
   return element.document.toStr(element.localName)
 
 func findAttr(element: Element; qualifiedName: CAtom): int =
-  for i, attr in element.attrs:
+  for i, attr in element.attrs.mpairs:
     if attr.qualifiedName == qualifiedName:
       return i
   return -1
@@ -1075,7 +1083,7 @@ func findAttr(element: Element; qualifiedName: StaticAtom): int =
   return element.findAttr(element.document.toAtom(qualifiedName))
 
 func findAttrNS(element: Element; namespace, qualifiedName: CAtom): int =
-  for i, attr in element.attrs:
+  for i, attr in element.attrs.mpairs:
     if attr.namespace == namespace and attr.qualifiedName == qualifiedName:
       return i
   return -1
@@ -1276,6 +1284,12 @@ proc finalize(collection: HTMLCollection) {.jsfin.} =
 proc finalize(collection: NodeList) {.jsfin.} =
   collection.finalize0()
 
+proc finalize(collection: HTMLFormControlsCollection) {.jsfin.} =
+  collection.finalize0()
+
+proc finalize(collection: RadioNodeList) {.jsfin.} =
+  collection.finalize0()
+
 proc finalize(collection: HTMLAllCollection) {.jsfin.} =
   collection.finalize0()
 
@@ -1287,7 +1301,7 @@ func ownerDocument(node: Node): Document {.jsfget.} =
 func hasChildNodes(node: Node): bool {.jsfunc.} =
   return node.childList.len > 0
 
-func len(collection: Collection): int =
+proc len(collection: Collection): int =
   collection.refreshCollection()
   return collection.snapshot.len
 
@@ -1361,6 +1375,19 @@ func childNodes(node: Node): NodeList {.jsfget.} =
     )
   return node.cachedChildNodes
 
+func isForm(node: Node): bool =
+  return node of HTMLFormElement
+
+func forms(document: Document): HTMLCollection {.jsfget.} =
+  if document.cachedForms == nil:
+    document.cachedForms = newCollection[HTMLCollection](
+      root = document,
+      match = isForm,
+      islive = true,
+      childonly = false
+    )
+  return document.cachedForms
+
 # DOMTokenList
 func length(tokenList: DOMTokenList): uint32 {.jsfget.} =
   return uint32(tokenList.toks.len)
@@ -1493,10 +1520,6 @@ func validateAttributeQName(name: string): Err[DOMException] =
   return errDOMException("Invalid character in attribute name",
     "InvalidCharacterError")
 
-func hasprop(map: var DOMStringMap; name: string): bool {.jshasprop.} =
-  let name = map.target.document.toAtom("data-" & name)
-  return map.target.attrb(name)
-
 proc delete(map: var DOMStringMap; name: string): bool {.jsfunc.} =
   let name = map.target.document.toAtom("data-" & name.camelToKebabCase())
   let i = map.target.findAttr(name)
@@ -1537,60 +1560,52 @@ func names(ctx: JSContext; map: var DOMStringMap): JSPropertyEnumList
   return list
 
 # NodeList
-func length(nodeList: NodeList): uint32 {.jsfget.} =
-  return uint32(nodeList.len)
-
-func hasprop(nodeList: NodeList; i: uint32): bool {.jshasprop.} =
-  return int(i) < nodeList.len
+func length(this: NodeList): uint32 {.jsfget.} =
+  return uint32(this.len)
 
-func item(nodeList: NodeList; i: int): Node {.jsfunc.} =
-  if i < nodeList.len:
-    return nodeList.snapshot[i]
+func item(this: NodeList; u: uint32): Node {.jsfunc.} =
+  let i = int(u)
+  if i < this.len:
+    return this.snapshot[i]
   return nil
 
-func getter(ctx: JSContext; nodeList: NodeList; atom: JSAtom): Node
-    {.jsgetprop.} =
+func getter(ctx: JSContext; this: NodeList; atom: JSAtom): Node {.jsgetprop.} =
   var u: uint32
   if ctx.fromJS(atom, u).isSome:
-    return nodeList.item(int(u))
+    return this.item(u)
   return nil
 
-func names(ctx: JSContext; nodeList: NodeList): JSPropertyEnumList
-    {.jspropnames.} =
-  let L = nodeList.length
+func names(ctx: JSContext; this: NodeList): JSPropertyEnumList {.jspropnames.} =
+  let L = this.length
   var list = newJSPropertyEnumList(ctx, L)
   for u in 0 ..< L:
     list.add(u)
   return list
 
 # HTMLCollection
-proc length(collection: HTMLCollection): uint32 {.jsfget.} =
-  return uint32(collection.len)
-
-func hasprop(collection: HTMLCollection; u: uint32): bool {.jshasprop.} =
-  return u < collection.length
+proc length(this: HTMLCollection): uint32 {.jsfget.} =
+  return uint32(this.len)
 
-func item(collection: HTMLCollection; u: uint32): Element {.jsfunc.} =
-  if u < collection.length:
-    return Element(collection.snapshot[int(u)])
+func item(this: HTMLCollection; u: uint32): Element {.jsfunc.} =
+  if u < this.length:
+    return Element(this.snapshot[int(u)])
   return nil
 
-func namedItem(collection: HTMLCollection; s: string): Element {.jsfunc.} =
-  let a = collection.root.document.toAtom(s)
-  for it in collection.snapshot:
+func namedItem(this: HTMLCollection; atom: CAtom): Element {.jsfunc.} =
+  for it in this.snapshot:
     let it = Element(it)
-    if it.id == a or it.namespace == Namespace.HTML and it.name == a:
+    if it.id == atom or it.namespace == Namespace.HTML and it.name == atom:
       return it
   return nil
 
-func getter(ctx: JSContext; collection: HTMLCollection; atom: JSAtom): Element
+proc getter(ctx: JSContext; this: HTMLCollection; atom: JSAtom): Element
     {.jsgetprop.} =
   var u: uint32
   if ctx.fromJS(atom, u).isSome:
-    return collection.item(u)
-  var s: string
+    return this.item(u)
+  var s: CAtom
   if ctx.fromJS(atom, s).isSome:
-    return collection.namedItem(s)
+    return this.namedItem(s)
   return nil
 
 func names(ctx: JSContext; collection: HTMLCollection): JSPropertyEnumList
@@ -1609,16 +1624,43 @@ func names(ctx: JSContext; collection: HTMLCollection): JSPropertyEnumList
     list.add(collection.root.document.toStr(id))
   return list
 
-# HTMLAllCollection
-proc length(this: HTMLAllCollection): uint32 {.jsfget.} =
-  return uint32(this.len)
+# HTMLFormControlsCollection
+proc namedItem(ctx: JSContext; this: HTMLFormControlsCollection; name: CAtom):
+    JSValue {.jsfunc.} =
+  let nodes = newCollection[RadioNodeList](
+    this.root,
+    func(node: Node): bool =
+      if not this.match(node):
+        return false
+      let element = Element(node)
+      return element.id == name or
+        element.namespace == Namespace.HTML and element.name == name,
+    islive = true,
+    childonly = false
+  )
+  if nodes.len == 0:
+    return JS_NULL
+  if nodes.len == 1:
+    return ctx.toJS(nodes.snapshot[0])
+  return ctx.toJS(nodes)
 
-func hasprop(ctx: JSContext; this: HTMLAllCollection; atom: JSAtom): bool
-    {.jshasprop.} =
+proc names(ctx: JSContext; this: HTMLFormControlsCollection): JSPropertyEnumList
+    {.jspropnames.} =
+  return ctx.names(HTMLCollection(this))
+
+proc getter(ctx: JSContext; this: HTMLFormControlsCollection; atom: JSAtom):
+    JSValue {.jsgetprop.} =
   var u: uint32
   if ctx.fromJS(atom, u).isSome:
-    return int(u) < this.len
-  return false
+    return ctx.toJS(this.item(u))
+  var s: CAtom
+  if ctx.fromJS(atom, s).isSome:
+    return ctx.namedItem(this, s)
+  return JS_NULL
+
+# HTMLAllCollection
+proc length(this: HTMLAllCollection): uint32 {.jsfget.} =
+  return uint32(this.len)
 
 func item(this: HTMLAllCollection; u: uint32): Element {.jsfunc.} =
   let i = int(u)
@@ -1847,17 +1889,17 @@ func hasAttributes(element: Element): bool {.jsfunc.} =
   return element.attrs.len > 0
 
 func attributes(element: Element): NamedNodeMap {.jsfget.} =
-  if element.attributesInternal != nil:
-    return element.attributesInternal
-  element.attributesInternal = NamedNodeMap(element: element)
-  for i, attr in element.attrs:
-    element.attributesInternal.attrlist.add(Attr(
+  if element.internalAttributes != nil:
+    return element.internalAttributes
+  element.internalAttributes = NamedNodeMap(element: element)
+  for i, attr in element.attrs.mpairs:
+    element.internalAttributes.attrlist.add(Attr(
       internalDocument: element.document,
       index: -1,
       dataIdx: i,
       ownerElement: element
     ))
-  return element.attributesInternal
+  return element.internalAttributes
 
 func findAttr(element: Element; qualifiedName: string): int =
   return element.findAttr(element.normalizeAttrQName(qualifiedName))
@@ -1919,13 +1961,6 @@ func getter(ctx: JSContext; map: NamedNodeMap; atom: JSAtom): Opt[Attr]
   ?ctx.fromJS(atom, s)
   return ok(map.getNamedItem(s))
 
-func hasprop(ctx: JSContext; map: NamedNodeMap; atom: JSAtom): cint
-    {.jshasprop.} =
-  var x = ctx.getter(map, atom)
-  if x.isNone:
-    return -1
-  return cint(x.get != nil)
-
 func names(ctx: JSContext; map: NamedNodeMap): JSPropertyEnumList
     {.jspropnames.} =
   let len = if map.element.namespace == Namespace.HTML:
@@ -2381,9 +2416,6 @@ func serializeFragment*(node: Node): string =
   return s
 
 # Element attribute reflection (getters)
-func jsId(element: Element): string {.jsfget: "id".} =
-  return element.document.toStr(element.id)
-
 func innerHTML(element: Element): string {.jsfget.} =
   #TODO xml
   return element.serializeFragment()
@@ -2707,10 +2739,29 @@ proc setRelList(link: HTMLLinkElement; s: string) {.jsfset: "relList".} =
 proc setRelList(form: HTMLFormElement; s: string) {.jsfset: "relList".} =
   form.attr(satRel, s)
 
+func elements(form: HTMLFormElement): HTMLFormControlsCollection {.jsfget.} =
+  if form.cachedElements == nil:
+    form.cachedElements = newCollection[HTMLFormControlsCollection](
+      root = form.rootNode,
+      match = func(node: Node): bool =
+        if node of FormAssociatedElement:
+          let element = FormAssociatedElement(node)
+          if element.tagType in ListedElements:
+            return element.form == form
+        return false,
+      islive = true,
+      childonly = false
+    )
+  return form.cachedElements
+
 # <input>
 func jsForm(this: HTMLInputElement): HTMLFormElement {.jsfget: "form".} =
   return this.form
 
+proc setValue(this: HTMLInputElement; value: string) {.jsfset: "value".} =
+  this.value = value
+  this.setInvalid()
+
 # <select>
 func jsForm(this: HTMLSelectElement): HTMLFormElement {.jsfget: "form".} =
   return this.form
@@ -2972,7 +3023,7 @@ proc setInvalid*(element: Element) =
     element.document.invalid = true
 
 proc delAttr(element: Element; i: int; keep = false) =
-  let map = element.attributesInternal
+  let map = element.internalAttributes
   let name = element.attrs[i].qualifiedName
   element.attrs.delete(i) # ordering matters
   if map != nil:
@@ -3552,6 +3603,7 @@ proc remove*(node: Node; suppressObservers: bool) =
     parent.childList[i].index = i
   parent.childList.setLen(parent.childList.len - 1)
   parent.invalidateCollections()
+  node.invalidateCollections()
   if parent of Element:
     Element(parent).setInvalid()
   node.parentNode = nil
@@ -3565,7 +3617,8 @@ proc remove*(node: Node; suppressObservers: bool) =
   #TODO not suppress observers => queue tree mutation record
 
 proc remove*(node: Node) {.jsfunc.} =
-  node.remove(suppressObservers = false)
+  if node.parentNode != nil:
+    node.remove(suppressObservers = false)
 
 proc adopt(document: Document; node: Node) =
   let oldDocument = node.document
@@ -4396,7 +4449,7 @@ func isEqualNode(node, other: Node): bool {.jsfunc.} =
         node.localName != other.localName or
         node.attrs.len != other.attrs.len:
       return false
-    for i, attr in node.attrs:
+    for i, attr in node.attrs.mpairs:
       if not attr.equals(other.attrs[i]):
         return false
   elif node of Attr:
@@ -4743,9 +4796,11 @@ proc addDOMModule*(ctx: JSContext) =
   let eventTargetCID = ctx.getClass("EventTarget")
   let nodeCID = ctx.registerType(Node, parent = eventTargetCID)
   ctx.defineConsts(nodeCID, NodeType, uint16)
-  ctx.registerType(NodeList)
-  ctx.registerType(HTMLCollection)
+  let nodeListCID = ctx.registerType(NodeList)
+  let htmlCollectionCID = ctx.registerType(HTMLCollection)
   ctx.registerType(HTMLAllCollection, ishtmldda = true)
+  ctx.registerType(HTMLFormControlsCollection, parent = htmlCollectionCID)
+  ctx.registerType(RadioNodeList, parent = nodeListCID)
   ctx.registerType(Location)
   ctx.registerType(Document, parent = nodeCID)
   ctx.registerType(DOMImplementation)