I recently published in the PnP community a new Application Extension that allows the user to select a list or library and receive a real-time notification with the information that has changed. This article explains the motivation and my experience in implementing this application.
Not having to be concerned about when information that is relevant to me changes was the main motivation for creating this extension, don’t matter where it is and if it is a File, Document or a List Item. Having this information centralised in a single place was the second motivation.
This extension was developed in TypeScript, React, Fluent UI and uses Microsoft Graph API’s available to subscribe notifications, read information about changed data and save user settings.
The extension adds an icon (ringer) in the navigation bar of the sites next to the settings option, if the site is a Communication Site or next to the announcements if it is a Team Site, the Icon can be positioned in any position from the right, because the extension has a property where you can specify its position in pixels, This Icon indicates the number of notifications received, (Badge).
The extension on startup subscribes to all lists that user has selected to receive notifications and listens for changes.
Technically the extension will fetch the WebSocket EndPoint defined for the list and make a connection to that server to receive notifications.
Sample:
.
.
.
const listSubscription = (await msGraphClient
.api(`/sites/${siteId}/lists/${listId}/subscriptions/socketIo`)
.get()) as Subscription;
return listSubscription?.notificationUrl;
.
.
.
const connectToSocketIOServer = (notificationUrl: string): Socket => {
const split = notificationUrl.split("/callback?");
const socket = io(split[0], { query: split[1] as any, transports: ["websocket"] });
socket.on("connect", () => {
console.log("Connected!", notificationUrl);
});
socket.on("notification", handleNotifications);
return socket;
};
Here the link to Microsoft Graph API that explains how to use API
For the user to receive notifications he has to configure the extension and select the lists for which he wants to be notified.
Clicking on the Icon will open a Panel that allows you to view the notifications received as well as access the configuration settings identified by the gear wheel.
In the configuration settings the user can search the lists that he wants to be notified.
Here I use my React-Graph-control, ListPicker that use MicrosoftGraph Search API.
User settings are saved in the user’s OnDrive for Business in a folder called “Apps” and is known as user’s application’s personal folder. This is provide by Microsoft Graph API, Get Special folder and is automatic created when you read or write data.
Usually in Apps folder we have this organisation: /Apps/{Application Name}
, the Application name is the name of AAD registered Application, in context of SPFx has the name of SharePoint Online Client Extensibility Web Application Principal,.
It is good practice to create a subfolder for each application in order to avoid possible conflicts.
const createAppFolder = useCallback(
async (folderName: string) => {
try {
const msGraphClient = await
context.serviceScope.consume(MSGraphClientFactory.serviceKey).getClient();
if (!msGraphClient) return;
await msGraphClient.api(`/me/drive/special/approot`).header("content-
type", "application/json").put({
name: folderName,
folder: {},
});
} catch (error) {
console.log("er", error);
if (error.code !== "nameAlreadyExists") {
throw error;
}
}
},
[context.serviceScope]
);
const saveSettings = useCallback(
async (settings: string) => {
try {
const msGraphClient = await
context.serviceScope.consume(MSGraphClientFactory.serviceKey).getClient();
await msGraphClient
.api("/me/drive/special/approot:/MyListsNotifications/appsettings.json:/content")
.header("content-type", "plain/text")
.put(JSON.stringify(settings));
} catch (error) {
throw error;
}
},
[context.serviceScope]
);
const getSettings = useCallback(async (): Promise<IConfigurationListItem[]> => {
try {
const msGraphClient = await
context.serviceScope.consume(MSGraphClientFactory.serviceKey).getClient();
const downLoadUrlResponse = (await msGraphClient
.api("/me/drive/special/approot:/MyListsNotifications/appsettings.json?
select=@microsoft.graph.downloadUrl")
.get()) as HttpClientResponse;
const appSettings = await context.httpClient.get(
downLoadUrlResponse["@microsoft.graph.downloadUrl"],HttpClient.configurations.v1);
const data: IConfigurationListItem[] = JSON.parse(await appSettings.json());
return data;
} catch (error) {
throw error;
}
}, [context.serviceScope, context.httpClient]);
After connecting our lists to the corresponding SockectIO endpoint, we will receive in real time notification about the changes made to the list.
{"value":[{"subscriptionId":"9710b5f6-d97f-4794-bb65-939a4b7b0f9a","clientState":"system-managed:8082D436-D8DA-458D-96AD-34C902B73F37","expirationDateTime":"2021-07-10T20:40:15.5870000Z","resource":"b3a4755f-18be-4fbf-a6ae-e0bc14ac857e","tenantId":"ddba30c7-d040-4a20-9739-6a75edeadff2","siteUrl":"/","webId":"b06503c7-8710-4c0a-9b66-f2c36dc94a1b"}]}
With the information, I will get the most recent activity from the list with the Microsoft Graph API Recent Activities, to create a notification block with the action that was applied to the Item.
Note: Microsoft Graph API Recent Activities, is in Beta and can be changed in the future.
const getListActivities = useCallback(
async (siteId: string, listId: string):Promise<IActivity[]> => {
try {
const msGraphClient = await
serviceScope.consume(MSGraphClientFactory.serviceKey).getClient();
const listActivitiesResults = (await msGraphClient
.api(`/sites/${siteId}/lists/${listId}/activities`)
.expand("listItem($expand=fields),driveItem")
.top(1)
.version("beta")
.get()) as IActivities;
return listActivitiesResults.value;
} catch (error) {
throw error;
}
},
[serviceScope]
);
List notifications and Information of activities
I have to thank the entire Microsoft Graph team for the excellent work they have done with regard to providing API’s that make it possible to access Microsoft 365 data in a simple and versatile way.
Thank you, thank you for making a developer’s life easier.
Thank you very much for read and hope this be helpful in someway. this is available on SharePoint Framework Client-Side Extension Samples
To obtain information about the user and the respective photo, we have to make two requests, one to obtain user information and the other to obtain a photo. If your application obtains information from just one user, this may not be a problem, but if you need to obtain a list of users, there will be a performance degradation, since for each user we have to make two requests.
For example to create a list of contacts like the below, even with paging (20 items) we will have to wait some seconds to show the list.
To limit the number of requests we can use MS Graph Batch, this allows us to combine several requests in one HTTP call, all requests are executed on the server and when all requests are made we receive the result of each request. Each request is defined in the JSON object array that is passed as a property to the MS Graph Batch API.
Here my snippet to get information of user and photo and save the information on local cache:
/**
* Get User Data
* @param user
*/
const getUserId = async (user: string): Promise<IUserInfo> => {
let userInfo: IUserInfo;
let blobPhoto: string;
let usersResults: User;
// Create a Batch Request
// 2 rquests
// id=1 = user Info
// id=2 = user Photo
const batchRequests: IBatchRequest[] = [
{
id: "1",
url: `/users/${user}`,
method: "GET",
headers: {
ConsistencyLevel: "eventual",
},
},
{
id: "2",
url: `/users/${user}/photo/$value`,
headers: { "content-type": "img/jpg" },
method: "GET",
},
];
// Try to get user information from cache
try {
userInfo = await cache.get(`${user}`);
return userInfo;
} catch (error) {
// execute batch
const batchResults: any = await msgGraphclient
.api(`/$batch`)
.version("v1.0")
.post({ requests: batchRequests });
// get Responses
const responses: any = batchResults.responses;
// load responses
for (const response of responses) {
// user info
switch (response.id) {
case "1":
usersResults = response.body;
break;
case "2":
const binToBlob = response?.body
? await b64toBlob(response?.body, "img/jpg")
: undefined;
blobPhoto = (await blobToBase64(binToBlob)) ?? undefined;
break;
default:
break;
}
}
// save userinfo in cache
userInfo = { ...usersResults, userPhoto: blobPhoto };
// return Userinfo with photo
await cache.put(`${user}`, userInfo);
return userInfo;
}
};
With this code we get all the information of the user and photo in a single request and minimize the application network latency and with cache we only do a request if user not exists, and return an object
Please see all code here
Thank you for reading !
On the profile card in Microsoft 365, you can find information about users that is stored and maintained by your organization, for example Job title or Office location.
Microsoft Graph has new API’s that allows us to add Azure AD Attributes to the profile card, they use the profileCardProperty resource to show additional properties for an organization, by:
Additional properties will display in the Contact section of the profile card in Microsoft 365.
The following attributes from Azure Active Directory are available and can be visible on users’ profile cards.
The following table shows how the Azure AD attributes correspond with properties of the Microsoft Graph user entity.
Azure AD attribute | User entity property |
---|---|
UserPrincipalName | userPrincipalName |
Fax | faxNumber |
StreetAddress | streetAddress |
PostalCode | postalCode |
StateOrProvince | state |
Alias | mailNickname |
You can add any of the 15 Azure AD custom extension attributes to users’ profile cards by calling the new Microsoft Graph API’s
The onPremisesExtensionAttributes property of the user entity contains fifteen custom extension attribute properties. For an onPremisesSyncEnabled user, the source of authority for this set of properties is the on-premises Active Directory which is synchronized to Azure AD, and is read-only. For a cloud-only user (where onPremisesSyncEnabled is false), these properties may be set during creation or update.
Note: These extension attributes are also known as Exchange custom attributes 1-15.
The following table shows how the Azure AD custom extension attribute names correspond to the supported values for the directoryPropertyName property of the profileCardProperty resource. These Azure AD custom extension attribute names are not case-sensitive:
Azure AD custom extension attribute | Value to specify as directoryPropertyName |
---|---|
extensionAttribute1 | customAttribute1 |
extensionAttribute2 | customAttribute2 |
extensionAttribute3 | customAttribute3 |
extensionAttribute4 | customAttribute4 |
extensionAttribute5 | customAttribute5 |
extensionAttribute6 | customAttribute6 |
extensionAttribute7 | customAttribute7 |
extensionAttribute8 | customAttribute8 |
extensionAttribute9 | customAttribute9 |
extensionAttribute10 | customAttribute10 |
extensionAttribute11 | customAttribute11 |
extensionAttribute12 | customAttribute12 |
extensionAttribute13 | customAttribute13 |
extensionAttribute14 | customAttribute14 |
extensionAttribute15 | customAttribute15 |
There are several advantages to using custom attributes:
To associate Azure Ad custom attributes to User’s Profile we can use Exchange Management Shell to manage the attributes or we can use the Exchange Admin Center to add the values for this properties to recipientes.
Example using PowerShel:
Set-Mailbox -Identity ABCD -CustomAttribute1 "C-123456789"
Example using Exchange Admin Center:
Thank you for reading!
Quick overview of Invitation Resource Type, for full information and details please go to Microsoft Graph API Reference.
The resource type has the following definition:
Method | Return Type | Description |
---|---|---|
Create invitation | invitation | Write properties and relationships of invitation object. |
Property | Type | Description |
---|---|---|
invitedUserDisplayName | String | The display name of the user being invited. |
invitedUserEmailAddress | String | The email address of the user being invited. Required. |
invitedUserMessageInfo | invitedUserMessageInfo | Additional configuration for the message being sent to the invited user, including customizing message text, language and cc recipient list. |
sendInvitationMessage | Boolean | Indicates whether an email should be sent to the user being invited or not. The default is false. |
inviteRedirectUrl | String | The URL user should be redirected to once the invitation is redeemed. Required. |
inviteRedeemUrl | String | The URL user can use to redeem his invitation. Read-Only |
invitedUserType | String | The userType of the user being invited. By default, this is Guest. You can invite as Member if you’re are company administrator. |
status | String | The status of the invitation. Possible values: PendingAcceptance, Completed, InProgress, and Error |
To create a external user we have to do a POST to https://graph.microsoft.com/beta/invitations, I use here the beta version of the API but it is available in version 1 too, in the request body we have to specify the following parameters :
If the invitation is created the API return a Invitation object with the follow information:
{
"id": "string",
"invitedUserDisplayName": "string",
"invitedUserEmailAddress": "string",
"invitedUserMessageInfo": {"@odata.type": "microsoft.graph.invitedUserMessageInfo"},
"sendInvitationMessage": false,
"inviteRedirectUrl": "string",
"inviteRedeemUrl": "string",
"status": "string",
"invitedUser": {"@odata.type": "microsoft.graph.user"},
"invitedUserType": "string"
}
I create an Azure Function, in TypeScript that is part of a provisioning process of creating a collaborative Team Site that will be used by internal and external users and it is called by the provisioning process.
The Azure Function receive as parameter, user e-mail, user name of external user and the group id of group to add the user. The Azure Function add the user to AAD and Group and return the information of invitation created.
Here the extract of code of the Azure Function that create the Invite and add user to the Office365 group.
At this moment we have the user created on AAD and added to Office 365 group, but the process is not completed! The user must complete the redemption process and once completed, the invited user becomes an external user in the organization.
In this my Scenario the process of provisioning is responsible to send a email to the user with the redemption url returned by the Azure Function, property “inviteRedeemUrl”
Here exemple of execution of Azure Function:
Until the external user complete the Redemption Process the user is an “Invited User in AAD”.
The Redemption process check if the user has a Microsoft Account and if not the user must follow the steps to create an account and after he will be redirect to the url of the group defined.
The Code is available here .
Thanks for reading !
Sometimes we need to include some assets with our SPFx Application (web part or application customizer), like images, JSON files, or another stuff. By default this assets aren’t recognised by SPFx in build chain process.
For example we have some images that are in a folder that I called “assets” and I want that will be deployed with my SPFx Application because we use them in our SPFx application.
To include this assets in our SPFx package we need to create a file called “copy-static-assets.json” in the config folder and in the extensions property that is an array add an entry for each file extension that you want be deployed.
When we execute gulp bundle we can see in the log messages the indication that our assets was include, checking the messages :
After our assets will be include in our SPFx Aplication.
Thanks for reading!
In the communication site we can create a Full width Column that allows us to add a web part that will have full On the communication website, we can create a full width column that allows us to add a web part that will be full width. Once created, we will see a list of Web Parts that can be added to the section, not all Web Parts can be added to a Full Width Column Section.
Here the screenshot how it look like after added a Full with Column to a page.
We can allow our SPFx web part to be added to Full width Column, for that we must have to add a new property to our web part manifest file, called “supportsFullBleed“, this property is type boolean and have to set to true.
Here sample of manifest file with property enable.
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "bd3165a7-de0f-46bf-b961-7c07b0a5b7ad",
"alias": "NewsWebPart",
"componentType": "WebPart",
"supportsFullBleed": true,
"version": "*",
"manifestVersion": 2,
...
}
After deploy the web part , and fi we go to our page that have a Full Width Column Section, you will see our web part in a list of web parts allowed to add.
Here a sample:
Thanks for reading!
Microsoft Graph Open Extension allows to add untyped custom data to resources, like User and Messages, and there single API endpoint that gives you the possibility to extend Microsoft Graph with your own application data. You can add custom properties to Microsoft Graph resources without requiring an external data store.
Resource | Version |
---|---|
Administrative unit | Preview only |
Calendar event | GA |
Group calendar event | GA |
Group conversation thread post | GA |
device | GA |
group | GA |
message | GA |
organization | GA |
Personal contact | GA |
user | GA |
Here I will show you how to use open extension to add new custom data to User Resource.
I want to give the user the possibility to manage their owns application links, for that I create SPFx web part where the user can define the name, description, Url and Icon. This applications will be rendering as grid of tiles on SharePoint Site, Teams Tab or Teams Personal App.
The links are saved in User resource instance in Extensions property.
We can see how the extension was created in User resource instance executing the followed MSGraph Rest API.
https://graph.microsoft.com/v1.0/me?$select=id,displayName,mail,mobilePhone&$expand=extensions
In the response you can see in the Extensions property the data that was saved.
{
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users(id,displayName,mail,mobilePhone,extensions())/$entity",
"id": "33be46e1-8651-4ba3-8b43-2acbf95e71e8",
"displayName": "João José Mendes",
"mail": "joao.mendes@devjjm.onmicrosoft.com",
"mobilePhone": null,
"extensions@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('33be46e1-8651-4ba3-8b43-2acbf95e71e8')/extensions",
"extensions": [
{
"@odata.type": "#microsoft.graph.openTypeExtension",
"Apps": [
{
"name": "My SAP",
"description": "SAP Access",
"Url": "https://www.sap.pt",
"iconName": "AppIconDefault",
"tableData": {
"id": 0
}
},
{
"name": "Q&A",
"description": "Quick Access",
"Url": "https://www.qa.pt",
"iconName": "SurveyQuestions",
"tableData": {
"id": 1
}
},
{
"name": "My Reports",
"description": "FI Reports 2",
"Url": "https://www.sap.pt",
"iconName": "CRMReport",
"tableData": {
"id": 2
}
},
{
"name": "My CRM",
"description": "CRM - Support",
"Url": "https://www.crm.pt",
"iconName": "AnalyticsView",
"tableData": {
"id": 3
}
},
{
"name": "Travels",
"description": "Travel Agencies",
"Url": "https://www.tap.pt",
"iconName": "AirplaneSolid",
"tableData": {
"id": 4
}
},
{
"name": "My Redmind",
"description": "Redmind",
"Url": "https://www.red.pt",
"iconName": "Help",
"tableData": {
"id": 5
}
},
{
"name": "Office 365",
"description": "My Office 365",
"Url": "https://office.com",
"iconName": "OfficeLogo",
"tableData": {
"id": 6
}
},
{
"name": "TN3270",
"description": "Mainframe Access",
"Url": "https://tn3270web/",
"iconName": "Database",
"tableData": {
"id": 7
}
},
{
"name": "My App",
"description": "Access to My App",
"Url": "https://www.google.pt",
"iconName": "PowerApps2Logo",
"tableData": {
"id": 8
}
}
],
"id": "MyApps"
}
]
}
The Extensions property is an array with all of Extensions created for the resource and is derived from the extension abstract type. Each extension has an extensionName (id in the response) property which is the only pre-defined, writable property for all extensions, along with your custom data.
My Personal Applications will create an extension called “MyApps” for the User and has an attribute called “Applications”, which is a set of objects with information about the applications defined by the user..
The Extension is automatically created if not exists when the User save their list of Applications .
We can create extensions when creating a new resource or for existing resources, we just have to include the value of the Extensions property in the body of the request.
In this case the Extension is created by the Application for the current user. we can access to extension of a resource using the
Sample of the request with body that is called.
POST https://graph.microsoft.com/v1.0/me/extensions
{
"@odata.type" : "microsoft.graph.openTypeExtension",
"extensionName" : "MyApps",
"Apps": [
{
"name": "My SAP",
"description": "SAP Access",
"Url": "https://www.sap.pt",
"iconName": "AppIconDefault",
"tableData": {
"id": 0
}
},
{
"name": "Q&A",
"description": "Quick Access",
"Url": "https://www.qa.pt",
"iconName": "SurveyQuestions",
"tableData": {
"id": 1
}
},
{
"name": "My Reports",
"description": "FI Reports 2",
"Url": "https://www.sap.pt",
"iconName": "CRMReport",
"tableData": {
"id": 2
}
},
{
"name": "My CRM",
"description": "CRM - Support",
"Url": "https://www.crm.pt",
"iconName": "AnalyticsView",
"tableData": {
"id": 3
}
},
{
"name": "Travels",
"description": "Travel Agencies",
"Url": "https://www.tap.pt",
"iconName": "AirplaneSolid",
"tableData": {
"id": 4
}
},
{
"name": "My Redmind",
"description": "Redmind",
"Url": "https://www.red.pt",
"iconName": "Help",
"tableData": {
"id": 5
}
},
{
"name": "Office 365",
"description": "My Office 365",
"Url": "https://office.com",
"iconName": "OfficeLogo",
"tableData": {
"id": 6
}
},
{
"name": "TN3270",
"description": "Mainframe Access",
"Url": "https://tn3270web/",
"iconName": "Database",
"tableData": {
"id": 7
}
},
{
"name": "My App",
"description": "Access to My App",
"Url": "https://www.google.pt",
"iconName": "PowerApps2Logo",
"tableData": {
"id": 8
}
}
]
}
To update an extension, we need to PATCH the extension created on the specified instance of a resource and on the property we want to update.
Sample of update a list of Applications of User.
PATCH https://graph.microsoft.com/v1.0/me/extensions/myapps
{
"Apps": [
{
"name": "My SAP",
"description": "SAP Access",
"Url": "https://www.sap.pt",
"iconName": "AppIconDefault",
"tableData": {
"id": 0
}
},
{
"name": "Q&A",
"description": "Quick Access",
"Url": "https://www.qa.pt",
"iconName": "SurveyQuestions",
"tableData": {
"id": 1
}
},
{
"name": "My Reports",
"description": "FI Reports 2",
"Url": "https://www.sap.pt",
"iconName": "CRMReport",
"tableData": {
"id": 2
}
},
{
"name": "My CRM",
"description": "CRM - Support",
"Url": "https://www.crm.pt",
"iconName": "AnalyticsView",
"tableData": {
"id": 3
}
},
{
"name": "Travels",
"description": "Travel Agencies",
"Url": "https://www.tap.pt",
"iconName": "AirplaneSolid",
"tableData": {
"id": 4
}
},
{
"name": "My Redmind",
"description": "Redmind",
"Url": "https://www.red.pt",
"iconName": "Help",
"tableData": {
"id": 5
}
},
{
"name": "Office 365",
"description": "My Office 365",
"Url": "https://office.com",
"iconName": "OfficeLogo",
"tableData": {
"id": 6
}
},
{
"name": "TN3270",
"description": "Mainframe Access",
"Url": "https://tn3270web/",
"iconName": "Database",
"tableData": {
"id": 7
}
},
{
"name": "My App 2",
"description": "Access to My App",
"Url": "https://www.google.pt",
"iconName": "PowerApps2Logo",
"tableData": {
"id": 8
}
}
]
}
To delete an extension, we need to do an DELETE on the extension created on the specified instance of a resource. This will delete the extension and its properties..
Sample:
DELETE https://graph.microsoft.com/v1.0/me/extensions/myapps
The following limits apply to directory resources (such as user, group, device):
The following limits apply to Outlook resources (such as message, event, and contact):
Add custom data to resources using extensions
SPFx – My Personal Apps – Web Part
Thank you for reading!
By default a list don’t have the tile view available like the document library has, but we can use List View Formating JSON to create Tiles and then the option will appear and we can switch from List to Tiles View.
Sometime we need to define the Tiles View as a default view on a list and here the steps to do it:
On the switch view options, selected “Save View as ” on the view that has the information you want.
On the switch view option select “Tiles” that appears after applied JSON, and then in switch view select “Set Current View as Default”.
After this steps the list has Tiles View as Default View.
Thanks for reading.
Office UI Fabric is a responsive, mobile-first, front-end framework for developers, designed to make it simple to quickly create web experiences.
With Office UI Fabric we can apply simple CSS styles to make web applications look and feel like the rest of Office 365 Applications.
To Styling an Office UI Fabric component we can use the follow approaches:
ClassName
propstyle
propstyles
prop using CSS-in-JSFabric’s recommended styling approach uses CSS-in-JS and revolves around the styles
prop, which is provided by most Fabric components and allows strongly-typed customisations to individual areas of a component.
Unlike a style
prop that only applies styles to the root component, the styles
prop (provided by most Fabric components) allows you to control the styling of every part of a component: the root, the children, and even sub-components.
A component consists of DOM elements, or “areas.” Each of the areas should be targetable for styling.
To find the available areas for a component, use intellisense or look at the IComponentStyles
interface of Control. (Substituting the actual component name for “Component”).
Here the TextField styles Interface as sample.
The styles can be defined in two ways – Object-based
or Function-Based
.
In Object-Based
, we create a jscript object and define styles for each area of the component. like define above:
// Define styling, split out styles for each area.
const styles: IComponentStyles {
root: { /* styles */ },
child1: ['className', { /* styles */ }],
child2: { /* styles */ }
subComponentStyles: {
subComponent: {
root: { /* styles */ },
child1: { /* styles */ },
}
}
}
// In render()
return <Component styles={styles} ... />;
const theme = getTheme();
const styles = {
root: [
{
background: theme.palette.themePrimary,
display: 'none',
selectors: {
':hover': {
background: theme.palette.themeSecondary,
},
'&.isExpanded': {
display: 'block'
},
'&:hover .childElement': {
color: 'white'
}
}
}
]
};
// In render()
return <Component styles={styles} ... />;
With Function-based, the styling applied to each area may depend on the state of the component. So you should also be able to define styles as a function of these inputs:
// Take in styling input, split out styles for each area.
const styles = (props: IComponentStyleProps): IComponentStyles => {
return {
root: { /* styles */ },
child1: ['className', { /* styles */ }],
child2: ['className', props.someBoolean && { /* styles */ }],
subComponentStyles: {
subComponent: (subProps:ISubComponentStyleProps) => {
const { theme, disabled, hasBoolean } = props; // parent props are available in subComponents
const { required, hasOtherBoolean } = subProps;
return ({
root: { /* styles */ },
child1: { /* styles */ },
});
}
}
};
}
// In render()
return <Component styles={styles} ... />;
const styles = props => ({
root: [
{
background: props.theme.palette.themePrimary,
selectors: {
':hover': {
background: props.theme.palette.themeSecondary,
}
}
},
props.isExpanded
? { display: 'block' }
: { display: 'none' }
]
});
// In render()
return <Component styles={styles} ... />;
Using the CSS-in-JS approach to styling components is flexible and powerful if we need to styling different “areas” or sub-components of component on easy way.
The Web Part Tree Organization Chart, shows the Organization Chart of the Company or the team, the webpart reads information from current user to build the Organization Chart.
We can configure in the webpart properties if it show all Organization Chart or the only user team, (same manager and peers).
Code is here, soon I will create a PR to sp-dev-fx-webparts.
Sharing is Caring…