import * as React from 'react' ; import * as ReactDom from 'react-dom' ; import { Version , Text , DisplayMode , ServiceScope , Log } from '@microsoft/sp-core-library' ; import { IComboBoxOption , Toggle , IToggleProps , MessageBarType , MessageBar , Link } from 'office-ui-fabric-react' ; import { IWebPartPropertiesMetadata } from '@microsoft/sp-webpart-base' ; import * as webPartStrings from 'SearchResultsWebPartStrings' ; import * as commonStrings from 'CommonStrings' ; import { ISearchResultsContainerProps } from './components/ISearchResultsContainerProps' ; import { IDataSource , IDataSourceDefinition , IComponentDefinition , ILayoutDefinition , ILayout , IDataFilter , LayoutType , FilterType , FilterComparisonOperator , BaseDataSource , IDataFilterValue , IDataFilterResult , FilterConditionOperator , IDataVertical } from '@pnp/modern-search-extensibility' ; import { IPropertyPaneConfiguration , IPropertyPaneChoiceGroupOption , IPropertyPaneGroup , PropertyPaneChoiceGroup , IPropertyPaneField , PropertyPaneHorizontalRule , PropertyPaneToggle , PropertyPaneTextField , PropertyPaneSlider , IPropertyPanePage , PropertyPaneDropdown , PropertyPaneCheckbox , PropertyPaneDynamicField , DynamicDataSharedDepth , PropertyPaneDynamicFieldSet , } from "@microsoft/sp-property-pane" ; import ISearchResultsWebPartProps , { QueryTextSource } from './ISearchResultsWebPartProps' ; import { AvailableDataSources , BuiltinDataSourceProviderKeys } from '../../dataSources/AvailableDataSources' ; import { ServiceKey } from "@microsoft/sp-core-library" ; import SearchResultsContainer from './components/SearchResultsContainer' ; import { AvailableLayouts , BuiltinLayoutsKeys } from '../../layouts/AvailableLayouts' ; import { ITemplateService } from '../../services/templateService/ITemplateService' ; import { TemplateService } from '../../services/templateService/TemplateService' ; import { ServiceScopeHelper } from '../../helpers/ServiceScopeHelper' ; import { cloneDeep , flatten , isEmpty , isEqual , uniq , uniqBy } from "@microsoft/sp-lodash-subset" ; import { AvailableComponents } from '../../components/AvailableComponents' ; import { DynamicProperty } from '@microsoft/sp-component-base' ; import { ITemplateSlot , IDataFilterToken , IDataFilterTokenValue , IDataContext , ITokenService } from '@pnp/modern-search-extensibility' ; import { ResultTypeOperator } from '../../models/common/IDataResultType' ; import { TokenService , BuiltinTokenNames } from '../../services/tokenService/TokenService' ; import { TaxonomyService } from '../../services/taxonomyService/TaxonomyService' ; import { SharePointSearchService } from '../../services/searchService/SharePointSearchService' ; import IDynamicDataService from '../../services/dynamicDataService/IDynamicDataService' ; import { IDataFilterSourceData } from '../../models/dynamicData/IDataFilterSourceData' ; import { ComponentType , DynamicDataProperties } from '../../common/ComponentType' ; import { DynamicDataService } from '../../services/dynamicDataService/DynamicDataService' ; import { IDynamicDataCallables , IDynamicDataPropertyDefinition } from '@microsoft/sp-dynamic-data' ; import { IDataResultSourceData } from '../../models/dynamicData/IDataResultSourceData' ; import { LayoutHelper } from '../../helpers/LayoutHelper' ; import { IAsyncComboProps } from '../../controls/PropertyPaneAsyncCombo/components/IAsyncComboProps' ; import { AsyncCombo } from '../../controls/PropertyPaneAsyncCombo/components/AsyncCombo' ; import { Constants } from '../../common/Constants' ; import PnPTelemetry from "@pnp/telemetry-js" ; import { IPageEventInfo } from '../../components/PaginationComponent' ; import { DataFilterHelper } from '../../helpers/DataFilterHelper' ; import { BuiltinFilterTemplates } from '../../layouts/AvailableTemplates' ; import { IExtensibilityConfiguration } from '../../models/common/IExtensibilityConfiguration' ; import { IDataVerticalSourceData } from '../../models/dynamicData/IDataVerticalSourceData' ; import { BaseWebPart } from '../../common/BaseWebPart' ; import { ApplicationInsights } from '@microsoft/applicationinsights-web' ; import commonStyles from '../../styles/Common.module.scss' ; import { UrlHelper } from '../../helpers/UrlHelper' ; import { ObjectHelper } from '../../helpers/ObjectHelper' ; import { ItemSelectionMode } from '../../models/common/IItemSelectionProps' ; import { PropertyPaneAsyncCombo } from '../../controls/PropertyPaneAsyncCombo/PropertyPaneAsyncCombo' ; import { DynamicPropertyHelper } from '../../helpers/DynamicPropertyHelper' ; const LogSource = "SearchResultsWebPart" ; export default class SearchResultsWebPart extends BaseWebPart < ISearchResultsWebPartProps > implements IDynamicDataCallables { /**
     * The error message
     */ private errorMessage : string = undefined ; /**
     * Dynamic data related fields
     */ private _filtersConnectionSourceData : DynamicProperty < IDataFilterSourceData > ; private _verticalsConnectionSourceData : DynamicProperty < IDataVerticalSourceData > ; private _currentDataResultsSourceData : IDataResultSourceData = { availableFieldsFromResults : [ ] , availablefilters : [ ] , selectedItems : [ ] } ; /**
     * Dynamically loaded components for property pane
     */ private _placeholderComponent : any = null ; private _propertyFieldCodeEditor : any = null ; private _propertyFieldCodeEditorLanguages : any = null ; private _propertyFieldCollectionData : any = null ; private _propertyFieldToogleWithCallout : any = null ; private _propertyPaneWebPartInformation : any = null ; private _propertyFieldCalloutTriggers : any = null ; private _propertyFieldNumber : any = null ; private _customCollectionFieldType : any = null ; private _textDialogComponent : any = null ; private _propertyPanePropertyEditor = null ; /**
     * The selected data source for the WebPart
     */ private dataSource : IDataSource ; /**
     * Properties to avoid to recreate instances every render
     */ private lastDataSourceKey : string ; 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 available data source definitions (not instanciated)
     */ private availableDataSourceDefinitions : IDataSourceDefinition [ ] = AvailableDataSources . BuiltinDataSources ; /**
     * The available layout definitions (not instanciated)
     */ private availableLayoutDefinitions : ILayoutDefinition [ ] = AvailableLayouts . BuiltinLayouts . filter ( layout => { return layout . type === LayoutType . Results ; } ) ; /**
     * The available web component definitions (not registered yet)
     */ private availableWebComponentDefinitions : IComponentDefinition < any > [ ] = AvailableComponents . BuiltinComponents ; /**
     * The current page number
     */ private currentPageNumber : number = 1 ; /**
     * The page URL link if provided by the data source
     */ private currentPageLinkUrl : string = null ; /**
     * The available page links available in the pagination control
     */ private availablePageLinks : string [ ] = [ ] ; /**
     * The token service instance
     */ private tokenService : ITokenService ; /**
     * the dynamic data service instance
     */ private dynamicDataService : IDynamicDataService ; /**
     * The service scope for this specific Web Part instance
     */ private webPartInstanceServiceScope : ServiceScope ; private _lastSelectedFilters : IDataFilter [ ] = [ ] ; private _lastInputQueryText : string = undefined ; /**
     * The default template slots when the data source is instanciated for the first time
     */ private _defaultTemplateSlots : ITemplateSlot [ ] ; private _pushStateCallback = null ; /**
     * The available connections as property pane group
     */ private propertyPaneConnectionsGroup : IPropertyPaneGroup [ ] = [ ] ; constructor ( ) { super ( ) ; this . _bindHashChange = this . _bindHashChange . bind ( this ) ; this . _onDataRetrieved = this . _onDataRetrieved . bind ( this ) ; this . _onItemSelected = this . _onItemSelected . bind ( this ) ; } public async render ( ) { await this . initTemplate ( ) ; await this . setTokens ( ) ; try { this . errorMessage = undefined ; if ( this . lastDataSourceKey !== this . properties . dataSourceKey ) { this . dataSource = await this . getDataSourceInstance ( this . properties . dataSourceKey ) ; this . lastDataSourceKey = this . properties . dataSourceKey ; } 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 ; } } catch ( error ) { this . errorMessage = error . message ? error . message : error ; } await this . setTokens ( ) ; if ( this . context && this . context . propertyPane && this . context . propertyPane . isPropertyPaneOpen ( ) ) { this . context . propertyPane . refresh ( ) ; } return this . renderCompleted ( ) ; } public getPropertyDefinitions ( ) { let propertyDefinitions = [ ] ; if ( this . properties . itemSelectionProps . allowItemSelection ) { propertyDefinitions . push ( { id : DynamicDataProperties . AvailableFieldValuesFromResults , title : webPartStrings . PropertyPane . ConnectionsPage . AvailableFieldValuesFromResults , } ) ; } propertyDefinitions . push ( { id : ComponentType . SearchResults , title : this . properties . title ? `${ this . properties . title } - ${ this . instanceId }` : `${ webPartStrings . General . WebPartDefaultTitle } - ${ this . instanceId }` , } ) ; return propertyDefinitions ; } public getPropertyValue ( propertyId ) { switch ( propertyId ) { case ComponentType . SearchResults : this . _currentDataResultsSourceData . handlebarsContext = this . templateService . Handlebars ; this . _currentDataResultsSourceData . totalCount = this . dataSource ?. getItemCount ( ) ; return this . _currentDataResultsSourceData ; case DynamicDataProperties . AvailableFieldValuesFromResults : let fields = { } ; this . _currentDataResultsSourceData . availableFieldsFromResults . forEach ( ( field ) => { let fieldValue = undefined ; if ( this . _currentDataResultsSourceData . selectedItems . length > 0 ) { fieldValue = ObjectHelper . byPath ( this . _currentDataResultsSourceData . selectedItems , field ) ; } fields [ field ] = fieldValue ? fieldValue . split ( ',' ) : undefined ; } ) ; return fields ; } throw new Error ( 'Bad property id' ) ; } protected renderCompleted ( ) { let renderRootElement = null ; let renderDataContainer = null ; if ( this . dataSource ) { const dataContext = this . getDataContext ( ) ; renderDataContainer = React . createElement ( SearchResultsContainer , { dataSource : this . dataSource , dataSourceKey : this . properties . dataSourceKey , templateContent : this . templateContentToDisplay , instanceId : this . instanceId , properties : JSON . parse ( JSON . stringify ( this . properties ) ) , onDataRetrieved : this . _onDataRetrieved , onItemSelected : this . _onItemSelected , pageContext : this . context . pageContext , dataContext : dataContext , themeVariant : this . _themeVariant , serviceScope : this . webPartInstanceServiceScope , webPartTitleProps : { displayMode : this . displayMode , title : this . properties . title , updateProperty : ( value ) => { this . properties . title = value ; } , themeVariant : this . _themeVariant , className : commonStyles . wpTitle } } as ISearchResultsContainerProps ) ; renderRootElement = renderDataContainer ; } else { if ( this . displayMode === DisplayMode . Edit ) { const placeholder = React . createElement ( this . _placeholderComponent , { iconName : 'Database' , iconText : webPartStrings . General . PlaceHolder . IconText , description : webPartStrings . General . PlaceHolder . Description , buttonLabel : webPartStrings . General . PlaceHolder . ConfigureBtnLabel , onConfigure : ( ) => { this . context . propertyPane . openDetails ( ) ; } } ) ; renderRootElement = placeholder ; } else { renderRootElement = null ; Log . verbose ( `[SearchResultsWebPart.renderCompleted]` , `The 'renderRootElement' was null during render.` , this . webPartInstanceServiceScope ) ; } } if ( this . _verticalsConnectionSourceData && this . properties . selectedVerticalKeys . length > 0 ) { const verticalData = DynamicPropertyHelper . tryGetValueSafe ( this . _verticalsConnectionSourceData ) ; const parentControlZone = this . getParentControlZone ( ) ; if ( verticalData && 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 . _currentDataResultsSourceData = { availableFieldsFromResults : [ ] , availablefilters : [ ] } ; if ( parentControlZone ) { parentControlZone . setAttribute ( 'style' , 'margin-top:0px;padding:0px' ) ; } } } else { if ( parentControlZone ) { parentControlZone . removeAttribute ( 'style' ) ; } } } if ( this . errorMessage ) { renderRootElement = React . createElement ( MessageBar , { messageBarType : MessageBarType . error , } , this . errorMessage , React . createElement ( Link , { target : '_blank' , href : this . properties . documentationLink } , commonStrings . General . Resources . PleaseReferToDocumentationMessage ) ) ; } ReactDom . render ( renderRootElement , this . domElement ) ; super . renderCompleted ( ) ; } protected async onInit ( ) { this . initializeProperties ( ) ; await this . initializeBaseWebPart ( ) ; this . initializeWebPartServices ( ) ; this . bindPagingEvents ( ) ; this . _bindHashChange ( ) ; this . _handleQueryStringChange ( ) ; await this . loadExtensions ( this . properties . extensibilityLibraryConfiguration ) ; await this . templateService . registerWebComponents ( this . availableWebComponentDefinitions , this . instanceId ) ; try { const telemetry = PnPTelemetry . getInstance ( ) ; telemetry . optOut ( ) ; } catch ( error ) { Log . warn ( LogSource , `Opt out for PnP Telemetry failed. Details: ${ error }` , this . context . serviceScope ) ; } if ( this . properties . dataSourceKey && this . properties . selectedLayoutKey && this . properties . enableTelemetry ) { const usageEvent = { name : Constants . PNP_MODERN_SEARCH_EVENT_NAME , properties : { selectedDataSource : this . properties . dataSourceKey , selectedLayout : this . properties . selectedLayoutKey , useFilters : this . properties . useFilters , useVerticals : this . properties . useVerticals } } ; const appInsights = new ApplicationInsights ( { config : { maxBatchInterval : 0 , instrumentationKey : Constants . PNP_APP_INSIGHTS_INSTRUMENTATION_KEY , namePrefix : LogSource , disableFetchTracking : true , disableAjaxTracking : true } } ) ; appInsights . loadAppInsights ( ) ; appInsights . context . application . ver = this . manifest . version ; appInsights . trackEvent ( usageEvent ) ; } if ( this . properties . useMicrosoftGraphToolkit ) { await this . loadMsGraphToolkit ( ) ; } this . context . dynamicDataSourceManager . initializeSource ( this ) ; if ( this . displayMode === DisplayMode . Edit ) { const { Placeholder } = await import ( '@pnp/spfx-controls-react/lib/Placeholder' ) ; this . _placeholderComponent = Placeholder ; } this . ensureDynamicDataSourcesConnection ( ) ; return super . onInit ( ) ; } protected onDispose ( ) { if ( this . _pushStateCallback ) { window . history . pushState = this . _pushStateCallback ; } ReactDom . unmountComponentAtNode ( this . domElement ) ; } protected get propertiesMetadata ( ) : IWebPartPropertiesMetadata { return { 'filtersData' : { dynamicPropertyType : 'object' } , 'queryText' : { dynamicPropertyType : 'string' } } ; } protected get dataVersion ( ) : Version { return Version . parse ( '1.0' ) ; } protected getPropertyPaneConfiguration ( ) { let propertyPanePages = [ ] ; let layoutSlotsGroup = [ ] ; let commonDataSourceProperties = [ ] ; let extensibilityConfigurationGroups = [ ] ; let dataSourceProperties = [ ] ; propertyPanePages . push ( { groups : [ { groupName : webPartStrings . PropertyPane . DataSourcePage . DataSourceConnectionGroupName , groupFields : [ PropertyPaneChoiceGroup ( 'dataSourceKey' , { options : this . getDataSourceOptions ( ) } ) ] } ] , displayGroupsAsAccordion : true } ) ; if ( this . dataSource && ! this . errorMessage ) { dataSourceProperties = this . dataSource . getPropertyPaneGroupsConfiguration ( ) ; if ( this . dataSource . getTemplateSlots ( ) . length > 0 ) { layoutSlotsGroup = [ { groupName : webPartStrings . PropertyPane . DataSourcePage . TemplateSlots . GroupName , groupFields : this . getTemplateSlotOptions ( ) } ] ; } propertyPanePages [ 0 ] . groups = propertyPanePages [ 0 ] . groups . concat ( [ ... layoutSlotsGroup , ... dataSourceProperties , ... commonDataSourceProperties , { groupName : webPartStrings . PropertyPane . DataSourcePage . PagingOptionsGroupName , groupFields : this . getPagingGroupFields ( ) } ] ) ; propertyPanePages . push ( { displayGroupsAsAccordion : true , groups : this . getStylingPageGroups ( ) } , { groups : [ ... this . propertyPaneConnectionsGroup ] , displayGroupsAsAccordion : true } ) ; } extensibilityConfigurationGroups . push ( { groupName : commonStrings . PropertyPane . InformationPage . Extensibility . GroupName , groupFields : this . getExtensibilityFields ( ) } ) ; propertyPanePages . push ( { displayGroupsAsAccordion : true , groups : [ ... this . getPropertyPaneWebPartInfoGroups ( ) , ... extensibilityConfigurationGroups , { groupName : commonStrings . PropertyPane . InformationPage . ImportExport , groupFields : [ this . _propertyPanePropertyEditor ( { webpart : this , key : 'propertyEditor' } ) , PropertyPaneToggle ( 'enableTelemetry' , { label : webPartStrings . PropertyPane . InformationPage . EnableTelemetryLabel , offText : webPartStrings . PropertyPane . InformationPage . EnableTelemetryOn , onText : webPartStrings . PropertyPane . InformationPage . EnableTelemetryOff , } ) ] } ] } ) ; return { pages : propertyPanePages } ; } protected async onPropertyPaneFieldChanged ( propertyPath , oldValue , newValue ) { if ( propertyPath . localeCompare ( 'filtersDataSourceReference' ) === 0 && this . properties . filtersDataSourceReference || propertyPath . localeCompare ( 'verticalsDataSourceReference' ) === 0 && this . properties . verticalsDataSourceReference ) { this . ensureDynamicDataSourcesConnection ( ) ; this . context . propertyPane . refresh ( ) ; } if ( propertyPath . localeCompare ( 'useFilters' ) === 0 ) { if ( ! this . properties . useFilters ) { this . properties . filtersDataSourceReference = undefined ; this . _filtersConnectionSourceData = undefined ; this . context . dynamicDataSourceManager . notifyPropertyChanged ( ComponentType . SearchResults ) ; } } if ( propertyPath . localeCompare ( 'useVerticals' ) === 0 ) { if ( ! this . properties . useVerticals ) { this . properties . verticalsDataSourceReference = undefined ; this . properties . selectedVerticalKeys = [ ] ; this . _verticalsConnectionSourceData = undefined ; } } if ( propertyPath . localeCompare ( 'useDynamicFiltering' ) === 0 && ! this . properties . useDynamicFiltering ) { this . properties . selectedItemFieldValue . setValue ( '' ) ; this . properties . selectedItemFieldValue . unregister ( this . render ) ; } if ( propertyPath . localeCompare ( 'inlineTemplateContent' ) === 0 ) { if ( this . properties . inlineTemplateContent && this . properties . selectedLayoutKey !== BuiltinLayoutsKeys . ResultsCustom ) { this . properties . selectedLayoutKey = BuiltinLayoutsKeys . ResultsCustom ; this . properties . externalTemplateUrl = '' ; this . context . propertyPane . refresh ( ) ; } } if ( ( propertyPath . localeCompare ( 'dataSourceKey' ) !== 0 ) && this . dataSource ) { this . dataSource . onPropertyUpdate ( propertyPath , oldValue , newValue ) ; } if ( propertyPath . localeCompare ( 'dataSourceKey' ) === 0 && ! isEqual ( oldValue , newValue ) ) { this . _currentDataResultsSourceData . availablefilters = [ ] ; this . _currentDataResultsSourceData . availableFieldsFromResults = [ ] ; this . context . dynamicDataSourceManager . notifyPropertyChanged ( ComponentType . SearchResults ) ; this . properties . dataSourceProperties = { } ; this . properties . templateSlots = null ; this . currentPageNumber = 1 ; this . _resetPagingData ( ) ; } if ( propertyPath . localeCompare ( 'selectedLayoutKey' ) === 0 && ! isEqual ( oldValue , newValue ) && this . properties . selectedLayoutKey !== BuiltinLayoutsKeys . ResultsDebug . toString ( ) ) { this . properties . layoutProperties = { } ; } if ( ( propertyPath . localeCompare ( 'selectedLayoutKey' ) !== 0 ) && this . layout ) { this . layout . onPropertyUpdate ( propertyPath , oldValue , newValue ) ; } if ( ( propertyPath . localeCompare ( 'queryTextSource' ) === 0 && this . properties . queryTextSource === QueryTextSource . StaticValue ) || ( propertyPath . localeCompare ( 'queryTextSource' ) === 0 && oldValue === QueryTextSource . StaticValue && newValue === QueryTextSource . DynamicValue ) || ( propertyPath . localeCompare ( 'useInputQueryText' ) === 0 && ! this . properties . useInputQueryText ) ) { if ( this . properties . queryText . tryGetSource ( ) ) { this . properties . queryText . unregister ( this . render ) ; } this . properties . queryText . setValue ( '' ) ; } if ( propertyPath . indexOf ( 'dataSourceProperties' ) !== - 1 && this . dataSource && this . _defaultTemplateSlots && ! isEqual ( this . _defaultTemplateSlots , this . dataSource . getTemplateSlots ( ) ) ) { this . properties . templateSlots = this . dataSource . getTemplateSlots ( ) ; this . _defaultTemplateSlots = this . dataSource . getTemplateSlots ( ) ; } if ( propertyPath . localeCompare ( 'paging.itemsCountPerPage' ) === 0 ) { this . _resetPagingData ( ) ; } if ( propertyPath . localeCompare ( 'extensibilityLibraryConfiguration' ) === 0 ) { const cleanConfiguration = uniqBy ( this . properties . extensibilityLibraryConfiguration , 'id' ) ; this . availableDataSourceDefinitions = AvailableDataSources . BuiltinDataSources ; this . availableLayoutDefinitions = AvailableLayouts . BuiltinLayouts . filter ( layout => { return layout . type === LayoutType . Results ; } ) ; this . availableWebComponentDefinitions = AvailableComponents . BuiltinComponents ; await this . loadExtensions ( cleanConfiguration ) ; } if ( this . properties . queryTextSource === QueryTextSource . StaticValue || ! this . properties . useDefaultQueryText || ! this . properties . useInputQueryText ) { this . properties . defaultQueryText = undefined ; } if ( propertyPath . localeCompare ( "useMicrosoftGraphToolkit" ) === 0 && this . properties . useMicrosoftGraphToolkit ) { await this . loadMsGraphToolkit ( ) ; } if ( propertyPath . localeCompare ( 'selectedItemFieldValue' ) === 0 ) { const reference = this . properties . selectedItemFieldValue . reference ; if ( reference && reference . indexOf ( ComponentType . SearchResults ) !== - 1 ) { this . properties . selectedItemFieldValue . setValue ( '' ) ; this . properties . selectedItemFieldValue . unregister ( this . render ) ; } else { if ( ! oldValue . reference ) { this . properties . selectedItemFieldValue . register ( this . render ) ; } } } if ( propertyPath . localeCompare ( 'itemSelectionProps.destinationFieldName' ) === 0 && ! isEqual ( oldValue , newValue ) ) { const filterToken = this . tokenService . getTokenValue ( BuiltinTokenNames . filters ) ; if ( filterToken ) { delete filterToken [ oldValue ] ; } } this . propertyPaneConnectionsGroup = await this . getConnectionOptionsGroup ( ) ; this . context . propertyPane . refresh ( ) ; this . currentPageNumber = 1 ; } 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 ( ) ; } if ( propertyPath . localeCompare ( 'itemSelectionProps.destinationFieldName' ) === 0 ) { changeCallback ( propertyPath , cloneDeep ( ( newValue as IComboBoxOption ) . key ) ) ; this . context . propertyPane . refresh ( ) ; } } protected get isRenderAsync ( ) : boolean { return true ; } protected async onPropertyPaneConfigurationStart ( ) { await this . loadPropertyPaneResources ( ) ; } /**
     * Determines the input query text value based on Dynamic Data
     */ private _getInputQueryTextValue ( ) { let inputQueryText = null ; let inputQueryFromDataSource = "" ; if ( this . properties . queryText ) { try { inputQueryFromDataSource = DynamicPropertyHelper . tryGetValueSafe ( this . properties . queryText ) ; if ( inputQueryFromDataSource !== undefined && typeof ( inputQueryFromDataSource ) === 'string' ) { inputQueryFromDataSource = decodeURIComponent ( inputQueryFromDataSource ) ; } } catch ( error ) { } } if ( ! inputQueryFromDataSource ) { if ( this . properties . useDefaultQueryText ) { inputQueryText = this . properties . defaultQueryText ; } } else if ( typeof ( inputQueryFromDataSource ) === 'string' ) { inputQueryText = decodeURIComponent ( inputQueryFromDataSource ) ; } return inputQueryText ; } /**
     * Loads the Microsoft Graph Toolkit library dynamically
     */ private async loadMsGraphToolkit ( ) { const { Providers , SharePointProvider } = await import ( '@microsoft/mgt/dist/es6' ) ; if ( ! Providers . globalProvider ) { Providers . globalProvider = new SharePointProvider ( this . context ) ; } } /**
     * Loads extensions from registered extensibility librairies
     */ private async loadExtensions ( librariesConfiguration ) { const extensibilityLibraries = await this . extensibilityService . loadExtensibilityLibraries ( librariesConfiguration ) ; if ( extensibilityLibraries . length > 0 ) { extensibilityLibraries . forEach ( extensibilityLibrary => { if ( extensibilityLibrary . getCustomLayouts ) this . availableLayoutDefinitions = this . availableLayoutDefinitions . concat ( extensibilityLibrary . getCustomLayouts ( ) ) ; if ( extensibilityLibrary . getCustomWebComponents ) this . availableWebComponentDefinitions = this . availableWebComponentDefinitions . concat ( extensibilityLibrary . getCustomWebComponents ( ) ) ; if ( extensibilityLibrary . registerHandlebarsCustomizations ) extensibilityLibrary . registerHandlebarsCustomizations ( this . templateService . Handlebars ) ; } ) ; } } 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 ; this . _textDialogComponent = await import ( '../../controls/TextDialog' ) ; const { PropertyFieldToggleWithCallout } = await import ( '@pnp/spfx-property-controls/lib/PropertyFieldToggleWithCallout' ) ; const { PropertyPaneWebPartInformation } = await import ( '@pnp/spfx-property-controls/lib/PropertyPaneWebPartInformation' ) ; this . _propertyPaneWebPartInformation = PropertyPaneWebPartInformation ; const { CalloutTriggers } = await import ( '@pnp/spfx-property-controls/lib/common/callout/Callout' ) ; const { PropertyFieldNumber } = await import ( '@pnp/spfx-property-controls/lib/PropertyFieldNumber' ) ; const { PropertyPanePropertyEditor } = await import ( '@pnp/spfx-property-controls/lib/PropertyPanePropertyEditor' ) ; this . _propertyPanePropertyEditor = PropertyPanePropertyEditor ; this . _propertyFieldToogleWithCallout = PropertyFieldToggleWithCallout ; this . _propertyFieldCalloutTriggers = CalloutTriggers ; this . _propertyFieldNumber = PropertyFieldNumber ; this . propertyPaneConnectionsGroup = await this . getConnectionOptionsGroup ( ) ; } /**
     * Binds event fired from pagination web components
     */ private bindPagingEvents ( ) { this . domElement . addEventListener ( 'pageNumberUpdated' , ( ( ev ) => { ev . stopImmediatePropagation ( ) ; const eventDetails = ev . detail ; this . currentPageNumber = eventDetails . pageNumber ; this . currentPageLinkUrl = eventDetails . pageLink ; this . availablePageLinks = eventDetails . pageLinks ; this . render ( ) ; } ) . bind ( this ) ) ; } /**
     * Initializes required Web Part properties
     */ private initializeProperties ( ) { this . properties . selectedLayoutKey = this . properties . selectedLayoutKey ? this . properties . selectedLayoutKey : BuiltinLayoutsKeys . Cards ; this . properties . resultTypes = this . properties . resultTypes ? this . properties . resultTypes : [ ] ; this . properties . dataSourceProperties = this . properties . dataSourceProperties ? this . properties . dataSourceProperties : { } ; if ( ! this . properties . queryText ) { this . properties . queryText = new DynamicProperty < string > ( this . context . dynamicDataProvider ) ; this . properties . queryText . setValue ( '' ) ; } this . properties . queryTextSource = this . properties . queryTextSource ? this . properties . queryTextSource : QueryTextSource . StaticValue ; this . properties . layoutProperties = this . properties . layoutProperties ? this . properties . layoutProperties : { } ; this . properties . showSelectedFilters = this . properties . showSelectedFilters !== undefined ? this . properties . showSelectedFilters : false ; this . properties . showResultsCount = this . properties . showResultsCount !== undefined ? this . properties . showResultsCount : true ; this . properties . showBlankIfNoResult = this . properties . showBlankIfNoResult !== undefined ? this . properties . showBlankIfNoResult : false ; this . properties . useMicrosoftGraphToolkit = this . properties . useMicrosoftGraphToolkit !== undefined ? this . properties . useMicrosoftGraphToolkit : false ; this . properties . enableTelemetry = this . properties . enableTelemetry !== undefined ? this . properties . enableTelemetry : true ; if ( ! this . properties . selectedItemFieldValue ) { this . properties . selectedItemFieldValue = new DynamicProperty < string > ( this . context . dynamicDataProvider ) ; this . properties . selectedItemFieldValue . setValue ( '' ) ; } this . properties . itemSelectionProps = this . properties . itemSelectionProps !== undefined ? this . properties . itemSelectionProps : { allowItemSelection : false , destinationFieldName : undefined , selectionMode : ItemSelectionMode . AsDataFilter , allowMulti : false , valuesOperator : FilterConditionOperator . OR } ; this . properties . extensibilityLibraryConfiguration = this . properties . extensibilityLibraryConfiguration ? this . properties . extensibilityLibraryConfiguration : [ { name : commonStrings . General . Extensibility . DefaultExtensibilityLibraryName , enabled : true , id : Constants . DEFAULT_EXTENSIBILITY_LIBRARY_COMPONENT_ID } ] ; if ( this . properties . selectedVerticalKeys === undefined ) { this . properties . selectedVerticalKeys = [ ] ; } if ( this . properties [ 'selectedVerticalKey' ] && this . properties . selectedVerticalKeys . indexOf ( this . properties [ 'selectedVerticalKey' ] ) === - 1 ) { this . properties . selectedVerticalKeys . push ( this . properties [ 'selectedVerticalKey' ] ) ; } this . properties . useVerticals = this . properties . useVerticals !== undefined ? this . properties . useVerticals : false ; if ( ! this . properties . paging ) { this . properties . paging = { itemsCountPerPage : 10 , pagingRange : 5 , showPaging : true , hideDisabled : true , hideFirstLastPages : false , hideNavigation : false , useNextLinks : false } ; } } /**
     * Returns property pane 'Styling' page groups
     */ private getStylingPageGroups ( ) { const canEditTemplate = this . properties . externalTemplateUrl && this . properties . selectedLayoutKey === BuiltinLayoutsKeys . ResultsCustom ? false : true ; let stylingFields = [ PropertyPaneChoiceGroup ( 'selectedLayoutKey' , { options : LayoutHelper . getLayoutOptions ( this . availableLayoutDefinitions ) } ) ] ; let resultTypeInlineTemplate = undefined ; switch ( this . properties . selectedLayoutKey ) { case BuiltinLayoutsKeys . SimpleList : resultTypeInlineTemplate = require ( '../../layouts/resultTypes/default_simple_list.html' ) ; break ; case BuiltinLayoutsKeys . Cards : resultTypeInlineTemplate = require ( '../../layouts/resultTypes/default_cards.html' ) ; break ; case BuiltinLayoutsKeys . ResultsCustom : resultTypeInlineTemplate = require ( '../../layouts/resultTypes/default_custom.html' ) ; break ; case BuiltinLayoutsKeys . People : resultTypeInlineTemplate = require ( '../../layouts/resultTypes/default_people.html' ) ; break ; default : break ; } stylingFields . push ( this . _propertyFieldCodeEditor ( 'inlineTemplateContent' , { label : webPartStrings . PropertyPane . LayoutPage . DialogButtonLabel , panelTitle : webPartStrings . PropertyPane . LayoutPage . DialogTitle , initialValue : this . templateContentToDisplay , deferredValidationTime : 500 , onPropertyChange : this . onPropertyPaneFieldChanged . bind ( this ) , properties : this . properties , disabled : ! canEditTemplate , key : 'inlineTemplateContentCodeEditor' , language : this . _propertyFieldCodeEditorLanguages . Handlebars } ) , this . _propertyFieldCollectionData ( 'resultTypes' , { manageBtnLabel : webPartStrings . PropertyPane . LayoutPage . ResultTypes . EditResultTypesLabel , key : 'resultTypes' , panelHeader : webPartStrings . PropertyPane . LayoutPage . ResultTypes . EditResultTypesLabel , panelDescription : webPartStrings . PropertyPane . LayoutPage . ResultTypes . ResultTypesDescription , enableSorting : true , label : webPartStrings . PropertyPane . LayoutPage . ResultTypes . ResultTypeslabel , value : this . properties . resultTypes , disabled : this . properties . selectedLayoutKey === BuiltinLayoutsKeys . DetailsList || this . properties . selectedLayoutKey === BuiltinLayoutsKeys . ResultsDebug || this . properties . selectedLayoutKey === BuiltinLayoutsKeys . Slider ? true : false , fields : [ { id : 'property' , title : webPartStrings . PropertyPane . LayoutPage . ResultTypes . ConditionPropertyLabel , type : this . _customCollectionFieldType . dropdown , required : true , options : this . _currentDataResultsSourceData . availableFieldsFromResults . map ( field => { return { key : field , text : field } ; } ) } , { id : 'operator' , title : webPartStrings . PropertyPane . LayoutPage . ResultTypes . CondtionOperatorValue , type : this . _customCollectionFieldType . dropdown , defaultValue : ResultTypeOperator . Equal , required : true , options : [ { key : ResultTypeOperator . Equal , text : webPartStrings . PropertyPane . LayoutPage . ResultTypes . EqualOperator } , { key : ResultTypeOperator . NotEqual , text : webPartStrings . PropertyPane . LayoutPage . ResultTypes . NotEqualOperator } , { key : ResultTypeOperator . Contains , text : webPartStrings . PropertyPane . LayoutPage . ResultTypes . ContainsOperator } , { key : ResultTypeOperator . StartsWith , text : webPartStrings . PropertyPane . LayoutPage . ResultTypes . StartsWithOperator } , { key : ResultTypeOperator . NotNull , text : webPartStrings . PropertyPane . LayoutPage . ResultTypes . NotNullOperator } , { key : ResultTypeOperator . GreaterOrEqual , text : webPartStrings . PropertyPane . LayoutPage . ResultTypes . GreaterOrEqualOperator } , { key : ResultTypeOperator . GreaterThan , text : webPartStrings . PropertyPane . LayoutPage . ResultTypes . GreaterThanOperator } , { key : ResultTypeOperator . LessOrEqual , text : webPartStrings . PropertyPane . LayoutPage . ResultTypes . LessOrEqualOperator } , { key : ResultTypeOperator . LessThan , text : webPartStrings . PropertyPane . LayoutPage . ResultTypes . LessThanOperator } ] } , { id : 'value' , title : webPartStrings . PropertyPane . LayoutPage . ResultTypes . ConditionValueLabel , type : this . _customCollectionFieldType . string , required : false , } , { id : "inlineTemplateContent" , title : webPartStrings . PropertyPane . LayoutPage . ResultTypes . InlineTemplateContentLabel , type : this . _customCollectionFieldType . custom , onCustomRender : ( ( field , value , onUpdate ) => { return ( React . createElement ( "div" , null , React . createElement ( this . _textDialogComponent . TextDialog , { language : this . _propertyFieldCodeEditorLanguages . Handlebars , dialogTextFieldValue : value ? value : resultTypeInlineTemplate , onChanged : ( fieldValue ) => onUpdate ( field . id , fieldValue ) , strings : { cancelButtonText : webPartStrings . PropertyPane . LayoutPage . ResultTypes . CancelButtonText , dialogButtonText : webPartStrings . PropertyPane . LayoutPage . ResultTypes . DialogButtonText , dialogTitle : webPartStrings . PropertyPane . LayoutPage . ResultTypes . DialogTitle , saveButtonText : webPartStrings . PropertyPane . LayoutPage . ResultTypes . SaveButtonText } } ) ) ) ; } ) . bind ( this ) } , { id : 'externalTemplateUrl' , title : webPartStrings . PropertyPane . LayoutPage . ResultTypes . ExternalUrlLabel , type : this . _customCollectionFieldType . url , onGetErrorMessage : this . onTemplateUrlChange . bind ( this ) , placeholder : 'https://mysite/Documents/external.html' } ] } ) ) ; if ( this . properties . selectedLayoutKey === BuiltinLayoutsKeys . ResultsCustom ) { stylingFields . push ( PropertyPaneTextField ( 'externalTemplateUrl' , { label : webPartStrings . PropertyPane . LayoutPage . TemplateUrlFieldLabel , placeholder : webPartStrings . PropertyPane . LayoutPage . TemplateUrlPlaceholder , deferredValidationTime : 500 , validateOnFocusIn : true , validateOnFocusOut : true , onGetErrorMessage : this . onTemplateUrlChange . bind ( this ) } ) ) ; } let groups = [ { groupName : webPartStrings . PropertyPane . LayoutPage . LayoutSelectionGroupName , groupFields : stylingFields } ] ; let layoutOptionsFields = [ PropertyPaneToggle ( 'itemSelectionProps.allowItemSelection' , { label : webPartStrings . PropertyPane . LayoutPage . AllowItemSelection } ) , PropertyPaneToggle ( 'showBlankIfNoResult' , { label : webPartStrings . PropertyPane . LayoutPage . ShowBlankIfNoResult , } ) , PropertyPaneToggle ( 'showResultsCount' , { label : webPartStrings . PropertyPane . LayoutPage . ShowResultsCount , } ) , PropertyPaneToggle ( 'useMicrosoftGraphToolkit' , { label : webPartStrings . PropertyPane . LayoutPage . UseMicrosoftGraphToolkit , } ) ] ; if ( this . properties . filtersDataSourceReference ) { layoutOptionsFields . push ( PropertyPaneToggle ( 'showSelectedFilters' , { label : webPartStrings . PropertyPane . LayoutPage . ShowSelectedFilters , } ) ) ; } if ( this . properties . itemSelectionProps . allowItemSelection ) { layoutOptionsFields . splice ( 1 , 0 , PropertyPaneToggle ( 'itemSelectionProps.allowMulti' , { label : webPartStrings . PropertyPane . LayoutPage . AllowMultipleItemSelection , } ) , PropertyPaneHorizontalRule ( ) ) ; } const layoutOptions = this . getLayoutTemplateOptions ( ) ; groups . push ( { groupName : webPartStrings . PropertyPane . LayoutPage . CommonOptionsGroupName , groupFields : layoutOptionsFields } , { groupName : webPartStrings . PropertyPane . LayoutPage . LayoutTemplateOptionsGroupName , groupFields : layoutOptions } ) ; return groups ; } /**
     * Returns property pane 'Paging' group fields
     */ private getPagingGroupFields ( ) { let groupFields = [ ] ; if ( this . dataSource ) { if ( this . dataSource . getPagingBehavior ( ) ) { groupFields . push ( PropertyPaneToggle ( 'paging.showPaging' , { label : webPartStrings . PropertyPane . DataSourcePage . ShowPagingFieldName , } ) , this . _propertyFieldNumber ( 'paging.itemsCountPerPage' , { label : webPartStrings . PropertyPane . DataSourcePage . ItemsCountPerPageFieldName , maxValue : 500 , minValue : 1 , value : this . properties . paging . itemsCountPerPage , disabled : ! this . properties . paging . showPaging , key : 'paging.itemsCountPerPage' } ) , PropertyPaneSlider ( 'paging.pagingRange' , { label : webPartStrings . PropertyPane . DataSourcePage . PagingRangeFieldName , max : 50 , min : 0 , step : 1 , showValue : true , value : this . properties . paging . pagingRange , disabled : ! this . properties . paging . showPaging } ) , PropertyPaneHorizontalRule ( ) , PropertyPaneToggle ( 'paging.hideNavigation' , { label : webPartStrings . PropertyPane . DataSourcePage . HideNavigationFieldName , disabled : ! this . properties . paging . showPaging } ) , PropertyPaneToggle ( 'paging.hideFirstLastPages' , { label : webPartStrings . PropertyPane . DataSourcePage . HideFirstLastPagesFieldName , disabled : ! this . properties . paging . showPaging } ) , PropertyPaneToggle ( 'paging.hideDisabled' , { label : webPartStrings . PropertyPane . DataSourcePage . HideDisabledFieldName , disabled : ! this . properties . paging . showPaging } ) ) ; } } return groupFields ; } private getExtensibilityFields ( ) { let extensibilityFields = [ this . _propertyFieldCollectionData ( 'extensibilityLibraryConfiguration' , { manageBtnLabel : commonStrings . PropertyPane . InformationPage . Extensibility . ManageBtnLabel , key : 'extensibilityLibraryConfiguration' , enableSorting : true , panelHeader : webPartStrings . PropertyPane . InformationPage . Extensibility . PanelHeader , panelDescription : webPartStrings . PropertyPane . InformationPage . Extensibility . PanelDescription , label : commonStrings . PropertyPane . InformationPage . Extensibility . FieldLabel , value : this . properties . extensibilityLibraryConfiguration , fields : [ { id : 'name' , title : commonStrings . PropertyPane . InformationPage . Extensibility . Columns . Name , type : this . _customCollectionFieldType . string } , { id : 'id' , title : commonStrings . PropertyPane . InformationPage . Extensibility . Columns . Id , type : this . _customCollectionFieldType . string , onGetErrorMessage : this . _validateGuid . bind ( this ) } , { id : 'enabled' , title : commonStrings . PropertyPane . InformationPage . Extensibility . Columns . Enabled , type : this . _customCollectionFieldType . custom , required : true , onCustomRender : ( field , value , onUpdate , item , itemId ) => { return ( React . createElement ( "div" , null , React . createElement ( Toggle , { key : itemId , checked : value , offText : commonStrings . General . OffTextLabel , onText : commonStrings . General . OnTextLabel , onChange : ( ( evt , checked ) => { onUpdate ( field . id , checked ) ; } ) . bind ( this ) } as IToggleProps ) ) ) ; } } ] } ) ] ; return extensibilityFields ; } /**
     * Builds the data source options list from the available data sources
     */ private getDataSourceOptions ( ) { let dataSourceOptions = [ ] ; this . availableDataSourceDefinitions . forEach ( ( source ) => { dataSourceOptions . push ( { iconProps : { officeFabricIconFontName : source . iconName } , imageSize : { width : 200 , height : 100 } , key : source . key , text : source . name , } ) ; } ) ; return dataSourceOptions ; } /**
     * Returns layout template options if any
     */ private getLayoutTemplateOptions ( ) { if ( this . layout && ! this . errorMessage ) { return this . layout . getPropertyPaneFieldsConfiguration ( this . _currentDataResultsSourceData . availableFieldsFromResults ) ; } else { return [ ] ; } } private getTemplateSlotOptions ( ) { let templateSlotFields = [ ] ; if ( this . dataSource ) { let availableOptions ; if ( this . _currentDataResultsSourceData . availableFieldsFromResults . length > 0 ) { availableOptions = this . _currentDataResultsSourceData . availableFieldsFromResults . map ( field => { return { key : field , text : field } ; } ) ; } else { availableOptions = this . dataSource . getTemplateSlots ( ) . map ( slot => { return { key : slot . slotField , text : slot . slotField } ; } ) ; } templateSlotFields . push ( this . _propertyFieldCollectionData ( 'templateSlots' , { manageBtnLabel : webPartStrings . PropertyPane . DataSourcePage . TemplateSlots . ConfigureSlotsBtnLabel , key : 'templateSlots' , enableSorting : false , panelHeader : webPartStrings . PropertyPane . DataSourcePage . TemplateSlots . ConfigureSlotsPanelHeader , panelDescription : webPartStrings . PropertyPane . DataSourcePage . TemplateSlots . ConfigureSlotsPanelDescription , label : webPartStrings . PropertyPane . DataSourcePage . TemplateSlots . ConfigureSlotsLabel , value : this . properties . templateSlots , fields : [ { id : 'slotName' , title : webPartStrings . PropertyPane . DataSourcePage . TemplateSlots . SlotNameFieldName , type : this . _customCollectionFieldType . string } , { id : 'slotField' , title : webPartStrings . PropertyPane . DataSourcePage . TemplateSlots . SlotFieldFieldName , type : this . _customCollectionFieldType . custom , required : false , onCustomRender : ( field , value , onUpdate , item ) => { return ( React . createElement ( "div" , null , React . createElement ( AsyncCombo , { allowFreeform : true , availableOptions : availableOptions , placeholder : webPartStrings . PropertyPane . DataSourcePage . TemplateSlots . SlotFieldPlaceholderName , textDisplayValue : item [ field . id ] ? item [ field . id ] : '' , defaultSelectedKey : item [ field . id ] ? item [ field . id ] : '' , onLoadOptions : ( ) => { return Promise . resolve ( availableOptions ) ; } , onUpdateOptions : ( ) => { } , onUpdate : ( filterValue ) => { onUpdate ( field . id , filterValue . key ) ; } } as IAsyncComboProps ) ) ) ; } } ] } ) ) ; } return templateSlotFields ; } private getSearchQueryTextFields ( ) { let searchQueryTextFields = [ this . _propertyFieldToogleWithCallout ( 'useInputQueryText' , { label : webPartStrings . PropertyPane . ConnectionsPage . UseInputQueryText , calloutTrigger : this . _propertyFieldCalloutTriggers . Hover , key : 'useInputQueryText' , calloutContent : React . createElement ( 'p' , { style : { maxWidth : 250 , wordBreak : 'break-word' } } , webPartStrings . PropertyPane . ConnectionsPage . UseInputQueryTextHoverMessage ) , onText : commonStrings . General . OnTextLabel , offText : commonStrings . General . OffTextLabel , checked : this . properties . useInputQueryText } ) ] ; if ( this . properties . useInputQueryText ) { searchQueryTextFields . push ( PropertyPaneChoiceGroup ( 'queryTextSource' , { options : [ { key : QueryTextSource . StaticValue , text : webPartStrings . PropertyPane . ConnectionsPage . InputQueryTextStaticValue } , { key : QueryTextSource . DynamicValue , text : webPartStrings . PropertyPane . ConnectionsPage . InputQueryTextDynamicValue } ] } ) ) ; switch ( this . properties . queryTextSource ) { case QueryTextSource . StaticValue : searchQueryTextFields . push ( PropertyPaneTextField ( 'queryText' , { label : webPartStrings . PropertyPane . ConnectionsPage . SearchQueryTextFieldLabel , description : webPartStrings . PropertyPane . ConnectionsPage . SearchQueryTextFieldDescription , multiline : true , resizable : true , placeholder : webPartStrings . PropertyPane . ConnectionsPage . SearchQueryPlaceHolderText , onGetErrorMessage : this . _validateEmptyField . bind ( this ) , deferredValidationTime : 500 } ) ) ; break ; case QueryTextSource . DynamicValue : searchQueryTextFields . push ( PropertyPaneDynamicField ( 'queryText' , { label : '' } ) , PropertyPaneCheckbox ( 'useDefaultQueryText' , { text : webPartStrings . PropertyPane . ConnectionsPage . SearchQueryTextUseDefaultQuery , disabled : this . properties . queryText . reference === undefined } ) ) ; if ( this . properties . useDefaultQueryText && this . properties . queryText . reference !== undefined ) { searchQueryTextFields . push ( PropertyPaneTextField ( 'defaultQueryText' , { label : webPartStrings . PropertyPane . ConnectionsPage . SearchQueryTextDefaultValue , multiline : true } ) ) ; } break ; default : break ; } } return searchQueryTextFields ; } private async getDataResultsConnectionFields ( ) { let dataResultsConnectionFields = [ PropertyPaneToggle ( 'useDynamicFiltering' , { label : webPartStrings . PropertyPane . ConnectionsPage . UseDynamicFilteringsWebPartLabel , checked : this . properties . useDynamicFiltering } ) ] ; if ( this . properties . useDynamicFiltering ) { let isSourceFieldConfigured = false ; if ( this . properties . selectedItemFieldValue . reference ) { isSourceFieldConfigured = /^.+:.+:(.+)$/ . test ( this . properties . selectedItemFieldValue . reference ) ; } dataResultsConnectionFields . push ( PropertyPaneDynamicFieldSet ( { label : webPartStrings . PropertyPane . ConnectionsPage . UseDataResultsFromComponentsLabel , fields : [ PropertyPaneDynamicField ( 'selectedItemFieldValue' , { label : webPartStrings . PropertyPane . ConnectionsPage . UseDataResultsFromComponentsLabel , } ) ] , sharedConfiguration : { depth : DynamicDataSharedDepth . Property , property : { filters : { propertyId : DynamicDataProperties . AvailableFieldValuesFromResults } } } } ) ) ; if ( isSourceFieldConfigured ) { const availableOptions = this . _currentDataResultsSourceData . availableFieldsFromResults . map ( field => { return { key : field , text : field } ; } ) ; dataResultsConnectionFields . splice ( 4 , 0 , new PropertyPaneAsyncCombo ( 'itemSelectionProps.destinationFieldName' , { label : webPartStrings . PropertyPane . ConnectionsPage . SourceDestinationFieldLabel , availableOptions : availableOptions , description : webPartStrings . PropertyPane . ConnectionsPage . SourceDestinationFieldDescription , allowMultiSelect : false , allowFreeform : true , searchAsYouType : false , defaultSelectedKeys : this . properties . selectedVerticalKeys , textDisplayValue : this . properties . itemSelectionProps . destinationFieldName , onPropertyChange : this . onCustomPropertyUpdate . bind ( this ) , } ) ) ; } if ( isSourceFieldConfigured && this . properties . itemSelectionProps . destinationFieldName ) { dataResultsConnectionFields . splice ( 4 , 0 , PropertyPaneChoiceGroup ( 'itemSelectionProps.selectionMode' , { options : [ { key : ItemSelectionMode . AsDataFilter , text : webPartStrings . PropertyPane . LayoutPage . AsDataFiltersSelectionMode } , { key : ItemSelectionMode . AsTokenValue , text : webPartStrings . PropertyPane . LayoutPage . AsTokensSelectionMode } ] , label : webPartStrings . PropertyPane . LayoutPage . SelectionModeLabel , } ) ) ; if ( this . properties . itemSelectionProps . selectionMode === ItemSelectionMode . AsDataFilter ) { dataResultsConnectionFields . splice ( 5 , 0 , this . _propertyPaneWebPartInformation ( { description : `<em>${ webPartStrings . PropertyPane . LayoutPage . AsDataFiltersDescription }</em>` , key : 'selectionModeText' } ) , PropertyPaneChoiceGroup ( 'itemSelectionProps.valuesOperator' , { options : [ { key : FilterConditionOperator . OR , text : 'OR' } , { key : FilterConditionOperator . AND , text : 'AND' } , ] , label : webPartStrings . PropertyPane . LayoutPage . FilterValuesOperator } ) ) ; } else { dataResultsConnectionFields . splice ( 4 , 0 , this . _propertyPaneWebPartInformation ( { description : `<em>${ webPartStrings . PropertyPane . LayoutPage . AsTokensDescription }</em>` , key : 'selectionModeText' } ) ) ; } } } return dataResultsConnectionFields ; } private async getFiltersConnectionFields ( ) { let filtersConnectionFields = [ PropertyPaneToggle ( 'useFilters' , { label : webPartStrings . PropertyPane . ConnectionsPage . UseFiltersWebPartLabel , checked : this . properties . useFilters } ) ] ; if ( this . properties . useFilters ) { filtersConnectionFields . splice ( 1 , 0 , PropertyPaneDropdown ( 'filtersDataSourceReference' , { options : await this . dynamicDataService . getAvailableDataSourcesByType ( ComponentType . SearchFilters ) , label : webPartStrings . PropertyPane . ConnectionsPage . UseFiltersFromComponentLabel } ) ) ; } return filtersConnectionFields ; } private async getVerticalsConnectionFields ( ) { let verticalsConnectionFields = [ PropertyPaneToggle ( 'useVerticals' , { label : webPartStrings . PropertyPane . ConnectionsPage . UseSearchVerticalsWebPartLabel , checked : this . properties . useVerticals } ) ] ; if ( this . properties . useVerticals ) { verticalsConnectionFields . splice ( 1 , 0 , PropertyPaneDropdown ( 'verticalsDataSourceReference' , { options : await this . dynamicDataService . getAvailableDataSourcesByType ( ComponentType . SearchVerticals ) , label : webPartStrings . PropertyPane . ConnectionsPage . UseSearchVerticalsFromComponentLabel } ) ) ; if ( this . properties . verticalsDataSourceReference ) { if ( this . _verticalsConnectionSourceData ) { const availableVerticals = DynamicPropertyHelper . tryGetValueSafe ( this . _verticalsConnectionSourceData ) ; 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 ; } private async getConnectionOptionsGroup ( ) { const filterConnectionFields = await this . getFiltersConnectionFields ( ) ; const verticalConnectionFields = await this . getVerticalsConnectionFields ( ) ; const dataResultsConnectionsFields = await this . getDataResultsConnectionFields ( ) ; let availableConnectionsGroup = [ { groupName : webPartStrings . PropertyPane . ConnectionsPage . ConnectionsPageGroupName , groupFields : [ ... this . getSearchQueryTextFields ( ) , PropertyPaneHorizontalRule ( ) , ... filterConnectionFields , PropertyPaneHorizontalRule ( ) , ... verticalConnectionFields , PropertyPaneHorizontalRule ( ) , ... dataResultsConnectionsFields ] } ] ; return availableConnectionsGroup ; } param dataSourceKey param dataSourceDefinitions returns private async getDataSourceInstance ( dataSourceKey ) { let dataSource = undefined ; let serviceKey = undefined ; if ( dataSourceKey ) { switch ( dataSourceKey ) { case BuiltinDataSourceProviderKeys . SharePointSearch : const { SharePointSearchDataSource } = await import ( '../../dataSources/SharePointSearchDataSource' ) ; serviceKey = ServiceKey . create < IDataSource > ( 'ModernSearch:SharePointSearchDataSource' , SharePointSearchDataSource ) ; break ; case BuiltinDataSourceProviderKeys . MicrosoftSearch : const { MicrosoftSearchDataSource } = await import ( '../../dataSources/MicrosoftSearchDataSource' ) ; serviceKey = ServiceKey . create < IDataSource > ( 'ModernSearch:SharePointSearchDataSource' , MicrosoftSearchDataSource ) ; break ; default : break ; } return new Promise < IDataSource > ( ( resolve , reject ) => { const childServiceScope = ServiceScopeHelper . registerChildServices ( this . webPartInstanceServiceScope , [ serviceKey , TaxonomyService . ServiceKey , SharePointSearchService . ServiceKey , TokenService . ServiceKey ] ) ; childServiceScope . whenFinished ( async ( ) => { this . tokenService = childServiceScope . consume < ITokenService > ( TokenService . ServiceKey ) ; await this . setTokens ( ) ; dataSource = childServiceScope . consume < IDataSource > ( serviceKey ) ; const isValidDataSource = ( dataSourceInstance ) : dataSourceInstance is BaseDataSource < any > => { return ( ( dataSourceInstance as BaseDataSource < any > ) . getAppliedFilters !== undefined && ( dataSourceInstance as BaseDataSource < any > ) . getData !== undefined && ( dataSourceInstance as BaseDataSource < any > ) . getFilterBehavior !== undefined && ( dataSourceInstance as BaseDataSource < any > ) . getItemCount !== undefined && ( dataSourceInstance as BaseDataSource < any > ) . getPagingBehavior !== undefined && ( dataSourceInstance as BaseDataSource < any > ) . getPropertyPaneGroupsConfiguration !== undefined && ( dataSourceInstance as BaseDataSource < any > ) . getTemplateSlots !== undefined && ( dataSourceInstance as BaseDataSource < any > ) . onInit !== undefined && ( dataSourceInstance as BaseDataSource < any > ) . onPropertyUpdate !== undefined ) ; } ; if ( ! isValidDataSource ( dataSource ) ) { reject ( new Error ( Text . format ( commonStrings . General . Extensibility . InvalidDataSourceInstance , dataSourceKey ) ) ) ; } if ( dataSource ) { dataSource . properties = this . properties . dataSourceProperties ; dataSource . context = this . context ; dataSource . editMode = this . displayMode == DisplayMode . Edit ; dataSource . render = this . render ; dataSource . serviceKeys = { TokenService : TokenService . ServiceKey } ; await dataSource . onInit ( ) ; if ( isEmpty ( this . properties . templateSlots ) ) { this . properties . templateSlots = dataSource . getTemplateSlots ( ) ; this . _defaultTemplateSlots = dataSource . getTemplateSlots ( ) ; } resolve ( dataSource ) ; } } ) ; } ) ; } } param value private async onTemplateUrlChange ( value ) { try { if ( isEmpty ( value ) ) { return Promise . resolve ( '' ) ; } else if ( ! this . templateService . isValidTemplateFile ( value ) ) { return Promise . resolve ( webPartStrings . PropertyPane . LayoutPage . ErrorTemplateExtension ) ; } else { await this . templateService . ensureFileResolves ( value ) ; return Promise . resolve ( '' ) ; } } catch ( error ) { return Promise . resolve ( 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 . ResultsCustom ) { 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 ; } await this . templateService . registerResultTypes ( this . properties . resultTypes ) ; return ; } /**
      * Initializes the service scope manager singleton instance
      * The scopes whithin the solution are as follow
      *   Top root scope (Shared by all client side components)
      *   |--- ExtensibilityService
      *   |--- DateHelper
      *   |--- Client side component scope (i.e shared with all Web Part instances)
      *     |--- SPHttpClient
      *     |--- <other SPFx http services>
      *     |--- (Web Part Scope (created with startNewChild))
      *       |--- DynamicDataService
      *       |--- TemplateService
      *       |--- (Data Source scope)
      *         |--- SharePointSearchDataSource
      *         |--- TokenService, SearchService, etc.
    */ private 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 ( ) ; } /**
     * Set token values from Web Part property bag
     */ private async setTokens ( ) { if ( this . tokenService ) { let inputQueryFromDataSource = null ; try { inputQueryFromDataSource = DynamicPropertyHelper . tryGetValueSafe ( this . properties . queryText ) ; } catch ( error ) { } const inputQueryText = inputQueryFromDataSource ? inputQueryFromDataSource : this . properties . defaultQueryText ; this . tokenService . setTokenValue ( BuiltinTokenNames . inputQueryText , inputQueryText ) ; this . tokenService . setTokenValue ( BuiltinTokenNames . searchTerms , inputQueryText === undefined ? '' : inputQueryText ) ; if ( this . _filtersConnectionSourceData ) { const filtersSourceData = DynamicPropertyHelper . tryGetValueSafe ( this . _filtersConnectionSourceData ) ; if ( filtersSourceData ) { let filterTokens = null ; const allValues = flatten ( filtersSourceData . selectedFilters . map ( f => f . values ) ) ; if ( filtersSourceData . selectedFilters . length > 0 && ! isEmpty ( allValues ) ) { filterTokens = { } ; filtersSourceData . filterConfiguration . forEach ( filterConfiguration => { filterTokens [ filterConfiguration . filterName ] = null ; } ) ; filtersSourceData . selectedFilters . forEach ( filter => { const configuration = DataFilterHelper . getConfigurationForFilter ( filter , filtersSourceData . filterConfiguration ) ; if ( configuration ) { let filterTokenValue = null ; const filterValues = filter . values . map ( value => value . value ) . join ( ',' ) ; if ( filterValues . length > 0 ) { filterTokenValue = { valueAsText : filterValues } ; } if ( configuration . selectedTemplate === BuiltinFilterTemplates . DateRange ) { let fromDate = undefined ; let toDate = undefined ; filter . values . forEach ( filterValue => { if ( filterValue . operator === FilterComparisonOperator . Geq && ! fromDate ) { fromDate = filterValue . value ; } if ( filterValue . operator === FilterComparisonOperator . Lt && ! toDate ) { toDate = fromDate = filterValue . value ; } } ) ; filterTokenValue . fromDate = fromDate ; filterTokenValue . toDate = toDate ; } filterTokens [ filter . filterName ] = filterTokenValue ; } } ) ; } this . tokenService . setTokenValue ( BuiltinTokenNames . filters , filterTokens ) ; } } const destinationFieldName = this . properties . itemSelectionProps . destinationFieldName ; const itemFieldValues = DynamicPropertyHelper . tryGetValuesSafe ( this . properties . selectedItemFieldValue ) ; if ( destinationFieldName ) { let filterTokens = { [ destinationFieldName ] : { valueAsText : null , } as IDataFilterTokenValue } ; filterTokens [ destinationFieldName ] . valueAsText = itemFieldValues . join ( ',' ) ; this . tokenService . setTokenValue ( BuiltinTokenNames . filters , filterTokens ) ; } if ( this . _verticalsConnectionSourceData ) { const verticalSourceData = DynamicPropertyHelper . tryGetValueSafe ( this . _verticalsConnectionSourceData ) ; if ( verticalSourceData && verticalSourceData . selectedVertical ) { const resolvedSelectedVertical = { key : verticalSourceData . selectedVertical . key , name : verticalSourceData . selectedVertical . name , value : await this . tokenService . resolveTokens ( verticalSourceData . selectedVertical . value ) } ; this . tokenService . setTokenValue ( BuiltinTokenNames . verticals , resolvedSelectedVertical ) ; } } } } /**
     * Make sure the dynamic properties are correctly connected to the corresponding sources according to the proeprty pane settings
     */ private ensureDynamicDataSourcesConnection ( ) { if ( this . properties . filtersDataSourceReference ) { if ( ! this . _filtersConnectionSourceData ) { this . _filtersConnectionSourceData = new DynamicProperty < IDataFilterSourceData > ( this . context . dynamicDataProvider ) ; } this . _filtersConnectionSourceData . setReference ( this . properties . filtersDataSourceReference ) ; this . _filtersConnectionSourceData . register ( this . render ) ; } else { if ( this . _filtersConnectionSourceData ) { this . _filtersConnectionSourceData . unregister ( this . render ) ; } } if ( this . properties . verticalsDataSourceReference ) { if ( ! this . _verticalsConnectionSourceData ) { this . _verticalsConnectionSourceData = new DynamicProperty < IDataVerticalSourceData > ( this . context . dynamicDataProvider ) ; } this . _verticalsConnectionSourceData . setReference ( this . properties . verticalsDataSourceReference ) ; this . _verticalsConnectionSourceData . register ( this . render ) ; } else { if ( this . _verticalsConnectionSourceData ) { this . _verticalsConnectionSourceData . unregister ( this . render ) ; } } } param value private _validateEmptyField ( value ) { if ( ! value ) { return commonStrings . General . EmptyFieldErrorMessage ; } return '' ; } param value private _validateGuid ( value ) { if ( value . length > 0 ) { if ( ! ( /^(\{){0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}$/ ) . test ( value ) ) { return 'Invalid GUID' ; } } return '' ; } /**
     * Reset the paging information for PagingBehavior.Dynamic data sources
     */ private _resetPagingData ( ) { this . availablePageLinks = [ ] ; this . currentPageLinkUrl = null ; } /**
   * Get the data context to be passed to the data source according to current connections/configurations
   */ private getDataContext ( ) { const inputQueryText = this . _getInputQueryTextValue ( ) ; let dataContext = { pageNumber : this . currentPageNumber , itemsCountPerPage : this . properties . paging . itemsCountPerPage , paging : { nextLinkUrl : this . currentPageLinkUrl , pageLinks : this . availablePageLinks } , filters : { selectedFilters : [ ] , filtersConfiguration : [ ] , instanceId : undefined , filterOperator : undefined } , verticals : { selectedVertical : undefined } , inputQueryText : inputQueryText , queryStringParameters : UrlHelper . getQueryStringParams ( ) } ; const itemFieldValues = DynamicPropertyHelper . tryGetValuesSafe ( this . properties . selectedItemFieldValue ) ; if ( itemFieldValues && itemFieldValues . length > 0 && this . properties . itemSelectionProps . destinationFieldName ) { dataContext . selectedItemValues = itemFieldValues ; if ( this . properties . itemSelectionProps . selectionMode === ItemSelectionMode . AsDataFilter ) { const filterValues = uniq ( itemFieldValues ) . filter ( value => ! isEmpty ( value ) && typeof value === 'string' ) . map ( fieldValue => { return { name : fieldValue , value : fieldValue , operator : FilterComparisonOperator . Eq } ; } ) ; if ( filterValues . length > 0 ) { dataContext . filters . selectedFilters . push ( { filterName : this . properties . itemSelectionProps . destinationFieldName , values : filterValues , operator : this . properties . itemSelectionProps . valuesOperator } ) ; } } } if ( this . _filtersConnectionSourceData ) { const filtersSourceData = DynamicPropertyHelper . tryGetValueSafe ( this . _filtersConnectionSourceData ) ; if ( filtersSourceData ) { if ( ! isEqual ( filtersSourceData . selectedFilters , this . _lastSelectedFilters ) ) { dataContext . pageNumber = 1 ; } dataContext . filters . filtersConfiguration = filtersSourceData . filterConfiguration ; dataContext . filters . selectedFilters = dataContext . filters . selectedFilters . concat ( filtersSourceData . selectedFilters ) ; dataContext . filters . filterOperator = filtersSourceData . filterOperator ; dataContext . filters . instanceId = filtersSourceData . instanceId ; this . _lastSelectedFilters = dataContext . filters . selectedFilters ; } } if ( this . _verticalsConnectionSourceData ) { const verticalsSourceData = DynamicPropertyHelper . tryGetValueSafe ( this . _verticalsConnectionSourceData ) ; if ( verticalsSourceData ) { dataContext . verticals . selectedVertical = verticalsSourceData . selectedVertical ; } } if ( ! isEqual ( inputQueryText , this . _lastInputQueryText ) ) { dataContext . pageNumber = 1 ; this . currentPageNumber = 1 ; this . _resetPagingData ( ) ; } this . _lastInputQueryText = inputQueryText ; return dataContext ; } /**
     * Subscribes to URL hash change if the dynamic property is set to the default 'URL Fragment' property
     */ private _bindHashChange ( ) { if ( this . properties . queryText . tryGetSource ( ) && this . properties . queryText . reference . localeCompare ( 'PageContext:UrlData:fragment' ) === 0 ) { window . addEventListener ( 'hashchange' , this . render ) ; } else { window . removeEventListener ( 'hashchange' , this . render ) ; } } param availableFields param filters param pageNumber param nextLinkUrl param pageLinks private _onDataRetrieved ( availableDataSourceFields , filters ? , pageNumber ? , nextLinkUrl ? , pageLinks ? ) { this . _currentDataResultsSourceData . availableFieldsFromResults = availableDataSourceFields ; this . currentPageNumber = pageNumber ; this . availablePageLinks = pageLinks ; this . currentPageLinkUrl = nextLinkUrl ; if ( filters ) { this . _currentDataResultsSourceData . availablefilters = filters ; } if ( this . _verticalsConnectionSourceData && this . properties . selectedVerticalKeys . length > 0 ) { const verticalData = DynamicPropertyHelper . tryGetValueSafe ( this . _verticalsConnectionSourceData ) ; if ( verticalData && this . properties . selectedVerticalKeys . indexOf ( verticalData . selectedVertical . key ) === - 1 && this . displayMode === DisplayMode . Read ) { this . _currentDataResultsSourceData = { availableFieldsFromResults : [ ] , availablefilters : [ ] } ; } } if ( this . context && this . context . dynamicDataSourceManager && ! this . context . dynamicDataSourceManager . isDisposed ) { this . context . dynamicDataSourceManager . notifyPropertyChanged ( ComponentType . SearchResults ) ; } if ( this . context && this . context . propertyPane ) { this . context . propertyPane . refresh ( ) ; } } param currentSelectedItems private _onItemSelected ( currentSelectedItems ) { this . _currentDataResultsSourceData . selectedItems = cloneDeep ( currentSelectedItems ) ; this . context . dynamicDataSourceManager . notifyPropertyChanged ( DynamicDataProperties . AvailableFieldValuesFromResults ) ; } /**
     * Subscribes to URL query string change events using SharePoint page router
     */ private _handleQueryStringChange ( ) { if ( /^(PageContext:SearchData:searchQuery)|(PageContext:UrlData:queryParameters)/ . test ( this . properties . queryText . reference ) ) { ( ( h ) => { this . _pushStateCallback = history . pushState ; h . pushState = this . pushStateHandler . bind ( this ) ; } ) ( window . history ) ; } } private pushStateHandler ( state , key , path ) { this . _pushStateCallback . apply ( history , [ state , key , path ] ) ; if ( this . properties . queryText . isDisposed ) { return ; } const source = this . properties . queryText . tryGetSource ( ) ; if ( source && source . id === ComponentType . PageEnvironment ) { this . render ( ) ; } } }