diff --git a/CHANGELOG.md b/CHANGELOG.md index 755d9b5accc5998b3c8014aa4e2217c6824083e0..84d0a7b55d2d81ded244140b9beab0d3ea54069c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added ### +* `BUILD_EDIT_MODE_LAZY_DROPDOWN_LOADING` build variable that + determines whether reference dropdowns in the edit mode are filled + automatically or only after manually clicking on "edit this + property". Default is `ENABLED`, i.e., don't load the dropdown + automatically. +* `caosdb-v-property-not-yet-lazy-loaded` class for reference + properties in the edit mode, the reference dropdown menu of which + hasn't been initialized yet. + ### Changed ### +* [#262](https://gitlab.com/linkahead/linkahead-webui/-/issues/262) + Reference dropdowns in the edit mode don't load automatically by + default anymore but have to be initialized manually and per property + by default. Set `BUILD_EDIT_MODE_LAZY_DROPDOWN_LOADING=DISABLED` to + restore the previous behavior. + ### Deprecated ### ### Removed ### diff --git a/build.properties.d/00_default.properties b/build.properties.d/00_default.properties index 17d3d2fdf979f373073f9f5efb19085b124200a6..6b15cbf2a696d4faa751ea5eb996fe760995ba0c 100644 --- a/build.properties.d/00_default.properties +++ b/build.properties.d/00_default.properties @@ -158,6 +158,12 @@ BUILD_MODULE_EXT_TRIGGER_CRAWLER_FORM_TOOLBOX="Tools" ############################################################################## BUILD_MAX_EDIT_MODE_DROPDOWN_OPTIONS=-1 +############################################################################## +# Toggle lazy-loading of reference dropdowns. ENABLED is especially +# recommended in case of instances with a lot of entities. +############################################################################## +BUILD_EDIT_MODE_LAZY_DROPDOWN_LOADING=ENABLED + ############################################################################## # Build a dist file containing all JS code from the files in the # MODULE_DEPENDENCIES array. diff --git a/src/core/js/edit_mode.js b/src/core/js/edit_mode.js index 5b97fe30d5ecd1cc4bd4e00bff366014d6dc58a9..ff5ce7306317c7807a73e10378095dcec600f85f 100644 --- a/src/core/js/edit_mode.js +++ b/src/core/js/edit_mode.js @@ -29,7 +29,7 @@ /** * Edit mode module */ -var edit_mode = new function () { +var edit_mode = new function() { var logger = log.getLogger("edit_mode"); @@ -65,10 +65,16 @@ var edit_mode = new function () { */ this.property_data_type_changed = new Event("caosdb.edit_mode.property_data_type_changed"); + /** + * Fired when a reference property is editted. Only relevant if + * BUILD_EDIT_MODE_LAZY_DROPDOWN_LOADING=ENABLED. + */ + this.property_edit_reference_value = new Event("caosdb.edit_mode.property_edit_reference_value"); + /** * Initialize this module */ - this.init = function () { + this.init = function() { if (isAuthenticated()) { this._init(); } else { @@ -76,17 +82,17 @@ var edit_mode = new function () { } } - this.has_edit_fragment = function () { + this.has_edit_fragment = function() { const fragment = window.location.hash.substr(1); return fragment === "edit"; } - this.has_new_record_fragment = function () { + this.has_new_record_fragment = function() { const fragment = window.location.hash.substr(1); return fragment === "new_record"; } - this._init = function () { + this._init = function() { var target = $("#top-navbar").find("ul").first(); this.add_edit_mode_button(target, edit_mode.toggle_edit_mode); @@ -135,15 +141,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); @@ -156,7 +162,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"); } @@ -176,7 +182,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]; @@ -201,7 +207,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("-"); @@ -220,15 +226,15 @@ 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(); @@ -248,7 +254,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(); @@ -256,7 +262,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; @@ -276,7 +282,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); @@ -291,11 +297,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); @@ -330,7 +336,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") { @@ -346,7 +352,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); } @@ -354,7 +360,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"); } @@ -370,7 +376,7 @@ 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) { button.attr("title", title); @@ -399,7 +405,7 @@ var edit_mode = new function () { * @param {str} datatype * @returns {object} */ - this.get_datatype_from_string = function (datatype) { + this.get_datatype_from_string = function(datatype) { var atomic = datatype; const is_list = edit_mode.isListDatatype(datatype); @@ -438,7 +444,7 @@ var edit_mode = new function () { * @param {object} datatype * @returns {str} */ - this.get_datatype_str = function (datatype) { + this.get_datatype_str = function(datatype) { var result = datatype["atomic_datatype"]; const ref = datatype["reference_scope"]; @@ -462,7 +468,7 @@ 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) { @@ -478,7 +484,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; @@ -506,11 +512,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(); // subseconds are stored in the subsec attribue of the input element @@ -522,6 +528,11 @@ var edit_mode = new function () { } } + // The property is a reference, but wasn't editted (the + // dropdown options were never loaded, so leave unchanged). + if (property.reference && ("${BUILD_EDIT_MODE_LAZY_DROPDOWN_LOADING}" == "ENABLED") && $(element).hasClass("caosdb-v-property-not-yet-lazy-loaded")) { + return property; + } // LISTs need to be handled here if (property.list == true) { property.value = []; @@ -565,7 +576,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); @@ -579,14 +590,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); @@ -597,7 +608,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(); @@ -610,13 +621,13 @@ 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() {} /** * Initializes the edit mode and loads the tool box. */ - this.enter_edit_mode = async function (editApp = undefined) { + this.enter_edit_mode = async function(editApp = undefined) { window.localStorage.edit_mode = "true"; try { @@ -630,7 +641,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); }; @@ -643,7 +654,7 @@ var edit_mode = new function () { /** * (Re-)load the toolbox, i.e. retrieve Properties and RecordTypes */ - this.load_edit_mode_toolbox = async function () {} + this.load_edit_mode_toolbox = async function() {} /** * Retrieve all properties and record types and convert them into @@ -651,7 +662,7 @@ var edit_mode = new function () { * * @return {Promise<XMLDocument>} An array of entities in xml representation. */ - this.retrieve_data_model = async function () { + this.retrieve_data_model = async function() { const props_prom = connection.get("Entity/?query=FIND Property"); const rts_prom = connection.get("Entity/?query=FIND RecordType"); @@ -680,7 +691,7 @@ var edit_mode = new function () { * Remove all warnings and set the HTML of the toolbox panel to * the model given by model. */ - this.init_tool_box = async function () { + this.init_tool_box = async function() { // remove previously added model $(".caosdb-f-edit-mode-existing").remove() @@ -708,9 +719,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(); @@ -723,16 +734,16 @@ 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._init_select = function (select) { + this._init_select = function(select) { $(select).selectpicker(); } - 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(); @@ -787,11 +798,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); } @@ -810,10 +821,10 @@ 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 () { + $(datatype).find("select").change(function() { const new_type = $(this).val(); logger.debug(`datatype changed to ${new_type}.`); edit_mode.on_datatype_change(form, new_type); @@ -831,7 +842,7 @@ var edit_mode = new function () { * @param {string} new_type - the new datatype which is used by this * listener to determine which fields need to be enabled or disabled. */ - this.on_datatype_change = function (form, new_type) { + this.on_datatype_change = function(form, new_type) { logger.trace('enter on_datatype_change', form, new_type); if (new_type === "REFERENCE") { @@ -854,7 +865,7 @@ 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", @@ -885,7 +896,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. @@ -945,11 +956,11 @@ var edit_mode = new function () { return form_group[0]; } - this.make_input = function (label, value) { + 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); @@ -967,7 +978,7 @@ var edit_mode = new function () { * optional and only used for reference properties. This parameter * might as well be a Promise for such an array. */ - this.createElementForProperty = function (property, options) { + this.createElementForProperty = function(property, options) { var result; if (property.datatype == "TEXT") { result = `<textarea>${property.value || ""}</textarea>`; @@ -1030,7 +1041,7 @@ var edit_mode = new function () { if (_options.length > 0 && _options[0].getAttribute('value') == "NA") { // The button to show the input field for manual ID insertion var manualInsertButton = $('<button title="Insert Entity Id manually." class="btn btn-link caosdb-update-entity-button caosdb-f-list-item-button"><i class="bi-pencil"></i></button>'); - $(manualInsertButton).click(function () { + $(manualInsertButton).click(function() { // toggle input // forward: if ($(result).find('input.caosdb-f-manual-id-insert').is(':hidden')) { @@ -1049,7 +1060,7 @@ var edit_mode = new function () { var idinput = $("<input type='number' class='caosdb-f-manual-id-insert' value=''></input>") idinput.hide() // Add callback for pressing Enter: the insertion is completed - idinput.on('keyup', function (e) { + idinput.on('keyup', function(e) { if (e.key === 'Enter' || e.keyCode === 13) { // hide the input, show the dropdown again and append the value that // was entered to the candidates and select that item @@ -1089,10 +1100,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(); @@ -1102,7 +1113,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, @@ -1131,7 +1142,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]; } @@ -1146,7 +1157,7 @@ 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 = []; @@ -1184,7 +1195,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, @@ -1233,7 +1244,7 @@ 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 @@ -1270,7 +1281,7 @@ 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); @@ -1297,7 +1308,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); } @@ -1309,7 +1320,7 @@ var edit_mode = new function () { * @param {boolean} list - whether the property is a list, initially. * @param {string} datatype - the initial data type of the property. */ - this.add_toggle_list_checkbox = function (element, list, datatype) { + this.add_toggle_list_checkbox = function(element, list, datatype) { var editfield = $(element).find(".caosdb-f-property-value"); var label = $('<label>List</label>'); var checkbox = $('<input type="checkbox" class="form-check-input caosdb-f-entity-is-list"/>'); @@ -1375,7 +1386,7 @@ 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") @@ -1389,6 +1400,36 @@ var edit_mode = new function () { // create inputs + if (property.reference && ("${BUILD_EDIT_MODE_LAZY_DROPDOWN_LOADING}" == "ENABLED")) { + // Only add button to load reference options lazily. + const editValueButton = $(`<button title="Edit this property" class="btn btn-link caosdb-f-edit-reference-property-button"><i class="bi-pencil"/></button>`); + // Set data-lazy-loaded to false to indicate that it hasn't been initialized. + $(element).addClass("caosdb-v-property-not-yet-lazy-loaded"); + $(editValueButton).click(() => { + // Remove edit and trash button + $(element).find(".caosdb-f-edit-reference-property-button").remove(); + $(element).find(".caosdb-f-property-trash-button").remove(); + // Create the input + edit_mode._addPropertyEditInputs(element, property, editfield); + element.dispatchEvent(edit_mode.property_edit_reference_value); + $(element).removeClass("caosdb-v-property-not-yet-lazy-loaded"); + }); + // append button + $(element).find(".caosdb-property-edit").append(editValueButton); + // Trash button can exist without having to load all options + edit_mode.add_property_trash_button($(element).find(".caosdb-property-edit")[0], element); + // No preview of references in edit mode (might mess up the update) + $(element).find(".caosdb-show-preview-button").remove(); + } else { + edit_mode._addPropertyEditInputs(element, property, editfield); + } + } + + /** + * Add input elements for value, units, list toggling, and + * deletion to a property row. + */ + this._addPropertyEditInputs = function(element, property, editfield) { var inputs = edit_mode.create_value_inputs(property); editfield.children().remove(); editfield.append(inputs); @@ -1410,7 +1451,7 @@ var edit_mode = new function () { * * Currently name is ignored. TODO: check whether that behavior is intended. */ - this.create_new_record = async function (recordtype_id, name = undefined) { + 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]), @@ -1431,9 +1472,9 @@ var edit_mode = new function () { * * This is now done using bubbling so that the listeners are also dynamically updated. */ - this.init_dragable = function () { + this.init_dragable = function() { // Bubbling: Add the listener to the document and check whether the class is present. - document.addEventListener("dragstart", function (event) { + document.addEventListener("dragstart", function(event) { if (event.target.classList.contains("caosdb-f-edit-drag")) { edit_mode.dragstart(event); } @@ -1444,7 +1485,7 @@ var edit_mode = new function () { * 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 @@ -1488,17 +1529,17 @@ 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'); // handler for new property button @@ -1551,7 +1592,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({ @@ -1591,9 +1632,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 @@ -1616,17 +1657,17 @@ 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); @@ -1634,7 +1675,7 @@ var edit_mode = new function () { 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 @@ -1664,9 +1705,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); @@ -1675,7 +1716,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]; @@ -1690,11 +1731,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 => { @@ -1703,7 +1744,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); @@ -1726,13 +1767,13 @@ var edit_mode = new function () { } 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"); } @@ -1760,7 +1801,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 => { @@ -1773,7 +1814,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) { @@ -1781,7 +1822,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. @@ -1806,38 +1847,38 @@ 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); } }); } - this.add_property_dropzone = function (entity) { + this.add_property_dropzone = function(entity) { $(entity).find("ul.caosdb-properties") .append('<li class="caosdb-v-edit-mode-dropzone caosdb-f-edit-mode-property-dropzone caosdb-v-edit-mode-property-dropzone">Drag and drop Properties and RecordTypes from the Edit Mode Toolbox here.</li>'); } - this.add_parent_dropzone = function (entity) { + this.add_parent_dropzone = function(entity) { $(entity).find(".caosdb-f-parent-list") .addClass("caosdb-v-edit-mode-parent-dropzone") .addClass("caosdb-v-edit-mode-dropzone") .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++) { @@ -1871,7 +1912,7 @@ var edit_mode = new function () { * a Promise for such an array. * @param {String} [current_value] - The value which is to be selected. */ - this.fill_reference_drop_down = function (drop_down, options, current_value) { + this.fill_reference_drop_down = function(drop_down, options, current_value) { drop_down = $(drop_down); drop_down.append($(options).clone()); @@ -1892,7 +1933,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) { const find_entity = ["FILE", "REFERENCE"].includes(datatype) ? "" : `"${datatype}"`; const max_options = parseInt("${BUILD_MAX_EDIT_MODE_DROPDOWN_OPTIONS}"); //for each query; there might be more candidates in total @@ -1920,32 +1961,32 @@ var edit_mode = new function () { return options; } - 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.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"); 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. @@ -1953,18 +1994,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); @@ -1972,35 +2013,35 @@ 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>'); var ent_element = await transformation.transformEntities(empty_entity); $(ent_element).addClass("caosdb-v-entity-being-edited"); return ent_element[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"; @@ -2040,7 +2081,7 @@ var edit_mode = new function () { * @param {HTMLElement} entity - the entity which is to be changed. * @return {HTMLElement} the entity form */ - this.edit = function (entity) { + this.edit = function(entity) { if (!this.is_edit_mode()) { throw Error("edit_mode is not active"); } @@ -2082,7 +2123,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)) { @@ -2101,12 +2142,12 @@ 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; } @@ -2118,11 +2159,11 @@ 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; } @@ -2153,7 +2194,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(); } @@ -2180,6 +2221,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/fileupload.js b/src/core/js/fileupload.js index c5995c0594e58c155c2b46bb8c6d272bd21b84c9..cd272117e92d89e28850c2ba4af2678dcb40c438 100644 --- a/src/core/js/fileupload.js +++ b/src/core/js/fileupload.js @@ -261,18 +261,29 @@ var fileupload = new function() { } this.init = function() { - // add global listener for start_edit event - document.body.addEventListener(edit_mode.start_edit.type, function(e) { - $(e.target).find(".caosdb-properties .caosdb-f-entity-property").each(function(idx) { - fileupload.create_upload_app(this); - }); - }, true); - - // add global listener for property_added event - document.body.addEventListener(edit_mode.property_added.type, function(e) { - fileupload.create_upload_app(e.target); - }, true); + if ("${BUILD_EDIT_MODE_LAZY_DROPDOWN_LOADING}" == "ENABLED") { + // If we have to manually enable editting of reference + // properties, we only want to add the file-upload button + // **after** we started editing the particular + // property. So no global event listener and not + // immediatly after adding a new reference property. + document.body.addEventListener(edit_mode.property_edit_reference_value.type, function(e) { + fileupload.create_upload_app(e.target); + }, true); + } else { + // add global listener for start_edit event + document.body.addEventListener(edit_mode.start_edit.type, function(e) { + $(e.target).find(".caosdb-properties .caosdb-f-entity-property").each(function(idx) { + fileupload.create_upload_app(this); + }); + }, true); + + // add global listener for property_added event + document.body.addEventListener(edit_mode.property_added.type, function(e) { + fileupload.create_upload_app(e.target); + }, true); + } // add global listener for data_type_changed event document.body.addEventListener(edit_mode.property_data_type_changed.type, function(e) { @@ -296,7 +307,7 @@ var fileupload = new function() { this.create_upload_app = function(target) { const non_file_datatypes = ["TEXT", "DOUBLE", "BOOLEAN", "INTEGER", "DATETIME"]; var par = getPropertyDatatype(target); - var atom_par = par && par.startsWith("LIST<") && par.endsWith(">") ? par.substring(5, par.length-1) : par; + var atom_par = par && par.startsWith("LIST<") && par.endsWith(">") ? par.substring(5, par.length - 1) : par; if (non_file_datatypes.indexOf(atom_par) !== -1) { return; @@ -338,7 +349,7 @@ var fileupload = new function() { */ this.get_default_path = function() { var by = "unauthenticated/"; - if(isAuthenticated()) { + if (isAuthenticated()) { by = getUserRealm() + "/" + getUserName() + "/"; } return "/uploaded.by/" + by + fileupload.uuidv4() + "/"; diff --git a/src/doc/tutorials/edit-lazy-load-dropdown.png b/src/doc/tutorials/edit-lazy-load-dropdown.png new file mode 100644 index 0000000000000000000000000000000000000000..98396c85f5eeb8498fbf3576223b0b20db2ba5bc Binary files /dev/null and b/src/doc/tutorials/edit-lazy-load-dropdown.png differ diff --git a/src/doc/tutorials/edit_mode.rst b/src/doc/tutorials/edit_mode.rst index f7f50508856bc1abfbd3b04e28c20b7fa7c1a385..09d94d588cc421bd1095b1b09f951e45309d0813 100644 --- a/src/doc/tutorials/edit_mode.rst +++ b/src/doc/tutorials/edit_mode.rst @@ -75,6 +75,26 @@ to the corresponding area at the bottom of the Record. Properties and parents can be removed from the entity by clicking on the trash-can symbol. Note that a Record must always have at least one parent. +From LinkAhead WebUI version 0.15, the default behavior for changing +the values of reference properties has changed in that the dropdown +menus to select possible values are not filled automatically for +`performance reasons +<https://gitlab.com/linkahead/linkahead-webui/-/issues/262>`_. Instead, +you have to click on the pencil symbol to the right of the property +you want to edit: + +.. image:: edit-lazy-load-dropdown.png + :width: 720 + :alt: Clicking on the pencil symbol enables the editing of the + reference property. + +.. note:: + + The behavior of version 0.14.x and before, i.e., enable and load + all reference dropdowns immediatly after starting to edit the + entity, can be restored by starting LinkAhead with the + ``BUILD_EDIT_MODE_LAZY_DROPDOWN_LOADING = DISABLED`` option. + When many Entities are valid candidates for a reference property not all might be available in the drop down element. In those cases, the drop down warns, that only a subset is shown and a new button with a pencil symbol is