19

I want to create a totally encapsulated sub-document in JavaScript, with its own <head>, <body>, styles, html, and js. Basically a shadow dom, or an iframe, but without an src attribute.

Though I love the idea of shadow dom, its support is very low, and thus is not ready for prime time.

So I have been working on creating an iframe, but have been hitting various road-blocks along the way. Here is a jsFiddle demonstrating my various attempts.

The iframe cannot exist in the dom. This part is critical. To clarify, it is okay if it momentarily exists in the dom, but it must be able to be extracted and exist only in JS.

$('body').append('<iframe id="iframeGenerator" />');
var iframe3 = $('#iframeGenerator');
var iframe3Contents = iframe3.contents();
$('#iframeGenerator').remove();
var head = iframe3.contents().find('head');

sweet, we have the head

console.log(head.length);

but what do the contents look like?

console.log(iframe3Contents.get(0));

It is a document, but not inside of an element so lets try to put it in the dom or inside of another element? both of the following attempts don't work because the selector has no context or something ...

$('body').append(iframe3Contents);
var generatedIframe = $('<iframe />').append(iframe3Contents);

I would love to be able to create the iframe / subdocuemnt without appending anything to the dom... but if I must, I would still like to be able to subsequently remove it from the dom and manage it further in js.

I have this little function, which doesn't work, but illustrates the kind of iframe or subdocument generator I would like to create

var iframeHtml;
giveMeIframe = function() {
  var iframeContents;
  if (iframeHtml) {
    return iframeHtml;
  } else {
    $('body').append('<iframe id="iframeGenerator" style="display: none" />');
    iframeContents = $('#iframeGenerator').contents();
    iframeHtml = $('<iframe />');
    iframeHtml.append(iframeContents);
    $('#iframeGenerator').remove();
    return iframeHtml;
  }
}
ROMANIA_engineer
  • 54,432
  • 29
  • 203
  • 199
dezman
  • 18,087
  • 10
  • 53
  • 91
  • 1
    Trying to append a whole document to `body` makes no sense (obviously, I think). And what’s your end goal with this whole process? – CBroe Feb 14 '14 at 21:06
  • @CBroe I want to be able to encapsulate parts of a page, and have these encapsulated parts be generated in JS. You are right that trying to append a document to `body` is contrived, but appending it to an iframe, which is what the browser somehow automatically does when you add an iframe without an src to the dom makes a little more sense I hope. – dezman Feb 14 '14 at 21:07
  • 1
    If you want _parts_ of a page, then why do you need a `head` and `body` element in there? A `DocumentFragment` doesn’t work for what you’re trying to do …? – CBroe Feb 14 '14 at 21:09
  • @CBroe document fragments can't encapsulate css, which is something i need. – dezman Feb 14 '14 at 21:12
  • @Watson, could you perhaps feed all of the encapsulated sub-docs DOM info to html2canvas and then display the generated output in js on your page as the sub-doc? – Chris Feb 14 '14 at 21:16
  • I think DocumentFragment should support _scoped_ styles … if the browser understands that part of the HTML5 spec … – CBroe Feb 14 '14 at 21:18
  • @Chris that is a good idea, but as the description says "...may not be 100% accurate to the real representation..." it wont be a good option for me. Plus there may be a lot of interaction in these encapsulated sections, so i need them to be legit html documents. – dezman Feb 14 '14 at 21:21
  • @watson, if there needs to be interaction and cannot be an iframe what about embedding the sub-doc in a fixed lightbox or popup? If you voided the controls and set it to launch on ready, the the popup would look like just part of the main page but contain the sub-doc with real DOM everything including ajax if necessary? – Chris Feb 14 '14 at 21:32
  • @Chris I didn't say it can't be an iframe... iframes are good. Lightbox and popups are not really relevant... but thanks – dezman Feb 14 '14 at 21:34
  • @Watson - What is your end goal? Lets say you get the sub document, what next? – ShankarSangoli Feb 21 '14 at 01:38
  • If I understand your question correctly then it can't be done. You cannot create an element within a page that's accessible through Javascript, but isn't through, say, the native dom inspector. – Etheryte Feb 21 '14 at 19:16
  • @ShankarSangoli The goal is to generate content, including iframes with css, js, and html, which will be inserted into the DOM at a later time, and importantly, nothing will have to be done to get the iframes working after the content has been inserted into the DOM. – dezman Feb 21 '14 at 19:26
  • @Nit What about `var element = $('
    ');` ?
    – dezman Feb 21 '14 at 19:27

2 Answers2

4

To access info from the frame (or write to the frame), it must be in the DOM. It can be hidden, but it still must live in the frames object. JQuery is accessing the iFrame through the frames object and when removed from the dom, it's removed from the frames object

For future reference to anyone stumbling across this question, you can get the encapsulation like so:

$('#iframeGenerator2').contents().find('html').html(frame2HTML);

Here's an example: http://jsfiddle.net/yP34y/4/

In the jsfiddle example, notice everything only works after it's been added to the DOM.

Paul Way
  • 1,966
  • 1
  • 13
  • 10
  • Thanks, I am aware of a variety of work arounds -- but are you saying that what I want to do is impossible? If so, I would prefer an answer that explains why it is impossible rather than a work around. Also it is hard to read the code in your fiddle because it is mixed up with my code and there are like 3 or 4 iframes moving around, could you possible remove the code unrelated to your answer from the fiddle? – dezman Feb 18 '14 at 18:12
  • Maybe I'm missing something, but what you are attempting isn't impossible at all. Here's a cleaner version: http://jsfiddle.net/yP34y/4/ – Paul Way Feb 18 '14 at 19:14
  • I think you are missing the part about "The iframe cannot exist in the dom. To clarify, it is okay if it momentarily exists in the dom, but it must be able to be extracted and exist only in JS." http://jsfiddle.net/yP34y/5/ – dezman Feb 18 '14 at 20:06
  • To access info from the frame (or write to the frame), it must be in the DOM. I was thinking you'd create it, add it, manipulate it, and then destroy it each time (i.e. momentarily). JQuery is really accessing the iFrame through the frames object and when removed from the dom, it's removed from the frames object. – Paul Way Feb 18 '14 at 20:49
  • Sounds good, alas, that may be the case. Would you mind changing your answer to reflect what you said in your last comment about it not being possible? I am not really interested in perusing work-arounds in this thread, I am simply interested in whether or not we can have a dom-like encapsulated iframe or sub-document including a head and a body in JS, without it existing in the dom. – dezman Feb 18 '14 at 21:15
  • Thanks, you are right it seems :) It would be cool to know a bit more about why we cant create an iframe with contents before it gets into the DOM, if possible. – dezman Feb 21 '14 at 21:27
0

I played around with your fiddle and was able to get it working. I'm using seamless (only Chrome) to make it behave more in line with what you're looking for and I have a CSS fallback for other browsers.

As a note, the iframe needs to be added to the DOM before you start editing it's contents(adding styles and body). You can remove afterwards document.body.removeChild(iframe);.

There is still a lot you can do to make it behave very similarly to a shadow DOM element, this presentation will help you out Seamless iframes: The future, today!

JS

var styles = '<style> .pink { color: pink; width: 100px; height: 100px; } body{background-color:#eee;}</style>';

var html = '<div class="pink">PINK!</div>';

// create iframe
var iframe = document.createElement('iframe');
document.body.appendChild(iframe);

//add head and body
iframe.contentDocument.open();
iframe.contentDocument.write(styles);
iframe.contentDocument.write(html);
iframe.contentDocument.close();
iframe.setAttribute('seamless', 'seamless');

//check everything
console.log(iframe);
var head = $(iframe).contents().find('head')[0];
console.log(head);
var body = $(iframe).contents().find('body')[0];
console.log(body);

//remove from DOM
//document.body.removeChild(iframe);

CSS

iframe[seamless]{
    background-color: transparent;
    border: 0px none transparent;
    padding: 0px;
    overflow: hidden;
}
Valentin
  • 2,772
  • 1
  • 27
  • 40
  • As you say, the iframe needs to be added to the DOM before you start editing it's contents. – dezman Feb 21 '14 at 19:54
  • @watson I don't believe there's a way around that and I think it's because of how iframe was intended to be used. It's meant to load content from other pages and not be a wrapper for custom generated html. But if you add it and then remove it like I did(uncomment the last line) it won't show up in the page. – Valentin Feb 21 '14 at 20:01
  • Also, you have to understand that until shadow DOM gets proper support there's no other way to achieve what you're looking for without using iframes. Any content that you load any other way will be added to the DOM and will inherit the CSS rules from it's parent page, there's no way to encapsulate that, it's how the DOM works. – Valentin Feb 21 '14 at 20:05