diff --git a/src/core/js/form_elements.js b/src/core/js/form_elements.js index 62b1b01c17ac1a98e9109b8570be627fdd4930c8..248523ba61c3b97ebf14413675abb88edac13ce9 100644 --- a/src/core/js/form_elements.js +++ b/src/core/js/form_elements.js @@ -326,7 +326,7 @@ var form_elements = new function () { * @returns {HTMLElement} SELECT element with entity options. */ this.make_reference_select = async function (entities, make_desc, - make_value, multiple) { + make_value, name, multiple) { caosdb_utils.assert_array(entities, "param `entities`", false); if (typeof make_desc !== "undefined") { caosdb_utils.assert_type(make_desc, "function", @@ -336,7 +336,7 @@ var form_elements = new function () { caosdb_utils.assert_type(make_value, "function", "param `make_value`"); } - const ret = $(form_elements._make_select(multiple)); + const ret = $(form_elements._make_select(multiple, name)); for (let entity of entities) { this.logger.trace("add option", entity); let entity_id = getEntityID(entity); @@ -349,8 +349,12 @@ var form_elements = new function () { return ret[0]; } - this._make_select = function (multiple) { - const ret = $('<select class="selectpicker form-control" title="Nothing selected"/>'); + this._make_select = function (multiple, name) { + const ret = $(`<select class="selectpicker form-control" name="${name}" title="Nothing selected"/>`); + if (typeof name !== "undefined") { + caosdb_utils.assert_string(name, "param `name`"); + ret.attr("name", name); + } if (multiple) { ret.attr("multiple", ""); } else { @@ -405,7 +409,9 @@ var form_elements = new function () { } this._run_script = async function (script, form) { - const json_str = JSON.stringify(form_elements.form_to_object(form[0])); + const form_objects = form_elements.form_to_object(form[0]); + const json_str = JSON.stringify(form_objects[0]); + const params = { "-p0": { "filename": "form.json", @@ -414,6 +420,15 @@ var form_elements = new function () { }) } }; + + const files = form_objects[1]; + for (let i = 0; i < files.length; i++) { + params[`file_${i}`] = { + "filename": `${files[i]["fieldname"]}_${files[i]["filename"]}`, + "blob": files[i]["blob"] + }; + } + const result = await connection.runScript(script, params); this.logger.debug("server-side script returned", result); return this.parse_script_result(result); @@ -464,15 +479,15 @@ var form_elements = new function () { this.make_reference_drop_down = function (config) { let ret = $(this._make_field_wrapper(config.name)); let label = this._make_input_label_str(config); - let loading = $('<div class="caosdb-f-field-not-ready">loading...</div>'); + let loading = $(createWaitingNotification("loading...")) + .addClass("caosdb-f-field-not-ready"); let input_col = $('<div class="col-sm-9"/>'); input_col.append(loading); this._query(config.query).then(async function (entities) { let select = $(await form_elements.make_reference_select( - entities, config.make_desc, config.make_value, config.multiple, + entities, config.make_desc, config.make_value, config.name, config.multiple, config.value)); - select.attr("name", config.name); loading.remove(); input_col.append(select); form_elements.init_select_picker(ret[0], config.value); @@ -590,8 +605,8 @@ var form_elements = new function () { this.logger.trace("entity form_to_json", form); caosdb_utils.assert_html_element(form, "parameter `form`"); - const _to_json = (element, data) => { - this.logger.trace("enter element_to_json", element, data); + const _to_json = (element, data, files) => { + this.logger.trace("enter element_to_json", element, data, files); for (const child of element.children) { // ignore disabled fields and subforms @@ -603,7 +618,7 @@ var form_elements = new function () { if (is_subform) { const subform = $(child).data("subform-name"); // recursive - var subform_obj = _to_json(child, {}); + var subform_obj = _to_json(child, {}, files)[0]; if (typeof data[subform] === "undefined") { data[subform] = subform_obj; } else if (Array.isArray(data[subform])) { @@ -614,7 +629,27 @@ var form_elements = new function () { } else if (name && name !== "") { // input elements const not_checkbox = !$(child).is(":checkbox"); - if (not_checkbox || $(child).is(":checked")) { + const is_file = $(child).is("input:file"); + if (is_file) { + var fileList = child.files; + if(fileList.length > 0) { + for (let i = 0; i < fileList.length; i++) { + value = name + "_" + fileList[i].name; + if (typeof data[name] === "undefined") { + data[name] = value + } else if (Array.isArray(data[name])) { + data[name].push(value); + } else { + data[name] = [data[name], value] + } + files.push({ + "fieldname": name, + "filename": fileList[i].name, + "blob": fileList[i] + }); + } + } + } else if (not_checkbox || $(child).is(":checked")) { // checked or not a checkbox var value = $(child).val(); if (typeof data[name] === "undefined") { @@ -629,15 +664,15 @@ var form_elements = new function () { } } else if (child.children.length > 0) { // recursive - _to_json(child, data); + _to_json(child, data, files); } } - this.logger.trace("leave element_to_json", element, data); - return data; + this.logger.trace("leave element_to_json", element, data, files); + return [ data, files ]; }; - const ret = _to_json(form, {}); + const ret = _to_json(form, {}, []); this.logger.trace("leave form_to_json", ret); return ret; } @@ -657,11 +692,9 @@ var form_elements = new function () { } /** - * TODO make syncronous - * * @return {HTMLElement} */ - this.make_form_field = async function (config) { + this.make_form_field = function (config) { caosdb_utils.assert_type(config, "object", "param `config`"); caosdb_utils.assert_string(config.type, "`config.type` of param `config`"); @@ -669,6 +702,8 @@ var form_elements = new function () { const type = config.type; if (type === "date") { field = this.make_date_input(config); + } else if (type === "file") { + field = this.make_file_input(config); } else if (type === "checkbox") { field = this.make_checkbox_input(config); } else if (type === "text") { @@ -678,14 +713,13 @@ var form_elements = new function () { } else if (type === "integer") { field = this.make_integer_input(config); } else if (type === "range") { - field = await this.make_range_input(config); + field = this.make_range_input(config); } else if (type === "reference_drop_down") { field = this.make_reference_drop_down(config); } else if (type === "select") { field = this.make_select_input(config); } else if (type === "subform") { - // TODO handle cache and required for subforms - return await this.make_subform(config); + return this.make_subform(config); } else { throw new TypeError("undefined field type `" + type + "`"); } @@ -745,7 +779,7 @@ var form_elements = new function () { var header = this.make_heading(config); wrapper.append(header); - var loading = $('<div>loading...</div>'); + var loading = $(createWaitingNotification("loading...")); var logger = this.logger; var cancel = (e) => { logger.trace("cancel form", e); @@ -812,9 +846,9 @@ var form_elements = new function () { } /** - * TODO make syncronous + * @return {HTMLElement} */ - this.make_subform = async function (config) { + this.make_subform = function (config) { this.logger.trace("enter make_subform"); caosdb_utils.assert_type(config, "object", "param `config`"); caosdb_utils.assert_string(config.name, "`config.name` of param `config`"); @@ -825,7 +859,7 @@ var form_elements = new function () { for (let field of config.fields) { this.logger.trace("add subform field", field); - let elem = await this.make_form_field(field); + let elem = this.make_form_field(field); form.append(elem); } @@ -875,6 +909,7 @@ var form_elements = new function () { this.disable_fields = function (fields) { $(fields).toggleClass("caosdb-f-field-disabled", true).hide(); + $(fields).find(":input").prop("required", false); for (const field of $(fields)) { field.dispatchEvent(this.field_disabled_event); } @@ -882,6 +917,7 @@ var form_elements = new function () { this.enable_fields = function (fields) { $(fields).toggleClass("caosdb-f-field-disabled", false).show(); + $(fields).filter(".caosdb-f-form-field-required").find("input.caosdb-f-property-single-raw-value, select.selectpicker").prop("required", true); for (const field of $(fields)) { field.dispatchEvent(this.field_enabled_event); } @@ -934,9 +970,9 @@ var form_elements = new function () { * The `config.fields` array may contain `form_elements.field_config` * objects or HTMLElements. * - * TODO + * @return {HTMLElement} */ - this.make_generic_form = async function (config) { + this.make_generic_form = function (config) { this.logger.trace("enter make_generic_form"); caosdb_utils.assert_type(config, "object", "param `config`"); @@ -956,7 +992,7 @@ var form_elements = new function () { if (field instanceof HTMLElement) { form.append(field); } else { - let elem = await this.make_form_field(field); + let elem = this.make_form_field(field); form.append(elem); } } @@ -1135,9 +1171,9 @@ var form_elements = new function () { } /** - * TODO make syncronous + * @return {HTMLElement} */ - this.make_range_input = async function (config) { + this.make_range_input = function (config) { // TODO // 1. wrapp both inputs to separate it from the label into a container @@ -1154,8 +1190,8 @@ var form_elements = new function () { type: "double" }, config.to); - const from_input = await this.make_form_field(from_config); - const to_input = await this.make_form_field(to_config); + const from_input = this.make_form_field(from_config); + const to_input = this.make_form_field(to_config); const ret = $(this._make_field_wrapper(config.name)); if (config.label) { @@ -1191,10 +1227,16 @@ var form_elements = new function () { .css({"padding": "0"})[0]; } + /** + * @return {HTMLElement} + */ this.make_date_input = function (config) { return this._make_input(config); } + /** + * @return {HTMLElement} + */ this.make_text_input = function (config) { return this._make_input(config); } @@ -1229,9 +1271,32 @@ var form_elements = new function () { this.make_integer_input = function (config) { var ret = $(this.make_double_input(config)); ret.find("input").attr("step", "1"); + if (config.min) { + ret.find("input").attr("min", config.min); + } + if (config.max) { + ret.find("input").attr("max", config.max); + } return ret[0]; } + /** + * @return {HTMLElement} + */ + this.make_file_input = function (config) { + const ret = this._make_input(config); + $(ret) + .find("input:file") + .prop("multiple", !!config.multiple) + .css({"display": "block"}); + if (config.accept) { + $(ret) + .find("input:file") + .attr("accept", config.accept); + } + + return ret; + } /** * Return a select field. @@ -1241,23 +1306,15 @@ var form_elements = new function () { */ this.make_select_input = function (config) { const options = config.options; - const multiple = config.multiple; - const select = $(form_elements._make_select(multiple)); + const select = $(form_elements._make_select(config.multiple, config.name)); for (let option of options) { select.append(form_elements._make_option(option.value, option.label)); } + const ret = form_elements._make_input(config, select[0]); form_elements.init_select_picker(select[0], config.value); - const ret = $(form_elements._make_field_wrapper(config.name)); - const label = form_elements._make_input_label_str(config); - select.change(function () { - ret[0].dispatchEvent(form_elements.field_changed_event); - }); - - const input_col = $('<div class="caosdb-f-property-value col-sm-9"/>'); - input_col.append(select); - return ret.append(label, input_col)[0]; + return ret; } @@ -1380,23 +1437,23 @@ var form_elements = new function () { * optional `label` * @returns {HTMLElement} a form field. */ - this._make_input = function (config) { + this._make_input = function (config, input) { caosdb_utils.assert_string(config.name, "the name of a form field"); let ret = $(this._make_field_wrapper(config.name)); let name = config.name; let label = this._make_input_label_str(config); let type = config.type || "text"; let value = config.value; - let input = $('<input class="form-control caosdb-f-property-single-raw-value" type="' + type + - '" name="' + name + - '" />'); - input.change(function () { + const _input = $(input || + '<input class="form-control caosdb-f-property-single-raw-value" type="' + + type + '" name="' + name + '" />'); + _input.change(function () { ret[0].dispatchEvent(form_elements.field_changed_event); }); let input_col = $('<div class="caosdb-f-property-value col-sm-9"/>'); - input_col.append(input); + input_col.append(_input); if (value) { - input.val(value); + _input.val(value); } return ret.append(label, input_col)[0]; } diff --git a/test/core/js/modules/form_elements.js.js b/test/core/js/modules/form_elements.js.js index 5b595ee06f892f45699c8c6ae747d81f5a379cb5..1bf83675aa2b5ad8d33834fff31b47cd10158b3d 100644 --- a/test/core/js/modules/form_elements.js.js +++ b/test/core/js/modules/form_elements.js.js @@ -64,8 +64,6 @@ QUnit.test("make_reference_option", function(assert) { QUnit.test("make_reference_select", async function(assert) { assert.equal(typeof form_elements.make_reference_select, "function", "function available"); - //assert.throws(()=> unasync(form_elements.make_reference_select), /param `entities` is expected to be an array/, "undefined entities throws"); - //assert.throws(()=> unasync(form_elements.make_reference_select, "test"), /param `entities` is expected to be an array/, "string entities throws"); var select = await form_elements.make_reference_select([ {dataset: {entityId : "id17"}}, {dataset: {entityId : "id18"}}, @@ -101,6 +99,11 @@ QUnit.test("make_script_form", async function(assert) { ], fields: [ {type: "date", name: "baldate"}, + {type: "select", name: "Sex", label: "Sex", value: "female", required: true, options: [ + { value: "female", label: "female" }, + { value: "diverse", label: "diverse" }, + { value: "male", label: "male" } + ]} ], }; @@ -115,8 +118,9 @@ QUnit.test("make_script_form", async function(assert) { assert.equal(cancel_button.length, 1, "has cancel button"); var field = $(script_form).find(".caosdb-f-field"); - assert.equal(field.length, 1, "has one field"); + assert.equal(field.length, 2, "has two field"); assert.equal(field.find("input[type='date']").length, 1, "has date input"); + assert.equal(field.find("select").length, 1, "has select input"); script_form.addEventListener("caosdb.form.cancel", function(e) { done(); @@ -221,7 +225,7 @@ QUnit.test("make_form_field", async function(assert) { QUnit.test("make_subform", async function(assert) { - assert.equal(typeof form_elements.make_reference_drop_down, "function", "function available"); + assert.equal(typeof form_elements.make_subform, "function", "function available"); const config = { type: "subform", @@ -274,7 +278,7 @@ QUnit.test("make_checkbox_input", function(assert) { assert.equal(input.attr("name"), "approved", "input has name"); assert.ok(input.is(":checked"), "input is checked"); - var obj = form_elements.form_to_object(field); + var obj = form_elements.form_to_object(field)[0]; assert.equal(obj["approved"], "yes!!!", "checked value"); @@ -286,7 +290,7 @@ QUnit.test("make_checkbox_input", function(assert) { assert.equal(input.attr("name"), "approved", "input has name"); assert.notOk(input.is(":checked"), "input is not checked"); - obj = form_elements.form_to_object(field); + obj = form_elements.form_to_object(field)[0]; assert.equal(typeof obj["approved"], "undefined", "no checked value"); @@ -309,7 +313,7 @@ QUnit.test("form_to_object", async function(assert) { var form = await form_elements.make_script_form(config, "bla"); - var json = form_elements.form_to_object(form); + var json = form_elements.form_to_object(form)[0]; assert.equal(typeof json["cancel"], "undefined", "cancel button not serialized"); assert.equal(json["the-date"], "", "date"); @@ -638,6 +642,7 @@ QUnit.test("make_select_input", function(assert) { } const select = $(form_elements.make_select_input(config)); assert.equal(select.find("select").length, 1, "select input there"); + assert.equal(select.find("select").attr("name"), "sex", "has select with correct name"); assert.equal(select.find("select option").length, 3, "three options there"); });