Hi Bryntum team.
We have been looking for about a week into B internals and source code, attempting to understand how classes and configs work. We appreciate what we have seen so far – good quality code, well-architected widgets with numerous options and configurations, class and widget lifecycles, good parity with ExtJs, and more. Additionally, every widget has fewer than twice as many DOM elements compared to ExtJs (specifically Modern, which we have checked). So, excellent work team, and btw, we are pleased to see former ExtJs engineers in the team.
Our app is currently based on ExtJs Modern, and we are exploring the best possible way to migrate to B. Some of the initial things we appear to be missing so far are the ViewControllers and config bindings (not the ViewModel, we will keep some state configs in controller for now).
We would still want ViewControllers to split the business logic of View internals, plus will make migration smoother. First possible idea we have tried is:
import InstancePlugin from '@bryntum/grid/source/lib/Core/mixin/InstancePlugin';
// Abstract class, to further add more stuff in it.
export default class ViewController extends InstancePlugin {
static get $name() {
return 'ViewController';
}
static get configurable() {
return {};
}
construct(view, config) {
super.construct(view, config);
this.view = view;
}
}
// Add new 'controller' config
Widget.setupConfigs(
Widget.$meta,
{
controller: null,
},
false
);
// Automatically instantiate the controller and store the ref in widget.
Widget.prototype.changeController = function (controllerCtor) {
if (controllerCtor) {
InstancePlugin.initPlugins(this, controllerCtor);
return this.plugins[controllerCtor.$$name];
}
};
class BaseOverride {
static get target() {
return {
class: Base,
};
}
resolveCallback(handler, thisObj = this, enforceCallability = true) {
if (handler?.substring) {
if (handler.startsWith('ctrl.')) {
handler = handler.substring(5);
const view = this.up((parent) => {
if (parent.controller) {
return true;
}
});
if (view && view.controller[handler]) {
thisObj = view.controller;
handler = view.controller[handler];
}
}
}
return this._overridden.resolveCallback.call(
this,
handler,
thisObj,
enforceCallability
);
}
}
Override.apply(BaseOverride);
We would want also config bindings, as the code it's more declarative, readable and probaly easier to be maintained. We still haven't figured out how to nicely propagate config values from one config/formula to widget's config (hidden, disabled), but for now can be done through FunctionHelper.after - is just and ugly idea (no cleanup yet after widgets destroy) to test the principle...but we will find a better way to do it (using events or proxies).
// Question, is this the correct and/or the only way to add configs to internal classes, and attach a change/update method ?
Widget.setupConfigs(
Widget.$meta,
{
bind: null,
},
false
);
Widget.prototype.changeBind = function (bindings) {
if (bindings) {
const view = this.up((parent) => {
if (parent.controller) {
return true;
}
});
const controller = view?.controller;
if (controller) {
for (const field in bindings) {
const flag = bindings[field];
const { changer } = controller.$meta.configs[flag];
FunctionHelper.after(controller, changer, (val) => {
this[field] = val;
});
}
}
}
};
Test example:
import ViewController from '@/bryntum/core/app/ViewController';
import Toolbar from '@bryntum/grid/source/lib/Core/widget/Toolbar';
import '@bryntum/grid/source/lib/Core/widget/Button';
import '@/bryntum/theme.scss';
import './WidgetOverride';
describe('Widget.bind', function () {
class ToolbarController extends ViewController {
static get configurable() {
return {
isButtonHidden: false,
isButtonDisabled: false,
};
}
construct(view, config) {
super.construct(view, config);
}
onSelectionChange() {
this.isButtonHidden = true;
}
changeIsButtonHidden(val) {
return val;
}
changeIsButtonDisabled(val) {
return val;
}
onHideButtonClick() {
this.isButtonHidden = !this.isButtonHidden;
}
}
it('Widget.bind', () => {
new Toolbar({
appendTo: document.body,
controller: ToolbarController,
width: 800,
items: [
{
type: 'button',
text: 'hide buttons',
listeners: {
click: 'ctrl.onHideButtonClick',
},
},
{
type: 'button',
text: 'disabled buttons',
onClick() {
this.up().controller.isButtonDisabled =
!this.up().controller.isButtonDisabled;
},
},
{
type: 'button',
text: 'hide me 1',
bind: {
hidden: 'isButtonHidden',
},
},
{
type: 'button',
text: 'hide me 2',
bind: {
hidden: 'isButtonHidden',
},
},
{
type: 'button',
ref: 'buttonDisable',
text: 'disable me 2',
bind: {
disabled: 'isButtonDisabled',
},
},
],
});
});
});
Everythign here is just a draft to test some ideas and also to learn B internals, so suggestions and constructive discussions are welcomed.
Thanks.
Vadim
Edit: feel free to move it to the appropiate forum group, if this one is not ok, thx.