|
@ -33,7 +33,7 @@ Whenever you are submitting any changes to the SharePoint repositories, please f
|
||||||
When you are submitting a new sample, it has to follow up below guidelines
|
When you are submitting a new sample, it has to follow up below guidelines
|
||||||
|
|
||||||
* You will need to have a `README.md` file for your contribution, which is based on [the provided template](../samples/README-template.md) under the `samples` folder. Please copy this template to your project and update it accordingly. Your `README.md` must be named exactly `README.md` -- with capital letters -- as this is the information we use to make your sample public.
|
* You will need to have a `README.md` file for your contribution, which is based on [the provided template](../samples/README-template.md) under the `samples` folder. Please copy this template to your project and update it accordingly. Your `README.md` must be named exactly `README.md` -- with capital letters -- as this is the information we use to make your sample public.
|
||||||
* You will need to have a screenshot picture of your sample in action in the `README.md` file ("pics or it didn't happen"). The preview image must be located in the `/assets/` folder in the root your you solution.
|
* You will need to have a screenshot picture of your sample in action in the `README.md` file ("pics or it didn't happen"). The preview image must be located in the `/assets/` folder in the root of your solution.
|
||||||
* The `README` template contains a specific tracking image at the bottom of the file with an `img` tag, where the `src` attribute points to `https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/readme-template`. This is a transparent image which is used to track viewership of individual samples in GitHub.
|
* The `README` template contains a specific tracking image at the bottom of the file with an `img` tag, where the `src` attribute points to `https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/readme-template`. This is a transparent image which is used to track viewership of individual samples in GitHub.
|
||||||
* Update the image `src` attribute according with the repository name and folder information. For example, if your sample is named `react-todo` in the `samples` folder, you should update the `src` attribute to `https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-todo`
|
* Update the image `src` attribute according with the repository name and folder information. For example, if your sample is named `react-todo` in the `samples` folder, you should update the `src` attribute to `https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-todo`
|
||||||
* If you find an existing sample which is similar to yours, please extend the existing one rather than submitting a new similar sample
|
* If you find an existing sample which is similar to yours, please extend the existing one rather than submitting a new similar sample
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
name: "\U0001F41E Bug"
|
name: "\U0001F41E Bug"
|
||||||
about: Report an anomaly or unexpected behavior with a sample from this repository.
|
about: Report an anomaly or unexpected behavior with a sample from this repository.
|
||||||
title: ''
|
title: '[Web Part Name]: [Issue Title]'
|
||||||
labels: type:bug
|
labels: type:bug
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
|
@ -19,19 +19,41 @@ Thank you for reporting a bug! Use the sections below to submit a bug ONLY if it
|
||||||
|
|
||||||
DELETE EVERYTHING ABOVE AFTER READING - THANKS!
|
DELETE EVERYTHING ABOVE AFTER READING - THANKS!
|
||||||
|
|
||||||
## Sample (which sample are you having trouble with)
|
## Sample
|
||||||
|
> Which sample are you having trouble with?
|
||||||
|
>
|
||||||
|
> Issues without a sample name will be closed automatically
|
||||||
|
>
|
||||||
|
> _(DELETE THIS PARAGRAPH AFTER READING)_
|
||||||
|
|
||||||
## Authors
|
## Authors
|
||||||
|
|
||||||
> Because of the way this repository is configured, samples authors do not get a notification when you create an issue. *It makes it less likely for you to get your issue resolved or to get help*. For the section above **@mention any author of the sample**. Authors' GitHub handle can be found on the main sample documentation page, under the "solution" section. Use the `PREVIEW` tab at the top right to preview the rendering before submitting your issue.
|
> Because of the way this repository is configured, samples authors do not get a notification when you create an issue. *It makes it less likely for you to get your issue resolved or to get help*. For the section above **@mention any author of the sample**. Authors' GitHub handle can be found on the main sample documentation page, under the "solution" section. Use the `PREVIEW` tab at the top right to preview the rendering before submitting your issue.
|
||||||
|
>
|
||||||
|
> The author's name can be found in the sample's `README.md` file, under **Authors**.
|
||||||
>
|
>
|
||||||
|
> Issues without an author will be closed automatically
|
||||||
|
>
|
||||||
> _(DELETE THIS PARAGRAPH AFTER READING)_
|
> _(DELETE THIS PARAGRAPH AFTER READING)_
|
||||||
|
|
||||||
|
|
||||||
## Expected or Desired Behavior
|
## Expected or Desired Behavior
|
||||||
|
|
||||||
|
> What should it do?
|
||||||
|
>
|
||||||
|
> Issues without an expected behavior will be closed automatically
|
||||||
|
>
|
||||||
|
> _(DELETE THIS PARAGRAPH AFTER READING)_
|
||||||
|
|
||||||
|
|
||||||
## Observed Behavior
|
## Observed Behavior
|
||||||
|
|
||||||
|
> What does it do?
|
||||||
|
>
|
||||||
|
> Issues without an observed behavior will be closed automatically
|
||||||
|
>
|
||||||
|
> _(DELETE THIS PARAGRAPH AFTER READING)_
|
||||||
|
|
||||||
|
|
||||||
## Steps to Reproduce
|
## Steps to Reproduce
|
||||||
|
|
||||||
|
@ -41,6 +63,11 @@ Steps to reproduce the behavior:
|
||||||
3. Scroll down to '....'
|
3. Scroll down to '....'
|
||||||
4. See error
|
4. See error
|
||||||
|
|
||||||
|
> Issues without an steps to reproduce will be closed automatically
|
||||||
|
>
|
||||||
|
> _(DELETE THIS PARAGRAPH AFTER READING)_
|
||||||
|
|
||||||
|
|
||||||
# Environment Details (*Development & Target environment*)
|
# Environment Details (*Development & Target environment*)
|
||||||
|
|
||||||
- **OS**: [e.g. Windows 10 | MacOS 10.15.x]
|
- **OS**: [e.g. Windows 10 | MacOS 10.15.x]
|
||||||
|
@ -50,4 +77,8 @@ Steps to reproduce the behavior:
|
||||||
- **Tooling**: [e.g. VS Code | SPFx v1.10.0 | Visual Studio 2019]
|
- **Tooling**: [e.g. VS Code | SPFx v1.10.0 | Visual Studio 2019]
|
||||||
- **Additional details**: *The more context you can provide, the easier it is (and therefore quicker) to help.*
|
- **Additional details**: *The more context you can provide, the easier it is (and therefore quicker) to help.*
|
||||||
|
|
||||||
|
> Issues without environment details will be closed automatically
|
||||||
|
>
|
||||||
|
> _(DELETE THIS PARAGRAPH AFTER READING)_
|
||||||
|
|
||||||
## Additional context
|
## Additional context
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
blank_issues_enabled: false
|
blank_issues_enabled: false
|
||||||
contact_links:
|
contact_links:
|
||||||
|
- name: Start a discussion
|
||||||
|
about: 🗨 Start a friendly conversation with the other members of the community about the samples in this repository.
|
||||||
|
url: https://github.com/pnp/sp-dev-fx-webparts/discussions
|
||||||
- name: SharePoint Developer Documentation
|
- name: SharePoint Developer Documentation
|
||||||
url: https://github.com/SharePoint/sp-dev-docs/issues/new
|
url: https://github.com/SharePoint/sp-dev-docs/issues/new
|
||||||
about: 📚 Issue related to the SharePoint Framework or its documentation? Submit an issue here.
|
about: 📚 Issue related to the SharePoint Framework or its documentation? Submit an issue here.
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
name: "\U0001F4A1 Suggestion"
|
name: "\U0001F4A1 Suggestion"
|
||||||
about: Suggest an enhancement to make one of our web part samples even better
|
about: Suggest an enhancement to make one of our web part samples even better
|
||||||
title: Let's make SAMPLE even better
|
title: '[Web Part Name]: [Suggestion Title]'
|
||||||
labels: type:enhancement
|
labels: type:enhancement
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
|
@ -18,13 +18,26 @@ Thank you for your suggestion! Use the sections below to submit a suggestion ONL
|
||||||
|
|
||||||
DELETE EVERYTHING ABOVE AFTER READING - THANKS!
|
DELETE EVERYTHING ABOVE AFTER READING - THANKS!
|
||||||
|
|
||||||
## Sample (which sample are you talking about)
|
## Sample
|
||||||
|
> Which sample are you talking about
|
||||||
|
>
|
||||||
|
> Suggestions without a sample name will be closed automatically
|
||||||
|
>
|
||||||
|
> _(DELETE THIS PARAGRAPH AFTER READING)_
|
||||||
|
|
||||||
|
|
||||||
## Authors
|
## Authors
|
||||||
|
|
||||||
> Because of the way this repository is configured, samples authors do not get a notification when you create an issue. *It makes it less likely for you to get your issue resolved or to get help*. For the section above **@mention any author of the sample**. The authors' GitHub handle can be found on the main sample documentation page, under the "solution" section. Use the `PREVIEW` tab at the top right to preview the rendering before submitting your issue.
|
> Because of the way this repository is configured, samples authors do not get a notification when you create an issue. *It makes it less likely for you to get your issue resolved or to get help*. For the section above **@mention any author of the sample**. The authors' GitHub handle can be found on the main sample documentation page, under the "solution" section. Use the `PREVIEW` tab at the top right to preview the rendering before submitting your issue.
|
||||||
>
|
>
|
||||||
|
> The authors can be found in the sample's `README.md` file, under **Authors**
|
||||||
|
>
|
||||||
|
> Suggestions without an author will be closed automatically
|
||||||
|
>
|
||||||
> _(DELETE THIS PARAGRAPH AFTER READING)_
|
> _(DELETE THIS PARAGRAPH AFTER READING)_
|
||||||
|
|
||||||
## Suggestion (the more details, the better)
|
## Suggestion
|
||||||
|
|
||||||
|
> The more details, the better
|
||||||
|
>
|
||||||
|
> _(DELETE THIS PARAGRAPH AFTER READING)_
|
||||||
|
|
|
@ -15,20 +15,30 @@ Thank you for asking a question! Use the sections below to submit a question ONL
|
||||||
- Include sufficient details and context.
|
- Include sufficient details and context.
|
||||||
- If you have multiple questions please submit them separately so we can track resolution.
|
- If you have multiple questions please submit them separately so we can track resolution.
|
||||||
- Screenshots are always helpful (just paste any images right here in the question)
|
- Screenshots are always helpful (just paste any images right here in the question)
|
||||||
|
- If you're trying to start a conversation on a topic, consider using [Discussions](https://github.com/pnp/sp-dev-fx-webparts/discussions). We want to close every issue as quickly as possible, but discussions are intended for longer conversations.
|
||||||
|
|
||||||
DELETE EVERYTHING ABOVE AFTER READING - THANKS!
|
DELETE EVERYTHING ABOVE AFTER READING - THANKS!
|
||||||
|
|
||||||
## Sample (which sample are you talking about)
|
## Sample (which sample are you talking about)
|
||||||
|
|
||||||
|
> Questions without a sample name will be rejected automatically
|
||||||
|
>
|
||||||
|
> _(DELETE THIS PARAGRAPH AFTER READING)_
|
||||||
|
|
||||||
|
|
||||||
## Authors
|
## Authors
|
||||||
|
|
||||||
> Because of the way this repository is configured, samples authors do not get a notification when you create an issue. *It makes it less likely for you to get your issue resolved or to get help*. For the section above **@mention any author of the sample**. The authors' GitHub handle can be found on the main sample documentation page, under the "solution" section. Use the `PREVIEW` tab at the top right to preview the rendering before submitting your issue.
|
> Because of the way this repository is configured, samples authors do not get a notification when you create an issue. *It makes it less likely for you to get your issue resolved or to get help*. For the section above **@mention any author of the sample**. The authors' GitHub handle can be found on the main sample documentation page, under the "solution" section. Use the `PREVIEW` tab at the top right to preview the rendering before submitting your issue.
|
||||||
>
|
>
|
||||||
|
> Questions without an author will be rejected automatically
|
||||||
|
>
|
||||||
> _(DELETE THIS PARAGRAPH AFTER READING)_
|
> _(DELETE THIS PARAGRAPH AFTER READING)_
|
||||||
|
|
||||||
|
|
||||||
## Question (the more details, the better)
|
|
||||||
|
|
||||||
|
|
||||||
## Question
|
## Question
|
||||||
|
|
||||||
|
> The more details, the better
|
||||||
|
>
|
||||||
|
> _(DELETE THIS PARAGRAPH AFTER READING)_
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,14 @@
|
||||||
|
|
||||||
> This is how you want the sample to appear in the samples browser.
|
> This is how you want the sample to appear in the samples browser.
|
||||||
> When naming your sample, try to give it a friendly name that describes what it does. Avoid using terms like `SharePoint` and `WebPart` -- because that's what all the samples in this repo is all about. Also, don't use `React`, `Angular`, `JavaScript`, etc. in your sample title -- unless that's what the sample is about.
|
> When naming your sample, try to give it a friendly name that describes what it does. Avoid using terms like `SharePoint` and `WebPart` -- because that's what all the samples in this repo is all about. Also, don't use `React`, `Angular`, `JavaScript`, etc. in your sample title -- unless that's what the sample is about.
|
||||||
|
> GOOD 👍:
|
||||||
|
> Kitten Videos
|
||||||
|
> BAD 👎:
|
||||||
|
> react-kittenvideos
|
||||||
|
> SPFx Kitten Videos Web Part for SharePoint using React
|
||||||
|
>
|
||||||
|
> DELETE THIS PARAGRAPH BEFORE SUBMITTING
|
||||||
|
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
|
@ -15,20 +23,47 @@ Short summary on functionality and used technologies.
|
||||||
|
|
||||||
![picture of the web part in action](assets/preview.png)
|
![picture of the web part in action](assets/preview.png)
|
||||||
|
|
||||||
## Used SharePoint Framework Version
|
## Compatibility
|
||||||
|
|
||||||
![SPFx 1.11.0](https://img.shields.io/badge/version-1.11.0-green.svg)
|
![SPFx 1.11](https://img.shields.io/badge/SPFx-1.11.0-green.svg)
|
||||||
|
![Node.js LTS 10.x](https://img.shields.io/badge/Node.js-LTS%2010.x-green.svg)
|
||||||
|
![SharePoint Online](https://img.shields.io/badge/SharePoint-Online-yellow.svg)
|
||||||
|
![Teams N/A: Untested with Microsoft Teams](https://img.shields.io/badge/Teams-N%2FA-lightgrey.svg "Untested with Microsoft Teams")
|
||||||
|
![Workbench Local | Hosted](https://img.shields.io/badge/Workbench-Local%20%7C%20Hosted-green.svg)
|
||||||
|
|
||||||
|
> Don't worry if you're unsure about the compatibility matrix above. We'll verify it when we approve the PR.
|
||||||
|
>
|
||||||
|
> If using an older version of SPFx, update the SPFx compatibility tag accordingly:
|
||||||
|
> ![SPFx 1.4.1](https://img.shields.io/badge/SPFx-1.4.1-green.svg)
|
||||||
|
>
|
||||||
|
> Here's the Node.js compatibility tag for SPFx 1.4.1:
|
||||||
|
> ![Node.js LTS 6.x | LTS 8.x](https://img.shields.io/badge/Node.js-LTS%206.x%20%7C%20LTS%208.x-green.svg)
|
||||||
|
>
|
||||||
|
> If you built this sample specifically for SharePoint 2016, or SharePoint 2019 support, update the SharePoint compatibility tag accordingly:
|
||||||
|
> ![SharePoint 2019 | Online](https://img.shields.io/badge/SharePoint-2019%20%7C%20Online-yellow.svg)
|
||||||
|
> ![SharePoint 2016 | 2019 | Online](https://img.shields.io/badge/SharePoint-2016%20%7C%202019%20%7C%20Online-green.svg)
|
||||||
|
> If you know your web part only works on the hosted workbench, you can use this for the workbench compatibility tag:
|
||||||
|
> ![Workbench Hosted: Does not work with local workbench](https://img.shields.io/badge/Workbench-Hosted-yellow.svg "Does not work with local workbench")
|
||||||
|
>
|
||||||
|
> If you specifically built and tested this web part to work with Teams, use this for the Teams compatibility tag:
|
||||||
|
> ![Teams Yes: Designed for Microsoft Teams](https://img.shields.io/badge/Teams-Yes-green.svg "Designed for Microsoft Teams")
|
||||||
|
> And if you know for sure that it is NOT compatible with Teams, use this:
|
||||||
|
> ![Teams No: Not designed for Microsoft Teams](https://img.shields.io/badge/Teams-No-red.svg "Not designed for Microsoft Teams")
|
||||||
|
>
|
||||||
|
> DELETE THIS PARAGRAPH BEFORE SUBMITTING
|
||||||
|
|
||||||
## Applies to
|
## Applies to
|
||||||
|
|
||||||
* [SharePoint Framework](https://docs.microsoft.com/sharepoint/dev/spfx/sharepoint-framework-overview)
|
* [SharePoint Framework](https://docs.microsoft.com/sharepoint/dev/spfx/sharepoint-framework-overview)
|
||||||
* [Office 365 tenant](https://docs.microsoft.com/sharepoint/dev/spfx/set-up-your-development-environment)
|
* [Microsoft 365 tenant](https://docs.microsoft.com/sharepoint/dev/spfx/set-up-your-development-environment)
|
||||||
|
|
||||||
> Update accordingly as needed.
|
> Update accordingly as needed.
|
||||||
|
> DELETE THIS PARAGRAPH BEFORE SUBMITTING
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
> Any special pre-requisites?
|
> Any special pre-requisites? Include any lists, permissions, offerings to the demo gods, or whatever else needs to be done for this web part to work.
|
||||||
|
> DELETE THIS PARAGRAPH BEFORE SUBMITTING
|
||||||
|
|
||||||
## Solution
|
## Solution
|
||||||
|
|
||||||
|
@ -40,8 +75,8 @@ folder name | Author details
|
||||||
|
|
||||||
Version|Date|Comments
|
Version|Date|Comments
|
||||||
-------|----|--------
|
-------|----|--------
|
||||||
1.1|September 2, 2025|Update comment
|
1.1|September 2, 2021|Update comment
|
||||||
1.0|August 29, 2025|Initial release
|
1.0|August 29, 2021|Initial release
|
||||||
|
|
||||||
## Disclaimer
|
## Disclaimer
|
||||||
|
|
||||||
|
@ -57,6 +92,7 @@ Version|Date|Comments
|
||||||
* `gulp serve`
|
* `gulp serve`
|
||||||
|
|
||||||
> Include any additional steps as needed.
|
> Include any additional steps as needed.
|
||||||
|
> DELETE THIS PARAGRAPH BEFORE SUBMITTING
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
|
@ -67,4 +103,7 @@ This Web Part illustrates the following concepts on top of the SharePoint Framew
|
||||||
* topic 2
|
* topic 2
|
||||||
* topic 3
|
* topic 3
|
||||||
|
|
||||||
|
> Note that better pictures and documentation will increase the sample usage and the value you are providing for others. Thanks for your submissions in advance! You rock ❤.
|
||||||
|
> DELETE THIS PARAGRAPH BEFORE SUBMITTING
|
||||||
|
|
||||||
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/readme-template" />
|
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/readme-template" />
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
"privacyUrl": "https://contoso.com/privacy",
|
"privacyUrl": "https://contoso.com/privacy",
|
||||||
"termsOfUseUrl": "https://contoso.com/terms-of-use",
|
"termsOfUseUrl": "https://contoso.com/terms-of-use",
|
||||||
"websiteUrl": "https://contoso.com/my-app",
|
"websiteUrl": "https://contoso.com/my-app",
|
||||||
"mpnId": "000000"
|
"mpnId": "m365pnp"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"paths": {
|
"paths": {
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
"privacyUrl": "",
|
"privacyUrl": "",
|
||||||
"termsOfUseUrl": "",
|
"termsOfUseUrl": "",
|
||||||
"websiteUrl": "",
|
"websiteUrl": "",
|
||||||
"mpnId": ""
|
"mpnId": "m365pnp"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"paths": {
|
"paths": {
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
"websiteUrl": "",
|
"websiteUrl": "",
|
||||||
"privacyUrl": "",
|
"privacyUrl": "",
|
||||||
"termsOfUseUrl": "",
|
"termsOfUseUrl": "",
|
||||||
"mpnId": ""
|
"mpnId": "m365pnp"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"paths": {
|
"paths": {
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
- This sample is based on [Erik Benke] and [Mike Zimmerman] (Accordion Section FAQ Builder web part. I has extended it support single FAQs list based on Category and dynamic properties selection.
|
- This sample is based on [Erik Benke](https://github.com/ejbenke) and [Mike Zimmerman](https://github.com/mikezimm)'s [Accordion Section FAQ Builder web part](https://github.com/pnp/sp-dev-fx-webparts/tree/master/samples/react-accordion-section). It adds extended support for single FAQs list based on Category and dynamic properties selection.
|
||||||
- Adds a collapsible accordion section to an Office 365 SharePoint page or Teams Tab.
|
- Adds a collapsible accordion section to an Office 365 SharePoint page or Teams Tab.
|
||||||
- Ideal for displaying FAQs.
|
- Ideal for displaying FAQs.
|
||||||
- When adding the web part, you'll be prompted to select a list from a property panel dropdown (target list must be created with FAQ type Question and Answer.).
|
- When adding the web part, you'll be prompted to select a list from a property panel dropdown (target list must be created with FAQ type Question and Answer.).
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/en-us/json-schemas/pnp/samples/v1.0/metadata-schema.json",
|
||||||
|
"name": "pnp-sp-dev-spfx-web-parts-react-accordion-dynamic-section",
|
||||||
|
"version": "1.4.2.0",
|
||||||
|
"source": "pnp",
|
||||||
|
"title": "Dynamic Accordion - FAQ Builder",
|
||||||
|
"shortDescription": "This sample is based on Erik Benke and Mike Zimmerman Accordion Section FAQ Builder web part. It was extended support single FAQs list based on Category and dynamic properties selection.",
|
||||||
|
"longDescription":
|
||||||
|
[
|
||||||
|
"This sample is based on Erik Benke and Mike Zimmerman's Accordion Section FAQ Builder web part. It was extended support single FAQs list based on Category and dynamic properties selection.",
|
||||||
|
"Adds a collapsible accordion section to an Office 365 SharePoint page or Teams Tab.",
|
||||||
|
"Ideal for displaying FAQs.",
|
||||||
|
"When adding the web part, you'll be prompted to select a list from a property panel dropdown (target list must be created with FAQ type Question and Answer.).",
|
||||||
|
"The web part expects a column called Category of type choice that will be used as the category.",
|
||||||
|
"The web part will automatically load all the properties in two dropdowns. One for Accordion Title and One for Accordion Content that must be html type.",
|
||||||
|
"This will generate an accordion with one section for each item in the list.",
|
||||||
|
"Modifications/deletions/additions to the list items in the target list of an added web part are automatically reflected on the page."
|
||||||
|
],
|
||||||
|
"products": [
|
||||||
|
"Microsoft Teams", "SharePoint", "Office"
|
||||||
|
],
|
||||||
|
"categories": [
|
||||||
|
],
|
||||||
|
"metadata": [
|
||||||
|
{
|
||||||
|
"key": "CLIENT-SIDE-DEV",
|
||||||
|
"value":"React"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "SPFX-VERSION",
|
||||||
|
"value":"1.10"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"thumbnails": [
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"order": 100,
|
||||||
|
"url": "https://github.com/pnp/sp-dev-fx-webparts/raw/master/samples/react-accordion-dynamic-section/assets/react-accordion-section.gif",
|
||||||
|
"alt": "Alt text for the image"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "slideshow",
|
||||||
|
"order": 102,
|
||||||
|
"alt": "Alt text for the image",
|
||||||
|
"slides": [
|
||||||
|
{ "url": "https://github.com/pnp/sp-dev-fx-webparts/raw/master/samples/react-accordion-dynamic-section/assets/FAQsList.png","order": 10, "alt": "The FAQ list, showing the category column"},
|
||||||
|
{ "url": "https://github.com/pnp/sp-dev-fx-webparts/raw/master/samples/react-accordion-dynamic-section/assets/AccordionSettings1.png","order": 20, "alt": "The web part before being configured"},
|
||||||
|
{ "url": "https://github.com/pnp/sp-dev-fx-webparts/raw/master/samples/react-accordion-dynamic-section/assets/AccordionSettings2.png","order": 30, "alt": "An illustration showing the relation between the configuration settings and the web part results"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"gitHubAccount": "ejbenke",
|
||||||
|
"company": "Clackamas County",
|
||||||
|
"pictureUrl": "https://avatars.githubusercontent.com/u/5017358?s=460&u=789d537eb1e89ce82b36604fa7ff48cc40fcd5a2&v=4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"gitHubAccount": "mikezimm",
|
||||||
|
"pictureUrl": "https://avatars.githubusercontent.com/u/49648086?s=460&v=4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"gitHubAccount": "jyasir",
|
||||||
|
"company": "DXC Technology",
|
||||||
|
"pictureUrl": "https://pbs.twimg.com/profile_images/1145701162190082050/pbgFe-BJ_400x400.png"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -56,6 +56,7 @@ Solution|Author(s)
|
||||||
SPFx Collapsible Accordion Section|[Erik Benke](https://github.com/ejbenke) ([@erikjbenke](https://twitter.com/erikjbenke))
|
SPFx Collapsible Accordion Section|[Erik Benke](https://github.com/ejbenke) ([@erikjbenke](https://twitter.com/erikjbenke))
|
||||||
SPFx Collapsible Accordion Section|[Mike Zimmerman](https://github.com/mikezimm)
|
SPFx Collapsible Accordion Section|[Mike Zimmerman](https://github.com/mikezimm)
|
||||||
SPFx Collapsible Accordion Section|[Ravi Chandra](https://github.com/Ravikadri)
|
SPFx Collapsible Accordion Section|[Ravi Chandra](https://github.com/Ravikadri)
|
||||||
|
SPFx Collapsible Accordion Section|[Jack Vinitsky](https://github.com/jack-vinitsky)
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
|
@ -68,7 +69,7 @@ Version|Date|Comments
|
||||||
1.4|July 10, 2020|Upgraded to SPFx 1.10.
|
1.4|July 10, 2020|Upgraded to SPFx 1.10.
|
||||||
1.5|September 1, 2020|Adds ability to click on expanded section headers to collapse accordions
|
1.5|September 1, 2020|Adds ability to click on expanded section headers to collapse accordions
|
||||||
1.6|September 2, 2020|Added Web Part Title, and ability to expand multiple sections
|
1.6|September 2, 2020|Added Web Part Title, and ability to expand multiple sections
|
||||||
1.7|January 5, 2021|Fixed web part title style to be consistent with first-party web parts
|
1.7|January 5, 2021|Fixed web part title style to be consistent with first-party web parts; updated other dependencies
|
||||||
|
|
||||||
## Disclaimer
|
## Disclaimer
|
||||||
|
|
||||||
|
|
|
@ -17,17 +17,17 @@
|
||||||
"@microsoft/sp-office-ui-fabric-core": "1.10.0",
|
"@microsoft/sp-office-ui-fabric-core": "1.10.0",
|
||||||
"@microsoft/sp-property-pane": "1.10.0",
|
"@microsoft/sp-property-pane": "1.10.0",
|
||||||
"@microsoft/sp-webpart-base": "1.10.0",
|
"@microsoft/sp-webpart-base": "1.10.0",
|
||||||
"@pnp/common": "^1.3.4",
|
"@pnp/common": "^1.3.11",
|
||||||
"@pnp/logging": "^1.3.4",
|
"@pnp/logging": "^1.3.11",
|
||||||
"@pnp/odata": "^1.3.4",
|
"@pnp/odata": "^1.3.11",
|
||||||
"@pnp/sp": "^1.3.4",
|
"@pnp/sp": "^1.3.11",
|
||||||
"@pnp/spfx-controls-react": "^2.3.0",
|
"@pnp/spfx-controls-react": "2.3.0",
|
||||||
"@pnp/spfx-property-controls": "1.16.0",
|
"@pnp/spfx-property-controls": "1.16.0",
|
||||||
"@types/es6-promise": "0.0.33",
|
"@types/es6-promise": "0.0.33",
|
||||||
"@types/react": "16.8.8",
|
"@types/react": "16.8.8",
|
||||||
"@types/react-dom": "16.8.3",
|
"@types/react-dom": "16.8.3",
|
||||||
"@types/webpack-env": "1.13.1",
|
"@types/webpack-env": "1.13.1",
|
||||||
"office-ui-fabric-react": "6.189.2",
|
"office-ui-fabric-react": "6.214.0",
|
||||||
"react": "16.8.5",
|
"react": "16.8.5",
|
||||||
"react-accessible-accordion": "^3.0.0",
|
"react-accessible-accordion": "^3.0.0",
|
||||||
"react-dom": "16.8.5"
|
"react-dom": "16.8.5"
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
"websiteUrl": "",
|
"websiteUrl": "",
|
||||||
"privacyUrl": "",
|
"privacyUrl": "",
|
||||||
"termsOfUseUrl": "",
|
"termsOfUseUrl": "",
|
||||||
"mpnId": ""
|
"mpnId": "m365pnp"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"paths": {
|
"paths": {
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
"privacyUrl": "https://contoso.com/privacy",
|
"privacyUrl": "https://contoso.com/privacy",
|
||||||
"termsOfUseUrl": "https://contoso.com/terms-of-use",
|
"termsOfUseUrl": "https://contoso.com/terms-of-use",
|
||||||
"websiteUrl": "https://contoso.com/my-app",
|
"websiteUrl": "https://contoso.com/my-app",
|
||||||
"mpnId": "000000"
|
"mpnId": "m365pnp"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"paths": {
|
"paths": {
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
"privacyUrl": "https://contoso.com/privacy",
|
"privacyUrl": "https://contoso.com/privacy",
|
||||||
"termsOfUseUrl": "https://contoso.com/terms-of-use",
|
"termsOfUseUrl": "https://contoso.com/terms-of-use",
|
||||||
"websiteUrl": "https://contoso.com/my-app",
|
"websiteUrl": "https://contoso.com/my-app",
|
||||||
"mpnId": "000000"
|
"mpnId": "m365pnp"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"paths": {
|
"paths": {
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
"privacyUrl": "https://contoso.com/privacy",
|
"privacyUrl": "https://contoso.com/privacy",
|
||||||
"termsOfUseUrl": "https://contoso.com/terms-of-use",
|
"termsOfUseUrl": "https://contoso.com/terms-of-use",
|
||||||
"websiteUrl": "https://contoso.com/my-app",
|
"websiteUrl": "https://contoso.com/my-app",
|
||||||
"mpnId": "000000"
|
"mpnId": "m365pnp"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"paths": {
|
"paths": {
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
> **NOTE:** This web part was built with SPFx 1.10.0, making it only compatible with SharePoint Online. If you wish, you can use [an earlier version of this web part](../OnPrem/README.md) which is compatible on-premises versions of SharePoint.
|
> **NOTE:** This web part was built with SPFx 1.11.0, making it only compatible with SharePoint Online. If you wish, you can use [an earlier version of this web part](../react-content-query-onprem/README.md) which is compatible on-premises versions of SharePoint.
|
||||||
|
|
||||||
The **Content Query web part** is a modern version of the good old **Content by Query web part** that was introduced in SharePoint 2007. Built for Office 365, this modern version is built using the **SharePoint Framework (SPFx)** and uses the latest *Web Stack* practices.
|
The **Content Query web part** is a modern version of the good old **Content by Query web part** that was introduced in SharePoint 2007. Built for Office 365, this modern version is built using the **SharePoint Framework (SPFx)** and uses the latest *Web Stack* practices.
|
||||||
|
|
||||||
|
@ -10,9 +10,17 @@ While the original web part was based on an **XSLT** templating engine, this *Re
|
||||||
|
|
||||||
![Web Part Preview](assets/toolpart.gif)
|
![Web Part Preview](assets/toolpart.gif)
|
||||||
|
|
||||||
## Used SharePoint Framework Version
|
## Compatibility
|
||||||
|
|
||||||
![1.11.0](https://img.shields.io/badge/drop-1.11.0-green.svg)
|
![SPFx 1.11](https://img.shields.io/badge/spfx-1.11.0-green.svg)
|
||||||
|
|
||||||
|
![Node.js LTS 10.x](https://img.shields.io/badge/Node.js-LTS%2010.x-green.svg)
|
||||||
|
|
||||||
|
![SharePoint Online](https://img.shields.io/badge/SharePoint-Online-red.svg)
|
||||||
|
|
||||||
|
![Teams N/A](https://img.shields.io/badge/Teams-N%2FA-lightgrey.svg)
|
||||||
|
|
||||||
|
![Workbench Hosted](https://img.shields.io/badge/Workbench-Hosted-yellow.svg)
|
||||||
|
|
||||||
## Applies to
|
## Applies to
|
||||||
|
|
||||||
|
@ -28,6 +36,7 @@ react-content-query-web part (Online)|Hugo Bernier ([Tahoe Ninjas](http://tahoen
|
||||||
react-content-query-web part (Online)|Paolo Pialorsi ([PiaSys.com](https://piasys.com/), [@PaoloPia](https://twitter.com/PaoloPia?s=20))
|
react-content-query-web part (Online)|Paolo Pialorsi ([PiaSys.com](https://piasys.com/), [@PaoloPia](https://twitter.com/PaoloPia?s=20))
|
||||||
react-content-query-web part |Simon-Pierre Plante
|
react-content-query-web part |Simon-Pierre Plante
|
||||||
react-content-query-web part (Online)|Abderahman Moujahid
|
react-content-query-web part (Online)|Abderahman Moujahid
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
Version|Date|Comments
|
Version|Date|Comments
|
||||||
|
@ -48,6 +57,7 @@ Version|Date|Comments
|
||||||
1.0.14|October 30, 2020|Fixed (lookup-)fields with special characters
|
1.0.14|October 30, 2020|Fixed (lookup-)fields with special characters
|
||||||
1.0.15|November 2, 2020|Upgraded to SPFx 1.11; Added support for jsonValue
|
1.0.15|November 2, 2020|Upgraded to SPFx 1.11; Added support for jsonValue
|
||||||
1.0.16|November 14, 2020|Fixed a bug where the fieldname starts with a special character; Added more special characters
|
1.0.16|November 14, 2020|Fixed a bug where the fieldname starts with a special character; Added more special characters
|
||||||
|
1.1.0|January 5, 2021|Updated dependencies and added MGT support
|
||||||
|
|
||||||
## Disclaimer
|
## Disclaimer
|
||||||
|
|
||||||
|
@ -273,16 +283,19 @@ Property | Description
|
||||||
`{{MyField.htmlValue}}` | Renders the HTML value of the field. For example, a *Link* field HTML value would render something like `<a href="...">My Link Field</a>`
|
`{{MyField.htmlValue}}` | Renders the HTML value of the field. For example, a *Link* field HTML value would render something like `<a href="...">My Link Field</a>`
|
||||||
`{{MyField.rawValue}}` | Returns the raw value of the field. For example, a *Taxonomy* field raw value would return an object which contains the term `wssId` and its label
|
`{{MyField.rawValue}}` | Returns the raw value of the field. For example, a *Taxonomy* field raw value would return an object which contains the term `wssId` and its label
|
||||||
`{{MyField.jsonValue}}` | Returns a JSON object value of the field. For example, an *Image* field JSON value would return a JSON object which contains the `serverRelativeUrl` property
|
`{{MyField.jsonValue}}` | Returns a JSON object value of the field. For example, an *Image* field JSON value would return a JSON object which contains the `serverRelativeUrl` property
|
||||||
|
`{{MyField.personValue}}` | Returns an object value of a person field. The `personValue` property provides `email`, `displayName` and `image` properties. The `image` property contains `small`, `medium`, and `large` properties, each of which pointing to the profile image URL for the small, medium, and large profile images.
|
||||||
|
|
||||||
|
|
||||||
##### Handlebars
|
##### Handlebars
|
||||||
|
|
||||||
```handlebars
|
```handlebars
|
||||||
{{#each items}}
|
{{#each items}}
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<p>MyUserField text value : {{MyUserField.textValue}}</p>
|
<p>MyField text value : {{MyField.textValue}}</p>
|
||||||
<p>MyUserField html value : {{MyUserField.htmlValue}}</p>
|
<p>MyField html value : {{MyField.htmlValue}}</p>
|
||||||
<p>MyUserField raw value : {{MyUserField.rawValue}}</p>
|
<p>MyField raw value : {{MyField.rawValue}}</p>
|
||||||
<p>MyImageField JSON value : {{MyImageField.jsonValue}}</p>
|
<p>MyImageField JSON value : {{MyImageField.jsonValue}}</p>
|
||||||
|
<p>MyPersonField person value : {{MyPersonField.personValue}}</p>
|
||||||
</div>
|
</div>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
```
|
```
|
||||||
|
@ -291,10 +304,11 @@ Property | Description
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<p>MyUserField text value : Simon-Pierre Plante</p>
|
<p>MyField text value : Simon-Pierre Plante</p>
|
||||||
<p>MyUserField html value : <a href="..." onclick="...">Simon-Pierre Plante</a></p>
|
<p>MyField html value : <a href="..." onclick="...">Simon-Pierre Plante</a></p>
|
||||||
<p>MyUserField raw value : 26</p>
|
<p>MyField raw value : 26</p>
|
||||||
<p>MyImageField JSON value: [Object] </p>
|
<p>MyImageField JSON value: [Object] </p>
|
||||||
|
<p>MyPersonField person value: [Object] </p>
|
||||||
</div>
|
</div>
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
@ -305,6 +319,12 @@ You can use `JSONValue` to parse complex fields -- such as image fields -- and d
|
||||||
<img src="{{MyImageField.jsonValue.serverRelativeUrl}}" />
|
<img src="{{MyImageField.jsonValue.serverRelativeUrl}}" />
|
||||||
```
|
```
|
||||||
|
|
||||||
|
For fields containing person values (e.g.: the `Author`, `Editor` fields and **Person or Group** fields), you can use the `personValue` property to retrieve values such as `email`, `displayName`, and `image.small`, `image.medium`, `image.large`.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<img src="{{MyPersonField.personValue.image.small}}" />
|
||||||
|
```
|
||||||
|
|
||||||
### Including your own external scripts and/or block helpers
|
### Including your own external scripts and/or block helpers
|
||||||
|
|
||||||
#### Including basic library files
|
#### Including basic library files
|
||||||
|
@ -394,4 +414,61 @@ ReactContentQuery.ExternalScripts.MyCustomBlockHelper = {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-web parts/samples/react-content-query-web part/online" />
|
### Using Microsoft Graph Toolkit
|
||||||
|
|
||||||
|
The Content Query Web Part provides support for [Microsoft Graph Toolkit](https://docs.microsoft.com/en-us/graph/toolkit/overview) (MGT) integration to easily query the [Microsoft Graph](https://docs.microsoft.com/en-us/graph/overview) within your handlebars templates.
|
||||||
|
|
||||||
|
You can use any of the MGT components without additional steps, although the MGT integration was designed specifically with [Person](https://docs.microsoft.com/en-us/graph/toolkit/components/person), [People](https://docs.microsoft.com/en-us/graph/toolkit/components/people), and [Person card](https://docs.microsoft.com/en-us/graph/toolkit/components/person-card) components.
|
||||||
|
|
||||||
|
#### Using MGT with a person field
|
||||||
|
|
||||||
|
If you have a SharePoint list containing a user field (like a **Created By** or **Modified By** field) or a person field, you can pass the `email` property from field's the `personValue` to the MGT component's `person-query` attribute.
|
||||||
|
|
||||||
|
For example, to use the `mgt-person` component with a person field called `MyPersonField`, you would use the following template:
|
||||||
|
|
||||||
|
|
||||||
|
```handlebars
|
||||||
|
<mgt-person person-query="{{MyPersonField.personValue.email}}" view="twoLines"></mgt-person>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Using MGT templates
|
||||||
|
|
||||||
|
MGT supports the use of custom templates to modify the content of a components.
|
||||||
|
|
||||||
|
According to [MGT documentation](https://docs.microsoft.com/en-us/graph/toolkit/customize-components/templates), the binding syntax to inject dynamic content in a template uses a block delimited by `{{` and `}}` -- which conflicts with the Handlebars binding syntax.
|
||||||
|
|
||||||
|
In order to use a custom MGT template in your Handlebars template, use `[[` and `]]` instead of `{{` and `}}` to bind to the MGT context.
|
||||||
|
|
||||||
|
For example, you would replace the following Handlebars template:
|
||||||
|
|
||||||
|
```handlebars
|
||||||
|
<mgt-person person-query="{{MyPersonField.personValue.email}}">
|
||||||
|
<template>
|
||||||
|
<div data-if="person.image">
|
||||||
|
<img src="{{ person.image }}" />
|
||||||
|
</div>
|
||||||
|
<div data-else>
|
||||||
|
{{ person.displayName }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</mgt-person>
|
||||||
|
```
|
||||||
|
|
||||||
|
With the following template:
|
||||||
|
|
||||||
|
```handlebars
|
||||||
|
<mgt-person person-query="{{MyPersonField.personValue.email}}">
|
||||||
|
<template>
|
||||||
|
<div data-if="person.image">
|
||||||
|
<img src="[[ person.image ]]" />
|
||||||
|
</div>
|
||||||
|
<div data-else>
|
||||||
|
[[ person.displayName ]]
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</mgt-person>
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that, in the example above, the `person-query` attribute is still bound using the Handlebars syntax `person-query="{{MyPersonField.personValue.email}}"`, whereas the MGT template uses `[[ person.image ]]` and `[[ person.displayName ]]`.
|
||||||
|
|
||||||
|
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-web parts/samples/react-content-query-online" />
|
Before Width: | Height: | Size: 242 KiB After Width: | Height: | Size: 242 KiB |
Before Width: | Height: | Size: 498 KiB After Width: | Height: | Size: 498 KiB |
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 7.9 KiB |
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 7.0 KiB |
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 716 KiB After Width: | Height: | Size: 716 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 4.6 MiB After Width: | Height: | Size: 4.6 MiB |
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 90 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 9.7 MiB After Width: | Height: | Size: 9.7 MiB |
|
@ -0,0 +1,58 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||||
|
"solution": {
|
||||||
|
"name": "React Content Query",
|
||||||
|
"id": "00406271-0276-406f-9666-512623eb6709",
|
||||||
|
"version": "1.1.0.0",
|
||||||
|
"isDomainIsolated": false,
|
||||||
|
"webApiPermissionRequests": [
|
||||||
|
{
|
||||||
|
"resource": "Microsoft Graph",
|
||||||
|
"scope": "User.Read"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resource": "Microsoft Graph",
|
||||||
|
"scope": "People.Read"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resource": "Microsoft Graph",
|
||||||
|
"scope": "Contacts.Read"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resource": "Microsoft Graph",
|
||||||
|
"scope": "User.ReadBasic.All"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resource": "Microsoft Graph",
|
||||||
|
"scope": "Calendars.Read"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resource": "Microsoft Graph",
|
||||||
|
"scope": "Directory.Read.All"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resource": "Microsoft Graph",
|
||||||
|
"scope": "User.Read.All"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resource": "Microsoft Graph",
|
||||||
|
"scope": "Group.Read.All"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resource": "Microsoft Graph",
|
||||||
|
"scope": "Files.Read.All"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"includeClientSideAssets": true,
|
||||||
|
"developer": {
|
||||||
|
"name": "PnP Community",
|
||||||
|
"mpnId": "m365pnp",
|
||||||
|
"privacyUrl": "",
|
||||||
|
"termsOfUseUrl": "",
|
||||||
|
"websiteUrl": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"paths": {
|
||||||
|
"zippedPackage": "solution/react-content-query-webpart.sppkg"
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "react-content-query-webpart",
|
"name": "react-content-query-webpart",
|
||||||
"version": "1.0.16",
|
"version": "1.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"engines": {
|
"engines": {
|
||||||
|
@ -12,13 +12,14 @@
|
||||||
"test": "gulp test"
|
"test": "gulp test"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@microsoft/mgt": "^2.0.1",
|
||||||
"@microsoft/sp-core-library": "1.11.0",
|
"@microsoft/sp-core-library": "1.11.0",
|
||||||
"@microsoft/sp-lodash-subset": "1.11.0",
|
"@microsoft/sp-lodash-subset": "1.11.0",
|
||||||
"@microsoft/sp-office-ui-fabric-core": "1.11.0",
|
"@microsoft/sp-office-ui-fabric-core": "1.11.0",
|
||||||
"@microsoft/sp-property-pane": "1.11.0",
|
"@microsoft/sp-property-pane": "1.11.0",
|
||||||
"@microsoft/sp-webpart-base": "1.11.0",
|
"@microsoft/sp-webpart-base": "1.11.0",
|
||||||
"@pnp/spfx-controls-react": "^1.17.0",
|
"@pnp/spfx-controls-react": "^2.3.0",
|
||||||
"@pnp/spfx-property-controls": "^1.17.0",
|
"@pnp/spfx-property-controls": "2.2.0",
|
||||||
"@types/handlebars": "4.0.32",
|
"@types/handlebars": "4.0.32",
|
||||||
"acorn": ">=5.7.4",
|
"acorn": ">=5.7.4",
|
||||||
"atob": ">=2.1.0",
|
"atob": ">=2.1.0",
|
||||||
|
@ -33,7 +34,7 @@
|
||||||
"fstream": ">=1.0.12",
|
"fstream": ">=1.0.12",
|
||||||
"growl": ">=1.10.0",
|
"growl": ">=1.10.0",
|
||||||
"handlebars": "^4.0.6",
|
"handlebars": "^4.0.6",
|
||||||
"handlebars-helpers": "^0.8.2",
|
"handlebars-helpers": "^0.8.4",
|
||||||
"hoek": ">=4.2.1",
|
"hoek": ">=4.2.1",
|
||||||
"is-my-json-valid": ">=2.17.2",
|
"is-my-json-valid": ">=2.17.2",
|
||||||
"js-yaml": ">=3.13.1",
|
"js-yaml": ">=3.13.1",
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { IPersonValue } from "./IPersonValue";
|
||||||
|
|
||||||
|
export interface INormalizedResult {
|
||||||
|
textValue: string;
|
||||||
|
htmlValue: string;
|
||||||
|
rawValue: any;
|
||||||
|
jsonValue: any;
|
||||||
|
personValue?: IPersonValue;
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
export interface IPersonValue {
|
||||||
|
email: string;
|
||||||
|
displayName: string;
|
||||||
|
picture: {
|
||||||
|
small: string;
|
||||||
|
medium: string;
|
||||||
|
large: string;
|
||||||
|
};
|
||||||
|
}
|
|
@ -15,6 +15,8 @@ import { ListService, IListTitle } from './ListService';
|
||||||
import { SearchService } from './SearchService';
|
import { SearchService } from './SearchService';
|
||||||
import { PeoplePickerService } from './PeoplePickerService';
|
import { PeoplePickerService } from './PeoplePickerService';
|
||||||
import { TaxonomyService } from './TaxonomyService';
|
import { TaxonomyService } from './TaxonomyService';
|
||||||
|
import { INormalizedResult } from '../dataContracts/INormalizedResult';
|
||||||
|
import { IPersonValue } from '../dataContracts/IPersonValue';
|
||||||
|
|
||||||
export class ContentQueryService implements IContentQueryService {
|
export class ContentQueryService implements IContentQueryService {
|
||||||
|
|
||||||
|
@ -75,7 +77,7 @@ export class ContentQueryService implements IContentQueryService {
|
||||||
return new Promise<IContentQueryTemplateContext>((resolve, reject) => {
|
return new Promise<IContentQueryTemplateContext>((resolve, reject) => {
|
||||||
|
|
||||||
// Initializes the base template context
|
// Initializes the base template context
|
||||||
let templateContext: IContentQueryTemplateContext = {
|
const templateContext: IContentQueryTemplateContext = {
|
||||||
pageContext: this.context.pageContext,
|
pageContext: this.context.pageContext,
|
||||||
webUrl: querySettings.webUrl,
|
webUrl: querySettings.webUrl,
|
||||||
listId: querySettings.listId,
|
listId: querySettings.listId,
|
||||||
|
@ -85,11 +87,12 @@ export class ContentQueryService implements IContentQueryService {
|
||||||
callTimeStamp: callTimeStamp
|
callTimeStamp: callTimeStamp
|
||||||
};
|
};
|
||||||
|
|
||||||
// Builds the CAML query based on the webpart settings
|
// Builds the CAML query based on the web part settings
|
||||||
let query = CamlQueryHelper.generateCamlQuery(querySettings);
|
const query: string = CamlQueryHelper.generateCamlQuery(querySettings);
|
||||||
|
|
||||||
//Log.info(this.logSource, Text.format("Generated CAML query {0}...", query), this.context.serviceScope);
|
//Log.info(this.logSource, Text.format("Generated CAML query {0}...", query), this.context.serviceScope);
|
||||||
|
|
||||||
// Queries the list with the generated caml query
|
// Queries the list with the generated CAML query
|
||||||
this.listService.getListItemsByQuery(querySettings.webUrl, querySettings.listId, query)
|
this.listService.getListItemsByQuery(querySettings.webUrl, querySettings.listId, query)
|
||||||
.then((data: any) => {
|
.then((data: any) => {
|
||||||
// Updates the template context with the normalized query results
|
// Updates the template context with the normalized query results
|
||||||
|
@ -257,7 +260,7 @@ export class ContentQueryService implements IContentQueryService {
|
||||||
return new Promise<IDropdownOption[]>((resolve, reject) => {
|
return new Promise<IDropdownOption[]>((resolve, reject) => {
|
||||||
this.listService.getListTitlesFromWeb(webUrl).then((listTitles: IListTitle[]) => {
|
this.listService.getListTitlesFromWeb(webUrl).then((listTitles: IListTitle[]) => {
|
||||||
let options: IDropdownOption[] = [{ key: "", text: strings.ListTitleFieldPlaceholder }];
|
let options: IDropdownOption[] = [{ key: "", text: strings.ListTitleFieldPlaceholder }];
|
||||||
let listTitleOptions = listTitles.map((list) => { return { key: list.id, text: list.title }; });
|
const listTitleOptions = listTitles.map((list) => { return { key: list.id, text: list.title }; });
|
||||||
options = options.concat(listTitleOptions);
|
options = options.concat(listTitleOptions);
|
||||||
this.listTitleOptions = options;
|
this.listTitleOptions = options;
|
||||||
resolve(options);
|
resolve(options);
|
||||||
|
@ -290,9 +293,9 @@ export class ContentQueryService implements IContentQueryService {
|
||||||
// Otherwise gets the options asynchronously
|
// Otherwise gets the options asynchronously
|
||||||
return new Promise<IDropdownOption[]>((resolve, reject) => {
|
return new Promise<IDropdownOption[]>((resolve, reject) => {
|
||||||
this.listService.getListFields(webUrl, listId, ['InternalName', 'Title', 'Sortable'], 'Title').then((data: any) => {
|
this.listService.getListFields(webUrl, listId, ['InternalName', 'Title', 'Sortable'], 'Title').then((data: any) => {
|
||||||
let sortableFields: any[] = data.value.filter((field) => { return field.Sortable == true; });
|
const sortableFields: any[] = data.value.filter((field) => { return field.Sortable == true; });
|
||||||
let options: IDropdownOption[] = [{ key: "", text: strings.queryFilterPanelStrings.queryFilterStrings.fieldSelectLabel }];
|
let options: IDropdownOption[] = [{ key: "", text: strings.queryFilterPanelStrings.queryFilterStrings.fieldSelectLabel }];
|
||||||
let orderByOptions: IDropdownOption[] = sortableFields.map((field) => { return { key: field.InternalName, text: Text.format("{0} \{\{{1}\}\}", field.Title, field.InternalName) }; });
|
const orderByOptions: IDropdownOption[] = sortableFields.map((field) => { return { key: field.InternalName, text: Text.format("{0} \{\{{1}\}\}", field.Title, field.InternalName) }; });
|
||||||
options = options.concat(orderByOptions);
|
options = options.concat(orderByOptions);
|
||||||
this.orderByOptions = options;
|
this.orderByOptions = options;
|
||||||
resolve(options);
|
resolve(options);
|
||||||
|
@ -480,8 +483,8 @@ export class ContentQueryService implements IContentQueryService {
|
||||||
let selectItemStr = "\n <span><button class='selectItem' data-itemId='{{ID.textValue}}'>Select</button></span>";
|
let selectItemStr = "\n <span><button class='selectItem' data-itemId='{{ID.textValue}}'>Select</button></span>";
|
||||||
let template = Text.format(`<style type="text/css">
|
let template = Text.format(`<style type="text/css">
|
||||||
.dynamic-template h2 {
|
.dynamic-template h2 {
|
||||||
font-size: 24px;
|
font-size: 20px;
|
||||||
font-weight: 300;
|
font-weight: 600;
|
||||||
color: "[theme:neutralPrimary, default:#323130]";
|
color: "[theme:neutralPrimary, default:#323130]";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -576,31 +579,35 @@ export class ContentQueryService implements IContentQueryService {
|
||||||
* Normalizes the results coming from a CAML query into a userfriendly format for handlebars
|
* Normalizes the results coming from a CAML query into a userfriendly format for handlebars
|
||||||
* @param results : The results returned by a CAML query executed against a list
|
* @param results : The results returned by a CAML query executed against a list
|
||||||
**************************************************************************************************/
|
**************************************************************************************************/
|
||||||
private normalizeQueryResults(results: any[], viewFields: string[]): any[] {
|
private normalizeQueryResults(results: any[], viewFields: string[]): INormalizedResult[] {
|
||||||
//Log.verbose(this.logSource, "Normalizing results for the requested handlebars context...", this.context.serviceScope);
|
//Log.verbose(this.logSource, "Normalizing results for the requested handlebars context...", this.context.serviceScope);
|
||||||
|
|
||||||
let normalizedResults: any[] = [];
|
const normalizedResults: INormalizedResult[] = results.map((result) => {
|
||||||
|
|
||||||
for (let result of results) {
|
|
||||||
let normalizedResult: any = {};
|
let normalizedResult: any = {};
|
||||||
let formattedCharsRegex = /_x00(20|3a|[c-f]{1}[0-9a-f]{1})_/gi;
|
let formattedCharsRegex = /_x00(20|3a|[c-f]{1}[0-9a-f]{1})_/gi;
|
||||||
for (let viewField of viewFields) {
|
for (let viewField of viewFields) {
|
||||||
|
|
||||||
//check if the intenal fieldname begins with a special character (_x00)
|
//check if the intenal fieldname begins with a special character (_x00)
|
||||||
let viewFieldOdata = viewField;
|
let viewFieldOdata: string = viewField;
|
||||||
if (viewField.indexOf("_x00") == 0) {
|
if (viewField.indexOf("_x00") == 0) {
|
||||||
viewFieldOdata = `OData_${viewField}`;
|
viewFieldOdata = `OData_${viewField}`;
|
||||||
}
|
}
|
||||||
let formattedName = viewFieldOdata.replace(formattedCharsRegex, "_x005f_x00$1_x005f_");
|
let formattedName = viewFieldOdata.replace(formattedCharsRegex, "_x005f_x00$1_x005f_");
|
||||||
formattedName = formattedName.replace(/_x00$/, "_x005f_x00");
|
formattedName = formattedName.replace(/_x00$/, "_x005f_x00");
|
||||||
|
|
||||||
|
const htmlValue: string = result.FieldValuesAsHtml[formattedName];
|
||||||
normalizedResult[viewField] = {
|
normalizedResult[viewField] = {
|
||||||
textValue: result.FieldValuesAsText[formattedName],
|
textValue: result.FieldValuesAsText[formattedName],
|
||||||
htmlValue: result.FieldValuesAsHtml[formattedName],
|
htmlValue: htmlValue,
|
||||||
rawValue: result[viewField] || result[viewField + 'Id'],
|
rawValue: result[viewField] || result[viewField + 'Id'],
|
||||||
jsonValue: this.jsonParseField(result[viewField] || result[viewField + 'Id'])
|
jsonValue: this.jsonParseField(result[viewField] || result[viewField + 'Id']),
|
||||||
|
personValue: this.extractPersonInfo(htmlValue)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
normalizedResults.push(normalizedResult);
|
|
||||||
}
|
return normalizedResult;
|
||||||
|
});
|
||||||
|
|
||||||
return normalizedResults;
|
return normalizedResults;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -621,6 +628,50 @@ export class ContentQueryService implements IContentQueryService {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns user profile information based on a user field
|
||||||
|
* @param htmlValue : A string representation of the HTML field rendering
|
||||||
|
* This function does a very rudimentary extraction of user information based on very limited
|
||||||
|
* HTML parsing. We need to update this in the future to make it more sturdy.
|
||||||
|
*/
|
||||||
|
private extractPersonInfo(htmlValue: string): IPersonValue {
|
||||||
|
|
||||||
|
try {
|
||||||
|
const sipIndex = htmlValue.indexOf(`sip='`);
|
||||||
|
if (sipIndex === -1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// Try to extract the user email and name
|
||||||
|
|
||||||
|
// Get the email address -- we should use RegExp for this, but I suck at RegExp
|
||||||
|
const sipValue = htmlValue.substring(sipIndex + 5, htmlValue.indexOf(`'`, sipIndex + 5));
|
||||||
|
const anchorEnd: number = htmlValue.lastIndexOf('</a>');
|
||||||
|
const anchorStart: number = htmlValue.substring(0, anchorEnd).lastIndexOf('>');
|
||||||
|
const name: string = htmlValue.substring(anchorStart + 1, anchorEnd);
|
||||||
|
|
||||||
|
// Generate picture URLs
|
||||||
|
const smallPictureUrl: string = `/_layouts/15/userphoto.aspx?size=S&username=${sipValue}`;
|
||||||
|
const medPictureUrl: string = `/_layouts/15/userphoto.aspx?size=M&username=${sipValue}`;
|
||||||
|
const largePictureUrl: string = `/_layouts/15/userphoto.aspx?size=L&username=${sipValue}`;
|
||||||
|
|
||||||
|
|
||||||
|
let result: IPersonValue = {
|
||||||
|
email: sipValue,
|
||||||
|
displayName: name,
|
||||||
|
picture: {
|
||||||
|
small: smallPictureUrl,
|
||||||
|
medium: medPictureUrl,
|
||||||
|
large: largePictureUrl
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return result;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**************************************************************************************************
|
/**************************************************************************************************
|
||||||
* Returns an error message based on the specified error object
|
* Returns an error message based on the specified error object
|
|
@ -0,0 +1,9 @@
|
||||||
|
export enum QueryFilterFieldType {
|
||||||
|
Text = 1,
|
||||||
|
Number = 2,
|
||||||
|
Datetime = 3,
|
||||||
|
User = 4,
|
||||||
|
Lookup = 5,
|
||||||
|
Taxonomy = 6,
|
||||||
|
Url = 7
|
||||||
|
}
|