Get text and set range from iframe - javascript

I must change the style of a part of text in an HTML document showed in iframe.
I have a json file in which there are start/end/id of text that should be modified.
My problem is that i can't set text range.
var r = document.createRange();
var node = document.getElementById("file").contentWindow.document.getElementById("d1e152");
r.setStart(node, 0);
r.setEnd(node, 12);
file is the id of the iframe, while d1e152 is the id of the paragraph.
It return "TypeError: Argument 1 of Range.setStart is not an object." error in r.setStart() line.

Sounds like node is null. This could be for one of many reasons, but I'd guess you're trying to retrieve the element within the iframe before the iframe has fully loaded, so you'll need to wait using window.setTimeout() or an event handler.
Another issue which is not your immediate problem is that it's a good idea to create the range on the iframe's document instead of the main document. According to the spec it shouldn't matter but I've definitely seen issues in older browsers. Can't remember specifics now.
var iframeDoc = document.getElementById("file").contentWindow.document;
var r = iframeDoc.createRange();
var node = iframeDoc.getElementById("d1e152");
r.setStart(node, 0);
r.setEnd(node, 12);

Related

Google Scripts - keep track of element [duplicate]

Update: This is a better way of asking the following question.
Is there an Id like attribute for an Element in a Document which I can use to reach that element at a later time. Let's say I inserted a paragraph to a document as follows:
var myParagraph = 'This should be highlighted when user clicks a button';
body.insertParagraph(0, myParagraph);
Then the user inserts another one at the beginning manually (i.e. by typing or pasting). Now the childIndex of my paragraph changes to 1 from 0. I want to reach that paragraph at a later time and highlight it. But because of the insertion, the childIndex is not valid anymore. There is no Id like attribute for Element interface or any type implementing that. CahceService and PropertiesService only accepts String data, so I can't store myParagraphas an Object.
Do you guys have any idea to achieve what I want?
Thanks,
Old version of the same question (Optional Read):
Imagine that user selects a word and presses the highlight button of my add-on. Then she does the same thing for several more words. Then she edits the document in a way that the start end end indexes of those highlighted words change.
At this point she presses the remove highlighting button. My add-on should disable highlighting on all previously selected words. The problem is that I don't want to scan the entire document and find any highlighted text. I just want direct access to those that previously selected.
Is there a way to do that? I tried caching selected elements. But when I get them back from the cache, I get TypeError: Cannot find function insertText in object Text. error. It seems like the type of the object or something changes in between cache.put() and cache.get().
var elements = selection.getSelectedElements();
for (var i = 0; i < elements.length; ++i) {
if (elements[i].isPartial()) {
Logger.log('partial');
var element = elements[i].getElement().asText();
var cache = CacheService.getDocumentCache();
cache.put('element', element);
var startIndex = elements[i].getStartOffset();
var endIndex = elements[i].getEndOffsetInclusive();
}
// ...
}
When I get back the element I get TypeError: Cannot find function insertText in object Text. error.
var cache = CacheService.getDocumentCache();
cache.get('text').insertText(0, ':)');
I hope I can clearly explained what I want to achieve.
One direct way is to add a bookmark, which is not dependent on subsequent document changes. It has a disadvantage: a bookmark is visible for everyone...
More interesting way is to add a named range with a unique name. Sample code is below:
function setNamedParagraph() {
var doc = DocumentApp.getActiveDocument();
// Suppose you want to remember namely the third paragraph (currently)
var par = doc.getBody().getParagraphs()[2];
Logger.log(par.getText());
var rng = doc.newRange().addElement(par);
doc.addNamedRange("My Unique Paragraph", rng);
}
function getParagraphByName() {
var doc = DocumentApp.getActiveDocument();
var rng = doc.getNamedRanges("My Unique Paragraph")[0];
if (rng) {
var par = rng.getRange().getRangeElements()[0].getElement().asParagraph();
Logger.log(par.getText());
} else {
Logger.log("Deleted!");
}
}
The first function "marks" the third paragraph as named range. The second one takes this paragraph by the range name despite subsequent document changes. Really here we need to consider the exception, when our "unique paragraph" was deleted.
Not sure if cache is the best approach. Cache is volatile, so it might happen that the cached value doesn't exist anymore. Probably PropertiesService is a better choice.

what's the difference between appending a child element and setting innerHTML

In the first example, the script was executed, but not in the second example, the Dom results are the same.
// executable
var c = 'alert("append a div in which there is a script element")';
var div = document.createElement('div');
var script_2 = document.createElement('script');
script_2.textContent = c;
div.appendChild(script_2);
document.body.appendChild(div);
// unexecutable although the dom result is same as above case
var d = '<script>alert("append a div that has script tag as innerHTML")';
var div_d = document.createElement('div');
div_d.innerHTML = d;
document.body.appendChild(div_d);
.innerHTML allows you to add as much HTML as you want in one easy call.
.appendChild allows you to add a single element (Or multiple elements if you append a DocumentFragment).
If you use .innerHTML then you need to include the opening and closing tags correctly. Your HTML must be proper.
When elements that were created using document.createElement then auto generate the appropriate opening and closing tags.
Your example for .innerHTML is not properly formed. Instead of:
var d = '<script>alert("append a div that has script tag as innerHTML")';
it should be:
var d = '<script>alert("append a div that has script tag as innerHTML")</script>';
UPDATE:
Interesting!!
I know that, in the past, your second example would have worked. But it seems that, probably for security reasons, the browser no longer allows you to insert <script> through .innerHTML.
I tried on Chrome 62 and it fails. Firefox 57 fails and Safari 11.0.2 fails.
My best guess is that this is a security update.
Look here:
https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML
And go down to the Security considerations section.
It reads:
It is not uncommon to see innerHTML used to insert text in a web page. This comes with a security risk.
const name = "John";
// assuming 'el' is an HTML DOM element
el.innerHTML = name; // harmless in this case
// ...
name = "<script>alert('I am John in an annoying alert!')</script>";
el.innerHTML = name; // harmless in this case
Although this may look like a cross-site scripting attack, the result is harmless. HTML5 specifies that a tag inserted via innerHTML should not execute.

Get the className of the tag within a Range object

I have two custom context menu (one was written by my, the other one was already there). Mine show up only when a word is underlined because he is poorly written and the other one show up on table so the user can choose to add another line or to delete a line from this table.
The trouble I got is that the one working on table is blocking mine to execute if there is an error in the table and you want to correct it.
For this contextMenu, they write it so they got a range object from the place where the right click is done.
document.oncontextmenu = elementSelect;
function elementSelect(){
Rng = document.selection.createRange();
}
I've tried to get the className of the tag inside the range object so if it match the class from my error tag (a span with class="error"), I do a simple return in the contextMenu for the table so it does not trigger.
var rngClassName = Rng(0).getAttribute("class").value;
alert("class =>" + rngClassName);
if(rngClassName == "error")
{
return;
}
The Rng(0) is used after several time to retrieve somme attribute and work fine for them. But It seems that it does not work to get the class value, no value is return, the script stop execution at line var rngClassName = Rng(0).getAttribute("class").value; and no error is returned from the debugging (F12 in IE).
It is my first time working with Range object so I think I am missing something but don't know what.
Spec : IE5, Vanilla Javascript.
Try using element.classList .
with following methods : classList.add() , classList.remove() , classList.contains()
edit: i see your comment now, and below won't work in IE5...
But if I understand your question correctly, you want to check the class of an element whitin the selection? I don't have IE5 so I checked it in 11 and Chrome. You could try something like this:
var selection = document.getSelection();
var selRange = selection.getRangeAt(0);
var parentNode = selRange.startContainer.parentNode;
var hasError = parentNode.classList.contains('error');
Demo: https://jsfiddle.net/spdwn7bo/1/ (select a text within 2 seconds :))

Javascript: match using dom loaded page

I am trying to grab all links from google search result using Chrome console.
First I wanted to get the dom loaded source. I tried below code.
var source = document.documentElement.innerHTML;
Now when I type source in console source it shows the correct dom loaded source. But if I run alert(source); It's showing default html source of page.
So problem is when I run below code
source.match(/class="r"><a href="(.*?)"/);
It is returning null, because variable source has the source code before dom loaded.
You can use DOM API (i.e. getElementsByTagName) to find all a tags in a page. Take a look:
var anchors = document.getElementsByTagName('A');
var matchingHrefs = Array.prototype.slice.call(anchors).filter(function(a) {
return a.className == 'r';
}).map(function(a) {
return a.href;
});
A
B
C
The Array.prototype.slice.call call turns node list into regular array.
Probably, you need to add /g flag to your regex to match globally.
Like this:
yourHtml.match(/href="([^"]*")/g)

Using document.implementation.createDocument to create a new HTML document

I am being passed html as a string. My goal is to create a new document from the html that has all the appropriate nodes so that I can do things like call doc.getElementsByTagName on the doc I create and have it work as expected. An example of my code is here.
var doc = window.document.implementation.createDocument
('http://www.w3.org/1999/xhtml', 'html', null);
doc.getElementsByTagName('html')[0].innerHTML =
'<head><script>somejs</script>' +
'<script>var x = 5; var y = 2; var foo = x + y;</script>' +
'</head><body></body>';
var scripts = doc.getElementsByTagName('script');
console.log(scripts[0] + " code = " + scripts[0].innerHTML);
I am having the following issues:
If something inside a script tag contains a character like < (eg in the example above in the "var foo = x + y;" statement change the + to a < symbol), I get an INVALID_STATE_ERR: DOM Exception 11.
Even if nothing inside the script tag uses such characters, when I run the above I get the output "[object Element] code =undefined"
So my questions are:
A. How do I handle characters such as < that give DOM Exception 11 when I try to use them in whatever I am setting the innerHTML to
B. How do I make the document properly parse the script tags and put their code into their innerHTML attribute so that I can later read it.
EDIT: As Ryan P pointed out this code actually works in FF. So if anyone could help me get it working in chrome that would be much appreciated!
Taken from https://github.com/rails/turbolinks,
why dont you try to create the document this way:
doc = document.implementation.createHTMLDocument("");
doc.open("replace");
doc.write(html);
doc.close();
where the html should be your html contents.
I havent tested it and dont know if you should escape characters first.
A. You need to convert any < to an HTML entity (<). The rules don't cease to apply just cause you're in a script tag.
B. You call your variable 'doc' but try to get the script tags from an undefined variable 'tempDoc'. When I run your code in my browser after changing that variable, it all seems to work fine.

Categories

Resources