Updated Dynamic Data sample to SPFx@1.7.0 (#669)
This commit is contained in:
parent
67a3279ef7
commit
0f78f7241b
|
@ -0,0 +1 @@
|
||||||
|
.npmrc
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"@microsoft/generator-sharepoint": {
|
||||||
|
"isCreatingSolution": true,
|
||||||
|
"environment": "spo",
|
||||||
|
"version": "1.7.0",
|
||||||
|
"libraryName": "react-events-dynamicdata",
|
||||||
|
"libraryId": "dcd4558f-8ea7-4f77-b494-c2b8a4d51b1d",
|
||||||
|
"packageManager": "npm",
|
||||||
|
"componentType": "webpart"
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,7 +5,7 @@ Sample web parts illustrating using the SharePoint Framework Dynamic data capabi
|
||||||
![Web parts placed on a modern SharePoint page showing information about events](./assets/dynamic-data-webparts.png)
|
![Web parts placed on a modern SharePoint page showing information about events](./assets/dynamic-data-webparts.png)
|
||||||
|
|
||||||
## Used SharePoint Framework Version
|
## Used SharePoint Framework Version
|
||||||
![drop](https://img.shields.io/badge/drop-1.5.0--plusbeta-blue.svg)
|
![drop](https://img.shields.io/badge/drop-1.7.0-green.svg)
|
||||||
|
|
||||||
## Applies to
|
## Applies to
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ react-events-dynamicdata|Waldek Mastykarz (MVP, Rencore, @waldekm)
|
||||||
|
|
||||||
Version|Date|Comments
|
Version|Date|Comments
|
||||||
-------|----|--------
|
-------|----|--------
|
||||||
|
1.1|November 9, 2018|Updated sample to SPFx v1.7.0
|
||||||
1.0|June 5, 2018|Initial release
|
1.0|June 5, 2018|Initial release
|
||||||
|
|
||||||
## Disclaimer
|
## Disclaimer
|
||||||
|
@ -43,12 +44,14 @@ Version|Date|Comments
|
||||||
* edit a page
|
* edit a page
|
||||||
* add the three web parts named: _Events_, _Event details_ and _Map_
|
* add the three web parts named: _Events_, _Event details_ and _Map_
|
||||||
* configure the _Event details_ web part:
|
* configure the _Event details_ web part:
|
||||||
* as _Data source_, choose the _Events_ option
|
* as _Connect to source_, choose the _Events_ option
|
||||||
* as _Data property_, choose the _Event_ option
|
* as _Event's properties_, choose the _Event_ option
|
||||||
* configure the _Map_ web part:
|
* configure the _Map_ web part:
|
||||||
* get a Bing maps API key (follow the link in the web part)
|
* get a Bing maps API key (follow the link in the web part)
|
||||||
* as _Data source_, choose the _Events_ option
|
* as _Connect to source_, choose the _Events_ option
|
||||||
* as _Data property_, choose the _Location_ option
|
* as _Event's properties_, choose the _Location_ option
|
||||||
|
* as _Address_, choose the _address_ option
|
||||||
|
* as _City_, choose the _city_ option
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
|
@ -59,7 +62,6 @@ Web parts in this solution illustrate the following concepts on top of the Share
|
||||||
* making web part a dynamic data source
|
* making web part a dynamic data source
|
||||||
* exposing multiple data properties from a single data source
|
* exposing multiple data properties from a single data source
|
||||||
* subscribing to dynamic data source notifications from a web part
|
* subscribing to dynamic data source notifications from a web part
|
||||||
* persisting dynamic data subscription information in web part properties
|
|
||||||
* deploying list instances from a SharePoint Framework solution package
|
* deploying list instances from a SharePoint Framework solution package
|
||||||
* using [PnPjs](https://github.com/pnp/pnpjs) to retrieve data from a SharePoint list
|
* using [PnPjs](https://github.com/pnp/pnpjs) to retrieve data from a SharePoint list
|
||||||
* using [SharePoint Framework React Controls](https://github.com/SharePoint/sp-dev-fx-controls-react) in web parts
|
* using [SharePoint Framework React Controls](https://github.com/SharePoint/sp-dev-fx-controls-react) in web parts
|
||||||
|
|
|
@ -1,45 +0,0 @@
|
||||||
{
|
|
||||||
"$schema": "https://developer.microsoft.com/json-schemas/core-build/tslint.schema.json",
|
|
||||||
// Display errors as warnings
|
|
||||||
"displayAsWarning": true,
|
|
||||||
// The TSLint task may have been configured with several custom lint rules
|
|
||||||
// before this config file is read (for example lint rules from the tslint-microsoft-contrib
|
|
||||||
// project). If true, this flag will deactivate any of these rules.
|
|
||||||
"removeExistingRules": true,
|
|
||||||
// When true, the TSLint task is configured with some default TSLint "rules.":
|
|
||||||
"useDefaultConfigAsBase": false,
|
|
||||||
// Since removeExistingRules=true and useDefaultConfigAsBase=false, there will be no lint rules
|
|
||||||
// which are active, other than the list of rules below.
|
|
||||||
"lintConfig": {
|
|
||||||
// Opt-in to Lint rules which help to eliminate bugs in JavaScript
|
|
||||||
"rules": {
|
|
||||||
"class-name": false,
|
|
||||||
"export-name": false,
|
|
||||||
"forin": false,
|
|
||||||
"label-position": false,
|
|
||||||
"member-access": true,
|
|
||||||
"no-arg": false,
|
|
||||||
"no-console": false,
|
|
||||||
"no-construct": false,
|
|
||||||
"no-duplicate-case": true,
|
|
||||||
"no-duplicate-variable": true,
|
|
||||||
"no-eval": false,
|
|
||||||
"no-function-expression": true,
|
|
||||||
"no-internal-module": true,
|
|
||||||
"no-shadowed-variable": true,
|
|
||||||
"no-switch-case-fall-through": true,
|
|
||||||
"no-unnecessary-semicolons": true,
|
|
||||||
"no-unused-expression": true,
|
|
||||||
"no-use-before-declare": true,
|
|
||||||
"no-with-statement": true,
|
|
||||||
"semicolon": true,
|
|
||||||
"trailing-comma": false,
|
|
||||||
"typedef": false,
|
|
||||||
"typedef-whitespace": false,
|
|
||||||
"use-named-parameter": true,
|
|
||||||
"valid-typeof": true,
|
|
||||||
"variable-name": false,
|
|
||||||
"whitespace": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
|
@ -11,27 +11,27 @@
|
||||||
"test": "gulp test"
|
"test": "gulp test"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@microsoft/sp-core-library": "1.5.0-plusbeta",
|
"@microsoft/sp-core-library": "1.7.0",
|
||||||
"@microsoft/sp-lodash-subset": "1.5.0-plusbeta",
|
"@microsoft/sp-lodash-subset": "1.7.0",
|
||||||
"@microsoft/sp-office-ui-fabric-core": "1.5.0-plusbeta",
|
"@microsoft/sp-office-ui-fabric-core": "1.7.0",
|
||||||
"@microsoft/sp-webpart-base": "1.5.0-plusbeta",
|
"@microsoft/sp-webpart-base": "1.7.0",
|
||||||
"@pnp/common": "^1.1.0",
|
"@pnp/common": "^1.1.0",
|
||||||
"@pnp/logging": "^1.1.0",
|
"@pnp/logging": "^1.1.0",
|
||||||
"@pnp/odata": "^1.1.0",
|
"@pnp/odata": "^1.1.0",
|
||||||
"@pnp/sp": "^1.1.0",
|
"@pnp/sp": "^1.1.0",
|
||||||
"@pnp/spfx-controls-react": "^1.4.0",
|
"@pnp/spfx-controls-react": "^1.4.0",
|
||||||
"@types/es6-promise": "0.0.33",
|
"@types/es6-promise": "0.0.33",
|
||||||
"@types/react": "15.6.6",
|
"@types/react": "16.4.2",
|
||||||
"@types/react-dom": "15.5.6",
|
"@types/react-dom": "16.0.5",
|
||||||
"@types/webpack-env": "1.13.1",
|
"@types/webpack-env": "1.13.1",
|
||||||
"react": "15.6.2",
|
"react": "16.3.2",
|
||||||
"react-dom": "15.6.2"
|
"react-dom": "16.3.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@microsoft/generator-sharepoint": "1.5.0",
|
"@microsoft/sp-build-web": "1.7.0",
|
||||||
"@microsoft/sp-build-web": "1.5.0-plusbeta",
|
"@microsoft/sp-module-interfaces": "1.7.0",
|
||||||
"@microsoft/sp-module-interfaces": "1.5.0-plusbeta",
|
"@microsoft/sp-webpart-workbench": "1.7.0",
|
||||||
"@microsoft/sp-webpart-workbench": "1.5.0-plusbeta",
|
"@microsoft/sp-tslint-rules": "1.7.0",
|
||||||
"@types/chai": "3.4.34",
|
"@types/chai": "3.4.34",
|
||||||
"@types/mocha": "2.2.38",
|
"@types/mocha": "2.2.38",
|
||||||
"ajv": "~5.2.2",
|
"ajv": "~5.2.2",
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
// A file is required to be in the root of the /src directory by the TypeScript compiler
|
|
@ -20,6 +20,7 @@
|
||||||
"description": { "default": "Shows details of the selected event" },
|
"description": { "default": "Shows details of the selected event" },
|
||||||
"officeFabricIconFontName": "CustomList",
|
"officeFabricIconFontName": "CustomList",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"event": {},
|
||||||
"title": "Event details"
|
"title": "Event details"
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
|
|
|
@ -4,28 +4,24 @@ import { Version } from '@microsoft/sp-core-library';
|
||||||
import {
|
import {
|
||||||
BaseClientSideWebPart,
|
BaseClientSideWebPart,
|
||||||
IPropertyPaneConfiguration,
|
IPropertyPaneConfiguration,
|
||||||
PropertyPaneTextField,
|
IWebPartPropertiesMetadata,
|
||||||
IPropertyPaneDropdownOption,
|
PropertyPaneDynamicFieldSet,
|
||||||
PropertyPaneDropdown
|
PropertyPaneDynamicField
|
||||||
} from '@microsoft/sp-webpart-base';
|
} from '@microsoft/sp-webpart-base';
|
||||||
|
|
||||||
import * as strings from 'EventDetailsWebPartStrings';
|
import * as strings from 'EventDetailsWebPartStrings';
|
||||||
import { EventDetails, IEventDetailsProps } from './components';
|
import { EventDetails, IEventDetailsProps } from './components';
|
||||||
import { IDynamicDataSource } from '@microsoft/sp-dynamic-data';
|
|
||||||
import { IEvent } from '../../data';
|
import { IEvent } from '../../data';
|
||||||
|
import { DynamicProperty } from '@microsoft/sp-component-base';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents properties of the Event details web part
|
* Represents properties of the Event details web part
|
||||||
*/
|
*/
|
||||||
export interface IEventDetailsWebPartProps {
|
export interface IEventDetailsWebPartProps {
|
||||||
/**
|
/**
|
||||||
* The ID of the dynamic data to which the web part is subscribed
|
* Event to show the details for
|
||||||
*/
|
*/
|
||||||
propertyId: string;
|
event: DynamicProperty<IEvent>;
|
||||||
/**
|
|
||||||
* The dynamic data source ID to which the web part is subscribed
|
|
||||||
*/
|
|
||||||
sourceId: string;
|
|
||||||
/**
|
/**
|
||||||
* Web part title
|
* Web part title
|
||||||
*/
|
*/
|
||||||
|
@ -37,20 +33,6 @@ export interface IEventDetailsWebPartProps {
|
||||||
* from the connected dynamic data source.
|
* from the connected dynamic data source.
|
||||||
*/
|
*/
|
||||||
export default class EventDetailsWebPart extends BaseClientSideWebPart<IEventDetailsWebPartProps> {
|
export default class EventDetailsWebPart extends BaseClientSideWebPart<IEventDetailsWebPartProps> {
|
||||||
/**
|
|
||||||
* The previous ID of the dynamic data source to which the web part is
|
|
||||||
* subscribed. Used to unsubscribe from previously registered dynamic data
|
|
||||||
* source notifications after changing web part configuration in the property
|
|
||||||
* pane.
|
|
||||||
*/
|
|
||||||
private _lastSourceId: string = undefined;
|
|
||||||
/**
|
|
||||||
* The previous ID of the dynamic data to which the web part is subscribed.
|
|
||||||
* Used to unsubscribe from previously registered dynamic data source
|
|
||||||
* notifications after changing web part configuration in the property pane.
|
|
||||||
*/
|
|
||||||
private _lastPropertyId: string = undefined;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event handler for clicking the Configure button on the Placeholder
|
* Event handler for clicking the Configure button on the Placeholder
|
||||||
*/
|
*/
|
||||||
|
@ -58,48 +40,14 @@ export default class EventDetailsWebPart extends BaseClientSideWebPart<IEventDet
|
||||||
this.context.propertyPane.open();
|
this.context.propertyPane.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onInit(): Promise<void> {
|
|
||||||
// bind render method to the current instance so that it can be correctly
|
|
||||||
// invoked when dynamic data change notification is triggered
|
|
||||||
this.render = this.render.bind(this);
|
|
||||||
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
public render(): void {
|
public render(): void {
|
||||||
let event: IEvent = undefined;
|
const needsConfiguration: boolean = !this.properties.event.tryGetSource();
|
||||||
const needsConfiguration: boolean = !this.properties.sourceId || !this.properties.propertyId;
|
|
||||||
|
|
||||||
// subscribe to dynamic data changes notifications
|
|
||||||
// do this only once the first time the web part is rendered and only,
|
|
||||||
// if the dynamic data source ID and property ID are provided
|
|
||||||
if (this.renderedOnce === false && !needsConfiguration) {
|
|
||||||
try {
|
|
||||||
this.context.dynamicDataProvider.registerPropertyChanged(this.properties.sourceId, this.properties.propertyId, this.render);
|
|
||||||
// store current values for the dynamic data source ID and property ID
|
|
||||||
// so that the web part can unsubscribe from notifications when the
|
|
||||||
// web part configuration changes
|
|
||||||
this._lastSourceId = this.properties.sourceId;
|
|
||||||
this._lastPropertyId = this.properties.propertyId;
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
this.context.statusRenderer.renderError(this.domElement, `An error has occurred while connecting to the data source. Details: ${e}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// retrieve the current value of dynamic data only if the dynamic data
|
|
||||||
// source ID and property ID have been provided
|
|
||||||
if (!needsConfiguration) {
|
|
||||||
const source: IDynamicDataSource = this.context.dynamicDataProvider.tryGetSource(this.properties.sourceId);
|
|
||||||
event = source ? source.getPropertyValue(this.properties.propertyId) : undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const element: React.ReactElement<IEventDetailsProps> = React.createElement(
|
const element: React.ReactElement<IEventDetailsProps> = React.createElement(
|
||||||
EventDetails,
|
EventDetails,
|
||||||
{
|
{
|
||||||
needsConfiguration: needsConfiguration,
|
needsConfiguration: needsConfiguration,
|
||||||
event: event,
|
event: this.properties.event,
|
||||||
onConfigure: this._onConfigure,
|
onConfigure: this._onConfigure,
|
||||||
displayMode: this.displayMode,
|
displayMode: this.displayMode,
|
||||||
title: this.properties.title,
|
title: this.properties.title,
|
||||||
|
@ -116,47 +64,28 @@ export default class EventDetailsWebPart extends BaseClientSideWebPart<IEventDet
|
||||||
return Version.parse('1.0');
|
return Version.parse('1.0');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected get propertiesMetadata(): IWebPartPropertiesMetadata {
|
||||||
|
return {
|
||||||
|
'event': {
|
||||||
|
dynamicPropertyType: 'object'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
||||||
// get all available dynamic data sources on the page
|
|
||||||
const sourceOptions: IPropertyPaneDropdownOption[] =
|
|
||||||
this.context.dynamicDataProvider.getAvailableSources().map(source => {
|
|
||||||
return {
|
|
||||||
key: source.id,
|
|
||||||
text: source.metadata.title
|
|
||||||
};
|
|
||||||
});
|
|
||||||
const selectedSource: string = this.properties.sourceId;
|
|
||||||
|
|
||||||
let propertyOptions: IPropertyPaneDropdownOption[] = [];
|
|
||||||
if (selectedSource) {
|
|
||||||
const source: IDynamicDataSource = this.context.dynamicDataProvider.tryGetSource(selectedSource);
|
|
||||||
if (source) {
|
|
||||||
// get the list of all properties exposed by the currently selected
|
|
||||||
// data source
|
|
||||||
propertyOptions = source.getPropertyDefinitions().map(prop => {
|
|
||||||
return {
|
|
||||||
key: prop.id,
|
|
||||||
text: prop.title
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
pages: [
|
pages: [
|
||||||
{
|
{
|
||||||
groups: [
|
groups: [
|
||||||
{
|
{
|
||||||
groupFields: [
|
groupFields: [
|
||||||
PropertyPaneDropdown('sourceId', {
|
PropertyPaneDynamicFieldSet({
|
||||||
label: strings.SourceIdFieldLabel,
|
label: 'Select event source',
|
||||||
options: sourceOptions,
|
fields: [
|
||||||
selectedKey: this.properties.sourceId
|
PropertyPaneDynamicField('event', {
|
||||||
}),
|
label: 'Event source'
|
||||||
PropertyPaneDropdown('propertyId', {
|
})
|
||||||
label: strings.PropertyIdFieldLabel,
|
]
|
||||||
options: propertyOptions,
|
|
||||||
selectedKey: this.properties.propertyId
|
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -165,27 +94,4 @@ export default class EventDetailsWebPart extends BaseClientSideWebPart<IEventDet
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onPropertyPaneFieldChanged(propertyPath: string): void {
|
|
||||||
if (propertyPath === 'sourceId') {
|
|
||||||
// reset the selected property ID after selecting a different dynamic
|
|
||||||
// data source
|
|
||||||
this.properties.propertyId =
|
|
||||||
this.context.dynamicDataProvider.tryGetSource(this.properties.sourceId).getPropertyDefinitions()[0].id;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._lastSourceId && this._lastPropertyId) {
|
|
||||||
// unsubscribe from the previously registered dynamic data changes
|
|
||||||
// notifications
|
|
||||||
this.context.dynamicDataProvider.unregisterPropertyChanged(this._lastSourceId, this._lastPropertyId, this.render);
|
|
||||||
}
|
|
||||||
|
|
||||||
// subscribe to the newly configured dynamic data changes notifications
|
|
||||||
this.context.dynamicDataProvider.registerPropertyChanged(this.properties.sourceId, this.properties.propertyId, this.render);
|
|
||||||
// store current values for the dynamic data source ID and property ID
|
|
||||||
// so that the web part can unsubscribe from notifications when the
|
|
||||||
// web part configuration changes
|
|
||||||
this._lastSourceId = this.properties.sourceId;
|
|
||||||
this._lastPropertyId = this.properties.propertyId;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,12 +4,13 @@ import { IEventDetailsProps } from './IEventDetailsProps';
|
||||||
import { Placeholder } from "@pnp/spfx-controls-react/lib/Placeholder";
|
import { Placeholder } from "@pnp/spfx-controls-react/lib/Placeholder";
|
||||||
import { Label } from 'office-ui-fabric-react/lib/Label';
|
import { Label } from 'office-ui-fabric-react/lib/Label';
|
||||||
import { Icon } from 'office-ui-fabric-react/lib/Icon';
|
import { Icon } from 'office-ui-fabric-react/lib/Icon';
|
||||||
import { IEvent } from '../../../data';
|
|
||||||
import { WebPartTitle } from '@pnp/spfx-controls-react/lib/WebPartTitle';
|
import { WebPartTitle } from '@pnp/spfx-controls-react/lib/WebPartTitle';
|
||||||
|
import { IEvent } from '../../../data';
|
||||||
|
|
||||||
export class EventDetails extends React.Component<IEventDetailsProps, {}> {
|
export class EventDetails extends React.Component<IEventDetailsProps, {}> {
|
||||||
public render(): React.ReactElement<IEventDetailsProps> {
|
public render(): React.ReactElement<IEventDetailsProps> {
|
||||||
const { needsConfiguration, event, onConfigure, displayMode, title, updateProperty } = this.props;
|
const { needsConfiguration, event, onConfigure, displayMode, title, updateProperty } = this.props;
|
||||||
|
const eventData: IEvent | undefined = event.tryGetValue();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.eventDetails}>
|
<div className={styles.eventDetails}>
|
||||||
|
@ -24,8 +25,8 @@ export class EventDetails extends React.Component<IEventDetailsProps, {}> {
|
||||||
buttonLabel='Configure'
|
buttonLabel='Configure'
|
||||||
onConfigure={onConfigure} />}
|
onConfigure={onConfigure} />}
|
||||||
{!needsConfiguration &&
|
{!needsConfiguration &&
|
||||||
event &&
|
eventData &&
|
||||||
(!event.name || !event.address) &&
|
(!eventData.name || !eventData.address) &&
|
||||||
<Placeholder
|
<Placeholder
|
||||||
iconName='Edit'
|
iconName='Edit'
|
||||||
iconText='Configure your web part'
|
iconText='Configure your web part'
|
||||||
|
@ -33,19 +34,19 @@ export class EventDetails extends React.Component<IEventDetailsProps, {}> {
|
||||||
buttonLabel='Configure'
|
buttonLabel='Configure'
|
||||||
onConfigure={onConfigure} />}
|
onConfigure={onConfigure} />}
|
||||||
{!needsConfiguration &&
|
{!needsConfiguration &&
|
||||||
!event &&
|
!eventData &&
|
||||||
<Placeholder
|
<Placeholder
|
||||||
iconName='CustomList'
|
iconName='CustomList'
|
||||||
iconText='Event details'
|
iconText='Event details'
|
||||||
description='Select an event' />}
|
description='Select an event' />}
|
||||||
{!needsConfiguration &&
|
{!needsConfiguration &&
|
||||||
event &&
|
eventData &&
|
||||||
<ul>
|
<ul>
|
||||||
<li><Label>Event name</Label> {event.name}</li>
|
<li><Label>Event name</Label> {eventData.name}</li>
|
||||||
<li><Label>City</Label> {event.city}</li>
|
<li><Label>City</Label> {eventData.city}</li>
|
||||||
<li><Label>Address</Label> {event.address}</li>
|
<li><Label>Address</Label> {eventData.address}</li>
|
||||||
<li><Label>Organizer</Label> <Icon iconName='Mail' /> <a href={`mailto:${event.organizerEmail}`}>{event.organizerName}</a></li>
|
<li><Label>Organizer</Label> <Icon iconName='Mail' /> <a href={`mailto:${eventData.organizerEmail}`}>{eventData.organizerName}</a></li>
|
||||||
<li><Label>Date</Label> {new Date(event.date).toLocaleDateString()}</li>
|
<li><Label>Date</Label> {new Date(eventData.date).toLocaleDateString()}</li>
|
||||||
</ul>}
|
</ul>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { DisplayMode } from "@microsoft/sp-core-library";
|
import { DisplayMode } from "@microsoft/sp-core-library";
|
||||||
|
import { DynamicProperty } from "@microsoft/sp-component-base";
|
||||||
import { IEvent } from "../../../data";
|
import { IEvent } from "../../../data";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -12,7 +13,7 @@ export interface IEventDetailsProps {
|
||||||
/**
|
/**
|
||||||
* The currently selected event
|
* The currently selected event
|
||||||
*/
|
*/
|
||||||
event: IEvent;
|
event: DynamicProperty<IEvent>;
|
||||||
/**
|
/**
|
||||||
* Determines if the web part has been connected to a dynamic data source or
|
* Determines if the web part has been connected to a dynamic data source or
|
||||||
* not
|
* not
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
"description": { "default": "Shows upcoming events" },
|
"description": { "default": "Shows upcoming events" },
|
||||||
"officeFabricIconFontName": "Calendar",
|
"officeFabricIconFontName": "Calendar",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"event": "",
|
||||||
"title": "Upcoming events"
|
"title": "Upcoming events"
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
|
|
|
@ -3,8 +3,7 @@ import * as ReactDom from 'react-dom';
|
||||||
import { Version } from '@microsoft/sp-core-library';
|
import { Version } from '@microsoft/sp-core-library';
|
||||||
import {
|
import {
|
||||||
BaseClientSideWebPart,
|
BaseClientSideWebPart,
|
||||||
IPropertyPaneConfiguration,
|
IPropertyPaneConfiguration
|
||||||
PropertyPaneTextField
|
|
||||||
} from '@microsoft/sp-webpart-base';
|
} from '@microsoft/sp-webpart-base';
|
||||||
|
|
||||||
import * as strings from 'EventsWebPartStrings';
|
import * as strings from 'EventsWebPartStrings';
|
||||||
|
@ -12,7 +11,7 @@ import { Events } from './components';
|
||||||
import { IEventsProps } from './components/IEventsProps';
|
import { IEventsProps } from './components/IEventsProps';
|
||||||
|
|
||||||
import { sp } from '@pnp/sp';
|
import { sp } from '@pnp/sp';
|
||||||
import { IDynamicDataController, IDynamicDataPropertyDefinition } from '@microsoft/sp-dynamic-data';
|
import { IDynamicDataPropertyDefinition, IDynamicDataCallables } from '@microsoft/sp-dynamic-data';
|
||||||
import { IEvent, ILocation } from '../../data';
|
import { IEvent, ILocation } from '../../data';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -28,7 +27,7 @@ export interface IEventsWebPartProps {
|
||||||
/**
|
/**
|
||||||
* Events web part. Shows list of events retrieved from a SharePoint list
|
* Events web part. Shows list of events retrieved from a SharePoint list
|
||||||
*/
|
*/
|
||||||
export default class EventsWebPart extends BaseClientSideWebPart<IEventsWebPartProps> implements IDynamicDataController {
|
export default class EventsWebPart extends BaseClientSideWebPart<IEventsWebPartProps> implements IDynamicDataCallables {
|
||||||
/**
|
/**
|
||||||
* Currently selected event
|
* Currently selected event
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import styles from './Events.module.scss';
|
import styles from './Events.module.scss';
|
||||||
import { IEventsProps, IEventsState } from '.';
|
import { IEventsProps, IEventsState } from '.';
|
||||||
import { IEvent, IEventItem } from "../../../data";
|
import { IEventItem } from "../../../data";
|
||||||
import { sp } from "@pnp/sp";
|
import { sp } from "@pnp/sp";
|
||||||
import { Spinner, SpinnerSize } from 'office-ui-fabric-react/lib/Spinner';
|
import { Spinner, SpinnerSize } from 'office-ui-fabric-react/lib/Spinner';
|
||||||
import { WebPartTitle } from "@pnp/spfx-controls-react/lib/WebPartTitle";
|
import { WebPartTitle } from "@pnp/spfx-controls-react/lib/WebPartTitle";
|
||||||
import { ListView, IViewField, SelectionMode, GroupOrder, IGrouping } from "@pnp/spfx-controls-react/lib/ListView";
|
import { ListView, SelectionMode } from "@pnp/spfx-controls-react/lib/ListView";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Events component
|
* Events component
|
||||||
*/
|
*/
|
||||||
export class Events extends React.Component<IEventsProps, IEventsState> {
|
export class Events extends React.Component<IEventsProps, IEventsState> {
|
||||||
constructor() {
|
constructor(props: IEventsProps) {
|
||||||
super();
|
super(props);
|
||||||
|
|
||||||
// set default state
|
// set default state
|
||||||
this.state = {
|
this.state = {
|
||||||
|
@ -38,31 +38,60 @@ export class Events extends React.Component<IEventsProps, IEventsState> {
|
||||||
loading: true
|
loading: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
events: [{
|
||||||
|
name: 'Tampa Home Show',
|
||||||
|
city: 'Tampa, FL',
|
||||||
|
address: '333 S Franklin St',
|
||||||
|
organizerName: 'Grady Archie',
|
||||||
|
organizerEmail: 'GradyA@contoso.OnMicrosoft.com',
|
||||||
|
date: '2018-05-29T00:00:00Z'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Custom Electronic Design and Installation Association (CEDIA)',
|
||||||
|
city: 'San Diego, CA',
|
||||||
|
address: '111 W Harbor Dr',
|
||||||
|
organizerName: 'Megan Bowen',
|
||||||
|
organizerEmail: 'MeganB@contoso.OnMicrosoft.com',
|
||||||
|
date: '2018-06-15T00:00:00Z'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Design Automation Conference (DAC)',
|
||||||
|
city: 'San Francisco, CA',
|
||||||
|
address: '747 Howard St Fl 5',
|
||||||
|
organizerName: 'Irvin Sayers',
|
||||||
|
organizerEmail: 'IrvinSB@contoso.OnMicrosoft.com',
|
||||||
|
date: '2018-07-05T00:00:00Z'
|
||||||
|
}],
|
||||||
|
error: undefined
|
||||||
|
});
|
||||||
|
|
||||||
// load information about events from the SharePoint list
|
// load information about events from the SharePoint list
|
||||||
sp.web
|
// sp.web
|
||||||
.getList(`${this.props.siteUrl}/Lists/CompanyEvents`)
|
// .getList(`${this.props.siteUrl}/Lists/CompanyEvents`)
|
||||||
.items.getAll()
|
// .items.getAll()
|
||||||
.then((items: IEventItem[]): void => {
|
// .then((items: IEventItem[]): void => {
|
||||||
this.setState({
|
// this.setState({
|
||||||
loading: false,
|
// loading: false,
|
||||||
events: items.map(i => {
|
// events: items.map(i => {
|
||||||
return {
|
// return {
|
||||||
date: i.PnPEventDate,
|
// date: i.PnPEventDate,
|
||||||
name: i.Title,
|
// name: i.Title,
|
||||||
city: i.PnPCity,
|
// city: i.PnPCity,
|
||||||
address: i.PnPAddress,
|
// address: i.PnPAddress,
|
||||||
organizerName: i.PnPOrganizerName,
|
// organizerName: i.PnPOrganizerName,
|
||||||
organizerEmail: i.PnPOrganizerEmail
|
// organizerEmail: i.PnPOrganizerEmail
|
||||||
};
|
// };
|
||||||
})
|
// })
|
||||||
});
|
// });
|
||||||
}, (error: any): void => {
|
// }, (error: any): void => {
|
||||||
// communicate error
|
// // communicate error
|
||||||
this.setState({
|
// this.setState({
|
||||||
loading: false,
|
// loading: false,
|
||||||
error: error
|
// error: error
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(): React.ReactElement<IEventsProps> {
|
public render(): React.ReactElement<IEventsProps> {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
|
||||||
"id": "44181a37-27e5-4802-8f0b-4f8766792ede",
|
"id": "44181a37-27e5-4802-8f0b-4f8766792eda",
|
||||||
"alias": "MapWebPart",
|
"alias": "MapWebPart",
|
||||||
"componentType": "WebPart",
|
"componentType": "WebPart",
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@
|
||||||
"properties": {
|
"properties": {
|
||||||
"address": "",
|
"address": "",
|
||||||
"bingMapsApiKey": "",
|
"bingMapsApiKey": "",
|
||||||
|
"city": "",
|
||||||
"title": ""
|
"title": ""
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
|
|
|
@ -5,16 +5,17 @@ import {
|
||||||
BaseClientSideWebPart,
|
BaseClientSideWebPart,
|
||||||
IPropertyPaneConfiguration,
|
IPropertyPaneConfiguration,
|
||||||
PropertyPaneTextField,
|
PropertyPaneTextField,
|
||||||
PropertyPaneDropdown,
|
PropertyPaneLink,
|
||||||
IPropertyPaneDropdownOption,
|
IPropertyPaneConditionalGroup,
|
||||||
PropertyPaneLabel,
|
PropertyPaneDynamicFieldSet,
|
||||||
PropertyPaneLink
|
PropertyPaneDynamicField,
|
||||||
|
IWebPartPropertiesMetadata,
|
||||||
|
DynamicDataSharedDepth
|
||||||
} from '@microsoft/sp-webpart-base';
|
} from '@microsoft/sp-webpart-base';
|
||||||
|
|
||||||
import * as strings from 'MapWebPartStrings';
|
import * as strings from 'MapWebPartStrings';
|
||||||
import { Map, IMapProps } from './components';
|
import { Map, IMapProps } from './components';
|
||||||
import { IDynamicDataSource } from '@microsoft/sp-dynamic-data';
|
import { DynamicProperty } from '@microsoft/sp-component-base';
|
||||||
import { ILocation } from '../../data';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map web part properties
|
* Map web part properties
|
||||||
|
@ -23,19 +24,15 @@ export interface IMapWebPartProps {
|
||||||
/**
|
/**
|
||||||
* The address to display on the map
|
* The address to display on the map
|
||||||
*/
|
*/
|
||||||
address: string;
|
address: DynamicProperty<string>;
|
||||||
/**
|
/**
|
||||||
* Bing maps API key to use with the Bing maps API
|
* Bing maps API key to use with the Bing maps API
|
||||||
*/
|
*/
|
||||||
bingMapsApiKey: string;
|
bingMapsApiKey: string;
|
||||||
/**
|
/**
|
||||||
* The ID of the dynamic data to which the web part is subscribed
|
* The city where the address is located
|
||||||
*/
|
*/
|
||||||
propertyId: string;
|
city: DynamicProperty<string>;
|
||||||
/**
|
|
||||||
* The dynamic data source ID to which the web part is subscribed
|
|
||||||
*/
|
|
||||||
sourceId: string;
|
|
||||||
/**
|
/**
|
||||||
* Web part title
|
* Web part title
|
||||||
*/
|
*/
|
||||||
|
@ -48,20 +45,6 @@ export interface IMapWebPartProps {
|
||||||
* source connection.
|
* source connection.
|
||||||
*/
|
*/
|
||||||
export default class MapWebPart extends BaseClientSideWebPart<IMapWebPartProps> {
|
export default class MapWebPart extends BaseClientSideWebPart<IMapWebPartProps> {
|
||||||
/**
|
|
||||||
* The previous ID of the dynamic data source to which the web part is
|
|
||||||
* subscribed. Used to unsubscribe from previously registered dynamic data
|
|
||||||
* source notifications after changing web part configuration in the property
|
|
||||||
* pane.
|
|
||||||
*/
|
|
||||||
private _lastSourceId: string;
|
|
||||||
/**
|
|
||||||
* The previous ID of the dynamic data to which the web part is subscribed.
|
|
||||||
* Used to unsubscribe from previously registered dynamic data source
|
|
||||||
* notifications after changing web part configuration in the property pane.
|
|
||||||
*/
|
|
||||||
private _lastPropertyId: string;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event handler for clicking the Configure button on the Placeholder
|
* Event handler for clicking the Configure button on the Placeholder
|
||||||
*/
|
*/
|
||||||
|
@ -69,50 +52,14 @@ export default class MapWebPart extends BaseClientSideWebPart<IMapWebPartProps>
|
||||||
this.context.propertyPane.open();
|
this.context.propertyPane.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onInit(): Promise<void> {
|
|
||||||
// bind render method to the current instance so that it can be correctly
|
|
||||||
// invoked when dynamic data change notification is triggered
|
|
||||||
this.render = this.render.bind(this);
|
|
||||||
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
public render(): void {
|
public render(): void {
|
||||||
// subscribe to dynamic data changes notifications
|
// Get the location to show on the map. The location will be retrieved
|
||||||
// do this only once the first time the web part is rendered and only,
|
// either from the event selected in the connected data source or from the
|
||||||
// if the dynamic data source ID and property ID are provided
|
// address entered in web part properties
|
||||||
if (this.renderedOnce === false) {
|
const address: string | undefined = this.properties.address.tryGetValue();
|
||||||
if (this.properties.sourceId && this.properties.propertyId) {
|
const city: string | undefined = this.properties.city.tryGetValue();
|
||||||
try {
|
const needsConfiguration: boolean = !this.properties.bingMapsApiKey || (!address && !this.properties.address.tryGetSource()) ||
|
||||||
this.context.dynamicDataProvider.registerPropertyChanged(this.properties.sourceId, this.properties.propertyId, this.render);
|
(!city && !this.properties.city.tryGetSource());
|
||||||
this._lastSourceId = this.properties.sourceId;
|
|
||||||
this._lastPropertyId = this.properties.propertyId;
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
this.context.statusRenderer.renderError(this.domElement, `${strings.ErrorText}${e}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const dynamicAddress: boolean = !!this.properties.sourceId;
|
|
||||||
|
|
||||||
const needsConfiguration: boolean = !this.properties.bingMapsApiKey ||
|
|
||||||
(!dynamicAddress && !this.properties.address) ||
|
|
||||||
(dynamicAddress && !this.properties.propertyId);
|
|
||||||
|
|
||||||
let address: string = dynamicAddress ? undefined : this.properties.address;
|
|
||||||
|
|
||||||
// if the web part is set to retrieve its address from a dynamic data source
|
|
||||||
// and the dynamic data source has been configured, try to retrieve the
|
|
||||||
// currently selected location
|
|
||||||
if (!needsConfiguration && dynamicAddress) {
|
|
||||||
const source: IDynamicDataSource = this.context.dynamicDataProvider.tryGetSource(this.properties.sourceId);
|
|
||||||
const location: ILocation = source ? source.getPropertyValue(this.properties.propertyId) : undefined;
|
|
||||||
if (location) {
|
|
||||||
address = `${location.address} ${location.city}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const element: React.ReactElement<IMapProps> = React.createElement(
|
const element: React.ReactElement<IMapProps> = React.createElement(
|
||||||
Map,
|
Map,
|
||||||
|
@ -120,8 +67,8 @@ export default class MapWebPart extends BaseClientSideWebPart<IMapWebPartProps>
|
||||||
needsConfiguration: needsConfiguration,
|
needsConfiguration: needsConfiguration,
|
||||||
httpClient: this.context.httpClient,
|
httpClient: this.context.httpClient,
|
||||||
bingMapsApiKey: this.properties.bingMapsApiKey,
|
bingMapsApiKey: this.properties.bingMapsApiKey,
|
||||||
dynamicAddress: dynamicAddress,
|
dynamicAddress: !!this.properties.address.tryGetSource(),
|
||||||
address: address,
|
address: `${address} ${city}`,
|
||||||
onConfigure: this._onConfigure,
|
onConfigure: this._onConfigure,
|
||||||
width: this.domElement.clientWidth,
|
width: this.domElement.clientWidth,
|
||||||
height: this.domElement.clientHeight,
|
height: this.domElement.clientHeight,
|
||||||
|
@ -140,39 +87,21 @@ export default class MapWebPart extends BaseClientSideWebPart<IMapWebPartProps>
|
||||||
return Version.parse('1.0');
|
return Version.parse('1.0');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected get propertiesMetadata(): IWebPartPropertiesMetadata {
|
||||||
|
return {
|
||||||
|
// Denote the address web part property as a dynamic property of type
|
||||||
|
// object to allow the address information to be serialized by
|
||||||
|
// the SharePoint Framework.
|
||||||
|
'address': {
|
||||||
|
dynamicPropertyType: 'string'
|
||||||
|
},
|
||||||
|
'city': {
|
||||||
|
dynamicPropertyType: 'string'
|
||||||
|
}
|
||||||
|
} as any as IWebPartPropertiesMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
||||||
// get all available dynamic data sources on the page
|
|
||||||
const sourceOptions: IPropertyPaneDropdownOption[] =
|
|
||||||
this.context.dynamicDataProvider.getAvailableSources().map(source => {
|
|
||||||
return {
|
|
||||||
key: source.id,
|
|
||||||
text: source.metadata.title
|
|
||||||
};
|
|
||||||
});
|
|
||||||
// add an extra option on top to indicate, that the web part should get
|
|
||||||
// its address information from the web part configuration rather than from
|
|
||||||
// a dynamic data source
|
|
||||||
sourceOptions.unshift({
|
|
||||||
key: '',
|
|
||||||
text: 'Web part configuration'
|
|
||||||
});
|
|
||||||
const selectedSource: string = this.properties.sourceId;
|
|
||||||
|
|
||||||
let propertyOptions: IPropertyPaneDropdownOption[] = [];
|
|
||||||
if (selectedSource) {
|
|
||||||
const source: IDynamicDataSource = this.context.dynamicDataProvider.tryGetSource(selectedSource);
|
|
||||||
if (source) {
|
|
||||||
// get the list of all properties exposed by the currently selected
|
|
||||||
// data source
|
|
||||||
propertyOptions = source.getPropertyDefinitions().map(prop => {
|
|
||||||
return {
|
|
||||||
key: prop.id,
|
|
||||||
text: prop.title
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
pages: [
|
pages: [
|
||||||
{
|
{
|
||||||
|
@ -190,62 +119,55 @@ export default class MapWebPart extends BaseClientSideWebPart<IMapWebPartProps>
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
// Web part properties group for specifying the information about
|
||||||
|
// the address to show on the map.
|
||||||
{
|
{
|
||||||
|
// Primary group is used to provide the address to show on the map
|
||||||
|
// in a text field in the web part properties
|
||||||
|
primaryGroup: {
|
||||||
groupName: strings.DataGroupName,
|
groupName: strings.DataGroupName,
|
||||||
groupFields: [
|
groupFields: [
|
||||||
PropertyPaneTextField('address', {
|
PropertyPaneTextField('address', {
|
||||||
label: strings.AddressFieldLabel
|
label: strings.AddressFieldLabel
|
||||||
|
}),
|
||||||
|
PropertyPaneTextField('city', {
|
||||||
|
label: strings.CityFieldLabel
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
// Secondary group is used to retrieve the address from the
|
||||||
groupName: strings.ConnectionGroupName,
|
// connected dynamic data source
|
||||||
|
secondaryGroup: {
|
||||||
|
groupName: strings.DataGroupName,
|
||||||
groupFields: [
|
groupFields: [
|
||||||
PropertyPaneDropdown('sourceId', {
|
PropertyPaneDynamicFieldSet({
|
||||||
label: strings.SourceIdFieldLabel,
|
label: 'Address',
|
||||||
options: sourceOptions,
|
fields: [
|
||||||
selectedKey: this.properties.sourceId
|
PropertyPaneDynamicField('address', {
|
||||||
|
label: strings.AddressFieldLabel
|
||||||
}),
|
}),
|
||||||
PropertyPaneDropdown('propertyId', {
|
PropertyPaneDynamicField('city', {
|
||||||
label: strings.PropertyIdFieldLabel,
|
label: strings.CityFieldLabel
|
||||||
options: propertyOptions,
|
})
|
||||||
selectedKey: this.properties.propertyId
|
],
|
||||||
|
sharedConfiguration: {
|
||||||
|
// because address and city come from the same data source
|
||||||
|
// the connection can share the selected dynamic property
|
||||||
|
depth: DynamicDataSharedDepth.Property
|
||||||
|
}
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
|
// Show the secondary group only if the web part has been
|
||||||
|
// connected to a dynamic data source
|
||||||
|
showSecondaryGroup: !!this.properties.address.tryGetSource()
|
||||||
|
} as IPropertyPaneConditionalGroup
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onPropertyPaneFieldChanged(propertyPath: string): void {
|
|
||||||
if (!this.properties.sourceId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (propertyPath === 'sourceId') {
|
|
||||||
// reset the selected property ID after selecting a different dynamic
|
|
||||||
// data source
|
|
||||||
this.properties.propertyId =
|
|
||||||
this.context.dynamicDataProvider.tryGetSource(this.properties.sourceId).getPropertyDefinitions()[0].id;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._lastSourceId && this._lastPropertyId) {
|
|
||||||
// unsubscribe from the previously registered dynamic data changes
|
|
||||||
// notifications
|
|
||||||
this.context.dynamicDataProvider.unregisterPropertyChanged(this._lastSourceId, this._lastPropertyId, this.render);
|
|
||||||
}
|
|
||||||
|
|
||||||
// subscribe to the newly configured dynamic data changes notifications
|
|
||||||
this.context.dynamicDataProvider.registerPropertyChanged(this.properties.sourceId, this.properties.propertyId, this.render);
|
|
||||||
// store current values for the dynamic data source ID and property ID
|
|
||||||
// so that the web part can unsubscribe from notifications when the
|
|
||||||
// web part configuration changes
|
|
||||||
this._lastSourceId = this.properties.sourceId;
|
|
||||||
this._lastPropertyId = this.properties.propertyId;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected get disableReactivePropertyChanges(): boolean {
|
protected get disableReactivePropertyChanges(): boolean {
|
||||||
// set property changes mode to reactive, so that the Bing Maps API is not
|
// set property changes mode to reactive, so that the Bing Maps API is not
|
||||||
// called on each keystroke when typing in the address to show on the map
|
// called on each keystroke when typing in the address to show on the map
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import { HttpClient } from "@microsoft/sp-http";
|
import { HttpClient } from "@microsoft/sp-http";
|
||||||
import { DisplayMode } from "@microsoft/sp-core-library";
|
import { DisplayMode } from "@microsoft/sp-core-library";
|
||||||
|
import { DynamicProperty } from "@microsoft/sp-component-base";
|
||||||
|
import { ILocation } from "../../../data";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map component properties
|
* Map component properties
|
||||||
|
@ -8,7 +10,7 @@ export interface IMapProps {
|
||||||
/**
|
/**
|
||||||
* The address to show on the map
|
* The address to show on the map
|
||||||
*/
|
*/
|
||||||
address: string;
|
address: ILocation | string | undefined;
|
||||||
/**
|
/**
|
||||||
* The Bing maps API key to use when communicating with the Bing maps API
|
* The Bing maps API key to use when communicating with the Bing maps API
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -10,8 +10,8 @@ import { WebPartTitle } from '@pnp/spfx-controls-react/lib/WebPartTitle';
|
||||||
* Map component. Renders map for the specified location
|
* Map component. Renders map for the specified location
|
||||||
*/
|
*/
|
||||||
export class Map extends React.Component<IMapProps, IMapState> {
|
export class Map extends React.Component<IMapProps, IMapState> {
|
||||||
constructor() {
|
constructor(props: IMapProps) {
|
||||||
super();
|
super(props);
|
||||||
|
|
||||||
// set default state
|
// set default state
|
||||||
this.state = {
|
this.state = {
|
||||||
|
@ -30,6 +30,10 @@ export class Map extends React.Component<IMapProps, IMapState> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.props.address) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// indicate that the component will be loading its data
|
// indicate that the component will be loading its data
|
||||||
this.setState({
|
this.setState({
|
||||||
error: undefined,
|
error: undefined,
|
||||||
|
@ -68,14 +72,14 @@ export class Map extends React.Component<IMapProps, IMapState> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentWillMount(): void {
|
public componentDidMount(): void {
|
||||||
// get coordinates for the current address after the component has been
|
// get coordinates for the current address after the component has been
|
||||||
// instantiated
|
// instantiated
|
||||||
this._resolveCoordinates();
|
this._resolveCoordinates();
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentDidUpdate?(prevProps: IMapProps, prevState: IMapState, prevContext: any): void {
|
public componentDidUpdate?(prevProps: IMapProps, prevState: IMapState, snapshot: any): void {
|
||||||
if (this.props.address !== prevProps.address) {
|
if (prevProps.address !== this.props.address) {
|
||||||
// get coordinates for the new address
|
// get coordinates for the new address
|
||||||
this._resolveCoordinates();
|
this._resolveCoordinates();
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ define([], function() {
|
||||||
"AddressFieldLabel": "Address",
|
"AddressFieldLabel": "Address",
|
||||||
"BingMapsApiKeyFieldLabel": "Bing Maps API Key",
|
"BingMapsApiKeyFieldLabel": "Bing Maps API Key",
|
||||||
"BingMapsGroupName": "Bing Maps configuration",
|
"BingMapsGroupName": "Bing Maps configuration",
|
||||||
|
"CityFieldLabel": "City",
|
||||||
"ConnectionGroupName": "Connection",
|
"ConnectionGroupName": "Connection",
|
||||||
"DataGroupName": "Data",
|
"DataGroupName": "Data",
|
||||||
"ErrorText": "An error has occurred while connecting to the data source. Details: ",
|
"ErrorText": "An error has occurred while connecting to the data source. Details: ",
|
||||||
|
|
|
@ -2,6 +2,7 @@ declare interface IMapWebPartStrings {
|
||||||
AddressFieldLabel: string;
|
AddressFieldLabel: string;
|
||||||
BingMapsApiKeyFieldLabel: string;
|
BingMapsApiKeyFieldLabel: string;
|
||||||
BingMapsGroupName: string;
|
BingMapsGroupName: string;
|
||||||
|
CityFieldLabel: string;
|
||||||
ConnectionGroupName: string;
|
ConnectionGroupName: string;
|
||||||
DataGroupName: string;
|
DataGroupName: string;
|
||||||
ErrorText: string;
|
ErrorText: string;
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
|
"outDir": "lib",
|
||||||
"typeRoots": [
|
"typeRoots": [
|
||||||
"./node_modules/@types",
|
"./node_modules/@types",
|
||||||
"./node_modules/@microsoft"
|
"./node_modules/@microsoft"
|
||||||
|
@ -22,5 +23,12 @@
|
||||||
"dom",
|
"dom",
|
||||||
"es2015.collection"
|
"es2015.collection"
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules",
|
||||||
|
"lib"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
"extends": "@microsoft/sp-tslint-rules/base-tslint.json",
|
||||||
|
"rules": {
|
||||||
|
"class-name": false,
|
||||||
|
"export-name": false,
|
||||||
|
"forin": false,
|
||||||
|
"label-position": false,
|
||||||
|
"member-access": true,
|
||||||
|
"no-arg": false,
|
||||||
|
"no-console": false,
|
||||||
|
"no-construct": false,
|
||||||
|
"no-duplicate-variable": true,
|
||||||
|
"no-eval": false,
|
||||||
|
"no-function-expression": true,
|
||||||
|
"no-internal-module": true,
|
||||||
|
"no-shadowed-variable": true,
|
||||||
|
"no-switch-case-fall-through": true,
|
||||||
|
"no-unnecessary-semicolons": true,
|
||||||
|
"no-unused-expression": true,
|
||||||
|
"no-use-before-declare": true,
|
||||||
|
"no-with-statement": true,
|
||||||
|
"semicolon": true,
|
||||||
|
"trailing-comma": false,
|
||||||
|
"typedef": false,
|
||||||
|
"typedef-whitespace": false,
|
||||||
|
"use-named-parameter": true,
|
||||||
|
"variable-name": false,
|
||||||
|
"whitespace": false
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue