diff --git a/CHANGELOG.md b/CHANGELOG.md index 57472cad21ba770edb8f7a2eb0894936d1be0ed8..b9dbf5f4a7ae168c3d1efe819fdd514b1fe82453 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- #202 - Make filter fields in edit mode toolbox visible +- #117 - Reload data model after adding an RT or a Property * #214 - Paging panel is hidden. * #156 - Edit mode for Safari 11 * #160 - Entity preview for Safari 11 @@ -44,6 +46,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Displaying issues with long lists in property values * An issue whereby a grey container would appear above the map when changing the map view. +* #200 - Re-enabled the file-upload button ### Security (in case of vulnerabilities) diff --git a/src/core/js/caosdb.js b/src/core/js/caosdb.js index e5f77ab21d27de1e8a36d6b5ba1d09994934f343..176572f472014e7b06e0f269291baf392e8ef01f 100644 --- a/src/core/js/caosdb.js +++ b/src/core/js/caosdb.js @@ -1158,22 +1158,6 @@ async function retrieve_dragged_property(id) { return transformation.transformProperty(entities); } -/** - * Retrieve all properties and record types and convert them into - * a web page using a specialized XSLT named entity palette. - * @return An array of entities. - */ -async function retrieve_data_model() { - // TODO possibly allow single query - let props = await connection.get("Entity/?query=FIND Property"); - let rts = await connection.get("Entity/?query=FIND RecordType"); - for (var p of props.children[0].children) { - rts.children[0].appendChild(p.cloneNode()); - } - return transformation.transformEntityPalette(rts); -} - - /** * Update an entity using its xml representation. * @param xml The xml of the entity which will be automatically wrapped with an Update. diff --git a/src/core/js/edit_mode.js b/src/core/js/edit_mode.js index 8644fff20894cc775919c8dfe76f49cec96460fb..8ec85661cd5c62212aab44fbdafee4368938b35e 100644 --- a/src/core/js/edit_mode.js +++ b/src/core/js/edit_mode.js @@ -27,7 +27,7 @@ /** * Edit mode module */ -var edit_mode = new function() { +var edit_mode = new function () { var logger = log.getLogger("edit_mode"); @@ -66,7 +66,7 @@ var edit_mode = new function() { /** * Initialize this module */ - this.init = function() { + this.init = function () { if (isAuthenticated()) { this._init(); } else { @@ -89,15 +89,15 @@ var edit_mode = new function() { } - this.dragstart = function(e) { + this.dragstart = function (e) { e.dataTransfer.setData("text/plain", e.target.id); } - this.dragleave = function(e) { + this.dragleave = function (e) { edit_mode.unhighlight(); } - this.dragover = function(e) { + this.dragover = function (e) { e.preventDefault(); e.dataTransfer.dropEffect = "copy"; edit_mode.highlight(this); @@ -110,7 +110,7 @@ var edit_mode = new function() { * @param new_prop * @param make_property_editable_cb */ - this.add_new_property = function(entity, new_prop, make_property_editable_cb = edit_mode.make_property_editable) { + this.add_new_property = function (entity, new_prop, make_property_editable_cb = edit_mode.make_property_editable) { if (typeof entity === "undefined" || !(entity instanceof HTMLElement)) { throw new TypeError("entity must instantiate HTMLElement"); } @@ -130,7 +130,7 @@ var edit_mode = new function() { * @param {Event} e - the drop event. * @param {HTMLElement} entity - the entity. */ - this.add_dropped_property = function(e, entity) { + this.add_dropped_property = function (e, entity) { var propsrcid = e.dataTransfer.getData("text/plain"); var tmp_id = propsrcid.split("-"); var prop_id = tmp_id[tmp_id.length - 1]; @@ -155,7 +155,7 @@ var edit_mode = new function() { * @param {Event} e - the drop event. * @param {HTMLElement} entity - the entity. */ - this.add_dropped_parent = function(e, entity) { + this.add_dropped_parent = function (e, entity) { var propsrcid = e.dataTransfer.getData("text/plain"); var parent_list = entity.getElementsByClassName("caosdb-f-parent-list")[0] var tmp_id = propsrcid.split("-"); @@ -174,22 +174,22 @@ var edit_mode = new function() { } } - this.property_drop_listener = function(e) { + this.property_drop_listener = function (e) { edit_mode._drop_listener.call(this, e, edit_mode.add_dropped_property); } - this.parent_drop_listener = function(e) { + this.parent_drop_listener = function (e) { edit_mode._drop_listener.call(this, e, edit_mode.add_dropped_parent); } - this._drop_listener = function(e, add_cb) { + this._drop_listener = function (e, add_cb) { e.preventDefault(); edit_mode.unhighlight(); const app = edit_mode.app; const state = app.state; - if(state === "initial"){ + if (state === "initial") { var entity = $(this).parent(); app.startEdit(entity[0]); } else if (state !== "changed") { @@ -202,7 +202,7 @@ var edit_mode = new function() { // Dropping a RecordType in the heading will add it as a parent. // This is done by this function. - this.parent_drop = function(e) { + this.parent_drop = function (e) { logger.assert(edit_mode.app.state === "changed", "state should be changed. Current state: ", edit_mode.app.state, edit_mode.app, e); e.preventDefault(); edit_mode.unhighlight(); @@ -210,7 +210,7 @@ var edit_mode = new function() { } - this.set_entity_dropable = function(entity, dragover, dragleave, parent_drop, property_drop) { + this.set_entity_dropable = function (entity, dragover, dragleave, parent_drop, property_drop) { if (getEntityRole(entity) === "Property") { // currently no parents and subproperties for properties. return; @@ -230,7 +230,7 @@ var edit_mode = new function() { } - this.unset_entity_dropable = function(entity, dragover, dragleave, parent_drop, property_drop) { + this.unset_entity_dropable = function (entity, dragover, dragleave, parent_drop, property_drop) { var rts = entity.getElementsByClassName("caosdb-entity-panel-body"); for (var rel of rts) { rel.removeEventListener("dragleave", dragleave); @@ -245,11 +245,11 @@ var edit_mode = new function() { } } - this.remove_save_button = function(ent) { + this.remove_save_button = function (ent) { $(ent).find('.caosdb-f-entity-save-button').remove(); } - this.add_save_button = function(ent, callback) { + this.add_save_button = function (ent, callback) { var save_btn = $('<button class="btn btn-link caosdb-update-entity-button caosdb-f-entity-save-button">Save</button>'); $(ent).find(".caosdb-f-edit-mode-entity-actions-panel").append(save_btn); @@ -284,7 +284,7 @@ var edit_mode = new function() { * last trash button for the last parent if the header belongs to a * record). */ - this.add_parent_delete_buttons = function(header) { + this.add_parent_delete_buttons = function (header) { $(header).find(".caosdb-f-parent-trash-button").remove(); var parents = $(header).find(".caosdb-parent-item"); if ((parents.length > 1) || getEntityRole(header) != "Record") { @@ -300,7 +300,7 @@ var edit_mode = new function() { * Append a trash button with class "caosdb-f-parent-trash-button", bind a * remove on the deletable, and bind a callback function to click. */ - this.add_parent_trash_button = function(appendable, deletable, callback = undefined) { + this.add_parent_trash_button = function (appendable, deletable, callback = undefined) { edit_mode.add_trash_button(appendable, deletable, "caosdb-f-parent-trash-button", callback); } @@ -308,7 +308,7 @@ var edit_mode = new function() { * Append a trash button with class "caosdb-f-property-trash-button" and * bind a remove on the deletable. */ - this.add_property_trash_button = function(appendable, deletable) { + this.add_property_trash_button = function (appendable, deletable) { edit_mode.add_trash_button(appendable, deletable, "caosdb-f-property-trash-button", undefined, "Remove this property"); } @@ -324,9 +324,9 @@ var edit_mode = new function() { * @param {string} [title] - optional title for the button element. * @return {undefined} */ - this.add_trash_button = function(appendable, deletable, className, callback = undefined, title = undefined) { + this.add_trash_button = function (appendable, deletable, className, callback = undefined, title = undefined) { var button = $('<button class="btn btn-link p-0 ' + className + ' caosdb-f-entity-trash-button"><i class="bi-trash"></i></button>'); - if(title) { + if (title) { button.attr("title", title); } $(appendable).append(button); @@ -371,7 +371,11 @@ var edit_mode = new function() { atomic = "REFERENCE"; } - return { "atomic_datatype": atomic, "reference_scope": ref, "is_list": is_list}; + return { + "atomic_datatype": atomic, + "reference_scope": ref, + "is_list": is_list + }; } /** @@ -412,10 +416,10 @@ var edit_mode = new function() { * of entities in HTML representation. * @returns {XMLDocument} the unprocessed server response. */ - this.insert_entity = async function(ent_elements) { + this.insert_entity = async function (ent_elements) { ent_elements = caosdb_utils.assert_array(ent_elements, "param `ent_elements`", true); var xmls = []; - for ( const ent_element of ent_elements ) { + for (const ent_element of ent_elements) { xmls.push(edit_mode.form_to_xml(ent_element)); } return await insert(xmls); @@ -428,7 +432,7 @@ var edit_mode = new function() { * @returns {Document|DocumentFragment} - An xml document containing the * entity in XML representation. */ - this.form_to_xml = function(entity_form) { + this.form_to_xml = function (entity_form) { const obj = form_elements.form_to_object($(entity_form).find("form")[0])[0]; var entityRole = getEntityRole(entity_form); var file_path = undefined; @@ -459,11 +463,11 @@ var edit_mode = new function() { /** * TODO merge with getPropertyFromElement in caosdb.js */ - this.getPropertyFromElement = function(element) { + this.getPropertyFromElement = function (element) { var editfield = $(element).find(".caosdb-f-property-value"); var property = getPropertyFromElement(element); - var _parse_single_datetime = function(field) { + var _parse_single_datetime = function (field) { let time = $(field).find(":input[type='time']").val() let date = $(field).find(":input[type='date']").val(); if (time) { @@ -476,7 +480,7 @@ var edit_mode = new function() { // LISTs need to be handled here if (property.list == true) { property.value = []; - if (["TEXT","DOUBLE","INTEGER"].includes(property.listDatatype)) { + if (["TEXT", "DOUBLE", "INTEGER"].includes(property.listDatatype)) { // LOOP over elements of editfield.find(":input") for (var singleelement of $(editfield).find(":not(.caosdb-f-list-item-button):input:not(.caosdb-unit)")) { property.value.push($(singleelement).val()); @@ -494,7 +498,7 @@ var edit_mode = new function() { throw ("This property's data type is not supported by the webui. Please issue a feature request for support for `" + property.datatype + "`."); } } else { - if (["TEXT","DOUBLE","INTEGER"].includes(property.datatype)) { + if (["TEXT", "DOUBLE", "INTEGER"].includes(property.datatype)) { property.value = editfield.find(":input:not(.caosdb-unit)").val(); } else if (property.datatype == "DATETIME") { property.value = _parse_single_datetime(editfield); @@ -516,7 +520,7 @@ var edit_mode = new function() { * * ent_element : {HTMLElement} entity in view mode */ - this.getProperties = function(ent_element) { + this.getProperties = function (ent_element) { const properties = []; if (ent_element) { const prop_elements = getPropertyElements(ent_element); @@ -530,14 +534,14 @@ var edit_mode = new function() { } - this.update_entity = async function(ent_element) { + this.update_entity = async function (ent_element) { var xml = edit_mode.form_to_xml(ent_element); return await edit_mode.update(xml); } this.update = update; - this.add_edit_mode_button = function(target, toggle_function) { + this.add_edit_mode_button = function (target, toggle_function) { var edit_mode_li = $('<li class="nav-item"><a class="nav-link caosdb-f-btn-toggle-edit-mode" role="button">Edit Mode</a></li>'); $(target).append(edit_mode_li); $(".caosdb-f-btn-toggle-edit-mode").click(toggle_function); @@ -548,7 +552,7 @@ var edit_mode = new function() { return edit_mode_li[0]; } - this.toggle_edit_mode = async function() { + this.toggle_edit_mode = async function () { edit_mode.toggle_edit_panel(); if (edit_mode.is_edit_mode()) { await edit_mode.leave_edit_mode(); @@ -561,21 +565,20 @@ var edit_mode = new function() { * To be overridden by an instance of `leave_edit_mode_template` during the * `enter_edit_mode()` execution. */ - this.leave_edit_mode = function() {} + this.leave_edit_mode = function () {} - this.enter_edit_mode = async function(editApp = undefined) { + /** + * Initializes the edit mode and loads the tool box. + */ + this.enter_edit_mode = async function (editApp = undefined) { window.localStorage.edit_mode = "true"; - var editPanel = edit_mode.get_edit_panel(); - removeAllWaitingNotifications(editPanel); - this.add_wait_datamodel_info(); - try { - const model = await edit_mode.retrieve_data_model(); $(".caosdb-f-btn-toggle-edit-mode").text("Leave Edit Mode"); - edit_mode.init_tool_box(model); + edit_mode.init_tool_box(); + edit_mode.init_dragable(); var nextEditApp = editApp; if (typeof nextEditApp == "undefined") { @@ -583,7 +586,7 @@ var edit_mode = new function() { } edit_mode.init_new_buttons(nextEditApp); - edit_mode.leave_edit_mode = function() { + edit_mode.leave_edit_mode = function () { edit_mode.leave_edit_mode_template(nextEditApp); }; @@ -593,18 +596,57 @@ var edit_mode = new function() { } } + /** + * (Re-)load the toolbox, i.e. retrieve Properties and RecordTypes + */ + this.load_edit_mode_toolbox = async function () {} + + /** + * Retrieve all properties and record types and convert them into + * a web page using a specialized XSLT named entity palette. + * + * @return {Promise<XMLDocument>} An array of entities in xml representation. + */ + this.retrieve_data_model = async function () { + const props_prom = connection.get("Entity/?query=FIND Property"); + const rts_prom = connection.get("Entity/?query=FIND RecordType"); + + const [props, rts] = await Promise.all([props_prom, rts_prom]); + + // add all properties to rts + for (var p of props.children[0].children) { + rts.documentElement.appendChild(p.cloneNode()); + } + return rts; + } - this.retrieve_data_model = retrieve_data_model; + /** + * @param {Promise<XMLElement[]>} xml - an array of XMLElements. + * @return {Promise<HTMLDocument>} an HTMLElements. + */ + this.transform_entity_palette = async function _tME(xml) { + var xsl = transformation.retrieveXsltScript("entity_palette.xsl"); + let html = await asyncXslt(xml, xsl); + return html; + } - this.init_tool_box = function(model) { - var editPanel = edit_mode.get_edit_panel(); - removeAllWaitingNotifications(editPanel); + /** + * Remove all warnings and set the HTML of the toolbox panel to + * the model given by model. + */ + this.init_tool_box = async function () { + // remove previously added model + $(".caosdb-f-edit-mode-existing").remove() - editPanel.innerHTML = xml2str(model); - edit_mode.init_dragable(); + const editPanel = $(edit_mode.get_edit_panel()); + removeAllWaitingNotifications(editPanel[0]); + editPanel.append(createWaitingNotification("Please wait.")); + const model = await edit_mode.transform_entity_palette(edit_mode.retrieve_data_model()); + removeAllWaitingNotifications(editPanel[0]); + editPanel.children()[0].appendChild(model); } @@ -615,9 +657,9 @@ var edit_mode = new function() { * * @param {HTMLElement} entity */ - this.init_actions_panels = function(entity) { + this.init_actions_panels = function (entity) { this.reset_actions_panels([entity]); - $(entity).find(".caosdb-entity-actions-panel").each(function(index) { + $(entity).find(".caosdb-entity-actions-panel").each(function (index) { var clone = $(this).clone(true)[0]; $(clone).removeClass("caosdb-entity-actions-panel").addClass("caosdb-f-edit-mode-entity-actions-panel").insertBefore(this); $(clone).children().remove(); @@ -630,26 +672,26 @@ var edit_mode = new function() { * * @param {HTMLElements[]} array of entities in HTML representation. */ - this.reset_actions_panels = function(entities) { + this.reset_actions_panels = function (entities) { $(entities).find(".caosdb-f-edit-mode-entity-actions-panel").remove(); $(entities).find(".caosdb-entity-actions-panel").show(); } - this.make_header_editable = function(entity) { + this.make_header_editable = function (entity) { var header = $(entity).find('.caosdb-entity-panel-heading'); var roleElem = $(header).find('.caosdb-f-entity-role'); roleElem.detach(); var parentsElem = $(header).find('.caosdb-f-parent-list'); parentsElem.detach(); const parentsSection = $(` - <div class="row"> - <div class="col-2 text-end"> - <label class="col-form-label">parents</label> - </div> - <div class="col caosdb-f-parents-form-element"> - </div> + <div class="row"> + <div class="col-2 text-end"> + <label class="col-form-label">parents</label> + </div> + <div class="col caosdb-f-parents-form-element"> </div> - `); + </div> + `); parentsSection.find("div.caosdb-f-parents-form-element").append(parentsElem); header.attr("title", "Drop parents from the right panel here."); @@ -687,11 +729,11 @@ var edit_mode = new function() { edit_mode.add_parent_delete_buttons(header[0]); } - this.isListDatatype = function(datatype) { + this.isListDatatype = function (datatype) { return (typeof datatype !== 'undefined' && datatype.substring(0, 5) == "LIST<"); } - this.unListDatatype = function(datatype) { + this.unListDatatype = function (datatype) { return datatype.substring(5, datatype.length - 1); } @@ -710,7 +752,7 @@ var edit_mode = new function() { * * @param {HTMLElement} form - The form containing the input fields. */ - this.make_datatype_input_logic = function(form) { + this.make_datatype_input_logic = function (form) { const datatype = form_elements.get_fields(form, "atomic_datatype"); $(datatype).find("select").change(function () { @@ -754,13 +796,13 @@ var edit_mode = new function() { * @param {string} unit - the initial value of the input element. * @returns {HTMLElement} - a labeled form field. */ - this.make_unit_input = function(unit) { + this.make_unit_input = function (unit) { const unit_input = $(form_elements - .make_text_input({ - name: "unit", - label: "unit", - value: unit, - })); + .make_text_input({ + name: "unit", + label: "unit", + value: unit, + })); unit_input.toggleClass("form-control", true); unit_input.find(".col-sm-3").toggleClass("col-sm-2", true).toggleClass("col-sm-3", false); unit_input.find(".col-sm-9").toggleClass("col-sm-2", true).toggleClass("col-sm-9", false); @@ -768,14 +810,14 @@ var edit_mode = new function() { } this._known_atomic_datatypes = [ - "TEXT", - "DOUBLE", - "INTEGER", - "DATETIME", - "BOOLEAN", - "FILE", - "REFERENCE", - ]; + "TEXT", + "DOUBLE", + "INTEGER", + "DATETIME", + "BOOLEAN", + "FILE", + "REFERENCE", + ]; /** * Make three input elements which contain all necessary parts of a datatype. @@ -785,7 +827,7 @@ var edit_mode = new function() { * @param {string} [datatype] - defaults to TEXT if undefined. * @returns {HTMLElement} */ - this.make_datatype_input = function(datatype) { + this.make_datatype_input = function (datatype) { var _datatype = datatype || "TEXT"; // split/convert datatype string into more practical variables. @@ -822,7 +864,7 @@ var edit_mode = new function() { make_value: getEntityName, }; const ref_selector = form_elements - .make_reference_drop_down(reference_config); + .make_reference_drop_down(reference_config); // generate the checkbox ([ ] list) @@ -832,11 +874,11 @@ var edit_mode = new function() { checked: is_list, } const list_checkbox = form_elements - .make_checkbox_input(list_checkbox_config); + .make_checkbox_input(list_checkbox_config); // styling //$(list_checkbox).children().toggleClass("col-sm-3",false).toggleClass("col-sm-9", false).toggleClass("col-sm-1", true); - $(list_checkbox).find(".caosdb-f-property-value").toggleClass("my-auto",true) + $(list_checkbox).find(".caosdb-f-property-value").toggleClass("my-auto", true) const form_group = $('<div class="">').append([datatype_selector, ref_selector, list_checkbox]); form_group.find(".col-sm-3").toggleClass("text-end", true).toggleClass("col-sm-2", true).toggleClass("col-sm-3", false); form_group.find(".col-sm-9").toggleClass("col-sm-3", true).toggleClass("col-sm-9", false); @@ -845,11 +887,11 @@ var edit_mode = new function() { return form_group[0]; } - this.make_input = function(label, value) { - return $('<div class="row"> <div class="col-2 text-end"> <label class="col-form-label">' + label +' </label> </div> <div class="col caosdb-f-parents-form-element"> <input type="text" class="form-control caosdb-f-entity-' + label + '" value="' + (typeof value == 'undefined' ? "" : value) + '"></input> </div> </div>')[0]; + this.make_input = function (label, value) { + return $('<div class="row"> <div class="col-2 text-end"> <label class="col-form-label">' + label + ' </label> </div> <div class="col caosdb-f-parents-form-element"> <input type="text" class="form-control caosdb-f-entity-' + label + '" value="' + (typeof value == 'undefined' ? "" : value) + '"></input> </div> </div>')[0]; } - this.smooth_replace = function(from, to) { + this.smooth_replace = function (from, to) { $(to).hide(); $(from).fadeOut(); $(from).after(to); @@ -858,22 +900,22 @@ var edit_mode = new function() { } /** - * Create an input element for a single property's value. - * - * @param {object} property - entity property object. - * @param {HTMLElements[]} [options] - an array of OPTION elements - * which represent possible candidates for reference values. The - * options will be appended to the SELECT input. This parameter is - * optional and only used for reference properties. This parameter - * might as well be a Promise for such an array. - */ - this.createElementForProperty = function(property, options) { + * Create an input element for a single property's value. + * + * @param {object} property - entity property object. + * @param {HTMLElements[]} [options] - an array of OPTION elements + * which represent possible candidates for reference values. The + * options will be appended to the SELECT input. This parameter is + * optional and only used for reference properties. This parameter + * might as well be a Promise for such an array. + */ + this.createElementForProperty = function (property, options) { var result; if (property.datatype == "TEXT") { result = `<textarea>${property.value || ""}</textarea>`; } else if (property.datatype == "DATETIME") { var dateandtime = [""]; - if(property.value) { + if (property.value) { dateandtime = caosdb2InputDate(property.value); } let date = dateandtime[0]; @@ -910,10 +952,10 @@ var edit_mode = new function() { * @param {object} property - a property object. * @returns {HTMLElement} a SPAN element. */ - this.generate_list_item_control_panel = function(property, options) { + this.generate_list_item_control_panel = function (property, options) { // Add list delete buttons: var deleteButton = $('<button title="Delete this list element." class="btn btn-link caosdb-update-entity-button caosdb-f-list-item-button"><i class="bi-trash"></i></button>'); - $(deleteButton).click(function() { + $(deleteButton).click(function () { var ol = this.parentElement.parentElement.parentElement; $(this.parentElement.parentElement).remove(); @@ -923,7 +965,7 @@ var edit_mode = new function() { // Add list insert buttons: var insertButton = $('<button title="Insert a new list element before this element." class="btn btn-link caosdb-update-entity-button caosdb-f-list-item-button"><i class="bi-plus"></i></button>'); - $(insertButton).click(function() { + $(insertButton).click(function () { // TODO MERGE WITH OTHER PLACES WHERE THIS STRING APPEARS var proptemp = { list: false, @@ -952,7 +994,7 @@ var edit_mode = new function() { /** * */ - this.create_unit_field = function(unit) { + this.create_unit_field = function (unit) { return $(`<input class='caosdb-unit' title='unit' style='width: 60px;' placeholder='unit' value='${unit||""}' type='text'></input>`)[0]; } @@ -967,10 +1009,10 @@ var edit_mode = new function() { * @param {object} property - a property object. * @return {HTMLElement[]} */ - this.create_value_inputs = function(property) { + this.create_value_inputs = function (property) { logger.trace("enter create_value_inputs", arguments); - var result = [ ]; + var result = []; if (!property.list) { var options = property.reference ? edit_mode.retrieve_datatype_list(property.datatype) : undefined; result.push(edit_mode.createElementForProperty(property, options)); @@ -986,7 +1028,7 @@ var edit_mode = new function() { result.push(edit_mode.create_unit_field(property.unit)); } - for (var i=0; i<property.value.length; i++) { + for (var i = 0; i < property.value.length; i++) { // TODO MERGE WITH OTHER PLACES WHERE THIS STRING APPEARS var proptemp = { list: false, @@ -1005,7 +1047,7 @@ var edit_mode = new function() { // PLUS-button for appending inputs to the list. var insertButton = $('<button title="Append a new field at the end." class="btn btn-link caosdb-update-entity-button caosdb-f-list-item-button"><i class="bi-plus"></i></button>'); - $(insertButton).click(function() { + $(insertButton).click(function () { // TODO MERGE WITH OTHER PLACES WHERE THIS STRING APPEARS var proptemp = { list: false, @@ -1027,7 +1069,8 @@ var edit_mode = new function() { result = result.concat([inputs[0], $("<span>Insert element at the end of the list: </span>") - .append(insertButton)[0]]); + .append(insertButton)[0] + ]); } return result; @@ -1053,11 +1096,11 @@ var edit_mode = new function() { * * @see {@link _toggle_list_property} which is the most prominent callee. */ - this._toggle_list_property_object = function(element, toList) { + this._toggle_list_property_object = function (element, toList) { const property = edit_mode.getPropertyFromElement(element); // property.list XAND toList - if(property.list ? toList : !toList) { + if (property.list ? toList : !toList) { // already in desired state. return undefined; } @@ -1070,7 +1113,7 @@ var edit_mode = new function() { if (!toList && $.isArray(property.value) && property.value.length < 2) { property.value = property.value[0] || ""; } else if (toList && !$.isArray(property.value)) { - property.value = [ property.value ] + property.value = [property.value] } else { throw new Error(`Could not toggle to list=${toList} with value=${property.value}.`); } @@ -1090,12 +1133,12 @@ var edit_mode = new function() { * @see {@link _toggle_list_property_object} which does the actual * conversion work. */ - this._toggle_list_property = function(element, toList) { + this._toggle_list_property = function (element, toList) { logger.trace("enter _toggle_list_property", arguments); let property = edit_mode._toggle_list_property_object(element, toList); - if(!property) { + if (!property) { // already in desired state. return; } @@ -1114,7 +1157,7 @@ var edit_mode = new function() { } - this.change_property_data_type = function(element, datatype) { + this.change_property_data_type = function (element, datatype) { $(element).find(".caosdb-property-datatype").text(datatype); element.dispatchEvent(edit_mode.property_data_type_changed); } @@ -1146,7 +1189,7 @@ var edit_mode = new function() { // disable checkbox when list has more than 1 element let disabled = editfield.find("li").length > 1; checkbox.prop("disabled", disabled); - const listDatatype = list ? datatype.substring(5, datatype.length-1) : datatype; + const listDatatype = list ? datatype.substring(5, datatype.length - 1) : datatype; if (disabled) { checkbox.attr("title", `You must remove all elements of the list but one by clicking the trash buttons (to the left) before you can toggle the LIST<${listDatatype}>`); } else { @@ -1157,23 +1200,23 @@ var edit_mode = new function() { // list has more than one element. editfield[0].addEventListener( edit_mode.list_value_input_added.type, (e) => { - let disabled = editfield.find("li").length > 1; - checkbox.prop("disabled", disabled); - if (disabled) { - checkbox.attr("title", `You must remove all elements of the list but one by clicking the trash buttons (to the left) before you can toggle the LIST<${listDatatype}>`); - } - }, true); + let disabled = editfield.find("li").length > 1; + checkbox.prop("disabled", disabled); + if (disabled) { + checkbox.attr("title", `You must remove all elements of the list but one by clicking the trash buttons (to the left) before you can toggle the LIST<${listDatatype}>`); + } + }, true); // enable the checkbox when elements removed to the list and the number // of elements is smaller than 2. editfield[0].addEventListener( edit_mode.list_value_input_removed.type, (e) => { - let disabled = editfield.find("li").length > 1; - checkbox.prop("disabled", disabled); - if (!disabled) { - checkbox.attr("title", `Toggle LIST<${listDatatype}> data type of this property.`); - } - }, true); + let disabled = editfield.find("li").length > 1; + checkbox.prop("disabled", disabled); + if (!disabled) { + checkbox.attr("title", `Toggle LIST<${listDatatype}> data type of this property.`); + } + }, true); } /** @@ -1192,14 +1235,16 @@ var edit_mode = new function() { * * @param {HTMLElement} element - entity property in HTML representation. */ - this.make_property_editable = function(element) { + this.make_property_editable = function (element) { caosdb_utils.assert_html_element(element, "param 'element'"); var editfield = $(element).find(".caosdb-f-property-value") - .removeClass("col-sm-8") - .addClass("col-sm-6") + .removeClass("col-sm-6") + .removeClass("col-md-8") + .addClass("col-sm-4") + .addClass("col-md-6") .addClass("caosdb-v-property-value-inputs") - .after(`<div class="col-sm-2 caosdb-v-property-other-inputs caosdb-property-edit" style="text-align: right;"/>`); + .after(`<div class="col-sm-2 col-sm-2 caosdb-v-property-other-inputs caosdb-property-edit" style="text-align: right;"/>`); var property = getPropertyFromElement(element); @@ -1212,10 +1257,17 @@ var edit_mode = new function() { edit_mode.add_toggle_list_checkbox(element, property.list, property.datatype); // TRASH BUTTON - edit_mode.add_property_trash_button($(element).find(".caosdb-property-edit")[0],element); + edit_mode.add_property_trash_button($(element).find(".caosdb-property-edit")[0], element); } - this.create_new_record = async function(recordtype_id, name = undefined) { + + /** + * Create a new Record using an existing RecordType. + * The RecordType can be specified using recordtype_id. + * + * Currently name is ignored. TODO: check whether that behavior is intended. + */ + this.create_new_record = async function (recordtype_id, name = undefined) { var rt = await retrieve(recordtype_id); var newrecord = createEntityXML("Record", undefined, undefined, getProperties(rt[0]), @@ -1229,20 +1281,26 @@ var edit_mode = new function() { return x[0]; } - - this.init_dragable = function() { - var props = document.getElementsByClassName("caosdb-f-edit-drag"); - for (var pel of props) { - pel.addEventListener("dragstart", edit_mode.dragstart); - pel.setAttribute("draggable", true); - } + /** + * All draggable elements (that are those which have a css class + * caosdb-f-edit-drag are added the correct drag listener and the attribute draggable. + * + * This is now done using bubbling so that the listeners are also dynamically updated. + */ + this.init_dragable = function () { + // Bubbling: Add the listener to the document and check whether the class is present. + document.addEventListener("dragstart", function (event) { + if (event.target.classList.contains("caosdb-f-edit-drag")) { + edit_mode.dragstart(event); + } + }); } - /* + /** * This function treats the deletion of entities, i.e. when the "delete" * button is clicked. */ - this.delete_action = async function(entity) { + this.delete_action = async function (entity) { var app = edit_mode.app; // show waiting notification @@ -1286,33 +1344,19 @@ var edit_mode = new function() { } - this.disable_new_buttons = function() { + this.disable_new_buttons = function () { var new_buttons = $('.caosdb-f-edit-panel-new-button'); new_buttons.attr("disabled", true); } - this.enable_new_buttons = function() { + this.enable_new_buttons = function () { var new_buttons = $('.caosdb-f-edit-panel-new-button'); new_buttons.attr("disabled", false); } - this.init_new_buttons = function(app) { + this.init_new_buttons = function (app) { var new_buttons = $('.caosdb-f-edit-panel-new-button'); - // Show a button "+" to create a new property when filter results in empty list. - new_buttons.filter('.caosdb-f-hide-on-empty-input').parent().each(function(index) { - var button = $(this); - button.hide(); - var input = button.parent().find("input"); - input.on("input", function(e) { - if (input.val() == '') { - button.fadeOut(); - } else { - button.fadeIn(); - } - }); - }); - // handler for new property button // calls newEntity transition of state machine new_buttons.filter('.new-property').click(() => { @@ -1363,7 +1407,7 @@ var edit_mode = new function() { * * `finish` is triggered automatically when leaving the edit mode and does a little cleanup. */ - this.init_edit_app = function() { + this.init_edit_app = function () { var app = new StateMachine({ @@ -1403,9 +1447,9 @@ var edit_mode = new function() { }); - var init_drag_n_drop = function() { + var init_drag_n_drop = function () { const state = app.state; - $('.caosdb-entity-panel').each(function(index) { + $('.caosdb-entity-panel').each(function (index) { let entity = this; if ($(entity).is("[data-version-successor]")) { // not the latest version -> ignore @@ -1428,24 +1472,24 @@ var edit_mode = new function() { } // Define the error handler for the state machine. - app.errorHandler = function(fn) { + app.errorHandler = function (fn) { try { fn(); } catch (e) { edit_mode.handle_error(e); } }; - app.onAfterTransition = function(e) { + app.onAfterTransition = function (e) { init_drag_n_drop(); } - app.onEnterInitial = async function(e) { + app.onEnterInitial = async function (e) { $(".caosdb-f-edit-mode-existing").toggleClass("d-none", true); $(".caosdb-f-edit-mode-create-buttons").toggleClass("d-none", false); app.old = undefined; app.errorHandler(() => { // make entities dropable and freezable edit_mode.enable_new_buttons(); - $('.caosdb-entity-panel').each(function(index) { + $('.caosdb-entity-panel').each(function (index) { let entity = this; if ($(entity).is("[data-version-successor]")) { // not the latest version -> ignore @@ -1475,9 +1519,9 @@ var edit_mode = new function() { }); }); }; - app.onLeaveInitial = function(e) { + app.onLeaveInitial = function (e) { app.errorHandler(() => { - $('.caosdb-entity-panel').each(function(index) { + $('.caosdb-entity-panel').each(function (index) { // add the save button an so on edit_mode.remove_start_edit_button(this); edit_mode.remove_new_record_button(this); @@ -1486,7 +1530,7 @@ var edit_mode = new function() { }); edit_mode.disable_new_buttons(); }; - app.onBeforeStartEdit = function(e, entity) { + app.onBeforeStartEdit = function (e, entity) { edit_mode.unhighlight(); app.old = entity; app.entity = $(entity).clone(true)[0]; @@ -1499,11 +1543,11 @@ var edit_mode = new function() { edit_mode.freeze_but(app.entity); }; - app.onBeforeCancel = function(e) { + app.onBeforeCancel = function (e) { edit_mode.smooth_replace(app.entity, app.old); edit_mode.unfreeze(); }; - app.onUpdate = function(e, entity) { + app.onUpdate = function (e, entity) { edit_mode.update_entity(entity).then(response => { return transformation.transformEntities(response); }, edit_mode.handle_error).then(entities => { @@ -1512,7 +1556,7 @@ var edit_mode = new function() { app.showResults(); }, edit_mode.handle_error); }; - app.onEnterChanged = function(e) { + app.onEnterChanged = function (e) { // show existing entities in toolbox $(".caosdb-f-edit-mode-existing").toggleClass("d-none", false); $(".caosdb-f-edit-mode-create-buttons").toggleClass("d-none", true); @@ -1529,18 +1573,18 @@ var edit_mode = new function() { for (var element of prop_elements) { edit_mode.make_property_editable(element); } - if(getEntityRole(app.entity) != "Property") { - edit_mode.add_property_dropzone(app.entity); + if (getEntityRole(app.entity) != "Property") { + edit_mode.add_property_dropzone(app.entity); } app.entity.dispatchEvent(edit_mode.start_edit); } - app.onEnterWait = function(e) { + app.onEnterWait = function (e) { edit_mode.smooth_replace(app.entity, app.waiting); } - app.onLeaveWait = function(e) { + app.onLeaveWait = function (e) { edit_mode.smooth_replace(app.waiting, app.entity); } - app.onBeforeNewEntity = function(e, entity) { + app.onBeforeNewEntity = function (e, entity) { if (typeof entity == "undefined") { throw new TypeError("entity is undefined"); } @@ -1568,7 +1612,7 @@ var edit_mode = new function() { app.old = $('<div/>')[0]; } - app.onInsert = function(e, entity) { + app.onInsert = function (e, entity) { edit_mode.insert_entity(entity).then(response => { return transformation.transformEntities(response); }, edit_mode.handle_error).then(entities => { @@ -1581,7 +1625,7 @@ var edit_mode = new function() { app.showResults(); }, edit_mode.handle_error); } - app.onFinish = function(e) { + app.onFinish = function (e) { // this transition is triggerd on leaving the edit mode. edit_mode.unhighlight(); if (app.old) { @@ -1589,7 +1633,7 @@ var edit_mode = new function() { } edit_mode.unfreeze(); } - app.onShowResults = function(e) { + app.onShowResults = function (e) { // this transition is triggered by the response of the server on a // previous insert/update request. @@ -1601,6 +1645,7 @@ var edit_mode = new function() { app.entity.dispatchEvent(edit_mode.end_edit); resolve_references.init(); preview.init(); + edit_mode.init_tool_box(); } app.waiting = createWaitingNotification("Please wait."); $(app.waiting).hide(); @@ -1613,12 +1658,12 @@ var edit_mode = new function() { return app; } - this.has_errors = function(entity) { + this.has_errors = function (entity) { return $(entity).find(".alert.alert-danger").length > 0; } - this.freeze_but = function(element) { - $('.caosdb-f-main-entities').children().each(function(index) { + this.freeze_but = function (element) { + $('.caosdb-f-main-entities').children().each(function (index) { if (element != this) { edit_mode.freeze_entity(this); } @@ -1637,14 +1682,14 @@ var edit_mode = new function() { .prepend('<div>Drag and drop RecordTypes from the Edit Mode Toolbox here.</div>'); } - this.unfreeze = function() { - $('.caosdb-f-main-entities').children().each(function(index) { + this.unfreeze = function () { + $('.caosdb-f-main-entities').children().each(function (index) { edit_mode.unfreeze_entity(this); }); } - this._create_reference_options = function(entities) { + this._create_reference_options = function (entities) { var results = []; for (var i = 0; i < entities.length; i++) { @@ -1680,7 +1725,7 @@ var edit_mode = new function() { this.fill_reference_drop_down = async function (drop_down, options) { var resolved_options = await options; var old = $(drop_down).find("option.caosdb-f-option-default"); - if(old.length == 0) { + if (old.length == 0) { // no option selected, append all $(drop_down).append($(resolved_options).clone()); return; @@ -1707,7 +1752,7 @@ var edit_mode = new function() { * @returns {HTMLElement[]} array of OPTION element, representing an entity * which can be referenced by the property. */ - this.retrieve_datatype_list = async function(datatype) { + this.retrieve_datatype_list = async function (datatype) { var find_entity = ["FILE", "REFERENCE"].includes(datatype) ? "" : datatype; var entities = datatype !== "FILE" ? await edit_mode.query(`FIND Record ${find_entity}`) : []; var files = await edit_mode.query(`FIND File ${find_entity}`); @@ -1721,36 +1766,32 @@ var edit_mode = new function() { } - this.highlight = function(entity) { + this.highlight = function (entity) { $(entity).find(".caosdb-v-edit-mode-dropzone") .addClass("caosdb-v-edit-mode-highlight"); } - this.unhighlight = function() { + this.unhighlight = function () { $('.caosdb-v-edit-mode-highlight') .removeClass("caosdb-v-edit-mode-highlight"); } - this.handle_error = function(err) { + this.handle_error = function (err) { globalError(err); } - this.get_edit_panel = function() { + this.get_edit_panel = function () { return $('.caosdb-f-edit-panel-body')[0]; } - this.add_wait_datamodel_info = function() { - $(this.get_edit_panel()).append(createWaitingNotification("Please wait.")); - } - - this.toggle_edit_panel = function() { + this.toggle_edit_panel = function () { //$(".caosdb-f-main").toggleClass("container-fluid").toggleClass("container"); $(".caosdb-f-main-entities").toggleClass("caosdb-f-main-entities-edit"); - $(".caosdb-f-edit").toggle();//.toggleClass("col-xs-4"); + $(".caosdb-f-edit").toggle(); //.toggleClass("col-xs-4"); this._toggle_min_width_warning(); } - this._toggle_min_width_warning = function() { + this._toggle_min_width_warning = function () { // Somewhat counter-intuitive, but when we're not in edit mode // and toggle the panel, we're entering and the warning should // be shown on small screens and vice-versa. @@ -1758,18 +1799,18 @@ var edit_mode = new function() { $(".caosdb-edit-min-width-warning").toggleClass("d-block", !(edit_mode.is_edit_mode())); } - this.leave_edit_mode_template = function(app) { + this.leave_edit_mode_template = function (app) { app.finish(); $(".caosdb-f-btn-toggle-edit-mode").text("Edit Mode"); edit_mode.reset_actions_panels($(".caosdb-f-main").toArray()); window.localStorage.removeItem("edit_mode"); } - this.is_edit_mode = function() { + this.is_edit_mode = function () { return window.localStorage.edit_mode !== undefined; } - this.add_cancel_button = function(ent, callback) { + this.add_cancel_button = function (ent, callback) { var cancel_btn = $('<button class="btn btn-link caosdb-update-entity-button caosdb-f-entity-cancel-button">Cancel</button>'); $(ent).find(".caosdb-f-edit-mode-entity-actions-panel").append(cancel_btn); @@ -1777,33 +1818,33 @@ var edit_mode = new function() { $(cancel_btn).click(callback); } - this.create_new_entity = async function(role) { + this.create_new_entity = async function (role) { var empty_entity = str2xml('<Response><' + role + '/></Response>'); return (await transformation.transformEntities(empty_entity))[0]; } - this.remove_cancel_button = function(entity) { + this.remove_cancel_button = function (entity) { $(entity).find('.caosdb-f-entity-cancel-button').remove() } /** * entity : HTMLElement */ - this.freeze_entity = function(entity) { + this.freeze_entity = function (entity) { $(entity).css("pointer-events", "none").css("filter", "blur(10px)"); } /** * entity : HTMLElement */ - this.unfreeze_entity = function(entity) { + this.unfreeze_entity = function (entity) { $(entity).css("pointer-events", "").css("filter", ""); } /** * this function is called in entity_palette.xsl */ - this.filter = function(ent_type) { + this.filter = function (ent_type) { var text = $("#caosdb-f-filter-" + ent_type).val(); if (ent_type == "properties") { var short_type = "p"; @@ -1844,14 +1885,14 @@ var edit_mode = new function() { * @return {HTMLElement} the entity form */ this.edit = function (entity) { - if (!this.is_edit_mode()) { - throw Error("edit_mode is not active"); - } - if (!edit_mode.app.can("startEdit")) { - throw Error("edit_mode.app does not allow to start the edit"); - } - edit_mode.app.startEdit(entity); - return edit_mode.app.entity; + if (!this.is_edit_mode()) { + throw Error("edit_mode is not active"); + } + if (!edit_mode.app.can("startEdit")) { + throw Error("edit_mode.app does not allow to start the edit"); + } + edit_mode.app.startEdit(entity); + return edit_mode.app.entity; } /** @@ -1859,19 +1900,19 @@ var edit_mode = new function() { * visible. */ const UPDATE_PERMISSIONS = [ - "UPDATE:DESCRIPTION", - "UPDATE:VALUE", - "UPDATE:ROLE", - "UPDATE:PARENT:REMOVE", - "UPDATE:PARENT:ADD", - "UPDATE:PROPERTY:REMOVE", - "UPDATE:PROPERTY:ADD", - "UPDATE:NAME", - "UPDATE:DATA_TYPE", - "UPDATE:FILE:REMOVE", - "UPDATE:FILE:ADD", - "UPDATE:FILE:MOVE", - "UPDATE:QUERY_TEMPLATE_DEFINITION", + "UPDATE:DESCRIPTION", + "UPDATE:VALUE", + "UPDATE:ROLE", + "UPDATE:PARENT:REMOVE", + "UPDATE:PARENT:ADD", + "UPDATE:PROPERTY:REMOVE", + "UPDATE:PROPERTY:ADD", + "UPDATE:NAME", + "UPDATE:DATA_TYPE", + "UPDATE:FILE:REMOVE", + "UPDATE:FILE:ADD", + "UPDATE:FILE:MOVE", + "UPDATE:QUERY_TEMPLATE_DEFINITION", ]; /** @@ -1885,7 +1926,7 @@ var edit_mode = new function() { * @parma {function} callback - the function which initializes and opens * the edit form. */ - this.add_start_edit_button = function(entity, callback) { + this.add_start_edit_button = function (entity, callback) { var has_any_update_permission = false; for (let permission of UPDATE_PERMISSIONS) { if (hasEntityPermission(entity, permission)) { @@ -1904,14 +1945,14 @@ var edit_mode = new function() { $(button).click(callback); } - this.remove_start_edit_button = function(entity) { + this.remove_start_edit_button = function (entity) { $(entity).find(".caosdb-f-entity-start-edit-button").remove(); } - this.add_new_record_button = function(entity, callback) { + this.add_new_record_button = function (entity, callback) { if (!hasEntityPermission(entity, "USE:AS_PARENT")) { - return; + return; } edit_mode.remove_new_record_button(entity); var button = $('<button title="Create a new Record from this RecordType." class="btn btn-link caosdb-update-entity-button caosdb-f-entity-new-record-button">+Record</button>'); @@ -1921,13 +1962,13 @@ var edit_mode = new function() { $(button).click(callback); } - this.remove_new_record_button = function(entity) { + this.remove_new_record_button = function (entity) { $(entity).find(".caosdb-f-entity-new-record-button").remove(); } - this.add_delete_button = function(entity, callback) { + this.add_delete_button = function (entity, callback) { if (!hasEntityPermission(entity, "DELETE")) { - return; + return; } edit_mode.remove_delete_button(entity); var button = $('<button title="Delete this ' + getEntityRole(entity) + '." class="btn btn-link caosdb-update-entity-button caosdb-f-entity-delete-button">Delete</button>'); @@ -1937,17 +1978,17 @@ var edit_mode = new function() { $(button).click(() => { // hide other elements const _alert = form_elements.make_alert({ - title: "Warning", - message: "You are going to delete this entity permanently. This cannot be undone.", - proceed_callback: () => { - edit_mode.delete_action(entity) - }, - cancel_callback: () => { - $(_alert).remove(); - $(entity).find(".caosdb-f-edit-mode-entity-actions-panel").show(); - }, - proceed_text: "Yes, delete!", - remember_my_decision_id : "delete_entity", + title: "Warning", + message: "You are going to delete this entity permanently. This cannot be undone.", + proceed_callback: () => { + edit_mode.delete_action(entity) + }, + cancel_callback: () => { + $(_alert).remove(); + $(entity).find(".caosdb-f-edit-mode-entity-actions-panel").show(); + }, + proceed_text: "Yes, delete!", + remember_my_decision_id: "delete_entity", }); $(_alert).addClass("text-end"); @@ -1956,7 +1997,7 @@ var edit_mode = new function() { }); } - this.remove_delete_button = function(entity) { + this.remove_delete_button = function (entity) { $(entity).find(".caosdb-f-entity-delete-button").remove(); } @@ -1968,6 +2009,6 @@ var edit_mode = new function() { /** * Add the extensions to the webui. */ -$(document).ready(function() { +$(document).ready(function () { edit_mode.init(); }); diff --git a/src/core/js/ext_autocomplete.js b/src/core/js/ext_autocomplete.js index cd145ee9e29bf370fd44f035f26d477a9cc0d478..080fc37f7f98840073e178405922aa10fed25ed1 100644 --- a/src/core/js/ext_autocomplete.js +++ b/src/core/js/ext_autocomplete.js @@ -42,12 +42,47 @@ var ext_autocomplete = new function () { "WHICH", "WITH", "CREATED BY", + "CREATED BY ME", + "CREATED AT", "CREATED ON", + "CREATED IN", + "CREATED BEFORE", + "CREATED UNTIL", + "CREATED AFTER", + "CREATED SINCE", "SOMEONE", "STORED AT", "HAS A PROPERTY", "HAS BEEN", "ANY VERSION OF", + "FROM", + "INSERTED AT", + "INSERTED ON", + "INSERTED IN", + "INSERTED BY", + "INSERTED BY ME", + "INSERTED BEFORE", + "INSERTED UNTIL", + "INSERTED AFTER", + "INSERTED SINCE", + "UPDATED AT", + "UPDATED ON", + "UPDATED IN", + "UPDATED BY", + "UPDATED BY ME", + "UPDATED BEFORE", + "UPDATED UNTIL", + "UPDATED AFTER", + "UPDATED SINCE", + "SINCE", + "BEFORE", + "ON", + "IN", + "AFTER", + "UNTIL", + "AT", + "BY", + "BY ME", ]; this.version = "0.1"; @@ -158,4 +193,4 @@ $(document).ready(function () { if ("${BUILD_MODULE_EXT_AUTOCOMPLETE}" == "ENABLED") { caosdb_modules.register(ext_autocomplete); } -}); +}); \ No newline at end of file diff --git a/src/core/js/webcaosdb.js b/src/core/js/webcaosdb.js index 4142575d96b197ecd2608a4f3b520203bd9dbbd7..89a729da6441900124fd8e04261a7f0c8d8d9a78 100644 --- a/src/core/js/webcaosdb.js +++ b/src/core/js/webcaosdb.js @@ -608,15 +608,6 @@ this.transformation = new function () { let html = await asyncXslt(xml, xslt); return html; } - /** - * @param {XMLDocument} xml - * @return {HTMLElement[]} an array of HTMLElements. - */ - this.transformEntityPalette = async function _tME(xml) { - var xsl = await transformation.retrieveXsltScript("entity_palette.xsl"); - let html = await asyncXslt(xml, xsl); - return html; - } /** * Retrieve the entity.xsl script and modify it such that we can use it diff --git a/src/core/xsl/entity_palette.xsl b/src/core/xsl/entity_palette.xsl index e45364c80a4ba3849d5b9abaf29c5b85c1b76af1..9d3a13f1c96a7ce864554b78b9d9e0dbebfb3c5f 100644 --- a/src/core/xsl/entity_palette.xsl +++ b/src/core/xsl/entity_palette.xsl @@ -3,19 +3,13 @@ <xsl:output method="html"/> <xsl:template match="/Response"> - <div class="list-group list-group-flush"> - <div class="list-group-item btn-group-vertical caosdb-v-editmode-btngroup caosdb-f-edit-mode-create-buttons"> - <button type="button" class="btn btn-secondary caosdb-f-edit-panel-new-button new-property">Create Property</button> - <button type="button" class="btn btn-secondary caosdb-f-edit-panel-new-button new-recordtype">Create RecordType</button> - </div> <div title="Drag and drop Properties from this panel to the Entities on the left." class="caosdb-v-editmode-existing caosdb-f-edit-mode-existing d-none"> <div class="card-header"> - <h6>Existing Properties</h6> + <span class="card-title">Existing Properties</span> </div> <div class="card"> <div class="input-group" style="width: 100%;"> <input class="form-control" placeholder="filter..." title="Type a name (full or partial)." oninput="edit_mode.filter('properties');" id="caosdb-f-filter-properties" type="text"/> - <button class="btn btn-secondary caosdb-f-edit-panel-new-button new-property caosdb-f-hide-on-empty-input" title="Create this Property." ><i class="bi-plus"></i></button> </div> <ul class="caosdb-v-edit-list list-group"> <xsl:apply-templates select="./Property"/> @@ -24,24 +18,22 @@ </div> <div title="Drag and drop RecordTypes from this panel to the Entities on the left." class="caosdb-v-editmode-existing caosdb-f-edit-mode-existing d-none"> <div class="card-header"> - <h6>Existing RecordTypes</h6> + <span class="card-title">Existing RecordTypes</span> </div> <div class="card"> <div class="input-group" style="width: 100%;"> <input class="form-control" placeholder="filter..." title="Type a name (full or partial)." oninput="edit_mode.filter('recordtypes');" id="caosdb-f-filter-recordtypes" type="text"/> - <button class="btn btn-secondary caosdb-f-edit-panel-new-button new-recordtype caosdb-f-hide-on-empty-input" title="Create this RecordType"><i class="bi-plus"></i></button> </div> <ul class="caosdb-v-edit-list list-group"> <xsl:apply-templates select="./RecordType"/> </ul> </div> - </div> </div> </xsl:template> <xsl:template match="RecordType"> <xsl:if test="string-length(@name)>0"> - <li class="caosdb-f-edit-drag list-group-item caosdb-v-edit-drag"> + <li draggable="true" class="caosdb-f-edit-drag list-group-item caosdb-v-edit-drag"> <xsl:attribute name="id">caosdb-f-edit-rt-<xsl:value-of select="@id"/></xsl:attribute> <xsl:value-of select="@name"/> </li> @@ -60,7 +52,7 @@ <!-- ignore unit property --> </xsl:when> <xsl:otherwise> - <li class="caosdb-f-edit-drag list-group-item caosdb-v-edit-drag"> + <li draggable="true" class="caosdb-f-edit-drag list-group-item caosdb-v-edit-drag"> <xsl:attribute name="id">caosdb-f-edit-p-<xsl:value-of select="@id"/></xsl:attribute> <xsl:value-of select="@name"/> </li> diff --git a/src/core/xsl/main.xsl b/src/core/xsl/main.xsl index c14c7ff28f3d172f6c067740fedfcfe036546bb5..205cc1dbc8ab7decfecca3f9690196bac09e8dbb 100644 --- a/src/core/xsl/main.xsl +++ b/src/core/xsl/main.xsl @@ -120,7 +120,14 @@ <div class="card-header"> <span class="card-title">Edit Mode Toolbox</span> </div> - <div class="caosdb-f-edit-panel-body"></div> + <div class="caosdb-f-edit-panel-body"> + <div class="list-group list-group-flush"> + <div class="list-group-item btn-group-vertical caosdb-v-editmode-btngroup caosdb-f-edit-mode-create-buttons"> + <button type="button" class="btn btn-secondary caosdb-f-edit-panel-new-button new-property">Create Property</button> + <button type="button" class="btn btn-secondary caosdb-f-edit-panel-new-button new-recordtype">Create RecordType</button> + </div> + </div> + </div> </div> </div> </div> diff --git a/src/doc/tutorials/edit_mode.rst b/src/doc/tutorials/edit_mode.rst index 01502f55d5d76959a17bf74c71632d0d902f625e..3e7d9ffb4472ab8ff0ec9a4539ab9ab11563ffd2 100644 --- a/src/doc/tutorials/edit_mode.rst +++ b/src/doc/tutorials/edit_mode.rst @@ -183,11 +183,3 @@ stored within ``/uploaded.by/<REALM>/<USER>/``. The same is true for properties with data type ``REFERENCE``, too. In that case, the Record of the file that is uploaded will be assigned the RecordType of value of the original reference property. - -.. warning:: - - Until `this bug - <https://gitlab.indiscale.com/caosdb/src/caosdb-webui/-/issues/200>`_ - has been fixed, the upload button is broken and does not open the - upload dialogue. - diff --git a/src/ext/js/fileupload.js b/src/ext/js/fileupload.js index 4e9f7cd02a4f3c5e63080f1e520c10a9328bfe22..31d86589286f1761f481cc9d9bb6a557a63cbce1 100644 --- a/src/ext/js/fileupload.js +++ b/src/ext/js/fileupload.js @@ -25,18 +25,31 @@ var fileupload = new function() { // TODO * action to config * upload-path id -> class * message configurable // * style path input - const _modal_str = ` <div class="modal fade" tabindex="-1" role="dialog"> - <div class="modal-dialog modal-lg" role="document"> <div - class="modal-content"> <div class="modal-header"> <button type="button" - class="btn-close" data-bs-dismiss="modal">×</button> <h4 - class="modal-title">File Upload</h4> </div> <div class="modal-body"> <form - action="/Entity/" class="dropzone dz-clickable" > <label>path</label><input - id="upload-path" type="text" value="/"/> <div class="dz-message"> - Drag'n'drop files to this area or click to upload. </div> </form> </div> - <div class="modal-footer"> <button type="button" class="btn btn-secondary - caosdb-f-file-upload-submit-button">Ok</button> <button type="button" - class="btn btn-secondary" data-bs-dismiss="modal">Close</button> </div> </div> - </div> </div>`; + const _modal_str = ` +<div class="modal fade" tabindex="-1" role="dialog"> + <div class="modal-dialog modal-lg" role="document"> + <div class="modal-content"> + <div class="modal-header"> + <h4 class="modal-title">File Upload</h4> + <button type="button" class="btn-close" data-bs-dismiss="modal"> + </button> + </div> + <div class="modal-body"> + <form action="/Entity/" class="dropzone dz-clickable" > + <label>path</label> + <input id="upload-path" type="text" value="/"/> + <div class="dz-message"> + Drag'n'drop files to this area or click to upload. + </div> + </form> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-secondary caosdb-f-file-upload-submit-button">Ok</button> + <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button> + </div> + </div> + </div> +</div>`; /** Create a dropzone.js form for the file upload. */ @@ -122,7 +135,7 @@ var fileupload = new function() { input.after(`<div class="alert alert-danger alert-dismissible" role="alert"> <button type="button" class="btn-close" data-bs-dismiss="alert" - aria-label="Close"><span aria-hidden="true">×</span></button> + aria-label="Close"></button> <strong>Error!</strong> You are not logged in!.</div>`); } else { globalError(event, error, xhr); @@ -188,7 +201,7 @@ var fileupload = new function() { input.after(`<div class="alert alert-success alert-dismissible" role="alert"> <button type="button" class="btn-close" data-bs-dismiss="alert" - aria-label="Close"><span aria-hidden="true">×</span></button> + aria-label="Close"></button> <strong>Success!</strong> The file <code class="caosdb-f-file-upload-file-name">` + getEntityName(entity) + `</code> has been uploaded.</div>`); @@ -308,7 +321,7 @@ var fileupload = new function() { error_handler, atom_par); var toggle_function = function() { - $(modal).modal() + $(modal).modal("toggle"); }; this.add_file_upload_button(edit_menu, button, toggle_function); diff --git a/test/core/js/modules/edit_mode.js.js b/test/core/js/modules/edit_mode.js.js index 905b57ef0947f652e5d4959acd802244f0d4998d..3a87ee73d167d114d0b7db2b0abe50e4a643f8fa 100644 --- a/test/core/js/modules/edit_mode.js.js +++ b/test/core/js/modules/edit_mode.js.js @@ -420,10 +420,6 @@ QUnit.test("get_edit_panel", function (assert) { assert.ok(edit_mode.get_edit_panel); }); -QUnit.test("add_wait_datamodel_info", function (assert) { - assert.ok(edit_mode.add_wait_datamodel_info); -}); - QUnit.test("toggle_edit_panel", function (assert) { assert.ok(edit_mode.toggle_edit_panel); }); @@ -485,97 +481,90 @@ QUnit.test("remove_delete_button", function (assert) { }); -{ - - const datamodel = ` -<div><div class=\"btn-group-vertical\"><button type=\"button\" class=\"btn btn-secondary caosdb-f-edit-panel-new-button new-property\">Create new Property</button><button type=\"button\" class=\"btn btn-secondary caosdb-f-edit-panel-new-button new-recordtype\">Create new RecordType</button></div><div title=\"Drag and drop Properties from this panel to the Entities on the left.\" class=\"card\"><div class=\"card-header\"><h5>Existing Properties</h5></div><div class=\"card-body\"><div class=\"input-group\" style=\"width: 100%;\"><input class=\"form-control\" placeholder=\"filter...\" title=\"Type a name (full or partial).\" oninput=\"edit_mode.filter('properties');\" id=\"caosdb-f-filter-properties\" type=\"text\" /><span class=\"input-group-btn\"><button class=\"btn btn-secondary caosdb-f-edit-panel-new-button new-property caosdb-f-hide-on-empty-input\" title=\"Create this Property.\"><span class=\"glyphicon glyphicon-plus\"></span></button></span></div><ul class=\"caosdb-v-edit-list\"><li class=\"caosdb-f-edit-drag list-group-item caosdb-v-edit-drag\" id=\"caosdb-f-edit-p-20\">name</li><li class=\"caosdb-f-edit-drag list-group-item caosdb-v-edit-drag\" id=\"caosdb-f-edit-p-21\">unit</li><li class=\"caosdb-f-edit-drag list-group-item caosdb-v-edit-drag\" id=\"caosdb-f-edit-p-24\">description</li></ul></div></div><div title=\"Drag and drop RecordTypes from this panel to the Entities on the left.\" class=\"card\"><div class=\"card-header\"><h5>Existing RecordTypes</h5></div><div class=\"card-body\"><div class=\"input-group\" style=\"width: 100%;\"><input class=\"form-control\" placeholder=\"filter...\" title=\"Type a name (full or partial).\" oninput=\"edit_mode.filter('recordtypes');\" id=\"caosdb-f-filter-recordtypes\" type=\"text\" /><span class=\"input-group-btn\"><button class=\"btn btn-secondary caosdb-f-edit-panel-new-button new-recordtype caosdb-f-hide-on-empty-input\" title=\"Create this RecordType\"><span class=\"glyphicon glyphicon-plus\"></span></button></span></div><ul class=\"caosdb-v-edit-list\"><li class=\"caosdb-f-edit-drag list-group-item caosdb-v-edit-drag\" id=\"caosdb-f-edit-rt-30992\">Test</li><li class=\"caosdb-f-edit-drag list-group-item caosdb-v-edit-drag\" id=\"caosdb-f-edit-rt-31015\">Test2</li></ul></div></div></div>`; - - edit_mode.query = async function(q) { - return []; - } +edit_mode.query = async function(q) { + return []; +} - QUnit.test("test case 1 - insert property", async function (assert) { +QUnit.test("test case 1 - insert property", async function (assert) { - // here lives the test tool box - const test_tool_box = $('<div class="caosdb-f-edit-panel-body" />'); + // here lives the test tool box + const test_tool_box = $('<div class="caosdb-f-edit-panel-body" > <div class="list-group list-group-flush"> <div class="list-group-item btn-group-vertical caosdb-v-editmode-btngroup caosdb-f-edit-mode-create-buttons"> <button type="button" class="btn btn-secondary caosdb-f-edit-panel-new-button new-property">Create Property</button> <button type="button" class="btn btn-secondary caosdb-f-edit-panel-new-button new-recordtype">Create RecordType</button> </div> </div>'); - // here live the entities - const main_panel = $('<div class="caosdb-f-main-entities"/>'); - assert.equal($(".caosdb-f-main-entities").length, 0); + // here live the entities + const main_panel = $('<div class="caosdb-f-main-entities"/>'); + assert.equal($(".caosdb-f-main-entities").length, 0); - $(document.body).append(test_tool_box).append(main_panel); + $(document.body).append(test_tool_box).append(main_panel); - // ENTER EDIT MODE - assert.equal(edit_mode.is_edit_mode(), false, "edit_mode should not be active"); - // fake server response - edit_mode.retrieve_data_model = async function () { - return str2xml(datamodel); - } - var app = await edit_mode.enter_edit_mode(); - assert.equal(edit_mode.is_edit_mode(), true, "now, edit_mode should be active"); + // ENTER EDIT MODE + assert.equal(edit_mode.is_edit_mode(), false, "edit_mode should not be active"); + // fake server response + edit_mode.retrieve_data_model = async function () { + return str2xml("<Response/>"); + } + var app = await edit_mode.enter_edit_mode(); + assert.equal(edit_mode.is_edit_mode(), true, "now, edit_mode should be active"); - // NEW PROPERTY - assert.equal($(".caosdb-f-edit-panel-new-button.new-property").length, 2, "two new-property buttons should be present"); - assert.equal($(".caosdb-entity-panel").length, 0, "no entities"); - assert.equal(app.state, "initial", "initial state"); - // click on "new property" - $(".caosdb-f-edit-panel-new-button.new-property").first().click(); + // NEW PROPERTY + assert.equal($(".caosdb-f-edit-panel-new-button.new-property").length, 1, "one new-property button should be present"); + assert.equal($(".caosdb-entity-panel").length, 0, "no entities"); + assert.equal(app.state, "initial", "initial state"); + // click on "new property" + $(".caosdb-f-edit-panel-new-button.new-property").first().click(); - while (app.state === "initial") { - await sleep(500); - } - - // EDIT PROPERTY - assert.equal(app.state, "changed", "changed state"); - var entity = $(".caosdb-entity-panel"); - assert.equal(entity.length, 1, "entity added"); - // set name - $(".caosdb-entity-panel .caosdb-f-entity-name").val("TestProperty"); - - // SAVE - var save_button = $(".caosdb-f-entity-save-button"); - assert.equal(save_button.length, 1, "save button available"); - // fake server response - connection.post = async function (uri, data) { - await sleep(500); - assert.equal(xml2str(data), "<Request><Property name=\"TestProperty\" datatype=\"TEXT\"/></Request>"); - assert.equal(app.state, "wait", "in wait state"); - return str2xml("<Response><Property id=\"newId\" name=\"TestProperty\" datatype=\"TEXT\"/></Response>"); - } - // click save button - var updated_entity = main_panel.find(".caosdb-entity-panel .caosdb-id:contains('newId')"); - assert.equal(updated_entity.length, 0, "entity with id not yet in main panel"); - save_button.click(); + while (app.state === "initial") { + await sleep(500); + } - while (app.state === "changed" || app.state === "wait") { - await sleep(500); - } + // EDIT PROPERTY + assert.equal(app.state, "changed", "changed state"); + var entity = $(".caosdb-entity-panel"); + assert.equal(entity.length, 1, "entity added"); + // set name + $(".caosdb-entity-panel .caosdb-f-entity-name").val("TestProperty"); + + // SAVE + var save_button = $(".caosdb-f-entity-save-button"); + assert.equal(save_button.length, 1, "save button available"); + // fake server response + connection.post = async function (uri, data) { + await sleep(500); + assert.equal(xml2str(data), "<Request><Property name=\"TestProperty\" datatype=\"TEXT\"/></Request>"); + assert.equal(app.state, "wait", "in wait state"); + return str2xml("<Response><Property id=\"newId\" name=\"TestProperty\" datatype=\"TEXT\"/></Response>"); + } + // click save button + var updated_entity = main_panel.find(".caosdb-entity-panel .caosdb-id:contains('newId')"); + assert.equal(updated_entity.length, 0, "entity with id not yet in main panel"); + save_button.click(); - // SEE RESPONSE - assert.equal(app.state, "initial", "initial state"); + while (app.state === "changed" || app.state === "wait") { + await sleep(500); + } - var response = $("#newId"); - assert.equal(response.length, 1, "entity added"); + // SEE RESPONSE + assert.equal(app.state, "initial", "initial state"); - // entity has been added to main panel - updated_entity = main_panel.find(".caosdb-entity-panel .caosdb-id:contains('newId')"); - assert.equal(updated_entity.length, 1, "entity with new id now in main panel"); + var response = $("#newId"); + assert.equal(response.length, 1, "entity added"); - // tests for closed issue https://gitlab.com/caosdb/caosdb-webui/issues/47 - assert.equal(main_panel.find(".caosdb-entity-panel .caosdb-entity-actions-panel").length, 1, "general actions panel there"); - assert.equal(main_panel.find(".caosdb-entity-panel .caosdb-f-edit-mode-entity-actions-panel").length, 1, "edit_mode actions panel there (BUG caosdb-webui#47)"); + // entity has been added to main panel + updated_entity = main_panel.find(".caosdb-entity-panel .caosdb-id:contains('newId')"); + assert.equal(updated_entity.length, 1, "entity with new id now in main panel"); - main_panel.remove(); - test_tool_box.remove(); + // tests for closed issue https://gitlab.com/caosdb/caosdb-webui/issues/47 + assert.equal(main_panel.find(".caosdb-entity-panel .caosdb-entity-actions-panel").length, 1, "general actions panel there"); + assert.equal(main_panel.find(".caosdb-entity-panel .caosdb-f-edit-mode-entity-actions-panel").length, 1, "edit_mode actions panel there (BUG caosdb-webui#47)"); - edit_mode.leave_edit_mode(); - assert.equal(edit_mode.is_edit_mode(), false, "edit_mode should not be active"); + main_panel.remove(); + test_tool_box.remove(); - }); + edit_mode.leave_edit_mode(); + assert.equal(edit_mode.is_edit_mode(), false, "edit_mode should not be active"); -} +}); var transformProperty = async function (xml_str) { diff --git a/test/docker/Dockerfile b/test/docker/Dockerfile index 92889dc526229475463ddcdfe6a2080a669b9d25..f90db2f9b2b869e12f52de595b7a7677f52374b5 100644 --- a/test/docker/Dockerfile +++ b/test/docker/Dockerfile @@ -13,8 +13,8 @@ RUN apt-get update \ && apt-get install -f RUN pip3 install pylint pytest -RUN pip3 install caosdb==0.5.1 -RUN pip3 install pandas xlrd==1.2.0 +RUN pip3 install caosdb>=0.5.2 +RUN pip3 install pandas RUN pip3 install git+https://gitlab.com/caosdb/caosdb-advanced-user-tools.git@dev # For automatic documentation #RUN npm install -g jsdoc