Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Placeholder after initialization #14627

Merged
merged 15 commits into from
Jul 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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';
mlewand marked this conversation as resolved.
Show resolved Hide resolved
* ```
*
* @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 }: {
illia-stv marked this conversation as resolved.
Show resolved Hide resolved
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, {
illia-stv marked this conversation as resolved.
Show resolved Hide resolved
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;
mlewand marked this conversation as resolved.
Show resolved Hide resolved
}
Loading