8
0
Pārlūkot izejas kodu

Expand the description of bootstrap, schema and converter sections.

Maciej Gołaszewski 6 gadi atpakaļ
vecāks
revīzija
5d7f7b6271

+ 127 - 43
docs/framework/guides/tutorials/implementing-an-inline-widget.md

@@ -6,17 +6,17 @@ menu-title: Implementing an inline widget
 
 # Implementing an inline widget
 
-In this tutorial you will learn how to implement an inline widget. We will build a "Placeholder" feature which allow the user to insert predefined placeholders into the document.
+In this tutorial you will learn how to implement an inline widget. We will build a "Placeholder" feature which allow the user to insert predefined placeholders into the document. We will use widget utils and conversion in order to define the behavior of this feature. Later on, we will create a dropdown for editor toolbar which will allow to insert predefined placeholders. 
 
 ## Before you start
 
-This guide assumes that you're familiar with {@link framework/tutorials/implementing-a-widget Implementing a simple widget} tutorial.
+This guide assumes that you're familiar with widgets concept introduced in the {@link framework/tutorials/implementing-a-widget Implementing a simple widget} tutorial. We will reference various concepts from {@link framework/guides/architecture/intro CKEditor 5 architecture}.
 
 ## Bootstrap project
 
-Th overall project structure and concept will be similar as those described in {@link framework/guides/tutorials/implementing-a-widget#lets-start Let's start} & and {@link framework/guides/tutorials/implementing-a-widget#plugin-structure Plugin structure} sections. 
+Th overall project structure and concept will be similar as those described in {@link framework/guides/tutorials/implementing-a-widget#lets-start Let's start} & and {@link framework/guides/tutorials/implementing-a-widget#plugin-structure Plugin structure} sections.
 
-Install required dependencies:
+First, install required dependencies:
 
 ```bash
 npm install --save \
@@ -113,31 +113,6 @@ Add and `index.html` page:
 </html>
 ```
 
-### Project structure
-
-The project will have a structure as below:
-
-```
-├── app.js
-├── dist
-│   ├── bundle.js
-│   └── bundle.js.map
-├── index.html
-├── node_modules
-├── package.json
-├── placeholder
-│   ├── placeholder.js
-│   ├── placeholdercommand.js
-│   ├── placeholderediting.js
-│   ├── placeholderui.js
-│   └── theme
-│       └── placeholder.css
-│
-│   ... the rest of plugin files go here as well
-│
-└── webpack.config.js
-``` 
-
 The application entry point (`app.js`):
 
 ```js
@@ -168,6 +143,35 @@ ClassicEditor
 	} );
 ```
 
+Before building the project we still need to define `Placeholder` plugin.
+
+### Project structure
+
+The project will have a structure as below:
+
+```
+├── app.js
+├── dist
+│   ├── bundle.js
+│   └── bundle.js.map
+├── index.html
+├── node_modules
+├── package.json
+├── placeholder
+│   ├── placeholder.js
+│   ├── placeholdercommand.js
+│   ├── placeholderediting.js
+│   ├── placeholderui.js
+│   └── theme
+│       └── placeholder.css
+│
+│   ... the rest of plugin files go here as well
+│
+└── webpack.config.js
+```
+
+You can see that the `/placeholder` feature has an established plugin structure: the master (glue) plugin (`placeholder/placeholder.js`), the "editing" (`placeholder/placeholderediting.js`) and the "ui" (`placeholder/placeholderui.js`) parts.
+
 The master (glue) plugin:
 
 ```js
@@ -197,11 +201,23 @@ export default class PlaceholderUI extends Plugin {
 }
 ```
 
-The plugin is not ready yet - we will create an editing part in next step. 
+And the editing part (empty for now):
+
+```js
+// placeholder/placeholderediting.js
+
+export default class PlaceholderEditing extends Plugin {
+	init() {
+		console.log( 'PlaceholderEditing#init() got called' );
+	}
+}
+```
+
+At this stage we can build the project and open it in the browser to verify if it is building correctly.
 
 ## The model and the view layers
 
-The placeholder feature will be represented as an inline (text-like) widget so it will be inserted in other editor blocks like `<paragraph>`:
+The placeholder feature will be {@link module:engine/model/schema~SchemaItemDefinition defined as  an inline} (text-like) widget so it will be inserted in other editor blocks, like `<paragraph>`, that allow text. The placeholder will have `type` attribute.
 
 ```html
 <paragraph>
@@ -211,6 +227,8 @@ The placeholder feature will be represented as an inline (text-like) widget so i
 
 ### Defining the schema
 
+The inline widget's `<placeholder>` element should be treated as `$text` so it must be defined with `isInline = true`. Also we want to put it wherever the `$text` is allowed so we add `allowWhere = '$text'`. Also the attribute `type` must be allowed for the `placeholder` element.
+
 ```js
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 
@@ -243,14 +261,22 @@ export default class PlaceholderEditing extends Plugin {
 }
 ```
 
+The schema is defined but we mast also add the converters for it.
+
 ### Defining converters
 
-The structure:
+The HTML structure of the converter will be a `<span>` with `data-placeholder` attribute:
 
 ```html
-<p>Hello <div data-placeholder="name">name</div>!</p>
+<span data-placeholder="name">{name}</span>
 ```
 
+The `data-placeholder` attribute holds the type of the placeholder and holds information of its type. The text inside `<span>` will be ignored during view-to-model conversion and will be generated based on placeholder's type in model-to-view conversion. 
+
+#### The "upcast" conversion
+
+In the view-to-model conversion we define converter that will create a model element. This converter will read the `data-placeholder` attribute value and create a `placeholder` element with a `type` attribute.
+
 ```js
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 
@@ -279,19 +305,61 @@ export default class PlaceholderEditing extends Plugin {
 				attributes: [ 'data-placeholder' ]
 			},
 			model: ( viewElement, modelWriter ) => {
-				const type = viewElement.getAttribute( 'data-placeholder' ) || 'general';
+				// Read the type from `data-placeholder` attribute or put 'general' if none defined.
+				const placeholderType = viewElement.getAttribute( 'data-placeholder' ) || 'general';
 
-				return modelWriter.createElement( 'placeholder', { type } );
+				const attributes = { 
+					type: placeholderType
+				};
+
+				return modelWriter.createElement( 'placeholder', attributes );
 			}
 		} ) );
+	}
 
-		conversion.for( 'editingDowncast' ).add( downcastElementToElement( {
+	_defineSchema() {
+		// ...
+	}
+}
+```
+
+#### The "downcast" conversion
+
+The view-to-model conversion will be different for "editing" and "data" pipelines as the "editing downcast" pipeline will use widget utilities to enable widget specific behavior in the editing view. In both pipelines the element will be rendered as using the same utility function.
+
+```js
+import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+
+// Added imports:
+import { downcastElementToElement } from '@ckeditor/ckeditor5-engine/src/conversion/downcast-converters';
+import { upcastElementToElement } from '@ckeditor/ckeditor5-engine/src/conversion/upcast-converters';
+import { toWidget } from '@ckeditor/ckeditor5-widget/src/utils';
+
+// Added theme file.
+import './theme/placeholder.css';
+
+export default class PlaceholderEditing extends Plugin {
+	constructor( editor ) {
+		super( editor );
+
+		this._defineSchema();
+		this._defineConverters();
+	}
+
+	_defineConverters() {
+		const conversion = this.editor.conversion;
+
+		conversion.for( 'upcast' ).add( upcastElementToElement( {
+			// ...
+		} ) );
+
+		conversion.for( 'editingDowncast' ).add( downcastElementToElement( {        // ADDED
 			model: 'placeholder',
 			view: ( modelItem, viewWriter ) => {
-				const widgetElement = createPlaceholderView( modelItem, viewWriter );
+				const placeholderView = createPlaceholderView( modelItem, viewWriter );
 
 				// Enable widget handling on placeholder element inside editing view.
-				return toWidget( widgetElement, viewWriter );
+				return toWidget( placeholderView, viewWriter );
 			}
 		} ) );
 
@@ -304,13 +372,13 @@ export default class PlaceholderEditing extends Plugin {
 		function createPlaceholderView( modelItem, viewWriter ) {
 			const type = modelItem.getAttribute( 'type' );
 
-			const widgetElement = viewWriter.createContainerElement( 'span', { 'data-placeholder': type } );
+			const placeholderView = viewWriter.createContainerElement( 'span', { 'data-placeholder': type } );
 
 			// Insert text node with type so the placeholder type will be displayed in the view.
-			const innerText = viewWriter.createText( type );
-			viewWriter.insert( viewWriter.createPositionAt( widgetElement, 0 ), innerText );
+			const innerText = viewWriter.createText( `{ ${ type } }` );
+			viewWriter.insert( viewWriter.createPositionAt( placeholderView, 0 ), innerText );
 
-			return widgetElement;
+			return placeholderView;
 		}
 	}
 
@@ -320,9 +388,25 @@ export default class PlaceholderEditing extends Plugin {
 }
 ```
 
-As you could notice the editing part imports the `./theme/placeholder.css` CSS file which describes how the placeholder is displayed in th editing view. 
+As you could notice the editing part imports the `./theme/placeholder.css` CSS file which describes how the placeholder is displayed in th editing view:
+
+```css
+/* placeholder/theme/placeholder.css */
+
+[data-placeholder] {
+	background: #ffff00;
+	line-height: 1em;
+	outline-offset: -2px;
+	padding: 4px 2px;
+}
+
+[data-placeholder]::selection{
+	display: none;
+}
+```
 
 ### Command
+
 ```js
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';