UNPKG

codekart

Version:

Web application framework for Node.js

558 lines (537 loc) 22.7 kB
/* * Class: Ajax * Author: Bachue Zhou * Description: Deliver Ajax module from jquery * Date: 12/08/2010 * Depend on: JSON.parse() Must be provided! * Version: 0.02 */ (function() { var Ajax; if (!Ajax || typeof(Ajax) != "object") { Ajax = {}; } Ajax.ajaxSettings = { //init ajax setting url: location.href, type: "GET", contentType: "application/x-www-form-urlencoded", processData: true, async: true, /* timeout: 0, data: null, username: null, password: null, traditional: false, */ xhr: function () { //return ajax object. must be comparable with fuck ie. try { return new XMLHttpRequest(); } catch (e) { try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) { return new ActiveXObject("Microsoft.XMLHTTP"); } } }, accepts: { xml: "application/xml, text/xml", html: "text/html", script: "text/javascript, application/javascript", json: "application/json, text/javascript", text: "text/plain", _default: "*/*" } }; Ajax.setup = function (settings) { Ajax._extend(Ajax.ajaxSettings, settings); }; Ajax.send = function (origSettings) { function getType(obj) { return obj === null ? String(obj) : class2type[toString.call(obj)] || "object"; } function isFunction(obj) { return getType(obj) === "function"; } function isArray(obj) { return getType(obj) === "array"; } function isEmptyObject(obj) { for (var name in obj) { return false; } return true; } function param(a) { var s = [], name, add = function (key, value) { // If value is a function, invoke it and return its value value = isFunction(value) ? value() : value; s[s.length] = encodeURIComponent(key) + "=" + encodeURIComponent(value); }; // If an array was passed in, assume that it is an array of form elements. if (isArray(a)) { // Serialize the form elements for (name in a) { add(name, a[name]); } } else { // If traditional, encode the "old" way (the way 1.3.2 or older // did it), otherwise encode params recursively. for (var prefix in a) { buildParams(prefix, a[prefix], add); } } // Return the resulting serialization return s.join("&").replace(r20, "+"); } function buildParams(prefix, obj, add) { if (isArray(obj) && obj.length) { // Serialize array item. for (var i in obj) { var v = obj[i]; if (rbracket.test(prefix)) { // Treat each array item as a scalar. add(prefix, v); } else { // If array item is non-scalar (array or object), encode its // numeric index to resolve deserialization ambiguity issues. // Note that rack (as of 1.0.0) can't currently deserialize // nested arrays properly, and attempting to do so may cause // a server error. Possible fixes are to modify rack's // deserialization algorithm or to provide an option or flag // to force array serialization to be shallow. buildParams(prefix + "[" + (typeof v === "object" || isArray(v) ? i : "") + "]", v, add); } } } else if (obj !== null && typeof(obj) === "object") { if (isEmptyObject(obj)) { add(prefix, ""); } else { // Serialize object item. for (var k in obj) { var v = obj[k]; buildParams(prefix + "[" + k + "]", v, add); } } } else { // Serialize scalar item. add(prefix, obj); } } function globalEval(data) { if (data && rnotwhite.test(data)) { // Inspired by code by Andrea Giammarchi // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html var head = document.getElementsByTagName("head")[0] || document.documentElement, script = document.createElement("script"); script.type = "text/javascript"; if (scriptEval) { script.appendChild(document.createTextNode(data)); } else { script.text = data; } // Use insertBefore instead of appendChild to circumvent an IE6 bug. // This arises when a base node is used (#2709). head.insertBefore(script, head.firstChild); head.removeChild(script); } } //return current time function now() { return (new Date()).getTime(); } function handleError(s, xhr, status, e) { // If a local callback was specified, fire it if (s.error) { s.error.call(s.context, xhr, status, e); } } function handleSuccess(s, xhr, status, data) { // If a local callback was specified, fire it and pass it the data if (s.success) { s.success.call(s.context, data, status, xhr); } } function handleComplete(s, xhr, status) { // Process result if (s.complete) { s.complete.call(s.context, xhr, status); } } // Determines if an XMLHttpRequest was successful or not function httpSuccess(xhr) { try { // IE error sometimes returns 1223 when it should be 204 so treat it as success, see #1450 return !xhr.status && location.protocol === "file:" || xhr.status >= 200 && xhr.status < 300 || xhr.status === 304 || xhr.status === 1223; } catch (e) { } return false; } // Determines if an XMLHttpRequest returns NotModified function httpNotModified(xhr, url) { var lastModified = xhr.getResponseHeader("Last-Modified"), etag = xhr.getResponseHeader("Etag"); if (lastModified) { lastModified[url] = lastModified; } if (etag) { etag[url] = etag; } return xhr.status === 304; } function httpData(xhr, type, s) { var ct = xhr.getResponseHeader("content-type") || "", xml = type === "xml" || !type && ct.indexOf("xml") >= 0, data = xml ? xhr.responseXML : xhr.responseText; if (xml && data.documentElement.nodeName === "parsererror") { throw "parsererror"; } // Allow a pre-filtering function to sanitize the response // s is checked to keep backwards compatibility if (s && s.dataFilter) { data = s.dataFilter(data, type); } // The filter can actually parse the response if (typeof data === "string") { // Get the JavaScript object, if JSON is used. if (type === "json" || !type && ct.indexOf("json") >= 0) { data = JSON.parse(data); } else if (type === "script" || !type && ct.indexOf("javascript") >= 0) { globalEval(data); } } return data; } function noop() { } var root = document.documentElement, script = document.createElement("script"), id = "script" + now(), scriptEval = false; script.type = "text/javascript"; try { script.appendChild(document.createTextNode("window." + id + "=1;")); } catch (e) { } root.insertBefore(script, root.firstChild); // Make sure that the execution of code works by injecting a script // tag with appendChild/createTextNode // (IE doesn't support this, fails, and uses .text instead) if (window[id]) { scriptEval = true; delete window[id]; } root.removeChild(script); var class2type = { "[object Array]": "array", "[object Boolean]": "boolean", "[object Date]": "date", "[object Function]": "function", "[object Number]": "number", "[object Object]": "object", "[object RegExp]": "regexp", "[object String]": "string" }, jsc = now(), rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, rselectTextarea = /^(?:select|textarea)/i, rinput = /^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i, rnoContent = /^(?:GET|HEAD)$/, rbracket = /\[\]$/, jsre = /\=\?(&|$)/, rquery = /\?/, rts = /([?&])_=[^&]*/, rurl = /^(\w+:)?\/\/([^\/?#]+)/, r20 = /%20/g, rhash = /#.*$/, rnotwhite = /\S/, lastModified = {}, etag = {}, s = Ajax._extend(Ajax.ajaxSettings, origSettings), jsonp, status, data, type = s.type.toUpperCase(), noContent = rnoContent.test(type); //erase some meaningless symbol s.url = s.url.replace(rhash, ""); // Use original (not extended) context object if it was provided s.context = origSettings && origSettings.context !== null ? origSettings.context : s; // convert data if not already a string if (s.data && s.processData && typeof s.data !== "string") { s.data = param(s.data); } if (s.dataType === "jsonp") { if (type === "GET") { //test whether there is '=?' in URL, if not, splice it if (!jsre.test(s.url)) { s.url += (rquery.test(s.url) ? "&" : "?") + (s.jsonp || "callback") + "=?"; } } //test data else if (!s.data || !jsre.test(s.data)) { s.data = (s.data ? s.data + "&" : "") + (s.jsonp || "callback") + "=?"; } //set datatype to json s.dataType = "json"; } if (s.dataType === "json" && (s.data && jsre.test(s.data) || jsre.test(s.url))) { jsonp = s.jsonpCallback || ("jsonp" + jsc++); // Replace the =? sequence both in the query string and the data if (s.data) { s.data = (s.data + "").replace(jsre, "=" + jsonp + "$1"); } s.url = s.url.replace(jsre, "=" + jsonp + "$1"); // We need to make sure // that a JSONP style response is executed properly s.dataType = "script"; // Handle JSONP-style loading var customJsonp = window[jsonp]; window[jsonp] = function (tmp) { if (isFunction(customJsonp)) { customJsonp(tmp); } else { // Garbage collect window[jsonp] = undefined; try { delete window[jsonp]; } catch (jsonpError) { } } data = tmp; handleSuccess(s, xhr, status, data); handleComplete(s, xhr, status, data); if (head) { head.removeChild(script); } }; } // If data is available, append data to url for GET/HEAD requests if (s.data && noContent) { s.url += (rquery.test(s.url) ? "&" : "?") + s.data; } // Matches an absolute URL, and saves the domain var parts = rurl.exec(s.url), remote = parts && (parts[1] && parts[1].toLowerCase() !== location.protocol || parts[2].toLowerCase() !== location.host); if (s.dataType === "script" && type === "GET" && remote) { var head = document.getElementsByTagName("head")[0] || document.documentElement; var script = document.createElement("script"); if (s.scriptCharset) { script.charset = s.scriptCharset; } script.src = s.url; // Handle Script loading if (!jsonp) { var done = false; // Attach handlers for all browsers script.onload = script.onreadystatechange = function () { if (!done && (!this.readyState || this.readyState === "loaded" || this.readyState === "complete")) { done = true; handleSuccess(s, xhr, status, data); handleComplete(s, xhr, status, data); // Handle memory leak in IE script.onload = script.onreadystatechange = null; if (head && script.parentNode) { head.removeChild(script); } } }; } // Use insertBefore instead of appendChild to circumvent an IE6 bug. // This arises when a base node is used (#2709 and #4378). head.insertBefore(script, head.firstChild); // We handle everything using the script element injection return undefined; } var requestDone = false; // Create the request object var xhr = s.xhr(); if (!xhr) { return; } // Open the socket // Passing null username, generates a login popup on Opera (#2865) if (s.username) { xhr.open(type, s.url, s.async, s.username, s.password); } else { xhr.open(type, s.url, s.async); } // Need an extra try/catch for cross domain requests in Firefox 3 try { // Set content-type if data specified and content-body is valid for this type if ((s.data !== null && !noContent) || (origSettings && origSettings.contentType)) { xhr.setRequestHeader("Content-Type", s.contentType); } // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. if (s.ifModified) { if (lastModified[s.url]) { xhr.setRequestHeader("If-Modified-Since", lastModified[s.url]); } if (etag[s.url]) { xhr.setRequestHeader("If-None-Match", etag[s.url]); } } // Set header so the called script knows that it's an XMLHttpRequest // Only send the header if it's not a remote XHR if (!remote) { xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); } // Set the Accepts header for the server, depending on the dataType xhr.setRequestHeader("Accept", s.dataType && s.accepts[s.dataType] ? s.accepts[s.dataType] + ", */*; q=0.01" : s.accepts._default); } catch (headerError) { } // Allow custom headers/mimetypes and early abort if (s.beforeSend && s.beforeSend.call(s.context, xhr, s) === false) { // close opended socket xhr.abort(); return false; } var onreadystatechange = xhr.onreadystatechange = function (isTimeout) { // The request was aborted if (!xhr || xhr.readyState === 0 || isTimeout === "abort") { // Opera doesn't call onreadystatechange before this point // so we simulate the call if (!requestDone) { handleComplete(s, xhr, status, data); } requestDone = true; if (xhr) { xhr.onreadystatechange = noop; } // The transfer is complete and the data is available, or the request timed out } else if (!requestDone && xhr && (xhr.readyState === 4 || isTimeout === "timeout")) { requestDone = true; xhr.onreadystatechange = noop; status = isTimeout === "timeout" ? "timeout" : !httpSuccess(xhr) ? //really success? "error" : s.ifModified && httpNotModified(xhr, s.url) ? "notmodified" : "success"; var errMsg; if (status === "success") { // Watch for, and catch, XML document parse errors try { // process the data (runs the xml through httpData regardless of callback) data = httpData(xhr, s.dataType, s); //judge responseXML or responseText } catch (parserError) { status = "parsererror"; errMsg = parserError; } } // Make sure that the request was successful or notmodified if (status === "success" || status === "notmodified") { // JSONP handles its own success callback if (!jsonp) { handleSuccess(s, xhr, status, data); } } else { handleError(s, xhr, status, errMsg); } // Fire the complete handlers if (!jsonp) { handleComplete(s, xhr, status, data); } if (isTimeout === "timeout") { xhr.abort(); } // Stop memory leaks if (s.async) { xhr = null; } } }; // Override the abort handler, if we can (IE 6 doesn't allow it, but that's OK) // Opera doesn't fire onreadystatechange at all on abort try { var oldAbort = xhr.abort; xhr.abort = function () { if (xhr) { // oldAbort has no call property in IE7 so // just do it this way, which works in all // browsers Function.prototype.call.call(oldAbort, xhr); } onreadystatechange("abort"); }; } catch (abortError) { } // Timeout checker if (s.async && s.timeout > 0) { setTimeout(function () { // Check to see if the request is still happening if (xhr && !requestDone) { onreadystatechange("timeout"); } }, s.timeout); } // Send the data try { xhr.send(noContent || s.data === null ? null : s.data); } catch (sendError) { handleError(s, xhr, null, sendError); // Fire the complete handlers handleComplete(s, xhr, status, data); } // firefox 1.5 doesn't fire statechange for sync requests if (!s.async) { onreadystatechange(); } // return XMLHttpRequest to allow aborting the request etc. return xhr; }; //merge multi objects into the first object and return Ajax._extend = function () { var target = arguments[0] || {}, i, length = arguments.length, options, src, copy, clone, name; for (i = 1; i < length; ++i) { // Only deal with non-null/undefined values if ((options = arguments[i]) !== null) { // Extend the base object for (name in options) { src = target[name]; copy = options[name]; // Prevent never-ending loop if (target === copy) { continue; } // Recurse if we're merging objects if (typeof(copy) == "object") { clone = (src && typeof(src) == "object" ? src : {}); target[name] = Ajax._extend(clone, copy); } // Don't bring in undefined values else if (copy !== undefined) { target[name] = copy; } } } } // Return the modified object return target; }; C.ajax = Ajax; C.ajax.get = function(url,data,callback){ C.ajax.send({ url: url, type:'GET', data:data, error:function(xhr,err){ callback?callback(err,null):0; }, success:function(data){ callback?callback(null,data):0; } }); }; C.ajax.post = function(url,data,callback){ C.ajax.send({ url: url, type:'POST', data:data, error:function(xhr,err){ callback?callback(err,null):0; }, success:function(data){ callback?callback(null,data):0; } }); }; })();