updates for concept search service, adding LCSH search capability

This commit is contained in:
hudajkhan 2013-09-16 14:02:47 -04:00
parent e032ceeca4
commit ba1c6c7075
10 changed files with 1009 additions and 135 deletions

View file

@ -1,6 +1,6 @@
<#-- $This file is distributed under the terms of the license in /doc/license.txt$ -->
<#import "lib-vivo-form.ftl" as lvf>
<#include "addAssociatedConceptVocabSpecificDisplay.ftl" >
<#assign existingConcepts = editConfiguration.pageData.existingConcepts/>
<#assign userDefinedConceptUrl = editConfiguration.pageData.userDefinedConceptUrl/>
<#assign sources = editConfiguration.pageData.searchServices/>
@ -35,29 +35,44 @@
<ul id="existingConcepts" >
<ul id="existingConcepts">
<script type="text/javascript">
var existingConceptsData = [];
</script>
<#if (existingConcepts?size > 0)>
<li class="conceptHeadings conceptsListContainer">
<div class="row">
<div class="column conceptLabelInfo">
<h4>Concept (Type)</h4>
</div>
<div class="column conceptVocabSource">
<h4>Vocabulary Source</h4>
</div>
<div class="column">&nbsp;
</div>
</div>
</li>
</#if>
<#list existingConcepts as existingConcept>
<li class="existingConcept">
<span class="concept">
<span class="conceptWrapper">
<span class="conceptLabel"> ${existingConcept.conceptLabel}
<#if existingConcept.vocabURI?has_content && existingConcept.vocabLabel?has_content>
(${existingConcept.vocabLabel})
</#if>
<li class="existingConcept conceptsListContainer">
<div class="row">
<div class="column conceptLabelInfo"> ${existingConcept.conceptLabel}
<#if existingConcept.conceptSemanticTypeLabel?has_content>
${existingConcept.conceptSemanticTypeLabel}
(${existingConcept.conceptSemanticTypeLabel})
</#if>
</span>
</span>
&nbsp;<a href="${urls.base}/edit/primitiveRdfEdit" class="remove" title="${i18n().remove_capitalized}">${i18n().remove_capitalized}</a>
</span>
</div>
<div class="column conceptVocabSource">
<#if existingConcept.vocabURI?has_content && existingConcept.vocabLabel?has_content>
${existingConcept.vocabLabel}
</#if>
</div>
<div class="column">
<a href="${urls.base}/edit/primitiveRdfEdit" class="remove" title="${i18n().remove_capitalized}">${i18n().remove_capitalized}</a>
</div>
</div>
</li>
<script type="text/javascript">
@ -86,7 +101,7 @@
<form id="addConceptForm" class="customForm" action="${submitUrl}">
<#assign checkedSource = false />
<h4 class="services">${i18n().external_vocabulary_services}</h4>
<#list sources?keys as sourceUri>
<#list sources?keys?sort as sourceUri>
<#assign thisSource = sources[sourceUri]/>
<input type="radio" name="source" value="${sourceUri}" role="radio" <#if checkedSource = false><#assign checkedSource = true/>checked="checked"</#if>>
<label class="inline" for="${thisSource.label}"> <a href="${thisSource.url}">${thisSource.label}</a> &nbsp;(${thisSource.description})</label>
@ -101,12 +116,21 @@
<input type="hidden" id="conceptSource" name="conceptSource" value="" /> <!-- Field value populated by JavaScript -->
<input type="hidden" id="conceptSemanticTypeURI" name="conceptSemanticTypeURI" value="" /> <!-- Field value populated by JavaScript -->
<input type="hidden" id="conceptSemanticTypeLabel" name="conceptSemanticTypeLabel" value="" /> <!-- Field value populated by JavaScript -->
<input type="hidden" id="conceptBroaderURI" name="conceptBroaderURI" value=""/><!-- Field value populated by JavaScript -->
<input type="hidden" id="conceptNarrowerURI" name="conceptNarrowerURI" value=""/><!-- Field value populated by JavaScript -->
<div id="indicator" class="hidden">
<img id="loadingIndicator" class="indicator" src="${urls.base}/images/indicatorWhite.gif" alt="${i18n().processing_indicator}"/>
</div>
<div id="selectedConcept" name="selectedConcept" class="acSelection">
<p class="inline">
</p>
<!-- Search results populated by JavaScript -->
</div>
<div id="showHideResults" name="showHideResults">
<a href="#" id="showHideLink">Results</a>
</div>
<div id="errors" name="errors"></div>
<input type="hidden" name="editKey" id="editKey" value="${editKey}"/>
@ -133,16 +157,21 @@ var customFormData = {
predicateUri: '${editConfiguration.predicateUri}',
inversePredicateUri: '${inversePredicate}'
};
var vocabSpecificDisplay = {};
<#list vocabSpecificDisplay?keys as vocab>
vocabSpecificDisplay["${vocab}"] = "${vocabSpecificDisplay[vocab]}";
</#list>
var i18nStrings = {
vocServiceUnavailable: '${i18n().vocabulary_service_unavailable}',
noResultsFound: '${i18n().no_serch_results_found}',
labelTypeString: '${i18n().label_type}',
defaultLabelTypeString: '${i18n().label_type}',
definitionString: '${i18n().definition_capitalized}',
bestMatchString: '${i18n().best_match}',
selectTermFromResults: '${i18n().select_term_from_results}',
selectVocSource: '${i18n().select_vocabulary_source_to_search}',
confirmTermDelete: '${i18n().confirm_term_deletion}',
errorTernNotRemoved: '${i18n().error_term_not_deleted}'
errorTernNotRemoved: '${i18n().error_term_not_deleted}',
vocabSpecificLabels: vocabSpecificDisplay
};
</script>

View file

@ -0,0 +1,14 @@
<#-- $This file is distributed under the terms of the license in /doc/license.txt$ -->
<#--The original concept javascript is service independent, i.e. all vocabulary service information is returned from the servlet
and the template itself generates the same display for all the services. Right now we would like to show a different label
in the search results based on the service. I am storing that information here and later we can consider how the display
can return to being independent of vocabulary service-specific display options.
These values will be passed to the javascript-->
<#assign vocabSpecificDisplay = {
"http://link.informatics.stonybrook.edu/umls":"${i18n().label_type}",
"http://aims.fao.org/aos/agrovoc/agrovocScheme":"${i18n().label_altLabels}",
"http://www.eionet.europa.eu/gemet/gemetThesaurus":"${i18n().label_type}",
"http://id.loc.gov/authorities/subjects":"${i18n().label_altLabels}"
}/>

View file

@ -1,5 +1,9 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
.conceptsListContainer {
overflow:hidden;
width:100%;
}
.concepts .column {
float:left;
padding-right:3px;
@ -44,4 +48,29 @@ form#addConceptForm {
form#addConceptForm span#createOwnOne{
float:left;
margin-top:24px
}
/*For existing concepts, display will also be tabular with columns*/
.existingConcept .row, .conceptHeadings .row {
clear:both;
float:left;
}
.existingConcept .column , .conceptHeadings .column {
float:left;
padding-right:3px;
clear:none !important; /*Overriding customFor div's clearing*/
}
/*label and semantic type if it exists*/
.existingConcept .conceptLabelInfo, .conceptHeadings .conceptLabelInfo {
width:220px;
}
.existingConcept .conceptVocabSource, .conceptHeadings .conceptVocabSource {
width:220px;
}
.conceptHeadings .row {
border-bottom: 1px solid #5F6464;
}

View file

@ -55,12 +55,19 @@ var addConceptForm = {
this.externalConceptLabel = $('#conceptLabel');
this.externalConceptSource = $('#conceptSource');
this.externalConceptSemanticTypeLabel = $("#conceptSemanticTypeLabel");
this.externalConceptBroaderUris = $("#conceptBroaderURI");
this.externalConceptNarrowerUris = $("#conceptNarrowerURI");
//remove links
this.removeConceptLinks = $('a.remove');
this.errors = $('#errors');
this.createOwn1 = $('#createOwnOne');
this.createOwn2 = $('#createOwnTwo');
this.orSpan = $('span.or')
this.loadingIndicator = $("#indicator");
this.showHideSearchResults = $("#showHideResults");
//Value we are setting to cut off length of alternate labels string
this.maxNumberAlternateLabels = 4;
this.numberOfMaxInitialSearchResults = 7;
},
initPage: function() {
@ -87,6 +94,10 @@ var addConceptForm = {
addConceptForm.removeExistingConcept(this);
return false;
});
this.showHideSearchResults.find("a#showHideLink").click(function() {
addConceptForm.showHideMultipleSearchResults(this);
return false;
});
},
initForm: function() {
// Hide the button that shows the form
@ -99,7 +110,9 @@ var addConceptForm = {
//Also clear the search input
this.searchTerm.val("");
this.cancel.unbind('click');
//make sure results loading indicator is hidden
this.loadingIndicator.addClass("hidden");
this.showHideSearchResults.hide();
// Show the form
this.form.show();
},
@ -114,6 +127,8 @@ var addConceptForm = {
},
clearSearchResults:function() {
$('#selectedConcept').empty();
//Hide the indicator icon if still there
$("#indicator").addClass("hidden");
},
clearErrors:function() {
addConceptForm.errors.empty();
@ -134,6 +149,25 @@ var addConceptForm = {
this.hideForm();
this.showFormButtonWrapper.show();
},
showHideMultipleSearchResults: function(link) {
if($(link).hasClass("showmore")) {
//if clicking and already says show more then need to show the rest of the results
$("li.concepts").show(); //show everything
$(link).html("Show fewer results");
$(link).removeClass("showmore");
} else {
//if clicking and does not say show more than need to show less
$("li.concepts").slice(addConceptForm.numberOfMaxInitialSearchResults).hide();
$(link).html("Show more results");
$(link).addClass("showmore");
}
},
//reset this to default, which is hidden with show more link
resetShowHideMultipleSearchResults: function() {
addConceptForm.showHideSearchResults.hide();
addConceptForm.showHideSearchResults.find("a#showHideLink").html("Show more results");
addConceptForm.showHideSearchResults.find("a#showHideLink").addClass("showmore");
},
submitSearchTerm: function() {
//Get value of search term
var searchValue = this.searchTerm.val();
@ -145,7 +179,11 @@ var addConceptForm = {
}
var vocabSourceValue = checkedVocabSource.val();
var dataServiceUrl = addConceptForm.dataServiceUrl + "?searchTerm=" + encodeURIComponent(searchValue) + "&source=" + encodeURIComponent(vocabSourceValue);
//This should return an object including the concept list or any errors if there are any
//Show the loading icon until the results appear
addConceptForm.loadingIndicator.removeClass("hidden");
//Hide and reset the show more button
addConceptForm.resetShowHideMultipleSearchResults();
//This should return an object including the concept list or any errors if there are any
$.getJSON(dataServiceUrl, function(results) {
var htmlAdd = "";
var vocabUnavailable = "<p>" + addConceptForm.vocServiceUnavailable + "</p>";
@ -166,7 +204,7 @@ var addConceptForm = {
//For each result, display
if(numberTotalMatches > 0) {
htmlAdd = "<ul class='dd' id='concepts' name='concepts'>";
htmlAdd+= addConceptForm.addResultsHeader();
htmlAdd+= addConceptForm.addResultsHeader(vocabSourceValue);
//Show best matches first
for(i = 0; i < numberBestMatches; i++) {
var conceptResult = bestMatchResults[i];
@ -184,6 +222,8 @@ var addConceptForm = {
}
if(htmlAdd.length) {
//hide the loading icon again
addConceptForm.loadingIndicator.addClass("hidden");
$('#selectedConcept').html(htmlAdd);
if (htmlAdd.indexOf("No search results") >= 0) {
addConceptForm.showHiddenElements(hasResults);
@ -191,6 +231,8 @@ var addConceptForm = {
else {
hasResults = true;
addConceptForm.showHiddenElements(hasResults);
//Here, tweak the display based on the number of results
addConceptForm.displayUptoMaxResults();
}
}
});
@ -203,9 +245,12 @@ var addConceptForm = {
var definedBy = conceptResult.definedBy;
var type = conceptResult.type;
var uri = conceptResult.uri;
//also adding broader and narrower uris wherever they exist
var broaderUris = conceptResult.broaderURIList;
var narrowerUris = conceptResult.narrowerURIList;
//this will be null if there are no alternate labels
var altLabels = conceptResult.altLabelList;
return addConceptForm.generateIndividualConceptDisplay(uri, label, altLabels, definition, type, definedBy, isBestMatch);
return addConceptForm.generateIndividualConceptDisplay(uri, label, altLabels, definition, type, definedBy, isBestMatch, broaderUris, narrowerUris);
},
//This should now return all best matches in one array and other results in another
parseResults:function(resultsArray) {
@ -225,10 +270,19 @@ var addConceptForm = {
}
return {"bestMatch":bestMatchResults, "alternate":alternateResults};
},
addResultsHeader:function() {
var htmlAdd = "<li class='concepts'><div class='row'><span class='column conceptLabel'>" + addConceptForm.labelTypeString + " </span><span class='column conceptDefinition'>" + addConceptForm.definitionString + "</span><span class='column'>" + addConceptForm.bestMatchString + "</span></div></li>";
addResultsHeader:function(vocabSourceValue) {
var htmlAdd = "<li class='concepts'><div class='row'><span class='column conceptLabel'>" +
addConceptForm.getVocabSpecificColumnLabel(vocabSourceValue) + " </span><span class='column conceptDefinition'>" + addConceptForm.definitionString + "</span><span class='column'>" + addConceptForm.bestMatchString + "</span></div></li>";
return htmlAdd;
},
//currently just the first column label depends on which service has been utilized
getVocabSpecificColumnLabel: function(vocabSourceValue) {
var columnLabel = addConceptForm.vocabSpecificLabels[vocabSourceValue];
if(columnLabel == undefined) {
columnLabel = addConceptForm.defaultLabelTypeString;
}
return columnLabel;
},
hideSearchResults:function() {
this.selectedConcept.hide();
},
@ -239,11 +293,14 @@ var addConceptForm = {
}
var i;
var len = checkedElements.length;
var checkedConcept, checkedConceptElement, conceptLabel, conceptSource, conceptSemanticType;
var checkedConcept, checkedConceptElement, conceptLabel, conceptSource, conceptSemanticType,
conceptBroaderUri, conceptNarrowerUri;
var conceptNodes = [];
var conceptLabels = [];
var conceptSources = [];
var conceptSemanticTypes = [];
var conceptBroaderUris = []; //each array element can be a string which is comma delimited for multiple uris
var conceptNarrowerUris = [];//same as above
checkedElements.each(function() {
checkedConceptElement = $(this);
@ -251,22 +308,29 @@ var addConceptForm = {
conceptLabel = checkedConceptElement.attr("label");
conceptSource = checkedConceptElement.attr("conceptDefinedBy");
conceptSemanticType = checkedConceptElement.attr("conceptType");
conceptBroaderUri = checkedConceptElement.attr("broaderUris");
conceptNarrowerUri = checkedConceptElement.attr("narrowerUris");
conceptNodes.push(checkedConcept);
conceptLabels.push(conceptLabel);
conceptSources.push(conceptSource);
conceptSemanticTypes.push(conceptSemanticType);
conceptBroaderUris.push(conceptBroaderUri);
conceptNarrowerUris.push(conceptNarrowerUri);
});
this.externalConceptURI.val(conceptNodes);
this.externalConceptLabel.val(conceptLabels);
this.externalConceptSource.val(conceptSources);
this.externalConceptSemanticTypeLabel.val(conceptSemanticTypes);
this.externalConceptBroaderUris.val(conceptBroaderUris);
this.externalConceptNarrowerUris.val(conceptNarrowerUris);
return true;
},
generateIndividualConceptDisplay: function(cuiURI, label, altLabels, definition, type, definedBy, isBestMatch) {
generateIndividualConceptDisplay: function(cuiURI, label, altLabels, definition, type, definedBy, isBestMatch, broaderUris, narrowerUris) {
var htmlAdd = "<li class='concepts'>" +
"<div class='row'>" +
"<div class='column conceptLabel'>" +
addConceptForm.generateIndividualCUIInput(cuiURI, label, type, definedBy) +
addConceptForm.generateIndividualCUIInput(cuiURI, label, type, definedBy, broaderUris, narrowerUris) +
addConceptForm.generateIndividualLabelsDisplay(label, altLabels) + addConceptForm.generateIndividualTypeDisplay(type) + "</div>" +
addConceptForm.generateIndividualDefinitionDisplay(definition) +
addConceptForm.generateBestOrAlternate(isBestMatch) +
@ -274,14 +338,23 @@ var addConceptForm = {
"</li>";
return htmlAdd;
},
generateIndividualCUIInput:function(cuiURI, label, type, definedBy) {
return "<input type='checkbox' name='CUI' value='" + cuiURI + "' label='" + label + "' conceptType='" + type + "' conceptDefinedBy='" + definedBy + "'/>";
generateIndividualCUIInput:function(cuiURI, label, type, definedBy, broaderUris, narrowerUris) {
return "<input type='checkbox' name='CUI' value='" + cuiURI + "' label='" +
label + "' conceptType='" + type + "' conceptDefinedBy='" + definedBy + "' " +
"broaderUris='" + broaderUris + "' narrowerUris='" + narrowerUris + "'/>";
},
//In case there are multiple labels display those
generateIndividualLabelsDisplay:function(label, altLabels) {
var labelDisplay = label;
var displayAltLabels = altLabels;
if(altLabels != null && altLabels.length > 0) {
labelDisplay += "<br> [" + altLabels + "]";
//Certain vocabulary services might return a long list of alternate labels, in which case we will show fewer
//display only upto a certain number of alternate labels and use an ellipsis to signify there
//are additional terms
if(altLabels.length > addConceptForm.maxNumberAlternateLabels) {
displayAltLabels = altLabels.slice(0, addConceptForm.maxNumberAlternateLabels) + ",...";
}
labelDisplay += "<br> (" + displayAltLabels + ")";
}
return labelDisplay;
},
@ -307,6 +380,18 @@ var addConceptForm = {
}
return "<div class='column'><div class='" + className + "'>&nbsp;</div></div>";
},
//Certain vocabulary services return a great number of results, we would like the ability to show more or less of those results
displayUptoMaxResults:function() {
var numberConcepts = $("li.concepts").length;
if(numberConcepts > addConceptForm.numberOfMaxInitialSearchResults) {
$("li.concepts").slice(addConceptForm.numberOfMaxInitialSearchResults).hide();
//Hide the link for showing/hiding search results
addConceptForm.showHideSearchResults.show();
addConceptForm.showHideSearchResults.find("a#showHideLink").html("Show more results");
addConceptForm.showHideSearchResults.find("a#showHideLink").addClass("showmore");
}
},
validateConceptSelection:function(checkedElements) {
var numberElements = checkedElements.length;
if(numberElements < 1) {