mirror of
https://github.com/OpenXE-org/OpenXE.git
synced 2024-12-27 23:20:28 +01:00
643 lines
22 KiB
JavaScript
643 lines
22 KiB
JavaScript
var SuperSearch = (function ($) {
|
|
'use strict';
|
|
|
|
var me = {
|
|
|
|
config: {
|
|
inputBuffer: 300 // in milliseconds
|
|
},
|
|
|
|
storage: {
|
|
$input: null,
|
|
$overlay: null,
|
|
$details: null,
|
|
$results: null,
|
|
$lastUpdate: null,
|
|
debounceBuffer: null,
|
|
hasResults: false,
|
|
isOpen: false
|
|
},
|
|
|
|
init: function () {
|
|
me.storage.$input = $('#supersearch-input');
|
|
if (me.storage.$input.length === 0) {
|
|
return;
|
|
}
|
|
|
|
me.registerEvents();
|
|
},
|
|
|
|
registerEvents: function () {
|
|
me.storage.$input.on('keyup.SuperSearch', me.onKeyUpSearchInput);
|
|
|
|
// Overlay anzeigen bei Focus in das Such-Eingabefeld; nur wenn es schon mal geöffnet war
|
|
me.storage.$input.on('focus.SuperSearch', me.onFocusSearchInput);
|
|
|
|
// Overlay mit ESC schließen
|
|
$(document).bind('keydown', function(e) {
|
|
if (me.storage.$overlay === null) {
|
|
return;
|
|
}
|
|
if (me.storage.isOpen !== true) {
|
|
return;
|
|
}
|
|
|
|
// ESC
|
|
if (e.keyCode === 27) {
|
|
me.hideOverlay();
|
|
}
|
|
});
|
|
},
|
|
|
|
/**
|
|
* @return {jQuery}
|
|
*/
|
|
getOverlay: function () {
|
|
if (typeof me.storage.$overlay === 'undefined' || me.storage.$overlay === null) {
|
|
me.storage.$overlay = me.createOverlay();
|
|
me.storage.$details = me.storage.$overlay.find('section.detail');
|
|
me.storage.$results = me.storage.$overlay.find('section.result');
|
|
me.storage.$lastUpdate = me.storage.$overlay.find('section.last-update');
|
|
}
|
|
|
|
return me.storage.$overlay;
|
|
},
|
|
|
|
showOverlay: function () {
|
|
var $overlay = me.getOverlay();
|
|
$overlay.show();
|
|
me.storage.isOpen = true;
|
|
me.showDetails();
|
|
},
|
|
|
|
hideOverlay: function () {
|
|
me.getOverlay().hide();
|
|
me.storage.isOpen = false;
|
|
},
|
|
|
|
/**
|
|
* @return {jQuery}
|
|
*/
|
|
createOverlay: function () {
|
|
var overlaySelector = '#supersearch-overlay';
|
|
if ($(overlaySelector).length > 0) {
|
|
return $(overlaySelector);
|
|
}
|
|
|
|
var overlayTemplate =
|
|
'<span id="supersearch-icon-close" class="icon icon-close"></span>' +
|
|
'<div class="result-wrapper">' +
|
|
'<section class="empty-message">Keine Suchergebnisse gefunden</section>' +
|
|
'<section class="error-message"></section>' +
|
|
'<section class="result"></section>' +
|
|
'<section class="last-update"></section>' +
|
|
'</div>' +
|
|
'<div class="detail-wrapper">' +
|
|
'<section class="detail"></section>' +
|
|
'</div>';
|
|
|
|
var overlayIdAttr = overlaySelector.substr(1);
|
|
var $overlay = $('<div>').attr('id', overlayIdAttr).addClass('supersearch-overlay').html(overlayTemplate);
|
|
|
|
$overlay.off('click.SuperSearch', '#supersearch-icon-close');
|
|
$overlay.on('click.SuperSearch', '#supersearch-icon-close', function (event) {
|
|
event.preventDefault();
|
|
me.hideOverlay();
|
|
});
|
|
|
|
$overlay.hide();
|
|
$overlay.appendTo('#header');
|
|
me.storage.isOpen = false;
|
|
|
|
return $overlay;
|
|
},
|
|
|
|
/**
|
|
* @param {Event} event
|
|
*/
|
|
onKeyUpSearchInput: function (event) {
|
|
event.preventDefault();
|
|
var controlKeyCodes = [
|
|
9, // Tab
|
|
13, // Enter
|
|
16, // Shift
|
|
17, // Strg
|
|
18, // Alt
|
|
20, // Caps lock
|
|
27, // ESC
|
|
37, // Cursor Left
|
|
38, // Cursor Up
|
|
39, // Cursor Right
|
|
40 // Cursor Down
|
|
];
|
|
if ($.inArray(event.keyCode, controlKeyCodes) !== -1) {
|
|
return;
|
|
}
|
|
|
|
var that = this;
|
|
me.debounce(function () {
|
|
var searchQuery = $(that).val();
|
|
me.fetchSearchResults(searchQuery).then(me.renderSearchResults);
|
|
}, me.config.inputBuffer);
|
|
},
|
|
|
|
/**
|
|
* Overlay anzeigen bei Focus in das Such-Eingabefeld; nur wenn es schon mal geöffnet war
|
|
*
|
|
* @param {Event} event
|
|
*/
|
|
onFocusSearchInput: function (event) {
|
|
event.preventDefault();
|
|
if (me.storage.$overlay === null) {
|
|
return;
|
|
}
|
|
if (me.storage.hasResults === false) {
|
|
return;
|
|
}
|
|
|
|
me.showOverlay();
|
|
},
|
|
|
|
/**
|
|
* @param {string} searchQuery
|
|
*
|
|
* @return {jqXHR}
|
|
*/
|
|
fetchSearchResults: function (searchQuery) {
|
|
if (typeof searchQuery !== 'string') {
|
|
searchQuery = '';
|
|
}
|
|
|
|
return $.ajax({
|
|
url: 'index.php?module=supersearch&action=ajax&cmd=search',
|
|
method: 'post',
|
|
dataType: 'json',
|
|
data: {
|
|
search_query: searchQuery
|
|
},
|
|
error: function (jqXHR, textStatus, errorThrown) {
|
|
var errorMessage = 'SuperSearch - Unbekannter Fehler #31: ' + errorThrown;
|
|
|
|
// PHP-Skript hat Fehler geliefert (z.b. 404)
|
|
if (textStatus === 'error') {
|
|
errorMessage = 'SuperSearch - Unbekannter Server-Fehler beim Laden der Such-Ergebnisse: ';
|
|
errorMessage += errorThrown;
|
|
}
|
|
|
|
// PHP-Skript liefert JSON-Error-Response
|
|
if (jqXHR.hasOwnProperty('responseJSON') && jqXHR.responseJSON.hasOwnProperty('error')) {
|
|
errorMessage = 'SuperSearch - Server-Fehler beim Laden der Such-Ergebnisse: ';
|
|
errorMessage += jqXHR.responseJSON.error;
|
|
|
|
if (jqXHR.responseJSON.hasOwnProperty('data') &&
|
|
jqXHR.responseJSON.data === 'index-empty') {
|
|
me.showErrorMessage('Fehler: ' + jqXHR.responseJSON.error);
|
|
return;
|
|
}
|
|
}
|
|
|
|
alert(errorMessage);
|
|
}
|
|
});
|
|
},
|
|
|
|
/**
|
|
* @param {Array} rawResult
|
|
*/
|
|
renderSearchResults: function (rawResult) {
|
|
var $overlay = me.getOverlay();
|
|
var $resultContainer = $overlay.find('section.result');
|
|
$resultContainer.html('');
|
|
|
|
if (rawResult.length === 0 || !rawResult.hasOwnProperty('data')) {
|
|
$resultContainer.html('Fehler: Suche hat fehlerhaftes Ergebnis geliefert.');
|
|
me.storage.hasResults = false;
|
|
me.hideResults();
|
|
return;
|
|
}
|
|
|
|
// Overlay ausblenden, wenn Suchbegriff zu kurz
|
|
if (rawResult.data === null) {
|
|
me.storage.hasResults = false;
|
|
me.hideOverlay();
|
|
return;
|
|
}
|
|
|
|
// Anzeigen wann der Such-Index das letzte Mal aktualisiert wurde
|
|
if (rawResult.data.hasOwnProperty('last_index_update_formatted')) {
|
|
if (rawResult.data.last_index_update_formatted !== null) {
|
|
var lastIndexUpdate = rawResult.data.last_index_update_formatted;
|
|
me.storage.$lastUpdate.text('Such-Index vom ' + lastIndexUpdate).show();
|
|
} else {
|
|
me.storage.$lastUpdate.text('').hide();
|
|
}
|
|
}
|
|
|
|
var resultCount = rawResult.data.count;
|
|
var searchResults = rawResult.data.results;
|
|
if (resultCount === 0) {
|
|
me.storage.hasResults = false;
|
|
me.showEmptyResults();
|
|
return;
|
|
}
|
|
|
|
me.storage.$details.html('');
|
|
Object.keys(searchResults).forEach(function (group) {
|
|
var groupResult = searchResults[group];
|
|
var $groupHtml = me.buildGroupResult(groupResult.key, groupResult.title, groupResult.items);
|
|
$resultContainer.append($groupHtml);
|
|
});
|
|
|
|
me.storage.hasResults = true;
|
|
me.showResults();
|
|
me.showOverlay();
|
|
},
|
|
|
|
/**
|
|
* @param {string} groupKey
|
|
* @param {string} groupTitle
|
|
* @param {array} items
|
|
*
|
|
* @return {jQuery}
|
|
*/
|
|
buildGroupResult: function (groupKey, groupTitle, items) {
|
|
if (items.length === 0) {
|
|
return;
|
|
}
|
|
if (typeof groupTitle === 'undefined') {
|
|
groupTitle = 'Ergebnis';
|
|
}
|
|
|
|
var $resultWrapper = $('<div class="result-group">');
|
|
var $resultList = $('<ul class="result-list">');
|
|
var $listHead = $('<li class="result-head">').html(groupTitle);
|
|
|
|
$resultList.append($listHead);
|
|
items.forEach(function (item) {
|
|
item.group = groupKey;
|
|
var itemType = item.type !== null ? item.type : 'default';
|
|
var $listItem;
|
|
|
|
switch (itemType) {
|
|
case 'default':
|
|
default:
|
|
$listItem = me.buildDefaultItemResult(item);
|
|
break;
|
|
}
|
|
|
|
$resultList.append($listItem);
|
|
});
|
|
$resultWrapper.append($resultList);
|
|
|
|
return $resultWrapper;
|
|
},
|
|
|
|
/**
|
|
* @param {object} item
|
|
*
|
|
* @return {jQuery}
|
|
*/
|
|
buildDefaultItemResult: function (item) {
|
|
var hasSubtitle = item.hasOwnProperty('subtitle') && typeof item.subtitle === 'string';
|
|
var hasAdditionalInfos =
|
|
item.hasOwnProperty('additionalInfos') &&
|
|
typeof item.additionalInfos === 'object' &&
|
|
item.additionalInfos !== null;
|
|
|
|
var mainTitle = '<span class="title-main">' + item.title + '</span>';
|
|
var subTitle = hasSubtitle ? '<span class="title-sub">' + item.subtitle + '</span>' : '';
|
|
var titleString = '<span class="title">' + mainTitle + subTitle + '</span>';
|
|
|
|
if (hasAdditionalInfos) {
|
|
titleString += '<span class="caption">';
|
|
$.each(item.additionalInfos, function (index, additionalInfo) {
|
|
titleString += '<span class="additional">' + additionalInfo + '</span>';
|
|
});
|
|
titleString += '</span>';
|
|
}
|
|
|
|
var $listItem = $('<li>').addClass('result-item');
|
|
var $itemLink = $('<a>').attr('href', item.link).html(titleString);
|
|
$itemLink.appendTo($listItem);
|
|
|
|
$itemLink.on('click', function (e) {
|
|
e.preventDefault();
|
|
me.renderItemDetails(item);
|
|
});
|
|
|
|
return $listItem;
|
|
},
|
|
|
|
/**
|
|
* Rendert Ergebnisdetails
|
|
*
|
|
* @param {object} item
|
|
*/
|
|
renderItemDetails: function (item) {
|
|
// Per AJAX ausführliche Inhalte nachladen
|
|
me.fetchItemDetailsDynamicContent(item).then(
|
|
function (data) {
|
|
me.renderItemDetailsDynamicContent(data, item);
|
|
},
|
|
function (jqXhr) {
|
|
var error =
|
|
typeof jqXhr.responseJSON !== 'undefined' &&
|
|
typeof jqXhr.responseJSON.error !== 'undefined'
|
|
? jqXhr.responseJSON.error
|
|
: 'Unbekannter Fehler';
|
|
alert('Fehler beim Laden der Detail-Informationen: ' + error);
|
|
}
|
|
);
|
|
},
|
|
|
|
/**
|
|
* @param {object} detailResult
|
|
* @param {object} listItem Originales Item-Objekt aus Suchergebnis-Liste
|
|
*
|
|
* @return {void}
|
|
*/
|
|
renderItemDetailsDynamicContent: function (detailResult, listItem) {
|
|
if (!detailResult.hasOwnProperty('data') || detailResult.data === false) {
|
|
// Es wurde kein Detail-Result gefunden
|
|
// Link aus Suchergebnis-Item aufrufen
|
|
me.hideDetails();
|
|
window.location.href = listItem.link;
|
|
return;
|
|
}
|
|
|
|
var detail = detailResult.data;
|
|
var $details = me.storage.$details;
|
|
|
|
// Überschrift rendern
|
|
var $headline = $('<h1>').html(detail.title);
|
|
$details.html('').append($headline);
|
|
|
|
// Attachments (z.B. Buttons) rendern
|
|
if (detail.hasOwnProperty('attachments')) {
|
|
var $attachments = me.generateDetailAttachments(detail.attachments);
|
|
$details.append($attachments);
|
|
}
|
|
|
|
me.showDetails();
|
|
},
|
|
|
|
/**
|
|
* @param {object} item
|
|
*
|
|
* @return {jqXHR}
|
|
*/
|
|
fetchItemDetailsDynamicContent: function (item) {
|
|
return $.ajax({
|
|
url: 'index.php?module=supersearch&action=ajax&cmd=detail',
|
|
method: 'post',
|
|
dataType: 'json',
|
|
data: {
|
|
detail_group: item.group,
|
|
detail_identifier: item.identifier
|
|
},
|
|
error: function (jqXHR, textStatus, errorThrown) {
|
|
var errorMessage = 'SuperSearch - Unbekannter Fehler #32: ' + errorThrown;
|
|
|
|
// PHP-Skript hat Fehler geliefert (z.b. 404)
|
|
if (textStatus === 'error') {
|
|
errorMessage = 'SuperSearch - Unbekannter Server-Fehler beim Laden des Detail-Ergebnisses: ';
|
|
errorMessage += errorThrown;
|
|
}
|
|
|
|
// PHP-Skript liefer JSON-Error-Response
|
|
if (jqXHR.hasOwnProperty('responseJSON') && jqXHR.responseJSON.hasOwnProperty('error')) {
|
|
errorMessage = 'SuperSearch - Server-Fehler beim Laden des Detail-Ergebnisses: ';
|
|
errorMessage += jqXHR.responseJSON.error;
|
|
}
|
|
|
|
alert(errorMessage);
|
|
}
|
|
});
|
|
},
|
|
|
|
/**
|
|
* @param {Array} attachments
|
|
*
|
|
* @return {jQuery} jQuery-Element
|
|
*/
|
|
generateDetailAttachments: function (attachments) {
|
|
var $attachments = $('<div>');
|
|
|
|
$.each(attachments, function (index, attachment) {
|
|
if (!attachment.hasOwnProperty('type')) {
|
|
console.error('Attachment ungültig. "type"-Property fehlt.');
|
|
return;
|
|
}
|
|
if (!attachment.hasOwnProperty('data')) {
|
|
console.error('Attachment ungültig. "data"-Property fehlt.');
|
|
return;
|
|
}
|
|
|
|
if (attachment.type === 'button_block') {
|
|
var $buttonBlock = me.generateDetailAttachmentTypeButtonBlock(attachment.data);
|
|
$attachments.append($buttonBlock);
|
|
}
|
|
if (attachment.type === 'content_static') {
|
|
var $contentStatic = me.generateDetailAttachmentTypeStaticContent(attachment.data);
|
|
$attachments.append($contentStatic);
|
|
}
|
|
if (attachment.type === 'content_dynamic') {
|
|
var $contentDynamic = me.generateDetailAttachmentTypeDynamicContent(attachment.data);
|
|
$attachments.append($contentDynamic);
|
|
}
|
|
});
|
|
|
|
return $attachments;
|
|
},
|
|
|
|
/**
|
|
* @param {Array} items
|
|
*
|
|
* @return {jQuery} jQuery-Element
|
|
*/
|
|
generateDetailAttachmentTypeButtonBlock: function (items) {
|
|
var $buttonBlock = $('<div>');
|
|
|
|
$.each(items, function (index, item) {
|
|
var $button = $('<a>').text(item.title).addClass('button');
|
|
if (item.hasOwnProperty('attributes')) {
|
|
|
|
// Button-Attribute verarbeiten
|
|
$.each(item.attributes, function (attrName, attrValue) {
|
|
if (attrName === 'class') {
|
|
$button.addClass(attrValue);
|
|
return;
|
|
}
|
|
if (attrName === 'data-icon') {
|
|
var iconUrl = '';
|
|
switch (attrValue) {
|
|
case 'help':
|
|
iconUrl = './themes/new/images/help.svg';
|
|
break;
|
|
case 'settings':
|
|
iconUrl = './themes/new/images/settings.svg';
|
|
break;
|
|
}
|
|
if (iconUrl !== '') {
|
|
$button.addClass('icon');
|
|
$button.addClass('icon-' + attrValue);
|
|
var $iconElem = $('<img alt="Handbuch">').attr('src', iconUrl);
|
|
var $iconWrapper = $('<span class="icon">').append($iconElem);
|
|
$button.prepend($iconWrapper);
|
|
|
|
}
|
|
}
|
|
$button.attr(attrName, attrValue);
|
|
});
|
|
}
|
|
$button.appendTo($buttonBlock);
|
|
});
|
|
|
|
return $buttonBlock;
|
|
},
|
|
|
|
/**
|
|
* @param {Object} data
|
|
*
|
|
* @return {jQuery} jQuery-Element
|
|
*/
|
|
generateDetailAttachmentTypeStaticContent: function (data) {
|
|
return $('<p>').html(data.content);
|
|
},
|
|
|
|
/**
|
|
* @param {Object} data
|
|
*
|
|
* @return {jQuery} jQuery-Element
|
|
*/
|
|
generateDetailAttachmentTypeDynamicContent: function (data) {
|
|
var $dynamicContent = $('<div>').addClass('minidetail');
|
|
|
|
if (data.hasOwnProperty('url') && data.url !== null) {
|
|
me.fetchMiniDetailContent(data.url, data.params)
|
|
.then(
|
|
function (htmlContent) {
|
|
$dynamicContent.html(htmlContent);
|
|
me.storage.$details.append($dynamicContent);
|
|
},
|
|
function (jqXhr) {
|
|
var message = 'Fehler beim Laden der Mini-Details: ';
|
|
if (jqXhr.hasOwnProperty('responseJSON') && jqXhr.responseJSON.hasOwnProperty('error')) {
|
|
message += jqXhr.responseJSON.error;
|
|
} else {
|
|
message += jqXhr.status + ' ' + jqXhr.statusText;
|
|
}
|
|
$('<div class="error"></div>').text(message).appendTo(me.storage.$details);
|
|
}
|
|
);
|
|
}
|
|
|
|
return $dynamicContent;
|
|
},
|
|
|
|
/**
|
|
* @param {string} miniDetailUrl
|
|
* @param {Object} miniDetailParams Zusätzliche POST-Parameter
|
|
*
|
|
* @return {jqXHR}
|
|
*/
|
|
fetchMiniDetailContent: function (miniDetailUrl, miniDetailParams) {
|
|
if (miniDetailUrl.substr(0, 10) !== 'index.php?') {
|
|
alert('Mini-Detail-URL ist ungültig: ' + miniDetailUrl);
|
|
throw 'Mini-Detail-URL ist ungültig: ' + miniDetailUrl;
|
|
}
|
|
if (typeof miniDetailParams !== 'object') {
|
|
miniDetailParams = {};
|
|
}
|
|
|
|
return $.ajax({
|
|
url: miniDetailUrl,
|
|
data: miniDetailParams,
|
|
method: 'post',
|
|
dataType: 'html'
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Suchergebnisse einblenden
|
|
*/
|
|
showResults: function () {
|
|
me.getOverlay().addClass('has-result');
|
|
me.getOverlay().find('section.empty-message').hide();
|
|
me.getOverlay().find('section.error-message').hide();
|
|
},
|
|
|
|
/**
|
|
* Suchergebnisse ausblenden
|
|
*/
|
|
hideResults: function () {
|
|
me.getOverlay().removeClass('has-result');
|
|
me.getOverlay().find('section.empty-message').hide();
|
|
me.getOverlay().find('section.error-message').hide();
|
|
me.getOverlay().find('section.last-update').hide();
|
|
},
|
|
|
|
/**
|
|
* Details einblenden
|
|
*/
|
|
showDetails: function () {
|
|
me.getOverlay().addClass('has-detail');
|
|
me.getOverlay().find('.detail-wrapper').scrollTop(0);
|
|
},
|
|
|
|
/**
|
|
* Details einblenden
|
|
*/
|
|
hideDetails: function () {
|
|
me.getOverlay().removeClass('has-detail');
|
|
},
|
|
|
|
/**
|
|
* Hinweis anzeigen das keine Ergebnisse gefunden wurden
|
|
*/
|
|
showEmptyResults: function () {
|
|
me.showOverlay();
|
|
me.hideDetails();
|
|
me.getOverlay().removeClass('has-result');
|
|
me.getOverlay().find('section.empty-message').show();
|
|
me.getOverlay().find('section.error-message').hide();
|
|
},
|
|
|
|
/**
|
|
* @param {string} errorMessage
|
|
*/
|
|
showErrorMessage: function (errorMessage) {
|
|
me.showOverlay();
|
|
me.hideDetails();
|
|
me.getOverlay().find('section.empty-message').hide();
|
|
me.getOverlay().find('section.error-message').html(errorMessage).show();
|
|
},
|
|
|
|
/**
|
|
* Puffer-Funktion um Events erst nach einer bestimmten Zeit auszuführen
|
|
*
|
|
* @param {function} callback
|
|
* @param {number} delay
|
|
* @param {object|null} contextParam
|
|
*/
|
|
debounce: function (callback, delay, contextParam) {
|
|
var context = typeof contextParam !== 'undefined' && contextParam !== null ? contextParam : this;
|
|
var args = arguments;
|
|
|
|
window.clearTimeout(me.storage.debounceBuffer);
|
|
me.storage.debounceBuffer = window.setTimeout(function () {
|
|
callback.apply(context, args);
|
|
}, delay || 250);
|
|
}
|
|
};
|
|
|
|
return {
|
|
init: me.init
|
|
};
|
|
|
|
})(jQuery);
|
|
|
|
$(function () {
|
|
SuperSearch.init();
|
|
});
|