[react-search-refiners] Miscellaneous updates (#695)
* * Migrated to SPFx 1.7.0 * Fixed sort feature * Added a sample TypeScript function to demonstrate NLP processing for the search query * Miscelleanous improvements * * Fixed wrong ids and dependencies * * Updated README * * Replaced JSOM taxonomy methods by the @pnp/sp-taxonomy counterparts + refactored refiners translation logic * Updated the filter panel to close on click out * Updgraded to @pnp 1.2.6 * Added a event listeners for hash change when the search box in bound to the 'URL fragment' SPFx builtin data source property so you can now build predefined filters with '#'. * Fix suggestions panel position to be absolute * * Quick fix on the search box * * Added a default query option (related to https://github.com/SharePoint/sp-dev-fx-webparts/issues/556) * * Added the ability to search by clicking on the search box icon
This commit is contained in:
parent
0265e4c7e6
commit
398d3b0363
|
@ -44,7 +44,5 @@ build.configureWebpack.mergeConfig({
|
||||||
return generatedConfiguration;
|
return generatedConfiguration;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
build.webpack.buildConfig
|
|
||||||
build.addSuppression(new RegExp("\[sass\]",'g'));
|
|
||||||
|
|
||||||
build.initialize(gulp);
|
build.initialize(gulp);
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -19,11 +19,13 @@
|
||||||
"@microsoft/sp-lodash-subset": "1.7.0",
|
"@microsoft/sp-lodash-subset": "1.7.0",
|
||||||
"@microsoft/sp-office-ui-fabric-core": "1.7.0",
|
"@microsoft/sp-office-ui-fabric-core": "1.7.0",
|
||||||
"@microsoft/sp-webpart-base": "1.7.0",
|
"@microsoft/sp-webpart-base": "1.7.0",
|
||||||
"@pnp/common": "1.2.5",
|
"@pnp/common": "1.2.6",
|
||||||
"@pnp/logging": "1.2.5",
|
"@pnp/logging": "1.2.6",
|
||||||
"@pnp/odata": "1.2.5",
|
"@pnp/odata": "1.2.6",
|
||||||
"@pnp/polyfill-ie11": "1.0.0",
|
"@pnp/polyfill-ie11": "1.0.0",
|
||||||
"@pnp/sp": "1.2.5",
|
"@pnp/sp": "1.2.6",
|
||||||
|
"@pnp/sp-taxonomy": "1.2.6",
|
||||||
|
"@pnp/sp-clientsvc": "1.2.6",
|
||||||
"@pnp/spfx-controls-react": "1.10.0",
|
"@pnp/spfx-controls-react": "1.10.0",
|
||||||
"@pnp/spfx-property-controls": "1.12.0",
|
"@pnp/spfx-property-controls": "1.12.0",
|
||||||
"@types/es6-promise": "0.0.33",
|
"@types/es6-promise": "0.0.33",
|
||||||
|
@ -43,7 +45,7 @@
|
||||||
"on-el-resize": "0.0.4",
|
"on-el-resize": "0.0.4",
|
||||||
"react": "16.3.2",
|
"react": "16.3.2",
|
||||||
"react-ace": "6.1.4",
|
"react-ace": "6.1.4",
|
||||||
"react-custom-scrollbars": "4.1.2",
|
"react-custom-scrollbars": "4.2.1",
|
||||||
"react-dom": "16.3.2",
|
"react-dom": "16.3.2",
|
||||||
"react-js-pagination": "3.0.0",
|
"react-js-pagination": "3.0.0",
|
||||||
"video.js": "^7.3.0"
|
"video.js": "^7.3.0"
|
||||||
|
|
|
@ -161,8 +161,9 @@ class SearchService implements ISearchService {
|
||||||
const resultRows = r2.RawSearchResults.PrimaryQueryResult.RelevantResults.Table.Rows;
|
const resultRows = r2.RawSearchResults.PrimaryQueryResult.RelevantResults.Table.Rows;
|
||||||
let refinementResultsRows = r2.RawSearchResults.PrimaryQueryResult.RefinementResults;
|
let refinementResultsRows = r2.RawSearchResults.PrimaryQueryResult.RefinementResults;
|
||||||
|
|
||||||
const refinementRows = refinementResultsRows ? refinementResultsRows['Refiners'] : [];
|
const refinementRows: any = refinementResultsRows ? refinementResultsRows.Refiners : [];
|
||||||
if (refinementRows.length > 0) {
|
if (refinementRows.length > 0) {
|
||||||
|
|
||||||
const component = await import(
|
const component = await import(
|
||||||
/* webpackChunkName: 'search-handlebars-helpers' */
|
/* webpackChunkName: 'search-handlebars-helpers' */
|
||||||
'handlebars-helpers'
|
'handlebars-helpers'
|
||||||
|
|
|
@ -1,18 +1,13 @@
|
||||||
interface ITaxonomyService {
|
import { ITerm } from "@pnp/sp-taxonomy";
|
||||||
|
|
||||||
/**
|
interface ITaxonomyService {
|
||||||
* Ensure all script dependencies are loaded before using the taxonomy SharePoint CSOM functions
|
|
||||||
* https://dev.office.com/sharepoint/docs/spfx/web-parts/guidance/connect-to-sharepoint-using-jsom
|
|
||||||
* @return {Promise<void>} A promise allowing you to execute your code logic.
|
|
||||||
*/
|
|
||||||
initialize();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets multiple terms by their ids using the current taxonomy context
|
* Gets multiple terms by their ids using the current taxonomy context
|
||||||
* @param termIds An array of term ids to search for
|
* @param termIds An array of term ids to search for
|
||||||
* @return {Promise<SP.Taxonomy.TermCollection>} A promise containing the terms.
|
* @return {Promise<ITerm[]>} A promise containing the terms.
|
||||||
*/
|
*/
|
||||||
getTermsById(termIds: string[]): Promise<SP.Taxonomy.TermCollection>;
|
getTermsById(termIds: string[]): Promise<ITerm[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ITaxonomyService;
|
export default ITaxonomyService;
|
|
@ -1,5 +1,6 @@
|
||||||
|
|
||||||
import ITaxonomyService from './ITaxonomyService';
|
import ITaxonomyService from './ITaxonomyService';
|
||||||
|
import { ITerm } from '@pnp/sp-taxonomy';
|
||||||
|
|
||||||
class MockTaxonomyService implements ITaxonomyService {
|
class MockTaxonomyService implements ITaxonomyService {
|
||||||
|
|
||||||
|
@ -11,7 +12,7 @@ class MockTaxonomyService implements ITaxonomyService {
|
||||||
return p1;
|
return p1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getTermsById(termIds: string[]): Promise<SP.Taxonomy.TermCollection> {
|
public getTermsById(termIds: string[]): Promise<ITerm[]> {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,129 +1,38 @@
|
||||||
import { IWebPartContext } from '@microsoft/sp-webpart-base';
|
|
||||||
import { Logger, LogLevel } from '@pnp/logging';
|
|
||||||
import { SPComponentLoader } from '@microsoft/sp-loader';
|
|
||||||
import ITaxonomyService from './ITaxonomyService';
|
import ITaxonomyService from './ITaxonomyService';
|
||||||
import { Text } from '@microsoft/sp-core-library';
|
import { ITermStore, ITerms, ITermData, Session, ITerm } from "@pnp/sp-taxonomy";
|
||||||
|
|
||||||
class TaxonomyService implements ITaxonomyService {
|
class TaxonomyService implements ITaxonomyService {
|
||||||
|
|
||||||
private _workingLanguageLcid: number;
|
private _siteUrl: string;
|
||||||
private _context: IWebPartContext;
|
|
||||||
|
|
||||||
public constructor(webPartContext: IWebPartContext, workingLanguage?: number){
|
public constructor(siteUrl: string){
|
||||||
this._context = webPartContext;
|
this._siteUrl = siteUrl;
|
||||||
this._workingLanguageLcid = workingLanguage ? workingLanguage : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensure all script dependencies are loaded before using the taxonomy SharePoint CSOM functions
|
|
||||||
* https://dev.office.com/sharepoint/docs/spfx/web-parts/guidance/connect-to-sharepoint-using-jsom
|
|
||||||
* @return {Promise<void>} A promise allowing you to execute your code logic.
|
|
||||||
*/
|
|
||||||
public initialize(): Promise<void> {
|
|
||||||
|
|
||||||
const loadScriptPromise = new Promise<void>((resolve) => {
|
|
||||||
|
|
||||||
const siteCollectionUrl = this._context.pageContext.site.absoluteUrl;
|
|
||||||
|
|
||||||
SPComponentLoader.loadScript(siteCollectionUrl + '/_layouts/15/init.js', {
|
|
||||||
globalExportsName: '$_global_init',
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
Logger.write(Text.format("Error when loading '{0}' script. Details: {1}.", "init.js", error));
|
|
||||||
})
|
|
||||||
.then((): Promise<{}> => {
|
|
||||||
|
|
||||||
return SPComponentLoader.loadScript(siteCollectionUrl + '/_layouts/15/MicrosoftAjax.js', {
|
|
||||||
globalExportsName: 'Sys'
|
|
||||||
});
|
|
||||||
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
Logger.write(Text.format("Error when loading '{0}' script. Details: {1}.", "MicrosoftAjax.js", error));
|
|
||||||
})
|
|
||||||
.then((): Promise<{}> => {
|
|
||||||
|
|
||||||
// The SP.Runtime.js file is needed in the hosted workbench environment
|
|
||||||
// However, in a production environment, there will be an error message in the console saying the file is loaded twice
|
|
||||||
// This is not a real issue for our purpose so we can keep these lines.
|
|
||||||
return SPComponentLoader.loadScript(siteCollectionUrl + '/_layouts/15/SP.Runtime.js', {
|
|
||||||
globalExportsName: 'SP'
|
|
||||||
});
|
|
||||||
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
Logger.write(Text.format("Error when loading '{0}' script. Details: {1}.", "SP.Runtime.js", error));
|
|
||||||
})
|
|
||||||
.then((): Promise<{}> => {
|
|
||||||
return SPComponentLoader.loadScript(siteCollectionUrl + '/_layouts/15/SP.js', {
|
|
||||||
globalExportsName: 'SP'
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
Logger.write(Text.format("Error when loading '{0}' script. Details: {1}.", "SP.js", error));
|
|
||||||
})
|
|
||||||
.then((): Promise<{}> => {
|
|
||||||
return SPComponentLoader.loadScript(siteCollectionUrl + '/_layouts/15/SP.taxonomy.js', {
|
|
||||||
globalExportsName: 'SP'
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
Logger.write(Text.format("Error when loading '{0}' script. Details: {1}.", "SP.taxonomy.js", error));
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
|
|
||||||
// Hack the default method to pass the correct parameters to the server (bug in SP.taxonomy.js)
|
|
||||||
// https://www.stephensaw.me/sharepoint-sp-taxonomy-js-term-getterms-not-working/
|
|
||||||
const getTerms: any = function (g, h, e, d, c, f) {
|
|
||||||
var a = this.get_context(), b;
|
|
||||||
b = new SP.Taxonomy.TermCollection(a, new SP.ObjectPathMethod(a, this.get_path(), "GetTerms", [g, h, e, d, c, f]));
|
|
||||||
return b;
|
|
||||||
};
|
|
||||||
|
|
||||||
SP.Taxonomy.Term.prototype.getTerms = getTerms;
|
|
||||||
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return loadScriptPromise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets multiple terms by their ids using the current taxonomy context
|
* Gets multiple terms by their ids using the current taxonomy context
|
||||||
* @param termIds An array of term ids to search for
|
* @param termIds An array of term ids to search for
|
||||||
*/
|
*/
|
||||||
public getTermsById(termIds: string[]): Promise<SP.Taxonomy.TermCollection> {
|
public async getTermsById(termIds: string[]): Promise<(ITerm & ITermData)[]> {
|
||||||
|
|
||||||
if (termIds.length > 0) {
|
if (termIds.length > 0) {
|
||||||
|
|
||||||
const spContext = SP.ClientContext.get_current();
|
const taxonomySession = new Session(this._siteUrl);
|
||||||
const taxSession: SP.Taxonomy.TaxonomySession = SP.Taxonomy.TaxonomySession.getTaxonomySession(spContext);
|
taxonomySession.setup({
|
||||||
const termStore = taxSession.getDefaultSiteCollectionTermStore();
|
sp: {
|
||||||
|
headers: {
|
||||||
if (this._workingLanguageLcid) {
|
Accept: "application/json;odata=nometadata",
|
||||||
termStore.set_workingLanguage(this._workingLanguageLcid);
|
},
|
||||||
}
|
},
|
||||||
|
|
||||||
// The namespace SP is only available here (because of the init() method)
|
|
||||||
const terms: SP.Taxonomy.TermCollection = termStore.getTermsById(termIds.map(t => new SP.Guid(t)));
|
|
||||||
|
|
||||||
// Additional properties can be loaded here
|
|
||||||
spContext.load(terms, "Include(Id, Name)");
|
|
||||||
|
|
||||||
const p = new Promise<SP.Taxonomy.TermCollection>((resolve, reject) => {
|
|
||||||
|
|
||||||
spContext.executeQueryAsync(() => {
|
|
||||||
resolve(terms);
|
|
||||||
|
|
||||||
}, (sender, args) => {
|
|
||||||
const errorMessage = "[TaxonomyProvider.getTermById()]: Error: " + args.get_message();
|
|
||||||
Logger.write(errorMessage, LogLevel.Error);
|
|
||||||
reject(errorMessage);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return p;
|
// Get the default termstore
|
||||||
|
const store: ITermStore = await taxonomySession.getDefaultSiteCollectionTermStore();
|
||||||
|
const terms: ITerms = await store.getTermsById(...termIds);
|
||||||
|
|
||||||
|
return await terms.select('Id','Labels').get();
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
.searchBox {
|
.searchBox {
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
|
||||||
.errorMessage {
|
.errorMessage {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
@ -12,10 +14,31 @@
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.searchFieldGroup {
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.searchTextField {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchBtn {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.suggestionPanel {
|
.suggestionPanel {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
z-index: 1;
|
||||||
|
background-color: #ffffff;
|
||||||
border-bottom: 1px solid #ccc;
|
border-bottom: 1px solid #ccc;
|
||||||
border-left: 1px solid #ccc;
|
border-left: 1px solid #ccc;
|
||||||
border-right: 1px solid #ccc;
|
border-right: 1px solid #ccc;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selected {
|
.selected {
|
||||||
|
|
|
@ -46,6 +46,8 @@ export default class SearchBoxWebPart extends BaseClientSideWebPart<ISearchBoxWe
|
||||||
rawInputValue: '',
|
rawInputValue: '',
|
||||||
enhancedQuery: ''
|
enhancedQuery: ''
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this._bindHashChange = this._bindHashChange.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(): void {
|
public render(): void {
|
||||||
|
@ -53,6 +55,9 @@ export default class SearchBoxWebPart extends BaseClientSideWebPart<ISearchBoxWe
|
||||||
let inputValue = this.properties.defaultQueryKeywords.tryGetValue();
|
let inputValue = this.properties.defaultQueryKeywords.tryGetValue();
|
||||||
|
|
||||||
if (inputValue && typeof(inputValue) === 'string') {
|
if (inputValue && typeof(inputValue) === 'string') {
|
||||||
|
|
||||||
|
// Notify subsscriber a new value if available
|
||||||
|
this.context.dynamicDataSourceManager.notifyPropertyChanged('searchQuery');
|
||||||
this._searchQuery.rawInputValue = inputValue;
|
this._searchQuery.rawInputValue = inputValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,6 +124,8 @@ export default class SearchBoxWebPart extends BaseClientSideWebPart<ISearchBoxWe
|
||||||
this.initSearchService();
|
this.initSearchService();
|
||||||
this.initNlpService();
|
this.initNlpService();
|
||||||
|
|
||||||
|
this._bindHashChange();
|
||||||
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,6 +164,16 @@ export default class SearchBoxWebPart extends BaseClientSideWebPart<ISearchBoxWe
|
||||||
protected onPropertyPaneFieldChanged(propertyPath: string) {
|
protected onPropertyPaneFieldChanged(propertyPath: string) {
|
||||||
this.initSearchService();
|
this.initSearchService();
|
||||||
this.initNlpService();
|
this.initNlpService();
|
||||||
|
|
||||||
|
if (!this.properties.useDynamicDataSource) {
|
||||||
|
this.properties.defaultQueryKeywords.setValue("");
|
||||||
|
} else {
|
||||||
|
this._bindHashChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (propertyPath === 'enableNlpService') {
|
||||||
|
this.properties.enableDebugMode = !this.properties.enableDebugMode ? false : true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -352,4 +369,19 @@ export default class SearchBoxWebPart extends BaseClientSideWebPart<ISearchBoxWe
|
||||||
|
|
||||||
return searchQueryOptimizationFields;
|
return searchQueryOptimizationFields;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribes to URL hash change if the dynamic property is set to the default 'URL Fragment' property
|
||||||
|
*/
|
||||||
|
private _bindHashChange() {
|
||||||
|
|
||||||
|
if (this.properties.defaultQueryKeywords.tryGetSource()) {
|
||||||
|
if (this.properties.defaultQueryKeywords.reference.localeCompare('PageContext:UrlData:fragment') === 0) {
|
||||||
|
// Manually subscribe to hash change since the default property doesn't
|
||||||
|
window.addEventListener('hashchange', this.render);
|
||||||
|
} else {
|
||||||
|
window.removeEventListener('hashchange', this.render);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import * as update from 'immutability-helper';
|
||||||
import styles from '../SearchBoxWebPart.module.scss';
|
import styles from '../SearchBoxWebPart.module.scss';
|
||||||
import ISearchQuery from '../../../models/ISearchQuery';
|
import ISearchQuery from '../../../models/ISearchQuery';
|
||||||
import NlpDebugPanel from './NlpDebugPanel/NlpDebugPanel';
|
import NlpDebugPanel from './NlpDebugPanel/NlpDebugPanel';
|
||||||
|
import { IconButton } from 'office-ui-fabric-react/lib/Button';
|
||||||
|
|
||||||
const SUGGESTION_CHAR_COUNT_TRIGGER = 3;
|
const SUGGESTION_CHAR_COUNT_TRIGGER = 3;
|
||||||
|
|
||||||
|
@ -48,45 +49,49 @@ export default class SearchBoxContainer extends React.Component<ISearchBoxContai
|
||||||
selectedItem,
|
selectedItem,
|
||||||
highlightedIndex,
|
highlightedIndex,
|
||||||
openMenu,
|
openMenu,
|
||||||
clearItems
|
clearItems,
|
||||||
}) => (
|
}) => (
|
||||||
<div>
|
<div>
|
||||||
<TextField {...getInputProps({
|
<div className={ styles.searchFieldGroup }>
|
||||||
placeholder: strings.SearchInputPlaceholder,
|
<TextField {...getInputProps({
|
||||||
onKeyDown: event => {
|
placeholder: strings.SearchInputPlaceholder,
|
||||||
|
onKeyDown: event => {
|
||||||
|
|
||||||
// Submit search on "Enter"
|
// Submit search on "Enter"
|
||||||
if (event.keyCode === 13 && (!isOpen || (isOpen && highlightedIndex === null))) {
|
if (event.keyCode === 13 && (!isOpen || (isOpen && highlightedIndex === null))) {
|
||||||
this._onSearch(this.state.searchInputValue);
|
this._onSearch(this.state.searchInputValue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
})}
|
||||||
})}
|
className={ styles.searchTextField }
|
||||||
value={ this.state.searchInputValue }
|
value={ this.state.searchInputValue }
|
||||||
autoComplete= "off"
|
autoComplete= "off"
|
||||||
onChanged={ (value) => {
|
onChanged={ (value) => {
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
searchInputValue: value,
|
searchInputValue: value,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.state.selectedQuerySuggestions.length === 0) {
|
if (this.state.selectedQuerySuggestions.length === 0) {
|
||||||
clearItems();
|
clearItems();
|
||||||
this._onChange(value);
|
this._onChange(value);
|
||||||
openMenu();
|
openMenu();
|
||||||
} else {
|
} else {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
|
|
||||||
// Reset the selected suggestions if input is empty
|
// Reset the selected suggestions if input is empty
|
||||||
this.setState({
|
this.setState({
|
||||||
selectedQuerySuggestions: [],
|
selectedQuerySuggestions: [],
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}}/>
|
||||||
}}
|
<IconButton iconProps={{
|
||||||
iconProps={{
|
iconName: 'Search',
|
||||||
iconName: 'Search',
|
iconType: IconType.default,
|
||||||
iconType: IconType.default
|
}} onClick= {() => { this._onSearch(this.state.searchInputValue);} } className={ styles.searchBtn }>
|
||||||
}}/>
|
</IconButton>
|
||||||
|
</div>
|
||||||
{isOpen ?
|
{isOpen ?
|
||||||
this.renderSuggestions(getItemProps, selectedItem, highlightedIndex)
|
this.renderSuggestions(getItemProps, selectedItem, highlightedIndex)
|
||||||
: null}
|
: null}
|
||||||
|
@ -96,25 +101,30 @@ export default class SearchBoxContainer extends React.Component<ISearchBoxContai
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderBasicSearchBox(): JSX.Element {
|
private renderBasicSearchBox(): JSX.Element {
|
||||||
return <TextField
|
return <div className={ styles.searchFieldGroup }>
|
||||||
placeholder={ strings.SearchInputPlaceholder }
|
<TextField
|
||||||
value={ this.state.searchInputValue }
|
className={ styles.searchTextField }
|
||||||
onChanged={ (value) => {
|
placeholder={ strings.SearchInputPlaceholder }
|
||||||
this.setState({
|
value={ this.state.searchInputValue }
|
||||||
searchInputValue: value,
|
onChanged={ (value) => {
|
||||||
});
|
this.setState({
|
||||||
}}
|
searchInputValue: value,
|
||||||
onKeyDown={ (event) => {
|
});
|
||||||
|
}}
|
||||||
|
onKeyDown={ (event) => {
|
||||||
|
|
||||||
// Submit search on "Enter"
|
// Submit search on "Enter"
|
||||||
if (event.keyCode === 13) {
|
if (event.keyCode === 13) {
|
||||||
this._onSearch(this.state.searchInputValue);
|
this._onSearch(this.state.searchInputValue);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
iconProps={{
|
/>
|
||||||
iconName: 'Search',
|
<IconButton iconProps={{
|
||||||
iconType: IconType.default
|
iconName: 'Search',
|
||||||
}}/>;
|
iconType: IconType.default,
|
||||||
|
}} onClick= {() => { this._onSearch(this.state.searchInputValue);} } className={ styles.searchBtn }>
|
||||||
|
</IconButton>
|
||||||
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -251,47 +261,52 @@ export default class SearchBoxContainer extends React.Component<ISearchBoxContai
|
||||||
*/
|
*/
|
||||||
public async _onSearch(queryText: string) {
|
public async _onSearch(queryText: string) {
|
||||||
|
|
||||||
let query: ISearchQuery = {
|
// Don't send empty value
|
||||||
rawInputValue: queryText,
|
if (queryText) {
|
||||||
enhancedQuery: ''
|
|
||||||
};
|
|
||||||
|
|
||||||
this.setState({
|
let query: ISearchQuery = {
|
||||||
searchInputValue: queryText,
|
rawInputValue: queryText,
|
||||||
});
|
enhancedQuery: ''
|
||||||
|
};
|
||||||
|
|
||||||
if (this.props.enableNlpService && this.props.NlpService && queryText) {
|
this.setState({
|
||||||
|
searchInputValue: queryText,
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
if (this.props.enableNlpService && this.props.NlpService && queryText) {
|
||||||
|
|
||||||
let enhancedQuery = await this.props.NlpService.enhanceSearchQuery(queryText, this.props.isStaging);
|
try {
|
||||||
query.enhancedQuery = enhancedQuery.enhancedQuery;
|
|
||||||
|
|
||||||
enhancedQuery.entities.map((entity) => {
|
let enhancedQuery = await this.props.NlpService.enhanceSearchQuery(queryText, this.props.isStaging);
|
||||||
});
|
query.enhancedQuery = enhancedQuery.enhancedQuery;
|
||||||
|
|
||||||
this.setState({
|
enhancedQuery.entities.map((entity) => {
|
||||||
enhancedQuery: enhancedQuery,
|
});
|
||||||
});
|
|
||||||
|
|
||||||
} catch (error) {
|
this.setState({
|
||||||
|
enhancedQuery: enhancedQuery,
|
||||||
// In case of failure, use the non-optimized query instead
|
});
|
||||||
query.enhancedQuery = queryText;
|
|
||||||
|
} catch (error) {
|
||||||
|
|
||||||
|
// In case of failure, use the non-optimized query instead
|
||||||
|
query.enhancedQuery = queryText;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (this.props.searchInNewPage) {
|
if (this.props.searchInNewPage) {
|
||||||
// Send the query to the a new via the query string
|
|
||||||
const url = UrlHelper.addOrReplaceQueryStringParam(this.props.pageUrl, 'q', encodeURIComponent(queryText));
|
// Send the query to the a new via the hash
|
||||||
|
const url = `${this.props.pageUrl}#${encodeURIComponent(queryText)}`;
|
||||||
|
|
||||||
const behavior = this.props.openBehavior === PageOpenBehavior.NewTab ? '_blank' : '_self';
|
const behavior = this.props.openBehavior === PageOpenBehavior.NewTab ? '_blank' : '_self';
|
||||||
window.open(url, behavior);
|
window.open(url, behavior);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// Notify the dynamic data controller
|
// Notify the dynamic data controller
|
||||||
this.props.onSearch(query);
|
this.props.onSearch(query);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ define([], function() {
|
||||||
return {
|
return {
|
||||||
"SearchInputPlaceholder": "Entrez vos termes de recherche...",
|
"SearchInputPlaceholder": "Entrez vos termes de recherche...",
|
||||||
"SearchBoxNewPage": "Options de la boîte de recherche",
|
"SearchBoxNewPage": "Options de la boîte de recherche",
|
||||||
|
"SearchBoxEnableQuerySuggestions": "Activer les suggestions de recherche",
|
||||||
"SearchBoxSearchInNewPageLabel": "Envoyer la requête sur une nouvelle page",
|
"SearchBoxSearchInNewPageLabel": "Envoyer la requête sur une nouvelle page",
|
||||||
"SearchBoxPageUrlLabel": "URL de la page",
|
"SearchBoxPageUrlLabel": "URL de la page",
|
||||||
"SearchBoxUrlErrorMessage": "Veuillez spécifier une URL valide",
|
"SearchBoxUrlErrorMessage": "Veuillez spécifier une URL valide",
|
||||||
|
|
|
@ -3,6 +3,8 @@ import { DynamicProperty } from '@microsoft/sp-component-base';
|
||||||
|
|
||||||
export interface ISearchResultsWebPartProps {
|
export interface ISearchResultsWebPartProps {
|
||||||
queryKeywords: DynamicProperty<string>;
|
queryKeywords: DynamicProperty<string>;
|
||||||
|
defaultSearchQuery: string;
|
||||||
|
useDefaultSearchQuery: boolean;
|
||||||
queryTemplate: string;
|
queryTemplate: string;
|
||||||
resultSourceId: string;
|
resultSourceId: string;
|
||||||
sortList: string;
|
sortList: string;
|
||||||
|
|
|
@ -21,11 +21,11 @@
|
||||||
},
|
},
|
||||||
"title": {
|
"title": {
|
||||||
"default": "Search Results with Refiners",
|
"default": "Search Results with Refiners",
|
||||||
"fr-fr": "Résultats de recherche"
|
"fr-fr": "Résultats de recherche"
|
||||||
},
|
},
|
||||||
"description": {
|
"description": {
|
||||||
"default": "Displays search results with customizable dynamic refiners",
|
"default": "Displays search results with customizable dynamic refiners",
|
||||||
"fr-fr": "Affiche des résulats de recherche avec filtres personnalisables"
|
"fr-fr": "Affiche des résulats de recherche avec filtres personnalisables"
|
||||||
},
|
},
|
||||||
"officeFabricIconFontName": "SearchAndApps",
|
"officeFabricIconFontName": "SearchAndApps",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
@ -37,7 +37,8 @@
|
||||||
"maxResultsCount": 10,
|
"maxResultsCount": 10,
|
||||||
"showBlank": true,
|
"showBlank": true,
|
||||||
"showResultsCount": true,
|
"showResultsCount": true,
|
||||||
"webPartTitle": ""
|
"webPartTitle": "",
|
||||||
|
"useDefaultSearchQuery": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import * as ReactDom from 'react-dom';
|
import * as ReactDom from 'react-dom';
|
||||||
import { Version, Text, Environment, EnvironmentType, DisplayMode, Log } from '@microsoft/sp-core-library';
|
import { Version, Text, Environment, EnvironmentType, DisplayMode, Log } from '@microsoft/sp-core-library';
|
||||||
import {
|
import {
|
||||||
|
@ -14,7 +14,9 @@ import {
|
||||||
PropertyPaneToggle,
|
PropertyPaneToggle,
|
||||||
PropertyPaneSlider,
|
PropertyPaneSlider,
|
||||||
IPropertyPaneChoiceGroupOption,
|
IPropertyPaneChoiceGroupOption,
|
||||||
PropertyPaneChoiceGroup
|
PropertyPaneChoiceGroup,
|
||||||
|
PropertyPaneCheckbox,
|
||||||
|
|
||||||
} from '@microsoft/sp-webpart-base';
|
} from '@microsoft/sp-webpart-base';
|
||||||
import * as strings from 'SearchResultsWebPartStrings';
|
import * as strings from 'SearchResultsWebPartStrings';
|
||||||
import SearchResultsContainer from './components/SearchResultsContainer/SearchResultsContainer';
|
import SearchResultsContainer from './components/SearchResultsContainer/SearchResultsContainer';
|
||||||
|
@ -27,7 +29,6 @@ import TemplateService from '../../services/TemplateService/TemplateService';
|
||||||
import { update, isEmpty } from '@microsoft/sp-lodash-subset';
|
import { update, isEmpty } from '@microsoft/sp-lodash-subset';
|
||||||
import MockSearchService from '../../services/SearchService/MockSearchService';
|
import MockSearchService from '../../services/SearchService/MockSearchService';
|
||||||
import MockTemplateService from '../../services/TemplateService/MockTemplateService';
|
import MockTemplateService from '../../services/TemplateService/MockTemplateService';
|
||||||
import LocalizationHelper from '../../helpers/LocalizationHelper';
|
|
||||||
import SearchService from '../../services/SearchService/SearchService';
|
import SearchService from '../../services/SearchService/SearchService';
|
||||||
import TaxonomyService from '../../services/TaxonomyService/TaxonomyService';
|
import TaxonomyService from '../../services/TaxonomyService/TaxonomyService';
|
||||||
import MockTaxonomyService from '../../services/TaxonomyService/MockTaxonomyService';
|
import MockTaxonomyService from '../../services/TaxonomyService/MockTaxonomyService';
|
||||||
|
@ -35,7 +36,6 @@ import ISearchResultsContainerProps from './components/SearchResultsContainer/IS
|
||||||
import { Placeholder, IPlaceholderProps } from '@pnp/spfx-controls-react/lib/Placeholder';
|
import { Placeholder, IPlaceholderProps } from '@pnp/spfx-controls-react/lib/Placeholder';
|
||||||
import { SPHttpClientResponse, SPHttpClient } from '@microsoft/sp-http';
|
import { SPHttpClientResponse, SPHttpClient } from '@microsoft/sp-http';
|
||||||
|
|
||||||
declare var System: any;
|
|
||||||
const LOG_SOURCE: string = '[SearchResultsWebPart_{0}]';
|
const LOG_SOURCE: string = '[SearchResultsWebPart_{0}]';
|
||||||
|
|
||||||
export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchResultsWebPartProps> {
|
export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchResultsWebPartProps> {
|
||||||
|
@ -54,6 +54,7 @@ export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchR
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this._parseFieldListString = this._parseFieldListString.bind(this);
|
this._parseFieldListString = this._parseFieldListString.bind(this);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async render(): Promise<void> {
|
public async render(): Promise<void> {
|
||||||
|
@ -83,6 +84,8 @@ export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchR
|
||||||
protected renderCompleted(): void {
|
protected renderCompleted(): void {
|
||||||
super.renderCompleted();
|
super.renderCompleted();
|
||||||
|
|
||||||
|
let queryKeywords;
|
||||||
|
|
||||||
let renderElement = null;
|
let renderElement = null;
|
||||||
if (typeof this.properties.useHandlebarsHelpers === 'undefined') {
|
if (typeof this.properties.useHandlebarsHelpers === 'undefined') {
|
||||||
this.properties.useHandlebarsHelpers = true;
|
this.properties.useHandlebarsHelpers = true;
|
||||||
|
@ -96,6 +99,12 @@ export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchR
|
||||||
this.context.propertyPane.refresh();
|
this.context.propertyPane.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!dataSourceValue) {
|
||||||
|
queryKeywords = this.properties.defaultSearchQuery;
|
||||||
|
} else {
|
||||||
|
queryKeywords = dataSourceValue;
|
||||||
|
}
|
||||||
|
|
||||||
const isValueConnected = !!this.properties.queryKeywords.tryGetSource();
|
const isValueConnected = !!this.properties.queryKeywords.tryGetSource();
|
||||||
|
|
||||||
const searchContainer: React.ReactElement<ISearchResultsContainerProps> = React.createElement(
|
const searchContainer: React.ReactElement<ISearchResultsContainerProps> = React.createElement(
|
||||||
|
@ -103,7 +112,7 @@ export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchR
|
||||||
{
|
{
|
||||||
searchService: this._searchService,
|
searchService: this._searchService,
|
||||||
taxonomyService: this._taxonomyService,
|
taxonomyService: this._taxonomyService,
|
||||||
queryKeywords: this.properties.queryKeywords.tryGetValue(),
|
queryKeywords: queryKeywords,
|
||||||
maxResultsCount: this.properties.maxResultsCount,
|
maxResultsCount: this.properties.maxResultsCount,
|
||||||
resultSourceId: this.properties.resultSourceId,
|
resultSourceId: this.properties.resultSourceId,
|
||||||
sortList: this.properties.sortList,
|
sortList: this.properties.sortList,
|
||||||
|
@ -133,7 +142,9 @@ export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchR
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isValueConnected || (!isValueConnected && !isEmpty(this.properties.queryKeywords.tryGetValue()))) {
|
if (isValueConnected && !this.properties.useDefaultSearchQuery ||
|
||||||
|
isValueConnected && this.properties.useDefaultSearchQuery && this.properties.defaultSearchQuery ||
|
||||||
|
!isValueConnected && !isEmpty(queryKeywords)) {
|
||||||
renderElement = searchContainer;
|
renderElement = searchContainer;
|
||||||
} else {
|
} else {
|
||||||
if (this.displayMode === DisplayMode.Edit) {
|
if (this.displayMode === DisplayMode.Edit) {
|
||||||
|
@ -155,10 +166,8 @@ export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchR
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
const lcid = LocalizationHelper.getLocaleId(this.context.pageContext.cultureInfo.currentUICultureName);
|
|
||||||
|
|
||||||
this._searchService = new SearchService(this.context);
|
this._searchService = new SearchService(this.context);
|
||||||
this._taxonomyService = new TaxonomyService(this.context, lcid);
|
this._taxonomyService = new TaxonomyService(this.context.pageContext.site.absoluteUrl);
|
||||||
this._templateService = new TemplateService(this.context.spHttpClient, this.context.pageContext.cultureInfo.currentUICultureName);
|
this._templateService = new TemplateService(this.context.spHttpClient, this.context.pageContext.cultureInfo.currentUICultureName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,6 +176,7 @@ export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchR
|
||||||
// Configure search query settings
|
// Configure search query settings
|
||||||
this._useResultSource = false;
|
this._useResultSource = false;
|
||||||
|
|
||||||
|
|
||||||
// Set the default search results layout
|
// Set the default search results layout
|
||||||
this.properties.selectedLayout = this.properties.selectedLayout ? this.properties.selectedLayout : ResultsLayoutOption.List;
|
this.properties.selectedLayout = this.properties.selectedLayout ? this.properties.selectedLayout : ResultsLayoutOption.List;
|
||||||
|
|
||||||
|
@ -234,6 +244,10 @@ export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchR
|
||||||
|
|
||||||
protected async onPropertyPaneFieldChanged(propertyPath: string) {
|
protected async onPropertyPaneFieldChanged(propertyPath: string) {
|
||||||
|
|
||||||
|
if (!this.properties.useDefaultSearchQuery) {
|
||||||
|
this.properties.defaultSearchQuery = '';
|
||||||
|
}
|
||||||
|
|
||||||
if (propertyPath === 'selectedLayout') {
|
if (propertyPath === 'selectedLayout') {
|
||||||
// Refresh setting the right template for the property pane
|
// Refresh setting the right template for the property pane
|
||||||
await this._getTemplateContent();
|
await this._getTemplateContent();
|
||||||
|
@ -543,6 +557,30 @@ export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchR
|
||||||
*/
|
*/
|
||||||
private _getSearchQueryFields(): IPropertyPaneConditionalGroup {
|
private _getSearchQueryFields(): IPropertyPaneConditionalGroup {
|
||||||
|
|
||||||
|
let defaultSearchQueryFields: IPropertyPaneField<any>[] = [];
|
||||||
|
|
||||||
|
if (!!this.properties.queryKeywords.tryGetSource()) {
|
||||||
|
defaultSearchQueryFields.push(
|
||||||
|
PropertyPaneCheckbox('useDefaultSearchQuery', {
|
||||||
|
text: strings.UseDefaultSearchQueryKeywordsFieldLabel
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.properties.useDefaultSearchQuery) {
|
||||||
|
defaultSearchQueryFields.push(
|
||||||
|
PropertyPaneTextField('defaultSearchQuery', {
|
||||||
|
label: strings.DefaultSearchQueryKeywordsFieldLabel,
|
||||||
|
description: strings.DefaultSearchQueryKeywordsFieldDescription,
|
||||||
|
multiline: true,
|
||||||
|
resizable: true,
|
||||||
|
placeholder: strings.SearchQueryPlaceHolderText,
|
||||||
|
onGetErrorMessage: this._validateEmptyField.bind(this),
|
||||||
|
deferredValidationTime: 500
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
primaryGroup: {
|
primaryGroup: {
|
||||||
groupFields: [
|
groupFields: [
|
||||||
|
@ -561,20 +599,29 @@ export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchR
|
||||||
groupFields: [
|
groupFields: [
|
||||||
PropertyPaneDynamicFieldSet({
|
PropertyPaneDynamicFieldSet({
|
||||||
label: strings.SearchQueryKeywordsFieldLabel,
|
label: strings.SearchQueryKeywordsFieldLabel,
|
||||||
|
|
||||||
fields: [
|
fields: [
|
||||||
PropertyPaneDynamicField('queryKeywords', {
|
PropertyPaneDynamicField('queryKeywords', {
|
||||||
label: strings.SearchQueryKeywordsFieldLabel
|
label: strings.SearchQueryKeywordsFieldLabel
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
sharedConfiguration: {
|
sharedConfiguration: {
|
||||||
depth: DynamicDataSharedDepth.Source,
|
depth: DynamicDataSharedDepth.Source,
|
||||||
}
|
},
|
||||||
})
|
}),
|
||||||
]
|
].concat(defaultSearchQueryFields)
|
||||||
},
|
},
|
||||||
// Show the secondary group only if the web part has been
|
// Show the secondary group only if the web part has been
|
||||||
// connected to a dynamic data source
|
// connected to a dynamic data source
|
||||||
showSecondaryGroup: !!this.properties.queryKeywords.tryGetSource(),
|
showSecondaryGroup: !!this.properties.queryKeywords.tryGetSource(),
|
||||||
|
onShowPrimaryGroup: () => {
|
||||||
|
|
||||||
|
// Reset dynamic data fields related values to be consistent
|
||||||
|
this.properties.useDefaultSearchQuery = false;
|
||||||
|
this.properties.defaultSearchQuery = '';
|
||||||
|
this.properties.queryKeywords.setValue('');
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
} as IPropertyPaneConditionalGroup;
|
} as IPropertyPaneConditionalGroup;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -133,12 +133,9 @@ export default class FilterPanel extends React.Component<IFilterPanelProps, IFil
|
||||||
isOpen={this.state.showPanel}
|
isOpen={this.state.showPanel}
|
||||||
type={PanelType.custom}
|
type={PanelType.custom}
|
||||||
customWidth="450px"
|
customWidth="450px"
|
||||||
isBlocking={false}
|
|
||||||
isLightDismiss={true}
|
isLightDismiss={true}
|
||||||
onDismiss={this._onClosePanel}
|
onDismiss={this._onClosePanel}
|
||||||
headerText={strings.FilterPanelTitle}
|
headerText={strings.FilterPanelTitle}
|
||||||
closeButtonAriaLabel='Close'
|
|
||||||
hasCloseButton={true}
|
|
||||||
onRenderBody={() => {
|
onRenderBody={() => {
|
||||||
if (this.props.availableFilters.length > 0) {
|
if (this.props.availableFilters.length > 0) {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -15,6 +15,9 @@ import SearchResultsTemplate from '../Layouts/SearchResultsTemplate';
|
||||||
import styles from '../SearchResultsWebPart.module.scss';
|
import styles from '../SearchResultsWebPart.module.scss';
|
||||||
import { SortPanel } from '../SortPanel';
|
import { SortPanel } from '../SortPanel';
|
||||||
import { SortDirection } from "@pnp/sp";
|
import { SortDirection } from "@pnp/sp";
|
||||||
|
import { ITermData, ITerm } from '@pnp/sp-taxonomy';
|
||||||
|
import LocalizationHelper from '../../../../helpers/LocalizationHelper';
|
||||||
|
import { Text } from '@microsoft/sp-core-library';
|
||||||
|
|
||||||
declare var System: any;
|
declare var System: any;
|
||||||
let FilterPanel = null;
|
let FilterPanel = null;
|
||||||
|
@ -282,7 +285,9 @@ export default class SearchResultsContainer extends React.Component<ISearchConta
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.setState({
|
this.setState({
|
||||||
areResultsLoading: false
|
areResultsLoading: false,
|
||||||
|
lastQuery: '',
|
||||||
|
results: { RefinementResults: [], RelevantResults: [] },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -404,8 +409,12 @@ export default class SearchResultsContainer extends React.Component<ISearchConta
|
||||||
*/
|
*/
|
||||||
private async _getLocalizedFilters(rawFilters: IRefinementResult[]): Promise<IRefinementResult[]> {
|
private async _getLocalizedFilters(rawFilters: IRefinementResult[]): Promise<IRefinementResult[]> {
|
||||||
|
|
||||||
|
// Get the current lcid according to current page language
|
||||||
|
const lcid = LocalizationHelper.getLocaleId(this.props.context.pageContext.cultureInfo.currentUICultureName);
|
||||||
|
|
||||||
let termsToLocalize: { uniqueIdentifier: string, termId: string, localizedTermLabel: string }[] = [];
|
let termsToLocalize: { uniqueIdentifier: string, termId: string, localizedTermLabel: string }[] = [];
|
||||||
let updatedFilters = [];
|
let updatedFilters = [];
|
||||||
|
let localizedTerms = [];
|
||||||
|
|
||||||
rawFilters.map((filterResult) => {
|
rawFilters.map((filterResult) => {
|
||||||
|
|
||||||
|
@ -432,45 +441,49 @@ export default class SearchResultsContainer extends React.Component<ISearchConta
|
||||||
|
|
||||||
if (termsToLocalize.length > 0) {
|
if (termsToLocalize.length > 0) {
|
||||||
|
|
||||||
// Process all terms in a single JSOM call for performance purpose. In general JSOM is pretty slow so we try to limit the number of calls...
|
// Get the terms from taxonomy
|
||||||
await this.props.taxonomyService.initialize();
|
// If a term doesn't exist anymore, it won't be retrieved by the API so the termValues count could be less than termsToLocalize count
|
||||||
const termValues = await this.props.taxonomyService.getTermsById(termsToLocalize.map((t) => { return t.termId; }));
|
const termValues = await this.props.taxonomyService.getTermsById(termsToLocalize.map((t) => { return t.termId; }));
|
||||||
|
|
||||||
const termsEnumerator = termValues.getEnumerator();
|
termsToLocalize.map((termToLocalize) => {
|
||||||
|
|
||||||
while (termsEnumerator.moveNext()) {
|
// Check if the term has been retrieved from taxonomy (i.e. exists)
|
||||||
|
const termsFromTaxonomy = termValues.filter((taxonomyTerm: ITerm & ITermData) => {
|
||||||
|
const termIdFromTaxonomy = taxonomyTerm.Id.substring(taxonomyTerm.Id.indexOf('(') + 1, taxonomyTerm.Id.indexOf(')'));
|
||||||
|
return termIdFromTaxonomy === termToLocalize.termId;
|
||||||
|
});
|
||||||
|
|
||||||
const currentTerm = termsEnumerator.get_current();
|
if (termsFromTaxonomy.length > 0) {
|
||||||
|
|
||||||
// Need to do this check in the case where the term indexed by the search doesn't exist anymore in the term store
|
// Should be always unique since we can't have two terms with the same ids
|
||||||
if (!currentTerm.get_serverObjectIsNull()) {
|
const termFromTaxonomy: ITerm & ITermData = termsFromTaxonomy[0];
|
||||||
|
|
||||||
const termId = currentTerm.get_id();
|
// It supposes the 'Label' property has been selected in the underlying call
|
||||||
|
// A term always have a default label so the collection can't be empty
|
||||||
// Check if retrieved term is part of terms to localize
|
const localizedLabel = termFromTaxonomy["Labels"]._Child_Items_.filter((label: any) => {
|
||||||
const terms = termsToLocalize.filter((e) => { return e.termId === termId.toString(); });
|
return label.Language === lcid;
|
||||||
if (terms.length > 0) {
|
});
|
||||||
termsToLocalize = termsToLocalize.map((term) => {
|
|
||||||
if (term.termId === terms[0].termId) {
|
localizedTerms.push({
|
||||||
return {
|
uniqueIdentifier: termToLocalize.uniqueIdentifier,
|
||||||
uniqueIdentifier: term.uniqueIdentifier,
|
termId: termToLocalize.termId,
|
||||||
termId: termId.toString(),
|
localizedTermLabel: localizedLabel.length > 0 ? localizedLabel[0].Value : termFromTaxonomy.Name
|
||||||
localizedTermLabel: termsEnumerator.get_current().get_name(),
|
});
|
||||||
};
|
} else {
|
||||||
} else {
|
localizedTerms.push({
|
||||||
return term;
|
uniqueIdentifier: termToLocalize.uniqueIdentifier,
|
||||||
}
|
termId: termToLocalize.termId,
|
||||||
});
|
localizedTermLabel: Text.format(strings.TermNotFound, termToLocalize.termId)
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
// Update original filters with localized values
|
// Update original filters with localized values
|
||||||
rawFilters.map((filter) => {
|
rawFilters.map((filter) => {
|
||||||
let updatedValues = [];
|
let updatedValues = [];
|
||||||
|
|
||||||
filter.Values.map((value) => {
|
filter.Values.map((value) => {
|
||||||
const existingFilters = termsToLocalize.filter((e) => { return e.uniqueIdentifier === value.RefinementToken; });
|
const existingFilters = localizedTerms.filter((e) => { return e.uniqueIdentifier === value.RefinementToken; });
|
||||||
if (existingFilters.length > 0) {
|
if (existingFilters.length > 0) {
|
||||||
updatedValues.push({
|
updatedValues.push({
|
||||||
RefinementCount: value.RefinementCount,
|
RefinementCount: value.RefinementCount,
|
||||||
|
|
|
@ -63,6 +63,10 @@ define([], function() {
|
||||||
"SortPanelSortFieldAria":"Select a field",
|
"SortPanelSortFieldAria":"Select a field",
|
||||||
"SortPanelSortFieldPlaceHolder":"Select a field",
|
"SortPanelSortFieldPlaceHolder":"Select a field",
|
||||||
"SortPanelSortDirectionLabel":"Sort Direction",
|
"SortPanelSortDirectionLabel":"Sort Direction",
|
||||||
}
|
},
|
||||||
|
"TermNotFound": "(Term with ID '{0}' not found)",
|
||||||
|
"UseDefaultSearchQueryKeywordsFieldLabel": "Use a default search query",
|
||||||
|
"DefaultSearchQueryKeywordsFieldLabel": "Default search query",
|
||||||
|
"DefaultSearchQueryKeywordsFieldDescription": "This query will be used when the data source value is still empty."
|
||||||
}
|
}
|
||||||
});
|
});
|
|
@ -63,6 +63,10 @@ define([], function() {
|
||||||
"SortPanelSortFieldAria":"Sélectionner un champ",
|
"SortPanelSortFieldAria":"Sélectionner un champ",
|
||||||
"SortPanelSortFieldPlaceHolder":"Sélectionner un champ",
|
"SortPanelSortFieldPlaceHolder":"Sélectionner un champ",
|
||||||
"SortPanelSortDirectionLabel":"Direction de tri",
|
"SortPanelSortDirectionLabel":"Direction de tri",
|
||||||
}
|
},
|
||||||
|
"TermNotFound": "(Terme avec l'ID '{0}' non trouvé)",
|
||||||
|
"UseDefaultSearchQueryKeywordsFieldLabel": "Utiliser une requête initiale",
|
||||||
|
"DefaultSearchQueryKeywordsFieldLabel": "Requête de recherche par défaut",
|
||||||
|
"DefaultSearchQueryKeywordsFieldDescription": "Cette requête sera utilisée par défault dans le cas où la valeur de la source de données est encore vide."
|
||||||
}
|
}
|
||||||
});
|
});
|
|
@ -62,7 +62,11 @@ declare interface ISearchResultsWebPartStrings {
|
||||||
SortPanelSortFieldAria:string;
|
SortPanelSortFieldAria:string;
|
||||||
SortPanelSortFieldPlaceHolder:string;
|
SortPanelSortFieldPlaceHolder:string;
|
||||||
SortPanelSortDirectionLabel:string;
|
SortPanelSortDirectionLabel:string;
|
||||||
}
|
},
|
||||||
|
TermNotFound: string;
|
||||||
|
UseDefaultSearchQueryKeywordsFieldLabel: string;
|
||||||
|
DefaultSearchQueryKeywordsFieldLabel: string;
|
||||||
|
DefaultSearchQueryKeywordsFieldDescription: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module 'SearchResultsWebPartStrings' {
|
declare module 'SearchResultsWebPartStrings' {
|
||||||
|
|
Loading…
Reference in New Issue