Skip to content
Snippets Groups Projects
Commit 24c8c17b authored by Henrik tom Wörden's avatar Henrik tom Wörden
Browse files

Add documentation on the forms module

parent fc773a90
Branches
Tags
No related merge requests found
...@@ -15,6 +15,9 @@ ...@@ -15,6 +15,9 @@
/build /build
__pycache__ __pycache__
# auto-generated sources
/src/doc/api
# screen logs # screen logs
screenlog.* screenlog.*
xerr.log xerr.log
......
...@@ -81,5 +81,6 @@ Build documentation in `build/` with `make doc`. ...@@ -81,5 +81,6 @@ Build documentation in `build/` with `make doc`.
- sphinx - sphinx
- sphinx-autoapi - sphinx-autoapi
- jsdoc (`npm install jsdoc`) - jsdoc (`npm install jsdoc`)
- jsdoc-sphinx (`npm install jsdoc-sphinx`)
- sphinx-js - sphinx-js
- recommonmark - recommonmark
...@@ -23,6 +23,28 @@ ...@@ -23,6 +23,28 @@
'use strict'; 'use strict';
/**
* @typedef {BottomLineConfig}
* @property {string|HTMLElement} fallback - Fallback content if none of
* the creators are applicable.
* @property {string} version - the version of the configuration which must
* match this module's version.
* @property {CreatorConfig[]} creators - an array of creators.
*/
/**
* @typedef {CreatorConfig}
* @property {string} [id] - a unique id for the creator. optional, for
* debuggin purposes.
* @property {function|string} is_applicable - If this is a string this has
* to be valid javascript! An asynchronous function which accepts one
* parameter, an entity in html representation, and which returns true
* iff this creator is applicable for the given entity.
* @property {string} create - This has to be valid javascript! An
* asynchronous function which accepts one parameter, an entity in html
* representation. It returns a HTMLElement or text node which will be
* shown in the bottom line container iff the creator is applicable.
*/
/** /**
* Add a special section to each entity one the current page where a thumbnail, * Add a special section to each entity one the current page where a thumbnail,
...@@ -45,6 +67,7 @@ ...@@ -45,6 +67,7 @@
* @requires UTIF (from utif.js library) * @requires UTIF (from utif.js library)
* @requires ext_table_preview (module from ext_table_preview.js) * @requires ext_table_preview (module from ext_table_preview.js)
*/ */
var ext_bottom_line = function($, logger, is_in_view_port, load_config, getEntityPath, connection, UTIF, ext_table_preview) { var ext_bottom_line = function($, logger, is_in_view_port, load_config, getEntityPath, connection, UTIF, ext_table_preview) {
/** /**
...@@ -52,28 +75,7 @@ var ext_bottom_line = function($, logger, is_in_view_port, load_config, getEntit ...@@ -52,28 +75,7 @@ var ext_bottom_line = function($, logger, is_in_view_port, load_config, getEntit
* (entity) Note: This property can as well be a * (entity) Note: This property can as well be a
* javascript string which evaluates to a function. * javascript string which evaluates to a function.
*/ */
/**
* @type {BottomLineConfig}
* @property {string|HTMLElement} fallback - Fallback content if none of
* the creators are applicable.
* @property {string} version - the version of the configuration which must
* match this module's version.
* @property {CreatorConfig[]} creators - an array of creators.
*/
/**
* @type {CreatorConfig}
* @property {string} [id] - a unique id for the creator. optional, for
* debuggin purposes.
* @property {function|string} is_applicable - If this is a string this has
* to be valid javascript! An asynchronous function which accepts one
* parameter, an entity in html representation, and which returns true
* iff this creator is applicable for the given entity.
* @property {string} create - This has to be valid javascript! An
* asynchronous function which accepts one parameter, an entity in html
* representation. It returns a HTMLElement or text node which will be
* shown in the bottom line container iff the creator is applicable.
*/
/** /**
* Check if an entity has a path attribute and one of a set of extensions. * Check if an entity has a path attribute and one of a set of extensions.
...@@ -547,6 +549,9 @@ var ext_bottom_line = function($, logger, is_in_view_port, load_config, getEntit ...@@ -547,6 +549,9 @@ var ext_bottom_line = function($, logger, is_in_view_port, load_config, getEntit
} }
/**
* @exports ext_bottom_line
*/
return { return {
previewShownEvent: previewShownEvent, previewShownEvent: previewShownEvent,
previewReadyEvent: previewReadyEvent, previewReadyEvent: previewReadyEvent,
......
...@@ -25,8 +25,6 @@ ...@@ -25,8 +25,6 @@
* form_elements module for reusable form elemenst which already have a basic * form_elements module for reusable form elemenst which already have a basic
* css styling. * css styling.
* *
* @version 0.2
*
* IMPORTANCE CONCEPTS * IMPORTANCE CONCEPTS
* *
* FIELD - an HTMLElement which wraps a LABEL element (the fields name) and the * FIELD - an HTMLElement which wraps a LABEL element (the fields name) and the
...@@ -48,24 +46,53 @@ ...@@ -48,24 +46,53 @@
* SUBFORM - an HTMLElement which contains FIELDS and other SUBFORMS. SUBFORMS * SUBFORM - an HTMLElement which contains FIELDS and other SUBFORMS. SUBFORMS
* can be used to nest FIELDS, which is not supported by HTML5 but allows only * can be used to nest FIELDS, which is not supported by HTML5 but allows only
* for flat key-value pairs. * for flat key-value pairs.
*
* @version 0.2
* @exports form_elements
*/ */
var form_elements = new function () {
/** /**
* The configuration for double, integer, date input elements. * Config for an alert
* *
* @typedef {object} input_config * @typedef {object} AlertConfig
* @property {string} name * @property {string} [title] - an optional title for the alert.
* @property {string} type * @property {string} [severity="danger"] - a bootstrap class suffix. Other
* @property {string} label * examples: warning, info
* @property {string} message - informs the user what they are about to do.
* @property {function} proceed_callback - the function which is called
* then the user hits the "Proceed" button.
* @property {function} [cancel_callback] - a callback which is called then
* the cancel button is clicked. By default, only the alert is being
* closed an nothing happens.
* @property {string} [proceed_text="Proceed"] - the text on the proceed button.
* @property {string} [cancel_text="Cancel"] - the text on the cancel button.
* @property {string} [remember_my_decision_id] - if this parameter is
* present, a checkbox is appended to the alert ("Don't ask me
* again."). If the checkbox is checked the next time the make_alert
* function is called with the same remember_my_decision_id is created,
* the alert won't show up and the proceed_callback is called without
* any user interaction.
* @property {string} [remember_my_decision_text="Don't ask me again."] -
* label text for the checkbox.
* @property {HTMLElement} [proceed_button] - an optional custom proceed
* button.
* @property {HTMLElement} [cancel_button] - an optional custom cancel
* button.
*/ */
/** /**
* The configuration for reference_select input fields * The configuration for double, integer, date input elements.
* *
* TODO * There are specializations of this configuration object. See
* {@link ReferenceDropDownConfig}
* *
* @typedef {object} FieldConfig
* @property {string} name
* @property {string} type
* @property {string} label
* @see {@link ReferenceDropDownConfig}
*/ */
var form_elements = new function () {
this.version = "0.1"; this.version = "0.1";
this.dependencies = ["log", "caosdb_utils", "markdown"]; this.dependencies = ["log", "caosdb_utils", "markdown"];
...@@ -171,33 +198,6 @@ var form_elements = new function () { ...@@ -171,33 +198,6 @@ var form_elements = new function () {
localStorage["form_elements.alert_decision." + key] = val; localStorage["form_elements.alert_decision." + key] = val;
} }
/**
* @type {AlertConfig}
* @property {string} [title] - an optional title for the alert.
* @property {string} [severity="danger"] - a bootstrap class suffix. Other
* examples: warning, info
* @property {string} message - informs the user what they are about to do.
* @property {function} proceed_callback - the function which is called
* then the user hits the "Proceed" button.
* @property {function} [cancel_callback] - a callback which is called then
* the cancel button is clicked. By default, only the alert is being
* closed an nothing happens.
* @property {string} [proceed_text="Proceed"] - the text on the proceed button.
* @property {string} [cancel_text="Cancel"] - the text on the cancel button.
* @property {string} [remember_my_decision_id] - if this parameter is
* present, a checkbox is appended to the alert ("Don't ask me
* again."). If the checkbox is checked the next time the make_alert
* function is called with the same remember_my_decision_id is created,
* the alert won't show up and the proceed_callback is called without
* any user interaction.
* @property {string} [remember_my_decision_text="Don't ask me again."] -
* label text for the checkbox.
* @property {HTMLElement} [proceed_button] - an optional custom proceed
* button.
* @property {HTMLElement] [cancel_button] - an optional custom cancel
* button.
*/
/** /**
* Make an alert, that is a dialog which can intercept a function call and * Make an alert, that is a dialog which can intercept a function call and
* asks the user to proceed or cancel. * asks the user to proceed or cancel.
...@@ -270,10 +270,6 @@ var form_elements = new function () { ...@@ -270,10 +270,6 @@ var form_elements = new function () {
return _alert[0]; return _alert[0];
} }
/**
* (Re-)set this module's functions to standard implementation.
*/
this._init_functions = function () {
this.init = function () { this.init = function () {
this.logger.trace("enter init"); this.logger.trace("enter init");
...@@ -302,6 +298,12 @@ var form_elements = new function () { ...@@ -302,6 +298,12 @@ var form_elements = new function () {
return $(opt_str)[0]; return $(opt_str)[0];
} }
/**
* (Re-)set this module's functions to standard implementation.
*/
this._init_functions = function () {
/** /**
* Return SELECT form element with entity references. * Return SELECT form element with entity references.
* *
...@@ -350,8 +352,6 @@ var form_elements = new function () { ...@@ -350,8 +352,6 @@ var form_elements = new function () {
} }
/** /**
* @typedef {option} ReferenceDropDownConfig
*
* Configuration object for a drop down menu for selecting references. * Configuration object for a drop down menu for selecting references.
* `make_reference_drop_down` generates such a drop down menu using a * `make_reference_drop_down` generates such a drop down menu using a
* SELECT input with the references as its OPTION elements. * SELECT input with the references as its OPTION elements.
...@@ -369,6 +369,10 @@ var form_elements = new function () { ...@@ -369,6 +369,10 @@ var form_elements = new function () {
* defined by `label`. If the `label` property is undefined, the `name` * defined by `label`. If the `label` property is undefined, the `name`
* is shown instead. * is shown instead.
* *
* The ReferenceDropDownConfig is a specialisation of a
* {@link FieldConfig}.
*
* @typedef {option} ReferenceDropDownConfig
* @property {string} name - The name of the select input. * @property {string} name - The name of the select input.
* @property {string} query - Query for entities. * @property {string} query - Query for entities.
* @property {function} [make_value] - Call-back for the generation of * @property {function} [make_value] - Call-back for the generation of
...@@ -383,8 +387,65 @@ var form_elements = new function () { ...@@ -383,8 +387,65 @@ var form_elements = new function () {
* undefined. This property is used by `make_form_field` to decide * undefined. This property is used by `make_form_field` to decide
* which type of field is to be generated. * which type of field is to be generated.
* *
* @see {@link FieldConfig}
*/ */
this._query = async function (q) {
const result = await query(q);
this.logger.debug("query returned", result);
return result;
}
this._run_script = async function (script, form) {
const json_str = JSON.stringify(form_elements.form_to_object(form[0]));
const params = {
"-p0": {
"filename": "form.json",
"blob": new Blob([json_str], {
type: "application/json"
})
}
};
const result = await connection.runScript(script, params);
this.logger.debug("server-side script returned", result);
return this.parse_script_result(result);
}
}
/**
* @typedef {object} ScriptingResult
* @property {string} code
* @property {string} call
* @property {string} stdout
* @property {string} stderr
*/
/**
* Bla, TODO
*
* @param {XMLDocument} result
* @return {ScriptingResult}
*/
this.parse_script_result = function (result) {
console.log(result);
const scriptNode = result.evaluate("/Response/script", result, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
const code = result.evaluate("@code", scriptNode, null, XPathResult.STRING_TYPE, null).stringValue;
const call = result.evaluate("call", scriptNode, null, XPathResult.STRING_TYPE, null).stringValue;
const stderr = result.evaluate("stderr", scriptNode, null, XPathResult.STRING_TYPE, null).stringValue;
const stdout = result.evaluate("stdout", scriptNode, null, XPathResult.STRING_TYPE, null).stringValue;
const ret = {
"code": code,
"call": call,
"stdout": stdout,
"stderr": stderr
};
return ret;
}
/** /**
* Search and retrieve entities and create a SELECT from element. * Search and retrieve entities and create a SELECT from element.
* *
...@@ -422,6 +483,9 @@ var form_elements = new function () { ...@@ -422,6 +483,9 @@ var form_elements = new function () {
} }
/**
* Test 16
*/
this.init_select_picker = function (field, value) { this.init_select_picker = function (field, value) {
caosdb_utils.assert_html_element(field, "parameter `field`"); caosdb_utils.assert_html_element(field, "parameter `field`");
const select = $(field).find("select")[0]; const select = $(field).find("select")[0];
...@@ -440,6 +504,9 @@ var form_elements = new function () { ...@@ -440,6 +504,9 @@ var form_elements = new function () {
} }
/**
* Test 17
*/
this.init_actions_box = function (field) { this.init_actions_box = function (field) {
this.logger.trace("enter init_actions_box", field); this.logger.trace("enter init_actions_box", field);
caosdb_utils.assert_html_element(field, "parameter `field`"); caosdb_utils.assert_html_element(field, "parameter `field`");
...@@ -506,46 +573,10 @@ var form_elements = new function () { ...@@ -506,46 +573,10 @@ var form_elements = new function () {
}); });
} }
this._query = async function (q) {
const result = await query(q);
this.logger.debug("query returned", result);
return result;
}
this._run_script = async function (script, form) {
const json_str = JSON.stringify(form_elements.form_to_object(form[0]));
const params = {
"-p0": {
"filename": "form.json",
"blob": new Blob([json_str], {
type: "application/json"
})
}
};
const result = await connection.runScript(script, params);
this.logger.debug("server-side script returned", result);
return this.parse_script_result(result);
}
this.parse_script_result = function (result) {
console.log(result);
const scriptNode = result.evaluate("/Response/script", result, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
const code = result.evaluate("@code", scriptNode, null, XPathResult.STRING_TYPE, null).stringValue;
const call = result.evaluate("call", scriptNode, null, XPathResult.STRING_TYPE, null).stringValue;
const stderr = result.evaluate("stderr", scriptNode, null, XPathResult.STRING_TYPE, null).stringValue;
const stdout = result.evaluate("stdout", scriptNode, null, XPathResult.STRING_TYPE, null).stringValue;
return {
"code": code,
"call": call,
"stdout": stdout,
"stderr": stderr
};
}
/** /**
* generate a java script object representation of a form * generate a java script object representation of a form
*
* @function
*/ */
this.form_to_object = function (form) { this.form_to_object = function (form) {
this.logger.trace("entity form_to_json", form); this.logger.trace("entity form_to_json", form);
...@@ -619,6 +650,8 @@ var form_elements = new function () { ...@@ -619,6 +650,8 @@ var form_elements = new function () {
/** /**
* TODO make syncronous * TODO make syncronous
*
* @return {HTMLElement}
*/ */
this.make_form_field = async function (config) { this.make_form_field = async function (config) {
caosdb_utils.assert_type(config, "object", "param `config`"); caosdb_utils.assert_type(config, "object", "param `config`");
...@@ -728,6 +761,34 @@ var form_elements = new function () { ...@@ -728,6 +761,34 @@ var form_elements = new function () {
return wrapper[0]; return wrapper[0];
} }
/**
* Configuration objects which are passed to {@link make_form}.
*
* Note: either the `script` or the `name` property must be defined. If the former is defined, the latter will be overriden.
*
* @typedef {object} FormConfig
*
* @property {FieldConfig[]} fields - array of fields. The order is the
* order in which they appear in the resulting form.
* @property {string} [script] - if present the form will call a
* server-side script on submission.
* @property {string} [name] - The name of the form. This is being
* overridden by the `script` parameter if present.
* @property {function} [submit] - a callback which handles the submission
* of the form. This parameter is being overridden if the `script`
* parameter is present.
*/
/**
* Create a form.
*
* The returned element is a container which will eventually contain a HTML
* form element. The container emits a {@link form_ready_event} when the
* form is ready.
*
* @param {FormConfig} config
* @return {HTMLElement}
*/
this.make_form = function (config) { this.make_form = function (config) {
var form = undefined; var form = undefined;
...@@ -1322,7 +1383,6 @@ var form_elements = new function () { ...@@ -1322,7 +1383,6 @@ var form_elements = new function () {
'</label>' : ""; '</label>' : "";
} }
}
this._init_functions(); this._init_functions();
} }
......
...@@ -33,8 +33,9 @@ BUILDDIR = ../../build/doc ...@@ -33,8 +33,9 @@ BUILDDIR = ../../build/doc
# npm is not always in the global PATH # npm is not always in the global PATH
NPM_PATH = $(shell npm bin) NPM_PATH = $(shell npm bin)
NPM_PREFIX = $(shell npm prefix)
.PHONY: doc-help Makefile apidoc .PHONY: doc-help Makefile api
# Put it first so that "make" without argument is like "make help". # Put it first so that "make" without argument is like "make help".
doc-help: doc-help:
...@@ -42,10 +43,9 @@ doc-help: ...@@ -42,10 +43,9 @@ doc-help:
# Catch-all target: route all unknown targets to Sphinx using the new # Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile %: Makefile api
PATH=$(NPM_PATH):$$PATH $(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) PATH=$(NPM_PATH):$$PATH $(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
# sphinx-build -M html . ../../build/doc # sphinx-build -M html . ../../build/doc
# Not necessary in this repository, apidoc is doone with the sphinx-autoapi extension api:
# apidoc: PATH=$(NPM_PATH):$$PATH jsdoc -t $(NPM_PREFIX)/node_modules/jsdoc-sphinx/template -d $@ -r "../../src/core"
# @$(SPHINXAPIDOC) -o _apidoc --update --title="CaosDB Server" ../main/
...@@ -41,13 +41,15 @@ release = '0.x.y-beta-rc2' ...@@ -41,13 +41,15 @@ release = '0.x.y-beta-rc2'
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones. # ones.
extensions = [ extensions = [
'sphinx_js', # 'sphinx_js',
'sphinx.ext.todo', 'sphinx.ext.todo',
"sphinx.ext.autodoc", "sphinx.ext.autodoc",
'autoapi.extension', # 'autoapi.extension',
"recommonmark", # For markdown files. "recommonmark", # For markdown files.
"sphinx_rtd_theme", "sphinx_rtd_theme",
# 'sphinx.ext.intersphinx', 'sphinx.ext.intersphinx',
'sphinx.ext.mathjax',
'sphinx.ext.ifconfig',
# 'sphinx.ext.napoleon', # For Google style docstrings # 'sphinx.ext.napoleon', # For Google style docstrings
] ]
...@@ -207,3 +209,4 @@ autodoc_default_options = { ...@@ -207,3 +209,4 @@ autodoc_default_options = {
autoapi_type = 'javascript' autoapi_type = 'javascript'
autoapi_dirs = ['../core/js/'] autoapi_dirs = ['../core/js/']
autoapi_add_toctree_entry = False
Extending the CaosDB Web Interface
==================================
Here we collect information on how to extend the web interface as a developer.
.. toctree::
:maxdepth: 1
:glob:
extension/*
Creating forms for the CaosDB Web Interface
===========================================
The ``form_elements`` module provides a library for generating forms from simple config objects. The forms are styled for the seamless integration into the CaosDB web interface and are especially useful for calling server side scripts.
See also the :doc:`API documentation <../api/module-form_elements>`
Examples
--------
Generating a generic form
^^^^^^^^^^^^^^^^^^^^^^^^^
The following code snippet adds a form to the body of the HTML document.
.. code-block:: javascript
function my_special_submit_handler (form) {
// handle form submision
};
const config = {
name: "my_form",
fields: [
{ type: "reference_drop_down", name: "experiment_id", label: "Experiment", query: "FIND Record Experiment", required: true },
{ type: "integer", name: "number", label: "A Number", required: true },
{ type: "date", name: "date", label: "A Date", required: false },
{ type: "text", name: "comment", label: "A Comment", required: false },
],
submit: my_special_submit_handler
};
const form = form_elements.make_form(config);
$("body").append(form);
The form has four fields:
1. A drop-down menu which contains all Records of type "Experiment" as options,
2. an integer field, labeled "A Number",
3. a date field, labeled "A Date", and
4. a text field, labeled "A Comment".
The first two fields are required and the form cannot be submitted without it. The latter are optional.
On submission, the function ``my_special_submit_handler`` is being called with the form element as only parameter.
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.
Calling a server-side script
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If you intend to call a server-side script, the config has to be changed a litte bit and the script calling is done by the ``form_elements`` module. There is no need to define the submit_hander anymore. Instead, just name the script which is to be called.
.. code-block:: javascript
const config = {
script: "process.py",
fields: [
{ type: "reference_drop_down", name: "experiment_id", label: "Experiment", query: "FIND Record Experiment", required: true },
{ type: "integer", name: "number", label: "A Number", required: true },
{ type: "date", name: "date", label: "A Date", required: false },
{ type: "text", name: "comment", label: "A Comment", required: false },
],
};
const form = form_elements.make_form(config);
$("body").append(form);
On submission, the form data will be send as a json file to the script and passed as the first parameter. The call would look like ``./process.py form.json`` and the file would contain, for example,
.. code-block:: json
{
"experiment_id": "234234",
"number": "400",
"date": "2020-12-24",
"comment": "This is a comment",
}
For more and advanced options for the form see the :doc:`API documentation <../api/module-form_elements>`
...@@ -10,7 +10,8 @@ Welcome to the documentation of CaosDB's web UI! ...@@ -10,7 +10,8 @@ Welcome to the documentation of CaosDB's web UI!
Getting started <getting_started> Getting started <getting_started>
Tutorials <tutorials/index> Tutorials <tutorials/index>
Concepts <concepts> Concepts <concepts>
API Index<genindex> Extending the UI <extension>
API <api/index>
This documentation helps you to :doc:`get started<getting_started>`, explains the most important This documentation helps you to :doc:`get started<getting_started>`, explains the most important
......
...@@ -13,4 +13,5 @@ RUN pip3 install pandas xlrd==1.2.0 ...@@ -13,4 +13,5 @@ RUN pip3 install pandas xlrd==1.2.0
RUN pip3 install git+https://gitlab.com/caosdb/caosdb-advanced-user-tools.git@dev RUN pip3 install git+https://gitlab.com/caosdb/caosdb-advanced-user-tools.git@dev
# For automatic documentation # For automatic documentation
RUN npm install -g jsdoc RUN npm install -g jsdoc
RUN npm install -g jsdoc-sphinx
RUN pip3 install sphinx-js sphinx-autoapi recommonmark sphinx-rtd-theme RUN pip3 install sphinx-js sphinx-autoapi recommonmark sphinx-rtd-theme
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment