Path: blob/master/webroot/rsrc/externals/javelin/lib/Request.js
12242 views
/**1* @requires javelin-install2* javelin-stratcom3* javelin-util4* javelin-behavior5* javelin-json6* javelin-dom7* javelin-resource8* javelin-routable9* @provides javelin-request10* @javelin11*/1213/**14* Make basic AJAX XMLHTTPRequests.15*/16JX.install('Request', {17construct : function(uri, handler) {18this.setURI(uri);19if (handler) {20this.listen('done', handler);21}22},2324events : ['start', 'open', 'send', 'statechange', 'done', 'error', 'finally',25'uploadprogress'],2627members : {2829_xhrkey : null,30_transport : null,31_sent : false,32_finished : false,33_block : null,34_data : null,3536_getSameOriginTransport : function() {37try {38try {39return new XMLHttpRequest();40} catch (x) {41return new ActiveXObject('Msxml2.XMLHTTP');42}43} catch (x) {44return new ActiveXObject('Microsoft.XMLHTTP');45}46},4748_getCORSTransport : function() {49try {50var xport = new XMLHttpRequest();51if ('withCredentials' in xport) {52// XHR supports CORS53} else if (typeof XDomainRequest != 'undefined') {54xport = new XDomainRequest();55}56return xport;57} catch (x) {58return new XDomainRequest();59}60},6162getTransport : function() {63if (!this._transport) {64this._transport = this.getCORS() ? this._getCORSTransport() :65this._getSameOriginTransport();66}67return this._transport;68},6970getRoutable: function() {71var routable = new JX.Routable();72routable.listen('start', JX.bind(this, function() {73// Pass the event to allow other listeners to "start" to configure this74// request before it fires.75JX.Stratcom.pass(JX.Stratcom.context());76this.send();77}));78this.listen('finally', JX.bind(routable, routable.done));79return routable;80},8182send : function() {83if (this._sent || this._finished) {84if (__DEV__) {85if (this._sent) {86JX.$E(87'JX.Request.send(): ' +88'attempting to send a Request that has already been sent.');89}90if (this._finished) {91JX.$E(92'JX.Request.send(): ' +93'attempting to send a Request that has finished or aborted.');94}95}96return;97}9899// Fire the "start" event before doing anything. A listener may100// perform pre-processing or validation on this request101this.invoke('start', this);102if (this._finished) {103return;104}105106var xport = this.getTransport();107xport.onreadystatechange = JX.bind(this, this._onreadystatechange);108if (xport.upload) {109xport.upload.onprogress = JX.bind(this, this._onuploadprogress);110}111112var method = this.getMethod().toUpperCase();113114if (__DEV__) {115if (this.getRawData()) {116if (method != 'POST') {117JX.$E(118'JX.Request.send(): ' +119'attempting to send post data over GET. You must use POST.');120}121}122}123124var list_of_pairs = this._data || [];125list_of_pairs.push(['__ajax__', true]);126127this._block = JX.Stratcom.allocateMetadataBlock();128list_of_pairs.push(['__metablock__', this._block]);129130var q = (this.getDataSerializer() ||131JX.Request.defaultDataSerializer)(list_of_pairs);132var uri = this.getURI();133134// If we're sending a file, submit the metadata via the URI instead of135// via the request body, because the entire post body will be consumed by136// the file content.137if (method == 'GET' || this.getRawData()) {138uri += ((uri.indexOf('?') === -1) ? '?' : '&') + q;139}140141if (this.getTimeout()) {142this._timer = setTimeout(143JX.bind(144this,145this._fail,146JX.Request.ERROR_TIMEOUT),147this.getTimeout());148}149150xport.open(method, uri, true);151152// Must happen after xport.open so that listeners can modify the transport153// Some transport properties can only be set after the transport is open154this.invoke('open', this);155if (this._finished) {156return;157}158159this.invoke('send', this);160if (this._finished) {161return;162}163164if (method == 'POST') {165if (this.getRawData()) {166xport.send(this.getRawData());167} else {168xport.setRequestHeader(169'Content-Type',170'application/x-www-form-urlencoded');171xport.send(q);172}173} else {174xport.send(null);175}176177this._sent = true;178},179180abort : function() {181this._cleanup();182},183184_onuploadprogress : function(progress) {185this.invoke('uploadprogress', progress);186},187188_onreadystatechange : function() {189var xport = this.getTransport();190var response;191try {192this.invoke('statechange', this);193if (this._finished) {194return;195}196if (xport.readyState != 4) {197return;198}199// XHR requests to 'file:///' domains return 0 for success, which is why200// we treat it as a good result in addition to HTTP 2XX responses.201if (xport.status !== 0 && (xport.status < 200 || xport.status >= 300)) {202this._fail();203return;204}205206if (__DEV__) {207var expect_guard = this.getExpectCSRFGuard();208209if (!xport.responseText.length) {210JX.$E(211'JX.Request("'+this.getURI()+'", ...): '+212'server returned an empty response.');213}214if (expect_guard && xport.responseText.indexOf('for (;;);') !== 0) {215JX.$E(216'JX.Request("'+this.getURI()+'", ...): '+217'server returned an invalid response.');218}219if (expect_guard && xport.responseText == 'for (;;);') {220JX.$E(221'JX.Request("'+this.getURI()+'", ...): '+222'server returned an empty response.');223}224}225226response = this._extractResponse(xport);227if (!response) {228JX.$E(229'JX.Request("'+this.getURI()+'", ...): '+230'server returned an invalid response.');231}232} catch (exception) {233234if (__DEV__) {235JX.log(236'JX.Request("'+this.getURI()+'", ...): '+237'caught exception processing response: '+exception);238}239this._fail();240return;241}242243try {244this._handleResponse(response);245this._cleanup();246} catch (exception) {247// In Firefox+Firebug, at least, something eats these. :/248setTimeout(function() {249throw exception;250}, 0);251}252},253254_extractResponse : function(xport) {255var text = xport.responseText;256257if (this.getExpectCSRFGuard()) {258text = text.substring('for (;;);'.length);259}260261var type = this.getResponseType().toUpperCase();262if (type == 'TEXT') {263return text;264} else if (type == 'JSON' || type == 'JAVELIN') {265return JX.JSON.parse(text);266} else if (type == 'XML') {267var doc;268try {269if (typeof DOMParser != 'undefined') {270var parser = new DOMParser();271doc = parser.parseFromString(text, 'text/xml');272} else { // IE273// an XDomainRequest274doc = new ActiveXObject('Microsoft.XMLDOM');275doc.async = false;276doc.loadXML(xport.responseText);277}278279return doc.documentElement;280} catch (exception) {281if (__DEV__) {282JX.log(283'JX.Request("'+this.getURI()+'", ...): '+284'caught exception extracting response: '+exception);285}286this._fail();287return null;288}289}290291if (__DEV__) {292JX.$E(293'JX.Request("'+this.getURI()+'", ...): '+294'unrecognized response type.');295}296return null;297},298299_fail : function(error) {300this._cleanup();301302this.invoke('error', error, this);303this.invoke('finally');304},305306_done : function(response) {307this._cleanup();308309if (response.onload) {310for (var ii = 0; ii < response.onload.length; ii++) {311(new Function(response.onload[ii]))();312}313}314315var payload;316if (this.getRaw()) {317payload = response;318} else {319payload = response.payload;320JX.Request._parseResponsePayload(payload);321}322323this.invoke('done', payload, this);324this.invoke('finally');325},326327_cleanup : function() {328this._finished = true;329clearTimeout(this._timer);330this._timer = null;331332// Should not abort the transport request if it has already completed333// Otherwise, we may see an "HTTP request aborted" error in the console334// despite it possibly having succeeded.335if (this._transport && this._transport.readyState != 4) {336this._transport.abort();337}338},339340setData : function(dictionary) {341this._data = null;342this.addData(dictionary);343return this;344},345346addData : function(dictionary) {347if (!this._data) {348this._data = [];349}350for (var k in dictionary) {351this._data.push([k, dictionary[k]]);352}353return this;354},355356setDataWithListOfPairs : function(list_of_pairs) {357this._data = list_of_pairs;358return this;359},360361_handleResponse : function(response) {362if (this.getResponseType().toUpperCase() == 'JAVELIN') {363if (response.error) {364this._fail(response.error);365} else {366JX.Stratcom.mergeData(367this._block,368response.javelin_metadata || {});369370var when_complete = JX.bind(this, function() {371this._done(response);372JX.initBehaviors(response.javelin_behaviors || {});373});374375if (response.javelin_resources) {376JX.Resource.load(response.javelin_resources, when_complete);377} else {378when_complete();379}380}381} else {382this._cleanup();383this.invoke('done', response, this);384this.invoke('finally');385}386}387},388389statics : {390ERROR_TIMEOUT : -9000,391defaultDataSerializer : function(list_of_pairs) {392var uri = [];393for (var ii = 0; ii < list_of_pairs.length; ii++) {394var pair = list_of_pairs[ii];395396if (pair[1] === null) {397continue;398}399400var name = encodeURIComponent(pair[0]);401var value = encodeURIComponent(pair[1]);402uri.push(name + '=' + value);403}404return uri.join('&');405},406407/**408* When we receive a JSON blob, parse it to introduce meaningful objects409* where there are magic keys for placeholders.410*411* Objects with the magic key '__html' are translated into JX.HTML objects.412*413* This function destructively modifies its input.414*/415_parseResponsePayload: function(parent, index) {416var recurse = JX.Request._parseResponsePayload;417var obj = (typeof index !== 'undefined') ? parent[index] : parent;418if (JX.isArray(obj)) {419for (var ii = 0; ii < obj.length; ii++) {420recurse(obj, ii);421}422} else if (obj && typeof obj == 'object') {423if (('__html' in obj) && (obj.__html !== null)) {424parent[index] = JX.$H(obj.__html);425} else {426for (var key in obj) {427recurse(obj, key);428}429}430}431}432},433434properties : {435URI : null,436dataSerializer : null,437/**438* Configure which HTTP method to use for the request. Permissible values439* are "POST" (default) or "GET".440*441* @param string HTTP method, one of "POST" or "GET".442*/443method : 'POST',444/**445* Set the data parameter of transport.send. Useful if you want to send a446* file or FormData. Not that you cannot send raw data and data at the same447* time.448*449* @param Data, argument to transport.send450*/451rawData: null,452raw : false,453454/**455* Configure a timeout, in milliseconds. If the request has not resolved456* (either with success or with an error) within the provided timeframe,457* it will automatically fail with error JX.Request.ERROR_TIMEOUT.458*459* @param int Timeout, in milliseconds (e.g. 3000 = 3 seconds).460*/461timeout : null,462463/**464* Whether or not we should expect the CSRF guard in the response.465*466* @param bool467*/468expectCSRFGuard : true,469470/**471* Whether it should be a CORS (Cross-Origin Resource Sharing) request to472* a third party domain other than the current site.473*474* @param bool475*/476CORS : false,477478/**479* Type of the response.480*481* @param enum 'JAVELIN', 'JSON', 'XML', 'TEXT'482*/483responseType : 'JAVELIN'484}485486});487488489