qooxdoo / qooxdoo

qooxdoo - Universal JavaScript Framework
http://qooxdoo.org
Other
764 stars 259 forks source link

Proposal: New cached object system (think getQxObject) #10641

Closed p9malino26 closed 4 months ago

p9malino26 commented 5 months ago

Context

This proposes a new system in which cached objects for Qooxdoo classes are defined (getQxObject, getChildControl, etc). It is 100% BC and solves the major problems present in the current system, which include:

Design

Example:

qx.Class.define("com.myApp.MyWidget",{
    extend: qx.ui.core.Widget,

    qxObjects: {
        selectBox1() {
            const numItems = 5;
            let sb = new qx.ui.form.SelectBox();
            for (let i = 0; i < numItems; i++)
                sb.add(new qx.ui.form.ListItem("Item: " + i));            
            return sb;
        },

        ctlrSelectBox1() {
            let sb = this.getQxObject("selectBox1");
            let ctlr = new qx.data.controller.List(this.getQxObject("modelSelectbox1"), sb, "name");
            return ctlr;
        },

        btnClose() {
            let btn = super._createQxObjectImpl("btnClose");//if we want to execute the inherited function
            btn.setBackgroundColor("red");
            return btn;
        },

        edtName: () => new qx.ui.form.TextField(),

        edtSurname: "edtName" //just takes the function that is defined for 'edtName'
    }
});

Some points

As well as solving the above-mentioned problems, this solution offers many additional benefits, including:

Things to consider

cboulanger commented 5 months ago

I think this is an excellent idea. The qxObject system is a hidden gem of qooxdoo which is worth being made a first-class citizen. Your proposal makes it much more idiomatic to work with.

cboulanger commented 5 months ago

However, the key shold something different than "qxObjects". In most cases, the objects defined are widget components. Maybe "components" would be the best term.

p9malino26 commented 5 months ago

I think you're right. The system exists to make defining widget composition easier. Things like controllers should be defined as members.

If we were to use 'components' as the name, would the widgets defined under 'components' be referenced using getQxObject or would we use 'getComponent' for that? Or could we make getComponent a synonym of qxObject?

johnspackman commented 5 months ago

I like this idea too; although the "QxObject" name isn't as perfectly elegant as we might like for the QxObject API, we had to avoid conflicts and so the name has stuck.

As the backing for this code is the getQxObject / _createQxObjectImpl APIs, IMHO I think the qxObjects top level member is the right name

goldim commented 5 months ago

Widget/children/widgetChildren.

Forced use of the old 'var' instead of let or const because you cannot re-declare variables with the same name in the same scope.

I declare via let before switch. For me it is not problem.

WillsterJohnsonAtZenesis commented 5 months ago

The results of a little discussion we've had on this in the office;

Having a more declarative notation added to the qxObject system would be quite beneficial. Currently, it would be a little complex, however by setting it up correctly now, the new properties system proposal (#10410) would automatically promote it to the desired behavior.


The end result once all is said and done would be that the following two lines of code are equivalent;

this.qxObjects.myObject;
this.getQxObject("myObject");

The first one is the ideal access to qxObjects; completely declarative in nature. While in the second (current) syntax it's quite easy to make typos, in the first syntax this would be much more obvious, especially after the type checking system (#10639) is detailed and implemented, in which static compile-time type checking can be quite easily applied to the object. Applying the same to function paramaters is less simple and may require a more complicated solution to #10639.

Under the hood the implementation would be very simple. A property defined under the new system named qxObjects, with it's mutability set to readonly and it's value initialised to a get-trap Proxy instance which redirects all qxObjects[key] and qxObjects.key to getQxObject(key).

Below is a proof of concept demonstrating that this works with any keys through either index access or by dot notation.

class MyQxThingOrWhatever {
  // this would exist inside of `qx.core.MObjectId`, utilizing the new property system
  get qxObjects() {
    return this.#__cached_qxObjects;
  }
  #__cached_qxObjects = new Proxy({}, { get: (_, key) => this.getQxObject(key) });

  _createQxObjectImpl(id) {
    // defined by the class author
    switch (id) {
      case "foo":
        return "created a foo";
      default:
        return "dunno what a " + id + " is!";
    }
  }

  getQxObject(id) {
    // exists within `qx.core.MObjectId` (see `qx.core.Object|include`)
    return this._createQxObjectImpl(id);
  }
}

const thing = new MyQxThingOrWhatever();

console.log(thing.qxObjects.foo);
console.log(thing.qxObjects["foo"]);
console.log(thing.qxObjects.bar);
console.log(thing.qxObjects["0-weird_´´å^ß_text"]);
# output:
created a foo
created a foo
dunno what a bar is!
dunno what a 0-weird_´´å^ß_text is!

The actual cost of this syntax is incredibly low - near zero. Proxy instances are weightless compared to many other classes, and the code cost is defining one single property.

cboulanger commented 5 months ago

I like this idea too; although the "QxObject" name isn't as perfectly elegant as we might like for the QxObject API, we had to avoid conflicts and so the name has stuck.

As the backing for this code is the getQxObject / _createQxObjectImpl APIs, IMHO I think the qxObjects top level member is the right name

Couldn't we choose a more "elegant" name for the top-level key and preserve BC - there is no need to rename any of the existing infrastructure but this would allow us to develop the implementation in other ways if we so choose in the future, adding more functionalities that are independent of the qxObject architecture. I am not saying that there is a need for this (yet) but this would - in addition to look a bit nicer - preserve the freedom to do so.

johnspackman commented 5 months ago

Couldn't we choose a more "elegant" name for the top-level key and preserve BC

How about just objects: {}? I guess the bit Im not keen on is it being something completely different, eg components:{} declared but .getQxObject() elsewhere

cboulanger commented 5 months ago

How about just objects: {}? I guess the bit Im not keen on is it being something completely different, eg components:{} declared but .getQxObject() elsewhere

I like that idea: it is very generic so that one has a generative cache for any object, regardless of whether it is a widget component or anything else. The widget component architecture can then be built on top of this by describing it in the documentation.