import * as React from 'react' ; import * as ReactDom from 'react-dom' ; import { Version , Text , DisplayMode , Log } from '@microsoft/sp-core-library' ; import { IPropertyPaneConfiguration , IPropertyPaneField , PropertyPaneTextField , IPropertyPaneGroup , PropertyPaneChoiceGroup , PropertyPaneToggle , PropertyPaneDropdown , PropertyPaneHorizontalRule , } from '@microsoft/sp-property-pane' ; import { DynamicProperty } from '@microsoft/sp-component-base' ; import { IPropertyPanePage } from '@microsoft/sp-property-pane' ; import * as webPartStrings from 'SearchFiltersWebPartStrings' ; import * as commonStrings from 'CommonStrings' ; import SearchFilters from './components/SearchFiltersContainer' ; import { ISearchFiltersContainerProps } from './components/ISearchFiltersContainerProps' ; import ISearchFiltersWebPartProps from './ISearchFiltersWebPartProps' ; import IDynamicDataService from '../../services/dynamicDataService/IDynamicDataService' ; import { IDataResultSourceData } from '../../models/dynamicData/IDataResultSourceData' ; import { DynamicDataService } from '../../services/dynamicDataService/DynamicDataService' ; import { IDynamicDataCallables , IDynamicDataPropertyDefinition } from '@microsoft/sp-dynamic-data' ; import { ComponentType } from '../../common/ComponentType' ; import { IDataFilterSourceData } from '../../models/dynamicData/IDataFilterSourceData' ; import { IDataFilter , ILayoutDefinition , LayoutType , ILayout , FilterConditionOperator , IDataFilterResult , IDataFilterConfiguration , FilterType , IDataFilterResultValue , IComponentDefinition , FilterSortType , FilterSortDirection } from '@pnp/modern-search-extensibility' ; import { AsyncCombo } from '../../controls/PropertyPaneAsyncCombo/components/AsyncCombo' ; import { IAsyncComboProps } from '../../controls/PropertyPaneAsyncCombo/components/IAsyncComboProps' ; import { AvailableLayouts , BuiltinLayoutsKeys } from '../../layouts/AvailableLayouts' ; import { LayoutHelper } from '../../helpers/LayoutHelper' ; import { TemplateService } from '../../services/templateService/TemplateService' ; import { ITemplateService } from '../../services/templateService/ITemplateService' ; import { isEmpty , isEqual , uniqBy , cloneDeep , uniq , sortBy } from '@microsoft/sp-lodash-subset' ; import { Dropdown , IDropdownProps , IDropdownOption , Checkbox , Icon , IComboBoxOption , MessageBar , MessageBarType } from 'office-ui-fabric-react' ; import { BuiltinFilterTemplates , BuiltinFilterTypes } from '../../layouts/AvailableTemplates' ; import { ServiceScope } from '@microsoft/sp-core-library' ; import { AvailableComponents } from '../../components/AvailableComponents' ; import { PropertyPaneAsyncCombo } from '../../controls/PropertyPaneAsyncCombo/PropertyPaneAsyncCombo' ; import { BaseWebPart } from '../../common/BaseWebPart' ; import commonStyles from '../../styles/Common.module.scss' ; import { IDataVerticalSourceData } from '../../models/dynamicData/IDataVerticalSourceData' ; import { DynamicPropertyHelper } from '../../helpers/DynamicPropertyHelper' ; export default class SearchFiltersWebPart extends BaseWebPart < ISearchFiltersWebPartProps > implements IDynamicDataCallables { /**
     * Dynamic properties for all connected sources
     */ private _dataSourceDynamicProperties : DynamicProperty < IDataResultSourceData > [ ] = [ ] ; private _verticalsSourceData : DynamicProperty < IDataVerticalSourceData > ; private _selectedFilters : IDataFilter [ ] = [ ] ; /**
     * Dynamically loaded components for property pane
     */ private _placeholderComponent : any = null ; private _propertyFieldCollectionData : any = null ; private _propertyFieldCodeEditor : any = null ; private _propertyFieldCodeEditorLanguages : any = null ; private _customCollectionFieldType : any = null ; private _propertyPanePropertyEditor = null ; /**
     * Properties to avoid to recreate instances every render
     */ private lastLayoutKey : string ; /**
     * The selected layout for the Web Part
     */ private layout : ILayout ; /**
     * The template content to display
     */ private templateContentToDisplay : string ; /**
     * The template service instance
     */ private templateService : ITemplateService = undefined ; /**
     * the dynamic data service instance
     */ private dynamicDataService : IDynamicDataService ; /**
     * The available layout definitions (not instanciated)
     */ private availableLayoutDefinitions : ILayoutDefinition [ ] = AvailableLayouts . BuiltinLayouts . filter ( layout => { return layout . type === LayoutType . Filter ; } ) ; /**
     * The service scope for this specific Web Part instance
     */ private webPartInstanceServiceScope : ServiceScope ; /**
     * The available web component definitions (not registered yet)
     */ private availableWebComponentDefinitions : IComponentDefinition < any > [ ] = AvailableComponents . BuiltinComponents ; /**
     * The available connections as property pane fields
     */ private propertyPaneConnectionsFields : IPropertyPaneField < any > [ ] = [ ] ; protected async onInit ( ) { this . initializeProperties ( ) ; await this . initializeBaseWebPart ( ) ; await this . initializeWebPartServices ( ) ; if ( this . displayMode === DisplayMode . Edit ) { const { Placeholder } = await import ( '@pnp/spfx-controls-react/lib/Placeholder' ) ; this . _placeholderComponent = Placeholder ; } this . context . dynamicDataSourceManager . initializeSource ( this ) ; this . ensureDynamicDataSourcesConnection ( ) ; await this . templateService . registerWebComponents ( this . availableWebComponentDefinitions , this . instanceId ) ; return super . onInit ( ) ; } public async render ( ) { await this . initTemplate ( ) ; if ( this . lastLayoutKey !== this . properties . selectedLayoutKey ) { this . layout = await LayoutHelper . getLayoutInstance ( this . webPartInstanceServiceScope , this . context , this . properties , this . properties . selectedLayoutKey , this . availableLayoutDefinitions ) ; this . lastLayoutKey = this . properties . selectedLayoutKey ; } if ( this . context . propertyPane . isPropertyPaneOpen ( ) ) { this . context . propertyPane . refresh ( ) ; } return this . renderCompleted ( ) ; } protected renderCompleted ( ) { let renderRootElement = null ; let filterResults = [ ] ; if ( this . templateContentToDisplay && this . properties . filtersConfiguration . length > 0 ) { if ( this . _dataSourceDynamicProperties . length > 0 ) { const dataResultsSourceData = this . mergeFiltersDataFromSources ( this . _dataSourceDynamicProperties ) ; filterResults = dataResultsSourceData . availablefilters ; } filterResults = this . _initStaticFilters ( filterResults , this . properties . filtersConfiguration ) ; renderRootElement = React . createElement ( SearchFilters , { templateContent : this . templateContentToDisplay , availableFilters : filterResults , filtersConfiguration : this . properties . filtersConfiguration , domElement : this . domElement , instanceId : this . instanceId , selectedLayoutKey : this . properties . selectedLayoutKey , properties : JSON . parse ( JSON . stringify ( this . properties ) ) , themeVariant : this . _themeVariant , onUpdateFilters : ( updatedFilters ) => { this . _selectedFilters = updatedFilters ; this . context . dynamicDataSourceManager . notifyPropertyChanged ( ComponentType . SearchFilters ) ; } , templateService : this . templateService , webPartTitleProps : { displayMode : this . displayMode , title : this . properties . title , updateProperty : ( value ) => { this . properties . title = value ; } , className : commonStyles . wpTitle } } as ISearchFiltersContainerProps ) ; } else { if ( this . displayMode === DisplayMode . Edit ) { const placeholder = React . createElement ( this . _placeholderComponent , { iconName : 'Filter' , iconText : webPartStrings . General . PlaceHolder . IconText , description : webPartStrings . General . PlaceHolder . Description , buttonLabel : webPartStrings . General . PlaceHolder . ConfigureBtnLabel , onConfigure : ( ) => { this . context . propertyPane . open ( ) ; } } ) ; renderRootElement = placeholder ; } else { renderRootElement = null ; Log . verbose ( `[SearchResultsWebPart.renderCompleted]` , `The 'renderRootElement' was null during render.` , this . webPartInstanceServiceScope ) ; } } if ( this . _verticalsSourceData && this . properties . selectedVerticalKeys . length > 0 ) { const verticalData = DynamicPropertyHelper . tryGetValueSafe ( this . _verticalsSourceData ) ; const parentControlZone = this . getParentControlZone ( ) ; if ( verticalData && verticalData . selectedVertical && this . properties . selectedVerticalKeys . indexOf ( verticalData . selectedVertical . key ) === - 1 ) { if ( this . displayMode === DisplayMode . Edit ) { if ( parentControlZone ) { parentControlZone . removeAttribute ( 'style' ) ; } const verticalNames = verticalData . verticalsConfiguration . filter ( cfg => { return this . properties . selectedVerticalKeys . indexOf ( cfg . key ) !== - 1 ; } ) . map ( v => v . tabName ) ; renderRootElement = React . createElement ( 'div' , { } , React . createElement ( MessageBar , { messageBarType : MessageBarType . info , } , Text . format ( commonStrings . General . CurrentVerticalNotSelectedMessage , verticalNames . join ( ',' ) ) ) , renderRootElement ) ; } else { renderRootElement = null ; this . _selectedFilters = [ ] ; if ( parentControlZone ) { parentControlZone . setAttribute ( 'style' , 'margin-top:0px;padding:0px' ) ; } } } else { if ( parentControlZone ) { parentControlZone . removeAttribute ( 'style' ) ; } } } ReactDom . render ( renderRootElement , this . domElement ) ; super . renderCompleted ( ) ; } public getPropertyDefinitions ( ) { return [ { id : ComponentType . SearchFilters , title : this . properties . title ? `${ this . properties . title } - ${ this . instanceId }` : `${ webPartStrings . General . WebPartDefaultTitle } - ${ this . instanceId }` } ] ; } public getPropertyValue ( propertyId ) { switch ( propertyId ) { case propertyId : return { filterConfiguration : this . properties . filtersConfiguration , selectedFilters : this . _selectedFilters , filterOperator : this . properties . filterOperator , instanceId : this . instanceId } as IDataFilterSourceData ; default : throw new Error ( 'Bad property id' ) ; } } public onCustomPropertyUpdate ( propertyPath , newValue , changeCallback ? ) { if ( propertyPath . localeCompare ( 'selectedVerticalKeys' ) === 0 ) { changeCallback ( propertyPath , ( cloneDeep ( newValue ) as IComboBoxOption [ ] ) . map ( v => { return v . key as string ; } ) ) ; this . context . propertyPane . refresh ( ) ; } } protected onDispose ( ) { ReactDom . unmountComponentAtNode ( this . domElement ) ; } protected get dataVersion ( ) : Version { return Version . parse ( '1.0' ) ; } protected getPropertyPaneConfiguration ( ) { let propertyPanePages = [ ] ; propertyPanePages . push ( { groups : [ { groupName : commonStrings . PropertyPane . ConnectionsPage . DataConnectionsGroupName , groupFields : this . propertyPaneConnectionsFields , } , { groupName : webPartStrings . PropertyPane . FiltersSettingsPage . SettingsGroupName , groupFields : this . getFilterSettings ( ) } ] , displayGroupsAsAccordion : true } , { groups : this . getStylingPageGroups ( ) , displayGroupsAsAccordion : true } ) ; propertyPanePages . push ( { displayGroupsAsAccordion : true , groups : [ ... this . getPropertyPaneWebPartInfoGroups ( ) , { groupName : commonStrings . PropertyPane . InformationPage . ImportExport , groupFields : [ this . _propertyPanePropertyEditor ( { webpart : this , key : 'propertyEditor' } ) ] } ] } ) ; return { pages : propertyPanePages } ; } protected async onPropertyPaneConfigurationStart ( ) { await this . loadPropertyPaneResources ( ) ; } protected async onPropertyPaneFieldChanged ( propertyPath , oldValue , newValue ) { if ( propertyPath . localeCompare ( 'filtersConfiguration' ) === 0 && ! isEqual ( oldValue , newValue ) ) { this . properties . filtersConfiguration = uniqBy ( this . properties . filtersConfiguration , 'filterName' ) ; this . properties . filtersConfiguration = ( newValue as IDataFilterConfiguration [ ] ) . map ( configuration => { if ( configuration . selectedTemplate === BuiltinFilterTemplates . DateRange || configuration . selectedTemplate === BuiltinFilterTemplates . DateInterval ) { configuration . isMulti = false ; configuration . operator = FilterConditionOperator . AND ; } configuration . type = BuiltinFilterTypes [ configuration . selectedTemplate ] ; return configuration ; } ) ; this . _selectedFilters = [ ] ; this . context . dynamicDataSourceManager . notifyPropertyChanged ( ComponentType . SearchFilters ) ; } if ( propertyPath . localeCompare ( 'inlineTemplateContent' ) === 0 ) { if ( this . properties . inlineTemplateContent && this . properties . selectedLayoutKey !== BuiltinLayoutsKeys . FiltersCustom ) { this . properties . selectedLayoutKey = BuiltinLayoutsKeys . FiltersCustom ; this . properties . externalTemplateUrl = '' ; if ( this . context . propertyPane . isPropertyPaneOpen ( ) ) { this . context . propertyPane . refresh ( ) ; } } } if ( ( propertyPath . localeCompare ( 'selectedLayoutKey' ) !== 0 ) && this . layout ) { this . layout . onPropertyUpdate ( propertyPath , oldValue , newValue ) ; } if ( propertyPath . localeCompare ( 'selectedLayoutKey' ) === 0 && ! isEqual ( oldValue , newValue ) && this . properties . selectedLayoutKey !== BuiltinLayoutsKeys . ResultsDebug . toString ( ) ) { this . properties . layoutProperties = { } ; } if ( propertyPath . localeCompare ( 'verticalsDataSourceReference' ) === 0 && this . properties . verticalsDataSourceReference ) { this . ensureDynamicDataSourcesConnection ( ) ; this . context . propertyPane . refresh ( ) ; } if ( propertyPath . localeCompare ( 'useVerticals' ) === 0 ) { if ( ! this . properties . useVerticals ) { this . properties . verticalsDataSourceReference = undefined ; this . properties . selectedVerticalKeys = [ ] ; this . _verticalsSourceData = undefined ; } } this . propertyPaneConnectionsFields = await this . getConnectionOptionsFields ( ) ; this . context . propertyPane . refresh ( ) ; } protected get isRenderAsync ( ) : boolean { return true ; } private async getDataResultsConnectionFields ( ) { const availableSources = await this . dynamicDataService . getAvailableDataSourcesByType ( ComponentType . SearchResults ) ; const sourceOptions = availableSources . map ( source => { return { text : source . text , key : source . key } as IComboBoxOption ; } ) ; const textDisplayValues = [ ] ; sourceOptions . forEach ( option => { if ( this . properties . dataResultsDataSourceReferences . indexOf ( option . key as string ) !== - 1 ) { textDisplayValues . push ( option . text ) ; } } ) ; return [ new PropertyPaneAsyncCombo ( 'dataResultsDataSourceReferences' , { availableOptions : sourceOptions , allowFreeform : false , textDisplayValue : textDisplayValues . join ( ',' ) , label : webPartStrings . PropertyPane . ConnectionsPage . UseDataResultsFromComponentsLabel , description : webPartStrings . PropertyPane . ConnectionsPage . UseDataResultsFromComponentsDescription , onPropertyChange : ( propertyPath , newValue , changeCallback ? ) => { changeCallback ( propertyPath , newValue . map ( option => option . key as string ) ) ; this . ensureDynamicDataSourcesConnection ( ) ; this . context . propertyPane . refresh ( ) ; } , defaultSelectedKeys : this . properties . dataResultsDataSourceReferences , allowMultiSelect : true } ) , ] ; } private async getConnectionOptionsFields ( ) { const dataResultsConnectionsFields = await this . getDataResultsConnectionFields ( ) ; const verticalConnectionFields = await this . getVerticalsConnectionFields ( ) ; const connectionFields = [ ... dataResultsConnectionsFields , PropertyPaneHorizontalRule ( ) , ... verticalConnectionFields ] ; return connectionFields ; } /**
     * Returns property pane 'Styling' page groups
     */ private getStylingPageGroups ( ) { let stylingFields = [ PropertyPaneChoiceGroup ( 'selectedLayoutKey' , { options : LayoutHelper . getLayoutOptions ( this . availableLayoutDefinitions ) } ) ] ; stylingFields . push ( this . _propertyFieldCodeEditor ( 'inlineTemplateContent' , { label : webPartStrings . PropertyPane . LayoutPage . FiltersTemplateFieldLabel , panelTitle : webPartStrings . PropertyPane . LayoutPage . FiltersTemplatePanelHeader , initialValue : this . templateContentToDisplay , deferredValidationTime : 500 , onPropertyChange : this . onPropertyPaneFieldChanged . bind ( this ) , properties : this . properties , disabled : false , key : 'inlineTemplateContentCodeEditor' , language : this . _propertyFieldCodeEditorLanguages . Handlebars } ) ) ; if ( this . properties . selectedLayoutKey === BuiltinLayoutsKeys . FiltersCustom ) { stylingFields . push ( PropertyPaneTextField ( 'externalTemplateUrl' , { label : webPartStrings . PropertyPane . LayoutPage . TemplateUrlFieldLabel , placeholder : webPartStrings . PropertyPane . LayoutPage . TemplateUrlPlaceholder , deferredValidationTime : 500 , onGetErrorMessage : this . onTemplateUrlChange . bind ( this ) } ) ) ; } let groups = [ { groupName : webPartStrings . PropertyPane . LayoutPage . AvailableLayoutsGroupName , groupFields : stylingFields } ] ; const layoutOptions = this . getLayoutTemplateOptions ( ) ; if ( layoutOptions . length > 0 ) { groups . push ( { groupName : webPartStrings . PropertyPane . LayoutPage . LayoutTemplateOptionsGroupName , groupFields : layoutOptions } ) ; } return groups ; } /**
     * Determines the group fields for filters settings
     */ private getFilterSettings ( ) { let availableFieldOptionsFromResults = [ ] ; let filterNameCollectionField = [ { id : 'filterName' , title : webPartStrings . PropertyPane . DataFilterCollection . FilterNameLabel , type : this . _customCollectionFieldType . string , required : true } ] ; if ( this . _dataSourceDynamicProperties ) { let allAvailableFieldsFromResults = [ ] ; this . _dataSourceDynamicProperties . forEach ( dataSourceDynamicProperty => { const dataResultsData = DynamicPropertyHelper . tryGetValueSafe ( dataSourceDynamicProperty ) ; if ( dataResultsData ) { allAvailableFieldsFromResults = allAvailableFieldsFromResults . concat ( dataResultsData . availableFieldsFromResults ) ; } } ) ; availableFieldOptionsFromResults = uniq ( allAvailableFieldsFromResults ) . map ( field => { return { key : field , text : field } ; } ) ; } if ( availableFieldOptionsFromResults . length > 0 ) { filterNameCollectionField = [ { id : 'filterName' , title : webPartStrings . PropertyPane . DataFilterCollection . FilterNameLabel , type : this . _customCollectionFieldType . custom , required : true , onCustomRender : ( field , value , onUpdate , item ) => { return ( React . createElement ( "div" , null , React . createElement ( AsyncCombo , { allowFreeform : true , availableOptions : availableFieldOptionsFromResults , placeholder : webPartStrings . PropertyPane . DataFilterCollection . SelectFilterComboBoxLabel , textDisplayValue : item [ field . id ] ? item [ field . id ] : '' , defaultSelectedKey : item [ field . id ] ? item [ field . id ] : '' , onLoadOptions : ( ) => { return Promise . resolve ( availableFieldOptionsFromResults ) ; } , onUpdateOptions : ( ) => { } , onUpdate : ( filterValue ) => { onUpdate ( field . id , filterValue . key ) ; } } as IAsyncComboProps ) ) ) ; } } ] ; } let filterSettings = [ this . _propertyFieldCollectionData ( 'filtersConfiguration' , { manageBtnLabel : webPartStrings . PropertyPane . DataFilterCollection . CustomizeFiltersBtnLabel , key : 'filters' , enableSorting : true , panelHeader : webPartStrings . PropertyPane . DataFilterCollection . CustomizeFiltersHeader , panelDescription : webPartStrings . PropertyPane . DataFilterCollection . CustomizeFiltersDescription , label : webPartStrings . PropertyPane . DataFilterCollection . CustomizeFiltersFieldLabel , value : this . properties . filtersConfiguration , fields : [ { id : 'displayValue' , title : webPartStrings . PropertyPane . DataFilterCollection . FilterDisplayName , type : this . _customCollectionFieldType . string } , ... filterNameCollectionField , { id : 'maxBuckets' , title : webPartStrings . PropertyPane . DataFilterCollection . FilterMaxBuckets , type : this . _customCollectionFieldType . number , required : false , defaultValue : "" } , { id : 'selectedTemplate' , title : webPartStrings . PropertyPane . DataFilterCollection . FilterTemplate , type : this . _customCollectionFieldType . dropdown , required : true , options : [ { key : BuiltinFilterTemplates . CheckBox , text : webPartStrings . PropertyPane . DataFilterCollection . Templates . CheckBoxTemplate } , { key : BuiltinFilterTemplates . DateRange , text : webPartStrings . PropertyPane . DataFilterCollection . Templates . DateRangeTemplate } , { key : BuiltinFilterTemplates . DateInterval , text : webPartStrings . PropertyPane . DataFilterCollection . Templates . DateIntervalTemplate } , { key : BuiltinFilterTemplates . ComboBox , text : webPartStrings . PropertyPane . DataFilterCollection . Templates . ComboBoxTemplate } ] } , { id : 'type' , title : webPartStrings . PropertyPane . DataFilterCollection . FilterType , type : this . _customCollectionFieldType . custom , defaultValue : FilterType . Refiner , onCustomRender : ( field , value , onUpdate , item , itemId ) => { if ( item . selectedTemplate ) { const filterType = BuiltinFilterTypes [ item . selectedTemplate ] ; return React . createElement ( "div" , { key : `${ field . id }-${ itemId }` } , React . createElement ( Icon , { iconName : filterType === FilterType . Refiner ? 'HorizontalTabKey' : 'Export' , styles : { root : { color : filterType === FilterType . Refiner ? '#498205' : '#a4262c' } } , title : filterType === FilterType . Refiner ? webPartStrings . PropertyPane . DataFilterCollection . FilterTypeRefiner : webPartStrings . PropertyPane . DataFilterCollection . FilterTypeStaticFilter } ) ) ; } else { return null ; } } } , { id : 'expandByDefault' , title : webPartStrings . PropertyPane . DataFilterCollection . FilterExpandByDefault , type : this . _customCollectionFieldType . custom , defaultValue : false , onCustomRender : ( field , value , onUpdate , item , itemId ) => { return React . createElement ( "div" , { key : `${ field . id }-${ itemId }` } , React . createElement ( Checkbox , { defaultChecked : item . selectedTemplate === BuiltinFilterTemplates . ComboBox ? false : item . expandByDefault , disabled : item . selectedTemplate === BuiltinFilterTemplates . ComboBox , onChange : ( ev , checked ) => { onUpdate ( field . id , checked ) ; } } ) ) ; } } , { id : 'showCount' , title : webPartStrings . PropertyPane . DataFilterCollection . ShowCount , type : this . _customCollectionFieldType . custom , defaultValue : false , onCustomRender : ( field , value , onUpdate , item , itemId ) => { return React . createElement ( "div" , { key : `${ field . id }-${ itemId }` } , React . createElement ( Checkbox , { defaultChecked : item . selectedTemplate === BuiltinFilterTemplates . DateRange ? false : item . showCount , disabled : item . selectedTemplate === BuiltinFilterTemplates . DateRange , onChange : ( ev , checked ) => { onUpdate ( field . id , checked ) ; } } ) ) ; } } , { id : 'isMulti' , title : webPartStrings . PropertyPane . DataFilterCollection . IsMulti , type : this . _customCollectionFieldType . custom , defaultValue : false , onCustomRender : ( field , value , onUpdate , item , itemId ) => { return React . createElement ( "div" , { key : `${ field . id }-${ itemId }` } , React . createElement ( Checkbox , { defaultChecked : item . selectedTemplate === BuiltinFilterTemplates . DateRange || item . selectedTemplate === BuiltinFilterTemplates . DateInterval ? false : item . isMulti , disabled : item . selectedTemplate === BuiltinFilterTemplates . DateRange || item . selectedTemplate === BuiltinFilterTemplates . DateInterval , onChange : ( ev , checked ) => { onUpdate ( field . id , checked ) ; } } ) ) ; } } , { id : 'operator' , title : webPartStrings . PropertyPane . DataFilterCollection . Operator , type : this . _customCollectionFieldType . custom , defaultValue : FilterConditionOperator . AND , onCustomRender : ( field , value , onUpdate , item , itemId ) => { return React . createElement ( "div" , { key : `${ field . id }-${ itemId }` } , React . createElement ( Dropdown , { disabled : item . isMulti ? false : true , onChange : ( event , optionItem ) => { onUpdate ( field . id , optionItem . key ) ; } , selectedKey : item . selectedTemplate === BuiltinFilterTemplates . DateRange || item . selectedTemplate === BuiltinFilterTemplates . DateInterval ? FilterConditionOperator . AND : item . operator , options : [ { key : FilterConditionOperator . AND , text : webPartStrings . PropertyPane . DataFilterCollection . ANDOperator } , { key : FilterConditionOperator . OR , text : webPartStrings . PropertyPane . DataFilterCollection . OROperator } ] } as IDropdownProps ) ) ; } , required : false , } , { id : 'sortBy' , title : webPartStrings . PropertyPane . DataFilterCollection . SortBy , type : this . _customCollectionFieldType . custom , defaultValue : FilterSortType . ByName , onCustomRender : ( field , value , onUpdate , item ) => { return ( React . createElement ( "div" , null , React . createElement ( Dropdown , { options : [ { key : FilterSortType . ByName , text : webPartStrings . PropertyPane . DataFilterCollection . SortByName } , { key : FilterSortType . ByCount , text : webPartStrings . PropertyPane . DataFilterCollection . SortByCount } , ] , disabled : item . selectedTemplate === BuiltinFilterTemplates . DateRange || item . selectedTemplate === BuiltinFilterTemplates . DateInterval , defaultSelectedKey : item . sortBy , onChange : ( ev , option ) => onUpdate ( field . id , option . key ) , } as IDropdownProps ) ) ) ; } } , { id : 'sortDirection' , title : webPartStrings . PropertyPane . DataFilterCollection . SortDirection , type : this . _customCollectionFieldType . custom , defaultValue : FilterSortDirection . Ascending , onCustomRender : ( field , value , onUpdate , item ) => { return ( React . createElement ( "div" , null , React . createElement ( Dropdown , { options : [ { key : FilterSortDirection . Ascending , text : webPartStrings . PropertyPane . DataFilterCollection . SortAscending } , { key : FilterSortDirection . Descending , text : webPartStrings . PropertyPane . DataFilterCollection . SortDescending } , ] , disabled : item . selectedTemplate === BuiltinFilterTemplates . DateRange || item . selectedTemplate === BuiltinFilterTemplates . DateInterval , defaultSelectedKey : item . sortDirection , onChange : ( ev , option ) => onUpdate ( field . id , option . key ) , } as IDropdownProps ) ) ) ; } } ] } ) , PropertyPaneChoiceGroup ( 'filterOperator' , { label : webPartStrings . PropertyPane . FiltersSettingsPage . FilterOperator , options : [ { key : FilterConditionOperator . AND , text : webPartStrings . PropertyPane . DataFilterCollection . ANDOperator } , { key : FilterConditionOperator . OR , text : webPartStrings . PropertyPane . DataFilterCollection . OROperator } ] , } ) ] ; return filterSettings ; } private async getVerticalsConnectionFields ( ) { let verticalsConnectionFields = [ PropertyPaneToggle ( 'useVerticals' , { label : commonStrings . PropertyPane . ConnectionsPage . UseDataVerticalsWebPartLabel , checked : this . properties . useVerticals } ) ] ; if ( this . properties . useVerticals ) { verticalsConnectionFields . splice ( 1 , 0 , PropertyPaneDropdown ( 'verticalsDataSourceReference' , { options : await this . dynamicDataService . getAvailableDataSourcesByType ( ComponentType . SearchVerticals ) , label : commonStrings . PropertyPane . ConnectionsPage . UseDataVerticalsFromComponentLabel } ) ) ; if ( this . properties . verticalsDataSourceReference ) { if ( this . _verticalsSourceData ) { const availableVerticals = DynamicPropertyHelper . tryGetValueSafe ( this . _verticalsSourceData ) ; if ( availableVerticals ) { let selectedKeysAsText = [ ] ; availableVerticals . verticalsConfiguration . forEach ( verticalConfiguration => { if ( this . properties . selectedVerticalKeys . indexOf ( verticalConfiguration . key ) !== - 1 ) { selectedKeysAsText . push ( verticalConfiguration . tabName ) ; } } ) ; verticalsConnectionFields . push ( new PropertyPaneAsyncCombo ( 'selectedVerticalKeys' , { availableOptions : availableVerticals . verticalsConfiguration . filter ( v => ! v . isLink ) . map ( verticalConfiguration => { return { key : verticalConfiguration . key , text : verticalConfiguration . tabName } ; } ) , allowMultiSelect : true , allowFreeform : false , description : webPartStrings . PropertyPane . ConnectionsPage . LinkToVerticalLabelHoverMessage , label : webPartStrings . PropertyPane . ConnectionsPage . LinkToVerticalLabel , searchAsYouType : false , defaultSelectedKeys : this . properties . selectedVerticalKeys , textDisplayValue : selectedKeysAsText . join ( ',' ) , onPropertyChange : this . onCustomPropertyUpdate . bind ( this ) , } ) ) ; } } } } return verticalsConnectionFields ; } param value private async onTemplateUrlChange ( value ) { try { if ( isEmpty ( value ) ) { return '' ; } else if ( ! this . templateService . isValidTemplateFile ( value ) ) { return webPartStrings . PropertyPane . LayoutPage . ErrorTemplateExtension ; } else { await this . templateService . ensureFileResolves ( value ) ; return '' ; } } catch ( error ) { return Text . format ( webPartStrings . PropertyPane . LayoutPage . ErrorTemplateResolve , error ) ; } } returns private async initTemplate ( ) { const selectedLayoutTemplateContent = this . availableLayoutDefinitions . filter ( layout => { return layout . key === this . properties . selectedLayoutKey ; } ) [ 0 ] . templateContent ; if ( this . properties . selectedLayoutKey === BuiltinLayoutsKeys . FiltersCustom ) { if ( this . properties . externalTemplateUrl ) { this . templateContentToDisplay = await this . templateService . getFileContent ( this . properties . externalTemplateUrl ) ; } else { this . templateContentToDisplay = this . properties . inlineTemplateContent ? this . properties . inlineTemplateContent : selectedLayoutTemplateContent ; } } else { this . templateContentToDisplay = selectedLayoutTemplateContent ; } return ; } private async initializeWebPartServices ( ) { this . webPartInstanceServiceScope = this . context . serviceScope . startNewChild ( ) ; this . templateService = this . webPartInstanceServiceScope . createAndProvide ( TemplateService . ServiceKey , TemplateService ) ; this . dynamicDataService = this . webPartInstanceServiceScope . createAndProvide ( DynamicDataService . ServiceKey , DynamicDataService ) ; this . dynamicDataService . dynamicDataProvider = this . context . dynamicDataProvider ; this . webPartInstanceServiceScope . finish ( ) ; } /**
     * Initializes required Web Part properties
     */ private initializeProperties ( ) { this . properties . selectedLayoutKey = this . properties . selectedLayoutKey ? this . properties . selectedLayoutKey : BuiltinLayoutsKeys . Vertical ; this . properties . inlineTemplateContent = this . properties . inlineTemplateContent ? this . properties . inlineTemplateContent : '' ; this . properties . filterOperator = this . properties . filterOperator ? this . properties . filterOperator : FilterConditionOperator . OR ; this . properties . layoutProperties = this . properties . layoutProperties ? this . properties . layoutProperties : { } ; this . properties . dataResultsDataSourceReferences = this . properties . dataResultsDataSourceReferences ? this . properties . dataResultsDataSourceReferences : [ ] ; if ( ! this . properties . filtersConfiguration ) { this . properties . filtersConfiguration = [ ] ; } this . properties . useVerticals = this . properties . useVerticals !== undefined ? this . properties . useVerticals : false ; if ( this . properties . selectedVerticalKeys === undefined ) { this . properties . selectedVerticalKeys = [ ] ; } } /**
     * Make sure the dynamic properties are correctly connected to the corresponding sources according to the proeprty pane settings
     */ private ensureDynamicDataSourcesConnection ( ) { this . _dataSourceDynamicProperties . forEach ( dynamicProperty => { dynamicProperty . unregister ( this . render ) ; } ) ; this . _dataSourceDynamicProperties = [ ] ; if ( this . properties . dataResultsDataSourceReferences . length > 0 ) { this . properties . dataResultsDataSourceReferences . forEach ( reference => { const dataSourceDynamicProperty = new DynamicProperty < IDataResultSourceData > ( this . context . dynamicDataProvider ) ; dataSourceDynamicProperty . setReference ( reference ) ; dataSourceDynamicProperty . register ( this . render ) ; this . _dataSourceDynamicProperties . push ( dataSourceDynamicProperty ) ; } ) ; } if ( this . properties . verticalsDataSourceReference ) { if ( ! this . _verticalsSourceData ) { this . _verticalsSourceData = new DynamicProperty < IDataVerticalSourceData > ( this . context . dynamicDataProvider ) ; } this . _verticalsSourceData . setReference ( this . properties . verticalsDataSourceReference ) ; this . _verticalsSourceData . register ( this . render ) ; } else { if ( this . _verticalsSourceData ) { this . _verticalsSourceData . unregister ( this . render ) ; } } } public async loadPropertyPaneResources ( ) { const { PropertyFieldCodeEditor , PropertyFieldCodeEditorLanguages } = await import ( '@pnp/spfx-property-controls/lib/propertyFields/codeEditor' ) ; this . _propertyFieldCodeEditor = PropertyFieldCodeEditor ; this . _propertyFieldCodeEditorLanguages = PropertyFieldCodeEditorLanguages ; const { PropertyFieldCollectionData , CustomCollectionFieldType } = await import ( '@pnp/spfx-property-controls/lib/PropertyFieldCollectionData' ) ; this . _propertyFieldCollectionData = PropertyFieldCollectionData ; this . _customCollectionFieldType = CustomCollectionFieldType ; const { PropertyPanePropertyEditor } = await import ( '@pnp/spfx-property-controls/lib/PropertyPanePropertyEditor' ) ; this . _propertyPanePropertyEditor = PropertyPanePropertyEditor ; this . propertyPaneConnectionsFields = await this . getConnectionOptionsFields ( ) ; } /**
     * Returns layout template options if any
     */ private getLayoutTemplateOptions ( ) { if ( this . layout ) { return this . layout . getPropertyPaneFieldsConfiguration ( [ ] ) ; } else { return [ ] ; } } param filtersConfiguration private _initStaticFilters ( filterResults , filtersConfiguration ) { let updatedFilterResults = cloneDeep ( filterResults ) ; filtersConfiguration . forEach ( filterConfiguration => { if ( filterConfiguration . type === FilterType . StaticFilter ) { if ( filterResults . filter ( filterResult => filterResult . filterName === filterConfiguration . filterName ) . length === 0 ) { updatedFilterResults . push ( { filterName : filterConfiguration . filterName , values : [ ] } ) ; } } } ) ; return updatedFilterResults ; } param dynamicProperties private mergeFiltersDataFromSources ( dynamicProperties ) { let dataResultsSourceData = { availableFieldsFromResults : [ ] , availablefilters : [ ] } ; let allAvailableFieldsFromResults = [ ] ; let allAvailableFilters = [ ] ; let allMergedFilters = [ ] ; dynamicProperties . forEach ( dataSourceDynamicProperty => { const dataSourceData = DynamicPropertyHelper . tryGetValueSafe ( dataSourceDynamicProperty ) ; if ( dataSourceData ) { allAvailableFieldsFromResults = allAvailableFieldsFromResults . concat ( dataSourceData . availableFieldsFromResults ) ; allAvailableFilters = allAvailableFilters . concat ( dataSourceData . availablefilters ) ; if ( dataSourceData . handlebarsContext ) { Object . keys ( dataSourceData . handlebarsContext . helpers ) . forEach ( helperName => { if ( ! this . templateService . Handlebars . helpers [ helperName ] ) { this . templateService . Handlebars . registerHelper ( helperName , dataSourceData . handlebarsContext . helpers [ helperName ] ) ; } } ) ; } } } ) ; allAvailableFilters . forEach ( filterResult => { const mergedFilterIdx = allMergedFilters . map ( m => m . filterName ) . indexOf ( filterResult . filterName ) ; if ( mergedFilterIdx === - 1 ) { allMergedFilters . push ( filterResult ) ; } else { let allMergedValues = [ ] ; const allValues = allMergedFilters [ mergedFilterIdx ] . values . concat ( filterResult . values ) ; allValues . forEach ( value => { const mergedValueIdx = allMergedValues . map ( v => v . name ) . indexOf ( value . name ) ; if ( mergedValueIdx === - 1 ) { allMergedValues . push ( value ) ; } else { allMergedValues [ mergedValueIdx ] . count = allMergedValues [ mergedValueIdx ] . count + value . count ; } } ) ; allMergedFilters [ mergedFilterIdx ] . values = allMergedValues ; } } ) ; const sortedFilters = allMergedFilters . map ( filter => { let sortByField = 'name' ; let sortDirection = FilterSortDirection . Ascending ; const filterConfigurationIdx = this . properties . filtersConfiguration . map ( configuration => configuration . filterName ) . indexOf ( filter . filterName ) ; if ( filterConfigurationIdx !== - 1 ) { const filterConfiguration = this . properties . filtersConfiguration [ filterConfigurationIdx ] ; if ( filterConfiguration . sortBy === FilterSortType . ByCount ) { sortByField = 'count' ; } if ( filterConfiguration . sortDirection === FilterSortDirection . Descending ) { sortDirection = FilterSortDirection . Descending ; } } filter . values = sortDirection === FilterSortDirection . Ascending ? sortBy ( filter . values , sortByField ) : sortBy ( filter . values , sortByField ) . reverse ( ) ; return filter ; } ) ; dataResultsSourceData . availablefilters = sortedFilters ; dataResultsSourceData . availableFieldsFromResults = uniq ( allAvailableFieldsFromResults ) ; return dataResultsSourceData ; } }