Skip to content
This repository has been archived by the owner on Jun 26, 2020. It is now read-only.

Commit

Permalink
Merge pull request #1233 from ckeditor/t/ckeditor5-core/110
Browse files Browse the repository at this point in the history
BREAKING CHANGE: `view/Document#roots` and `model/Document#roots` now are `Collection` instances.
BREAKING CHANGE `view/Document#createRoot` is removed in favor of roots collections binding.
BREAKING CHANGE: `view/Model#hasRoot` is removed in favor of `view/Model#getRoot`.
BREAKING CHANGE: `EditingController#createRoot` is removed.
  • Loading branch information
Piotr Jasiun committed Jan 11, 2018
2 parents 2592bf1 + 6f1dd9d commit 6c388dd
Show file tree
Hide file tree
Showing 35 changed files with 349 additions and 352 deletions.
40 changes: 16 additions & 24 deletions src/controller/editingcontroller.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* @module engine/controller/editingcontroller
*/

import RootEditableElement from '../view/rooteditableelement';
import ModelDiffer from '../model/differ';
import ViewDocument from '../view/document';
import Mapper from '../conversion/mapper';
Expand Down Expand Up @@ -141,33 +142,24 @@ export default class EditingController {
this.modelToView.on( 'selection', clearFakeSelection(), { priority: 'low' } );
this.modelToView.on( 'selection', convertRangeSelection(), { priority: 'low' } );
this.modelToView.on( 'selection', convertCollapsedSelection(), { priority: 'low' } );
}

/**
* {@link module:engine/view/document~Document#createRoot Creates} a view root
* and {@link module:engine/conversion/mapper~Mapper#bindElements binds}
* the model root with view root and and view root with DOM element:
*
* editing.createRoot( document.querySelector( div#editor ) );
*
* If the DOM element is not available at the time you want to create a view root, for instance it is iframe body
* element, it is possible to create view element and bind the DOM element later:
*
* editing.createRoot( 'body' );
* editing.view.attachDomRoot( iframe.contentDocument.body );
*
* @param {Element|String} domRoot DOM root element or the name of view root element if the DOM element will be
* attached later.
* @param {String} [name='main'] Root name.
* @returns {module:engine/view/containerelement~ContainerElement} View root element.
*/
createRoot( domRoot, name = 'main' ) {
const viewRoot = this.view.createRoot( domRoot, name );
const modelRoot = this.model.document.getRoot( name );
// Binds {@link module:engine/view/document~Document#roots view roots collection} to
// {@link module:engine/model/document~Document#roots model roots collection} so creating
// model root automatically creates corresponding view root.
this.view.roots.bindTo( this.model.document.roots ).using( root => {
// $graveyard is a special root that has no reflection in the view.
if ( root.rootName == '$graveyard' ) {
return null;
}

this.mapper.bindElements( modelRoot, viewRoot );
const viewRoot = new RootEditableElement( root.name );

return viewRoot;
viewRoot.rootName = root.rootName;
viewRoot.document = this.view;
this.mapper.bindElements( root, viewRoot );

return viewRoot;
} );
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/dev-utils/enableenginedebug.js
Original file line number Diff line number Diff line change
Expand Up @@ -679,7 +679,7 @@ class DebugPlugin extends Plugin {
function dumpTrees( document, version ) {
let string = '';

for ( const root of document.roots.values() ) {
for ( const root of document.roots ) {
string += root.printTree() + '\n';
}

Expand Down
39 changes: 9 additions & 30 deletions src/model/document.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import RootElement from './rootelement';
import History from './history';
import DocumentSelection from './documentselection';
import TreeWalker from './treewalker';
import Collection from '@ckeditor/ckeditor5-utils/src/collection';
import clone from '@ckeditor/ckeditor5-utils/src/lib/lodash/clone';
import EmitterMixin from '@ckeditor/ckeditor5-utils/src/emittermixin';
import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
Expand Down Expand Up @@ -76,9 +77,9 @@ export default class Document {
* {@link #getRoot} to manipulate it.
*
* @readonly
* @member {Map}
* @member {module:utils/collection~Collection}
*/
this.roots = new Map();
this.roots = new Collection( { idProperty: 'rootName' } );

// Add events that will ensure selection correctness.
this.selection.on( 'change:range', () => {
Expand Down Expand Up @@ -155,7 +156,7 @@ export default class Document {
* @returns {module:engine/model/rootelement~RootElement} Created root.
*/
createRoot( elementName = '$root', rootName = 'main' ) {
if ( this.roots.has( rootName ) ) {
if ( this.roots.get( rootName ) ) {
/**
* Root with specified name already exists.
*
Expand All @@ -170,7 +171,7 @@ export default class Document {
}

const root = new RootElement( this, elementName, rootName );
this.roots.set( rootName, root );
this.roots.add( root );

return root;
}
Expand All @@ -187,42 +188,20 @@ export default class Document {
* Returns top-level root by its name.
*
* @param {String} [name='main'] Unique root name.
* @returns {module:engine/model/rootelement~RootElement} Root registered under given name.
* @returns {module:engine/model/rootelement~RootElement|null} Root registered under given name or null when
* there is no root of given name.
*/
getRoot( name = 'main' ) {
if ( !this.roots.has( name ) ) {
/**
* Root with specified name does not exist.
*
* @error model-document-getRoot-root-not-exist
* @param {String} name
*/
throw new CKEditorError(
'model-document-getRoot-root-not-exist: Root with specified name does not exist.',
{ name }
);
}

return this.roots.get( name );
}

/**
* Checks if root with given name is defined.
*
* @param {String} name Name of root to check.
* @returns {Boolean}
*/
hasRoot( name ) {
return this.roots.has( name );
}

/**
* Returns array with names of all roots (without the {@link #graveyard}) added to the document.
*
* @returns {Array.<String>} Roots names.
*/
getRootNames() {
return Array.from( this.roots.keys() ).filter( name => name != graveyardName );
return Array.from( this.roots, root => root.rootName ).filter( name => name != graveyardName );
}

/**
Expand Down Expand Up @@ -301,7 +280,7 @@ export default class Document {
* @returns {module:engine/model/rootelement~RootElement} The default root for this document.
*/
_getDefaultRoot() {
for ( const root of this.roots.values() ) {
for ( const root of this.roots ) {
if ( root !== this.graveyard ) {
return root;
}
Expand Down
2 changes: 1 addition & 1 deletion src/model/operation/rootattributeoperation.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ export default class RootAttributeOperation extends Operation {
* @returns {module:engine/model/operation/rootattributeoperation~RootAttributeOperation}
*/
static fromJSON( json, document ) {
if ( !document.hasRoot( json.root ) ) {
if ( !document.getRoot( json.root ) ) {
/**
* Cannot create RootAttributeOperation for document. Root with specified name does not exist.
*
Expand Down
2 changes: 1 addition & 1 deletion src/model/position.js
Original file line number Diff line number Diff line change
Expand Up @@ -764,7 +764,7 @@ export default class Position {
return new Position( doc.graveyard, json.path );
}

if ( !doc.hasRoot( json.root ) ) {
if ( !doc.getRoot( json.root ) ) {
/**
* Cannot create position for document. Root with specified name does not exist.
*
Expand Down
80 changes: 24 additions & 56 deletions src/view/document.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import Selection from './selection';
import Renderer from './renderer';
import DomConverter from './domconverter';
import RootEditableElement from './rooteditableelement';
import { injectQuirksHandling } from './filler';
import { injectUiElementHandling } from './uielement';
import log from '@ckeditor/ckeditor5-utils/src/log';
Expand All @@ -19,6 +18,7 @@ import SelectionObserver from './observer/selectionobserver';
import FocusObserver from './observer/focusobserver';
import KeyObserver from './observer/keyobserver';
import FakeSelectionObserver from './observer/fakeselectionobserver';
import Collection from '@ckeditor/ckeditor5-utils/src/collection';
import mix from '@ckeditor/ckeditor5-utils/src/mix';
import ObservableMixin from '@ckeditor/ckeditor5-utils/src/observablemixin';
import { scrollViewportToShowTarget } from '@ckeditor/ckeditor5-utils/src/dom/scroll';
Expand Down Expand Up @@ -74,12 +74,17 @@ export default class Document {
this.domConverter = new DomConverter();

/**
* Roots of the view tree. Map of the {module:engine/view/element~Element view elements} with roots names as keys.
* Roots of the view tree. Collection of the {module:engine/view/element~Element view elements}.
*
* View roots are created as a result of binding between {@link module:engine/view/document~Document#roots} and
* {@link module:engine/model/document~Document#roots} and this is handled by
* {@link module:engine/controller/editingcontroller~EditingController}, so to create view root we need to create
* model root using {@link module:engine/model/document~Document#createRoot}.
*
* @readonly
* @member {Map} module:engine/view/document~Document#roots
* @member {Collection} module:engine/view/document~Document#roots
*/
this.roots = new Map();
this.roots = new Collection( { idProperty: 'rootName' } );

/**
* Defines whether document is in read-only mode.
Expand Down Expand Up @@ -177,69 +182,31 @@ export default class Document {
}

/**
* Creates a {@link module:engine/view/document~Document#roots view root element}.
*
* If the DOM element is passed as a first parameter it will be automatically
* {@link module:engine/view/document~Document#attachDomRoot attached}:
*
* document.createRoot( document.querySelector( 'div#editor' ) ); // Will call document.attachDomRoot.
*
* However, if the string is passed, then only the view element will be created and the DOM element have to be
* attached separately:
*
* document.createRoot( 'body' );
* document.attachDomRoot( document.querySelector( 'body#editor' ) );
*
* In both cases, {@link module:engine/view/rooteditableelement~RootEditableElement#rootName element name} is always
* transformed to lower
* case.
*
* @param {Element|String} domRoot DOM root element or the tag name of view root element if the DOM element will be
* attached later.
* @param {String} [name='main'] Name of the root.
* @returns {module:engine/view/rooteditableelement~RootEditableElement} The created view root element.
*/
createRoot( domRoot, name = 'main' ) {
const rootTag = typeof domRoot == 'string' ? domRoot : domRoot.tagName;

const viewRoot = new RootEditableElement( rootTag.toLowerCase(), name );
viewRoot.document = this;

this.roots.set( name, viewRoot );

// Mark changed nodes in the renderer.
viewRoot.on( 'change:children', ( evt, node ) => this.renderer.markToSync( 'children', node ) );
viewRoot.on( 'change:attributes', ( evt, node ) => this.renderer.markToSync( 'attributes', node ) );
viewRoot.on( 'change:text', ( evt, node ) => this.renderer.markToSync( 'text', node ) );

if ( this.domConverter.isElement( domRoot ) ) {
this.attachDomRoot( domRoot, name );
}

return viewRoot;
}

/**
* Attaches DOM root element to the view element and enable all observers on that element. This method also
* {@link module:engine/view/renderer~Renderer#markToSync mark element} to be synchronized with the view what means that all child
* nodes will be removed and replaced with content of the view root.
* Attaches DOM root element to the view element and enable all observers on that element.
* Also {@link module:engine/view/renderer~Renderer#markToSync mark element} to be synchronized with the view
* what means that all child nodes will be removed and replaced with content of the view root.
*
* Note that {@link module:engine/view/document~Document#createRoot} will call this method automatically if the DOM element is
* passed to it.
* This method also will change view element name as the same as tag name of given dom root.
* Name is always transformed to lower case.
*
* @param {Element|String} domRoot DOM root element.
* @param {Element} domRoot DOM root element.
* @param {String} [name='main'] Name of the root.
*/
attachDomRoot( domRoot, name = 'main' ) {
const viewRoot = this.getRoot( name );

this.domRoots.set( name, domRoot );
// Set view root name the same as DOM root tag name.
viewRoot._name = domRoot.tagName.toLowerCase();

this.domRoots.set( name, domRoot );
this.domConverter.bindElements( domRoot, viewRoot );

this.renderer.markToSync( 'children', viewRoot );
this.renderer.domDocuments.add( domRoot.ownerDocument );

viewRoot.on( 'change:children', ( evt, node ) => this.renderer.markToSync( 'children', node ) );
viewRoot.on( 'change:attributes', ( evt, node ) => this.renderer.markToSync( 'attributes', node ) );
viewRoot.on( 'change:text', ( evt, node ) => this.renderer.markToSync( 'text', node ) );

for ( const observer of this._observers.values() ) {
observer.observe( domRoot, name );
}
Expand All @@ -250,7 +217,8 @@ export default class Document {
* specific "main" root is returned.
*
* @param {String} [name='main'] Name of the root.
* @returns {module:engine/view/rooteditableelement~RootEditableElement} The view root element with the specified name.
* @returns {module:engine/view/rooteditableelement~RootEditableElement|null} The view root element with the specified name
* or null when there is no root of given name.
*/
getRoot( name = 'main' ) {
return this.roots.get( name );
Expand Down
14 changes: 13 additions & 1 deletion src/view/rooteditableelement.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ export default class RootEditableElement extends EditableElement {
/**
* Creates root editable element.
*
* @param {module:engine/view/document~Document} document {@link module:engine/view/document~Document} that is an owner of the root.
* @param {String} name Node name.
*/
constructor( name ) {
Expand Down Expand Up @@ -56,4 +55,17 @@ export default class RootEditableElement extends EditableElement {
set rootName( rootName ) {
this.setCustomProperty( rootNameSymbol, rootName );
}

/**
* Overrides old element name and sets new one.
* This is needed because view roots are created before they are attached to the DOM.
* The name of the root element is temporary at this stage. It has to be changed when the
* view root element is attached to the DOM element.
*
* @protected
* @param {String} name The new name of element.
*/
set _name( name ) {
this.name = name;
}
}
Loading

0 comments on commit 6c388dd

Please sign in to comment.