diff --git a/solutions/ModernSearch/react-search-refiners/spfx/src/webparts/searchRefiners/components/Layouts/LinkPanel/ILinkPanelState.ts b/solutions/ModernSearch/react-search-refiners/spfx/src/webparts/searchRefiners/components/Layouts/LinkPanel/ILinkPanelState.ts index 09560011..4ac4ea08 100644 --- a/solutions/ModernSearch/react-search-refiners/spfx/src/webparts/searchRefiners/components/Layouts/LinkPanel/ILinkPanelState.ts +++ b/solutions/ModernSearch/react-search-refiners/spfx/src/webparts/searchRefiners/components/Layouts/LinkPanel/ILinkPanelState.ts @@ -1,9 +1,10 @@ import { IRefinementValue } from "../../../../../models/ISearchResult"; +import { IGroup } from "office-ui-fabric-react/lib/components/GroupedList"; interface ILinkPanelState { showPanel?: boolean; - valueToRemove: IRefinementValue; - expandedGroups?: string[]; + groups?: IGroup[]; + items?: JSX.Element[]; } export default ILinkPanelState; \ No newline at end of file diff --git a/solutions/ModernSearch/react-search-refiners/spfx/src/webparts/searchRefiners/components/Layouts/LinkPanel/LinkPanel.tsx b/solutions/ModernSearch/react-search-refiners/spfx/src/webparts/searchRefiners/components/Layouts/LinkPanel/LinkPanel.tsx index 47d1cbf4..5fddda1e 100644 --- a/solutions/ModernSearch/react-search-refiners/spfx/src/webparts/searchRefiners/components/Layouts/LinkPanel/LinkPanel.tsx +++ b/solutions/ModernSearch/react-search-refiners/spfx/src/webparts/searchRefiners/components/Layouts/LinkPanel/LinkPanel.tsx @@ -7,7 +7,8 @@ import * as update from 'immutabi import { GroupedList, IGroup, - IGroupDividerProps + IGroupDividerProps, + IGroupedList } from 'office-ui-fabric-react/lib/components/GroupedList/index'; import { Scrollbars } from 'react-custom-scrollbars'; import {Link} from 'office-ui-fabric-react/lib/Link'; @@ -15,18 +16,22 @@ import {ActionButton} from 'office-ui-fabric-react/lib/Button'; import styles from './LinkPanel.module.scss'; import * as strings from 'SearchRefinersWebPartStrings'; import TemplateRenderer from '../../Templates/TemplateRenderer'; -import { IRefinementResult } from '../../../../../models/ISearchResult'; +import { IRefinementResult, IRefinementValue } from '../../../../../models/ISearchResult'; import IRefinerConfiguration from '../../../../../models/IRefinerConfiguration'; +import IFilterLayoutProps from '../IFilterLayoutProps'; +import { isEqual } from '@microsoft/sp-lodash-subset'; export default class LinkPanel extends React.Component { + private _groupedList: IGroupedList; + public constructor(props: ILinkPanelProps) { super(props); this.state = { showPanel: false, - expandedGroups: [], - valueToRemove: null + items: [], + groups: [] }; this._onTogglePanel = this._onTogglePanel.bind(this); @@ -39,58 +44,6 @@ export default class LinkPanel extends React.Component { - let items: JSX.Element[] = []; - let groups: IGroup[] = []; - - if (this.props.refinementResults.length === 0) return ; - - // Initialize the Office UI grouped list - this.props.refinementResults.map((refinementResult, i) => { - - // Get group name - let groupName = refinementResult.FilterName; - const configuredFilter = this.props.refinersConfiguration.filter(e => { return e.refinerName === refinementResult.FilterName;}); - groupName = configuredFilter.length > 0 && configuredFilter[0].displayValue ? configuredFilter[0].displayValue : groupName; - - groups.push({ - key: i.toString(), - name: groupName, - count: 1, - startIndex: i, - isDropEnabled: true, - isCollapsed: this.state.expandedGroups.indexOf(groupName) === -1 ? true : false - }); - - // Get selected values for this specfic refiner - // This scenario happens due to the behavior of the Office UI Fabric GroupedList component who recreates child components when a greoup is collapsed/expanded, causing a state reset for sub components - // In this case we use the refiners global state to recreate the 'local' state for this component - const selectedFilter = this.props.selectedFilters.filter(filter => { return filter.FilterName === refinementResult.FilterName;}); - const selectedFilterValues = selectedFilter.length === 1 ? selectedFilter[0].Values : []; - - // Check if the value to remove concerns this refinement result - let valueToRemove = null; - if (this.state.valueToRemove) { - if (refinementResult.Values.filter(value => { - return value.RefinementToken === this.state.valueToRemove.RefinementToken || refinementResult.FilterName === this.state.valueToRemove.RefinementName; }).length > 0 - ) { - valueToRemove = this.state.valueToRemove; - } - } - - items.push( - - ); - }); - const renderSelectedFilterValues: JSX.Element[] = this.props.selectedFilterValues.map((value) => { // Get the 'display name' of the associated refiner for this value @@ -105,9 +58,8 @@ export default class LinkPanel extends React.Component { - this.setState({ - valueToRemove: value - }); + this._initItems(this.props, value); + this._groupedList.forceUpdate(); }}> {filterName} ); @@ -115,7 +67,8 @@ export default class LinkPanel extends React.Component { this._groupedList = g; }} + items={this.state.items} onRenderCell={this._onRenderCell} className={styles.linkPanelLayout__filterPanel__body__group} groupProps={ @@ -123,7 +76,7 @@ export default class LinkPanel extends React.Component; + groups={this.state.groups} />; const renderLinkRemoveAll = this.props.hasSelectedValues ? (
@@ -180,15 +133,22 @@ export default class LinkPanel extends React.Component 0 ? { marginTop: '10px' } : undefined } onClick={() => { - - // Update the index for expanded groups to be able to keep it open after a re-render - const updatedExpandedGroups = - props.group.isCollapsed ? - update(this.state.expandedGroups, { $push: [props.group.name] }) : - update(this.state.expandedGroups, { $splice: [[this.state.expandedGroups.indexOf(props.group.name), 1]] }); - - this.setState({ - expandedGroups: updatedExpandedGroups, - }); + props.onToggleCollapse(props.group); }}>
@@ -241,21 +192,86 @@ export default class LinkPanel extends React.Component { + let groups: IGroup[] = []; + props.refinementResults.map((refinementResult, i) => { // Get group name let groupName = refinementResult.FilterName; - const configuredFilter = refinersConfiguration.filter(e => { return e.refinerName === refinementResult.FilterName;}); - groupName = configuredFilter.length > 0 && configuredFilter[0].displayValue ? configuredFilter[0].displayValue : groupName; - const showExpanded = configuredFilter.length > 0 && configuredFilter[0].showExpanded ? configuredFilter[0].showExpanded : false; - - if (showExpanded) { - this.setState({ - expandedGroups: update(this.state.expandedGroups, { $push: [groupName] }) - }); + const configuredFilters = props.refinersConfiguration.filter(e => { return e.refinerName === refinementResult.FilterName;}); + groupName = configuredFilters.length > 0 && configuredFilters[0].displayValue ? configuredFilters[0].displayValue : groupName; + let isCollapsed = true; + + const existingGroups = this.state.groups.filter(g => { return g.name === groupName;}); + + if (existingGroups.length > 0 && !shouldResetCollapse) { + isCollapsed = existingGroups[0].isCollapsed; + } else { + isCollapsed = configuredFilters.length > 0 && configuredFilters[0].showExpanded ? !configuredFilters[0].showExpanded : true; } + + let group: IGroup = { + key: i.toString(), + name: groupName, + count: 1, + startIndex: i, + isCollapsed: isCollapsed + }; + + groups.push(group); + }); + + this.setState({ + groups: update(this.state.groups, { $set: groups }) + }); + } + + /** + * Initializes items in for goups in the GroupedList + * @param refinementResults the refinements results + */ + private _initItems(props: IFilterLayoutProps, refinementValueToRemove?: IRefinementValue): void { + + let items: JSX.Element[] = []; + + // Initialize the Office UI grouped list + props.refinementResults.map((refinementResult, i) => { + + const configuredFilter = props.refinersConfiguration.filter(e => { return e.refinerName === refinementResult.FilterName; }); + + // Get selected values for this specfic refiner + // This scenario happens due to the behavior of the Office UI Fabric GroupedList component who recreates child components when a greoup is collapsed/expanded, causing a state reset for sub components + // In this case we use the refiners global state to recreate the 'local' state for this component + const selectedFilter = props.selectedFilters.filter(filter => { return filter.FilterName === refinementResult.FilterName; }); + const selectedFilterValues = selectedFilter.length === 1 ? selectedFilter[0].Values : []; + + // Check if the value to remove concerns this refinement result + let valueToRemove = null; + if (refinementValueToRemove) { + if (refinementResult.Values.filter(value => { + return value.RefinementToken ===refinementValueToRemove.RefinementToken || refinementResult.FilterName === refinementValueToRemove.RefinementName; }).length > 0 + ) { + valueToRemove = refinementValueToRemove; + } + } + + items.push( + + ); + }); + + this.setState({ + items: update(this.state.items, { $set: items }) }); } } \ No newline at end of file diff --git a/solutions/ModernSearch/react-search-refiners/spfx/src/webparts/searchRefiners/components/Layouts/Vertical/IVerticalState.tsx b/solutions/ModernSearch/react-search-refiners/spfx/src/webparts/searchRefiners/components/Layouts/Vertical/IVerticalState.tsx index 973985eb..9a408aef 100644 --- a/solutions/ModernSearch/react-search-refiners/spfx/src/webparts/searchRefiners/components/Layouts/Vertical/IVerticalState.tsx +++ b/solutions/ModernSearch/react-search-refiners/spfx/src/webparts/searchRefiners/components/Layouts/Vertical/IVerticalState.tsx @@ -1,5 +1,8 @@ +import { IGroup } from "office-ui-fabric-react/lib/components/GroupedList"; + interface IVerticalState { - expandedGroups?: string[]; + groups?: IGroup[]; + items?: JSX.Element[]; } export default IVerticalState; \ No newline at end of file diff --git a/solutions/ModernSearch/react-search-refiners/spfx/src/webparts/searchRefiners/components/Layouts/Vertical/Vertical.tsx b/solutions/ModernSearch/react-search-refiners/spfx/src/webparts/searchRefiners/components/Layouts/Vertical/Vertical.tsx index def79cd6..6fec46a9 100644 --- a/solutions/ModernSearch/react-search-refiners/spfx/src/webparts/searchRefiners/components/Layouts/Vertical/Vertical.tsx +++ b/solutions/ModernSearch/react-search-refiners/spfx/src/webparts/searchRefiners/components/Layouts/Vertical/Vertical.tsx @@ -5,22 +5,25 @@ import * as update from 'immutability-helper'; import { GroupedList, IGroup, - IGroupDividerProps + IGroupDividerProps, + IGroupedList } from 'office-ui-fabric-react/lib/components/GroupedList/index'; import {Link} from 'office-ui-fabric-react/lib/Link'; import styles from './Vertical.module.scss'; import * as strings from 'SearchRefinersWebPartStrings'; import TemplateRenderer from '../../Templates/TemplateRenderer'; -import { IRefinementResult } from '../../../../../models/ISearchResult'; -import IRefinerConfiguration from '../../../../../models/IRefinerConfiguration'; +import { isEqual } from '@microsoft/sp-lodash-subset'; export default class Vertical extends React.Component { + private _groupedList: IGroupedList; + public constructor(props: IFilterLayoutProps) { super(props); this.state = { - expandedGroups: [] + items: [], + groups: [] }; this._removeAllFilters = this._removeAllFilters.bind(this); @@ -30,49 +33,12 @@ export default class Vertical extends React.Component { - let items: JSX.Element[] = []; - let groups: IGroup[] = []; let noResultsElement: JSX.Element; - // Initialize the Office UI grouped list - this.props.refinementResults.map((refinementResult, i) => { - - // Get group name - let groupName = refinementResult.FilterName; - const configuredFilter = this.props.refinersConfiguration.filter(e => { return e.refinerName === refinementResult.FilterName; }); - groupName = configuredFilter.length > 0 && configuredFilter[0].displayValue ? configuredFilter[0].displayValue : groupName; - - groups.push({ - key: i.toString(), - name: groupName, - count: 1, - startIndex: i, - isDropEnabled: true, - isCollapsed: this.state.expandedGroups.indexOf(groupName) === -1 ? true : false - }); - - // Get selected values for this specfic refiner - // This scenario happens due to the behavior of the Office UI Fabric GroupedList component who recreates child components when a greoup is collapsed/expanded, causing a state reset for sub components - // In this case we use the refiners global state to recreate the 'local' state for this component - const selectedFilter = this.props.selectedFilters.filter(filter => { return filter.FilterName === refinementResult.FilterName; }); - const selectedFilterValues = selectedFilter.length === 1 ? selectedFilter[0].Values : []; - - items.push( - - ); - }); - const renderAvailableFilters = (this.props.refinementResults.length > 0) ? { this._groupedList = g; }} onRenderCell={this._onRenderCell} className={styles.verticalLayout__filterPanel__body__group} groupProps={ @@ -80,7 +46,7 @@ export default class Vertical extends React.Component : noResultsElement; + groups={this.state.groups} /> : noResultsElement; const renderLinkRemoveAll = this.props.hasSelectedValues ? (
@@ -98,11 +64,23 @@ export default class Vertical extends React.Component 0 ? { marginTop: '10px' } : undefined} onClick={() => { - - // Update the index for expanded groups to be able to keep it open after a re-render - const updatedExpandedGroups = - props.group.isCollapsed ? - update(this.state.expandedGroups, { $push: [props.group.name] }) : - update(this.state.expandedGroups, { $splice: [[this.state.expandedGroups.indexOf(props.group.name), 1]] }); - - this.setState({ - expandedGroups: updatedExpandedGroups, - }); - props.onToggleCollapse(props.group); }}>
@@ -149,21 +116,75 @@ export default class Vertical extends React.Component { + let groups: IGroup[] = []; + props.refinementResults.map((refinementResult, i) => { // Get group name let groupName = refinementResult.FilterName; - const configuredFilter = refinersConfiguration.filter(e => { return e.refinerName === refinementResult.FilterName;}); - groupName = configuredFilter.length > 0 && configuredFilter[0].displayValue ? configuredFilter[0].displayValue : groupName; - const showExpanded = configuredFilter.length > 0 && configuredFilter[0].showExpanded ? configuredFilter[0].showExpanded : false; - - if (showExpanded) { - this.setState({ - expandedGroups: update(this.state.expandedGroups, { $push: [groupName] }) - }); + const configuredFilters = props.refinersConfiguration.filter(e => { return e.refinerName === refinementResult.FilterName;}); + groupName = configuredFilters.length > 0 && configuredFilters[0].displayValue ? configuredFilters[0].displayValue : groupName; + let isCollapsed = true; + + const existingGroups = this.state.groups.filter(g => { return g.name === groupName;}); + + if (existingGroups.length > 0 && !shouldResetCollapse) { + isCollapsed = existingGroups[0].isCollapsed; + } else { + isCollapsed = configuredFilters.length > 0 && configuredFilters[0].showExpanded ? !configuredFilters[0].showExpanded : true; } + + let group: IGroup = { + key: i.toString(), + name: groupName, + count: 1, + startIndex: i, + isCollapsed: isCollapsed + }; + + groups.push(group); + }); + + this.setState({ + groups: update(this.state.groups, { $set: groups }) + }); + } + + /** + * Initializes items in for goups in the GroupedList + * @param refinementResults the refinements results + */ + private _initItems(props: IFilterLayoutProps): void { + + let items: JSX.Element[] = []; + + // Initialize the Office UI grouped list + props.refinementResults.map((refinementResult, i) => { + + const configuredFilter = props.refinersConfiguration.filter(e => { return e.refinerName === refinementResult.FilterName; }); + + // Get selected values for this specfic refiner + // This scenario happens due to the behavior of the Office UI Fabric GroupedList component who recreates child components when a greoup is collapsed/expanded, causing a state reset for sub components + // In this case we use the refiners global state to recreate the 'local' state for this component + const selectedFilter = props.selectedFilters.filter(filter => { return filter.FilterName === refinementResult.FilterName; }); + const selectedFilterValues = selectedFilter.length === 1 ? selectedFilter[0].Values : []; + + items.push( + + ); + }); + + this.setState({ + items: update(this.state.items, { $set: items }) }); } } \ No newline at end of file diff --git a/solutions/ModernSearch/react-search-refiners/spfx/src/webparts/searchResults/SearchResultsWebPart.ts b/solutions/ModernSearch/react-search-refiners/spfx/src/webparts/searchResults/SearchResultsWebPart.ts index f6d3f6d3..b9604acd 100644 --- a/solutions/ModernSearch/react-search-refiners/spfx/src/webparts/searchResults/SearchResultsWebPart.ts +++ b/solutions/ModernSearch/react-search-refiners/spfx/src/webparts/searchResults/SearchResultsWebPart.ts @@ -27,7 +27,7 @@ import ISearchService from '../../services/SearchService/ISearchService'; import ITaxonomyService from '../../services/TaxonomyService/ITaxonomyService'; import ResultsLayoutOption from '../../models/ResultsLayoutOption'; import TemplateService from '../../services/TemplateService/TemplateService'; -import { isEmpty, find } from '@microsoft/sp-lodash-subset'; +import { isEmpty, find, sortBy } from '@microsoft/sp-lodash-subset'; import MockSearchService from '../../services/SearchService/MockSearchService'; import MockTemplateService from '../../services/TemplateService/MockTemplateService'; import SearchService from '../../services/SearchService/SearchService'; @@ -128,7 +128,7 @@ export default class SearchResultsWebPart extends BaseClientSideWebPart