1/* 2 * Copyright (c) 2024 Republic of Reinvention BV <[email protected]>. All Rights Reserved. 3 * This file is part of project "cinematek-filmout-site" and can not be copied and/or distributed without 4 * the express permission and written consent of the legal responsible of Republic of Reinvention BV. 5 */ 6 7/** 8 * Created by wouter on 25/08/15. 9 */ 10// WATCH OUT: this name is referenced from /templates/blocks/imports/search/results/results.html 11// KEEP THEM IN SYNC 12base.plugin("blocks.imports.SearchResults", ["base.core.Commons", "base.core.Security", "blocks.core.Broadcaster", "blocks.core.Notification", "blocks.core.Commons", "blocks.core.Async", "constants.blocks.core", "messages.blocks.core", "constants.blocks.imports.search", "messages.blocks.imports.search", "blocks.imports.SearchCommons", "blocks.imports.Dtable", "constants.blocks.imports.dtable", function (Commons, Security, Broadcaster, Notification, BlocksCommons, Async, BlocksConstants, BlocksMessages, SearchConstants, SearchMessages, SearchCommons, Dtable, DtableConstants) 13{ 14 var SearchResults = this; 15 16 var THEAD_BASE_URL_KEY = 'base-url'; 17 var THEAD_SORT_CONFIGS_KEY = 'sort-configs'; 18 19 // catch the events from the search box to reload the search results 20 $(document).on(Broadcaster.EVENTS.BLOCK.ASYNC.CHANGE, function (event, eventData) 21 { 22 // note that we don't check the type, for now, we'll reload on all async changes 23 if (eventData && eventData.source && SearchConstants.TAG_NAME_SEARCH_BOX === eventData.source.nodeName) { 24 // needs some explaining: the server side need access to the search box filter configs 25 // during the same request where the results are rendered, so we need to reload the search box 26 // together with the search results or else SearchBox.loadMeta() won't be called. 27 Async.reload($(SearchConstants.TAG_NAME_SEARCH_BOX + ',' + SearchConstants.TAG_NAME_SEARCH_RESULTS), 28 undefined, 29 // filter out the search results from the returning box+results 30 SearchConstants.TAG_NAME_SEARCH_RESULTS 31 ); 32 } 33 }); 34 35 /** 36 * This is called directly from the results DOM via a <script> tag tp sync the meta with the search box 37 */ 38 this.setResultsMeta = function (resultsMeta) 39 { 40 // this will be undefined when the server didn't have meta 41 if (resultsMeta && resultsMeta.length > 0) { 42 var SearchBox = base.getPlugin("blocks.imports.SearchBox"); 43 if (SearchBox) { 44 // note: let's also do this when resultsMeta is undefined, 45 // so existing filterconfigs get cleared during async reload 46 SearchBox.loadMeta(resultsMeta); 47 } 48 } 49 }; 50 51 this.initExportBtn = function (parent, btnSelector) 52 { 53 var exportBtn = parent.find(btnSelector); 54 55 if (exportBtn && exportBtn.length) { 56 57 var pageSize = parseInt(exportBtn.attr(SearchConstants.SEARCH_RESULTS_EXPORT_SIZE_ARG)); 58 var totalHits = parseInt(exportBtn.attr(SearchConstants.SEARCH_RESULTS_EXPORT_HITS_ARG)); 59 60 var buttons = [ 61 { 62 label: BlocksMessages.close, 63 action: function (dialogRef) 64 { 65 dialogRef.close(); 66 } 67 } 68 ]; 69 70 if (totalHits <= SearchConstants.SEARCH_MAX_EXPORT_SIZE || Security.isPermitted(SearchConstants.PAGE_SEARCH_EXPORT_UNLIMITED_ALL_PERM)) { 71 buttons.push({ 72 label: SearchMessages.exportResultsDialogSubmit, 73 cssClass: 'btn-primary', 74 icon: 'fa fa-check', 75 action: function (dialogRef) 76 { 77 var btn = $(this); 78 var form = dialogRef.getModalBody().find('form'); 79 80 // let's not disable the download button, but hide it instead, see below 81 //btn.disable(); 82 //btn.addClass(BlocksConstants.BTN_BUSY_CLASS); 83 84 btn.hide(); 85 // watch out: first submit, then clear, obviously 86 form.submit(); 87 form.html(SearchMessages.exportResultsDialogFeedback); 88 } 89 }); 90 } 91 92 // Note that we need to use event propagation because the block contents will be replaced
93 // eg. in case of sorting/filtering tabled results (and reverse results) 94 parent.on('click', btnSelector, function (e) 95 { 96 var dialog = BootstrapDialog.show({ 97 title: SearchMessages.exportResultsDialogTitle, 98 type: BootstrapDialog.TYPE_DEFAULT, 99 // prevent the user from closing by clicking in the background since this is quite an important dialog 100 closeByBackdrop: false, 101 // let's allow hitting esc though, especially for the "downloading" dialog 102 closeByKeyboard: true, 103 buttons: buttons, 104 message: $('<div>' + BlocksMessages.loadingData + '</div>').load(SearchConstants.GET_EXPORT_ENDPOINT, 105 // Note that we'll transfer all data arguments to the endpoint 106 // if this is a string, a GET is performed, otherwise a POST 107 //$.param(Commons.getAttributes(exportBtn, "data-")), 108 // update 070525 switched to POST for better SPARQL query support 109 Commons.getAttributes(exportBtn, "data-"), 110 function (data, status, xhr) 111 { 112 if (status === "success") { 113 if (jQuery().perfectScrollbar) { 114 dialog.getModalBody().perfectScrollbar(); 115 } 116 } 117 else { 118 if (dialog) { 119 dialog.close(); 120 } 121 Notification.jsonError(BlocksMessages.generalServerDataError, xhr, data); 122 } 123 }), 124 }); 125 }) 126 } 127 }; 128 129 this.initDtable = function (dtableEl) 130 { 131 Dtable.init(dtableEl, 132 function initCallback(parentBlock, columns) 133 { 134 // Use the init callback (fired when all filtering was installed correctly) to ajax-ize the pagination 135 // Note that we need to use event propagation because the block contents will be replaced 136 parentBlock.on('click', 'nav a', function (event) 137 { 138 // don't navigate away when the user clicks the page nav buttons 139 event.preventDefault(); 140 141 var linkEl = $(this); 142 // note: don't use the table 143 var theadEl = parentBlock.find('table thead'); 144 // store the target uri as the base referrer URI in the thead, it will be picked up during reload 145 theadEl.data(THEAD_BASE_URL_KEY, linkEl.attr('href')); 146 147 reloadTable(parentBlock, undefined, undefined); 148 }); 149 }, 150 function filterCallback(parentBlock, columnData, inputEl, query) 151 { 152 var columnField = columnData[Dtable.COL_DATA_FIELD]; 153 154 // create a filter and attach it to the input control 155 // to allow multiple filters on multiple columns 156 var filterAttr = ''; 157 if (Commons.isSet(query) && query !== '') { 158 filterAttr = Commons.toJson64( 159 // note that making it an array turns it into a filterGroup 160 [ 161 SearchCommons.createFieldFilter(columnField, 162 undefined, 163 query, 164 undefined, 165 SearchConstants.SEARCH_PARAM_FILTER_TYPE_INFIX, 166 SearchConstants.SEARCH_PARAM_FILTER_BOOL_NONE 167 ) 168 ] 169 ); 170 } 171 inputEl.attr(SearchConstants.SEARCH_RESULTS_DFILTER_VAL_ARG, filterAttr); 172 173 reloadTable(parentBlock, columnData, inputEl); 174 }, 175 function sortCallback(parentBlock, columnData, clickedEl, sortDir) 176 { 177 var columnField = columnData[Dtable.COL_DATA_FIELD]; 178 var columnTh = columnData[Dtable.COL_DATA_TH]; 179 var columnThSortEl = columnTh.find('.' + DtableConstants.DTABLE_TH_SORT_CLASS); 180 // note that we'll be using the <thead> element as the uppermost element we can use to attach data to 181 // because it's the element that's kept alive during ajax calls 182 var theadEl = columnTh.closest('thead'); 183 184 // an undefined sortDir means: no specific sorting for this columns 185 var hasValue = Commons.isSet(sortDir); 186 187 // note that we're re-using the sort dir constants as the class names 188 columnThSortEl.removeClass(DtableConstants.DTABLE_SORT_VAL_ASC); 189 columnThSortEl.removeClass(DtableConstants.DTABLE_SORT_VAL_DESC); 190 if (hasValue) { 191 columnThSortEl.addClass(sortDir); 192 } 193 194 // we'll pass 'true' as the value when we're sorting descending, 'false' otherwise 195 var sortConfig = SearchCommons.createSortConfig(columnField, sortDir === DtableConstants.DTABLE_SORT_VAL_DESC); 196 197 // by appending the sort configs to an array, we keep their order in which they were created 198 var sortConfigs = theadEl.data(THEAD_SORT_CONFIGS_KEY) || []; 199 200 var found = false;
201 for (var i = 0; i < sortConfigs.length; i++) { 202 var sconf = sortConfigs[i]; 203 if (sconf[SearchConstants.SEARCH_PARAM_FILTER_FIELD] === columnField) { 204 if (hasValue) { 205 sortConfigs[i] = sortConfig; 206 } 207 else { 208 sortConfigs.splice(i, 1); 209 } 210 found = true; 211 } 212 } 213 // push it to the back to keep sorting order 214 if (!found) { 215 sortConfigs.push(sortConfig); 216 } 217 218 // make sure to save it because it might be new 219 theadEl.data(THEAD_SORT_CONFIGS_KEY, sortConfigs); 220 221 reloadTable(parentBlock, columnData, undefined); 222 } 223 ); 224 225 var reloadTable = function (parentBlock, columnData, focusEl) 226 { 227 var theadEl = parentBlock.find('table thead'); 228 229 // show the user we're reloading 230 parentBlock.addClass(BlocksConstants.LOADING_CLASS); 231 232 var params = Commons.keyValObj( 233 BlocksConstants.ASYNC_BLOCK_HTML_PARAM, Async.getHtml(parentBlock) 234 ); 235 236 // this will be an array of (serialized) SearchFilterGroup 237 var filters = []; 238 theadEl.find('th input').each(function () 239 { 240 var f = $(this).attr(SearchConstants.SEARCH_RESULTS_DFILTER_VAL_ARG); 241 if (f) { 242 filters.push(f); 243 } 244 }); 245 if (filters && filters.length > 0) { 246 params[SearchConstants.SEARCH_PARAM_FILTERS] = filters; 247 } 248 249 var sortConfigs = theadEl.data(THEAD_SORT_CONFIGS_KEY); 250 if (sortConfigs && sortConfigs.length > 0) { 251 params[SearchConstants.SEARCH_PARAM_SORT_CONFIG] = Commons.toJson64(sortConfigs); 252 } 253 254 var referrer = theadEl.data(THEAD_BASE_URL_KEY); 255 if (referrer) { 256 params[BlocksConstants.PAGE_URL_PARAM] = referrer; 257 } 258 259 // make sure to use traditional URL encoding so the 'filter' query param is not turned into 'filter[]' 260 $.get(SearchConstants.TABLE_ASYNC_DTABLE_ENDPOINT, $.param(params, true)) 261 .done(function (newBlockHtml) 262 { 263 var block = $(newBlockHtml); 264 265 // watch out: this will also remove the thead from the parentBlock! 266 block.find('table thead').replaceWith(theadEl); 267 // we can't use replaceWith() directly because the event listeners are bound to the parentBlock 268 // watch out: never serialize the block (eg. using block.html()) or the state of the header will change 269 // also note that block.unwrap() didn't seem to work (it didn't unwrap), but block.children() works fine 270 parentBlock.empty().append(block.children()); 271 272 if (focusEl) { 273 focusEl.focus(); 274 } 275 }) 276 .fail(function (xhr, textStatus, exception) 277 { 278 Notification.jsonError(BlocksMessages.generalServerDataError, xhr, textStatus, exception); 279 }) 280 .always(function () 281 { 282 parentBlock.removeClass(BlocksConstants.LOADING_CLASS); 283 }); 284 }; 285 }; 286 287 $(SearchConstants.TAG_NAME_SEARCH_RESULTS).each(function (index) 288 { 289 SearchResults.initExportBtn($(this), ' .' + SearchConstants.SEARCH_RESULTS_EXPORT_CLASS); 290 }); 291 292 $(SearchConstants.TAG_NAME_SEARCH_RESULTS + ' .results table').each(function (index) 293 { 294 // note that the controller re-prints the argument on the table, so make sure to check that one 295 if ($(this).attr(SearchConstants.SEARCH_RESULTS_ENABLE_DTABLE_ARG) === 'true') { 296 SearchResults.initDtable($(this)); 297 } 298 }); 299 300}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.