Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature update for react-application-announcements #1456

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 38 additions & 39 deletions samples/react-application-announcements/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,59 +4,58 @@

SharePoint Framework application customizer displaying an information banner using office-fabric-ui MessageBar.

Inspired by react-app-announcements by Waldek Mastykarz (MVP, [Rencore](https://rencore.com), @waldekm),
js-application-alert-message Sudharsan K.(@sudharsank, Know More) and
Inspired by react-app-announcements by Waldek Mastykarz (MVP, [Rencore](https://rencore.com), @waldekm),
js-application-alert-message Sudharsan K.(@sudharsank, Know More) and
the sp-starter-kit alertNotification component.

![Announcements shown using this application customizer](./assets/announcements-MUI.png)



## Applies to

* [SharePoint Framework](https://dev.office.com/sharepoint)
* [Office 365 tenant](https://dev.office.com/sharepoint/docs/spfx/set-up-your-development-environment)
- [SharePoint Framework](https://dev.office.com/sharepoint)
- [Office 365 tenant](https://dev.office.com/sharepoint/docs/spfx/set-up-your-development-environment)

## Version history

Version|Date|Comments
-------|----|--------
2.0|May 20th, 2020|Initial release
2.1|June 18th, 2020|Handle empty start and end dates for announcements
2.2|March 12th, 2024|Update SPFx to 1.18 and pnpjs to 3.23
2.3|October 6th, 2024|Update SPFx to 1.20.0
| Version | Date | Comments |
| ------- | ------------------- | -------------------------------------------------- |
| 2.0 | May 20th, 2020 | Initial release |
| 2.1 | June 18th, 2020 | Handle empty start and end dates for announcements |
| 2.2 | March 12th, 2024 | Update SPFx to 1.18 and pnpjs to 3.23 |
| 2.3 | October 6th, 2024 | Update SPFx to 1.20.0 |
| 2.4 | December 10th, 2024 | Allow ability to hide dismiss button |

## Contributors

* [Mike Myers](https://github.com/thespooler)
* [Sandeep P S](https://github.com/Sandeep-FED)
- [Mike Myers](https://github.com/thespooler)
- [Sandeep P S](https://github.com/Sandeep-FED)

## Compatibility

| :warning: Important |
|:---------------------------|
| Every SPFx version is optimally compatible with specific versions of Node.js. In order to be able to build this sample, you need to ensure that the version of Node on your workstation matches one of the versions listed in this section. This sample will not work on a different version of Node.|
|Refer to <https://aka.ms/spfx-matrix> for more information on SPFx compatibility. |
| :warning: Important |
| :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Every SPFx version is optimally compatible with specific versions of Node.js. In order to be able to build this sample, you need to ensure that the version of Node on your workstation matches one of the versions listed in this section. This sample will not work on a different version of Node. |
| Refer to <https://aka.ms/spfx-matrix> for more information on SPFx compatibility. |

This sample is optimally compatible with the following environment configuration:

![SPFx 1.20.0](https://img.shields.io/badge/SPFx-1.20.0-green.svg)
![Node.js v16 | v18](https://img.shields.io/badge/Node.js-v16%20%7C%20v18-green.svg)
![Compatible with SharePoint Online](https://img.shields.io/badge/SharePoint%20Online-Compatible-green.svg)
![Does not work with SharePoint 2019](https://img.shields.io/badge/SharePoint%20Server%202019-Incompatible-red.svg "SharePoint Server 2019 requires SPFx 1.4.1 or lower")
![Does not work with SharePoint 2016 (Feature Pack 2)](https://img.shields.io/badge/SharePoint%20Server%202016%20(Feature%20Pack%202)-Incompatible-red.svg "SharePoint Server 2016 Feature Pack 2 requires SPFx 1.1")
![Does not work with SharePoint 2016 (Feature Pack 2)](<https://img.shields.io/badge/SharePoint%20Server%202016%20(Feature%20Pack%202)-Incompatible-red.svg> "SharePoint Server 2016 Feature Pack 2 requires SPFx 1.1")
![Local Workbench Unsupported](https://img.shields.io/badge/Local%20Workbench-Unsupported-red.svg "Local workbench is no longer available as of SPFx 1.13 and above")
![Hosted Workbench Compatible](https://img.shields.io/badge/Hosted%20Workbench-Compatible-green.svg)
![Compatible with Remote Containers](https://img.shields.io/badge/Remote%20Containers-Compatible-green.svg)


## Minimal Path to Awesome

- Clone this repository
- Create the announcements list in SharePoint
- `npm install`
- `gulp serve`
- Open a browser on a SharePoint site having the appropriate announcements list and append this query string to the URL (remember to change the `siteUrl` and `listName`!)

```
?loadSPFX=true&debugManifestsFile=https://localhost:4321/temp/manifests.js&customActions={"dd7ec4fd-97aa-44c5-b6ad-87535862e0bf":{"location":"ClientSideExtension.ApplicationCustomizer","properties":{"listName":"Site Announcements","siteUrl": "/sites/Contoso"}}}
```
Expand All @@ -65,25 +64,26 @@ This sample is optimally compatible with the following environment configuration

The Announcements list must have the following columns. Also note that a PnP Provisioning template is provided in `sharepoint/assets/pnptemplate.xml` that can (and should!) be used to deploy the list.

Column|Type|Values
------|----|------
Locale|Choice|`en-US`, `fr-FR`, `es-ES`, ...
Announcement|Note|
Link|URL|
Urgent|Boolean|
StartDateTime|DateTime|
EndDateTime|DateTime|
| Column | Type | Values |
| ------------- | -------- | ------------------------------ |
| Locale | Choice | `en-US`, `fr-FR`, `es-ES`, ... |
| Announcement | Note |
| Link | URL |
| Urgent | Boolean |
| StartDateTime | DateTime |
| EndDateTime | DateTime |
| IsHideClose | Boolean |

### Build and deploy

- `npm i`
- `gulp clean`
- `gulp bundle --ship`
- `gulp build --ship`
- `gulp package-solution --ship`
- Deploy the app package (`sharepoint/solution/react-application-announcements.spkg`) to your tenant AppCatalog
- Deploy a list with the appropriate fields
- Add the extension custom action with the appropriate settings
- `npm i`
- `gulp clean`
- `gulp bundle --ship`
- `gulp build --ship`
- `gulp package-solution --ship`
- Deploy the app package (`sharepoint/solution/react-application-announcements.spkg`) to your tenant AppCatalog
- Deploy a list with the appropriate fields
- Add the extension custom action with the appropriate settings

### Using PnPPowerShell

Expand All @@ -110,11 +110,11 @@ This extension illustrates the following concepts:

## 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.**
**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.**

## Help

We do not support samples, but we this community is always willing to help, and we want to improve these samples. We use GitHub to track issues, which makes it easy for community members to volunteer their time and help resolve issues.
We do not support samples, but we this community is always willing to help, and we want to improve these samples. We use GitHub to track issues, which makes it easy for community members to volunteer their time and help resolve issues.

You can try looking at [issues related to this sample](https://github.com/pnp/sp-dev-fx-extensions/issues?q=label%3Areact-application-announcements) to see if anybody else is having the same issues.

Expand All @@ -126,5 +126,4 @@ For questions regarding this sample, [create a new question](https://github.com/

Finally, if you have an idea for improvement, [make a suggestion](https://github.com/pnp/sp-dev-fx-extensions/issues/new?assignees=&labels=Needs%3A+Triage+%3Amag%3A%2Ctype%3Abug-suspected&template=suggestion.yml&sample=react-application-announcements&authors=@thespooler&title=react-application-announcements%20-%20).


<img src="https://m365-visitor-stats.azurewebsites.net/sp-dev-fx-extensions/samples/react-application-announcements" />
Original file line number Diff line number Diff line change
@@ -1,78 +1,119 @@
import * as React from 'react';
import * as React from "react";
import { ISPFXContext, SPFx, Web } from "@pnp/sp/presets/all";
import { useEffect, useState } from 'react';
import * as strings from 'announcementsStrings';
import { QUALIFIED_NAME } from '../AnnouncementsApplicationCustomizer';
import { Link, MessageBar, MessageBarType } from '@fluentui/react';
import { useEffect, useState } from "react";
import * as strings from "announcementsStrings";
import { QUALIFIED_NAME } from "../AnnouncementsApplicationCustomizer";
import { Link, MessageBar, MessageBarType } from "@fluentui/react";

interface IAnnouncementItem {
ID: number;
Title: string;
Announcement: string;
Urgent: boolean;
Link: { Description: string, Url: string };
ID: number;
Title: string;
Announcement: string;
Urgent: boolean;
Link: { Description: string; Url: string };
IsHideClose: boolean;
}

export interface IAnnouncementsProps {
context: ISPFXContext;
siteUrl: string;
listName: string;
culture: string;
context: ISPFXContext;
siteUrl: string;
listName: string;
culture: string;
}

export default function RenderAnnouncements(props: IAnnouncementsProps) {
// Two local state variables with their setter
let [announcements, setAnnouncements] = useState<IAnnouncementItem[]>([]);
let [acknowledgedAnnouncements, setAcknowledgedAnnouncements] = useState<number[]>([]);
// Two local state variables with their setter
let [announcements, setAnnouncements] = useState<IAnnouncementItem[]>([]);
let [acknowledgedAnnouncements, setAcknowledgedAnnouncements] = useState<
number[]
>([]);

// Use an effect to query the list data only once,
// not on every render. The effect will be re-run if
// props.siteUrl or props.listName changes
useEffect(() => {
// Use PnP JS to query SharePoint
const now: string = new Date().toISOString();
(async () => {
const announcements: IAnnouncementItem[] = await Web(props.siteUrl)
.using(SPFx(props.context))
.lists.getByTitle(props.listName)
.items
.filter(`(Locale eq '${props.culture}' or Locale eq null) and (StartDateTime le datetime'${now}' or StartDateTime eq null) and (EndDateTime ge datetime'${now}' or EndDateTime eq null)`)
.select("ID", "Title", "Announcement", "Urgent", "Link", "Locale", "StartDateTime", "EndDateTime")();
setAnnouncements(announcements);
// Use an effect to query the list data only once,
// not on every render. The effect will be re-run if
// props.siteUrl or props.listName changes
useEffect(() => {
// Use PnP JS to query SharePoint
const now: string = new Date().toISOString();
(async () => {
const announcements: IAnnouncementItem[] = await Web(props.siteUrl)
.using(SPFx(props.context))
.lists.getByTitle(props.listName)
.items.filter(
`(Locale eq '${props.culture}' or Locale eq null) and (StartDateTime le datetime'${now}' or StartDateTime eq null) and (EndDateTime ge datetime'${now}' or EndDateTime eq null)`
)
.select(
"ID",
"Title",
"Announcement",
"Urgent",
"Link",
"Locale",
"StartDateTime",
"EndDateTime",
"IsHideClose"
)();
setAnnouncements(announcements);

if (window.localStorage) {
const announcementsIDs = announcements.map(i => i.ID);
let acknowledgedAnnouncements: number[] = JSON.parse(window.localStorage.getItem(QUALIFIED_NAME) || "[]") || [];
let stillExistingAcknowledgedAnnouncements = acknowledgedAnnouncements.filter(announcement => announcementsIDs.indexOf(announcement) > -1);
window.localStorage.setItem(QUALIFIED_NAME, JSON.stringify(stillExistingAcknowledgedAnnouncements));
setAcknowledgedAnnouncements(stillExistingAcknowledgedAnnouncements);
}
})();
if (window.localStorage) {
const announcementsIDs = announcements.map((i) => i.ID);
let acknowledgedAnnouncements: number[] =
JSON.parse(window.localStorage.getItem(QUALIFIED_NAME) || "[]") || [];
let stillExistingAcknowledgedAnnouncements =
acknowledgedAnnouncements.filter(
(announcement) => announcementsIDs.indexOf(announcement) > -1
);
window.localStorage.setItem(
QUALIFIED_NAME,
JSON.stringify(stillExistingAcknowledgedAnnouncements)
);
setAcknowledgedAnnouncements(stillExistingAcknowledgedAnnouncements);
}
})();
}, [props.siteUrl, props.listName]);

}, [props.siteUrl, props.listName]);

const announcementElements = announcements
.filter(announcement => acknowledgedAnnouncements.indexOf(announcement.ID) < 0)
.map(announcement => <MessageBar
messageBarType={(announcement.Urgent ? MessageBarType.error : MessageBarType.warning)}
isMultiline={false}
onDismiss={() => {
// On dismiss, add the current announcement id to the array
const announcementElements = announcements
.filter(
(announcement) => acknowledgedAnnouncements.indexOf(announcement.ID) < 0
)
.map((announcement) => (
<MessageBar
messageBarType={
announcement.Urgent ? MessageBarType.error : MessageBarType.warning
}
isMultiline={false}
onDismiss={
!announcement.IsHideClose
? () => {
// On dismiss, add the current announcement id to the array
// STORAGE_KEY item in localStorage so it is remembered locally
let items: number[] = JSON.parse(window.localStorage.getItem(QUALIFIED_NAME) || "[]") || [];
let items: number[] =
JSON.parse(
window.localStorage.getItem(QUALIFIED_NAME) || "[]"
) || [];
items.push(announcement.ID);
window.localStorage.setItem(QUALIFIED_NAME, JSON.stringify(items));
window.localStorage.setItem(
QUALIFIED_NAME,
JSON.stringify(items)
);
setAcknowledgedAnnouncements(items);
}}
dismissButtonAriaLabel={strings.Close}>
<strong>{announcement.Title}</strong>&nbsp;
{/*
}
: undefined // No onDismiss handler if IsHideClose is true
}
dismissButtonAriaLabel={strings.Close}
>
<strong>{announcement.Title}</strong>&nbsp;
{/*
Unsafe set of HTML, this could cause XSS, use with care.
Since the source list is under administrative control, this should be safe.
*/}
<span dangerouslySetInnerHTML={{ __html: announcement.Announcement }} />
{ announcement.Link && <Link href={announcement.Link.Url} target="_blank">{announcement.Link.Description}</Link> }
</MessageBar>);
<span dangerouslySetInnerHTML={{ __html: announcement.Announcement }} />
{announcement.Link && (
<Link href={announcement.Link.Url} target="_blank">
{announcement.Link.Description}
</Link>
)}
</MessageBar>
));

return <div>{announcementElements}</div>;
}
return <div>{announcementElements}</div>;
}
Loading