Skip to content
Snippets Groups Projects
Commit 0006329d authored by aeschylus's avatar aeschylus Committed by GitHub
Browse files

Merge branch '2.4.0' into add-event-mainmenu

parents ef90f0c9 4efe99c3
Branches
Tags
No related merge requests found
Showing
with 25033 additions and 57 deletions
...@@ -43,6 +43,7 @@ module.exports = function(grunt) { ...@@ -43,6 +43,7 @@ module.exports = function(grunt) {
'node_modules/i18next/i18next.min.js', 'node_modules/i18next/i18next.min.js',
'node_modules/i18next-browser-languagedetector/i18nextBrowserLanguageDetector.min.js', 'node_modules/i18next-browser-languagedetector/i18nextBrowserLanguageDetector.min.js',
'node_modules/i18next-xhr-backend/i18nextXHRBackend.min.js', 'node_modules/i18next-xhr-backend/i18nextXHRBackend.min.js',
'bower_components/simplePagination.js/jquery.simplePagination.js',
'js/lib/modernizr.custom.js', 'js/lib/modernizr.custom.js',
'js/lib/sanitize-html.min.js' 'js/lib/sanitize-html.min.js'
], ],
...@@ -93,7 +94,8 @@ module.exports = function(grunt) { ...@@ -93,7 +94,8 @@ module.exports = function(grunt) {
'css/jquery.qtip.min.css', 'css/jquery.qtip.min.css',
'node_modules/spectrum-colorpicker/spectrum.css', 'node_modules/spectrum-colorpicker/spectrum.css',
'css/mirador.css', 'css/mirador.css',
'css/material-icons.css' 'css/material-icons.css',
'bower_components/simplePagination.js/simplePagination.css'
], ],
dest: 'build/mirador/css/mirador-combined.css' dest: 'build/mirador/css/mirador-combined.css'
} }
... ...
......
...@@ -21,7 +21,8 @@ ...@@ -21,7 +21,8 @@
], ],
"dependencies": { "dependencies": {
"sanitize-html": "~1.11.4", "sanitize-html": "~1.11.4",
"qtip2": "~2.2.1" "qtip2": "~2.2.1",
"simplePagination.js": "*"
}, },
"resolutions": { "resolutions": {
"jquery": ">=1.7.2" "jquery": ">=1.7.2"
... ...
......
...@@ -112,7 +112,7 @@ ...@@ -112,7 +112,7 @@
.tabContentArea { .tabContentArea {
position: relative; position: relative;
width:280px; width:270px;
height:100%; height:100%;
float: left; float: left;
background-color: @gray-ef; background-color: @gray-ef;
...@@ -127,10 +127,27 @@ ...@@ -127,10 +127,27 @@
margin:0; margin:0;
} }
.search-results {
padding:0 5px 0 0;
margin:0;
position:absolute;
height: 100%;
.search-results-container {
overflow-y: scroll;
position: absolute;
height: 70%;
}
.search-results-list .result-wrapper .result-paragraph .highlight {
background-color: rgb(255, 255, 0);
}
}
.toc { .toc {
margin:0; margin:0;
overflow-y: scroll; overflow-y: scroll;
width: 280px; width: 270px;
padding:0; padding:0;
position:absolute; position:absolute;
box-sizing: border-box; box-sizing: border-box;
... ...
......
...@@ -58,8 +58,19 @@ ...@@ -58,8 +58,19 @@
this.request.done(function(jsonLd) { this.request.done(function(jsonLd) {
_this.jsonLd = jsonLd; _this.jsonLd = jsonLd;
_this.buildCanvasMap();
}); });
}, },
buildCanvasMap: function() {
var _this = this;
this.canvasMap = {};
if (this.getCanvases()) {
this.getCanvases().forEach(function(canvas) {
_this.canvasMap[canvas['@id']] = canvas;
});
}
},
initFromInfoJson: function(infoJsonUrl) { initFromInfoJson: function(infoJsonUrl) {
var _this = this; var _this = this;
this.request = jQuery.ajax({ this.request = jQuery.ajax({
...@@ -133,7 +144,7 @@ ...@@ -133,7 +144,7 @@
}, },
getCanvases : function() { getCanvases : function() {
var _this = this; var _this = this;
return _this.jsonLd.sequences[0].canvases; return _this.jsonLd.sequences && _this.jsonLd.sequences[0].canvases;
}, },
getAnnotationsListUrls: function(canvasId) { getAnnotationsListUrls: function(canvasId) {
var _this = this; var _this = this;
...@@ -202,6 +213,46 @@ ...@@ -202,6 +213,46 @@
}; };
return dummyManifest; return dummyManifest;
},
getSearchWithinService: function(){
var _this = this;
var serviceProperty = _this.jsonLd.service;
var service = [];
if (serviceProperty === undefined){
service = null;
}
else if (serviceProperty.constructor === Array){
for (var i = 0; i < serviceProperty.length; i++){
//TODO: should we be filtering search by context
if (serviceProperty[i]["@context"] === "http://iiif.io/api/search/0/context.json" ||
serviceProperty[i]["@context"] === "http://iiif.io/api/search/1/context.json") {
//returns the first service object with the correct context
service.push(serviceProperty[i]);
}
}
}
else if (_this.jsonLd.service["@context"] === "http://iiif.io/api/search/0/context.json" ||
serviceProperty["@context"] === "http://iiif.io/api/search/1/context.json"){
service.push(_this.jsonLd.service);
}
else {
//no service object with the right context is found
service = null;
}
return service;
},
/**
* Get the label of the a canvas by ID
* @param {[type]} canvasId ID of desired canvas
* @return {[type]} string
*/
getCanvasLabel: function(canvasId) {
console.assert(canvasId && canvasId !== '', "No canvasId was specified.");
if (this.canvasMap && canvasId.indexOf('#') >= 0) {
var canvas = this.canvasMap[canvasId.split('#')[0]];
return canvas ? canvas.label : undefined;
}
} }
}; };
... ...
......
...@@ -52,7 +52,10 @@ ...@@ -52,7 +52,10 @@
//control what is available in the side panel. if "sidePanel" is false, these options won't be applied //control what is available in the side panel. if "sidePanel" is false, these options won't be applied
"sidePanelOptions" : { "sidePanelOptions" : {
"toc" : true, "toc" : true,
"annotations" : false "annotations" : false,
"tocTabAvailable": true,
// "layersTabAvailable": true,
"searchTabAvailable": false,
}, },
"sidePanelVisible" : true, //whether or not to make the side panel visible in this window on load. This setting is dependent on sidePanel being true "sidePanelVisible" : true, //whether or not to make the side panel visible in this window on load. This setting is dependent on sidePanel being true
"overlay" : true, //whether or not to make the metadata overlay available/visible in this window "overlay" : true, //whether or not to make the metadata overlay available/visible in this window
... ...
......
...@@ -329,6 +329,11 @@ bindEvents: function() { ...@@ -329,6 +329,11 @@ bindEvents: function() {
} }
}; };
if (_this.boundsToFocusOnNextOpen) {
_this.eventEmitter.publish('fitBounds.' + _this.windowId, _this.boundsToFocusOnNextOpen);
_this.boundsToFocusOnNextOpen = null;
}
_this.osd.world.addHandler( "add-item", addItemHandler ); _this.osd.world.addHandler( "add-item", addItemHandler );
_this.osd.addHandler('zoom', $.debounce(function(){ _this.osd.addHandler('zoom', $.debounce(function(){
... ...
......
...@@ -599,6 +599,11 @@ ...@@ -599,6 +599,11 @@
_this.setBounds(); _this.setBounds();
} }
if (_this.boundsToFocusOnNextOpen) {
_this.eventEmitter.publish('fitBounds.' + _this.windowId, _this.boundsToFocusOnNextOpen);
_this.boundsToFocusOnNextOpen = null;
}
_this.addAnnotationsLayer(_this.elemAnno); _this.addAnnotationsLayer(_this.elemAnno);
// get the state before resetting it so we can get back to that state // get the state before resetting it so we can get back to that state
... ...
......
...@@ -55,11 +55,17 @@ ...@@ -55,11 +55,17 @@
this.localState(localState); this.localState(localState);
}, },
toggle: function() {}, imageFocusUpdated: function(focus) {
var localState = this.localState();
localState.active = (focus === 'ThumbnailsView') ? false : true;
this.localState(localState);
},
listenForActions: function() { listenForActions: function() {
var _this = this; var _this = this;
// This event is fired by the component itself anytime its local state is updated.
_this.eventEmitter.subscribe('layersTabStateUpdated.' + _this.windowId, function(_, data) { _this.eventEmitter.subscribe('layersTabStateUpdated.' + _this.windowId, function(_, data) {
_this.render(data); _this.render(data);
}); });
...@@ -71,6 +77,12 @@ ...@@ -71,6 +77,12 @@
_this.eventEmitter.subscribe('currentCanvasIDUpdated.' + _this.windowId, function(event, canvasID) { _this.eventEmitter.subscribe('currentCanvasIDUpdated.' + _this.windowId, function(event, canvasID) {
//update layers for this canvasID //update layers for this canvasID
}); });
_this.eventEmitter.subscribe('focusUpdated' + _this.windowId, function(event, focus) {
// update the disabled state of the layersTab
// since it cannot be used in overview mode
_this.imageFocusUpdated(focus);
});
}, },
bindEvents: function() { bindEvents: function() {
...@@ -80,7 +92,9 @@ ...@@ -80,7 +92,9 @@
render: function(state) { render: function(state) {
var _this = this, var _this = this,
templateData = {}; templateData = {
active: state.active ? '' : 'inactive'
};
if (this.element) { if (this.element) {
_this.appendTo.find(".layersPanel").remove(); _this.appendTo.find(".layersPanel").remove();
...@@ -89,7 +103,6 @@ ...@@ -89,7 +103,6 @@
_this.bindEvents(); _this.bindEvents();
if (state.visible) { if (state.visible) {
this.element.show(); this.element.show();
} else { } else {
...@@ -97,8 +110,8 @@ ...@@ -97,8 +110,8 @@
} }
}, },
template: $.Handlebars.compile([ template: Handlebars.compile([
'<div class="layersPanel">', '<div class="layersPanel {{active}}">',
'</div>', '</div>',
].join('')) ].join(''))
}; };
... ...
......
...@@ -169,7 +169,7 @@ ...@@ -169,7 +169,7 @@
var collectionLabel = within.label || collectionUrl; var collectionLabel = within.label || collectionUrl;
return '<a href="' + collectionUrl + '" target="_blank">' + collectionLabel + '</a>'; return '<a href="' + collectionUrl + '" target="_blank">' + collectionLabel + '</a>';
} else if (within instanceof Array) { } else if (within instanceof Array) {
return within.map(this.getWithin).join("<br/>"); return within.map(this.getWithin, this).join("<br/>");
} else { } else {
return this.stringifyObject(within); return this.stringifyObject(within);
} }
... ...
......
(function($) {
$.SearchTab = function(options) {
jQuery.extend(true, this, {
element: null,
appendTo: null,
manifest: null,
visible: null,
canvasID: null,
windowId: null,
eventEmitter: null,
}, options);
this.init();
};
$.SearchTab.prototype = {
init: function() {
var _this = this;
this.windowId = this.windowId;
this.localState({
id: 'searchTab',
visible: this.visible,
}, true);
this.listenForActions();
this.render(this.localState());
this.loadTabComponents();
},
localState: function(state, initial) {
if (!arguments.length) return this.searchTabState;
this.searchTabState = state;
if (!initial) {
this.eventEmitter.publish('searchTabStateUpdated.' + this.windowId, this.searchTabState);
}
return this.searchTabState;
},
loadTabComponents: function() {
var _this = this;
},
tabStateUpdated: function(data) {
if (data.tabs[data.selectedTabIndex].options.id === 'searchTab') {
this.element.show();
}
else {
this.element.hide();
}
},
toggle: function() {},
listenForActions: function() {
var _this = this;
//jQuery.subscribe('searchTabStateUpdated.' + _this.windowId, function(_, data) {
// _this.render(data);
//});
this.eventEmitter.subscribe('tabStateUpdated.' + _this.windowId, function(_, data) {
_this.tabStateUpdated(data);
});
// eventEmitter.subscribe('currentCanvasIDUpdated.' + _this.windowId, function(event) {
//
// });
},
displaySearchWithin: function(query_params, searchUrl){
var _this = this;
if (query_params !== "") {
this.searchObject = new $.SearchWithinResults({
manifest: _this.manifest,
appendTo: _this.element.find(".search-results-list"),
panel: true,
canvasID: _this.canvasID,
windowId: _this.windowId,
imagesList: _this.imagesList,
thumbInfo: {thumbsHeight: 80, listingCssCls: 'panel-listing-thumbs', thumbnailCls: 'panel-thumbnail-view'},
query_params: query_params,
searchService: searchUrl,
eventEmitter: _this.eventEmitter
});
}
},
bindEvents: function() {
var _this = this;
this.element.find(".js-perform-query").on('submit', function(event){
event.preventDefault();
var query = _this.element.find(".js-query").val();
var motivation = _this.element.find(".js-motivation").val();
var date = _this.element.find(".js-date").val();
var user = _this.element.find(".js-user").val();
var searchUrl = _this.element.find("#search-within-selector").val();
_this.displaySearchWithin({
"q": query,
"motivation": motivation,
"date": date,
"user": user
}, searchUrl);
});
this.element.find(".js-search-expand").on('click', function(event){
event.preventDefault();
_this.element.find(".js-search-expanded").slideToggle("fast");
if (jQuery(this).text() === "more"){
jQuery(this).html("less");
}
else if (jQuery(this).text() === "less"){
jQuery(this).html("more");
}
});
},
render: function(state) {
var _this = this;
var searchService = this.manifest.getSearchWithinService(),
searchServiceIdArray = searchService && searchService.map(function(data){
return {
"url": data['@id'],
"label": data.label
};
});
templateData = {
searchService: searchServiceIdArray
};
if (!this.element) {
this.element = jQuery(_this.template(templateData)).appendTo(_this.appendTo);
_this.bindEvents();
} else {
_this.appendTo.find(".search-results").remove();
this.element = jQuery(_this.template(templateData)).appendTo(_this.appendTo);
}
if (state.visible) {
this.element.show();
} else {
this.element.hide();
}
},
template: Handlebars.compile([
'<div class="search-results">',
'{{#if searchService}}',
'<label>Select Search Service',
'<select id="search-within-selector" style="width: 100%">',
'{{#each searchService}}',
'<option value="{{ url }}">{{#if label}}{{ label }}{{ else }} {{ url }}{{/if}}</option>',
'{{/each}}',
'</select>',
'</label>',
'<form id="search-within-form" class="js-perform-query">',
'<input class="js-query" type="text" placeholder="search text"/>',
'<input style="margin: 10px 0" type="submit"/>',
'<a class="js-search-expand" style="display: block; margin: 0 0 5px 0">more</a>',
'<div class="js-search-expanded" style="display: none;">',
'<input class="js-motivation" type="text" placeholder="motivation"/>',
'<input class="js-date" type="text" placeholder="date"/>',
'<input class="js-user" type="text" placeholder="user"/>',
// '<input class="js-box" type="text" placeholder="box: x, y, w, h"/>',
'</div>',
'</form>',
'<div class="search-results-list"></div>',
'{{else}}',
'No search service available',
'{{/if}}',
'</div>',
].join(''))
};
}(Mirador));
(function($) {
/**
* UI + logic to get search results for a given search query. On initialization,
* the provided search query is given tothe provided IIIF Search service.
* The response is displayed as a list.
*
* Parameter 'query_params': {q: query, motivation: motivation, date: date, user: user}
*
* Currently follows IIIF Content Search API v1.0
* (http://iiif.io/api/search/0.9/)
*/
$.SearchWithinResults = function(options) {
jQuery.extend(this, {
manifest: null,
element: null,
metadataTypes: null,
metadataListingCls: 'metadata-listing',
/** Search service URL */
searchService: null,
windowId: null,
/** {q: query, motivation: motivation, date: date, user: user} */
query_params: null,
/** Used for paging. This assumes that searches start on page 1... */
currentPage: 1,
eventEmitter: null
}, options);
this.init();
};
$.SearchWithinResults.prototype = {
init: function() {
this.registerHandlebars();
jQuery(this.appendTo).empty();
this.element = jQuery(this.template()).appendTo(this.appendTo);
jQuery("<hr/><h3>Search results for: " + this.query_params.q + "</h3><hr/>")
.appendTo(this.appendTo.find('.search-results-messages'));
this.doSearchFromQuery(this.query_params);
},
doSearchFromQuery: function(query_params) {
query_string = "";
for (var param in query_params){
if (param === "q"){
query_string += param + "=" + query_params[param];
}
else if (query_params[param].length > 0){
query_string += "&" + param + "=" + query_params[param];
}
}
var url = this.searchService + '?' + query_string;
this.doSearchFromUrl(url);
},
/**
* AJAX request is made from here!
*
* TODO Perhaps emit a search event here for the purposes of
* state tracking or analytics?
*
* @param {[type]} url [description]
* @return {[type]} [description]
*/
doSearchFromUrl: function(url) {
var _this = this;
this.element.find('.search-results-container').empty();
jQuery.ajax({
url: url,
dataType: 'json',
async: true
})
.done(function(searchResults) {
if (searchResults.resources) {
// display result totals
_this.displayResultCounts(searchResults);
// show results list
_this.processResults(searchResults);
} else {
jQuery('.search-results-count').html("<hr/><p>No results</p><hr/>");
}
})
.fail(function() {
jQuery('.search-results-count').html("<hr/><p>No results</p><hr/>");
})
.always();
},
displayResultCounts: function(searchResults){
var total = searchResults.within.total,
startResultNumber = searchResults.startIndex + 1,
endResultNumber = "";
//if there is only only resource, it will not be array and therefore we can't
//take the length of it; this conditions check first to see if the resources
//property contains an array or single object
// TODO: not that this single object vs. array seems to be problem throughout.
// Pages with only one result do not display properly. See for example:
// http://exist.scta.info/exist/apps/scta/iiif/pl-zbsSII72/search?q=fides&page=3
if (searchResults.resources.constructor === Array) {
endResultNumber = searchResults.startIndex + searchResults.resources.length;
} else {
endResultNumber = searchResults.startIndex + 1;
}
jQuery('.search-results-count').html("<hr/><p>Showing " + startResultNumber + " - " + endResultNumber + " out of " + total + "</p><hr/>");
},
processResults: function(searchResults) {
//create tplData array
if (searchResults.hits) {
this.tplData = this.getHits(searchResults);
} else {
this.tplData = this.getSearchAnnotations(searchResults);
}
jQuery(Handlebars.compile('{{> resultsList }}')(this.tplData)).appendTo(jQuery(this.element.find('.search-results-container')));
this.bindEvents();
this.setPager(searchResults);
},
/**
* Look for necessary properties that point to the need for paging.
* Check for total number of results vs number of returned results.
*
* @param results IIIF Search results
* @return TRUE if paging is needed
*/
needsPager: function(results) {
// Check for some properties on the search results
if (!results || !results.within || !results.resources) {
return false;
}
var total = results.within.total;
// Check if 'resources' (list of annotations) is an array, or single value
if (Array.isArray(results.resources)) {
return results.resources.length < total;
} else {
return total > 1;
}
},
/**
* Initialize search results pager. It is assumed that it has already
* been determined whether or not the pager needs to be created.
* If a pager is created, it will be inserted into the DOM.
*
* If it is determined that this set of results does not need paging,
* then this function will exit early and no paging will be set.
* {@link SearchWithinResults#needsPager}
*
* @param results - IIIF Search results
*/
setPager: function(searchResults) {
var _this = this;
var pager = this.element.find('.search-results-pager');
// HACK: pager.pagination will be undefined until canvasID are set properly
if (!this.needsPager(searchResults) || !pager.pagination) {
return;
}
/*
* Hack to get proper page numbers.
* TODO probably shouldn't use this library if it requires page numbers,
* instead have something with ONLY FIRST/LAST and PREV/NEXT controls.
*/
if (!this.onPageCount) { // This will be set with initial page and not be changed.
this.onPageCount = searchResults.resources.length;
}
pager.pagination({
items: searchResults.within.total,
itemsOnPage: _this.onPageCount,
currentPage: _this.currentPage,
displayedPages: 1,
edges: 1,
ellipsePageSet: false,
cssStyle: 'compact-theme',
hrefTextPrefix: '',
prevText: '<i class="fa fa-lg fa-angle-left"></i>',
nextText: '<i class="fa fa-lg fa-angle-right"></i>',
onPageClick: function(pageNumber, event) {
event.preventDefault();
pager.pagination('disable');
if (pageNumber == _this.currentPage - 1) {
_this.currentPage--;
_this.doSearchFromUrl(searchResults.prev);
} else if (pageNumber == _this.currentPage + 1) {
_this.currentPage++;
_this.doSearchFromUrl(searchResults.next);
} else if (pageNumber == 1) {
_this.doSearchFromUrl(searchResults.within.first);
_this.currentPage = 1;
} else {
// Assume this is the last page........
_this.doSearchFromUrl(searchResults.within.last);
/*
* NOTE: There is no good way to get the page number from the search URL.
* IIIF Content Search v1.0 spec (I do not think) does not define
* how to specify a page.
* -- For example, the page could be put into the URL
* query string, or it could be put into the URL fragment, or somewhere
* else.
*
* This hack pulls the number of the last page on the pager. :/
*/
_this.currentPage = parseInt(
_this.element.find('.search-results-pager li:nth-last-child(2)').text()
);
}
}
});
pager.pagination('enable');
},
/**
* Do a Bitwise OR to truncate decimal
*
* @param num original number, could be integer or decimal
* @return integer with any decimal part of input truncated (no rounding)
*/
float2int: function(num) {
return num | 0;
},
parseSearchAnnotation: function(annotation){
var _this = this;
var canvasid = annotation.on;
// deals with possibiliy of "on" propert taking an object
if (typeof canvasid === 'object') {
canvasid = annotation.on['@id'];
}
var canvaslabel = _this.getLabel(annotation);
// Split ID from Coordinates if necessary
var id_parts = _this.splitBaseUrlAndCoordinates(canvasid);
return {
canvasid: id_parts.base,
coordinates: id_parts.coords,
canvaslabel: canvaslabel,
resulttext: annotation.resource.chars
};
},
getSearchAnnotations: function(searchResults) {
var _this = this;
tplData = [];
//add condition here to make sure searchResults.resources is not null
if (searchResults.resources !== null) {
//This conditional handles if the results come back as a single object or as an array
//TODO: This possibility is not yet handled in the getHits function
if (!Array.isArray(searchResults.resources)){
annotation = searchResults.resources;
tplData.push(_this.parseSearchAnnotation(annotation));
}
else {
searchResults.resources.forEach(function(annotation){
tplData.push(_this.parseSearchAnnotation(annotation));
});
}
return tplData;
}
},
getHits: function(searchResults) {
var _this = this;
tplData = [];
searchResults.hits.forEach(function(hit) {
//this seems like a really slow way to retrieve on property from hits
//note that at present it is only retrieving the first annotation
//but a hit annotation property takes an array and could have more than one
//annotation -- its not a very common case but a possibility.
var resultObject, resultObjects = [];
hit.annotations.forEach(function(annotation) {
//canvases could come back as an array
var resource = _this.getHitResources(searchResults, annotation)[0],
canvasLabel = _this.getLabel(resource),
canvasID = resource && resource.on;
// If you have the full annotation, set ID and label appropriately
if (typeof canvasID === 'object') {
canvasID = resource.on['@id'];
}
// Extract coordinates if necessary
var canvasIDParts = _this.splitBaseUrlAndCoordinates(canvasID);
resultObject = {
canvasid: canvasIDParts.base,
coordinates: canvasIDParts.coords,
canvaslabel: canvasLabel,
hit: hit // TODO must handle different results structures, see IIIF search spec for different responses
};
resultObjects.push(resultObject);
});
// First result is returned and gets attached an array of all annotations
if (resultObjects) {
resultObject = resultObjects[0];
if (resultObjects.length > 1) {
resultObject.annotations = resultObjects;
}
tplData.push(resultObject);
}
});
return tplData;
},
/**
* Get a label describing a search match. This label is set to the
* associated annotation label, if available, or to the label of the
* parent canvas.
*
* @param {[type]} resource annotation associated with the search match
* @return {[type]} string label
*/
getLabel: function(resource) {
var label;
if (resource && typeof resource === 'object') {
if (resource.label) {
return resource.label;
} else if (resource.resource.label){
return resource.resource.label;
} else if (resource.on && typeof resource.on === 'string') {
label = this.manifest.getCanvasLabel(resource.on);
return label ? 'Canvas ' + label : undefined;
} else if (resource.on && typeof resource.on === 'object') {
label = resource.on.label ? resource.on.label : this.manifest.getCanvasLabel(resource.on['@id']);
return label ? 'Canvas ' + label : undefined;
}
} else {
return undefined;
}
},
getHitResources: function(searchResults, annotationid) {
// Get array of results
return searchResults.resources.filter(function(resource){
return resource['@id'] === annotationid;
});
},
/**
* @param url - a resource ID
* @return {
* base: base URL, or the original url param if no coords are present,
* coords: coordinates from ID if present
* }
*/
splitBaseUrlAndCoordinates: function(url) {
var coordinates;
var base = url;
if (typeof url === 'string') {
// Separate base ID from fragment selector
var parts = url.split('#');
base = parts[0];
if (parts.length === 2) {
coordinates = parts[1];
}
}
return {
base: base,
coords: coordinates
};
},
bindEvents: function() {
var _this = this;
// jQuery.subscribe(('currentCanvasIDUpdated.' + _this.windowId), function(event, canvasID) {
// //if (!_this.structures) { return; }
// //_this.setSelectedElements($.getRangeIDByCanvasID(_this.structures, canvasID));
// //_this.render();
//
// });
//TODO
//This function works to move the user to the specified canvas
//but if there are associated coordinates, it does not yet know how to highlight
//those coordinates.
//thie miniAnnotatList attempted to do this, but is no longer working
//it needs to be replaced by a new strategy.
this.element.find('.js-show-canvas').on("click", function(event) {
event.stopPropagation();
var canvasid = jQuery(this).attr('data-canvasid'),
coordinates = jQuery(this).attr('data-coordinates'),
xywh = coordinates && coordinates.split('=')[1].split(',').map(Number),
bounds = xywh && {x: xywh[0], y: xywh[1], width: xywh[2], height: xywh[3]};
jQuery(".result-wrapper").css("background-color", "inherit");
jQuery(this).parent().css("background-color", "lightyellow");
//if there was more than one annotation
//(for example if a word crossed a line and needed two coordinates sets)
//the miniAnnotationList should have multiple objects
miniAnnotationList = [{
"@id": "test",
"@type": "oa:Annotation",
"motivation": "sc:painting",
"resource": {
"@type": "cnt:ContentAsText",
"chars": _this.query
},
"on": canvasid + (coordinates ? "#" + coordinates : '')
}];
//_this.parent.annotationsList = miniAnnotationList;
var options = {
"canvasID": canvasid,
"bounds": bounds
};
_this.eventEmitter.publish('SET_CURRENT_CANVAS_ID.' + _this.windowId, options);
});
},
registerHandlebars: function() {
Handlebars.registerPartial('resultsList', [
'{{#each this}}',
'<div class="result-wrapper">',
'<a class="search-result search-title js-show-canvas" data-canvasid="{{canvasid}}" data-coordinates="{{coordinates}}">',
'{{canvaslabel}}',
'</a>',
'{{#if annotations}}',
'<div>',
'Annotations: ',
'{{#each annotations}}',
'<a class="search-result search-annotation js-show-canvas" data-canvasid="{{canvasid}}" data-coordinates="{{coordinates}}">',
'<i class="fa fa-fw" aria-hidden="true"></i>',
'</a>',
'{{/each}}',
'</div>',
'{{/if}}',
'<div class="search-result result-paragraph js-show-canvas" data-canvasid="{{canvasid}}" data-coordinates="{{coordinates}}">',
'{{#if hit.before}}',
'{{hit.before}} ',
'{{/if}}',
'{{#if hit.match}}',
'<span class="highlight">{{hit.match}}</span>',
'{{else}}',
'{{{resulttext}}}', // If this text must NOT be escaped, use: '{{resulttext}}'
'{{/if}}',
'{{#if hit.after}}',
'{{ hit.after}}',
'{{/if}}',
'</div>',
'</div>',
'{{/each}}',
].join(''));
},
/**
* Handlebars template. Accepts data and formats appropriately. To use,
* just pass in the template data and this will return a String with
* the formatted HTML which can then be inserted into the DOM.
*
* This template expects a IIIF AnnotationList formatted to represent
* IIIF Search results.
*
* EX: assume context:
* var templateData = { template data goes here }
* var htmlString = template(templateData);
*/
template: Handlebars.compile([
'<div>',
'<div class="search-results-messages"></div>',
'<div class="search-results-count"></div>',
'<div class="search-results-pager"></div>',
'<div class="search-results-container">',
'{{> resultsList }}',
'</div>',
'</div>'
].join(""))};
}(Mirador));
...@@ -6,10 +6,11 @@ ...@@ -6,10 +6,11 @@
appendTo: null, appendTo: null,
manifest: null, manifest: null,
panelState: {}, panelState: {},
tocTabAvailable: false, tocTabAvailable: null,
annotationsTabAvailable: false, // annotationsTabAvailable: false,
layersTabAvailable: false, // layersTabAvailable: null,
toolsTabAvailable: false, // toolsTabAvailable: false,
searchTabAvailable: null,
hasStructures: false, hasStructures: false,
state: null, state: null,
eventEmitter: null eventEmitter: null
...@@ -40,14 +41,14 @@ ...@@ -40,14 +41,14 @@
label:'Annotations' label:'Annotations'
} }
},*/ },*/
{ // {
name : 'layers', // name : 'layers',
options : { // options : {
available: _this.layersTabAvailable, // available: _this.layersTabAvailable,
id:'layersTab', // id:'layersTab',
label:'Layers' // label:'Layers'
} // }
}, // },
/*{ /*{
name : 'tools', name : 'tools',
options : { options : {
...@@ -56,6 +57,14 @@ ...@@ -56,6 +57,14 @@
label:'Tools' label:'Tools'
} }
}*/ }*/
{
name : 'search',
options : {
available: _this.searchTabAvailable,
id: 'searchTab',
label: 'Search'
}
}
], ],
width: 280, width: 280,
open: true open: true
...@@ -97,16 +106,26 @@ ...@@ -97,16 +106,26 @@
eventEmitter: _this.eventEmitter eventEmitter: _this.eventEmitter
}); });
} }
if (_this.layersTabAvailable) { if (_this.searchTabAvailable) {
new $.LayersTab({ new $.SearchTab({
manifest: _this.manifest, manifest: _this.manifest,
windowId: this.windowId, windowId: this.windowId,
appendTo: _this.element.find('.tabContentArea'), appendTo: _this.element.find('.tabContentArea'),
canvasID: this.canvasID,
state: _this.state, state: _this.state,
manifestVersion: this.manifest.getVersion(),
eventEmitter: _this.eventEmitter eventEmitter: _this.eventEmitter
}); });
} }
// if (_this.layersTabAvailable) {
// new $.LayersTab({
// manifest: _this.manifest,
// windowId: this.windowId,
// appendTo: _this.element.find('.tabContentArea'),
// canvasID: this.canvasID,
// state: _this.state,
// eventEmitter: _this.eventEmitter
// });
// }
}, },
... ...
......
...@@ -19,8 +19,7 @@ ...@@ -19,8 +19,7 @@
this.state({ this.state({
tabs : this.tabs, tabs : this.tabs,
//tabs: [{id:'tocTab', label:'Indices'}, {id:'annotationsTab', label:'Annotations'}], // tabs: [{id:'tocTab', label:'Indices'}, {id:'searchTab', label:'Search'}],
//tabs: [{id:'tocTab', label:'Indices'}],
selectedTabIndex: 0 selectedTabIndex: 0
}, true); }, true);
this.listenForActions(); this.listenForActions();
...@@ -46,7 +45,8 @@ ...@@ -46,7 +45,8 @@
getTemplateData: function() { getTemplateData: function() {
return { return {
annotationsTab: this.state().annotationsTab, annotationsTab: this.state().annotationsTab,
tocTab: this.state().tocTab tocTab: this.state().tocTab,
searchTab: this.state().searchTab
}; };
}, },
listenForActions: function() { listenForActions: function() {
... ...
......
...@@ -85,12 +85,13 @@ ...@@ -85,12 +85,13 @@
scrollPosition, scrollPosition,
windowObject = this.state.getWindowObjectById(this.windowId); windowObject = this.state.getWindowObjectById(this.windowId);
if (target.position()) {
if (windowObject && windowObject.viewType === 'BookView') { if (windowObject && windowObject.viewType === 'BookView') {
scrollPosition = _this.element.scrollLeft() + (target.position().left + (target.next().width() + target.outerWidth())/2) - _this.element.width()/2; scrollPosition = _this.element.scrollLeft() + (target.position().left + (target.next().width() + target.outerWidth())/2) - _this.element.width()/2;
} else { } else {
scrollPosition = _this.element.scrollLeft() + (target.position().left + target.width()/2) - _this.element.width()/2; scrollPosition = _this.element.scrollLeft() + (target.position().left + target.width()/2) - _this.element.width()/2;
} }
}
_this.element.scrollTo(scrollPosition, 900); _this.element.scrollTo(scrollPosition, 900);
}, },
... ...
......
...@@ -307,7 +307,17 @@ ...@@ -307,7 +307,17 @@
})); }));
_this.events.push(_this.eventEmitter.subscribe('SET_CURRENT_CANVAS_ID.' + this.id, function(event, canvasID) { _this.events.push(_this.eventEmitter.subscribe('SET_CURRENT_CANVAS_ID.' + this.id, function(event, canvasID) {
if (typeof canvasID === "string") {
_this.setCurrentCanvasID(canvasID); _this.setCurrentCanvasID(canvasID);
} else {
if (_this.canvasID !== canvasID.canvasID) {
// Order is important
_this.setNextCanvasBounds(canvasID.bounds);
_this.setCurrentCanvasID(canvasID.canvasID);
} else {
_this.eventEmitter.publish('fitBounds.' + _this.id, canvasID.bounds);
}
}
})); }));
_this.events.push(_this.eventEmitter.subscribe('REMOVE_CLASS.' + this.id, function(event, className) { _this.events.push(_this.eventEmitter.subscribe('REMOVE_CLASS.' + this.id, function(event, className) {
...@@ -505,7 +515,8 @@ ...@@ -505,7 +515,8 @@
var _this = this, var _this = this,
tocAvailable = _this.sidePanelOptions.toc, tocAvailable = _this.sidePanelOptions.toc,
annotationsTabAvailable = _this.sidePanelOptions.annotations, annotationsTabAvailable = _this.sidePanelOptions.annotations,
layersTabAvailable = _this.sidePanelOptions.layers, layersTabAvailable = _this.sidePanelOptions.layersTabAvailable,
searchTabAvailable = _this.sidePanelOptions.searchTabAvailable,
hasStructures = true; hasStructures = true;
var structures = _this.manifest.getStructures(); var structures = _this.manifest.getStructures();
...@@ -523,6 +534,7 @@ ...@@ -523,6 +534,7 @@
canvasID: _this.canvasID, canvasID: _this.canvasID,
layersTabAvailable: layersTabAvailable, layersTabAvailable: layersTabAvailable,
tocTabAvailable: tocAvailable, tocTabAvailable: tocAvailable,
searchTabAvailable: searchTabAvailable,
annotationsTabAvailable: annotationsTabAvailable, annotationsTabAvailable: annotationsTabAvailable,
hasStructures: hasStructures hasStructures: hasStructures
}); });
...@@ -641,7 +653,7 @@ ...@@ -641,7 +653,7 @@
this.updateManifestInfo(); this.updateManifestInfo();
this.updatePanelsAndOverlay(focusState); this.updatePanelsAndOverlay(focusState);
this.updateSidePanel(); this.updateSidePanel();
_this.eventEmitter.publish("focusUpdated"); _this.eventEmitter.publish("focusUpdated" + _this.id, focusState);
_this.eventEmitter.publish("windowUpdated", { _this.eventEmitter.publish("windowUpdated", {
id: _this.id, id: _this.id,
viewType: _this.viewType, viewType: _this.viewType,
...@@ -691,6 +703,9 @@ ...@@ -691,6 +703,9 @@
}); });
} else { } else {
var view = this.focusModules.ImageView; var view = this.focusModules.ImageView;
if (this.boundsToFocusOnNextOpen) {
view.boundsToFocusOnNextOpen = this.boundsToFocusOnNextOpen;
}
view.updateImage(canvasID); view.updateImage(canvasID);
} }
this.toggleFocus('ImageView', 'ImageView'); this.toggleFocus('ImageView', 'ImageView');
...@@ -765,6 +780,12 @@ ...@@ -765,6 +780,12 @@
_this.eventEmitter.publish(('currentCanvasIDUpdated.' + _this.id), canvasID); _this.eventEmitter.publish(('currentCanvasIDUpdated.' + _this.id), canvasID);
}, },
setNextCanvasBounds: function(bounds) {
if (bounds) {
this.boundsToFocusOnNextOpen = bounds;
}
},
replaceWindow: function(newSlotAddress, newElement) { replaceWindow: function(newSlotAddress, newElement) {
this.slotAddress = newSlotAddress; this.slotAddress = newSlotAddress;
this.appendTo = newElement; this.appendTo = newElement;
... ...
......
This diff is collapsed.
{
"@context": "http://iiif.io/api/search/0/context.json",
"@id": "http://wellcomelibrary.org/annoservices/search/b18035978?q=blah",
"@type": "sc:AnnotationList",
"within": {
"@type": "sc:Layer",
"total": 0
},
"startIndex": 0,
"resources": [],
"hits": []
}
{
"@context": "http://iiif.io/api/search/0/context.json",
"@id": "http://wellcomelibrary.org/annoservices/search/b18035978?q=figures",
"@type": "sc:AnnotationList",
"within": {
"@type": "sc:Layer",
"total": 11
},
"startIndex": 0,
"resources": [
{
"@id": "http://wellcomelibrary.org/iiif/b18035978/annos/searchResults/a25h0r1220,2462,126,44",
"@type": "oa:Annotation",
"motivation": "sc:painting",
"resource": {
"@type": "cnt:ContentAsText",
"chars": "figures"
},
"on": "http://wellcomelibrary.org/iiif/b18035978/canvas/c25#xywh=1220,2462,126,44"
},
{
"@id": "http://wellcomelibrary.org/iiif/b18035978/annos/searchResults/a26h1r1495,375,150,58",
"@type": "oa:Annotation",
"motivation": "sc:painting",
"resource": {
"@type": "cnt:ContentAsText",
"chars": "figures"
},
"on": "http://wellcomelibrary.org/iiif/b18035978/canvas/c26#xywh=1495,375,150,58"
},
{
"@id": "http://wellcomelibrary.org/iiif/b18035978/annos/searchResults/a27h2r708,893,145,56",
"@type": "oa:Annotation",
"motivation": "sc:painting",
"resource": {
"@type": "cnt:ContentAsText",
"chars": "figures"
},
"on": "http://wellcomelibrary.org/iiif/b18035978/canvas/c27#xywh=708,893,145,56"
},
{
"@id": "http://wellcomelibrary.org/iiif/b18035978/annos/searchResults/a29h3r520,370,145,56",
"@type": "oa:Annotation",
"motivation": "sc:painting",
"resource": {
"@type": "cnt:ContentAsText",
"chars": "figures"
},
"on": "http://wellcomelibrary.org/iiif/b18035978/canvas/c29#xywh=520,370,145,56"
},
{
"@id": "http://wellcomelibrary.org/iiif/b18035978/annos/searchResults/a32h4r1424,1867,148,58",
"@type": "oa:Annotation",
"motivation": "sc:painting",
"resource": {
"@type": "cnt:ContentAsText",
"chars": "figures"
},
"on": "http://wellcomelibrary.org/iiif/b18035978/canvas/c32#xywh=1424,1867,148,58"
},
{
"@id": "http://wellcomelibrary.org/iiif/b18035978/annos/searchResults/a33h5r316,1061,127,44",
"@type": "oa:Annotation",
"motivation": "sc:painting",
"resource": {
"@type": "cnt:ContentAsText",
"chars": "figures"
},
"on": "http://wellcomelibrary.org/iiif/b18035978/canvas/c33#xywh=316,1061,127,44"
},
{
"@id": "http://wellcomelibrary.org/iiif/b18035978/annos/searchResults/a48h6r1100,631,160,58",
"@type": "oa:Annotation",
"motivation": "sc:painting",
"resource": {
"@type": "cnt:ContentAsText",
"chars": "figures,"
},
"on": "http://wellcomelibrary.org/iiif/b18035978/canvas/c48#xywh=1100,631,160,58"
},
{
"@id": "http://wellcomelibrary.org/iiif/b18035978/annos/searchResults/a48h7r1321,2670,128,46",
"@type": "oa:Annotation",
"motivation": "sc:painting",
"resource": {
"@type": "cnt:ContentAsText",
"chars": "figures"
},
"on": "http://wellcomelibrary.org/iiif/b18035978/canvas/c48#xywh=1321,2670,128,46"
},
{
"@id": "http://wellcomelibrary.org/iiif/b18035978/annos/searchResults/a51h8r512,2025,146,57",
"@type": "oa:Annotation",
"motivation": "sc:painting",
"resource": {
"@type": "cnt:ContentAsText",
"chars": "figures"
},
"on": "http://wellcomelibrary.org/iiif/b18035978/canvas/c51#xywh=512,2025,146,57"
},
{
"@id": "http://wellcomelibrary.org/iiif/b18035978/annos/searchResults/a55h9r979,383,145,57",
"@type": "oa:Annotation",
"motivation": "sc:painting",
"resource": {
"@type": "cnt:ContentAsText",
"chars": "figures"
},
"on": "http://wellcomelibrary.org/iiif/b18035978/canvas/c55#xywh=979,383,145,57"
},
{
"@id": "http://wellcomelibrary.org/iiif/b18035978/annos/searchResults/a56h10r1340,2244,128,45",
"@type": "oa:Annotation",
"motivation": "sc:painting",
"resource": {
"@type": "cnt:ContentAsText",
"chars": "figures"
},
"on": "http://wellcomelibrary.org/iiif/b18035978/canvas/c56#xywh=1340,2244,128,45"
}
],
"hits": [
{
"@type": "search:Hit",
"annotations": [
"http://wellcomelibrary.org/iiif/b18035978/annos/searchResults/a25h0r1220,2462,126,44"
],
"match": "figures",
"before": "anted or unplanned.f *Griselda Rowntree to the International Congress on Mental Health, London, 1968, reported in The Times, August 13th, 1968. Other ",
"after": " given here come from the Family Plaruiing Association, or the Registrar General's Statistical Reviews. f E.g. A. C. Fraser and P. C. Watson, 'Family "
},
{
"@type": "search:Hit",
"annotations": [
"http://wellcomelibrary.org/iiif/b18035978/annos/searchResults/a26h1r1495,375,150,58"
],
"match": "figures",
"before": " just cited found that less than 20 per cent of women having babies in two city hospitals had ever received any expert contraceptive advice. National ",
"after": " confirm that this must be a general rule. The Family Planning Association estimate that of the ten million British women of child-bearing age, of who"
},
{
"@type": "search:Hit",
"annotations": [
"http://wellcomelibrary.org/iiif/b18035978/annos/searchResults/a27h2r708,893,145,56"
],
"match": "figures",
"before": "to these overall patterns of 'open' birth-control, with their stench of failure and sexual frustration, there are the 'closed' methods enjoyed—as the ",
"after": " hint—by the few. These are the effective, sexually silent methods: medical abortion, sterihzation, LU.D.s, the diaphragm and oral contraceptives. I c"
},
{
"@type": "search:Hit",
"annotations": [
"http://wellcomelibrary.org/iiif/b18035978/annos/searchResults/a29h3r520,370,145,56"
],
"match": "figures",
"before": "e (Longmans, London, 1965). 24 BIRTH-CONTROL cent), dislike of the results (21 per cent), or 'don't care' (20 per cent) as their reason. Surely, what ",
"after": " like these demand is that those who wish to reduce sexual 'immorality' should ask themselves just what they mean by it. Effective contraception has b"
},
{
"@type": "search:Hit",
"annotations": [
"http://wellcomelibrary.org/iiif/b18035978/annos/searchResults/a32h4r1424,1867,148,58"
],
"match": "figures",
"before": "traceptive known—because it is the most effective method of preventing pregnancy and therefore the risks of childbirth. Look at the rather surprising ",
"after": " shown in Table 1.2, based on the best available data for the failure-rate and death risks of various contraceptives. Dr Malcolm Potts, Medical Secret"
},
{
"@type": "search:Hit",
"annotations": [
"http://wellcomelibrary.org/iiif/b18035978/annos/searchResults/a33h5r316,1061,127,44"
],
"match": "figures",
"before": "000,000 women aged 20-34 use the method for a year: this is what happens After D. M. Potts, I.P.P.F. Medical Bulletin (October 1968). Note that these ",
"after": " are for Britain, with a low maternal mortality rate. Where death in childbirth is more likely the pill is relatively much safer, and unprotected sex "
},
{
"@type": "search:Hit",
"annotations": [
"http://wellcomelibrary.org/iiif/b18035978/annos/searchResults/a48h6r1100,631,160,58",
"http://wellcomelibrary.org/iiif/b18035978/annos/searchResults/a48h7r1321,2670,128,46"
],
"match": "figures, figures",
"before": "ortions as births; and in other Soviet bloc countries abortion rates vary between 250 to 600 per 1,000 live births. These seem like staggeringly high ",
"after": " such as that 12 per cent of urban women admit to at least one abortion. 44 BIRTH-CONTROL Uppsala, Sweden, tested such a drug—called F6103 —on fifty y"
},
{
"@type": "search:Hit",
"annotations": [
"http://wellcomelibrary.org/iiif/b18035978/annos/searchResults/a51h8r512,2025,146,57"
],
"match": "figures",
"before": "y multiply forty times and the area they cover may increase a hundredfold to equal one-fifth of the entire land surface of the planet. Every year the ",
"after": " tell the same seemingly relentless story: while the world's ability to produce food and other essentials slowly rises, the amount each person gets sl"
},
{
"@type": "search:Hit",
"annotations": [
"http://wellcomelibrary.org/iiif/b18035978/annos/searchResults/a55h9r979,383,145,57"
],
"match": "figures",
"before": "te effects. Most predictions, therefore, are based on constantly revised estimates of what has been happening, and can give only hazy upper and lower ",
"after": " for future population size, not which figure is most likely. Some of these estimates are pure guesswork. For example, the official population forecas"
},
{
"@type": "search:Hit",
"annotations": [
"http://wellcomelibrary.org/iiif/b18035978/annos/searchResults/a56h10r1340,2244,128,45"
],
"match": "figures",
"before": " was due to imder- estimates by couples who thought they were more subfertile than they actually were. When these are excluded the 'expect' and 'had' ",
"after": " were -91 and -92 respectively. If short-tenn predictions can be so accurate, hope for long-term forecasts rises. In fact the G.A.F. projections of Am"
}
]
}
...@@ -85,7 +85,7 @@ describe('MetadataView', function() { ...@@ -85,7 +85,7 @@ describe('MetadataView', function() {
}); });
describe('renderWithin', function() { describe('renderWithin', function() {
it('should render simple strings as-sis', function() { it('should render simple strings as-is', function() {
var within = "http://example.com"; var within = "http://example.com";
expect(Mirador.MetadataView.prototype.getWithin(within)).toBe(within); expect(Mirador.MetadataView.prototype.getWithin(within)).toBe(within);
}); });
...@@ -110,5 +110,11 @@ describe('MetadataView', function() { ...@@ -110,5 +110,11 @@ describe('MetadataView', function() {
'<a href="http://example.com" target="_blank">foobar</a><br/>' + '<a href="http://example.com" target="_blank">foobar</a><br/>' +
'<a href="http://foo.org" target="_blank">barfoo</a>'); '<a href="http://foo.org" target="_blank">barfoo</a>');
}); });
it('should render a list of strings <br/>-concatenated', function() {
var within = ['http://example.com/foo', 'http://example.com/bar'];
expect(Mirador.MetadataView.prototype.getWithin(within)).toBe(
'http://example.com/foo<br/>http://example.com/bar');
});
}); });
}); });
describe('Search tab', function() {
beforeEach(function(){
var _this = this;
jasmine.getJSONFixtures().fixturesPath = 'spec/fixtures';
this.searchURI = "/search?q="
this.eventEmitter = new Mirador.EventEmitter();
this.jsonLd = getJSONFixture('searchManifest.json');
this.results = getJSONFixture('searchManifest.results.json');
this.manifest = new Mirador.Manifest(null, null, this.jsonLd);
this.jsonLdNoSearch = getJSONFixture('BNF-condorcet-florus-dispersus-manifest.json');
this.noResults = getJSONFixture('searchManifest.noresults.json');
this.manifestNoSearch = new Mirador.Manifest(null, null, this.jsonLdNoSearch);
this.sandbox = sandbox();
this.windowId = '380c9e54-7561-4010-a99f-f132f5dc13fd';
this.createSearchTab = function(manifest) {
return new Mirador.SearchTab({
manifest: manifest,
appendTo: _this.sandbox,
windowId: this.windowId,
canvasID: 1234,
eventEmitter: _this.eventEmitter
});
}
});
afterEach(function() {
});
describe('on instantiation', function(){
it('should instantiate a search tab object', function() {
expect(this.createSearchTab.bind(this.createSearchTab, this.manifest)).not.toThrow();
});
});
describe('on initialization', function(){
it('should render a search tab', function() {
this.createSearchTab(this.manifest);
expect(this.sandbox.find('.search-results')).toExist();
});
it('should render a search form', function() {
this.createSearchTab(this.manifest);
expect(this.sandbox.find('#search-within-form')).toExist();
});
it('should render a search tab when there is not such service in the manifest', function() {
this.createSearchTab(this.manifestNoSearch);
expect(this.sandbox.find('.search-results')).toExist();
});
it('should not render a search form when there is not such service in the manifest', function() {
this.createSearchTab(this.manifestNoSearch);
expect(this.sandbox.find('#search-within-form')).not.toExist();
});
it('should allow to search text', function() {
this.createSearchTab(this.manifest);
var _this = this,
searchForm = this.sandbox.find('#search-within-form'),
searchInput = this.sandbox.find('#search-within-form input.js-query');
expect(searchInput).toExist();
searchInput.val('found');
expect(searchForm.trigger.bind(searchForm, 'submit')).not.toThrow();
});
});
describe('on search', function(){
beforeEach(function() {
this.createSearchTab(this.manifest);
this.server = sinon.fakeServer.create();
this.server.respondImmediately = true;
this.server.respondWith("GET", this.searchURI + "found", [
200,
{ 'Content-Type': 'application/json' },
JSON.stringify(this.results)
]);
this.server.respondWith("GET", this.searchURI + "notfound", [
200,
{ 'Content-Type': 'application/json' },
JSON.stringify(this.noResults)
]);
});
afterEach(function() {
this.server.restore();
});
it('should show results after searching for some text', function() {
this.sandbox.find('#search-within-form input.js-query').val('found');
this.sandbox.find('#search-within-form').trigger('submit');
expect(this.sandbox.find('.search-result')).toExist();
});
it('should not show results when no results are found', function() {
this.sandbox.find('#search-within-form input.js-query').val('notfound');
this.sandbox.find('#search-within-form').trigger('submit');
expect(this.sandbox.find('.search-result')).not.toExist();
});
it('should allow clicking on results after searching for some text', function() {
this.sandbox.find('#search-within-form input.js-query').val('found');
this.sandbox.find('#search-within-form').trigger('submit');
var hit = this.sandbox.find('.result-paragraph').first();
expect(hit.trigger.bind(hit, 'click')).not.toThrow();
});
it('should navigate to a specific canvas and bounds after clicking on a search result', function() {
spyOn(this.eventEmitter, 'publish').and.callThrough();
this.sandbox.find('#search-within-form input.js-query').val('found');
this.sandbox.find('#search-within-form').trigger('submit');
var hit = this.sandbox.find('.result-paragraph').first(),
canvasID = hit.attr('data-canvasid'),
coordinates = hit.attr('data-coordinates'),
xywh = coordinates && coordinates.split('=')[1].split(',').map(Number),
bounds = xywh && {x: xywh[0], y: xywh[1], width: xywh[2], height: xywh[3]};
hit.trigger('click');
expect(this.eventEmitter.publish).toHaveBeenCalledWith('SET_CURRENT_CANVAS_ID.' + this.windowId, {
"canvasID": canvasID,
"bounds": bounds
});
});
it('should show annotations when a search result is linked to more than one', function() {
this.sandbox.find('#search-within-form input.js-query').val('found');
this.sandbox.find('#search-within-form').trigger('submit');
expect(this.sandbox.find('.search-annotation').first()).toExist();
});
it('should allow clicking on annotations when a search result is linked to more than one', function() {
this.sandbox.find('#search-within-form input.js-query').val('found');
this.sandbox.find('#search-within-form').trigger('submit');
var anno = this.sandbox.find('.search-annotation').first();
expect(anno).toExist();
expect(anno.trigger.bind(anno, 'click')).not.toThrow();
});
it('should navigate to a specific bounds and canvas after clicking on an search result linked annotation', function() {
spyOn(this.eventEmitter, 'publish').and.callThrough();
this.sandbox.find('#search-within-form input.js-query').val('found');
this.sandbox.find('#search-within-form').trigger('submit');
var anno = this.sandbox.find('.search-annotation').first(),
canvasID = anno.attr('data-canvasid'),
coordinates = anno.attr('data-coordinates'),
xywh = coordinates && coordinates.split('=')[1].split(',').map(Number),
bounds = xywh && {x: xywh[0], y: xywh[1], width: xywh[2], height: xywh[3]};
anno.trigger('click');
expect(this.eventEmitter.publish).toHaveBeenCalledWith('SET_CURRENT_CANVAS_ID.' + this.windowId, {
"canvasID": canvasID,
"bounds": bounds
});
});
it('should not change canvas after clicking on a second annotation linked to a search result', function() {
this.sandbox.find('#search-within-form input.js-query').val('found');
this.sandbox.find('#search-within-form').trigger('submit');
var anno1 = this.sandbox.find('.search-annotation').first(),
anno2 = this.sandbox.find('.search-annotation:nth-child(2)').first(),
canvasID1 = anno2.attr('data-canvasid'),
canvasID2 = anno2.attr('data-canvasid'),
coordinates = anno2.attr('data-coordinates'),
xywh = coordinates && coordinates.split('=')[1].split(',').map(Number),
bounds = xywh && {x: xywh[0], y: xywh[1], width: xywh[2], height: xywh[3]};
anno1.trigger('click');
spyOn(this.eventEmitter, 'publish').and.callThrough();
anno2.trigger('click');
expect(canvasID1).toBe(canvasID2);
expect(this.eventEmitter.publish).toHaveBeenCalledWith('SET_CURRENT_CANVAS_ID.' + this.windowId, {
"canvasID": canvasID2,
"bounds": bounds
});
});
});
});
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment