Обновление виджета CKEditor5 при изменении модели

У меня есть настраиваемый элемент схемы с узлом текстового атрибута, который отображается как виджет для editingDowncast:

conversion.for( 'editingDowncast' )
  .add( downcastElementToElement({
    model: 'myModelElement',
    view: (modelElement, viewWriter) => {
      return createMyModelWidget( modelElement, viewWriter, 'Label' );
    }
  } )
);

function createMyModelWidget( modelElement, writer, label ) {
  const content = modelElement.getAttribute('content')
  const placeholder = writer.createText( content );
  const container = writer.createContainerElement( 'div', { class: 'my-model-element--widget' } );

  writer.insert( ViewPosition.createAt( container ), placeholder );
  return toWidget( container, writer, label );
}

Через какое-то внешнее событие (например, результат внешнего модального окна, которое настраивает виджет), атрибут обновляется с помощью model.change.

model.change(writer => {
  writer.setAttribute( 'attribute', 'whatever', widget );
  writer.setAttribute( 'content', 'new content', widget );
});

Теперь, когда его атрибут изменился, я бы ожидал, что виджет будет повторно отрисован, однако это не так. Как я могу вручную запустить обновление, чтобы убедиться, что оно актуально?


person oliverguenther    schedule 13.07.2018    source источник


Ответы (1)


Обновление виджета представления при изменении модели выполняется преобразованием с понижением частоты. Я вижу, что вы хотите преобразовать атрибут модели в текстовое содержимое <div>. Я предполагаю, что атрибут "attribute" является атрибутом элемента <div>s (например, data-attribute). В таком случае вам понадобится набор конвертеров:

  1. Upcast
  2. Downcast
  3. Двусторонний преобразователь, который будет охватывать простой атрибут для атрибута преобразование (как восходящее, так и понижающее).

ps: Я использовал «downcast» вместо «EditDowncast», чтобы не определять понижающее преобразование для «EditDowncast» и «dataDowncast» отдельно.

// Required imports:
// import { toWidget } from '@ckeditor/ckeditor5-widget/src/utils';
// import { downcastElementToElement } from '@ckeditor/ckeditor5-engine/src/conversion/downcast-converters';
// import { upcastElementToElement } from '@ckeditor/ckeditor5-engine/src/conversion/upcast-converters';
// import ViewPosition from '@ckeditor/ckeditor5-engine/src/view/position';
// import ViewRange from '@ckeditor/ckeditor5-engine/src/view/range';

const conversion = editor.conversion;
const model = editor.model;
const schema = model.schema;

// Define model element and allow attributes on it.
schema.register( 'myModelElement', {
    allowWhere: '$block',
    allowAttributes: [ 'content', 'attribute' ],
    isObject: true
} );

// Simple attribute to attribute converter two-way converter
// - for upcast it will read "data-attribute" property of div and put it into model's "attribute" attribute
// - for downcast it will update "data-attribute" on changes in "attribute".
conversion.attributeToAttribute( {
    model: {
        name: 'myModelElement',
        key: 'attribute'
    },
    view: {
        key: 'data-attribute'
    }
} );

// Define upcast conversion:
conversion.for( 'upcast' ).add( upcastElementToElement( {
    model: ( viewElement, modelWriter ) => {
        const firstChild = viewElement.getChild( 0 );
        const text = firstChild.is( 'text' ) ? firstChild.data : '(empty)';

        return modelWriter.createElement( 'myModelElement', { content: text } );
    },
    view: {
        name: 'div',
        classes: 'my-model-element--widget'
    }
} ) );

// Define downcast conversion:
conversion.for( 'downcast' )
    .add( downcastElementToElement( {
        model: 'myModelElement',
        view: ( modelElement, viewWriter ) => {
            return createMyModelWidget( modelElement, viewWriter, 'Label' );
        }
    } ) )
    // Special conversion of attribute "content":
    .add( dispatcher => dispatcher.on( 'attribute:content', ( evt, data, conversionApi ) => {
        const myModelElement = data.item;

        // Mark element as consumed by conversion.
        conversionApi.consumable.consume( data.item, evt.name );

        // Get mapped view element to update.
        const viewElement = conversionApi.mapper.toViewElement( myModelElement );

        // Remove current <div> element contents.
        conversionApi.writer.remove( ViewRange.createOn( viewElement.getChild( 0 ) ) );

        // Set current content
        setContent( conversionApi.writer, data.attributeNewValue, viewElement );
    } ) );

function createMyModelWidget( modelElement, writer, label ) {
    const content = modelElement.getAttribute( 'content' );
    const container = writer.createContainerElement( 'div', { class: 'my-model-element--widget' } );

    setContent( writer, content, container );

    return toWidget( container, writer, label );
}

function setContent( writer, content, container ) {
    const placeholder = writer.createText( content || '' );
    writer.insert( ViewPosition.createAt( container ), placeholder );
}
person jodator    schedule 13.07.2018
comment
Это работает как шарм, спасибо! Я ошибочно ожидал, что смогу обойтись рендерингом виджета сверху вниз и обновлением всего виджета через какое-то событие, отличное от событий немедленного изменения атрибута. Я все еще должен осмыслить некоторые подходы, но оказывается, что они делают именно то, что я хотел бы в первую очередь. - person oliverguenther; 16.07.2018