echo = function(areq) { console.log(areq.response) }; requests = (function() { var module = {}; var AsyncRequest = XMLHttpRequest; // takes a dict and returns a string "a=b&c=d" var toUrlParams = function(dict) { var res = []; for(var key in dict) { var value = encodeURIComponent(dict[key]) res.push(key + "=" + value); } return res.join("&"); } // takes a path and a params dict and returns a string "/path/?param1=val1¶m2=val2" var makeUrl = function(path, params) { var params = toUrlParams(params); if(params) { return path + "?" + params; } else { return path; } } // get the hostname from a url/path the same way that the hostname is extracted from the location in window.href.hostname // from: http://stackoverflow.com/a/8498668/35364 var pathHost = function(url) { var a = document.createElement('a'); a.href = url; return a.hostname; } // call all the callbacks/hooks without failing var call = function(callback_array, this_arg, args) { for(var index = 0; index < callback_array.length; index++) { try { // call the callback using fn.apply callback_array[index].apply(this_arg, args); } catch(e) { // pass console.log("callback error"); } } } var before_hooks = []; module.before_send = function(fn) { before_hooks.push(fn); return module; // allow chaining; } // constructor var JsonRequest = function(pmethod, ppath) { var self = this; var method = pmethod.toUpperCase(); var path = ppath; var data = {}; var params = {}; var headers = []; var success_hooks = []; var error_hooks = []; var complete_hooks = []; // this one is a getter only! self.path = function() { return path; } self.method = function() { return method; } // the rest are only setters self.header = function(key, value) { var h = {key: key, value: value}; headers.push(h); return self; } self.data = function(pdata) { data = pdata; return self; } self.params = function(pparams) { params = pparams; return self; } self.success = function(fn) { success_hooks.push(fn); return self; } self.error = function(fn) { error_hooks.push(fn); return self; } self.complete = function(fn) { complete_hooks.push(fn); return self; } self.send = function() { // call before_send hooks // we must call it here before we start building the request and setting headers, etc // because these hooks are meant for addings headers, etc call(before_hooks, self, [self]); var areq = new AsyncRequest(); var url = makeUrl(path, params); areq.open(method, url); // force json areq.setRequestHeader("Content-Type", "application/json"); // set the ajax header areq.setRequestHeader("X-Requested-With", "XMLHttpRequest"); // set headers // Traverse the headers list by the order of insertion // Note: Do this after forcing the json header so that users can override it for(var i = 0; i < headers.length; i++) { var header = headers[i]; areq.setRequestHeader(header.key, header.value) } // TODO: have option to send datatypes other than json? var json_data = JSON.stringify(data); areq.send(json_data); areq.onreadystatechange = function() { if(areq.readyState == areq.DONE) { call(complete_hooks, areq, [areq]); if(areq.status == 200) { call(success_hooks, areq, [areq]); } else { call(error_hooks, areq, [areq]); } } } return self; } // utilities/helpers /// Check if the path is going to a different domain self.isCrossOrigin = function() { if(path.charAt(0) === "/" && path.charAt(1) !== "/") { return false; } // path has a domain; check if it's different from current domain if(pathHost(path) == window.location.hostname) { return false; } // has a host but not the same as this one; so it's cross-domain return true; } self.isSameOrigin = function() { return !self.isCrossOrigin(); } } module.request = function(method, path) { return new JsonRequest(method, path); } module.get = function(path) { return module.request("get", path); } module.post = function(path) { return module.request("post", path); } module.put = function(path) { return module.request("put", path); } module.del = function(path) { return module.request("delete", path); } return module; }());