# Hybrid HTML/DOM Editors This document expects the reader is professionally experienced in the authoring of Web content. Knowledge of the DOM, HTML and the difference between the two are pre-requisite. This document avoids prescribing the larger User eXperience of a Hybrid Editor, and merely (hah! *merely*) attempts to define the broad categories of editing modalities, and capabilities a Hybrid Editor provides. Further this document attempts to define a performant and maintainable surface area upon which Hybrid Editors may be built. Where possible this document is descriptive rather than specific. Authors: * Collin Miller ## Q & A Dear reader. Please Ask Questions Under this Section. **Q:** How do I Ask a Question? **A:** Ask a question by creating a new sub-item in the [Q & A item](/#q-a) . Hopefully somebody will answer it quickly. If you see an unanswered question and you know the answer, answer it. ## Short Glossary of Terms ### HTML Editor ## HTML Editors An HTML Editor is a typical Text editor. Any program capable of opening a text file, allowing manipulation of it's text content, and saving the changes to the file may be used as an HTML editor. Many applications go further: * highlighting the tokens of HTML while the author edits * auto completing closing tags of html elements * many other nice things ### Canvas ## Canvas The direct or conceptual rendering of a Document Object Model to a 2d plane. A Typical Canvas is a web browser. Any subsection of the DOM may be referred to as a Canvas. ### DOM Editor ## DOM Editors A DOM Editor is a special program capable of rendering a Document Object Model Object graph to a Canvas. A DOM Editor facilitates updating the edges and nodes of that graph while re-painting the Canvas. Most Web Browsers are DOM Editors. The specification for the DOM includes an API for editing DOM graphs with methods such as: * `document.createElement(nodeName)` * `Node#appendChild(childNode)` * `document.createAttribute(attributeName)` * and many others... DOM Editors may go further, facilitating creation and manipulation of a DOM graph without directly using the API methods. High level modalities such as "Drag and Drop" may be used. Visual lists of Node types may be shown to content authors. And text may be edited directly with a keyboard. These Editors are generally known by the term: WYSIWYG, or "What You See is What You Get". ### Code Projection ## Code Projection / Source Mapping Code projection is the direct mapping of source code from an HTML Editor to a DOM Editor. A sub-component of Code Projection is Source Mapping, or the reverse mapping of DOM Elements to character positions in the HTML Editor. There may not be an 1-to-1 mapping of the tags written in the HTML source to the Nodes in the Canvas. In the course of projection some elements may be transformed, expanded, or *exploded* to present alternate, extended, or generated content. This transformation capability will be explored further in the [Plugins]( /#plugins) section. It is ultimately the combination of Code Projection and Source Mapping that define the Hybrid Editor as a unique system. The **Quality** of the Code Projection is of ultimate importance to the function, development, and maintainability of a Hybrid Editor. Typically tools combining HTML and DOM editing into one authoring tool have been met with negative reaction. This is not without reason. A hybrid HTML/DOM Editor has many challenges and failure to meet these challenges leads to a sub-satisfactory work-flow. **Some of these challenges are detailed and explored:** ## Source to "Source" Mapping * Directly mapping DOM elements to HTML source locations. * Directly mapping HTML source locations to DOM elements. ### DOM —> HTML Mapping Knowing which character ranges in the HTML source correspond to which DOM elements in the Graph is useful for the following capabilities: * Clicking on an Element in the Canvas and moving the selection of the HTML Editor to the position in the source that defines that Element. And more importantly: * Knowing where to place new HTML source when changes are made to the Canvas This problem has been solved by Mozilla in their "Slowparse" module: https://github.com/mozilla/slowparse Contrary to the name, "Slowparse" is fast. Fast enough to parse a 700+ line text file on 2013 hardware in `> 10ms`. On other hardware this time may be in the `10-60ms` range. This makes Slowparse "Fast Enough" for typical workflows where source files can be kept under several hundred lines long. On the slower end of this scale Slowparse maybe 'debounced' to only run after user input has halted for a fraction of a second. Slow parse emits a DOM Tree in which Nodes and Attributes are given a `parseInfo` expando detailing the beginning and ending positions in the source text. Further performance might be attained by implementing a re-entrant parser. Rather than parsing the entire document each time the source code changes, the `parseInfo` could be used to determine what part of the DOM corresponds to the current character position and making that change directly or resuming parsing in the middle of the document. That would necessitate traversing the DOM and manually updating the `parseInfo` to include the expanded or contracted character range. The DOM is an optimal structure for traversing the nested intervals. Roughly the algorithm looks like this: (Glossing over special cases that exist depending on plugin functions.) ```coffeescript # Where delta is an Integer. # + for character insertion # - for character deletion nudgeIntervals(domNode, delta) -> domNode.parseInfo.endChar += delta while domNode = domNode.parentNode domNode.parseInfo.endChar += delta ``` ### HTML —> DOM Mapping HTML to DOM mapping is not currently solved by Slowparse. This is a necessary problem to solve for the following capability: * Changing the selection selection of the HTML Editor highlights the corresponding Element in the Canvas. * Minimally updating the Canvas as the HTML source is edited. [See "Minimal Changes"](/#minimal-changes) There are two flavors of this problem. 1. Point: The selection is collapsed to a single point. 2. Range: The selection covers a range of text. For the Point case the solution is the object in the DOM with the shortest difference between the start and end of it's `parseInfo` that overlaps the character position of the selection. For the Range case the solution is all objects in the DOM whose `parseInfo` ranges partially or fully intersect the character positions in the selection. Note that a DOM -> HTML source mapping is a special case of Interval Tree. No intervals in the mapping between. All nested intervals are wholly contained by their parents. ## Minimal Changes * Minimal changes to DOM tree when making changes to source HTML * Minimal changes to source HTML when making changes to DOM. ### HTML —> DOM updates AS edits are made to the HTML source a hidden Canvas will be updated with it's contents. A recursive algorithm will compare this hidden Canvas with the Canvas used for DOM Editing. Only elements that have been deemed "changed" will be updated. If only an attribute has been changed/added/removed the element will not be reconstructed. To save speed the hidden Canvas may not be a fully 'inflated' DOM but a similar tree of objects. This way only objects in the DOM that must be replaced will actually be re-constructed. Libraries for finding diffs between DOM trees can serve as the basis for this algorithm, though changes will be required to allow for projection plugins, `parseInfo` updates, and the noted speed increases. https://github.com/pomax/DOM-diff/tree/gh-pages https://github.com/facebook/react ### DOM —> HTML updates Updates from the DOM to the HTML should be performed through an API and not directly to the Canvas. This is because plugins may exist between the HTML source and the projected DOM. It is simpler if there is a single execution path for projection in the direction of HTML —> DOM. Fortunately this API can be made to mirror the existing W3C api for DOM editing. It may also be extended to a jQuery style api for convenience. This way the implementation of the DOM editor need not worry about how it's changes will affect the source. A possible editor might call this: ```coffeescript newChild = document.createElement('span') API(event.target).append newChild ``` or ```coffeescripte API(event.target).append """
""" ``` or ```coffeescript API(event.target).classList.remove 'jumbotron' ``` etc. The `API` will detect and interpret the `parseInfo` and insert/delete specific characters in the HTML source accordingly. Changes through the Projector API will be treated like end-user edits and kick off the normal HTML —> DOM update procedures. ## Editor "Chrome" ### Editor "Chrome" In the course of editing it may be desireable to affix "Chrome", or visual indications of the end-users current or potential actions . Because mappings between the HTML and DOM are tracked through `parseInfo` exapandos on DOM elements, and all edits to the DOM are sent through a Projector API, it is safe to insert elements to the Canvas DOM tree via the existing DOM editing methods. These elements will be ignored. The special case is when Editor "Chrome" has been inserted into an element which has been removed or so substantially changed that the diff algorithm considers the parent to have changed. Then the contents of the element will be destroyed. # Plugins Allowing plugins to manipulate the projection between HTML and DOM. ### Data ### Data Elements Given the following data object: ```json { "currentPage": { "title":"Welcome to Example.Org" } } ``` It may be desirable to transform the following source HTML ```html {{currentPage.title}} ``` into the following DOM (represented as HTML) ```html Welcome to Example.Org ``` ### Repeating Elements ### Repeating Elements A plugin might define the following behavior. Given source HTML: ```html
  1. List Item #{{x}}
``` The following DOM (represented here as HTML) will be generated: ```html
  1. List Item #1
  2. List Item #2
  3. List Item #3
``` The same plugin might define the following behavior: Given this source HTML ```html
{{key}}
{{value}}
``` And a JavasScript Object similar to this: ```javascript things = { "Ice Cream": "Delicious", "Bears": "Terrifying" } ``` The following DOM (again as HTML) is produced: ```
Ice Cream
Delicious
Bears
Terrifying
``` ### Transclusions ### Transclusions It is common in Web Development to have a system of **Layouts** and **Partials**, the exact nomenclature differs, but many tools provide these concepts. Given a File: `layouts/index.html` ```html
Example.Org