gethue
Version:
Hue is an Open source SQL Query Editor for Databases/Warehouses
79 lines (67 loc) • 2.99 kB
JavaScript
import * as ko from 'knockout';
import { createElement } from 'react';
import { createRoot } from 'react-dom/client';
import { loadComponent } from '../../reactComponents/imports';
/**
* REACT KNOCKOUT INTEGRATION
* This is a oneway binding from knockout to react.js. Use the data-binding called reactWrapper
* followed by the component name. Props are passed in as js object literal coded as a string using
* the props param. Any new components used must also be added to the import file
* desktop/core/src/desktop/js/reactComponents/imports.js.
*
* Example usage:
*
* <MyComponent data-bind="reactWrapper: 'MyComponent',
* props: { title: 'Result title', activeExecutable: activeExecutable }">
* </MyComponent>
*
*
* The name of the component element tag (eg <MyComponent>) can be anything, but for consistency
* and to stay close to how normal react components look we use the actual component name.
*/
const getProps = allBindings => {
const props = allBindings.get('props');
// Functions are not valid as a React child
return { ...props, children: ko.toJS(props.children) };
};
ko.bindingHandlers.reactWrapper = (() => {
return {
init: function (el, valueAccessor, allBindings, viewModel, bindingContext) {
const componentName = ko.unwrap(valueAccessor());
const props = getProps(allBindings);
// The component's react root should only be created once per DOM
// load so we pass it along via the bindingContext to be reused in the KO update call.
const reactRoot = createRoot(el);
el.__KO_React_root = reactRoot;
loadComponent(componentName).then(Component => {
reactRoot.render(createElement(Component, props));
});
// Since the react component is a root component we need to handle the
// unmounting explicitly if the dom node is disposed by Knockout, e.g. via "ko if:"
ko.utils.domNodeDisposal.addDisposeCallback(el, () => reactRoot.unmount());
// Tell Knockout that it does not need to update the children
// of this component, since that is now handled by React
return { controlsDescendantBindings: true };
},
update: function (el, valueAccessor, allBindings, viewModel, bindingContext) {
const componentName = ko.unwrap(valueAccessor());
const props = getProps(allBindings);
loadComponent(componentName).then(Component => {
el.__KO_React_root.render(createElement(Component, props));
});
// Handle KO observables
Object.entries(props).forEach(([propName, propValue]) => {
if (ko.isObservable(propValue)) {
const koSubscription = propValue.subscribe(() => {
loadComponent(componentName).then(Component => {
el.__KO_React_root.render(
createElement(Component, { ...props, [propName]: propValue() })
);
});
});
koSubscription.disposeWhenNodeIsRemoved(el);
}
});
}
};
})();