What is Sir Trevor?

Sir Trevor provides a means to transform a text input into a rich content editor that’s been re-imagined for the web. The content of the editor is stored as HTML inside a JSON object, with the structure and the contents of the post serialized inside of it.

Get Started

Installation

npm

npm package

npm install --save sir-trevor

Bower

Bower package.

Your bower.json file should look something like this:

{
  "name": "your-project",
  "dependencies": {
    "sir-trevor-js": "0.6.0"
  }
}

Then run bower install on your project to install the necessary dependencies.

Alternatively, grab the following files and include them in your project:

You’ll need the following CSS file too:

And the icons file:

Initialising

A Sir Trevor element must be contained inside a form like follows:

<form>
  <textarea class="js-st-instance"></textarea>
</form>

Then to transform this element to a Sir Trevor instance:

<script>
  var editor = new SirTrevor.Editor({
    el: document.querySelector('.js-st-instance'),
    defaultType: 'Text',
    iconUrl: 'build/sir-trevor-icons.svg'
  });
</script>

You’ll also need to configure icon url

The Output

Sir Trevor stores structured JSON that describes your document.

A typical piece of Sir Trevor JSON looks like this:

{
  "data": [{
    "type": "text",
    "data": {
      "text": "<p>Hello, my name is <b>Sir Trevor</b></p>",
      "format": "html"
    }
  }]
}

Each piece of JSON is made up of an object that contains the type and data for the block.

Generally, when rendering Sir Trevor on the server side you should map the types of the blocks to partials that define the presentation of that block, then all you have to do is loop over the JSON data and render the correct partial.

For a server-side example, please see our Sir Trevor Rails gem. The ideas within this could easily be extrapolated for other languages.

Retrieving Editor Instances

You can retrieve SirTrevor.Editor instance by assigning the editor to a variable.

var editor = new SirTrevor.Editor({});

Icons

Configuring iconUrl

Icons are included using an external svg file. This will need a url so that the icons can be found. This is set using default.

SirTrevor.setDefaults({
  iconUrl: "sir-trevor-icons.svg"
});

If you are using Sir Trevor in IE or a browser that doesn’t support svg fragments showing it could be because your browser doesn’t include svg sprite support.

<script src="../node_modules/svg4everybody/dist/svg4everybody.js" type="text/javascript" charset="utf-8"></script>
<script>svg4everybody();</script>

Customising icons

If you want to customise the icons, or add a custom block you’ll want to generate a new icon file. Icons are generated using Icomoon

Source svgs

Icomoon project

Once you’ve made changes to the icomoon project you can generate the svg file. Extract the svg from the downloaded project and add to your project. You’ll also need to set the location of iconUrl using the instructions above.

Options

The editor accepts the following options. Options are passed to the editor on initialisation.

blockTypes
Specify an array of block types to use with the editor.
Defaults to all block types.

{
  blockTypes: ["Text", "Tweet", "Image"]
}

defaultType
Specify a default block to start the editor with.
Defaults to no block.

{
  defaultType: "Text"
}

blockLimit
Set an overall total number of blocks that can be displayed.
Defaults to 0 (infinite).

{
  blockLimit: 1
}

blockTypeLimits
Set a limit on the number of blocks that can be displayed by its type.
Defaults to {}.

{
  blockTypeLimits: {
    "Text": 2,
    "Image": 1
  }
}

required
Specify which block types are required for validatation.
Defaults to none.

{
  required: ["Text", "Image"]
}

onEditorRender
Call a function once the Editor has rendered.
Defaults to undefined.

{
  onEditorRender: function() {
    alert('Do something');
  }
}

Block Options

You can set specific options for blocks by using the setBlockOptions method.

SirTrevor.setBlockOptions('Tweet', {
  someValue: true
});

Global Options

You can also set options globally for all Sir Trevor instances using the setDefaults method.

SirTrevor.setDefaults({
  required: ["Text"]
});

Blocks

Tweet Block

To use the Tweet block you’ll need some server side code. This will need to lookup a Tweet ID and return the tweet in JSON via the Twitter API. Unfortunately, the only way to do this via the API is with an authenticated call.

You can change the Tweet fetch URL as follows:

SirTrevor.setBlockOptions("Tweet", {
  fetchUrl: function(tweetID) {
    return "/tweets/?tweet_id=" + tweetID;
  }
});

Image Block

The image block relies on a server side component to store images on the server. By default Sir Trevor will do an AJAX file upload in the background to a /attachments endpoint. You can change this as follows:

SirTrevor.setDefaults({
  uploadUrl: "/images"
});

The uploader posts an attachment hash that has three properties:

There is an uploader example using Rails + Carrierwave.

Hooks

There are a few hook in points per block that you can use:

beforeBlockRender
Called immediately when render is called.

SirTrevor.Blocks.Text.beforeBlockRender = function() {
  alert('Do something');
};

onBlockRender
Called once a block has been rendered.

SirTrevor.Blocks.Text.onBlockRender = function() {
  alert('Do something');
};

Mediated Events

We use events bound to an individual editor to trigger actions internally. You can use these events to hook into the inner workings of Sir Trevor.

Block events

Error events

Formatter events

You can use access these events using the mediator object on an editor as follows:

var editor = SirTrevor.Editor({
  el: document.querySelector('.js-editor')
});

editor.mediator.on('block:create', function() {
  console.log(arguments);
});

Custom Blocks

A founding principle of Sir Trevor is to have an extensible editor that you can create your own block types on top of. We’ve made it really simple for you to get started and build your own block types.

A good place to start is to read through one of the blocks that comes bundled with Sir Trevor.

There’s also a Sir Trevor Blocks repository.

Concepts

A SirTrevor.Block essentially takes some data, provides an interface for that data and knows how to serialize itself into JSON.

Generally the flow for a block goes like this:

  1. Create block
  2. Render block
  3. Edit some data
  4. Submit form
  5. Validate block with validations
  6. Block data is serialized to the block store

1. Create block

// Trigger BlockManager to create a block of the type and data required.
mediator.trigger('block:create', block.type, block.data)

// Create a new block
var block = new Block(data);

// Render block
block.render()

// BlockManager.renderBlock then attaches the block to the dom and calls a final function
block.onBlockRender() // Placeholder function. Could deal with focus.

2. Render block

// Called when a block is first added to the dom or moved.
block.render()

block.beforeBlockRender() // Placeholder function
block._initTextBlocks() // Initialise scribe text areas
block.withMixin() // Load mixins for block
block.checkAndLoadData() // 
> block.beforeLoadingData() // Set block as loading, loadData and then set block as ready
> > block.loadData() // Each block should implement this to populate from data
block.save() // store current block data to the block store.
> block.setData()

The methods associated with doing this are:

loadData
Function is called with the JSON block data when updated and allows the block to rerender as required.

loadData(data) {
  this.el.querySelector('.js-cite').value = data.cite;
}

setData
Takes a JSON object and stores it against the block.

setData(data)

getBlockData
Get the current block data as a JSON object

getBlockData() // returns {}

_serializeData
Override the block data for storage and return a JSON object.

_serializeData() {
  return {
    cite: this.el.querySelector('.js-cite')
  };
}

Render Block UI

SirTrevor.Blocks.ComplexType = SirTrevor.Block.extend({
  type: "complex_type",

  // Custom html that is shown when a block is being edited.

  editorHTML: `
    <div>
      // Add a scribe controlled text field
      // Note: Only possible to have 1 per block at present
      <div class="st-text-block" contenteditable="true"></div>

      // Add a simple input field
      <input type="text" name="inputtext1" value="inputtext1" />

      // Add a simple text area
      <textarea name="textarea1">textarea1</textarea>

      // Add a checkbox
      <input type="checkbox" name="checkbox1" value="1" checked="checked" />
      <input type="checkbox" name="checkbox2" value="2" />

      // Add a checkbox which toggles `on` & `off` values
      <input type="checkbox" name="checkbox3" value="3" data-toggle="true" />
      <input type="checkbox" name="checkbox4" value="4" checked="checked" data-toggle="true" />

      // Add radio buttons. `name` will need to be defined per block
      // `data-name` is used as an override for the data key to be saved
      <input type="radio" name="block1-radio1" value="radio11" data-name="radio1" />
      <input type="radio" name="block1-radio1" value="radio12" data-name="radio1" checked="checked" />
      <input type="radio" name="block1-radio2" value="radio21" data-name="radio2" />
      <input type="radio" name="block1-radio2" value="radio22" data-name="radio2" />

      // Add select boxes
      <select name="select1">
        <option>select11</option>
        <option selected="selected">select12</option>
      </select>
    </div>
  `;
});

Mixins

We’ve abstracted all of the interface components for creating a Sir Trevor block. The following mixins are available to all blocks:

droppable
Provides a drop zone support for the block. Exposes the onDrop event which must be overwritten.

pastable
Provides the UI support for a pastable component (like the video block). You can override the default paste handler by re-implementing the onContentPasted method.

uploadable
Provides support for uploadable content and mixins in the uploader into the block. The uploadable mixin will use the uploadUrl set in the SirTrevor.setDefaults by default. Your custom block can override this by defining uploadUrl in the block body.

ajaxable
Gives a way to fetch content remotely through a editor managed queue.

controllable
Provides a second, smaller UI in the lower left block corner to control its contents. Requires the controls-object so be specified in the block configuration ('command-name': handler).

fetchable
Provides ajax functionality for a block. Along with a url you can pass options that will get passed to the Ajax.fetch library.

textable
Provides support for the block to be handled as an inline text block. Currently supported by Text, Heading and Quote blocks.

The droppable, uploadable and pastable components have the ability to override the default HTML that gets built as the UI. This can easily be done through the upload_options.html, drop_options.html or paste_options.html objects on the block. Each takes a string that is passed to an underscore _.template method.

Validations

There are two types of validations with Sir Trevor, required fields and custom validators. To add a field in your block as required, simply add the st-required class to it.

If you want to create custom validators, just define the method on your block and add it to the validations array on the block. To trigger an error on the block, you add an error to the errors array using the this.setError method.

validations: ['myCustomValidator'],

myCustomValidator: function() {
  var someField = this.$('.field');

  if (someField.value === "BEER!") {
    this.setError(someField, "must not contain beer");
  }
}

Text editing

Scribe

Sir Trevor uses Scribe for text editing. The document that is available can be found here https://github.com/guardian/scribe/wiki.

Text Formatting

The main reason for using Scribe is to provide text formatting and make content editable cross browser compatible.

Formatting has been enabled using various plugins that can be found here:
https://github.com/madebymany/sir-trevor-js/blob/master/src/scribe-interface.js

To add your own formatting you’ll want to install a plugin or create your own.

Create a Scribe plugin

If you want to create your own plugins then follow the steps below:

Step 1.

Define your toolbar commands
https://github.com/madebymany/sir-trevor-js/blob/master/src/config.js#L66-L112

Step 2.

Modify the linkPrompt plugin
https://github.com/guardian/scribe-plugin-link-prompt-command

The main file is
https://github.com/guardian/scribe-plugin-link-prompt-command/blob/master/src/scribe-plugin-link-prompt-command.js

You’ll find this assigns scribe.commands.linkPrompt which is the cmd set in the config.
So what you’ll do is write your own plugin and then assign it to something like scribe.commands.customLinkPrompt then modify the formatBar.commands in your config override.

Step 3.

You’ll need to find a way to call scribe.usefor the plugin.

If it needs to be on all blocks then you’ll need to patch something in:
https://github.com/madebymany/sir-trevor-js/blob/master/src/scribe-interface.js#L43

Per block you can define configureScribe
https://github.com/madebymany/sir-trevor-js/blob/master/src/blocks/text.js#L26