/** * AJAX.OOP JavaScript Framework Core Library * Home page, downoads and documentation at http://ajaxoop.org/ * * This source code is compatible with Javascript Compressor * http://javascriptcompressor.com/ * You can use it to minimize code weight and reduce traffic * amount on your website. * * Version 1.0.4 * * Copyright (c) 2008 Mykhailo Stadnyk * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ var $d, $D; $d = $D = document; var $b, $B; $b = $B = document.body; var $n, $N; $n = $N = navigator; $n.ua = $N.ua = $n.userAgent.toLowerCase(); // if function $ already defined - write it to _$ holder if ($) var _$ = $; var $ = function( id) { if (Ajax.isElement( id)) return id; var el = null; if (arguments.length > 1) { for (var i = 0, els = [], length = arguments.length; i < length; i++) { els.push( $( arguments[i])); } return els; } return $d.getElementById( id); }; if (!$$) { var $$ = function( name) { if (arguments.length > 1) { for (var i = 0, els = [], length = arguments.length; i < length; i++) { els.concat( $( arguments[i])); } return els; } var els = $d.getElementsByTagName( name); return els; };}; if (!$A) { var $a, $A; $a = $A = function( obj) { if (!obj) return []; if (obj.toArray) return obj.toArray(); var length = obj.length; var results = new Array( length); while (length--) results[length] = obj[length]; return results; };} else { var $a = $A; }; if (!$w) { var $w, $W; $w = $W = function( string) { if (typeof( string) != 'string') return []; string = string.trim(); return string ? string.split(/\s+/) : []; };} else { var $W = $w; }; String.prototype.has = function( ptrn) { return (this.indexOf( ptrn) > -1); }; Array.prototype.inArray = function( val) { for (var i = 0; i < this.length; i++) if (this[i] == val) return true; return false; }; Array.prototype.uniq = function() { var result = []; for (var i = 0; i < this.length; i++) if (!result.inArray( this[i])) result[result.length] = this[i]; return result; }; Object.$extensions = ['$super','$_super','$_self']; /** * Core object Ajax - namespace for the most classes in this library */ var Ajax = { Version : '1.0.4', Vendor : 'AJAX.OOP', connections : 0, debug : false, // turn it on whenever you need to debug your functionality isStrict : $d.compatMode == "CSS1Compat", isSecure : window.location.href.toLowerCase().indexOf( "https") === 0, /** * Detecting browsers and engines: */ isOpera : (window.opera || ($n.ua.has( "opera"))), isWebKit : $n.ua.has( 'applewebkit/'), isSafari : (/webkit|khtml/).test( $n.ua), isMobileSafari : !!$n.ua.match( /apple.*mobile.*safari/), isIe : !!(window.attachEvent && !window.opera), isIe7 : !window.opera && $n.ua.indexOf( "msie 7") > -1, isGecko : $n.ua.has( "gecko") && !$n.ua.has( "khtml"), isChrome : $n.ua.has( "chrome/") && $n.ua.has( "khtml"), hasXPath : !!$d.evaluate, hasElementExt : !!window.HTMLElement, osWindows : ($n.ua.has( "windows") || $n.ua.has( "win32")), osMac : ($n.ua.has( "macintosh") || $n.ua.has( "mac os x")), osLinux : ($n.ua.has( "linux")), extend : function( dest, src, ext) { var ext = ext || true; for (var i in src) { dest[i] = src[i]; if (ext) Object.$extensions.push( i); } if (ext) Object.$extensions = Object.$extensions.uniq(); return dest; }, /** * Try to resolve conflicts with $ function by using this method */ resolve : function() { if ($.vendor && $.vendor == Ajax.Vendor) return true; if (_$.vendor && _$.vendor == Ajax.Vendor) { var _tmp = $; $ = _$; _$ = _tmp; return true; } if (Ajax.debug) { alert( 'Error: Native AJAX.OOP function $ could not be found!'); } return false; }, /** * Toggle $ function between vendors */ toggle : function() { if (_$) { var _tmp = $; $ = _$; _$ = _tmp; return true; } return false }, toJson : function( obj) { try { if (obj.toJson) return obj.toJson(); var type = typeof obj; switch (type) { case 'undefined': case 'function': case 'unknown': return; case 'number' : case 'boolean': return obj.toString(); } if (Ajax.isNull( obj)) return 'null'; if (obj instanceof Date) { return '"' + obj.getUTCFullYear() + '-' + (obj.getUTCMonth() + 1).toPaddedString(2) + '-' + obj.getUTCDate().toPaddedString(2) + 'T' + obj.getUTCHours().toPaddedString(2) + ':' + obj.getUTCMinutes().toPaddedString(2) + ':' + obj.getUTCSeconds().toPaddedString(2) + 'Z"'; } if (Ajax.isElement( obj)) return; var results = []; for (var property in obj) { var value = Ajax.toJson( obj[property]); if (!Ajax.isUndefined( value)) { results.push( Ajax.toJson( property) + ': ' + value); } } return '{' + results.join(', ') + '}'; } catch (err) { if (Ajax.debug) alert( 'Ajax::toJson() has caught an exception: ' + err); } }, each : function( obj, callback, clean) { var args = $a( arguments); if (Ajax.isObject( obj)) { for (var i in obj) { if (clean || !Object.$extensions.inArray( i)) callback( i); } } else if (Ajax.isArray( obj)) { for (var i = 0, len = obj.length; i < len; i++) callback( i); } }, clone : function( obj) { try { if (Ajax.isObject( obj)) { return Ajax.extend( {}, obj); } else if (Ajax.isArray( obj)) { var result = []; for (var i = 0; i < obj.length; i++) { result[i] = obj[i]; } return result; } else { return obj; } } catch (err) { if (Ajax.debug) alert( 'Ajax::clone() has caught an exception: ' + err); return obj; } }, toQs : function( obj) { if (Ajax.isString( obj)) return this; var result = ''; try { for (var key in this) { var val = this[key]; if (Ajax.isSimple( val)) { result += ((result ? '&' : '') + key + '=' + new String( val)); } } } catch (err) { if (Ajax.debug) alert( 'Ajax::toQs() has caught an exception: ' + err); }; return result; }, /** * Working with types */ isElement : function( obj) { return (obj && obj.nodeType && obj.nodeType == 1) ? true : false; }, isArray : function( obj) { return (obj && obj.constructor === Array); }, isFunction : function( obj) { return (obj && obj.constructor === Function); }, isRegExp : function( obj) { return (obj && obj.constructor === RegExp); }, isString : function( obj) { return (typeof obj == 'string'); }, isNumber : function( obj) { return (typeof obj == 'number'); }, isBoolean : function( obj) { return (typeof obj == 'boolean'); }, isObject : function( obj) { return (obj && !(Ajax.isArray( obj) || Ajax.isFunction( obj) || Ajax.isSimple( obj))); }, isNull : function( obj) { return (obj === null); }, isUndefined : function( obj) { return (obj === 'undefined' || obj === undefined || obj === 'unknown'); }, isSimple : function( obj) { return (Ajax.isString( obj) || Ajax.isNumber( obj) || Ajax.isBoolean( obj) || Ajax.isRegExp( obj)); } }; $.vendor = Ajax.Vendor; /** * OOP implementation: classes definition, inheritance, polymorphism */ Ajax.Class = function() { /** * Parsing passed arguments, defining variables */ var _args = $a( arguments), parent = (_args.length > 1 ? _args.shift() : null), declaration = _args.shift() || {}, constructor = 'constructor'; /** * Check if passed arguments are valid */ if (parent && !Ajax.isFunction( parent)) { throw new TypeError( 'in class definition - first argument is not a parent class constructor!'); } if (declaration && (!Ajax.isObject( declaration) || declaration == window || declaration == $d || declaration == $b || Ajax.isElement( declaration))) { throw new TypeError( 'in class definition - ' + (_args.length > 1 ? 'second' : 'passed') + ' argument is not a class declaration object!'); } /** * Dynamically creating constructor for class */ var _class = function() { this[constructor].apply( this, $a( arguments)); }; /** * Inherit all parent properties */ if (parent) { var _parent = Function.blank; _parent.prototype = parent.prototype; _class.prototype = new _parent; } /** * Writing class definition (polymorphism icluded) */ if (declaration) { for (var property in declaration) { _class.prototype[property] = declaration[property]; } // line below is to fix bug with constructors in IE _class.prototype[constructor] = declaration[constructor] || Function.blank; } /** * Defining special accessors-properties, which can be obtained in classes declaration * as this.$parent, this.$_parent and this.$_self get access to manipulating with * parent class its methods and properties */ _class.prototype.$super = parent ? parent.prototype : Function.blank.prototype; _class.prototype.$_super = parent ? parent : Function.blank; _class.prototype.$_self = _class; /** * Returns dynamically created constructor */ return _class; }; Ajax.extend( Function.prototype, { blank : function() {}, bind : function() { if (arguments.length < 2 && Ajax.isUndefined( arguments[0])) return this; var f = this, args = $a( arguments), object = args.shift(); return function() { return f.apply( object, args.concat( $a( arguments))); }; } }, true); Ajax.extend( RegExp.prototype, { match : function() { return this.test.apply( this, $a( arguments)); }, escape : function( str) { return String( str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1'); } }, true); Ajax.extend( String.prototype, { toJson : function() { return ('"' + this + '"'); }, isJson : function() { var str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''); return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str); }, evalJson : function() { if (this.isJson()) return eval( '(' + this + ')'); return ''; }, ucFirst : function () { return this.substr( 0, 1).toUpperCase() + this.substr( 1, this.length); }, qsHash : function( separator) { var hash = {}; var match = this.trim().match(/([^?#]*)(#.*)?$/); if (!match) return { }; var pair = match[1].split( separator || '&'); for (var i = 0; i < pair.length; i++) { var param = pair[i].split( '='); if (param.length == 2) { hash[param[0]] = param[1]; } } return hash; }, trim : function() { return this.replace( /(^\s+|\s+$)/g, ''); }, strip : function() { return this.replace( /<.*?>/g, ''); }, toArray : function() { return this.split(''); }, qw : $w }, true); RegExp .prototype.toJson = Boolean .prototype.toJson = Number .prototype.toJson = String .prototype.toJson ; Ajax.extend( Array.prototype, { revoke : function( value) { var result = []; for (var i = 0; i < this.length; i++) { if (this[i] != value) { result[result.length] = this[i]; } } return result; }, toJson : function() { var results = []; for (var i = 0; i < this.length; i++) { results[i] = Ajax.toJson( this[i]); } return '[' + results.join(', ') + ']'; }, first : function() { return this[0]; }, last : function() { return this[this.length]; }, clear : function() { this.length = 0; return this; } }, true); /** * Backward compatibility - DEPRECATED methods and properties * {{{ */ Ajax.extend( Ajax, { Opera : Ajax.isOpera, WebKit : Ajax.isWebKit, Safari : Ajax.isSafari, MobileSafari : Ajax.isMobileSafari, IE : Ajax.isIe, IE7 : Ajax.isIe7, Gecko : Ajax.isGecko, Chrome : Ajax.isChrome, Resolve : Ajax.resolve, Toggle : Ajax.toggle, Extend : Ajax.extend, toJSON : Ajax.toJson }); Ajax.extend( String.prototype, { toJSON : String.prototype.toJson, isJSON : String.prototype.isJson, evalJSON : String.prototype.evalJson }); Array.prototype.toJSON = Array.prototype.toJson; /** * }}} */ /** * Ajax transport object types */ AJAX_TRANSPORT_TYPE_XHR = 0; AJAX_TRANSPORT_TYPE_SCRIPT = 1; /** * Emulating XHR (XmlHttpRequest) object. * Actually this class is interface wrapper to XHR or other objects, * which are used as transport gateways for handling AJAX requests */ Ajax.Transport = Ajax.Class({ /** * @access private */ _transport : null, _availableTypes : ['xhr', 'script'], _type : AJAX_TRANSPORT_TYPE_XHR, _connId : 0, _head : null, _callbackName : '_ajaxHttpRequest', _currName : '', _update : function() { var t = this._transport; for (var i in t) { try { if ( !Ajax.isFunction( t[i]) &&i != 'onreadystatechange' //<- specially for Firefox :( ) this[i] = t[i]; } catch( e) { continue; } } this.onreadystatechange.apply( this, $a( arguments)); }, _createCallback : function( scope) { var t = this; var scope = scope || window; scope[this.getCallbackName()] = function( responseText) { t.responseText = responseText; } }, /** * @access public but read-only */ readyState : -1, responseText : '', responseXML : '', status : 0, statusText : '', /** * public methods */ open : function( method, url, asynchronous, login, password, callback, scope) { if (!this._transport) return; this._connId = ++Ajax.connections; this._currName = this._callbackName + this._connId; if (!this._transport.open) { this._createCallback( scope); this._transport.setAttribute( 'src', url + (url.has( '?') ? '&' : '?') + callback + '=' + this.getCallbackName() + '&' + Math.random()); this._transport.setAttribute( 'id', this._connId); } else { this._transport.open( method, url, asynchronous, login, password); } }, send : function( postBody) { if (!this._transport) return; var body = postBody || null; if (!this._transport.send) { this.head.appendChild( this._transport); } else { this._transport.send( body); } }, abort : function() { if (!this._transport) return; if (!this._transport.abort) this._transport.abort = Function.blank; this._transport.abort.apply( this._transport, $a( arguments)); }, setRequestHeader : function( name, value) { if (!this._transport) return; if (!this._transport.setRequestHeader) this._transport.setRequestHeader = Function.blank; this._transport.setRequestHeader( name, value); }, getAllResponseHeaders : function() { if (!this._transport) return; if (!this._transport.getAllResponseHeaders) this._transport.getAllResponseHeaders = Function.blank; return this._transport.getAllResponseHeaders(); }, getResponseHeader : function( name) { if (!this._transport) return; if (!this._transport.getResponseHeader) this._transport.getResponseHeader = Function.blank; return this._transport.getResponseHeader( name); }, /** * Default ready state change event handler */ onreadystatechange : Function.blank, /** * Class constructor */ constructor : function( type) { if (type == AJAX_TRANSPORT_TYPE_XHR || type == AJAX_TRANSPORT_TYPE_SCRIPT) { this._type = type; } if (this._type == AJAX_TRANSPORT_TYPE_XHR) { try { this._transport = (new XMLHttpRequest() || new ActiveXObject('Msxml2.XMLHTTP') || new ActiveXObject('Microsoft.XMLHTTP') || null); this._transport.onreadystatechange = this._update.bind( this); this.readyState = 0; } catch( err) { if (Ajax.debug) alert( 'Ajax.Transport::constructor(): Cannot instantiate XHR Object. Exception caught: ' + err); this._transport = null; } } else if (this._type == AJAX_TRANSPORT_TYPE_SCRIPT) { var s = $d.createElement( 'script'); s.setAttribute( 'type', 'text/javascript'); this._transport = s; this.head = $$('head')[0]; this._currName = this._callbackName + this._connId; this._transport.onreadystatechange = this._transport.onload = this._update.bind( this); } }, /** * Extended functionality - public methods */ getType : function() { return this._type; }, getAvailableTypes : function() { return this._availableTypes; }, allow : function() { var args = $a( arguments), param = args.shift(), value = args.shift(); switch (param) { case 'method': { return ((this._type == AJAX_TRANSPORT_TYPE_SCRIPT && value && value != 'get') ? false : true); } case 'status': case 'headers': case 'body' : { return ((this._type == AJAX_TRANSPORT_TYPE_SCRIPT) ? false : true); } } return true; }, getCallbackName : function() { return this._currName; }, getTransport : function() { return this._transport; } }); /** * Ajax.Options - base class for sharing options between requests */ Ajax.Options = Ajax.Class({ method : 'post', asynchronous : true, contentType : 'application/x-www-form-urlencoded', encoding : 'UTF-8', parameters : '', postBody : null, requestHeaders : {}, evalJson : true, evalJs : true, crossdomain : false, events : null, login : null, password : null, callback : 'callback', /** * Class constructor */ constructor : function( options) { var set = false; this.events = new Ajax.Events(); if (Ajax.isObject( options) && !Ajax.isElement( options)) { var obj = this; Ajax.each( options, function( i) { if (!(Ajax.isUndefined( obj[i]) || Ajax.isFunction( obj[i]))) { obj[i] = options[i]; } /** * Bind passed event handlers to options class */ if (/^on[A-Z0-9]/.test( i) && Ajax.isFunction( options[i])) { obj.events.add( i, options[i]); } }); } } }); /** * Ajax.Events - base object for manipulation with events */ Ajax.Events = Ajax.Class({ /** * @access private */ _events : null, /** * Class constructor */ constructor : function() { this._events = {}; }, /** * Adds new event to a list of known events * * @access public * @param String name [required] - event handler name * @param Function handler [required] - event handler */ add : function( name, handler) { if (!this._events[name]) { this._events[name] = handler; } }, /** * Returns event by name if registered otherwise returns emty function * * @access public * @param String name [required] - event handler name * @return Function - event handler */ get : function( name) { if (this.registered( name)) return this._events[name]; return Function.blank; }, /** * Remove event handler by name from known events * * @access public * @param String name [required] - event handler name * @return void */ remove : function( name) { this._events[name] = undefined; }, /** * Checks if even with name already registered * * @access public * @param String name [required] - event handler name * @return Boolean */ registered : function( name) { return (this._events[name] ? true : false); }, /** * Fires event with given name if such event has been registered * and applying all passed arguments as well * * @access public * @param String name [required] - event handler name * @param [Mixed, ...] [optional] - other arguments to the handler * @return void */ fire : function( name) { var args = $a( arguments); args.shift(); if (this.registered( name)) { try { this._events[name].apply( this, args); } catch (e) { if (Ajax.debug) alert( 'Ajax.Events::fire() has caught an exception: ' + err); }; } } }); /** * AJAX requests handling functionality */ Ajax.Request = Ajax.Class( Ajax.Options, { url : '', callback : 'callback', scope : window, transport : null, body : '', /** * Class constructor. * * @param String url - required * @param Object options - optional * @param String type - optional * @param String callback - optional * @param Object scope - optional */ constructor : function( url, options, type, callback, scope) { this.$super.constructor( options); this.url = url; this.callback = Ajax.isString( callback) ? callback : this.callback; this.scope = (Ajax.isObject( scope) || Ajax.isElement( scope)) ? scope : this.scope; this.transport = new Ajax.Transport( type); if (type == AJAX_TRANSPORT_TYPE_SCRIPT) { this.transport.getTransport().charset = this.encoding; } this._request(); }, /** * @access private */ _request : function() { try { /** * Prepere request data */ if (!['get', 'post'].inArray( this.method.toLowerCase())) { this.parameters['_method'] = this.method; this.method = (this.transport.allow( 'method', 'post') ? 'post' : 'get'); } var params = Ajax.toQs( this.parameters); if (params) { if (this.method && this.method.toLowerCase() == 'get') { this.url += ((this.url.has( '?') ? '&' : '?') + params); } else if (/konqueror|safari|khtml/.test( $n.ua)) { params += '&_='; } } if (this.transport.allow( 'body')) { this.body = (this.method == 'post' ? (this.postBody || params) : null); } /** * Dispatch onCreate event */ this.events.fire( 'onCreate', this); /** * Create connection */ this.transport.open( this.method.toUpperCase(), this.url, this.asynchronous, this.login, this.password, this.callback, this.scope); this.transport.onreadystatechange = this._onStateChange.bind( this); /** * Apply request headers */ if (this.transport.allow( 'headers')) { this._applyHeaders(); } /** * Send request */ this.transport.send( this.body); if (!this.asynchronous && this.transport.getTransport().overrideMimeType) { this._onStateChange(); } } catch (e) { if (Ajax.debug) alert( 'Ajax.Request::_request() has caught an exception: ' + err); this.events.fire( 'onException', this, e); } }, _onStateChange : function() { var t = this.transport, _readyState = (t.readyState != -1 ? t.readyState : 4), _state = Ajax.Request.States[_readyState], _status = t.status || 0; if (t.getType() == AJAX_TRANSPORT_TYPE_SCRIPT && Ajax.isString( _readyState)) { _state = _readyState == 'loaded' ? 'Complete' : _readyState.ucFirst(); } if (_state == 'Complete') { if (t.allow( 'status')) { /** * Normal XHR object: */ var s = _status; s = (s == 200 ? 'Success' : 'Failure'); this.events.fire( 'on' + (this.events.registered( 'on' + _status) ? _status : s), t); } else { /** * Something else: */ this.events.fire( (this.events.registered( 'on200') ? 'on200' : 'onSuccess'), t); } } this.events.fire( 'on' + _state, t); if (_state == 'Complete') { t.onreadystatechange = Function.blank; if (t.getType() == AJAX_TRANSPORT_TYPE_SCRIPT) t.head.removeChild( t.getTransport()); this.transport = t = null; } }, _applyHeaders : function() { var headers = { 'X-Requested-With': 'XMLHttpRequest', 'X-Powered-By' : 'AJAX.OOP/' + Ajax.Version, 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' }; if (this.method == 'post') { headers['Content-type'] = this.contentType + (this.encoding ? '; charset=' + this.encoding : ''); if (this.transport.overrideMimeType && ($n.ua.match(/gecko\/(\d{4})/) || [0,2005])[1] < 2005) { headers['Connection'] = 'close'; } } if (Ajax.isObject( this.requestHeaders)) { Ajax.extend( headers, this.requestHeaders); } for (var name in headers) { if (Ajax.isString( headers[name])) { this.transport.setRequestHeader( name, headers[name]); } } } }); Ajax.Request.States = ['Unitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; var Element = function( tag, attr) { if (Ajax.isIe && attr.name) { tag = '<' + tag + ' name="' + attr.name + '">'; delete attr.name; } var el = $d.createElement( tag); if (Ajax.isObject( attr)) { Ajax.each( attr, function( i) { el.setAttribute( i, attr[i]); }); } return Ajax.extend( el, this); };