import { d3Event, HTMLWidget, select as d3Select, SVGWidget, Widget, WidgetArray } from "@hpcc-js/common"; import { Button } from "./Button.ts"; import "../src/Form.css"; export class Form extends HTMLWidget { tbody; tfoot; btntd; _controls; _maxCols; constructor() { super(); this._tag = "form"; } data(): any; data(_: any): this; data(_?: any): any | this { if (!arguments.length) { const retVal = []; this.inputsForEach(function (input) { retVal.push(input.value()); }); return retVal; } else { this.inputsForEach(function (input, idx) { if (_ && _.length > idx) { input.value(_[idx]).render(); } }); } return this; } inputsForEach(callback, scope?) { let idx = 0; this.inputs().forEach(function (inp) { const inpArray = inp instanceof WidgetArray ? inp.content() : [inp]; inpArray.forEach(function (inp2) { if (scope) { callback.call(scope, inp2, idx++); } else { callback(inp2, idx++); } }); }); } inputsMap(): { [name: string]: Widget } { const retVal: { [name: string]: Widget } = {}; this.inputs().forEach(function (inp) { retVal[inp.name()] = inp; }); return retVal; } calcMaxColumns() { let retVal = 0; this.inputs().forEach(function (inputWidget) { const inputWidgetArray = inputWidget instanceof WidgetArray ? inputWidget.content() : [inputWidget]; if (inputWidgetArray.length > retVal) { retVal = inputWidgetArray.length; } }); return retVal; } values(): any; values(_: any): this; values(_?: any): any | this { if (!arguments.length) { const dataArr = {}; this.inputsForEach(function (inp) { const type = inp.type ? inp.type() : "text"; const value = inp.value(); if (value || !this.omitBlank()) { switch (type) { case "checkbox": dataArr[inp.name()] = inp.value_exists() ? !!inp.value() : undefined; break; case "number": const v = inp.value(); dataArr[inp.name()] = v === "" ? undefined : +v; break; case "text": default: dataArr[inp.name()] = inp.value_exists() ? inp.value() : undefined; break; } } }, this); return dataArr; } else { this.inputsForEach(function (inp) { if (_[inp.name()]) { inp.value(_[inp.name()]); } else if (this.omitBlank()) { inp.value(""); } }, this); } return this; } submit() { let isValid = true; if (this.validate()) { isValid = this.checkValidation(); } if (!this.allowEmptyRequest() && !this.inputs().some(function (w) { if (w._class.indexOf("WidgetArray") !== -1) { return w.content().some(function (wa) { return wa.hasValue(); }); } return w.hasValue(); })) { return; } this.click(isValid ? this.values() : null, null, isValid); } clear() { this.inputsForEach(function (inp) { switch (inp.classID()) { case "form_Slider": if (inp.allowRange()) { inp.value([inp.low(), inp.low()]).render(); } else { inp.value(inp.low()).render(); } break; case "form_CheckBox": inp.value(false).render(); break; case "form_Button": /* skip */ break; default: inp.value(undefined).render(); break; } }); } checkValidation() { let ret = true; const msgArr = []; this.inputsForEach(function (inp) { if (!inp.isValid()) { msgArr.push("'" + inp.label() + "'" + " value is invalid."); } }); if (msgArr.length > 0) { alert(msgArr.join("\n")); ret = false; } return ret; } enter(domNode, element) { super.enter(domNode, element); element.on("submit", function () { d3Event().preventDefault(); }); this._placeholderElement.style("overflow", "auto"); const table = element .append("table") ; this.tbody = table.append("tbody"); this.tfoot = table.append("tfoot"); this.btntd = this.tfoot.append("tr").append("td") .attr("colspan", 2) ; const context = this; this._controls = [ new Button() .classed({ default: true }) .value("Submit") .on("click", function () { context.submit(); }, true), new Button() .value("Clear") .on("click", function () { context.clear(); }, true) ]; const rightJust = context.btntd .append("div") .style("float", "right") ; this._controls.forEach(function (w) { const leftJust = rightJust .append("span") .style("float", "left") ; w.target(leftJust.node()).render(); }); } update(domNode, element) { super.update(domNode, element); this._maxCols = this.calcMaxColumns(); const context = this; const rows = this.tbody.selectAll("tr").data(this.inputs()); rows.enter().append("tr") .each(function (inputWidget, i) { const element2 = d3Select(this); const inputWidgetArray = inputWidget instanceof WidgetArray ? inputWidget.content() : [inputWidget]; inputWidgetArray.forEach(function (inputWidget2, idx) { element2.append("td") .attr("class", "prompt") ; const input = element2.append("td") .attr("class", "input") ; if (idx === inputWidgetArray.length - 1 && inputWidgetArray.length < context._maxCols) { input.attr("colspan", (context._maxCols - inputWidgetArray.length + 1) * 2); } inputWidget2.target(input.node()).render(); if (inputWidget2 instanceof SVGWidget) { const bbox = inputWidget2.element().node().getBBox(); input.style("height", bbox.height + "px"); inputWidget2.resize().render(); } if (inputWidget2._inputElement instanceof Array) { inputWidget2._inputElement.forEach(function (e) { e.on("keyup.form", function (w) { setTimeout(function () { context._controls[0].disable(!context.allowEmptyRequest() && !context.inputs().some(function (w2) { if (w2._class.indexOf("WidgetArray") !== -1) { return w2.content().some(function (wa) { return wa.hasValue(); }); } return w2.hasValue(); })); }, 100); }); }); } }); }) .merge(rows) .each(function (inputWidget, i) { const element2 = d3Select(this); const inputWidgetArray = inputWidget instanceof WidgetArray ? inputWidget.content() : [inputWidget]; inputWidgetArray.forEach(function (inputWidget2, idx) { element2.select("td.prompt") .text(inputWidget2.label() + ":") ; }); }) ; rows.each(function (inputWidget, i) { if (i === 0 && inputWidget.setFocus) { inputWidget.setFocus(); } }); rows.exit() .each(function (inputWidget, i) { const inputWidgetArray = inputWidget instanceof WidgetArray ? inputWidget.content() : [inputWidget]; inputWidgetArray.forEach(function (inputWidget2, idx) { inputWidget2.target(null); }); }) .remove() ; this.tfoot .style("display", this.showSubmit() ? "table-footer-group" : "none") ; this.btntd .attr("colspan", this._maxCols * 2) ; // Disable Submit unless there is data if (!this.allowEmptyRequest()) { setTimeout(function () { context._controls[0].disable(!context.allowEmptyRequest() && !context.inputs().some(function (w) { if (w._class.indexOf("WidgetArray") !== -1) { return w.content().some(function (wa) { return wa.hasValue(); }); } return w.hasValue(); })); }, 100); } } exit(domNode, element) { this.inputsForEach(input => input.target(null)); super.exit(domNode, element); } click(row, col, sel) { } } Form.prototype._class += " form_Form"; export interface Form { validate(): boolean; validate(_: boolean): this; validate_exists(): boolean; inputs(): any[]; inputs(_: any[]): this; inputs_exists(): boolean; inputs_reset(): void; showSubmit(): boolean; showSubmit(_: boolean): this; showSubmit_exists(): boolean; omitBlank(): boolean; omitBlank(_: boolean): this; omitBlank_exists(): boolean; allowEmptyRequest(): boolean; allowEmptyRequest(_: boolean): this; allowEmptyRequest_exists(): boolean; } Form.prototype.publish("validate", true, "boolean", "Enable/Disable input validation"); Form.prototype.publish("inputs", [], "widgetArray", "Array of input widgets", null, { render: false }); Form.prototype.publish("showSubmit", true, "boolean", "Show Submit/Cancel Controls"); Form.prototype.publish("omitBlank", false, "boolean", "Drop Blank Fields From Submit"); Form.prototype.publish("allowEmptyRequest", false, "boolean", "Allow Blank Form to be Submitted");