@@ -7,26 +7,31 @@ import * as update from 'immutabi
77import {
88 GroupedList ,
99 IGroup ,
10- IGroupDividerProps
10+ IGroupDividerProps ,
11+ IGroupedList
1112} from 'office-ui-fabric-react/lib/components/GroupedList/index' ;
1213import { Scrollbars } from 'react-custom-scrollbars' ;
1314import { Link } from 'office-ui-fabric-react/lib/Link' ;
1415import { ActionButton } from 'office-ui-fabric-react/lib/Button' ;
1516import styles from './LinkPanel.module.scss' ;
1617import * as strings from 'SearchRefinersWebPartStrings' ;
1718import TemplateRenderer from '../../Templates/TemplateRenderer' ;
18- import { IRefinementResult } from '../../../../../models/ISearchResult' ;
19+ import { IRefinementResult , IRefinementValue } from '../../../../../models/ISearchResult' ;
1920import IRefinerConfiguration from '../../../../../models/IRefinerConfiguration' ;
21+ import IFilterLayoutProps from '../IFilterLayoutProps' ;
22+ import { isEqual } from '@microsoft/sp-lodash-subset' ;
2023
2124export default class LinkPanel extends React . Component < ILinkPanelProps , ILinkPanelState > {
2225
26+ private _groupedList : IGroupedList ;
27+
2328 public constructor ( props : ILinkPanelProps ) {
2429 super ( props ) ;
2530
2631 this . state = {
2732 showPanel : false ,
28- expandedGroups : [ ] ,
29- valueToRemove : null
33+ items : [ ] ,
34+ groups : [ ]
3035 } ;
3136
3237 this . _onTogglePanel = this . _onTogglePanel . bind ( this ) ;
@@ -39,58 +44,6 @@ export default class LinkPanel extends React.Component<ILinkPanelProps, ILinkPan
3944
4045 public render ( ) : React . ReactElement < ILinkPanelProps > {
4146
42- let items : JSX . Element [ ] = [ ] ;
43- let groups : IGroup [ ] = [ ] ;
44-
45- if ( this . props . refinementResults . length === 0 ) return < span /> ;
46-
47- // Initialize the Office UI grouped list
48- this . props . refinementResults . map ( ( refinementResult , i ) => {
49-
50- // Get group name
51- let groupName = refinementResult . FilterName ;
52- const configuredFilter = this . props . refinersConfiguration . filter ( e => { return e . refinerName === refinementResult . FilterName ; } ) ;
53- groupName = configuredFilter . length > 0 && configuredFilter [ 0 ] . displayValue ? configuredFilter [ 0 ] . displayValue : groupName ;
54-
55- groups . push ( {
56- key : i . toString ( ) ,
57- name : groupName ,
58- count : 1 ,
59- startIndex : i ,
60- isDropEnabled : true ,
61- isCollapsed : this . state . expandedGroups . indexOf ( groupName ) === - 1 ? true : false
62- } ) ;
63-
64- // Get selected values for this specfic refiner
65- // 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
66- // In this case we use the refiners global state to recreate the 'local' state for this component
67- const selectedFilter = this . props . selectedFilters . filter ( filter => { return filter . FilterName === refinementResult . FilterName ; } ) ;
68- const selectedFilterValues = selectedFilter . length === 1 ? selectedFilter [ 0 ] . Values : [ ] ;
69-
70- // Check if the value to remove concerns this refinement result
71- let valueToRemove = null ;
72- if ( this . state . valueToRemove ) {
73- if ( refinementResult . Values . filter ( value => {
74- return value . RefinementToken === this . state . valueToRemove . RefinementToken || refinementResult . FilterName === this . state . valueToRemove . RefinementName ; } ) . length > 0
75- ) {
76- valueToRemove = this . state . valueToRemove ;
77- }
78- }
79-
80- items . push (
81- < TemplateRenderer
82- key = { i }
83- refinementResult = { refinementResult }
84- shouldResetFilters = { this . props . shouldResetFilters }
85- templateType = { configuredFilter [ 0 ] . template }
86- valueToRemove = { valueToRemove }
87- onFilterValuesUpdated = { this . props . onFilterValuesUpdated }
88- language = { this . props . language }
89- selectedValues = { selectedFilterValues }
90- />
91- ) ;
92- } ) ;
93-
9447 const renderSelectedFilterValues : JSX . Element [ ] = this . props . selectedFilterValues . map ( ( value ) => {
9548
9649 // Get the 'display name' of the associated refiner for this value
@@ -105,25 +58,25 @@ export default class LinkPanel extends React.Component<ILinkPanelProps, ILinkPan
10558 return (
10659 < Label className = { styles . filter } >
10760 < i className = 'ms-Icon ms-Icon--ClearFilter' onClick = { ( ) => {
108- this . setState ( {
109- valueToRemove : value
110- } ) ;
61+ this . _initItems ( this . props , value ) ;
62+ this . _groupedList . forceUpdate ( ) ;
11163 } } > </ i >
11264 { filterName }
11365 </ Label > ) ;
11466 } ) ;
11567
11668 const renderAvailableFilters = < GroupedList
11769 ref = 'groupedList'
118- items = { items }
70+ componentRef = { ( g ) => { this . _groupedList = g ; } }
71+ items = { this . state . items }
11972 onRenderCell = { this . _onRenderCell }
12073 className = { styles . linkPanelLayout__filterPanel__body__group }
12174 groupProps = {
12275 {
12376 onRenderHeader : this . _onRenderHeader
12477 }
12578 }
126- groups = { groups } /> ;
79+ groups = { this . state . groups } /> ;
12780
12881 const renderLinkRemoveAll = this . props . hasSelectedValues ?
12982 ( < div className = { `${ styles . linkPanelLayout__filterPanel__body__removeAllFilters } ${ this . props . hasSelectedValues && "hiddenLink" } ` } >
@@ -180,15 +133,22 @@ export default class LinkPanel extends React.Component<ILinkPanelProps, ILinkPan
180133 }
181134
182135 public componentDidMount ( ) {
183- this . _initExpandedGroups ( this . props . refinementResults , this . props . refinersConfiguration ) ;
136+ this . _initGroups ( this . props ) ;
137+ this . _initItems ( this . props ) ;
184138 }
185139
186140 public componentWillReceiveProps ( nextProps : ILinkPanelProps ) {
187- this . setState ( {
188- valueToRemove : null
189- } ) ;
141+ let shouldReset = false ;
142+
143+ if ( ! isEqual ( this . props . refinersConfiguration , nextProps . refinersConfiguration ) ) {
144+ shouldReset = true ;
145+ }
146+
147+ this . _initGroups ( nextProps , shouldReset ) ;
148+ this . _initItems ( nextProps ) ;
190149
191- this . _initExpandedGroups ( nextProps . refinementResults , nextProps . refinersConfiguration ) ;
150+ // Need to force an update manually because nor items or groups update will be considered as an update by the GroupedList component.
151+ this . _groupedList . forceUpdate ( ) ;
192152 }
193153
194154 private _onRenderCell ( nestingDepth : number , item : any , itemIndex : number ) {
@@ -205,16 +165,7 @@ export default class LinkPanel extends React.Component<ILinkPanelProps, ILinkPan
205165 < div className = { styles . linkPanelLayout__filterPanel__body__group__header }
206166 style = { props . groupIndex > 0 ? { marginTop : '10px' } : undefined }
207167 onClick = { ( ) => {
208-
209- // Update the index for expanded groups to be able to keep it open after a re-render
210- const updatedExpandedGroups =
211- props . group . isCollapsed ?
212- update ( this . state . expandedGroups , { $push : [ props . group . name ] } ) :
213- update ( this . state . expandedGroups , { $splice : [ [ this . state . expandedGroups . indexOf ( props . group . name ) , 1 ] ] } ) ;
214-
215- this . setState ( {
216- expandedGroups : updatedExpandedGroups ,
217- } ) ;
168+ props . onToggleCollapse ( props . group ) ;
218169 } } >
219170 < div className = { styles . linkPanelLayout__filterPanel__body__headerIcon } >
220171 < i className = { props . group . isCollapsed ? 'ms-Icon ms-Icon--ChevronDown' : 'ms-Icon ms-Icon--ChevronUp' } > </ i >
@@ -241,21 +192,86 @@ export default class LinkPanel extends React.Component<ILinkPanelProps, ILinkPan
241192 * @param refinementResults the refinements results
242193 * @param refinersConfiguration the current refiners configuration
243194 */
244- private _initExpandedGroups ( refinementResults : IRefinementResult [ ] , refinersConfiguration : IRefinerConfiguration [ ] ) {
195+ private _initGroups ( props : IFilterLayoutProps , shouldResetCollapse ?: boolean ) {
245196
246- refinementResults . map ( ( refinementResult , i ) => {
197+ let groups : IGroup [ ] = [ ] ;
198+ props . refinementResults . map ( ( refinementResult , i ) => {
247199
248200 // Get group name
249201 let groupName = refinementResult . FilterName ;
250- const configuredFilter = refinersConfiguration . filter ( e => { return e . refinerName === refinementResult . FilterName ; } ) ;
251- groupName = configuredFilter . length > 0 && configuredFilter [ 0 ] . displayValue ? configuredFilter [ 0 ] . displayValue : groupName ;
252- const showExpanded = configuredFilter . length > 0 && configuredFilter [ 0 ] . showExpanded ? configuredFilter [ 0 ] . showExpanded : false ;
253-
254- if ( showExpanded ) {
255- this . setState ( {
256- expandedGroups : update ( this . state . expandedGroups , { $push : [ groupName ] } )
257- } ) ;
202+ const configuredFilters = props . refinersConfiguration . filter ( e => { return e . refinerName === refinementResult . FilterName ; } ) ;
203+ groupName = configuredFilters . length > 0 && configuredFilters [ 0 ] . displayValue ? configuredFilters [ 0 ] . displayValue : groupName ;
204+ let isCollapsed = true ;
205+
206+ const existingGroups = this . state . groups . filter ( g => { return g . name === groupName ; } ) ;
207+
208+ if ( existingGroups . length > 0 && ! shouldResetCollapse ) {
209+ isCollapsed = existingGroups [ 0 ] . isCollapsed ;
210+ } else {
211+ isCollapsed = configuredFilters . length > 0 && configuredFilters [ 0 ] . showExpanded ? ! configuredFilters [ 0 ] . showExpanded : true ;
258212 }
213+
214+ let group : IGroup = {
215+ key : i . toString ( ) ,
216+ name : groupName ,
217+ count : 1 ,
218+ startIndex : i ,
219+ isCollapsed : isCollapsed
220+ } ;
221+
222+ groups . push ( group ) ;
223+ } ) ;
224+
225+ this . setState ( {
226+ groups : update ( this . state . groups , { $set : groups } )
227+ } ) ;
228+ }
229+
230+ /**
231+ * Initializes items in for goups in the GroupedList
232+ * @param refinementResults the refinements results
233+ */
234+ private _initItems ( props : IFilterLayoutProps , refinementValueToRemove ?: IRefinementValue ) : void {
235+
236+ let items : JSX . Element [ ] = [ ] ;
237+
238+ // Initialize the Office UI grouped list
239+ props . refinementResults . map ( ( refinementResult , i ) => {
240+
241+ const configuredFilter = props . refinersConfiguration . filter ( e => { return e . refinerName === refinementResult . FilterName ; } ) ;
242+
243+ // Get selected values for this specfic refiner
244+ // 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
245+ // In this case we use the refiners global state to recreate the 'local' state for this component
246+ const selectedFilter = props . selectedFilters . filter ( filter => { return filter . FilterName === refinementResult . FilterName ; } ) ;
247+ const selectedFilterValues = selectedFilter . length === 1 ? selectedFilter [ 0 ] . Values : [ ] ;
248+
249+ // Check if the value to remove concerns this refinement result
250+ let valueToRemove = null ;
251+ if ( refinementValueToRemove ) {
252+ if ( refinementResult . Values . filter ( value => {
253+ return value . RefinementToken === refinementValueToRemove . RefinementToken || refinementResult . FilterName === refinementValueToRemove . RefinementName ; } ) . length > 0
254+ ) {
255+ valueToRemove = refinementValueToRemove ;
256+ }
257+ }
258+
259+ items . push (
260+ < TemplateRenderer
261+ key = { i }
262+ refinementResult = { refinementResult }
263+ shouldResetFilters = { props . shouldResetFilters }
264+ templateType = { configuredFilter [ 0 ] . template }
265+ valueToRemove = { valueToRemove }
266+ onFilterValuesUpdated = { props . onFilterValuesUpdated }
267+ language = { props . language }
268+ selectedValues = { selectedFilterValues }
269+ />
270+ ) ;
271+ } ) ;
272+
273+ this . setState ( {
274+ items : update ( this . state . items , { $set : items } )
259275 } ) ;
260276 }
261277}
0 commit comments