Skip to content
Jeffrey Guenther edited this page Apr 24, 2016 · 27 revisions

This is a work in progress and will evolve as I (@jrguenther) spelunk the code base. If others want to join in the effort, you are most welcome.

Keyboard Shortcuts

Navigation

  • left - move cursor one character to left
  • right - move cursor one character to right

Delete

  • backspace - deletes backward
  • delete - deletes forward
  • control + d - deletes forward
  • control + h - deletes backward

Line Breaks

  • control + o - inserts line break
  • return - insert line break

Modify Indent

  • tab - increase indent level
  • ⇧ shift + tab - decrease indent level

Selection

  • ⇧ shift + right -expand selection right
  • ⇧ shift + left - expand selection left

Execution Flow

When the trix.js code is included on your site and you add a <trix-editor> element to your HTML, the element will be automatically initialized. This is done by running registerElement in elements/trix_editor_element.coffee. The element is initially styled using the default CSS provide by the element registration. Creating the element creates an instance of the EditorController, which takes care of initializing the editor and hooking up its event listeners.

After the element is instantiated, the element listens for keyboard and mouse events. As the events stream in, the InputController handles them and delegates back to the EditorController as needed. To make this tour less abstract, let's examine what happens when the character "A" is typed in the editor.

In the constructor of the InputController, event handlers are created for each event defined in @events (a giant object definition at the bottom of the class definition containing callbacks for each event type).

# Create event handlers for all input events
    for eventName of @events
      handleEvent eventName, onElement: @element, withCallback: @handlerFor(eventName), inPhase: "capturing"

@handlerFor is called for each event in @events. In @handlerFor @handleInput is called:

  handlerFor: (eventName) ->
    (event) =>
      @handleInput ->
        unless innerElementIsActive(@element)
          @eventName = eventName
          @events[eventName].call(this, event)

This is where the magic 🎆 happens. The keypress event is handled and "A" is added to the current document instance.

In the entry for keypress, we find:

...

if character?
  @delegate?.inputControllerWillPerformTyping()
  @responder?.insertString(character)
  @setInputSummary(textAdded: character, didDelete: @selectionIsExpanded())
...

If the event is handled without error, EditorController is notified that the InputController has handled the event by calling @delegate?.inputControllerDidHandleInput()

handleInput: (callback) ->
    try
      @delegate?.inputControllerWillHandleInput()
      callback.call(this)
    finally
      @delegate?.inputControllerDidHandleInput()

inputControllerDidHandleInput requests the element be rendered.

In EditorController:

inputControllerDidHandleInput: ->
    @handlingInput = false
    if @requestedRender
      @requestedRender = false
      @render()

This initiates a chain of method calls that result in calling render in the CompositionController, which is handled efficiently by either rendering the DocumentView from scratch or by syncing the changes (We'll leave a description of sync for later).

  render: ->
    unless @revision is @composition.revision
      @documentView.setDocument(@composition.document)
      @documentView.render() # RENDER the view
      @revision = @composition.revision

    ....

    @delegate?.compositionControllerDidRender?()

To render the DocumentView, the internal representation of the document is converted into DOM elements. These elements are created recursively by rendering the DocumentView and child views.

Event Handling

The implementation makes heavy use of the delegation pattern.

Events are handled with a combination of code run in InputController and EditorController. In some cases, InputController delegates back to EditorController. EditorController acts as a delegate for the SelectionManager, Composition, InputController, AttachmentManager, and ToolbarController. If delegate?.method is called, chances are you will find the implementation in EditorController.

Views

Trix.ObjectView Provides base rendering methods

Subclasses:

Each of the subclasses is responsible for creating the HTMLElements needed to display the block.

  • Trix.PieceView
  • Trix.DocumentView
  • Trix.ObjectGroupView
  • Trix.TextView
  • Trix.BlockView
  • Trix.AttachmentView
    • Trix.PreviewableAttachmentView
Clone this wiki locally