Get start index of selected text in entire html page - javascript

I want to be able to obtain the character(s) that come before the text that the user has highlighed, and i need to include html in this. When the example below is rendered, the user will only see "word". When a user highlights this i want to be able to figure out that there is an angle bracket (or any other char)
<span>word</span>
The following will give me the selection and it gives me a start and an end index, but these are relative to the node that this text belongs to. It is also dom related and doesn't include html.
selectedText= childwindow.getSelection();
To get around this i have tried to get the indexOf value for this piece of text on the html string of the page and this works.
childwindow.document.documentElement.innerHTML.indexOf(selectedText)
The issue with this is that if I highlight something like "the", a word that appears on the page several times, the index is not correct. I can understand why, but I dont know what else to do. I would imagine i need to get this from the dom somehow as this knows the exact piece of text that I have obtained.
Even if this is not possible, is there anything i can grab from the dom that will help me make the indexOf request more reliable. I can add extra data under the hood to make sure it matches the value I want.

This ought to do the trick:
function textSelected() {
if (window.getSelection) {
t = window.getSelection().toString() || "";
} else if (document.selection && document.selection.type != "Control") {
t = document.selection.createRange().text || "";
}
return t;
}
Main thing is window's function getSelection which:
Returns a Selection object representing the range of text selected by
the user or the current position of the caret.
Once you have it, just get what you wanna from Selection obj.
The else part is in case you have selected across elements. Then you need to use selection range. More on document.selection:
docs

Related

Create a Range spanning the character before cursor in Word Javascript API / Office.js

I'm trying to create an Office Javascript Add-in which will examine the character before the cursor and replace that character depending on what it is. So I need to create a Range of the character before the cursor. I can do this easily with a VBA macro, but unfortunately, I can't find a way to do this with the new javascript api. Is this possible?
If this is possible, it would also be helpful if I could look at the 5 characters before and after the cursor for added context.
Thanks.
A couple months ago I tried something similar. In short there is no good way to do it. You could try what I will specify below, but I would advice against it. The example is not thought through and most likely will contain a number of bugs. Additionally I find this an incredibly inefficient way to do something so simple.
Limitations in the API that prevent an easy solution:
There is no cursor, only selections. This means that you need to make an assumption that the cursor is always at the beginning of a selection.
Selections cannot be directly modified through the Office.js API. So it is not possible to expand the selection to include the previous character.
The 'Range' object does allow to be extended into both directions, but it requires another range as input. This means an earlier range needs to created/found (i.e. a range object before the current selection).
You can only navigate outside of the selection through the property 'parentBody' which will give you the entire body of the document. This needs to be processed in order to isolate a range before the cursor that could help us replace the character.
From what I can tell it is not possible to create a range for a single character. So a bigger range needs to be taken before the cursor and needs to replaced entirely.
Example
// WARNING: Incredibly inefficient and poor code. Do not use directly!
// WARNING: Edge cases are not tackled in this example.
function replaceCharacterBeforeCursor() {
Word.run(function (context) {
var selection = context.document.getSelection();
// Assumption: Cursor always starts at the beginning of a selection.
var cursor = selection.getRange('Start');
// Create a new range that covers everything before the cursor (or the beginning of the selection).
var startDocument = selection.parentBody.getRange("Start");
var rangeBeforeSelection = startDocument.expandTo(startDocument);
// Capture parent paragraph.
var parentParagraph = rangeBeforeSelection.paragraphs.getLast();
context.load(parentParagraph);
context
.sync()
.then(function () {
// Create range that captures everything from the beginning of the parent
// paragraph until the cursor.
var paragraphStart = parentParagraph.getRange('Start');
var wordRangeBeforeCursor = paragraphStart.expandTo(cursor);
context.load(wordRangeBeforeCursor);
context
.sync()
.then(function () {
// Replace last character.
var oldText = wordRangeBeforeCursor.text;
var wordLength = oldText.length;
var lastCharacter = oldText.substring(wordLength - 1);
if (lastCharacter !== " ") {
var newText = oldText.substring(0, wordLength - 1) + "test";
wordRangeBeforeCursor.insertText(newText, 'Replace');
context.sync();
}
});
});
});
}
Another way to do it is through text ranges. This would be substantially more inefficient. Either way I hope this will help you in finding a solution that fits your needs.

How to select a sentence in a generic HTML file with Javascript only

I need to select a complete sentence in a HTML text in which I cannot write any placeholder.
When the user selects a piece of text, I want the selection to extend from point to point. The problem I'm facing is that if I extend my sentence over the "tag limit" I'm not able to get it with the standard methods.
This is what I'm trying to do:
Attach a selection changed method:
document.addEventListener('selectionchange', TextSelected);
Extend get my "range"
function ExpandSelection()
{
//get the next "." (end of the sentence)
var last = window.getSelection().focusOffset;
while(window.getSelection().focusNode.textContent.charAt(last) != '.')
{
last++;
}
//get the previous "." (beginning of the sentence" !!! NOT WORKING
var first = window.getSelection().anchorOffset;
while(window.getSelection().focusNode.textContent.charAt(first) != '.')
{
first--;
}
first++;
return { start: first, end : last }
}
My problem is, right now, that my selection is working in "moving forward" but when I try to move backward I'm not able to get the previous characters.
Does anybody has another way to do it?
Summarizing: if somebody is selecting something (also a single letter) I want the selection to expand to the complete sentence.

get the new added characters to an input by js

I know this seems a quite easy target. I have an input[type=text], and I want to detect the new added character(s) in it. The normal way is:
$selector.keypress(function(e) {
//do sth here
var newchar = String.fromCharCode(e.which);
});
But the above method not working properly for some browsers on android devices. Typing the android virtual keyboard will not fire the keypress.
Then I found the following method is better:
$selector.on('input', function(e){
//do sth here
});
It works fine for android devices, and also, it can detect cut/paste.
Now the question is, is there a way to know the new added character(s) to the input? Do I need to do the complicated string comparison during inputing each time, i.e. compare the previous string and the new string in the input box? I said it's complicated because you may not always type in char(s) at the end, you may insert some char(s) in the middle of the previous string. Think about this, the previous string in the input box is "abc", the new string after pasting is "abcxabc", how can we know the new pasted string is "abcx", or "xabc"?
The method from keypress is quite simple:
String.fromCharCode(e.which);
So, is there similar way to do this by the on('input') method?
After reading Yeldar Kurmangaliyev's answer, I dived into this issue for a while, and find this is really more complicated than my previous expectation. The key point here is that there's a way to get the cursor position by calling: selectionEnd.
As Yeldar Kurmangaliyev mentioned, his answer can't cover the situation:
it is not working is when you select text and paste another text with
replacing the original one.
Based on his answer, I modified the getInputedString function as following:
function getInputedString(prev, curr, selEnd) {
if (selEnd === 0) {
return "";
}
//note: substr(start,length) and substring(start,end) are different
var preLen = prev.length;
var curLen = curr.length;
var index = (preLen > selEnd) ? selEnd : preLen;
var subStrPrev;
var subStrCurr;
for(i=index; i > 0; i--){
subStrPrev = prev.substr(0, i);
subStrCurr = curr.substr(0, i);
if (subStrCurr === subStrPrev) {
var subInterval = selEnd - i;
var interval = curLen - preLen;
if (interval>subInterval) {
return curr.substring(i, selEnd+(interval-subInterval));
}
else{
return curr.substring(i, selEnd);
}
}
}
return curr.substring(0, selEnd);
}
The code is quite self explanation. The core idea is, no matter what character(s) were added(type or paste), the new content MUST be ended at the cursor position.
There's also one issue for my code, e.g. when the prev is abcabc|, you select them all, and paste abc, the return value from my code will be "". Actually, I think it's reasonable, because for my scenario, I think this is just the same with delete the abc from previous abcabc|.
Also, I changed the on('input') event to on('keyup'), the reason is, for some android browsers, the this.selectionEnd will not work in a same way, e.g., the previous text is abc|, now I paste de and the current string will be abcde|, but depending on different browsers, the this.selectionEnd inside on('input') may be 3, or 5. i.e. some browsers will report the cursor position before adding the input, some will report the cursor position after adding the input.
Eventually, I found on('keyup') worked in the same way for all the browsers I tested.
The whole demo is as following:
DEMO ON JSFIDDLE
Working on the cross-browser compatibility is always difficult, especially when you need to consider the touch screen ones. Hope this can help someone, and have fun.
Important notes:
when a user types in a character, the cursor stands after it
when a user pastes the text, the cursor is also located after the pasted text
Assuming this, we can try to suggest the inputed \ pasted string.
For example, when we have a string abc and it becomes abcx|abc (| is a cursor) - we know that actually he pasted "abcx", but not "xabc".
How do this algorithmically? Lets assume that we have the previous input abc and the current input: abcx|abc (cursor is after x).
The new one is of length 7, while the previous one is of length 4. It means that a user inputed 4 characters. Just return these four characters :)
The only case when it is not working is when you select text and paste another text with replacing the original one. I am sure you will come up with a solution for it yoruself :)
Here is the working snippet:
function getInputedString(prev, curr, selEnd) {
if (prev.length > curr.length) {
console.log("User has removed \ cut character(s)");
return "";
}
var lengthOfPasted = curr.length - prev.length;
if (curr.substr(0, selEnd - lengthOfPasted) + curr.substr(selEnd) === prev)
{
return curr.substr(selEnd - lengthOfPasted, lengthOfPasted);
} else {
console.log("The user has replaced a selection :(");
return "n\\a";
}
}
var prevText = "";
$("input").on('input', function() {
var lastInput = getInputedString(prevText, this.value, this.selectionEnd);
prevText = this.value;
$("#result").text("Last input: " + lastInput);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input type="text" />
<div id="result">Start inputing...</div>

jquery masked input plugin to not clear field when errored

I'm looking at the http://digitalbush.com/projects/masked-input-plugin/
I'm calling it like this:
$(control).mask('999-999-9999');
And I don't want it to throw away the users input if something is wrong, e.g. they haven't finished
[407-555-____]
If you leave the field after having typed this much, it clears it. I'd like to leave it so they can finish later.
I'm new to jQuery, and I've looked through his source, but I can't find any way to do that, nor can I find any way to edit it to accomplish what I want, because the code is arcane to my eyes.
Set autoclear option to false.
$(control).mask('999-999-9999', {autoclear: false});
It looks like I should just make the whole mask optional:
mask('?999-999-9999')
That way the control thinks what the user has is "valid" and I can continue. Even though it isn't really the optional part of the mask.
You should delete statement input.val(""); in checkVal() function for a proper solution.
If you're using minified version, you should search and delete statement:
if(!a&&c+1<i)f.val(""),t(0,k);else
Try update file jquery.maskedinput.js
In function function checkVal(allow) set parameter allow on true. Its help for me.
function checkVal(allow) {
allow = true; ///add this command
//..............
}
In addition to removing the input.val("") in checkVal() you can also change the call to clearBuffer.
In the original code it is: clearBuffer(0, len); removing all user input.
if you change this to clearBuffer(lastMatch + 1, len); the user input will be displayed, followed by the mask placeholders that are still needed to complete correct input.
I have also added a user message in the .bind. This works for us, as we are using the MaskedInput for exactly one type of input. I'm checking for any input going further than position 7, because that's where the user input starts.
Here is what I did:
.bind("blur.mask", function() {
// find out at which position the checkVal took place
var pos = checkVal();
// if there was no input, ignore
if (pos <=7) {input.val(""); clearBuffer(0, len);}
// if the user started to input something, which is not complete, issue an alert
if (pos > 7 && pos < partialPosition) alert("Tell the user what he needs to do.");
if (input.val() != focusText)
input.change();
})
Adding Placeholder could solve the problem.
$(control).mask('999-999-9999');
Add an empty place holder into mask. see below
$(control).mask('999-999-9999', { placeholder: "" });
which would replace _ on the input text field by default. so there would bot be any _ left if the input length is dynamic and not fixed.
Looking for into the pluging script the unmask method.
$('#checkbox').unmask();

Size limit to javascript [node].nodeValue field?

I'm receiving XML data via an AJAX call. One of the tags has a large amount of text, roughly 4000-5000 characters. In Firefox, the field is being truncated around the 3000th character. Most everything I've found online says there is no limit to node value sizes, but sometime it's implementation dependent - no solid answers.
Does anyone have any suggestions for why this might be occurring, assuming there is no restriction on the size of the nodeValue? Any workarounds if so?
<test>
<foo>very long string...</foo>
</test>
value = testTag.getElementsByTagName("foo").item(0).firstChild.nodeValue;
value is truncated.
-If I print the xmlHttp.responseText, all of the data from is printed.
Check this. It says:
"Also important to note is that although the specifications say that no matter how much text exists between tags, it should all be in one text node, in practice this is not always the case. In Opera 7-9.2x and Mozilla/Netscape 6+, if the text is larger than a specific maximum size, it is split into multiple text nodes. These text nodes will be next to each other in the childNodes collection of the parent element."
#Kooilnc has it right, 4k limit on text nodes in Firefox.
You can work around it by doing this:
function getNodeText(xmlNode) {
if(!xmlNode) return '';
if(typeof(xmlNode.textContent) != "undefined") return xmlNode.textContent;
return xmlNode.firstChild.nodeValue;
}
text = getNodeText(document.getElementsByTagName("div").item(0));
alert(text.length);
See it in action here: http://jsfiddle.net/Bkemk/2/
Function borrowed from here: http://www.quirksmode.org/dom/tests/textnodesize.html
What I've come up with instead of targeting a single node:
function getDataOfImmediateChild(parentTag, subTagName)
{
var val = "";
var listOfChildTextNodes;
var directChildren = parentTag.childNodes;
for (m=0; m < directChildren.length; m++)
{
if (directChildren[m].nodeName == subTagName)
{
/* Found the tag, extract its text value */
listOfChildTextNodes = directChildren[m].childNodes;
for (n=0; n < listOfChildTextNodes.length; n++)
{
if (typeof listOfChildTextNodes[n] == "TextNode")
val += listOfChildTextNodes[n].nodeValue;
}
}
}
return val;
It might be worthwhile to also ensure the listOfChildTextNodes[n] element is a TextNode.
#Ryley
The only reason I do an iteration over the direct children is because getElementsByTagName and getElementsById will return nodes that are farther down the hierarchy. Better explained as an example:
<foo>
<bar>
<zoo>
<bar>-</bar>
</zoo>
<bar></bar>
</zoo>
If I say fooTag.getElementsByTagName("bar"), it's going to return an array of both s, even though I only want the second (since it's the only true child of ). The only way I can think of enforcing this "search only my direct children" is by iterating over the children.

Categories

Resources