@microsoft/sp-dialog
Version:
SharePoint Framework support for displaying dialog boxes
344 lines • 14.4 kB
JavaScript
import { _LogSource, _TraceLogger } from '@microsoft/sp-diagnostics';
import DialogManager from './DialogManager';
/**
* The states for our internal state machine that manages dialogs
*
* @internal
*/
export var _DialogState;
(function (_DialogState) {
/**
* State: New
* Description: The dialog state after construction
* Next Possible States: Pending
*/
_DialogState[_DialogState["New"] = 0] = "New";
/**
* State: Pending
* Description: The dialog manager has acknowledged the request to show
* Next Possible States: Rejected, Aborted, Approved
*/
_DialogState[_DialogState["Pending"] = 1] = "Pending";
/**
* State: Rejected
* Description: The dialog manager has rejected the request to show
* Next Possible States: Pending (by sending a new request)
*/
_DialogState[_DialogState["Rejected"] = 2] = "Rejected";
/**
* State: Aborted
* Description: The dialog has aborted its request to show
* Next Possible States: Pending (by sending a new request)
*/
_DialogState[_DialogState["Aborted"] = 3] = "Aborted";
/**
* State: Approved
* Description: The dialog manager has approved the request to show
* Next Possible States: Open
*/
_DialogState[_DialogState["Approved"] = 4] = "Approved";
/**
* State: Open
* Description: The dialog is visually open
* Next Possible States: Closed, Hidden
*/
_DialogState[_DialogState["Open"] = 5] = "Open";
/**
* State: Closed
* Description: The dialog is closed (can only happen after Open)
* Next Possible States: Pending (by sending a new request)
*/
_DialogState[_DialogState["Closed"] = 6] = "Closed";
/**
* State: Hidden
* Description: The dialog is hidden behind a secondary dialog
* Next Possible States: Open
*/
_DialogState[_DialogState["Hidden"] = 7] = "Hidden";
})(_DialogState || (_DialogState = {}));
/**
* The base class for implementing dialogs in SharePoint Framework. This provides a supported way for showing
* dialogs to the user inside SharePoint Framework components.
*
* @remarks
* Extend this class to create dialogs for SharePoint Framework. By following the guidelines in implementation,
* the framework can ensure that the dialogs are shown in a user-friendly manner. While the content of the dialog is
* controlled by this class by implementing the render method, the framework can decide when to show the dialog and
* how to render the overlay and modal. The application on the page can also have control on allowing dialogs to show.
* Refer to the documentation of the individual methods and properties to learn more about how to extend and use this
* class.
*
* @public
*/
var BaseDialog = /** @class */ (function () {
/**
* Constructor for the `BaseDialog` class.
* @remarks
*
* It is important to keep the constructor lightweight. Use `onBeforeOpen()` for doing heavy-weight initialization
* that is needed for rendering the dialog such as resource allocations and asynchronous calls to fetch data. You
* can use the constructor to set initial parameters of your dialog such as references to resources in your
* application. The reason for this is that dialogs are usually constructed upon a UI event e.g. a button click,
* but the dialog may not always be shown right after construction. Keeping the constructor lightweight ensures
* smooth user experience and avoids doing throw-away work in case the dialog is not shown later e.g. if
* the framework rejects it. Another benefit of doing this is avoiding memory leaks by doing all the allocations and
* disposals in symmetric `onBeforeOpen()` and `onAfterClose()` events. If you allocate resources in the
* constructor, you have a memory leak because there is no guarantee onAfterClose() will get called,
* and onAfterClose() is your only opportunity to dispose.
*
* Example:
* ```
* constructor(cacheReference: any) {
* super();
*
* this._cache = cacheReference; // This is okay. Keeping a reference to my internal cache.
* this._cache.refresh(); // This is bad practice.
* // If you need to refresh the cache (or fetch data) for rendering, do it in onBeforeOpen()
* }
* ```
*
* @param config - the dialog configuration that affects how the dialog is displayed *
*/
function BaseDialog(config) {
this._config = config || {};
this._state = _DialogState.New;
this._isSecondary = false;
this.close = this.close.bind(this);
this.render = this.render.bind(this);
}
Object.defineProperty(BaseDialog.prototype, "_configuration", {
/**
* The dialog configuration
*
* @internal
*/
get: function () {
return this._config;
},
enumerable: false,
configurable: true
});
Object.defineProperty(BaseDialog.prototype, "isSecondary", {
/**
* If the dialog is a secondary dialog. This means that there is another dialog hidden behind this dialog.
*/
get: function () {
return this._isSecondary;
},
enumerable: false,
configurable: true
});
Object.defineProperty(BaseDialog.prototype, "isOpen", {
/**
* If the dialog is visually open. This returns true during onBeforeOpen() because there is a visual component.
* It returns false when the dialog is hidden.
*/
get: function () {
return this._state === _DialogState.Open;
},
enumerable: false,
configurable: true
});
Object.defineProperty(BaseDialog.prototype, "isHidden", {
/**
* If the dialog is visually hidden.
*
* @remarks
* This happens when the dialog goes behind a secondary dialog.
* Note that this is different from closed, because the dialog still has the permission to show and will
* eventually unhide. This returns false if the dialog is closed.
*/
get: function () {
return this._state === _DialogState.Hidden;
},
enumerable: false,
configurable: true
});
Object.defineProperty(BaseDialog.prototype, "secondaryDialogProvider", {
/**
* An active dialog is permitted to show a secondary dialog on top of itself. By design, only two layers of
* dialogs are permitted.
*
* @remarks
* Secondary dialogs do not have to wait for permission and will immediately be shown or rejected. All calls to
* show a secondary dialog reject while there is already a secondary dialog showing.
* This property may be undefined if a secondary dialog is not available i.e. the current dialog is secondary itself
* or the dialog is not active.
*/
get: function () {
if (this.isSecondary) {
return undefined;
}
if (!this._secondaryDialogProvider && this._requestId) {
this._secondaryDialogProvider = DialogManager.instance.createSecondaryDialogProvider(this._requestId);
}
return this._secondaryDialogProvider;
},
enumerable: false,
configurable: true
});
/**
* Request the framework to show this dialog.
*
* @returns A promise that resolves once the dialog is successfully closed (after being shown).
* The promise rejects if the request is rejected or aborted.
*
* @param options - Dialog showing options. See {@link IDialogShowOptions} for more information.
*/
BaseDialog.prototype.show = function (options) {
if (DialogManager.instance) {
return DialogManager.instance.show(this, options);
}
else {
return Promise.reject(new Error('This application is not configured to accept dialogs.'));
}
};
/**
* Close the dialog.
*
* @remarks
* This will void the permission to show for this dialog. Every dialog should have a mechanism to
* eventually close to avoid blocking the user interface. If called on an inactive dialog it will abort the request
* to show.
*
* @returns A promise that resolves when the dialog is visually closed, or if it was already closed
*/
BaseDialog.prototype.close = function () {
if (DialogManager.instance) {
if (this._isActive) {
// Note: onAfterClose() is called by the manager
return DialogManager.instance._close(this);
}
else {
DialogManager.instance._abort(this);
}
}
return Promise.resolve(undefined);
};
/**
* Called by the manager after the modal is open to render the dialog content.
* This internally calls onBeforeOpen() and returns a promise that resolves only if the render was successful.
* If onBeforeOpen() is rejected or dialog is closed while rendering, the returned promise will reject.
*
* @internal
*/
BaseDialog.prototype._render = function (container) {
var _this = this;
this._domElement = container;
if (!this._renderPromise) {
var rejectRender_1, resolveRender_1;
this._renderPromise = new Promise(function (resolve, reject) {
resolveRender_1 = resolve;
rejectRender_1 = reject;
});
this._closedWhileRendering = false;
this.onBeforeOpen()
.then(function () {
if (!_this._closedWhileRendering) {
_this.render();
resolveRender_1();
}
else {
rejectRender_1();
}
})
.catch(function () {
// At this point the modal is opened by framework but the dialog itself rejected opening
DialogManager.instance._rejectOnOpen(_this);
rejectRender_1();
});
}
return this._renderPromise;
};
/**
* Called by the manager to change the state of the dialog.
* Note: State management is all done by the manager using this method. Do not call this method from this class.
* If you want to do something upon state change, do it in this method by checking the newState.
*
* @internal
*/
BaseDialog.prototype._setState = function (newState) {
if (newState === _DialogState.Closed) {
this._secondaryDialogProvider = undefined;
if (this._renderPromise) {
this._closedWhileRendering = true;
this._renderPromise = undefined;
}
}
_TraceLogger.logVerboseData({
source: BaseDialog._logSource,
message: "Dialog state changed from '".concat(_DialogState[this._state], "' to '").concat(_DialogState[newState], "'.")
});
this._state = newState;
};
/**
* Called by the manager to acknowledging a show request and sets some parameters that active dialogs need
*
* @param requestId - A Guid which is unique string for the manager to identify the request for this dialog
* @param isSecondary - If the dialog is secondary (called via secondary dialog API)
*
* @internal
*/
BaseDialog.prototype._requestAck = function (requestId, isSecondary) {
this._requestId = requestId;
this._isSecondary = !!isSecondary;
};
Object.defineProperty(BaseDialog.prototype, "domElement", {
/**
* Use this property to access the container element provided by the framework for rendering.
*
* @remarks
* See {@link BaseDialog.render} for more information on rendering.
*/
get: function () {
return this._domElement;
},
enumerable: false,
configurable: true
});
/**
* This method is called before the render method and can be overridden to make preparations for rendering.
* The loading indicator is displayed during the lifetime of this method.
* virtual
* @remarks
* All resource allocations in onBeforeOpen() should be properly disposed in `onAfterClose()` to a avoid memory leak.
*
* @returns A promise that resolves when the operations are done and the dialog is ready to render. If the
* promise is rejected, the dialog will not be rendered and `onAfterClose()` will not be called.
*/
BaseDialog.prototype.onBeforeOpen = function () {
return Promise.resolve(undefined);
};
/**
* This method is called after the dialog is visually closed and gives an opportunity for doing clean up.
* @remarks
* The dialog lifecycle completes after closing and there should be no resources left inside the object. Even though
* the dialog may be revived again for a new lifecycle using show() method, this is considered a whole new lifecycle
* that should reallocate its own resources. If there are any resources that you would like to keep for multiple
* lifecycles, consider allocating it outside the dialog object and passing its reference to the dialog constructor.
*/
BaseDialog.prototype.onAfterClose = function () {
// nothing to do.
};
Object.defineProperty(BaseDialog.prototype, "_isActive", {
/**
* A dialog is active if it is in one of these states:
* - The framework has approved to show it and has started the opening process
* - It is open and showing
* - It is visually hidden behind a secondary dialog
*
* When a dialog is active, the framework considers that dialog to have permission to show until it is closed.
*/
get: function () {
return (this._state === _DialogState.Approved ||
this._state === _DialogState.Open ||
this._state === _DialogState.Hidden);
},
enumerable: false,
configurable: true
});
BaseDialog._logSource = _LogSource.create('BaseDialog');
return BaseDialog;
}());
export default BaseDialog;
//# sourceMappingURL=BaseDialog.js.map