@microsoft/sp-webpart-base
Version:
SharePoint Framework support for building web parts
649 lines • 28.3 kB
TypeScript
import type { IA11yCheckResult } from '@msinternal/sp-a11y-checker-util';
import { BaseComponent } from '@microsoft/sp-component-base';
import { DisplayMode, Version } from '@microsoft/sp-core-library';
import type { _IPropertyPaneData as IPropertyPaneData, _IPropertyPaneConsumer as IPropertyPaneConsumer, _PropertyPaneLifeCycleEvent as PropertyPaneLifeCycleEvent } from '@microsoft/sp-property-pane';
import type { ITopActions } from '@microsoft/sp-top-actions';
import type BaseWebPartContext from './BaseWebPartContext';
import { type IPropertyPaneConfiguration, PropertyPaneFieldType } from '../SPPropertyPane';
import type { IHideOn, IModifiedMetadata, default as IWebPartData } from './IWebPartData';
import type { default as IWebPartPropertiesMetadata } from './IWebPartPropertiesMetadata';
/**
* This abstract class implements the UI-agnostic base functionality for a client-side web part.
* The purpose is to allow a common core between 2D and MR WebParts.
*
* @privateRemarks
* The _refresh() and _dynamicPropertyRefresh() API are the only API that are
* mandatory to be implemented by our base WebPart classes.
*
* @public
*/
export default abstract class BaseWebPart<TProperties extends {}> extends BaseComponent implements IPropertyPaneConsumer {
/**
* {@inheritDoc @microsoft/sp-component-base#BaseComponent.context}
*/
readonly context: BaseWebPartContext;
/**
* @internal
*/
protected _renderedFromPersistedData: boolean | undefined;
private _displayMode;
private _properties;
private _initialized;
/**
* Keep a copy of the properties to be used for discard in non-reactive mode and this new set
* of `properties('_backUpProperties')` is what will be persisted until the apply button is clicked.
*/
private _backupProperties;
private _previousState;
private _baseLogSource;
private _title;
private _modifiedByGraph;
private _description;
private _iconImageUrl;
private _audiences;
private _hideOn;
/**
* It makes sure we only log edit events once during one editing session.
*/
private _hasEditLogged;
/**
* Cache init properties value, it would be used to check whether web part has been changed or not.
*/
private _initPropertiesSnapshot;
private _loadPropertyPaneResourcesPromise;
private _emptyResolvedPromise;
/**
* This property points to the preview image for the web part. The base implementation returns undefined. Web parts
* that want to provide a valid preview image url need to override this API. The preview image url can be used to
* create a preview of the web part or of the page on which the web part is present.
* virtual
*/
get previewImageUrl(): string | undefined;
/**
* This property is the current display mode of the web part.
*
* @readonly
*/
get displayMode(): DisplayMode;
/**
* This property holds metadata about Graph API calls that touch the web part.
* @internal
*/
get modifiedByGraph(): IModifiedMetadata;
/**
* The value of this property is stored in the serialized data of the web part to allow developers to manage
* versioning of their web part. The default version is 1.0
*/
protected get dataVersion(): Version;
/**
* This property is the pointer to the custom property bag of the web part.
*
* @readonly
*/
protected get properties(): TProperties;
/**
* This property defines metadata for the web part property bag. The metadata can help SharePoint understand
* the content of the properties better and perform relevant services on the data.
* virtual
*
* @remarks
* See {@link IWebPartPropertiesMetadata} for more information about how to define metadata
*/
protected get propertiesMetadata(): IWebPartPropertiesMetadata | undefined;
/**
* This property is used to change the web part's property pane interaction from Reactive to NonReactive.
* virtual
*
* @remarks
* The default behavior is Reactive.
*
* Reactive implies that changes made in the PropertyPane are transmitted to the web part instantly and the user can
* see instant updates. This helps the page creator get instant feedback and decide if they should keep the new
* configuration changes or not.
*
* NonReactive implies that the configuration changes are transmitted to the web part only after "Apply" PropertyPane
* button is clicked.
*/
protected get disableReactivePropertyChanges(): boolean;
/**
* This property points to the accessible title of web part made available to screen readers. The base implementation
* returns that default title in the manifest. Web parts that want to provide more descriptive title containing
* contextual information need to override this API.
* virtual
*/
protected get accessibleTitle(): string;
/**
* Title of the WebPart
*
* @readonly
*/
protected get title(): string;
/**
* Icon image URL of the WebPart
*
* @internal
*/
protected get iconImageUrl(): string | undefined;
/**
* Description of the WebPart
*
* @readonly
*/
protected get description(): string;
/**
* List of user audiences that can see the WebPart
*
* @internal
*/
protected get audiences(): string[];
/**
* Whether or not to show the web part in various view contexts (e.g., mobile, email, etc.).
*
* @internal
*/
protected get hideOn(): IHideOn;
/**
* This property is a pointer to the current set of properties which are already or needs to be persisted.
*
* @readonly
*/
private get _persistedProperties();
/**
* Constructor for the `BaseWebPart` class.
*
* @remarks
* It is highly recommended that the web part use the OnInit API to perform any web part specific initialization.
* Most of the web part features like this.context and this.properties are not available to be used before the
* the onInit part of the web part loading lifecycle.
*/
constructor();
/**
* API to get property pane configuration asynchronously.
*
* @internal
*/
_getPropertyPaneData(isDetails?: boolean, context?: any): Promise<IPropertyPaneData>;
/**
* API to enable asynchronous loading of property pane related resources of a web part.
*
* @internal
*/
_loadPropertyPaneResources(): Promise<void>;
/**
* Internal API which is invoked when a property field is changed on the property pane.
* This API is invoked only for the reactive property pane.
*
* @remarks
* If the old value is of type DynamicProperty, but the new value is a static value
* (This could happen when the 'targetProperty' is same for both a DynamicField on the
* Dynamic Data Widget and a TextField which represents afore mentioned Dynamic Field
* when the DD connection is removed by the end user on the property pane UI. In that
* case, property pane sends in the static value which corresponds to the dynamic property(DP)
* present before the DD connection is removed)
* then to respect the web part developer's choice of that target property being a DP,
* we create a new instance of DP and assign the incoming value as the static value
* then update the properties bag with the newly created DP instance.
* else, update the properties bag with the new value for the property.
*
* @param propertyPath - JSON path of the property in the property bag.
* @param newValue - New value of the property.
*
* @returns if property was updated
* @internal
*/
_onPropertyPaneFieldChanged(propertyPath: string, newValue: any, fieldType?: PropertyPaneFieldType): boolean;
/**
* Internal API which is invoked when one of the predefined configuration events
* (defined in `PropertyPaneLifeCycleEvent` enum.) is triggered.
*
* See PropertyPaneLifeCycleEvent for more details on the event definitions.
*
* @param event - Type of PropertyPaneLifeCycleEvent.
*
* @internal
*/
_onPropertyPaneLifeCycleEvent(event: PropertyPaneLifeCycleEvent): void;
/**
* Indicates whether the property pane is reactive or not.
*
* @remarks
* The default behavior is Reactive.
*
* Reactive implies that changes made in the PropertyPane are transmitted to the web part instantly and the user can
* see instant updates. This helps the page creator get instant feedback and decide if they should keep the new
* configuration changes or not.
*
* NonReactive implies that the configuration changes are transmitted to the web part only after "Apply" PropertyPane
* button is clicked.
*
* @internal
*/
_isPropertyPaneReactive(): boolean;
/**
* Internal API to serialize the web part properties.
*
* @internal
*/
_internalSerialize<T>(): T | undefined;
/**
* Internal API to dispose the web part.
*
* See onDispose for more details.
*
* @internal
*/
_internalDispose(): void;
/**
* Internal API triggered upon a resize of the DOM window's viewport
*
* @internal
*/
_internalOnAfterResize(): void;
/**
* Internal API to update the web part data.
*
* !!!WARNING!!! updating the web part data can be risky. If you end up updating the web part properties
* to an invalid format, this could cause the web part to persist invalid data format.
*
* @internal
*/
_internalSetWebPartData(webPartData: IWebPartData, addedFromPersistedData?: boolean): void;
/**
* Internal API to switch the web part's display mode. This API updates the display mode and then re-renders the web
* part in the new mode.
* @internal
*/
_internalSetDisplayMode(newDisplayMode: DisplayMode): void;
/**
* Internal API to set the dirty bit on the web part host if the web part properties have changed.
* @internal
*/
_internalSetDirtyBit(): void;
/**
* This is the initial entry point, which is called by the `ClientSideWebPartManager` immediately
* after the web part is constructed. The initialization contract is internal.
*
* @param webPartContext - The web part context
* @param addedFromPersistedData - Tells the Web Part it will need to reinstate some properties
* from server processed content (see BaseWebPart._reInstateServerProcessedData)
*
* @internal
*/
_internalInitialize(webPartContext: BaseWebPartContext, addedFromPersistedData: boolean, mode: DisplayMode): void;
/**
* Internal base implementation of the web part data deserialization.
*
* @param data - web part data
*
* @internal
*/
_internalDeserialize(data: IWebPartData): void;
/**
* Returns the configuration for this webpart's top actions or undefined if the webpart
* doesn't support top actions.
*/
getTopActionsConfiguration(): ITopActions | undefined;
/**
* Returns the accessibility results for the web part.
*
* @internal
*/
_checkA11y(): Promise<(IA11yCheckResult | undefined)[]>;
/**
* This API is called when user clicks on the violations in the accessibility assistant panel.
* virtual
*
* @remarks
* This API should be overridden to perform any actions when a violation is clicked in the accessibility assistant panel.
* Usually this API is used to redirect the user to the specific item editing configuration pane to fix the violation.
*
* @param result - The result(violation) data that was clicked.
*
* @internal
*/
_preOnClickA11yResult(result: IA11yCheckResult): void;
/**
* This event method is called when the web part is initialized.
* virtual
*
* @remarks
* This API should be overridden to perform long running operations e.g. data fetching from a remote service before
* the initial rendering of the web part. The loading indicator is displayed during the lifetime of this method.
* This API is called only once during the lifecycle of a web part.
*/
protected onInit(): Promise<void>;
/**
* This event method is called when the display mode of a web part is changed.
* virtual
*
* @remarks
* The default implementation of this API calls
* the web part render method to re-render the web part with the new display mode. If a web part developer does not
* want a full re-render to happen on display mode change, they can override this API and perform specific updates
* to the web part DOM to switch its display mode.
*
* If the web part is initialized or re-initialized when switching to a different display mode then this
* lifecycle method is not called. Example: SharePoint Site Page.
*
* @param oldDisplayMode - The old display mode.
*/
protected onDisplayModeChanged(oldDisplayMode: DisplayMode): void;
/**
* This event method is called before the web part is serialized.
* virtual
*
* @remarks
* The default implementation is a no-op. The serialization
* process serializes the web part property bag i.e. this.properties. This API gives the web part a chance to
* update it's property bag before the serialization happens. Some web part's may keep their state other objects
* or even in the DOM. If a web part needs to persist some of that state, it needs to override this API and update
* the web part property bag to the latest state. If a web part updates the property bag with invalid property
* values, those will get persisted. So that should be avoided. The web part property bag should always contain
* valid property values.
*/
protected onBeforeSerialize(): void;
/**
* This API is called after the web part is deserialized to an object, right before the property bag is populated.
* virtual
*
* @remarks
* The default implementation is a no-op. A web part developer can override this API if the deserialized object
* does not fully reflect the initial state of the property bag. This gives the web part developer a chance to
* populate the property bag right after the data is deserialized to an object.
*
* An important scenario to use deserialize is upgrading. An upgraded web part may load the data
* that was serialized by an older version of the web part that supported a different schema of the property bag,
* resulting the deserialized object to be inconsistent with the current schema of the property bag. The developer
* can use `onAfterDeserialize` to check the dataVersion and fix the property bag.
*
* @param deserializedObject - The object deserialized from the stored data. Note that the schema of this object
* is not necessarily consistent with the current property bag, because the serialization could have been done by
* an older version of the web part
* @param dataVersion - The data version of the stored data being deserialized. You can use this value to determine
* if the data was serialized by an older web part. Web parts can define their data version by overriding the
* dataVersion property.
*
* @returns The property bag of the web part
*/
protected onAfterDeserialize(deserializedObject: any, dataVersion: Version): TProperties;
/**
* This API enables asynchronous loading of property pane related resources of the web part.
* @virtual
*
* @remarks
* If not overridden by the web part, base web part resolves the promise immediately.
*
* This method is called before any property pane APIs are called.
* Other property pane APIs cannot be called until this promise is resolved.
* No other property pane related events are raised except 'PropertyPaneConfigurationComplete'.
*
* 'PropertyPaneConfigurationComplete' event can be raised to clean up any pending resources
* including 'loadPropertyPaneResources' promise.
*/
protected loadPropertyPaneResources(): Promise<void>;
/**
* This API is used to ger the configuration to build the property pane for the web part. If the web part wants
* to use the PropertyPane for configuration, this API needs to be overridden and the web part needs to return
* the configuration for the PropertyPane.
*
* This API is not invoked until the 'loadPropertyPaneResources' promise is resolved.
*
* See IPropertyPane and other PropertyPane integration wiki documentation for more details.
* virtual
*/
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration;
/**
* This API is used to get the configuration to build the property pane for details.
* If the web part wants to use the PropertyPane for details configuration,
* this API needs to be overridden and the web part needs to return the configuration for details.
*
* This API is not invoked until the 'loadPropertyPaneResources' promise is resolved.
*
* See IPropertyPane and other PropertyPane integration wiki documentation for more details.
* virtual
*
* @param context - additional context passed to getPropertyPaneDetailsConfiguration
*
* @alpha
*/
protected getDetailsPaneConfiguration(context?: any): IPropertyPaneConfiguration;
/**
* This API should be used to refresh the contents of the PropertyPane.
* virtual
*
* @remarks
* This API is called at the end of the web part lifecycle on a page. It should be used to dispose any local
* resources (i.e. DOM elements) that the web part is holding onto. This API is expected to be called in scenarios
* like page navigation i.e. the host is transitioning from one page to another and disposes the page that is being
* transitioned out.
*/
protected onDispose(): void;
/**
* This API is invoked after updating the new value of the property in the property bag when the PropertyPane
* is being used in Reactive mode.
* virtual
*
* @param propertyPath - JSON path of the property in the property bag.
* In the case of custom field, if no target property is provided then a custom value is assigned,
* which will be in the form of `__CustomField_<key provided when the custom field is created>`.
* @param oldValue - Old value of the property.
* This value could be undefined/empty in the case of custom field.
* @param newValue - New value of the property.
* This value could be undefined/empty in the case of custom field.
*/
protected onPropertyPaneFieldChanged(propertyPath: string, oldValue: any, newValue: any): void;
/**
* This event method is invoked when the configuration starts on the PropertyPane.
* virtual
*
* @remarks
* This event method is invoked in the following cases:
*
* - When the PropertyPane is opened.
*
* - When the user switches web parts then the new web part gets this event.
*/
protected onPropertyPaneConfigurationStart(): void;
/**
* This API is invoked when the configuration is completed on the PropertyPane.
* virtual
*
* @remarks
* This event method is invoked in the following cases:
*
* - When the CONFIGURATION_COMPLETE_TIMEOUT((currently the value is 5 secs) elapses after the last change.
*
* - When user clicks the "X" (close) button before the CONFIGURATION_COMPLETE_TIMEOUT elapses.
*
* - When user clicks the 'Apply' button before the CONFIGURATION_COMPLETE_TIMEOUT elapses.
*
* - When the user switches web parts then the current web part gets this event.
*/
protected onPropertyPaneConfigurationComplete(): void;
/**
* This API is invoked after the changes made on the PropertyPane are applied when the PropertyPane is used in
* Non-Reactive mode. This API is not invoked when the PropertyPane is used in Reactive mode.
* virtual
*/
protected onAfterPropertyPaneChangesApplied(): void;
/**
* This API is invoked when the PropertyPane is rendered.
* virtual
* @privateRemarks
* From framework standpoint, we do not want to allow this event handler to be passed in, and trigger it.
* This api should be deprecated and then removed as part of refactoring.
*/
protected onPropertyPaneRendered(): void;
/**
* This API is invoked after properties are updated by sources other than the property pane or the web part host
* (except for isolated web parts).
* virtual
*
* @remarks For isolated web parts, this lifecycle is invoked instead of `onPropertyPaneFieldChanged`.
*
* @alpha
* @param prevProperties - The web part properties before the update.
*/
protected onAfterPropertiesUpdatedExternally(prevProperties: TProperties): void;
/**
* @internal
*/
protected _internalGetData(): Promise<void>;
/**
* Internal API to trigger a refresh to the WebPart's visual rendition.
*
* @internal
*/
protected abstract _refresh(): void;
/**
* Internal API triggered by a dynamic property's callback.
*
* @internal
*/
protected abstract _dynamicPropertyRefresh(): void;
/**
* Gets default accessible title in the format `"<Web part name> web part"`, such as "Image web part".
* Note: pulled into own method because TypeScript doesn't allow derived classes to call super protected properties.
*
* @internal
*/
protected _getDefaultAccessibleTitle(): string;
/**
* Allows for asynchronous updating of properties and setting of dirty bit
*
* @internal
*/
protected _onPropertyPaneChangesApplied(): Promise<TProperties>;
/**
* This method consumes the properties object from the serialized data and re-applies
* the field values from the serverProcessedContent. This process will result in the
* properties bag as it was when the serialization happened.
*/
private _reInstateServerProcessedData;
/**
* Iterates and validates each metadata property before calling metadata processor.
*
* @param metadataProcessor - Callback handler to to process validated metadata.
* @param properties - Optional properties to process instead of default this.properties
*/
private _forEachPropertyWithMetaData;
/**
* Validates path and iterate over one or multiple wildcard paths
*
* @param propPath - Original property path provided in metadata
* @param properties - Properties to look for the given path.
* @param propertyPathHandler - Handler to process validated and fixed path
*/
private _validateAndIteratePath;
/**
* Operations to be done after the property bag is updated.
*
* @param shouldRefresh - Indicating whether web part needs to be shouldRefresh or not.
*/
private _afterPropertyUpdated;
/**
* Fixes up a property pane configuration to use dynamic data. This happens in-place.
* The fix-up consists on adding the necessary hooks to the web part that are required,
* like the dynamic data provider, the render function and the object it refers to.
*
* @param configuration - Property pane configuration to fix up.
*/
private _fixUpDynamicDataConfiguration;
/**
* Adds a new group with an audiences picker to the first page of the property pane configuration.
* This happens in-place.
*
* @param configuration - Property pane configuration to add the audience targeting picker group to
*/
private _addAudienceTargetingConfiguration;
/**
* Adds "Visibility" group (including "Show in mobile and email" toggle) to property pane.
*
* @param configuration - Property pane configuration to add the group / toggle to.
*/
private _addVisibilityConfiguration;
/**
* The serializer for the exposed part of the web part data
*
* @privateRemarks
* This serialize API is a public candidate for post-GA (after removing the input parameter)
*/
private _serialize;
/**
* This function serializes the Dynamic Data objects from the web part properties into the serialized web part data.
*
* If there is a property "myDynamicProperty" in the property bag that points to `"PageContext:user"`,
* the serialized data is updated as the following:
* ```
* serializedData = {
* dynamicDataPaths: {
* "myDynamicProperty": "PageContext:user"
* }
* }
* ```
*/
private _serializeDynamicData;
/**
* This function deserializes the Dynamic Data objects from serialized web part data into web part properties.
*
* If the serializedData has a "dynamicDataPaths" or "dynamicDataValues" property,
* it creates DynamicData objects for it.
*
* Example:
* ```
* serializedData = {
* dynamicDataPaths: {
* "myDynamicProperty": "PageContext:user"
* }
* }
* ```
*
* This will create a property "myDynamicProperty" in the web parts properties, with a new DynamicData object that
* points at "PageContext:user"
*/
private _deserializeDynamicData;
/**
* For each property in the serialized properties of the web part, we check if the property is declared
* as dynamicProperty in the properties metadata of the web part.
* - If yes, then we check
* - if the value of it is an instance of DynamicProperty
* - If No, then we create and assign a DynamicProperty with the type given specified in the
* 'dynamicPropertyType' property
* - else, it's a no-op.
* - If no, then it's a no-op
*
* While creating the DynamicProperty, we use the pre-configured value associated with that property from
* the web part's property bag as the default value.
* @param deSerializedProperties - deserialized properties of the web part.
*/
private _initializeDynamicPropertiesIfRequired;
/**
* Disposing the dynamic properties, for those which are declared as dynamic properties.
*
* @privateRemarks
* We are disposing only the declared dynamic properties because we created them on behalf of
* the web part and hence it is our responsiblity to clean them.
*/
private _disposeDynamicPropertiesIfRequired;
/**
* Update the web part property if it is in the property bag.
*
* @param propertyPath - property path as expected by lodash update (https://lodash.com/docs#update).
* (e.g. 'o.a.b', 'o[0].a.b[1].c'). These paths are similar to those in the JSONPath spec described by
* Stefan Goessner at (http://goessner.net/articles/JsonPath/). Currently we plan to use lodash.get, has
* and update APIs which support paths. Some day we may migrate to using actual JSONPath.js. But that
* should not be an issue because the path formatting conventions are same in lodash and JSONPath.
* @param newValue - new value entered by the user for the associated target property
*/
private _updateProperty;
/**
* Using 'cloneDeepWith' because the web part properties bag will now serialize the
* dynamic properties as well. One of the building blocks of these properties are dataProviders,
* which when serialized will result in 'Maximum call stack size exceeded' based on what kind of
* sources they hold references to.
* Hence using cloneDeepWith, which lets us handle the special cases and in this case if the property
* in question is a 'DynamicProperty' and we call '_cloneDeep' api on the dynamicProperty.
*/
private _cloneProperties;
}
//# sourceMappingURL=BaseWebPart.d.ts.map