diff options
author | bptato <nincsnevem662@gmail.com> | 2022-07-17 22:31:04 +0200 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2022-07-17 22:31:04 +0200 |
commit | 24fc8e940a935f0579cf7bc03bf01e27e5853b80 (patch) | |
tree | 8590acda7c4bd8f856e2869ed35a804116926f0d /src/html | |
parent | 7cdb8c1b679431b1be52c9fbb19b67445a0bb588 (diff) | |
download | chawan-24fc8e940a935f0579cf7bc03bf01e27e5853b80.tar.gz |
Implement select element display
You can't actually use them yet. But at least they don't flood the screen with options now.
Diffstat (limited to 'src/html')
-rw-r--r-- | src/html/dom.nim | 227 | ||||
-rw-r--r-- | src/html/htmlparser.nim | 10 |
2 files changed, 183 insertions, 54 deletions
diff --git a/src/html/dom.nim b/src/html/dom.nim index 5312dc8b..6788bd35 100644 --- a/src/html/dom.nim +++ b/src/html/dom.nim @@ -1,7 +1,7 @@ -import tables import options import streams import strutils +import tables import css/sheet import html/tags @@ -89,7 +89,11 @@ type HTMLElement* = ref object of Element - HTMLInputElement* = ref object of HTMLElement + FormAssociatedElement* = ref object of HTMLElement + form*: HTMLFormElement + parserInserted*: bool + + HTMLInputElement* = ref object of FormAssociatedElement inputType*: InputType autofocus*: bool required*: bool @@ -99,19 +103,16 @@ type xcoord*: int ycoord*: int file*: Option[Url] - form*: HTMLFormElement HTMLAnchorElement* = ref object of HTMLElement - HTMLSelectElement* = ref object of HTMLElement - name*: string - value*: string - valueSet*: bool + HTMLSelectElement* = ref object of FormAssociatedElement + size*: int HTMLSpanElement* = ref object of HTMLElement HTMLOptionElement* = ref object of HTMLElement - value*: string + selected*: bool HTMLHeadingElement* = ref object of HTMLElement rank*: uint16 @@ -142,7 +143,7 @@ type target*: string novalidate*: bool constructingentrylist*: bool - inputs*: seq[HTMLInputElement] + controls*: seq[FormAssociatedElement] HTMLTemplateElement* = ref object of HTMLElement content*: DocumentFragment @@ -190,6 +191,11 @@ iterator elements*(node: Node, tag: TagType): Element {.inline.} = if child.nodeType == ELEMENT_NODE: stack.add(Element(child)) +iterator inputs(form: HTMLFormElement): HTMLInputElement {.inline.} = + for control in form.controls: + if control.tagType == TAG_INPUT: + yield HTMLInputElement(control) + iterator radiogroup(form: HTMLFormElement): HTMLInputElement {.inline.} = for input in form.inputs: if input.inputType == INPUT_RADIO: @@ -228,6 +234,22 @@ iterator branch*(node: Node): Node {.inline.} = yield node node = node.parentNode +# Returns the node's descendants +iterator descendants*(node: Node): Node {.inline.} = + var stack: seq[Node] + stack.add(node.childNodes) + while stack.len > 0: + let node = stack.pop() + yield node + for i in countdown(node.childNodes.high, 0): + stack.add(node.childNodes[i]) + +iterator options*(select: HTMLSelectElement): HTMLOptionElement {.inline.} = + for child in select.children: + if child.tagType == TAG_OPTION: + yield HTMLOptionElement(child) + #TODO optgroups + func qualifiedName*(element: Element): string = if element.namespacePrefix.issome: element.namespacePrefix.get & ':' & element.localName else: element.localName @@ -280,6 +302,9 @@ func rootNode*(node: Node): Node = if node.root == nil: return node return node.root +func connected*(node: Node): bool = + return node.rootNode.nodeType == DOCUMENT_NODE #TODO shadow root + func inSameTree*(a, b: Node): bool = a.rootNode == b.rootNode @@ -520,6 +545,7 @@ func newHTMLElement*(document: Document, tagType: TagType, namespace = Namespace result = new(HTMLAnchorElement) of TAG_SELECT: result = new(HTMLSelectElement) + HTMLSelectElement(result).size = 1 of TAG_OPTION: result = new(HTMLOptionElement) of TAG_H1, TAG_H2, TAG_H3, TAG_H4, TAG_H5, TAG_H6: @@ -652,6 +678,22 @@ func title*(document: Document): string = return title.childTextContent.stripAndCollapse() return "" +func disabled*(option: HTMLOptionElement): bool = + #TODO optgroup + return option.attrb("disabled") + +func text*(option: HTMLOptionElement): string = + for child in option.descendants: + if child.nodeType == TEXT_NODE: + let child = Text(child) + if child.parentElement.tagType != TAG_SCRIPT: #TODO svg + result &= child.data.stripAndCollapse() + +func value*(option: HTMLOptionElement): string = + if option.attrb("value"): + return option.attr("value") + return option.childTextContent.stripAndCollapse() + # WARNING the ordering of the arguments in the standard is whack so this doesn't match that func preInsertionValidity*(parent, node, before: Node): bool = if parent.nodeType notin {DOCUMENT_NODE, DOCUMENT_FRAGMENT_NODE, ELEMENT_NODE}: @@ -729,6 +771,97 @@ proc applyChildInsert(parent, child: Node, index: int) = child.nextSibling = parent.childNodes[index + 1] child.nextSibling.previousSibling = child +proc resetElement*(element: Element) = + case element.tagType + of TAG_INPUT: + let input = HTMLInputELement(element) + case input.inputType + of INPUT_SEARCH, INPUT_TEXT, INPUT_PASSWORD: + input.value = input.attr("value") + of INPUT_CHECKBOX, INPUT_RADIO: + input.checked = input.attrb("checked") + of INPUT_FILE: + input.file = none(Url) + else: discard + input.rendered = false + of TAG_SELECT: + let select = HTMLSelectElement(element) + if not select.attrb("multiple"): + if select.size == 1: + var i = 0 + var firstOption: HTMLOptionElement + for option in select.options: + if firstOption == nil: + firstOption = option + if option.selected: + inc i + if i == 0 and firstOption != nil: + firstOption.selected = true + elif i > 2: + # Set the selectedness of all but the last selected option element to + # false. + var j = 0 + for option in select.options: + if j == i: break + if option.selected: + option.selected = false + inc j + else: discard + +proc setForm*(element: FormAssociatedElement, form: HTMLFormElement) = + case element.tagType + of TAG_INPUT: + let input = HTMLInputElement(element) + input.form = form + form.controls.add(input) + of TAG_SELECT: + let select = HTMLSelectElement(element) + select.form = form + form.controls.add(select) + of TAG_BUTTON, TAG_FIELDSET, TAG_OBJECT, TAG_OUTPUT, TAG_TEXTAREA, TAG_IMG: + discard #TODO + else: assert false + +proc resetFormOwner(element: FormAssociatedElement) = + element.parserInserted = false + if element.form != nil and + element.tagType notin ListedElements or not element.attrb("form") and + element.findAncestor({TAG_FORM}) == element.form: + return + element.form = nil + if element.tagType in ListedElements and element.attrb("form") and element.connected: + let form = element.attr("form") + for desc in element.rootNode.descendants: + if desc.nodeType == ELEMENT_NODE: + let desc = Element(desc) + if desc.id == form: + if desc.tagType == TAG_FORM: + element.setForm(HTMLFormElement(desc)) + +proc insertionSteps(insertedNode: Node) = + if insertedNode.nodeType == ELEMENT_NODE: + let element = Element(insertedNode) + let tagType = element.tagType + case tagType + of TAG_OPTION: + if element.parentElement != nil: + let parent = element.parentElement + var select: HTMLSelectElement + if parent.tagType == TAG_SELECT: + select = HTMLSelectElement(parent) + elif parent.tagType == TAG_OPTGROUP and parent.parentElement != nil and parent.parentElement.tagType == TAG_SELECT: + select = HTMLSelectElement(parent.parentElement) + if select != nil: + select.resetElement() + else: discard + if tagType in FormAssociatedElements: + if tagType notin {TAG_SELECT, TAG_INPUT}: + return #TODO TODO TODO implement others too + let element = FormAssociatedElement(element) + if element.parserInserted: + return + element.resetFormOwner() + # WARNING ditto proc insert*(parent, node, before: Node) = let nodes = if node.nodeType == DOCUMENT_FRAGMENT_NODE: node.childNodes @@ -754,7 +887,9 @@ proc insert*(parent, node, before: Node) = let index = parent.childNodes.find(before) parent.childNodes.insert(node, index) parent.applyChildInsert(node, index) - #TODO shadow root + if node.nodeType == ELEMENT_NODE: + #TODO shadow root + insertionSteps(node) # WARNING ditto proc preInsert*(parent, node, before: Node) = @@ -766,25 +901,10 @@ proc preInsert*(parent, node, before: Node) = proc append*(parent, node: Node) = parent.preInsert(node, nil) -proc reset*(element: Element) = - case element.tagType - of TAG_INPUT: - let input = HTMLInputELement(element) - case input.inputType - of INPUT_SEARCH, INPUT_TEXT, INPUT_PASSWORD: - input.value = input.attr("value") - of INPUT_CHECKBOX, INPUT_RADIO: - input.checked = input.attrb("checked") - of INPUT_FILE: - input.file = none(Url) - else: discard - input.rendered = false - else: discard - proc reset*(form: HTMLFormElement) = - for input in form.inputs: - input.reset() - input.rendered = false + for control in form.controls: + control.resetElement() + control.rendered = false proc appendAttribute*(element: Element, k, v: string) = case k @@ -794,31 +914,32 @@ proc appendAttribute*(element: Element, k, v: string) = for class in classes: if class != "" and class notin element.classList: element.classList.add(class) - if element.tagType == TAG_INPUT: - case k - of "value": HTMLInputElement(element).value = v - of "type": HTMLInputElement(element).inputType = inputType(v) - of "size": - var i = 20 - var fail = v.len == 0 - for c in v: - if not c.isDigit: - fail = true - break - if not fail: - i = parseInt(v) - if i <= 0: - i = 20 - HTMLInputElement(element).size = i - of "checked": HTMLInputElement(element).checked = true - element.attributes[k] = v - -proc setForm*(element: Element, form: HTMLFormElement) = case element.tagType of TAG_INPUT: let input = HTMLInputElement(element) - input.form = form - form.inputs.add(input) - of TAG_BUTTON, TAG_FIELDSET, TAG_OBJECT, TAG_OUTPUT, TAG_SELECT, TAG_TEXTAREA, TAG_IMG: - discard #TODO - else: assert false + case k + of "value": input.value = v + of "type": input.inputType = inputType(v) + of "size": + if v.isValidNonZeroInt(): + input.size = parseInt(v) + else: + input.size = 20 + of "checked": input.checked = true + of TAG_OPTION: + let option = HTMLOptionElement(element) + if k == "selected": + option.selected = true + of TAG_SELECT: + let select = HTMLSelectElement(element) + case k + of "multiple": + if not select.attributes["size"].isValidNonZeroInt(): + select.size = 4 + of "size": + if v.isValidNonZeroInt(): + select.size = parseInt(v) + elif "multiple" in select.attributes: + select.size = 4 + else: discard + element.attributes[k] = v diff --git a/src/html/htmlparser.nim b/src/html/htmlparser.nim index cec952f6..835e6de0 100644 --- a/src/html/htmlparser.nim +++ b/src/html/htmlparser.nim @@ -196,13 +196,15 @@ func createElement(parser: HTML5Parser, token: Token, namespace: Namespace, inte for k, v in token.attrs: element.appendAttribute(k, v) if element.isResettable(): - element.reset() + element.resetElement() if element.tagType in FormAssociatedElements and parser.form != nil and not parser.openElements.hasElement(TAG_TEMPLATE) and (element.tagType notin ListedElements or not element.attrb("form")) and intendedParent.inSameTree(parser.form): + let element = FormAssociatedElement(element) element.setForm(parser.form) + element.parserInserted = true return element proc insert(location: AdjustedInsertionLocation, node: Node) = @@ -1791,6 +1793,12 @@ proc processInHTMLContent(parser: var HTML5Parser, token: Token, insertionMode = while parser.openElements.pop().tagType != TAG_SELECT: discard parser.resetInsertionMode() ) + "<select>" => (block: + parse_error + if parser.openElements.hasElementInSelectScope(TAG_SELECT): + while parser.openElements.pop().tagType != TAG_SELECT: discard + parser.resetInsertionMode() + ) ("<input>", "<keygen>", "<textarea>") => (block: parse_error if not parser.openElements.hasElementInSelectScope(TAG_SELECT): |