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

Virtual DOM and JSX support #9967

Closed
wants to merge 21 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 5 additions & 7 deletions docs/README.md
Expand Up @@ -126,7 +126,7 @@ qx.Class.define("myapp.Application", {
If you have worked with javascript before you may find that it looks like
javascript but also different from most javascript you have seen to date. This
is because qooxdoo introduces both a rich set of widgets as well as class based
object orientation into the language. There is quote a lot of documentation on
object orientation into the language. There is quite a lot of documentation on
these toppics in the next few sections.

And yes, this is the complete program, there is neither HTML nor CSS. The
Expand All @@ -137,14 +137,12 @@ your application a distinctive look.
Try editing the file a bit. You will notice that the server detects your
modifications and recompiles the application automatically.

If you look at the files that are created by the compiler (in the `compiled/` directory),
If you look at the files that are created by the compiler (in the `compiled/source` directory),
you might notice that the resulting code is quite large in size. This is because even
the tiniest application makes use of qooxdoo's core classes, and the compiler produces
a large number of artefacts that are needed for quick recompilation, but not for the
functioning of your app. What you will finally deploy is much smaller (in our case, it is
in the `compiled/source/myapp` folder). As your app grows, the size of that folder will grow
linearly with your new code (and with whatever additional features of qooxdoo
your code uses).
a large number of artefacts that are needed for quick recompilation and debugging.
The compiler can also produce a deployable version of your app wich will be much smaller
and not contain any of the debugging support. More on that later.

## Reading on

Expand Down
1 change: 1 addition & 0 deletions docs/_sidebar.md
@@ -1,6 +1,7 @@
- Overview
- [Contents](/contents.md)
- [Getting Started](/?id=getting-started)
- [Qooxdoo Tutorials](/tutorials.md)
- [Qooxdoo Apps](/apps.md)
- [About](/about.md)

Expand Down
2 changes: 1 addition & 1 deletion docs/core/data_binding/data_binding.md
Expand Up @@ -165,7 +165,7 @@ What if you want to to bring your own code to the generated model classes or if
- Add your code as a mixin to the created model classes.
- Use your own class instead of the created model classes.

Take a look at the API-Documentation of the [qx.data.store.IStoreDelegate](apps://apiviewer/index.html#qx.data.store.IStoreDelegate) to see the available methods and how to implement them.
Take a look at the API-Documentation of the [qx.data.store.IStoreDelegate](apps://apiviewer/#qx.data.store.IStoreDelegate) to see the available methods and how to implement them.

## Controller Component

Expand Down
14 changes: 7 additions & 7 deletions docs/core/gestures.md
Expand Up @@ -4,18 +4,18 @@ Based on the `low-level pointer events pointer.md#pointer_events`,
the following gesture events are available in all three of qooxdoo's GUI
toolkits (qx.Desktop, qx.Mobile and qx.Website):

- [tap](apps://apiviewer/index.html#qx.event.type.Tap):
- [tap](apps://apiviewer/#qx.event.type.Tap):
A brief finger/stylus tap or mouse click
- [longtap](apps://apiviewer/index.html#qx.event.type.Tap):
- [longtap](apps://apiviewer/#qx.event.type.Tap):
A tap that is held down for 500 milliseconds
- [dbltap](apps://apiviewer/index.html#qx.event.type.Tap):
- [dbltap](apps://apiviewer/#qx.event.type.Tap):
Two taps in quick succession
- [swipe](apps://apiviewer/index.html#qx.event.type.Swipe):
- [swipe](apps://apiviewer/#qx.event.type.Swipe):
Horizontal or vertical swipe motion
- [rotate](apps://apiviewer/index.html#qx.event.type.Rotate):
- [rotate](apps://apiviewer/#qx.event.type.Rotate):
Two contact points (e.g. fingers) rotating around a central point
- [pinch](apps://apiviewer/index.html#qx.event.type.Pinch):
- [pinch](apps://apiviewer/#qx.event.type.Pinch):
Two contact points moving towards or away from each other
- [track](apps://apiviewer/index.html#qx.event.type.Track):
- [track](apps://apiviewer/#qx.event.type.Track):
Cross-device equivalent to mouse drag (fired only between
pointerdown and pointerup)
2 changes: 1 addition & 1 deletion docs/core/pointer.md
Expand Up @@ -31,7 +31,7 @@ The following events are available in all of qooxdoo's GUI toolkits:
- pointercancel

[Pointer event API
documentation](apps://apiviewer/index.html#qx.event.type.Pointer)
documentation](apps://apiviewer/#qx.event.type.Pointer)

Note that not all events mentioned in the specification are listed here.
We chose to only implement the event types required for qooxdoo's
Expand Down
118 changes: 110 additions & 8 deletions docs/desktop/gui/html.md
Expand Up @@ -6,11 +6,11 @@ This document describes the ideas and concepts behind the classes in the `qx.htm
Idea
----

The classes in `qx.html` are wrappers around native DOM elements, which were created primarily to solve one major issue:
The classes in `qx.html` form a Virtual DOM, which on the the server allow DOM-like manipulation and then serialization
into HTML text, and on the browser the Virtual DOM is wrappers around native DOM elements which are created only when
absolutely necessary.

**Automatically keeping care of DOM manipulation and creation while dealing with large number of elements.**

In details this means:
This solves a number of problems:

> - **Automatic performance**: Programmatically constructing DOM hierarchies is hard to get fast because the order in which elements are nested can heavily influence the runtime performance. What `qx.html.Element` tries to do is keep the number of element instances to the minimum actually needed (DOM nodes are expensive, in terms of both performance and memory) and to insert the DOM nodes in an efficient manner. Further, all changes to the DOM are cached and applied in batch mode, which improves the performance even more.
> - **Normalized API**: Working with HTML DOM elements usually involves many browser differences, especially when it comes to reading and setting of attributes or styles. For each style, one has to remember whether a normalization method should be called or if the value can be set directly. `qx.html.Element` does this kind of normalization transparently. The browser normalization is based on the [existing low-level APIs](../../core/tech_website_apis.md).
Expand All @@ -21,8 +21,10 @@ Typical Use Cases

> - Building a widget system on top
> - Massively building DOM elements from data structures
> - Sharing DOM-specific code between client and server

It may be used for smaller things as well, but incurs a large overhead. The size of the API, additional to a basic low-level package of qooxdoo is about 20 KB (5 KB gzipped). Also it consumes a bit more memory when all underlying DOM elements are created. Keep in mind that the instances are around all the time. Even when all jobs for a instance are done at the moment.
It may be used for smaller things as well, but incurs a large overhead. Also it consumes a bit more memory when all
underlying DOM elements are created.

Features
--------
Expand All @@ -31,7 +33,8 @@ Features
> - Full cross-browser support through usage of low-level APIs e.g. `setStyle()`, `getAttribute()`, ...
> - Advanced children handling with a lot of convenience methods e.g. `addAfter()`, ...
> - Reuse existing markup as a base of any element via `useMarkup()`
> - Reuse an existing DOM node via `useElement()`
> - Reuse an existing DOM node via `useNode()`
> - Serialize into an HTML string
> - Powerful visibility handling to `include()` or `exclude()` specific sub trees
> - Support for scrolling and scroll into view (`scrollTo()`, `scrollIntoView()`, ...)
> - Integration of text selection APIs (`setSelection()`, `getSelection()`, ...)
Expand Down Expand Up @@ -65,9 +68,108 @@ This element is used to create iframes to embed content from other sources to th

Renders a [HTML5 Canvas](https://html.spec.whatwg.org/multipage/scripting.html#the-canvas-element) to the DOM. Has methods to access the render context as well to configure the dimensions of the Canvas.


Code Sharing
------------
When you create instances of `qx.html.Element` on the server, at some point you can call `serialize()` to convert it into
an HTML string which the server can pass to the client; this is useful because it provides a structured mechanism for
defining and manipulating the DOM before it reaches the browser.

However, rather than simply creating instances of `qx.html.Element`, you can derive classes from it and this has a
particular advantage in that your code can be used on the client and the server and will be able to connect to the
browser's "real" DOM nodes.

For example, consider this class:

```
qx.Class.define("mypackage.MySignupForm", {
extend: qx.html.Element,

construct() {
this.base(arguments, "div");
let body = this.getBody();
body.add(<h1>Welcome to My Corporate Website</h1>);
body.add(
<p>This website is for internal use by authorised personnel only
);
body.add(this.getQxObject("form"));
body.add(
<p><a href="#">Click here if you have <b>forgotten your password</b></a></p>
);
this.setStyle("max-width", "600px");
},

members: {
_createQxObjectImpl(id) {
switch(id) {
case "form":
let form = <form method="post" action="#"></form>;
oetiker marked this conversation as resolved.
Show resolved Hide resolved
form.add(this.getQxObject("edtEmail"));
form.add(this.getQxObject("edtPassword"));
form.add(this.getQxObject("btnLogin"));
form.addListener("submit", evt => evt.preventDefault());
return form;

case "edtEmail":
return new qx.html.Input("text");

case "edtPassword":
return new qx.html.Input("password");

case "btnLogin":
var btn = new qx.html.Element("button");
btn.addListener("click", async evt => {
let email = this.getQxObject("edtEmail").getValue();
let password = this.getQxObject("edtPassword").getValue();
this.fireDataEvent("login", { email, password });
});
return btn;
}
return this.base(arguments, id);
}
}
});
```

(The above example uses JSX to embed HTML inside Javascript - this will not work with the Python toolchain, you *must*
use the new compiler)

Note that this class does *not* use the `qx.ui.*` classes - it is just about manipulating the DOM, and is
the sort of lightweight code you might use as part of the interaction with a user long before they are able to start
a full blown Desktop or Mobile Qooxdoo application.

The class above is quite obviously all about defining the DOM elements needed for a simple signup form,
and you can see that it attaches event listeners to those (Virtual) DOM elements as well as set CSS styles etc.
The Login button (`btnLogin`) performs an action when it is clicked, and you could add some validation, an AJAX
call, etc.

You could use this class on the browser and create an instance of it - the Virtual DOM will create the "real" DOM
when it is needed and you have a working signup form. However, the disadvantage of that approach is that if the
DOM you are trying to create is supposed to be on the page from the beginning, the page will appear empty to the
user until your code is downloaded and run. The page will "flash" and re-layout as the new DOM is added in, and this
looks ugly. Worse, if there is any kind of runtime error, your page will simply remain blank.

However - this class can be used on both the server and the client, because on the server you would be able to
create an instance and then call `.serialize()` in order to output the form as HTML text, which would be included
in the .html page that the browser's requesting. This allows the browser to render the entire DOM, including
the DOM for your form, in one pass - there is no delay or awkward flashing/relayout of the page.

In order for your event handlers (eg the Login button's click event) to work, you have to create an instance of
this class on the browser and then immediately attach it to the "real" DOM.

When you have a tree of Virtual DOM instances (eg in the form above, there are input fields and a button) the
Virtual DOM can automatically join them to the "real" DOM as well, provided that you identify each one individually
via the QxObjectID mechanism.


The Queue
---------

Internally, most actions applied to the instances of `qx.html.Element` are applied lazily to the DOM. All style or attribute changes are queued, for example, to set at one time. This is especially useful to allow bumping out changes at once to the browser even when these happen in multi places, and more importantly, on more than one element.
Internally, most actions applied to the instances of `qx.html.Element` are applied lazily to the DOM. All style or
attribute changes are queued, for example, to set at one time. This is especially useful to allow bumping out changes
at once to the browser even when these happen in multi places, and more importantly, on more than one element.

Even things like focus handling or scrolling may be queued. It depends on if the element is currently visible, etc., to determine whether these are queued. `focus` often makes more sense when it is directly executed, as the following code may make assumptions that the changes are applied already. Generally Qooxdoo allows it to apply most changes without the queue, as well, using a `direct` flag which is part of most setters offered by `qx.html.Element`.
Even things like focus handling or scrolling may be queued. It depends on if the element is currently visible, etc., to
determine whether these are queued. `focus` often makes more sense when it is directly executed, as the following code
may make assumptions that the changes are applied already. Generally Qooxdoo allows it to apply most changes without
the queue, as well, using a `direct` flag which is part of most setters offered by `qx.html.Element`.
2 changes: 1 addition & 1 deletion docs/desktop/gui/pointer.md
Expand Up @@ -19,6 +19,6 @@ The following events are available in all of qooxdoo's GUI toolkits:
- pointerup
- pointercancel

[Pointer event API documentation](apps://apiviewer/index.html#qx.event.type.Pointer)
[Pointer event API documentation](apps://apiviewer/#qx.event.type.Pointer)

Note that not all events mentioned in the specification are listed here. We chose to only implement the event types required for qooxdoo's widgets, which is one reason why we don't like to call this implementation a polyfill, even if in most areas it conforms to the the spec and is quite broad in scope.
4 changes: 2 additions & 2 deletions docs/desktop/gui/window_management.md
Expand Up @@ -80,5 +80,5 @@ windows to a root widget.

## Demos and API

To find out more, you can check the [desktop demo](apps://demobrowser/index.html#widget~Desktop.html)
and the [API reference](apps://apiviewer/index.html#qx.ui.window).
To find out more, you can check the [desktop demo](apps://demobrowser/#widget~Desktop.html)
and the [API reference](apps://apiviewer/#qx.ui.window).
2 changes: 1 addition & 1 deletion docs/desktop/layout/basic.md
Expand Up @@ -48,4 +48,4 @@ API
---

Here is a link to the API of the layout manager:
[qx.ui.layout.Basic](apps://apiviewer/index.html#qx.ui.layout.Basic)
[qx.ui.layout.Basic](apps://apiviewer/#qx.ui.layout.Basic)
4 changes: 2 additions & 2 deletions docs/desktop/layout/box.md
Expand Up @@ -72,5 +72,5 @@ API

Here is a link to the API of the layout manager:

[qx.ui.layout.HBox](apps://apiviewer/index.html#qx.ui.layout.HBox)
[qx.ui.layout.VBox](apps://apiviewer/index.html#qx.ui.layout.VBox)
[qx.ui.layout.HBox](apps://apiviewer/#qx.ui.layout.HBox)
[qx.ui.layout.VBox](apps://apiviewer/#qx.ui.layout.VBox)
2 changes: 1 addition & 1 deletion docs/desktop/layout/canvas.md
Expand Up @@ -53,4 +53,4 @@ API
---

Here is a link to the API of the layout manager:
[qx.ui.layout.Canvas](apps://apiviewer/index.html#qx.ui.layout.Canvas)
[qx.ui.layout.Canvas](apps://apiviewer/#qx.ui.layout.Canvas)
2 changes: 1 addition & 1 deletion docs/desktop/layout/dock.md
Expand Up @@ -57,4 +57,4 @@ API
---

Here is a link to the API of the layout manager:
[qx.ui.layout.Dock](apps://apiviewer/index.html#qx.ui.layout.Dock)
[qx.ui.layout.Dock](apps://apiviewer/#qx.ui.layout.Dock)
2 changes: 1 addition & 1 deletion docs/desktop/layout/flow.md
Expand Up @@ -51,4 +51,4 @@ API
---

Here is a link to the API of the layout manager:
[qx.ui.layout.Flow](apps://apiviewer/index.html#qx.ui.layout.Flow)
[qx.ui.layout.Flow](apps://apiviewer/#qx.ui.layout.Flow)
2 changes: 1 addition & 1 deletion docs/desktop/layout/grid.md
Expand Up @@ -59,4 +59,4 @@ API
---

Here is a link to the API of the layout manager:
[qx.ui.layout.Grid](apps://apiviewer/index.html#qx.ui.layout.Grid)
[qx.ui.layout.Grid](apps://apiviewer/#qx.ui.layout.Grid)
2 changes: 1 addition & 1 deletion docs/desktop/layout/grow.md
Expand Up @@ -28,4 +28,4 @@ API
---

Here is a link to the API of the layout manager:
[qx.ui.layout.Grow](apps://apiviewer/index.html#qx.ui.layout.Grow)
[qx.ui.layout.Grow](apps://apiviewer/#qx.ui.layout.Grow)
2 changes: 1 addition & 1 deletion docs/desktop/widget/button.md
Expand Up @@ -32,4 +32,4 @@ API
---

Here is a link to the API of the Widget:
[qx.ui.form.Button](apps://apiviewer/index.html#qx.ui.form.Button)
[qx.ui.form.Button](apps://apiviewer/#qx.ui.form.Button)
4 changes: 2 additions & 2 deletions docs/desktop/widget/canvas.md
Expand Up @@ -36,10 +36,10 @@ Demos

Here are some links that demonstrate the usage of the widget:

- [Canvas demo](apps://demobrowser/index.html#widget-Canvas.html)
- [Canvas demo](apps://demobrowser/#widget-Canvas.html)

API
---

Here is a link to the API of the Widget:
[Canvas API](apps://apiviewer/index.html#qx.ui.embed.Canvas)
[Canvas API](apps://apiviewer/#qx.ui.embed.Canvas)
2 changes: 1 addition & 1 deletion docs/desktop/widget/composite.md
Expand Up @@ -27,4 +27,4 @@ API
---

Here is a link to the API of the Widget:
[qx.ui.container.Composite](apps://apiviewer/index.html#qx.ui.container.Composite)
[qx.ui.container.Composite](apps://apiviewer/#qx.ui.container.Composite)
4 changes: 2 additions & 2 deletions docs/desktop/widget/html.md
Expand Up @@ -32,10 +32,10 @@ Demos

Here are some links that demonstrate the usage of the widget:

- [HTML embed demo](apps://demobrowser/index.html#widget-HtmlEmbed.html)
- [HTML embed demo](apps://demobrowser/#widget-HtmlEmbed.html)

API
---

Here is a link to the API of the Widget:
[HTML Embed API](apps://apiviewer/index.html#qx.ui.embed.Html)
[HTML Embed API](apps://apiviewer/#qx.ui.embed.Html)
4 changes: 2 additions & 2 deletions docs/desktop/widget/iframe.md
Expand Up @@ -25,10 +25,10 @@ Demos

Here are some links that demonstrate the usage of the widget:

- [Iframe demo](apps://demobrowser/index.html#widget-Iframe.html)
- [Iframe demo](apps://demobrowser/#widget-Iframe.html)

API
---

Here is a link to the API of the Widget:
[API for Iframe](apps://apiviewer/index.html#qx.ui.embed.Iframe)
[API for Iframe](apps://apiviewer/#qx.ui.embed.Iframe)
2 changes: 1 addition & 1 deletion docs/desktop/widget/label.md
Expand Up @@ -34,4 +34,4 @@ API
---

Here is a link to the API of the Widget:
[qx.ui.basic.Label](apps://apiviewer/index.html#qx.ui.basic.Label)
[qx.ui.basic.Label](apps://apiviewer/#qx.ui.basic.Label)
2 changes: 1 addition & 1 deletion docs/desktop/widget/resizer.md
Expand Up @@ -31,4 +31,4 @@ API
---

Here is a link to the API of the Widget:
[qx.ui.container.Resizer](apps://apiviewer/index.html#qx.ui.container.Resizer)
[qx.ui.container.Resizer](apps://apiviewer/#qx.ui.container.Resizer)
2 changes: 1 addition & 1 deletion docs/desktop/widget/scroll.md
Expand Up @@ -35,4 +35,4 @@ API
---

Here is a link to the API of the Widget:
[qx.ui.container.Scroll](apps://apiviewer/index.html#qx.ui.container.Scroll)
[qx.ui.container.Scroll](apps://apiviewer/#qx.ui.container.Scroll)
6 changes: 3 additions & 3 deletions docs/desktop/widget/scrollbar.md
Expand Up @@ -23,12 +23,12 @@ Demos

Here are some links that demonstrate the usage of the widget:

- [Scroll bar demo](apps://demobrowser/index.html#widget~ScrollBar.html)
- [Scroll bar demo](apps://demobrowser/#widget~ScrollBar.html)
- [A simple scroll container demo](apps://demobrowser/#ui~ScrollContainer_Simple.html)

API
---

Here is a link to the API of the Widget:
[qx.ui.core.ScrollBar](apps://apiviewer/index.html#qx.ui.core.scroll.ScrollBar)
[qx.ui.core.NativeScrollBar](apps://apiviewer/index.html#qx.ui.core.scroll.NativeScrollBar)
[qx.ui.core.ScrollBar](apps://apiviewer/#qx.ui.core.scroll.ScrollBar)
[qx.ui.core.NativeScrollBar](apps://apiviewer/#qx.ui.core.scroll.NativeScrollBar)
4 changes: 2 additions & 2 deletions docs/desktop/widget/slidebar.md
Expand Up @@ -24,10 +24,10 @@ Demos

Here are some links that demonstrate the usage of the widget:

- [SlideBar demo](apps://demobrowser/index.html#widget-SlideBar.html)
- [SlideBar demo](apps://demobrowser/#widget-SlideBar.html)

API
---

Here is a link to the API of the Widget:
[qx.ui.container.SlideBar](apps://apiviewer/index.html#qx.ui.container.SlideBar)
[qx.ui.container.SlideBar](apps://apiviewer/#qx.ui.container.SlideBar)
2 changes: 1 addition & 1 deletion docs/desktop/widget/spacer.md
Expand Up @@ -20,4 +20,4 @@ API
---

Here is a link to the API of the Widget:
[qx.ui.core.Spacer](apps://apiviewer/index.html#qx.ui.core.Spacer)
[qx.ui.core.Spacer](apps://apiviewer/#qx.ui.core.Spacer)