I am building a WYSIWYG rich text editor.
When the user selects a portion of his/her text, I would like to present a menu in a tooltip. Presenting the menu works fine but I would like to only show it if the user hovers over the selected text.
As Illustrated:
I also haven't decided on positioning (I like the way that it's illustrated) but to clarify, that's not the point of the question.
The question is: How do I filter for a hover event that happens over selected text?
The Problems:
I can't just listen for a text selection event or test hover events to see whether they are over elements that have selected text inside them. The left image would generate a false positive with that.
I know how to get the selected text but I don't know how to get the selected region.
To my mind, the ideal solution is somehow to calculate the region of the selected text and test whether mouseover events happen in that region.
On mouseup, use Range.getClientRects to grab the bounding rectangles of each line of the selection.
You can then test if the mouse is over the selection like this:
var cr= [];
$(document).on({
'mouseup': function() {
cr= window.getSelection().getRangeAt(0).getClientRects();
},
'mousemove': function(ev) {
//hide the pop up
for(var i = 0 ; i < cr.length ; i++) {
if(ev.pageX >= cr[i].left && ev.pageX <= cr[i].right &&
ev.pageY >= cr[i].top && ev.pageY <= cr[i].bottom
) {
//show the pop up
break;
}
}
}
});
Fiddle
Try this way:
JS
$(document).ready(function () {
$(document).on("mouseup", ".conttext", function () {
var highlight = window.getSelection();
console.log(highlight);
var spn = '<span class="highlight">' + highlight + '</span>';
var text = $('.conttext').text();
$('.conttext').html(text.replace(highlight, spn));
});
$(document).on("mouseover", ".highlight", function () {
alert("You hovered on selected tex"); // Your tooltip code goes here
})
});
CSS:
.highlight {
color:#888;
position:relative;/*This will not matter if you inject tooltip using JS*/
display:inline-block;/*This will not matter if you inject tooltip using JS*/
}
HTML:
<div class="conttext">Sample text</div>
Demo: http://jsfiddle.net/lotusgodkk/BGKSN/202/
Related
Text Selection and highlighting is not working when I want to select the text on clicking of a dynamic button, but it works when I directly select the text from paragraph (on mouseup) itself. (see JSFiddle for complete code)
I have generated dynamic buttons for each tag with various color, eg. Tag1-Green, Tag2-Blue, Tag3-Red,...etc
So when user clicks on :
Tag1 button, selected text from the paragraph should get highlighted in the green color
Tag2 button, selected text from the paragraph should get highlighted in the blue color
Tag3 button, selected text from the paragraph should get highlighted in the red color
...etc
var getSelectedText = function() {
var selectedRange ="";
try {
if (window.getSelection) {
selectedRange = window.getSelection().getRangeAt(0);
} else {
selectedRange = document.getSelection().getRangeAt(0);
}
} catch (err) {
console.log(err)
}
return selectedRange
};
function highlight(){
//Below Commented code works fine
// $("p.raw_text").on("mouseup", function(e) {
// e.preventDefault()
// var selection = getSelectedText();
// alert("Selection: " + selection);
// });
//But this code does not work as buttons with id that starts with highlight are dynamically generated
$(document).on('click', '[id^=highlight]', function(e) {
e.preventDefault()
var selection = getSelectedText();
alert("Selection: " + selection);
});
}
$(function(){
highlight();
});
P.S Highlighting of the text is not yet implemented as I am not getting the selected text to highlight it..
EDIT
Note - I am trying to build a data annotation tool to annotate my machine learning training data. What it does is, it allows users to select the NER text to be annotated and mark it with specific label(or tag). Ex. selected text could be a person's name from the paragraph, then user should click on the Tag (Person) and highlight the selected text from the paragraph with some color, similarly there could be any number of Tags(labels) in the paragraph such as organization, city, country, etc User should be able to highlight each NER with specific color. (which further, I will pass that resultant data along with its highlighted indexes to build a machine learning model.)
Below code perfectly worked for me as I moved button click inside paragraph click.
function highlight(){
$(document).on('click', 'p.raw_text', function(e) {
e.stopPropagation()
var selectedRange = getSelectedText()
$(document).off('click', '[id^=highlight]')
$(document).on('click', '[id^=highlight]', function(ev) {
var text = selectedRange.toString();
if(!text)return;
console.log(selectedRange.toString())
hiliter(text, e.target, $(ev.target).data())
});
});
}
function hiliter(word, element, data) {
console.log("data", data.color)
var rgxp = new RegExp(word, 'g');
var repl = '<span style="background-color:'+ data.color +';">' + word + '</span>';
element.innerHTML = element.innerHTML.replace(rgxp, repl);
}
P.S- With my little knowledge of jquery, I am not sure if I am doing it right way. Please verify someone. Thank you
I am making a task to recreate paint kind of by making 100 x 50 cells and onlcick giving them a background color that is selected. But to make the drawing easier a user should be able to hold his mouse button and drag over the cells to make them fill in with a background color. As soon as he releases the mouse button the drawing should stop and you are back to clicking or have to hold the mouse button again.
I have tried to do this with a .mousemove but that did not work out.
// MAKING THE GRID
for (let i = 1; i <= 49; i++){
$('#canvas').append('<tr id="table' + i + '"</tr>');
for(let j = 1; j <= 100; j++){
$('#table' + i).append('<td></td>');
}
}
// ADDING A COLOR ON CLICK
$('td').click(function kleur(){
if($(this).attr('style')){
$(this).removeAttr('style')
} else {
$(this).attr('style', 'background-color:' + color);
}
})
// SELECTING COLOR ON RIGHT CLICK
$('td').mousedown(function(e) {
switch (event.which) {
case 3:
$('.popup').show();
$('.popup').css({left: e.pageX});
$('.popup').css({top: e.pageY});
}
});
Try listen to mouseover event and check if the button was pressed. It would look somehow like:
$("#canvas").on("mouseover", function(e) {
if(e.buttons == 1)
Draw(e.target);
});
I made quick example for you (in vanilla JS unfortunately, I dont use jQuery anymore):
https://codepen.io/DooMxDD/pen/RmWqjM
It worked out by adding these lines of code.
// Adding the selected color to coloring function
function kleur(target) {
target.style.backgroundColor = color;
}
// Coloring when mouse is held down
$("#canvas").on("mouseover", function(e) {
if(e.buttons == 1)
kleur(e.target);
});
I've been trying to do a design which requires a preview of an image within an input box and playing around hide and show elements. I have successfully done the preview image with the help of someone on this site and now I've been trying to solve the hide and show element using JS but to no avail.
Let me explain my problem, I have six main boxes and one orange box. But I just want to show 3 boxes on the form and hide the other 3 boxes, so that whenever a user clicks on the orange box it will display the 3 hidden box and hide itself and show a checkbox which will be marked or checked. When for example someone unchecks the check box it will return as it was before.
I tried doing it this way , here is my script
$(function() {
var count = 0;
$('.upload-img').on('change', function(evt) {
var file = evt.target.files[0];
var _this = evt.target;
$(this).parent('.upload-section').hide();
var reader = new FileReader();
reader.onload = function(e) {
var span = '<img class="thumb mrm mts" src="' + e.target.result + '" title="' + escape(file.name) + '"/><span class="remove_img_preview"></span>';
$(_this).parent('.upload-section').next().append($(span));
};
reader.readAsDataURL(file);
evt.target.value = "";
});
$('.preview-section').on('click', '.remove_img_preview', function() {
$(this).parent('.preview-section').prev().show();
$(this).parent('.preview-section').remove();
});
});
$(function(){
var answer = $(".answer");
var clickable = $(".clickable");
answer.hide()
$(".file_container-orange").on("click", function(){
$(this).hide()
if(!answer.is(":visible")){
answer.show().addClass("clickable")
}else{
answer.hide();
}
})
//EXTRA:: makes the button and answer toggle on click
$("body").on("click", ".clickable", function(){
$(this).hide();
if(!answer.is(":visible")){
$(".file_container-orange").show();
}
})
})
and also you can check the link and see, more on it
https://plnkr.co/edit/jtUvlq4lClyfFHrFmzzO?p=preview
But don't know how I can make it return to how it was before when I will click on the check box.
To check the checkbox when click on the orange box, use this below code after the line
answer.show().addClass("clickable")
$("[type='checkbox']").prop("checked", "checked")
But don't know how I can make it return to how it was before when I will click on the check box. Use the below code:
$("body").on("change", "[type='checkbox']", function() {
if (!$(this).prop("checked")) {
answer.hide();
$(".file_container-orange").show();
}
})
Please refer the working sample here https://plnkr.co/edit/5Wy6rNkATY3cbT8v4RWH?p=preview
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 ! ;)
I have a paragraph of text in which the user may place a "pin" to mark a position. Once a pin has been placed, I would like to allow the user to move its position by dragging it to a new location in the paragraph. This is simple to do with block elements, but I have yet to see a good way to do it with inline elements. How might I accomplish this?
I have already implemented it using window.selection as a way to find the cursor's location in the paragraph, but it is not as smooth as I would like.
As a note, I am using the Rangy library to wrap the native Range and Selection functionality, but it works the same way as the native functions do.
Here is the code:
$(document).on("mousedown", '.pin', function () {
//define what a pin is
var el = document.createElement("span");
el.className = "pin";
el.id = "test";
//make it contain an empty space so we can color it
el.appendChild(document.createTextNode("d"));
$(document).on("mousemove", function () {
//get the current selection
var selection = rangy.getSelection();
//collapse the selection to either the front
//or back, since we do not want the user to see it.
if (selection.isBackwards()) {
selection.collapseToStart();
} else {
selection.collapseToEnd();
}
//remove the old pin
$('.pin').remove();
//place the new pin at the current selection
selection.getAllRanges()[0].insertNode(el);
});
//remove the handler when the user has stopped dragging it
$(document).on("mouseup", function () {
$(document).off("mousemove");
});
});
And here is a working demo: http://jsfiddle.net/j1LLmr5b/22/ .
As you can see, it works(usually), but the user can see the selection being made. Have any ideas on how to move the span without showing the selection highlight? I will also accept an alternate method that does not use the selection at all. The goal is to allow movement of the span as cleanly as possible.
You can do this using ranges instead using code similar to this answer. Unfortunately the code is a bit longer than ideal because IE hasn't yet implemented document.caretPositionFromPoint(). However, the old proprietary TextRange object, still present in IE 11, comes to the rescue.
Here's a demo:
http://jsfiddle.net/j1LLmr5b/26/
Here's the relevant code:
var range, textRange, x = e.clientX, y = e.clientY;
//remove the old pin
$('.pin').remove();
// Try the standards-based way first
if (document.caretPositionFromPoint) {
var pos = document.caretPositionFromPoint(x, y);
range = document.createRange();
range.setStart(pos.offsetNode, pos.offset);
range.collapse();
}
// Next, the WebKit way
else if (document.caretRangeFromPoint) {
range = document.caretRangeFromPoint(x, y);
}
// Finally, the IE way
else if (document.body.createTextRange) {
textRange = document.body.createTextRange();
textRange.moveToPoint(x, y);
var spanId = "temp_" + ("" + Math.random()).slice(2);
textRange.pasteHTML('<span id="' + spanId + '"> </span>');
var span = document.getElementById(spanId);
//place the new pin
span.parentNode.replaceChild(el, span);
}
if (range) {
//place the new pin
range.insertNode(el);
}
Try this my friend
el.appendChild(document.createTextNode("d"));
You have create empty span tag that's why you found empty.
add after
el.id = "test";
this
var value = $('.pin').text();
$(el).text(value);
You can hide selection with css
::selection {color:red;background:yellow;}
::-moz-selection {color:red;background:yellow;}
that's all how i can help for a now