1 /** 2 * @fileOverview 3 * @author <a href="https://www.labkey.org">LabKey</a> (<a href="mailto:info@labkey.com">info@labkey.com</a>) 4 * @license Copyright (c) 2011-2016 LabKey Corporation 5 * <p/> 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * <p/> 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * <p/> 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 * <p/> 18 */ 19 20 /** 21 * @namespace 22 * Utility for making XHR. 23 */ 24 LABKEY.Ajax = new function () { 25 'use strict'; 26 27 var DEFAULT_HEADERS = {'X-LABKEY-CSRF': LABKEY.CSRF}; 28 29 var callback = function(fn, scope, args) { 30 if (fn) { 31 fn.apply(scope, args); 32 } 33 }; 34 35 /** 36 * Returns true iff obj contains case-insensitive key 37 * @param {object} obj 38 * @param {string} key 39 */ 40 var contains = function(obj, key) { 41 if (key) { 42 var lowerKey = key.toLowerCase(); 43 for (var k in obj) { 44 if (obj.hasOwnProperty(k) && k.toLowerCase() === lowerKey) { 45 return true; 46 } 47 } 48 } 49 return false; 50 }; 51 52 var configureOptions = function(config) { 53 var url, params, method = 'GET', data, isForm = false; 54 55 if (!config.hasOwnProperty('url') || config.url === null) { 56 throw new Error("a URL is required to make a request"); 57 } 58 59 url = config.url; 60 params = config.params; 61 62 // configure data 63 if (config.form) { 64 data = config.form instanceof FormData ? config.form : new FormData(config.form); 65 isForm = true; 66 } 67 else if (config.jsonData) { 68 data = JSON.stringify(config.jsonData); 69 } 70 else { 71 data = null; 72 } 73 74 // configure method 75 if (config.hasOwnProperty('method') && config.method !== null) { 76 method = config.method.toUpperCase(); 77 } 78 else if (data) { 79 method = 'POST'; 80 } 81 82 // configure params 83 if (params !== undefined && params !== null) { 84 85 var qs = LABKEY.ActionURL.queryString(params); 86 87 // 26617: backwards compatibility to append params to the body in the case of a POST without form/jsonData 88 if (method === 'POST' && (data === undefined || data === null)) { 89 data = qs; 90 } 91 else { 92 url += (url.indexOf('?') === -1 ? '?' : '&') + qs; 93 } 94 } 95 96 return { 97 url: url, 98 method: method, 99 data: data, 100 isForm: isForm 101 }; 102 }; 103 104 var configureHeaders = function(xhr, config, options) { 105 var headers = config.headers, 106 jsonData = config.jsonData; 107 108 if (headers === undefined || headers === null) { 109 headers = {}; 110 } 111 112 // only set Content-Type if this is not FormData and it has not been set explicitly 113 if (!options.isForm && !contains(headers, 'Content-Type')) { 114 if (jsonData !== undefined && jsonData !== null) { 115 headers['Content-Type'] = 'application/json'; 116 } 117 else { 118 headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8'; 119 } 120 } 121 122 if (!contains(headers, 'X-Requested-With')) { 123 headers['X-Requested-With'] = 'XMLHttpRequest'; 124 } 125 126 for (var k in DEFAULT_HEADERS) { 127 if (DEFAULT_HEADERS.hasOwnProperty(k)) { 128 xhr.setRequestHeader(k, DEFAULT_HEADERS[k]); 129 } 130 } 131 132 for (var k in headers) { 133 if (headers.hasOwnProperty(k)) { 134 xhr.setRequestHeader(k, headers[k]); 135 } 136 } 137 138 return headers; 139 }; 140 141 /** @scope LABKEY.Ajax */ 142 return { 143 DEFAULT_HEADERS : DEFAULT_HEADERS, 144 145 request : function(config) { 146 var options = configureOptions(config), 147 scope = config.hasOwnProperty('scope') && config.scope !== null ? config.scope : this, 148 xhr = new XMLHttpRequest(); 149 150 xhr.onreadystatechange = function() { 151 if (xhr.readyState === 4) { 152 var success = (xhr.status >= 200 && xhr.status < 300) || xhr.status == 304; 153 154 callback(success ? config.success : config.failure, scope, [xhr, config]); 155 callback(config.callback, scope, [config, success, xhr]); 156 } 157 }; 158 159 xhr.open(options.method, options.url, true); 160 161 // configure headers after request is open 162 configureHeaders(xhr, config, options); 163 164 // configure timeout after request is open 165 if (config.hasOwnProperty('timeout') && config.timeout !== null) { 166 xhr.ontimeout = function() { 167 callback(config.failure, scope, [xhr, config]); 168 callback(config.callback, scope, [config, false /* success */, xhr]); 169 }; 170 } 171 172 xhr.send(options.data); 173 174 return xhr; 175 } 176 } 177 }; 178