Skip to content

Commit

Permalink
Introduce lifecycle hooks
Browse files Browse the repository at this point in the history
* didUpdatePost, which gets passed the postEditor
* willRender, before render after post changes are complete
* didRender, after render when DOM and post are in sync
  • Loading branch information
mixonic committed Aug 28, 2015
1 parent b57465d commit 34104aa
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 1 deletion.
38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,44 @@ editor.render(element);
* `editor.enableEditing()` - allow the user to make direct edits directly
to a post's text.

### Editor Lifecycle Hooks

API consumers may want to react to given interaction by the user (or by
a programmatic edit of the post). Lifecyle hooks provide notification
of change and opportunity to edit the post where appropriate.

Register a lifecycle hook by calling the hook name on the editor with a
callback function. For example:

```js
editor.didUpdatePost(postEditor => {
let { offsets } = editor.offsets,
cursorSection;

if (offset.headSection.text === 'add-section-when-i-type-this') {
let section = editor.builder.createMarkupSection('p');
postEditor.insertSectionBefore(section, cursorSection.next);
cursorSection = section;
}

postEditor.scheduleRerender();
postEditor.schedule(() => {
if (cursorSection) {
editor.moveToSection(cursorSection, 0);
}
});
});
```

The available lifecycle hooks are:

* `editor.didUpdatePost(postEditor => {})` - An opprotunity to use the
`postEditor` and possibly change the post before rendering begins.
* `editor.willRender()` - After all post mutation has finished, but before
the DOM is updated.
* `editor.didRender()` - After the DOM has been updated to match the
edited post.

### Programmatic Post Editing

A major goal of Content-Kit is to allow complete customization of user
Expand Down
2 changes: 1 addition & 1 deletion demo/demo.js
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ function bootEditor(element, mobiledoc) {
ContentKitDemo.syncCodePane(editor);
}

editor.on('update', sync);
editor.willRender(sync);
sync();
}

Expand Down
32 changes: 32 additions & 0 deletions src/js/editor/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@ const defaults = {
html: null
};

function runCallbacks(callbacks, args) {
let i;
for (i=0;i<callbacks.length;i++) {
callbacks[i].apply(null, args);
}
}

function bindContentEditableTypingListeners(editor) {
// On 'PASTE' sanitize and insert
editor.addEventListener(editor.element, 'paste', function(e) {
Expand Down Expand Up @@ -230,6 +237,10 @@ class Editor {

this.builder = new PostNodeBuilder();

this._didUpdatePostCallbacks = [];
this._willRenderCallbacks = [];
this._didRenderCallbacks = [];

// FIXME: This should merge onto this.options
mergeWithOptions(this, defaults, options);

Expand Down Expand Up @@ -275,7 +286,9 @@ class Editor {
postRenderNode.markDirty();
}

runCallbacks(this._willRenderCallbacks, []);
this._renderer.render(this._renderTree);
runCallbacks(this._didRenderCallbacks, []);
}

render(element) {
Expand Down Expand Up @@ -319,6 +332,9 @@ class Editor {
showForTag: 'a'
}));

// A call to `run` will trigger the didUpdatePostCallbacks hooks with a
// postEditor.
this.run(() => {});
this.rerender();

if (this.autofocus) {
Expand Down Expand Up @@ -456,6 +472,9 @@ class Editor {
this._reparseCurrentSection();
this._removeDetachedSections();

// A call to `run` will trigger the didUpdatePostCallbacks hooks with a
// postEditor.
this.run(() => {});
this.rerender();
this.trigger('update');

Expand Down Expand Up @@ -598,9 +617,22 @@ class Editor {
run(callback) {
let postEditor = new PostEditor(this);
let result = callback(postEditor);
runCallbacks(this._didUpdatePostCallbacks, [postEditor]);
postEditor.complete();
return result;
}

didUpdatePost(callback) {
this._didUpdatePostCallbacks.push(callback);
}

willRender(callback) {
this._willRenderCallbacks.push(callback);
}

didRender(callback) {
this._didRenderCallbacks.push(callback);
}
}

mixin(Editor, EventEmitter);
Expand Down
69 changes: 69 additions & 0 deletions tests/unit/editor/editor-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,75 @@ test('rendering an editor adds EDITOR_ELEMENT_CLASS_NAME if not there', (assert)
assert.ok(hasClass('abc') && hasClass('def'), 'preserves existing class names');
});

test('editor fires lifecycle hooks', (assert) => {
assert.expect(4);
let didCallUpdatePost, didCallWillRender, didCallDidRender;
editor = new Editor();
editor.didUpdatePost(postEditor => {
assert.ok(postEditor, 'Post editor provided');
assert.ok(!didCallWillRender && !didCallDidRender,
'didUpdatePost called before render hooks');
didCallUpdatePost = true;
});
editor.willRender(() => {
assert.ok(didCallUpdatePost && !didCallDidRender,
'willRender called between didUpdatePost, didRender');
didCallWillRender = true;
});
editor.didRender(() => {
assert.ok(didCallUpdatePost && didCallWillRender,
'didRender called last');
didCallDidRender = true;
});
editor.render(editorElement);
});

test('editor fires lifecycle hooks for edit', (assert) => {
assert.expect(4);
editor = new Editor();
editor.render(editorElement);

let didCallUpdatePost, didCallWillRender, didCallDidRender;
editor.didUpdatePost(postEditor => {
assert.ok(postEditor, 'Post editor provided');
assert.ok(!didCallWillRender && !didCallDidRender,
'didUpdatePost called before render hooks');
didCallUpdatePost = true;
});
editor.willRender(() => {
assert.ok(didCallUpdatePost && !didCallDidRender,
'willRender called between didUpdatePost, didRender');
didCallWillRender = true;
});
editor.didRender(() => {
assert.ok(didCallUpdatePost && didCallWillRender,
'didRender called last');
didCallDidRender = true;
});

editor.run(postEditor => {
postEditor.removeSection(editor.post.sections.head);
});
});

test('editor fires lifecycle hooks for noop edit', (assert) => {
assert.expect(1);
editor = new Editor();
editor.render(editorElement);

editor.didUpdatePost(postEditor => {
assert.ok(postEditor, 'Post editor provided');
});
editor.willRender(() => {
assert.ok(false, 'willRender should not be called');
});
editor.didRender(() => {
assert.ok(false, 'didRender should not be called');
});

editor.run(() => {});
});

test('editor fires update event', (assert) => {
assert.expect(2);
let done = assert.async();
Expand Down

0 comments on commit 34104aa

Please sign in to comment.