diff --git a/CHANGELOG.md b/CHANGELOG.md index 78608dd944b4c5b79762548e1a55f32e754d248a..ae5c058e15f82a98df92e6822efe174fe54edece 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,24 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.4.1] - 2021-11-04 + +### Added (for new features, dependecies etc.) + +* `form_panel` module for conveniently creating a panel for web forms. + +### Changed (for changes in existing functionality) + +### Deprecated (for soon-to-be removed features) + +### Removed (for now removed features) + +### Fixed (for any bug fixes) + +### Security (in case of vulnerabilities) + +### Documentation (for notable additions or changes of the documentation) + ## [0.4.0] - 2021-10-28 ### Added (for new features, dependecies etc.) diff --git a/build.properties.d/00_default.properties b/build.properties.d/00_default.properties index 65a2b9bc42f9e0f22dc0cd6ca6baac094e94eee7..df68f1012cd71b49538b073a4c9d17e85b2214e4 100644 --- a/build.properties.d/00_default.properties +++ b/build.properties.d/00_default.properties @@ -156,4 +156,5 @@ MODULE_DEPENDENCIES=( ext_cosmetics.js qrcode.js ext_qrcode.js + form_panel.js ) diff --git a/src/core/js/form_elements.js b/src/core/js/form_elements.js index d01b45ee9148febc592d615a8fa947d0d53656d3..193235a2f8a799c07ccc893742d5df9a7d0fa7d1 100644 --- a/src/core/js/form_elements.js +++ b/src/core/js/form_elements.js @@ -100,14 +100,41 @@ var form_elements = new function () { this.version = "0.1"; this.dependencies = ["log", "caosdb_utils", "markdown", "bootstrap"]; this.logger = log.getLogger("form_elements"); + /** + * Event. On form cancel. + */ this.cancel_form_event = new Event("caosdb.form.cancel"); + /** + * Event. On form submit. + */ this.submit_form_event = new Event("caosdb.form.submit"); + /** + * Event. On field change. + */ this.field_changed_event = new Event("caosdb.field.changed"); + /** + * Event. On field enabled. + */ this.field_enabled_event = new Event("caosdb.field.enabled"); + /** + * Event. On field disabled. + */ this.field_disabled_event = new Event("caosdb.field.disabled"); + /** + * Event. On field ready (e.g. for reference drop downs) + */ this.field_ready_event = new Event("caosdb.field.ready"); + /** + * Event. On field error (e.g. for reference drop downs) + */ this.field_error_event = new Event("caosdb.field.error"); + /** + * Event. Form submitted successfully. + */ this.form_success_event = new Event("caosdb.form.success"); + /** + * Event. Error after form was submitted. + */ this.form_error_event = new Event("caosdb.form.error"); @@ -1266,7 +1293,10 @@ var form_elements = new function () { }, config.to); const from_input = this.make_form_field(from_config); + $(from_input).toggleClass("form-control", false); + const to_input = this.make_form_field(to_config); + $(to_input).toggleClass("form-control", false); const ret = $(this._make_field_wrapper(config.name)); if (config.label) { @@ -1277,12 +1307,8 @@ var form_elements = new function () { ret.append(to_input); // styling - $(from_input).toggleClass("form-control", false); - $(from_input).find(".col-sm-3").toggleClass("col-sm-3", false).toggleClass("col-sm-1"); - $(from_input).find(".col-sm-9").toggleClass("col-sm-9", false).toggleClass("col-sm-3"); - $(to_input).toggleClass("form-control", false); - $(to_input).find(".col-sm-3").toggleClass("col-sm-3", false).toggleClass("col-sm-1").toggleClass("col-sm-offset-1"); - $(to_input).find(".col-sm-9").toggleClass("col-sm-9", false).toggleClass("col-sm-3"); + $(from_input).toggleClass("col-sm-4", true); + $(to_input).toggleClass("col-sm-4", true); return ret[0]; } diff --git a/src/core/js/form_panel.js b/src/core/js/form_panel.js new file mode 100644 index 0000000000000000000000000000000000000000..dabb28c9d9dad79a3768fde7552d483bdd3bf570 --- /dev/null +++ b/src/core/js/form_panel.js @@ -0,0 +1,94 @@ +/* + * ** header v3.0 + * This file is a part of the CaosDB Project. + * + * Copyright (C) 2021 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * ** end header + */ + +'use strict'; + +/** + * form_panel module for creating a panel below the navbar where forms can be + * placed. + */ +var form_panel = new function () { + const logger = log.getLogger("form_panel"); + this.version = "0.1"; + this.dependencies = ["log", "caosdb_utils", "markdown", "bootstrap"]; + + /** + * Return a the panel which shall contain the form. + * + * Side-effects: + * 1. Creates the form panel if it does not exist. + * 2. Removes the welcome panel if present. + */ + this.get_form_panel = function (panel_id, title) { + // remove welcome + $(".caosdb-f-welcome-panel").remove(); + $(".caosdb-v-welcome-panel").remove(); + + var existing = $("#" + panel_id); + if (existing.length > 0) { + return existing[0]; + } + const panel = $('<div id="' + panel_id + '" class="caosdb-f-form-panel bg-light container"/>'); + const header = $('<h2 class="text-center">' + title + '</h2>'); + panel.append(header); + + // add to main panel + $('nav').after(panel); + + return panel[0]; + }; + + /** + * Remove the form panel from the DOM tree. + */ + this.destroy_form_panel = function (panel) { + $(panel).remove(); + }; + + /** + * Creates a callback function that toggles the form panel which + */ + this.create_show_form_callback = function (panel_id, title, form_config) { + return (e) => { + logger.trace("enter show_form_panel", e); + + const panel = $(form_panel.get_form_panel(panel_id, title)); + if (panel.find("form").length === 0) { + const form = form_elements.make_form(form_config); + panel.append(form); + $(form).find(".selectpicker").selectpicker(); + + form.addEventListener("caosdb.form.cancel", + (e) => form_panel.destroy_form_panel(panel), + true + ); + } + } + }; + + this.init = function () { + } +} + +$(document).ready(function () { + caosdb_modules.register(form_panel); +}); diff --git a/src/doc/extension/forms.rst b/src/doc/extension/forms.rst index 1bced612b5f9517c5ec149871cbf53321b4671d4..e1891b8d7e571c4a273cec98fcc39e50398936f0 100644 --- a/src/doc/extension/forms.rst +++ b/src/doc/extension/forms.rst @@ -45,6 +45,36 @@ On submission, the function ``my_special_submit_handler`` is being called with t As the generated form is a plain HTML form, the javascript form API can be used. However, there are special methods in the ``form_elements`` module e.g. :doc:`get_fields <../api/module-form_elements>` which are especially designed to interact with the forms generated by the ``make_form`` factory. + +Placing the form in a panel below the navbar +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +There are functions in the `form_panel` module to make it easy to place forms at the typical location: +below the navbar. The following shows how the config (see above) is passed to +`init_show_form_panel_button` a direct call to `make_form` is no longer necessary. + +.. code-block:: javascript + + const title = "Upload CSV File"; // title of the form and text in the toolbox + const panel_id = "csv_upload_form_panel"; + + /** + * Add a button to the navbar, saying "Upload CSV File" which opens a + * form for file upload. + */ + const init_show_form_panel_button = function () { + navbar.add_tool(title, tool_box, { + callback: form_panel.create_show_form_callback( + panel_id, + title, + csv_form_config) + }); + }; + + const init = function () { + init_show_form_panel_button(); + } + Calling a server-side script ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/src/doc/extension/module.md b/src/doc/extension/module.md index d4f9b56db508d7c3be9dc0a52953de5c82bf310d..207d3fff9d013cda657ef8c01f3cca5988622f4a 100644 --- a/src/doc/extension/module.md +++ b/src/doc/extension/module.md @@ -4,7 +4,14 @@ The CaosDB WebUI is organized in modules which can easily be added and on a modu There are a few steps necessary to create a new module. ## Create the module file -Create a new file in `src/core/js` starting with `ext_`. E.g. `ext_flight_preview.js`. This file should define one function that wraps every thing and which is enabled at the bottom of the file: + +Create a new file for each new module. We have the convention, that extensions +which are optional and should stay that way and also custom extensions for +special purposes to name the file starting with `ext_`. E.g. +`ext_flight_preview.js`. + +This file should define one function that wraps every thing and which is +enabled at the bottom of the file: ```js /* @@ -23,16 +30,14 @@ Create a new file in `src/core/js` starting with `ext_`. E.g. `ext_flight_previe * @requires somelibrary * (pass the dependencies as arguments) */ -var ext_flight_preview = function (somelibrary) { +const ext_flight_preview = function (libA, libB) { - var init = function (toolbox) { + const init = function () { /* initialization of the module */ } - /** - * doc string - */ - var some_function = function (arg1, arg2) { + /* doc string */ + const some_function = function (arg1, arg2) { } /* the main function must return the initialization of the module */ @@ -40,7 +45,7 @@ var ext_flight_preview = function (somelibrary) { init: init, }; //pass the dependencies as arguments here as well -}(somelibrary); +}(libA, libB); // this will be replaced by require.js in the future. $(document).ready(function() { @@ -52,21 +57,36 @@ $(document).ready(function() { } }); ``` -## Update xml -Add a section to `src/core/xsl/main.xsl` to include your new file. - -```xsl -<xsl:element name="script"> - <xsl:attribute name="src"> - <xsl:value-of select="concat($basepath,'webinterface/${BUILD_NUMBER}/js/ext_sss_markdown.js')"/> - </xsl:attribute> -</xsl:element> -``` -## Add to index.html in test -If you have unittests (and you should), you need to add a line in : -`test/core/index.html`. +## Install the module + +The new new file should be placed in `src/core/js` if it is intended to be merged into the main repository eventually. For development purposes and for custom extensions which are not to be published you may place it in `src/ext/js`. + +Everything inside `src/core/js` and `src/ext/js` will eventually being loaded. +So, if there are no other modules which depend on this particular new module, +you are done. + +Otherwise, when we need to configure the order in which the +module is being loaded. + + +### Dependency order + +#### For Upstream Code + +For modules which are about to be merged into the main or dev branch of this +repository, add the module's file to `build.properties.d/00_default.properties` +at the right location in the list of module files (Array +`MODULE_DEPENDENCIES`). The list defines the order in which module files are +being loaded. + +#### For Custom Extensions + +For modules which will not be published and merged with the main repository you +may append all your module files in the desired order to the +`MODULE_DEPENDENCIES` array in a new `*.properties` file (e.g. +`build.properties.d/99_local_stuff`): -## Update the changelog + MODULE_DEPENDENCIES+=(libA.js libB.js ext_flight_preview.js) -## Create a merge request \ No newline at end of file +In this example, `libA.js`, `libB.js` and `ext_flight_preview.js` are custom modules developed for this particular CaosDB webui instance. diff --git a/test/core/js/modules/form_panel.js.js b/test/core/js/modules/form_panel.js.js new file mode 100644 index 0000000000000000000000000000000000000000..bc8343d4a65233e025039e7476861fb998c2abbc --- /dev/null +++ b/test/core/js/modules/form_panel.js.js @@ -0,0 +1,63 @@ +/* + * ** header v3.0 + * This file is a part of the CaosDB Project. + * + * Copyright (C) 2021 IndiScale GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * ** end header + */ + +'use strict'; + +QUnit.module("form_panel.js", { + before: function (assert) { + + }, + after: function (assert) { + } +}); + +QUnit.test("availability", function (assert) { + assert.ok(form_panel.init, "init available"); + assert.ok(form_panel.create_show_form_callback , "version available"); +}); + +QUnit.test("create_show_form_callback ", function (assert) { + const title = "Upload CSV File"; // title of the form and text in the toolbox + const panel_id = "csv_upload_form_panel"; + const server_side_script = "csv_script.py"; + const tool_box = "Tools"; // Name of the drop-down menu where the button is added in the navbar + const help_text = "something"; + const accepted_files_formats = [ ".csv", "text/tsv", ] // Mime types and file endings. + + const csv_form_config = { + script: server_side_script, + fields: [{ + type: "file", + name: "csv_file", + label: "CSV File", // label of the file selector in the form + required: true, + cached: false, + accept: accepted_files_formats.join(","), + help: help_text, + }, ], + }; + cb = form_panel.create_show_form_callback( panel_id, title, csv_form_config); + assert.equal(typeof cb, "function", "function created"); + cb() +}); + +