Range Selection and Mozilla - javascript

I would like to specify that firefox select a range. I can do this easily with IE, using range.select();. It appears that FFX expects a dom element instead. Am I mistaken, or is there a better way to go about this?
I start by getting the text selection, converting it to a range (I think?) and saving the text selection. This is where I'm getting the range from initially:
// Before modifying selection, save it
var userSelection,selectedText = '';
if(window.getSelection){
userSelection=window.getSelection();
}
else if(document.selection){
userSelection=document.selection.createRange();
}
selectedText=userSelection;
if(userSelection.text){
selectedText=userSelection.text;
}
if(/msie|MSIE/.test(navigator.userAgent) == false){
selectedText=selectedText.toString();
}
origRange = userSelection;
I later change the selection (successfully). I do so by range in IE and by a dom ID in ffx. But after I do that, I want to set back the selection to the original selection.
This works like a charm in IE:
setTimeout(function(){
origRange.select();
},1000);
I would like to do something like this in FFX:
var s = w.getSelection();
setTimeout(function(){
s.removeAllRanges();
s.addRange(origRange);
},1000);
Unfortunately, FFX has not been cooperative and this doesn't work. Any ideas?

The short answer is: IE and other browsers differ in their implementations of selecting text using JavaScript (IE has its proprietary methods). Have a look at Selecting text with JavaScript.
Also, see setSelectionRange at MDC.
EDIT: After making a little test case, the problem becomes clear.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>addRange test</title>
<style>
#trigger { background: lightgreen }
</style>
</head>
<body>
<p id="test">This is some (rather short) text.</p>
<span id="trigger">Trigger testCase().</span>
<script>
var origRange;
var reselectFunc = function () {
var savedRange = origRange;
savedRange.removeAllRanges();
savedRange.addRange(origRange);
};
var testCase = function () {
// Before modifying selection, save it
var userSelection,selectedText = '';
if(window.getSelection){
userSelection=window.getSelection();
}
else if(document.selection){
userSelection=document.selection.createRange();
}
selectedText=userSelection;
if(userSelection.text){
selectedText=userSelection.text;
}
if(/msie|MSIE/.test(navigator.userAgent) === false){
/* you shouldn't do this kind of browser sniffing,
users of Opera and WebKit based browsers
can easily spoof the UA string */
selectedText=selectedText.toString();
}
origRange = userSelection;
window.setTimeout(reselectFunc, 1000);
};
window.onload = function () {
var el = document.getElementById("trigger");
el.onmouseover = testCase;
};
</script>
</body>
</html>
When testing this in Firefox, Chromium and Opera, the debugging tools show that after invoking removeAllRanges in reselectFunc, both savedRange and origRange are reset. Invoking addRange with such an object causes an exception to be thrown in Firefox:
uncaught exception: [Exception...
"Could not convert JavaScript argument
arg 0 [nsISelection.addRange]"
nsresult: "0x80570009
(NS_ERROR_XPC_BAD_CONVERT_JS)"
location: "JS frame ::
file:///home/mk/tests/addrange.html ::
anonymous :: line 19" data: no]
No need to say that in all three browsers no text is selected.
Apparently this in intended behaviour. All variables assigned a (DOM)Selection object are reset after calling removeAllRanges.

Thank you Marcel. You're right, the trick is to clone the range, then remove the specific original range. This way we can revert to the cloned range. Your help led me to the below code, which switches the selection to elsewhere, and then back according to a timeout.
I couldn't have done it without you, and grant you the correct answer for it :D
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>addRange test</title>
<style>
#trigger { background: lightgreen }
</style>
</head>
<body>
<p id="switch">Switch to this text</p>
<p id="test">This is some (rather short) text.</p>
<span id="trigger">Trigger testCase().</span>
<script>
var origRange;
var s = window.getSelection();
var reselectFunc = function () {
s.removeAllRanges();
s.addRange(origRange);
};
var testCase = function () {
// Before modifying selection, save it
var userSelection,selectedText = '';
if(window.getSelection){
userSelection=window.getSelection();
}
else if(document.selection){
userSelection=document.selection.createRange();
}
selectedText=userSelection;
if(userSelection.text){
selectedText=userSelection.text;
}
if(/msie|MSIE/.test(navigator.userAgent) === false){
/* you shouldn't do this kind of browser sniffing,
users of Opera and WebKit based browsers
can easily spoof the UA string */
selectedText=selectedText.toString();
}
origRange = userSelection;
var range = s.getRangeAt(0);
origRange = range.cloneRange();
var sasDom = document.getElementById("switch");
s.removeRange(range);
range.selectNode(sasDom);
s.addRange(range);
window.setTimeout(reselectFunc, 1000);
};
window.onload = function () {
var el = document.getElementById("trigger");
el.onmouseover = testCase;
};
</script>
</body>
</html>

Related

Fetching word count in a web page

This must have been a very generic question but I have not come across any concrete or stable solution for this.
I just want to fetch the number of words in a web page but across all the browsers. My current implementation is
var body = top.document.body;
if(body) {
var content = body.innerText || body.textContent;
content = content.replace(/\n/ig,' ');
content = content.replace(/\s+/gi,' ');
content = content.replace(/(^\s|\s$)/gi,'');
if(!body.innerText) {
content = content.replace(/<script/gi,'');
}
console.log(content);
console.log(content.split(' ').length);
}
This works well but it does not work with some Firefox browsers as innerText does not work on Firefox.
If I use textContent then it displays the contents of JS tags too if present. Eg if a web page content is
<body>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script type="text/javascript">
console.log('Hellow World');
var some = "some";
var two = "two";
var three = "three";
</script>
<h1 style="text-align:center">Static content from Nginx</h1>
<div>
This is a
static.
<div>
This is a
static.
</div>
</div>
</body>
Then textContent will have JS code too in the content which will give me wrong word count.
What is the concrete solution that can work across any environment.
PS: No JQuery
Ok, you have there two problems:
Cross-browser innerText
I'd go with:
var text = document.body[('innerText' in document.body) ? 'innerText' : 'textContent'];
That, to prefer innerText over textContent.
Stripping result of <script> tags.
dandavis offers a neat solution to that:
function noscript(strCode){
var html = $(strCode.bold());
html.find('script').remove();
return html.html();
}
And a non-jQuery solution:
function noscript(strCode){
return strCode.replace(/<script.*?>.*?<\/script>/igm, '')
}
A function that will turn the string into a "fake" html document, strip its script tags and return the raw result.
Of course, you may improve the function to remove also <style> tags and others.
Counting letters
Your method to do the job is alright, but still, I think that a simple regex would do the job much better. You can count the words in a string using:
str.match(/\S+/g).length;
Finally
Final result should look like
var body = top.document.body;
if(body) {
var content = document.body[('innerText' in document.body) ? 'innerText' : 'textContent'];
content = noscript(content);
alert(content.match(/\S+/g).length);
}
What about hidden/invisible/overlayed blocks? do you want to count words inside all of it? what about images (alt tag of image)
if you want to count all - just strip tags and count test of all rest blocks. smth like that $('body :not(script)').text()
Thank you so much for giving such a helpful answers. I found this approach to use if the innerText is not defined in a browser. And the result that we get is very much similar to innerText. Hence I think it will be consistent across all the browsers.
All of you please look into it and let me know if this answer can be accepted. And let me know if you guys find any discrepancy in this method I am using.
function getWordCount() {
try {
var body = top.document.querySelector("body");
if (body) {
var content = body.innerText || getInnerText(top.document.body, top);
content = content.replace(/\n/ig, ' ');
var wordCount = content.match(/\S+/gi).length;
return wordCount;
}
} catch (e) {
processError("getWordCount", e);
}
}
function getInnerText(el, win) {
try {
win = win || window;
var doc = win.document,
sel, range, prevRange, selString;
if (win.getSelection && doc.createRange) {
sel = win.getSelection();
if (sel.rangeCount) {
prevRange = sel.getRangeAt(0);
}
range = doc.createRange();
range.selectNodeContents(el);
sel.removeAllRanges();
sel.addRange(range);
selString = sel.toString();
sel.removeAllRanges();
prevRange && sel.addRange(prevRange);
} else if (doc.body.createTextRange) {
range = doc.body.createTextRange();
range.moveToElementText(el);
range.select();
}
return selString;
} catch (e) {
processError('getInnerText', e);
}
}
The result that I am getting is same as that of innerText and is more accurate than using regex, or removing tags etc.
Please give me ur views on this.

Fire an event before `cut` event in ace editor

I want to fire an event before cut, so that I can get what text is being cut i.e. what has been already selected. I am currently using the following code, which doesn't seem to work as desired.
$("#editor").bind({
cut:function(){
console.log('Cut Detected');
alert(editor.selection.getRange());
}
});
editor is the id of the "div" tag which is editable. editor.selection.getRange() returns the start and end of selection.
edit I am woring with content editable div and want to apply the functionality on it.
<!DOCTYPE html>
<html lang="en">
<head>
<title>Editor</title>
</head>
<body>
<div id='myTa' contenteditable>hello world where are you</div>
<script type='text/javascript' src='jquery-2.1.4.min.js'></script>
<script type='text/javascript'>
$("#myTa").on("cut", function(){
alert(this.selectionStart+ " to " + this.selectionEnd);
})
</script>
</body>
</html>
You are correct that you need to use the cut event. The ClipboardEvent API is apparently unstable but yes, I would have thought it would include the text being moved onto the clipboard.
The following works for me:
$("textarea").on("cut", function(){
alert(this.value.substring(this.selectionStart, this.selectionEnd));
})
It's worth noting that bind is deprecated in jQuery, you should use on instead. Try out the snippet:
$("textarea").on("cut", function() {
alert(this.value.substring(this.selectionStart, this.selectionEnd));
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<textarea></textarea>
I think at first that clipboardEvent will have the clipped text, but it seems not, so I try to find the selection related properties of input and found it.
And the reference is HTMLInputElement.
This codes work in presumption that your $("#editor") is either an input or textarea.
$("#editor").bind({
cut:function(e){
console.log('Cut Detected');
var $this = $(this);
var selectStart = this.selectionStart;
var selectionEnd = this.selectionEnd;
var clippedValue = $this.val().slice(selectStart, selectionEnd);
// Now you have the clipped value, do whatever you want with the
// value.
alert(clippedValue);
}
});
For works on contentediable, you can do this, which I just found info from Return HTML from a user-selected text and MDN
$("#myTa").on("cut", function(e){
// Seems diff bro
var selections = window.getSelection();
var currentSelection = selections.getRangeAt(0);
var start = currentSelection.startOffset;
var end = currentSelection.endOffset;
var selectedContents = currentSelection.toString();
// Do whatever you want.
console.log(start, end);
console.log(selectedContents);
alert(selectedContents);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<div id='myTa' contenteditable>ask;ndjkasn asdbasj aujs d sdib askjbnsaab asbh mjn a</div>
I have got the solution to my answer. apperently there is an editor.on('cut',function(e)) in ace editor I use
editor.on("cut", function(e){
console.log('Cut Detected');
console.log(editor.selection.getRange());
});

Print external page without open it

I want print a page without open it on all major browsers. (Safari, IE, firefox, Chrome and Opera)
I tried that but doesn't work on firefox (Error : Permission denied to access property 'print') :
<!DOCTYPE html>
<html>
<head>
<title>Test</title>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/>
<link rel="alternate" media="print" href="print.php">
<script type="text/javascript">
function impression() {
window.frames[0].focus();
window.frames[0].print();
}
</script>
</head>
<body>
<iframe height="0px" src="print.php" id="fileToPrint" style="visibility: hidden"></iframe>
Imprimer
</body>
</html>
This code works on Chrome.
I want one thing like that for all browsers to mention but I don't know how.
Is there another way to do that?
Create an iframe, hide it, and then call the proper print functions. The execCommand should work for all versions of IE.
Mind you: $.browser won't work for newer versions of jQuery and should be avoided. Use your preferred way of detecting features.
var ifr = createIframe();
ifr.hide();
if ($.browser.msie) {
ifr.contentWindow.document.execCommand('print', false, null);
} else {
ifr.contentWindow.focus();
ifr.contentWindow.print();
}
This was developed for IE, FF and Chrome. I have no idea how well this will work for Safari and Opera, but it might give you some ideas.
Edit: as adeneo correctly pointed out, $.browser is deprecated and should be avoided. I updated my statement. I'll leave my code untouched, as it still expresses the correct intent.
You can try this code, but it's Javascript ;
<script language="JavaScript">
var gAutoPrint = true; // Tells whether to automatically call the print function
function printSpecial()
{
if (document.getElementById != null)
{
var html = '<HTML>\n<HEAD>\n';
if (document.getElementsByTagName != null)
{
var headTags = document.getElementsByTagName("head");
if (headTags.length > 0)
html += headTags[0].innerHTML;
}
html += '\n</HE>\n<BODY>\n';
var printReadyElem = document.getElementById("printReady");
if (printReadyElem != null)
{
html += printReadyElem.innerHTML;
}
else
{
alert("Could not find the printReady function");
return;
}
html += '\n</BO>\n</HT>';
var printWin = window.open("","printSpecial");
printWin.document.open();
printWin.document.write(html);
printWin.document.close();
if (gAutoPrint)
printWin.print();
}
else
{
alert("The print ready feature is only available if you are using an browser. Please update your browswer.");
}
}
</script>

Input with html keyboard

I'm trying to create a html/javascript keyboard which will fill an input
The problem is that when the user selects in the middle of input and clicks any keyboard button the character will be added to the end of the input.
<input type="text" id="input"/>
<button onclick="document.getElementById('input').value+=this.innerHTML;document.getElementById('input').focus()">A</button>
<button onclick="document.getElementById('input').value+=this.innerHTML;document.getElementById('input').focus()">B</button>
<button onclick="document.getElementById('input').value+=this.innerHTML;document.getElementById('input').focus()">C</button>
Any solution
Jsfiddle here
Full example (missing some numbers etc..)
can point whereveryou want.. and it stays there.
Creates the keyboard dynamically,only one eventlistener
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>keyboard</title>
<style>
body>div{
clear:both;
overflow:auto;
border:2px solid grey;
}
body>div>div{
width:64px;line-height:64px;float:left;
border:1px solid grey;
text-align:center;
}
</style>
<script>
(function(W){
var D,K,I,pos=0;
function init(){
D=W.document;
I=document.createElement('input');
document.body.appendChild(I);
K=D.createElement('div');
K.id="k";
K.addEventListener('click',h,false);
var L='a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z'.split(','),
l=L.length;
for(var a=0;a<l;a++){
K.appendChild(document.createElement('div')).innerText=L[a];
}
document.body.appendChild(K);
}
function h(e){
if(e.target.parentNode.id=='k'){
pos=(I.selectionStart?I.selectionStart:pos?pos:0);
var end=I.selectionEnd?I.selectionEnd:pos;
I.value=I.value.substr(0,pos)+
e.target.innerText+
I.value.substr(end);
I.focus();
pos++
I.selectionStart=pos;
I.selectionEnd=pos;
}
}
W.addEventListener('load',init,false);
})(window)
</script>
</head>
<body>
</body>
</html>
ps.: I tested in Chrome.
EDIT
the only thing that doesnot work is if you select a text and write before deleting it it starts where te selection starts and leaves yor other selected letters where they are.
EDIT 2 everything you expect works
Without correcting any other bad JS practices in the snippet, the correect solution consists of the use of selectionStart.
document.getElementById('input').value =
document.getElementById('input').value.substr(
0, document.getElementById('input').selectionStart) +
this.innerHTML +
document.getElementById('input').value.substr(
document.getElementById('input').selectionStart);
document.getElementById('input').focus();
I updated your JSfiddle to have a working example.
Link
<html>
<head>
<title>Testing</title>
<script type="text/javascript">
var cursorLocation = 0;
function insertValue(buttonClicked) {
var input = document.getElementById('input');
input.value = input.value.substring(0, cursorLocation) + buttonClicked.innerHTML + input.value.substring(cursorLocation);
cursorLocation += 1;
}
// Script found here:
// http://stackoverflow.com/questions/2897155/get-cursor-position-within-an-text-input-field
function doGetCaretPosition(oField) {
// Initialize
var iCaretPos = 0;
// IE Support
if (document.selection) {
// Set focus on the element
oField.focus ();
// To get cursor position, get empty selection range
var oSel = document.selection.createRange ();
// Move selection start to 0 position
oSel.moveStart ('character', -oField.value.length);
// The caret position is selection length
iCaretPos = oSel.text.length;
}
// Firefox support
else if (oField.selectionStart || oField.selectionStart == '0')
iCaretPos = oField.selectionStart;
// Return results
cursorLocation = iCaretPos;
}
</script>
</head>
<body>
<input onblur="doGetCaretPosition(this)" type="text" id="input"/>
<button onclick="insertValue(this)">A</button>
<button onclick="insertValue(this)">B</button>
<button onclick="insertValue(this)">C</button>
</body>
</html>

cloneNode in internet explorer

While executing the following code IE throws the error -- Object doesn't support this property or method -- referring to the cloneNode() method. 'i' is the loop counter, source and dest are both HTML select elements.
dest.options[dest.options.length] = source.options[i].cloneNode( true );
FF and Chrome behave as expected. Any ideas on how to get IE to execute cloneNode()? The IE 8 debugger shows source.options[i] does have a cloneNode() method.
Thanks.
IE requires the
new Option()
construct.
document.createElement( 'option' );
or
cloneNode()
will fail. Of course, all options work as expected in a proper web browser.
Actually, cloneNode isn't throwing any error. Break your code down into smaller chunks to properly identify the source of the error:
var origOpt = source.options[i];
var clonedOpt = origOpt.cloneNode( true ); // no error here
var destOptLength = dest.options.length;
dest.options[destOptLength] = clonedOpt; // error!
dest.options.add(clonedOpt); // this errors too!
dest.appendChild(clonedOpt); // but this works!
Or, putting it back the way you had it, all on one line:
dest.appendChild(source.options[i].cloneNode( true ));
I've found this post useful: IE’s cloneNode doesn’t actually clone!
<!doctype html>
<html lang="en">
<head>
<meta charset= "utf-8">
<title>Untitled Document</title>
<style>
p, select,option{font-size:20px;max-width:640px}
</style>
<script>
function testSelect(n, where){
var pa= document.getElementsByName('testselect')[0];
if(!pa){
pa= document.createElement('select');
where.appendChild(pa);
pa.name= 'testselect';
pa.size= '1';
}
while(pa.options.length<n){
var i= pa.options.length;
var oi= document.createElement('option');
pa.appendChild(oi);
oi.value= 100*(i+1)+'';
oi.text= oi.value;
}
pa.selectedIndex= 0;
pa.onchange= function(e){
e= window.event? event.srcElement: e.target;
var val= e.options[e.selectedIndex];
alert(val.text);
}
return pa;
}
window.onload= function(){
var pa= testSelect(10, document.getElementsByTagName('h2')[0]);
var ox= pa.options[0];
pa.appendChild(ox.cloneNode(true))
}
</script>
</head>
<body>
<h2>Dynamic Select:</h2>
<p>You need to insert the select into the document,
and the option into the select,
before IE grants the options any attributes.
This bit creates a select element and 10 options,
and then clones and appends the first option to the end.
<br>It works in most browsers.
</p>
</body>
</html>

Categories

Resources