Skip to content

Commit

Permalink
Do not share state when rendering
Browse files Browse the repository at this point in the history
fixes #11
  • Loading branch information
bantic committed Nov 16, 2015
1 parent 2c6dcad commit b51510e
Show file tree
Hide file tree
Showing 5 changed files with 190 additions and 153 deletions.
7 changes: 4 additions & 3 deletions lib/cards/image.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
createElement
} from "../utils";
createElement,
appendChild
} from '../utils/dom';

const ImageCard = {
name: 'image',
Expand All @@ -9,7 +10,7 @@ const ImageCard = {
if (payload.src) {
let img = createElement('img');
img.src = payload.src;
element.appendChild(img);
appendChild(element, img);
}
}
}
Expand Down
147 changes: 0 additions & 147 deletions lib/dom-renderer.js

This file was deleted.

6 changes: 3 additions & 3 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import DOMRenderer from './dom-renderer';
import Renderer from './renderer';

export function registerGlobal(window) {
window.MobiledocDOMRenderer = DOMRenderer;
window.MobiledocDOMRenderer = Renderer;
}

export default DOMRenderer;
export default Renderer;
140 changes: 140 additions & 0 deletions lib/renderer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import {
createElement,
appendChild,
createTextNode,
setAttribute
} from './utils/dom';
import ImageCard from './cards/image';

const MARKUP_SECTION_TYPE = 1;
const IMAGE_SECTION_TYPE = 2;
const LIST_SECTION_TYPE = 3;
const CARD_SECTION_TYPE = 10;

/**
* runtime DOM renderer
* renders a mobiledoc to DOM
*
* input: mobiledoc
* output: DOM
*/

function createElementFromMarkerType([tagName, attributes]=['', []]){
let element = createElement(tagName);
attributes = attributes || [];

for (let i=0,l=attributes.length; i<l; i=i+2) {
let propName = attributes[i],
propValue = attributes[i+1];
setAttribute(element, propName, propValue);
}
return element;
}

function renderMarkersOnElement(element, markers, renderState) {
let elements = [element];
let currentElement = element;

for (let i=0, l=markers.length; i<l; i++) {
let marker = markers[i];
let [openTypes, closeTypes, text] = marker;

for (let j=0, m=openTypes.length; j<m; j++) {
let markerType = renderState.markerTypes[openTypes[j]];
let openedElement = createElementFromMarkerType(markerType);
appendChild(currentElement, openedElement);
elements.push(openedElement);
currentElement = openedElement;
}

appendChild(currentElement, createTextNode(text));

for (let j=0, m=closeTypes; j<m; j++) {
elements.pop();
currentElement = elements[elements.length - 1];
}
}
}

function renderListItem(markers, renderState) {
const element = createElement('li');
renderMarkersOnElement(element, markers, renderState);
return element;
}

function renderListSection([type, tagName, listItems], renderState) {
const element = createElement(tagName);
listItems.forEach(li => {
appendChild(element, (renderListItem(li, renderState)));
});
return element;
}

function renderImageSection([type, src]) {
let element = createElement('img');
element.src = src;
return element;
}

function renderCardSection([type, name, payload], renderState) {
let { cards } = renderState;
let card = cards[name];
if (!card) {
throw new Error(`Cannot render unknown card named ${name}`);
}
if (!payload) {
payload = {};
}
let element = createElement('div');
card.display.setup(element, {}, {name}, payload);
return element;
}

function renderMarkupSection([type, tagName, markers], renderState) {
const element = createElement(tagName);
renderMarkersOnElement(element, markers, renderState);
return element;
}

function renderSection(section, renderState) {
const [type] = section;
switch (type) {
case MARKUP_SECTION_TYPE:
return renderMarkupSection(section, renderState);
case IMAGE_SECTION_TYPE:
return renderImageSection(section, renderState);
case LIST_SECTION_TYPE:
return renderListSection(section, renderState);
case CARD_SECTION_TYPE:
return renderCardSection(section, renderState);
default:
throw new Error('Unimplement renderer for type ' + type);
}
}

export default class Renderer {
/**
* @param {Mobiledoc} mobiledoc
* @param {DOMNode} [rootElement] defaults to an empty div
* @param {Object} [cards] Each top-level property on the object is considered
* to be a card's name, its value is an object with `setup` and (optional) `teardown`
* properties
* @return DOMNode
*/
render({version, sections: sectionData}, root=createElement('div'), cards={}) {
const [markerTypes, sections] = sectionData;
cards.image = cards.image || ImageCard;
const renderState = {root, markerTypes, cards};

if (Array.isArray(cards)) {
throw new Error('`cards` must be passed as an object, not an array.');
}

sections.forEach(section => {
let rendered = renderSection(section, renderState);
appendChild(renderState.root, rendered);
});

return root;
}
}
43 changes: 43 additions & 0 deletions tests/unit/renderer-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -315,3 +315,46 @@ test('multiple spaces should preserve whitespace with nbsps', (assert) => {
let textNode = rendered.firstChild.firstChild;
assert.equal(textNode.textContent, expectedText, 'renders the text');
});

test('rendering nested mobiledocs in cards', (assert) => {
let renderer = new Renderer();

let cards = {
'nested-card': {
display: {
setup(element, options, env, payload) {
renderer.render(payload.mobiledoc, element, cards);
}
}
}
};

let innerMobiledoc = {
version: MOBILEDOC_VERSION,
sections: [
[], // markers
[ // sections
[1, 'P', [
[[], 0, 'hello world']]
]
]
]
};

let mobiledoc = {
version: MOBILEDOC_VERSION,
sections: [
[], // markers
[ // sections
[10, 'nested-card', {mobiledoc: innerMobiledoc}]
]
]
};

let rendered = renderer.render(mobiledoc, document.createElement('div'), cards);
assert.equal(rendered.childNodes.length, 1, 'renders 1 section');
let card = rendered.childNodes[0];
assert.equal(card.childNodes.length, 1, 'card has 1 child');
assert.equal(card.childNodes[0].tagName, 'P', 'card has P child');
assert.equal(card.childNodes[0].innerText, 'hello world');
});

0 comments on commit b51510e

Please sign in to comment.