How Frontal is Parsed and Rendered
From Frontal Wiki
An overview of how Frontal turns a document definition into a graphically rendered site may be in order, as sometimes such an understanding can help in debugging a problem.
The Parse Process
The document definition begins with a single tag:
<document xmlns="http://frontalcode.com/" />
From that point on, defining the site is a process of adding children elements to this document element.
By default, the next step is to include the default stylesheet. It's this stylesheet that defines the default scrollbar and progress indicator, how links and markers work, which form inputs are available and how to render them, etc. In an earlier section, we showed how the Frontal console allows us to view the default stylesheet.
Then, the usual next step is to load the Frontal definition file and append it to the document element.
Whenever the document definition is changed, including when the initial document element is added, the Frontal renderer parses the new XML nodes. The parsing process works as follows:
- 1. If the XML node is a text kind, that is, if it's an untagged piece of text in the XML, then it's wrapped in a 'text' tag. For example, "Hello, world." would become "<text>Hello, world.</text>".
- 2. If the node has the attribute 'custom', then it's value is used as the name of the class that will handle the graphical presentation of the element.
- 3. Otherwise, the node's name is used to determine the Frontal class that will perform this function.
- 4. If neither step 2 nor step 3 were able to determine a class for the tag, then the interaction onGetClassRefByNodeName is run.
- 5. If a class has been determined for the tag, then an object is instantiated from it. The document, parent, and node properties discussed in the section "Accessing Frontal Tags for Scripting" are set at this point. Also, a FrontalSprite (which extends the Flash Sprite) is created and assigned to the "movie" property. Finally, this object has its "needsRender" property set to true.
- 6. If this node has any children and doesn't have the style "processes-own-content" set to true on it, then we go to step one of this process with the first child's node as the node to be parsed.
- 7. If this node has a next sibling, then we go to step one with this sibling node as the node to be parsed.
When this process is complete, we have a tree of objects that matches the structure of the XML definition. Since it's most likely that each of these objects was created by a class that is a subclass DocumentElement (introduced in the "Accessing Frontal Tags for Scripting" section), we call this tree of objects the document's DocumentElement objects.
The next step is to render these objects.
The Render Process
The render process is a recurrent process that runs whenever at least one DocumentElement object sets its needsRender property to true. Note that in step 5 of the parse process, whenever a new object is created, it sets this flag to true. Thus, whenever a new element is parsed, the render process will be triggered.
The actual mechanics of this are that when needsRender is set on a DocumentElement, then it also sets it on its parent DocumentElement. In this way, the needsRender flag propagates its way up to the document object.
When the document object has its needsRender flag set, then it invalidates the stage and schedules a task to render the document at the next frame. (Depending on the state of the movie, a stage invalidation will not necessarily trigger an event, and so the scheduled task acts as a back up in that case.) The point of not acting immediately on the needsRender flag is that several DocumentElement objects may be setting their needsRender flag, and so we would like a single render pass to handle all of these rather than dedicate a render pass to each one.
Then the document's render process is initiated either from receiving a render event from the Flash stage, or because of the scheduled task mentioned. The process works as follows, with the document object being the initial object to be rendered.
- 1. The DocumentElement object and all of its siblings that need rendering are initialized for render. (It's possible to remove a DocumentElement object from the rendering process even if its needsRender flag is set to true with the style "disable-rendering". This can be useful if, for example, a DocumentElement object is the target of a Flash startDrag or tween where the styles used for rendering aren't necessarily in synch with the object's movie's properties.):
- 1. The needsRender flag is cleared.
- 2. If the needsParse flag is set, then the object's attributes and styles are reset to their original values at the time the document was defined, and then any matching style sheet selectors are reapplied. The point here is that when a DocumentElement object's state changes, e.g., the mouse is moved over it, or pressed on it, or a custom pseudo-class changes, etc., then it may match selectors it previously didn't match, or mismatch selectors that it previously did. In those cases, event listeners set the needsParse flag, and the render process causes the DocumentElement object to act on it.
- 2. The DocumentElement object and all of its siblings that need rendering then evaluate their dimensions. This step is taken to convert styles like "height: 50%;" or "margin: auto;" into actual numbers.
- 3. Then the DocumentElement object and all of its siblings that need rendering are laid out. This is a process in which the position of the siblings is determined. The complication here rises primarily from Frontal's support of the "float" and "clear" styles taken from CSS. (Except in the case that the parent element has the style "layout: stack;" set on it. In that case, every child element is pinned to the top left corner regardless.) With these styles, the position of one DocumentElement object is dependent on its siblings as well as the size of its parent.
- 4. Once the positions are determined, we calculate each element's "leftover" dimensions. These dimensions are how much each element can expand widthwise and heightwise, such that it will not cause its siblings to reflow vertically, and it will not expand beyond its parent's dimensions.
- 5. Next, with each element's leftover dimensions determined, we can convert styles like "width: 100% leftover". "Leftover" is a Frontal concept by which an element may be sized based on the difference between its parent's dimensions and all of the fixed dimensions specified by its siblings. For example, if an element had the styles "width: 40% leftover; margin-left: 50px;" then its width would be 40% of its parent's width minus 50 pixels. This can come in very handy when building flexible layouts.
- 6. Now with the left over dimensions calculated, we need to lay out the DocumentElement object and all of its siblings that need rendering again, since their position may have changed.
- 7. Then, for each DocumentElement object and its siblings that need rendering, we repeat this render process from step 1 for their children. If after this, any DocumentElement object still does not have a width or height dimension set, then this is because its dimensions are determined by the space taken up by their children. Now that its children have been rendered, these dimensions are ready to be determined, so we set a flag to do these calculations.
- 8. If in step 7, any object sets the flag to perform the dimensions calculations, then we do so now. This is a repetition of steps 2, 3, 4 and 5.
- 9. Next, for each DocumentElement object and its siblings, we let them render themselves:
- 1. Each adds or removes its children's movie to its own movie according to the "display" and "visibility" styles.
- 2. The children depths are reset according to their "z-index" styles and their original order in the document definition.
- 3. Styles are applied to the object next. That is, whatever functional work needs to be done for each style is now done. For example, a style like "alpha: .5;" would cause the equivalent to "movie.alpha = .5;" to run. At the end of this, the interaction "onApplyStyles" is run.
- 4. At this point, the interaction "onCustomRender" is run. Then particular objects will do any sort of custom rendering they need to display themselves. For example, a "video" tag will create its Flash FLVPlayback object if necessary, and a "text" tag will create it Flash TextField object.
- 5. Then the object will render its background. The background is drawn on the object's movie but the process is gated and may be customized by calls to "onRenderBackground." See the section on interactions for more details.
- 6. If the style "overflow: hidden;" is set, then we create and apply a mask to the movie.
- 7. If the style "show-progress-indicator" is "normal" or "always", then we render the progress indicator. (See the advanced topics for more details.)
- 8. If the style "scroll" is not set to "never", then we determine if horizontal or vertical scrollbars are needed on this object and create or hide them as needed.
- 9. We then check the object's element attributes for any interaction specifications, and add listeners as necessary to trigger them.
- 10. We call the "onRender" interaction.
- 10. Finally, for each DocumentElement object and its siblings, we position their movies according to where we've calculated they should be.