@qooxdoo/framework
Version:
The JS Framework for Coders
437 lines (360 loc) • 11.5 kB
JavaScript
/* ************************************************************************
qooxdoo - the new era of web development
http://qooxdoo.org
Copyright:
2004-2008 1&1 Internet AG, Germany, http://www.1und1.de
License:
MIT: https://opensource.org/licenses/MIT
See the LICENSE file in the project's top-level directory for details.
Authors:
* Sebastian Werner (wpbasti)
* Andreas Ecker (ecker)
* Til Schneider (til132)
* Jonathan Weiß (jonathan_rass)
************************************************************************ */
/**
* Container widget for internal frames (iframes).
* An iframe can display any HTML page inside the widget.
*
* *Example*
*
* Here is a little example of how to use the widget.
*
* <pre class='javascript'>
* var document = this.getRoot();
* var iframe = new qx.ui.embed.Iframe("http://www.qooxdoo.org");
* document.add(iframe);
* </pre>
*
*
* *External Documentation*
*
* <a href='http://manual.qooxdoo.org/${qxversion}/pages/widget/iframe.html' target='_blank'>
* Documentation of this widget in the qooxdoo manual.</a>
*
*
* *Notes*
* When modifying this file, note that the test qx.test.ui.embed.Iframe.testSyncSourceAfterDOMMove
* has been disabled under Chrome because of problems with Travis and Github. Changes to this file
* should be tested manually against that test.
*/
qx.Class.define("qx.ui.embed.Iframe",
{
extend : qx.ui.embed.AbstractIframe,
/*
*****************************************************************************
CONSTRUCTOR
*****************************************************************************
*/
/**
* @ignore(MutationObserver)
* @param source {String} URL which should initially set.
*/
construct : function(source)
{
if (source != null) {
this.__source = source;
}
this.base(arguments, source);
qx.event.Registration.addListener(document.body, "pointerdown", this.block, this, true);
qx.event.Registration.addListener(document.body, "pointerup", this.release, this, true);
qx.event.Registration.addListener(document.body, "losecapture", this.release, this, true);
this.__blockerElement = this._createBlockerElement();
if ( qx.core.Environment.get("ecmascript.mutationobserver") )
{
this.addListenerOnce("appear", function ()
{
var element = this.getContentElement().getDomElement();
// Mutation record check callback
var isDOMNodeInserted = function (mutationRecord)
{
var i;
// 'our' iframe was either added...
if (mutationRecord.addedNodes)
{
for (i = mutationRecord.addedNodes.length; i>=0; --i) {
if (mutationRecord.addedNodes[i] == element) {
return true;
}
}
}
// ...or removed
if (mutationRecord.removedNodes)
{
for (i = mutationRecord.removedNodes.length; i>=0; --i) {
if (mutationRecord.removedNodes[i] == element) {
return true;
}
}
}
return false;
};
var observer = new MutationObserver(function (mutationRecords)
{
if (mutationRecords.some(isDOMNodeInserted)) {
this._syncSourceAfterDOMMove();
}
}.bind(this));
// Observe parent element
var parent = this.getLayoutParent().getContentElement().getDomElement();
observer.observe(parent, { childList: true });
}, this);
}
else // !qx.core.Environment.get("ecmascript.mutationobserver")
{
this.addListenerOnce("appear", function ()
{
var element = this.getContentElement().getDomElement();
qx.bom.Event.addNativeListener(element, "DOMNodeInserted", this._onDOMNodeInserted);
}, this);
this._onDOMNodeInserted = qx.lang.Function.listener(this._syncSourceAfterDOMMove, this);
}
},
properties :
{
// overridden
appearance :
{
refine : true,
init : "iframe"
},
/**
* Whether to show the frame's native context menu.
*
* Note: This only works if the iframe source is served from the same domain
* as the main application.
*/
nativeContextMenu :
{
refine: true,
init : false
},
/**
* If the user presses F1 in IE by default the onhelp event is fired and
* IE’s help window is opened. Setting this property to <code>false</code>
* prevents this behavior.
*
* Note: This only works if the iframe source is served from the same domain
* as the main application.
*/
nativeHelp :
{
check : "Boolean",
init : false,
apply : "_applyNativeHelp"
},
/**
* Whether the widget should have scrollbars.
*/
scrollbar :
{
check : ["auto", "no", "yes"],
nullable : true,
themeable : true,
apply : "_applyScrollbar"
}
},
/*
*****************************************************************************
MEMBERS
*****************************************************************************
*/
members :
{
__source : null,
__blockerElement : null,
// overridden
renderLayout : function(left, top, width, height)
{
this.base(arguments, left, top, width, height);
var pixel = "px";
var insets = this.getInsets();
this.__blockerElement.setStyles({
"left": (left + insets.left) + pixel,
"top": (top + insets.top) + pixel,
"width": (width - insets.left - insets.right) + pixel,
"height": (height - insets.top - insets.bottom) + pixel
});
},
// overridden
_createContentElement : function()
{
var iframe = new qx.html.Iframe(this.__source);
iframe.addListener("load", this._onIframeLoad, this);
return iframe;
},
// overridden
_getIframeElement : function() {
return this.getContentElement();
},
/**
* Creates <div> element which is aligned over iframe node to avoid losing pointer events.
*
* @return {Object} Blocker element node
*/
_createBlockerElement : function()
{
var el = new qx.html.Blocker();
el.setStyles({
"zIndex": 20,
"display": "none"
});
return el;
},
/**
* Reacts on native load event and redirects it to the widget.
*
* @param e {qx.event.type.Event} Native load event
*/
_onIframeLoad : function(e)
{
this._applyNativeContextMenu(this.getNativeContextMenu(), null);
this._applyNativeHelp(this.getNativeHelp(), null);
this.fireNonBubblingEvent("load");
},
/*
---------------------------------------------------------------------------
METHODS
---------------------------------------------------------------------------
*/
/**
* Cover the iframe with a transparent blocker div element. This prevents
* pointer or key events to be handled by the iframe. To release the blocker
* use {@link #release}.
*
*/
block : function() {
this.__blockerElement.setStyle("display", "block");
},
/**
* Release the blocker set by {@link #block}.
*
*/
release : function() {
this.__blockerElement.setStyle("display", "none");
},
/*
---------------------------------------------------------------------------
EVENT HANDLER
---------------------------------------------------------------------------
*/
// property apply
_applyNativeContextMenu : function(value, old)
{
if (value !== false && old !== false) {
return;
}
var doc = this.getDocument();
if (!doc) {
return;
}
try {
var documentElement = doc.documentElement;
} catch(e) {
// this may fail due to security restrictions
return;
}
if (old === false)
{
qx.event.Registration.removeListener(
documentElement, "contextmenu",
this._onNativeContextMenu, this, true
);
}
if (value === false)
{
qx.event.Registration.addListener(
documentElement, "contextmenu",
this._onNativeContextMenu, this, true
);
}
},
/**
* Stops the <code>contextmenu</code> event from showing the native context menu
*
* @param e {qx.event.type.Mouse} The event object
*/
_onNativeContextMenu : function(e) {
e.preventDefault();
},
// property apply
_applyNativeHelp : function(value, old)
{
if (qx.core.Environment.get("event.help")) {
var document = this.getDocument();
if (!document) {
return;
}
try
{
if (old === false) {
qx.bom.Event.removeNativeListener(document, "help", (function() {return false;}));
}
if (value === false) {
qx.bom.Event.addNativeListener(document, "help", (function() {return false;}));
}
} catch (e) {
if (qx.core.Environment.get("qx.debug")) {
this.warn(
"Unable to set 'nativeHelp' property, possibly due to security restrictions"
);
}
}
}
},
/**
* Checks if the iframe element is out of sync. This can happen in Firefox
* if the iframe is moved around and the source is changed right after.
* The root cause is that Firefox is reloading the iframe when its position
* in DOM has changed.
*/
_syncSourceAfterDOMMove : function()
{
var iframeDomElement = this.getContentElement() && this.getContentElement().getDomElement();
if (!iframeDomElement) {
return;
}
var iframeSource = iframeDomElement.src;
// remove trailing "/"
if (iframeSource.charAt(iframeSource.length-1) == "/") {
iframeSource = iframeSource.substring(0, iframeSource.length-1);
}
if (iframeSource != this.getSource())
{
if ( qx.core.Environment.get("browser.name") != "edge" &&
qx.core.Environment.get("browser.name") != "ie" )
{
qx.bom.Iframe.getWindow(iframeDomElement).stop();
}
iframeDomElement.src = this.getSource();
}
},
// property apply
_applyScrollbar : function(value) {
this.getContentElement().setAttribute("scrolling", value);
},
// overridden
setLayoutParent : function(parent)
{
this.base(arguments, parent);
if (parent) {
this.getLayoutParent().getContentElement().add(this.__blockerElement);
}
}
},
/*
*****************************************************************************
DESTRUCTOR
*****************************************************************************
*/
destruct : function()
{
if (this.getLayoutParent() && this.__blockerElement.getParent()) {
this.getLayoutParent().getContentElement().remove(this.__blockerElement);
}
this._disposeObjects("__blockerElement");
qx.event.Registration.removeListener(document.body, "pointerdown", this.block, this, true);
qx.event.Registration.removeListener(document.body, "pointerup", this.release, this, true);
qx.event.Registration.removeListener(document.body, "losecapture", this.release, this, true);
}
});