frontend_editing
frontend_editing copied to clipboard
Decouple design from controller
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 :unamused:
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 :wink:
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:
checkedattributselectedattributdataattributopenattribut- 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?
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?
@Messj1 It looks great! Go ahead with it, what is the next step you plan to do with this?
@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 :zany_face:
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:
-
initialize
- register data change listener --> trigger next steps
-
fetch data
- look up predefined HTML data attributes
- or check if it is an input element
-
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:
- initialized find and bind in GUI after DOM is ready
// @module Typo3/CMS/FrontendEditing/GUI
init: function() {
...
this.findAndBindData();
}
- register it later somewhere - we don't care :wink:
// inline
GUI.appendData(dataHandler);
- or like runtime just in time --> for
datatypeinput 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.
@Messj1 Great idea, right now all is interconnected.
@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
Editorwhich is not a component. It is to much core feature of a frontend editing application. BTW: TheEditorshould be placed in aRegistryor something like this. - It has to match also with the
FrontendEditorbecause 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

Hint: the DataHandler get coupled with GUI due the DataBinder, so as they are truly independent! :muscle: :godmode:
What do we win with all this heavy load bootstrap: :eyes:
- 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 :wink:
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 :scream:
- or the
ComponentgiveGUIa hint what otherComponentit would like to meet :thinking:
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! :exploding_head:
Updated on 2020-03-02: simplified flow; abstracted data access in business layer with Interpreter

@Messj1 The only thing I can add is awesome! Just go ahead with it :)
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)

I also made a rough 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.
@Messj1 This is just something I have had in my head before. So cool! Which tool are you using for this? Just curious :)
@MattiasNilsson Yeah, cause I used to base structure of already existing components. So names are already known. :wink:
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.
@Messj1 Thanks for all the nice work! 👍
@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:
Parserwith the already parsed information.DataHandlerwhich 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)DataBinderto connectParserandHandlertogether and configure handlers.
I also added xstate.fsm as a nice Finit State Machine (FSM) :nerd_face:
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:
- 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 theDataHandlerwould become aDataBroker. :arrow_right: some kind of extendableViewModel - 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
disabledand transition to it from statepanelClosedlistening on fullscreen whilepanelOpenis goin into statehidden - (simillary to the previous one) combine FSM and use transition in events. eg: add state
fullscreenand add transitionhideButtonto it from statepanelClosedlistening on fullscreen whilepanelOpenhas transitionhide - create different type of
DataHandler. Eg.AnimationDataHandler - handle it outside, like in CSS.
.panelOpen.fullscreen{...} .panelOpen{...} .fullscreen{...}
- create combined FSM and use transition in events. eg: add state
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
@Messj1 I think solution 1 is the best to go with. Then it feels more organised :)