Skip to content

Commit

Permalink
Split render from editor instantiation
Browse files Browse the repository at this point in the history
  • Loading branch information
mixonic committed Aug 25, 2015
1 parent 97dedb9 commit 6b05a4f
Show file tree
Hide file tree
Showing 16 changed files with 203 additions and 96 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ var simpleMobiledoc = {
};
var element = document.querySelector('#editor');
var options = { mobiledoc: simpleMobiledoc };
var editor = new ContentKit.Editor(element, options);
var editor = new ContentKit.Editor(options);
editor.render(element);
```

`options` is an object which may include the following properties:
Expand Down
3 changes: 2 additions & 1 deletion demo/demo.js
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ function bootEditor(element, mobiledoc) {
if (editor) {
editor.destroy();
}
editor = new ContentKit.Editor(element, {
editor = new ContentKit.Editor({
autofocus: false,
mobiledoc: mobiledoc,
cards: [simpleCard, cardWithEditMode, cardWithInput, selfieCard],
Expand All @@ -279,6 +279,7 @@ function bootEditor(element, mobiledoc) {
}
}
});
editor.render(element);

function sync() {
ContentKitDemo.syncCodePane(editor);
Expand Down
97 changes: 60 additions & 37 deletions src/js/editor/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ import MobiledocRenderer from '../renderers/mobiledoc';
import { mergeWithOptions } from 'content-kit-utils';
import {
clearChildNodes,
addClassName
addClassName,
parseHTML
} from '../utils/dom-utils';
import {
forEach,
Expand Down Expand Up @@ -69,7 +70,8 @@ const defaults = {
unknownCardHandler: () => {
throw new Error('Unknown card encountered');
},
mobiledoc: null
mobiledoc: null,
html: null
};

function bindContentEditableTypingListeners(editor) {
Expand Down Expand Up @@ -218,14 +220,13 @@ function makeButtons(editor) {
* @param options hash of options
*/
class Editor {
constructor(element, options) {
if (!element) {
throw new Error('Editor requires an element as the first argument');
constructor(options={}) {
if (!options || options.nodeType) {
throw new Error('editor create accepts an options object. For legacy usage passing an element for the first argument, consider the `html` option for loading DOM or HTML posts. For other cases call `editor.render(domNode)` after editor creation');
}

this._elementListeners = [];
this._views = [];
this.element = element;
this.isEditable = null;

this.builder = new PostNodeBuilder();

Expand All @@ -237,22 +238,63 @@ class Editor {
this._parser = new PostParser(this.builder);
this._renderer = new Renderer(this, this.cards, this.unknownCardHandler, this.cardOptions);

this.applyClassName(EDITOR_ELEMENT_CLASS_NAME);
this.applyPlaceholder();

element.spellcheck = this.spellcheck;
this.enableEditing();

if (this.mobiledoc) {
this.post = new MobiledocParser(this.builder).parse(this.mobiledoc);
} else if (this.html) {
if (typeof this.html === 'string') {
this.html = parseHTML(this.html);
}
this.post = new DOMParser(this.builder).parse(this.html);
} else {
this.post = new DOMParser(this.builder).parse(this.element);
this.post = this.builder.createBlankPost();
}

this._renderTree = this.prepareRenderTree(this.post);
}

addView(view) {
this._views.push(view);
}

prepareRenderTree(post) {
let renderTree = new RenderTree();
let node = renderTree.buildRenderNode(post);
renderTree.node = node;
return renderTree;
}

rerender() {
let postRenderNode = this.post.renderNode;

// if we haven't rendered this post's renderNode before, mark it dirty
if (!postRenderNode.element) {
if (!this.element) {
throw new Error('Initial call to `render` must happen before `rerender` can be called.');
}
postRenderNode.element = this.element;
postRenderNode.markDirty();
}

this._renderer.render(this._renderTree);
}

render(element) {
if (this.element) {
throw new Error('Cannot render an editor twice. Use `rerender` to update the rendering of an existing editor instance');
}

this.element = element;

this.applyClassName(EDITOR_ELEMENT_CLASS_NAME);
this.applyPlaceholder();

element.spellcheck = this.spellcheck;

if (this.isEditable === null) {
this.enableEditing();
}

clearChildNodes(element);
this.rerender();

bindContentEditableTypingListeners(this);
bindAutoTypingListeners(this);
Expand All @@ -277,30 +319,11 @@ class Editor {
showForTag: 'a'
}));

if (this.autofocus) { element.focus(); }
}

addView(view) {
this._views.push(view);
}

prepareRenderTree(post) {
let renderTree = new RenderTree();
let node = renderTree.buildRenderNode(post);
renderTree.node = node;
return renderTree;
}

rerender() {
let postRenderNode = this.post.renderNode;
this.rerender();

// if we haven't rendered this post's renderNode before, mark it dirty
if (!postRenderNode.element) {
postRenderNode.element = this.element;
postRenderNode.markDirty();
if (this.autofocus) {
element.focus();
}

this._renderer.render(this._renderTree);
}

handleDeletion(event) {
Expand Down
11 changes: 11 additions & 0 deletions src/js/models/post-node-builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ export default class PostNodeBuilder {
return post;
}

createBlankPost() {
let blankMarkupSection = this.createBlankMarkupSection('p');
return this.createPost([ blankMarkupSection ]);
}

createMarkupSection(tagName, markers=[], isGenerated=false) {
tagName = normalizeTagName(tagName);
const section = new MarkupSection(tagName, markers);
Expand All @@ -30,6 +35,12 @@ export default class PostNodeBuilder {
return section;
}

createBlankMarkupSection(tagName) {
tagName = normalizeTagName(tagName);
let blankMarker = this.createBlankMarker();
return this.createMarkupSection(tagName, [ blankMarker ]);
}

createImageSection(url) {
let section = new ImageSection();
if (url) {
Expand Down
9 changes: 8 additions & 1 deletion src/js/utils/dom-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,12 @@ function normalizeTagName(tagName) {
return tagName.toLowerCase();
}

function parseHTML(html) {
var div = document.createElement('div');
div.innerHTML = html;
return div;
}

export {
detectParentNode,
containsNode,
Expand All @@ -131,5 +137,6 @@ export {
walkTextNodes,
addClassName,
normalizeTagName,
isTextNode
isTextNode,
parseHTML
};
6 changes: 4 additions & 2 deletions tests/acceptance/basic-editor-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ module('Acceptance: editor: basic', {
test('sets element as contenteditable', (assert) => {
let innerHTML = `<p>Hello</p>`;
editorElement.innerHTML = innerHTML;
editor = new Editor(document.getElementById('editor'));
editor = new Editor();
editor.render(editorElement);

assert.equal(editorElement.getAttribute('contenteditable'),
'true',
Expand All @@ -31,7 +32,8 @@ test('sets element as contenteditable', (assert) => {
test('#disableEditing and #enableEditing toggle contenteditable', (assert) => {
let innerHTML = `<p>Hello</p>`;
editorElement.innerHTML = innerHTML;
editor = new Editor(document.getElementById('editor'));
editor = new Editor();
editor.render(editorElement);

assert.equal(editorElement.getAttribute('contenteditable'),
'true',
Expand Down
3 changes: 2 additions & 1 deletion tests/acceptance/editor-cards-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ module('Acceptance: editor: cards', {

test('changing to display state triggers update on editor', (assert) => {
const cards = [simpleCard];
editor = new Editor(editorElement, {mobiledoc, cards});
editor = new Editor({mobiledoc, cards});
editor.render(editorElement);

let updateCount = 0,
triggeredUpdate = () => updateCount++;
Expand Down
3 changes: 2 additions & 1 deletion tests/acceptance/editor-commands-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ module('Acceptance: Editor commands', {
editorElement = document.createElement('div');
editorElement.setAttribute('id', 'editor');
fixture.appendChild(editorElement);
editor = new Editor(editorElement, {mobiledoc});
editor = new Editor({mobiledoc});
editor.render(editorElement);

selectedText = 'IS A';
Helpers.dom.selectText(selectedText, editorElement);
Expand Down
Loading

0 comments on commit 6b05a4f

Please sign in to comment.