@microsoft/sp-webpart-base
Version:
SharePoint Framework support for building web parts
1,065 lines (1,064 loc) • 61.6 kB
JavaScript
"use strict";
// Copyright (c) Microsoft. All rights reserved.
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
var sp_component_base_1 = require("@microsoft/sp-component-base");
var sp_core_library_1 = require("@microsoft/sp-core-library");
var sp_diagnostics_1 = require("@microsoft/sp-diagnostics");
var lodash = tslib_1.__importStar(require("@microsoft/sp-lodash-subset"));
var sp_edit_customer_promise_1 = require("@ms/sp-edit-customer-promise");
var ExecuteAndReThrow_1 = require("../utils/ExecuteAndReThrow");
var Object_1 = require("../utils/Object");
var SPPropertyPane_1 = require("../SPPropertyPane");
var SPWebPartError_1 = require("./error/SPWebPartError");
var AudienceFormServiceKey_1 = require("../services/AudienceForm/AudienceFormServiceKey");
var Flights_1 = require("../common/Flights");
var Strings_resx_1 = tslib_1.__importDefault(require("./loc/Strings.resx"));
var KillSwitches_1 = require("../common/KillSwitches");
var AUDIENCES_PROPERTY = '4eae8b0b-40f6-4ea9-aabb-b6834ae24532';
var ACE_WEBPART_MANIFEST_ID = 'f3bc67ee-015d-4678-a95f-c7e90ea62c1e';
/**
* 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
*/
var BaseWebPart = /** @class */ (function (_super) {
tslib_1.__extends(BaseWebPart, _super);
/**
* 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.
*/
function BaseWebPart() {
var _this = _super.call(this) || this;
_this._initialized = false;
_this._baseLogSource = sp_diagnostics_1._LogSource.create('BaseWebPart');
/**
* It makes sure we only log edit events once during one editing session.
*/
_this._hasEditLogged = false;
_this._emptyResolvedPromise = Promise.resolve();
_this._disposeDynamicPropertiesIfRequired = _this._disposeDynamicPropertiesIfRequired.bind(_this);
_this._onPropertyPaneFieldChanged = _this._onPropertyPaneFieldChanged.bind(_this);
// Disallow instantiation of the base class by itself
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if (_this.constructor.name === 'BaseWebPart') {
throw SPWebPartError_1.SPWebPartError.create(SPWebPartError_1.SPWebPartErrorCode.BaseConstructError);
}
return _this;
}
Object.defineProperty(BaseWebPart.prototype, "previewImageUrl", {
/**
* 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: function () {
return undefined;
},
enumerable: false,
configurable: true
});
Object.defineProperty(BaseWebPart.prototype, "displayMode", {
/**
* This property is the current display mode of the web part.
*
* @readonly
*/
get: function () {
return this._displayMode;
},
enumerable: false,
configurable: true
});
Object.defineProperty(BaseWebPart.prototype, "modifiedByGraph", {
/**
* This property holds metadata about Graph API calls that touch the web part.
* @internal
*/
get: function () {
return this._modifiedByGraph;
},
enumerable: false,
configurable: true
});
Object.defineProperty(BaseWebPart.prototype, "dataVersion", {
/**
* 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
*/
get: function () {
return sp_core_library_1.Version.parse('1.0');
},
enumerable: false,
configurable: true
});
Object.defineProperty(BaseWebPart.prototype, "properties", {
/**
* This property is the pointer to the custom property bag of the web part.
*
* @readonly
*/
get: function () {
if (this._initialized) {
return this._properties;
}
else {
throw SPWebPartError_1.SPWebPartError.create(SPWebPartError_1.SPWebPartErrorCode.NotInitializedError);
}
},
enumerable: false,
configurable: true
});
Object.defineProperty(BaseWebPart.prototype, "propertiesMetadata", {
/**
* 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
*/
get: function () {
return undefined;
},
enumerable: false,
configurable: true
});
Object.defineProperty(BaseWebPart.prototype, "disableReactivePropertyChanges", {
/**
* 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.
*/
get: function () {
return false;
},
enumerable: false,
configurable: true
});
Object.defineProperty(BaseWebPart.prototype, "accessibleTitle", {
/**
* 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
*/
get: function () {
return this._getDefaultAccessibleTitle();
},
enumerable: false,
configurable: true
});
Object.defineProperty(BaseWebPart.prototype, "title", {
/**
* Title of the WebPart
*
* @readonly
*/
get: function () {
return this._title;
},
enumerable: false,
configurable: true
});
Object.defineProperty(BaseWebPart.prototype, "iconImageUrl", {
/**
* Icon image URL of the WebPart
*
* @internal
*/
get: function () {
return this._iconImageUrl;
},
enumerable: false,
configurable: true
});
Object.defineProperty(BaseWebPart.prototype, "description", {
/**
* Description of the WebPart
*
* @readonly
*/
get: function () {
return this._description;
},
enumerable: false,
configurable: true
});
Object.defineProperty(BaseWebPart.prototype, "audiences", {
/**
* List of user audiences that can see the WebPart
*
* @internal
*/
get: function () {
return this._audiences;
},
enumerable: false,
configurable: true
});
Object.defineProperty(BaseWebPart.prototype, "_persistedProperties", {
/**
* This property is a pointer to the current set of properties which are already or needs to be persisted.
*
* @readonly
*/
get: function () {
return this.disableReactivePropertyChanges && this._backupProperties
? this._backupProperties
: this.properties;
},
enumerable: false,
configurable: true
});
/**
* API to get property pane configuration asynchronously.
*
* @internal
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
BaseWebPart.prototype._getPropertyPaneData = function (isDetails, context) {
var _this = this;
return this._loadPropertyPaneResources().then(function () {
var configuration = isDetails
? _this.getDetailsPaneConfiguration(context)
: _this.getPropertyPaneConfiguration();
var shouldAddAudiencePicker = _this.context.manifest.id === ACE_WEBPART_MANIFEST_ID ||
((0, Flights_1.isWebPartAudienceTargetingEnabled)() && !configuration.disableAudiencePicker);
if (shouldAddAudiencePicker) {
_this._addAudienceTargetingConfiguration(configuration);
}
_this._fixUpDynamicDataConfiguration(configuration);
return {
webPartId: _this.context.instanceId,
title: _this.title,
isReactive: !_this.disableReactivePropertyChanges,
configuration: configuration,
properties: _this._cloneProperties(_this.properties),
// Below methods are being overridden in the PropertyPane controller and hence
// we do not need to bind it here. We still do not want to make it optional because
// that could lead to unwanted bugs.
onPropertyPaneFieldChanged: undefined,
onConfigurationEvent: undefined,
onRendered: _this.onPropertyPaneRendered,
dynamicConfiguration: {
defaultCallback: (function () {
_this._dynamicPropertyRefresh();
}).bind(_this),
dynamicDataProvider: _this.context.dynamicDataProvider
}
};
});
};
/**
* API to enable asynchronous loading of property pane related resources of a web part.
*
* @internal
*/
BaseWebPart.prototype._loadPropertyPaneResources = function () {
// Below promise will be undefined only when the web part is opening its property pane for the first time.
if (!this._loadPropertyPaneResourcesPromise) {
this._loadPropertyPaneResourcesPromise = this.loadPropertyPaneResources();
}
return this._loadPropertyPaneResourcesPromise;
};
/**
* 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
*/
BaseWebPart.prototype._onPropertyPaneFieldChanged = function (propertyPath,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
newValue, fieldType) {
var _this = this;
var _a, _b;
var monitor;
var componentType = this.context.manifest.id === ACE_WEBPART_MANIFEST_ID ? 'ACE' : 'WebPart';
try {
var oldValue = lodash.get(this._properties, propertyPath);
// Property updates and web part re-rendering happens only in the following cases:
// case 1: when there is a change between oldValue and newValue.
// case 2: when the event is coming from a button click(this is because, in this case
// regardless of what newValue is we need to re-render the web part.)
// case 3: when the event is coming from a custom field(this is because, framework wouldn't
// know what exactly changed inside the custom field and we come to this spot in case
// of custom field only when the web part specifically requests it.)
if (!lodash.isEqual(oldValue, newValue) ||
fieldType === SPPropertyPane_1.PropertyPaneFieldType.Button ||
fieldType === SPPropertyPane_1.PropertyPaneFieldType.Custom) {
if (!(0, KillSwitches_1.isPropertyPaneUpdateASHAVetoKSActivated)() && this.manifest.isInternal) {
// create a monitor only when an update is required.
monitor = new sp_diagnostics_1._QosMonitorShareFailureWithCP("PropertyPane.".concat(componentType, ".").concat(this.manifest.alias, ".Update"), false, [
{
errorModuleName: "".concat(componentType, " PropertyPaneUpdate Failure"),
veto: 'WebPart Property Pane Failure',
logFailure: this.context.serviceScope.consume(sp_edit_customer_promise_1._EditModeCustomerPromiseHandler.serviceKey)
.logFailure
}
]);
}
// Create backupProperties if it is non-reactive property pane.
if (this.disableReactivePropertyChanges && !this._backupProperties) {
this._backupProperties = this._cloneProperties(this.properties);
}
if (oldValue instanceof sp_component_base_1.DynamicProperty && !(newValue instanceof sp_component_base_1.DynamicProperty)) {
var newDynamicProperty = new sp_component_base_1.DynamicProperty(this.context.dynamicDataProvider, (function () {
_this._dynamicPropertyRefresh();
}).bind(this));
newDynamicProperty.setValue(newValue);
newValue = newDynamicProperty;
}
if (propertyPath === AUDIENCES_PROPERTY) {
this._audiences = newValue;
if ((0, Flights_1.isWebPartAudienceTargetingEnabled)()) {
sp_diagnostics_1._EngagementLogger.logEvent("".concat(componentType, ".PropertyPane"), 'AudienceUpdated');
(_b = (_a = this.context.host).onAudiencesChanged) === null || _b === void 0 ? void 0 : _b.call(_a, this.instanceId, newValue);
}
}
else {
this._updateProperty(propertyPath, newValue);
}
this.onPropertyPaneFieldChanged(propertyPath, oldValue, newValue);
this._afterPropertyUpdated(!this.disableReactivePropertyChanges);
if (!(0, KillSwitches_1.isPropertyPaneUpdateASHAVetoKSActivated)() && monitor) {
monitor.writeSuccess();
}
return true;
}
}
catch (error) {
if (!(0, KillSwitches_1.isPropertyPaneUpdateASHAVetoKSActivated)() && monitor) {
monitor.writeUnexpectedFailure(error.message);
}
throw error;
}
return false;
};
/**
* 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
*/
BaseWebPart.prototype._onPropertyPaneLifeCycleEvent = function (event) {
var _this = this;
// Invoke the callback so that the host can handle the property pane life cycle event.
if (this.context.host.propertyPaneLifeCycleEventCallback) {
this.context.host.propertyPaneLifeCycleEventCallback(event, {
webPartData: this._internalSerialize(),
isPropertyPaneReactive: this._isPropertyPaneReactive()
});
}
switch (event) {
case 'ConfigurationStart':
this._loadPropertyPaneResources()
.then(function () { return _this.onPropertyPaneConfigurationStart(); })
.catch(function (e) {
return sp_diagnostics_1._TraceLogger.logVerboseData({ source: _this._baseLogSource, message: e.message });
});
break;
case 'ConfigurationComplete':
// Make sure we discard any backed up properties in the non-reactive mode.
if (this.disableReactivePropertyChanges && this._backupProperties) {
// resetting the properties bag.
this._properties = this._backupProperties;
this._backupProperties = undefined;
}
this.onPropertyPaneConfigurationComplete();
break;
case 'ApplyClicked':
// Updating the _backUpProperties with new set of properties.
this._backupProperties = this.properties;
if (!(0, KillSwitches_1.isHandleAsyncPropertyUpdateKSActivated)()) {
this._onPropertyPaneChangesApplied()
.then(function () {
_this.onAfterPropertyPaneChangesApplied();
_this._afterPropertyUpdated(true);
})
.catch(function (err) {
sp_diagnostics_1._TraceLogger.logVerboseData({ source: _this._baseLogSource, message: err.message });
throw err;
});
}
else {
this.onAfterPropertyPaneChangesApplied();
this._afterPropertyUpdated(true);
}
break;
}
};
/**
* 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
*/
BaseWebPart.prototype._isPropertyPaneReactive = function () {
return !this.disableReactivePropertyChanges;
};
/**
* Internal API to serialize the web part properties.
*
* @internal
*/
BaseWebPart.prototype._internalSerialize = function () {
var _this = this;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
var data;
(0, ExecuteAndReThrow_1.executeAndReThrow)(function () {
_this.onBeforeSerialize();
var propertiesJson = JSON.stringify(_this.properties);
// Only log edit, if properties have changed and it haven't been logged.
if (!_this._hasEditLogged && _this._initPropertiesSnapshot !== propertiesJson) {
var isInternal = !!_this.context.manifest.isInternal;
var logEntry = new sp_diagnostics_1._LogEntry(_this._baseLogSource.id, 'WebPartEdited', sp_diagnostics_1._LogType.Event, {
alias: _this.context.manifest.alias,
isInternal: isInternal.toString()
});
sp_diagnostics_1._EngagementLogger.logEventWithLogEntry(logEntry);
_this._hasEditLogged = true;
}
var serializedData = _this._serialize();
sp_core_library_1.Validate.isNotNullOrUndefined(serializedData.dataVersion, 'serialized data version');
// Avoid doing extra work if we know there is no dynamic data
if (propertiesJson && propertiesJson.indexOf(sp_component_base_1.DynamicProperty._TYPE_NAME) !== -1) {
_this._serializeDynamicData(serializedData);
}
data = {
// The serialized data wrapped by the framework
id: _this.context.manifest.id,
instanceId: _this.context.instanceId,
title: _this.title,
description: _this.description,
audiences: _this.audiences,
// The serialized data provided by the web part
serverProcessedContent: serializedData.serverProcessedContent,
dynamicDataPaths: serializedData.dynamicDataPaths,
dynamicDataValues: serializedData.dynamicDataValues,
dataVersion: serializedData.dataVersion.toString(),
properties: serializedData.properties
};
if (!(0, KillSwitches_1.isExemptWebPartWithDynamicDataFromLazyLoadKSActivated)()) {
data.containsDynamicDataSource = _this._containsDynamicDataSource;
}
}, SPWebPartError_1.SPWebPartError.create(SPWebPartError_1.SPWebPartErrorCode.SerializationFailed, this.context.webPartTag), this._baseLogSource);
return data;
};
/**
* Internal API to dispose the web part.
*
* See onDispose for more details.
*
* @internal
*/
BaseWebPart.prototype._internalDispose = function () {
this._disposeDynamicPropertiesIfRequired();
_super.prototype.dispose.call(this);
};
/**
* Internal API triggered upon a resize of the DOM window's viewport
*
* @internal
*/
BaseWebPart.prototype._internalOnAfterResize = function () {
/* EMPTY BLOCK */
};
/**
* 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
*/
BaseWebPart.prototype._internalSetWebPartData = function (webPartData) {
var oldProperties = this.properties;
if (!(0, KillSwitches_1.isClearPreviousStateWhenSetWebPartDataKSActive)()) {
// Clear _previousState to avoid the new webPartData set by host being tracked as a dirty change.
this._previousState = undefined;
}
this._internalDeserialize(webPartData);
this.onAfterPropertiesUpdatedExternally(oldProperties);
};
/**
* 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
*/
BaseWebPart.prototype._internalSetDisplayMode = function (newDisplayMode) {
if (this._displayMode !== newDisplayMode) {
var oldDisplayMode = this._displayMode;
this._displayMode = newDisplayMode;
this.onDisplayModeChanged(oldDisplayMode);
}
};
/**
* Internal API to set the dirty bit on the web part host if the web part properties have changed.
* @internal
*/
BaseWebPart.prototype._internalSetDirtyBit = function () {
// This method could get invoked in classic pages in read mode also. Just ignore the call.
if (this.displayMode === sp_core_library_1.DisplayMode.Read) {
return;
}
var serializedState = this._internalSerialize();
var newState = JSON.stringify(serializedState);
// Don't set dirty bit the first time because the Canvas
// would have done it when the webpart got added.
if (!this._previousState) {
this._previousState = newState;
}
else if (this._previousState !== newState && this.context.host.setDirty) {
this.context.host.setDirty(this.context.instanceId, serializedState);
this._previousState = newState;
}
};
/**
* 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
*/
BaseWebPart.prototype._internalInitialize = function (webPartContext, addedFromPersistedData, mode) {
sp_core_library_1.Validate.isNotNullOrUndefined(webPartContext, 'webPartContext');
(0, Object_1.deepFreeze)(webPartContext.manifest);
_super.prototype._initializeContext.call(this, webPartContext);
// Bind the callbacks
this.onDispose = this.onDispose.bind(this);
this.onPropertyPaneRendered = this.onPropertyPaneRendered.bind(this);
this._initialized = true;
// Set the display mode of the web part
this._displayMode = mode;
// This is important for reinstating properties the way they were
// before serialization. It must be set before any deserialization occurs.
this._renderedFromPersistedData = addedFromPersistedData;
if (!this._renderedFromPersistedData) {
var isInternal = this.context.manifest.isInternal || false;
var logEntry = new sp_diagnostics_1._LogEntry(this._baseLogSource.id, 'WebPartAdded', sp_diagnostics_1._LogType.Event, {
alias: this.context.manifest.alias,
isInternal: isInternal.toString()
});
sp_diagnostics_1._EngagementLogger.logEventWithLogEntry(logEntry);
}
};
/**
* Internal base implementation of the web part data deserialization.
*
* @param data - web part data
*
* @internal
*/
BaseWebPart.prototype._internalDeserialize = function (data) {
if (!(0, KillSwitches_1.isAvoidMutatingWebPartDataBeforeDeserializationKSActive)()) {
data = lodash.cloneDeep(data);
}
if (data) {
if (!this._previousState) {
this._previousState = JSON.stringify(data);
}
if (data.title) {
this._title = data.title;
}
if (data.modifiedByGraph) {
this._modifiedByGraph = data.modifiedByGraph;
}
if (data.description) {
this._description = data.description;
}
if (data.iconImageUrl) {
this._iconImageUrl = data.iconImageUrl;
}
this._audiences = data.audiences || [];
// For backward-compatibility, convert non-string versions to '1.0'
// Because we used to serialize the version object in the early versions
if (typeof data.dataVersion !== 'string') {
data.dataVersion = this.dataVersion.toString();
}
// Note: it is okay not to create clones here
var deserializedData = {
properties: data.properties,
serverProcessedContent: data.serverProcessedContent,
dynamicDataPaths: data.dynamicDataPaths,
dynamicDataValues: data.dynamicDataValues,
dataVersion: sp_core_library_1.Version.tryParse(data.dataVersion)
};
var shouldProcessDynamicDataAfterServerScrub = (0, Flights_1.isServerPropertiesSanitizedEnabled)();
var deserializedPropsObject = void 0;
if (shouldProcessDynamicDataAfterServerScrub) {
deserializedPropsObject = this._reInstateServerProcessedData(deserializedData.properties, deserializedData.serverProcessedContent);
}
if (!(0, KillSwitches_1.isExemptWebPartWithDynamicDataFromLazyLoadKSActivated)()) {
deserializedData.containsDynamicDataSource = data.containsDynamicDataSource;
this._containsDynamicDataSource = !!deserializedData.containsDynamicDataSource;
}
this._deserializeDynamicData(deserializedData);
// Note: Initialization of DynamicProperties should always happen after the
// properties are deserialized. This ensures that all the properties have proper
// values constructed with types, if required.
this._initializeDynamicPropertiesIfRequired(deserializedData.properties);
if (!shouldProcessDynamicDataAfterServerScrub) {
deserializedPropsObject = this._reInstateServerProcessedData(deserializedData.properties, deserializedData.serverProcessedContent);
}
// Give the web part an opportunity to deserialize the properties. If the web part
// returns a valid property bag, use it as is else perform default deserialization.
var fixedProps = this.onAfterDeserialize(deserializedPropsObject, deserializedData.dataVersion);
// Cache the properties data just deserialized.
this._initPropertiesSnapshot = JSON.stringify(deserializedPropsObject);
if (!fixedProps) {
throw SPWebPartError_1.SPWebPartError.create(SPWebPartError_1.SPWebPartErrorCode.OnAfterDeserializeReturnedNull, this.context.webPartTag);
}
this._properties = fixedProps;
}
};
/**
* Returns the configuration for this webpart's top actions or undefined if the webpart
* doesn't support top actions.
*/
BaseWebPart.prototype.getTopActionsConfiguration = function () {
return undefined;
};
/**
* 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.
*/
BaseWebPart.prototype.onInit = function () {
// Return a resolved promise by default
return Promise.resolve(undefined);
};
/**
* 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.
*/
BaseWebPart.prototype.onDisplayModeChanged = function (oldDisplayMode) {
/* EMPTY BLOCK */
};
/**
* 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.
*/
BaseWebPart.prototype.onBeforeSerialize = function () {
/* EMPTY BLOCK */
};
/**
* 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
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
BaseWebPart.prototype.onAfterDeserialize = function (deserializedObject, dataVersion) {
return deserializedObject;
};
/**
* 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.
*/
BaseWebPart.prototype.loadPropertyPaneResources = function () {
return this._emptyResolvedPromise;
};
/**
* 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
*/
BaseWebPart.prototype.getPropertyPaneConfiguration = function () {
return {
pages: []
};
};
/**
* 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
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
BaseWebPart.prototype.getDetailsPaneConfiguration = function (context) {
return {
pages: []
};
};
/**
* 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.
*/
BaseWebPart.prototype.onDispose = function () {
/* EMPTY BLOCK */
};
/**
* 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.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
BaseWebPart.prototype.onPropertyPaneFieldChanged = function (propertyPath, oldValue, newValue) {
/* EMPTY BLOCK */
};
/**
* 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.
*/
BaseWebPart.prototype.onPropertyPaneConfigurationStart = function () {
/* EMPTY BLOCK */
};
/**
* 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.
*/
BaseWebPart.prototype.onPropertyPaneConfigurationComplete = function () {
/* EMPTY BLOCK */
};
/**
* 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
*/
BaseWebPart.prototype.onAfterPropertyPaneChangesApplied = function () {
/* EMPTY BLOCK */
};
/**
* 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.
*/
BaseWebPart.prototype.onPropertyPaneRendered = function () {
/* EMPTY BLOCK */
};
/**
* 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.
*/
BaseWebPart.prototype.onAfterPropertiesUpdatedExternally = function (prevProperties) {
return this._refresh();
};
/**
* @internal
*/
BaseWebPart.prototype._internalGetData = function () {
return this._emptyResolvedPromise;
};
/**
* 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
*/
BaseWebPart.prototype._getDefaultAccessibleTitle = function () {
return sp_core_library_1.Text.format(Strings_resx_1.default.GenericAccessibleLabelTemplate, this.title);
};
/**
* Allows for asynchronous updating of properties and setting of dirty bit
*
* @internal
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
BaseWebPart.prototype._onPropertyPaneChangesApplied = function () {
return Promise.resolve(this._properties);
};
/**
* 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.
*/
BaseWebPart.prototype._reInstateServerProcessedData = function (deserializedProperties, serverProcessedContent) {
var metadata = this.propertiesMetadata;
if (!deserializedProperties) {
return {};
}
var fixedProperties = deserializedProperties;
var shouldSkip = !(0, Flights_1.isServerPropertiesSanitizedEnabled)()
? (!sp_core_library_1._SPFlight.isEnabled(60834 /* WEXWebPartFlicker */) || !serverProcessedContent) &&
!this._renderedFromPersistedData
: !this._renderedFromPersistedData;
if (shouldSkip) {
return fixedProperties;
}
sp_component_base_1._PropertyMetadataProcessor.reInstateServerProcessedData(metadata, fixedProperties, serverProcessedContent, this.context.manifest);
return fixedProperties;
};
/**
* 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
*/
BaseWebPart.prototype._forEachPropertyWithMetaData = function (metadataProcessor, properties) {
if (this.propertiesMetadata) {
var _loop_1 = function (propPath, metadata) {
this_1._validateAndIteratePath(propPath, properties || this_1.properties, function (fixedPropPath, index) {
metadataProcessor(fixedPropPath, metadata, index);
});
};
var this_1 = this;
for (var _i = 0, _a = Object.entries(this.propertiesMetadata); _i < _a.length; _i++) {
var _b = _a[_i], propPath = _b[0], metadata = _b[1];
_loop_1(propPath, metadata);
}
}
};
/**
* 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
*/
BaseWebPart.prototype._validateAndIteratePath = function (propPath, properties, propertyPathHandler) {
var wildcardCount = (propPath.match(/\*/g) || []).length;
if (wildcardCount > 1) {
// Skip if there are more than one asterisks in the path (not supported)
// eslint-disable-next-line no-console
console.warn("Invalid property path: Multiple wildcards are not supported in property paths.\nEntry with path '".concat(propPath, "' got ignored in the properties metadata."));
return;
}
else if (wildcardCount === 1) {
var index = propPath.indexOf('[*]');
if (index < -1) {
// eslint-disable-next-line no-console
console.warn("Invalid property path: Wildcards are only supported inside brackets to select array\nindices as in 'foo[*].bar'. Entry with path '".concat(propPath, "' got ignored in the properties metadata."));
// Skip if the wildcard is not used for an array (not supported)
return;
}
else {
// Handle the array wildcard by generating each individual property path
var arrayPath = propPath.substr(0, index);
var arrayInstance = lodash.get(properties, arrayPath);
if (!arrayInstance) {
// eslint-disable-next-line no-console
console.warn("Invalid property path: Could not find an array named '".concat(arrayPath, "' in the properties.\nEntry with path '").concat(propPath, "' got ignored in the properties metadata."));
return;
}
else {
for (var i = 0; i < arrayInstance.length; i++) {
var fixedPropPath = propPath.replace('*', "".concat(i));
propertyPathHandler(fixedPropPath, i);
}
}
}
}
else {
propertyPathHandler(propPath);
}
};
/**
* Operations to be done after the property bag is updated.
*
* @param shouldRefresh - Indicating whether web part needs to be shouldRefresh or not.
*/
BaseWebPart.prototype._afterPropertyUpdated = function (shouldRefresh) {
this._internalSetDirtyBit();
if (shouldRefresh) {
this._refresh();
}
};
/**
* 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.
*/
BaseWebPart.prototype._fixUpDynamicDataConfiguration = function (configuration) {
var _this = this;
configuration.pages.forEach(function (page) {
page.groups.forEach(function (group) {
// Checking if the group is PropertyPaneConditionalGroup, if yes then extracting
// the required group from it.
if ('primaryGroup' in group) {
var _a = group, primaryGroup = _a.primaryGroup, secondaryGroup = _a.secondaryGroup, showSecondaryGroup = _a.showSecondaryGroup;
group = showSecondaryGroup ? secondaryGroup : primaryGroup;
}
group = group;
group.groupFields.forEach(function (field) {
if (field.type === SPPropertyPane_1.PropertyPaneFieldType.DynamicTextField) {
var props = field.properties;
props.dynamicDataProvider = _this.context.dynamicDataProvider;
props.changeCallback = (function () {
_this._dynamicPropertyRefresh();
}).bind(_this);
}
});
});
});
};
/**
* 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
*/
BaseWebPart.prototype._addAudienceTargetingConfiguration = function (configuration) {
if (!configuration.pages || configuration.pages.length === 0) {
configuration.pages = [
{
groups: []
}
];
}
var audienceFormService = this.context.serviceScope.consume(AudienceFormServiceKey_1.audienceFormServiceKey);
if (audienceFormService && audienceFormService.renderAudienceFormPropertyPaneField) {
var isAceWebPart = this.context.manifest.id === ACE_WEBPART_MANIFEST_ID;
configuration.pages[0].groups.push({
groupName: Strings_resx_1.default.AudiencePickerLabel,
groupFields: [
audienceFormService.renderAudienceFormPropertyPaneField({
pageContext: this.context.pageContext.legacyPageContext,
audiences: this.audiences,