Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split render from editor instantiation #89

Merged
merged 1 commit into from
Aug 25, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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