opened 04:56AM - 09 Sep 20 UTC
type: discussion
#### My Pain
I would like my analytics (Panel) apps to look and feel awesome.… A part of that is custom branding, a consistent look, feel and maybe some animations/ transitions.
I tried for a long time to style the existing Bokeh widgets. But it really takes a long time and as I see it it's not really made for changing the look and feel. A lot of the things are just hard coded in the CSS files.
Also today a lot of front end has moved from Bootstrap to Material and other Frameworks. I would like to be able to use those for example in an Enterprise setting where the official branding is not based on Bootstrap but one of the other frameworks.
What I've found is that Web Components like MWC or Fast are nice because they extend the basic functionality of the core HTML elements. And their styling is in the shadow dom and thus does not disturb/ get disturbed by the css and javascript of Bokeh, Panel or Jupyter. And they come with design systems. I.e. you just need to change a few css properties to change the look and feel across all widgets or change from light to dark mode.
Currently I'm working on Fast. But If I'm able to speed up the development process I would like to support Material and Maybe others like Material MWC, Smart Elements, SAP UI 5 Elements, Wired Elements etc.
A prerequisite to speed up the process is code reuse and inheritance.
After having tried to extend the Bokeh models I can see that they can almost be used in an easy way but not fully. The `render` method should be refactored a bit such that
1. construction of the element is a subfunction that can be reused or overridden.
2. the addition of eventlisteners should be a seperate subfunction that can be reused or overridden.
## Examples
### Button
The Button is prepared for reuse

And it's easy to implement a FastButton

### TextInput
For the FastTextInput I could not get inheritance from TextInput working because of the way the code was structured. So I had to inherit from `InputWidgetView` instead.
```typescript
export class FastTextInputView extends InputWidgetView {
model: FastTextInput
protected input_el: HTMLInputElement
connect_signals(): void {
super.connect_signals()
this.connect(this.model.properties.name.change, () => this.input_el.name = this.model.name ?? "")
this.connect(this.model.properties.value.change, () => {this.input_el.value = this.model.value;console.log("value")})
this.connect(this.model.properties.value_input.change, () => {this.input_el.value = this.model.value_input;console.log("value_input")})
this.connect(this.model.properties.disabled.change, () => this.input_el.disabled = this.model.disabled)
this.connect(this.model.properties.placeholder.change, () => this.input_el.placeholder = this.model.placeholder)
this.connect(this.model.properties.appearance.change, () => this.input_el_any.appearance = this.model.appearance)
this.connect(this.model.properties.autofocus.change, () => this.input_el_any.autofocus = this.model.autofocus)
this.connect(this.model.properties.type_of_text.change, () => this.input_el_any.type = this.model.type_of_text)
this.connect(this.model.properties.maxlength.change, () => this.input_el_any.maxlength = this.model.maxlength)
this.connect(this.model.properties.minlength.change, () => this.input_el_any.minlength = this.model.minlength)
this.connect(this.model.properties.pattern.change, () => this.input_el_any.pattern = this.model.pattern)
// Could not get size working. It raises an error
// this.connect(this.model.properties.size.change, () => this.input_el_any.size = this.model.size)
this.connect(this.model.properties.spellcheck.change, () => this.input_el_any.spellcheck = this.model.spellcheck)
this.connect(this.model.properties.required.change, () => this.input_el_any.required = this.model.required)
// Could not get readonly working as a property.
// https://github.com/microsoft/fast/issues/3852
this.connect(this.model.properties.readonly.change, () => this.input_el_any.setAttribute("readonly", this.model.readonly))
}
public get input_el_any() : any {
return <any>this.input_el;
}
render(): void {
super.render()
const fastTextField = <any>document.createElement("fast-text-field")
this.input_el = <HTMLInputElement>fastTextField;
this.input_el.className="bk-fast-input"
this.input_el.addEventListener("change", () => this.change_input())
this.input_el.addEventListener("input", () => this.change_input_oninput())
this.group_el.appendChild(this.input_el)
// For some unknown reason we need to set these properties after the above
// Otherwise for example the value is reset to ""
fastTextField.name = this.model.name;
fastTextField.value = this.model.value;
fastTextField.appearance = this.model.appearance;
fastTextField.autofocus = this.model.autofocus;
fastTextField.placeholder = this.model.placeholder;
fastTextField.disabled = this.model.disabled;
fastTextField.type = this.model.type_of_text;
fastTextField.maxlength = this.model.maxlength;
fastTextField.minlength = this.model.minlength;
fastTextField.pattern = this.model.pattern;
// Could not get size working. It raises an error.
// fastTextField.size = this.model.size;
fastTextField.spellcheck = this.model.spellcheck;
fastTextField.required = this.model.required;
fastTextField.disabled = this.model.disabled;
fastTextField.setAttribute("readonly", this.model.readonly)
}
change_input(): void {
this.model.value = this.input_el.value
super.change_input()
}
change_input_oninput(): void {
this.model.value_input = this.input_el.value
super.change_input()
}
}
```
### CheckBoxGroup
For the `FastCheckboxGroupView` I could use inheritance to some extend. But calling `super.render()` would add the group twice. So I don't call that.
```python
export class FastCheckboxGroupView extends CheckboxGroupView {
model: FastCheckboxGroup;
render(): void {
// Cannot call super.render() as this will add the group twice
// super.render()
const group = div({class: [bk_input_group, this.model.inline ? bk_inline : null]})
this.el.innerHTML="";
this.el.appendChild(group)
const {active, labels} = this.model
this._inputs = []
for (let i = 0; i < labels.length; i++) {
let fastCheckBox = <any>document.createElement("fast-checkbox")
if (this.model.readonly)
// Setting the property did not work for me. Thus I set the attribute
fastCheckBox.setAttribute("readonly", true)
fastCheckBox.innerHTML = labels[i]
const checkbox = <HTMLInputElement>fastCheckBox
checkbox.value = `${i}`
// const checkbox = input({type: `checkbox`, value: `${i}`})
checkbox.addEventListener("change", () => this.change_active(i))
this._inputs.push(checkbox)
if (this.model.disabled)
checkbox.disabled = true
if (includes(active, i))
checkbox.checked = true
// const label_el = label({}, checkbox, span({}, labels[i]))
group.appendChild(checkbox)
}
}
}
```
#### Additional Context
Maybe it's not such a big deal as I get up to speed implementing this. Maybe my TypeScript skills are not well developed.
But it's just nice if it's clear what you need to implement/ override, if you can reuse a lot and don't have to implement much :-)