terriajs
Version:
Geospatial data visualization platform.
97 lines (78 loc) • 4.29 kB
JavaScript
;
import defined from 'terriajs-cesium/Source/Core/defined';
import knockout from 'terriajs-cesium/Source/ThirdParty/knockout';
import PureRenderMixin from 'react-addons-pure-render-mixin';
const ObserveModelMixin = {
componentWillMount() {
this.__observeModelChangeSubscriptions = undefined;
const originalRender = this.render;
this.render = function renderForObserveModelMixin() {
const that = this;
let isFirstRender = true;
// Clean up the previous subscription, if any.
disposeSubscription(that);
// Ignore dependencies so that the parent component's render function does not
// depend on the child component's render function. If it did, a change to a child
// would trigger re-rendering of all its ancestors, which is wasteful.
return knockout.ignoreDependencies(function() {
const computed = knockout.computed(function() {
// The first time our computed render function is called, pass through to the real
// render and track dependencies along the way. Any time after that is a result of
// a change in one of the properties that the render used. But if we re-evaluate
// the render function there, we're stepping outside the normal React lifecycle.
// Instead, unsubscribe from the computed observable and force an update of the
// React component (which will create a new computed observable).
if (isFirstRender) {
isFirstRender = false;
return originalRender.call(that);
}
}).extend({notify: 'always'});
that.__observeModelChangeSubscriptions = [];
let updateForced = false;
/**
* Disposes of the subscription to this component and forces an update, which will create a new
* computed observable.
*/
function disposeAndForceUpdate() {
disposeSubscription(that);
if (!updateForced) {
updateForced = true;
that.forceUpdate();
}
}
// We also need to update on change of anything in props with a __knockoutSubscribable property. This
// property is added to arrays by knockout-es5 and is notified whenever the array is modified.
// Without this, changes in an observable array passed as props won't trigger re-render of the component,
// even if the array is used in rendering. This is because Knockout observable arrays don't note a
// dependency when accessing individual elements of the array.
for (const prop in that.props) {
if (that.props.hasOwnProperty(prop)) {
if (defined(that.props[prop]) && defined(that.props[prop].__knockoutSubscribable)) {
that.__observeModelChangeSubscriptions.push(that.props[prop].__knockoutSubscribable.subscribe(disposeAndForceUpdate));
}
}
}
that.__observeModelChangeSubscriptions.push(computed.subscribe(disposeAndForceUpdate));
return computed();
});
};
},
componentWillUnmount() {
disposeSubscription(this);
},
shouldComponentUpdate: PureRenderMixin.shouldComponentUpdate
};
/**
* Disposes of all subscriptions that a component currently has.
*
* @param component The component to find and dispose subscriptions on.
*/
function disposeSubscription(component) {
if (defined(component.__observeModelChangeSubscriptions)) {
for (let i = 0; i < component.__observeModelChangeSubscriptions.length; ++i) {
component.__observeModelChangeSubscriptions[i].dispose();
}
component.__observeModelChangeSubscriptions = undefined;
}
}
module.exports = ObserveModelMixin;