I need to find out if html document inside iframe contains some occurance of word wysiwyg (purpose: to check if it is wysiwyg editor).
What I have tried:
iframes = $('iframe').filter(
function () {
var result = this.id.match(/wysiwyg/i);
if (!result)
result = this.className.match(/wysiwyg/i);
if (!result)
{
var success = this.innerHTML.match(/wysiwyg/i);
if ( success && success.length )
return this;
}
return result;
});
using JQuery.
The problem here is that innerHTML is empty. I thought, contentDocument could contain innerhtml, but this is not the case. I try to do case insensitive search, where the word wysiwyg can be in the middle of any element. Originally I tried to find a tag with href value or img tag with src value but I found that is too much complicated and the word could be used in other parts of the html document and I would miss it. I don't know where the word could be, it can be anywhere in the iframe -> html document.
Your suggestion?
Note:
Permissions here are not problem, they are granted by Firefox webextentions API - which is not subject of the question.
If permissions are not the problem, you should be able to access the iframe HTML by doing the following:
$('#specificIframe').contents().find('#thingToFind');
jQuery .contents()
You may use .contents() and jQuery( ":contains(text)" ) plus the load event to check if the iframe contains the string.
In order to test if the id contains the string you may refer to attributeContains selector.
$(function () {
$('iframe[id*="wysiwyg"]').on('load', function (e) {
var iframes = $(this).contents().find(':contains("wysiwyg")');
});
});
As guest271314 has mentioned, you are not currently using .contentDocument in your code.
You could change your code as follows:
iframes = $('iframe').filter(function() {
var result = this.id.match(/wysiwyg/i);
if (!result){
result = this.className.match(/wysiwyg/i);
}
if (!result) {
var success = this.contentDocument.querySelector('html').innerHTML.match(/wysiwyg/i);
if ( success && success.length ){
return this;
}
}
return result;
});
From MDN's <iframe> page:
From the DOM iframe element, scripts can get access to the window object of the included HTML page via the contentWindow property. The contentDocument property refers to the document element inside the iframe (this is equivalent to contentWindow.document), but is not supported by Internet Explorer versions before IE8.
However, if this is the same issue you were asking about in a prior, now deleted, question, this will, not solve your actual problem. The actual problem appeared to be that your (nearly identical) code was executing prior to the <iframe> you are looking for being inserted into the DOM by other JavaScript on the page. Your code, of course, can not find it when it is not, yet, in the DOM. Your code in that question was verified to find the <iframe> desired if it existed in the DOM in the state that the DOM was once the page scripts finished setting up the DOM. Prior to that code, ckeditor_new/ckeditor.js, executing, what exists on the page is:
<script src="ckeditor_new/ckeditor.js"></script>
<textarea id="FCKeditor1" name="FCKeditor1" rows="8" cols="60"></textarea>
<script>CKEDITOR.replace( 'FCKeditor1', {customConfig: '/ckeditor_new/config.js'});</script>
The page script hides that <textarea> and inserts a <div> containing the <iframe> in which you are interested (about 15kB of inserted HTML).
You will need to delay looking for the existence of that <iframe> until after that other JavaScript inserts it into the DOM.
While there may be better ways to perform this delay (e.g. watching for the insert, etc.), it could be something as simple as:
setTimeout(findIframeAndDoAllTasksNeedingIt,150);
If still not found, you could retry looking for the <iframe> a limited number of times after additional delays.
I thought this could be solution:
iframes = $('iframe').filter(
function () {
var result = this.id.match(/wysiwyg/i);
if (!result)
result = this.className.match(/wysiwyg/i);
if (!result)
{
if (!this.contentDocument)
return null;
var success = this.contentDocument.head.innerHTML.match(/wysiwyg|editor|editable/i);
if ( success && success.length )
return this;
success = this.contentDocument.body.innerHTML.match(/wysiwyg|editor|editable/i);
if ( success && success.length )
return this;
}
return result;
});
edit: bugfix
I tried improvement which enables to use this code for almost all WYSIWYG editors, except TINYMCE which is kind of strange behaviour. There are found some frames with different id than that one containing "mce". Maybe we will find solution later.
iframes = $('iframe').filter(
function () {
var result = this.id.match(/wysiwyg/i);
if (!result)
result = this.className.match(/editable|wysiwyg|editor/i);
if (!result)
result = this.id.match(/mce/i);
if (!result)
{
if (!this.contentDocument)
return null;
var success = this.contentDocument.head.innerHTML.match(/editable|wysiwyg|editor|tinymce/i);
if ( success && success.length )
return this;
success = this.contentDocument.body.innerHTML.match(/editable|wysiwyg|editor|tinymce/i);
if ( success && success.length )
return this;
}
return result;
});
I love this code (I use it in my program which adds css styles to WYSIWYG editors - quite usable).
Related
Solution
This was not the real issue. The issue was that one of the javascript didn't load.
I asked a new question about it here: Can't call functions from first included javascript file from the second included js file
My original question
When I view my application in a browser (before I build/compile it with Cordova), everything works fine. But after i build it with Cordova, $("#content").html("test"); doesn't work anymore on android 4.2.2. however, it does work on android 6.0.0. alert("test");.
First I though that jQuery isn't working... But then I tried this:
$("body").click(function() {
alert("test");
});
and it worked.
Any ideas why the .html() method is not working?
UPDATE
This is how the element with the id "content" looks like:
<div id="content">
</div>
I tried to add some content like this:
$('#content').html(`<span>test1</span>`);
On all android versions i use Google Chrome as my browser.
UPDATE #2
I tried the html() method inside and outside of
$(document).ready(function(){});
Start with:
Make sure the <div id="content"> is loaded to DOM before you 'set text' script executes, could utilize onload() to ensure. Probably checked this, but a heads up for others.
Next, I wonder if the issue is with the .html() method of JQuery. Documentation states "This method uses the browser's innerHTML property."
Check innerHTML() alone via:
document.getElementById("content").innerHTML = "test";
I know there are certain limitation when utilizing innerHTML() in vanilla JS, for instance issues with <script>, so if JQuery's .html() utilizes it, it may be an issue somehow.
If you can, try using vanilla JS to set the #content <div> via:
document.getElementById("content").textContent = "test";
This will allow you eliminate .html() and it's use of .innerHTML() truly isn't to blame.
Edit: Here is JQuery's .html() method, the true issue may lie with how it handles the setting. It attempts to use innerHTML(), if that fails somehow, it then defaults to append().
See below:
function (value) {
return access(this, function (value) {
var elem = this[0] || {},
i = 0,
l = this.length;
if (value === undefined && elem.nodeType === 1) {
return elem.innerHTML;
}
// See if we can take a shortcut and just use innerHTML
if (typeof value === "string" && !rnoInnerhtml.test(value) && !wrapMap[(rtagName.exec(value) || ["", ""])[1].toLowerCase()]) {
value = value.replace(rxhtmlTag, "<$1></$2>");
try {
for (; i < l; i++) {
elem = this[i] || {};
// Remove element nodes and prevent memory leaks
if (elem.nodeType === 1) {
jQuery.cleanData(getAll(elem, false));
elem.innerHTML = value;
}
}
elem = 0;
// If using innerHTML throws an exception, use the fallback method
} catch(e) {}
}
if (elem) {
this.empty().append(value);
}
},
null, value, arguments.length);
}
Furthermore, here is the source for the fallback default called, append(), if the innerHTML() in the html() method fails:
function () {
return this.domManip(arguments, function (elem) {
if (this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9) {
var target = manipulationTarget(this, elem);
target.appendChild(elem);
}
});
}
You can find the methods in JQuery by searching with Ctrl + F and searching when viewing the source... for instance html:, with the colon. See picture below:
Notice the search bar on the bottom.
I think the reason why your .html() is not working is because it might not be binded to an action/element whereas alert() in itself is a message box or way of conveying information to the user.
Let us see using example:
Alert():
$("body").click(function() {
alert("test");
});
html():
$("body").click(function(){
$("h1").html("test");
});
here as you can see clearly, button triggers an action of click which would then change the content of "h1" but alert does not requires any such biding to an element or so.......
Hope this is helpful :)
I have an element that I want to populate as a request's HTML streams in, instead of waiting for the complete response. This turned out to be incredibly difficult.
Things I've tried:
1. milestoneNode.insertAdjacentHTML('beforebegin', text)
Be lovely if this worked. Unfortunately, elements with quirky parsing wreck it — such as <p> and <table>. The resulting DOM can be charitably described as Dada.
2. Using a virtual DOM/DOM update management library
Google's incremental-dom seemed most promising, but its patch() operation always restarts from the beginning of the container node. Not sure how to "freeze" it in place.
This also has baggage from doing at least HTML tokenization in JavaScript, and some actual tree-building would have to happen, unless one serves well-formed XHTML5. (Nobody does.) Reimplementing a browser's HTML parser seems like a sign I've gone horribly wrong.
3. document.write()
I was desperate. Ironically, this ancient boogeyman has almost the behavior I need, sans the "throwing away the existing page" thing.
4. Appending to a string, then innerHTMLing it periodically
Defeats the point of streaming, since eventually the entire response gets held in memory. Also has repeated serialization overhead.
On the plus side, it actually works. But surely there's a better way?
Jake Archibald figured out a silly hack to get this behavior in browsers today. His example code says it better than I would:
// Create an iframe:
const iframe = document.createElement('iframe');
// Put it in the document (but hidden):
iframe.style.display = 'none';
document.body.appendChild(iframe);
// Wait for the iframe to be ready:
iframe.onload = () => {
// Ignore further load events:
iframe.onload = null;
// Write a dummy tag:
iframe.contentDocument.write('<streaming-element>');
// Get a reference to that element:
const streamingElement = iframe.contentDocument.querySelector('streaming-element');
// Pull it out of the iframe & into the parent document:
document.body.appendChild(streamingElement);
// Write some more content - this should be done async:
iframe.contentDocument.write('<p>Hello!</p>');
// Keep writing content like above, and then when we're done:
iframe.contentDocument.write('</streaming-element>');
iframe.contentDocument.close();
};
// Initialise the iframe
iframe.src = '';
Although <p>Hello!</p> is written to the iframe, it appears in the parent document! This is because the parser maintains a stack of open elements, which newly created elements are inserted into. It doesn't matter that we moved <streaming-element>, it just works.
You can use fetch(), process Response.body which is a ReadableStream; TextDecoder()
let decoder = new TextDecoder();
function handleResponse(result) {
element.innerHTML += decoder.decode(result.value);
return result
}
fetch("/path/to/resource/")
.then(response => response.body.getReader())
.then(reader => {
return reader.read().then(function process(result) {
if (result.done) {
console.log("stream done");
return reader.closed;
}
return reader.read().then(handleResponse).then(process)
})
.then(function() {
console.log("stream complete", element.innerHTML);
})
.catch(function(err) {
console.log(err)
})
});
Edit: New title.
What I'm looking for is a document.querySelector for elements inside an iframe.
I've done quite a bit of Googling for an answer and finally I'm stumped.
I'm trying to query inside an iframe. I'm building string selectors to be used in Selenium and usually I just inspect the element with Firebug, and use document.querySelectorAll("theStringIBuid");
But it doesn't work with elements inside iframes. I've tried all of the below to get an element "radiobutton1" inside the "page-iframe" iframe.
var elem1 = ".page-iframe";
console.log(elem1);
var elem2 = ".radiobutton1";
console.log(elem2);
document.querySelectorAll(elem1+elem2+"");
document.querySelectorAll('.page-iframe').contentWindow.document.body.querySelectorAll('.radiobutton1')
document.getElementById('.page-iframe').contentWindow.document.body.innerHTML;
[].forEach.call( document.querySelectorAll('.page-iframe'),
function fn(elem){
console.log(elem.contentWindow.document.body.querySelectorAll('.radiobutton1')); });
var contentWindow = document.getElementById('.page-iframe').contentWindow
var contentWindow = document.querySelectorAll('.page-iframe')
var contentWindow = document.querySelectorAll('.page-iframe')[0].contentWindow
Thanks-
simple es6 adapted from h3manth:
document.querySelectorAll('iframe').forEach( item =>
console.log(item.contentWindow.document.body.querySelectorAll('a'))
)
if the original page's url isn't at the same domain with the iframe content, the javascript will treat the iframe as a black box, meaning it will not see anything inside it.
You can do this:
document.querySelector("iframe").contentWindow.document.querySelector("button")
https://m.youtube.com/watch?v=-mNp3-UX9Qc
You can simply use
document.querySelector("iframe").contentDocument.body.querySelector("#btn")
First query selector is to select the iframe. Then we can access ifram dom using content document and use the 2nd query selector to select the element inside iframe.
Here's a snippet for diving into same-origin frames (ie-compatible ES5):
function findInFramesRec(selector, doc) {
var hit = doc.querySelector(selector);
if (hit) return hit;
var frames = Array.prototype.slice.call(doc.frames);
for(var i = 0; (i < frames.length) && !hit ; i++) {
try {
if (!frames[i] || !frames[i].document) continue;
hit = findInFramesRec(selector, frames[i].document);
} catch(e) {}
}
return hit;
}
This dives into both frameset frames and iframes alike. It may even survive (though not enter) cross origin frames.
I am making a live editor for my website. I have the CSS and HTML parts down, only issue is the JS part now. Here is a snippet of the code
var frame = $('#preview_content'),
contents = frame.contents(),
body = contents.find('body');
csstag = contents.find('head').append('<style></style>').children('style');
java = contents.find('head').append('<script><\/script>').children('script');//Issues here
$('.area_content_box').focus(function() {
var $this = $(this);
var check = $this.attr('id');
$this.keyup(function() {
if (check === "html_process"){
body.html($this.val());
} else if(check === "css_process") {
csstag.text($this.val());
} else if (check === "java_process"){
java.text( $this.val() );
}
});
});
Problem is it is not injecting script tags in the iframes body nor the head when ever I try this. I've read up on injecting and some issues containing the ending script tag. I need to figure out how to inject script tags, really want them in the head but if it is easier in the body that is fine.
jfriend00 - I will be focusing on making this vanilla, if you think I should honestly.
So any words of advice on how to go about making my editor work correctly with the injecting JS?
These two lines of code look like they could have problems:
csstag = contents.find('head').append('<style></style>').children('style');
java = contents.find('head').append('<script><\/script>').children('script');//Issues here
It seems like it would be much better to create the style tag and remember that DOM element.
var iframe = document.getElementById("preview_content");
var iframewindow = iframe.contentWindow || iframe.contentDocument.defaultView;
var doc = iframewindow.document;
var csstag = doc.createElement("style");
var scripttag = doc.createElement("script");
var head = doc.getElementsByTagName("head")[0];
head.appendChild.cssTag;
head.appendChild.scriptTag;
$('.area_content_box').focus(function() {
var $this = $(this);
var check = this.id;
$this.keyup(function() {
if (check === "html_process") {\
// I would expect this to work
doc.body.html($this.val());
} else if(check === "css_process") {
// I don't know if you can just replace CSS text like this
// or if you have to physically remove the previous style tag
// and then insert a new one
csstag.text($this.val());
} else if (check === "java_process"){
// this is unlikely to work
// you probably have to create and insert a new script tag each time
java.text( $this.val() );
}
});
});
This should work for the body HTML and it may work for the CSS text into the style tag.
I do not believe it will work for the javascript as you can't change a script by assigning text to the tag the way you are. Once a script has been parsed, it's in the javascript namespace.
There is no public API I'm aware of for removing previously defined and interpreted scripts. You can redefine global symbols with subsequent scripts, but not remove previous scripts or their effects.
If this were my code, I'd remove the previous style tag, create a new one, set the text on it before it was inserted in the DOM and then insert it in the DOM.
If you're not going to do it that way, then you'll have to test to see if this concept of setting .text() on an already inserted (and parsed) style tag works or not in all relevant browsers.
For the script tag, I'm pretty sure you'll have to create a new script tag and reinsert it, but there's no way to get rid of older code that has already been parsed other than redefining global symbols. If you really wanted to start fresh on the code, you'd have to create a new iframe object from scratch.
There are other issues with this too. You're installing a .keyup() event handler every time the .area_content_box gets focus which could easily end up with many of the event handlers installed, all getting called and all trying to do the same work.
I have several iframes on a page that display ads unicorns/bacon to users. Because its not possible to detect an iframe's domready event via the parent (please let me know if this isn't true) I have some initialization-code in each iframe like this:
<body data-controller="unicorn">
<!-- content -->
<script>
var $ = parent.jQuery;
if($ && $.frameReady){
$(document).ready(function(){
$.frameReady(document);
});
}
</script>
</body>
The parent document has code very similar to the following (about this technique via #Paul Irish):
var frames = {
// the following is irrelevant to my question but awesome.
"unicorn": function (document) {
var script = document.createElement("script"),
element = document.getElementsByTagName("script")[0];
script.src = "http://www.cornify.com/js/cornify.js";
script.onload = function () {
// defaultView is the DOMWindow.
document.defaultView.cornify_add();
$(document).click(document.defaultView.cornify_add);
script.parentNode.removeChild(script);
};
element.parentNode.appendChild(script, element);
},
"bacon" : function(document) { /** mmm, bacon **/ }
};
// relevant but boring...
$.frameReady = function(document){
var controller = $(document.body).data("controller");
controller && frames[controller] && frames[controller](document);
};
Here is an example in jsfiddle (you can edit it here). It works great (at least it does in Chrome dev).
Now what I would LIKE to do is get rid of the data-controller bit in the iframe and instead use the id (or data-* or whatever) of the actual iframe element that is in the parent document to initialize the code.
If I could query the DOM via DOMWindow it would look like this:
$.frameReady = function(document){
var iframe = $("body").find(document.defaultView),
controller = iframe.data("controller");
controller && frames[controller] && frames[controller](document);
};
Luckily, I only need this to run on webkit based browsers, Adobe Air 2.5 actually (but I'm testing in Chrome ATM).
Because S.O. answerers like it when a Question has a question here it is:
Is there any (efficient) ways to query the DOM via document or window in webkit-based browsers - including Adobe Air 2.5?
I have now found that one iframe containing the unicorn for you
console.log(
$("iframe").contents().filter( function(){
return this == document
}).length
);