Skip to content
Martijn Schrage edited this page Mar 2, 2017 · 2 revisions

The Proxima JavaScript client (source: $proxima/scripts/Editor.xml) keeps track of a HTML rendering tree that represents the rendering of the edited document. The script catches all edit events that occur in the browser (mouse and keyboard) and sends these as commands to the server. The incremental updates that are received from the server are applied to the HTML rendering tree.

The function sendCommand sends a command to the server. The syntax of these commands consists of the showed elements of the Command datatype declared in $proxima/src/Proxima/GUIServer.hs:

data Command = Metrics ((String,Int,Bool,Bool),(Int,Int,[Int]))
             | ContextMenuRequest ((Int,Int),(Int,Int))
             | ContextMenuSelect Int
             | Key (Int,Modifiers)
             | Chr (Int,Modifiers)
             | Mouse MouseCommand (Int,Int, Modifiers)
             | EditStyle StyleEdit
             | Find String
             | SetViewedArea CommonTypes.Rectangle
             | Redraw
             | Rearrange
             | ClearMetrics
               deriving (Show, Read)

data MouseCommand = MouseDown | MouseMove | MouseUp | MouseDragStart | MouseDrop
                    deriving (Show, Read)

type Modifiers = (Bool,Bool,Bool)

Because edit events may occur in quick succession, the client maintains a queue for edit events (global variable commandQueue). When the client is waiting for a server response, any additional events are queued until the result has arrived, after which all queued commands are sent. Communication with the server is performed through an XMLHTTP object, and on a server response, function state_Change updates the status lights and processes the received updates by calling updateTree for each update.

Font Metrics

For each font in the presentation, the server requires the character height, the baseline and a list of character widths. Unfortunately, JavaScript does not support general font queries. To solve the problem, initFont creates an invisible DIV element with id="font" that contains all characters of the font. The height and widths can be measured from this font element. (Each character is replicated a 1000 times to increase accuracy.) In order to determine the baseline, a single letter is rendered next to an element with height 0. Because the element is aligned at the baseline of the letter, its relative position (which can be queried) is the value for the baseline.

On a font query, the function queryFont applies the queried font and style to the font element and returns the collected metrics, which are sent to the server by function updateTree as a Metrics command.

Context menus

Because there is no default support for context menus in JavaScript, we use code from Dynamic Drive (www.dynamicdrive.com). On a right click mouse event, a ContextMenuRequest command with the mouse position and the coordinates of the top-left corner of the screen are sent to the server. The server responds with a list of items that are displayed as a context menu, and when an item is clicked, a ContextMenuSelect command is sent with the index of the clicked menu item.

Mouse handler

The mouse handler (mouseHandler) sends events to the server based on user mouse events. In addition, it handles scrolling and drag and drop. Standard JavaScript croll events cannot be used, because these are also generated when the rendering is scrolled programmatically. Using a global variable to signal this difference did not turn out reliable. Therefore, scroll events are generated explicitly, when the user drags inside a scroll bar. A downside of this method is that it doesn't support scroll wheels.

The mouse handler implements a small finite state machine to handle drag and drop. Its states are:

0: No button down
1: Button down, but no drag yet
2: Drag in progress
3: Dragging on a non-dragsource, for example when the user extends the selection

The state transitions are straightforward and can be seen in the code. Drag sessions are only started when the element under the cursor has class Draggable. In order to provide an image of the dragged node, a clone is stored in draggedElementClone.

Predictive Rendering

Network latency may cause a delay between sending the edit event and receiving the update from the server. We tackle this problem by having the client show the predicted effect of an edit operation, until the actual update is obtained from the server.

When a key is entered, insertCharPredictively inserts the corresponding character in the HTML rendering tree at the position of the cursor, and the cursor is moved to the right by the width of the character. Furthermore, all right sibling elements in the same row and in parent rows are moved to the right as well, until a parent column is encountered.

Because edit operations are queued if the client is already waiting for a response, we need to keep track of two kinds of predictively inserted characters: characters corresponding to the already sent insert operations (sentPredictiveInserts), and characters corresponding to the currently queued insert operations (currentPredictiveInserts). On arrival of the server response, all predictive inserts are undone with removeAllPredictiveInserts, to make it possible to apply the received incremental update on the rendering. Subsequently, the queued inserts are sent to the server, and the corresponding predictive updates are performed on the rendering again (by reApplySentPredictiveInserts).

Because a presentation in Proxima can be complex, it is not always possible to correctly predict the effect of an inserted character. Nevertheless, the algorithm works well for all editors in the quite diverse suite of sample editors, and it provides a massive increase in response time. An area of future research will be to enhance the predictive rendering model for handling cursor movement and navigation, and perhaps extend the presentation language to allow predictive rendering for specialized cases, such as highlighting drop targets during dragging.

-- Main.MartijnSchrage - 25 Feb 2010

Clone this wiki locally