UNPKG

@microsoft/sp-webpart-base

Version:

SharePoint Framework support for building web parts

1,065 lines (1,064 loc) 61.6 kB
"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,