Skip to content
Rob Garrison edited this page Mar 8, 2018 · 3 revisions

Sections: Limitations | Commands | Methods

Limitations

Contenteditable elements require a complex algorithm to determine the caret position. As such, there are some problems and limitations that occur:

Pressing Enter (adding <br>s)

Pressing the Enter key will automatically add a line break wrapped in a div (<div><br></div>).

In the original method, the line break and a non-breaking space was inserted, then the caret was then moved to the right a single character. This added unnecessary spaces.

When adding a div wrapped line break and moving the caret to the right, also has some problems with the caret position. When the user presses Enter, the same line break would continue to be nested in multiple divs. To solve this problem, the user would need to continue to press the left action key to move the caret to the before the added line break. This was fixed in v1.27.3-beta by ensuring the caret position was saved.

Currently, a line break is inserted and the caret is moved after the insertion of the line break; but behaves oddly only after the second line break is added - it appears to move down instead of inserting a line break; what is actually happening is the break is nested in a second div. Now the problem is using the backspace to delete these newly added line breaks. What happens is any text node after the caret position (if you added a line break in the middle of a text node), will be shifted up through the wrapped line breaks. The line breaks will remain, but can be deleted by using the Delete key or using the right caret to move to the end and then using backspace. Investigation for a "better" fix will continue.

Text Selection & Content length

Due to block elements (<div>, <p>, etc) and line breaks (<br>) within the contenteditable, the calculated caret position must be modified. The get and set contenteditable caret functions will return selected text that includes extra characters to account for the new lines. This changes the overall content length. With the normal method of text selection, the block and break elements are ignored. For example:

Given this HTML:

<div contenteditable>Lorem<br>ipsum</div>

Selecting all the text would result in "Loremipsum" with no space between the words. It's text length is 10.

The algorithm used by this plugin replaces the <br> with a \n. The value is set by the replaceCR option (replace carriage return). The algorithm will return "Lorem\nipsum" with a text length of 11.

As you can see, the resulting text length using the normal method and the algorithm will differ. Normal text highlighting and replacing of text content may or may not work due to these differences; but changing the replaceCR option ("\n" by default) with an empty string should resolve any issues that may occur.

Unwrapped line breaks

As mentioned in the text selection limitation, line breaks (<br>) are not accounted for by the browser's internal text selection range methods. Any unwrapped line breaks will return the same caret container and offset value if the caret is to the left or right of the break. To resolve this, all line breaks must be wrapped in a block element (<div>s are preferred).

This plugin will automatically wrap line breaks. A wrapBRs option (true by default) was added to allow disabling of this functionality, but disabling of function is not recommended.

Commands

With the use of contenteditable elements comes the use of the document.execCommand method. With this you can use the browser's built-in methods to backspace, delete, undo and redo; actions added to the contenteditable example in the main demo. There are numerous other commands available, but not added to the keyboard internally which would allow you to create a simple editor.

The document.execCommand can be called directly, but has been added to the plugin to simplify its usage by calling the function with only the command string. See the example below.

This code is used in the main contenteditable demo. The backspace & delete actions use the internal command, but the undo and redo commands are not internalized and need to be added separately:

$.keyboard.keyaction.undo = function(keyboard) {
	keyboard.execCommand('undo');
	return false;
};
$.keyboard.keyaction.redo = function(keyboard) {
	keyboard.execCommand('redo');
	return false;
};

$('#contenteditable').keyboard({
	usePreview: false,
	useCombos: false,
	autoAccept: true,
	layout: 'custom',
	customLayout: {
		'normal': [
			'` 1 2 3 4 5 6 7 8 9 0 - = {del} {b}',
			'{tab} q w e r t y u i o p [ ] \\',
			'a s d f g h j k l ; \' {enter}',
			'{shift} z x c v b n m , . / {shift}',
			'{accept} {space} {left} {right} {undo:Undo} {redo:Redo}'
		],
		'shift': [
			'~ ! @ # $ % ^ & * ( ) _ + {del} {b}',
			'{tab} Q W E R T Y U I O P { } |',
			'A S D F G H J K L : " {enter}',
			'{shift} Z X C V B N M < > ? {shift}',
			'{accept} {space} {left} {right} {undo:Undo} {redo:Redo}'
		]
	},
	display: {
		del: '\u2326:Delete',
		redo: '↻',
		undo: '↺'
	}
});

Methods

Get/Set caret

The method to get and set the caret for contenteditable elements does not differ in the API. The easiest method would be to use the keyboard.caret:

var kb = $('.keyboard').data('keyboard'),
	// get caret position
	caret = kb.caret();

The caret value will contain an object with the following keys:

  • start - numeric caret position zero-based index.
  • end - numeric caret position zero-based index.
  • text - The selected text. If the start and end values are the same, this will be an empty string.

To set the caret, pass a start and/or end position

var kb = $('.keyboard').data('keyboard');
// ** set caret examples
// returns an object with start, end and text keys
kb.caret(10, 14); 
// selects all text; returns a numeric start and end with the text content
kb.caret(0, 'end');
// sets the caret with the same start/end value
kb.caret(20);
// sets the caret to the end
kb.caret('end');

Content length

Because the internal caret positioning algorithm modifies the text returned from the get/set function, the length will not match the normal content length method; but only if the contenteditable contains block elements and/or line breaks:

var $el = $('.keyboard'),
	adjustedContentLength = $.keyboard.getEditableLength($el);
console.log($el[0].textContent.length === adjustedContentLength); // false

A getEditableLength function is available to get the text content length (see above). It requires one parameter, the contenteditable container, and returns an adjusted numeric text content length, if passed a contenteditable element; or null if not.