Skip to content

Commit

Permalink
Adds createComponentAtom util to use Ember.Component as atoms
Browse files Browse the repository at this point in the history
  • Loading branch information
rlivsey authored and bantic committed Apr 19, 2016
1 parent 25199d1 commit f553a4d
Show file tree
Hide file tree
Showing 6 changed files with 213 additions and 12 deletions.
43 changes: 38 additions & 5 deletions addon/components/mobiledoc-editor/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@ import { MOBILEDOC_VERSION } from 'mobiledoc-kit/renderers/mobiledoc';
let { computed, Component } = Ember;
let { capitalize, camelize } = Ember.String;

export const ADD_HOOK = 'addComponent';
export const REMOVE_HOOK = 'removeComponent';
export const ADD_CARD_HOOK = 'addComponent';
export const REMOVE_CARD_HOOK = 'removeComponent';
export const ADD_ATOM_HOOK = 'addAtomComponent';
export const REMOVE_ATOM_HOOK = 'removeAtomComponent';
export const WILL_CREATE_EDITOR_ACTION = 'will-create-editor';
export const DID_CREATE_EDITOR_ACTION = 'did-create-editor';

const EDITOR_CARD_SUFFIX = '-editor';
const EMPTY_MOBILEDOC = {
version: MOBILEDOC_VERSION,
Expand Down Expand Up @@ -63,6 +66,7 @@ export default Component.extend({
this.set('mobiledoc', mobiledoc);
}
this.set('componentCards', Ember.A([]));
this.set('componentAtoms', Ember.A([]));
this.set('linkOffsets', null);
this.set('activeMarkupTagNames', {});
this.set('activeSectionTagNames', {});
Expand Down Expand Up @@ -154,7 +158,7 @@ export default Component.extend({
let editorOptions = this.get('editorOptions');
editorOptions.mobiledoc = mobiledoc;
editorOptions.cardOptions = {
[ADD_HOOK]: ({env, options, payload}, isEditing=false) => {
[ADD_CARD_HOOK]: ({env, options, payload}, isEditing=false) => {
let cardId = Ember.uuid();
let cardName = env.name;
if (isEditing) {
Expand All @@ -180,8 +184,35 @@ export default Component.extend({
});
return { card, element };
},
[REMOVE_HOOK]: (card) => {
[ADD_ATOM_HOOK]: ({env, options, payload, value}) => {
let atomId = Ember.uuid();
let atomName = env.name;
let destinationElementId = `mobiledoc-editor-atom-${atomId}`;
let element = document.createElement('span');
element.id = destinationElementId;

// The data must be copied to avoid sharing the reference
payload = Ember.copy(payload, true);

let atom = Ember.Object.create({
destinationElementId,
atomName,
payload,
value,
callbacks: env,
editor,
postModel: env.postModel
});
Ember.run.schedule('afterRender', () => {
this.get('componentAtoms').pushObject(atom);
});
return { atom, element };
},
[REMOVE_CARD_HOOK]: (card) => {
this.get('componentCards').removeObject(card);
},
[REMOVE_ATOM_HOOK]: (atom) => {
this.get('componentAtoms').removeObject(atom);
}
};
editor = new Editor(editorOptions);
Expand Down Expand Up @@ -233,7 +264,9 @@ export default Component.extend({

willDestroyElement() {
let editor = this.get('editor');
editor.destroy();
try {
editor.destroy();
} catch(e) {}
},

postDidChange(editor) {
Expand Down
11 changes: 11 additions & 0 deletions addon/components/mobiledoc-editor/template.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,14 @@
removeCard=(action card.env.remove)}}
{{/ember-wormhole}}
{{/each}}

{{#each componentAtoms as |atom|}}
{{#ember-wormhole to=atom.destinationElementId}}
{{component atom.atomName
editor=editor
postModel=atom.postModel
atomName=atom.atomName
payload=atom.payload
value=atom.value}}
{{/ember-wormhole}}
{{/each}}
31 changes: 31 additions & 0 deletions addon/utils/create-component-atom.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
const RENDER_TYPE = 'dom';

import { ADD_ATOM_HOOK, REMOVE_ATOM_HOOK } from '../components/mobiledoc-editor/component';

function renderFallback() {
let element = document.createElement('span');
element.innerHTML = '[placeholder for Ember atom]';
return element;
}

export default function createComponentAtom(name) {

return {
name,
type: RENDER_TYPE,
render(atomArg) {
let {env, options} = atomArg;
if (!options[ADD_ATOM_HOOK]) {
return renderFallback();
}

let { atom, element } = options[ADD_ATOM_HOOK](atomArg);
let { onTeardown } = env;

onTeardown(() => options[REMOVE_ATOM_HOOK](atom));

return element;
}
};

}
14 changes: 7 additions & 7 deletions addon/utils/create-component-card.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const RENDER_TYPE = 'dom';

import { ADD_HOOK, REMOVE_HOOK } from '../components/mobiledoc-editor/component';
import { ADD_CARD_HOOK, REMOVE_CARD_HOOK } from '../components/mobiledoc-editor/component';

function renderFallback() {
let element = document.createElement('div');
Expand All @@ -15,28 +15,28 @@ export default function createComponentCard(name) {
type: RENDER_TYPE,
render(cardArg) {
let {env, options} = cardArg;
if (!options[ADD_HOOK]) {
if (!options[ADD_CARD_HOOK]) {
return renderFallback();
}

let { card, element } = options[ADD_HOOK](cardArg);
let { card, element } = options[ADD_CARD_HOOK](cardArg);
let { onTeardown } = env;

onTeardown(() => options[REMOVE_HOOK](card));
onTeardown(() => options[REMOVE_CARD_HOOK](card));

return element;
},
edit(cardArg) {
let {env, options} = cardArg;
if (!options[ADD_HOOK]) {
if (!options[ADD_CARD_HOOK]) {
return renderFallback();
}

let isEditing = true;
let { card, element } = options[ADD_HOOK](cardArg, isEditing);
let { card, element } = options[ADD_CARD_HOOK](cardArg, isEditing);
let { onTeardown } = env;

onTeardown(() => options[REMOVE_HOOK](card));
onTeardown(() => options[REMOVE_CARD_HOOK](card));

return element;
}
Expand Down
93 changes: 93 additions & 0 deletions tests/integration/components/mobiledoc-editor/component-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { moduleForComponent, test } from 'ember-qunit';
import { selectRange } from 'dummy/tests/helpers/selection';
import hbs from 'htmlbars-inline-precompile';
import createComponentCard from 'ember-mobiledoc-editor/utils/create-component-card';
import createComponentAtom from 'ember-mobiledoc-editor/utils/create-component-atom';
import moveCursorTo from '../../../helpers/move-cursor-to';
import simulateMouseup from '../../../helpers/simulate-mouse-up';
import Ember from 'ember';
Expand Down Expand Up @@ -807,3 +808,95 @@ test('#activeSectionTagNames is correct when a card is selected', function(asser
done();
});
});

test('wraps component-atom adding in runloop correctly', function(assert) {
assert.expect(3);
let mobiledoc = simpleMobileDoc('Howdy');
let editor;

this.set('mobiledoc', mobiledoc);
this.register('component:gather-editor', Ember.Component.extend({
didRender() {
editor = this.get('editor');
}
}));
this.registry.register('template:components/demo-atom', hbs`
<span id="demo-atom">demo-atom</span>
`);
this.set('atoms', [createComponentAtom('demo-atom')]);
this.set('mobiledoc', simpleMobileDoc(''));
this.render(hbs`
{{#mobiledoc-editor mobiledoc=mobiledoc atoms=atoms as |editor|}}
{{gather-editor editor=editor.editor}}
{{/mobiledoc-editor}}
`);

// Add an atom without being in a runloop
assert.ok(!Ember.run.currentRunLoop, 'precond - no run loop');
editor.run((postEditor) => {
moveCursorTo(this, 'p:first');
let position = editor.cursor.offsets.head;
let atom = postEditor.builder.createAtom('demo-atom', 'value', {});
postEditor.insertMarkers(position, [atom]);
});
assert.ok(!Ember.run.currentRunLoop, 'postcond - no run loop after editor.run');

assert.ok(this.$('#demo-atom').length, 'demo atom is added');
});

test('throws on unknown atom when `unknownAtomHandler` is not passed', function(assert) {
this.set('mobiledoc', {
version: MOBILEDOC_VERSION,
atoms: [
['missing-atom', 'value', {}]
],
markups: [],
cards: [],
sections: [
[1, 'P', [
[1, [], 0, 0]]
]
]
});
this.set('unknownAtomHandler', undefined);

assert.throws(() => {
this.render(hbs`
{{#mobiledoc-editor mobiledoc=mobiledoc
options=(hash unknownAtomHandler=unknownAtomHandler) as |editor|}}
{{/mobiledoc-editor}}
`);
}, /Unknown atom "missing-atom" found.*no unknownAtomHandler/);
});

test('calls `unknownAtomHandler` when it renders an unknown atom', function(assert) {
assert.expect(4);
let expectedPayload = {};

this.set('unknownAtomHandler', ({env, value, payload}) => {
assert.equal(env.name, 'missing-atom', 'correct env.name');
assert.equal(value, 'value', 'correct name');
assert.ok(!!env.onTeardown, 'has onTeardown hook');
assert.deepEqual(payload, expectedPayload, 'has payload');
});

this.set('mobiledoc', {
version: MOBILEDOC_VERSION,
atoms: [
['missing-atom', 'value', expectedPayload]
],
markups: [],
cards: [],
sections: [
[1, 'P', [
[1, [], 0, 0]]
]
]
});

this.render(hbs`
{{#mobiledoc-editor mobiledoc=mobiledoc
options=(hash unknownAtomHandler=unknownAtomHandler) as |editor|}}
{{/mobiledoc-editor}}
`);
});
33 changes: 33 additions & 0 deletions tests/unit/utils/create-component-atom-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import createComponentAtom from 'ember-mobiledoc-editor/utils/create-component-atom';
import { module, test } from 'qunit';
import MobiledocDOMRenderer from 'mobiledoc-dom-renderer';

module('Unit | Utility | create component atom');

test('it creates an atom', function(assert) {
var result = createComponentAtom('foo-atom');
assert.ok(result.name === 'foo-atom' &&
result.type === 'dom' &&
typeof result.render === 'function',
'created a named atom'
);
});

test('it creates a renderable atom', function(assert) {
var atom = createComponentAtom('foo-atom');
let renderer = new MobiledocDOMRenderer({atoms: [atom]});

let {result} = renderer.render({
version: '0.3.0',
atoms: [
['foo-atom', '', {}]
],
sections: [
[1, 'P', [
[1, [], 0, 0]]
]
]
});

assert.ok(result, 'atom rendered');
});

0 comments on commit f553a4d

Please sign in to comment.