This PR updates the shiny-bindings-core pkg to not use web components for the basic makeInputBinding() and makeOutputBinding() functions. (The old webcomponents versions are now called make{Input,Output}BindingWebComponent())
The main goal here is to simplify the structure and make it less confusing what is going on under the hood. The API's for the main two functions are now almost as clean as the react bindings, with most logic coming in a single callback function that keeps the element as a scope-level variable.
New plain bindings API:
makeInputBinding() now looks very similar to the syntax of the react binding. See packages/core/README.md and packages/core/src/makeInputBinding.ts for more details.
makeOutputBinding() is similarly cleaned up. See packages/core/README.md and packages/core/src/makeOutputBinding.ts for more details.
makeOutputBinding<{ value: number }>({
name: "custom-component-simple",
setup: (el) => {
return {
onNewValue: (payload) => {
el.innerHTML = `
<span>I am a plain output with value:</span>
<strong> ${payload.value} </strong>
`;
},
};
},
});
New demo project
To make it easier to develop, a third "package" has been added that is a python package that implements inputs and ouputs using the various binding helpers provided. It sits at packages/demo-pkg and has a readme with details about spinning up a live-reload dev environment.
Implementation notes
Both the input and output functions operate around the concept of a single closure being called for the binding. This allows users to store things like state variables in that closure along with referencing the same element variable. This was done by using a map keyed by the bound element within the make*Binding() functions.
E.g. in packages/core/src/makeOutputBinding.ts:
...
type BindingCallbacks<T> = {
onNewValue: (x: T) => void;
};
...
class NewCustomBinding extends Shiny.OutputBinding {
override find(scope: HTMLElement) {
return $(scope).find(selector);
}
boundElements = new WeakMap<HTMLElement, BindingCallbacks<T>>();
private getCallbacks(el: HTMLElement): BindingCallbacks<T> {
// Get the callbacks for this element if they exist
// If they don't exist, setup the element and get the callbacks
if (!this.boundElements.has(el)) {
this.boundElements.set(el, setup(el));
}
const callbacks = this.boundElements.get(el);
if (typeof callbacks === "undefined") {
throw new Error("Unable to get callbacks for element");
}
return callbacks;
}
override renderValue(el: HTMLElement, data: T): void {
this.getCallbacks(el).onNewValue(data);
}
}
...
This PR updates the
shiny-bindings-core
pkg to not use web components for the basicmakeInputBinding()
andmakeOutputBinding()
functions. (The old webcomponents versions are now calledmake{Input,Output}BindingWebComponent()
)The main goal here is to simplify the structure and make it less confusing what is going on under the hood. The API's for the main two functions are now almost as clean as the react bindings, with most logic coming in a single callback function that keeps the element as a scope-level variable.
New plain bindings API:
makeInputBinding()
now looks very similar to the syntax of the react binding. Seepackages/core/README.md
andpackages/core/src/makeInputBinding.ts
for more details.makeOutputBinding()
is similarly cleaned up. Seepackages/core/README.md
andpackages/core/src/makeOutputBinding.ts
for more details.New demo project
To make it easier to develop, a third "package" has been added that is a python package that implements inputs and ouputs using the various binding helpers provided. It sits at
packages/demo-pkg
and has a readme with details about spinning up a live-reload dev environment.Implementation notes
Both the input and output functions operate around the concept of a single closure being called for the binding. This allows users to store things like state variables in that closure along with referencing the same element variable. This was done by using a map keyed by the bound element within the
make*Binding()
functions.E.g. in
packages/core/src/makeOutputBinding.ts
: