| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188 |
- /**
- * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
- 'use strict';
- import diff from '../utils-diff.js';
- import CKEditorError from '../ckeditorerror.js';
- /**
- * Renderer updates DOM tree, to make it a reflection of the view tree. Changed nodes need to be
- * {@link core.treeView.Renderer#markToSync marked} to be rendered. Then, on {@link core.treeView.Renderer#render render}, renderer
- * ensure they need to be refreshed and creates DOM nodes from view nodes,
- * {@link core.treeView.Converter#bindElements bind} them and insert into DOM tree. Renderer use {@link core.treeView.Converter}
- * to transform and bind nodes.
- *
- * @memberOf core.treeView
- */
- export default class Renderer {
- /**
- * Creates a renderer instance.
- *
- * @param {core.treeView.Converter} converter Converter instance.
- */
- constructor( converter ) {
- /**
- * Converter instance.
- *
- * @readonly
- * @member {core.treeView.Converter} core.treeView.Renderer#converter
- */
- this.converter = converter;
- /**
- * Set of nodes which attributes changed and may need to be rendered.
- *
- * @readonly
- * @member {Set.<core.treeView.Node>} core.treeView.Renderer#markedAttributes
- */
- this.markedAttributes = new Set();
- /**
- * Set of elements which child lists changed and may need to be rendered.
- *
- * @readonly
- * @member {Set.<core.treeView.Node>} core.treeView.Renderer#markedChildren
- */
- this.markedChildren = new Set();
- /**
- * Set of text nodes which text data changed and may need to be rendered.
- *
- * @readonly
- * @member {Set.<core.treeView.Node>} core.treeView.Renderer#markedTexts
- */
- this.markedTexts = new Set();
- }
- /**
- * Mark node to be synchronized.
- *
- * Note that only view nodes which parents have corresponding DOM elements need to be marked to be synchronized.
- *
- * @see core.treeView.Renderer#markedAttributes
- * @see core.treeView.Renderer#markedChildren
- * @see core.treeView.Renderer#markedTexts
- *
- * @param {core.treeView.ChangeType} type Type of the change.
- * @param {core.treeView.Node} node Node to be marked.
- */
- markToSync( type, node ) {
- if ( type === 'TEXT' ) {
- if ( this.converter.getCorrespondingDom( node.parent ) ) {
- this.markedTexts.add( node );
- }
- } else {
- // If the node has no DOM element it is not rendered yet,
- // its children/attributes do not need to be marked to be sync.
- if ( !this.converter.getCorrespondingDom( node ) ) {
- return;
- }
- if ( type === 'ATTRIBUTES' ) {
- this.markedAttributes.add( node );
- } else if ( type === 'CHILDREN' ) {
- this.markedChildren.add( node );
- } else {
- /**
- * Unknown type passed to Renderer.markToSync.
- *
- * @error renderer-unknown-type
- */
- throw new CKEditorError( 'renderer-unknown-type: Unknown type passed to Renderer.markToSync.' );
- }
- }
- }
- /**
- * Render method check {@link core.treeView.Renderer#markedAttributes}, {@link core.treeView.Renderer#markedChildren} and
- * {@link core.treeView.Renderer#markedTexts} and updated all nodes which needs to be updated. Then it clear all three
- * sets.
- *
- * Renderer try not to bread IME, so it do as little as it is possible to update DOM.
- *
- * For attributes it adds new attributes to DOM elements, update attributes with different values and remove
- * attributes which does not exists in the view element.
- *
- * For text nodes it update the text string if it is different. Note that if parent element is marked as an element
- * which changed child list, text node update will not be done, because it may not be possible do find a
- * {@link core.treeView.Converter#getCorrespondingDomText corresponding DOM text}. The change will be handled in the
- * parent element.
- *
- * For nodes which changed child list it calculates a {@link diff} using {@link core.treeView.Converter#compareNodes}
- * and add or removed nodes which changed.
- */
- render() {
- const converter = this.converter;
- for ( let node of this.markedTexts ) {
- if ( !this.markedChildren.has( node.parent ) && converter.getCorrespondingDom( node.parent ) ) {
- updateText( node );
- }
- }
- for ( let element of this.markedAttributes ) {
- updateAttrs( element );
- }
- for ( let element of this.markedChildren ) {
- updateChildren( element );
- }
- this.markedTexts.clear();
- this.markedAttributes.clear();
- this.markedChildren.clear();
- function updateText( viewText ) {
- const domText = converter.getCorrespondingDom( viewText );
- if ( domText.data != viewText.data ) {
- domText.data = viewText.data;
- }
- }
- function updateAttrs( viewElement ) {
- const domElement = converter.getCorrespondingDom( viewElement );
- const domAttrKeys = Array.from( domElement.attributes ).map( attr => attr.name );
- const viewAttrKeys = viewElement.getAttributeKeys();
- // Add or overwrite attributes.
- for ( let key of viewAttrKeys ) {
- domElement.setAttribute( key, viewElement.getAttribute( key ) );
- }
- // Remove from DOM attributes which do not exists in the view.
- for ( let key of domAttrKeys ) {
- if ( !viewElement.hasAttribute( key ) ) {
- domElement.removeAttribute( key );
- }
- }
- }
- function updateChildren( viewElement ) {
- const domElement = converter.getCorrespondingDom( viewElement );
- const domChildren = domElement.childNodes;
- const viewChildren = Array.from( viewElement.getChildren() );
- const domDocument = domElement.ownerDocument;
- const actions = diff( domChildren, viewChildren,
- ( domNode, viewNode ) => converter.compareNodes( domNode, viewNode ) );
- let i = 0;
- for ( let action of actions ) {
- if ( action === 'INSERT' ) {
- let domChildToInsert = converter.viewToDom( viewChildren[ i ], domDocument, { bind: true } );
- domElement.insertBefore( domChildToInsert, domChildren[ i ] || null );
- i++;
- } else if ( action === 'DELETE' ) {
- domElement.removeChild( domChildren[ i ] );
- } else { // 'EQUAL'
- i++;
- }
- }
- }
- }
- }
|