src/app/modules/content-search/components/global-search-filter/global-search-filter.component.ts
OnInit
OnChanges
OnDestroy
selector | app-global-search-filter |
styleUrls | ./global-search-filter.component.scss |
templateUrl | ./global-search-filter.component.html |
Properties |
|
Methods |
|
Inputs |
Outputs |
constructor(resourceService: ResourceService, router: Router, activatedRoute: ActivatedRoute, cdr: ChangeDetectorRef, utilService: UtilService, userService: UserService, cacheService: CacheService, connectionService: ConnectionService)
|
|||||||||||||||||||||||||||
Parameters :
|
cachedFilters | |
Type : any
|
|
facets | |
Type : any
|
|
isOpen | |
Type : any
|
|
layoutConfiguration | |
Type : any
|
|
queryParamsToOmit | |
Type : any
|
|
supportedFilterAttributes | |
Type : {}
|
|
Default value : ['se_boards', 'se_mediums', 'se_gradeLevels', 'se_subjects', 'primaryCategory', 'mediaType', 'additionalCategories', 'channel']
|
|
filterChange | |
Type : EventEmitter<literal type>
|
|
Private emitFilterChangeEvent | ||||||
emitFilterChangeEvent(skipUrlUpdate)
|
||||||
Parameters :
Returns :
void
|
Private fetchSelectedFilterAndFilterOption |
fetchSelectedFilterAndFilterOption()
|
Returns :
void
|
Private handleFilterChange |
handleFilterChange()
|
Returns :
void
|
Private hardRefreshFilter |
hardRefreshFilter()
|
Returns :
void
|
ngOnChanges | ||||||
ngOnChanges(changes: SimpleChanges)
|
||||||
Parameters :
Returns :
void
|
ngOnDestroy |
ngOnDestroy()
|
Returns :
void
|
ngOnInit |
ngOnInit()
|
Returns :
void
|
onChange | ||||
onChange(facet)
|
||||
Parameters :
Returns :
void
|
onSearchFacetFilterReset |
onSearchFacetFilterReset()
|
Returns :
void
|
removeFilterSelection | ||||
removeFilterSelection(data)
|
||||
Parameters :
Returns :
void
|
Public resetFilters |
resetFilters()
|
Returns :
void
|
Private setResetFilterInteractData |
setResetFilterInteractData()
|
Returns :
void
|
Public updateRoute |
updateRoute()
|
Returns :
void
|
updateTerms |
updateTerms()
|
Returns :
void
|
Public connectionService |
Type : ConnectionService
|
Public filterChangeEvent |
Default value : new Subject()
|
Optional filterFormTemplateConfig |
Type : IFacetFilterFieldTemplateConfig[]
|
Public filterLayout |
Default value : LibraryFiltersLayout
|
Public isConnected |
Default value : true
|
Public refresh |
Default value : true
|
Public resetFilterInteractEdata |
Type : IInteractEventEdata
|
Public resourceService |
Type : ResourceService
|
Public router |
Type : Router
|
searchFacetFilterComponent |
Type : any
|
Decorators :
@ViewChild('sbSearchFacetFilterComponent')
|
Public selectedFilters |
Type : any
|
Default value : {}
|
Public selectedMediaType |
Type : string
|
Public selectedMediaTypeIndex |
Type : number
|
Default value : 0
|
Private unsubscribe$ |
Default value : new Subject<void>()
|
Public userService |
Type : UserService
|
import {
Component,
Output,
EventEmitter,
Input,
OnInit,
OnDestroy,
ChangeDetectorRef,
OnChanges,
SimpleChanges, ViewChild
} from '@angular/core';
import * as _ from 'lodash-es';
import { ResourceService, UtilService, ConnectionService } from '@sunbird/shared';
import { IInteractEventEdata } from '@sunbird/telemetry';
import { Router, ActivatedRoute } from '@angular/router';
import { Subject } from 'rxjs';
import { debounceTime, map, takeUntil, filter } from 'rxjs/operators';
import { LibraryFiltersLayout } from '@project-sunbird/common-consumption';
import { UserService } from '@sunbird/core';
import { IFacetFilterFieldTemplateConfig } from '@project-sunbird/common-form-elements-full';
import { CacheService } from '../../../shared/services/cache-service/cache.service';
@Component({
selector: 'app-global-search-filter',
templateUrl: './global-search-filter.component.html',
styleUrls: ['./global-search-filter.component.scss']
})
export class GlobalSearchFilterComponent implements OnInit, OnChanges, OnDestroy {
@Input() facets;
@Input() queryParamsToOmit;
@Input() supportedFilterAttributes = ['se_boards', 'se_mediums', 'se_gradeLevels', 'se_subjects', 'primaryCategory', 'mediaType', 'additionalCategories', 'channel'];
public filterLayout = LibraryFiltersLayout;
public selectedMediaTypeIndex = 0;
public selectedMediaType: string;
public selectedFilters: any = {};
public refresh = true;
public isConnected = true;
public filterChangeEvent = new Subject();
private unsubscribe$ = new Subject<void>();
public resetFilterInteractEdata: IInteractEventEdata;
@Input() layoutConfiguration;
@Input() isOpen;
@Output() filterChange: EventEmitter<{ status: string, filters?: any }> = new EventEmitter();
@Input() cachedFilters?: any;
@ViewChild('sbSearchFacetFilterComponent') searchFacetFilterComponent: any;
filterFormTemplateConfig?: IFacetFilterFieldTemplateConfig[];
constructor(public resourceService: ResourceService, public router: Router,
private activatedRoute: ActivatedRoute, private cdr: ChangeDetectorRef, private utilService: UtilService,
public userService: UserService, private cacheService: CacheService, public connectionService: ConnectionService) {
}
onChange(facet) {
let channelData;
if (this.selectedFilters.channel) {
const channelIds = [];
const facetsData = _.find(this.facets, {'name': 'channel'});
_.forEach(this.selectedFilters.channel, (value, index) => {
channelData = _.find(facetsData.values, {'identifier': value});
if (!channelData) {
channelData = _.find(facetsData.values, {'name': value});
}
channelIds.push(channelData.name);
});
this.selectedFilters.channel = channelIds;
}
this.filterChangeEvent.next({event: this.selectedFilters[facet.name], type: facet.name});
}
ngOnChanges(changes: SimpleChanges) {
if (_.get(changes, 'facets.currentValue.length')) {
const updatedFacets = changes['facets'].currentValue;
this.filterFormTemplateConfig = [...updatedFacets].sort((a, b) => {
if (a.index && b.index) {
return a.index.localeCompare(b.index);
}
if (a.index) {
return 1;
}
return -1;
}).map((f) => {
if (f.name === 'mediaType') {
f.values = f.mimeTypeList.map((m) => ({name: m}));
return {
facet: f.name,
type: 'pills',
labelText: f.label || f.name,
placeholderText: `${this.resourceService.frmelmnts.lbl.Select} ${f.label || f.name}`,
multiple: true
};
}
return {
facet: f.name,
type: 'dropdown',
labelText: f.label || f.name,
placeholderText: `${this.resourceService.frmelmnts.lbl.Select} ${f.label || f.name}`,
multiple: true
};
});
this.resourceService.languageSelected$.pipe(takeUntil(this.unsubscribe$)).subscribe((languageData) => {
this.filterFormTemplateConfig.forEach((facet) => {
facet['labelText'] = this.utilService.transposeTerms(facet['labelText'], facet['labelText'], this.resourceService.selectedLang);
facet['placeholderText'] = this.utilService.transposeTerms(facet['placeholderText'], facet['placeholderText'], this.resourceService.selectedLang);
});
});
}
}
ngOnInit() {
this.setResetFilterInteractData();
this.fetchSelectedFilterAndFilterOption();
this.handleFilterChange();
// screen size
if (window.innerWidth <= 992 ) {
this.isOpen = false;
}
this.connectionService.monitor().subscribe(isConnected => {
this.isConnected = isConnected;
});
}
public resetFilters() {
this.selectedFilters = _.pick(this.selectedFilters, ['key', 'selectedTab']);
if (this.utilService.isDesktopApp) {
const userPreferences: any = this.userService.anonymousUserPreference;
if (userPreferences) {
_.forEach(['board', 'medium', 'gradeLevel', 'channel'], (item) => {
if (!_.has(this.selectedFilters, item)) {
this.selectedFilters[item] = _.isArray(userPreferences.framework[item]) ?
userPreferences.framework[item] : _.split(userPreferences.framework[item], ', ');
}
});
}
}
let queryFilters = _.get(this.activatedRoute, 'snapshot.queryParams');
let redirectUrl; // if pageNumber exist then go to first page every time when filter changes, else go exact path
if (_.get(this.activatedRoute, 'snapshot.params.pageNumber')) { // when using dataDriven filter should this should be verified
redirectUrl = this.router.url.split('?')[0].replace(/[^\/]+$/, '1');
} else {
redirectUrl = this.router.url.split('?')[0];
}
if (this.queryParamsToOmit) {
queryFilters = _.omit(_.get(this.activatedRoute, 'snapshot.queryParams'), this.queryParamsToOmit);
queryFilters = {...queryFilters, ...this.selectedFilters};
}
redirectUrl = decodeURI(redirectUrl);
this.router.navigate([redirectUrl], {
relativeTo: this.activatedRoute.parent, queryParams: this.queryParamsToOmit ? queryFilters : this.selectedFilters
});
this.hardRefreshFilter();
}
private fetchSelectedFilterAndFilterOption() {
this.activatedRoute.queryParams.pipe(takeUntil(this.unsubscribe$),
map((queryParams) => {
const queryFilters: any = {};
_.forIn(queryParams, (value, key) => {
if (['medium', 'gradeLevel', 'board', 'channel', 'subject', 'primaryCategory', 'key', 'mediaType', 'se_boards', 'se_mediums', 'se_gradeLevels', 'se_subjects', 'additionalCategories'].includes(key)) {
queryFilters[key] = key === 'key' || _.isArray(value) ? value : [value];
}
});
if(_.get(queryParams,'ignoreSavedFilter')){
queryFilters['ignoreSavedFilter'] = queryParams.ignoreSavedFilter;
}
if (queryParams.selectedTab) {
queryFilters['selectedTab'] = queryParams.selectedTab;
}
if (queryParams.mediaType) {
this.selectedMediaType = _.isArray(queryParams.mediaType) ? queryParams.mediaType[0] : queryParams.mediaType;
} else {
this.selectedMediaType = '';
this.selectedMediaTypeIndex = 0;
}
return queryFilters;
})).subscribe(filters => {
if (_.get(filters, 'key') && _.get(filters, 'ignoreSavedFilter') !== 'true') {
this.cacheService.remove('searchFiltersAll');
}
if (this.cacheService.exists('searchFiltersAll') && !_.get(filters, 'key') &&
_.get(filters, 'ignoreSavedFilter') !== 'true') {
this.selectedFilters = _.cloneDeep(this.cacheService.get('searchFiltersAll'));
} else {
if( _.get(filters, 'ignoreSavedFilter') === 'true'){
} else{
this.cacheService.remove('searchFiltersAll');
this.selectedFilters = _.cloneDeep(filters);
}
}
this.emitFilterChangeEvent(true);
this.hardRefreshFilter();
}, error => {
console.error('fetching filter data failed', error);
});
}
private handleFilterChange() {
this.filterChangeEvent.pipe(
filter(({type, event}) => {
if (type === 'mediaType' && this.selectedMediaTypeIndex !== event.data.index) {
this.selectedMediaTypeIndex = event.data.index;
}
return true;
}),
debounceTime(1000)).subscribe(({ type, event }) => {
this.emitFilterChangeEvent();
});
}
public updateRoute() {
let queryFilters = _.get(this.activatedRoute, 'snapshot.queryParams');
if (this?.selectedFilters?.channel) {
const channelIds = [];
const facetsData = _.find(this.facets, {'name': 'channel'});
_.forEach(this.selectedFilters.channel, (value, index) => {
const data = _.find(facetsData.values, {'name': value});
channelIds.push(data.identifier);
});
this.selectedFilters.channel = channelIds;
}
if(this?.utilService?.isDesktopApp && queryFilters?.selectedTab === 'mydownloads' && this.isConnected) {
this.queryParamsToOmit = this.queryParamsToOmit && this.queryParamsToOmit.length ? this.queryParamsToOmit.push('key') : ['key']
if(this.selectedFilters.key) {
delete this.selectedFilters.key;
}
}
if (this.queryParamsToOmit) {
queryFilters = _.omit(_.get(this.activatedRoute, 'snapshot.queryParams'), this.queryParamsToOmit);
queryFilters = {...queryFilters, ...this.selectedFilters};
}
if (this.cacheService.get('searchFiltersAll')) {
this.selectedFilters['selectedTab'] = 'all';
}
this.router.navigate([], {
queryParams: this.queryParamsToOmit ? queryFilters : { ...queryFilters, ...this.selectedFilters },
relativeTo: this.activatedRoute.parent
});
}
private emitFilterChangeEvent(skipUrlUpdate = false) {
this.updateTerms();
this.filterChange.emit({ status: 'FETCHED', filters: this.selectedFilters });
if (!skipUrlUpdate) {
this.updateRoute();
} else if (this.cacheService.get('searchFiltersAll') && !_.get(this.selectedFilters, 'key')) {
this.updateRoute();
} else if (_.get(this.selectedFilters, 'key')) {
this.updateRoute();
}
}
private hardRefreshFilter() {
this.refresh = false;
this.cdr.detectChanges();
this.refresh = true;
}
private setResetFilterInteractData() {
setTimeout(() => { // wait for model to change
this.resetFilterInteractEdata = {
id: 'reset-filter',
type: 'click',
pageid: _.get(this.activatedRoute, 'snapshot.data.telemetry.pageid'),
extra: { filters: this.selectedFilters }
};
}, 5);
}
removeFilterSelection(data) {
_.map(this.selectedFilters, (value, key) => {
if (this.selectedFilters[data.type] && !_.isEmpty(this.selectedFilters[data.type])) {
_.remove(value, (n) => {
return n === data.value && data.type === key;
});
}
if (_.isEmpty(value)) { delete this.selectedFilters[key]; }
});
this.filterChange.emit({ status: 'FETCHED', filters: this.selectedFilters });
this.updateRoute();
}
onSearchFacetFilterReset() {
this.cacheService.remove('searchFiltersAll');
/* istanbul ignore else */
if (this.searchFacetFilterComponent) {
this.searchFacetFilterComponent.resetFilter();
}
/* istanbul ignore next */
this.router.navigate([], {
queryParams: {
...(() => {
const queryParams = _.cloneDeep(this.activatedRoute.snapshot.queryParams);
const queryFilters = [...this.supportedFilterAttributes, ...['board', 'medium', 'gradeLevel', 'channel']];
queryFilters.forEach((attr) => delete queryParams[attr]);
return queryParams;
})()
},
relativeTo: this.activatedRoute.parent
});
}
ngOnDestroy() {
this.unsubscribe$.next();
this.unsubscribe$.complete();
}
updateTerms() {
this.resourceService.languageSelected$.pipe(takeUntil(this.unsubscribe$)).subscribe((languageData) => {
if (this.facets) {
this.facets.forEach((facet) => {
facet['label'] = this.utilService.transposeTerms(facet['label'], facet['label'], this.resourceService.selectedLang);
});
}
});
}
}
<ng-container *ngIf="facets && layoutConfiguration">
<div class="sbt-filter">
<mat-accordion class="sb-mat-accordion sbt-filter-accordion">
<mat-expansion-panel [expanded]="isOpen">
<mat-expansion-panel-header>
<div class="sbt-filter-overlay"></div>
<div class="sbt-filter-switcher-container cursor-pointer mobile only" (click)="isOpen = !isOpen" tabindex="0">
<div class="sbt-filter-switcher"><i class="sliders horizontal icon"></i></div>
<div class="sbt-filter-text">{{resourceService.frmelmnts?.lbl?.filters | translate}}</div>
</div>
<div class="sbt-filter-switcher-container cursor-pointer computer only">
<div class="sbt-filter-switcher"><i class="sliders horizontal icon"></i></div>
<div class="sbt-filter-text">{{resourceService.frmelmnts?.lbl?.filters | translate}}</div>
</div>
</mat-expansion-panel-header>
<ng-container>
<div class="sbt-filter-bar mr-16 pl-24">
<div class="sbt-reset-bar d-flex flex-ai-center flex-ai-jc-center">
<button class="sb-btn sb-btn-xs sb-btn-link-primary pull-right sbt-btn-reset cursor-pointer"
(click)="onSearchFacetFilterReset()" tabindex="0" appTelemetryInteract
[telemetryInteractEdata]="resetFilterInteractEdata">{{resourceService.frmelmnts?.btn?.reset |
translate}} <i class="icon undo"></i></button>
<span class="sbt-filter-close"><i class="icon-svg icon-svg--xxs icon-close cursor-pointer"
(click)="isOpen = !isOpen" tabindex="0" attr.aria-label="{{resourceService.frmelmnts?.btn?.close}}">
<svg class="icon icon-svg--red">
<use xlink:href="./assets/images/sprite.svg#close"></use>
</svg>
</i>
</span>
</div>
<div class="sbt-filter-scrollable pr-24">
<div class="fsmall my-8 text-left filter-pref-text" tabindex="0">
{{resourceService.frmelmnts?.lbl?.basedOnPref | translate}}</div>
<sb-search-facet-filter *ngIf="filterFormTemplateConfig"
[supportedFilterAttributes]="supportedFilterAttributes" [searchResultFacets]="facets"
[filterFormTemplateConfig]="filterFormTemplateConfig"
(searchFilterChange)="this.selectedFilters = $event; emitFilterChangeEvent(true)"
#sbSearchFacetFilterComponent>
</sb-search-facet-filter>
</div>
</div>
</ng-container>
</mat-expansion-panel>
</mat-accordion>
</div>
</ng-container>
<ng-container *ngIf="facets && !layoutConfiguration">
<div class="sb-prominent-filter p-0 mt-8">
<div class="ui container">
<mat-accordion>
<mat-expansion-panel>
<mat-expansion-panel-header appTelemetryInteract [telemetryInteractEdata]="filterInteractEdata"
[telemetryInteractCdata]="telemetryCdata">
<mat-panel-title>
{{resourceService.frmelmnts?.lbl?.filters}}
</mat-panel-title>
</mat-expansion-panel-header>
<ng-container>
<div class="mb-16" *ngIf="facets">
<ng-container *ngFor="let facet of facets | sortBy:'index':'asc'">
<div class="d-inline-block">
<div class="d-flex flex-ai-center pr-8">
<div *ngIf="selectedFilters[facet.name] && selectedFilters[facet.name].length > 0">
<label>{{facet.label}} : </label>
<ng-container *ngFor="let item of selectedFilters[facet.name]">
<a class="ui label mt-8 mr-8 text-capitalize" *ngIf="item && item.length > 0">
{{item}}
<i class="delete icon" tabindex="0"
(click)="removeFilterSelection({type: facet.name, value: item})"></i>
</a>
</ng-container>
</div>
</div>
</div>
</ng-container>
</div>
<div class="sb-prominent-filter-container sb-global-search" *ngIf="refresh">
<div class="sb-prominent-filter-field" *ngFor="let facet of facets | sortBy:'index':'asc'">
<!-- <label class="fnormal font-weight-bold d-flex">{{facet.label}} </label> -->
<label *ngIf="facet.name !== 'mediaType'">
<sui-multi-select *ngIf="facet.name !== 'mediaType'" [hasLabels]="false"
defaultSelectionText={{facet.label}} zeroSelectionText={{resourceService.frmelmnts.lbl.Select}}
labelField="name" valueField="name" class="ui selection dropdown sbt-dropdown-tick multiple"
[(ngModel)]="selectedFilters[facet.name]" (ngModelChange)="onChange(facet)">
<div *ngIf="facet.name !== 'gradeLevel' else gradeLevel">
<sui-select-option class="text-capitalize"
*ngFor="let dropdownValue of facet.values | sortBy:'name':'asc'" [value]="dropdownValue">
</sui-select-option>
</div>
<ng-template #gradeLevel>
<sui-select-option class="text-capitalize" *ngFor="let dropdownValue of facet.values"
[value]="dropdownValue">
</sui-select-option>
</ng-template>
</sui-multi-select>
</label>
<!-- <div *ngIf="facet.name === 'mediaType'" class="sb-class-bar sb-bg-color-gray-0">
<sb-library-filters [list]="facet.mimeTypeList" [layout]="filterLayout.ROUND"
[selectedItems]="[facet.mimeTypeList.indexOf(selectedMediaType) !== -1 ? facet.mimeTypeList.indexOf(selectedMediaType): selectedMediaTypeIndex]"
(selectedFilter)="filterChangeEvent.next({ event: $event, type: 'mediaType'}); selectedFilters[facet.name]=[$event.data.text]">
</sb-library-filters>
</div> -->
</div>
<div class="d-flex w-100" *ngFor="let facet of facets | sortBy:'index':'asc'">
<div *ngIf="facet.name === 'mediaType'" class="sb-class-bar sb-bg-color-gray-0">
<label>
<sb-library-filters [list]="facet.mimeTypeList" [layout]="filterLayout.ROUND"
[selectedItems]="[facet.mimeTypeList.indexOf(selectedMediaType) !== -1 ? facet.mimeTypeList.indexOf(selectedMediaType): selectedMediaTypeIndex]"
(selectedFilter)="filterChangeEvent.next({ event: $event, type: 'mediaType'}); selectedFilters[facet.name]=[$event.data.text]">
</sb-library-filters>
</label>
</div>
</div>
<div class="sb-prominent-filter-field">
<button class="sb-btn sb-btn-normal sb-btn-outline-primary" (click)="resetFilters()" tabindex="0"
appTelemetryInteract [telemetryInteractEdata]="resetFilterInteractEdata">
{{resourceService.frmelmnts?.btn?.reset}}
</button>
</div>
</div>
</ng-container>
</mat-expansion-panel>
</mat-accordion>
</div>
</div>
</ng-container>
./global-search-filter.component.scss
@use "@project-sunbird/sb-styles/assets/mixins/mixins" as *;
@use "pages/sbt-filter" as *;
@use "components/sb-forms/facet-filters" as *;
:host ::ng-deep {
.ui.selection.sbt-dropdown.dropdown .menu>div .item {
color: var(--sbt-theme-purple-selectbox);
padding: 0.75rem 1.25rem !important;
font-size: 0.86rem;
position: relative;
cursor: pointer;
display: block;
border: none;
height: auto;
text-align: left;
border-top: none;
line-height: 1em;
text-transform: none;
box-shadow: none;
-webkit-touch-callout: none;
}
.ui.dropdown .menu div .active.item{
font-weight: 700 !important;
}
.ui.dropdown .menu>div .item:hover {
background: rgba(var(--rc-rgba-black), 0.05);
}
.ui.dropdown .menu div .selected.item, .ui.dropdown.selected{
font-weight: 700 !important;
}
}