diff --git a/src/core/js/ext_autocomplete.js b/src/core/js/ext_autocomplete.js index 932a9466a83f17594894ad389f6535bcd6caffa2..02ae1199494ae3634afa16ad417b886f19068ee0 100644 --- a/src/core/js/ext_autocomplete.js +++ b/src/core/js/ext_autocomplete.js @@ -179,6 +179,19 @@ var ext_autocomplete = new function () { */ this.switch_on_completion = function () { var field = $("#caosdb-query-textarea"); + + // submit on "enter" when the drop-down menu is not visible. + field.on("keydown", (e) => { + if(e.originalEvent.keyCode == 13) { // Enter + if($(e.target).siblings(".bootstrap-autocomplete.show").length == 0) { + $(".caosdb-search-btn").click(); + } else { + // don't submit - the user is just selecting something + e.originalEvent.preventDefault(); + } + } + }); + field.attr("autocomplete", "off"); field.toggleClass("basicAutoComplete", true); field.autoComplete({ diff --git a/src/core/js/webcaosdb.js b/src/core/js/webcaosdb.js index 03e372919377e537017a0f2917c0d66db9ed580e..955bdd0b777033531242b8533055cb925d96bae4 100644 --- a/src/core/js/webcaosdb.js +++ b/src/core/js/webcaosdb.js @@ -1378,10 +1378,66 @@ var queryForm = new function () { this.restoreLastQuery(form, () => window.sessionStorage.lastQuery); this.bindOnClick(form, (set) => { window.sessionStorage.lastQuery = set; - return null; }); + const BUILD_FREE_SEARCH_ROLE_NAME_FACET_OPTIONS = "PathObject, MapObject"; + this.initFreeSearch(form, `${BUILD_FREE_SEARCH_ROLE_NAME_FACET_OPTIONS}`); }; + this.role_name_facet_select = undefined; + + /** + * Initialize the free search to generate search queries which search only + * within one of several options of roles or entity names. + * + * E.g. when `options` is "Person, Experiment, Sample" the user can select + * one of these options before submitting the query. When the user types in + * something that doesn't looks like a CQL-query, a query is generated + * instead which goes like: FIND Persion WHICH HAS A PROPERTY LIKE + * "*something*". + * + * @param {HTMLElement} form - the form which will be initialized for the + * free search. + * @param {string} options - comma separated list of options. + * @return {boolean} - true if the initialization was successful, false + * otherwise. + */ + this.initFreeSearch = function (form, options) { + const textArea = $(form).find(".caosdb-query-textarea"); + if (textArea.length > 0 && options && options != "") { + const lastQuery = window.localStorage["freeTextQuery:" + textArea[0].value]; + if (lastQuery) { + textArea[0].value = lastQuery; + } + + const selected = window.localStorage["role_name_facet_option"]; + const select = $(`<select class="btn btn-secondary"/>`); + for (let option of options.split(",")) { + select.append(`<option ${selected === option ? "selected" : ""}>${option}</option>`); + } + $(form).find(".input-group").prepend(select); + this.role_name_facet_select = select[0]; + + const switchFreeSearch = (text_area) => { + if(this._isCql(text_area.value)) { + select.hide(); + $(text_area).css({"border-top-left-radius": "0.375rem", "border-bottom-left-radius": "0.375rem"}); + } else { + select.show(); + $(text_area).css({}); + } + } + + switchFreeSearch(textArea[0]); + + textArea.on("keydown", (e) => { + switchFreeSearch(e.target); + }); + return true; + } + this.role_name_facet_select = undefined; + return false; + } + this.restoreLastQuery = function (form, getter) { if (form == null) { throw new Error("form was null"); @@ -1403,6 +1459,15 @@ var queryForm = new function () { location.href = connection.getBasePath() + "Entity/?" + pagingparam + "query=" + query; } + this.getRoleNameFacet = function () { + if (this.role_name_facet_select) { + const result = this.role_name_facet_select.value; + window.localStorage["role_name_facet_option"] = result; + return result; + } + return "RECORD"; + } + const _splitSearchTermsPattern = /"(?<dq>[^"]*)" |'(?<sq>[^']*)' |(?<nq>[^ ]+)/g; /** @@ -1421,6 +1486,11 @@ var queryForm = new function () { return Array.from((query + " ").matchAll(_splitSearchTermsPattern), (m) => m[1] || m[2] || m[3]).filter((word) => word); } + this._isCql = function (query) { + query = query.toUpperCase().trim(); + return (query.startsWith("FIND") || query.startsWith("COUNT") || query.startsWith("SELECT")); + } + this.bindOnClick = function (form, setter) { if (setter == null || typeof (setter) !== 'function' || setter.length !== 1) { throw new Error("setter must be a function with one param"); @@ -1434,19 +1504,22 @@ var queryForm = new function () { var submithandler = function () { // store current query var queryField = form.query; - var value = queryField.value.toUpperCase().trim(); + var value = queryField.value; if (typeof value == "undefined" || value.length == 0) { return; } - if (!(value.startsWith("FIND") || value.startsWith("COUNT") || value.startsWith("SELECT"))) { + if (!queryForm._isCql(value)) { // split words in query field at space and create query fragments var words = queryForm.splitSearchTerms(queryField.value).map(word => `A PROPERTY LIKE '*${word.replaceAll("'", `\\'`)}*'`); if (!words.length) { return false; } - var query_string = "FIND ENTITY WHICH HAS "; - // send a query that combines all fragments with an AND - queryField.value = query_string + words.join(" AND "); + const e = queryForm.getRoleNameFacet(); + const query_string = `FIND ${e} WHICH HAS ` + words.join(" AND "); + queryField.value = query_string; + + // store original value of the text field + window.localStorage["freeTextQuery:" + query_string] = value; } setter(queryField.value); @@ -1456,15 +1529,15 @@ var queryForm = new function () { } queryForm.redirect(queryField.value.trim(), paging); - }; - $("#caosdb-query-textarea").on("keydown", (e) => { - // prevent submit on enter - if (e.originalEvent.which == 13) { - e.originalEvent.preventDefault(); - } - }) + var btn = $(form).find(".caosdb-search-btn"); + btn.html(`<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>`); + btn.prop("disabled", true); + var textField = $(form).find(".caosdb-query-textarea"); + textField.blur(); + textField.prop("disabled", true); + }; // handler for the form form.onsubmit = function (e) { diff --git a/test/core/js/modules/webcaosdb.js.js b/test/core/js/modules/webcaosdb.js.js index 328a057f19af953399e08867085f6e04b5e785c5..b47c98a6547b89192fd003c2734dfde5e89ee7f4 100644 --- a/test/core/js/modules/webcaosdb.js.js +++ b/test/core/js/modules/webcaosdb.js.js @@ -1111,6 +1111,7 @@ QUnit.test("initEntity", function (assert) { QUnit.module("webcaosdb.js - queryForm", { before: function (assert) { assert.ok(queryForm, "queryForm is defined"); + assert.notOk(queryForm.initFreeSearch(), "free search reset"); } }); @@ -1196,14 +1197,14 @@ QUnit.test("bindOnClick", function (assert) { assert.equal(storage(), undefined, "before2: storage empty."); form.getElementsByClassName("caosdb-search-btn")[0].onclick(); - assert.equal(storage(), "FIND ENTITY WHICH HAS A PROPERTY LIKE '*freetext*'", "after2: storage not empty."); + assert.equal(storage(), "FIND RECORD WHICH HAS A PROPERTY LIKE '*freetext*'", "after2: storage not empty."); // test the form submit handler analogously form.query.value = "freetext2"; $("body").append(form); $(form).append(submitButton); submitButton.click(); - assert.equal(storage(), "FIND ENTITY WHICH HAS A PROPERTY LIKE '*freetext2*'", "after3: storage not empty."); + assert.equal(storage(), "FIND RECORD WHICH HAS A PROPERTY LIKE '*freetext2*'", "after3: storage not empty."); $(form).remove(); @@ -1212,7 +1213,7 @@ QUnit.test("bindOnClick", function (assert) { $("body").append(form); $(form).append(submitButton); submitButton.click(); - assert.equal(storage(), "FIND ENTITY WHICH HAS A PROPERTY LIKE '*free*' AND A PROPERTY LIKE '*text*' AND A PROPERTY LIKE '*3*'", "after4: storage not empty."); + assert.equal(storage(), "FIND RECORD WHICH HAS A PROPERTY LIKE '*free*' AND A PROPERTY LIKE '*text*' AND A PROPERTY LIKE '*3*'", "after4: storage not empty."); $(form).remove(); @@ -1221,7 +1222,7 @@ QUnit.test("bindOnClick", function (assert) { $("body").append(form); $(form).append(submitButton); submitButton.click(); - assert.equal(storage(), `FIND ENTITY WHICH HAS A PROPERTY LIKE '*with double*' AND A PROPERTY LIKE '*and single*' AND A PROPERTY LIKE '*what\\'s wrong?*' AND A PROPERTY LIKE '*\\'*' AND A PROPERTY LIKE '*nothin\\'*' AND A PROPERTY LIKE '*"\\'bla*'`, "after5: stuff with quotation marks"); + assert.equal(storage(), `FIND RECORD WHICH HAS A PROPERTY LIKE '*with double*' AND A PROPERTY LIKE '*and single*' AND A PROPERTY LIKE '*what\\'s wrong?*' AND A PROPERTY LIKE '*\\'*' AND A PROPERTY LIKE '*nothin\\'*' AND A PROPERTY LIKE '*"\\'bla*'`, "after5: stuff with quotation marks"); // ... then with empty quotation marks. this will not trigger the query execution at all storage("not triggered"); @@ -1251,6 +1252,40 @@ QUnit.test("splitSearchTerms", function (assert) { } }); +QUnit.test("initFreeSearch", function (assert) { + const form = $('<form></form>'); + const inputGroup = $('<div class="input-group"></div>'); + const textArea = $( '<textarea class="caosdb-query-textarea" name="query"></textarea>'); + const submitButton = $('<input class="caosdb-search-btn" type="submit">'); + inputGroup.append(textArea); + form.append(inputGroup).append(submitButton); + $("body").append(form); + + // without initialization + assert.ok(queryForm.initFreeSearch, "available"); + assert.notOk(queryForm.role_name_facet_select, "role_name_facet_select is undefined 1"); + assert.notOk(queryForm.initFreeSearch(), "not initialized"); + + assert.notOk(queryForm.role_name_facet_select, "role_name_facet_select is undefined 2"); + + assert.equal(form.find("select").length, 0, "no select present"); + assert.equal(queryForm.getRoleNameFacet(), "RECORD"); + + + // after initialization + assert.ok(queryForm.initFreeSearch(form, "Person, Experiment, Sample"), "initialized"); + assert.ok(queryForm.role_name_facet_select, "role_name_facet_select is defined"); + assert.equal(queryForm.role_name_facet_select.tagName, "SELECT", "role_name_facet_select is SELECT"); + assert.equal(form.find("select").length, 1, "select is present"); + + assert.equal(queryForm.getRoleNameFacet(), "Person"); + form.find("select")[0].value = "Experiment"; + assert.equal(queryForm.getRoleNameFacet(), "Experiment"); + + // clean up + form.remove(); +}) + /* MODULE paging */ QUnit.module("webcaosdb.js - paging", { before: function (assert) {}