Передача параметров из команды в конвертер
Я определил новый тип элемента модели как плагин; давайте назовем это как Foo
, Foo
узел в модели должен переводиться в section
элемент в представлении. Все идет нормально. Мне удалось сделать это, определив простые правила преобразования. Мне также удалось определить новый FooCommand
который преобразует (переименовывает) выбранные блоки в Foo
,
Я застрял, пытаясь иметь атрибуты на тех Foo
узлы модели должны быть переведены в атрибуты элементов представления (и наоборот). Предположим, у Foos есть атрибут с именем fooClass
который должен отображаться на элемент представления class
приписывать.
<Foo fooClass="green-foo"> should map to/from <section class="green-foo">
Я могу успешно получить параметры в FooCommand
, но я не могу установить их для блоков, обрабатываемых командой:
execute(options = {}) {
const document = this.editor.document;
const fooClass = options.fooClass;
document.enqueueChanges(() => {
const batch = options.batch || document.batch();
const blocks = (options.selection || document.selection).getSelectedBlocks();
for (const block of blocks) {
if (!block.is('foo')) {
batch.rename(block, 'foo');
batch.setAttribute(block, 'fooClass', fooClass);
}
}
});
}
Ниже приведен код для init
функция в Foo
плагин, включая модель → вид и вид → преобразования модели:
init() {
const editor = this.editor;
const doc = editor.document;
const data = editor.data;
const editing = editor.editing;
editor.commands.add('foo', new FooCommand(editor));
doc.schema.registerItem('foo', '$block');
buildModelConverter().for(data.modelToView, editing.modelToView)
.fromElement('foo')
.toElement(modelElement => {
const fooClass = modelElement.item.getAttribute('fooClass'));
return new ContainerElement('section', {'class': fooClass});
});
buildViewConverter().for(data.viewToModel)
.fromElement('section')
.toElement(viewElement => {
let classes = Array.from(viewElement.getClassNames());
let modelElement = new ModelElement('foo', {'fooClass': classes[0]});
return modelElement;
});
}
Когда я пытаюсь запустить команду через
editor.execute('foo', { fooClass: 'green-foo' })
Я вижу, что green-foo
значение доступно для FooCommand
, но modelElement
в модели → преобразование вида, с другой стороны, не имеет fooClass
приписывать.
Я уверен, что здесь не хватает смысла и неправильно использую API. Я был бы очень благодарен, если бы кто-то мог пролить свет на этот вопрос. Я могу предоставить более подробную информацию, по мере необходимости.
Продолжение после первоначальных предложений
Спасибо @Reinmar и @jodator за их предложения по настройке схемы документа для учета настраиваемого атрибута. Я действительно думал, что позаботился бы об этом, но нет. В любом случае это мог быть необходимый шаг, но я все еще не могу получить значение атрибута из элемента модели во время преобразования модель → представление.
Во-первых, позвольте мне добавить важную информацию, которую я пропустил: версия CKEditor5, с которой я работаю, это 1.0.0-alpha2. Я знаю, что некоторые API-интерфейсы могут измениться, но я все же хотел бы, чтобы все работало с настоящей версией.
Модель → просмотр конверсии
Если я правильно понимаю, можно пройти string
или function
к toElement
вызов. Вопрос об использовании последнего: какие именно параметры передаются в функцию? Я предполагал, что это будет модельный элемент (узел?) Для преобразования. Это тот случай? Если так, то почему атрибут установлен на этом узле через batch.setAttribute
(внутри document.enqueueChanges
) не доступно по запросу? Должно ли это быть?
Проблема секвенирования?
Дополнительное тестирование, похоже, указывает на то, что возникает какая-то проблема порядка выполнения. Я заметил, что, хотя атрибут недоступен, когда я впервые пытаюсь прочитать его с modelElement
Параметр, так и будет, если я прочитаю это позже. Позвольте мне попытаться проиллюстрировать ситуацию ниже. Сначала я изменю код преобразования, чтобы он использовал фиктивное значение в случае, если значение атрибута недоступно при чтении:
buildModelConverter().for(data.modelToView, editing.modelToView)
.fromElement('foo')
.toElement(modelElement => {
let fooClass = modelElement.item.getAttribute('fooClass') || 'naught';
let viewElement = new ContainerElement('section');
viewElement.setAttribute('class', fooClass);
return viewElement;
});
Теперь я перезагружаю страницу и выполняю следующие инструкции на консоли:
c = Array.from(editor.document.getRoot().getChildren());
c[1].is('paragraph'); // true
// Changing the node from 'paragraph' to 'foo' and adding an attribute
// 'fooClass' with value 'green-foo' to it.
editor.document.enqueueChanges(() => {
const batch = editor.document.batch();
batch.rename(c[1], 'foo');
batch.setAttribute(c[1], 'fooClass', 'green-foo');
return batch;
});
c[1].is('paragraph'); // false
c[1].is('foo'); // true
c[1].hasAttribute('fooClass'); // true
c[1].getAttribute('fooClass'); // 'green-foo'
Даже при том, что похоже, что ожидаемый вывод произведен, взгляд на сгенерированный элемент представления показывает проблему:
<section class="naught"/>
Наконец, даже если я попытаюсь сбросить fooClass
атрибут элемента модели, изменение не отражается на элементе вида. Это почему? Не следует вносить изменения через enqueueChanges
заставить вид обновить?
Извините за очень длинный пост, но я пытаюсь передать как можно больше деталей. Мы надеемся, что кто-то заметит мою ошибку или непонимание того, как на самом деле работает API CKEditor 5.
Просмотр не обновляется?
Я повернулся к Document
События и эксперименты с changesDone
событие. Он успешно решает проблему "синхронизации", поскольку он последовательно срабатывает только после обработки всех изменений. Тем не менее, проблема представления не обновляется в ответ на изменение модели остается. Чтобы было понятно, модель меняется, но представление не отражает этого. Вот звонок:
editor.document.enqueueChanges(() => editor.document.batch().setAttribute(c[1], 'fooClass', 'red-foo'));
1 ответ
Чтобы быть на 100% уверенным, я написал всю статью сам. Я использую 1.0.0-beta.1 API, который полностью отличается от того, что у вас было.
В основном - это работает. Это еще не на 100% правильно, но я вернусь к этому.
Как конвертировать пару элемент + атрибут?
При реализации функции, которая должна преобразовывать элемент + атрибут, важно то, что она требует обработки преобразования элемента и атрибута отдельно, так как они обрабатываются CKEditor 5 отдельно.
Поэтому в приведенном ниже коде вы обнаружите, что я использовал elementToElement()
:
editor.conversion.elementToElement( {
model: 'foo',
view: 'section'
} );
Так что конвертер между моделями <foo>
элемент и вид <section>
элемент. Это двусторонний преобразователь, поэтому он выполняет преобразование с повышением частоты (представление -> модель) и преобразование с понижением частоты (модель -> представление).
ПРИМЕЧАНИЕ: он не обрабатывает атрибут.
Теоретически, как view
Свойство, которое вы могли бы написать обратный вызов, который будет читать атрибут элемента модели и создать элемент представления с этим атрибутом установленным тоже. Но это не сработает, потому что такая конфигурация будет иметь смысл только в случае понижения (модель -> вид). Как мы можем использовать этот обратный вызов для понижения структуры представления?
ПРИМЕЧАНИЕ. Вы можете написать конвертеры для нисходящего и восходящего конвейеров отдельно (используя editor.conversion.for()
), в этом случае вы могли бы действительно использовать обратные вызовы. Но это не имеет смысла в этом случае.
Атрибут может измениться независимо!
Другая проблема заключается в том, что вы написали конвертер элементов, который одновременно устанавливает атрибут. Тада, ты грузишь <section class=ohmy>
и получает <foo class=ohmy
> в твоей модели.
Но тогда... что если атрибут изменится в модели?
В нисходящем конвейере CKEditor 5 обрабатывает изменения элементов отдельно от изменений атрибутов. Он запускает их как отдельные события. Итак, когда ваш FooCommand
выполняется по заголовку writer.rename()
и мы получаем следующие события в DowncastDispatcher
:
remove
с<heading>
insert:section
Но тогда атрибут тоже меняется (writer.setAttribute()
), поэтому мы также получаем:
setAttibute:class:section
elementToElement()
помощник по конверсии слушает insert:section
событие. Так что слепо setAttribute:class:selection
,
Поэтому, когда вы меняете значение атрибута, вам нужно attributeToAttribute()
преобразование.
Последовательность действий
Я не хотел отвечать на ваш вопрос до того, как мы выпустили 1.0.0-бета.1, потому что 1.0.0-бета.1 принесла разницу.
До 1.0.0-бета.1 все изменения были преобразованы сразу же после их применения. Так, rename()
приведет к немедленному remove
а также insert:section
События. На данный момент элемент, который вы получили в последнем, не будет иметь class
атрибут установлен.
Благодаря различиям мы можем начать преобразование, как только все изменения будут применены (после change()
блок выполнен). Это означает, что insert:section
событие происходит, как только модель <foo>
элемент имеет class
атрибут уже установлен. Вот почему вы могли бы написать конвертеры на основе обратного вызова... но вы не должны:D
Код
import { downcastAttributeToAttribute } from '@ckeditor/ckeditor5-engine/src/conversion/downcast-converters';
import { upcastAttributeToAttribute } from '@ckeditor/ckeditor5-engine/src/conversion/upcast-converters';
class FooCommand extends Command {
execute( options = {} ) {
const model = this.editor.model;
const fooClass = options.class;
model.change( writer => {
const blocks = model.document.selection.getSelectedBlocks();
for ( const block of blocks ) {
if ( !block.is( 'foo' ) ) {
writer.rename( block, 'foo' );
writer.setAttribute( 'class', fooClass, block );
}
}
} );
}
}
class FooPlugin extends Plugin {
init() {
const editor = this.editor;
editor.commands.add( 'foo', new FooCommand( editor ) );
editor.model.schema.register( 'foo', {
allowAttributes: 'class',
inheritAllFrom: '$block'
} );
editor.conversion.elementToElement( {
model: 'foo',
view: 'section'
} );
editor.conversion.for( 'upcast' ).add(
upcastAttributeToAttribute( {
model: 'class',
view: 'class'
} )
);
editor.conversion.for( 'downcast' ).add(
downcastAttributeToAttribute( {
model: 'class',
view: 'class'
} )
);
// This should work but it does not due to https://github.com/ckeditor/ckeditor5-engine/issues/1379 :(((
// EDIT: The above issue is fixed and will be released in 1.0.0-beta.2.
// editor.conversion.attributeToAttribute( {
// model: {
// name: 'foo',
// key: 'class'
// },
// view: {
// name: 'section',
// key: 'class'
// }
// } );
}
}
Этот код работает довольно хорошо, за исключением того факта, что он преобразует class
атрибут любого возможного элемента, который имеет его. Это потому, что я должен был использовать очень общий downcastAttributeToAttribute()
а также upcastAttributeToAttribute()
конвертеры из-за ошибки, которую я обнаружил (РЕДАКТИРОВАТЬ: это исправлено и будет доступно в 1.0.0-бета.2). Закомментированный кусок кода - это то, как вы должны его определить, если все работало нормально, и оно будет работать в 1.0.0-бета.2.
Печально, что мы пропустили такой простой случай, но это в основном из-за того, что все наши функции... намного сложнее, чем это.