import html/dom import html/tags import js/javascript import types/blob import types/formdata import utils/twtstr proc constructEntryList*(form: HTMLFormElement, submitter: Element = nil, encoding: string = ""): Option[seq[FormDataEntry]] proc newFormData*(form: HTMLFormElement = nil, submitter: HTMLElement = nil): FormData {.jserr, jsctor.} = let this = FormData() if form != nil: if submitter != nil: if not submitter.isSubmitButton(): JS_ERR JS_TypeError, "Submitter must be a submit button" if FormAssociatedElement(submitter).form != form: #TODO should be DOMException JS_ERR JS_TypeError, "InvalidStateError" this.entries = constructEntryList(form, submitter).get(@[]) return this #TODO as jsfunc proc append*(this: FormData, name: string, svalue: string, filename = "") = this.entries.add(FormDataEntry( name: name, isstr: true, svalue: svalue, filename: filename )) proc append*(this: FormData, name: string, value: Blob, filename = "blob") = this.entries.add(FormDataEntry( name: name, isstr: false, value: value, filename: filename )) #TODO hack proc append(ctx: JSContext, this: FormData, name: string, value: JSValue, filename = none(string)) {.jsfunc.} = let blob = fromJS[Blob](ctx, value) if blob.isSome: let filename = if filename.isSome: filename.get elif blob.get of WebFile: WebFile(blob.get).name else: "blob" this.append(name, blob.get, filename) else: let s = fromJS[string](ctx, value) # toString should never fail (?) this.append(name, s.get, filename.get("")) proc delete(this: FormData, name: string) {.jsfunc.} = for i in countdown(this.entries.high, 0): if this.entries[i].name == name: this.entries.delete(i) proc get(ctx: JSContext, this: FormData, name: string): JSValue {.jsfunc.} = for entry in this.entries: if entry.name == name: if entry.isstr: return toJS(ctx, entry.svalue) else: return toJS(ctx, entry.value) return JS_NULL proc getAll(this: FormData, name: string): seq[Blob] {.jsfunc.} = for entry in this.entries: if entry.name == name: result.add(entry.value) # may be null proc add(list: var seq[FormDataEntry], entry: tuple[name, value: string]) = list.add(FormDataEntry( name: entry.name, isstr: true, svalue: entry.value )) func toNameValuePairs*(list: seq[FormDataEntry]): seq[tuple[name, value: string]] = for entry in list: if entry.isstr: result.add((entry.name, entry.svalue)) else: result.add((entry.name, entry.name)) # https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#constructing-the-form-data-set proc constructEntryList*(form: HTMLFormElement, submitter: Element = nil, encoding: string = ""): Option[seq[FormDataEntry]] = if form.constructingentrylist: return form.constructingentrylist = true var entrylist: seq[FormDataEntry] for field in form.controls: if field.findAncestor({TAG_DATALIST}) != nil or field.attrb("disabled") or field.isButton() and Element(field) != submitter: continue if field.tagType == TAG_INPUT: let field = HTMLInputElement(field) if field.inputType == INPUT_IMAGE: let name = if field.attr("name") != "": field.attr("name") & '.' else: "" entrylist.add((name & 'x', $field.xcoord)) entrylist.add((name & 'y', $field.ycoord)) continue #TODO custom elements let name = field.attr("name") if name == "": continue if field.tagType == TAG_SELECT: let field = HTMLSelectElement(field) for option in field.options: if option.selected or option.disabled: entrylist.add((name, option.value)) elif field.tagType == TAG_INPUT and HTMLInputElement(field).inputType in {INPUT_CHECKBOX, INPUT_RADIO}: let value = if field.attr("value") != "": field.attr("value") else: "on" entrylist.add((name, value)) elif field.tagType == TAG_INPUT and HTMLInputElement(field).inputType == INPUT_FILE: #TODO file discard elif field.tagType == TAG_INPUT and HTMLInputElement(field).inputType == INPUT_HIDDEN and name.equalsIgnoreCase("_charset_"): let charset = if encoding != "": encoding else: "UTF-8" entrylist.add((name, charset)) else: case field.tagType of TAG_INPUT: entrylist.add((name, HTMLInputElement(field).value)) of TAG_BUTTON: entrylist.add((name, HTMLButtonElement(field).value)) of TAG_TEXTAREA: entrylist.add((name, HTMLTextAreaElement(field).value)) else: assert false, "Tag type " & $field.tagType & " not accounted for in constructEntryList" if field.tagType == TAG_TEXTAREA or field.tagType == TAG_INPUT and HTMLInputElement(field).inputType in {INPUT_TEXT, INPUT_SEARCH}: if field.attr("dirname") != "": let dirname = field.attr("dirname") let dir = "ltr" #TODO bidi entrylist.add((dirname, dir)) form.constructingentrylist = false return some(entrylist) proc addFormDataModule*(ctx: JSContext) = ctx.registerType(FormData)