/* global Ext */
/* @class Ext.ux.ManagedIFrame
* Version: 1.2
* Author: Doug Hendricks. doug[always-At]theactivegroup.com
* Copyright 2007-2008, Active Group, Inc. All rights reserved.
*
************************************************************************************
* This file is distributed on an AS IS BASIS WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE.
************************************************************************************
License: ux.ManagedIFrame and ux.ManagedIFramePanel 1.2 are licensed under the terms of
the Open Source LGPL 3.0 license: http://www.gnu.org/licenses/lgpl.html
Commercial use is prohibited without a Commercial License. See http://licensing.theactivegroup.com.
Donations are welcomed: http://donate.theactivegroup.com
*
An Ext.Element harness for iframe elements.
Adds Ext.UpdateManager(Updater) support and a compatible 'update' method for
writing content directly into an iFrames' document structure.
Signals various DOM/document states as the frames content changes with 'domready',
'documentloaded', and 'exception' events. The domready event is only raised when
a proper security context exists for the frame's DOM to permit modification.
(ie, Updates via Updater or documents retrieved from same-domain servers).
Frame sand-box permits eval/script-tag writes of javascript source.
(See execScript, writeScript, and loadFunction methods for more info.)
* Usage:
*
* // Harnessed from an existing Iframe from markup:
* var i = new Ext.ux.ManagedIFrame("myIframe");
* // Replace the iFrames document structure with the response from the requested URL.
* i.load("http://myserver.com/index.php", "param1=1¶m2=2");
* // Notes: this is not the same as setting the Iframes src property !
* // Content loaded in this fashion does not share the same document namespaces as it's parent --
* // meaning, there (by default) will be no Ext namespace defined in it since the document is
* // overwritten after each call to the update method, and no styleSheets.
*
*
* @cfg {Boolean/Object} autoCreate True to auto generate the IFRAME element, or a {@link Ext.DomHelper} config of the IFRAME to create
* @cfg {String} html Any markup to be applied to the IFRAME's document content when rendered.
* @cfg {Object} loadMask An {@link Ext.LoadMask} config or true to mask the iframe while using the update or setSrc methods (defaults to false).
* @cfg {Object} src The src attribute to be assigned to the Iframe after initialization (overrides the autoCreate config src attribute)
* @constructor
* @param {Mixed} el, Config object The iframe element or it's id to harness or a valid config object.
* Release: 1.2 (8/22/2008) FF3 Compatibility Fixes, loadMask tweaks
1.1 (4/13/2008) Adds Ext.Element, CSS Selectors (query,select) fly, and CSS
interface support (same-domain only)
Adds blur,focus,unload events (same-domain only)
*/
(function(){
var EV = Ext.lib.Event;
Ext.ux.ManagedIFrame = function(){
var args=Array.prototype.slice.call(arguments, 0)
,el = Ext.get(args[0])
,config = args[0];
if(el && el.dom && el.dom.tagName == 'IFRAME'){
config = args[1] || {};
}else{
config = args[0] || args[1] || {};
el = config.autoCreate?
Ext.get(Ext.DomHelper.append(config.autoCreate.parent||document.body,
Ext.apply({tag:'iframe', src:(Ext.isIE&&Ext.isSecure)?Ext.SSL_SECURE_URL:''},config.autoCreate))):null;
}
if(!el || el.dom.tagName != 'IFRAME') return el;
el.dom.name || (el.dom.name = el.dom.id); //make sure there is a valid frame name
el.dom.mifId = el.dom.id; //create an un-protected reference for browsers which prevent access to 'id'(FF3).
this.addEvents({
/**
* @event focus
* Fires when the frame gets focus.
* @param {Ext.ux.ManagedIFrame} this
* @param {Ext.Event}
* Note: This event is only available when overwriting the iframe document using the update method and to pages
* retrieved from a "same domain".
* Returning false from the eventHandler [MAY] NOT cancel the event, as this event is NOT ALWAYS cancellable in all browsers.
*/
"focus" : true,
/**
* @event blur
* * Fires when the frame is blurred (loses focus).
* @param {Ext.ux.ManagedIFrame} this
* @param {Ext.Event}
* Note: This event is only available when overwriting the iframe document using the update method and to pages
* retrieved from a "same domain".
* Returning false from the eventHandler [MAY] NOT cancel the event, as this event is NOT ALWAYS cancellable in all browsers.
*/
"blur" : true,
/**
* @event unload
* * Fires when(if) the frames window object raises the unload event
* @param {Ext.ux.ManagedIFrame} this
* @param {Ext.Event}
* Note: This event is only available when overwriting the iframe document using the update method and to pages
* retrieved from a "same domain".
* Note: Opera does not raise this event.
*/
"unload" : true,
/**
* @event domready
* Fires ONLY when an iFrame's Document(DOM) has reach a state where the DOM may be manipulated (ie same domain policy)
* @param {Ext.ux.ManagedIFrame} this
* Note: This event is only available when overwriting the iframe document using the update method and to pages
* retrieved from a "same domain".
* Returning false from the eventHandler stops further event (documentloaded) processing.
*/
"domready" : true,
/**
* @event documentloaded
* Fires when the iFrame has reached a loaded/complete state.
* @param {Ext.ux.ManagedIFrame} this
*/
"documentloaded" : true,
/**
* @event exception
* Fires when the iFrame raises an error
* @param {Ext.ux.ManagedIFrame} this
* @param {Object/string} exception
*/
"exception" : true,
/**
* @event message
* Fires upon receipt of a message generated by window.sendMessage method of the embedded Iframe.window object
* @param {Ext.ux.ManagedIFrame} this
* @param {object} message (members: type: {string} literal "message",
* data {Mixed} [the message payload],
* domain [the document domain from which the message originated ],
* uri {string} the document URI of the message sender
* source (Object) the window context of the message sender
* tag {string} optional reference tag sent by the message sender
*/
"message" : true
/**
* Alternate event handler syntax for message:tag filtering
* @event message:tag
* Fires upon receipt of a message generated by window.sendMessage method
* which includes a specific tag value of the embedded Iframe.window object
* @param {Ext.ux.ManagedIFrame} this
* @param {object} message (members: type: {string} literal "message",
* data {Mixed} [the message payload],
* domain [the document domain from which the message originated ],
* uri {string} the document URI of the message sender
* source (Object) the window context of the message sender
* tag {string} optional reference tag sent by the message sender
*/
//"message:tagName" is supported for X-frame messaging
});
if(config.listeners){
this.listeners=config.listeners;
Ext.ux.ManagedIFrame.superclass.constructor.call(this);
}
Ext.apply(el,this); // apply this class interface ( pseudo Decorator )
el.addClass('x-managed-iframe');
if(config.style){
el.applyStyles(config.style);
}
el._maskEl = el.parent('.x-managed-iframe-mask')||el.parent().addClass('x-managed-iframe-mask');
Ext.apply(el,{
disableMessaging : config.disableMessaging===true
,loadMask : Ext.apply({msg:'Loading..'
,msgCls:'x-mask-loading'
,maskEl: el._maskEl
,hideOnReady:true
,disabled:!config.loadMask},config.loadMask)
//Hook the Iframes loaded state handler
,_eventName : Ext.isIE?'onreadystatechange':'onload'
,_windowContext : null
,eventsFollowFrameLinks : typeof config.eventsFollowFrameLinks=='undefined'?
true : config.eventsFollowFrameLinks
});
el.dom[el._eventName] = el.loadHandler.createDelegate(el);
var um = el.updateManager=new Ext.UpdateManager(el,true);
um.showLoadIndicator= config.showLoadIndicator || false;
if(config.src){
el.setSrc(config.src);
}else{
var content = config.html || config.content || false;
if(content){
el.update.defer(10,el,[content]); //allow frame to quiesce
}
}
return Ext.ux.ManagedIFrame.Manager.register(el);
};
var MIM = Ext.ux.ManagedIFrame.Manager = function(){
var frames = {};
//private DOMFrameContentLoaded handler for browsers that support it.
var readyHandler = function(e, target){
//use the unprotected DOM Id reference first
try{
var id =target?target.mifId:null, frame;
if((frame = this.getFrameById(id || target.id)) && frame._frameAction){
frame.loadHandler({type:'domready'});
}
}catch(rhEx){}
};
var implementation= {
shimCls : 'x-frame-shim',
register :function(frame){
frame.manager = this;
frames[frame.id] = frames[frame.dom.name] = {ref:frame, elCache:{}};
return frame;
},
deRegister :function(frame){
frame._unHook();
delete frames[frame.id];
delete frames[frame.dom.name];
},
hideShims : function(){
if(!this.shimApplied)return;
Ext.select('.'+this.shimCls,true).removeClass(this.shimCls+'-on');
this.shimApplied = false;
},
/* Mask ALL ManagedIframes (eg. when a region-layout.splitter is on the move.)*/
showShims : function(){
if(!this.shimApplied){
this.shimApplied = true;
//Activate the shimCls globally
Ext.select('.'+this.shimCls,true).addClass(this.shimCls+'-on');
}
},
getFrameById : function(id){
return typeof id == 'string'?(frames[id]?frames[id].ref||null:null):null;
},
getFrameByName : function(name){
return this.getFrameById(name);
},
//retrieve the internal frameCache object
getFrameHash : function(frame){
return frame.id?frames[frame.id]:null;
},
//to be called under the scope of the managing MIF
eventProxy : function(e){
if(!e)return;
e = Ext.EventObject.setEvent(e);
var be=e.browserEvent||e;
//same-domain unloads should clear ElCache for use with the next document rendering
if(e.type == 'unload'){ this._unHook(); }
if(!be['eventPhase'] || (be['eventPhase'] == (be['AT_TARGET']||2))){
return this.fireEvent(e.type, e);
}
},
_flyweights : {},
destroy : function(){
if(this._domreadySignature ){
Ext.EventManager.un.apply(Ext.EventManager,this._domreadySignature);
}
},
//safe removal of embedded frame elements
removeNode : Ext.isIE ?
function(frame, n){
frame = MIM.getFrameHash(frame);
if(frame && n && n.tagName != 'BODY'){
d = frame.scratchDiv || (frame.scratchDiv = frame.getDocument().createElement('div'));
d.appendChild(n);
d.innerHTML = '';
}
}
: function(frame, n){
if(n && n.parentNode && n.tagName != 'BODY'){
n.parentNode.removeChild(n);
}
}
};
if(document.addEventListener){ //for Gecko and Opera and any who might support it later
Ext.EventManager.on.apply(Ext.EventManager,implementation._domreadySignature=[window,"DOMFrameContentLoaded", readyHandler , implementation]);
}
Ext.EventManager.on(window,'beforeunload', implementation.destroy, implementation);
return implementation;
}();
MIM.showDragMask = MIM.showShims;
MIM.hideDragMask = MIM.hideShims;
//Provide an Ext.Element interface to frame document elements
MIM.El =function(frame, el, forceNew){
var frameObj;
frame = (frameObj = MIM.getFrameHash(frame))?frameObj.ref:null ;
if(!frame ){ return null; }
var elCache = frameObj.elCache || (frameObj.elCache = {});
var dom = frame.getDom(el);
if(!dom){ // invalid id/element
return null;
}
var id = dom.id;
if(forceNew !== true && id && elCache[id]){ // element object already exists
return elCache[id];
}
/**
* The DOM element
* @type HTMLElement
*/
this.dom = dom;
/**
* The DOM element ID
* @type String
*/
this.id = id || Ext.id(dom);
};
MIM.El.get =function(frame, el){
var ex, elm, id, doc;
if(!frame || !el ){ return null; }
var frameObj;
frame = (frameObj = MIM.getFrameHash(frame))?frameObj.ref:null ;
if(!frame ){ return null;}
var elCache = frameObj.elCache || (frameObj.elCache = {} );
if(!(doc = frame.getDocument())){ return null; }
if(typeof el == "string"){ // element id
if(!(elm = frame.getDom(el))){
return null;
}
if(ex = elCache[el]){
ex.dom = elm;
}else{
ex = elCache[el] = new MIM.El(frame, elm);
}
return ex;
}else if(el.tagName){ // dom element
if(!(id = el.id)){
id = Ext.id(el);
}
if(ex = elCache[id]){
ex.dom = el;
}else{
ex = elCache[id] = new MIM.El(frame, el);
}
return ex;
}else if(el instanceof MIM.El){
if(el != frameObj.docEl){
el.dom = frame.getDom(el.id) || el.dom; // refresh dom element in case no longer valid,
// catch case where it hasn't been appended
elCache[el.id] = el; // in case it was created directly with Element(), let's cache it
}
return el;
}else if(el.isComposite){
return el;
}else if(Ext.isArray(el)){
return frame.select(el);
}else if(el == doc){
// create a bogus element object representing the document object
if(!frameObj.docEl){
var f = function(){};
f.prototype = MIM.El.prototype;
frameObj.docEl = new f();
frameObj.docEl.dom = doc;
}
return frameObj.docEl;
}
return null;
};
Ext.apply(MIM.El.prototype,Ext.Element.prototype);
Ext.extend(Ext.ux.ManagedIFrame , Ext.util.Observable,
{
src : null ,
resetUrl :Ext.isIE&&Ext.isSecure?Ext.SSL_SECURE_URL: 'about:blank' ,
/**
* Sets the embedded Iframe src property.
* @param {String/Function} url (Optional) A string or reference to a Function that returns a URI string when called
* @param {Boolean} discardUrl (Optional) If not passed as false the URL of this action becomes the default SRC attribute for
* this iframe, and will be subsequently used in future setSrc calls (emulates autoRefresh by calling setSrc without params).
* Note: invoke the function with no arguments to refresh the iframe based on the current src value.
*/
setSrc : function(url, discardUrl, callback){
var src = url || this.src || this.resetUrl;
this._windowContext = null;
this._unHook();
this._frameAction = this.frameInit= this._domReady =false;
if(Ext.isOpera){ this.reset(); }
this._callBack = callback || false;
this.showMask();
(function(){
var s = typeof src == 'function'?src()||'':src;
try{
this._frameAction = true; //signal listening now
this.dom.src = s;
this.frameInit= true; //control initial event chatter
this.checkDOM();
}catch(ex){ this.fireEvent('exception', this, ex); }
}).defer(100,this);
if(discardUrl !== true){ this.src = src; }
return this;
},
reset : function(src, callback){
this.dom.src = src || this.resetUrl;
if(typeof callback == 'function'){ callback.defer(100);}
return this;
},
//Private: script removal RegeXp
scriptRE : /(?:)((\n|\r|.)*?)(?:<\/script>)/gi
,
/*
* Write(replacing) string content into the IFrames document structure
* @param {String} content The new content
* @param {Boolean} loadScripts (optional) true to also render and process embedded scripts
* @param {Function} callback (optional) Callback when update is complete.
*/
update : function(content,loadScripts,callback){
loadScripts = loadScripts || this.getUpdateManager().loadScripts || false;
content = Ext.DomHelper.markup(content||'');
content = loadScripts===true ? content:content.replace(this.scriptRE , "");
var doc;
if(doc = this.getDocument()){
this._frameAction = !!content.length;
this._windowContext = this.src = null;
this._callBack = callback || false;
this._unHook();
this.showMask();
doc.open();
doc.write(content);
doc.close();
this.frameInit= true; //control initial event chatter
if(this._frameAction){
this.checkDOM();
} else {
this.hideMask(true);
if(this._callBack)this._callBack();
}
}else{
this.hideMask(true);
if(this._callBack)this._callBack();
}
return this;
},
/* Enables/disables x-frame messaging interface */
disableMessaging : true,
//Private, frame messaging interface (for same-domain-policy frames only)
_XFrameMessaging : function(){
//each tag gets a hash queue ($ = no tag ).
var tagStack = {'$' : [] };
var isEmpty = function(v, allowBlank){
return v === null || v === undefined || (!allowBlank ? v === '' : false);
};
window.sendMessage = function(message, tag, origin ){
var MIF;
if(MIF = arguments.callee.manager){
if(message._fromHost){
var fn, result;
//only raise matching-tag handlers
var compTag= message.tag || tag || null;
var mstack = !isEmpty(compTag)? tagStack[compTag.toLowerCase()]||[] : tagStack["$"];
for(var i=0,l=mstack.length;i Note: will only work after a successful iframe.(Updater) update
* or after same-domain document has been hooked, otherwise an exception is raised.
*/
,execScript: function(block, useDOM){
try{
if(this.domWritable()){
if(useDOM){
this.writeScript(block);
}else{
return this._windowContext.eval(block);
}
}else{ throw 'execScript:non-secure context' }
}catch(ex){
this.fireEvent('exception', this, ex);
return false;
}
return true;
}
/*
* write a