Skip to content

Commit

Permalink
Merge pull request #7256 from ckeditor/i/6829-isLimit-doc
Browse files Browse the repository at this point in the history
Docs: Improve the docs for "limit elements" and "object elements". Closes #6829.
  • Loading branch information
Reinmar authored May 21, 2020
2 parents ad22791 + e6520ee commit 3cb1c78
Show file tree
Hide file tree
Showing 2 changed files with 171 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ This article assumes that you have already read the {@link framework/guides/arch

## Quick recap

The editor's schema is available in the {@link module:engine/model/model~Model#schema `editor.model.schema`} property. It defines allowed model structures (how model elements can be nested) and allowed attributes (of both elements and text nodes). This information is later used by editing features and the editing engine to decide how to process the model, where to enable features, etc.
The editor's schema is available in the {@link module:engine/model/model~Model#schema `editor.model.schema`} property. It defines allowed model structures (how model elements can be nested), allowed attributes (of both elements and text nodes), and other characteristics (inline vs. block, atomicity in regards of external actions). This information is later used by editing features and the editing engine to decide how to process the model, where to enable features, etc.

Schema rules can be defined by using the {@link module:engine/model/schema~Schema#register `Schema#register()`} or {@link module:engine/model/schema~Schema#extend `Schema#extend()`} methods. The former can be used only once for a given item name which ensures that only a single editing feature can introduce this item. Similarly, `extend()` can only be used for defined items.

Expand Down Expand Up @@ -44,6 +44,71 @@ While this would be incorrect:
</$root>
```

## Defining additional semantics

In addition to setting allowed structures, the schema can also define additional traits of model elements. By using the `is*` properties a feature author may declare how a certain element should be treated by other features and the engine.

### Limit elements

Consider a feature like an image caption. The caption text area should construct a boundary to some internal actions:

* A selection that starts inside should not end outside.
* Pressing <kbd>Backspace</kbd> or <kbd>Delete</kbd> should not delete the area. Pressing <kbd>Enter</kbd> should not split the area.

It should also act as a boundary for external actions. This is mostly enforced by a selection post-fixer that ensures that a selection that starts outside, should not end inside. That means that most actions will either apply to the "outside" of such an element or to a content inside it.

Taken these characteristics, the image caption should be defined as limit element by using the {@link module:engine/model/schema~SchemaItemDefinition#isLimit `isLimit`} property.

```js
schema.register( 'myCaption', {
isLimit: true
} );
```

The engine and various features then check it via {@link module:engine/model/schema~Schema#isLimit `Schema#isLimit()`} and can act accordingly.

<info-box>
"Limit element" does not mean "editable element". The concept of "editability" is reserved for the view and expressed by the {@link module:engine/view/editableelement~EditableElement `EditableElement` class}.
</info-box>

### Object elements

For the image caption as in the example above it does not make much sense to select the caption box, then copy or drag it somewhere else.

A caption without the image that it describes does not make much sense. However, the image is more self-sufficient. Most likely users should be able to select the entire image (with all its internals), then copy or move it around. {@link module:engine/model/schema~SchemaItemDefinition#isObject `isObject`} should be used to mark such behavior.

```js
schema.register( 'myImage', {
isObject: true
} );
```

The {@link module:engine/model/schema~Schema#isObject `Schema#isObject()`} can later be used to check this property.

<info-box>
Every "object" is also a "limit" element.

It means that for every element with `isObject` set to `true`, {@link module:engine/model/schema~Schema#isLimit `Schema#isLimit( element )`} will always return `true`.
</info-box>

### Block elements

Generally speaking, content is usually made out of blocks like paragraphs, list items, images, headings, etc. All these elements should be marked as blocks by using {@link module:engine/model/schema~SchemaItemDefinition#isBlock `isBlock`}.

It is important to remember that a block should not allow another block inside. Container elements like `<blockQuote>` which can contain other block elements should not be marked as blocks.

<info-box>
There is also the `$block` generic item which has `isBlock` set to `true`. Most block type items will inherit from `$block` (through `inheritAllFrom`).
</info-box>

### Inline elements

In the editor, all HTML formatting elements such as `<strong>` or `<code>` are represented by text attributes. Therefore, inline model elements are not to be used for this scenarios.

Currently, the {@link module:engine/model/schema~SchemaItemDefinition#isInline `isInline`} property is used for the `$text` token (so, text nodes) and elements such as `<softBreak>` or placeholder elements such as in the {@link framework/guides/tutorials/implementing-an-inline-widget Implementing an inline widget} tutorial.

The support for inline elements in CKEditor 5 is so far limited to self-contained elements. This is &mdash; all elements marked with `isInline` should also be marked with `isObject`.

## Generic items

There are three basic generic items: `$root`, `$block` and `$text`. They are defined as follows:
Expand Down
136 changes: 105 additions & 31 deletions packages/ckeditor5-engine/src/model/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,12 @@ export default class Schema {
}

/**
* Returns all registered items.
* Returns data of all registered items.
*
* This method should normally be used for reflection purposes (e.g. defining a clone of a certain element,
* checking a list of all block elements, etc).
* Use specific methods (such as {@link #checkChild `checkChild()`} or {@link #isLimit `isLimit()`})
* in other cases.
*
* @returns {Object.<String,module:engine/model/schema~SchemaCompiledItemDefinition>}
*/
Expand All @@ -168,6 +173,11 @@ export default class Schema {
/**
* Returns a definition of the given item or `undefined` if item is not registered.
*
* This method should normally be used for reflection purposes (e.g. defining a clone of a certain element,
* checking a list of all block elements, etc).
* Use specific methods (such as {@link #checkChild `checkChild()`} or {@link #isLimit `isLimit()`})
* in other cases.
*
* @param {module:engine/model/item~Item|module:engine/model/schema~SchemaContextItem|String} item
* @returns {module:engine/model/schema~SchemaCompiledItemDefinition}
*/
Expand Down Expand Up @@ -210,6 +220,9 @@ export default class Schema {
* const paragraphElement = writer.createElement( 'paragraph' );
* schema.isBlock( paragraphElement ); // -> true
*
* See the {@glink framework/guides/deep-dive/schema#block-elements Block elements} section of the "Schema" deep dive}
* guide for more details.
*
* @param {module:engine/model/item~Item|module:engine/model/schema~SchemaContextItem|String} item
*/
isBlock( item ) {
Expand All @@ -219,15 +232,22 @@ export default class Schema {
}

/**
* Returns `true` if the given item is defined to be
* a limit element by {@link module:engine/model/schema~SchemaItemDefinition}'s `isLimit` or `isObject` property
* (all objects are also limits).
* Returns `true` if the given item should be treated as a limit element.
*
* It considers the item to be a limit element if its
* {@link module:engine/model/schema~SchemaItemDefinition}'s
* {@link module:engine/model/schema~SchemaItemDefinition#isLimit `isLimit`} or
* {@link module:engine/model/schema~SchemaItemDefinition#isObject `isObject`} property
* were set to `true`.
*
* schema.isLimit( 'paragraph' ); // -> false
* schema.isLimit( '$root' ); // -> true
* schema.isLimit( editor.model.document.getRoot() ); // -> true
* schema.isLimit( 'image' ); // -> true
*
* See the {@glink framework/guides/deep-dive/schema#limit-elements Limit elements} section of the "Schema" deep dive}
* guide for more details.
*
* @param {module:engine/model/item~Item|module:engine/model/schema~SchemaContextItem|String} item
*/
isLimit( item ) {
Expand All @@ -241,15 +261,22 @@ export default class Schema {
}

/**
* Returns `true` if the given item is defined to be
* an object element by {@link module:engine/model/schema~SchemaItemDefinition}'s `isObject` property.
* Returns `true` if the given item is should be treated as an object element.
*
* It considers the item to be an object element if its
* {@link module:engine/model/schema~SchemaItemDefinition}'s
* {@link module:engine/model/schema~SchemaItemDefinition#isObject `isObject`} property
* were set to `true`.
*
* schema.isObject( 'paragraph' ); // -> false
* schema.isObject( 'image' ); // -> true
*
* const imageElement = writer.createElement( 'image' );
* schema.isObject( imageElement ); // -> true
*
* See the {@glink framework/guides/deep-dive/schema#object-elements Object elements} section of the "Schema" deep dive}
* guide for more details.
*
* @param {module:engine/model/item~Item|module:engine/model/schema~SchemaContextItem|String} item
*/
isObject( item ) {
Expand All @@ -268,6 +295,9 @@ export default class Schema {
* const text = writer.createText('foo' );
* schema.isInline( text ); // -> true
*
* See the {@glink framework/guides/deep-dive/schema#inline-elements Inline elements} section of the "Schema" deep dive}
* guide for more details.
*
* @param {module:engine/model/item~Item|module:engine/model/schema~SchemaContextItem|String} item
*/
isInline( item ) {
Expand Down Expand Up @@ -712,8 +742,8 @@ export default class Schema {
* as long as {@link module:engine/model/schema~Schema#isLimit limit element},
* {@link module:engine/model/schema~Schema#isObject object element} or top-most ancestor won't be reached.
*
* @params {module:engine/model/position~Position} position Position from searching will start.
* @params {module:engine/model/node~Node|String} node Node for which allowed parent should be found or its name.
* @param {module:engine/model/position~Position} position Position from searching will start.
* @param {module:engine/model/node~Node|String} node Node for which allowed parent should be found or its name.
* @returns {module:engine/model/element~Element|null} element Allowed parent or null if nothing was found.
*/
findAllowedParent( position, node ) {
Expand Down Expand Up @@ -999,32 +1029,34 @@ mix( Schema, ObservableMixin );
*
* You can define the following rules:
*
* * `allowIn` &ndash; A string or an array of strings. Defines in which other items this item will be allowed.
* * `allowAttributes` &ndash; A string or an array of strings. Defines allowed attributes of the given item.
* * `allowContentOf` &ndash; A string or an array of strings. Inherits "allowed children" from other items.
* * `allowWhere` &ndash; A string or an array of strings. Inherits "allowed in" from other items.
* * `allowAttributesOf` &ndash; A string or an array of strings. Inherits attributes from other items.
* * `inheritTypesFrom` &ndash; A string or an array of strings. Inherits `is*` properties of other items.
* * `inheritAllFrom` &ndash; A string. A shorthand for `allowContentOf`, `allowWhere`, `allowAttributesOf`, `inheritTypesFrom`.
* * Additionally, you can define the following `is*` properties: `isBlock`, `isLimit`, `isObject`, `isInline`. Read about them below.
* * {@link ~SchemaItemDefinition#allowIn `allowIn`} &ndash; Defines in which other items this item will be allowed.
* * {@link ~SchemaItemDefinition#allowAttributes `allowAttributes`} &ndash; Defines allowed attributes of the given item.
* * {@link ~SchemaItemDefinition#allowContentOf `allowContentOf`} &ndash; Inherits "allowed children" from other items.
* * {@link ~SchemaItemDefinition#allowWhere `allowWhere`} &ndash; Inherits "allowed in" from other items.
* * {@link ~SchemaItemDefinition#allowAttributesOf `allowAttributesOf`} &ndash; Inherits attributes from other items.
* * {@link ~SchemaItemDefinition#inheritTypesFrom `inheritTypesFrom`} &ndash; Inherits `is*` properties of other items.
* * {@link ~SchemaItemDefinition#inheritAllFrom `inheritAllFrom`} &ndash;
* A shorthand for `allowContentOf`, `allowWhere`, `allowAttributesOf`, `inheritTypesFrom`.
*
* # The is* properties
* # The `is*` properties
*
* There are 3 commonly used `is*` properties. Their role is to assign additional semantics to schema items.
* There are a couple commonly used `is*` properties. Their role is to assign additional semantics to schema items.
* You can define more properties but you will also need to implement support for them in the existing editor features.
*
* * `isBlock` &ndash; Whether this item is paragraph-like. Generally speaking, content is usually made out of blocks
* like paragraphs, list items, images, headings, etc. All these elements are marked as blocks. A block
* should not allow another block inside. Note: There is also the `$block` generic item which has `isBlock` set to `true`.
* Most block type items will inherit from `$block` (through `inheritAllFrom`).
* * `isLimit` &ndash; It can be understood as whether this element should not be split by <kbd>Enter</kbd>.
* Examples of limit elements: `$root`, table cell, image caption, etc. In other words, all actions that happen inside
* a limit element are limited to its content. **Note:** All objects (`isObject`) are treated as limit elements, too.
* * `isObject` &ndash; Whether an item is "self-contained" and should be treated as a whole. Examples of object elements:
* `image`, `table`, `video`, etc. **Note:** An object is also a limit, so
* * {@link ~SchemaItemDefinition#isBlock `isBlock`} &ndash; Whether this item is paragraph-like.
* Generally speaking, content is usually made out of blocks like paragraphs, list items, images, headings, etc.
* * {@link ~SchemaItemDefinition#isInline `isInline`} &ndash; Whether an item is "text-like" and should be treated as an inline node.
* Examples of inline elements: `$text`, `softBreak` (`<br>`), etc.
* * {@link ~SchemaItemDefinition#isLimit `isLimit`} &ndash; It can be understood as whether this element
* should not be split by <kbd>Enter</kbd>. Examples of limit elements: `$root`, table cell, image caption, etc.
* In other words, all actions that happen inside a limit element are limited to its content.
* All objects are treated as limit elements, too.
* * {@link ~SchemaItemDefinition#isObject `isObject`} &ndash; Whether an item is "self-contained" and should be treated as a whole.
* Examples of object elements: `image`, `table`, `video`, etc. An object is also a limit, so
* {@link module:engine/model/schema~Schema#isLimit `isLimit()`} returns `true` for object elements automatically.
* * `isInline` &ndash; Whether an item is "text-like" and should be treated as an inline node. Examples of inline elements:
* `$text`, `softBreak` (`<br>`), etc.
*
* Read more about the meaning of these types in the
* {@glink framework/guides/deep-dive/schema#defining-additional-semantics Dedicated section of the "Schema" deep dive} guide.
*
* # Generic items
*
Expand All @@ -1047,7 +1079,7 @@ mix( Schema, ObservableMixin );
* (paragraphs, lists items, headings, images) which, in turn, may contain text inside.
*
* By inheriting from the generic items you can define new items which will get extended by other editor features.
* Read more about generic types in the {@glink framework/guides/deep-dive/schema Defining schema} guide.
* Read more about generic types in the {@glink framework/guides/deep-dive/schema Schema deep dive} guide.
*
* # Example definitions
*
Expand Down Expand Up @@ -1107,10 +1139,52 @@ mix( Schema, ObservableMixin );
* * If you want to publish your feature so other developers can use it, try to use
* generic items as much as possible.
* * Keep your model clean. Limit it to the actual data and store information in a normalized way.
* * Remember about definining the `is*` properties. They do not affect the allowed structures, but they can
* * Remember about defining the `is*` properties. They do not affect the allowed structures, but they can
* affect how the editor features treat your elements.
*
* @typedef {Object} module:engine/model/schema~SchemaItemDefinition
*
* @property {String|Array.<String>} allowIn Defines in which other items this item will be allowed.
* @property {String|Array.<String>} allowAttributes Defines allowed attributes of the given item.
* @property {String|Array.<String>} allowContentOf Inherits "allowed children" from other items.
* @property {String|Array.<String>} allowWhere Inherits "allowed in" from other items.
* @property {String|Array.<String>} allowAttributesOf Inherits attributes from other items.
* @property {String|Array.<String>} inheritTypesFrom Inherits `is*` properties of other items.
* @property {String} inheritAllFrom A shorthand for `allowContentOf`, `allowWhere`, `allowAttributesOf`, `inheritTypesFrom`.
*
* @property {Boolean} isBlock
* Whether this item is paragraph-like. Generally speaking, content is usually made out of blocks
* like paragraphs, list items, images, headings, etc. All these elements are marked as blocks. A block
* should not allow another block inside. Note: There is also the `$block` generic item which has `isBlock` set to `true`.
* Most block type items will inherit from `$block` (through `inheritAllFrom`).
*
* Read more about the block elements in the
* {@glink framework/guides/deep-dive/schema#block-elements Block elements} section of the "Schema" deep dive} guide.
*
* @property {Boolean} isInline
* Whether an item is "text-like" and should be treated as an inline node. Examples of inline elements:
* `$text`, `softBreak` (`<br>`), etc.
*
* Read more about the inline elements in the
* {@glink framework/guides/deep-dive/schema#inline-elements Inline elements} section of the "Schema" deep dive} guide.
*
* @property {Boolean} isLimit
* It can be understood as whether this element should not be split by <kbd>Enter</kbd>.
* Examples of limit elements: `$root`, table cell, image caption, etc. In other words, all actions that happen inside
* a limit element are limited to its content.
*
* Read more about the limit elements in the
* {@glink framework/guides/deep-dive/schema#limit-elements Limit elements} section of the "Schema" deep dive} guide.
*
* @property {Boolean} isObject
* Whether an item is "self-contained" and should be treated as a whole. Examples of object elements:
* `image`, `table`, `video`, etc.
*
* **Note:** An object is also a limit, so
* {@link module:engine/model/schema~Schema#isLimit `isLimit()`} returns `true` for object elements automatically.
*
* Read more about the object elements in the
* {@glink framework/guides/deep-dive/schema#object-elements Object elements} section of the "Schema" deep dive} guide.
*/

/**
Expand Down

0 comments on commit 3cb1c78

Please sign in to comment.