/**
* Ext.ux.CssProxy
* Version 1.0
* Copyright (c) 2009 - David W Davis - http://xant.us/
*
* Adapted from CSSHttpRequest for Extjs
*
* CSSHttpRequest
* Copyright 2008 nb.io - http://nb.io/
* http://nb.io/hacks/csshttprequest/
* Licensed under Apache License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.html
*/
/**
* @class Ext.ux.CssProxy
* @extends Ext.data.DataProxy
* An implementation of Ext.data.DataProxy that reads a data object from a URL which may be in a domain
* other than the originating domain of the running page.
*
* Note that if you are retrieving data from a page that is in a domain that is NOT the same as the originating domain
* of the running page, you must use Ext.data.ScriptTagProxy or this class, rather than HttpProxy.
*
* The content passed back from a server resource requested by a CssProxy must be specially encoded css. *
* Data is encoded on the server into URI-encoded 2KB chunks and serialized into CSS rules with a modified
* data: URI scheme. The selector should be in the form #c
*
* You can pass a simple reader to Ext.ux.CssProxy and handle the data directly, almost like you would with Ext.Ajax.
* For example this fetches a json response and returns an object:
*
* #c0 { background: url(data:,Hello%20World!); }
* #c1 { background: url(data:,I.m%20text%20encoded%20in%20CSS!); }
* #c2 { background: url(data:,I%20like%20arts%20and%20crafts.); }
*
*
*
* @constructor
* @param {Object} config A configuration object.
*/
Ext.namespace('Ext.ux');
Ext.ux.CssProxy = function(config){
Ext.ux.CssProxy.superclass.constructor.call(this);
Ext.apply(this, config);
this.doc = document.documentElement;
/**
* @event loadexception
* Fires if an exception occurs in the Proxy during data loading. This event can be fired for one of two reasons:
*
*
* SimpleReader = function() {
* SimpleReader.superclass.constructor.apply(this, arguments);
* };
* Ext.extend(SimpleReader, Ext.data.DataReader, {
* read: function(r) { return r.responseText; },
* readRecords: function(o) { return o; }
* });
*
* var conn = new Ext.ux.CssProxy({ url: "http://s.nb.io/hacks/csshttprequest/time-json/" });
* conn.load(
* { }, // params
* new SimpleReader(), // reader
* function(r) { // callback
* r = Ext.decode(r); // convert to json
* alert('Happy '+r.year+'! iso:'+r.isoformat);
* }
* );
*
* Note that this event is also relayed through {@link Ext.data.Store}, so you can listen for it directly
* on any Store instance.
* @param {Object} this
* @param {Object} options The loading options that were specified (see {@link #load} for details). If the load
* call timed out, this parameter will be null.
* @param {Object} arg The callback's arg object passed to the {@link #load} function
* @param {Error} e The JavaScript Error object caught if the configured Reader could not read the data.
* If the load call returned success: false, this parameter will be null.
*/
};
Ext.ux.CssProxy.TRANS_ID = 1000;
Ext.ux.CssProxy.sandbox = function(x) { };
Ext.ux.CssProxy.MATCH_ORDINAL = /#c(\d+)/;
Ext.ux.CssProxy.MATCH_URL = /url\("?data\:[^,]*,([^")]+)"?\)/; // "
Ext.extend(Ext.ux.CssProxy, Ext.data.DataProxy, {
/**
* @cfg {String} url The URL from which to request the data object.
*/
/**
* @cfg {Number} timeout (optional) The number of milliseconds to wait for a response. Defaults to 30 seconds.
*/
timeout: 30000,
/**
* @cfg {Boolean} nocache (optional) Defaults to true. Disable caching by adding a unique parameter
* name to the request.
*/
nocache: true,
/**
* Load data from the configured URL, read the data object into
* a block of Ext.data.Records using the passed Ext.data.DataReader implementation, and
* process that block using the passed callback.
* @param {Object} params An object containing properties which are to be used as HTTP parameters
* for the request to the remote server.
* @param {Ext.data.DataReader} reader The Reader object which converts the data
* object into a block of Ext.data.Records.
* @param {Function} callback The function into which to pass the block of Ext.data.Records.
* The function must be passed
*
* @param {Object} scope The scope in which to call the callback
* @param {Object} arg An optional argument which is passed to the callback as its second parameter.
*/
load: function(params, reader, callback, scope, arg){
if (this.fireEvent('beforeload', this, params) !== false) {
if (this.autoAbort !== false) {
this.abort();
}
var p = Ext.urlEncode(Ext.apply(params, this.extraParams));
var url = this.url;
url += (url.indexOf('?') != -1 ? '&' : '?') + p;
if (this.nocache) {
url += '&_dc=' + (new Date().getTime());
}
var transId = ++Ext.ux.CssProxy.TRANS_ID;
var trans = {
id: transId,
cb: 'chrCallback'+transId,
frameId: 'chrIframe'+transId,
params: params,
arg: arg,
url: url,
callback: callback,
scope: scope,
reader: reader
};
var iframe = document.createElement( 'iframe' );
iframe.setAttribute( 'id', trans.frameId );
iframe.style.position = 'absolute';
iframe.style.left = iframe.style.top = '-1000px';
iframe.style.width = iframe.style.height = 0;
this.doc.appendChild( iframe );
trans.document = iframe.contentDocument || iframe.contentWindow.document;
window[trans.cb] = this.handleResponse.createDelegate( this, [trans] );
trans.document.open('text/html', false);
trans.document.write("");
trans.document.write("");
trans.document.write("");
trans.document.write("");
trans.document.write("");
trans.document.close();
trans.timeoutId = this.handleFailure.defer( this.timeout, this, [trans] );
this.trans = trans;
}else{
callback.call(scope||this, null, arg, false);
}
},
// private
isLoading: function(){
return this.trans ? true : false;
},
/**
* Abort the current server request.
*/
abort: function(){
if(this.isLoading()){
this.destroyTrans(this.trans);
}
},
// private
destroyTrans: function(trans, isLoaded){
window.setTimeout(function() {
try { this.doc.removeChild(document.getElementById(trans.frameId)); } catch(e) {};
}, 0);
clearTimeout(trans.timeoutId);
if(isLoaded){
window[trans.cb] = undefined;
try{
delete window[trans.cb];
}catch(e){}
}else{
// if hasn't been loaded, wait for load to remove it to prevent script error
window[trans.cb] = function(){
window[trans.cb] = undefined;
try{
delete window[trans.cb];
}catch(e){}
};
}
},
// private
handleResponse: function(trans){
var o = this.parseCSS(trans);
this.trans = false;
this.destroyTrans(trans, true);
var result;
try {
result = trans.reader.read({ responseText: o });
//result = trans.reader.read(o);
}catch(e){
this.fireEvent('loadexception', this, o, trans.arg, e);
trans.callback.call(trans.scope||window, null, trans.arg, false);
return;
}
this.fireEvent('load', this, o, trans.arg);
trans.callback.call(trans.scope||window, result, trans.arg, true);
},
// private
handleFailure: function(trans){
this.trans = false;
this.destroyTrans(trans, false);
this.fireEvent('loadexception', this, null, trans.arg);
trans.callback.call(trans.scope||window, null, trans.arg, false);
},
// private
parseCSS: function(trans) {
var data = [];
try {
// Safari, IE and same-domain Firefox
var rules = trans.document.styleSheets[0].cssRules || trans.document.styleSheets[0].rules;
for ( var i = 0, len = rules.length; i < len; i++ ) {
try {
var r = rules.item ? rules.item(i) : rules[i];
var ord = r.selectorText.match(Ext.ux.CssProxy.MATCH_ORDINAL)[1];
var val = r.style.backgroundImage.match(Ext.ux.CssProxy.MATCH_URL)[1];
data[ord] = val;
} catch(e) {};
}
} catch(e) {
// catch same-domain exception
trans.document.getElementsByTagName('link')[0].setAttribute('media', 'screen');
var x = trans.document.createElement('div');
x.innerHTML = 'x';
trans.document.body.appendChild(x);
var ord = 0;
try {
while (1) {
x.id = 'c' + ord;
var style = trans.document.defaultView.getComputedStyle(x, null);
var bg = style['background-image'] || style.backgroundImage || style.getPropertyValue('background-image');
var val = bg.match(Ext.ux.CssProxy.MATCH_URL)[1];
data[ord] = val;
ord++;
}
} catch(e) {};
}
return decodeURIComponent(data.join(''));
}
});