Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Decouple design from controller #488

Open
Messj1 opened this issue Feb 24, 2021 · 13 comments
Open

Decouple design from controller #488

Messj1 opened this issue Feb 24, 2021 · 13 comments

Comments

@Messj1
Copy link
Contributor

Messj1 commented Feb 24, 2021

Hi

As part of improve code quality #432 and to improve extend ability there should be more separation of design and frontend-editing functionality.

I see following wrong dependencies flow:

  • FrontendEditing -> own iframe
    FrontendEditing should use FrontendEditing.GUI since it is part of an process and GUI is the controller
  • FrontendEditing.GUI -> HTML Element
    FrontendEditing.GUI has to be simply a controller. So no animation at all since it can not know the template.

In my opinion we should not attache events on server generated templates.
The frontend editing GUI appearance should be part of the design or else you can only change color 😒

So what should GUI be?

It is used as

  • Controller
    Initialize frontend editing an configure them
  • HTML data binding
    Of courseGUI has to deal with data but in a predefined way configurable with identifiers

So it has to contain only bootstrap code and base composition logic code

What should be part of design

Everything that is generated on the server. So if it has to communicate we have to use id attribute because it should be unique or else it is to unclear what to do in GUI.

So as a good example lets have look at a simple slide and open problem.

checkbox hack

So we could use a good old classic hack to save and transfer the state (usable in GUI with t3-frontend-editing__right-bar as id)

#t3-frontend-editing__right-bar + .t3-frontend-editing__right-bar {
    transition: right 0.2s linear;
}

#t3-frontend-editing__right-bar:checked + .t3-frontend-editing__right-bar {
    right: 0;
}

#t3-frontend-editing__right-bar:checked + .t3-frontend-editing__right-bar .top-right-title .right-bar-button {
    left: 0;
}
<input type="checkbox" id="t3-frontend-editing__right-bar--open"/>
<div class="t3-frontend-editing__right-bar">
  <div class="top-right-bar-wrapper ">
    <div class="top-right-relative">
      <div class="top-right-title">
        <label for="t3-frontend-editing__right-bar--open" class="right-bar-button icons icon-icons-arrow-double"></label>
        ...
      </div>
      ...
    </div>
    ...
  </div>
</div>

Also possible to use radio instead of checkbox to get an real accordion 😉

details and summary elements

Good old details elements have the advanced, that the have an open attribute which indicate open or close.

details[open] .icon-icons-arrow-down:before {
    content: "\e90f";
}
<details open>
  <summary class="accordion">
    ...
    <div class="element-action">
      ...
      <span class="icons icon-icons-arrow-down trigger"></span>
    </div>
  </summary>
  <div class="accordion-content">
  ...
  </div>
</details>

separate javascript based widgets

There are plenty technologies for js based widgets.
From Web Components to attached function calls.
From scripts to suits.

Conclusion

GUI should have different strategies to fetch data from the attached bindings like:

  • checked attribut
  • selected attribut
  • data attribut
  • open attribut
  • and mybe more

At least there should be the opportunity to write an own editor and use only the base logic from frontend editing to transfer and handle data.
I would love to see an different frontend editing GUI implementation.

Any opinions?

@Messj1
Copy link
Contributor Author

Messj1 commented Feb 25, 2021

I created some examples an put them on gist:
https://gist.github.com/Messj1/de7a069c2199851fe3711a3729d96271

Once downloaded you can put them in <project-folder>/storybook/stories/design/RightBar

@MattiasNilsson can you please check this out and give me our opinions?

@MattiasNilsson
Copy link
Contributor

@Messj1 It looks great! Go ahead with it, what is the next step you plan to do with this?

@Messj1
Copy link
Contributor Author

Messj1 commented Feb 25, 2021

@MattiasNilsson Good question ... I think the goal should be to simplify the whole HTML handling in a structural way.
=> in general separate: Data, Logic, Control and Design

In fact i don't know what would be better, to have a listening or a callable system. Maybe both 🤪

Any comment is welcome!

Next steps

There are generated HTML files from the server and some CSS which handle the look and feel.

Data exchanging

So next step would be to declare the listening data flow respectively data exchange.
For example:

simple state binding with local storage save and restore:

<input type="checkbox" data-gui-datatype="input" data-gui-persist="local" name="wizzard.plugins" />

simple class toggle:

<div data-gui-datatype="bind" data-gui-name="screenloading" data-gui-class="toogleClassname">
</div>

simple attribute (open, disabled, checked, selected) toggle:

<details data-gui-datatype="input" data-gui-attribute="open">
</details>

Or something more complex example with component involved

<!-- listening component state -->
<input type="checkbox" data-gui-datatype="bind" data-gui-component="D3IndentedTree" name="isSearchRunning" />
<!-- listening event and call component -->
<input type="text" data-gui-datatype="input" data-gui-component="D3IndentedTree" name="treeFilter" />

Maybe some structure like this

data-gui: {
    //indicates an data exchange element
    "datatype": {
        "default": null
        "value": ["bind", "input"]
    },
    //component which is used to call and fetch data
    "component": {
        "default": "GUI",
        "value": ["GUI", "Editor", "D3IndentedTree"]
    },
    //Name of action, variable or persist key
    "name": {
        "default": element is input? input.name
        "value": *
    },
    //saves the "return" value in the selected storage. if no return save the getter
    "persist": {
        "default": null
        "value": ["local", "session", "server"]
    },
    //toggle the CSS class
    "class": {
        "default": null
        "value": "*",
        "condition": [
            "return" value is boolean,
        ]
    },
}

DataBinder

Afterward DataBinder and DataHandlers module is used to fetch the data:

  1. initialize

    1. register data change listener --> trigger next steps
  2. fetch data

    1. look up predefined HTML data attributes
    2. or check if it is an input element
  3. trigger data event

So maybe there is also an Factory involved to get the right DataHandler according to data-gui-datatype.

GUI

The binding could be done in 3 ways as followed:

  1. initialized find and bind in GUI after DOM is ready
// @module Typo3/CMS/FrontendEditing/GUI
init: function() {
    ...
    this.findAndBindData();
}
  1. register it later somewhere - we don't care 😉
// inline
GUI.appendData(dataHandler);
  1. or like runtime just in time --> for datatype input only.
<input onclick="GUI.handleData" />

At least there would be much less code in GUI.
In my opinion it should only bootstraps the whole application.

@MattiasNilsson
Copy link
Contributor

@Messj1 Great idea, right now all is interconnected.

@Messj1
Copy link
Contributor Author

Messj1 commented Mar 1, 2021

@MattiasNilsson Yes, that is the reason why it is currently not testable. It is also (k)im possible to extend.
I made some overview what the new GUI architecture could look like.
It is currently in draft:

  • I am not sure how to handle the iFrame. As component would be nice.
  • It has to match with the Editor which is not a component. It is to much core feature of a frontend editing application.
    BTW: The Editor should be placed in a Registry or something like this.
  • It has to match also with the FrontendEditor because it should contain the business logic. The how to build.

I split into several categories to show how they should work independently:

  • Server Generated
    Server side initialization (runtime configurable)
  • Bootstrap
    Bootstrap process used to initialize the app
  • Resource Binding
    Data binding process, lookup DOM and handle data exchange
  • GUI Controller
    Binding all together. Add listeners, configure and initialize.
  • Component Controller
    DataHandler holds the representation and Component Controller handle data similare like F.* currently

FrontendEditing - bootstrap process

Hint: the DataHandler get coupled with GUI due the DataBinder, so as they are truly independent! 💪 :godmode:

What do we win with all this heavy load bootstrap: 👀

  • simple architecture to implement new features
  • freedom to switch single component logic
  • use different skins as much as you like
  • independent from resource - yes HTML is common sense, but a js generated template is also possible.
  • code gets cleaner

I see no problem to make this changes without breaking anything because we are able to implement 2 ComponentFactory and 2 DataBinder, one as legacy mode and one with the new data-* approach.

By the way we should also replace the javascript GlobalEventHandler (onclick, ...) with help of DataBinder cause of CSP.

Maybe something like

<div data-gui-datatype="trigger" data-gui-component="CE" data-gui-name="dragAndDrop">
   ...
</div>

Any feedback is welcome 😉

@Messj1
Copy link
Contributor Author

Messj1 commented Mar 1, 2021

I have now extended the diagram with the missing modules to get a real overview of the complete idea.

the compatibility check (red dashed line) is only the consequence of the idea to create factories outside the loader in server generated code. Would also be possible to handle in loader.

As we can see, the main work part of GUI is handled in components (see relation count).
DataHandler coupling with Component is handled by GUI controller.
HTML coupling with component is handled by DataHandler.

The Component to EditorRegistry relation exists only for EditorComponent.

Missing part: How does Component get partials (related component like an editor toolbar)

  • maybe some signaling would help 😱
  • or the Component give GUI a hint what other Component it would like to meet 🤔

And yes, I know that the Business Layer and especially Data Layer part is not finished. But the Idea is to get rid of undocumented calls since this is planed highly extendable. So everything in Business Layer get started by Action. Action only! 🤯

Updated on 2020-03-02: simplified flow; abstracted data access in business layer with Interpreter
20200302 - frontend_editing - architecture

@MattiasNilsson
Copy link
Contributor

@Messj1 The only thing I can add is awesome! Just go ahead with it :)

@Messj1
Copy link
Contributor Author

Messj1 commented Mar 16, 2021

I finished the big picture as an communication and interface overview.
There are some open issues like versioning. Eg. It is possible that Executers of different versions or implementation get used parallel if components uses different Action (Interface) version.

Note: Engine is a extendable State Machine and Executer package contains Interpreter (API) and Action (ActionData Interface)
20200316-T3 Frontend Editing - Big Picture

I also made a rough component overview.
20200316-T3 Frontend Editing - Component Overview

More details make no sense without prototype.

  • So next steps is to made a legacy DataBinder to replace direct GUI calls and create first simple component.
  • Afterward a nice MVVM like DataBinder would be nice.

@MattiasNilsson
Copy link
Contributor

@Messj1 This is just something I have had in my head before. So cool! Which tool are you using for this? Just curious :)

@Messj1
Copy link
Contributor Author

Messj1 commented Mar 17, 2021

@MattiasNilsson Yeah, cause I used to base structure of already existing components. So names are already known. 😉
The big change is the Engine and Excecuter part which will gone be the game changer.
BTW is every package planed to be replaceable. So everything can be connect as it prefer.

I use diagrams.net. Previously known as draw.io.
You can find the latest version in my branch:
https://github.com/Messj1/frontend_editing/tree/488-Decouple-design-from-controller/Documentation/Assets

I use only the simplest functions. There are plenty of nice functions like the native mermaid support but this becomes handy in a later phase.
There is also a layering system and you are able to preview data online with a data link. For example with the layered LegacyDataBinder
This becomes the next steps to close this issue an start another one with goal to rewrite the monolithic system into a modular (extendable) one.

@MattiasNilsson
Copy link
Contributor

@Messj1 Thanks for all the nice work! 👍

@Messj1
Copy link
Contributor Author

Messj1 commented Mar 22, 2021

@MattiasNilsson found some small time slice to implement a simple prototype. Can you please have a look on it?

https://github.com/Messj1/frontend_editing/tree/488-Decouple-design-from-controller/Resources/Public/JavaScript/DataBinder

There is following structure:

  • Parser with the already parsed information.
  • DataHandler which is the owner of the HTML and the component. Acts as a MVVM like ViewModel. It configures the HTML and Components. It also Listens on both to do the handling. (Bidirectional)
  • DataBinder to connect Parser and Handler together and configure handlers.

I also added xstate.fsm as a nice Finit State Machine (FSM) 🤓
As a test component i implemented ToggleState and glued it in storybook with the DataBinderWrapper.
The main idea (currently) is to have an FSM as frontend (GUI) models and a extendable FSM as business model.

Storybook Test
I have adapted the following Story /story/design-bar-right--default

  • Default: works as expected.
    Added 2 new states as described in Diagram: if disabled get exited it will fall in lock mode for 1s, and if enable get toggled it will delay first for 1s before change state
  • Checkbox: change the checkbox with id t3-frontend-editing__right-bar--open (<input type="checkbox" id="t3-frontend-editing__right-bar--open" />) as the state of the rightPanel change

I'm insecure what would be better:

  1. Using the FSM in a Component. This means to handle the HTML in the component and transparently call it with the DataHandler.
    In fact: The Component would extends the DataHandler. Then the DataHandler would become a DataBroker.
    ➡️ some kind of extendable ViewModel
  2. Using the FSM directly as an Component. So the Component would be a Model without any HTML Relation.
    Eg. TYPO3/CMS/FrontendEditing/Component/Presentation/ToggleState
    In fact: The combined states handling (eg. ['panelOpen', 'fullscreen']) has to get somewhere. I see following options:
    • create combined FSM and use transition in events. eg: add state disabled and transition to it from state panelClosed listening on fullscreen while panelOpen is goin into state hidden
    • (simillary to the previous one) combine FSM and use transition in events. eg: add state fullscreen and add transition hideButton to it from state panelClosed listening on fullscreen while panelOpen has transition hide
    • create different type of DataHandler. Eg. AnimationDataHandler
    • handle it outside, like in CSS. .panelOpen.fullscreen{...} .panelOpen{...} .fullscreen{...}

I think solution 1. would lead to create a DataHandler for every Component. This gives us an extra portion flexibility but i guess this is mostly not needed.

Would love to hear from you.

edit on 2021-03-25: added a show case branch an made git pages of this issue

@MattiasNilsson
Copy link
Contributor

@Messj1 I think solution 1 is the best to go with. Then it feels more organised :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants