Skip to content

Commit

Permalink
Merge pull request #4 from alexeckermann/tests
Browse files Browse the repository at this point in the history
v0.2.0 Change to CSS selectors
  • Loading branch information
alexeckermann authored Jul 5, 2018
2 parents cfbb627 + f621136 commit 5a8b223
Show file tree
Hide file tree
Showing 8 changed files with 223 additions and 64 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
87 changes: 43 additions & 44 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,21 @@ Emptyness — _How empty is **your** CKEditor 5 instance?_

Why does something so simple exist? With great simplicity comes great possibilities.

...

Well maybe _one_ possibility. The purpose of this plugin was to have an observable way to know if an editor instance has content or not.
Well... maybe _one_ possibility. The purpose of this plugin was to have an observable way to know if an editor instance has content or not.

Specifically, how can an application know when an editor is empty so it can show a placeholder — like an input element.

## Documentation

Simply build your editor with this plugin and you will get access to a `isEmpty` observable attribute (care of `ObservableMixin`) on your editor instance.
Simply build your editor with this plugin and you will get access to a `isEmpty` observable attribute (care of `Observable`) on your editor instance. Additionaly, the plugin will add a `ck-editor__is-empty` class name on the editor element when is is empty.

The origin use case was for the application to observe the attribute and know when the editor enters or exits an empty state. From there the application applies an attribute to the editors HTML element so that a CSS based placeholder can be applied — using CSS's `::before` rules to inject 'phantom' content without disturbing CKEditor's actual content, and no messy invisible HTML elements.
The origin use case was for the application to observe the attribute and know when the editor enters or exits an empty state. From there the application applies an attribute to the editors HTML element so that a CSS based placeholder can be applied — using CSS's `::before` rules to inject _phantom_ content without disturbing CKEditor's actual content.

### General Example
### Placeholder Example

```
```js
import BalloonEditorBase from '@ckeditor/ckeditor5-editor-balloon/src/ballooneditor';
import EmptynessPlugin from 'ckeditor5-emptyness/src/emptyness';
import Emptyness from 'ckeditor5-emptyness/src/emptyness';
// .. all your other imports here

export default class MyEditor extends BalloonEditorBase { }
Expand All @@ -29,61 +27,62 @@ MyEditor.build = {

plugins: [
// ... all your other plugins here
EmptynessPlugin
Emptyness
]

};
```

```css
.ck-editor__is-empty .ck-content.ck-editor__editable::before,
.ck-editor__is-empty.ck-content.ck-editor__editable::before {
content: 'The editor is empty';
position: absolute;
display: block;

margin: var(--ck-spacing-large) 0;

color: #aaa;
}
```
const element = document.querySelector('#editor');

MyEditor.create( element ).then( editor => {
**_Whats with the weird CSS scope?_**

editor.on( 'change:isEmpty', () => {
console.log( 'is you empty or is you aint?', editor.isEmpty ? 'is empty' : 'aint empty' );
} );
Depending on the editor, the element used for content may not be the same as the one used when initialising the editor. Editors with toolbars will generate a new element structure. To cover all bases the scope is what it is -- where the `ck-editor__is-empty` class name is on the same element as the `ck-content` element or on a parent element.

} );
```
## Changelog

### Placeholder Example

```
const element = document.querySelector('#editor');
### v0.2.0 - 4 July 2018

MyEditor.create( element ).then( editor => {
- Updating event listener to use `change:data` event on the editors document.
- No more `data-empty`! The plugin now extends the editors template with a bound CSS class name.
- Plugin now depends on CKEditor 5 core and engine `^10.1.0`.
- We now have tests!
- Updated README.

editor.on( 'change:isEmpty', () => {
### v0.1.0

if ( editor.isEmpty ) {
element.setAttribute( 'data-empty', true );
} else {
element.removeAttribute( 'data-empty' );
}
- Initial _rough_ version.
- No tests.

} );
} );
```
## Testing

```
#editor[data-empty]::before {
content: 'Untitled';
position: absolute;
display: block;
color: #aaa;
font-weight: bold;
}
```
### Required setup

**_Why can't we use CSS classes to use as a target for CSS?_**
1. Setup the CKEditor [development environment](https://docs.ckeditor.com/ckeditor5/latest/framework/guides/contributing/development-environment.html).
2. Add a dependency to the CKEditor project development environment:
1. `cd ckeditor` enter into the CKEditor project as created in step 1
2. Open and edit `mgit.json`
3. Add `"ckeditor5-emptyness": "alexeckermann/ckeditor5-emptyness` to the dependencies list
3. `mgit update`
4. `lerna bootstrap --scope=ckeditor5-emptyness`

The HTML element used for CKEditor instances is a managed view. Throughout use of the editor, the elements class list will change and can remove classes added outside of CKEditors management. In initial testing of this plugin we found no obvious way to extend the CKEditor's view `Template` for a editor view to enable CSS class based functionality. This could be resolved in future given advice from CKEditor maintainers.
### Running tests

## Testing
1. `cd ckeditor` enter into the CKEditor project
2. `npm run test -- --files=emptyness`

TODO _Sorry..._
It is important to note that tests are run from within the CKEditor project and not solely on this project.

## License

Expand Down
22 changes: 15 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
{
"name": "ckeditor5-emptyness",
"version": "0.1.0",
"description": "How empty is your CKEditor instance? Adds isEmpty attribute to editors.",
"version": "0.2.0",
"description": "How empty is your CKEditor instance? Adds observable isEmpty property to editors.",
"keywords": [
"ckeditor5",
"ckeditor5-plugin"
],
"dependencies": {
"@ckeditor/ckeditor5-core": "^10.0.0"
"@ckeditor/ckeditor5-core": "^10.1.0",
"@ckeditor/ckeditor5-engine": "^10.1.0"
},
"devDependencies": {
"@ckeditor/ckeditor5-editor-classic": "^10.0.1",
"@ckeditor/ckeditor5-essentials": "^10.1.0",
"@ckeditor/ckeditor5-paragraph": "^10.1.0",
"@ckeditor/ckeditor5-utils": "^10.1.0",
"eslint": "^4.15.0",
"eslint-config-ckeditor5": "^1.0.7",
"husky": "^0.14.3",
"lint-staged": "^7.0.0"
},
"devDependencies": { },
"engines": {
"node": ">=6.0.0",
"npm": ">=3.0.0"
Expand All @@ -26,7 +36,5 @@
"files": [
"src"
],
"scripts": {

}
"scripts": { }
}
30 changes: 17 additions & 13 deletions src/emptyness.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
import Template from '@ckeditor/ckeditor5-ui/src/template';

export default class Emptyness extends Plugin {

Expand All @@ -8,26 +9,29 @@ export default class Emptyness extends Plugin {

init() {
const editor = this.editor;
const model = editor.data.model;

const doc = editor.model.document;
const view = editor.ui.view;
const bind = view.bindTemplate;

editor.set( 'isEmpty', !modelHasContent(model) );
editor.set( 'isEmpty', !documentHasContent(doc) );

model.on( 'applyOperation', () => {
editor.set( 'isEmpty', !modelHasContent(model) );
return true;
this.listenTo( doc, 'change:data', () => {
editor.set( 'isEmpty', !documentHasContent(doc) );
} );

const bind = Template.bind( editor, editor );

view.extendTemplate( {
attributes: {
class: [
bind.if( 'isEmpty', 'ck-editor__is-empty' )
]
}
} );

}

};

function dataControllerRootHasContent(data) {
return data.hasContent(data.model.getRoot());
};

function modelHasContent(model) {
return model.hasContent(model.document.getRoot());
function documentHasContent(doc) {
return doc.model.hasContent(doc.getRoot());
};
110 changes: 110 additions & 0 deletions tests/emptyness.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import Emptyness from '../src/emptyness';

import ModelText from '@ckeditor/ckeditor5-engine/src/model/text';
import ModelRange from '@ckeditor/ckeditor5-engine/src/model/range';

import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';

import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
import { setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';

testUtils.createSinonSandbox();

describe( 'Emptyness', () => {
let editor, editorElement;

const insertContent = () => {

editor.model.change( writer => {
writer.setSelection( ModelRange.createIn( editor.model.document.getRoot().getChild( 0 ) ) );
editor.model.insertContent( new ModelText( 'test' ), editor.model.document.selection );
} );

};

const removeAllContent = () => {

editor.model.change( writer => {
writer.setSelection( ModelRange.createIn( editor.model.document.getRoot().getChild( 0 ) ) );
editor.model.deleteContent( editor.model.document.selection );
} );

};

beforeEach( () => {
editorElement = document.createElement( 'div' );
document.body.appendChild( editorElement );

return ClassicTestEditor
.create( editorElement, {
plugins: [ Emptyness, Paragraph ]
} )
.then( newEditor => {
editor = newEditor;
} );
} );

afterEach( () => {
editorElement.remove();

return editor.destroy();
} );

it( 'should be loaded', () => {
expect( editor.plugins.get( Emptyness ) ).to.be.instanceOf( Emptyness );
} );

describe( 'init()', () => {

it( 'should set the isEmpty property', () => {
expect( editor.isEmpty ).to.be.true;
} );

} );

describe( 'isEmpty lifecycle', () => {

it( 'should be false when content is inserted', () => {
const setSpy = testUtils.sinon.spy( editor, 'set' );

insertContent();

sinon.assert.calledWithExactly( setSpy, 'isEmpty', false );
} );

it( 'should be true when all content is emptied', () => {
setData( editor.model, '<paragraph>test{}</paragraph>' );

const setSpy = testUtils.sinon.spy( editor, 'set' );

removeAllContent();

sinon.assert.calledWithExactly( setSpy, 'isEmpty', true );
} );

} );

describe( 'view class lifecycle', () => {

it( 'should not have the empty class content is inserted', () => {
expect( editor.ui.view.element.classList.contains('ck-editor__is-empty') ).to.be.true;

insertContent();

expect( editor.ui.view.element.classList.contains('ck-editor__is-empty') ).to.be.false;
} );

it( 'should add the empty class when all content is emptied', () => {
setData( editor.model, '<paragraph>test{}</paragraph>' );

expect( editor.ui.view.element.classList.contains('ck-editor__is-empty') ).to.be.false;

removeAllContent();

expect( editor.ui.view.element.classList.contains('ck-editor__is-empty') ).to.be.true;
} );

} );

} );
17 changes: 17 additions & 0 deletions tests/manual/emptyness.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<head>
<style>
.ck-editor__is-empty .ck-content.ck-editor__editable::before,
.ck-editor__is-empty.ck-content.ck-editor__editable::before {
content: 'The editor is empty';
position: absolute;
display: block;

margin: var(--ck-spacing-large) 0;

color: #aaa;
}
</style>
</head>

<div id="editor">
</div>
15 changes: 15 additions & 0 deletions tests/manual/emptyness.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials';
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';

import Template from '@ckeditor/ckeditor5-ui/src/template';
import Emptyness from '../../src/emptyness';

ClassicEditor
.create( document.querySelector( '#editor' ), {
plugins: [ Essentials, Paragraph, Emptyness ],
toolbar: [ ]
} )
.then( editor => {
window.editor = editor;
} );
5 changes: 5 additions & 0 deletions tests/manual/emptyness.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
1. Upon loading the page the editor should contain the placeholder text: `The editor is empty`.

2. Type something and notice that the placeholder text disappears as expected.

3. Remove all contentent in the editor and see the placeholder reappear.

0 comments on commit 5a8b223

Please sign in to comment.