From 37142c0231a98d6c645dfd41f06acfd9b2bf8e0d Mon Sep 17 00:00:00 2001 From: tworrall Date: Mon, 5 Aug 2013 15:12:24 -0400 Subject: [PATCH] VIVO-208: all fields now displayed when custom forms are rendered; customized 2-stage forms will still work as before --- .../controller/AutocompleteController.java | 139 ++++++++++-------- .../forms/js/customFormWithAutocomplete.js | 92 ++++++++---- 2 files changed, 143 insertions(+), 88 deletions(-) diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/search/controller/AutocompleteController.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/search/controller/AutocompleteController.java index 359a53529..51958ba45 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/search/controller/AutocompleteController.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/search/controller/AutocompleteController.java @@ -33,7 +33,7 @@ import edu.cornell.mannlib.vitro.webapp.search.solr.SolrSetup; /** * AutocompleteController generates autocomplete content - * via the search index. + * via the search index. */ public class AutocompleteController extends VitroAjaxController { @@ -49,7 +49,7 @@ public class AutocompleteController extends VitroAjaxController { String NORESULT_MSG = ""; - private static final int DEFAULT_MAX_HIT_COUNT = 1000; + private static final int DEFAULT_MAX_HIT_COUNT = 1000; public static final int MAX_QUERY_LENGTH = 500; @@ -57,50 +57,50 @@ public class AutocompleteController extends VitroAjaxController { protected Actions requiredActions(VitroRequest vreq) { return SimplePermission.USE_BASIC_AJAX_CONTROLLERS.ACTIONS; } - + @Override protected void doRequest(VitroRequest vreq, HttpServletResponse response) throws IOException, ServletException { - + try { - + String qtxt = vreq.getParameter(PARAM_QUERY); - - SolrQuery query = getQuery(qtxt, vreq); + + SolrQuery query = getQuery(qtxt, vreq); if (query == null ) { log.debug("query for '" + qtxt +"' is null."); doNoQuery(response); return; } log.debug("query for '" + qtxt +"' is " + query.toString()); - + SolrServer solr = SolrSetup.getSolrServer(getServletContext()); QueryResponse queryResponse = solr.query(query); if ( queryResponse == null) { - log.error("Query response for a search was null"); + log.error("Query response for a search was null"); doNoSearchResults(response); return; } - + SolrDocumentList docs = queryResponse.getResults(); if ( docs == null) { - log.error("Docs for a search was null"); + log.error("Docs for a search was null"); doNoSearchResults(response); return; } - + long hitCount = docs.getNumFound(); log.debug("Total number of hits = " + hitCount); - if ( hitCount < 1 ) { + if ( hitCount < 1 ) { doNoSearchResults(response); return; - } + } List results = new ArrayList(); for (SolrDocument doc : docs) { - try { + try { String uri = doc.get(VitroSearchTermNames.URI).toString(); // RY 7/1/2011 // Comment was: VitroSearchTermNames.NAME_RAW is a multivalued field, so doc.get() returns a list. @@ -116,61 +116,71 @@ public class AutocompleteController extends VitroAjaxController { } else { name = (String) nameRaw; } - SearchResult result = new SearchResult(name, uri); + + Object mostSpecificType = doc.get(VitroSearchTermNames.MOST_SPECIFIC_TYPE_URIS); + String mst = null; + if (mostSpecificType instanceof List) { + @SuppressWarnings("unchecked") + List mstList = (List) mostSpecificType; + mst = mstList.get(0); + } else { + mst = (String) mostSpecificType; + } + + SearchResult result = new SearchResult(name, uri, mst); results.add(result); + log.debug("results = " + results.toString()); } catch(Exception e){ log.error("problem getting usable individuals from search " + "hits" + e.getMessage()); } - } + } Collections.sort(results); - + JSONArray jsonArray = new JSONArray(); for (SearchResult result : results) { jsonArray.put(result.toMap()); } response.getWriter().write(jsonArray.toString()); - + } catch (Throwable e) { - log.error(e, e); + log.error(e, e); doSearchError(response); } } private SolrQuery getQuery(String queryStr, VitroRequest vreq) { - + if ( queryStr == null) { - log.error("There was no parameter '"+ PARAM_QUERY - +"' in the request."); + log.error("There was no parameter '"+ PARAM_QUERY + +"' in the request."); return null; } else if( queryStr.length() > MAX_QUERY_LENGTH ) { log.debug("The search was too long. The maximum " + "query length is " + MAX_QUERY_LENGTH ); return null; } - + SolrQuery query = new SolrQuery(); query.setStart(0) - .setRows(DEFAULT_MAX_HIT_COUNT); - + .setRows(DEFAULT_MAX_HIT_COUNT); setNameQuery(query, queryStr, vreq); - // Filter by type String typeParam = (String) vreq.getParameter(PARAM_RDFTYPE); String multipleTypesParam = (String) vreq.getParameter(PARAM_MULTIPLE_RDFTYPE); if (typeParam != null) { addFilterQuery(query, typeParam, multipleTypesParam); - } - - query.setFields(VitroSearchTermNames.NAME_RAW, VitroSearchTermNames.URI); // fields to retrieve - + } + + query.setFields(VitroSearchTermNames.NAME_RAW, VitroSearchTermNames.URI, VitroSearchTermNames.MOST_SPECIFIC_TYPE_URIS); // fields to retrieve + // Can't sort on multivalued field, so we sort the results in Java when we get them. // query.setSortField(VitroSearchTermNames.NAME_LOWERCASE, SolrQuery.ORDER.asc); - + return query; } - + private void addFilterQuery(SolrQuery query, String typeParam, String multipleTypesParam) { if(multipleTypesParam == null || multipleTypesParam.equals("null") || multipleTypesParam.isEmpty()) { //Single type parameter, process as usual @@ -181,15 +191,13 @@ public class AutocompleteController extends VitroAjaxController { int len = typeParams.length; int i; List filterQueries = new ArrayList(); - + for(i = 0; i < len; i++) { filterQueries.add(VitroSearchTermNames.RDFTYPE + ":\"" + typeParams[i] + "\" "); } String filterQuery = StringUtils.join(filterQueries, " OR "); query.addFilterQuery(filterQuery); } - - } private void setNameQuery(SolrQuery query, String queryStr, HttpServletRequest request) { @@ -197,10 +205,9 @@ public class AutocompleteController extends VitroAjaxController { if (StringUtils.isBlank(queryStr)) { log.error("No query string"); } - - String tokenizeParam = (String) request.getParameter("tokenize"); + String tokenizeParam = (String) request.getParameter("tokenize"); boolean tokenize = "true".equals(tokenizeParam); - + // Note: Stemming is only relevant if we are tokenizing: an untokenized name // query will not be stemmed. So we don't look at the stem parameter until we get to // setTokenizedNameQuery(). @@ -210,43 +217,43 @@ public class AutocompleteController extends VitroAjaxController { setUntokenizedNameQuery(query, queryStr); } } - + private void setTokenizedNameQuery(SolrQuery query, String queryStr, HttpServletRequest request) { /* We currently have no use case for a tokenized, unstemmed autocomplete search field, so the option * has been disabled. If needed in the future, will need to add a new field and field type which * is like AC_NAME_STEMMED but doesn't include the stemmer. - String stemParam = (String) request.getParameter("stem"); + String stemParam = (String) request.getParameter("stem"); boolean stem = "true".equals(stemParam); if (stem) { String acTermName = VitroSearchTermNames.AC_NAME_STEMMED; String nonAcTermName = VitroSearchTermNames.NAME_STEMMED; } else { String acTermName = VitroSearchTermNames.AC_NAME_UNSTEMMED; - String nonAcTermName = VitroSearchTermNames.NAME_UNSTEMMED; + String nonAcTermName = VitroSearchTermNames.NAME_UNSTEMMED; } */ - + String acTermName = VitroSearchTermNames.AC_NAME_STEMMED; String nonAcTermName = VitroSearchTermNames.NAME_STEMMED; String acQueryStr; - + if (queryStr.endsWith(" ")) { - acQueryStr = makeTermQuery(nonAcTermName, queryStr, true); + acQueryStr = makeTermQuery(nonAcTermName, queryStr, true); } else { int indexOfLastWord = queryStr.lastIndexOf(" ") + 1; List terms = new ArrayList(2); - + String allButLastWord = queryStr.substring(0, indexOfLastWord); if (StringUtils.isNotBlank(allButLastWord)) { terms.add(makeTermQuery(nonAcTermName, allButLastWord, true)); } - + String lastWord = queryStr.substring(indexOfLastWord); if (StringUtils.isNotBlank(lastWord)) { terms.add(makeTermQuery(acTermName, lastWord, false)); } - + acQueryStr = StringUtils.join(terms, " AND "); } @@ -255,26 +262,26 @@ public class AutocompleteController extends VitroAjaxController { } - private void setUntokenizedNameQuery(SolrQuery query, String queryStr) { - queryStr = queryStr.trim(); + private void setUntokenizedNameQuery(SolrQuery query, String queryStr) { + queryStr = queryStr.trim(); queryStr = makeTermQuery(VitroSearchTermNames.AC_NAME_UNTOKENIZED, queryStr, true); query.setQuery(queryStr); } - + private String makeTermQuery(String term, String queryStr, boolean mayContainWhitespace) { if (mayContainWhitespace) { queryStr = "\"" + escapeWhitespaceInQueryString(queryStr) + "\""; } return term + ":" + queryStr; } - + private String escapeWhitespaceInQueryString(String queryStr) { // Solr wants whitespace to be escaped with a backslash return queryStr.replaceAll("\\s+", "\\\\ "); } - + private void doNoQuery(HttpServletResponse response) throws IOException { - // For now, we are not sending an error message back to the client because + // For now, we are not sending an error message back to the client because // with the default autocomplete configuration it chokes. doNoSearchResults(response); } @@ -288,36 +295,46 @@ public class AutocompleteController extends VitroAjaxController { private void doNoSearchResults(HttpServletResponse response) throws IOException { response.getWriter().write("[]"); } - + public class SearchResult implements Comparable { private String label; private String uri; - - SearchResult(String label, String uri) { + private String msType; + + SearchResult(String label, String uri, String msType) { this.label = label; this.uri = uri; + this.msType = msType; } - + public String getLabel() { return label; } - + public String getJsonLabel() { return JSONObject.quote(label); } - + public String getUri() { return uri; } - + public String getJsonUri() { return JSONObject.quote(uri); } + public String getMsType() { + return msType; + } + + public String getJsonMsType() { + return JSONObject.quote(msType); + } Map toMap() { Map map = new HashMap(); map.put("label", label); map.put("uri", uri); + map.put("msType", msType); return map; } diff --git a/webapp/web/templates/freemarker/edit/forms/js/customFormWithAutocomplete.js b/webapp/web/templates/freemarker/edit/forms/js/customFormWithAutocomplete.js index 1489a0038..d02e8e8d0 100644 --- a/webapp/web/templates/freemarker/edit/forms/js/customFormWithAutocomplete.js +++ b/webapp/web/templates/freemarker/edit/forms/js/customFormWithAutocomplete.js @@ -55,7 +55,12 @@ var customForm = { // the verify popup window. Although there could be multiple verifyMatch objects // selecting one and binding the event works for all of them this.verifyMatch = this.form.find('.verifyMatch'); - + this.defaultAcType = ""; // will be set in setType() first time through + this.templateDefinedAcTypes = false; + if ( this.acTypes != undefined ) { + this.templateDefinedAcTypes = true; + } + // find all the acSelector input elements this.acSelectors = [] ; @@ -86,7 +91,7 @@ var customForm = { // Used with the cancel link. If the user cancels after a type selection, this check // ensures that any a/c fields (besides the one associated with the type) will be reset this.clearAcSelections = false; - + }, // Set up the form on page load @@ -126,6 +131,10 @@ var customForm = { this.initFormView(); + // Set the initial autocomplete help text in the acSelector fields. + $.each(this.acSelectors, function() { + customForm.addAcHelpText($(this)); + }); }, initFormView: function() { @@ -288,7 +297,7 @@ var customForm = { //to the filtering list this.getAcFilterForIndividuals(); this.acCache = {}; - + $(selectedObj).autocomplete({ minLength: 3, source: function(request, response) { @@ -312,8 +321,9 @@ var customForm = { }, complete: function(xhr, status) { // Not sure why, but we need an explicit json parse here. - var results = $.parseJSON(xhr.responseText), + var results = $.parseJSON(xhr.responseText), filteredResults = customForm.filterAcResults(results); + customForm.acCache[request.term] = filteredResults; response(filteredResults); } @@ -321,6 +331,9 @@ var customForm = { }, select: function(event, ui) { customForm.showAutocompleteSelection(ui.item.label, ui.item.uri, $(selectedObj)); + if ( $(selectedObj).attr('acGroupName') == customForm.typeSelector.attr('acGroupName') ) { + customForm.typeSelector.val(ui.item.msType); + } } }); }, @@ -420,17 +433,24 @@ var customForm = { // provides a way to monitor selection in other js files, e.g. to hide fields upon selection $acDiv.addClass("userSelected"); - // If the form has a type selector, add type name to label in add mode. In edit mode, use typeSelectorSpan - // html. The second case is an "else if" and not an else because the template may not be passing the label - // to the acSelection macro or it may not be using the macro at all and the label is hard-coded in the html. - if ( this.typeSelector.length && ($acDiv.attr('acGroupName') == this.typeSelector.attr('acGroupName')) ) { - $acDiv.find('label').html('Selected ' + this.typeName + ':'); - } - else if ( this.typeSelectorSpan.html() && ($acDiv.attr('acGroupName') == this.typeSelectorInput.attr('acGroupName')) ) { - $acDiv.find('label').html('Selected ' + this.typeSelectorSpan.html() + ':'); - } - else if ( $acDiv.find('label').html() == '' ) { - $acDiv.find('label').html('Selected ' + this.multipleTypeNames[$(selectedObj).attr('acGroupName')] + ':'); + // If the form has a type selector, add type name to label in add mode. In edit mode, + // use typeSelectorSpan html. The second case is an "else if" and not an else because + // the template may not be passing the label to the acSelection macro or it may not be + // using the macro at all and the label is hard-coded in the html. + // ** With release 1.6 and display of all fields, more labels are hard-coded in html. + // ** So check if there's a label before doing anything else. + + if ( $acDiv.find('label').html().length === 0 ) { + + if ( this.typeSelector.length && ($acDiv.attr('acGroupName') == this.typeSelector.attr('acGroupName')) ) { + $acDiv.find('label').html('Selected ' + this.typeName + ':'); + } + else if ( this.typeSelectorSpan.html() && ($acDiv.attr('acGroupName') == this.typeSelectorInput.attr('acGroupName')) ) { + $acDiv.find('label').html('Selected ' + this.typeSelectorSpan.html() + ':'); + } + else if ( $acDiv.find('label').html() == '' ) { + $acDiv.find('label').html('Selected ' + this.multipleTypeNames[$(selectedObj).attr('acGroupName')] + ':'); + } } $acDiv.show(); @@ -447,7 +467,6 @@ var customForm = { //On initialization in this mode, submit button is disabled this.enableSubmit(); } - }, undoAutocompleteSelection: function(selectedObj) { @@ -482,11 +501,12 @@ var customForm = { $acSelector = customForm.getAcSelector($checkSelection); $acSelector.parent('p').show(); } - }); - } + }); + } } else { $acSelectionObj = $(selectedObj); + customForm.typeSelector.val(''); } $acSelector = this.getAcSelector($acSelectionObj); @@ -530,10 +550,9 @@ var customForm = { // Note: we still need this in edit mode, to set the text values. setType: function() { var selectedType; - // If there's no type selector, these values have been specified in customFormData, // and will not change over the life of the form. - if (!this.typeSelector.length) { + if (!this.typeSelector.length) { if ( this.editMode == 'edit' && (this.typeSelectorSpan.html() != null && this.typeSelectorInput.val() != null) ) { this.typeName = this.typeSelectorSpan.html(); this.acTypes[this.typeSelectorInput.attr('acGroupName')] = this.typeSelectorInput.val(); @@ -542,7 +561,11 @@ var customForm = { } selectedType = this.typeSelector.find(':selected'); - var acTypeKey = this.typeSelector.attr('acGroupName'); + var acTypeKey = this.typeSelector.attr('acGroupName'); + + if ( this.templateDefinedAcTypes && !this.defaultAcType.length ) { + this.defaultAcType = this.acTypes[acTypeKey]; + } if (selectedType.val().length) { this.acTypes[acTypeKey] = selectedType.val(); this.typeName = selectedType.html(); @@ -551,15 +574,20 @@ var customForm = { $acSelect.find('label').html( customForm.selectedString + ' ' + this.typeName + ':'); } } - // reset to empty values; may not need + // reset to empty values; else { - delete this.acTypes[acTypeKey]; - this.typeName = ''; + if ( this.templateDefinedAcTypes ) { + this.acTypes[acTypeKey] = this.defaultAcType; + } + else { + this.acTypes = new Object(); + } + this.typeName = this.defaultTypeName; } }, // Set field labels based on type selection. Although these won't change in edit - // mode, it's easier to specify the text here than in the jsp. + // mode, it's easier to specify the text here than in the ftl. setLabels: function() { var typeName = this.getTypeNameForLabels(); @@ -575,10 +603,20 @@ var customForm = { // or in repair mode in a two-step form with no type selected. Use the default type // name specified in the form data. if ( !selectedObj || !this.hasMultipleTypeNames ) { - return this.acTypes ? this.typeName : this.capitalize(this.defaultTypeName); + if ( this.acTypes && this.typeName ) { + return this.typeName; + } + else { + return this.capitalize(this.defaultTypeName); + } } else if ( selectedObj && ( $(selectedObj).attr('acGroupName') == this.typeSelector.attr('acGroupName') ) ) { - return this.acTypes ? this.typeName : this.capitalize(this.defaultTypeName); + if ( this.acTypes && this.typeName ) { + return this.typeName; + } + else { + return this.capitalize(this.defaultTypeName); + } } else { var name = customForm.multipleTypeNames[$(selectedObj).attr('id')];