FLUID-5948: Think of scheme for "orthogonal propagation" of listener namespaces in wide component hierarchies

Metadata

Source
FLUID-5948
Type
Improvement
Priority
Major
Status
Open
Resolution
N/A
Assignee
Antranig Basman
Reporter
Antranig Basman
Created
2016-08-24T11:43:21.927-0400
Updated
2021-07-29T01:46:53.944-0400
Versions
N/A
Fixed Versions
  1. 5.0
Component
  1. Framework

Description

An interesting question about listener namespacing was raised in #fluid-work at https://botbot.me/freenode/fluid-work/2016-08-24/?msg=71866852&page=1

In the following snippet, intended to appear in a base panel grade, we want to bind a panel's refresh generically to an event held in the core prefs editor:

listeners: {
            "{fluid.prefs.prefsEditor}.events.onPrefsEditorRefresh": "{fluid.prefs.panel}.refreshView",
            namespace: "someNameSpace"
        },

The namespace cannot be named statically since all the contributed listeners would collapse. Architecturally, it seems that this situation of wide-scale multicast listeners would best be treated at the model level of the architecture. However, this provides no recommendation for developers who don't have the opportunity to refactor in such a substantial way.

In the past we have had stopgaps based on various kinds of "singleTypeName"s. e.g. see the implementation of "resolveRoot" which allows a particular one of a component's gradeNames to be singled out as serving as "identity for a particular purpose": http://docs.fluidproject.org/infusion/development/Contexts.html#global-components-fluid-resolveroot-and-fluid-resolverootsingle- - we are getting to the point where this pattern has recurred enough times that we need to think of some scheme for structuring grade hierarchies in a generalised way that allow these names to emerge naturally. Then we could imagine a scheme for automatically deriving namespaces via expansion - such that, say, a panel SelfVoicing would register a listener namespaced SelfVoicing-refreshView etc.

Comments

  • Antranig Basman commented 2016-08-24T13:59:04.931-0400

    It seems there is scope for solving several problems at once here. Note that FLUID-5784 is still on the table, to remove the irregularity of our "type -> typeName" distinguished grade name. If we worked towards a model whereby we accepted a free hash of gradeNames rather than a list (fulfilling the overall trajectory of the framework in numerous areas - e.g. with distributeOptions, etc.) we could then refer to "names of gradeNames" as clear labels of their function. A user who just wanted to freely mix in a new gradeName would have to choose a new "namespace" for it... or perhaps we would grandfather in existing gradeNames as being "self-named".

  • Antranig Basman commented 2016-11-29T23:36:23.537-0500

    Clearly there are even worse problems than the one described here. In fluid-authoring, we have a situation where one component has multiple children all of the same type, which requires to relay an event (onRefreshView) to all children. What namespace can be given to this event?

    It's no accident that the headline issue here also involves a "refreshView" event which suggests that solving this problem is fundamental to a "new renderer" architecture. See FLUID-4260, etc. Note that we currently have no facility to fire events during component construction, due to fundamental framework limitations. See also https://wiki.fluidproject.org/display/fluid/Plan+to+Abolish+Invokers+and+Events - which was suggesting even removing the facility for registering listeners with no namespace, our only saving grace here!

  • Antranig Basman commented 2016-11-29T23:37:24.632-0500

    It is seeming like we would like the facility for namespaces themselves to behave a little like grades - for there to be derivation possible, but with constraints expressible with respect to "base grades" such that all listeners attached to it will sort together.

  • Antranig Basman commented 2016-12-01T23:48:47.548-0500

    Note that we had made an abortive attempt in FLUID-5866 to allow namespaces to be resolved hierarchically. The model of "extremal priorities" could/should be accommodated into this system, which the reverted FLUID-5866 implementation failed to do. This will be relevant to our work on "elements" described at https://wiki.fluidproject.org/display/fluid/Plan+to+Abolish+Invokers+and+Events - since we will need a clean way to "multiply" priorities (that is, to easily allocate "nonce" priorities within an existing class) in a way that we can currently only do with the "unnamed namespace".

    Given the existence of component ids as a source of uniqueness within the system, it seems possible that we will end up using references to these as our "extra source of uniqueness" - but this doesn't explain how we could retain the semantics of the ancient existing addListener scheme which supports old-fashioned uniqueness of listeners. It seems that we will simply end up with divergence between the programmatic and declarative forms of this API. It seems reasonable that a future version of the framework will prohibit the declarative use of listeners without a namespace, and provide sufficiently powerful primitives to compensate for the fact we can no longer address the "unnamed namespace" via this route.

  • Gregor Moss commented 2018-12-13T18:03:41.413-0500

    Much like the case outlined in @@Antranig Basman's second comment, I was attempting to use Options Distribution to attach some listeners to a number of sub-components throughout the component tree, but I saw that only one of them was firing when the listened-to event was fired. I then set up two separate Options Distribution entries to target two such sub-components separately, and still saw that only one of them worked as expected. I eventually realized, with @@Alan Harnum's help, that the cause of the issue was my use of a namespace in the listener definition. In the first case, the same namespace was assigned to all of the listeners, and I'd forgotten that namespaces are registered to a particular event more or less universally across all components, so they must be unique in order to avoid collisions. In the second case, the two separate records still had the same namespace, producing the same issue.

    The solution was to revert to the original setup of one Options Distribution record /without/ a namespace, so it was able to be registered by all components which matched the target IoCSS expression.

    Here is the code I was attempting to use initially (BROKEN):

    {
        record: {
            "{page}.events.onContextChangeRequested": {
                namespace: "stopAllVideos",
                funcName: "sjrk.storyTelling.page.stopAllVideos",
                args: ["{that}.managedViewComponentRegistry"]
            }
        },
        target: "{that blockManager}.options.listeners"
    }
    

    Here is the second attempt at a solution (BROKEN):

    {
        record: {
            "{page}.events.onContextChangeRequested": {
                namespace: "stopAllVideos",
                funcName: "sjrk.storyTelling.page.stopAllVideos",
                args: ["{that}.managedViewComponentRegistry"]
            }
        },
        target: "{that storyEditor blockManager}.options.listeners"
    },
    {
        record: {
            "{page}.events.onContextChangeRequested": {
                namespace: "stopAllVideos",
                funcName: "sjrk.storyTelling.page.stopAllVideos",
                args: ["{that}.managedViewComponentRegistry"]
            }
        },
        target: "{that storyViewer blockManager}.options.listeners"
    }
    

    Here is the code that works (WORKING):

    {
        record: {
            "{page}.events.onContextChangeRequested": {
                funcName: "sjrk.storyTelling.page.stopAllVideos",
                args: ["{that}.managedViewComponentRegistry"]
            }
        },
        target: "{that blockManager}.options.listeners"
    }
    
  • Antranig Basman commented 2020-09-30T07:40:52.914-0400

    If we worked towards a model whereby we accepted a free hash of gradeNames rather than a list

    Note that this is written up as FLUID-6439

  • Antranig Basman commented 2020-10-28T09:23:40.878-0400

    Further note on limitations/extensions of the priority scheme - in developing the new renderer we ended up with the following "sorted workflow cache" -

    0: {namespace: "enlistModel", workflowType: "global", workflowName: "enlistModel", gradeName: "fluid.modelComponent", workflowOptions: {…}, …}
    1: {namespace: "resolveResourceModel", workflowType: "global", workflowName: "resolveResourceModel", priority: {…}, gradeName: "fluid.modelComponent", …}
    2: {namespace: "renderMarkup", workflowType: "global", workflowName: "renderMarkup", priority: {…}, gradeName: "fluid.newRendererComponent", …}
    3: {namespace: "fetchTemplates", workflowType: "global", workflowName: "fetchTemplates", priority: {…}, gradeName: "fluid.templateResourceFetcher", …}
    4: {namespace: "evaluateContainers", workflowType: "global", workflowName: "evaluateContainers", priority: {…}, gradeName: "fluid.containerRenderingView", …}
    5: {namespace: "concludeComponentObservation", workflowType: "local", workflowName: "concludeComponentObservation", priority: {…}, gradeName: "fluid.component", …}
    6: {namespace: "fetchOldRendererTemplate", workflowType: "local", workflowName: "fetchOldRendererTemplate", priority: {…}, gradeName: "fluid.oldRendererComponent", …}
    7: {namespace: "initOldRendererComponent", workflowType: "local", workflowName: "initOldRendererComponent", priority: {…}, gradeName: "fluid.oldRendererComponent", …}
    8: {namespace: "notifyInitModelWorkflow", workflowType: "local", workflowName: "notifyInitModelWorkflow", priority: {…}, gradeName: "fluid.modelComponent", …}
    9: {namespace: "concludeComponentInit", workflowType: "local", workflowName: "concludeComponentInit", priority: {…}, gradeName: "fluid.component", …}
    length: 10
    

    The problem here is that "renderMarkup" ended up before "fetchTemplates" - because fetchTemplates was "after:enlistModel" and renderMarkup was "after:resolveResourceModel". We really wanted to specify that renderMarkup depends both on "resolveResourceModel" and "fetchTemplates".
    This increasingly suggests that workflows should be components.... priority namespaces should be components .... everything should be components!
    See Gilad.

  • Antranig Basman commented 2020-10-28T13:57:04.468-0400

    Yet another case, encountered in just the same day - in trying to ensure that the "rewriting handler" for an app was mounted after any static handlers in a Kettle app, we merely managed to ensure that it was mounted after its own handler - after registering the following set of priorities:

    17:11:46.113:  Registering request handler {
        "type": "kettle.request.http.static",
        "options": undefined,
        "method": "get",
        "route": "/*",
        "prefix": "/fluidTocDemoStatic"
    } with key fluidTocDemoStaticHandler
    17:11:46.120:  Registering request handler {
        "type": "kettle.request.http.static",
        "options": undefined,
        "method": "get",
        "route": "/*",
        "prefix": "/fluidTableOfContentsStatic"
    } with key fluidTableOfContentsStaticHandler
    17:11:46.124:  Registering request handler {
        "type": "kettle.request.http.static",
        "options": undefined,
        "method": "get",
        "route": "/*",
        "prefix": "/newRendererDemoStatic"
    } with key newRendererDemoStaticHandler
    17:11:46.127:  Registering request handler {
        "type": "kettle.request.http.static",
        "options": undefined,
        "method": "get",
        "route": "/*",
        "prefix": "/infusionStatic"
    } with key infusionStaticHandler
    17:11:46.130:  Registering request handler {
        "type": "fluid.renderer.rewriting.request",
        "route": "/*.html",
        "prefix": "",
        "options": {
            "mountedRoot": "%fluid-toc-demo/"
        },
        "method": "get",
        "priority": "after:fluidTocDemoStaticHandler"
    } with key fluidTocDemoRewritingHandler
    

    we ended up with the following ordering

    sortedHandlers: Array(5)
    0: {namespace: "fluidTocDemoStaticHandler", method: "get", type: "kettle.request.http.static", route: "/*", prefix: "/fluidTocDemoStatic", …}
    1: {namespace: "fluidTocDemoRewritingHandler", method: "get", type: "fluid.renderer.rewriting.request", route: "/*.html", prefix: "", …}
    2: {namespace: "fluidTableOfContentsStaticHandler", method: "get", type: "kettle.request.http.static", route: "/*", prefix: "/fluidTableOfContentsStatic", …}
    3: {namespace: "newRendererDemoStaticHandler", method: "get", type: "kettle.request.http.static", route: "/*", prefix: "/newRendererDemoStatic", …}
    4: {namespace: "infusionStaticHandler", method: "get", type: "kettle.request.http.static", route: "/*", prefix: "/infusionStatic", …}
    

    with the rewriting handler in position 2 - a request for the fluidTableOfContentsStaticHandler then ended up hitting it.