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 8 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.

## Updating the placeholder
mlewand marked this conversation as resolved.
Show resolved Hide resolved

The editor placeholder could be updated in runtime changing `placeholder` property in editing root. You can use it also in {@link installation/getting-started/predefined-builds#multi-root-editor `Multi-root editor`} and {@link installation/getting-started/predefined-builds#inline-editor `Inline editor`}.
mlewand marked this conversation as resolved.
Show resolved Hide resolved

```js
editor.editing.view.document.getRoot( 'main' ).placeholder = 'new placeholder'
mlewand marked this conversation as resolved.
Show resolved Hide resolved
```

{@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).
28 changes: 14 additions & 14 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 @@ -110,22 +109,23 @@ export default class BalloonEditorUI extends EditorUI {
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' );
let placeholderText;

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

if ( placeholderText ) {
enablePlaceholder( {
view: editingView,
element: editingRoot,
text: placeholderText,
isDirectHost: false,
keepOnFocus: true
} );
}
placeholderText = typeof placeholder === 'string' ? placeholder : placeholder[ editingRoot.rootName ];
mlewand marked this conversation as resolved.
Show resolved Hide resolved
}

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

enablePlaceholder( {
view: editingView,
element: editingRoot,
isDirectHost: false,
keepOnFocus: true
} );
}
}
15 changes: 8 additions & 7 deletions packages/ckeditor5-editor-classic/src/classiceditorui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
28 changes: 14 additions & 14 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 @@ -118,22 +117,23 @@ export default class DecoupledEditorUI extends EditorUI {
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' );
let placeholderText;

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

if ( placeholderText ) {
enablePlaceholder( {
view: editingView,
element: editingRoot,
text: placeholderText,
isDirectHost: false,
keepOnFocus: true
} );
}
placeholderText = typeof placeholder === 'string' ? placeholder : placeholder[ editingRoot.rootName ];
}

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

enablePlaceholder( {
view: editingView,
element: editingRoot,
isDirectHost: false,
keepOnFocus: true
} );
}
}
28 changes: 15 additions & 13 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 @@ -153,22 +152,25 @@ export default class InlineEditorUI extends EditorUI {
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' );
let placeholderText;

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

if ( placeholderText ) {
enablePlaceholder( {
view: editingView,
element: editingRoot,
text: placeholderText,
isDirectHost: false,
keepOnFocus: true
} );
}
placeholderText = typeof placeholder === 'string' ? placeholder : placeholder[ editingRoot.rootName ];
}

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

if ( placeholderText ) {
mlewand marked this conversation as resolved.
Show resolved Hide resolved
enablePlaceholder( {
view: editingView,
element: editingRoot,
isDirectHost: false,
keepOnFocus: true
} );
}
}
}
9 changes: 4 additions & 5 deletions packages/ckeditor5-editor-multi-root/src/multirooteditorui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
10 changes: 10 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,15 @@ export default class EditableElement extends ObservableMixin( ContainerElement )
*/
declare public isFocused: boolean;

/**
* Placeholder of editable element. Could be updated.
mlewand marked this conversation as resolved.
Show resolved Hide resolved
*
* 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 +71,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
44 changes: 31 additions & 13 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 @@ -30,17 +31,15 @@ const documentPlaceholders = new WeakMap<Document, Map<Element, PlaceholderConfi
* @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 );
} );
}

if ( element.placeholder ) {
setPlaceholder( element.placeholder );
}

// Update the placeholders right away.
view.change( writer => updateDocumentPlaceholders( doc, writer ) );
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,10 @@ interface PlaceholderConfig {
keepOnFocus: boolean;
hostElement: Element | null;
}

/**
* Element which could have placeholder.
*/
export type PlaceholderableElement = Element & {
placeholder?: string;
mlewand marked this conversation as resolved.
Show resolved Hide resolved
};
Loading