How to set focus on child of content editable HTML element - javascript

I've been trying to create an advanced text input where users can write hashtags.
The input is a div with contenteditable set true, and the hashtags should be child nodes as I'd allow users to put space inside the hashtags.
My problem is that on some browsers I can not set the focus on the hashtag's child node as the user types. On Chrome/Linux and Safari/OSX it seems to work well, but on Firefox and Chrome/OSX setting the focus don't seem to work. (I haven't got to Windows yet.)
var $elComposer = $('#composer');
var InsertTagPair = function (tagtype) {
var newTag = document.createElement (tagtype);
$(newTag).attr ('contenteditable', 'true');
$(newTag).attr ('class', 'tag');
$elComposer.off ('keypress');
if (window.getSelection) {
var selection = window.getSelection();
if (selection.getRangeAt && selection.rangeCount) {
var range = selection.getRangeAt (0);
range.deleteContents ();
range.insertNode (newTag);
range.selectNodeContents (newTag);
range.collapse (false);
selection.removeAllRanges ();
selection.addRange (range);
};
};
newTag.focus ();
return newTag;
};
var ComposerOnKeyPressed = function (event) {
if (event.charCode == 35) { // #
var contextTag = InsertTagPair ('span');
$elComposer.attr ('contenteditable', 'false');
return false;
};
return event;
};
$elComposer.on ('keypress', ComposerOnKeyPressed);
The above code is the essential part that's not working. See it here in action:
JSFiddle - http://jsfiddle.net/cja963ym/1/
To see a more complete version of the composer that makes more sense see this instead:
JSFiddle - http://jsfiddle.net/g_borgulya/ur6zk32s/15/
Symptom: on Firefox if you type in the editable div and press '#', you manually have to click it by mouse or move the focus with tab to be able to edit the hashtag, while on other platforms it gets the focus when I call newTag.focus() on the element.
I don't know how to move on, even how to debug this problem.

Adding newTag.focus() in the "#" handler made it work in Chrome and Firefox (Win) (it didn't before, but did on IE11, just wanted to let you know)
Fiddle fork : http://jsfiddle.net/ekxo4ra3/
Does that look OK for what you wanted ?

Related

Making the search field (with results) disappear when clicking outside on Ipad

I have two html elements as follows :
A input element whose class is "fts-input". This input serves as a search box.
A div container, containing all the list of results, whose class is "dropdown-items-wrapper"
I made a code who worked in all browsers except Safari, that made the search results disappear when clicking outside of the search field, or outside of the search results. This code is :
var ftsInput = $('.fts-input');
var dropDownList = $('.dropdown-items-wrapper');
function closeSearchWhenClickingElsewhere(event, dropDown) {
var clickedElement = event.target;
if (!clickedElement) return;
var elementClasses = clickedElement.classList;
var clickedOnSearchOrResults = elementClasses.contains('fts-input') ||
elementClasses.contains('dropdown-items-wrapper');
if(!clickedOnSearchOrResults) {
dropDown.fadeOut('slow');
}
}
$document.on('click', (e) => {
this.closeSearchWhenClickingElsewhere(e, dropDownList);
});
But this code seems to not work on Ipad (iOs Safari). Do you have a clue why it isn't working ?
You have to wrap around document within the $ jQuery symbol.
So your code becomes,
$(document).on('click', (e) => {
this.closeSearchWhenClickingElsewhere(e, dropDownList);
});
Alternatively, you can assign as this:
$document = $(document);
Created a fiddle for you which works fine on Safari 10: https://jsfiddle.net/1ymrtcbf/

Type of selections

I am trying to but together a highlighting menu that hovers over a selection with the following properties:
It should appear when a selection is made.
It should disappear when the selection is destroyed.
All that works quite nicely except for one thing: If an existing selection gets clicked the selection disappears and so should the hovering menu. But for whatever reason it doesn't.
When you click outside of an existing selection the selection type changes to 'caret' or to 'none' if you click on that very selection. So I tried setting the visibility of the menu according to the type. The problem is though that although the type of the selection appears to change in the object you get by window.getSelection(), it does not if you try to get the type from the object.
I put this jsfiddle together to illustrate the problem. https://jsfiddle.net/nxo2d7ew/1/
var el = document.getElementById("simple-text");
el.addEventListener("mouseup", placeDiv, false);
function placeDiv(x_pos, y_pos) {
var sel = window.getSelection();
var position = sel.getRangeAt(0).getBoundingClientRect();
// console.log(sel)
// console.log(position)
var highlighter = document.getElementById('highlighter').offsetWidth
var d = document.getElementById('highlighter');
d.style.left = position.left+position.width*0.5-highlighter*0.5 +'px';
d.style.top = position.top-50 +'px';
// console.log('sel.type: ' + sel.type)
var test = window.getSelection()
console.log(test) // returns an object with "type: None"
console.log(test.type) //returns "Range"
if (test.type !== 'Range') {
d.style.visibility = 'hidden';
}
else {
d.style.visibility = 'visible';
}
var sel = ''
}
Thank you :-)
The real change to selection doesn't really happen on mouseup event. There is another step afterwards that changes the selection, so when mouseup is fired, the selection hasn't changed yet. What you see in your console is not the exact state of the selection object on the mouseup event.
I'm not sure there's a cross-browser way to have access to the real selection change event, there's a selectionchange event in Chrome, and supposedly in IE (but I couldn't test it). But in Firefox it's not available. That said, the type property you're using to test if it's an empty selection doesn't seem to work on Firefox either. But you could use isCollapsed.
One way you can maybe solve the problem, though not the most elegant, is using a timeout, you only need a few milliseconds for the selection to update, then your logic will work - using isCollapsed to make it work on Firefox. Like this:
setTimeout(function(){
var test = window.getSelection()
console.log(test) // returns an object with "type: None"
console.log(test.type) //returns "Range"
if (test.isCollapsed) {
d.style.visibility = 'hidden';
}
else {
d.style.visibility = 'visible';
}}, 25);
https://jsfiddle.net/q1f4xfw9/6/
Or with selectionchange event in Chrome, you move the hide condition into this handler. Like this:
document.addEventListener("selectionchange", function () {
var d = document.getElementById('highlighter');
var test = window.getSelection()
if (test.type !== 'Range') {
d.style.visibility = 'hidden';
}
});
https://jsfiddle.net/q1f4xfw9/5/
EDIT:
There's another way to solve the problem, you could remove selection on mouse down using removelAllRanges. Then the selection change event would be triggered before the mouseup. Up to you to see if that little change in functionality works with what you want. Like this:
el.addEventListener("mousedown", function(e){
window.getSelection().removeAllRanges()
}, false);
https://jsfiddle.net/q1f4xfw9/8/

Firing an event when the caret gets within a particular div/span/a tag and also, when the caret leaves the tag

The idea is this -
There is a contenteditable element with some text in it. Am trying to build out a tagging mechanism (kind of like twitter's people tagging when you type '#'). Whenever a user types '#', it shows up a popover with suggestions and filters when they continue typing. Until here it's easy and I have got it figured out. The problem comes when I need to show the popover if/only if the caret is over the element containing the tag.
<div contenteditable="">
<p>Some random text before
<a href="javascript:;"
class="name-suggest"
style="color:inherit !important;text-decoration:inherit !important">#samadams</a>
Some random text after</p>
</div>
Now, whenever the user moves the caret over the a tag / clicks on it, I want to trigger an event that shows the popover, and remove it whenever the caret leaves the a tag. (kind of like focus / blur but they don't seem to work). onmousedown works but there is no way to tell if the cursor has been moved into the anchor tag with the keyboard.
Also, am doing this in angularjs, so, any solution targeted towards that would be preferable but not necessary.
Have been trying to get this to work for a day and any help is greatly appreciated.
This will let you know when your caret position is in an anchor node containing an #
$('#content').on('mouseup keydown keyup', function (event) {
var sel = getSelection();
if (sel.type === "Caret") {
var anchorNodeVal = sel.anchorNode.nodeValue;
if ( anchorNodeVal.indexOf('#') >= 0) {
$('#pop').show()
} else {
$('#pop').hide()
}
}
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="content" contenteditable="">
<p>Some random text before
<a href="javascript:;"
class="name-suggest"
style="color:inherit !important;text-decoration:inherit !important">#samadams</a>
Some random text after</p>
</div>
<div id="pop" style="display:none">Twitter node found</div>
You could add some regex to further validate the selection.
There is a weird move with RegExps and offset calculation in the code below, but let me explain why it's a better solution.
I've been building a complicated editor using contenteditable about a year ago. It wasn't just a disaster. It was a fucking disaster. There is no cover-all-the-cases spec. Browsers behave differently in every possible detail and it changes frequently. Put a caret before # char and you will get this is Gecko:
<a href="#">|#name
And this in WebKit:
|<a href="#">#name
Well, unless <a> is paragraph's first child. Then result would be the same as in Gecko. Try to put caret after the nickname and both will tell it's inside the link. Start typing, and caret will pop out the element - a year ago Gecko wasn't doing it.
I've used native Selection & Range APIs in this example, they are IE9+. You may want to use Rangy instead.
$el = $('#content');
var showTip = function (nickname) {
// ...
console.log('Show: ' + nickname);
};
var dismissTip = function () {
// ...
console.log('Hide');
};
// I'm sure there is a better RegExp for this :)
var nicknameRegexp = /(^|\b|\s)\#(\w+)(\s|\b|$)/g;
var trackSelection = function () {
var selection = window.getSelection(),
range = selection.rangeCount > 0 ? selection.getRangeAt(0) : null;
if (range == null || $el[0].contains(range.commonAncestorContainer) == false) {
return dismissTip();
}
var comparer = range.cloneRange();
comparer.setStart($el[0], 0);
var offset = comparer.toString().length;
var match, from, to;
while (match = nicknameRegexp.exec($el[0].textContent)) {
from = match.index + match[1].length;
to = match.index + match[1].length + match[2].length + 1;
if (offset >= from && offset <= to) {
// Force rewind, otherwise next time result might be incorrect
nicknameRegexp.lastIndex = 0;
return showTip(match[2]);
}
}
return dismissTip();
};
$el.on({
// `mousedown` can happen outside #content
'mousedown': function (e) {
$(document).one('mouseup', function (e) {
// Calling function without a tiny delay will lead to a wrong selection info
setTimeout(trackSelection, 5);
});
},
'keyup': trackSelection
});
Just looked at Fire event when caret enters span element which led me here, pretending your case was quite similar except finding if current word is specifically beginning with # for the modal to show...
The thing you need is a way to get the word we're on at the moment we move or type, then check the first character and hide/show the modal pane accordingly will be pretty easy.
function getSelectedWord(grab=document.getSelection()) {
var i = grab.focusOffset, node = grab.focusNode, // find cursor
text = node.data || node.innerText, // get focus-node text
a = text.substr(0, i), p = text.substr(i); // split on caret
return a.split(/\s/).pop() + p.split(/\s/)[0]} // cut-out at spaces
Now you can listen for keydown or selectionchange events and show your pane knowning what have already been written of the current/selected word.
editor.addEventListener('keydown', ev => {
if (ev.key.substr(0, 5) != 'Arrow') // react when we move caret or
if (ev.key != '#') return; // react when we type an '#' or quit
var word = getSelectedWord(); // <-- checking value
if (word[0] == '#') showModal(word.substr(1)); // pass without '#'
});
Note that social networks and code completion usually stops at caret position while I did check for word tail... You can go usual by removing p off of getSelectedWord function definition if desired.
Hope this still helps; Happy coding ! ;)

'Ctrl+B' functionality through jQuery using input type=button

I am trying to build an blog writing textbox, in which I am using a div element (contenteditable="true").
I am placing buttons for Bold, Italic,etc at the bottom. I want to trigger 'Ctrl+B' event when clicked on 'Bold' button and 'Ctrl+I' while clicked on 'Italic'. I have written following snippet for this functionality, but I am not getting the expected results.
Can anybody put some thought on this?
$('#bold').mousedown(function(){
var e = jQuery.Event("keydown");
e.ctrlKey= true;
e.which = 66;
e.keyCode=66;
$('#displayBox').trigger(e);
$('#displayBox').focus();
});
$('#bold').mouseup(function(){
$('#displayBox').focus();
var e = jQuery.Event("keyup");
e.ctrlKey= false;
e.which = 66;
e.keyCode=66;
$('#displayBox').trigger(e);
});
The way you make these changes to a contenteditable element is via document.execCommand
var box = document.getElementById('displayBox');
document.getElementById('bold').addEventListener('click', function (e) {
box.focus(); // make it the active element so the command is applied to it
document.execCommand('bold', false, null); // apply command
});
DEMO

Set mouse focus and move cursor to end of input using jQuery

This question has been asked in a few different formats but I can't get any of the answers to work in my scenario.
I am using jQuery to implement command history when user hits up/down arrows. When up arrow is hit, I replace the input value with previous command and set focus on the input field, but want the cursor always to be positioned at the end of the input string.
My code, as is:
$(document).keydown(function(e) {
var key = e.charCode ? e.charCode : e.keyCode ? e.keyCode : 0;
var input = self.shell.find('input.current:last');
switch(key) {
case 38: // up
lastQuery = self.queries[self.historyCounter-1];
self.historyCounter--;
input.val(lastQuery).focus();
// and it continues on from there
How can I force the cursor to be placed at the end of 'input' after focus?
Looks like clearing the value after focusing and then resetting works.
input.focus();
var tmpStr = input.val();
input.val('');
input.val(tmpStr);
It looks a little odd, even silly, but this is working for me:
input.val(lastQuery);
input.focus().val(input.val());
Now, I'm not certain I've replicated your setup. I'm assuming input is an <input> element.
By re-setting the value (to itself) I think the cursor is getting put at the end of the input. Tested in Firefox 3 and MSIE7.
Hope this help you:
var fieldInput = $('#fieldName');
var fldLength= fieldInput.val().length;
fieldInput.focus();
fieldInput[0].setSelectionRange(fldLength, fldLength);
Chris Coyier has a mini jQuery plugin for this which works perfectly well: http://css-tricks.com/snippets/jquery/move-cursor-to-end-of-textarea-or-input/
It uses setSelectionRange if supported, else has a solid fallback.
jQuery.fn.putCursorAtEnd = function() {
return this.each(function() {
$(this).focus()
// If this function exists...
if (this.setSelectionRange) {
// ... then use it (Doesn't work in IE)
// Double the length because Opera is inconsistent about whether a carriage return is one character or two. Sigh.
var len = $(this).val().length * 2;
this.setSelectionRange(len, len);
} else {
// ... otherwise replace the contents with itself
// (Doesn't work in Google Chrome)
$(this).val($(this).val());
}
// Scroll to the bottom, in case we're in a tall textarea
// (Necessary for Firefox and Google Chrome)
this.scrollTop = 999999;
});
};
Then you can just do:
input.putCursorAtEnd();
Ref: #will824 Comment, This solution worked for me with no compatibility issues. Rest of solutions failed in IE9.
var input = $("#inputID");
var tmp = input.val();
input.focus().val("").blur().focus().val(tmp);
Tested and found working in:
Firefox 33
Chrome 34
Safari 5.1.7
IE 9
What about in one single line...
$('#txtSample').focus().val($('#txtSample').val());
This line works for me.
2 artlung's answer:
It works with second line only in my code (IE7, IE8; Jquery v1.6):
var input = $('#some_elem');
input.focus().val(input.val());
Addition: if input element was added to DOM using JQuery, a focus is not set in IE. I used a little trick:
input.blur().focus().val(input.val());
I know this answer comes late, but I can see people havent found an answer. To prevent the up key to put the cursor at the start, just return false from the method handling the event. This stops the event chain that leads to the cursor movement. Pasting revised code from the OP below:
$(document).keydown(function(e) {
var key = e.charCode ? e.charCode : e.keyCode ? e.keyCode : 0;
var input = self.shell.find('input.current:last');
switch(key) {
case 38: // up
lastQuery = self.queries[self.historyCounter-1];
self.historyCounter--;
input.val(lastQuery).focus();
// HERE IS THE FIX:
return false;
// and it continues on from there
I use code below and it works fine
function to_end(el) {
var len = el.value.length || 0;
if (len) {
if ('setSelectionRange' in el) el.setSelectionRange(len, len);
else if ('createTextRange' in el) {// for IE
var range = el.createTextRange();
range.moveStart('character', len);
range.select();
}
}
}
It will be different for different browsers:
This works in ff:
var t =$("#INPUT");
var l=$("#INPUT").val().length;
$(t).focus();
var r = $("#INPUT").get(0).createTextRange();
r.moveStart("character", l);
r.moveEnd("character", l);
r.select();
More details are in these articles here at SitePoint, AspAlliance.
like other said, clear and fill worked for me:
var elem = $('#input_field');
var val = elem.val();
elem.focus().val('').val(val);
set the value first. then set the focus. when it focuses, it will use the value that exists at the time of focus, so your value must be set first.
this logic works for me with an application that populates an <input> with the value of a clicked <button>. val() is set first. then focus()
$('button').on('click','',function(){
var value = $(this).attr('value');
$('input[name=item1]').val(value);
$('input[name=item1]').focus();
});
I have found the same thing as suggested above by a few folks. If you focus() first, then push the val() into the input, the cursor will get positioned to the end of the input value in Firefox,Chrome and IE. If you push the val() into the input field first, Firefox and Chrome position the cursor at the end, but IE positions it to the front when you focus().
$('element_identifier').focus().val('some_value')
should do the trick (it always has for me anyway).
At the first you have to set focus on selected textbox object and next you set the value.
$('#inputID').focus();
$('#inputID').val('someValue')
function focusCampo(id){
var inputField = document.getElementById(id);
if (inputField != null && inputField.value.length != 0){
if (inputField.createTextRange){
var FieldRange = inputField.createTextRange();
FieldRange.moveStart('character',inputField.value.length);
FieldRange.collapse();
FieldRange.select();
}else if (inputField.selectionStart || inputField.selectionStart == '0') {
var elemLen = inputField.value.length;
inputField.selectionStart = elemLen;
inputField.selectionEnd = elemLen;
inputField.focus();
}
}else{
inputField.focus();
}
}
$('#urlCompany').focus(focusCampo('urlCompany'));
works for all ie browsers..
Here is another one, a one liner which does not reassign the value:
$("#inp").focus()[0].setSelectionRange(99999, 99999);
function CurFocus()
{
$('.txtEmail').focus();
}
function pageLoad()
{
setTimeout(CurFocus(),3000);
}
window.onload = pageLoad;
The answer from scorpion9 works. Just to make it more clear see my code below,
<script src="~/js/jquery.js"></script>
<script type="text/javascript">
$(function () {
var input = $("#SomeId");
input.focus();
var tmpStr = input.val();
input.val('');
input.val(tmpStr);
});
</script>
var prevInputVal = $('#input_id').val();
$('#input_id').val('').focus().val(prevInputVal)
Store input previous value in a variable -> empty input value -> focus input -> reassign original value SIMPLE !
It will focus with mouse point
$("#TextBox").focus();

Categories