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.
npm package
npm install --save sir-trevor
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:
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
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.
You can retrieve SirTrevor.Editor
instance by assigning the editor to a variable.
var editor = new SirTrevor.Editor({});
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>
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
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.
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');
}
}
You can set specific options for blocks by using the setBlockOptions
method.
SirTrevor.setBlockOptions('Tweet', {
someValue: true
});
You can also set options globally for all Sir Trevor instances using the setDefaults
method.
SirTrevor.setDefaults({
required: ["Text"]
});
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;
}
});
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:
attachment[name]
– the files nameattachment[file]
– the fileattachment[uid]
– a unique identifier for this fileThere is an uploader example using Rails + Carrierwave.
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');
};
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:create
block:remove
block:changePosition
block:limitReached
block:rerender
block:replace
block:countUpdate
errors:add
errors:render
errors:reset
formatter:position
formatter:hide
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);
});
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.
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:
// 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.
// 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')
};
}
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>
`;
});
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.
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");
}
}
Sir Trevor uses Scribe for text editing. The document that is available can be found here https://github.com/guardian/scribe/wiki.
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.
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.use
for 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