1/** 2 * Facet facet result filter component functionality 3 * @module facetResultsFilter 4 * @param {jQuery} $ Instance of jQuery 5 * @param {Document} document dom document object 6 * @return {Object} list of methods for working with component facet result filter 7*/ 8XA.component.search.facet.resultsfilter = (function ($, document) { 9 /** 10 * This object stores all public api methods 11 * @type {Object.<Methods>} 12 * @memberOf module:facetResultsFilter 13 */ 14 var api = {}, 15 urlHelperModel, 16 queryModel, 17 apiModel; 18 /** 19 * @name module:facetResultsFilter.FacetResultsFilterModel 20 * @constructor 21 * @augments Backbone.Model 22 */ 23 var FacetResultsFilterModel = XA.component.search.baseModel.extend( 24 /** @lends module:facetResultsFilter.FacetResultsFilterModel.prototype **/ 25 { 26 /** 27 * Default model options 28 * @default 29 */ 30 defaults: { 31 template: "<div class='facet-search-filter <% if(!showAllFacets){%>facet-hided<%}%>'><% " + 32 "_.forEach(facet.Values, function(value,key){" + 33 "%><p class='facet-value <% if(highlightBehaviour<=key){ %> hide-facet-value <% } %>' data-facetValue='<%= value.Name !== '' ? encodeURI(value.Name) : '_empty_' %>'>" + 34 "<span><%= value.Name !== '' ? value.Name : emptyText %> " + 35 "<span class='facet-count'>(<%= value.Count %>)</span>" + 36 "</span>" + 37 "</p><%" + 38 " }); %>" + 39 "<% if(highlightBehaviour>=1 && resultsCount>highlightBehaviour){ %>" + 40 "<div class='toogle-facet-visibility'><% if(showAllFacets){ %><%=showLessText%><%}else{%><%=showMoreText%><%} %></div>" + 41 "<%}%>" + 42 "</div>", 43 44 templateMulti: "<div class='facet-search-filter <% if(!showAllFacets){%>facet-hided<%}%>'><% " + 45 "_.forEach(facet.Values, function(value,key){" + 46 "%><p class='facet-value <% if(highlightBehaviour<=key){ %> hide-facet-value <% } %>' data-facetValue='<%= value.Name !== '' ? encodeURI(value.Name) : '_empty_' %>'>" + 47 "<input type='checkbox' name='facetValue' />" + 48 "<label for='facetName'><%= value.Name !== '' ? value.Name : emptyText %> " + 49 "<span class='facet-count' data-facetCount='<%= value.Count %>'>(<%= value.Count %>)</span>" + 50 "</label>" + 51 "</p><%" + 52 " }); %>" + 53 "<% if(highlightBehaviour>=1 && resultsCount>highlightBehaviour){ %>" + 54 "<div class='toogle-facet-visibility'><% if(showAllFacets){ %><%=showLessText%><%}else{%><%=showMoreText%><%} %></div>" + 55 "<%}%>" + 56 "</div>", 57 dataProperties: {}, 58 blockNextRequest: false, 59 resultData: {}, 60 timeStamp: '', 61 showAllFacets: false, 62 sig: [] 63 }, 64 /** 65 * Listens to changes on facets and hash 66 * @listens module:XA.component.search.vent~event:facet-data-loaded 67 * @listens module:XA.component.search.vent~event:facet-data-filtered 68 * @listens module:XA.component.search.vent~event:facet-data-partial-filtered 69 * @listens module:XA.component.search.vent~event:hashChanged 70 */ 71 initialize: function () { 72 //event to get data at the begining or in case that there are no hash parameters in the url - one request for all controls 73 XA.component.search.vent.on("facet-data-loaded", this.processData.bind(this)); 74 //if in the url hash we have this control facet name (someone clicked this control) then we have to listen for partial filtering 75 XA.component.search.vent.on("facet-data-partial-filtered", this.processData.bind(this)); 76 //in case that we are not filtering by this control (not clicked) 77 XA.component.search.vent.on("facet-data-filtered", this.processData.bind(this)); 78 //event after change of hash 79 XA.component.search.vent.on("hashChanged", this.updateComponent.bind(this)); 80 81 this.set({ facetArray: [] }); 82 }, 83 /** 84 * Toggles value of blockNextRequest variable 85 * @memberof module:facetResultsFilter.FacetResultsFilterModel 86 * @alias module:facetResultsFilter.FacetResultsFilterModel#toggleBlockRequests 87 */ 88 toggleBlockRequests: function () { 89 var state = this.get("blockNextRequest"); 90 this.set(this.get("blockNextRequest"), !state); 91 }, 92 /** 93 * Processes data that comes as parameter update 94 * model and sort facets 95 * @param {Object} data Data from server with facet values 96 * @memberof module:facetResultsFilter.FacetResultsFilterModel 97 * @alias module:facetResultsFilter.FacetResultsFilterModel#processData 98 */ 99 processData: function (data) { 100 var inst = this, 101 dataProperties = this.get('dataProperties'), 102 sig = dataProperties.searchResultsSignature.split(','), 103 sortOrder = dataProperties.sortOrder, 104 i; 105 106 if (data.Signature === null) { 107 data.Signature = ""; 108 } 109 110 111 for (i = 0; i < sig.length; i++) { 112 if (data.Facets.length > 0 && (data.Signature === sig[i])) { 113 var facedData = _.find(data.Facets, function (f) { 114 return f.Key.toLowerCase() === inst.get('dataProperties').f.toLowerCase(); 115 }); 116 if (facedData !== undefined) { 117 this.sortFacetArray(sortOrder, facedData.Values); 118 inst.set({ resultData: facedData }); 119 } 120 } else { 121 inst.set({ resultData: [] }); 122 } 123 } 124 }, 125 /** 126 * Updates model value 'facetArray' with values from valuesString parameter 127 * @param {String} valuesString facet values separated by coma 128 * @memberof module:facetResultsFilter.FacetResultsFilterModel 129 * @alias module:facetResultsFilter.FacetResultsFilterModel#updateFacetArray 130 */ 131 updateFacetArray: function (valuesString) { 132 if (valuesString) { 133 var values = valuesString.split("||"), 134 array = []; 135 for (var i = 0; i < values.length; i++) { 136 array.push(values[i]); 137 } 138 this.set({ facetArray: _.unique(array) }); 139 } 140 }, 141 /** 142 * Sets option selected value to model based on hash 143 * @param {Object} hash Hash stored as an object 144 * @memberof module:facetResultsFilter.FacetResultsFilterModel 145 * @alias module:facetResultsFilter.FacetResultsFilterModel#updateComponent 146 */
147 updateComponent: function (hash) { 148 var sig = this.get("sig"); 149 for (i = 0; i < sig.length; i++) { 150 if (!hash.hasOwnProperty(sig[i])) { 151 this.set({ facetArray: [] }); 152 } else { 153 this.updateFacetArray(hash[sig[i]]); 154 } 155 //in some cases change of facetArray doesn't trigger model change event (why?) and view isn't updates 156 //and because of that timeStamp is updated which properly triggers model change event 157 this.set("timeStamp", (new Date()).getTime()); 158 } 159 } 160 }); 161 /** 162 * @name module:facetResultsFilter.FacetResultsFilterView 163 * @constructor 164 * @augments Backbone.View 165 */ 166 var FacetResultsFilterView = XA.component.search.baseView.extend( 167 /** @lends module:facetResultsFilter.FacetResultsFilterView.prototype **/ 168 { 169 /** 170 * Initially sets data to model and watches events on which 171 * view should be updated 172 * @listens module:facetResultsFilter.FacetResultsFilterModel~event:change 173 * @memberof module:facetResultsFilter.FacetResultsFilterView 174 * @alias module:facetResultsFilter.FacetResultsFilterView#initialize 175 */ 176 initialize: function () { 177 var dataProperties = this.$el.data(), 178 hash = queryModel.parseHashParameters(window.location.hash), 179 properties = dataProperties.properties, 180 signatures, 181 i; 182 183 184 if (dataProperties.properties.searchResultsSignature === null) { 185 dataProperties.properties.searchResultsSignature = ""; 186 } 187 188 signatures = this.translateSignatures(properties.searchResultsSignature, properties.f); 189 190 this.model.set({ dataProperties: properties }); 191 this.model.set("sig", signatures); 192 193 for (i = 0; i < signatures.length; i++) { 194 if (!jQuery.isEmptyObject(_.pick(hash, signatures[i]))) { 195 var values = _.values(_.pick(hash, signatures[i]))[0]; 196 this.model.updateFacetArray(values) 197 } 198 } 199 200 this.model.on("change", this.render, this); 201 }, 202 /** 203 * list of events for Backbone View 204 * @memberof module:facetResultsFilter.FacetResultsFilterView 205 * @alias module:facetResultsFilter.FacetResultsFilterView#events 206 */ 207 events: { 208 'click .facet-value': 'updateFacet', 209 'click .filterButton': 'updateFacet', 210 'click .clear-filter': 'removeFacet', 211 'click .bottom-remove-filter > button': 'removeFacet', 212 'click .toogle-facet-visibility': 'toogleFacetVisibility' 213 }, 214 215 toogleFacetVisibility: function () { 216 var showFacets = this.model.get('showAllFacets'); 217 this.model.set('showAllFacets', !showFacets); 218 219 220 }, 221 /** 222 * Updates model 'facetArray' based on params 223 * @param {Event} param Event object with current target 224 * @memberof module:facetResultsFilter.FacetResultsFilterView 225 * @alias module:facetResultsFilter.FacetResultsFilterView#updateFacet 226 */ 227 updateFacet: function (param) { 228 var currentFacet = $(param.currentTarget), 229 facetArray = this.model.get('facetArray'), 230 properties = this.model.get('dataProperties'), 231 facetClose = this.$el.find('.facet-heading > span'), 232 facetGroup = currentFacet.parents('.component-content').find('.facet-search-filter'), 233 facetName = properties.f.toLowerCase(), 234 facetDataValue = currentFacet.data('facetvalue'), 235 facetValue = typeof facetDataValue !== "undefined" ? decodeURIComponent(facetDataValue) : facetDataValue, 236 sig = this.model.get('sig'), 237 index, 238 hash = {}, 239 i; 240 241 if (properties.multi) { 242 if (facetValue) { 243 if (currentFacet.is(':not(.active-facet)')) { 244 this.setActiveFacet(facetName, facetValue); 245 facetArray.push(facetValue); 246 } else { 247 currentFacet.removeClass('active-facet'); 248
249 currentFacet.find('[type=checkbox]').prop('checked', false); 250 currentFacet.find('[type=checkbox] + label:after').css({ 'background': '#fff' }); 251 252 index = facetArray.indexOf(facetValue); 253 if (index > -1) { 254 facetArray.splice(index, 1); 255 } 256 257 if (facetArray.length == 0) { 258 facetClose.removeClass('has-active-facet'); 259 } 260 } 261 this.model.set({ facetArray: facetArray }); 262 } 263 264 //is there any better way to check what action start method? 265 if (currentFacet[0].type == "button") { 266 for (i = 0; i < sig.length; i++) { 267 hash[sig[i]] = _.uniq(facetArray, function (item) { 268 return JSON.stringify(item); 269 }).join("||"); 270 } 271 queryModel.updateHash(hash); 272 } 273 } else { 274 if (facetValue) { 275 for (i = 0; i < sig.length; i++) { 276 hash[sig[i]] = facetValue; 277 } 278 facetGroup.data('active-facet', hash); 279 this.setActiveFacet(facetName, facetValue); 280 queryModel.updateHash(hash); 281 } 282 } 283 284 }, 285 /** 286 * Sets default values for Search Result Filter and updates hash accordingly 287 * @param {Event} evt Event object 288 * @memberof module:facetResultsFilter.FacetResultsFilterView 289 * @alias module:facetResultsFilter.FacetResultsFilterView#removeFacet 290 */ 291 removeFacet: function (evt) { 292 evt.preventDefault(); 293 294 var facets = this.$el, 295 facetClose = facets.find('.facet-heading > span'), 296 facetValues = facets.find('.facet-value'), 297 sig = this.model.get('sig'); 298 299 queryModel.updateHash(this.updateSignaturesHash(sig, "", {})); 300 301 facetClose.removeClass('has-active-facet'); 302 303 _.each(facetValues, function (single) { 304 var $single = $(single); 305 if ($single.hasClass('active-facet')) { 306 $single.removeClass('active-facet'); 307 $single.find('[type=checkbox]').prop('checked', false); 308 $single.find('[type=checkbox] + label:after').css({ 'background': '#fff' }); 309 } 310 }); 311 312 this.model.set({ facetArray: [] }); 313 }, 314 /** 315 * Renders view 316 * @memberof module:facetResultsFilter.FacetResultsFilterView 317 * @alias module:facetResultsFilter.FacetResultsFilterView#render 318 */ 319 render: function () { 320 var inst = this, 321 resultData = this.model.get("resultData"), 322 facetClose = this.$el.find('.facet-heading > span'), 323 facetNames = this.model.get('dataProperties').f.split('|'), 324 emptyValueText = this.model.get('dataProperties').emptyValueText, 325 highlightThreshold = this.model.get('dataProperties').highlightThreshold, 326 showLessText = this.model.get('dataProperties').showLessText, 327 showMoreText = this.model.get('dataProperties').showMoreText, 328 highlightBehaviour = parseInt(this.model.get('dataProperties').highlightBehaviour), 329 showAllFacets = this.model.get('showAllFacets'), 330 hash = queryModel.parseHashParameters(window.location.hash), 331 sig = this.model.get('sig'), 332 template, facetName, templateResult; 333 334 //checks if page is opened from disc - if yes then we are in Creative Exchange mode 335 if (window.location.href.startsWith("file://")) { 336 return; 337 } 338 this.manageVisibilityByData(this.$el, resultData) 339 if (resultData !== undefined) { 340 if (inst.model.get('dataProperties').multi === true) { 341 template = _.template(inst.model.get("templateMulti")); 342 } else { 343 template = _.template(inst.model.get("template")); 344 } 345 templateResult = template({ 346 facet: resultData, emptyText: emptyValueText, showLessText: showLessText, 347 showMoreText: showMoreText, highlightBehaviour: highlightBehaviour, showAllFacets: showAllFacets, 348 resultsCount: resultData.Values ? resultData.Values.length : -1 349 }); 350 } 351 352 inst.$el.find(".contentContainer").html(templateResult); 353 354 //checks url hash for facets and runs setActiveFacet method for each facet filter 355 _.each(facetNames, function (val) { 356 facetName = val.toLowerCase(); 357 for (var i = 0; i < sig.length; i++) { 358 if (!jQuery.isEmptyObject(_.pick(hash, sig))) { 359 var values = _.values(_.pick(hash, sig))[0]; 360 if (values) { 361 inst.setActiveFacet(facetName, values); 362 363 //If this rendering is supporting multiple signatures, we will mark active facet once. 364 return; 365 } 366 } 367 } 368 }); 369 370 //highlights facets count greater than chosen threshold 371 if (highlightThreshold) { 372 this.handleThreshold(highlightThreshold); 373 } 374 375 //if no facet is selected, remove previously highlighted cross icon (while back button) 376 if (this.model.get("facetArray").length === 0) { 377 facetClose.removeClass('has-active-facet'); 378 } else { 379 facetClose.addClass('has-active-facet'); 380 } 381 }, 382 /** 383 * Manages search result filter active state 384 * @param {String} facetGroupName facet name 385 * @param {String} facetValueName facet value 386 * @memberof module:facetResultsFilter.FacetResultsFilterView 387 * @alias module:facetResultsFilter.FacetResultsFilterView#setActiveFacet 388 */
389 setActiveFacet: function (facetGroupName, facetValueName) { 390 var properties = this.model.get('dataProperties'), 391 facetChildren = this.$el.find('p[data-facetvalue]'), 392 facetClose = this.$el.find('.facet-heading > span'), 393 inst = this, 394 facetValue, 395 values; 396 397 facetValueName = facetValueName.toString().toLowerCase(); 398 facetValue = this.$el.find("[data-facetvalue]").filter(function () { 399 return decodeURIComponent($(this).attr("data-facetvalue").toLowerCase()) === facetValueName; 400 }); 401 402 403 if (typeof (facetValueName) !== "undefined" && facetValueName !== null) { 404 values = facetValueName.split("||"); 405 } else { 406 return; 407 } 408 409 if (values.length > 1) { 410 properties.multi = true; 411 } 412 413 if (properties.multi) { 414 //multi selection facet search results 415 _.each(facetChildren, function (val) { 416 417 if (values.length > 1) { 418 for (var i = 0, l = values.length; i < l; i++) { 419 facetValue = inst.$el.find("[data-facetvalue]").filter(function () { 420 return decodeURIComponent($(this).attr('data-facetvalue').toLowerCase()) === values[i]; 421 }); 422 if (val === facetValue[0]) { 423 $(val).addClass('active-facet'); 424 $(val).find('[type=checkbox]').prop('checked', true); 425 } 426 } 427 } 428 429 if (val === facetValue[0]) { 430 $(val).addClass('active-facet'); 431 $(val).find('[type=checkbox]').prop('checked', true); 432 } 433 434 435 }); 436 } else { 437 //single selection facet search results filter allows only one facet type to be selected 438 _.each(facetChildren, function (val) { 439 if (val !== facetValue[0]) { 440 $(val).removeClass('active-facet'); 441 $(val).find('[type=checkbox]').prop('checked', false); 442 $(val).find('[type=checkbox] + label:after').css({ 'background': '#fff' }); 443 } else { 444 $(val).addClass('active-facet'); 445 } 446 }); 447 } 448 449 //adds active class to group close button 450 facetClose.addClass('has-active-facet'); 451 }, 452 /** 453 * Manages search result filter highlight of threshold 454 * @param {Number} highlightThreshold value of threshold highlight 455 * @memberof module:facetResultsFilter.FacetResultsFilterView 456 * @alias module:facetResultsFilter.FacetResultsFilterView#handleThreshold 457 */ 458 handleThreshold: function (highlightThreshold) { 459 var facets = this.$el.find('.facet-search-filter').children('p'); 460 461 _.each(facets, function (single) { 462 var $facet = $(single), 463 $facetCount = $facet.find('.facet-count'), 464 facetCount = $facetCount.data('facetcount'); 465 466 if (facetCount > highlightThreshold) { 467 $facetCount.addClass('highlighted'); 468 } 469 }); 470 } 471 }); 472 /** 473 * For each search result component on a page creates instance of 474 * ["FacetResultsFilterModel"]{@link module:facetResultsFilter.FacetResultsFilterModel} and 475 * ["FacetResultsFilterView"]{@link module:facetResultsFilter.FacetResultsFilterView} 476 * @memberOf module:facetResultsFilter 477 * @alias module:facetResultsFilter.init 478 */ 479 api.init = function () { 480 if ($("body").hasClass("on-page-editor")) { 481 return; 482 } 483
484 queryModel = XA.component.search.query; 485 apiModel = XA.component.search.ajax; 486 urlHelperModel = XA.component.search.url; 487 488 var facetResultsFilterList = $(".facet-single-selection-list:not(.initialized)"); 489 490 _.each(facetResultsFilterList, function (elem) { 491 var $el = $(elem), 492 model = new FacetResultsFilterModel(), 493 view = new FacetResultsFilterView({ el: $el, model: model }); 494 495 $el.addClass("initialized"); 496 }); 497 }; 498 /** 499 * Returns information about facet component 500 * @memberOf module:facetResultsFilter 501 * @alias module:facetResultsFilter.getFacetDataRequestInfo 502 * @returns {Array<FacetDataRequestInfo>} facet data needed for request 503 */ 504 api.getFacetDataRequestInfo = function () { 505 var facetList = $(".facet-single-selection-list"), 506 result = []; 507 508 _.each(facetList, function (elem) { 509 var properties = $(elem).data().properties, 510 signatures = properties.searchResultsSignature.split(','), 511 i; 512 513 for (i = 0; i < signatures.length; i++) { 514 result.push({ 515 signature: signatures[i] === null ? "" : signatures[i], 516 facetName: properties.f, 517 endpoint: properties.endpoint, 518 showMoreText: properties.showMoreText, 519 showLessText: properties.showLessText, 520 highlightBehaviour: properties.highlightBehaviour, 521 s: properties.s, 522 filterWithoutMe: !properties.collapseOnSelection 523 }); 524 } 525 }); 526 527 return result; 528 }; 529 530 return api; 531 532}(jQuery, document)); 533 534XA.register('facetResultsFilter', XA.component.search.facet.resultsfilter);
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.