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