CKEditor no space on buttons - javascript

I'm using the CKEDITOR.inline(element) feature of CKEditor (contenteditable=true) in a "WYSIWYG" content editor that I'm working on. The thing works great for all kinds of tags that I've tested with except on:
When I go to edit a <button>, I can edit the text inside of the tag, just fine, except for "space".
When I hit the spacebar, instead of getting a space character inserted in the text, the button attempts to be "pressed", since, I'm assuming the default functionality of the browser is to try and "press" a button that is focused.
So.. I tried to "highjack" the $(element).on('keydown') event and checked for keycode 32 to apply preventDefault to the event. That technically worked to do the "highjacking" (I can see the below console.log), but I still don't get a "space" in the content.
var _fixButtonSpaceKeydown = function(elem, removeBinding){
console.log("_fixButtonSpaceKeydown()");
if(removeBinding){
jQuery(elem).off('keydown');
} else {
jQuery(elem).on('keydown', function (e) {
if (e.keyCode == 32) {
console.log('Caught space!');
e.preventDefault();
}
});
}
}
Has anyone come across this w/ CKEditor before, and have they found a solution?

Below workaround won't stop onclick() on Chrome while focusing on a button and type space. This is a browser issue and it seems there's no way to totally prevent the event. Anyway, we can still add space(Make sure to insert HTML encoding) at the cursor position:
jQuery(contentEditableBtn).attr('onclick','event.preventDefault(); insertSpace();');
function insertSpace(){
var range, node;
if (window.getSelection && window.getSelection().getRangeAt) {
range = window.getSelection().getRangeAt(0);
node = range.createContextualFragment(" ");
range.insertNode(node);
window.getSelection().collapseToEnd();
window.getSelection().modify("move", "forward", "character");
} else if (document.selection && document.selection.createRange) {
document.selection.createRange().pasteHTML(" ");
document.selection.collapseToEnd();
document.selection.modify("move", "forward", "character");
};
}

Maybe you could manually insert a space after preventDefault(). I'm not sure if this would work, but it's worth a try:
var _fixButtonSpaceKeydown = function(elem, removeBinding){
console.log("_fixButtonSpaceKeydown()");
if(removeBinding){
jQuery(elem).off('keydown');
} else {
jQuery(elem).on('keydown', function (e) {
if (e.keyCode == 32) {
console.log('Caught space!');
e.preventDefault();
e.target.innerHTML += ' ';
}
});
}
}

Try this: I haven't tested it but I'm sure you can add space by getting the cursor position where a char is then add a space before the char and replace the paragraph.
Something like this:
var _fixButtonSpaceKeydown = function(elem, removeBinding){
console.log("_fixButtonSpaceKeydown()");
if(removeBinding){
jQuery(elem).off('keydown');
} else {
jQuery(elem).on('keydown', function (e) {
var p = "";
if (e.keyCode == 32) {
console.log('Caught space!');
e.preventDefault();
p = e.target.innerHTML;
// get position of cursor
var cursorPosition = window.getSelection().getRangeAt(0).startOffset;
p.replace(p.charAt(position), " " + p.charAt(position));
// replace the content after change
e.target.innerHTML = p;
}
});
}
}

Related

jQuery event trigger is not working on annotorious and seadragon

I am trying to get the down arrow keyup event to fire automagically using jQuery. The annotorious/seadragon combination has a listener that opens all preconfigured tags when I press the down arrow.
I have written jQuery code to find the input field, put focus on it and then trigger the keyup event.
function triggerDownArrowOnInput() {
$("[id^=downshift][id$=input]").each(function(index) {
// There should only be 1, but let's not assume.
console.log(index);
if (index == 0) {
console.log("Found an input: " + $(this).attr("id"))
$(this).focus();
var event = jQuery.Event("keyup");
event.keyCode = event.which = 40; // down arrow
$(this).trigger(event);
} else {
console.log("Multiple elements found that match the id: " + $(this).attr("id"));
} // if
})
} // triggerDownArrowOnInput
The focus is working great, but not the trigger. If I manually hit the down arrow key, then the preconfigured tags all appear:
I have tried "keyCode" and "which" separately.
I have tried triggering $(this).keyup(event).
I have tried putting in a delay between the focus call and the trigger/keyup call.
I have tried calling $(document).trigger(event).
I thought maybe I was sending the event to the wrong element, but it appears (going through Dev tools) that only the Input field and the document have the listeners enabled.
No matter what I do, I can't get the event to fire. Any ideas?
Thanks.
I think I've got this working without jQuery, using a KeyboardEvent and dispatchEvent. With my tests I don't think you need the focus before hand either because it's an event on the element, but worth testing this on your application.
function triggerDownArrowOnInput() {
$("[id^=downshift][id$=input]").each(function(index) {
// There should only be 1, but let's not assume.
console.log(index);
if (index == 0) {
console.log("Found an input: " + $(this).attr("id"))
$(this).focus();
this.dispatchEvent(new KeyboardEvent('keyup',{'keyCode': 40, 'key':'ArrowDown', 'code':'ArrowDown'}));
} else {
console.log("Multiple elements found that match the id: " + $(this).attr("id"));
}
})
}
Have you tried keydown?
var e = jQuery.Event("keydown");
e.which = 40;
e.keyCode = 40
$(this).trigger(e);
function triggerDownArrowOnInput() {
$("[id^=downshift][id$=input]").each(function(index) {
// There should only be 1, but let's not assume.
console.log(index);
if (index == 0) {
console.log("Found an input: " + $(this).attr("id"))
$(this).focus();
var event = jQuery.Event("keydown");
event.keyCode = event.which = 40;
$(this).trigger(event);
} else {
console.log("Multiple elements found that match the id: " + $(this).attr("id"));
}
})
} // triggerDownArrowOnInput
I was able to get the event to fire, but still wasn't able to open the menu on focus. I ended up having to create a development environment for:
recogito/recogito-client-core
recogito/recogito-js
recogito/annotorious
recogito/annotorious-openseadragon
I then modified Autocomplete.jsx in recogito/recogito-client-core, added an OnFocus listener and then added the following code:
const onFocus = evt => {
if (!isOpen) {
this.setState({ inputItems: this.props.vocabulary }); // Show all options on focus
openMenu()
} // if
} // onFocus
Way more than I wanted to do, but it is working now.

If statement evaluates to false despite being true

UPDATE
Issue is not with e.key === " " but with the inner if statement, to clarify. The inner if always returns false no matter what. The outer if is fine. So far, we think that this might be an issue with .innerText. It could be returning a different whitespace character at the end.
I think I might have found an internal bug with Javascript or something because this does not work. I'm trying to prevent the user from inserting multiple spaces in a row in a content editable div using Javascript. User should be allowed to enter one space at a time.
This is allowed
This is not
Here's what I'm trying:
document.getElementById("div")
.addEventListener("keydown", function(e) {
if (e.key === " ") {
const text = this.innerText,
i = window.getSelection().anchorOffset;
const chars = text.substring(i - 1, i + 1);
if (chars.includes(" ")) {
e.preventDefault();
return false;
}
}
});
<div contenteditable=true id=div></div>
The logic seems correct to me. The problem is that it doesn't work. I can insert as many spaces as I want to and the browser doesn't care. I have logged the variables using a debugger at each step and it has the expected value, but when chars.includes(" ") is evaluated, the if statement always evaluates to false for some reason even though chars contains a (space). Am i doing something wrong? please help.
Because of how contenteditable elements work, spaces you type are non-breaking spaces (ASCII 160) instead of normal spaces (ASCII 32). HTML collapses multiple spaces to one by default so spaces must be non-breaking inside the contenteditable, otherwise you wouldn't see more than one when typing multiple spaces.
You'll have to check for a non-breaking space specifically:
document.getElementById("div")
.addEventListener("keydown", function(e) {
if (e.key === " ") {
const text = this.innerText,
i = window.getSelection().anchorOffset;
const chars = text.substring(i - 1, i + 1);
if (chars.includes( String.fromCharCode(160) )) {
e.preventDefault();
return false;
}
}
});
<div contenteditable=true id=div></div>
You might also want to check for normal spaces as well because I'm not sure if this behavior is consistent among browsers.
ok, you can't put 2 spaces anywhere
edit: works in chrome, ff, edge
document.getElementById('div').addEventListener('keydown', function(e) {
if (e.code === 'Space' || e.key === ' ') {
const node = window.getSelection();
const text = node.anchorNode.textContent;
const cur = node.anchorOffset;
const front = text[cur - 1];
const back = text[cur];
const reg = /\s/;
if (reg.test(front) || reg.test(back)) e.preventDefault();
}
});
div {
border: 1px solid tomato;
}
<div contenteditable="true" id="div"></div>
First, add a keydown listener to check what the last key pressed was like this:
document.getElementById("div").addEventListener("keydown", function(e) {
});
Second, Within that listener function, add another function, say stop() that has all the codes that you want to run (e.g. preventDefault(), return false, etc) if someone is trying to add two consecutive spaces like this:
document.getElementById("div").addEventListener("keydown", function(e) {
function stop() {
alert("two whitespaces detected");
e.preventDefault();
return false;
}
});
Third, add an if statement inside the listener to check if the key last pressed was space like this:
document.getElementById("div").addEventListener("keydown", function(e) {
function stop() {
alert("two whitespaces detected");
e.preventDefault();
return false;
}
if (e.code == "Space" || e.key == " ") {
}
});
Fourth, assign the value of whatever has been typed on the div to a variable, say content as a string and also assigned another variable, say prevVal to the last character of the content string like this:
document.getElementById("div").addEventListener("keydown", function(e) {
function stop() {
alert("two whitespaces detected");
e.preventDefault();
return false;
}
if (e.code == "Space" || e.key == " ") {
var divContent = e.target;
var content = divContent.innerText;
var prevVal = content.substr(content.length - 1);
}
});
Lastly, you can either use another if statement to check if the last key pressed is a white-space or not by comparing it to the assigned variable prevVal above and if it is, run the stop() function that we wrote earlier like this:
document.getElementById("div").addEventListener("keydown", function(e) {
function stop() {
alert("two whitespaces detected");
e.preventDefault();
return false;
}
if (e.code == "Space" || e.key == " ") {
var divContent = e.target;
var content = divContent.innerText;
var prevVal = content.substr(content.length - 1);
if (prevVal.trim() === '') {
stop();
} else {
console.log("nevermind");
}
}
});
Check the code snippet below or go to this jsFiddle to see the above code in action:
var div = document.getElementById("div");
div.addEventListener("keydown", function(e) {
function stop() {
alert("two whitespaces detected");
e.preventDefault();
return false;
}
if (e.code == "Space" || e.key == " ") {
var divContent = e.target;
var content = divContent.innerText;
var prevVal = content.substr(content.length - 1);
if (prevVal.trim() === '') {
stop();
} else {
console.log("nevermind");
}
}
});
#div {border: 1px solid #000;}
<div contenteditable="true" id="div"></div>
Update
This implementation ignores newline characters and will cause bugs if they are added by the user (In the app i'm implementing, I don't allow the user to add any newline characters, so this implementation works). For a full implementation please have a look at #WASD 's answer.
Update TLDR: Implementation works on Firefox, Edge, Safari, Chrome, and Opera when 'Enter' key is not allowed.
After some playing around, I finally figured out a solution. In firefox, .innerText is very buggy. Therefore, we need to use .textContent instead which works well for all browsers. If we use .textContent, in all browsers except Firefox, when the user presses the spacebar, a character that looks like a space is added to the content, but IT IS NOT a space. It's actually a different character which is the ASCII character 160. In Firefox, the normal ASCII character 32 is used for the space. Now that we have this information, we can get started on solving the problem. Here is a cross-browser implementation that works.
document.getElementById("div")
.addEventListener("keydown", function(e) {
if (e.key === " ") {
const text = this.textContent,
i = window.getSelection().anchorOffset;
const chars = text.substring(i - 1, i + 1);
if (chars.includes(" ") ||
chars.includes(String.fromCharCode(160))) {
e.preventDefault();
return false;
}
}
});
<div style="border: 2px solid black;" contenteditable=true id=div></div>
Firstly, we use .textContent instead of .innerText. Additionally, in the if statement, we check if the text in the contenteditable div includes the conventional space char (ASCII 32) and we also check if it includes the ASCII 160 which is used by every browser except FF. If the text includes the 160 or 32 char, then we need to preventDefault() and stop the user from adding multiple space characters consecutively.

JavaScript how to make backspace work in my keyCode text prompt?

I'm creating a way to type anywhere by intercepting the keydown event instead of using a text box for a project. I'm having trouble finding out how to implement the backspace. This is a shortened version of my code:
$(document).keydown(function(event){
typed = String.fromCharCode(event.keyCode);
display += typed;
document.getElementById("text").innerHTML = letterContainer;
});
I was trying to use the .replace function like this...
if (event.keyCode == 8) {
display.replace(typed,'');
}
...and put it at the beginning, but that doesn't work. Any ideas?
You're getting there. How are you emptying the text in the input tag?
if (event.keyCode == 8) {
display.replace(typed,'');
// ^ This does not change the value of the <input>
}
I'd suggest something like:
function isDeleteKeyCode(event) {
return event && event.keyCode === 8;
}
function resetValue(element) {
element.value = '';
}
$('#input-id').keydown(function(event) {
if (isDeleteKeyCode(event)) {
resetValue(event.target);
}
// ^ This can be simplified as: isDeleteKeyCode(event) && resetValue(event.target)
});
That would add a keydown listener to an input tag with id="input-id".
Demo: https://jsfiddle.net/pp16tru7/
var display = '';
$(document).keydown(function(event) {
var typed = String.fromCharCode(event.keyCode);
// if backspace, get text without the last character, else add character to display
if (event.keyCode === 8) {
display = display.substr(0, display.length - 1);
} else {
display += typed;
}
document.getElementById("text").innerHTML = display;
});

Input is not detecting that it is empty if removing text with "ctrl + a + backspace"

I am doing some easy div filtering with jQuery and input field. It is working, however it is not detecting that it is empty if I remove input using " Ctrl + a + backspace ", in other words if I select all text and remove it. What causes this?
It is not reordering divs back to default if using the keyboard commands but is going back to normal if you backspace every character.
This is how I do it:
$('#brandSearch').keyup(function() {
var valThis = $(this).val().toLowerCase();
if (valThis.length == 0) {
$('.card').show();
} else {
$('.card').each(function() {
var text = $(this).text().toLowerCase();
(text.indexOf(valThis) >= 0) ? $(this).parent().show(): $(this).parent().hide();
});
};
});
Your if block that handles the empty string is not showing the same elements that the else block hides. The else block calls .parent() but the if block does not.
So the else case shows or hides the parent of each .card element, but the if case shows the .card elements themselves—without unhiding their parents. See my comments added to the code (I also reformatted the conditional expression in the else for clarity):
$('#brandSearch').keyup(function() {
var valThis = $(this).val().toLowerCase();
if (valThis.length == 0) {
// Show all of the .card elements
$('.card').show();
} else {
$('.card').each(function() {
var text = $(this).text().toLowerCase();
// Show or hide the *parent* of this .card element
text.indexOf(valThis) >= 0 ?
$(this).parent().show() :
$(this).parent().hide();
});
};
});
Since it sounds like the non-empty-string case is working correctly, it should just be a matter of adding .parent() in the if block so it matches the others:
$('#brandSearch').keyup(function() {
var valThis = $(this).val().toLowerCase();
if (valThis.length == 0) {
// Show the parent of each .card element
$('.card').parent().show();
} else {
// Show or hide the parent of each .card element
$('.card').each(function() {
var text = $(this).text().toLowerCase();
text.indexOf(valThis) >= 0 ?
$(this).parent().show() :
$(this).parent().hide();
});
};
});
This is the kind of situation where familiarity with your browser's debugging tools would pay off big time. The .show() or .hide() methods manipulate the DOM, and by using the DOM inspector you could easily see which elements are being hidden and shown.
In fact, as a learning exercise I recommend un-fixing the bug temporarily by going back to your original code, and then open the DOM inspector and see how it reveals the problem. While you're there, also try out the JavaScript debugger and other tools.
If you use Chrome, here's an introduction to the Chrome Developer Tools. Other browsers have similar tools and documentation for them.
It seems to be working just fine:
$('#brandSearch').keyup(function() {
var valThis = $(this).val().toLowerCase();
if (valThis.length == 0) {
$('.card').show();
console.log("input is empty");
} else {
console.log("input is not empty");
$('.card').each(function() {
var text = $(this).text().toLowerCase();
(text.indexOf(valThis) >= 0) ? $(this).parent().show(): $(this).parent().hide();
});
};
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="brandSearch">

Create an autocompleter like the Facebook status update

I'm trying to create a div with contenteditable like the Facebook status update. Then I mean I want to show an autocomplete box when the user have written #.
How would you do that. Currently I'm just playing with keypress and check if the keycode = 64. Somehow that works, but it doesn't validate if there's a space before the alfa, or if the user has unfocused the box, then focused it again.
Any ideas? Or do you know about any plugin that works something like that?
Tnx
I'd probably do it with keypress too.
but we need to check the cursor position to check the character before the '#'.
here's the function I used from http://javascript.nwbox.com/cursor_position/cursor.js
function getSelectionStart(o) {
if (o.createTextRange) {
var r = document.selection.createRange().duplicate();
r.moveEnd('character', o.value.length);
if (r.text == '') return o.value.length
return o.value.lastIndexOf(r.text);
} else {
return o.selectionStart;
}
}
then with jquery I wrote this keypress callback function:
txt.keypress(function(event) {
if (event.which == 64) {
var index = getSelectionStart(this)
var prevChar = txt.val().substring(index - 1, index);
// now you can check if the previous char was a space
}
});

Categories

Resources