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 initialized = false; 19 /** 20 * @name module:facetResultsFilter.FacetResultsFilterModel 21 * @constructor 22 * @augments Backbone.Model 23 */ 24 var FacetResultsFilterModel = XA.component.search.baseModel.extend( 25 /** @lends module:facetResultsFilter.FacetResultsFilterModel.prototype **/ 26 { 27 /** 28 * Default model options 29 * @default 30 */ 31 defaults: { 32 template: "<div class='facet-search-filter <% if(!showAllFacets){%>facet-hided<%}%>'><% " + 33 "_.forEach(facet.Values, function(value,key){" + 34 "%><p class='facet-value <% if(highlightBehaviour<=key){ %> hide-facet-value <% } %>' data-facetValue='<%= value.Name !== '' ? encodeURI(value.Name) : '_empty_' %>'>" + 35 "<span><%= value.Name !== '' ? value.Name : emptyText %> " + 36 "<span class='facet-count'>(<%= value.Count %>)</span>" + 37 "</span>" + 38 "</p><%" + 39 " }); %>" + 40 "<% if(highlightBehaviour>=1){ %>" + 41 "<div class='toogle-facet-visibility'><% if(showAllFacets){ %><%=showLessText%><%}else{%><%=showMoreText%><%} %></div>" + 42 "<%}%>" + 43 "</div>", 44 45 templateMulti: "<div class='facet-search-filter <% if(!showAllFacets){%>facet-hided<%}%>'><% " + 46 "_.forEach(facet.Values, function(value,key){" + 47 "%><p class='facet-value <% if(highlightBehaviour<=key){ %> hide-facet-value <% } %>' data-facetValue='<%= value.Name !== '' ? encodeURI(value.Name) : '_empty_' %>'>" + 48 "<input type='checkbox' name='facetValue' />" + 49 "<label for='facetName'><%= value.Name !== '' ? value.Name : emptyText %> " + 50 "<span class='facet-count' data-facetCount='<%= value.Count %>'>(<%= value.Count %>)</span>" + 51 "</label>" + 52 "</p><%" + 53 " }); %>" + 54 "<% if(highlightBehaviour>=1){ %>" + 55 "<div class='toogle-facet-visibility'><% if(showAllFacets){ %><%=showLessText%><%}else{%><%=showMoreText%><%} %></div>" + 56 "<%}%>" + 57 "</div>", 58 dataProperties: {}, 59 blockNextRequest: false, 60 resultData: {}, 61 timeStamp: '', 62 showAllFacets: false, 63 sig: [] 64 }, 65 /** 66 * Listens to changes on facets and hash 67 * @listens module:XA.component.search.vent~event:facet-data-loaded 68 * @listens module:XA.component.search.vent~event:facet-data-filtered 69 * @listens module:XA.component.search.vent~event:facet-data-partial-filtered 70 * @listens module:XA.component.search.vent~event:hashChanged 71 */ 72 initialize: function () { 73 //event to get data at the begining or in case that there are no hash parameters in the url - one request for all controls 74 XA.component.search.vent.on("facet-data-loaded", this.processData.bind(this)); 75 //if in the url hash we have this control facet name (someone clicked this control) then we have to listen for partial filtering 76 XA.component.search.vent.on("facet-data-partial-filtered", this.processData.bind(this)); 77 //in case that we are not filtering by this control (not clicked) 78 XA.component.search.vent.on("facet-data-filtered", this.processData.bind(this)); 79 //event after change of hash 80 XA.component.search.vent.on("hashChanged", this.updateComponent.bind(this)); 81 82 this.set({ facetArray: [] }); 83 }, 84 /** 85 * Toggles value of blockNextRequest variable 86 * @memberof module:facetResultsFilter.FacetResultsFilterModel 87 * @alias module:facetResultsFilter.FacetResultsFilterModel#toggleBlockRequests 88 */ 89 toggleBlockRequests: function () { 90 var state = this.get("blockNextRequest"); 91 this.set(this.get("blockNextRequest"), !state); 92 }, 93 /** 94 * Processes data that comes as parameter update 95 * model and sort facets 96 * @param {Object} data Data from server with facet values 97 * @memberof module:facetResultsFilter.FacetResultsFilterModel 98 * @alias module:facetResultsFilter.FacetResultsFilterModel#processData 99 */ 100 processData: function (data) { 101 var inst = this, 102 dataProperties = this.get('dataProperties'), 103 sig = dataProperties.searchResultsSignature.split(','), 104 sortOrder = dataProperties.sortOrder, 105 i; 106 107 if (data.Signature === null) { 108 data.Signature = ""; 109 } 110 111 112 for (i = 0; i < sig.length; i++) { 113 if (data.Facets.length > 0 && (data.Signature === sig[i])) { 114 var facedData = _.find(data.Facets, function (f) { 115 return f.Key.toLowerCase() === inst.get('dataProperties').f.toLowerCase(); 116 }); 117 if (facedData !== undefined) { 118 this.sortFacetArray(sortOrder, facedData.Values); 119 inst.set({ resultData: facedData }); 120 } 121 } 122 } 123 }, 124 /** 125 * Updates model value 'facetArray' with values from valuesString parameter 126 * @param {String} valuesString facet values separated by coma 127 * @memberof module:facetResultsFilter.FacetResultsFilterModel 128 * @alias module:facetResultsFilter.FacetResultsFilterModel#updateFacetArray 129 */ 130 updateFacetArray: function (valuesString) { 131 if (valuesString) { 132 var values = valuesString.split(','), 133 array = this.get('facetArray'); 134 for (var i = 0; i < values.length; i++) { 135 array.push(values[i]); 136 } 137 this.set({ facetArray: _.unique(array) }); 138 } 139 }, 140 /** 141 * Sets option selected value to model based on hash 142 * @param {Object} hash Hash stored as an object 143 * @memberof module:facetResultsFilter.FacetResultsFilterModel 144 * @alias module:facetResultsFilter.FacetResultsFilterModel#updateComponent 145 */
146 updateComponent: function (hash) { 147 var sig = this.get("sig"); 148 for (i = 0; i < sig.length; i++) { 149 if (!hash.hasOwnProperty(sig[i])) { 150 this.set({ facetArray: [] }); 151 } else { 152 this.updateFacetArray(hash[sig[i]]); 153 } 154 //in some cases change of facetArray doesn't trigger model change event (why?) and view isn't updates 155 //and because of that timeStamp is updated which properly triggers model change event 156 this.set("timeStamp", (new Date()).getTime()); 157 } 158 } 159 }); 160 /** 161 * @name module:facetResultsFilter.FacetResultsFilterView 162 * @constructor 163 * @augments Backbone.View 164 */ 165 var FacetResultsFilterView = XA.component.search.baseView.extend( 166 /** @lends module:facetResultsFilter.FacetResultsFilterView.prototype **/ 167 { 168 /** 169 * Initially sets data to model and watches events on which 170 * view should be updated 171 * @listens module:facetResultsFilter.FacetResultsFilterModel~event:change 172 * @memberof module:facetResultsFilter.FacetResultsFilterView 173 * @alias module:facetResultsFilter.FacetResultsFilterView#initialize 174 */ 175 initialize: function () { 176 var dataProperties = this.$el.data(), 177 hash = queryModel.parseHashParameters(window.location.hash), 178 properties = dataProperties.properties, 179 signatures, 180 i; 181 182 183 if (dataProperties.properties.searchResultsSignature === null) { 184 dataProperties.properties.searchResultsSignature = ""; 185 } 186 187 signatures = this.translateSignatures(properties.searchResultsSignature, properties.f); 188 189 this.model.set({ dataProperties: properties }); 190 this.model.set("sig", signatures); 191 192 for (i = 0; i < signatures.length; i++) { 193 if (!jQuery.isEmptyObject(_.pick(hash, signatures[i]))) { 194 var values = _.values(_.pick(hash, signatures[i]))[0]; 195 this.model.updateFacetArray(values) 196 } 197 } 198 199 this.model.on("change", this.render, this); 200 }, 201 /** 202 * list of events for Backbone View 203 * @memberof module:facetResultsFilter.FacetResultsFilterView 204 * @alias module:facetResultsFilter.FacetResultsFilterView#events 205 */ 206 events: { 207 'click .facet-value': 'updateFacet', 208 'click .filterButton': 'updateFacet', 209 'click .clear-filter': 'removeFacet', 210 'click .bottom-remove-filter > button': 'removeFacet', 211 'click .toogle-facet-visibility': 'toogleFacetVisibility' 212 }, 213 214 toogleFacetVisibility: function () { 215 var showFacets = this.model.get('showAllFacets'); 216 this.model.set('showAllFacets', !showFacets); 217 218 219 }, 220 /** 221 * Updates model 'facetArray' based on params 222 * @param {Event} param Event object with current target 223 * @memberof module:facetResultsFilter.FacetResultsFilterView 224 * @alias module:facetResultsFilter.FacetResultsFilterView#updateFacet 225 */ 226 updateFacet: function (param) { 227 var currentFacet = $(param.currentTarget), 228 facetArray = this.model.get('facetArray'), 229 properties = this.model.get('dataProperties'), 230 facetClose = this.$el.find('.facet-heading > span'), 231 facetGroup = currentFacet.parents('.component-content').find('.facet-search-filter'), 232 facetName = properties.f.toLowerCase(), 233 facetDataValue = currentFacet.data('facetvalue'), 234 facetValue = typeof facetDataValue !== "undefined" ? decodeURIComponent(facetDataValue) : facetDataValue, 235 sig = this.model.get('sig'), 236 index, 237 hash = {}, 238 i; 239 240 if (properties.multi) { 241 if (facetValue) { 242 if (currentFacet.is(':not(.active-facet)')) { 243 this.setActiveFacet(facetName, facetValue); 244 facetArray.push(facetValue); 245 } else { 246 currentFacet.removeClass('active-facet'); 247
248 currentFacet.find('[type=checkbox]').prop('checked', false); 249 currentFacet.find('[type=checkbox] + label:after').css({ 'background': '#fff' }); 250 251 index = facetArray.indexOf(facetValue); 252 if (index > -1) { 253 facetArray.splice(index, 1); 254 } 255 256 if (facetArray.length == 0) { 257 facetClose.removeClass('has-active-facet'); 258 } 259 } 260 this.model.set({ facetArray: facetArray }); 261 } 262 263 //is there any better way to check what action start method? 264 if (currentFacet[0].type == "button") { 265 for (i = 0; i < sig.length; i++) { 266 hash[sig[i]] = _.uniq(facetArray, function (item) { 267 return JSON.stringify(item); 268 }).toString(); 269 } 270 queryModel.updateHash(hash); 271 } 272 } else { 273 if (facetValue) { 274 for (i = 0; i < sig.length; i++) { 275 hash[sig[i]] = facetValue; 276 } 277 facetGroup.data('active-facet', hash); 278 this.setActiveFacet(facetName, facetValue); 279 queryModel.updateHash(hash); 280 } 281 } 282 283 }, 284 /** 285 * Sets default values for Search Result Filter and updates hash accordingly 286 * @param {Event} evt Event object 287 * @memberof module:facetResultsFilter.FacetResultsFilterView 288 * @alias module:facetResultsFilter.FacetResultsFilterView#removeFacet 289 */ 290 removeFacet: function (evt) { 291 evt.preventDefault(); 292 293 var facets = this.$el, 294 facetClose = facets.find('.facet-heading > span'), 295 facetValues = facets.find('.facet-value'), 296 sig = this.model.get('sig'); 297 298 queryModel.updateHash(this.updateSignaturesHash(sig, "", {})); 299 300 facetClose.removeClass('has-active-facet'); 301 302 _.each(facetValues, function (single) { 303 var $single = $(single); 304 if ($single.hasClass('active-facet')) { 305 $single.removeClass('active-facet'); 306 $single.find('[type=checkbox]').prop('checked', false); 307 $single.find('[type=checkbox] + label:after').css({ 'background': '#fff' }); 308 } 309 }); 310 311 this.model.set({ facetArray: [] }); 312 }, 313 /** 314 * Renders view 315 * @memberof module:facetResultsFilter.FacetResultsFilterView 316 * @alias module:facetResultsFilter.FacetResultsFilterView#render 317 */ 318 render: function () { 319 var inst = this, 320 resultData = this.model.get("resultData"), 321 facetClose = this.$el.find('.facet-heading > span'), 322 facetNames = this.model.get('dataProperties').f.split('|'), 323 emptyValueText = this.model.get('dataProperties').emptyValueText, 324 highlightThreshold = this.model.get('dataProperties').highlightThreshold, 325 showLessText = this.model.get('dataProperties').showLessText, 326 showMoreText = this.model.get('dataProperties').showMoreText, 327 highlightBehaviour = parseInt(this.model.get('dataProperties').highlightBehaviour), 328 showAllFacets = this.model.get('showAllFacets'), 329 hash = queryModel.parseHashParameters(window.location.hash), 330 sig = this.model.get('sig'), 331 template, facetName, templateResult; 332 333 //checks if page is opened from disc - if yes then we are in Creative Exchange mode 334 if (window.location.href.startsWith("file://")) { 335 return; 336 } 337 this.manageVisibilityByData(this.$el, resultData) 338 if (resultData !== undefined) { 339 if (inst.model.get('dataProperties').multi === true) { 340 template = _.template(inst.model.get("templateMulti")); 341 } else { 342 template = _.template(inst.model.get("template")); 343 } 344 templateResult = template({ 345 facet: resultData, emptyText: emptyValueText, showLessText: showLessText, 346 showMoreText: showMoreText, highlightBehaviour: highlightBehaviour, showAllFacets: showAllFacets 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 $(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") || initialized) { 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"); 487 _.each(facetResultsFilterList, function (elem) { 488 var model = new FacetResultsFilterModel(), 489 view = new FacetResultsFilterView({ el: $(elem), model: model }); 490 }); 491 492 initialized = true; 493 }; 494 /** 495 * Returns information about facet component 496 * @memberOf module:facetResultsFilter 497 * @alias module:facetResultsFilter.getFacetDataRequestInfo 498 * @returns {Array<FacetDataRequestInfo>} facet data needed for request 499 */ 500 api.getFacetDataRequestInfo = function () { 501 var facetList = $(".facet-single-selection-list"), 502 result = []; 503 504 _.each(facetList, function (elem) { 505 var properties = $(elem).data().properties, 506 signatures = properties.searchResultsSignature.split(','), 507 i; 508 509 for (i = 0; i < signatures.length; i++) { 510 result.push({ 511 signature: signatures[i] === null ? "" : signatures[i], 512 facetName: properties.f, 513 endpoint: properties.endpoint, 514 showMoreText: properties.showMoreText, 515 showLessText: properties.showLessText, 516 highlightBehaviour: properties.highlightBehaviour, 517 s: properties.s, 518 filterWithoutMe: !properties.collapseOnSelection 519 }); 520 } 521 }); 522 523 return result; 524 }; 525 526 return api; 527 528}(jQuery, document)); 529 530XA.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.