GET /AJAXEngine/S02_AJAXCoreSamples/AJAXComparison.htm HTTP/1.1
Accept: */*
Accept-Language: en,de;q=0.5
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1.1.4322)
Host: www.mathertel.de
Proxy-Connection: Keep-Alive
Pragma: no-cache
Cookie: albumhandlerparams=thumbnailsize=140&previewsize=320
HTTP/1.1 200 OK
Age: 0
Date: Sat, 04 Feb 2006 11:10:43 GMT
Content-Length: 1154
Content-Type: text/html
Last-Modified: Sat, 04 Feb 2006 11:26:26 GMT
ETag: "395224d67d29c61:689"
Server: Microsoft-IIS/6.0
X-Powered-By: ASP.NET
MicrosoftOfficeWebServer: 5.0_Pub
AJAXComparison.htm
AJAXComparison
See AJAXComparison_Doku.htm
------------------------------------------------------------------
GET /AJAXEngine/ajaxcore/ajax.js HTTP/1.1
Accept: */*
Referer: http://www.mathertel.de/AJAXEngine/S02_AJAXCoreSamples/AJAXComparison.htm
Accept-Language: en,de;q=0.5
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1.1.4322)
Host: www.mathertel.de
Proxy-Connection: Keep-Alive
Pragma: no-cache
Cookie: albumhandlerparams=thumbnailsize=140&previewsize=320
HTTP/1.1 200 OK
Age: 0
Date: Sat, 04 Feb 2006 11:10:43 GMT
Content-Length: 21876
Content-Type: application/x-javascript
Last-Modified: Sat, 31 Dec 2005 13:00:46 GMT
ETag: "524dea36aec61:689"
Server: Microsoft-IIS/6.0
X-Powered-By: ASP.NET
MicrosoftOfficeWebServer: 5.0_Pub
// ajax.js
// Common Javascript methods and global objects
// Ajax framework for Internet Explorer (6.0, ...) and Firefox (1.0, ...)
// Copyright by Matthias Hertel, http://www.mathertel.de
// This work is licensed under a Creative Commons Attribution 2.0 Germany License.
// See http://creativecommons.org/licenses/by/2.0/de/
// More information on: http://ajaxaspects.blogspot.com/ and http://ajaxaspekte.blogspot.com/
// -----
// 05.06.2005 created by Matthias Hertel.
// 19.06.2005 minor corrections to webservices.
// 25.06.2005 ajax action queue and timing.
// 02.07.2005 queue up actions fixed.
// 10.07.2005 ajax.timeout
// 10.07.2005 a option object that is passed from ajax.Start() to prepare() is also queued.
// 10.07.2005 a option object that is passed from ajax.Start() to prepare(), finish()
// and onException() is also queued.
// 12.07.2005 correct xml encoding when CallSoap()
// 20.07.2005 more datatypes and XML Documents
// 20.07.2005 more datatypes and XML Documents fixed
// 06.08.2005 caching implemented.
// 07.08.2005 bugs fixed, when queuing without a delay time.
// 04.09.2005 bugs fixed, when entering non-multiple actions.
// 07.09.2005 proxies.IsActive added
// 27.09.2005 fixed error in handling bool as a datatype
// 13.12.2005 WebServices with arrays on strings, ints, floats and booleans - still undocumented
// 27.12.2005 fixed: empty string return values enabled.
// 27.12.2005 enable the late binding of proxy methods.
// ----- global variable for the proxies to webservices. -----
/// The root object for the proxies to webservices.
var proxies = new Object();
proxies.current = null; // the current active webservice call.
proxies.xmlhttp = null; // The current active xmlhttp object.
// ----- global variable for the ajax engine. -----
/// The root object for the ajax engine.
var ajax = new Object();
ajax.current = null; /// The current active AJAX action.
ajax.option = null; /// The options for the current active AJAX action.
ajax.queue = new Array(); /// The pending AJAX actions.
ajax.options = new Array(); /// The options for the pending AJAX actions.
ajax.timer = null; /// The timer for delayed actions.
// ----- AJAX engine and actions implementation -----
///Start an AJAX action by entering it into the queue
ajax.Start = function (action, options) {
ajax.Add(action, options);
// check if the action should start
if ((ajax.current == null) && (ajax.timer == null))
ajax._next(false);
} // ajax.Start
///Start an AJAX action by entering it into the queue
ajax.Add = function (action, options) {
if (action == null) {
alert("ajax.Start: Argument action must be set.");
return;
} else if (typeof(action.call) == "string") {
// enable the late binding of proxy methods
action.call = eval(action.call);
} // if
if ((action.queueClear != null) && (action.queueClear == true)) {
ajax.queue = new Array();
ajax.options = new Array();
} else if ((ajax.queue.length > 0) && ((action.queueMultiple == null) || (action.queueMultiple == false))) {
// remove existing action entries from the queue and clear a running timer
if ((ajax.timer != null) && (ajax.queue[0] == action)) {
window.clearTimeout(ajax.timer);
ajax.timer = null;
} // if
var n = 0;
while (n < ajax.queue.length) {
if (ajax.queue[n] == action) {
ajax.queue.splice(n, 1);
ajax.options.splice(n, 1);
} else {
n++;
} // if
} // while
} // if
if ((action.queueTop == null) || (action.queueTop == false)) {
// to the end.
ajax.queue.push(action);
ajax.options.push(options);
} else {
// to the top
ajax.queue.unshift(action);
ajax.options.unshift(options);
} // if
} // ajax.Add
///Check, if the next AJAX action can start.
///This is an internal method that should not be called from external.
///for private use only.
ajax._next = function (forceStart) {
var ca = null // current action
var co = null // current opptions
var data = null;
if (ajax.current != null)
return; // a call is active: wait more time
if (ajax.timer != null)
return; // a call is pendig: wait more time
if (ajax.queue.length == 0)
return; // nothing to do.
ca = ajax.queue[0];
co = ajax.options[0];
if ((forceStart == true) || (ca.delay == null) || (ca.delay == 0)) {
// start top action
ajax.current = ca;
ajax.queue.shift();
ajax.option = co;
ajax.options.shift();
// get the data
if (ca.prepare != null)
try {
data = ca.prepare(co);
} catch (ex) { }
if (ca.call == null) {
// no call
ajax.Finsh(data);
} else {
// start the call
ca.call.func = ajax.Finsh;
ca.call.onException = ajax.Exception;
ca.call(data);
// start timeout timer
if (ca.timeout != null)
ajax.timer = window.setTimeout(ajax.Cancel, ca.timeout * 1000);
} // if
} else {
// start a timer and wait
ajax.timer = window.setTimeout(ajax.EndWait, ca.delay);
} // if
} // ajax._next
///The delay time of an action is over.
ajax.EndWait = function() {
ajax.timer = null;
ajax._next(true);
} // ajax.EndWait
///The current action timed out.
ajax.Cancel = function() {
proxies.cancel(false); // cancel the current webservice call.
ajax.timer = null;
ajax.current = null;
ajax.option = null;
window.setTimeout(ajax._next, 200); // give some to time to cancel the http connection.
} // ajax.Cancel
///Finish an AJAX Action the normal way
ajax.Finsh = function (data) {
// clear timeout timer if set
if (ajax.timer != null) {
window.clearTimeout(ajax.timer);
ajax.timer = null;
} // if
// use the data
try {
if ((ajax.current != null) && (ajax.current.finish != null))
ajax.current.finish(data, ajax.option);
} catch (ex) { }
// reset the running action
ajax.current = null;
ajax.option = null;
ajax._next(false)
} // ajax.Finsh
///Finish an AJAX Action with an exception
ajax.Exception = function (ex) {
// use the data
if (ajax.current.onException != null)
ajax.current.onException(ex, ajax.option);
// reset the running action
ajax.current = null;
ajax.option = null;
} // ajax.Exception
///Clear the current and all pending AJAX actions.
ajax.CancelAll = function () {
ajax.Cancel();
// clear all pending AJAX actions in the queue.
ajax.queue = new Array();
ajax.options = new Array();
} // ajax.CancelAll
// ----- webservice proxy implementation -----
///Execute a soap call.
///Build the xml for the call of a soap method of a webservice
///and post it to the server.
proxies.callSoap = function (args) {
var p = args.callee;
var x = null;
// check for existing cache-entry
if (p._cache != null) {
if ((p.params.length == 1) && (args.length == 1) && (p._cache[args[0]] != null)) {
if (p.func != null) {
p.func(p._cache[args[0]]);
return(null);
} else {
return(p._cache[args[0]]);
} // if
} else {
p._cachekey = args[0];
}// if
} // if
proxies.current = p;
try {
x = new ActiveXObject("Msxml2.XMLHTTP");
} catch (e) { }
if (x == null) {
try {
x = new ActiveXObject("Microsoft.XMLHTTP");
} catch (e) { }
} // if
// Gecko / Mozilla / Firefox
if ((x == null) && (typeof(XMLHttpRequest) != "undefined"))
x = new XMLHttpRequest();
proxies.xmlhttp = x;
// envelope start
var soap = ""
+ ""
+ ""
+ "<" + p.fname + " xmlns='" + p.service.ns + "'>";
// parameters
for (n = 0; (n < p.params.length) && (n < args.length); n++) {
var val = args[n];
var typ = p.params[n].split(':');
if ((typ.length == 1) || (typ[1] == "string")) {
val = String(args[n]).replace(/&/g, "&").replace(//g, ">");
} else if (typ[1] == "int") {
val = parseInt(args[n]);
} else if (typ[1] == "float") {
val = parseFloat(args[n]);
} else if ((typ[1] == "x") && (typeof(args[n]) == "string")) {
val = args[n];
} else if ((typ[1] == "x") && (typeof(XMLSerializer) != "undefined")) {
val = (new XMLSerializer()).serializeToString(args[n].firstChild);
} else if (typ[1] == "x") {
val = args[n].xml;
} else if ((typ[1] == "bool") && (typeof(args[n]) == "string")) {
val = args[n].toLowerCase();
} else if (typ[1] == "bool") {
val = String(args[n]).toLowerCase();
} else if (typ[1] == "date") {
// calculate the xml format for datetime objects from a javascript date object
var s, ret;
ret = String(val.getFullYear());
ret += "-";
s = String(val.getMonth() + 1);
ret += (s.length == 1 ? "0" + s : s);
ret += "-";
s = String(val.getDate() + 1);
ret += (s.length == 1 ? "0" + s : s);
ret += "T";
s = String(val.getHours() + 1);
ret += (s.length == 1 ? "0" + s : s);
ret += ":";
s = String(val.getMinutes() + 1);
ret += (s.length == 1 ? "0" + s : s);
ret += ":";
s = String(val.getSeconds() + 1);
ret += (s.length == 1 ? "0" + s : s);
val = ret;
} else if (typ[1] == "s[]") {
val = "" + args[n].join("") + "";
} else if (typ[1] == "int[]") {
val = "" + args[n].join("") + "";
} else if (typ[1] == "float[]") {
val = "" + args[n].join("") + "";
} else if (typ[1] == "bool[]") {
val = "" + args[n].join("") + "";
} // if
soap += "<" + typ[0] + ">" + val + "" + typ[0] + ">"
} // for
// envelope end
soap += "" + p.fname + ">"
+ ""
+ "";
// enable cookieless sessions:
var u = p.service.url;
var cs = document.location.href.match(/\/\(.*\)\//);
if (cs != null) {
u = p.service.url.split('/');
u[3] += cs[0].substr(0, cs[0].length-1);
u = u.join('/');
} // if
x.open("POST", u, (p.func != null));
x.setRequestHeader("SOAPAction", p.action);
x.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
if (p.corefunc != null) {
// async call with xmlhttp-object as parameter
x.onreadystatechange = p.corefunc;
x.send(soap);
} else if (p.func != null) {
// async call
x.onreadystatechange = proxies._response;
x.send(soap);
} else {
// sync call
x.send(soap);
return(proxies._response());
} // if
} // proxies.callSoap
// cancel the running webservice call.
// raise: set raise to false to prevent raising an exception
proxies.cancel = function(raise) {
var cc = proxies.current;
var cx = proxies.xmlhttp;
if (raise == null) raise == true;
if (proxies.xmlhttp != null) {
proxies.xmlhttp.onreadystatechange = function() { };
proxies.xmlhttp.abort();
if (raise && (proxies.current.onException != null))
proxies.current.onException("WebService call was canceled.")
proxies.current = null;
proxies.xmlhttp = null;
} // if
} // proxies.cancel
// px is a proxies.service.func object !
proxies.EnableCache = function (px) {
// attach an empty _cache object.
px._cache = new Object();
} // proxies.EnableCache
// check, if a call is currently waiting for a result
proxies.IsActive = function () {
return(proxies.xmlhttp != null);
} // proxies.IsActive
///Callback method for a webservice call that dispatches the response to servive.func or service.onException.
///for private use only.
proxies._response = function () {
var ret = null;
var x = proxies.xmlhttp;
var cc = proxies.current;
var rtype = cc.rtype[0].split(':');
if ((x != null) && (x.readyState == 4)) {
if (x.status == 200) {
var xNode = x.responseXML.getElementsByTagName(rtype[0])[0];
if (xNode == null) {
ret = null;
} else if (xNode.firstChild == null) { // 27.12.2005: empty string return values
ret = ((rtype.length == 1) || (rtype[1] == "string") ? "" : null);
} else if ((rtype.length == 1) || (rtype[1] == "string")) {
ret = (xNode.textContent ? xNode.textContent : xNode.text);
} else if (rtype[1] == "bool") {
ret = ((xNode.textContent ? xNode.textContent : xNode.text).toLowerCase() == "true");
} else if (rtype[1] == "int") {
ret = parseInt(xNode.textContent ? xNode.textContent : xNode.text);
} else if (rtype[1] == "float") {
ret = parseFloat(xNode.textContent ? xNode.textContent : xNode.text);
} else if ((rtype[1] == "x") && (typeof(XMLSerializer) != "undefined")) {
ret = (new XMLSerializer()).serializeToString(xNode.firstChild);
ret = ajax._getXMLDOM(ret);
} else if ((rtype[1] == "ds") && (typeof(XMLSerializer) != "undefined")) {
// ret = (new XMLSerializer()).serializeToString(xNode.firstChild.nextSibling.firstChild);
ret = (new XMLSerializer()).serializeToString(xNode);
ret = ajax._getXMLDOM(ret);
} else if (rtype[1] == "x") {
ret = xNode.firstChild.xml;
ret = ajax._getXMLDOM(ret);
} else if (rtype[1] == "ds") {
// ret = xNode.firstChild.nextSibling.firstChild.xml;
ret = xNode.xml;
ret = ajax._getXMLDOM(ret);
} else if (rtype[1] == "s[]") {
// Array of strings
ret = new Array();
xNode = xNode.firstChild;
while (xNode != null) {
ret.push(xNode.textContent ? xNode.textContent : xNode.text);
xNode = xNode.nextSibling;
} // while
} else if (rtype[1] == "int[]") {
// Array of int
ret = new Array();
xNode = xNode.firstChild;
while (xNode != null) {
ret.push(parseInt(xNode.textContent ? xNode.textContent : xNode.text));
xNode = xNode.nextSibling;
} // while
} else if (rtype[1] == "float[]") {
// Array of float
ret = new Array();
xNode = xNode.firstChild;
while (xNode != null) {
ret.push(parseFloat(xNode.textContent ? xNode.textContent : xNode.text));
xNode = xNode.nextSibling;
} // while
} else if (rtype[1] == "bool[]") {
// Array of bool
ret = new Array();
xNode = xNode.firstChild;
while (xNode != null) {
ret.push((xNode.textContent ? xNode.textContent : xNode.text).toLowerCase() == "true");
xNode = xNode.nextSibling;
} // while
} else {
ret = (xNode.textContent ? xNode.textContent : xNode.text);
} // if
// store to _cache
if ((cc._cache != null) && (cc._cachekey != null)) {
cc._cache[cc._cachekey] = ret;
cc._cachekey = null;
} // if
proxies.xmlhttp = null;
proxies.current = null;
if (cc.func == null) {
return(ret); // sync
} else {
cc.func(ret); // async
return(null);
} // if
} else if (proxies.current.onException == null) {
// no exception
} else {
// raise an exception
ret = new Error();
if (x.status == 404) {
ret.message = "The webservice could not be found.";
} else if (x.status == 500) {
ret.name = "SoapException";
var n = x.responseXML.documentElement.firstChild.firstChild.firstChild;
while (n != null) {
if (n.nodeName == "faultcode") ret.message = n.firstChild.nodeValue;
if (n.nodeName == "faultstring") ret.description = n.firstChild.nodeValue;
n = n.nextSibling;
} // while
} else if ((x.status == 502) || (x.status == 12031)) {
ret.message = "The server could not be found.";
} else {
// no classified response.
ret.message = "Result-Status:" + x.status + "\n" + x.responseText;
} // if
proxies.current.onException(ret);
} // if
proxies.xmlhttp = null;
proxies.current = null;
} // if
} // proxies._response
///Callback method to show the result of a soap call in an alert box.
///To set up a debug output in an alert box use:
///proxies.service.method.corefunc = proxies.alertResult;
proxies.alertResult = function () {
var x = proxies.xmlhttp;
if (x.readyState == 4) {
if (x.status == 200) {
if (x.responseXML.documentElement.firstChild.firstChild.firstChild == null)
alert("(no result)");
else
alert(x.responseXML.documentElement.firstChild.firstChild.firstChild.firstChild.nodeValue);
} else if (x.status == 404) { alert("Error!\n\nThe webservice could not be found.");
} else if (x.status == 500) {
// a SoapException
var ex = new Error();
ex.name = "SoapException";
var n = x.responseXML.documentElement.firstChild.firstChild.firstChild;
while (n != null) {
if (n.nodeName == "faultcode") ex.message = n.firstChild.nodeValue;
if (n.nodeName == "faultstring") ex.description = n.firstChild.nodeValue;
n = n.nextSibling;
} // while
alert("The server threw an exception.\n\n" + ex.message + "\n\n" + ex.description);
} else if (x.status == 502) { alert("Error!\n\nThe server could not be found.");
} else {
// no classified response.
alert("Result-Status:" + x.status + "\n" + x.responseText);
} // if
proxies.xmlhttp = null;
proxies.current = null;
} // if
} // proxies.alertResult
///Show all the details of the returned data of a webservice call.
///Use this method for debugging transmission problems.
///To set up a debug output in an alert box use:
///proxies.service.method.corefunc = proxies.alertResponseText;
proxies.alertResponseText = function () {
if (proxies.xmlhttp.readyState == 4)
alert("Status:" + proxies.xmlhttp.status + "\nRESULT:" + proxies.xmlhttp.responseText);
} // proxies.alertResponseText
///show the details about an exception.
proxies.alertException = function(ex) {
var s = "Exception:\n\n";
if (ex.constructor == String) {
s = ex;
} else {
if ((ex.name != null) && (ex.name != ""))
s += "Type: " + ex.name + "\n\n";
if ((ex.message != null) && (ex.message != ""))
s += "Message:\n" + ex.message + "\n\n";
if ((ex.description != null) && (ex.description != "") && (ex.message != ex.description))
s += "Description:\n" + ex.description + "\n\n";
} // if
alert(s);
} // proxies.alertException
///Get a browser specific implementation of the XMLDOM object, containing a XML document.
///the xml document as string.
ajax._getXMLDOM = function (xmlText) {
var obj = null;
if ((document.implementation != null) && (typeof document.implementation.createDocument == "function")) {
// Gecko / Mozilla / Firefox
var parser = new DOMParser();
obj = parser.parseFromString(xmlText, "text/xml");
} else {
// IE
try {
obj = new ActiveXObject("MSXML2.DOMDocument");
} catch (e) { }
if (obj == null) {
try {
obj = new ActiveXObject("Microsoft.XMLDOM");
} catch (e) { }
} // if
if (obj != null) {
obj.async = false;
obj.validateOnParse = false;
} // if
obj.loadXML(xmlText);
} // if
return(obj);
} // _getXMLDOM
///show the details of a javascript object.
///This helps a lot while developing and debugging.
function inspectObj(obj) {
var s = "InspectObj:";
if (obj == null) {
s = "(null)"; alert(s); return;
} else if (obj.constructor == String) {
s = "\"" + obj + "\"";
} else if (obj.constructor == Array) {
s += " _ARRAY";
} else if (typeof(obj) == "function") {
s += " [function]" + obj;
} else if ((typeof(XMLSerializer) != "undefined") && (obj.constructor == XMLDocument)) {
s = "[XMLDocument]:\n" + (new XMLSerializer()).serializeToString(obj.firstChild);
alert(s); return;
} else if ((obj.constructor == null) && (typeof(obj) == "object") && (obj.xml != null)) {
s = "[XML]:\n" + obj.xml;
alert(s); return;
}
for (p in obj) {
try {
if (obj[p] == null) {
s += "\n" + String(p) + " (...)";
} else if (typeof(obj[p]) == "function") {
s += "\n" + String(p) + " [function]";
} else if (obj[p].constructor == Array) {
s += "\n" + String(p) + " [ARRAY]: " + obj[p];
for (n = 0; n < obj[p].length; n++)
s += "\n " + n + ": " + obj[p][n];
} else {
s += "\n" + String(p) + " [" + typeof(obj[p]) + "]: " + obj[p];
} // if
} catch (e) { s+= e;}
} // for
alert(s);
} // inspectObj
// ----- End -----
------------------------------------------------------------------
GET /AJAXEngine/ajaxcore/GetJavaScriptProxy.aspx?service=/AJAXEngine/S02_AJAXCoreSamples/AJAXComparison.asmx HTTP/1.1
Accept: */*
Referer: http://www.mathertel.de/AJAXEngine/S02_AJAXCoreSamples/AJAXComparison.htm
Accept-Language: en,de;q=0.5
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1.1.4322)
Host: www.mathertel.de
Proxy-Connection: Keep-Alive
Pragma: no-cache
Cookie: albumhandlerparams=thumbnailsize=140&previewsize=320
HTTP/1.1 200 OK
Date: Sat, 04 Feb 2006 11:10:43 GMT
Content-Length: 728
Content-Type: text/text; charset=utf-8
Cache-Control: private
Server: Microsoft-IIS/6.0
X-Powered-By: ASP.NET
MicrosoftOfficeWebServer: 5.0_Pub
X-AspNet-Version: 2.0.50727
// javascript proxy for webservices
// by Matthias Hertel
/* A WebService that returns the server date. */
proxies.AJAXComparision = {
url: "http://www.mathertel.de/AJAXEngine/S02_AJAXCoreSamples/AJAXComparison.asmx",
ns: "http://www.mathertel.de/AJAXComparisation/"
} // proxies.AJAXComparision
/** Return the server date&time. */
proxies.AJAXComparision.Now = function () { return(proxies.callSoap(arguments)); }
proxies.AJAXComparision.Now.fname = "Now";
proxies.AJAXComparision.Now.service = proxies.AJAXComparision;
proxies.AJAXComparision.Now.action = "\"http://www.mathertel.de/AJAXComparisation/Now\"";
proxies.AJAXComparision.Now.params = ["x"];
proxies.AJAXComparision.Now.rtype = ["NowResult"];
------------------------------------------------------------------
POST /AJAXEngine/S02_AJAXCoreSamples/AJAXComparison.asmx HTTP/1.1
Accept: */*
Accept-Language: en,de;q=0.5
Referer: http://www.mathertel.de/AJAXEngine/S02_AJAXCoreSamples/AJAXComparison.htm
soapaction: "http://www.mathertel.de/AJAXComparisation/Now"
Content-Type: text/xml; charset=utf-8
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1.1.4322)
Host: www.mathertel.de
Content-Length: 220
Proxy-Connection: Keep-Alive
Pragma: no-cache
Cookie: albumhandlerparams=thumbnailsize=140&previewsize=320
null
HTTP/1.1 200 OK
Date: Sat, 04 Feb 2006 11:10:49 GMT
Content-Length: 367
Content-Type: text/xml; charset=utf-8
Cache-Control: private, max-age=0
Server: Microsoft-IIS/6.0
X-Powered-By: ASP.NET
MicrosoftOfficeWebServer: 5.0_Pub
X-AspNet-Version: 2.0.50727
2/4/2006 12:27:31 PM
------------------------------------------------------------------