Merge branch 'dev'
|
@ -1,25 +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
|
||||
# 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
|
|
@ -1,32 +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
|
||||
# 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
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
{
|
||||
"@microsoft/generator-sharepoint": {
|
||||
"isCreatingSolution": true,
|
||||
"environment": "spo",
|
||||
"version": "1.6.0",
|
||||
"libraryName": "dynamic-bundling",
|
||||
"libraryId": "c40970b0-7ef0-40f2-b03b-718751fbc4b5",
|
||||
"packageManager": "npm",
|
||||
"componentType": "webpart"
|
||||
}
|
||||
{
|
||||
"@microsoft/generator-sharepoint": {
|
||||
"isCreatingSolution": true,
|
||||
"environment": "spo",
|
||||
"version": "1.7.0",
|
||||
"libraryName": "dynamic-bundling",
|
||||
"libraryId": "c40970b0-7ef0-40f2-b03b-718751fbc4b5",
|
||||
"packageManager": "npm",
|
||||
"componentType": "webpart",
|
||||
"isDomainIsolated": false
|
||||
}
|
||||
}
|
|
@ -1,59 +1,60 @@
|
|||
# title of the sample
|
||||
|
||||
## Summary
|
||||
This sample illustrates how SPFx functionality and packages can be bundled in multiple '.js' files then be dynamically & asynchronously loaded into the page at execution time, such as with a button click.
|
||||
|
||||
Pre Button Click:
|
||||
![preview](./assets/WebPart-Preview-PreClick.jpg)
|
||||
|
||||
Post Button Click that imports jQuery and additional functionality:
|
||||
![preview](./assets/WebPart-Preview-PostjQueryClick.jpg)
|
||||
|
||||
## Used SharePoint Framework Version
|
||||
![drop](https://img.shields.io/badge/drop-1.6.0-blue.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)
|
||||
|
||||
|
||||
## Solution
|
||||
|
||||
Solution|Author(s)
|
||||
--------|---------
|
||||
js-dynamic-bundling-libaries | David Warner II ([@DavidWarnerII](https://twitter.com/davidwarnerii) / [Warner Digital](http://warner.digital))
|
||||
|
||||
## Version history
|
||||
|
||||
Version|Date|Comments
|
||||
-------|----|--------
|
||||
1.0|September 21, 2018|Initial release
|
||||
|
||||
## Disclaimer
|
||||
**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
|
||||
|
||||
---
|
||||
|
||||
## Minimal Path to Awesome
|
||||
|
||||
- Clone this repository
|
||||
- in the command line run:
|
||||
- `npm install`
|
||||
- `gulp serve`
|
||||
|
||||
|
||||
|
||||
## Features
|
||||
This Web Part illustrates the following concepts on top of the SharePoint Framework:
|
||||
|
||||
- How to separate SPFx functionality into multiple bundled files
|
||||
- How to asynchronously load the seperate bundled files at execution time
|
||||
- Including a library in the separate bundled file.
|
||||
|
||||
## Additional Information:
|
||||
- [Dynamic loading of packages in SharePoint Framework](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/dynamic-loading)
|
||||
|
||||
- Video Demonstration on using, building and code specifics for the sample web part:<br> http://warner.digital/dynamic-spfx-package-bundling/
|
||||
|
||||
# title of the sample
|
||||
|
||||
## Summary
|
||||
This sample illustrates how SPFx functionality and packages can be bundled in multiple '.js' files then be dynamically & asynchronously loaded into the page at execution time, such as with a button click.
|
||||
|
||||
Pre Button Click:
|
||||
![preview](./assets/WebPart-Preview-PreClick.jpg)
|
||||
|
||||
Post Button Click that imports jQuery and additional functionality:
|
||||
![preview](./assets/WebPart-Preview-PostjQueryClick.jpg)
|
||||
|
||||
## Used SharePoint Framework Version
|
||||
![drop](https://img.shields.io/badge/drop-1.7.0-orange.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)
|
||||
|
||||
|
||||
## Solution
|
||||
|
||||
Solution|Author(s)
|
||||
--------|---------
|
||||
js-dynamic-bundling-libaries | David Warner II ([@DavidWarnerII](https://twitter.com/davidwarnerii) / [Warner Digital](http://warner.digital))
|
||||
|
||||
## Version history
|
||||
|
||||
Version|Date|Comments
|
||||
-------|----|--------
|
||||
1.0|September 21, 2018|Initial release
|
||||
1.1|December 3, 2018|Updated for SPFx 1.7.0
|
||||
|
||||
## Disclaimer
|
||||
**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
|
||||
|
||||
---
|
||||
|
||||
## Minimal Path to Awesome
|
||||
|
||||
- Clone this repository
|
||||
- in the command line run:
|
||||
- `npm install`
|
||||
- `gulp serve`
|
||||
|
||||
|
||||
|
||||
## Features
|
||||
This Web Part illustrates the following concepts on top of the SharePoint Framework:
|
||||
|
||||
- How to separate SPFx functionality into multiple bundled files
|
||||
- How to asynchronously load the seperate bundled files at execution time
|
||||
- Including a library in the separate bundled file.
|
||||
|
||||
## Additional Information:
|
||||
- [Dynamic loading of packages in SharePoint Framework](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/dynamic-loading)
|
||||
|
||||
- Video Demonstration on using, building and code specifics for the sample web part:<br> http://warner.digital/dynamic-spfx-package-bundling/
|
||||
|
||||
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/readme-template" />
|
|
@ -1,18 +1,18 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
|
||||
"version": "2.0",
|
||||
"bundles": {
|
||||
"dynamic-bundling-example-web-part": {
|
||||
"components": [
|
||||
{
|
||||
"entrypoint": "./lib/webparts/dynamicBundlingExample/DynamicBundlingExampleWebPart.js",
|
||||
"manifest": "./src/webparts/dynamicBundlingExample/DynamicBundlingExampleWebPart.manifest.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"externals": {},
|
||||
"localizedResources": {
|
||||
"DynamicBundlingExampleWebPartStrings": "lib/webparts/dynamicBundlingExample/loc/{locale}.js"
|
||||
}
|
||||
}
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
|
||||
"version": "2.0",
|
||||
"bundles": {
|
||||
"dynamic-bundling-example-web-part": {
|
||||
"components": [
|
||||
{
|
||||
"entrypoint": "./lib/webparts/dynamicBundlingExample/DynamicBundlingExampleWebPart.js",
|
||||
"manifest": "./src/webparts/dynamicBundlingExample/DynamicBundlingExampleWebPart.manifest.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"externals": {},
|
||||
"localizedResources": {
|
||||
"DynamicBundlingExampleWebPartStrings": "lib/webparts/dynamicBundlingExample/loc/{locale}.js"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
|
||||
"deployCdnPath": "temp/deploy"
|
||||
}
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
|
||||
"deployCdnPath": "temp/deploy"
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
|
||||
"workingDir": "./temp/deploy/",
|
||||
"account": "<!-- STORAGE ACCOUNT NAME -->",
|
||||
"container": "dynamic-bundling",
|
||||
"accessKey": "<!-- ACCESS KEY -->"
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
|
||||
"workingDir": "./temp/deploy/",
|
||||
"account": "<!-- STORAGE ACCOUNT NAME -->",
|
||||
"container": "dynamic-bundling",
|
||||
"accessKey": "<!-- ACCESS KEY -->"
|
||||
}
|
|
@ -1,12 +1,13 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||
"solution": {
|
||||
"name": "dynamic-bundling-client-side-solution",
|
||||
"id": "c40970b0-7ef0-40f2-b03b-718751fbc4b5",
|
||||
"version": "1.0.0.0",
|
||||
"includeClientSideAssets": true
|
||||
},
|
||||
"paths": {
|
||||
"zippedPackage": "solution/dynamic-bundling.sppkg"
|
||||
}
|
||||
}
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||
"solution": {
|
||||
"name": "dynamic-bundling-client-side-solution",
|
||||
"id": "c40970b0-7ef0-40f2-b03b-718751fbc4b5",
|
||||
"version": "1.1.0.0",
|
||||
"includeClientSideAssets": true,
|
||||
"isDomainIsolated": false
|
||||
},
|
||||
"paths": {
|
||||
"zippedPackage": "solution/dynamic-bundling.sppkg"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
|
||||
"port": 4321,
|
||||
"https": true,
|
||||
"initialPage": "https://localhost:5432/workbench",
|
||||
"api": {
|
||||
"port": 5432,
|
||||
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
|
||||
}
|
||||
}
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
|
||||
"port": 4321,
|
||||
"https": true,
|
||||
"initialPage": "https://localhost:5432/workbench",
|
||||
"api": {
|
||||
"port": 5432,
|
||||
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
|
||||
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
|
||||
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||
}
|
|
@ -1,7 +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);
|
||||
'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);
|
||||
|
|
|
@ -11,23 +11,22 @@
|
|||
"test": "gulp test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@microsoft/sp-core-library": "1.6.0",
|
||||
"@microsoft/sp-lodash-subset": "1.6.0",
|
||||
"@microsoft/sp-office-ui-fabric-core": "1.6.0",
|
||||
"@microsoft/sp-webpart-base": "1.6.0",
|
||||
"@microsoft/sp-core-library": "1.7.0",
|
||||
"@microsoft/sp-lodash-subset": "1.7.0",
|
||||
"@microsoft/sp-office-ui-fabric-core": "1.7.0",
|
||||
"@microsoft/sp-webpart-base": "1.7.0",
|
||||
"@types/es6-promise": "0.0.33",
|
||||
"@types/jquery": "^3.3.6",
|
||||
"@types/webpack-env": "1.13.1",
|
||||
"jquery": "^3.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/sp-build-web": "1.6.0",
|
||||
"@microsoft/sp-module-interfaces": "1.6.0",
|
||||
"@microsoft/sp-webpart-workbench": "1.6.0",
|
||||
"tslint-microsoft-contrib": "~5.0.0",
|
||||
"gulp": "~3.9.1",
|
||||
"@microsoft/sp-build-web": "1.7.0",
|
||||
"@microsoft/sp-module-interfaces": "1.7.0",
|
||||
"@microsoft/sp-webpart-workbench": "1.7.0",
|
||||
"@types/chai": "3.4.34",
|
||||
"@types/mocha": "2.2.38",
|
||||
"ajv": "~5.2.2"
|
||||
"ajv": "~5.2.2",
|
||||
"gulp": "~3.9.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
// A file is required to be in the root of the /src directory by the TypeScript compiler
|
||||
// A file is required to be in the root of the /src directory by the TypeScript compiler
|
||||
|
|
|
@ -1,26 +1,26 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
|
||||
"id": "5378270a-cb87-4be2-b3b7-0ddf597fd77d",
|
||||
"alias": "DynamicBundlingExampleWebPart",
|
||||
"componentType": "WebPart",
|
||||
|
||||
// The "*" signifies that the version should be taken from the package.json
|
||||
"version": "*",
|
||||
"manifestVersion": 2,
|
||||
|
||||
// If true, the component can only be installed on sites where Custom Script is allowed.
|
||||
// Components that allow authors to embed arbitrary script code should set this to true.
|
||||
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
|
||||
"requiresCustomScript": false,
|
||||
|
||||
"preconfiguredEntries": [{
|
||||
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
|
||||
"group": { "default": "Other" },
|
||||
"title": { "default": "Dynamic Bundling Example" },
|
||||
"description": { "default": "Dynamic Bundling Example description" },
|
||||
"officeFabricIconFontName": "Page",
|
||||
"properties": {
|
||||
"description": "Dynamic Bundling Example"
|
||||
}
|
||||
}]
|
||||
}
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
|
||||
"id": "5378270a-cb87-4be2-b3b7-0ddf597fd77d",
|
||||
"alias": "DynamicBundlingExampleWebPart",
|
||||
"componentType": "WebPart",
|
||||
|
||||
// The "*" signifies that the version should be taken from the package.json
|
||||
"version": "*",
|
||||
"manifestVersion": 2,
|
||||
|
||||
// If true, the component can only be installed on sites where Custom Script is allowed.
|
||||
// Components that allow authors to embed arbitrary script code should set this to true.
|
||||
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
|
||||
"requiresCustomScript": false,
|
||||
|
||||
"preconfiguredEntries": [{
|
||||
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
|
||||
"group": { "default": "Other" },
|
||||
"title": { "default": "Dynamic Bundling Example" },
|
||||
"description": { "default": "Dynamic Bundling Example description" },
|
||||
"officeFabricIconFontName": "Page",
|
||||
"properties": {
|
||||
"description": "Dynamic Bundling Example"
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
|
|
@ -1,77 +1,77 @@
|
|||
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
|
||||
|
||||
.dynamicBundlingExample {
|
||||
.container {
|
||||
max-width: 700px;
|
||||
margin: 0px auto;
|
||||
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.row {
|
||||
@include ms-Grid-row;
|
||||
@include ms-fontColor-white;
|
||||
background-color: $ms-color-themeDark;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.column {
|
||||
@include ms-Grid-col;
|
||||
@include ms-lg10;
|
||||
@include ms-xl8;
|
||||
@include ms-xlPush2;
|
||||
@include ms-lgPush1;
|
||||
width:90% !important;
|
||||
}
|
||||
|
||||
.title {
|
||||
@include ms-font-xl;
|
||||
@include ms-fontColor-white;
|
||||
}
|
||||
|
||||
.subTitle {
|
||||
@include ms-font-l;
|
||||
@include ms-fontColor-white;
|
||||
width:100% !important;
|
||||
|
||||
}
|
||||
|
||||
.description {
|
||||
@include ms-font-l;
|
||||
@include ms-fontColor-white;
|
||||
}
|
||||
|
||||
.button {
|
||||
// Our button
|
||||
text-decoration: none;
|
||||
height: 32px;
|
||||
|
||||
// Primary Button
|
||||
min-width: 80px;
|
||||
background-color: $ms-color-themePrimary;
|
||||
border-color: $ms-color-themePrimary;
|
||||
color: $ms-color-white;
|
||||
|
||||
// Basic Button
|
||||
outline: transparent;
|
||||
position: relative;
|
||||
font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
font-size: $ms-font-size-m;
|
||||
font-weight: $ms-font-weight-regular;
|
||||
border-width: 0;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
padding: 0 16px;
|
||||
|
||||
.label {
|
||||
font-weight: $ms-font-weight-semibold;
|
||||
font-size: $ms-font-size-m;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
margin: 0 4px;
|
||||
vertical-align: top;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
|
||||
|
||||
.dynamicBundlingExample {
|
||||
.container {
|
||||
max-width: 700px;
|
||||
margin: 0px auto;
|
||||
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.row {
|
||||
@include ms-Grid-row;
|
||||
@include ms-fontColor-white;
|
||||
background-color: $ms-color-themeDark;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.column {
|
||||
@include ms-Grid-col;
|
||||
@include ms-lg10;
|
||||
@include ms-xl8;
|
||||
@include ms-xlPush2;
|
||||
@include ms-lgPush1;
|
||||
width:90% !important;
|
||||
}
|
||||
|
||||
.title {
|
||||
@include ms-font-xl;
|
||||
@include ms-fontColor-white;
|
||||
}
|
||||
|
||||
.subTitle {
|
||||
@include ms-font-l;
|
||||
@include ms-fontColor-white;
|
||||
width:100% !important;
|
||||
|
||||
}
|
||||
|
||||
.description {
|
||||
@include ms-font-l;
|
||||
@include ms-fontColor-white;
|
||||
}
|
||||
|
||||
.button {
|
||||
// Our button
|
||||
text-decoration: none;
|
||||
height: 32px;
|
||||
|
||||
// Primary Button
|
||||
min-width: 80px;
|
||||
background-color: $ms-color-themePrimary;
|
||||
border-color: $ms-color-themePrimary;
|
||||
color: $ms-color-white;
|
||||
|
||||
// Basic Button
|
||||
outline: transparent;
|
||||
position: relative;
|
||||
font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
font-size: $ms-font-size-m;
|
||||
font-weight: $ms-font-weight-regular;
|
||||
border-width: 0;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
padding: 0 16px;
|
||||
|
||||
.label {
|
||||
font-weight: $ms-font-weight-semibold;
|
||||
font-size: $ms-font-size-m;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
margin: 0 4px;
|
||||
vertical-align: top;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,101 +1,101 @@
|
|||
import { Version } from '@microsoft/sp-core-library';
|
||||
import {
|
||||
BaseClientSideWebPart,
|
||||
IPropertyPaneConfiguration,
|
||||
PropertyPaneTextField
|
||||
} from '@microsoft/sp-webpart-base';
|
||||
import { escape } from '@microsoft/sp-lodash-subset';
|
||||
|
||||
import styles from './DynamicBundlingExampleWebPart.module.scss';
|
||||
import * as strings from 'DynamicBundlingExampleWebPartStrings';
|
||||
|
||||
export interface IDynamicBundlingExampleWebPartProps {
|
||||
description: string;
|
||||
}
|
||||
|
||||
export default class DynamicBundlingExampleWebPart extends BaseClientSideWebPart<IDynamicBundlingExampleWebPartProps> {
|
||||
|
||||
public render(): void {
|
||||
this.domElement.innerHTML = `
|
||||
<div class="${ styles.dynamicBundlingExample }">
|
||||
<div class="${ styles.container }">
|
||||
<div class="${ styles.row }">
|
||||
<div class="${ styles.column }">
|
||||
<span class="${ styles.title }">Dynamic Bundling & Loading of SPFx Packages</span>
|
||||
<p class="${ styles.subTitle }">When the buttons below are clicked, the associated ".ts" file and it's functionality will be loaded.</p>
|
||||
<a id="ImportButton" href="#" class="${ styles.button }">
|
||||
<span class="${ styles.label }">Dynamically Import Functionality</span>
|
||||
</a>
|
||||
|
||||
<a id="ImportButtonJQuery" href="#" class="${ styles.button }">
|
||||
<span class="${ styles.label }">Dynamically Import Functionality w/ jQuery</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="insertHTML"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
this.setupClickEvent();
|
||||
}
|
||||
|
||||
protected get dataVersion(): Version {
|
||||
return Version.parse('1.0');
|
||||
}
|
||||
|
||||
// Create a click event listener for the ImportButton Anchor element
|
||||
public setupClickEvent(): void {
|
||||
let btn = document.getElementById("ImportButton");
|
||||
btn.addEventListener("click", (e:Event) => this.dynamicallyImportFunctions());
|
||||
|
||||
let btnjQuery = document.getElementById("ImportButtonJQuery");
|
||||
btnjQuery.addEventListener("click", (e:Event) => this.dynamicallyImportFunctionsJQuery());
|
||||
}
|
||||
|
||||
// This will setup the 'DynamicImportedFunctions.ts' to be bundled into a separate .js file.
|
||||
protected async dynamicallyImportFunctions() {
|
||||
const importedTS = await import(
|
||||
'./DynamicImportedFunctions'
|
||||
)
|
||||
importedTS.default.executeDynamicLoad();
|
||||
}
|
||||
|
||||
//This will setup the 'DynamicImportedFunctionsJQuery.ts' to be bundled into a separate .js file.
|
||||
protected async dynamicallyImportFunctionsJQuery() {
|
||||
const importedJQueryTS = await import(
|
||||
'./DynamicImportedFunctionsJQuery'
|
||||
)
|
||||
importedJQueryTS.default.executeDynamicLoadJQuery();
|
||||
importedJQueryTS.default.addToDOM(this.domElement);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
||||
return {
|
||||
pages: [
|
||||
{
|
||||
header: {
|
||||
description: strings.PropertyPaneDescription
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
groupName: strings.BasicGroupName,
|
||||
groupFields: [
|
||||
PropertyPaneTextField('description', {
|
||||
label: strings.DescriptionFieldLabel
|
||||
})
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
import { Version } from '@microsoft/sp-core-library';
|
||||
import {
|
||||
BaseClientSideWebPart,
|
||||
IPropertyPaneConfiguration,
|
||||
PropertyPaneTextField
|
||||
} from '@microsoft/sp-webpart-base';
|
||||
import { escape } from '@microsoft/sp-lodash-subset';
|
||||
|
||||
import styles from './DynamicBundlingExampleWebPart.module.scss';
|
||||
import * as strings from 'DynamicBundlingExampleWebPartStrings';
|
||||
|
||||
export interface IDynamicBundlingExampleWebPartProps {
|
||||
description: string;
|
||||
}
|
||||
|
||||
export default class DynamicBundlingExampleWebPart extends BaseClientSideWebPart<IDynamicBundlingExampleWebPartProps> {
|
||||
|
||||
public render(): void {
|
||||
this.domElement.innerHTML = `
|
||||
<div class="${ styles.dynamicBundlingExample }">
|
||||
<div class="${ styles.container }">
|
||||
<div class="${ styles.row }">
|
||||
<div class="${ styles.column }">
|
||||
<span class="${ styles.title }">Dynamic Bundling & Loading of SPFx Packages</span>
|
||||
<p class="${ styles.subTitle }">When the buttons below are clicked, the associated ".ts" file and it's functionality will be loaded.</p>
|
||||
<a id="ImportButton" href="#" class="${ styles.button }">
|
||||
<span class="${ styles.label }">Dynamically Import Functionality</span>
|
||||
</a>
|
||||
|
||||
<a id="ImportButtonJQuery" href="#" class="${ styles.button }">
|
||||
<span class="${ styles.label }">Dynamically Import Functionality w/ jQuery</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="insertHTML"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
this.setupClickEvent();
|
||||
}
|
||||
|
||||
protected get dataVersion(): Version {
|
||||
return Version.parse('1.0');
|
||||
}
|
||||
|
||||
// Create a click event listener for the ImportButton Anchor element
|
||||
public setupClickEvent(): void {
|
||||
let btn = document.getElementById("ImportButton");
|
||||
btn.addEventListener("click", (e:Event) => this.dynamicallyImportFunctions());
|
||||
|
||||
let btnjQuery = document.getElementById("ImportButtonJQuery");
|
||||
btnjQuery.addEventListener("click", (e:Event) => this.dynamicallyImportFunctionsJQuery());
|
||||
}
|
||||
|
||||
// This will setup the 'DynamicImportedFunctions.ts' to be bundled into a separate .js file.
|
||||
protected async dynamicallyImportFunctions() {
|
||||
const importedTS = await import(
|
||||
'./DynamicImportedFunctions'
|
||||
)
|
||||
importedTS.default.executeDynamicLoad();
|
||||
}
|
||||
|
||||
//This will setup the 'DynamicImportedFunctionsJQuery.ts' to be bundled into a separate .js file.
|
||||
protected async dynamicallyImportFunctionsJQuery() {
|
||||
const importedJQueryTS = await import(
|
||||
'./DynamicImportedFunctionsJQuery'
|
||||
)
|
||||
importedJQueryTS.default.executeDynamicLoadJQuery();
|
||||
importedJQueryTS.default.addToDOM(this.domElement);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
||||
return {
|
||||
pages: [
|
||||
{
|
||||
header: {
|
||||
description: strings.PropertyPaneDescription
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
groupName: strings.BasicGroupName,
|
||||
groupFields: [
|
||||
PropertyPaneTextField('description', {
|
||||
label: strings.DescriptionFieldLabel
|
||||
})
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
/*
|
||||
This file demonstrates how we can separate functionality that might not need to be loaded with the SPFx Web Part or Extension immediately, but be bundled into a separate .js file and loaded dynamically.
|
||||
|
||||
This function will write a message to the console when it is dynamcially loaded.
|
||||
*/
|
||||
|
||||
|
||||
export default class DynamicFunctions {
|
||||
public static executeDynamicLoad():void {
|
||||
console.log('Dynamic Functionality has been imported');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
This file demonstrates how we can separate functionality that might not need to be loaded with the SPFx Web Part or Extension immediately, but be bundled into a separate .js file and loaded dynamically.
|
||||
|
||||
This function will write a message to the console when it is dynamcially loaded.
|
||||
*/
|
||||
|
||||
|
||||
export default class DynamicFunctions {
|
||||
public static executeDynamicLoad():void {
|
||||
console.log('Dynamic Functionality has been imported');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
.injectedContent {
|
||||
clear:both;
|
||||
padding-top:20px;
|
||||
margin-left:60px;
|
||||
font-size:20px;
|
||||
}
|
||||
.injectedContent {
|
||||
clear:both;
|
||||
padding-top:20px;
|
||||
margin-left:60px;
|
||||
font-size:20px;
|
||||
}
|
||||
|
|
|
@ -1,32 +1,32 @@
|
|||
/*
|
||||
This file demonstrates how we can separate functionality that might not need to be loaded with the SPFx Web Part or Extension immediately, but be bundled into a separate .js file and loaded dynamically.
|
||||
|
||||
We will be bundling jQuery in the imported .ts file.
|
||||
This demonstrates how if we have the need to bundle a third party library, such as jQuery, we can delay the loading of it as well as any additional functionality until aboslutely needed.
|
||||
|
||||
This example will add a CSS class to the <body> element, write a message to the console and inject some HTML letting us know that jQuery has been loaded.
|
||||
*/
|
||||
|
||||
import * as $ from 'jquery';
|
||||
require('./DynamicImportedFunctionsJQuery.scss');
|
||||
|
||||
export default class DynamicFunctionsJQuery {
|
||||
public static executeDynamicLoadJQuery():void {
|
||||
console.log('Dynamic Functionality with jQuery has been imported');
|
||||
$('body').addClass('jQueryImported');
|
||||
|
||||
}
|
||||
|
||||
public static addToDOM(webpart:HTMLElement){
|
||||
//console.log(webpart);
|
||||
var item = webpart.getElementsByClassName('insertHTML')[0];
|
||||
item.innerHTML = `
|
||||
<div class="injectedContent">
|
||||
<span class="">jQuery has been dynamically imported.</span>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
This file demonstrates how we can separate functionality that might not need to be loaded with the SPFx Web Part or Extension immediately, but be bundled into a separate .js file and loaded dynamically.
|
||||
|
||||
We will be bundling jQuery in the imported .ts file.
|
||||
This demonstrates how if we have the need to bundle a third party library, such as jQuery, we can delay the loading of it as well as any additional functionality until aboslutely needed.
|
||||
|
||||
This example will add a CSS class to the <body> element, write a message to the console and inject some HTML letting us know that jQuery has been loaded.
|
||||
*/
|
||||
|
||||
import * as $ from 'jquery';
|
||||
require('./DynamicImportedFunctionsJQuery.scss');
|
||||
|
||||
export default class DynamicFunctionsJQuery {
|
||||
public static executeDynamicLoadJQuery():void {
|
||||
console.log('Dynamic Functionality with jQuery has been imported');
|
||||
$('body').addClass('jQueryImported');
|
||||
|
||||
}
|
||||
|
||||
public static addToDOM(webpart:HTMLElement){
|
||||
//console.log(webpart);
|
||||
var item = webpart.getElementsByClassName('insertHTML')[0];
|
||||
item.innerHTML = `
|
||||
<div class="injectedContent">
|
||||
<span class="">jQuery has been dynamically imported.</span>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
define([], function() {
|
||||
return {
|
||||
"PropertyPaneDescription": "Description",
|
||||
"BasicGroupName": "Group Name",
|
||||
"DescriptionFieldLabel": "Description Field"
|
||||
}
|
||||
define([], function() {
|
||||
return {
|
||||
"PropertyPaneDescription": "Description",
|
||||
"BasicGroupName": "Group Name",
|
||||
"DescriptionFieldLabel": "Description Field"
|
||||
}
|
||||
});
|
|
@ -1,10 +1,10 @@
|
|||
declare interface IDynamicBundlingExampleWebPartStrings {
|
||||
PropertyPaneDescription: string;
|
||||
BasicGroupName: string;
|
||||
DescriptionFieldLabel: string;
|
||||
}
|
||||
|
||||
declare module 'DynamicBundlingExampleWebPartStrings' {
|
||||
const strings: IDynamicBundlingExampleWebPartStrings;
|
||||
export = strings;
|
||||
}
|
||||
declare interface IDynamicBundlingExampleWebPartStrings {
|
||||
PropertyPaneDescription: string;
|
||||
BasicGroupName: string;
|
||||
DescriptionFieldLabel: string;
|
||||
}
|
||||
|
||||
declare module 'DynamicBundlingExampleWebPartStrings' {
|
||||
const strings: IDynamicBundlingExampleWebPartStrings;
|
||||
export = strings;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
{
|
||||
"": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.2/MicrosoftTeams.schema.json",
|
||||
"manifestVersion": "1.2",
|
||||
"packageName": "Dynamic Bundling Example",
|
||||
"id": "5378270a-cb87-4be2-b3b7-0ddf597fd77d",
|
||||
"version": "0.1",
|
||||
"developer": {
|
||||
"name": "SPFx + Teams Dev",
|
||||
"websiteUrl": "https://products.office.com/en-us/sharepoint/collaboration",
|
||||
"privacyUrl": "https://privacy.microsoft.com/en-us/privacystatement",
|
||||
"termsOfUseUrl": "https://www.microsoft.com/en-us/servicesagreement"
|
||||
},
|
||||
"name": {
|
||||
"short": "Dynamic Bundling Example"
|
||||
},
|
||||
"description": {
|
||||
"short": "Dynamic Bundling Example description",
|
||||
"full": "Dynamic Bundling Example description"
|
||||
},
|
||||
"icons": {
|
||||
"outline": "tab20x20.png",
|
||||
"color": "tab96x96.png"
|
||||
},
|
||||
"accentColor": "#004578",
|
||||
"configurableTabs": [
|
||||
{
|
||||
"configurationUrl": "https://{teamSiteDomain}{teamSitePath}/_layouts/15/TeamsLogon.aspx?SPFX=true&dest={teamSitePath}/_layouts/15/teamshostedapp.aspx%3FopenPropertyPane=true%26teams%26componentId=5378270a-cb87-4be2-b3b7-0ddf597fd77d",
|
||||
"canUpdateConfiguration": true,
|
||||
"scopes": [
|
||||
"team"
|
||||
]
|
||||
}
|
||||
],
|
||||
"validDomains": [
|
||||
"*.login.microsoftonline.com",
|
||||
"*.sharepoint.com",
|
||||
"*.sharepoint-df.com",
|
||||
"spoppe-a.akamaihd.net",
|
||||
"spoprod-a.akamaihd.net",
|
||||
"resourceseng.blob.core.windows.net",
|
||||
"msft.spoppe.com"
|
||||
],
|
||||
"webApplicationInfo": {
|
||||
"resource": "https://{teamSiteDomain}",
|
||||
"id": "00000003-0000-0ff1-ce00-000000000000"
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 933 B |
After Width: | Height: | Size: 2.5 KiB |
|
@ -1,34 +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"
|
||||
]
|
||||
}
|
||||
{
|
||||
"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"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,32 +1,31 @@
|
|||
{
|
||||
"rulesDirectory": [
|
||||
"tslint-microsoft-contrib"
|
||||
],
|
||||
"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
|
||||
}
|
||||
{
|
||||
"rulesDirectory": [],
|
||||
"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
|
||||
}
|
||||
}
|
|
@ -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.0",
|
||||
"libraryName": "react-adaptive-cards-image-gallery",
|
||||
"libraryId": "2321325f-19a4-4895-8bdd-9a5447d462b1",
|
||||
"packageManager": "npm",
|
||||
"isDomainIsolated": false,
|
||||
"componentType": "webpart"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
## Image Gallery Web Part Built with Adaptive Cards
|
||||
|
||||
### Summary
|
||||
This sample demonstrates the capability of using [Adaptive Cards] (https://adaptivecards.io/) with SharePoint Framework. Adaptive cards are great fit for Bot, however they can be effectively used with SPFx to render the content. This web part helps to display the image gallery from SharePoint list.
|
||||
|
||||
![Web part preview][figure1]
|
||||
|
||||
When added to SharePoint site, the source list containing images information, number of images to display can be configured from web part properties.
|
||||
The sample also provisions the list called "Adaptive Card Images" which can be used as an example to start using the web part.
|
||||
![SharePoint Run][figure2]
|
||||
|
||||
### SharePoint Asset
|
||||
A SharePoint list (named "Adaptive Card Images") is provisioned to store the image information. The schema of the list is as below.
|
||||
![List Schema][figure3]
|
||||
- The "Image Link" column stores the url of image to be displayed in adaptive card.
|
||||
- The "Navigation URL" column represents the url to navigate by clicking on image in adaptive card.
|
||||
- The "Sort Order" column represents the order in which images to be displayed in adaptive card.
|
||||
|
||||
The solution also provisions sample data to the "Adaptive Card Images" list.
|
||||
![List Sample Data][figure4]
|
||||
|
||||
### NPM Packages Used
|
||||
Below NPM packages are used to develop this sample.
|
||||
1. sp-pnp-js (https://www.npmjs.com/package/sp-pnp-js)
|
||||
2. adaptivecards (https://www.npmjs.com/package/adaptivecards)
|
||||
|
||||
## Used SharePoint Framework Version
|
||||
![drop](https://img.shields.io/badge/drop-1.7-green.svg)
|
||||
|
||||
## Applies to
|
||||
|
||||
* [SharePoint Framework Developer Preview](http://dev.office.com/sharepoint/docs/spfx/sharepoint-framework-overview)
|
||||
* [Office 365 developer tenant](http://dev.office.com/sharepoint/docs/spfx/set-up-your-developer-tenant)
|
||||
|
||||
## Solution
|
||||
|
||||
Solution|Author(s)
|
||||
--------|---------
|
||||
react-adaptive-cards-image-gallery|[Nanddeep Nachan](https://www.linkedin.com/in/nanddeepnachan/) (SharePoint Consultant, [@NanddeepNachan](https://http://twitter.com/NanddeepNachan) )
|
||||
|[Ravi Kulkarni](https://www.linkedin.com/in/ravi-kulkarni-a5381723/) (SharePoint Consultant)
|
||||
|
||||
## Version history
|
||||
|
||||
Version|Date|Comments
|
||||
-------|----|--------
|
||||
1.0.0|November 28, 2018|Initial release
|
||||
|
||||
## Disclaimer
|
||||
**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- SharePoint Online tenant
|
||||
- Site Collection created under the **/sites/** or **/**
|
||||
|
||||
## Minimal Path to Awesome
|
||||
|
||||
- Clone this repo
|
||||
- npm i
|
||||
- gulp serve --nobrowser
|
||||
- Open workbench on your tennant, i.e. https://contoso.sharepoint.com/sites/salesteam/_layouts/15/workbench.aspx
|
||||
- Search and add web part "Adaptive Cards Image Gallery"
|
||||
|
||||
## Features
|
||||
This sample web part shows how adaptive cards can be used effectively with SharePoint Framework to render an image gallery with data stored in a SharePoint list.
|
||||
- Integrating adaptive cards
|
||||
- Rendering image gallery
|
||||
- SharePoint assets provisioning
|
||||
- Creating extensible services
|
||||
- Using @sp-pnp-js
|
||||
- Using @adaptivecards
|
||||
|
||||
|
||||
[figure1]: ./assets/webpart-preview.png
|
||||
[figure2]: ./assets/sharepoint-run.gif
|
||||
[figure3]: ./assets/list-schema.png
|
||||
[figure4]: ./assets/list-sample-data.png
|
||||
|
||||
|
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 65 KiB |
After Width: | Height: | Size: 122 KiB |
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
|
||||
"version": "2.0",
|
||||
"bundles": {
|
||||
"adaptive-cards-image-gallery-web-part": {
|
||||
"components": [
|
||||
{
|
||||
"entrypoint": "./lib/webparts/adaptiveCardsImageGallery/AdaptiveCardsImageGalleryWebPart.js",
|
||||
"manifest": "./src/webparts/adaptiveCardsImageGallery/AdaptiveCardsImageGalleryWebPart.manifest.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"externals": {},
|
||||
"localizedResources": {
|
||||
"AdaptiveCardsImageGalleryWebPartStrings": "lib/webparts/adaptiveCardsImageGallery/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-adaptive-cards-image-gallery",
|
||||
"accessKey": "<!-- ACCESS KEY -->"
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||
"solution": {
|
||||
"name": "react-adaptive-cards-image-gallery-client-side-solution",
|
||||
"id": "2321325f-19a4-4895-8bdd-9a5447d462b1",
|
||||
"version": "1.0.0.0",
|
||||
"includeClientSideAssets": true,
|
||||
"isDomainIsolated": false,
|
||||
"features": [
|
||||
{
|
||||
"title": "react-adaptive-cards-image-gallery-deployment",
|
||||
"description": "react-adaptive-cards-image-gallery-deployment",
|
||||
"id": "bf461e6f-0fd4-4f3c-a77a-bd185e91cf8f",
|
||||
"version": "1.0.0.0",
|
||||
"assets": {
|
||||
"elementManifests": [
|
||||
"elements.xml"
|
||||
],
|
||||
"elementFiles": [
|
||||
"schema.xml"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"paths": {
|
||||
"zippedPackage": "solution/react-adaptive-cards-image-gallery.sppkg"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
|
||||
"port": 4321,
|
||||
"https": true,
|
||||
"initialPage": "https://localhost:5432/workbench",
|
||||
"api": {
|
||||
"port": 5432,
|
||||
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
|
||||
}
|
||||
}
|
|
@ -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);
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"name": "react-adaptive-cards-image-gallery",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "gulp bundle",
|
||||
"clean": "gulp clean",
|
||||
"test": "gulp test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@microsoft/sp-core-library": "1.7.0",
|
||||
"@microsoft/sp-lodash-subset": "1.7.0",
|
||||
"@microsoft/sp-office-ui-fabric-core": "1.7.0",
|
||||
"@microsoft/sp-webpart-base": "1.7.0",
|
||||
"@types/es6-promise": "0.0.33",
|
||||
"@types/react": "16.4.2",
|
||||
"@types/react-dom": "16.0.5",
|
||||
"@types/webpack-env": "1.13.1",
|
||||
"adaptivecards": "^1.1.0",
|
||||
"react": "16.3.2",
|
||||
"react-dom": "16.3.2",
|
||||
"sp-pnp-js": "^3.0.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/sp-build-web": "1.7.0",
|
||||
"@microsoft/sp-tslint-rules": "1.7.0",
|
||||
"@microsoft/sp-module-interfaces": "1.7.0",
|
||||
"@microsoft/sp-webpart-workbench": "1.7.0",
|
||||
"gulp": "~3.9.1",
|
||||
"@types/chai": "3.4.34",
|
||||
"@types/mocha": "2.2.38",
|
||||
"ajv": "~5.2.2"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
|
||||
|
||||
<ListInstance
|
||||
CustomSchema="schema.xml"
|
||||
FeatureId="00bfea71-de22-43b2-a848-c05709900100"
|
||||
Title="Adaptive Card Images"
|
||||
Description="List with image information"
|
||||
TemplateType="100"
|
||||
Url="Lists/AdaptiveCardImages">
|
||||
<Data>
|
||||
<Rows>
|
||||
<Row>
|
||||
<Field Name="Title">Sea</Field>
|
||||
<Field Name="ImageLink">https://picsum.photos/200/200?image=100, Sea Image</Field>
|
||||
<Field Name="NavigationURL">https://en.wikipedia.org/wiki/Sea, Sea Wikipedia</Field>
|
||||
<Field Name="SortOrder">1</Field>
|
||||
</Row>
|
||||
<Row>
|
||||
<Field Name="Title">Yak</Field>
|
||||
<Field Name="ImageLink">https://picsum.photos/300/200?image=200, Yak Image</Field>
|
||||
<Field Name="NavigationURL">https://en.wikipedia.org/wiki/Domestic_yak, Yak Wikipedia</Field>
|
||||
<Field Name="SortOrder">2</Field>
|
||||
</Row>
|
||||
<Row>
|
||||
<Field Name="Title">Autumn</Field>
|
||||
<Field Name="ImageLink">https://picsum.photos/300/200?image=301, Autumn Image</Field>
|
||||
<Field Name="NavigationURL">https://en.wikipedia.org/wiki/Autumn, Autumn Wikipedia</Field>
|
||||
<Field Name="SortOrder">3</Field>
|
||||
</Row>
|
||||
<Row>
|
||||
<Field Name="Title">Plant</Field>
|
||||
<Field Name="ImageLink">https://picsum.photos/300/200?image=400, Plant Image</Field>
|
||||
<Field Name="NavigationURL">https://en.wikipedia.org/wiki/Plant, Plant Wikipedia</Field>
|
||||
<Field Name="SortOrder">4</Field>
|
||||
</Row>
|
||||
<Row>
|
||||
<Field Name="Title">Building</Field>
|
||||
<Field Name="ImageLink">https://picsum.photos/300/200?image=500, Building Image</Field>
|
||||
<Field Name="NavigationURL">https://en.wikipedia.org/wiki/Building, Building Wikipedia</Field>
|
||||
<Field Name="SortOrder">5</Field>
|
||||
</Row>
|
||||
<Row>
|
||||
<Field Name="Title">Forest</Field>
|
||||
<Field Name="ImageLink">https://picsum.photos/300/200?image=600, Forest Image</Field>
|
||||
<Field Name="NavigationURL">https://en.wikipedia.org/wiki/Forest, Forest Wikipedia</Field>
|
||||
<Field Name="SortOrder">6</Field>
|
||||
</Row>
|
||||
<Row>
|
||||
<Field Name="Title">Fish</Field>
|
||||
<Field Name="ImageLink">https://picsum.photos/300/200?image=700, Fish Image</Field>
|
||||
<Field Name="NavigationURL">https://en.wikipedia.org/wiki/Fish, Fish Wikipedia</Field>
|
||||
<Field Name="SortOrder">7</Field>
|
||||
</Row>
|
||||
<Row>
|
||||
<Field Name="Title">Auditorium</Field>
|
||||
<Field Name="ImageLink">https://picsum.photos/300/200?image=800, Auditorium Image</Field>
|
||||
<Field Name="NavigationURL">https://en.wikipedia.org/wiki/Auditorium, Auditorium Wikipedia</Field>
|
||||
<Field Name="SortOrder">8</Field>
|
||||
</Row>
|
||||
<Row>
|
||||
<Field Name="Title">Bridge</Field>
|
||||
<Field Name="ImageLink">https://picsum.photos/300/200?image=900, Bridge Image</Field>
|
||||
<Field Name="NavigationURL">https://en.wikipedia.org/wiki/Bridge, Bridge Wikipedia</Field>
|
||||
<Field Name="SortOrder">9</Field>
|
||||
</Row>
|
||||
<Row>
|
||||
<Field Name="Title">Sea Link</Field>
|
||||
<Field Name="ImageLink">https://picsum.photos/300/200?image=950, Sea Link Image</Field>
|
||||
<Field Name="NavigationURL">https://en.wikipedia.org/wiki/Sealink, Sea Link Wikipedia</Field>
|
||||
<Field Name="SortOrder">10</Field>
|
||||
</Row>
|
||||
</Rows>
|
||||
</Data>
|
||||
</ListInstance>
|
||||
|
||||
</Elements>
|
|
@ -0,0 +1,33 @@
|
|||
<List xmlns:ows="Microsoft SharePoint" Title="Image Gallery" EnableContentTypes="TRUE" FolderCreation="FALSE" Direction="$Resources:Direction;" Url="Lists/ImageGallery" BaseType="0" xmlns="http://schemas.microsoft.com/sharepoint/">
|
||||
<MetaData>
|
||||
<Fields>
|
||||
<Field ID="{e6954092-6678-4538-b809-cf9065480a5d}" Type="URL" DisplayName="Image Link" Required="TRUE" Format="Hyperlink" Title="ImageLink" StaticName="ImageLink" Name="ImageLink" ColName="nvarchar4" RowOrdinal="0" ColName2="nvarchar5" RowOrdinal2="0" CustomFormatter="" EnforceUniqueValues="FALSE" Indexed="FALSE" Version="1" />
|
||||
<Field ID="{3e142196-f3dc-49f1-8147-c7d8b05775d0}" Type="URL" DisplayName="Navigation URL" Required="TRUE" EnforceUniqueValues="FALSE" Indexed="FALSE" Format="Hyperlink" StaticName="NavigationURL" Name="NavigationURL" ColName="nvarchar6" RowOrdinal="0" ColName2="nvarchar7" RowOrdinal2="0" CustomFormatter="" Version="1" />
|
||||
<Field ID="{8e328744-f48d-4981-8720-d4efa4730f0e}" Type="Number" DisplayName="Sort Order" Required="TRUE" EnforceUniqueValues="FALSE" Indexed="FALSE" StaticName="SortOrder" Name="SortOrder" ColName="float1" RowOrdinal="0" />
|
||||
</Fields>
|
||||
<Views>
|
||||
<View BaseViewID="1" Type="HTML" WebPartZoneID="Main" DisplayName="$Resources:core,objectiv_schema_mwsidcamlidC24;" DefaultView="TRUE" MobileView="TRUE" MobileDefaultView="TRUE" SetupPath="pages\viewpage.aspx" ImageUrl="/_layouts/images/generic.png" Url="AllItems.aspx">
|
||||
<XslLink Default="TRUE">main.xsl</XslLink>
|
||||
<JSLink>clienttemplates.js</JSLink>
|
||||
<RowLimit Paged="TRUE">30</RowLimit>
|
||||
<Toolbar Type="Standard" />
|
||||
<ViewFields>
|
||||
<FieldRef Name="SortOrder"></FieldRef>
|
||||
<FieldRef Name="LinkTitle"></FieldRef>
|
||||
<FieldRef Name="ImageLink"></FieldRef>
|
||||
<FieldRef Name="NavigationURL"></FieldRef>
|
||||
</ViewFields>
|
||||
<Query>
|
||||
<OrderBy>
|
||||
<FieldRef Name="SortOrder" />
|
||||
</OrderBy>
|
||||
</Query>
|
||||
</View>
|
||||
</Views>
|
||||
<Forms>
|
||||
<Form Type="DisplayForm" Url="DispForm.aspx" SetupPath="pages\form.aspx" WebPartZoneID="Main" />
|
||||
<Form Type="EditForm" Url="EditForm.aspx" SetupPath="pages\form.aspx" WebPartZoneID="Main" />
|
||||
<Form Type="NewForm" Url="NewForm.aspx" SetupPath="pages\form.aspx" WebPartZoneID="Main" />
|
||||
</Forms>
|
||||
</MetaData>
|
||||
</List>
|
|
@ -0,0 +1 @@
|
|||
// A file is required to be in the root of the /src directory by the TypeScript compiler
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
|
||||
"id": "769ec7d3-bd0d-4afc-9426-23a0c279acb4",
|
||||
"alias": "AdaptiveCardsImageGalleryWebPart",
|
||||
"componentType": "WebPart",
|
||||
|
||||
// The "*" signifies that the version should be taken from the package.json
|
||||
"version": "*",
|
||||
"manifestVersion": 2,
|
||||
|
||||
// If true, the component can only be installed on sites where Custom Script is allowed.
|
||||
// Components that allow authors to embed arbitrary script code should set this to true.
|
||||
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
|
||||
"requiresCustomScript": false,
|
||||
|
||||
"preconfiguredEntries": [{
|
||||
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
|
||||
"group": { "default": "Other" },
|
||||
"title": { "default": "Adaptive Cards Image Gallery" },
|
||||
"description": { "default": "Image Gallery implemented with Adaptive Cards" },
|
||||
"officeFabricIconFontName": "ImageDiff",
|
||||
"properties": {
|
||||
"description": "AdaptiveCardsImageGallery"
|
||||
}
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
import * as React from 'react';
|
||||
import * as ReactDom from 'react-dom';
|
||||
import { Version } from '@microsoft/sp-core-library';
|
||||
import {
|
||||
BaseClientSideWebPart,
|
||||
IPropertyPaneConfiguration,
|
||||
PropertyPaneTextField
|
||||
} from '@microsoft/sp-webpart-base';
|
||||
|
||||
import * as strings from 'AdaptiveCardsImageGalleryWebPartStrings';
|
||||
import AdaptiveCardsImageGallery from './components/AdaptiveCardsImageGallery';
|
||||
import { IAdaptiveCardsImageGalleryProps } from './components/IAdaptiveCardsImageGalleryProps';
|
||||
import pnp from 'sp-pnp-js';
|
||||
|
||||
export interface IAdaptiveCardsImageGalleryWebPartProps {
|
||||
imageGalleryName: string;
|
||||
imagesToDisplay: number;
|
||||
}
|
||||
|
||||
export default class AdaptiveCardsImageGalleryWebPart extends BaseClientSideWebPart<IAdaptiveCardsImageGalleryWebPartProps> {
|
||||
public async onInit(): Promise<void> {
|
||||
return super.onInit().then(_ => {
|
||||
pnp.setup({
|
||||
spfxContext: this.context
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public render(): void {
|
||||
const element: React.ReactElement<IAdaptiveCardsImageGalleryProps > = React.createElement(
|
||||
AdaptiveCardsImageGallery,
|
||||
{
|
||||
serviceScope: this.context.serviceScope,
|
||||
imageGalleryName: this.properties.imageGalleryName || "Adaptive Card Images",
|
||||
imagesToDisplay: this.properties.imagesToDisplay || 10
|
||||
}
|
||||
);
|
||||
|
||||
ReactDom.render(element, this.domElement);
|
||||
}
|
||||
|
||||
protected onDispose(): void {
|
||||
ReactDom.unmountComponentAtNode(this.domElement);
|
||||
}
|
||||
|
||||
protected get dataVersion(): Version {
|
||||
return Version.parse('1.0');
|
||||
}
|
||||
|
||||
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
||||
return {
|
||||
pages: [
|
||||
{
|
||||
header: {
|
||||
description: strings.PropertyPaneDescription
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
groupName: strings.BasicGroupName,
|
||||
groupFields: [
|
||||
PropertyPaneTextField('imageGalleryName', {
|
||||
label: strings.ImageGalleryNameFieldLabel
|
||||
}),
|
||||
PropertyPaneTextField('imagesToDisplay', {
|
||||
label: strings.ImagesToDisplayFieldLabel
|
||||
})
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
|
||||
|
||||
.adaptiveCardsImageGallery {
|
||||
.spinner {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 700px;
|
||||
margin: 0px auto;
|
||||
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.row {
|
||||
@include ms-Grid-row;
|
||||
@include ms-fontColor-white;
|
||||
background-color: $ms-color-themeDark;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.column {
|
||||
@include ms-Grid-col;
|
||||
@include ms-lg10;
|
||||
@include ms-xl8;
|
||||
@include ms-xlPush2;
|
||||
@include ms-lgPush1;
|
||||
}
|
||||
|
||||
.title {
|
||||
@include ms-font-xl;
|
||||
@include ms-fontColor-white;
|
||||
}
|
||||
|
||||
.subTitle {
|
||||
@include ms-font-l;
|
||||
@include ms-fontColor-white;
|
||||
}
|
||||
|
||||
.description {
|
||||
@include ms-font-l;
|
||||
@include ms-fontColor-white;
|
||||
}
|
||||
|
||||
.button {
|
||||
// Our button
|
||||
text-decoration: none;
|
||||
height: 32px;
|
||||
|
||||
// Primary Button
|
||||
min-width: 80px;
|
||||
background-color: $ms-color-themePrimary;
|
||||
border-color: $ms-color-themePrimary;
|
||||
color: $ms-color-white;
|
||||
|
||||
// Basic Button
|
||||
outline: transparent;
|
||||
position: relative;
|
||||
font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
font-size: $ms-font-size-m;
|
||||
font-weight: $ms-font-weight-regular;
|
||||
border-width: 0;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
padding: 0 16px;
|
||||
|
||||
.label {
|
||||
font-weight: $ms-font-weight-semibold;
|
||||
font-size: $ms-font-size-m;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
margin: 0 4px;
|
||||
vertical-align: top;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
import * as React from 'react';
|
||||
import styles from './AdaptiveCardsImageGallery.module.scss';
|
||||
import { IAdaptiveCardsImageGalleryProps } from './IAdaptiveCardsImageGalleryProps';
|
||||
import { IAdaptiveCardsImageGalleryState } from './IAdaptiveCardsImageGalleryState';
|
||||
import { escape } from '@microsoft/sp-lodash-subset';
|
||||
|
||||
import * as AdaptiveCards from "adaptivecards";
|
||||
import { ImageGalleryService, IImageGalleryService } from '../services/ImageGalleryService';
|
||||
import { ServiceScope, Environment, EnvironmentType } from '@microsoft/sp-core-library';
|
||||
import { Spinner, SpinnerSize } from 'office-ui-fabric-react/lib/Spinner';
|
||||
|
||||
export default class AdaptiveCardsImageGallery extends React.Component<IAdaptiveCardsImageGalleryProps, IAdaptiveCardsImageGalleryState> {
|
||||
private ImageGalleryServiceInstance: IImageGalleryService;
|
||||
private _galleryListName: string;
|
||||
private _noOfItems: number;
|
||||
private card: any;
|
||||
private renderedCard: any = "";
|
||||
private imagesJSON = [];
|
||||
|
||||
constructor(props: IAdaptiveCardsImageGalleryProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
galleryItems: null,
|
||||
isLoading: true,
|
||||
showErrorMessage: false
|
||||
};
|
||||
|
||||
let serviceScope: ServiceScope;
|
||||
serviceScope = this.props.serviceScope;
|
||||
|
||||
this._galleryListName = this.props.imageGalleryName;
|
||||
this._noOfItems = this.props.imagesToDisplay;
|
||||
|
||||
// Based on the type of environment, return the correct instance of the ImageGalleryServiceInstance interface
|
||||
if (Environment.type == EnvironmentType.SharePoint || Environment.type == EnvironmentType.ClassicSharePoint) {
|
||||
// Mapping to be used when webpart runs in SharePoint.
|
||||
this.ImageGalleryServiceInstance = serviceScope.consume(ImageGalleryService.serviceKey);
|
||||
}
|
||||
|
||||
this.ImageGalleryServiceInstance.getGalleryImages(this._galleryListName, this._noOfItems).then((galleryImages: any[]) => {
|
||||
galleryImages.forEach(adaptiveImage => {
|
||||
let image = {};
|
||||
image["type"] = "Image";
|
||||
image["url"] = adaptiveImage.ImageLink.Url;
|
||||
|
||||
// Compose image action
|
||||
let imageAction = {};
|
||||
imageAction["title"] = adaptiveImage.NavigationURL.Description;
|
||||
imageAction["type"] = "Action.OpenUrl";
|
||||
imageAction["url"] = adaptiveImage.NavigationURL.Url;
|
||||
imageAction["iconUrl"] = adaptiveImage.NavigationURL.Url;
|
||||
|
||||
image["selectAction"] = imageAction;
|
||||
this.imagesJSON.push(image);
|
||||
});
|
||||
|
||||
this.card = {
|
||||
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
|
||||
"type": "AdaptiveCard",
|
||||
"version": "1.0",
|
||||
"body": [
|
||||
{
|
||||
"type": "TextBlock",
|
||||
"text": "Adaptive Image Gallery",
|
||||
"size": "medium"
|
||||
},
|
||||
{
|
||||
"type": "ImageSet",
|
||||
"imageSize": "medium",
|
||||
"images": this.imagesJSON
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// Create an AdaptiveCard instance
|
||||
var adaptiveCard = new AdaptiveCards.AdaptiveCard();
|
||||
|
||||
// Set its hostConfig property unless you want to use the default Host Config
|
||||
// Host Config defines the style and behavior of a card
|
||||
adaptiveCard.hostConfig = new AdaptiveCards.HostConfig({
|
||||
fontFamily: "Segoe UI, Helvetica Neue, sans-serif"
|
||||
});
|
||||
|
||||
// Set the adaptive card's event handlers. onExecuteAction is invoked
|
||||
// whenever an action is clicked in the card
|
||||
adaptiveCard.onExecuteAction = function(action) {
|
||||
window.location.href = action.iconUrl;
|
||||
};
|
||||
|
||||
// Parse the card
|
||||
adaptiveCard.parse(this.card);
|
||||
|
||||
// Render the card to an HTML element
|
||||
this.renderedCard = adaptiveCard.render();
|
||||
this.setState({ isLoading: false });
|
||||
});
|
||||
}
|
||||
|
||||
public render(): React.ReactElement<IAdaptiveCardsImageGalleryProps> {
|
||||
return (
|
||||
<div className={styles.adaptiveCardsImageGallery}>
|
||||
<div className={styles.container}>
|
||||
{this.state.isLoading && <Spinner className={styles.spinner} size={SpinnerSize.large} />}
|
||||
{!this.state.isLoading && <div ref={(n) => { n && n.appendChild(this.renderedCard) }} />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
import { ServiceScope } from '@microsoft/sp-core-library';
|
||||
|
||||
export interface IAdaptiveCardsImageGalleryProps {
|
||||
serviceScope: ServiceScope;
|
||||
imageGalleryName: string;
|
||||
imagesToDisplay: number;
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
export interface IAdaptiveCardsImageGalleryState {
|
||||
galleryItems: any[];
|
||||
isLoading: boolean;
|
||||
showErrorMessage: boolean;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
define([], function() {
|
||||
return {
|
||||
"PropertyPaneDescription": "Description",
|
||||
"BasicGroupName": "Group Name",
|
||||
"DescriptionFieldLabel": "Description Field",
|
||||
"ImageGalleryNameFieldLabel": "Image Gallery",
|
||||
"ImagesToDisplayFieldLabel": "Number of images to display",
|
||||
}
|
||||
});
|
|
@ -0,0 +1,11 @@
|
|||
declare interface IAdaptiveCardsImageGalleryWebPartStrings {
|
||||
PropertyPaneDescription: string;
|
||||
BasicGroupName: string;
|
||||
ImageGalleryNameFieldLabel: string;
|
||||
ImagesToDisplayFieldLabel: string;
|
||||
}
|
||||
|
||||
declare module 'AdaptiveCardsImageGalleryWebPartStrings' {
|
||||
const strings: IAdaptiveCardsImageGalleryWebPartStrings;
|
||||
export = strings;
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
import { ServiceKey, ServiceScope } from '@microsoft/sp-core-library';
|
||||
import { PageContext } from '@microsoft/sp-page-context';
|
||||
import { SPHttpClient, SPHttpClientResponse } from '@microsoft/sp-http';
|
||||
import * as pnp from "sp-pnp-js";
|
||||
|
||||
export interface IImageGalleryService {
|
||||
getGalleryImages: (listName: string, rowLimit: number) => Promise<any[]>;
|
||||
}
|
||||
|
||||
export class ImageGalleryService implements IImageGalleryService {
|
||||
public static readonly serviceKey: ServiceKey<IImageGalleryService> = ServiceKey.create<IImageGalleryService>('ImageGallery:ImageGalleryService', ImageGalleryService);
|
||||
private _pageContext: PageContext;
|
||||
|
||||
constructor(serviceScope: ServiceScope) {
|
||||
serviceScope.whenFinished(() => {
|
||||
this._pageContext = serviceScope.consume(PageContext.serviceKey);
|
||||
});
|
||||
}
|
||||
|
||||
public getGalleryImages(listName: string, rowLimit: number): Promise<any[]> {
|
||||
const xml = `<View>
|
||||
<ViewFields>
|
||||
<FieldRef Name='ID' />
|
||||
<FieldRef Name='Title' />
|
||||
<FieldRef Name='ImageLink' />
|
||||
<FieldRef Name='NavigationURL' />
|
||||
</ViewFields>
|
||||
<Query>
|
||||
<OrderBy>
|
||||
<FieldRef Name='SortOrder' />
|
||||
</OrderBy>
|
||||
</Query>
|
||||
<RowLimit>` + rowLimit + `</RowLimit>
|
||||
</View>`;
|
||||
|
||||
const q: any = {
|
||||
ViewXml: xml,
|
||||
};
|
||||
|
||||
return this._ensureList(listName).then((list) => {
|
||||
if (list) {
|
||||
return pnp.sp.web.lists.getByTitle(listName).getItemsByCAMLQuery(q).then((items: any[]) => {
|
||||
return Promise.resolve(items);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _ensureList(listName: string): Promise<pnp.List> {
|
||||
if (listName) {
|
||||
return pnp.sp.web.lists.ensure(listName).then((listEnsureResult) => Promise.resolve(listEnsureResult.list));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.2/MicrosoftTeams.schema.json",
|
||||
"manifestVersion": "1.2",
|
||||
"packageName": "AdaptiveCardsImageGallery",
|
||||
"id": "769ec7d3-bd0d-4afc-9426-23a0c279acb4",
|
||||
"version": "0.1",
|
||||
"developer": {
|
||||
"name": "SPFx + Teams Dev",
|
||||
"websiteUrl": "https://products.office.com/en-us/sharepoint/collaboration",
|
||||
"privacyUrl": "https://privacy.microsoft.com/en-us/privacystatement",
|
||||
"termsOfUseUrl": "https://www.microsoft.com/en-us/servicesagreement"
|
||||
},
|
||||
"name": {
|
||||
"short": "AdaptiveCardsImageGallery"
|
||||
},
|
||||
"description": {
|
||||
"short": "Image Gallery implemented with Adaptive Cards",
|
||||
"full": "Image Gallery implemented with Adaptive Cards"
|
||||
},
|
||||
"icons": {
|
||||
"outline": "tab20x20.png",
|
||||
"color": "tab96x96.png"
|
||||
},
|
||||
"accentColor": "#004578",
|
||||
"configurableTabs": [
|
||||
{
|
||||
"configurationUrl": "https://{teamSiteDomain}{teamSitePath}/_layouts/15/TeamsLogon.aspx?SPFX=true&dest={teamSitePath}/_layouts/15/teamshostedapp.aspx%3FopenPropertyPane=true%26teams%26componentId=769ec7d3-bd0d-4afc-9426-23a0c279acb4",
|
||||
"canUpdateConfiguration": false,
|
||||
"scopes": [
|
||||
"team"
|
||||
]
|
||||
}
|
||||
],
|
||||
"validDomains": [
|
||||
"*.login.microsoftonline.com",
|
||||
"*.sharepoint.com",
|
||||
"*.sharepoint-df.com",
|
||||
"spoppe-a.akamaihd.net",
|
||||
"spoprod-a.akamaihd.net",
|
||||
"resourceseng.blob.core.windows.net",
|
||||
"msft.spoppe.com"
|
||||
],
|
||||
"webApplicationInfo": {
|
||||
"resource": "https://{teamSiteDomain}",
|
||||
"id": "00000003-0000-0ff1-ce00-000000000000"
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 933 B |
After Width: | Height: | Size: 2.5 KiB |
|
@ -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
|
||||
}
|
||||
}
|
|
@ -61,6 +61,14 @@ Version|Date|Comments
|
|||
2.1.1.0 | Oct 30, 2018 | <ul><li>Bug fix for editing custom template.</li><li>Bug fix for dynamic loading of video helper library.</li><li>Added support for Page context query variables.</li><li>Added `getUniqueCount` helper function.</li></ul>
|
||||
2.1.2.0 | Nov 9, 2018 | <ul><li>Bug fix for IE11.</li><li>Added date query variables.</li><li>Added support for both result source id and query template.</li><li>Added `getUniqueCount` helper function.</li></ul>
|
||||
2.2.0.0 | Nov 11, 2018 | <ul><li>Upgraded to SPFx 1.7.0</li><li>Added a TypeScript Azure Function to demonstrate NLP processing on search query</li><li>Removed extension data source. Now we use the default SPFx 'Page Environment' data source.</li></ul>
|
||||
2.2.0.1 | Dec 3, 2018 | <ul><li>Remove switch for handlebar helpers, and instead load helpers if used in the template.</li></ul>
|
||||
|
||||
## Important notice on upgrading the solution from pre v2.2.0.0
|
||||
**Due to code restucturing we have hit an edge case which impacts upgrades from previous versions. To solve the issue go to `https://<tenant>.sharepoint.com/sites/<appcatalog>/Lists/ComponentManifests` and remove the entries for SearchBox and Search Results, and then upload the .sppkg for the new release.**
|
||||
|
||||
**Next you need to loop over all sites which have the web parts installed, and upgrade the App on those sites. Now the web parts should work on new and existing sites. You may use the PnP command `Update-PnPApp` to update the application.**
|
||||
|
||||
**If you have deployed the solution as a tenant wide extension, this should not impact you.**
|
||||
|
||||
## Disclaimer
|
||||
**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"solution": {
|
||||
"name": "PnP - Search Web Parts",
|
||||
"id": "890affef-33e0-4d72-bd72-36399e02143b",
|
||||
"version": "2.2.0.0",
|
||||
"version": "2.2.0.1",
|
||||
"includeClientSideAssets": true,
|
||||
"skipFeatureDeployment": false,
|
||||
"isDomainIsolated": false
|
||||
|
|
|
@ -44,7 +44,5 @@ build.configureWebpack.mergeConfig({
|
|||
return generatedConfiguration;
|
||||
}
|
||||
});
|
||||
build.webpack.buildConfig
|
||||
build.addSuppression(new RegExp("\[sass\]",'g'));
|
||||
|
||||
build.initialize(gulp);
|
||||
|
|
|
@ -19,11 +19,13 @@
|
|||
"@microsoft/sp-lodash-subset": "1.7.0",
|
||||
"@microsoft/sp-office-ui-fabric-core": "1.7.0",
|
||||
"@microsoft/sp-webpart-base": "1.7.0",
|
||||
"@pnp/common": "1.2.5",
|
||||
"@pnp/logging": "1.2.5",
|
||||
"@pnp/odata": "1.2.5",
|
||||
"@pnp/common": "1.2.6",
|
||||
"@pnp/logging": "1.2.6",
|
||||
"@pnp/odata": "1.2.6",
|
||||
"@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-property-controls": "1.12.0",
|
||||
"@types/es6-promise": "0.0.33",
|
||||
|
@ -43,7 +45,7 @@
|
|||
"on-el-resize": "0.0.4",
|
||||
"react": "16.3.2",
|
||||
"react-ace": "6.1.4",
|
||||
"react-custom-scrollbars": "4.1.2",
|
||||
"react-custom-scrollbars": "4.2.1",
|
||||
"react-dom": "16.3.2",
|
||||
"react-js-pagination": "3.0.0",
|
||||
"video.js": "^7.3.0"
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
interface IRefinerConfiguration {
|
||||
refinerName: string;
|
||||
displayValue: string;
|
||||
}
|
||||
|
||||
export default IRefinerConfiguration;
|
|
@ -0,0 +1,9 @@
|
|||
export interface ISortFieldConfiguration {
|
||||
sortField: string;
|
||||
sortDirection: ISortFieldDirection;
|
||||
}
|
||||
|
||||
export enum ISortFieldDirection {
|
||||
Ascending = 1,
|
||||
Descending= 2
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
interface ISortableFieldConfiguration {
|
||||
sortField: string;
|
||||
displayValue: string;
|
||||
}
|
||||
|
||||
export default ISortableFieldConfiguration;
|
|
@ -1,4 +1,5 @@
|
|||
import { ISearchResults, IRefinementFilter } from '../../models/ISearchResult';
|
||||
import { Sort } from '@pnp/sp';
|
||||
|
||||
interface ISearchService {
|
||||
|
||||
|
@ -25,7 +26,7 @@ interface ISearchService {
|
|||
/**
|
||||
* The sort order of the results
|
||||
*/
|
||||
sortList?: string;
|
||||
sortList?: Sort[];
|
||||
|
||||
/**
|
||||
* Indicates wheter or not the query rules should be applied in the query
|
||||
|
|
|
@ -21,7 +21,7 @@ class SearchService implements ISearchService {
|
|||
private _selectedProperties: string[];
|
||||
private _queryTemplate: string;
|
||||
private _resultSourceId: string;
|
||||
private _sortList: string;
|
||||
private _sortList: Sort[];
|
||||
private _enableQueryRules: boolean;
|
||||
|
||||
public get resultsCount(): number { return this._resultsCount; }
|
||||
|
@ -36,8 +36,8 @@ class SearchService implements ISearchService {
|
|||
public set resultSourceId(value: string) { this._resultSourceId = value; }
|
||||
public get resultSourceId(): string { return this._resultSourceId; }
|
||||
|
||||
public set sortList(value: string) { this._sortList = value; }
|
||||
public get sortList(): string { return this._sortList; }
|
||||
public set sortList(value: Sort[]) { this._sortList = value; }
|
||||
public get sortList(): Sort[] { return this._sortList; }
|
||||
|
||||
public set enableQueryRules(value: boolean) { this._enableQueryRules = value; }
|
||||
public get enableQueryRules(): boolean { return this._enableQueryRules; }
|
||||
|
@ -91,35 +91,7 @@ class SearchService implements ISearchService {
|
|||
searchQuery.RowLimit = this._resultsCount ? this._resultsCount : 50;
|
||||
searchQuery.SelectProperties = this._selectedProperties;
|
||||
searchQuery.TrimDuplicates = false;
|
||||
|
||||
let sortList: Sort[] = [
|
||||
{
|
||||
Property: 'Created',
|
||||
Direction: SortDirection.Descending
|
||||
},
|
||||
{
|
||||
Property: 'Size',
|
||||
Direction: SortDirection.Ascending
|
||||
}
|
||||
];
|
||||
|
||||
if (this._sortList) {
|
||||
let sortDirections = this._sortList.split(',');
|
||||
sortList = sortDirections.map(sorter => {
|
||||
let sort = sorter.split(':');
|
||||
let s: Sort = { Property: sort[0].trim(), Direction: SortDirection.Descending };
|
||||
if (sort.indexOf('[') !== -1) {
|
||||
s.Direction = SortDirection.FQLFormula;
|
||||
}
|
||||
else if (sort.length > 1) {
|
||||
let direction = sort[1].trim().toLocaleLowerCase();
|
||||
s.Direction = direction === "ascending" ? SortDirection.Ascending : SortDirection.Descending;
|
||||
}
|
||||
return s;
|
||||
});
|
||||
}
|
||||
|
||||
searchQuery.SortList = sortList;
|
||||
searchQuery.SortList = this._sortList ? this._sortList : [];
|
||||
|
||||
if (refiners) {
|
||||
// Get the refiners order specified in the property pane
|
||||
|
@ -161,8 +133,9 @@ class SearchService implements ISearchService {
|
|||
const resultRows = r2.RawSearchResults.PrimaryQueryResult.RelevantResults.Table.Rows;
|
||||
let refinementResultsRows = r2.RawSearchResults.PrimaryQueryResult.RefinementResults;
|
||||
|
||||
const refinementRows = refinementResultsRows ? refinementResultsRows['Refiners'] : [];
|
||||
const refinementRows: any = refinementResultsRows ? refinementResultsRows.Refiners : [];
|
||||
if (refinementRows.length > 0) {
|
||||
|
||||
const component = await import(
|
||||
/* webpackChunkName: 'search-handlebars-helpers' */
|
||||
'handlebars-helpers'
|
||||
|
|
|
@ -1,18 +1,13 @@
|
|||
interface ITaxonomyService {
|
||||
import { ITerm } from "@pnp/sp-taxonomy";
|
||||
|
||||
/**
|
||||
* 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();
|
||||
interface ITaxonomyService {
|
||||
|
||||
/**
|
||||
* Gets multiple terms by their ids using the current taxonomy context
|
||||
* @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;
|
|
@ -1,5 +1,6 @@
|
|||
|
||||
import ITaxonomyService from './ITaxonomyService';
|
||||
import { ITerm } from '@pnp/sp-taxonomy';
|
||||
|
||||
class MockTaxonomyService implements ITaxonomyService {
|
||||
|
||||
|
@ -11,7 +12,7 @@ class MockTaxonomyService implements ITaxonomyService {
|
|||
return p1;
|
||||
}
|
||||
|
||||
public getTermsById(termIds: string[]): Promise<SP.Taxonomy.TermCollection> {
|
||||
public getTermsById(termIds: string[]): Promise<ITerm[]> {
|
||||
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 { Text } from '@microsoft/sp-core-library';
|
||||
import { ITermStore, ITerms, ITermData, Session, ITerm } from "@pnp/sp-taxonomy";
|
||||
|
||||
class TaxonomyService implements ITaxonomyService {
|
||||
|
||||
private _workingLanguageLcid: number;
|
||||
private _context: IWebPartContext;
|
||||
private _siteUrl: string;
|
||||
|
||||
public constructor(webPartContext: IWebPartContext, workingLanguage?: number){
|
||||
this._context = webPartContext;
|
||||
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;
|
||||
public constructor(siteUrl: string){
|
||||
this._siteUrl = siteUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets multiple terms by their ids using the current taxonomy context
|
||||
* @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) {
|
||||
|
||||
const spContext = SP.ClientContext.get_current();
|
||||
const taxSession: SP.Taxonomy.TaxonomySession = SP.Taxonomy.TaxonomySession.getTaxonomySession(spContext);
|
||||
const termStore = taxSession.getDefaultSiteCollectionTermStore();
|
||||
|
||||
if (this._workingLanguageLcid) {
|
||||
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);
|
||||
});
|
||||
const taxonomySession = new Session(this._siteUrl);
|
||||
taxonomySession.setup({
|
||||
sp: {
|
||||
headers: {
|
||||
Accept: "application/json;odata=nometadata",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
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 [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,16 +22,14 @@ abstract class BaseTemplateService {
|
|||
this.registerTemplateServices();
|
||||
}
|
||||
|
||||
public async LoadHandlebarsHelpers(load: boolean) {
|
||||
if (load) {
|
||||
let component = await import(
|
||||
/* webpackChunkName: 'search-handlebars-helpers' */
|
||||
'handlebars-helpers'
|
||||
);
|
||||
this._helper = component({
|
||||
handlebars: Handlebars
|
||||
});
|
||||
}
|
||||
private async LoadHandlebarsHelpers() {
|
||||
let component = await import(
|
||||
/* webpackChunkName: 'search-handlebars-helpers' */
|
||||
'handlebars-helpers'
|
||||
);
|
||||
this._helper = component({
|
||||
handlebars: Handlebars
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -292,7 +290,7 @@ abstract class BaseTemplateService {
|
|||
});
|
||||
|
||||
// Return the URL or Title part of a URL automatic managed property
|
||||
// <p>{{getDate MyLinkOWSURLH "Title"}}</p>
|
||||
// <p>{{getUrlField MyLinkOWSURLH "Title"}}</p>
|
||||
Handlebars.registerHelper("getUrlField", (urlField: string, value: "URL" | "Title") => {
|
||||
let separatorPos = urlField.indexOf(",");
|
||||
if (value === "URL") {
|
||||
|
@ -325,6 +323,168 @@ abstract class BaseTemplateService {
|
|||
*/
|
||||
public async processTemplate(templateContext: any, templateContent: string): Promise<string> {
|
||||
// Process the Handlebars template
|
||||
const handlebarFunctionNames = [
|
||||
"getDate",
|
||||
"after",
|
||||
"arrayify",
|
||||
"before",
|
||||
"eachIndex",
|
||||
"filter",
|
||||
"first",
|
||||
"forEach",
|
||||
"inArray",
|
||||
"isArray",
|
||||
"itemAt",
|
||||
"join",
|
||||
"last",
|
||||
"lengthEqual",
|
||||
"map",
|
||||
"some",
|
||||
"sort",
|
||||
"sortBy",
|
||||
"withAfter",
|
||||
"withBefore",
|
||||
"withFirst",
|
||||
"withGroup",
|
||||
"withLast",
|
||||
"withSort",
|
||||
"embed",
|
||||
"gist",
|
||||
"jsfiddle",
|
||||
"isEmpty",
|
||||
"iterate",
|
||||
"length",
|
||||
"and",
|
||||
"compare",
|
||||
"contains",
|
||||
"gt",
|
||||
"gte",
|
||||
"has",
|
||||
"eq",
|
||||
"ifEven",
|
||||
"ifNth",
|
||||
"ifOdd",
|
||||
"is",
|
||||
"isnt",
|
||||
"lt",
|
||||
"lte",
|
||||
"neither",
|
||||
"or",
|
||||
"unlessEq",
|
||||
"unlessGt",
|
||||
"unlessLt",
|
||||
"unlessGteq",
|
||||
"unlessLteq",
|
||||
"moment",
|
||||
"fileSize",
|
||||
"read",
|
||||
"readdir",
|
||||
"css",
|
||||
"ellipsis",
|
||||
"js",
|
||||
"sanitize",
|
||||
"truncate",
|
||||
"ul",
|
||||
"ol",
|
||||
"thumbnailImage",
|
||||
"i18n",
|
||||
"inflect",
|
||||
"ordinalize",
|
||||
"info",
|
||||
"bold",
|
||||
"warn",
|
||||
"error",
|
||||
"debug",
|
||||
"_inspect",
|
||||
"markdown",
|
||||
"md",
|
||||
"mm",
|
||||
"match",
|
||||
"isMatch",
|
||||
"add",
|
||||
"subtract",
|
||||
"divide",
|
||||
"multiply",
|
||||
"floor",
|
||||
"ceil",
|
||||
"round",
|
||||
"sum",
|
||||
"avg",
|
||||
"default",
|
||||
"option",
|
||||
"noop",
|
||||
"withHash",
|
||||
"addCommas",
|
||||
"phoneNumber",
|
||||
"random",
|
||||
"toAbbr",
|
||||
"toExponential",
|
||||
"toFixed",
|
||||
"toFloat",
|
||||
"toInt",
|
||||
"toPrecision",
|
||||
"extend",
|
||||
"forIn",
|
||||
"forOwn",
|
||||
"toPath",
|
||||
"get",
|
||||
"getObject",
|
||||
"hasOwn",
|
||||
"isObject",
|
||||
"merge",
|
||||
"JSONparse",
|
||||
"parseJSON",
|
||||
"pick",
|
||||
"JSONstringify",
|
||||
"stringify",
|
||||
"absolute",
|
||||
"dirname",
|
||||
"relative",
|
||||
"basename",
|
||||
"stem",
|
||||
"extname",
|
||||
"segments",
|
||||
"camelcase",
|
||||
"capitalize",
|
||||
"capitalizeAll",
|
||||
"center",
|
||||
"chop",
|
||||
"dashcase",
|
||||
"dotcase",
|
||||
"hyphenate",
|
||||
"isString",
|
||||
"lowercase",
|
||||
"occurrences",
|
||||
"pascalcase",
|
||||
"pathcase",
|
||||
"plusify",
|
||||
"reverse",
|
||||
"replace",
|
||||
"sentence",
|
||||
"snakecase",
|
||||
"split",
|
||||
"startsWith",
|
||||
"titleize",
|
||||
"trim",
|
||||
"uppercase",
|
||||
"encodeURI",
|
||||
"decodeURI",
|
||||
"urlResolve",
|
||||
"urlParse",
|
||||
"stripQuerystring",
|
||||
"stripProtocol"
|
||||
];
|
||||
|
||||
for (let i = 0; i < handlebarFunctionNames.length; i++) {
|
||||
const element = handlebarFunctionNames[i];
|
||||
|
||||
let regEx = new RegExp("{{#.*?" + element + ".*?}}", "m");
|
||||
if (regEx.test(templateContent)) {
|
||||
await this.LoadHandlebarsHelpers();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let template = Handlebars.compile(templateContent);
|
||||
let result = template(templateContext);
|
||||
if (result.indexOf("-preview-item") != -1) {
|
||||
|
@ -418,7 +578,7 @@ abstract class BaseTemplateService {
|
|||
}));
|
||||
}
|
||||
|
||||
private async _loadVideoLibrary(){
|
||||
private async _loadVideoLibrary() {
|
||||
|
||||
// Load Videos-Js on Demand
|
||||
// Webpack will create a other bundle loaded on demand just for this library
|
||||
|
|
|
@ -3,14 +3,9 @@
|
|||
"id": "096b96cc-8a44-41fa-9b4d-c0ab2ab2a779",
|
||||
"alias": "SearchBoxWebPart",
|
||||
"componentType": "WebPart",
|
||||
|
||||
// The "*" signifies that the version should be taken from the package.json
|
||||
"version": "*",
|
||||
"manifestVersion": 2,
|
||||
|
||||
// If true, the component can only be installed on sites where Custom Script is allowed.
|
||||
// Components that allow authors to embed arbitrary script code should set this to true.
|
||||
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
|
||||
"supportsFullBleed": true,
|
||||
"requiresCustomScript": false,
|
||||
|
||||
"preconfiguredEntries": [{
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
.searchBox {
|
||||
|
||||
position: relative;
|
||||
|
||||
.errorMessage {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
@ -12,10 +14,31 @@
|
|||
padding: 10px;
|
||||
}
|
||||
|
||||
.searchFieldGroup {
|
||||
|
||||
display: flex;
|
||||
position: relative;
|
||||
|
||||
.searchTextField {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.searchBtn {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.suggestionPanel {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
z-index: 1;
|
||||
background-color: #ffffff;
|
||||
border-bottom: 1px solid #ccc;
|
||||
border-left: 1px solid #ccc;
|
||||
border-right: 1px solid #ccc;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.selected {
|
||||
|
|
|
@ -46,6 +46,8 @@ export default class SearchBoxWebPart extends BaseClientSideWebPart<ISearchBoxWe
|
|||
rawInputValue: '',
|
||||
enhancedQuery: ''
|
||||
};
|
||||
|
||||
this._bindHashChange = this._bindHashChange.bind(this);
|
||||
}
|
||||
|
||||
public render(): void {
|
||||
|
@ -53,6 +55,9 @@ export default class SearchBoxWebPart extends BaseClientSideWebPart<ISearchBoxWe
|
|||
let inputValue = this.properties.defaultQueryKeywords.tryGetValue();
|
||||
|
||||
if (inputValue && typeof(inputValue) === 'string') {
|
||||
|
||||
// Notify subsscriber a new value if available
|
||||
this.context.dynamicDataSourceManager.notifyPropertyChanged('searchQuery');
|
||||
this._searchQuery.rawInputValue = inputValue;
|
||||
}
|
||||
|
||||
|
@ -119,6 +124,8 @@ export default class SearchBoxWebPart extends BaseClientSideWebPart<ISearchBoxWe
|
|||
this.initSearchService();
|
||||
this.initNlpService();
|
||||
|
||||
this._bindHashChange();
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
|
@ -157,6 +164,16 @@ export default class SearchBoxWebPart extends BaseClientSideWebPart<ISearchBoxWe
|
|||
protected onPropertyPaneFieldChanged(propertyPath: string) {
|
||||
this.initSearchService();
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ISearchQuery from '../../../models/ISearchQuery';
|
||||
import NlpDebugPanel from './NlpDebugPanel/NlpDebugPanel';
|
||||
import { IconButton } from 'office-ui-fabric-react/lib/Button';
|
||||
|
||||
const SUGGESTION_CHAR_COUNT_TRIGGER = 3;
|
||||
|
||||
|
@ -48,45 +49,49 @@ export default class SearchBoxContainer extends React.Component<ISearchBoxContai
|
|||
selectedItem,
|
||||
highlightedIndex,
|
||||
openMenu,
|
||||
clearItems
|
||||
clearItems,
|
||||
}) => (
|
||||
<div>
|
||||
<TextField {...getInputProps({
|
||||
placeholder: strings.SearchInputPlaceholder,
|
||||
onKeyDown: event => {
|
||||
<div className={ styles.searchFieldGroup }>
|
||||
<TextField {...getInputProps({
|
||||
placeholder: strings.SearchInputPlaceholder,
|
||||
onKeyDown: event => {
|
||||
|
||||
// Submit search on "Enter"
|
||||
if (event.keyCode === 13 && (!isOpen || (isOpen && highlightedIndex === null))) {
|
||||
this._onSearch(this.state.searchInputValue);
|
||||
// Submit search on "Enter"
|
||||
if (event.keyCode === 13 && (!isOpen || (isOpen && highlightedIndex === null))) {
|
||||
this._onSearch(this.state.searchInputValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
})}
|
||||
value={ this.state.searchInputValue }
|
||||
autoComplete= "off"
|
||||
onChanged={ (value) => {
|
||||
})}
|
||||
className={ styles.searchTextField }
|
||||
value={ this.state.searchInputValue }
|
||||
autoComplete= "off"
|
||||
onChanged={ (value) => {
|
||||
|
||||
this.setState({
|
||||
searchInputValue: value,
|
||||
});
|
||||
this.setState({
|
||||
searchInputValue: value,
|
||||
});
|
||||
|
||||
if (this.state.selectedQuerySuggestions.length === 0) {
|
||||
clearItems();
|
||||
this._onChange(value);
|
||||
openMenu();
|
||||
} else {
|
||||
if (!value) {
|
||||
if (this.state.selectedQuerySuggestions.length === 0) {
|
||||
clearItems();
|
||||
this._onChange(value);
|
||||
openMenu();
|
||||
} else {
|
||||
if (!value) {
|
||||
|
||||
// Reset the selected suggestions if input is empty
|
||||
this.setState({
|
||||
selectedQuerySuggestions: [],
|
||||
});
|
||||
// Reset the selected suggestions if input is empty
|
||||
this.setState({
|
||||
selectedQuerySuggestions: [],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
iconProps={{
|
||||
iconName: 'Search',
|
||||
iconType: IconType.default
|
||||
}}/>
|
||||
}}/>
|
||||
<IconButton iconProps={{
|
||||
iconName: 'Search',
|
||||
iconType: IconType.default,
|
||||
}} onClick= {() => { this._onSearch(this.state.searchInputValue);} } className={ styles.searchBtn }>
|
||||
</IconButton>
|
||||
</div>
|
||||
{isOpen ?
|
||||
this.renderSuggestions(getItemProps, selectedItem, highlightedIndex)
|
||||
: null}
|
||||
|
@ -96,25 +101,30 @@ export default class SearchBoxContainer extends React.Component<ISearchBoxContai
|
|||
}
|
||||
|
||||
private renderBasicSearchBox(): JSX.Element {
|
||||
return <TextField
|
||||
placeholder={ strings.SearchInputPlaceholder }
|
||||
value={ this.state.searchInputValue }
|
||||
onChanged={ (value) => {
|
||||
this.setState({
|
||||
searchInputValue: value,
|
||||
});
|
||||
}}
|
||||
onKeyDown={ (event) => {
|
||||
return <div className={ styles.searchFieldGroup }>
|
||||
<TextField
|
||||
className={ styles.searchTextField }
|
||||
placeholder={ strings.SearchInputPlaceholder }
|
||||
value={ this.state.searchInputValue }
|
||||
onChanged={ (value) => {
|
||||
this.setState({
|
||||
searchInputValue: value,
|
||||
});
|
||||
}}
|
||||
onKeyDown={ (event) => {
|
||||
|
||||
// Submit search on "Enter"
|
||||
if (event.keyCode === 13) {
|
||||
this._onSearch(this.state.searchInputValue);
|
||||
}
|
||||
}}
|
||||
iconProps={{
|
||||
iconName: 'Search',
|
||||
iconType: IconType.default
|
||||
}}/>;
|
||||
// Submit search on "Enter"
|
||||
if (event.keyCode === 13) {
|
||||
this._onSearch(this.state.searchInputValue);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<IconButton iconProps={{
|
||||
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) {
|
||||
|
||||
let query: ISearchQuery = {
|
||||
rawInputValue: queryText,
|
||||
enhancedQuery: ''
|
||||
};
|
||||
// Don't send empty value
|
||||
if (queryText) {
|
||||
|
||||
this.setState({
|
||||
searchInputValue: queryText,
|
||||
});
|
||||
let query: ISearchQuery = {
|
||||
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);
|
||||
query.enhancedQuery = enhancedQuery.enhancedQuery;
|
||||
try {
|
||||
|
||||
enhancedQuery.entities.map((entity) => {
|
||||
});
|
||||
let enhancedQuery = await this.props.NlpService.enhanceSearchQuery(queryText, this.props.isStaging);
|
||||
query.enhancedQuery = enhancedQuery.enhancedQuery;
|
||||
|
||||
this.setState({
|
||||
enhancedQuery: enhancedQuery,
|
||||
});
|
||||
enhancedQuery.entities.map((entity) => {
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
|
||||
// In case of failure, use the non-optimized query instead
|
||||
query.enhancedQuery = queryText;
|
||||
this.setState({
|
||||
enhancedQuery: enhancedQuery,
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
|
||||
// In case of failure, use the non-optimized query instead
|
||||
query.enhancedQuery = queryText;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
if (this.props.searchInNewPage) {
|
||||
|
||||
// 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';
|
||||
window.open(url, behavior);
|
||||
|
||||
} else {
|
||||
const behavior = this.props.openBehavior === PageOpenBehavior.NewTab ? '_blank' : '_self';
|
||||
window.open(url, behavior);
|
||||
|
||||
} else {
|
||||
|
||||
// Notify the dynamic data controller
|
||||
this.props.onSearch(query);
|
||||
// Notify the dynamic data controller
|
||||
this.props.onSearch(query);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ define([], function() {
|
|||
return {
|
||||
"SearchInputPlaceholder": "Entrez vos termes 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",
|
||||
"SearchBoxPageUrlLabel": "URL de la page",
|
||||
"SearchBoxUrlErrorMessage": "Veuillez spécifier une URL valide",
|
||||
|
|
|
@ -1,22 +1,26 @@
|
|||
import ResultsLayoutOption from '../../models/ResultsLayoutOption';
|
||||
import { DynamicProperty } from '@microsoft/sp-component-base';
|
||||
import IRefinerConfiguration from '../../models/IRefinerConfiguration';
|
||||
import { ISortFieldConfiguration } from '../../models/ISortFieldConfiguration';
|
||||
import ISortableFieldConfiguration from '../../models/ISortableFieldConfiguration';
|
||||
|
||||
export interface ISearchResultsWebPartProps {
|
||||
queryKeywords: DynamicProperty<string>;
|
||||
defaultSearchQuery: string;
|
||||
useDefaultSearchQuery: boolean;
|
||||
queryTemplate: string;
|
||||
resultSourceId: string;
|
||||
sortList: string;
|
||||
sortList: ISortFieldConfiguration[];
|
||||
enableQueryRules: boolean;
|
||||
maxResultsCount: number;
|
||||
selectedProperties: string;
|
||||
refiners: string;
|
||||
sortableFields: string;
|
||||
refiners: IRefinerConfiguration[];
|
||||
sortableFields: ISortableFieldConfiguration[];
|
||||
showPaging: boolean;
|
||||
showResultsCount: boolean;
|
||||
showBlank: boolean;
|
||||
selectedLayout: ResultsLayoutOption;
|
||||
externalTemplateUrl: string;
|
||||
inlineTemplateText: string;
|
||||
useHandlebarsHelpers: boolean;
|
||||
webPartTitle: string;
|
||||
}
|
||||
|
|
|
@ -3,14 +3,9 @@
|
|||
"id": "42ad2740-3c60-49cf-971a-c44e33511b93",
|
||||
"alias": "SearchResultsWebPart",
|
||||
"componentType": "WebPart",
|
||||
|
||||
// The "*" signifies that the version should be taken from the package.json
|
||||
"version": "*",
|
||||
"manifestVersion": 2,
|
||||
|
||||
// If true, the component can only be installed on sites where Custom Script is allowed.
|
||||
// Components that allow authors to embed arbitrary script code should set this to true.
|
||||
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
|
||||
"supportsFullBleed": true,
|
||||
"requiresCustomScript": false,
|
||||
|
||||
"preconfiguredEntries": [
|
||||
|
@ -21,23 +16,43 @@
|
|||
},
|
||||
"title": {
|
||||
"default": "Search Results with Refiners",
|
||||
"fr-fr": "Résultats de recherche"
|
||||
"fr-fr": "Résultats de recherche"
|
||||
},
|
||||
"description": {
|
||||
"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",
|
||||
"properties": {
|
||||
"queryKeywords": "",
|
||||
"queryTemplate": "{searchTerms} Path:{Site}",
|
||||
"refiners": "Created:\"Created Date\",Size:\"Size of the file\"",
|
||||
"sortList": [
|
||||
{
|
||||
"sortField": "Created",
|
||||
"sortDirection": 1
|
||||
},
|
||||
{
|
||||
"sortField": "Size",
|
||||
"sortDirection": 2
|
||||
}
|
||||
],
|
||||
"refiners": [
|
||||
{
|
||||
"refinerName": "Created",
|
||||
"displayValue": "Created Date"
|
||||
},
|
||||
{
|
||||
"refinerName": "Size",
|
||||
"displayValue": "Size of the file"
|
||||
}
|
||||
],
|
||||
"selectedProperties": "Title,Path,Created,Filename,SiteLogo,PreviewUrl,PictureThumbnailURL,ServerRedirectedPreviewURL,ServerRedirectedURL,HitHighlightedSummary,FileType,contentclass,ServerRedirectedEmbedURL,DefaultEncodingURL",
|
||||
"enableQueryRules": false,
|
||||
"maxResultsCount": 10,
|
||||
"showBlank": 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 { Version, Text, Environment, EnvironmentType, DisplayMode, Log } from '@microsoft/sp-core-library';
|
||||
import {
|
||||
|
@ -14,7 +14,8 @@ import {
|
|||
PropertyPaneToggle,
|
||||
PropertyPaneSlider,
|
||||
IPropertyPaneChoiceGroupOption,
|
||||
PropertyPaneChoiceGroup
|
||||
PropertyPaneChoiceGroup,
|
||||
PropertyPaneCheckbox,
|
||||
} from '@microsoft/sp-webpart-base';
|
||||
import * as strings from 'SearchResultsWebPartStrings';
|
||||
import SearchResultsContainer from './components/SearchResultsContainer/SearchResultsContainer';
|
||||
|
@ -27,15 +28,16 @@ import TemplateService from '../../services/TemplateService/TemplateService';
|
|||
import { update, isEmpty } from '@microsoft/sp-lodash-subset';
|
||||
import MockSearchService from '../../services/SearchService/MockSearchService';
|
||||
import MockTemplateService from '../../services/TemplateService/MockTemplateService';
|
||||
import LocalizationHelper from '../../helpers/LocalizationHelper';
|
||||
import SearchService from '../../services/SearchService/SearchService';
|
||||
import TaxonomyService from '../../services/TaxonomyService/TaxonomyService';
|
||||
import MockTaxonomyService from '../../services/TaxonomyService/MockTaxonomyService';
|
||||
import ISearchResultsContainerProps from './components/SearchResultsContainer/ISearchResultsContainerProps';
|
||||
import { Placeholder, IPlaceholderProps } from '@pnp/spfx-controls-react/lib/Placeholder';
|
||||
import { PropertyFieldCollectionData, CustomCollectionFieldType } from '@pnp/spfx-property-controls/lib/PropertyFieldCollectionData';
|
||||
import { SPHttpClientResponse, SPHttpClient } from '@microsoft/sp-http';
|
||||
import { SortDirection, Sort } from '@pnp/sp';
|
||||
import { ISortFieldConfiguration, ISortFieldDirection } from '../../models/ISortFieldConfiguration';
|
||||
|
||||
declare var System: any;
|
||||
const LOG_SOURCE: string = '[SearchResultsWebPart_{0}]';
|
||||
|
||||
export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchResultsWebPartProps> {
|
||||
|
@ -51,17 +53,12 @@ export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchR
|
|||
*/
|
||||
private _templateContentToDisplay: string;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._parseFieldListString = this._parseFieldListString.bind(this);
|
||||
}
|
||||
|
||||
public async render(): Promise<void> {
|
||||
// Configure the provider before the query according to our needs
|
||||
this._searchService.resultsCount = this.properties.maxResultsCount;
|
||||
this._searchService.queryTemplate = await this.replaceQueryVariables(this.properties.queryTemplate);
|
||||
this._searchService.resultSourceId = this.properties.resultSourceId;
|
||||
this._searchService.sortList = this.properties.sortList;
|
||||
this._searchService.sortList = this._convertToSortList(this.properties.sortList);
|
||||
this._searchService.enableQueryRules = this.properties.enableQueryRules;
|
||||
|
||||
// Determine the template content to display
|
||||
|
@ -83,10 +80,9 @@ export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchR
|
|||
protected renderCompleted(): void {
|
||||
super.renderCompleted();
|
||||
|
||||
let queryKeywords;
|
||||
|
||||
let renderElement = null;
|
||||
if (typeof this.properties.useHandlebarsHelpers === 'undefined') {
|
||||
this.properties.useHandlebarsHelpers = true;
|
||||
}
|
||||
|
||||
// Get value from data source
|
||||
const dataSourceValue = this.properties.queryKeywords.tryGetValue();
|
||||
|
@ -96,6 +92,12 @@ export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchR
|
|||
this.context.propertyPane.refresh();
|
||||
}
|
||||
|
||||
if (!dataSourceValue) {
|
||||
queryKeywords = this.properties.defaultSearchQuery;
|
||||
} else {
|
||||
queryKeywords = dataSourceValue;
|
||||
}
|
||||
|
||||
const isValueConnected = !!this.properties.queryKeywords.tryGetSource();
|
||||
|
||||
const searchContainer: React.ReactElement<ISearchResultsContainerProps> = React.createElement(
|
||||
|
@ -103,14 +105,14 @@ export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchR
|
|||
{
|
||||
searchService: this._searchService,
|
||||
taxonomyService: this._taxonomyService,
|
||||
queryKeywords: this.properties.queryKeywords.tryGetValue(),
|
||||
queryKeywords: queryKeywords,
|
||||
maxResultsCount: this.properties.maxResultsCount,
|
||||
resultSourceId: this.properties.resultSourceId,
|
||||
sortList: this.properties.sortList,
|
||||
sortList: this._convertToSortList(this.properties.sortList),
|
||||
enableQueryRules: this.properties.enableQueryRules,
|
||||
selectedProperties: this.properties.selectedProperties ? this.properties.selectedProperties.replace(/\s|,+$/g, '').split(',') : [],
|
||||
refiners: this._parseFieldListString(this.properties.refiners),
|
||||
sortableFields: this._parseFieldListString(this.properties.sortableFields),
|
||||
refiners: this.properties.refiners,
|
||||
sortableFields: this.properties.sortableFields,
|
||||
showPaging: this.properties.showPaging,
|
||||
showResultsCount: this.properties.showResultsCount,
|
||||
showBlank: this.properties.showBlank,
|
||||
|
@ -133,7 +135,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;
|
||||
} else {
|
||||
if (this.displayMode === DisplayMode.Edit) {
|
||||
|
@ -148,6 +152,8 @@ export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchR
|
|||
|
||||
protected async onInit(): Promise<void> {
|
||||
|
||||
this.initializeRequiredProperties();
|
||||
|
||||
if (Environment.type === EnvironmentType.Local) {
|
||||
this._searchService = new MockSearchService();
|
||||
this._taxonomyService = new MockTaxonomyService();
|
||||
|
@ -155,15 +161,11 @@ export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchR
|
|||
|
||||
} else {
|
||||
|
||||
const lcid = LocalizationHelper.getLocaleId(this.context.pageContext.cultureInfo.currentUICultureName);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
await this._templateService.LoadHandlebarsHelpers(this.properties.useHandlebarsHelpers);
|
||||
|
||||
// Configure search query settings
|
||||
this._useResultSource = false;
|
||||
|
||||
|
@ -173,6 +175,32 @@ export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchR
|
|||
return super.onInit();
|
||||
}
|
||||
|
||||
private _convertToSortList(sortList: ISortFieldConfiguration[]): Sort[] {
|
||||
return sortList.map(e => {
|
||||
|
||||
let direction;
|
||||
|
||||
switch (e.sortDirection) {
|
||||
case ISortFieldDirection.Ascending:
|
||||
direction = SortDirection.Ascending;
|
||||
break;
|
||||
|
||||
case ISortFieldDirection.Descending:
|
||||
direction = SortDirection.Descending;
|
||||
break;
|
||||
|
||||
default:
|
||||
direction = SortDirection.Ascending;
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
Property: e.sortField,
|
||||
Direction: direction
|
||||
} as Sort;
|
||||
});
|
||||
}
|
||||
|
||||
protected onDispose(): void {
|
||||
ReactDom.unmountComponentAtNode(this.domElement);
|
||||
}
|
||||
|
@ -181,6 +209,37 @@ export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchR
|
|||
return Version.parse('1.0');
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the Web Part required properties if there are not present in the manifest (i.e. during an update scenario)
|
||||
*/
|
||||
private initializeRequiredProperties() {
|
||||
|
||||
this.properties.queryTemplate = this.properties.queryTemplate ? this.properties.queryTemplate : "{searchTerms} Path:{Site}";
|
||||
this.properties.refiners = Array.isArray(this.properties.refiners) ? this.properties.refiners : [
|
||||
{
|
||||
refinerName: "Created",
|
||||
displayValue: "Created Date"
|
||||
},
|
||||
{
|
||||
refinerName: "Size",
|
||||
displayValue: "Size of the file"
|
||||
}
|
||||
];
|
||||
this.properties.sortList = Array.isArray(this.properties.sortList) ? this.properties.sortList : [
|
||||
{
|
||||
sortField: "Created",
|
||||
sortDirection: ISortFieldDirection.Ascending
|
||||
},
|
||||
{
|
||||
sortField: "Size",
|
||||
sortDirection: ISortFieldDirection.Descending
|
||||
}
|
||||
];
|
||||
this.properties.sortableFields = Array.isArray(this.properties.sortableFields) ? this.properties.sortableFields : [];
|
||||
this.properties.selectedProperties = this.properties.selectedProperties ? this.properties.selectedProperties : "Title,Path,Created,Filename,SiteLogo,PreviewUrl,PictureThumbnailURL,ServerRedirectedPreviewURL,ServerRedirectedURL,HitHighlightedSummary,FileType,contentclass,ServerRedirectedEmbedURL,DefaultEncodingURL";
|
||||
this.properties.maxResultsCount = this.properties.maxResultsCount ? this.properties.maxResultsCount : 10;
|
||||
}
|
||||
|
||||
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
||||
|
||||
return {
|
||||
|
@ -234,6 +293,10 @@ export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchR
|
|||
|
||||
protected async onPropertyPaneFieldChanged(propertyPath: string) {
|
||||
|
||||
if (!this.properties.useDefaultSearchQuery) {
|
||||
this.properties.defaultSearchQuery = '';
|
||||
}
|
||||
|
||||
if (propertyPath === 'selectedLayout') {
|
||||
// Refresh setting the right template for the property pane
|
||||
await this._getTemplateContent();
|
||||
|
@ -251,8 +314,6 @@ export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchR
|
|||
this.properties.externalTemplateUrl = '';
|
||||
}
|
||||
}
|
||||
|
||||
await this._templateService.LoadHandlebarsHelpers(this.properties.useHandlebarsHelpers);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -294,39 +355,6 @@ export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchR
|
|||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a list of Fields from the property pane value by extracting the managed property and its label.
|
||||
* @param rawValue the raw value of the refiner
|
||||
*/
|
||||
private _parseFieldListString(rawValue: string): { [key: string]: string } {
|
||||
|
||||
let returnValues = {};
|
||||
if(!rawValue) { return returnValues; }
|
||||
|
||||
// Get each configuration
|
||||
let refinerKeyValuePair = rawValue.split(',');
|
||||
|
||||
if (refinerKeyValuePair.length > 0) {
|
||||
refinerKeyValuePair.map((e) => {
|
||||
|
||||
const refinerValues = e.split(':');
|
||||
switch (refinerValues.length) {
|
||||
case 1:
|
||||
// Take the same name as the refiner managed property
|
||||
returnValues[refinerValues[0]] = refinerValues[0];
|
||||
break;
|
||||
|
||||
case 2:
|
||||
// Trim quotes if present
|
||||
returnValues[refinerValues[0]] = refinerValues[1].replace(/^'(.*)'$/, '$1');
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return returnValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the correct results template content according to the property pane current configuration
|
||||
* @returns the template content as a string
|
||||
|
@ -489,21 +517,60 @@ export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchR
|
|||
onGetErrorMessage: this.validateSourceId.bind(this),
|
||||
deferredValidationTime: 300
|
||||
}),
|
||||
PropertyPaneTextField('sortList', {
|
||||
label: strings.Sort.SortList,
|
||||
description: strings.Sort.SortListDescription,
|
||||
multiline: false,
|
||||
resizable: true,
|
||||
PropertyFieldCollectionData('sortList', {
|
||||
manageBtnLabel: strings.ConfigureBtnLabel,
|
||||
key: 'sortList',
|
||||
panelHeader: strings.Sort.SortPropertyPaneFieldLabel,
|
||||
panelDescription: strings.Sort.SortListDescription,
|
||||
label: strings.Sort.SortPropertyPaneFieldLabel,
|
||||
value: this.properties.sortList,
|
||||
deferredValidationTime: 300
|
||||
fields: [
|
||||
{
|
||||
id: 'sortField',
|
||||
title: "Field name",
|
||||
type: CustomCollectionFieldType.string,
|
||||
required: true,
|
||||
placeholder: '\"Created\", \"Size\", etc.'
|
||||
},
|
||||
{
|
||||
id: 'sortDirection',
|
||||
title: "Direction",
|
||||
type: CustomCollectionFieldType.dropdown,
|
||||
required: true,
|
||||
options: [
|
||||
{
|
||||
key: ISortFieldDirection.Ascending,
|
||||
text: strings.Sort.SortDirectionAscendingLabel
|
||||
},
|
||||
{
|
||||
key: ISortFieldDirection.Descending,
|
||||
text: strings.Sort.SortDirectionDescendingLabel
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}),
|
||||
PropertyPaneTextField('sortableFields', {
|
||||
label: strings.SortableFieldsLabel,
|
||||
description: strings.SortableFieldsDescription,
|
||||
multiline: true,
|
||||
resizable: true,
|
||||
PropertyFieldCollectionData('sortableFields', {
|
||||
manageBtnLabel: strings.ConfigureBtnLabel,
|
||||
key: 'sortableFields',
|
||||
panelHeader: strings.Sort.SortableFieldsPropertyPaneField,
|
||||
panelDescription: strings.Sort.SortableFieldsDescription,
|
||||
label: strings.Sort.SortableFieldsPropertyPaneField,
|
||||
value: this.properties.sortableFields,
|
||||
deferredValidationTime: 300,
|
||||
fields: [
|
||||
{
|
||||
id: 'sortField',
|
||||
title: strings.Sort.SortableFieldManagedPropertyField,
|
||||
type: CustomCollectionFieldType.string,
|
||||
placeholder: '\"Created\", \"Size\", etc.',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
id: 'displayValue',
|
||||
title: strings.Sort.SortableFieldDisplayValueField,
|
||||
type: CustomCollectionFieldType.string
|
||||
}
|
||||
]
|
||||
}),
|
||||
PropertyPaneToggle('enableQueryRules', {
|
||||
label: strings.EnableQueryRulesLabel,
|
||||
|
@ -517,13 +584,26 @@ export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchR
|
|||
value: this.properties.selectedProperties,
|
||||
deferredValidationTime: 300
|
||||
}),
|
||||
PropertyPaneTextField('refiners', {
|
||||
label: strings.RefinersFieldLabel,
|
||||
description: strings.RefinersFieldDescription,
|
||||
multiline: true,
|
||||
resizable: true,
|
||||
PropertyFieldCollectionData('refiners', {
|
||||
manageBtnLabel: strings.ConfigureBtnLabel,
|
||||
key: 'refiners',
|
||||
panelHeader: strings.Refiners.RefinersFieldLabel,
|
||||
panelDescription: strings.Refiners.RefinersFieldDescription,
|
||||
label: strings.Refiners.RefinersFieldLabel,
|
||||
value: this.properties.refiners,
|
||||
deferredValidationTime: 300,
|
||||
fields: [
|
||||
{
|
||||
id: 'refinerName',
|
||||
title: strings.Refiners.RefinerManagedPropertyField,
|
||||
type: CustomCollectionFieldType.string,
|
||||
placeholder: '\"RefinableStringXXX\", etc.'
|
||||
},
|
||||
{
|
||||
id: 'displayValue',
|
||||
title: strings.Refiners.RefinerDisplayValueField,
|
||||
type: CustomCollectionFieldType.string
|
||||
}
|
||||
]
|
||||
}),
|
||||
PropertyPaneSlider('maxResultsCount', {
|
||||
label: strings.MaxResultsCount,
|
||||
|
@ -543,6 +623,30 @@ export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchR
|
|||
*/
|
||||
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 {
|
||||
primaryGroup: {
|
||||
groupFields: [
|
||||
|
@ -561,20 +665,29 @@ export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchR
|
|||
groupFields: [
|
||||
PropertyPaneDynamicFieldSet({
|
||||
label: strings.SearchQueryKeywordsFieldLabel,
|
||||
|
||||
fields: [
|
||||
PropertyPaneDynamicField('queryKeywords', {
|
||||
label: strings.SearchQueryKeywordsFieldLabel
|
||||
label: strings.SearchQueryKeywordsFieldLabel
|
||||
})
|
||||
],
|
||||
sharedConfiguration: {
|
||||
depth: DynamicDataSharedDepth.Source,
|
||||
}
|
||||
})
|
||||
]
|
||||
},
|
||||
}),
|
||||
].concat(defaultSearchQueryFields)
|
||||
},
|
||||
// Show the secondary group only if the web part has been
|
||||
// connected to a dynamic data source
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -642,10 +755,6 @@ export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchR
|
|||
dialogTitle: strings.DialogTitle,
|
||||
saveButtonText: strings.SaveButtonText
|
||||
}
|
||||
}),
|
||||
PropertyPaneToggle('useHandlebarsHelpers', {
|
||||
label: "Handlebars Helpers",
|
||||
checked: this.properties.useHandlebarsHelpers
|
||||
})
|
||||
];
|
||||
|
||||
|
|
|
@ -48,9 +48,14 @@ export default class FilterPanel extends React.Component<IFilterPanelProps, IFil
|
|||
// Initialize the Office UI grouped list
|
||||
this.props.availableFilters.map((filter, i) => {
|
||||
|
||||
// Get group name
|
||||
let groupName = filter.FilterName;
|
||||
const configuredFilter = this.props.refinersConfiguration.filter(e => { return e.refinerName === filter.FilterName;});
|
||||
groupName = configuredFilter.length > 0 && configuredFilter[0].displayValue ? configuredFilter[0].displayValue : groupName;
|
||||
|
||||
groups.push({
|
||||
key: i.toString(),
|
||||
name: this.props.refinersConfiguration[filter.FilterName],
|
||||
name: groupName,
|
||||
count: 1,
|
||||
startIndex: i,
|
||||
isDropEnabled: true,
|
||||
|
@ -133,12 +138,9 @@ export default class FilterPanel extends React.Component<IFilterPanelProps, IFil
|
|||
isOpen={this.state.showPanel}
|
||||
type={PanelType.custom}
|
||||
customWidth="450px"
|
||||
isBlocking={false}
|
||||
isLightDismiss={true}
|
||||
onDismiss={this._onClosePanel}
|
||||
headerText={strings.FilterPanelTitle}
|
||||
closeButtonAriaLabel='Close'
|
||||
hasCloseButton={true}
|
||||
onRenderBody={() => {
|
||||
if (this.props.availableFilters.length > 0) {
|
||||
return (
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import { IRefinementResult } from '../../../../models/ISearchResult';
|
||||
import RefinementFilterOperationCallback from '../../../../models/RefinementValueOperationCallback';
|
||||
import IRefinerConfiguration from '../../../../models/IRefinerConfiguration';
|
||||
|
||||
interface IFilterPanelProps {
|
||||
availableFilters: IRefinementResult[];
|
||||
refinersConfiguration: { [key: string]: string };
|
||||
refinersConfiguration: IRefinerConfiguration[];
|
||||
onUpdateFilters: RefinementFilterOperationCallback;
|
||||
resetSelectedFilters: boolean;
|
||||
}
|
||||
|
|
|
@ -33,12 +33,16 @@ export default class SearchResultsTemplate extends React.Component<ISearchResult
|
|||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
this.resize.removeResizeListener(this.parentRef, this.onComponentResize);
|
||||
try {
|
||||
this.resize.removeResizeListener(this.parentRef, this.onComponentResize);
|
||||
} catch (error) {}
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
this._updateTemplate(this.props);
|
||||
this.resize.addResizeListener(this.parentRef, this.onComponentResize);
|
||||
try {
|
||||
this.resize.addResizeListener(this.parentRef, this.onComponentResize);
|
||||
} catch (error) {}
|
||||
}
|
||||
|
||||
public componentDidUpdate() {
|
||||
|
|
|
@ -3,6 +3,9 @@ import ITaxonomyService from '../../../../services/TaxonomyService/ITaxonomyServ
|
|||
import { DisplayMode } from '@microsoft/sp-core-library';
|
||||
import TemplateService from '../../../../services/TemplateService/TemplateService';
|
||||
import { WebPartContext } from '@microsoft/sp-webpart-base';
|
||||
import IRefinerConfiguration from '../../../../models/IRefinerConfiguration';
|
||||
import { Sort } from '@pnp/sp';
|
||||
import ISortableFieldConfiguration from '../../../../models/ISortableFieldConfiguration';
|
||||
|
||||
interface ISearchResultsContainerProps {
|
||||
|
||||
|
@ -39,7 +42,7 @@ interface ISearchResultsContainerProps {
|
|||
/**
|
||||
* The sort order of the results
|
||||
*/
|
||||
sortList: string;
|
||||
sortList: Sort[];
|
||||
|
||||
/**
|
||||
* Enable SharePoint query rules
|
||||
|
@ -54,12 +57,12 @@ interface ISearchResultsContainerProps {
|
|||
/**
|
||||
* The managed properties used as refiners for the query
|
||||
*/
|
||||
refiners: { [key: string]: string };
|
||||
refiners: IRefinerConfiguration[];
|
||||
|
||||
/**
|
||||
* The managed properties used as sortable fields for the query
|
||||
*/
|
||||
sortableFields: { [key: string]: string };
|
||||
sortableFields: ISortableFieldConfiguration[];
|
||||
|
||||
/**
|
||||
* Show the paging control
|
||||
|
|
|
@ -15,6 +15,9 @@ import SearchResultsTemplate from '../Layouts/SearchResultsTemplate';
|
|||
import styles from '../SearchResultsWebPart.module.scss';
|
||||
import { SortPanel } from '../SortPanel';
|
||||
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;
|
||||
let FilterPanel = null;
|
||||
|
@ -176,7 +179,7 @@ export default class SearchResultsContainer extends React.Component<ISearchConta
|
|||
|
||||
this.props.searchService.selectedProperties = this.props.selectedProperties;
|
||||
|
||||
const refinerManagedProperties = Object.keys(this.props.refiners).join(',');
|
||||
const refinerManagedProperties = this.props.refiners.map(e => { return e.refinerName ;}).join(',');
|
||||
|
||||
const searchResults = await this.props.searchService.search(this.props.queryKeywords, refinerManagedProperties, this.state.selectedFilters, this.state.currentPage);
|
||||
const localizedFilters = await this._getLocalizedFilters(searchResults.RefinementResults);
|
||||
|
@ -242,10 +245,10 @@ export default class SearchResultsContainer extends React.Component<ISearchConta
|
|||
|
||||
this.props.searchService.selectedProperties = nextProps.selectedProperties;
|
||||
|
||||
const refinerManagedProperties = Object.keys(nextProps.refiners).join(',');
|
||||
const refinerManagedProperties = nextProps.refiners.map(e => { return e.refinerName ;}).join(',');
|
||||
|
||||
// Reset sortlist
|
||||
this.props.searchService.sortList = this.props.sortList;
|
||||
this.props.searchService.sortList = nextProps.sortList;
|
||||
|
||||
// We reset the page number and refinement filters
|
||||
const searchResults = await this.props.searchService.search(nextProps.queryKeywords, refinerManagedProperties, [], 1);
|
||||
|
@ -282,7 +285,9 @@ export default class SearchResultsContainer extends React.Component<ISearchConta
|
|||
}
|
||||
} else {
|
||||
this.setState({
|
||||
areResultsLoading: false
|
||||
areResultsLoading: false,
|
||||
lastQuery: '',
|
||||
results: { RefinementResults: [], RelevantResults: [] },
|
||||
});
|
||||
}
|
||||
} else {
|
||||
|
@ -317,7 +322,7 @@ export default class SearchResultsContainer extends React.Component<ISearchConta
|
|||
areResultsLoading: true,
|
||||
});
|
||||
|
||||
const refinerManagedProperties = Object.keys(this.props.refiners).join(',');
|
||||
const refinerManagedProperties = this.props.refiners.map(e => { return e.refinerName ;}).join(',');
|
||||
|
||||
const searchResults = await
|
||||
this.props.searchService.search(this.props.queryKeywords, refinerManagedProperties, newFilters, 1);
|
||||
|
@ -348,9 +353,9 @@ export default class SearchResultsContainer extends React.Component<ISearchConta
|
|||
errorMessage:null
|
||||
});
|
||||
|
||||
const refinerManagedProperties = Object.keys(this.props.refiners).join(',');
|
||||
const refinerManagedProperties = this.props.refiners.map(e => { return e.refinerName ;}).join(',');
|
||||
|
||||
this.props.searchService.sortList = `${sortField}:${SortDirection[sortDirection].toLocaleLowerCase()}`;
|
||||
this.props.searchService.sortList = [{Property: sortField, Direction: sortDirection}];
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -386,7 +391,7 @@ export default class SearchResultsContainer extends React.Component<ISearchConta
|
|||
areResultsLoading: true,
|
||||
});
|
||||
|
||||
const refinerManagedProperties = Object.keys(this.props.refiners).join(',');
|
||||
const refinerManagedProperties = this.props.refiners.map(e => { return e.refinerName ;}).join(',');
|
||||
|
||||
const searchResults = await this.props.searchService.search(this.props.queryKeywords, refinerManagedProperties, this.state.selectedFilters, pageNumber);
|
||||
|
||||
|
@ -404,8 +409,12 @@ export default class SearchResultsContainer extends React.Component<ISearchConta
|
|||
*/
|
||||
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 updatedFilters = [];
|
||||
let localizedTerms = [];
|
||||
|
||||
rawFilters.map((filterResult) => {
|
||||
|
||||
|
@ -432,45 +441,49 @@ export default class SearchResultsContainer extends React.Component<ISearchConta
|
|||
|
||||
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...
|
||||
await this.props.taxonomyService.initialize();
|
||||
// Get the terms from taxonomy
|
||||
// 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 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
|
||||
if (!currentTerm.get_serverObjectIsNull()) {
|
||||
// Should be always unique since we can't have two terms with the same ids
|
||||
const termFromTaxonomy: ITerm & ITermData = termsFromTaxonomy[0];
|
||||
|
||||
const termId = currentTerm.get_id();
|
||||
|
||||
// Check if retrieved term is part of terms to localize
|
||||
const terms = termsToLocalize.filter((e) => { return e.termId === termId.toString(); });
|
||||
if (terms.length > 0) {
|
||||
termsToLocalize = termsToLocalize.map((term) => {
|
||||
if (term.termId === terms[0].termId) {
|
||||
return {
|
||||
uniqueIdentifier: term.uniqueIdentifier,
|
||||
termId: termId.toString(),
|
||||
localizedTermLabel: termsEnumerator.get_current().get_name(),
|
||||
};
|
||||
} else {
|
||||
return term;
|
||||
}
|
||||
});
|
||||
}
|
||||
// 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
|
||||
const localizedLabel = termFromTaxonomy["Labels"]._Child_Items_.filter((label: any) => {
|
||||
return label.Language === lcid;
|
||||
});
|
||||
|
||||
localizedTerms.push({
|
||||
uniqueIdentifier: termToLocalize.uniqueIdentifier,
|
||||
termId: termToLocalize.termId,
|
||||
localizedTermLabel: localizedLabel.length > 0 ? localizedLabel[0].Value : termFromTaxonomy.Name
|
||||
});
|
||||
} else {
|
||||
localizedTerms.push({
|
||||
uniqueIdentifier: termToLocalize.uniqueIdentifier,
|
||||
termId: termToLocalize.termId,
|
||||
localizedTermLabel: Text.format(strings.TermNotFound, termToLocalize.termId)
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Update original filters with localized values
|
||||
rawFilters.map((filter) => {
|
||||
let updatedValues = [];
|
||||
|
||||
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) {
|
||||
updatedValues.push({
|
||||
RefinementCount: value.RefinementCount,
|
||||
|
|
|
@ -30,6 +30,11 @@
|
|||
color: "[theme: themePrimary, default: #005a9e]";
|
||||
}
|
||||
|
||||
&__sortDropdown {
|
||||
min-width: 135px;
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
&__sortResultBtn {
|
||||
color: "[theme: themePrimary]";
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import UpdateSortOperationCallback from '../../../../models/UpdateSortOperationCallback';
|
||||
import { SortDirection } from "@pnp/sp";
|
||||
import ISortableFieldConfiguration from '../../../../models/ISortableFieldConfiguration';
|
||||
|
||||
interface ISortPanelProps {
|
||||
sortableFieldsConfiguration: { [key: string]: string };
|
||||
sortableFieldsConfiguration: ISortableFieldConfiguration[];
|
||||
onUpdateSort: UpdateSortOperationCallback;
|
||||
sortDirection?:SortDirection;
|
||||
sortField?:string;
|
||||
|
|
|
@ -4,7 +4,7 @@ import ISortPanelState from './ISortP
|
|||
import { Dropdown, IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown';
|
||||
import * as strings from 'SearchResultsWebPartStrings';
|
||||
import { ActionButton } from 'office-ui-fabric-react/lib/Button';
|
||||
import { SortDirection } from "@pnp/sp";
|
||||
import { SortDirection } from '@pnp/sp';
|
||||
import styles from '../SearchResultsWebPart.module.scss';
|
||||
|
||||
export default class SortPanel extends React.Component<ISortPanelProps, ISortPanelState> {
|
||||
|
@ -17,14 +17,13 @@ export default class SortPanel extends React.Component<ISortPanelProps, ISortPan
|
|||
sortField:this.props.sortField ? this.props.sortField : null
|
||||
};
|
||||
|
||||
this._getSortableFieldCount = this._getSortableFieldCount.bind(this);
|
||||
this._setSortDirection = this._setSortDirection.bind(this);
|
||||
this._getDropdownOptions = this._getDropdownOptions.bind(this);
|
||||
this._onChangedSelectedField = this._onChangedSelectedField.bind(this);
|
||||
}
|
||||
|
||||
public render(): React.ReactElement<ISortPanelProps> {
|
||||
if (this._getSortableFieldCount() === 0) return <span />;
|
||||
if (this.props.sortableFieldsConfiguration.length === 0) return <span />;
|
||||
|
||||
const dropdownOptions: IDropdownOption[] = this._getDropdownOptions();
|
||||
|
||||
|
@ -42,6 +41,7 @@ export default class SortPanel extends React.Component<ISortPanelProps, ISortPan
|
|||
}}
|
||||
/>
|
||||
<Dropdown
|
||||
className={ styles.searchWp__sortDropdown }
|
||||
placeHolder={strings.Sort.SortPanelSortFieldPlaceHolder}
|
||||
ariaLabel={strings.Sort.SortPanelSortFieldAria}
|
||||
onChanged={this._onChangedSelectedField}
|
||||
|
@ -52,14 +52,6 @@ export default class SortPanel extends React.Component<ISortPanelProps, ISortPan
|
|||
);
|
||||
}
|
||||
|
||||
private _getSortableFieldCount() {
|
||||
if(!this.props.sortableFieldsConfiguration) return 0;
|
||||
|
||||
return Object.keys(this.props.sortableFieldsConfiguration).filter(value => {
|
||||
return value;
|
||||
}).length;
|
||||
}
|
||||
|
||||
private _setSortDirection() {
|
||||
|
||||
let sortDirection;
|
||||
|
@ -86,14 +78,16 @@ export default class SortPanel extends React.Component<ISortPanelProps, ISortPan
|
|||
}
|
||||
|
||||
private _getDropdownOptions():IDropdownOption[] {
|
||||
let dropdownOptions:IDropdownOption[] = [];
|
||||
const sortableFields = Object.keys(this.props.sortableFieldsConfiguration);
|
||||
|
||||
sortableFields.forEach((fieldKey) => {
|
||||
//Strip " from start and end of the display name if present
|
||||
const fieldDisplayName = this.props.sortableFieldsConfiguration[fieldKey].replace(/^\"+|\"+$/g, '');
|
||||
dropdownOptions.push({ key: fieldKey, text: fieldDisplayName});
|
||||
let dropdownOptions:IDropdownOption[] = [];
|
||||
|
||||
this.props.sortableFieldsConfiguration.map(e => {
|
||||
dropdownOptions.push({
|
||||
key: e.sortField,
|
||||
text: e.displayValue
|
||||
});
|
||||
});
|
||||
|
||||
return dropdownOptions;
|
||||
}
|
||||
|
||||
|
|
|
@ -8,12 +8,8 @@ define([], function() {
|
|||
"LoadingMessage": "Results are loading, please wait...",
|
||||
"MaxResultsCount": "Number of items to retrieve per page",
|
||||
"NoResultMessage": "There are no results to show",
|
||||
"RefinersFieldLabel": "Refiners",
|
||||
"SortableFieldsLabel": "Sortable fields",
|
||||
"FilterPanelTitle": "Available filters",
|
||||
"SortPanelTitle":"Sort",
|
||||
"FilterResultsButtonLabel": "Filters",
|
||||
"SortResultsButtonLabel":"Sort",
|
||||
"SelectedFiltersLabel": "Selected filters:",
|
||||
"RemoveAllFiltersLabel": "Remove all filters",
|
||||
"ShowPagingLabel": "Show paging",
|
||||
|
@ -31,8 +27,6 @@ define([], function() {
|
|||
"InvalidResultSourceIdMessage": "Invalid identifier",
|
||||
"EnableQueryRulesLabel": "Enable query rules",
|
||||
"StylingSettingsGroupName": "Styling options",
|
||||
"RefinersFieldDescription": "Specifies managed properties used as refiners (ordered comma-separated list). You can specify the label by using the following format <Managed Property Name>:\"My friendly name\".",
|
||||
"SortableFieldsDescription": "Specifies sortable properties used by the sort panel (ordered comma-separated list). You can specify the label by using the following format <Managed Property Name>:\"My friendly name\".",
|
||||
"SelectedPropertiesFieldDescription": "Speficies the properties to retrieve from the search results.",
|
||||
"SearchQueryKeywordsFieldDescription": "Use pre-defined search query keywords to retrieve a static set of results.",
|
||||
"CountMessageLong": "<b>{0}</b> results for '<em>{1}</em>'",
|
||||
|
@ -54,8 +48,10 @@ define([], function() {
|
|||
"PromotedResultsLabel": "Promoted result(s)",
|
||||
"PanelCloseButtonAria":"Close",
|
||||
"Sort": {
|
||||
"SortList": "Initial sort order",
|
||||
"SortListDescription": "Specify initial sort order in a comma separated list on the format <Managed Property Name>:ascending/descending (default:Created:descending,Size:ascending).",
|
||||
"SortableFieldsPropertyPaneField": "Sortable properties",
|
||||
"SortableFieldsDescription": "Specifies sortable properties that users can use in the UI. Only one property can be used at a time for sorting and will override the search order specified in the WP if exists.",
|
||||
"SortPropertyPaneFieldLabel": "Sort order",
|
||||
"SortListDescription": "Specify the sort order for the search results. This will only applied when no manual filters have been set (i.e. sortable fields)",
|
||||
"SortDirectionAscendingLabel":"Ascending",
|
||||
"SortDirectionDescendingLabel":"Descending",
|
||||
"SortErrorMessage":"Invalid search property (Check if the managed property is sortable).",
|
||||
|
@ -63,6 +59,19 @@ define([], function() {
|
|||
"SortPanelSortFieldAria":"Select a field",
|
||||
"SortPanelSortFieldPlaceHolder":"Select a field",
|
||||
"SortPanelSortDirectionLabel":"Sort Direction",
|
||||
}
|
||||
"SortableFieldManagedPropertyField": "Sort managed property",
|
||||
"SortableFieldDisplayValueField": "Field name to display"
|
||||
},
|
||||
"Refiners": {
|
||||
"RefinersFieldLabel": "Refiners",
|
||||
"RefinerManagedPropertyField": "Filter managed property",
|
||||
"RefinerDisplayValueField": "Filter name to display",
|
||||
"RefinersFieldDescription": "Specifies managed properties used as refiners. If there are no values for a filter property, it won't appear in the panel.",
|
||||
},
|
||||
"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.",
|
||||
"ConfigureBtnLabel": "Configure"
|
||||
}
|
||||
});
|
|
@ -8,12 +8,8 @@ define([], function() {
|
|||
"LoadingMessage": "Les résultats sont en cours de chargement, veuillez patienter...",
|
||||
"MaxResultsCount": "Nombre de résulats à récupérer par page",
|
||||
"NoResultMessage": "Il n'y a aucun résultat à afficher.",
|
||||
"RefinersFieldLabel": "Filtres",
|
||||
"SortableFieldsLabel": "Triables",
|
||||
"FilterPanelTitle": "Filtres disponibles",
|
||||
"SortPanelTitle":"Trier",
|
||||
"FilterResultsButtonLabel": "Filtrer",
|
||||
"SortResultsButtonLabel":"Trier",
|
||||
"SelectedFiltersLabel": "Filtre(s) appliqué(s):",
|
||||
"RemoveAllFiltersLabel": "Supprimer tous les filtres",
|
||||
"ShowPagingLabel": "Afficher la pagination",
|
||||
|
@ -31,8 +27,6 @@ define([], function() {
|
|||
"InvalidResultSourceIdMessage": "Identifiant invalide",
|
||||
"EnableQueryRulesLabel": "Activer les règles de requête",
|
||||
"StylingSettingsGroupName": "Options d'affichage",
|
||||
"RefinersFieldDescription": "Propriétés gerées à utiliser comme filtres (liste ordonnée séparée par une virgule). Vous pouvez spécifier un label personnalisé en utilisant le format suivant <Nom de la propriété gérée>:\"Nom convivial\".",
|
||||
"SortableFieldsDescription": "Propriétés gerées à utiliser comme triables (liste ordonnée séparée par une virgule). Vous pouvez spécifier un label personnalisé en utilisant le format suivant <Nom de la propriété gérée>:\"Nom convivial\".",
|
||||
"SelectedPropertiesFieldDescription": "Propriétés à récupérer des résulats de recherche.",
|
||||
"SearchQueryKeywordsFieldDescription": "Utilisez une requête de recherche prédéfinie pour obtenir un ensemble de résultats statique.",
|
||||
"CountMessageLong": "<b>{0}</b> résultats pour '<em>{1}</em>'",
|
||||
|
@ -54,15 +48,30 @@ define([], function() {
|
|||
"PromotedResultsLabel": "Résultat(s) promu(s)",
|
||||
"PanelCloseButtonAria":"Proche",
|
||||
"Sort": {
|
||||
"SortList": "Ordre de tri",
|
||||
"SortListDescription": "Spécifiez l'ordre de tri dans une liste séparée par des virgules au format <Nom de la propriété gérée>:ascending/descending (par défaut:Created:descending,Size:ascending).",
|
||||
"SortableFieldsPropertyPaneField":"Propriétés triables",
|
||||
"SortableFieldsDescription": "Propriétés à utiliser pour permettre aux utilisateurs de trier les résultats depuis l'interface. Le tri ne peut être porter que sur une seule propriété à la fois et surpasse le tri par défaut des résulats si existant.",
|
||||
"SortPropertyPaneFieldLabel":"Ordre de tri",
|
||||
"SortListDescription": "Spécifiez l'ordre de tri des résultats de recherche. Ceux-ci ne s'appliqueront que si aucun champ de tri n'a été configurée pour ce Web Part (i.e propriétés triables)",
|
||||
"SortDirectionAscendingLabel":"Ascendant",
|
||||
"SortDirectionDescendingLabel":"Descendant",
|
||||
"SortErrorMessage":"Propriété de recherche non valide (Vérifiez si la propriété managée est triable).",
|
||||
"SortPanelSortFieldLabel":"Trier sur le champ",
|
||||
"SortPanelSortFieldAria":"Sélectionner un champ",
|
||||
"SortPanelSortFieldPlaceHolder":"Sélectionner un champ",
|
||||
"SortPanelSortDirectionLabel":"Direction de tri",
|
||||
}
|
||||
"SortPanelSortDirectionLabel":"Direction de tri",
|
||||
"SortableFieldManagedPropertyField": "Propriété gérée de tri",
|
||||
"SortableFieldDisplayValueField": "Intitulé du champ à afficher"
|
||||
},
|
||||
"Refiners": {
|
||||
"RefinersFieldLabel": "Filtres",
|
||||
"RefinersFieldDescription": "Configurez ici les propriétés gerées à utiliser comme filtres. Si il n'existe pas de valeurs pour le filtre spécifié, il n'apparaîtra pas dans le panneau.",
|
||||
"RefinerManagedPropertyField": "Propriété gérée de filtre",
|
||||
"RefinerDisplayValueField": "Intitulé du filtre à afficher",
|
||||
},
|
||||
"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.",
|
||||
"ConfigureBtnLabel": "Configurer"
|
||||
}
|
||||
});
|
|
@ -9,14 +9,10 @@ declare interface ISearchResultsWebPartStrings {
|
|||
LoadingMessage: string;
|
||||
MaxResultsCount: string;
|
||||
NoResultMessage: string;
|
||||
RefinersFieldLabel: string;
|
||||
SortableFieldsLabel: string;
|
||||
RefinersFieldDescription: string;
|
||||
SortableFieldsDescription: string;
|
||||
FilterPanelTitle: string;
|
||||
SortPanelTitle: string;
|
||||
FilterResultsButtonLabel: string;
|
||||
SortResultsButtonLabel:string;
|
||||
SelectedFiltersLabel: string;
|
||||
RemoveAllFiltersLabel: string;
|
||||
ShowPagingLabel: string;
|
||||
|
@ -52,8 +48,9 @@ declare interface ISearchResultsWebPartStrings {
|
|||
HandlebarsHelpersDescription: string;
|
||||
PromotedResultsLabel: string;
|
||||
PanelCloseButtonAria:string;
|
||||
ConfigureBtnLabel: string;
|
||||
Sort: {
|
||||
SortList: string;
|
||||
SortPropertyPaneFieldLabel
|
||||
SortListDescription: string;
|
||||
SortDirectionAscendingLabel:string;
|
||||
SortDirectionDescendingLabel:string;
|
||||
|
@ -61,8 +58,22 @@ declare interface ISearchResultsWebPartStrings {
|
|||
SortPanelSortFieldLabel:string;
|
||||
SortPanelSortFieldAria:string;
|
||||
SortPanelSortFieldPlaceHolder:string;
|
||||
SortPanelSortDirectionLabel:string;
|
||||
}
|
||||
SortPanelSortDirectionLabel:string;
|
||||
SortableFieldsPropertyPaneField: string;
|
||||
SortableFieldsDescription: string;
|
||||
SortableFieldManagedPropertyField: string;
|
||||
SortableFieldDisplayValueField: string;
|
||||
},
|
||||
Refiners: {
|
||||
RefinersFieldLabel: string;
|
||||
RefinersFieldDescription: string;
|
||||
RefinerManagedPropertyField: string;
|
||||
RefinerDisplayValueField: string;
|
||||
},
|
||||
TermNotFound: string;
|
||||
UseDefaultSearchQueryKeywordsFieldLabel: string;
|
||||
DefaultSearchQueryKeywordsFieldLabel: string;
|
||||
DefaultSearchQueryKeywordsFieldDescription: string;
|
||||
}
|
||||
|
||||
declare module 'SearchResultsWebPartStrings' {
|
||||
|
|