将参数从Command传递到转换器

时间:2018-03-11 00:34:57

标签: ckeditor ckeditor5

我将一种新型的模型元素定义为插件;我们将其称为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必然会改变,但我仍然希望能够使用现有版本。

模型→视图转换

如果我理解正确,可以将stringfunction传递给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进行更改会导致视图更新吗?

很抱歉很长的帖子,但我尽量传达尽可能多的细节。希望有人能够发现我的错误或误解CKEditor 5的API实际工作原理。

查看未更新?

我转向Document's events并尝试了changesDone事件。它成功地解决了&#34;时间问题。问题,因为只有在处理完所有更改后才会始终触发。尽管如此,仍然存在响应模型变化而未更新视图的问题。为清楚起见,模型确实发生了变化,但视图并没有反映出来。这是电话:

editor.document.enqueueChanges(() => editor.document.batch().setAttribute(c[1], 'fooClass', 'red-foo'));

1 个答案:

答案 0 :(得分:1)

我百分百肯定自己写了整个功能。我使用的是1.0.0-beta.1 API,它与你的API完全不同。

基本上 - 它有效。它还不是100%正确,但我会达到目的。

如何转换元素+属性对?

实现需要转换元素+属性的功能时,它需要单独处理元素和属性转换,因为它们由CKEditor 5单独处理。

因此,在下面的代码中,您会发现我使用了elementToElement()

editor.conversion.elementToElement( {
    model: 'foo',
    view: 'section'
} );

所以模型的<foo>元素和视图的<section>元素之间的转换器。这是一个双向转换器,因此它处理向上转换(视图 - &gt;模型)和向下转换(模型 - &gt;视图)转换。

注意:它不处理属性。

理论上,作为view属性,您可以编写一个回调函数来读取模型元素的属性,并创建具有此属性集的视图元素。但这不起作用,因为这种配置只有在向下转换(模型 - &gt;视图)的情况下才有意义。我们怎样才能使用该回调来转发视图结构呢?

注意:您可以单独编写用于向下和向上管道的转换器(使用editor.conversion.for()),在这种情况下,您可以真正使用回调。但在这种情况下,它确实没有意义。

该属性可能会独立更改!

另一个问题是,假设你写了一个元素转换器,它同时设置了属性。 Tada,您加载<section class=ohmy>并获取<foo class=ohmy&gt;在你的模型中。

但是......如果属性会在模型中发生变化怎么办?

在向下转换管道中,CKEditor 5将元素更改与属性更改分开处理。它将它们作为单独的事件发射。因此,当您在FooCommand标题上执行writer.rename()时,我们会在DowncastDispatcher中收到以下事件:

  1. remove<heading>
  2. insert:section
  3. 但是属性也被改变了(writer.setAttribute()),所以我们也得到了:

    1. setAttibute:class:section
    2. elementToElement()转换帮助器侦听insert:section事件。所以它对setAttribute:class:selection是盲目的。

      因此,当您更改属性的值时,您需要进行attributeToAttribute()转换。

      测序

      我们在发布1.0.0-beta.1之前不想回复您的问题,因为1.0.0-beta.1带来了Differ

      在1.0.0-beta.1之前,所有更改在应用时立即转换。因此,rename()会导致即时的removeinsert: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()转换器,因为bug that I found编辑:它已修复,将在1.0.0-beta.2中提供)。如果一切正常并且它将在1.0.0-beta.2中运行,那么注释掉的代码就是如何定义代码。

      很遗憾我们错过了这么简单的案例,但这主要是因为我们所有的功能......都比这复杂得多。