Skip to content

Commit

Permalink
Merge pull request #14627 from ckeditor/ck/9925-update-placeholder-af…
Browse files Browse the repository at this point in the history
…ter-initialization

Feature (engine): Placeholders can now be changed after the initialization. This can be done by changing `placeholder` property of the `element` passed to `enablePlaceholder()` helper. Closes #9925.

MINOR BREAKING CHANGE (engine): The `enablePlaceholder()` helper now uses a `placeholder` property of passed `element`. It no longer takes the placeholder text as a `text` argument.
  • Loading branch information
mlewand authored Jul 26, 2023
2 parents 43055d8 + eec4cce commit a7e0947
Show file tree
Hide file tree
Showing 15 changed files with 222 additions and 111 deletions.
4 changes: 4 additions & 0 deletions docs/_snippets/features/update-placeholder.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<div id="snippet-update-placeholder">
<p></p>
</div>
<button id="update-placeholder-button">Update placeholder</button>
36 changes: 36 additions & 0 deletions docs/_snippets/features/update-placeholder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/

/* globals console, window, document, ClassicEditor */

import { CS_CONFIG } from '@ckeditor/ckeditor5-cloud-services/tests/_utils/cloud-services-config';

ClassicEditor
.create( document.querySelector( '#snippet-update-placeholder' ), {
cloudServices: CS_CONFIG,
toolbar: [
'undo', 'redo', '|', 'heading',
'|', 'bold', 'italic',
'|', 'link', 'uploadImage', 'insertTable', 'mediaEmbed',
'|', 'bulletedList', 'numberedList', 'outdent', 'indent'
],
ui: {
viewportOffset: {
top: window.getViewportTopOffsetConfig()
}
},
placeholder: 'Type some content here!'
} )
.then( editor => {
const button = document.getElementById( 'update-placeholder-button' );
window.editor = editor;

button.addEventListener( 'click', () => {
editor.editing.view.document.getRoot( 'main' ).placeholder = 'New placeholder';
} );
} )
.catch( err => {
console.error( err.stack );
} );
10 changes: 10 additions & 0 deletions docs/features/editor-placeholder.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,16 @@ The editor placeholder text is displayed using a CSS pseudo–element (`::before

**Note**: The `.ck-placeholder` class is also used to display placeholders in other places, for instance, {@link features/images-captions image captions}. Make sure your custom styles apply to the right subset of placeholders.

## Changing the placeholder

The editor placeholder could be updated at runtime by changing the `placeholder` property in editing root.

```js
editor.editing.view.document.getRoot( 'main' ).placeholder = 'new placeholder';
```

{@snippet features/update-placeholder}

## Contribute

The source code of the feature is available on GitHub at [https://github.com/ckeditor/ckeditor5/tree/master/packages/ckeditor5-core](https://github.com/ckeditor/ckeditor5/tree/master/packages/ckeditor5-core).
22 changes: 10 additions & 12 deletions packages/ckeditor5-editor-balloon/src/ballooneditorui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@
*/

import {
type Editor,
type ElementApi
type Editor
} from 'ckeditor5/src/core';

import {
Expand Down Expand Up @@ -104,28 +103,27 @@ export default class BalloonEditorUI extends EditorUI {
}

/**
* Enable the placeholder text on the editing root, if any was configured.
* Enable the placeholder text on the editing root.
*/
private _initPlaceholder(): void {
const editor = this.editor;
const editingView = editor.editing.view;
const editingRoot = editingView.document.getRoot()!;
const sourceElement = ( editor as Editor & ElementApi ).sourceElement;

const placeholder = editor.config.get( 'placeholder' );

if ( placeholder ) {
const placeholderText = typeof placeholder === 'string' ? placeholder : placeholder[ editingRoot.rootName ];

if ( placeholderText ) {
enablePlaceholder( {
view: editingView,
element: editingRoot,
text: placeholderText,
isDirectHost: false,
keepOnFocus: true
} );
editingRoot.placeholder = placeholderText;
}
}

enablePlaceholder( {
view: editingView,
element: editingRoot,
isDirectHost: false,
keepOnFocus: true
} );
}
}
17 changes: 9 additions & 8 deletions packages/ckeditor5-editor-classic/src/classiceditorui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ export default class ClassicEditorUI extends EditorUI {
}

/**
* Enable the placeholder text on the editing root, if any was configured.
* Enable the placeholder text on the editing root.
*/
private _initPlaceholder(): void {
const editor = this.editor;
Expand All @@ -162,14 +162,15 @@ export default class ClassicEditorUI extends EditorUI {
}

if ( placeholderText ) {
enablePlaceholder( {
view: editingView,
element: editingRoot,
text: placeholderText,
isDirectHost: false,
keepOnFocus: true
} );
editingRoot.placeholder = placeholderText;
}

enablePlaceholder( {
view: editingView,
element: editingRoot,
isDirectHost: false,
keepOnFocus: true
} );
}

/**
Expand Down
22 changes: 10 additions & 12 deletions packages/ckeditor5-editor-decoupled/src/decouplededitorui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@
*/

import {
type Editor,
type ElementApi
type Editor
} from 'ckeditor5/src/core';

import {
Expand Down Expand Up @@ -112,28 +111,27 @@ export default class DecoupledEditorUI extends EditorUI {
}

/**
* Enable the placeholder text on the editing root, if any was configured.
* Enable the placeholder text on the editing root.
*/
private _initPlaceholder(): void {
const editor = this.editor;
const editingView = editor.editing.view;
const editingRoot = editingView.document.getRoot()!;
const sourceElement = ( editor as Editor & ElementApi ).sourceElement;

const placeholder = editor.config.get( 'placeholder' );

if ( placeholder ) {
const placeholderText = typeof placeholder === 'string' ? placeholder : placeholder[ editingRoot.rootName ];

if ( placeholderText ) {
enablePlaceholder( {
view: editingView,
element: editingRoot,
text: placeholderText,
isDirectHost: false,
keepOnFocus: true
} );
editingRoot.placeholder = placeholderText;
}
}

enablePlaceholder( {
view: editingView,
element: editingRoot,
isDirectHost: false,
keepOnFocus: true
} );
}
}
20 changes: 9 additions & 11 deletions packages/ckeditor5-editor-inline/src/inlineeditorui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
*/

import {
type ElementApi,
type Editor
} from 'ckeditor5/src/core';

Expand Down Expand Up @@ -147,28 +146,27 @@ export default class InlineEditorUI extends EditorUI {
}

/**
* Enable the placeholder text on the editing root, if any was configured.
* Enable the placeholder text on the editing root.
*/
private _initPlaceholder(): void {
const editor = this.editor;
const editingView = editor.editing.view;
const editingRoot = editingView.document.getRoot()!;
const sourceElement = ( editor as Editor & ElementApi ).sourceElement;

const placeholder = editor.config.get( 'placeholder' );

if ( placeholder ) {
const placeholderText = typeof placeholder === 'string' ? placeholder : placeholder[ editingRoot.rootName ];

if ( placeholderText ) {
enablePlaceholder( {
view: editingView,
element: editingRoot,
text: placeholderText,
isDirectHost: false,
keepOnFocus: true
} );
editingRoot.placeholder = placeholderText;
}
}

enablePlaceholder( {
view: editingView,
element: editingRoot,
isDirectHost: false,
keepOnFocus: true
} );
}
}
11 changes: 5 additions & 6 deletions packages/ckeditor5-editor-multi-root/src/multirooteditorui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ export default class MultiRootEditorUI extends EditorUI {
}

/**
* Enables the placeholder text on a given editable, if the placeholder was configured.
* Enables the placeholder text on a given editable.
*
* @param editable Editable on which the placeholder should be set.
* @param placeholder Placeholder for the editable element. If not set, placeholder value from the
Expand All @@ -204,17 +204,16 @@ export default class MultiRootEditorUI extends EditorUI {
}
}

if ( !placeholder ) {
return;
}

const editingView = this.editor.editing.view;
const editingRoot = editingView.document.getRoot( editable.name! )!;

if ( placeholder ) {
editingRoot.placeholder = placeholder;
}

enablePlaceholder( {
view: editingView,
element: editingRoot,
text: placeholder,
isDirectHost: false,
keepOnFocus: true
} );
Expand Down
12 changes: 12 additions & 0 deletions packages/ckeditor5-engine/src/view/editableelement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,17 @@ export default class EditableElement extends ObservableMixin( ContainerElement )
*/
declare public isFocused: boolean;

/**
* Placeholder of editable element.
*
* ```ts
* editor.editing.view.document.getRoot( 'main' ).placeholder = 'New placeholder';
* ```
*
* @observable
*/
declare public placeholder?: string;

/**
* Creates an editable element.
*
Expand All @@ -62,6 +73,7 @@ export default class EditableElement extends ObservableMixin( ContainerElement )

this.set( 'isReadOnly', false );
this.set( 'isFocused', false );
this.set( 'placeholder', undefined );

this.bind( 'isReadOnly' ).to( document );

Expand Down
50 changes: 36 additions & 14 deletions packages/ckeditor5-engine/src/view/placeholder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import '../../theme/placeholder.css';

import type Document from './document';
import type DowncastWriter from './downcastwriter';
import type EditableElement from './editableelement';
import type Element from './element';
import type View from './view';

Expand All @@ -23,24 +24,22 @@ const documentPlaceholders = new WeakMap<Document, Map<Element, PlaceholderConfi
* A helper that enables a placeholder on the provided view element (also updates its visibility).
* The placeholder is a CSS pseudo–element (with a text content) attached to the element.
*
* To change the placeholder text, simply call this method again with new options.
* To change the placeholder text, change value of the `placeholder` property in the provided `element`.
*
* To disable the placeholder, use {@link module:engine/view/placeholder~disablePlaceholder `disablePlaceholder()`} helper.
*
* @param options Configuration options of the placeholder.
* @param options.view Editing view instance.
* @param options.element Element that will gain a placeholder. See `options.isDirectHost` to learn more.
* @param options.text Placeholder text.
* @param options.isDirectHost If set `false`, the placeholder will not be enabled directly
* in the passed `element` but in one of its children (selected automatically, i.e. a first empty child element).
* Useful when attaching placeholders to elements that can host other elements (not just text), for instance,
* editable root elements.
* @param options.keepOnFocus If set `true`, the placeholder stay visible when the host element is focused.
*/
export function enablePlaceholder( { view, element, text, isDirectHost = true, keepOnFocus = false }: {
export function enablePlaceholder( { view, element, isDirectHost = true, keepOnFocus = false }: {
view: View;
element: Element;
text: string;
element: PlaceholderableElement | EditableElement;
isDirectHost?: boolean;
keepOnFocus?: boolean;
} ): void {
Expand All @@ -60,16 +59,28 @@ export function enablePlaceholder( { view, element, text, isDirectHost = true, k
}, { priority: 'high' } );
}

// Store information about the element placeholder under its document.
documentPlaceholders.get( doc )!.set( element, {
text,
isDirectHost,
keepOnFocus,
hostElement: isDirectHost ? element : null
} );
if ( element.is( 'editableElement' ) ) {
element.on( 'change:placeholder', ( evtInfo, evt, text ) => {
setPlaceholder( text );
} );
}

// Update the placeholders right away.
view.change( writer => updateDocumentPlaceholders( doc, writer ) );
if ( element.placeholder ) {
setPlaceholder( element.placeholder );
}

function setPlaceholder( text: string ) {
// Store information about the element placeholder under its document.
documentPlaceholders.get( doc )!.set( element, {
text,
isDirectHost,
keepOnFocus,
hostElement: isDirectHost ? element : null
} );

// Update the placeholders right away.
view.change( writer => updateDocumentPlaceholders( doc, writer ) );
}
}

/**
Expand Down Expand Up @@ -297,3 +308,14 @@ interface PlaceholderConfig {
keepOnFocus: boolean;
hostElement: Element | null;
}

/**
* Element that could have a placeholder.
*/
export interface PlaceholderableElement extends Element {

/**
* The text of element's placeholder.
*/
placeholder?: string;
}
Loading

0 comments on commit a7e0947

Please sign in to comment.