React search refiners renderer - An example code renderer for React Search Refiners (#742)
* Re-did the new changes to dev instead of master. * Added templating options. * Removed console logs * No filter chosen should default to no filter, not two filters. * New settings now handling locales.. * Handled the fact that IE does not support Event constructor. * Updated documentation. * Better images for documentation. * Updated version history. * Upped version number correctly. * Update ResultsLayoutOption.ts Removed comma * Initial commit for react-search-refiner-renderer * Boilerplate cleanup * Initial readme commit. * Updated variables image * Updated readme. * Fixed broken link. * Another attempt at fixing broken link.
This commit is contained in:
parent
5b74bb9352
commit
e587071d01
|
@ -0,0 +1,25 @@
|
|||
# EditorConfig helps developers define and maintain consistent
|
||||
# coding styles between different editors and IDEs
|
||||
# editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
|
||||
[*]
|
||||
|
||||
# change these settings to your own preference
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
# we recommend you to keep these unchanged
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[{package,bower}.json]
|
||||
indent_style = space
|
||||
indent_size = 2
|
|
@ -0,0 +1,32 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Dependency directories
|
||||
node_modules
|
||||
|
||||
# Build generated files
|
||||
dist
|
||||
lib
|
||||
solution
|
||||
temp
|
||||
*.sppkg
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# OSX
|
||||
.DS_Store
|
||||
|
||||
# Visual Studio files
|
||||
.ntvs_analysis.dat
|
||||
.vs
|
||||
bin
|
||||
obj
|
||||
|
||||
# Resx Generated Code
|
||||
*.resx.ts
|
||||
|
||||
# Styles Generated Code
|
||||
*.scss.ts
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"@microsoft/generator-sharepoint": {
|
||||
"isCreatingSolution": true,
|
||||
"environment": "spo",
|
||||
"version": "1.7.1",
|
||||
"libraryName": "react-search-refiners-renderer",
|
||||
"libraryId": "41f46e12-4ce6-4826-b1e3-10e7a2a0b895",
|
||||
"packageManager": "npm",
|
||||
"componentType": "extension",
|
||||
"extensionType": "ApplicationCustomizer"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
# Sharepoint Framework renderer for react-search-refiners
|
||||
|
||||
## Summary
|
||||
This sample is an example on how to use custom code renderers in the react-search-refiners project. Custom code renderers lets you render the search results from a react-search-refiners webpart in your own way, with whatever tools you prefer.
|
||||
Typically, you will use SPFx Application Customizers to mount these renderers, but you may also use SPFx Webparts. This sample contains a SPFx Application Customizer which uses React and Office-UI-Fabric-React to render the search results.
|
||||
|
||||
<p align="center">
|
||||
<img src="./img/coderenderer.gif"/>
|
||||
</p>
|
||||
|
||||
## Used Sharepoint Framework Version
|
||||
![drop](https://img.shields.io/badge/drop-1.7.1-green.svg)
|
||||
|
||||
## Applies to
|
||||
* [SharePoint Framework](https:/dev.office.com/sharepoint)
|
||||
* [Office 365 tenant](https://dev.office.com/sharepoint/docs/spfx/set-up-your-development-environment)
|
||||
* [React Search Refiners](https://github.com/SharePoint/sp-dev-fx-webparts/tree/master/samples/react-search-refiners)
|
||||
|
||||
## Solution
|
||||
Solution|Author(s)
|
||||
--------|---------
|
||||
react-search-refiners-renderers | Tarald Gåsbakk - [@taraldga](http://www.twitter.com/taraldgasbakk)
|
||||
|
||||
## Version history
|
||||
Version|Date|Comments
|
||||
-------|----|--------
|
||||
1.0 | Jan 06, 2019 | Initial release
|
||||
|
||||
## Minimal Path to Awesome
|
||||
|
||||
- Follow the Minimal Path to Awesome for [React Search Refiners](https://github.com/SharePoint/sp-dev-fx-webparts/tree/master/samples/react-search-refiners).
|
||||
- In this repository, run from the command line:
|
||||
- `npm install`
|
||||
- `gulp serve`
|
||||
|
||||
|
||||
## Creating your own renderers
|
||||
The renderer bundlet in this sample is a simple renderer designed to show how a simple implementation of a code renderer would look like. Following is a quick tour of the setup-process and the different settings you will encounter when creating your own renderer.
|
||||
|
||||
### Registering a new renderer
|
||||
The renderers work by announcing themselves to any react-search-refiners webparts that exists on the pages they are installed on. The registration process is handled by the 'registerRenderer'- function in the 'ResultService' - class in './src/services'. A typicall registration will take place onInit.
|
||||
|
||||
The parameters of the registerRenderer function:
|
||||
|
||||
- **rendererId**: The id of the current renderer. Used to idententify which renderer the user has selected. Typically 'this.componentId'.
|
||||
- **rendererName**: The displayName of the renderer in the react-search-refiners webpart property panel.
|
||||
- **rendererIcon**: The office-ui-fabric-react icon that should be displayed in the property pane of the webpart.
|
||||
- **callback**: The callback function that is used to render the webpart. This will be called when the renderer is selected and new data is available.
|
||||
- **customFields**: Optional names of custom fields that may be used for templating in the renderer.
|
||||
|
||||
<p align="center"><img width="300px" src="./img/renderervariables.png"/><p>
|
||||
|
||||
### Templating
|
||||
By registering a field-name in the 'customFields' value, you may expose them to the user, letting them choose which fields should be displayed in different places in your renderer. In the bundled example, a subheader is passed along as a template field. The dropdown selection that the user can chose elements from are the elements selected for fetching by the search webpart.
|
||||
|
||||
<p align="center">
|
||||
<img src="./img/coderenderertemplate.gif"/>
|
||||
</p>
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
|
||||
"version": "2.0",
|
||||
"bundles": {
|
||||
"code-renderer-application-customizer": {
|
||||
"components": [
|
||||
{
|
||||
"entrypoint": "./lib/extensions/codeRenderer/CodeRendererApplicationCustomizer.js",
|
||||
"manifest": "./src/extensions/codeRenderer/CodeRendererApplicationCustomizer.manifest.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"externals": {},
|
||||
"localizedResources": {
|
||||
"CodeRendererApplicationCustomizerStrings": "lib/extensions/codeRenderer/loc/{locale}.js"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
|
||||
"deployCdnPath": "temp/deploy"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
|
||||
"workingDir": "./temp/deploy/",
|
||||
"account": "<!-- STORAGE ACCOUNT NAME -->",
|
||||
"container": "react-search-refiners-renderer",
|
||||
"accessKey": "<!-- ACCESS KEY -->"
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||
"solution": {
|
||||
"name": " Code renderer for React Search Refiners",
|
||||
"id": "41f46e12-4ce6-4826-b1e3-10e7a2a0b895",
|
||||
"version": "1.0.0.0",
|
||||
"includeClientSideAssets": true,
|
||||
"features": [
|
||||
{
|
||||
"title": "Application Extension - Deployment of custom action.",
|
||||
"description": "Deploys a custom action with ClientSideComponentId association",
|
||||
"id": "cbc3a036-8112-4a19-a924-4bfe45af2675",
|
||||
"version": "1.0.0.0",
|
||||
"assets": {
|
||||
"elementManifests": [
|
||||
"elements.xml",
|
||||
"clientsideinstance.xml"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"paths": {
|
||||
"zippedPackage": "solution/react-search-refiners-renderer.sppkg"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
|
||||
"port": 4321,
|
||||
"https": true,
|
||||
"serveConfigurations": {
|
||||
"default": {
|
||||
"pageUrl": "https://contoso.sharepoint.com/sites/mySite/SitePages/myPage.aspx",
|
||||
"customActions": {
|
||||
"6c5daeed-02bf-4f02-806c-7e44a770d71c": {
|
||||
"location": "ClientSideExtension.ApplicationCustomizer",
|
||||
"properties": {
|
||||
"testMessage": "Test message"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"codeRenderer": {
|
||||
"pageUrl": "https://contoso.sharepoint.com/sites/mySite/SitePages/myPage.aspx",
|
||||
"customActions": {
|
||||
"6c5daeed-02bf-4f02-806c-7e44a770d71c": {
|
||||
"location": "ClientSideExtension.ApplicationCustomizer",
|
||||
"properties": {
|
||||
"testMessage": "Test message"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
|
||||
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
const gulp = require('gulp');
|
||||
const build = require('@microsoft/sp-build-web');
|
||||
build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`);
|
||||
|
||||
build.initialize(gulp);
|
Binary file not shown.
After Width: | Height: | Size: 2.0 MiB |
Binary file not shown.
After Width: | Height: | Size: 596 KiB |
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"name": "react-search-refiners-renderer",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "gulp bundle",
|
||||
"clean": "gulp clean",
|
||||
"test": "gulp test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@microsoft/decorators": "1.7.1",
|
||||
"@microsoft/sp-application-base": "1.7.1",
|
||||
"@microsoft/sp-core-library": "1.7.1",
|
||||
"@microsoft/sp-dialog": "1.7.1",
|
||||
"@types/es6-promise": "0.0.33",
|
||||
"@types/webpack-env": "1.13.1",
|
||||
"custom-event-polyfill": "^1.0.6",
|
||||
"moment": "^2.23.0",
|
||||
"office-ui-fabric-react": "^5.133.0",
|
||||
"react": "16.3.2",
|
||||
"react-dom": "16.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/sp-build-web": "1.7.1",
|
||||
"@microsoft/sp-tslint-rules": "1.7.1",
|
||||
"@microsoft/sp-module-interfaces": "1.7.1",
|
||||
"@microsoft/sp-webpart-workbench": "1.7.1",
|
||||
"gulp": "~3.9.1",
|
||||
"@types/chai": "3.4.34",
|
||||
"@types/mocha": "2.2.38",
|
||||
"ajv": "~5.2.2"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
|
||||
<ClientSideComponentInstance
|
||||
Title="CodeRenderer"
|
||||
Location="ClientSideExtension.ApplicationCustomizer"
|
||||
ComponentId="6c5daeed-02bf-4f02-806c-7e44a770d71c"
|
||||
Properties="{"testMessage":"Test message"}">
|
||||
</ClientSideComponentInstance>
|
||||
</Elements>
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
|
||||
<CustomAction
|
||||
Title="CodeRenderer"
|
||||
Location="ClientSideExtension.ApplicationCustomizer"
|
||||
ClientSideComponentId="6c5daeed-02bf-4f02-806c-7e44a770d71c"
|
||||
ClientSideComponentProperties="{"testMessage":"Test message"}">
|
||||
</CustomAction>
|
||||
</Elements>
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-extension-manifest.schema.json",
|
||||
|
||||
"id": "6c5daeed-02bf-4f02-806c-7e44a770d71c",
|
||||
"alias": "PnP Search Code-Renderer",
|
||||
"componentType": "Extension",
|
||||
"extensionType": "ApplicationCustomizer",
|
||||
|
||||
"version": "*",
|
||||
"manifestVersion": 2,
|
||||
|
||||
"requiresCustomScript": false
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
import { override } from '@microsoft/decorators';
|
||||
import {
|
||||
BaseApplicationCustomizer
|
||||
} from '@microsoft/sp-application-base';
|
||||
|
||||
import { ResultService, ISearchEvent } from '../../services/ResultService/ResultService';
|
||||
import IResultService from '../../services/ResultService/IResultService';
|
||||
import SearchResult from './SearchResult/SearchResults';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import * as React from 'react';
|
||||
|
||||
export interface ISearchRendererApplicationCustomizerProperties {
|
||||
}
|
||||
|
||||
export default class SearchRendererApplicationCustomizer
|
||||
extends BaseApplicationCustomizer<ISearchRendererApplicationCustomizerProperties> {
|
||||
private _resultService: IResultService;
|
||||
|
||||
@override
|
||||
public onInit(): Promise<void> {
|
||||
this._resultService = new ResultService();
|
||||
this.onChangeHappened.bind(this);
|
||||
this._resultService.registerRenderer(this.componentId, 'CodeRenderer', 'QueryList', this.onChangeHappened, ['Subheader']);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
public onChangeHappened(e: ISearchEvent) {
|
||||
const subheaderFieldName = e.customTemplateFieldValues[0].searchProperty && e.customTemplateFieldValues[0].searchProperty.length > 0 ? e.customTemplateFieldValues[0].searchProperty : 'Path';
|
||||
const resultDisplay = React.createElement(SearchResult, {
|
||||
searchResults: e.results,
|
||||
componentId: e.rendererId,
|
||||
subheaderFieldName: subheaderFieldName,
|
||||
});
|
||||
let node = document.getElementById(e.mountNode);
|
||||
if (node) {
|
||||
ReactDOM.render(resultDisplay, node);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
import { ISearchResults } from '../../../models/ISearchResult';
|
||||
import IResultService from '../../../services/ResultService/IResultService';
|
||||
|
||||
export default interface ISearchResultProps {
|
||||
searchResults: ISearchResults;
|
||||
componentId: string;
|
||||
subheaderFieldName: string;
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
.resultContainer {
|
||||
li.resultItem {
|
||||
display: block;
|
||||
a {
|
||||
transition: box-shadow 0.467s ease;
|
||||
text-decoration: none;
|
||||
display: flex;
|
||||
padding: 15px 0 0 0;
|
||||
border-top: 1px solid #eaeaea;
|
||||
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 3px #FFBD65;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 8px 10px -2px rgba(0,0,0,.1);
|
||||
}
|
||||
&:before {
|
||||
display: block;
|
||||
content: " ";
|
||||
border-bottom: 1px solid #eaeaea;
|
||||
}
|
||||
.imageContainer {
|
||||
height:100px;
|
||||
width: 78px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
>img {
|
||||
max-width: 78px;
|
||||
max-height: 48px;
|
||||
margin: auto 0;
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
.coin {
|
||||
width: 48px;
|
||||
height: 100px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
padding: 0 15px;
|
||||
> div > div {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
}
|
||||
> article {
|
||||
margin-left: 10px;
|
||||
h3, p, ul {
|
||||
font-weight: 400;
|
||||
font-family:"Segoe UI Web (West European)",Segoe UI,-apple-system,BlinkMacSystemFont,Roboto,Helvetica Neue,sans-serif;
|
||||
}
|
||||
p, ul {
|
||||
font-size: 12px;
|
||||
color: #666666;
|
||||
}
|
||||
h3 {
|
||||
color: #106ebe;
|
||||
text-decoration: none;
|
||||
margin: 0;
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
li {
|
||||
display: inline-block;
|
||||
}
|
||||
li:last-child {
|
||||
&:before {
|
||||
content: "\2022";
|
||||
display: inline-block;
|
||||
margin: 0 .5em;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
import * as React from 'react';
|
||||
import ISearchResultProps from './ISearchResultProps';
|
||||
import styles from './SearchResult.module.scss';
|
||||
import { PersonaCoin } from 'office-ui-fabric-react/lib/PersonaCoin';
|
||||
import * as moment from 'moment';
|
||||
|
||||
export default class SearchResult extends React.Component<ISearchResultProps, {}> {
|
||||
public render() {
|
||||
return (
|
||||
<div className="template_root">
|
||||
<ul className={styles.resultContainer}>
|
||||
{this.props.searchResults.RelevantResults.map(result => {
|
||||
const image = result.SiteLogo ?
|
||||
<div className={styles.imageContainer}><img src={result.SiteLogo}></img></div> :
|
||||
<PersonaCoin className={styles.coin} text={result.Title} />;
|
||||
return (
|
||||
<li className={styles.resultItem}>
|
||||
<a href={result.Path}>
|
||||
{image}
|
||||
<article>
|
||||
<header>
|
||||
<h3>{result.Title}</h3>
|
||||
<p>{result[this.props.subheaderFieldName]}</p>
|
||||
<ul>
|
||||
<li>Oppdatert for {result.Updated ? this.fmtDateString(result.Updated) : this.fmtDateString(result.Created)}</li>
|
||||
<li>{result.CreatedBy}</li>
|
||||
</ul>
|
||||
</header>
|
||||
</article>
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private fmtDateString(utcString) {
|
||||
return moment(utcString).fromNow();
|
||||
}
|
||||
}
|
4
samples/react-search-refiners-renderer/src/extensions/codeRenderer/loc/en-us.js
vendored
Normal file
4
samples/react-search-refiners-renderer/src/extensions/codeRenderer/loc/en-us.js
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
define([], function() {
|
||||
return {
|
||||
}
|
||||
});
|
7
samples/react-search-refiners-renderer/src/extensions/codeRenderer/loc/myStrings.d.ts
vendored
Normal file
7
samples/react-search-refiners-renderer/src/extensions/codeRenderer/loc/myStrings.d.ts
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
declare interface ICodeRendererApplicationCustomizerStrings {
|
||||
}
|
||||
|
||||
declare module 'CodeRendererApplicationCustomizerStrings' {
|
||||
const strings: ICodeRendererApplicationCustomizerStrings;
|
||||
export = strings;
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
// A file is required to be in the root of the /src directory by the TypeScript compiler
|
|
@ -0,0 +1,34 @@
|
|||
export interface ISearchResults {
|
||||
RelevantResults: ISearchResult[];
|
||||
RefinementResults: IRefinementResult[];
|
||||
PromotedResults?: IPromotedResult[];
|
||||
TotalRows?: number;
|
||||
}
|
||||
|
||||
export interface ISearchResult {
|
||||
[key: string]: string;
|
||||
IconSrc?: string;
|
||||
}
|
||||
|
||||
export interface IRefinementResult {
|
||||
FilterName: string;
|
||||
Values: IRefinementValue[];
|
||||
}
|
||||
|
||||
export interface IPromotedResult {
|
||||
Url: string;
|
||||
Title: string;
|
||||
Description: string;
|
||||
}
|
||||
|
||||
export interface IRefinementValue {
|
||||
RefinementCount: number;
|
||||
RefinementName: string;
|
||||
RefinementToken: string;
|
||||
RefinementValue: string;
|
||||
}
|
||||
|
||||
export interface IRefinementFilter {
|
||||
FilterName: string;
|
||||
Value: IRefinementValue;
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
import { ISearchResults } from "../../models/ISearchResult";
|
||||
import { IRenderer } from "./ResultService";
|
||||
import {ICustomTemplateFieldValue} from './ResultService';
|
||||
|
||||
export default interface IResultService {
|
||||
/**
|
||||
* Persists the results to the local storage and fires and update event.
|
||||
* @param results The new results
|
||||
* @param rendererId The Id of the custom action chosen to render the resultdata.
|
||||
* @param mountNode The name of the html node which the renderers should use to display the results
|
||||
*/
|
||||
updateResultData(results: ISearchResults, rendererId: string, mountNode: string, customTemplateFieldValues?: ICustomTemplateFieldValue[]);
|
||||
|
||||
/**
|
||||
* Registerer the renderer as an renderer to be picked up by the search-refiners webpart.
|
||||
* @param rendererId The id of the renderer
|
||||
* @param rendererName The name that should be displayed in the search-refiners webpart
|
||||
* @param rendererIcon The office-ui-fabric icon to be displayed.
|
||||
* @param callback The function that should run whenever the renderer recieves data
|
||||
*/
|
||||
registerRenderer(rendererId: string, rendererName: string, rendererIcon: string, callback: (e) => void, customFields?: string[]);
|
||||
|
||||
/**
|
||||
* Get all registered renderers on the current page.
|
||||
*/
|
||||
getRegisteredRenderers(): IRenderer[];
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
import { ISearchResults } from "../../models/ISearchResult";
|
||||
import IResultService from "./IResultService";
|
||||
import 'custom-event-polyfill';
|
||||
|
||||
export interface ISearchEvent extends CustomEvent {
|
||||
rendererId?: string;
|
||||
results?: ISearchResults;
|
||||
mountNode?: string;
|
||||
customTemplateFieldValues?: ICustomTemplateFieldValue[];
|
||||
}
|
||||
|
||||
export interface IRenderer {
|
||||
id: string;
|
||||
name: string;
|
||||
icon: string;
|
||||
customFields?: string[];
|
||||
}
|
||||
|
||||
export interface ICustomTemplateFieldValue {
|
||||
fieldName: string;
|
||||
searchProperty: string;
|
||||
}
|
||||
|
||||
export class ResultService implements IResultService {
|
||||
private SEARCH_CHANGED_EVENT_NAME: string = "pnp-spfx-search-changed";
|
||||
private SEARCH_RENDERERS_OBJECT_NAME: string = "pnp-spfx-search-renderers";
|
||||
|
||||
public updateResultData(results: ISearchResults, rendererId: string, mountNode: string, customTemplateFieldValues?: ICustomTemplateFieldValue[]) {
|
||||
let searchEvent: ISearchEvent = new CustomEvent(this.SEARCH_CHANGED_EVENT_NAME);
|
||||
searchEvent.rendererId = rendererId;
|
||||
searchEvent.results = results;
|
||||
searchEvent.mountNode = mountNode;
|
||||
searchEvent.customTemplateFieldValues = customTemplateFieldValues;
|
||||
window.dispatchEvent(searchEvent);
|
||||
}
|
||||
|
||||
public registerRenderer(rendererId: string, rendererName: string, rendererIcon: string, callback: (e: ISearchEvent) => void, customFields?: string[]): void {
|
||||
const newRenderer = {
|
||||
id: rendererId,
|
||||
name: rendererName,
|
||||
icon: rendererIcon,
|
||||
customFields: customFields
|
||||
};
|
||||
if( window[this.SEARCH_RENDERERS_OBJECT_NAME] === undefined) {
|
||||
window[this.SEARCH_RENDERERS_OBJECT_NAME] = [newRenderer];
|
||||
} else {
|
||||
window[this.SEARCH_RENDERERS_OBJECT_NAME].push(newRenderer);
|
||||
}
|
||||
addEventListener(this.SEARCH_CHANGED_EVENT_NAME, (e: ISearchEvent) => this.handleNewDataRegistered(e, rendererId, callback));
|
||||
}
|
||||
|
||||
public getRegisteredRenderers(): IRenderer[] {
|
||||
return window[this.SEARCH_RENDERERS_OBJECT_NAME];
|
||||
}
|
||||
|
||||
private handleNewDataRegistered(e: ISearchEvent, rendererId, callback: (e) => void ) {
|
||||
if(e.rendererId === rendererId) {
|
||||
callback(e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"jsx": "react",
|
||||
"declaration": true,
|
||||
"sourceMap": true,
|
||||
"experimentalDecorators": true,
|
||||
"skipLibCheck": true,
|
||||
"outDir": "lib",
|
||||
"typeRoots": [
|
||||
"./node_modules/@types",
|
||||
"./node_modules/@microsoft"
|
||||
],
|
||||
"types": [
|
||||
"es6-promise",
|
||||
"webpack-env"
|
||||
],
|
||||
"lib": [
|
||||
"es5",
|
||||
"dom",
|
||||
"es2015.collection"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"lib"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"extends": "@microsoft/sp-tslint-rules/base-tslint.json",
|
||||
"rules": {
|
||||
"class-name": false,
|
||||
"export-name": false,
|
||||
"forin": false,
|
||||
"label-position": false,
|
||||
"member-access": true,
|
||||
"no-arg": false,
|
||||
"no-console": false,
|
||||
"no-construct": false,
|
||||
"no-duplicate-variable": true,
|
||||
"no-eval": false,
|
||||
"no-function-expression": true,
|
||||
"no-internal-module": true,
|
||||
"no-shadowed-variable": true,
|
||||
"no-switch-case-fall-through": true,
|
||||
"no-unnecessary-semicolons": true,
|
||||
"no-unused-expression": true,
|
||||
"no-use-before-declare": true,
|
||||
"no-with-statement": true,
|
||||
"semicolon": true,
|
||||
"trailing-comma": false,
|
||||
"typedef": false,
|
||||
"typedef-whitespace": false,
|
||||
"use-named-parameter": true,
|
||||
"variable-name": false,
|
||||
"whitespace": false
|
||||
}
|
||||
}
|
|
@ -246,34 +246,7 @@ Handlebars [partials](https://handlebarsjs.com/partials.html) are used behind th
|
|||
#### Custom code renderers
|
||||
You may also define your own renderers, which most often should be SPFx application customizers. These should use the resultservice to register themselves as renderers, and will upon registration be available as a rendering choice in the "Result Layouts" Section.
|
||||
|
||||
<p align="center">
|
||||
<img width="500px" src="./images/results_layout.PNG"/>
|
||||
</p>
|
||||
|
||||
When registering new renderers, must include:
|
||||
- The component id
|
||||
- The name of the renderer (displayed in the settings panel of the react-search-refiners webpart)
|
||||
- The icon of the renderer (displayed in the settings panel of the react-search-refiners webpart)
|
||||
- The Update-function that takes care of any updates in the results.
|
||||
- A list of properties that may be selected for usage of template funcitonality (a list of strings that should be descriptions of the template fields).
|
||||
|
||||
A typical registration looks like this:
|
||||
```
|
||||
this._resultService = new ResultService();
|
||||
this.onChangeHappened.bind(this);
|
||||
this._resultService.registerRenderer(this.componentId, 'Defaultrenderer', 'QueryList', this.onChangeHappened, ['subheader']);
|
||||
```
|
||||
|
||||
#### Custom code renderer template fields
|
||||
If you register fields as template fields in your renderer, they will become editable through a menu in the webpart.
|
||||
|
||||
The users may choose what values to display from a dropdown, which is populated by properties chosen in the "Selected properties" field in the webpart settings.
|
||||
<p align="center">
|
||||
<img width="5000px" src="./images/custom_template_fields_selection.PNG"/>
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
More information about custom code renderers may be found in a [seperate sample](../react-search-refiners-renderer), which showcases such a renderer.
|
||||
|
||||
#### Query variables
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "pnp-react-search-refiners",
|
||||
"version": "2.3.0",
|
||||
"version": "2.4.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
@ -5092,6 +5092,11 @@
|
|||
"integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=",
|
||||
"dev": true
|
||||
},
|
||||
"custom-event-polyfill": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/custom-event-polyfill/-/custom-event-polyfill-1.0.6.tgz",
|
||||
"integrity": "sha512-3FxpFlzGcHrDykwWu+xWVXZ8PfykM/9/bI3zXb953sh+AjInZWcQmrnmvPoZgiqNjmbtTm10PWvYqvRW527x6g=="
|
||||
},
|
||||
"d": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "http://registry.npmjs.org/d/-/d-1.0.0.tgz",
|
||||
|
@ -7137,7 +7142,7 @@
|
|||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"safer-buffer": "^2.1.0"
|
||||
"safer-buffer": "2.1.2"
|
||||
}
|
||||
},
|
||||
"ignore-walk": {
|
||||
|
@ -7175,7 +7180,7 @@
|
|||
"bundled": true,
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"number-is-nan": "^1.0.0"
|
||||
"number-is-nan": "1.0.1"
|
||||
}
|
||||
},
|
||||
"isarray": {
|
||||
|
@ -7235,9 +7240,9 @@
|
|||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"debug": "^2.1.2",
|
||||
"iconv-lite": "^0.4.4",
|
||||
"sax": "^1.2.4"
|
||||
"debug": "2.6.9",
|
||||
"iconv-lite": "0.4.21",
|
||||
"sax": "1.2.4"
|
||||
}
|
||||
},
|
||||
"node-pre-gyp": {
|
||||
|
@ -7264,8 +7269,8 @@
|
|||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"abbrev": "1",
|
||||
"osenv": "^0.1.4"
|
||||
"abbrev": "1.1.1",
|
||||
"osenv": "0.1.5"
|
||||
}
|
||||
},
|
||||
"npm-bundled": {
|
||||
|
@ -7280,8 +7285,8 @@
|
|||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ignore-walk": "^3.0.1",
|
||||
"npm-bundled": "^1.0.1"
|
||||
"ignore-walk": "3.0.1",
|
||||
"npm-bundled": "1.0.3"
|
||||
}
|
||||
},
|
||||
"npmlog": {
|
||||
|
@ -7333,8 +7338,8 @@
|
|||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"os-homedir": "^1.0.0",
|
||||
"os-tmpdir": "^1.0.0"
|
||||
"os-homedir": "1.0.2",
|
||||
"os-tmpdir": "1.0.2"
|
||||
}
|
||||
},
|
||||
"path-is-absolute": {
|
||||
|
@ -7355,10 +7360,10 @@
|
|||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"deep-extend": "^0.5.1",
|
||||
"ini": "~1.3.0",
|
||||
"minimist": "^1.2.0",
|
||||
"strip-json-comments": "~2.0.1"
|
||||
"deep-extend": "0.5.1",
|
||||
"ini": "1.3.5",
|
||||
"minimist": "1.2.0",
|
||||
"strip-json-comments": "2.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"minimist": {
|
||||
|
@ -7433,9 +7438,9 @@
|
|||
"bundled": true,
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"code-point-at": "^1.0.0",
|
||||
"is-fullwidth-code-point": "^1.0.0",
|
||||
"strip-ansi": "^3.0.0"
|
||||
"code-point-at": "1.1.0",
|
||||
"is-fullwidth-code-point": "1.0.0",
|
||||
"strip-ansi": "3.0.1"
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
|
@ -7444,7 +7449,7 @@
|
|||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
"safe-buffer": "5.1.1"
|
||||
}
|
||||
},
|
||||
"strip-ansi": {
|
||||
|
@ -7452,7 +7457,7 @@
|
|||
"bundled": true,
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^2.0.0"
|
||||
"ansi-regex": "2.1.1"
|
||||
}
|
||||
},
|
||||
"strip-json-comments": {
|
||||
|
|
Loading…
Reference in New Issue