I have a contentEditable div with following text for example:
<div contentEditable='true'> This document is classified</div>
Now for example if user clicks on 'm' in word document I want to show a context menu containing few text choices. That context menu will be contained in a div element. I want to replace the word "document" with the option(text) selected by user from context menu. In my view I have to find absolute position of click to show context menu and then I have to find space elements before and after the caret position and then replace the selection with option selected from context menu. Any idea how I can do it using JavaScript and jQuery?
Edit:
one part of my question is about context menu and other the more important is how i can detect the word on wchich user has clicked on in contentEditable div or in text area on the other hand. my goal is something like in below picture
actually i want to make a similar transliteration application. the process of script conversion from roman to Urdu has been done however i m facing lot of problems in user interface development on the web. google transliteration application can be found here. i hope someone can help me in getting word under user's mouse and display a context menu containing few choices.
You may want to have a look at some of the existing context menu plugins
http://www.trendskitchens.co.nz/jquery/contextmenu/
http://labs.abeautifulsite.net/projects/js/jquery/contextMenu/demo/
http://plugins.jquery.com/plugin-tags/context-menu
To get the currently selected word in a text area, have a look at the fieldSelection plugin.
So using the second plugin and fieldSelection (Disclaimer: I think the indexing regarding string replacement is slightly off - have not fully tested)
Javascript
$(document).ready(function () {
//replace
$("#tx").contextMenu({
menu: 'replace'
}, function (action, el, pos) {
update(action, el)
});
function update(action, el) {
var range = $("#tx").getSelection();
var textLength = $("#tx").val().length;
var firstPart = $("#tx").val().substring(0, range.start);
var secondPart = $("#tx").val().substring(range.start, textLength);
var lastIndexofSpaceInfirstPart = firstPart.lastIndexOf(" ");
var firstIndexofSpaceInSecondPart = secondPart.indexOf(" ");
var startOfWord = 0
var endOfWord = 0
if (lastIndexofSpaceInfirstPart < 0) {
// start of string
startOfWord = 0;
} else {
startOfWord = lastIndexofSpaceInfirstPart;
}
if (firstIndexofSpaceInSecondPart < 0) {
// end of textArea
endOfWord = textLength
} else {
endOfWord = firstIndexofSpaceInSecondPart + range.start;
}
var word = $.trim($("#tx").val().substring(startOfWord, endOfWord));
var replaceMent = $("#tx").val().substr(0, startOfWord)
+ action.toString()
+ $("#tx").val().substr(endOfWord, textLength);
alert(
"rangeStart: " + range.start +
"\n\nrangeEnd: " + range.end +
"\n\nrangeLength: " + range.length +
+"\n\nfirstPart: " + firstPart
+ "\n\n secondPart: " + secondPart
+ "\n\n lastIndexofSpaceInfirstPart: " + lastIndexofSpaceInfirstPart
+ "\n\n firstIndexofSpaceInSecondPart:" + firstIndexofSpaceInSecondPart
+ "\n\nWordStart: " + startOfWord
+ "\n\nEndofWord: " + endOfWord
+ "\n\nWord: '" + word + "'"
+ "\n\nNew Text: '" + replaceMent + "'"
);
}
});
Html
<textarea id="tx">
Some text
</textarea>
<ul id="replace" class="contextMenu">
<li class="edit">youwordhere1</li>
<li class="cut separator">youwordhere2</li>
<li class="copy">youwordhere3</li>
<li class="paste">youwordhere4</li>
<li class="delete">youwordhere5</li>
<li class="quit separator">youwordhere6</li>
</ul>
To detect a word under the mouse cursor rock solid reliable: Just wrap every word you want to detect in a separate container, span for example. It is not difficult to do with javascript. Just parse the words in a loop and create span element for every one, then set its innerHTML = word. Then you can read the click event target and it will give you the span with the word.
To show a context menu on the spot is a trivial task:
Create a div with your content menu and make it positiion:absolute and display:none. When you want to show it, set its top and left styles (for example) equal to the target span element offset from the parent container and make its style display = "box". You are all set ;)
Related
I recently asked about it, but seems like the previous answer wasn't about what I was looking for. If I have some code like this:
<br><br><br>...Lots of BRs...<br><br><br>
<div id="thetargetone"></div><div id="show"></div>
<br><br><br>...Lots of BRs...<br><br><br>
<div id="thetargettwo"></div><div id="show"></div>
<br><br><br>...Lots of BRs...<br><br><br>
<div id="thetargetthree"></div><div id="show"></div>
<br><br><br>...Lots of BRs...<br><br><br>
//and so on...
And what I want is, when user scrolls(or pressing "space" or in other way) comes to this specific IDs (id=thetargetone or thetargettwo etc) I will target them with JavaScript code to manipulate the properties. For example, if user scrolls to id="thetargetone" JavaScript will fire opacity of the id="thetargetone". Is this possible to do with only JavaScript? If so, how can I do this, please?
One easy solution would be to use Bootstrap's plugin Scrollspy. You can find documentation on Bootstrap's page there. It's very simple to use:
$('#myScrollspy').on('activate.bs.scrollspy', function () {
// do something…
})
Here is a pure Javascript function:
var targets = document.getElementsByClassName("target");
var theTarget = targets[0];
window.findTarget = function() {
var closest = 10000000;
for(var i=0; i<targets.length; i++) {
var scrolled = document.body.scrollTop;
var fromTop = targets[i].offsetTop;
//console.log(Math.abs(fromTop - scrolled) + " | " + i + " | " + scrolled + " | " + closest);
if(Math.abs(fromTop - scrolled) < closest) {
closest = Math.abs(fromTop - scrolled);
theTarget = targets[i];
//console.log(closest + " | " + i);
}
}
console.log(theTarget.id);
}
window.addEventListener("scroll", findTarget);
Set an arbitrary large number
Calculate the window scroll position
Calculate all the element's position from the top
Get the smallest number and store the corresponding reference (theTarget)
Once you get the reference you would say for example:
theTarget.style.display = "none";
JS Fiddle: https://jsfiddle.net/vr69ngcr/1/
There are console logs that show the id of the closest target changing as you scroll
We are using Selenium Webdriver for Test automation. Here is my requirement.
HTML looks like this.
<p> I need to click before this. Help me achieve this </p>
The text within "p" tag can have any number of lines. I get a specific word as test input and need to place cursor before that word.
I tried locating element using Xpath contains text, it returns the entire paragraph and clicks in the middle of the paragraph(for chrome).
Can some one help me how I can achieve this ?
In JavaScript you can specify coordinates using document.elementFromPoint
document.elementFromPoint(x, y).click();
In C# and Java you can use Actions class
WebElement element = driver.findElement(By...);
Actions actions = new Actions(driver);
actions.moveToElement(element).moveByOffset(dx, dy).click().perform();
Use getSize function and then divide height and width of element, after that apply Action class click method, here is sample code:
WebElement el = driver.findElement(By.xpath("//xpath"));
Dimension location = el.getSize();
int y = location.height/2;
int x = location.width/2;
Actions build = new Actions(driver);
build.moveToElement(el, x, y).click().build().perform();
Hope this will work!!
Selenium doesn't have an API to directly deal with a text node.
However you can retrieve the word position with a piece of JavaScript and click the previous word with the Actions class by providing an offset position relative to the element holding the text.
This is an example that double clicks the word in front of "Exchange" which is "Stack":
// script to get the relative position/size of a word in an element
final String JS_GET_WORD_RECT =
"var ele=arguments[0], word=arguments[1], rg=document.createRange(); " +
"for(var c=ele.firstChild, i; c; c=c.nextSibling){ " +
" if(c.nodeType != 3 || (i=c.nodeValue.indexOf(word)) < 0) continue; " +
" rg.setStart(c, i); rg.setEnd(c, i + word.length); " +
" var r = ele.getBoundingClientRect(), rr = rg.getClientRects()[0]; " +
" return { left: (rr.left-r.left) | 0, top: (rr.top-r.top) | 0, " +
" width: rr.width | 0, height: rr.height | 0 }; " +
"};";
WebDriver driver = new ChromeDriver();
JavascriptExecutor js = (JavascriptExecutor)driver;
// load the page
driver.get("http://stackexchange.com/legal/content-policy");
// get the text element
WebElement element = driver.findElement(By.cssSelector(".sectionheader > h2:nth-child(1)"));
// get the relative position/size {left, top, width, height} for the word "Exchange"
Map rect = (Map)js.executeScript(JS_GET_WORD_RECT, element, "Exchange");
// define a relative ckick point for the previous word "Stack"
Long offset_x = (long)rect.get("left") - (long)rect.get("width") / 2;
Long offset_y = (long)rect.get("top") + (long)rect.get("height") / 2;
// double click the word "Stack"
new Actions(driver)
.moveToElement(element, offset_x.intValue(), offset_y.intValue())
.doubleClick()
.perform();
driver.quit();
I want to write a Greasemonkey script that will change the color of the text on any page while leaving the structure as it is. I would like to change the first 10 visible characters to be red, the next 10 to be blue, the next to be red again and so on.
I see two possible ways of going about this:
iterating through every element on the page, checking if it has text that is displayed and changing the text color. I guess this can be done by getting all elements using document.getElementsByTagName('html')[0].innerHTML and then calling elements[i].textContent to get the text but I do not know how to determine if the text is visible or not. This will return the text inside <script> elements and adding color attributes to those elements will break the page.
selecting the text on the page with something like window.getSelection().addRange(WholePage) but then I don't know of any way of changing the text color.
If you think of any other method please feel free to suggest it.
Try this (use jQuery).
$('p, li').each(function(){
var length = $(this).text().length;
var newStr = "";
for (var i = 0; i < length; i+=20) {
newStr += '<span style="color:red">' + $(this).text().substring(i, i + 10) + '</span>';
newStr += '<span style="color:blue">' + $(this).text().substring(i + 10, i + 20) + '</span>';
}
$(this).html(newStr);
});
I've known how to use the document.selection to do the highlighting. For example
/* http://jsfiddle.net/4J2dy/ */
$("#content").on('mouseup', function() {
highlighting();
});
var highlighting = function () {
var seleted_str = (document.all) ? document.selection.createRange().text : document.getSelection();
if(seleted_str != "") {
var stringToBeHighlighted = seleted_str.getRangeAt(0);
var span = document.createElement("span");
span.style.cssText = "background-color: #80deea";
span.className = "MT";
stringToBeHighlighted.surroundContents(span);
}
};
But there is something I don't know how to achieve.
Let's say that I have four layers created with the same content at the same time.
And I would like to select a sentence on the controlling layer while all the same sentence in the other three layers will be selected too.(See image below)
After the selection, I would like to pop out a menu(which I can do), and get the DOM element based on which button is pressed.(See image below)
Could anyone tell me how to achieve this? Or it just can't be done? I would be grateful if someone could answer for me.
It's kind of possible, and I would appreciate the input of SO user Tim Down as he knows a lot about JS Range/Selections, but I'll present my partial solution already.
Instead of selecting the 4 layers, you could just store the startOffset & endOffset in an external object that is updated on mouseup. The only by-effect this has, is that the user's selection will only get the color of the span when they click a layer button.
The advantage is that you can now simply work with DOM Textnodes as opposed to ranges/ selection (more complex, to me anyway).
I've chosen to find the layers with a data-layer attribute on the buttons and a corresponding id on the layers themselves. I handled the 'appending' of the 'selected span' by slicing the text content of the text nodes in the layers, like so:
layer.innerHTML = txt.slice(0, selText.start)
+ '<span class="MT" style="background-color: #80deea">'
+ txt.slice(selText.start, selText.end) + '</span>'
+ txt.slice(selText.end, txt.length);
See it in action here. I've added a cleanSelection function so only one selection is possible at a time (the start & end counters fail because selection ranges don't take into account HTML tags, so you have to get rid of the spans).
Final notes:
The fiddle will not work in browsers not supporting getElementsByClassName
The fiddle only supports one selection at a time.
The fiddle does not extensively test all conditions (eg, whether the nodetype of the first child is truly a text node, etc. But it ain't hard to add that yourself)
Entire JS code as reference (also in fiddle):
// this object will hold the start & end offsets of selection value
var selText = false;
// selText will be updated on mouseup
document.body.onmouseup = getSelText;
// on button click, selText will be highlighted
document.body.onclick = function(e) {
var target = e.target || e.srcElement, range, layer, txt;
// only do if it's a layer button & the selection is non-empty
if (target.getAttribute('data-layer') && selText !== false) {
// first remove previous spans, they break the startOffset & endOffset of the selection range
cleanSelection();
// get the clicked layer
layer = document.getElementById(target.getAttribute('data-layer'));
// this assumes that the first node in the layer is a textNode
txt = layer.firstChild.nodeValue;
// let's append the selection container now
layer.innerHTML = txt.slice(0, selText.start)
+ '<span class="MT" style="background-color: #80deea">'
+ txt.slice(selText.start, selText.end) + '</span>'
+ txt.slice(selText.end, txt.length);
// ...and empty the 'real selection'
window.getSelection().collapse();
// log results to console
console.log('From char ' + selText.start + ' to char ' + selText.end + ', in ' + layer.id);
}
};
function getSelText () {
var seleted_str = (document.all) ? document.selection.createRange().text : document.getSelection(), stringToBeHighlighted;
if(seleted_str !== "") {
stringToBeHighlighted = seleted_str.getRangeAt(0);
selText = {
start: stringToBeHighlighted.startOffset,
end: stringToBeHighlighted.endOffset
};
} else {
selText = false;
}
}
function cleanSelection() {
var getText, mtSpan = document.getElementsByClassName('MT');
for ( var i = 0; i < mtSpan.length; i++) {
getText = mtSpan[i].innerHTML;
mtSpan[i].previousSibling.nodeValue = mtSpan[i].previousSibling.nodeValue + getText + mtSpan[i].nextSibling.nodeValue;
mtSpan[i].parentNode.removeChild(mtSpan[i].nextSibling);
mtSpan[i].parentNode.removeChild(mtSpan[i]);
}
}
I have a textarea that keeps track of a moveble div like this
$('#content').children().draggable({
drag : function () {
$('#textarea').text("left:" +($(this).position().left) + "px" + "\ntop:" + $(this).position().top + "px");
}
});
The problem is that if i write something in that textarea, it will stop updating the position if i move the div.
If the text in the textarea is like this:
blablablabla
left:10px
top:20px;
blablablablabla
I want to be able to write in the textarea and update the position if i move the div without the other content of the textarea being removed.
someone should be able write whatever they want in the textarea and if they move it, the position will appear a specific place or at the end of what they have written
Any ideas?
Example using ".val" instead of ".text": http://jsfiddle.net/Ydkrw/
".val" will remove the existing text...
Update: based on valentinos answer i did like so: http://jsfiddle.net/8G82U/
But this doesn't work if you write something in the textarea before you move it
This could be something to get you started. This is just a rough design that might help you. Used substring and replace javascript methods.
Fiddle
You could save the string denoting the position in variable, and replace only that string when you drag the div. Like this:
var position;
var oldposition = '';
$('#content').children().draggable({
drag: function () {
position = "left:" + ($(this).position().left) + "px" + "\ntop:" + $(this).position().top + "px";
$('#textarea').val($('#textarea').val().replace(oldposition, position));
oldposition = position;
}
});
http://jsfiddle.net/kCT9f/
Typically when I'm doing something like this I add a replacement keys to the content and replace it when I need to prepare the content.
<textarea>blablablabla {left: 10px} {top: 20px} blablablablabla</textarea>
Update the keywords when the div is moved like this.
$('#content').children().draggable({
drag: function(){
var text = $('#textarea').text();
// Replace Keys
text = replaceLengthKey(text, "left", $(this).position().left);
text = replaceLengthKey(text, "top", $(this).position().top);
// Return changes
$("#textarea").text(text);
}
});
function replaceLengthKey(source, name, value){
// Absolute length units: https://developer.mozilla.org/en-US/docs/CSS/length#Absolute_length_units
return source.replace(RegExp("\\{" + name + ":\\s*[0-9]*(px|mm|cm|in|pt|pc)?\\}", "gm"), "{" + name + ": " + value + "}")
}
Then, when you prepare the content as you want on the server or client, but at least the structure will be consistent.
You can enclose the position text within brackets and then when the div is moved apply a regular expression and change only the text within the brackets. Here is a working example.
var text=$('#textarea').val();
var newPos = "(left:" + ($(this).position().left) + "px" + "\ntop:" +($(this).position().top) + "px)"
text=text.replace(/ *\([^)]*\) */g, newPos);
$('#textarea').val(text);