UNPKG

@aappddeevv/dynamics-client-ui

Version:

## What is it? A library to help you create great dynamics applications.

229 lines 9.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = require("tslib"); /** * Helpers for dealing with entity form state. */ const React = require("react"); const Utils = require("./Utils"); const PropTypes = require("prop-types"); const Dynamics_1 = require("./Dynamics"); const Utils_1 = require("../Data/Utils"); const R = require("ramda"); const BuildSettings_1 = require("BuildSettings"); const monet_1 = require("monet"); const Deferred_1 = require("./Deferred"); const Utils_2 = require("./Utils"); /** Empty state. */ exports.EmptyAttributeState = {}; /** * Add on change handlers to attributes. Return a new state. Existing * attributes in AttributeState are included in the return value untouched * so this is safe to call incrementally. */ function connect(attributes, state, getAttribute, onChangeHandler) { const x = attributes.map(logicalName => { const existing = state[logicalName]; if (existing) return monet_1.Maybe.Some(existing); // get Dynamics attribute const attribute = getAttribute(logicalName); if (attribute) { const token = ctx => onChangeHandler(logicalName, attribute.getValue()); attribute.addOnChange(token); // set hasValue const value = attribute.getValue(); const hasValue = attribute ? (value !== null) : false; return monet_1.Maybe.Some({ name: logicalName, logicalName, attribute, hasValue, unregisterToken: token, value, }); } else { // throw an error? if (BuildSettings_1.DEBUG) console.log("connect: Unable to connect:", logicalName); return monet_1.Maybe.None(); } }).filter(aOpt => aOpt.isSome()).map(aOpt => aOpt.some()); return Object.assign({}, state, Utils_1.normalizeWith("logicalName", x)); } exports.connect = connect; /** Clears all values but setting it to null. */ function clear(state) { R.values(state).forEach(attr => { if (attr.attribute.getValue() !== null) { attr.attribute.setValue(null); attr.attribute.fireOnChange(); } }); } exports.clear = clear; /** Not used yet. */ exports.entityFormShape = PropTypes.shape(Object.assign({ formContextP: PropTypes.instanceOf(Promise) }, Dynamics_1.Dynamics.childContextTypes)); /** * Inject Xrm state into a child and provide Xrm state through * a component's context. Can detect when the form has been saved * because the entityId will appear as a value in the child props. * Save handlers are run properly after the save. An update after * save is automatically called. Using this component as your parent * is alot like using `connect` in `react-redux`. * * The Xrm.FormContext is obtained via FormContextHolder or window.parent. */ class EntityForm extends Dynamics_1.Dynamics { constructor(props, context) { super(props, context); this.formContextResolved = false; this.__disposables = []; this.__afterSaves = []; /** Attributes live outside the react world, so make it instance var. */ this._attributeState = {}; /** * Setup connections force an update so that values are propagated. * @returns true if connections were created, false otherwise. */ this.connect = (attributes) => tslib_1.__awaiter(this, void 0, void 0, function* () { if (BuildSettings_1.DEBUG) console.log("EntityForm.connect", attributes); return this.deferredFormContext.promise.then(fctx => { if (!fctx) return Promise.resolve(false); const newState = connect(attributes, this._attributeState || {}, (n) => { // should we throw an error if its not found??? // shouldn't it be an error? return fctx.getAttribute(n); }, this.onChangeHandler); this._attributeState = newState; this.forceUpdate(); return Promise.resolve(true); }) .catch(e => { console.log("Error while EntityForm.connect", e); return Promise.resolve(false); }); }); this.onChangeHandler = (name, value) => { if (BuildSettings_1.DEBUG) console.log("EntityForm.onChangeHandler", name, value, this._attributeState); const updated = Object.assign({}, this._attributeState[name], { hasValue: (value ? true : false), value }); this._attributeState[name] = updated; this.forceUpdate(); }; /** For each value in a connected state. */ this.clear = (names, fire = false) => { names.forEach(n => this.setValue(n, null)); this.forceUpdate(); return Promise.resolve(); }; /** Give a FormContext, extract some values to pass to children as props on the next render. */ this.extractValues = (fctx) => { const entity = fctx.data.entity; const ui = fctx.ui; const stateAttr = fctx.getAttribute("statecode"); const ename = entity.getEntityName(); this.setState({ stateCode: stateAttr ? stateAttr.getValue() : null, //isActive: stateAttr ? stateAttr.getValue() === 0 : true, canChange: ui.getFormType() !== 3 ? true : false, entityId: entity.getId() ? Utils_1.cleanId(entity.getId()) : null, ename, entityName: ename ? ename : (Utils.getURLParameter("typename") || null), formType: ui.getFormType(), }); }; /** * Setting value in dynamics attribute does *not* fire change event automatically, * which is good for us. If fire is true, `fireOnChange()` is called. */ this.setValue = (name, value, fire = false) => { const x = this._attributeState[name]; if (x) { x.attribute.setValue(value); x.attribute.fireOnChange(); } return Promise.resolve(); }; this.deferredFormContext = Deferred_1.default(); const self = this; this.deferredFormContext.promise.then(fctx => { self.formContextResolved = true; self.extractValues(fctx); }); if (props.formContext) { // resolve immediately if a strict value was provided this.deferredFormContext.resolve(props.formContext); } this.state = { stateCode: null, //isActive: true, canChange: null, entityId: null, ename: null, entityName: null, formType: null, }; } // tslint:enable:variable-name getChildContext() { return Object.assign({ formContextP: this.deferredFormContext.promise }, super.getChildContext()); } /** Can push a thunk. */ get _afterSaves() { return this.__afterSaves; } /** Can push a Disposable. */ get _disposables() { return this.__disposables; } /** Get the class name. From Office Fabric. */ get className() { if (!this.__className) { const funcNameRegex = /function (.{1,})\(/; const results = (funcNameRegex).exec((this).constructor.toString()); this.__className = (results && results.length > 1) ? results[1] : ""; } return this.__className; } /** * Setup the FormContext if it is not already set. When resolved, force an update. */ componentDidMount() { const self = this; if (!this.formContextResolved) { const p = Utils_2.getFormContextP(); p.then(fctx => { if (BuildSettings_1.DEBUG) //console.log("EntityForm: form context set from form context holder:", fctx) console.log("EntityForm: form context set:", fctx); self.deferredFormContext.resolve(fctx); }).then(() => this.forceUpdate()); } } componentWillUnmount() { this.__disposables.forEach(d => { if (d.dispose) d.dispose(); }); this.__disposables = []; // remove connections... R.values(this._attributeState).forEach(a => { if (a.attribute && a.unregisterToken) a.attribute.removeOnChange(a.unregisterToken); }); } componentWillMount() { const xrm = this.getXrm(); if (xrm && !!this.props.trackSave) Utils.runAfterSave(xrm, () => true, () => { this.__afterSaves.forEach(t => t()); this.forceUpdate(); }, 500); } render() { const xrm = this.getXrm(); const userId = xrm ? xrm.Utility.getGlobalContext().getUserId() : null; return React.cloneElement(React.Children.only(this.props.children), Object.assign({ userId: userId ? Utils_1.cleanId(userId) : null, //isActive: this.state.isActive, canChange: this.state.canChange, entityId: this.state.entityId, entityName: this.state.entityName, formType: this.state.formType, connect: this.connect, clear: this.clear, setValue: this.setValue, formContextP: this.deferredFormContext.promise, notifier: this.context.notifier }, this._attributeState)); } } EntityForm.childContextTypes = Object.assign({ formContextP: PropTypes.instanceOf(Promise) }, Dynamics_1.Dynamics.childContextTypes); exports.EntityForm = EntityForm; exports.default = EntityForm; //# sourceMappingURL=EntityForm.js.map