/** * 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, where N is an integer index in [0,]. * The response is decoded and returned to the callback function as a string: *

*


 * #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.); }
 * 

 * 

* 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: *


 * 
 * 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);
 *     }
 * );
 * 
* * @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: * * 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('')); } });