@aappddeevv/dynamics-client-ui
Version:
## What is it? A library to help you create great dynamics applications.
229 lines • 9.8 kB
JavaScript
"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