SPFx v1.14, custom props of terms, other updates
This commit is contained in:
parent
51eebc82de
commit
823f273e95
|
@ -1,25 +0,0 @@
|
|||
# EditorConfig helps developers define and maintain consistent
|
||||
# coding styles between different editors and IDEs
|
||||
# editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
|
||||
[*]
|
||||
|
||||
# change these settings to your own preference
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
# we recommend you to keep these unchanged
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[{package,bower}.json]
|
||||
indent_style = space
|
||||
indent_size = 2
|
|
@ -9,6 +9,7 @@ node_modules
|
|||
# Build generated files
|
||||
dist
|
||||
lib
|
||||
release
|
||||
solution
|
||||
temp
|
||||
*.sppkg
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
!dist
|
||||
config
|
||||
|
||||
gulpfile.js
|
||||
|
||||
release
|
||||
src
|
||||
temp
|
||||
|
||||
tsconfig.json
|
||||
tslint.json
|
||||
|
||||
*.log
|
||||
|
||||
.yo-rc.json
|
||||
.vscode
|
|
@ -2,7 +2,7 @@
|
|||
"@microsoft/generator-sharepoint": {
|
||||
"isCreatingSolution": true,
|
||||
"environment": "spo",
|
||||
"version": "1.9.1",
|
||||
"version": "1.14.0",
|
||||
"libraryName": "react-graph-cascading-managed-metadata",
|
||||
"libraryId": "cdc626ca-e9a7-4d1d-bded-a574bc5e61d0",
|
||||
"packageManager": "npm",
|
||||
|
|
|
@ -8,21 +8,13 @@ This web part shows how to use the Microsoft Graph APIs (beta) for Taxonomy to g
|
|||
|
||||
![Cascading managed metadata](./assets/cmmd.gif)
|
||||
|
||||
### Termstore
|
||||
|
||||
![Term store](./assets/termstore.png)
|
||||
|
||||
## Compatibility
|
||||
|
||||
![SPFx 1.11](https://img.shields.io/badge/SPFx-1.11.0-green.svg)
|
||||
![Node.js v10](https://img.shields.io/badge/Node.js-v10-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")
|
||||
![Teams Incompatible](https://img.shields.io/badge/Teams-Incompatible-lightgrey.svg)
|
||||
![Local Workbench Incompatible](https://img.shields.io/badge/Local%20Workbench-Incompatible-yellow.svg "This solution requires access to data terms using Microsoft Graph API")
|
||||
![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)
|
||||
![SPFx 1.14.0](https://img.shields.io/badge/SPFx-1.14.0-green.svg)
|
||||
![Node.js LTS v14 | LTS v12 | LTS v10](https://img.shields.io/badge/Node.js-LTS%20v14%20%7C%20LTS%20v12%20%7C%20LTS%20v10-green.svg)
|
||||
![SharePoint Online](https://img.shields.io/badge/SharePoint-Online-yellow.svg)
|
||||
![Workbench Hosted](https://img.shields.io/badge/Workbench-Hosted-green.svg)
|
||||
|
||||
## Applies to
|
||||
|
||||
|
@ -31,16 +23,18 @@ This web part shows how to use the Microsoft Graph APIs (beta) for Taxonomy to g
|
|||
|
||||
## Pre-requisites
|
||||
|
||||
* Set up the termset structure as shown in the image above.
|
||||
* Set up the termset structure as shown in the image below - .
|
||||
* To the termset, add a custom property called `UsedForShowingMaps` and set it's value to `true` as shown in the image below
|
||||
![Term store properties](./assets/termsetproperties.png)
|
||||
* For the cities, get the required latitude and longitude.
|
||||
* Set the description of the city term as `latitude;longitude` (as highlighted for the term `London` in the image above).
|
||||
|
||||
* Add 2 custom properties for the city terms `latitude` and `longitude` (as highlighted for the term `London` in the image below).
|
||||
![Term store](./assets/termstore.png)
|
||||
|
||||
## Solution
|
||||
|
||||
Solution|Author(s)
|
||||
--------|---------
|
||||
react-graph-cascading-managed-metadata| Anoop Tatti ([anoopt](https://github.com/anoopt), [@anooptells](https://twitter.com/anooptells))
|
||||
react-graph-cascading-managed-metadata| Anoop Tatti ([anoopt](https://github.com/anoopt), ([https://linktr.ee/anoopt](https://linktr.ee/anoopt))
|
||||
|
||||
## Version history
|
||||
|
||||
|
@ -48,6 +42,7 @@ Version|Date|Comments
|
|||
-------|----|--------
|
||||
1.0.0|Aug 24, 2020|Initial release
|
||||
1.0.1|Sep 03, 2020|Error handling and logging improvements
|
||||
2.0.0|Mar 04, 2022|Updated to SPFx 1.14, used term `custom properties` to get co-ordinates (as Graph API provides that capability now), usage of `PropertyFieldGuid` and several other improvements
|
||||
|
||||
## Minimal Path to Awesome
|
||||
|
||||
|
@ -55,6 +50,9 @@ Version|Date|Comments
|
|||
* in the command line run:
|
||||
* `npm install`
|
||||
* `gulp serve`
|
||||
* Make sure you have completed the [pre-requisites](#Pre-requisites)
|
||||
* Add the web part to the workbench page of a site
|
||||
* Edit the web part and add the termset id in the properties
|
||||
|
||||
> This sample can also be opened with [VS Code Remote Development](https://code.visualstudio.com/docs/remote/remote-overview). Visit https://aka.ms/spfx-devcontainer for further instructions.
|
||||
|
||||
|
@ -62,7 +60,7 @@ Version|Date|Comments
|
|||
|
||||
This sample illustrates the following concepts on top of the SharePoint Framework:
|
||||
|
||||
* Get data terms using Microsoft Graph API (beta).
|
||||
* Get termset, terms and their custom properties using Microsoft Graph API (beta).
|
||||
* React Hooks
|
||||
* Using async / await for the async calls
|
||||
* Caching the data in session storage
|
||||
|
@ -72,12 +70,15 @@ This sample illustrates the following concepts on top of the SharePoint Framewor
|
|||
### Enhancements
|
||||
|
||||
* Currently, this web part supports 2-level cascading. So there is scope to enhance this such that it supports more levels of cascading dynamically.
|
||||
* Currently, this web part reads latitude and longitude from description of the city terms. If there is a way of getting these from the custom properties of the city terms, then that needs to be implemented.
|
||||
|
||||
## Video
|
||||
|
||||
[![Cascading managed metadata using Microsoft Graph and SharePoint Framework](./assets/video-thumbnail.jpg)](https://www.youtube.com/watch?v=lk47ijo_H6Y "Cascading managed metadata using Microsoft Graph and SharePoint Framework")
|
||||
|
||||
## Need to show more details?
|
||||
|
||||
An Adaptive Card Extension (ACE) which performs similar operations and provides more data like the local time of the office, weather data of the office location and address of the office location along with it's map can be found in the `Office locations` sample of [pnp/sp-dev-fx-aces repostory](https://github.com/pnp/sp-dev-fx-aces/tree/main/samples/ImageCard-OfficeLocations).
|
||||
|
||||
## 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.
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 47 KiB |
Binary file not shown.
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 33 KiB |
|
@ -14,6 +14,7 @@
|
|||
"externals": {},
|
||||
"localizedResources": {
|
||||
"CascadingManagedMetadataWebPartStrings": "lib/webparts/cascadingManagedMetadata/loc/{locale}.js",
|
||||
"ControlStrings": "node_modules/@pnp/spfx-controls-react/lib/loc/{locale}.js"
|
||||
"ControlStrings": "node_modules/@pnp/spfx-controls-react/lib/loc/{locale}.js",
|
||||
"PropertyControlStrings": "node_modules/@pnp/spfx-property-controls/lib/loc/{locale}.js"
|
||||
}
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
|
||||
"deployCdnPath": "temp/deploy"
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
|
||||
"workingDir": "./temp/deploy/",
|
||||
"workingDir": "./release/assets/",
|
||||
"account": "<!-- STORAGE ACCOUNT NAME -->",
|
||||
"container": "react-graph-cascading-managed-metadata",
|
||||
"accessKey": "<!-- ACCESS KEY -->"
|
||||
|
|
|
@ -2,6 +2,21 @@
|
|||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||
"solution": {
|
||||
"name": "react-graph-cascading-managed-metadata-client-side-solution",
|
||||
"developer": {
|
||||
"name": "",
|
||||
"privacyUrl": "",
|
||||
"termsOfUseUrl": "",
|
||||
"websiteUrl": "",
|
||||
"mpnId": "Undefined-1.14.0"
|
||||
},
|
||||
"metadata": {
|
||||
"shortDescription": {
|
||||
"default": "react-graph-cascading-managed-metadata description"
|
||||
},
|
||||
"longDescription": {
|
||||
"default": "react-graph-cascading-managed-metadata description"
|
||||
}
|
||||
},
|
||||
"id": "cdc626ca-e9a7-4d1d-bded-a574bc5e61d0",
|
||||
"version": "1.0.0.0",
|
||||
"includeClientSideAssets": true,
|
||||
|
@ -11,4 +26,4 @@
|
|||
"paths": {
|
||||
"zippedPackage": "solution/react-graph-cascading-managed-metadata.sppkg"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,9 +2,5 @@
|
|||
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
|
||||
"port": 4321,
|
||||
"https": true,
|
||||
"initialPage": "https://localhost:5432/workbench",
|
||||
"api": {
|
||||
"port": 5432,
|
||||
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
|
||||
}
|
||||
"initialPage": "https://enter-your-SharePoint-site/_layouts/workbench.aspx"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"$schema": "https://raw.githubusercontent.com/s-KaiNet/spfx-fast-serve/master/schema/config.latest.schema.json",
|
||||
"cli": {
|
||||
"isLibraryComponent": false
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* User webpack settings file. You can add your own settings here.
|
||||
* Changes from this file will be merged into the base webpack configuration file.
|
||||
* This file will not be overwritten by the subsequent spfx-fast-serve calls.
|
||||
*/
|
||||
|
||||
// you can add your project related webpack configuration here, it will be merged using webpack-merge module
|
||||
// i.e. plugins: [new webpack.Plugin()]
|
||||
const webpackConfig = {
|
||||
|
||||
}
|
||||
|
||||
// for even more fine-grained control, you can apply custom webpack settings using below function
|
||||
const transformConfig = function (initialWebpackConfig) {
|
||||
// transform the initial webpack config here, i.e.
|
||||
// initialWebpackConfig.plugins.push(new webpack.Plugin()); etc.
|
||||
|
||||
return initialWebpackConfig;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
webpackConfig,
|
||||
transformConfig
|
||||
}
|
|
@ -1,36 +1,21 @@
|
|||
'use strict';
|
||||
|
||||
const gulp = require('gulp');
|
||||
const build = require('@microsoft/sp-build-web');
|
||||
|
||||
build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`);
|
||||
|
||||
const argv = build.rig.getYargs().argv;
|
||||
const useCustomServe = argv['custom-serve'];
|
||||
const fs = require("fs");
|
||||
const workbenchApi = require("@microsoft/sp-webpart-workbench/lib/api");
|
||||
var getTasks = build.rig.getTasks;
|
||||
build.rig.getTasks = function () {
|
||||
var result = getTasks.call(build.rig);
|
||||
|
||||
if (useCustomServe) {
|
||||
build.tslintCmd.enabled = false;
|
||||
|
||||
const ensureWorkbenchSubtask = build.subTask('ensure-workbench-task', function (gulp, buildOptions, done) {
|
||||
this.log('Creating workbench.html file...');
|
||||
try {
|
||||
workbenchApi.default["/workbench"]();
|
||||
} catch (e) { }
|
||||
result.set('serve', result.get('serve-deprecated'));
|
||||
|
||||
done();
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
build.rig.addPostBuildTask(build.task('ensure-workbench', ensureWorkbenchSubtask));
|
||||
|
||||
build.configureWebpack.mergeConfig({
|
||||
additionalConfiguration: (generatedConfiguration) => {
|
||||
fs.writeFileSync("./temp/_webpack_config.json", JSON.stringify(generatedConfiguration, null, 2));
|
||||
return generatedConfiguration;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
/* fast-serve */
|
||||
const { addFastServe } = require("spfx-fast-serve-helpers");
|
||||
addFastServe(build);
|
||||
/* end of fast-serve */
|
||||
|
||||
build.initialize(require('gulp'));
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -3,51 +3,34 @@
|
|||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"main": "lib/index.js",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "gulp bundle",
|
||||
"clean": "gulp clean",
|
||||
"test": "gulp test",
|
||||
"serve": "cross-env NODE_OPTIONS=--max_old_space_size=4096 gulp bundle --custom-serve && cross-env NODE_OPTIONS=--max_old_space_size=4096 webpack-dev-server --mode development --config ./webpack.js --env.env=dev"
|
||||
"serve": "gulp bundle --custom-serve --max_old_space_size=4096 && fast-serve"
|
||||
},
|
||||
"dependencies": {
|
||||
"@microsoft/sp-core-library": "1.11.0",
|
||||
"@microsoft/sp-lodash-subset": "1.11.0",
|
||||
"@microsoft/sp-office-ui-fabric-core": "1.11.0",
|
||||
"@microsoft/sp-property-pane": "1.11.0",
|
||||
"@microsoft/sp-webpart-base": "1.11.0",
|
||||
"@pnp/spfx-controls-react": "^1.20.0-beta.52e4e9f",
|
||||
"office-ui-fabric-react": "6.214.0",
|
||||
"react": "16.8.5",
|
||||
"react-dom": "16.8.5"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/react": "16.8.8"
|
||||
"@microsoft/sp-core-library": "1.14.0",
|
||||
"@microsoft/sp-lodash-subset": "1.14.0",
|
||||
"@microsoft/sp-office-ui-fabric-core": "1.14.0",
|
||||
"@microsoft/sp-property-pane": "1.14.0",
|
||||
"@microsoft/sp-webpart-base": "1.14.0",
|
||||
"@pnp/spfx-controls-react": "3.5.0",
|
||||
"@pnp/spfx-property-controls": "3.5.0",
|
||||
"office-ui-fabric-react": "7.174.1",
|
||||
"react": "16.13.1",
|
||||
"react-dom": "16.13.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/rush-stack-compiler-2.9": "0.7.16",
|
||||
"@microsoft/rush-stack-compiler-3.3": "0.3.5",
|
||||
"@microsoft/sp-build-web": "1.11.0",
|
||||
"@microsoft/sp-module-interfaces": "1.11.0",
|
||||
"@microsoft/sp-tslint-rules": "1.11.0",
|
||||
"@microsoft/sp-webpart-workbench": "1.11.0",
|
||||
"@types/chai": "3.4.34",
|
||||
"@types/mocha": "2.2.38",
|
||||
"@microsoft/rush-stack-compiler-3.9": "0.4.47",
|
||||
"@microsoft/sp-build-web": "1.14.0",
|
||||
"@microsoft/sp-module-interfaces": "1.14.0",
|
||||
"@microsoft/sp-tslint-rules": "1.14.0",
|
||||
"@types/react": "16.9.51",
|
||||
"@types/react-dom": "16.9.8",
|
||||
"@types/webpack-env": "1.13.1",
|
||||
"ajv": "~5.2.2",
|
||||
"cross-env": "7.0.2",
|
||||
"css-loader": "3.4.2",
|
||||
"css-modules-typescript-loader": "4.0.0",
|
||||
"del": "5.1.0",
|
||||
"fork-ts-checker-webpack-plugin": "4.1.0",
|
||||
"gulp": "~3.9.1",
|
||||
"node-sass": "4.13.1",
|
||||
"sass-loader": "8.0.2",
|
||||
"style-loader": "1.1.3",
|
||||
"ts-loader": "6.2.1",
|
||||
"webpack": "4.42.0",
|
||||
"webpack-cli": "3.3.11",
|
||||
"webpack-dev-server": "3.10.3"
|
||||
"gulp": "~4.0.2",
|
||||
"spfx-fast-serve-helpers": "~1.14.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
import * as React from 'react';
|
||||
import * as ReactDom from 'react-dom';
|
||||
import { Version, DisplayMode, Guid } from '@microsoft/sp-core-library';
|
||||
import {
|
||||
BaseClientSideWebPart,
|
||||
IPropertyPaneConfiguration,
|
||||
PropertyPaneTextField
|
||||
} from '@microsoft/sp-webpart-base';
|
||||
|
||||
import { Version, DisplayMode } from '@microsoft/sp-core-library';
|
||||
import { BaseClientSideWebPart } from "@microsoft/sp-webpart-base";
|
||||
import { IPropertyPaneConfiguration } from "@microsoft/sp-property-pane";
|
||||
import { PropertyFieldGuid } from '@pnp/spfx-property-controls/lib/PropertyFieldGuid';
|
||||
import * as strings from 'CascadingManagedMetadataWebPartStrings';
|
||||
import CascadingManagedMetadata from './components/CascadingManagedMetadata';
|
||||
import { MSGraph } from './services/MSGraph';
|
||||
|
@ -68,18 +65,6 @@ export default class CascadingManagedMetadataWebPart extends BaseClientSideWebPa
|
|||
return Version.parse('1.0');
|
||||
}
|
||||
|
||||
private validateTermSetId(value: string): string {
|
||||
if (value === null ||
|
||||
value.trim().length === 0) {
|
||||
return 'Provide a term set Id.';
|
||||
}
|
||||
|
||||
if (!Guid.isValid(value.trim())) {
|
||||
return 'Term set Id must be a valid GUID.';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
||||
return {
|
||||
|
@ -92,9 +77,11 @@ export default class CascadingManagedMetadataWebPart extends BaseClientSideWebPa
|
|||
{
|
||||
groupName: strings.BasicGroupName,
|
||||
groupFields: [
|
||||
PropertyPaneTextField('termSetId', {
|
||||
PropertyFieldGuid('termSetId', {
|
||||
key: 'termSetId',
|
||||
label: strings.TermSetIdFieldLabel,
|
||||
onGetErrorMessage: this.validateTermSetId.bind(this)
|
||||
value: this.properties.termSetId,
|
||||
errorMessage: "Term set Id must be a valid GUID"
|
||||
})
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
import * as React from 'react';
|
||||
import styles from './CascadingManagedMetadata.module.scss';
|
||||
import { ICascadingManagedMetadataProps } from './ICascadingManagedMetadataProps';
|
||||
import { MSGraph } from '../services/MSGraph';
|
||||
import { ITerms } from '../../interfaces';
|
||||
import { ICMMDDropdownOption, IProperty, ITerms } from '../../interfaces';
|
||||
|
||||
import { Dropdown, IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown';
|
||||
import { MessageBar, MessageBarType } from 'office-ui-fabric-react/lib/MessageBar';
|
||||
import { Map, ICoordinates } from "@pnp/spfx-controls-react/lib/Map";
|
||||
import { MMDService } from '../services/MMDService';
|
||||
import { find, isEmpty } from '@microsoft/sp-lodash-subset';
|
||||
|
||||
|
||||
const CascadingManagedMetadata: React.SFC<ICascadingManagedMetadataProps> = (props) => {
|
||||
|
||||
const [countriesList, setCountriesList] = React.useState<IDropdownOption[]>([]);
|
||||
const [citiesList, setCitiesList] = React.useState<IDropdownOption[]>([]);
|
||||
const [selectedCityCoordinates, setSelectedCityCoordinates] = React.useState<string>(null);
|
||||
const [citiesList, setCitiesList] = React.useState<ICMMDDropdownOption[]>([]);
|
||||
const [selectedCityKey, setselectedCityKey] = React.useState<string>(null);
|
||||
const [selectedCity, setSelectedCity] = React.useState<string>(null);
|
||||
const [showMap, setShowMap] = React.useState<boolean>(false);
|
||||
const [coordinates, setCoordinates] = React.useState<ICoordinates>({ latitude: null, longitude: null });
|
||||
|
@ -26,17 +26,29 @@ const CascadingManagedMetadata: React.SFC<ICascadingManagedMetadataProps> = (pro
|
|||
|
||||
const LOG_SOURCE: string = "Cascading MMD -";
|
||||
|
||||
React.useEffect(() => {
|
||||
_getCountries().then(countries => {
|
||||
if (countries) {
|
||||
const options: IDropdownOption[] = countries.value.map(c => ({ key: c.id, text: c.labels[0].name }));
|
||||
setCountriesList(options);
|
||||
} else {
|
||||
setCountriesList([]);
|
||||
clearData();
|
||||
//* Check if the term set has a property called UsedForShowingMaps
|
||||
const _checkIfTermsetIsUsedForShowingMaps = async (): Promise<boolean> => {
|
||||
try {
|
||||
const termsetData = await MSGraph.Get(`/termStore/sets/${props.termSetId}`, "beta", ["properties"]);
|
||||
const termsetProperties: IProperty[] = termsetData.properties;
|
||||
console.debug("%s Retrieved termset properties. %o", LOG_SOURCE, termsetProperties);
|
||||
|
||||
if (isEmpty(termsetProperties)) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}, [props.termSetId]); //* Run this also when the property termSetId changes
|
||||
return find(termsetProperties, (p: IProperty) => p.key === "UsedForShowingMaps")?.value === "true";
|
||||
|
||||
}
|
||||
catch (error) {
|
||||
console.error("%s Error retrieving termset properties. Details - %o", LOG_SOURCE, error);
|
||||
setMessageBarStatus({
|
||||
type: MessageBarType.error,
|
||||
message: <span>Error retrieving termset properties. Please contact admin.</span>,
|
||||
show: true
|
||||
});
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
//* Get the country terms i.e. level 1 children using Graph
|
||||
const _getCountries = async (): Promise<ITerms> => {
|
||||
|
@ -58,51 +70,52 @@ const CascadingManagedMetadata: React.SFC<ICascadingManagedMetadataProps> = (pro
|
|||
};
|
||||
|
||||
//* Get the city terms under a country i.e. level 2 children using Graph
|
||||
const _onCountryChange = (event: React.FormEvent<HTMLDivElement>, item: IDropdownOption): void => {
|
||||
clearData();
|
||||
let countryTermId: string = item.key.toString();
|
||||
const _onCountryChange = async (event: React.FormEvent<HTMLDivElement>, item: IDropdownOption): Promise<void> => {
|
||||
|
||||
MMDService.GetTermsAsDropdownOptions(`/termStore/sets/${props.termSetId}/terms/${countryTermId}/children`, countryTermId, true)
|
||||
.then(options => {
|
||||
setCitiesList(options);
|
||||
//setShowMap(false);
|
||||
console.debug("%s Retrieved cities. %o", LOG_SOURCE, options);
|
||||
setMessageBarStatus({
|
||||
type: MessageBarType.warning,
|
||||
message: options.length > 0 ?
|
||||
<span>To see the map, please select a city. </span> :
|
||||
<span> No city terms in the selected country.</span>,
|
||||
show: true
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("%s Error retrieving cities. Details - %o", LOG_SOURCE, error);
|
||||
setMessageBarStatus({
|
||||
type: MessageBarType.error,
|
||||
message: <span>Error in retrieving cities. Please contact admin.</span>,
|
||||
show: true
|
||||
});
|
||||
setCitiesList([]);
|
||||
try {
|
||||
clearData();
|
||||
let countryTermId: string = item.key.toString();
|
||||
|
||||
let cities: ICMMDDropdownOption[] = await MMDService
|
||||
.GetTermsAsDropdownOptions(
|
||||
`/termStore/sets/${props.termSetId}/terms/${countryTermId}/children`,
|
||||
["id", "labels", "properties"],
|
||||
countryTermId,
|
||||
true);
|
||||
setCitiesList(cities);
|
||||
setMessageBarStatus({
|
||||
type: MessageBarType.warning,
|
||||
message: cities.length > 0 ?
|
||||
<span>To see the map, please select a city. </span> :
|
||||
<span> No city terms in the selected country.</span>,
|
||||
show: true
|
||||
});
|
||||
console.debug("%s Retrieved cities. %o", LOG_SOURCE, cities);
|
||||
}
|
||||
catch (error) {
|
||||
console.error("%s Error retrieving cities. Details - %o", LOG_SOURCE, error);
|
||||
setMessageBarStatus({
|
||||
type: MessageBarType.error,
|
||||
message: <span>Error in retrieving cities. Please contact admin.</span>,
|
||||
show: true
|
||||
});
|
||||
setCitiesList([]);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
//* Extract co-ordinates from key of the dropdown option
|
||||
//* The key will contain the description of the term
|
||||
//* The description of the term will be of the format latitude;longitude
|
||||
const _onCityChange = (event: React.FormEvent<HTMLDivElement>, item: IDropdownOption): void => {
|
||||
setSelectedCity(item.text);
|
||||
setSelectedCityCoordinates(item.key.toString());
|
||||
const [lat, long] = item.key.toString().split(';');
|
||||
const coordinates: ICoordinates = {
|
||||
latitude: isNaN(Number(lat)) ? null : Number(lat),
|
||||
longitude: isNaN(Number(long)) ? null : Number(long)
|
||||
};
|
||||
setCoordinates(coordinates);
|
||||
|
||||
console.debug("%s Retrieved coordinates. %o", LOG_SOURCE, coordinates);
|
||||
const _onCityChange = (event: React.FormEvent<HTMLDivElement>, item: ICMMDDropdownOption): void => {
|
||||
|
||||
if (coordinates.latitude && coordinates.longitude) {
|
||||
const { text, key, data } = item;
|
||||
|
||||
setSelectedCity(text);
|
||||
setselectedCityKey(key.toString());
|
||||
setCoordinates(data);
|
||||
|
||||
console.debug("%s Retrieved coordinates. %o", LOG_SOURCE, data);
|
||||
|
||||
if (data.latitude && data.longitude) {
|
||||
setShowMap(true);
|
||||
setMessageBarStatus(state => ({ ...state, show: false }));
|
||||
} else {
|
||||
|
@ -118,11 +131,58 @@ const CascadingManagedMetadata: React.SFC<ICascadingManagedMetadataProps> = (pro
|
|||
//* Clear the data related to cities and maps
|
||||
const clearData = () => {
|
||||
setCoordinates({ latitude: null, longitude: null });
|
||||
setSelectedCityCoordinates(null);
|
||||
setselectedCityKey(null);
|
||||
setSelectedCity(null);
|
||||
setCitiesList([]);
|
||||
setShowMap(false);
|
||||
}
|
||||
};
|
||||
|
||||
const _start = async (): Promise<void> => {
|
||||
|
||||
let isTermsetUsedForShowingMaps: boolean = await _checkIfTermsetIsUsedForShowingMaps();
|
||||
|
||||
if(isTermsetUsedForShowingMaps === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isTermsetUsedForShowingMaps) {
|
||||
setCountriesList([]);
|
||||
clearData();
|
||||
setMessageBarStatus({
|
||||
type: MessageBarType.warning,
|
||||
message: <span>The selected term set is not used for showing maps.</span>,
|
||||
show: true
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let countries: ITerms = await _getCountries();
|
||||
|
||||
if(countries === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isEmpty(countries.value)) {
|
||||
setCountriesList([]);
|
||||
clearData();
|
||||
setMessageBarStatus({
|
||||
type: MessageBarType.warning,
|
||||
message: <span>No country terms in the selected termset. Please contact admin.</span>,
|
||||
show: true
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let countriesList: IDropdownOption[] = countries.value.map(c => ({ key: c.id, text: c.labels[0].name }));
|
||||
setCountriesList(countriesList);
|
||||
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
_start();
|
||||
}, [props.termSetId]); //* Run this also when the property termSetId changes
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
@ -136,7 +196,7 @@ const CascadingManagedMetadata: React.SFC<ICascadingManagedMetadataProps> = (pro
|
|||
|
||||
<Dropdown
|
||||
label="City"
|
||||
selectedKey={selectedCityCoordinates}
|
||||
selectedKey={selectedCityKey}
|
||||
placeHolder="Select a city"
|
||||
options={citiesList}
|
||||
onChange={_onCityChange}
|
||||
|
|
|
@ -1,47 +1,44 @@
|
|||
import { ITerms, IOption } from "../../interfaces";
|
||||
import { ITerms, ICMMDDropdownOption } from "../../interfaces";
|
||||
import { MSGraph } from "./MSGraph";
|
||||
import { IDropdownOption } from "office-ui-fabric-react/lib/Dropdown";
|
||||
import { find } from '@microsoft/sp-lodash-subset';
|
||||
|
||||
export class MMDService {
|
||||
|
||||
private static _sessionStorageKey: string = "CMMD_Options";
|
||||
private static _logSource: string = "Cascading MMD Service -";
|
||||
|
||||
public static async GetTermsAsDropdownOptions(apiUrl: string, parent: string, tryFromCache: boolean): Promise<IDropdownOption[]> {
|
||||
|
||||
let options: IDropdownOption[] = [];
|
||||
public static async GetTermsAsDropdownOptions(apiUrl: string, selectProperties: string[], parent: string, tryFromCache: boolean): Promise<ICMMDDropdownOption[]> {
|
||||
if (tryFromCache) {
|
||||
let optionsFromCache: IOption[] = this._fetchFromSessionStorge();
|
||||
let optionsFromCache: ICMMDDropdownOption[] = this._fetchFromSessionStorge();
|
||||
|
||||
if (optionsFromCache.length) {
|
||||
let requiredOptionsFromCache = optionsFromCache.filter(o => o.parent == parent);
|
||||
if (requiredOptionsFromCache.length) {
|
||||
options = requiredOptionsFromCache.map(r => ({ key: r.key, text: r.text }));
|
||||
return options;
|
||||
return requiredOptionsFromCache;
|
||||
}
|
||||
}
|
||||
}
|
||||
//Get data using Graph
|
||||
return await this._getTermsAsDropdownOptionsUsingGraph(apiUrl, parent);
|
||||
return await this._getTermsAsDropdownOptionsUsingGraph(apiUrl, selectProperties, parent);
|
||||
}
|
||||
|
||||
private static async _getTermsAsDropdownOptionsUsingGraph(apiUrl: string, parent: string): Promise<IDropdownOption[]> {
|
||||
private static async _getTermsAsDropdownOptionsUsingGraph(apiUrl: string, selectProperties: string[], parent: string): Promise<ICMMDDropdownOption[]> {
|
||||
try {
|
||||
let terms: ITerms = await MSGraph.Get(apiUrl, "beta");
|
||||
let terms: ITerms = await MSGraph.Get(apiUrl, "beta", selectProperties);
|
||||
if (terms.value) {
|
||||
//* Set key as description of the term
|
||||
//* Description will be of the format latitude;longitude
|
||||
//* This will be used to render maps
|
||||
const options: IDropdownOption[] = terms.value.map(t => ({
|
||||
key: t.descriptions[0] ? t.descriptions[0].description : t.id,
|
||||
text: t.labels[0].name
|
||||
}));
|
||||
let optionsToStoreInCache: IOption[] = options.map(o => ({
|
||||
key: o.key.toString(),
|
||||
text: o.text,
|
||||
const options: ICMMDDropdownOption[] = terms.value.map(t => ({
|
||||
key: t.id,
|
||||
text: t.labels[0].name,
|
||||
data: {
|
||||
latitude: Number(find(t.properties, p => p.key === "latitude")?.value) ?? null,
|
||||
longitude: Number(find(t.properties, p => p.key === "longitude")?.value) ?? null,
|
||||
},
|
||||
parent
|
||||
}));
|
||||
let optionsFromCache: IOption[] = this._fetchFromSessionStorge();
|
||||
optionsToStoreInCache = [...optionsFromCache, ...optionsToStoreInCache];
|
||||
|
||||
let optionsFromCache: ICMMDDropdownOption[] = this._fetchFromSessionStorge();
|
||||
let optionsToStoreInCache: ICMMDDropdownOption[] = [...optionsFromCache, ...options];
|
||||
window.sessionStorage.setItem(this._sessionStorageKey, JSON.stringify(optionsToStoreInCache));
|
||||
console.debug("%s Data added in cache.", this._logSource);
|
||||
return options;
|
||||
|
@ -55,8 +52,8 @@ export class MMDService {
|
|||
|
||||
}
|
||||
|
||||
private static _fetchFromSessionStorge(): IOption[] {
|
||||
let result: IOption[] = [];
|
||||
private static _fetchFromSessionStorge(): ICMMDDropdownOption[] {
|
||||
let result: ICMMDDropdownOption[] = [];
|
||||
let stringResult: string = window.sessionStorage.getItem(this._sessionStorageKey);
|
||||
if (stringResult) {
|
||||
try {
|
||||
|
|
|
@ -10,7 +10,7 @@ export class MSGraph {
|
|||
this._graphClient = await context.msGraphClientFactory.getClient();
|
||||
}
|
||||
|
||||
public static async Get(apiUrl: string, version: string = "v1.0", selectProperties?: string[], expandProperties?: string[], filter?: string): Promise<any> {
|
||||
public static async Get(apiUrl: string, version: string = "v1.0", selectProperties?: string[], expandProperties?: string[], filter?: string, count?: boolean): Promise<any> {
|
||||
var p = new Promise<string>(async (resolve, reject) => {
|
||||
let query = this._graphClient.api(apiUrl).version(version);
|
||||
if (selectProperties && selectProperties.length > 0) {
|
||||
|
@ -22,6 +22,9 @@ export class MSGraph {
|
|||
if (expandProperties && expandProperties.length > 0) {
|
||||
query = query.expand(expandProperties);
|
||||
}
|
||||
if (count) {
|
||||
query = query.count(true);
|
||||
}
|
||||
|
||||
let callback = (error: GraphError, response: any, rawResponse?: any) => {
|
||||
if (error) {
|
||||
|
@ -46,7 +49,7 @@ export class MSGraph {
|
|||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve();
|
||||
resolve(_response);
|
||||
}
|
||||
};
|
||||
await query.update(content, callback);
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
import { IDropdownOption } from "office-ui-fabric-react/lib/Dropdown";
|
||||
import { ICoordinates } from "@pnp/spfx-controls-react/lib/Map";
|
||||
|
||||
export interface ICMMDDropdownOption extends IDropdownOption {
|
||||
data: ICoordinates;
|
||||
parent?: string;
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
export interface IOption {
|
||||
key: string;
|
||||
text: string;
|
||||
parent: string;
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
export interface IProperty {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
import { IDescription } from "./IDescription";
|
||||
import { ILabel } from "./ILabel";
|
||||
import { IProperty, ILabel, IDescription } from ".";
|
||||
|
||||
export interface ITerm {
|
||||
id: string;
|
||||
|
@ -7,4 +6,5 @@ export interface ITerm {
|
|||
lastModifiedDateTime: Date;
|
||||
labels: ILabel[];
|
||||
descriptions: IDescription[];
|
||||
properties: IProperty[];
|
||||
}
|
|
@ -2,4 +2,5 @@ export { ILabel } from './ILabel';
|
|||
export { IDescription } from './IDescription';
|
||||
export { ITerm } from './ITerm';
|
||||
export { ITerms } from './ITerms';
|
||||
export { IOption } from './IOption';
|
||||
export { IProperty } from './IProperty';
|
||||
export { ICMMDDropdownOption } from './ICMMDDropdownOption';
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"extends": "./node_modules/@microsoft/rush-stack-compiler-2.9/includes/tsconfig-web.json",
|
||||
"extends": "./node_modules/@microsoft/rush-stack-compiler-3.9/includes/tsconfig-web.json",
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
|
@ -19,20 +19,17 @@
|
|||
"./node_modules/@microsoft"
|
||||
],
|
||||
"types": [
|
||||
"es6-promise",
|
||||
"webpack-env"
|
||||
],
|
||||
"lib": [
|
||||
"es5",
|
||||
"dom",
|
||||
"es2015.collection"
|
||||
"es2015.collection",
|
||||
"es2015.promise"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"lib"
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"extends": "@microsoft/sp-tslint-rules/base-tslint.json",
|
||||
"extends": "./node_modules/@microsoft/sp-tslint-rules/base-tslint.json",
|
||||
"rules": {
|
||||
"class-name": false,
|
||||
"export-name": false,
|
||||
|
@ -17,7 +17,6 @@
|
|||
"no-switch-case-fall-through": true,
|
||||
"no-unnecessary-semicolons": true,
|
||||
"no-unused-expression": true,
|
||||
"no-use-before-declare": true,
|
||||
"no-with-statement": true,
|
||||
"semicolon": true,
|
||||
"trailing-comma": false,
|
||||
|
|
Loading…
Reference in New Issue