@qooxdoo/framework
Version:
The JS Framework for Coders
652 lines (531 loc) • 18.1 kB
JavaScript
/* ************************************************************************
qooxdoo - the new era of web development
http://qooxdoo.org
Copyright:
2004-2008 1&1 Internet AG, Germany, http://www.1und1.de
2006 Derrell Lipman
2006 STZ-IDA, Germany, http://www.stz-ida.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)
* Derrell Lipman (derrell)
* Andreas Junghans (lucidcake)
************************************************************************ */
/* ************************************************************************
************************************************************************ */
/**
* Transports requests to a server using an IFRAME.
*
* This class should not be used directly by client programmers.
*
* NOTE: Instances of this class must be disposed of after use
*
* @asset(qx/static/blank.gif)
*/
qx.Class.define("qx.io.remote.transport.Iframe",
{
extend : qx.io.remote.transport.Abstract,
implement: [ qx.core.IDisposable ],
/*
*****************************************************************************
CONSTRUCTOR
*****************************************************************************
*/
construct : function()
{
this.base(arguments);
// Unique identifiers for iframe and form
var vUniqueId = (new Date).valueOf();
var vFrameName = "frame_" + vUniqueId;
var vFormName = "form_" + vUniqueId;
// This is to prevent the "mixed secure and insecure content" warning in IE with https
var vFrameSource;
if ((qx.core.Environment.get("engine.name") == "mshtml")) {
vFrameSource = "javascript:void(0)";
}
// Create a hidden iframe.
// The purpose of the iframe is to receive data coming back from the server (see below).
this.__frame = qx.bom.Iframe.create({id: vFrameName, name: vFrameName, src: vFrameSource});
qx.bom.element.Style.set(this.__frame, "display", "none");
// Create form element with textarea as conduit for request data.
// The target of the form is the hidden iframe, which means the response
// coming back from the server is written into the iframe.
this.__form = qx.dom.Element.create("form", {id: vFormName, name: vFormName, target: vFrameName});
qx.bom.element.Style.set(this.__form, "display", "none");
qx.dom.Element.insertEnd(this.__form, qx.dom.Node.getBodyElement(document));
this.__data = qx.dom.Element.create("textarea", {id: "_data_", name: "_data_"});
qx.dom.Element.insertEnd(this.__data, this.__form);
// Finally, attach iframe to DOM and add listeners
qx.dom.Element.insertEnd(this.__frame, qx.dom.Node.getBodyElement(document));
qx.event.Registration.addListener(this.__frame, "load", this._onload, this);
// qx.event.handler.Iframe does not yet support the readystatechange event
this.__onreadystatechangeWrapper = qx.lang.Function.listener(this._onreadystatechange, this);
qx.bom.Event.addNativeListener(this.__frame, "readystatechange", this.__onreadystatechangeWrapper);
},
/*
*****************************************************************************
STATICS
*****************************************************************************
*/
statics :
{
/**
* Capabilities of this transport type.
*
* @internal
*/
handles :
{
synchronous : false,
asynchronous : true,
crossDomain : false,
fileUpload : true,
programmaticFormFields : true,
responseTypes : [ "text/plain", "text/javascript", "application/json", "application/xml", "text/html" ]
},
/**
* Returns always true, because iframe transport is supported by all browsers.
*
* @return {Boolean}
*/
isSupported : function() {
return true;
},
/*
---------------------------------------------------------------------------
EVENT LISTENER
---------------------------------------------------------------------------
*/
/**
* For reference:
* http://msdn.microsoft.com/en-us/library/ie/ms534359%28v=vs.85%29.aspx
*
* @internal
*/
_numericMap :
{
"uninitialized" : 1,
"loading" : 2,
"loaded" : 2,
"interactive" : 3,
"complete" : 4
}
},
/*
*****************************************************************************
MEMBERS
*****************************************************************************
*/
members :
{
__data : null,
__lastReadyState : 0,
__form : null,
__frame : null,
__onreadystatechangeWrapper : null,
/*
---------------------------------------------------------------------------
USER METHODS
---------------------------------------------------------------------------
*/
/**
* Sends a request with the use of a form.
*
*/
send : function()
{
var vMethod = this.getMethod();
var vUrl = this.getUrl();
// --------------------------------------
// Adding parameters
// --------------------------------------
var vParameters = this.getParameters(false);
var vParametersList = [];
for (var vId in vParameters)
{
var value = vParameters[vId];
if (value instanceof Array)
{
for (var i=0; i<value.length; i++) {
vParametersList.push(encodeURIComponent(vId) + "=" + encodeURIComponent(value[i]));
}
}
else
{
vParametersList.push(encodeURIComponent(vId) + "=" + encodeURIComponent(value));
}
}
if (vParametersList.length > 0) {
vUrl += (vUrl.indexOf("?") >= 0 ? "&" : "?") + vParametersList.join("&");
}
// --------------------------------------------------------
// Adding data parameters (if no data is already present)
// --------------------------------------------------------
if (this.getData() === null)
{
var vParameters = this.getParameters(true);
var vParametersList = [];
for (var vId in vParameters)
{
var value = vParameters[vId];
if (value instanceof Array)
{
for (var i=0; i<value.length; i++)
{
vParametersList.push(encodeURIComponent(vId) +
"=" +
encodeURIComponent(value[i]));
}
}
else
{
vParametersList.push(encodeURIComponent(vId) +
"=" +
encodeURIComponent(value));
}
}
if (vParametersList.length > 0)
{
this.setData(vParametersList.join("&"));
}
}
// --------------------------------------
// Adding form fields
// --------------------------------------
var vFormFields = this.getFormFields();
for (var vId in vFormFields)
{
var vField = document.createElement("textarea");
vField.name = vId;
vField.appendChild(document.createTextNode(vFormFields[vId]));
this.__form.appendChild(vField);
}
// --------------------------------------
// Preparing form
// --------------------------------------
this.__form.action = vUrl;
this.__form.method = vMethod;
// --------------------------------------
// Sending data
// --------------------------------------
this.__data.appendChild(document.createTextNode(this.getData()));
this.__form.submit();
this.setState("sending");
},
/**
* Converting complete state to numeric value and update state property
*
* @signature function(e)
* @param e {qx.event.type.Event} event object
*/
_onload : qx.event.GlobalError.observeMethod(function(e)
{
// Timing-issue in Opera
// Do not switch state to complete in case load event fires before content
// of iframe was updated
if (qx.core.Environment.get("engine.name") == "opera" && this.getIframeHtmlContent() == "") {
return;
}
if (this.__form.src) {
return;
}
this._switchReadyState(qx.io.remote.transport.Iframe._numericMap.complete);
}),
/**
* Converting named readyState to numeric value and update state property
*
* @signature function(e)
* @param e {qx.event.type.Event} event object
*/
_onreadystatechange : qx.event.GlobalError.observeMethod(function(e) {
this._switchReadyState(qx.io.remote.transport.Iframe._numericMap[this.__frame.readyState]);
}),
/**
* Switches the readystate by setting the internal state.
*
* @param vReadyState {String} readystate value
*/
_switchReadyState : function(vReadyState)
{
// Ignoring already stopped requests
switch(this.getState())
{
case "completed":
case "aborted":
case "failed":
case "timeout":
this.warn("Ignore Ready State Change");
return;
}
// Updating internal state
while (this.__lastReadyState < vReadyState) {
this.setState(qx.io.remote.Exchange._nativeMap[++this.__lastReadyState]);
}
},
/*
---------------------------------------------------------------------------
REQUEST HEADER SUPPORT
---------------------------------------------------------------------------
*/
/**
* Sets a request header with the given value.
*
* This method is not implemented at the moment.
*
* @param vLabel {String} request header name
* @param vValue {var} request header value
*/
setRequestHeader : function(vLabel, vValue) {},
/*
---------------------------------------------------------------------------
RESPONSE HEADER SUPPORT
---------------------------------------------------------------------------
*/
/**
* Returns the value of the given response header.
*
* This method is not implemented at the moment and returns always "null".
*
* @param vLabel {String} Response header name
* @return {null} Returns null
*/
getResponseHeader : function(vLabel) {
return null;
},
/**
* Provides an hash of all response headers.
*
* This method is not implemented at the moment and returns an empty map.
*
* @return {Map} empty map
*/
getResponseHeaders : function() {
return {};
},
/*
---------------------------------------------------------------------------
STATUS SUPPORT
---------------------------------------------------------------------------
*/
/**
* Returns the current status code of the request if available or -1 if not.
* This method needs implementation (returns always 200).
*
* @return {Integer} status code
*/
getStatusCode : function() {
return 200;
},
/**
* Provides the status text for the current request if available and null otherwise.
* This method needs implementation (returns always an empty string)
*
* @return {String} status code text
*/
getStatusText : function() {
return "";
},
/*
---------------------------------------------------------------------------
FRAME UTILITIES
---------------------------------------------------------------------------
*/
/**
* Returns the DOM window object of the used iframe.
*
* @return {Object} DOM window object
*/
getIframeWindow : function() {
return qx.bom.Iframe.getWindow(this.__frame);
},
/**
* Returns the document node of the used iframe.
*
* @return {Object} document node
*/
getIframeDocument : function() {
return qx.bom.Iframe.getDocument(this.__frame);
},
/**
* Returns the body node of the used iframe.
*
* @return {Object} body node
*/
getIframeBody : function() {
return qx.bom.Iframe.getBody(this.__frame);
},
/*
---------------------------------------------------------------------------
RESPONSE DATA SUPPORT
---------------------------------------------------------------------------
*/
/**
* Returns the iframe content (innerHTML) as text.
*
* @return {String} iframe content as text
*/
getIframeTextContent : function()
{
var vBody = this.getIframeBody();
if (!vBody) {
return null;
}
if (!vBody.firstChild) {
return "";
}
// Mshtml returns the content inside a PRE
// element if we use plain text
if (vBody.firstChild.tagName &&
vBody.firstChild.tagName.toLowerCase() == "pre") {
return vBody.firstChild.innerHTML;
} else {
return vBody.innerHTML;
}
},
/**
* Returns the iframe content as HTML.
*
* @return {String} iframe content as HTML
*/
getIframeHtmlContent : function()
{
var vBody = this.getIframeBody();
return vBody ? vBody.innerHTML : null;
},
/**
* Returns the length of the content as fetched thus far.
* This method needs implementation (returns always 0).
*
* @return {Integer} Returns 0
*/
getFetchedLength : function() {
return 0;
},
/**
* Returns the content of the response
*
* @return {null | String} null or text of the response (=iframe content).
*/
getResponseContent : function()
{
if (this.getState() !== "completed")
{
if (qx.core.Environment.get("qx.debug"))
{
if (qx.core.Environment.get("qx.debug.io.remote")) {
this.warn("Transfer not complete, ignoring content!");
}
}
return null;
}
if (qx.core.Environment.get("qx.debug"))
{
if (qx.core.Environment.get("qx.debug.io.remote")) {
this.debug("Returning content for responseType: " + this.getResponseType());
}
}
var vText = this.getIframeTextContent();
switch(this.getResponseType())
{
case "text/plain":
if (qx.core.Environment.get("qx.debug"))
{
if (qx.core.Environment.get("qx.debug.io.remote.data"))
{
this.debug("Response: " + this._responseContent);
}
}
return vText;
case "text/html":
vText = this.getIframeHtmlContent();
if (qx.core.Environment.get("qx.debug"))
{
if (qx.core.Environment.get("qx.debug.io.remote.data"))
{
this.debug("Response: " + this._responseContent);
}
}
return vText;
case "application/json":
vText = this.getIframeHtmlContent();
if (qx.core.Environment.get("qx.debug"))
{
if (qx.core.Environment.get("qx.debug.io.remote.data"))
{
this.debug("Response: " + this._responseContent);
}
}
try {
return vText && vText.length > 0 ? qx.lang.Json.parse(vText) : null;
} catch(ex) {
return this.error("Could not execute json: (" + vText + ")", ex);
}
case "text/javascript":
vText = this.getIframeHtmlContent();
if (qx.core.Environment.get("qx.debug"))
{
if (qx.core.Environment.get("qx.debug.io.remote.data"))
{
this.debug("Response: " + this._responseContent);
}
}
try {
return vText && vText.length > 0 ? window.eval(vText) : null;
} catch(ex) {
return this.error("Could not execute javascript: (" + vText + ")", ex);
}
case "application/xml":
vText = this.getIframeDocument();
if (qx.core.Environment.get("qx.debug"))
{
if (qx.core.Environment.get("qx.debug.io.remote.data"))
{
this.debug("Response: " + this._responseContent);
}
}
return vText;
default:
this.warn("No valid responseType specified (" + this.getResponseType() + ")!");
return null;
}
}
},
/*
*****************************************************************************
DEFER
*****************************************************************************
*/
defer : function()
{
// basic registration to qx.io.remote.Exchange
// the real availability check (activeX stuff and so on) follows at the first real request
qx.io.remote.Exchange.registerType(qx.io.remote.transport.Iframe, "qx.io.remote.transport.Iframe");
},
/*
*****************************************************************************
DESTRUCTOR
*****************************************************************************
*/
destruct : function()
{
if (this.__frame)
{
qx.event.Registration.removeListener(this.__frame, "load", this._onload, this);
qx.bom.Event.removeNativeListener(this.__frame, "readystatechange", this.__onreadystatechangeWrapper);
// Reset source to a blank image for gecko
// Otherwise it will switch into a load-without-end behaviour
if ((qx.core.Environment.get("engine.name") == "gecko")) {
this.__frame.src = qx.util.ResourceManager.getInstance().toUri("qx/static/blank.gif");
}
// Finally, remove element node
qx.dom.Element.remove(this.__frame);
}
if (this.__form) {
qx.dom.Element.remove(this.__form);
}
this.__frame = this.__form = this.__data = null;
}
});