diff --git a/src/ChatPage/chat/BypassLoginForm/chatOrCallback.html b/src/ChatPage/chat/BypassLoginForm/chatOrCallback.html new file mode 100755 index 0000000..9237279 --- /dev/null +++ b/src/ChatPage/chat/BypassLoginForm/chatOrCallback.html @@ -0,0 +1,58 @@ + + + + + + + + Interaction Web Tools + + + + +
+ +
+ + + + diff --git a/src/ChatPage/chat/BypassLoginForm/img/WebChatHeader.png b/src/ChatPage/chat/BypassLoginForm/img/WebChatHeader.png new file mode 100755 index 0000000..f333cc9 Binary files /dev/null and b/src/ChatPage/chat/BypassLoginForm/img/WebChatHeader.png differ diff --git a/src/ChatPage/chat/BypassLoginForm/img/WebChatHeader_rtl.png b/src/ChatPage/chat/BypassLoginForm/img/WebChatHeader_rtl.png new file mode 100755 index 0000000..490889c Binary files /dev/null and b/src/ChatPage/chat/BypassLoginForm/img/WebChatHeader_rtl.png differ diff --git a/src/ChatPage/chat/BypassLoginForm/img/ball.png b/src/ChatPage/chat/BypassLoginForm/img/ball.png new file mode 100755 index 0000000..ae0ef19 Binary files /dev/null and b/src/ChatPage/chat/BypassLoginForm/img/ball.png differ diff --git a/src/ChatPage/chat/BypassLoginForm/img/check.png b/src/ChatPage/chat/BypassLoginForm/img/check.png new file mode 100755 index 0000000..4f8668d Binary files /dev/null and b/src/ChatPage/chat/BypassLoginForm/img/check.png differ diff --git a/src/ChatPage/chat/BypassLoginForm/img/disconnect.gif b/src/ChatPage/chat/BypassLoginForm/img/disconnect.gif new file mode 100755 index 0000000..b8061f9 Binary files /dev/null and b/src/ChatPage/chat/BypassLoginForm/img/disconnect.gif differ diff --git a/src/ChatPage/chat/BypassLoginForm/img/error.png b/src/ChatPage/chat/BypassLoginForm/img/error.png new file mode 100755 index 0000000..a967815 Binary files /dev/null and b/src/ChatPage/chat/BypassLoginForm/img/error.png differ diff --git a/src/ChatPage/chat/BypassLoginForm/img/message_indicator.png b/src/ChatPage/chat/BypassLoginForm/img/message_indicator.png new file mode 100755 index 0000000..53946aa Binary files /dev/null and b/src/ChatPage/chat/BypassLoginForm/img/message_indicator.png differ diff --git a/src/ChatPage/chat/BypassLoginForm/img/message_indicator_rtl.png b/src/ChatPage/chat/BypassLoginForm/img/message_indicator_rtl.png new file mode 100755 index 0000000..da96fae Binary files /dev/null and b/src/ChatPage/chat/BypassLoginForm/img/message_indicator_rtl.png differ diff --git a/src/ChatPage/chat/BypassLoginForm/img/spinner.gif b/src/ChatPage/chat/BypassLoginForm/img/spinner.gif new file mode 100755 index 0000000..ce2baf8 Binary files /dev/null and b/src/ChatPage/chat/BypassLoginForm/img/spinner.gif differ diff --git a/src/ChatPage/chat/BypassLoginForm/img/triangle.png b/src/ChatPage/chat/BypassLoginForm/img/triangle.png new file mode 100755 index 0000000..529f5ef Binary files /dev/null and b/src/ChatPage/chat/BypassLoginForm/img/triangle.png differ diff --git a/src/ChatPage/chat/BypassLoginForm/img/typing_16x32.png b/src/ChatPage/chat/BypassLoginForm/img/typing_16x32.png new file mode 100755 index 0000000..35844ff Binary files /dev/null and b/src/ChatPage/chat/BypassLoginForm/img/typing_16x32.png differ diff --git a/src/ChatPage/chat/BypassLoginForm/img/typing_16x32_rtl.png b/src/ChatPage/chat/BypassLoginForm/img/typing_16x32_rtl.png new file mode 100755 index 0000000..4ae3dc2 Binary files /dev/null and b/src/ChatPage/chat/BypassLoginForm/img/typing_16x32_rtl.png differ diff --git a/src/ChatPage/chat/BypassLoginForm/img/user.png b/src/ChatPage/chat/BypassLoginForm/img/user.png new file mode 100755 index 0000000..e12702c Binary files /dev/null and b/src/ChatPage/chat/BypassLoginForm/img/user.png differ diff --git a/src/ChatPage/chat/BypassLoginForm/img/user_rtl.png b/src/ChatPage/chat/BypassLoginForm/img/user_rtl.png new file mode 100755 index 0000000..e12702c Binary files /dev/null and b/src/ChatPage/chat/BypassLoginForm/img/user_rtl.png differ diff --git a/src/ChatPage/chat/BypassLoginForm/index.html b/src/ChatPage/chat/BypassLoginForm/index.html new file mode 100755 index 0000000..822141a --- /dev/null +++ b/src/ChatPage/chat/BypassLoginForm/index.html @@ -0,0 +1,54 @@ + + + + Bypass Login Form Example + + + + + + + + +
+
+ + + + + + + + + + + + +
Name:
Password (optional):
+
+
+
+ + + + + + + + + + + + + + + + + + + + +
Name:
Password (optional):
Telephone:
Subject:
+
+ + diff --git a/src/ChatPage/chat/BypassLoginForm/js/Customizations.js b/src/ChatPage/chat/BypassLoginForm/js/Customizations.js new file mode 100755 index 0000000..6391407 --- /dev/null +++ b/src/ChatPage/chat/BypassLoginForm/js/Customizations.js @@ -0,0 +1,264 @@ +/************************************************************************************************ +* This file contains Javascript code authored by Interactive Intelligence, Inc. * +* * +* The contents of this file are warranted to function as intended, provided they are not * +* modified in any way by customers, end users, or other parties. * +* * +* During the course of this product's support lifecycle, Interactive Intelligence, Inc. may * +* publish updates to this file at any time, via an SU or similar process. If other * +* modifications are made to this file, these modifications may therefore be overwritten. * +* * +* Customers are encouraged to extend the functionality provided in this file, by creating * +* additional file(s) which use this file as an API. * +************************************************************************************************/ + +define(['common', 'ui', 'WebServices'], function(common, ui, webservices) +{ + var customizations = common.Type.registerLocalNamespace("customizations"); + +/** + * CustomLoginInfoSource class + * + * In the default installation, a page is shown which allows the user to + * select between tabs for Chat, Callback, and Registration. To create a + * chat, the user must type their name (and optionally, a password) and + * press the "Start Chat" button. To create a callback, the user must type + * their name, telephone number, callback description, (and optionally a + * password), and press the "Start Callback" button. + * + * This is because ui._Internal._DefaultLoginInfoSource is not + * able to get login information from any other source, so the default action is + * to show that page. + * + * This subclass of _DefaultLoginInfoSource obtains login data from form submission + * data, and the login form is therefore not shown. + */ +customizations.CustomLoginInfoSource = Class.create(ui._Internal._DefaultLoginInfoSource, +{ + initialize : function() + { + // Create an instance of the class that is defined just below this one. + // This line could be placed elsewhere if desired, such as in chatOrCallback.html. + var customLifecycleEventsObserverSingleton = new customizations.CustomLifecycleEventsObserver(); + }, + + /** + * Skip the login page, and begin a chat right away using a username + * obtained from the form in this example's index.html + */ + get_chatUsername : function() + { + // This line may be used to extract the value from the query string + return this.get_queryStringValue("chatUsername"); + }, + + /** + * If get_chatUsername() returns non-null, this method may optionally be + * used to return the password of that user. If an anonymous chat is + * desired, simply implement get_chatUsername() but allow get_chatPassword() to + * return null. + */ + get_chatPassword : function() + { + // This line may be used to extract the value from the query string (though that + // is perhaps not a wise place for a password to be!) + return this.get_queryStringValue("chatPassword"); + }, + + /** + * Skip the login page, and begin a callback right away using a username + * obtained from the form in this example's index.html + * Note that if get_chatUsername() also returns a non-null value, that will + * take priority and a chat will be started, not a callback. + */ + get_callbackUsername : function() + { + // This line may be used to extract the value from the query string + return this.get_queryStringValue("callbackUsername"); + }, + + /** + * If get_callbackUsername() returns non-null, this method may optionally be + * used to return the password of that user. If an anonymous callback is + * desired, simply implement get_callbackUsername() but allow get_callbackPassword() to + * return null. + */ + get_callbackPassword : function() + { + // This line may be used to extract the value from the query string (though that + // is perhaps not a wise place for a password to be!) + return this.get_queryStringValue("callbackPassword"); + }, + + /** + * If get_callbackUsername() returns non-null, this method shall return that user's + * telephone number. + */ + get_callbackTelephone : function() + { + // This line may be used to extract the value from the query string + return this.get_queryStringValue("callbackTelephone"); + }, + + /** + * If get_callbackUsername() returns non-null, this method shall return the subject which + * that user wishes to discuss. + */ + get_callbackDescription : function() + { + // This line may be used to extract the value from the query string + return this.get_queryStringValue("callbackDescription"); + }, + + /** + * The purpose of this example is to use usernames, passwords, etc. that were obtained from + * an external source. This is a helper method which uses the query string as that source. + * Customers could easily replace this (or the calls to this) with code that gets the values + * from other sources instead. + * + * @param key A key from a key+value pair in the query string + */ + get_queryStringValue : function(key) + { + var value = common.Utilities.getQueryStringValue(key); + + if (null == value) + { + return null; + } + + // Undo the URL encoding that was done when the form was submitted. + // For instance, change "John+Doe" back to "John Doe" + return decodeURIComponent(value.replace(/\+/g, ' ')); + } +}); + +/** + * CustomLifecycleEventObserver class + * + * In the default implementation, Notifications are sent when a chat or callback is + * created, when a chat ends, and when an attempt to create a chat or callback + * fails. + * + * This class listens for these Notifications and simply pops up some Javascript alerts. + */ +customizations.CustomLifecycleEventsObserver = Class.create(common.InterfaceImplementation, +{ + initialize : function($super) + { + $super(); + this.addImplementedInterface(webservices.Interfaces.IChatCreationNotificationObserver, webservices); + this.addImplementedInterface(webservices.Interfaces.IChatCreationFailureNotificationObserver, webservices); + this.addImplementedInterface(webservices.Interfaces.IChatCompletionNotificationObserver, webservices); + this.addImplementedInterface(webservices.Interfaces.IChatCompletionFailureNotificationObserver, webservices); + this.addImplementedInterface(webservices.Interfaces.ICallbackCreationNotificationObserver, webservices); + this.addImplementedInterface(webservices.Interfaces.ICallbackCreationFailureNotificationObserver, webservices); + webservices.NotificationRegistry.registerObserver(this, webservices.Interfaces.IChatCreationNotification); + webservices.NotificationRegistry.registerObserver(this, webservices.Interfaces.IChatCreationFailureNotification); + webservices.NotificationRegistry.registerObserver(this, webservices.Interfaces.IChatCompletionNotification); + webservices.NotificationRegistry.registerObserver(this, webservices.Interfaces.IChatCompletionFailureNotification); + webservices.NotificationRegistry.registerObserver(this, webservices.Interfaces.ICallbackCreationNotification); + webservices.NotificationRegistry.registerObserver(this, webservices.Interfaces.ICallbackCreationFailureNotification); + }, + + /** + * This method is called when a chat is created successfully. + * + * @param chatCreationNotification An object that implements IChatCreationNotification. (Ignored) + */ + processChatCreationNotification : function(chatCreationNotification) + { + console.debug('Bypass Login Form Customization: Chat created successfully!'); + console.debug(chatCreationNotification); + }, + + /** + * This method is called when an attempt to create a chat fails. + * + * @param chatCreationFailureNotification An object that implements IChatCreationFailureNotification. + */ + processChatCreationFailureNotification : function(chatCreationFailureNotification) + { + var error = chatCreationFailureNotification.get_error(); + alert('Bypass Login Form Customization: Chat creation failed:\n' + + 'Error Source: ' + error.get_errorSource() + + '\nError Type: ' + error.get_errorType() + + '\nError SubType: ' + error.get_subErrorType() + + '\nError Code: ' + error.get_errorCode()); + //history.go(-1); + }, + + /** + * This method is called when a chat is exited successfully. + * + * @param chatCompletionNotification An object that implements IChatCompletionNotification. (Ignored) + */ + processChatCompletionNotification : function(chatCompletionNotification) + { + console.debug('Bypass Login Form Customization: Chat complete!'); + //history.go(-1); + }, + + /** + * This method is called when an attempt to exit a chat fails. + * + * @param chatCompletionFailureNotification An object that implements IChatCompletionFailureNotification. + */ + processChatCompletionFailureNotification : function(chatCompletionFailureNotification) + { + var error = chatCompletionFailureNotification.get_error(); + alert('Bypass Login Form Customization: Chat competion failed:\n' + + 'Error Source: ' + error.get_errorSource() + + '\nError Type: ' + error.get_errorType() + + '\nError SubType: ' + error.get_subErrorType() + + '\nError Code: ' + error.get_errorCode()); + //history.go(-1); + }, + + /** + * This method is called when a callback is created successfully. + * + * @param callbackCreationNotification An object that implements ICallbackCreationNotification. (Ignored) + */ + processCallbackCreationNotification : function(callbackCreationNotification) + { + console.debug('Bypass Login Form Customization: Callback created successfully!'); + //history.go(-1); + }, + + /** + * This method is called when an attempt to create a callback fails. + * + * @param callbackCreationFailureNotification An object that implements ICallbackCreationFailureNotification. + */ + processCallbackCreationFailureNotification : function(callbackCreationFailureNotification) + { + var error = callbackCreationFailureNotification.get_error(); + alert('Bypass Login Form Customization: Callback creation failed:\n' + + 'Error Source: ' + error.get_errorSource() + + '\nError Type: ' + error.get_errorType() + + '\nError SubType: ' + error.get_subErrorType() + + '\nError Code: ' + error.get_errorCode()); + //history.go(-1); + } +}); + + customizations.singletonImplementations = + { + "LoginInfoSource" : customizations.CustomLoginInfoSource, + "MaximumFieldLengths" : ui._Internal._DefaultMaximumFieldLengths, + "RetryCounts" : webservices._Internal._DefaultRetryCounts, + "TabVisibility" : ui._Internal._DefaultTabVisibility, + "StatusFieldsDisplay" : ui._Internal._DefaultStatusFieldsDisplay, + "Linkifier" : webservices._Internal._DefaultLinkifier, + "SendOnEnter" : webservices._Internal._DefaultSendOnEnter + }; + + customizations.nonSingletonImplementations = + { + "RegistrationFormPanel" : ui._Internal._DefaultRegistrationFormPanel + }; + + ININ.Web.Chat.Customizations = customizations; // Temporary shim + return customizations; +}); diff --git a/src/ChatPage/chat/BypassLoginForm/js/External.js b/src/ChatPage/chat/BypassLoginForm/js/External.js new file mode 100755 index 0000000..03535e8 --- /dev/null +++ b/src/ChatPage/chat/BypassLoginForm/js/External.js @@ -0,0 +1,300 @@ +define(function() +{ + +/* + * jQuery JavaScript Library v1.3.2 + * http://jquery.com/ + * + * Copyright (c) 2009 John Resig + * Dual licensed under the MIT and GPL licenses. + * http://docs.jquery.com/License + * + * Date: 2009-02-19 17:34:21 -0500 (Thu, 19 Feb 2009) + * Revision: 6246 + */ +(function(){var l=this,g,y=l.jQuery,p=l.$,o=l.jQuery=l.$=function(E,F){return new o.fn.init(E,F)},D=/^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/,f=/^.[^:#\[\.,]*$/;o.fn=o.prototype={init:function(E,H){E=E||document;if(E.nodeType){this[0]=E;this.length=1;this.context=E;return this}if(typeof E==="string"){var G=D.exec(E);if(G&&(G[1]||!H)){if(G[1]){E=o.clean([G[1]],H)}else{var I=document.getElementById(G[3]);if(I&&I.id!=G[3]){return o().find(E)}var F=o(I||[]);F.context=document;F.selector=E;return F}}else{return o(H).find(E)}}else{if(o.isFunction(E)){return o(document).ready(E)}}if(E.selector&&E.context){this.selector=E.selector;this.context=E.context}return this.setArray(o.isArray(E)?E:o.makeArray(E))},selector:"",jquery:"1.3.2",size:function(){return this.length},get:function(E){return E===g?Array.prototype.slice.call(this):this[E]},pushStack:function(F,H,E){var G=o(F);G.prevObject=this;G.context=this.context;if(H==="find"){G.selector=this.selector+(this.selector?" ":"")+E}else{if(H){G.selector=this.selector+"."+H+"("+E+")"}}return G},setArray:function(E){this.length=0;Array.prototype.push.apply(this,E);return this},each:function(F,E){return o.each(this,F,E)},index:function(E){return o.inArray(E&&E.jquery?E[0]:E,this)},attr:function(F,H,G){var E=F;if(typeof F==="string"){if(H===g){return this[0]&&o[G||"attr"](this[0],F)}else{E={};E[F]=H}}return this.each(function(I){for(F in E){o.attr(G?this.style:this,F,o.prop(this,E[F],G,I,F))}})},css:function(E,F){if((E=="width"||E=="height")&&parseFloat(F)<0){F=g}return this.attr(E,F,"curCSS")},text:function(F){if(typeof F!=="object"&&F!=null){return this.empty().append((this[0]&&this[0].ownerDocument||document).createTextNode(F))}var E="";o.each(F||this,function(){o.each(this.childNodes,function(){if(this.nodeType!=8){E+=this.nodeType!=1?this.nodeValue:o.fn.text([this])}})});return E},wrapAll:function(E){if(this[0]){var F=o(E,this[0].ownerDocument).clone();if(this[0].parentNode){F.insertBefore(this[0])}F.map(function(){var G=this;while(G.firstChild){G=G.firstChild}return G}).append(this)}return this},wrapInner:function(E){return this.each(function(){o(this).contents().wrapAll(E)})},wrap:function(E){return this.each(function(){o(this).wrapAll(E)})},append:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.appendChild(E)}})},prepend:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.insertBefore(E,this.firstChild)}})},before:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this)})},after:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this.nextSibling)})},end:function(){return this.prevObject||o([])},push:[].push,sort:[].sort,splice:[].splice,find:function(E){if(this.length===1){var F=this.pushStack([],"find",E);F.length=0;o.find(E,this[0],F);return F}else{return this.pushStack(o.unique(o.map(this,function(G){return o.find(E,G)})),"find",E)}},clone:function(G){var E=this.map(function(){if(!o.support.noCloneEvent&&!o.isXMLDoc(this)){var I=this.outerHTML;if(!I){var J=this.ownerDocument.createElement("div");J.appendChild(this.cloneNode(true));I=J.innerHTML}return o.clean([I.replace(/ jQuery\d+="(?:\d+|null)"/g,"").replace(/^\s*/,"")])[0]}else{return this.cloneNode(true)}});if(G===true){var H=this.find("*").andSelf(),F=0;E.find("*").andSelf().each(function(){if(this.nodeName!==H[F].nodeName){return}var I=o.data(H[F],"events");for(var K in I){for(var J in I[K]){o.event.add(this,K,I[K][J],I[K][J].data)}}F++})}return E},filter:function(E){return this.pushStack(o.isFunction(E)&&o.grep(this,function(G,F){return E.call(G,F)})||o.multiFilter(E,o.grep(this,function(F){return F.nodeType===1})),"filter",E)},closest:function(E){var G=o.expr.match.POS.test(E)?o(E):null,F=0;return this.map(function(){var H=this;while(H&&H.ownerDocument){if(G?G.index(H)>-1:o(H).is(E)){o.data(H,"closest",F);return H}H=H.parentNode;F++}})},not:function(E){if(typeof E==="string"){if(f.test(E)){return this.pushStack(o.multiFilter(E,this,true),"not",E)}else{E=o.multiFilter(E,this)}}var F=E.length&&E[E.length-1]!==g&&!E.nodeType;return this.filter(function(){return F?o.inArray(this,E)<0:this!=E})},add:function(E){return this.pushStack(o.unique(o.merge(this.get(),typeof E==="string"?o(E):o.makeArray(E))))},is:function(E){return !!E&&o.multiFilter(E,this).length>0},hasClass:function(E){return !!E&&this.is("."+E)},val:function(K){if(K===g){var E=this[0];if(E){if(o.nodeName(E,"option")){return(E.attributes.value||{}).specified?E.value:E.text}if(o.nodeName(E,"select")){var I=E.selectedIndex,L=[],M=E.options,H=E.type=="select-one";if(I<0){return null}for(var F=H?I:0,J=H?I+1:M.length;F=0||o.inArray(this.name,K)>=0)}else{if(o.nodeName(this,"select")){var N=o.makeArray(K);o("option",this).each(function(){this.selected=(o.inArray(this.value,N)>=0||o.inArray(this.text,N)>=0)});if(!N.length){this.selectedIndex=-1}}else{this.value=K}}})},html:function(E){return E===g?(this[0]?this[0].innerHTML.replace(/ jQuery\d+="(?:\d+|null)"/g,""):null):this.empty().append(E)},replaceWith:function(E){return this.after(E).remove()},eq:function(E){return this.slice(E,+E+1)},slice:function(){return this.pushStack(Array.prototype.slice.apply(this,arguments),"slice",Array.prototype.slice.call(arguments).join(","))},map:function(E){return this.pushStack(o.map(this,function(G,F){return E.call(G,F,G)}))},andSelf:function(){return this.add(this.prevObject)},domManip:function(J,M,L){if(this[0]){var I=(this[0].ownerDocument||this[0]).createDocumentFragment(),F=o.clean(J,(this[0].ownerDocument||this[0]),I),H=I.firstChild;if(H){for(var G=0,E=this.length;G1||G>0?I.cloneNode(true):I)}}if(F){o.each(F,z)}}return this;function K(N,O){return M&&o.nodeName(N,"table")&&o.nodeName(O,"tr")?(N.getElementsByTagName("tbody")[0]||N.appendChild(N.ownerDocument.createElement("tbody"))):N}}};o.fn.init.prototype=o.fn;function z(E,F){if(F.src){o.ajax({url:F.src,async:false,dataType:"script"})}else{o.globalEval(F.text||F.textContent||F.innerHTML||"")}if(F.parentNode){F.parentNode.removeChild(F)}}function e(){return +new Date}o.extend=o.fn.extend=function(){var J=arguments[0]||{},H=1,I=arguments.length,E=false,G;if(typeof J==="boolean"){E=J;J=arguments[1]||{};H=2}if(typeof J!=="object"&&!o.isFunction(J)){J={}}if(I==H){J=this;--H}for(;H-1}},swap:function(H,G,I){var E={};for(var F in G){E[F]=H.style[F];H.style[F]=G[F]}I.call(H);for(var F in G){H.style[F]=E[F]}},css:function(H,F,J,E){if(F=="width"||F=="height"){var L,G={position:"absolute",visibility:"hidden",display:"block"},K=F=="width"?["Left","Right"]:["Top","Bottom"];function I(){L=F=="width"?H.offsetWidth:H.offsetHeight;if(E==="border"){return}o.each(K,function(){if(!E){L-=parseFloat(o.curCSS(H,"padding"+this,true))||0}if(E==="margin"){L+=parseFloat(o.curCSS(H,"margin"+this,true))||0}else{L-=parseFloat(o.curCSS(H,"border"+this+"Width",true))||0}})}if(H.offsetWidth!==0){I()}else{o.swap(H,G,I)}return Math.max(0,Math.round(L))}return o.curCSS(H,F,J)},curCSS:function(I,F,G){var L,E=I.style;if(F=="opacity"&&!o.support.opacity){L=o.attr(E,"opacity");return L==""?"1":L}if(F.match(/float/i)){F=w}if(!G&&E&&E[F]){L=E[F]}else{if(q.getComputedStyle){if(F.match(/float/i)){F="float"}F=F.replace(/([A-Z])/g,"-$1").toLowerCase();var M=q.getComputedStyle(I,null);if(M){L=M.getPropertyValue(F)}if(F=="opacity"&&L==""){L="1"}}else{if(I.currentStyle){var J=F.replace(/\-(\w)/g,function(N,O){return O.toUpperCase()});L=I.currentStyle[F]||I.currentStyle[J];if(!/^\d+(px)?$/i.test(L)&&/^\d/.test(L)){var H=E.left,K=I.runtimeStyle.left;I.runtimeStyle.left=I.currentStyle.left;E.left=L||0;L=E.pixelLeft+"px";E.left=H;I.runtimeStyle.left=K}}}}return L},clean:function(F,K,I){K=K||document;if(typeof K.createElement==="undefined"){K=K.ownerDocument||K[0]&&K[0].ownerDocument||document}if(!I&&F.length===1&&typeof F[0]==="string"){var H=/^<(\w+)\s*\/?>$/.exec(F[0]);if(H){return[K.createElement(H[1])]}}var G=[],E=[],L=K.createElement("div");o.each(F,function(P,S){if(typeof S==="number"){S+=""}if(!S){return}if(typeof S==="string"){S=S.replace(/(<(\w+)[^>]*?)\/>/g,function(U,V,T){return T.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?U:V+">"});var O=S.replace(/^\s+/,"").substring(0,10).toLowerCase();var Q=!O.indexOf("",""]||!O.indexOf("",""]||O.match(/^<(thead|tbody|tfoot|colg|cap)/)&&[1,"","
"]||!O.indexOf("",""]||(!O.indexOf("",""]||!O.indexOf("",""]||!o.support.htmlSerialize&&[1,"div
","
"]||[0,"",""];L.innerHTML=Q[1]+S+Q[2];while(Q[0]--){L=L.lastChild}if(!o.support.tbody){var R=/"&&!R?L.childNodes:[];for(var M=N.length-1;M>=0;--M){if(o.nodeName(N[M],"tbody")&&!N[M].childNodes.length){N[M].parentNode.removeChild(N[M])}}}if(!o.support.leadingWhitespace&&/^\s/.test(S)){L.insertBefore(K.createTextNode(S.match(/^\s*/)[0]),L.firstChild)}S=o.makeArray(L.childNodes)}if(S.nodeType){G.push(S)}else{G=o.merge(G,S)}});if(I){for(var J=0;G[J];J++){if(o.nodeName(G[J],"script")&&(!G[J].type||G[J].type.toLowerCase()==="text/javascript")){E.push(G[J].parentNode?G[J].parentNode.removeChild(G[J]):G[J])}else{if(G[J].nodeType===1){G.splice.apply(G,[J+1,0].concat(o.makeArray(G[J].getElementsByTagName("script"))))}I.appendChild(G[J])}}return E}return G},attr:function(J,G,K){if(!J||J.nodeType==3||J.nodeType==8){return g}var H=!o.isXMLDoc(J),L=K!==g;G=H&&o.props[G]||G;if(J.tagName){var F=/href|src|style/.test(G);if(G=="selected"&&J.parentNode){J.parentNode.selectedIndex}if(G in J&&H&&!F){if(L){if(G=="type"&&o.nodeName(J,"input")&&J.parentNode){throw"type property can't be changed"}J[G]=K}if(o.nodeName(J,"form")&&J.getAttributeNode(G)){return J.getAttributeNode(G).nodeValue}if(G=="tabIndex"){var I=J.getAttributeNode("tabIndex");return I&&I.specified?I.value:J.nodeName.match(/(button|input|object|select|textarea)/i)?0:J.nodeName.match(/^(a|area)$/i)&&J.href?0:g}return J[G]}if(!o.support.style&&H&&G=="style"){return o.attr(J.style,"cssText",K)}if(L){J.setAttribute(G,""+K)}var E=!o.support.hrefNormalized&&H&&F?J.getAttribute(G,2):J.getAttribute(G);return E===null?g:E}if(!o.support.opacity&&G=="opacity"){if(L){J.zoom=1;J.filter=(J.filter||"").replace(/alpha\([^)]*\)/,"")+(parseInt(K)+""=="NaN"?"":"alpha(opacity="+K*100+")")}return J.filter&&J.filter.indexOf("opacity=")>=0?(parseFloat(J.filter.match(/opacity=([^)]*)/)[1])/100)+"":""}G=G.replace(/-([a-z])/ig,function(M,N){return N.toUpperCase()});if(L){J[G]=K}return J[G]},trim:function(E){return(E||"").replace(/^\s+|\s+$/g,"")},makeArray:function(G){var E=[];if(G!=null){var F=G.length;if(F==null||typeof G==="string"||o.isFunction(G)||G.setInterval){E[0]=G}else{while(F){E[--F]=G[F]}}}return E},inArray:function(G,H){for(var E=0,F=H.length;E0?this.clone(true):this).get();o.fn[F].apply(o(L[K]),I);J=J.concat(I)}return this.pushStack(J,E,G)}});o.each({removeAttr:function(E){o.attr(this,E,"");if(this.nodeType==1){this.removeAttribute(E)}},addClass:function(E){o.className.add(this,E)},removeClass:function(E){o.className.remove(this,E)},toggleClass:function(F,E){if(typeof E!=="boolean"){E=!o.className.has(this,F)}o.className[E?"add":"remove"](this,F)},remove:function(E){if(!E||o.filter(E,[this]).length){o("*",this).add([this]).each(function(){o.event.remove(this);o.removeData(this)});if(this.parentNode){this.parentNode.removeChild(this)}}},empty:function(){o(this).children().remove();while(this.firstChild){this.removeChild(this.firstChild)}}},function(E,F){o.fn[E]=function(){return this.each(F,arguments)}});function j(E,F){return E[0]&&parseInt(o.curCSS(E[0],F,true),10)||0}var h="jQuery"+e(),v=0,A={};o.extend({cache:{},data:function(F,E,G){F=F==l?A:F;var H=F[h];if(!H){H=F[h]=++v}if(E&&!o.cache[H]){o.cache[H]={}}if(G!==g){o.cache[H][E]=G}return E?o.cache[H][E]:H},removeData:function(F,E){F=F==l?A:F;var H=F[h];if(E){if(o.cache[H]){delete o.cache[H][E];E="";for(E in o.cache[H]){break}if(!E){o.removeData(F)}}}else{try{delete F[h]}catch(G){if(F.removeAttribute){F.removeAttribute(h)}}delete o.cache[H]}},queue:function(F,E,H){if(F){E=(E||"fx")+"queue";var G=o.data(F,E);if(!G||o.isArray(H)){G=o.data(F,E,o.makeArray(H))}else{if(H){G.push(H)}}}return G},dequeue:function(H,G){var E=o.queue(H,G),F=E.shift();if(!G||G==="fx"){F=E[0]}if(F!==g){F.call(H)}}});o.fn.extend({data:function(E,G){var H=E.split(".");H[1]=H[1]?"."+H[1]:"";if(G===g){var F=this.triggerHandler("getData"+H[1]+"!",[H[0]]);if(F===g&&this.length){F=o.data(this[0],E)}return F===g&&H[1]?this.data(H[0]):F}else{return this.trigger("setData"+H[1]+"!",[H[0],G]).each(function(){o.data(this,E,G)})}},removeData:function(E){return this.each(function(){o.removeData(this,E)})},queue:function(E,F){if(typeof E!=="string"){F=E;E="fx"}if(F===g){return o.queue(this[0],E)}return this.each(function(){var G=o.queue(this,E,F);if(E=="fx"&&G.length==1){G[0].call(this)}})},dequeue:function(E){return this.each(function(){o.dequeue(this,E)})}}); +/* + * Sizzle CSS Selector Engine - v0.9.3 + * Copyright 2009, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ +(function(){var R=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g,L=0,H=Object.prototype.toString;var F=function(Y,U,ab,ac){ab=ab||[];U=U||document;if(U.nodeType!==1&&U.nodeType!==9){return[]}if(!Y||typeof Y!=="string"){return ab}var Z=[],W,af,ai,T,ad,V,X=true;R.lastIndex=0;while((W=R.exec(Y))!==null){Z.push(W[1]);if(W[2]){V=RegExp.rightContext;break}}if(Z.length>1&&M.exec(Y)){if(Z.length===2&&I.relative[Z[0]]){af=J(Z[0]+Z[1],U)}else{af=I.relative[Z[0]]?[U]:F(Z.shift(),U);while(Z.length){Y=Z.shift();if(I.relative[Y]){Y+=Z.shift()}af=J(Y,af)}}}else{var ae=ac?{expr:Z.pop(),set:E(ac)}:F.find(Z.pop(),Z.length===1&&U.parentNode?U.parentNode:U,Q(U));af=F.filter(ae.expr,ae.set);if(Z.length>0){ai=E(af)}else{X=false}while(Z.length){var ah=Z.pop(),ag=ah;if(!I.relative[ah]){ah=""}else{ag=Z.pop()}if(ag==null){ag=U}I.relative[ah](ai,ag,Q(U))}}if(!ai){ai=af}if(!ai){throw"Syntax error, unrecognized expression: "+(ah||Y)}if(H.call(ai)==="[object Array]"){if(!X){ab.push.apply(ab,ai)}else{if(U.nodeType===1){for(var aa=0;ai[aa]!=null;aa++){if(ai[aa]&&(ai[aa]===true||ai[aa].nodeType===1&&K(U,ai[aa]))){ab.push(af[aa])}}}else{for(var aa=0;ai[aa]!=null;aa++){if(ai[aa]&&ai[aa].nodeType===1){ab.push(af[aa])}}}}}else{E(ai,ab)}if(V){F(V,U,ab,ac);if(G){hasDuplicate=false;ab.sort(G);if(hasDuplicate){for(var aa=1;aa":function(Z,U,aa){var X=typeof U==="string";if(X&&!/\W/.test(U)){U=aa?U:U.toUpperCase();for(var V=0,T=Z.length;V=0)){if(!V){T.push(Y)}}else{if(V){U[X]=false}}}}return false},ID:function(T){return T[1].replace(/\\/g,"")},TAG:function(U,T){for(var V=0;T[V]===false;V++){}return T[V]&&Q(T[V])?U[1]:U[1].toUpperCase()},CHILD:function(T){if(T[1]=="nth"){var U=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(T[2]=="even"&&"2n"||T[2]=="odd"&&"2n+1"||!/\D/.test(T[2])&&"0n+"+T[2]||T[2]);T[2]=(U[1]+(U[2]||1))-0;T[3]=U[3]-0}T[0]=L++;return T},ATTR:function(X,U,V,T,Y,Z){var W=X[1].replace(/\\/g,"");if(!Z&&I.attrMap[W]){X[1]=I.attrMap[W]}if(X[2]==="~="){X[4]=" "+X[4]+" "}return X},PSEUDO:function(X,U,V,T,Y){if(X[1]==="not"){if(X[3].match(R).length>1||/^\w/.test(X[3])){X[3]=F(X[3],null,null,U)}else{var W=F.filter(X[3],U,V,true^Y);if(!V){T.push.apply(T,W)}return false}}else{if(I.match.POS.test(X[0])||I.match.CHILD.test(X[0])){return true}}return X},POS:function(T){T.unshift(true);return T}},filters:{enabled:function(T){return T.disabled===false&&T.type!=="hidden"},disabled:function(T){return T.disabled===true},checked:function(T){return T.checked===true},selected:function(T){T.parentNode.selectedIndex;return T.selected===true},parent:function(T){return !!T.firstChild},empty:function(T){return !T.firstChild},has:function(V,U,T){return !!F(T[3],V).length},header:function(T){return/h\d/i.test(T.nodeName)},text:function(T){return"text"===T.type},radio:function(T){return"radio"===T.type},checkbox:function(T){return"checkbox"===T.type},file:function(T){return"file"===T.type},password:function(T){return"password"===T.type},submit:function(T){return"submit"===T.type},image:function(T){return"image"===T.type},reset:function(T){return"reset"===T.type},button:function(T){return"button"===T.type||T.nodeName.toUpperCase()==="BUTTON"},input:function(T){return/input|select|textarea|button/i.test(T.nodeName)}},setFilters:{first:function(U,T){return T===0},last:function(V,U,T,W){return U===W.length-1},even:function(U,T){return T%2===0},odd:function(U,T){return T%2===1},lt:function(V,U,T){return UT[3]-0},nth:function(V,U,T){return T[3]-0==U},eq:function(V,U,T){return T[3]-0==U}},filter:{PSEUDO:function(Z,V,W,aa){var U=V[1],X=I.filters[U];if(X){return X(Z,W,V,aa)}else{if(U==="contains"){return(Z.textContent||Z.innerText||"").indexOf(V[3])>=0}else{if(U==="not"){var Y=V[3];for(var W=0,T=Y.length;W=0)}}},ID:function(U,T){return U.nodeType===1&&U.getAttribute("id")===T},TAG:function(U,T){return(T==="*"&&U.nodeType===1)||U.nodeName===T},CLASS:function(U,T){return(" "+(U.className||U.getAttribute("class"))+" ").indexOf(T)>-1},ATTR:function(Y,W){var V=W[1],T=I.attrHandle[V]?I.attrHandle[V](Y):Y[V]!=null?Y[V]:Y.getAttribute(V),Z=T+"",X=W[2],U=W[4];return T==null?X==="!=":X==="="?Z===U:X==="*="?Z.indexOf(U)>=0:X==="~="?(" "+Z+" ").indexOf(U)>=0:!U?Z&&T!==false:X==="!="?Z!=U:X==="^="?Z.indexOf(U)===0:X==="$="?Z.substr(Z.length-U.length)===U:X==="|="?Z===U||Z.substr(0,U.length+1)===U+"-":false},POS:function(X,U,V,Y){var T=U[2],W=I.setFilters[T];if(W){return W(X,V,U,Y)}}}};var M=I.match.POS;for(var O in I.match){I.match[O]=RegExp(I.match[O].source+/(?![^\[]*\])(?![^\(]*\))/.source)}var E=function(U,T){U=Array.prototype.slice.call(U);if(T){T.push.apply(T,U);return T}return U};try{Array.prototype.slice.call(document.documentElement.childNodes)}catch(N){E=function(X,W){var U=W||[];if(H.call(X)==="[object Array]"){Array.prototype.push.apply(U,X)}else{if(typeof X.length==="number"){for(var V=0,T=X.length;V";var T=document.documentElement;T.insertBefore(U,T.firstChild);if(!!document.getElementById(V)){I.find.ID=function(X,Y,Z){if(typeof Y.getElementById!=="undefined"&&!Z){var W=Y.getElementById(X[1]);return W?W.id===X[1]||typeof W.getAttributeNode!=="undefined"&&W.getAttributeNode("id").nodeValue===X[1]?[W]:g:[]}};I.filter.ID=function(Y,W){var X=typeof Y.getAttributeNode!=="undefined"&&Y.getAttributeNode("id");return Y.nodeType===1&&X&&X.nodeValue===W}}T.removeChild(U)})();(function(){var T=document.createElement("div");T.appendChild(document.createComment(""));if(T.getElementsByTagName("*").length>0){I.find.TAG=function(U,Y){var X=Y.getElementsByTagName(U[1]);if(U[1]==="*"){var W=[];for(var V=0;X[V];V++){if(X[V].nodeType===1){W.push(X[V])}}X=W}return X}}T.innerHTML="";if(T.firstChild&&typeof T.firstChild.getAttribute!=="undefined"&&T.firstChild.getAttribute("href")!=="#"){I.attrHandle.href=function(U){return U.getAttribute("href",2)}}})();if(document.querySelectorAll){(function(){var T=F,U=document.createElement("div");U.innerHTML="

";if(U.querySelectorAll&&U.querySelectorAll(".TEST").length===0){return}F=function(Y,X,V,W){X=X||document;if(!W&&X.nodeType===9&&!Q(X)){try{return E(X.querySelectorAll(Y),V)}catch(Z){}}return T(Y,X,V,W)};F.find=T.find;F.filter=T.filter;F.selectors=T.selectors;F.matches=T.matches})()}if(document.getElementsByClassName&&document.documentElement.getElementsByClassName){(function(){var T=document.createElement("div");T.innerHTML="
";if(T.getElementsByClassName("e").length===0){return}T.lastChild.className="e";if(T.getElementsByClassName("e").length===1){return}I.order.splice(1,0,"CLASS");I.find.CLASS=function(U,V,W){if(typeof V.getElementsByClassName!=="undefined"&&!W){return V.getElementsByClassName(U[1])}}})()}function P(U,Z,Y,ad,aa,ac){var ab=U=="previousSibling"&&!ac;for(var W=0,V=ad.length;W0){X=T;break}}}T=T[U]}ad[W]=X}}}var K=document.compareDocumentPosition?function(U,T){return U.compareDocumentPosition(T)&16}:function(U,T){return U!==T&&(U.contains?U.contains(T):true)};var Q=function(T){return T.nodeType===9&&T.documentElement.nodeName!=="HTML"||!!T.ownerDocument&&Q(T.ownerDocument)};var J=function(T,aa){var W=[],X="",Y,V=aa.nodeType?[aa]:aa;while((Y=I.match.PSEUDO.exec(T))){X+=Y[0];T=T.replace(I.match.PSEUDO,"")}T=I.relative[T]?T+"*":T;for(var Z=0,U=V.length;Z0||T.offsetHeight>0};F.selectors.filters.animated=function(T){return o.grep(o.timers,function(U){return T===U.elem}).length};o.multiFilter=function(V,T,U){if(U){V=":not("+V+")"}return F.matches(V,T)};o.dir=function(V,U){var T=[],W=V[U];while(W&&W!=document){if(W.nodeType==1){T.push(W)}W=W[U]}return T};o.nth=function(X,T,V,W){T=T||1;var U=0;for(;X;X=X[V]){if(X.nodeType==1&&++U==T){break}}return X};o.sibling=function(V,U){var T=[];for(;V;V=V.nextSibling){if(V.nodeType==1&&V!=U){T.push(V)}}return T};return;l.Sizzle=F})();o.event={add:function(I,F,H,K){if(I.nodeType==3||I.nodeType==8){return}if(I.setInterval&&I!=l){I=l}if(!H.guid){H.guid=this.guid++}if(K!==g){var G=H;H=this.proxy(G);H.data=K}var E=o.data(I,"events")||o.data(I,"events",{}),J=o.data(I,"handle")||o.data(I,"handle",function(){return typeof o!=="undefined"&&!o.event.triggered?o.event.handle.apply(arguments.callee.elem,arguments):g});J.elem=I;o.each(F.split(/\s+/),function(M,N){var O=N.split(".");N=O.shift();H.type=O.slice().sort().join(".");var L=E[N];if(o.event.specialAll[N]){o.event.specialAll[N].setup.call(I,K,O)}if(!L){L=E[N]={};if(!o.event.special[N]||o.event.special[N].setup.call(I,K,O)===false){if(I.addEventListener){I.addEventListener(N,J,false)}else{if(I.attachEvent){I.attachEvent("on"+N,J)}}}}L[H.guid]=H;o.event.global[N]=true});I=null},guid:1,global:{},remove:function(K,H,J){if(K.nodeType==3||K.nodeType==8){return}var G=o.data(K,"events"),F,E;if(G){if(H===g||(typeof H==="string"&&H.charAt(0)==".")){for(var I in G){this.remove(K,I+(H||""))}}else{if(H.type){J=H.handler;H=H.type}o.each(H.split(/\s+/),function(M,O){var Q=O.split(".");O=Q.shift();var N=RegExp("(^|\\.)"+Q.slice().sort().join(".*\\.")+"(\\.|$)");if(G[O]){if(J){delete G[O][J.guid]}else{for(var P in G[O]){if(N.test(G[O][P].type)){delete G[O][P]}}}if(o.event.specialAll[O]){o.event.specialAll[O].teardown.call(K,Q)}for(F in G[O]){break}if(!F){if(!o.event.special[O]||o.event.special[O].teardown.call(K,Q)===false){if(K.removeEventListener){K.removeEventListener(O,o.data(K,"handle"),false)}else{if(K.detachEvent){K.detachEvent("on"+O,o.data(K,"handle"))}}}F=null;delete G[O]}}})}for(F in G){break}if(!F){var L=o.data(K,"handle");if(L){L.elem=null}o.removeData(K,"events");o.removeData(K,"handle")}}},trigger:function(I,K,H,E){var G=I.type||I;if(!E){I=typeof I==="object"?I[h]?I:o.extend(o.Event(G),I):o.Event(G);if(G.indexOf("!")>=0){I.type=G=G.slice(0,-1);I.exclusive=true}if(!H){I.stopPropagation();if(this.global[G]){o.each(o.cache,function(){if(this.events&&this.events[G]){o.event.trigger(I,K,this.handle.elem)}})}}if(!H||H.nodeType==3||H.nodeType==8){return g}I.result=g;I.target=H;K=o.makeArray(K);K.unshift(I)}I.currentTarget=H;var J=o.data(H,"handle");if(J){J.apply(H,K)}if((!H[G]||(o.nodeName(H,"a")&&G=="click"))&&H["on"+G]&&H["on"+G].apply(H,K)===false){I.result=false}if(!E&&H[G]&&!I.isDefaultPrevented()&&!(o.nodeName(H,"a")&&G=="click")){this.triggered=true;try{H[G]()}catch(L){}}this.triggered=false;if(!I.isPropagationStopped()){var F=H.parentNode||H.ownerDocument;if(F){o.event.trigger(I,K,F,true)}}},handle:function(K){var J,E;K=arguments[0]=o.event.fix(K||l.event);K.currentTarget=this;var L=K.type.split(".");K.type=L.shift();J=!L.length&&!K.exclusive;var I=RegExp("(^|\\.)"+L.slice().sort().join(".*\\.")+"(\\.|$)");E=(o.data(this,"events")||{})[K.type];for(var G in E){var H=E[G];if(J||I.test(H.type)){K.handler=H;K.data=H.data;var F=H.apply(this,arguments);if(F!==g){K.result=F;if(F===false){K.preventDefault();K.stopPropagation()}}if(K.isImmediatePropagationStopped()){break}}}},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(H){if(H[h]){return H}var F=H;H=o.Event(F);for(var G=this.props.length,J;G;){J=this.props[--G];H[J]=F[J]}if(!H.target){H.target=H.srcElement||document}if(H.target.nodeType==3){H.target=H.target.parentNode}if(!H.relatedTarget&&H.fromElement){H.relatedTarget=H.fromElement==H.target?H.toElement:H.fromElement}if(H.pageX==null&&H.clientX!=null){var I=document.documentElement,E=document.body;H.pageX=H.clientX+(I&&I.scrollLeft||E&&E.scrollLeft||0)-(I.clientLeft||0);H.pageY=H.clientY+(I&&I.scrollTop||E&&E.scrollTop||0)-(I.clientTop||0)}if(!H.which&&((H.charCode||H.charCode===0)?H.charCode:H.keyCode)){H.which=H.charCode||H.keyCode}if(!H.metaKey&&H.ctrlKey){H.metaKey=H.ctrlKey}if(!H.which&&H.button){H.which=(H.button&1?1:(H.button&2?3:(H.button&4?2:0)))}return H},proxy:function(F,E){E=E||function(){return F.apply(this,arguments)};E.guid=F.guid=F.guid||E.guid||this.guid++;return E},special:{ready:{setup:B,teardown:function(){}}},specialAll:{live:{setup:function(E,F){o.event.add(this,F[0],c)},teardown:function(G){if(G.length){var E=0,F=RegExp("(^|\\.)"+G[0]+"(\\.|$)");o.each((o.data(this,"events").live||{}),function(){if(F.test(this.type)){E++}});if(E<1){o.event.remove(this,G[0],c)}}}}}};o.Event=function(E){if(!this.preventDefault){return new o.Event(E)}if(E&&E.type){this.originalEvent=E;this.type=E.type}else{this.type=E}this.timeStamp=e();this[h]=true};function k(){return false}function u(){return true}o.Event.prototype={preventDefault:function(){this.isDefaultPrevented=u;var E=this.originalEvent;if(!E){return}if(E.preventDefault){E.preventDefault()}E.returnValue=false},stopPropagation:function(){this.isPropagationStopped=u;var E=this.originalEvent;if(!E){return}if(E.stopPropagation){E.stopPropagation()}E.cancelBubble=true},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=u;this.stopPropagation()},isDefaultPrevented:k,isPropagationStopped:k,isImmediatePropagationStopped:k};var a=function(F){var E=F.relatedTarget;while(E&&E!=this){try{E=E.parentNode}catch(G){E=this}}if(E!=this){F.type=F.data;o.event.handle.apply(this,arguments)}};o.each({mouseover:"mouseenter",mouseout:"mouseleave"},function(F,E){o.event.special[E]={setup:function(){o.event.add(this,F,a,E)},teardown:function(){o.event.remove(this,F,a)}}});o.fn.extend({bind:function(F,G,E){return F=="unload"?this.one(F,G,E):this.each(function(){o.event.add(this,F,E||G,E&&G)})},one:function(G,H,F){var E=o.event.proxy(F||H,function(I){o(this).unbind(I,E);return(F||H).apply(this,arguments)});return this.each(function(){o.event.add(this,G,E,F&&H)})},unbind:function(F,E){return this.each(function(){o.event.remove(this,F,E)})},trigger:function(E,F){return this.each(function(){o.event.trigger(E,F,this)})},triggerHandler:function(E,G){if(this[0]){var F=o.Event(E);F.preventDefault();F.stopPropagation();o.event.trigger(F,G,this[0]);return F.result}},toggle:function(G){var E=arguments,F=1;while(F=0){var E=G.slice(I,G.length);G=G.slice(0,I)}var H="GET";if(J){if(o.isFunction(J)){K=J;J=null}else{if(typeof J==="object"){J=o.param(J);H="POST"}}}var F=this;o.ajax({url:G,type:H,dataType:"html",data:J,complete:function(M,L){if(L=="success"||L=="notmodified"){F.html(E?o("
").append(M.responseText.replace(//g,"")).find(E):M.responseText)}if(K){F.each(K,[M.responseText,L,M])}}});return this},serialize:function(){return o.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?o.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||/select|textarea/i.test(this.nodeName)||/text|hidden|password|search/i.test(this.type))}).map(function(E,F){var G=o(this).val();return G==null?null:o.isArray(G)?o.map(G,function(I,H){return{name:F.name,value:I}}):{name:F.name,value:G}}).get()}});o.each("ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","),function(E,F){o.fn[F]=function(G){return this.bind(F,G)}});var r=e();o.extend({get:function(E,G,H,F){if(o.isFunction(G)){H=G;G=null}return o.ajax({type:"GET",url:E,data:G,success:H,dataType:F})},getScript:function(E,F){return o.get(E,null,F,"script")},getJSON:function(E,F,G){return o.get(E,F,G,"json")},post:function(E,G,H,F){if(o.isFunction(G)){H=G;G={}}return o.ajax({type:"POST",url:E,data:G,success:H,dataType:F})},ajaxSetup:function(E){o.extend(o.ajaxSettings,E)},ajaxSettings:{url:location.href,global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:function(){return l.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest()},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},ajax:function(M){M=o.extend(true,M,o.extend(true,{},o.ajaxSettings,M));var W,F=/=\?(&|$)/g,R,V,G=M.type.toUpperCase();if(M.data&&M.processData&&typeof M.data!=="string"){M.data=o.param(M.data)}if(M.dataType=="jsonp"){if(G=="GET"){if(!M.url.match(F)){M.url+=(M.url.match(/\?/)?"&":"?")+(M.jsonp||"callback")+"=?"}}else{if(!M.data||!M.data.match(F)){M.data=(M.data?M.data+"&":"")+(M.jsonp||"callback")+"=?"}}M.dataType="json"}if(M.dataType=="json"&&(M.data&&M.data.match(F)||M.url.match(F))){W="jsonp"+r++;if(M.data){M.data=(M.data+"").replace(F,"="+W+"$1")}M.url=M.url.replace(F,"="+W+"$1");M.dataType="script";l[W]=function(X){V=X;I();L();l[W]=g;try{delete l[W]}catch(Y){}if(H){H.removeChild(T)}}}if(M.dataType=="script"&&M.cache==null){M.cache=false}if(M.cache===false&&G=="GET"){var E=e();var U=M.url.replace(/(\?|&)_=.*?(&|$)/,"$1_="+E+"$2");M.url=U+((U==M.url)?(M.url.match(/\?/)?"&":"?")+"_="+E:"")}if(M.data&&G=="GET"){M.url+=(M.url.match(/\?/)?"&":"?")+M.data;M.data=null}if(M.global&&!o.active++){o.event.trigger("ajaxStart")}var Q=/^(\w+:)?\/\/([^\/?#]+)/.exec(M.url);if(M.dataType=="script"&&G=="GET"&&Q&&(Q[1]&&Q[1]!=location.protocol||Q[2]!=location.host)){var H=document.getElementsByTagName("head")[0];var T=document.createElement("script");T.src=M.url;if(M.scriptCharset){T.charset=M.scriptCharset}if(!W){var O=false;T.onload=T.onreadystatechange=function(){if(!O&&(!this.readyState||this.readyState=="loaded"||this.readyState=="complete")){O=true;I();L();T.onload=T.onreadystatechange=null;H.removeChild(T)}}}H.appendChild(T);return g}var K=false;var J=M.xhr();if(M.username){J.open(G,M.url,M.async,M.username,M.password)}else{J.open(G,M.url,M.async)}try{if(M.data){J.setRequestHeader("Content-Type",M.contentType)}if(M.ifModified){J.setRequestHeader("If-Modified-Since",o.lastModified[M.url]||"Thu, 01 Jan 1970 00:00:00 GMT")}J.setRequestHeader("X-Requested-With","XMLHttpRequest");J.setRequestHeader("Accept",M.dataType&&M.accepts[M.dataType]?M.accepts[M.dataType]+", */*":M.accepts._default)}catch(S){}if(M.beforeSend&&M.beforeSend(J,M)===false){if(M.global&&!--o.active){o.event.trigger("ajaxStop")}J.abort();return false}if(M.global){o.event.trigger("ajaxSend",[J,M])}var N=function(X){if(J.readyState==0){if(P){clearInterval(P);P=null;if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}}else{if(!K&&J&&(J.readyState==4||X=="timeout")){K=true;if(P){clearInterval(P);P=null}R=X=="timeout"?"timeout":!o.httpSuccess(J)?"error":M.ifModified&&o.httpNotModified(J,M.url)?"notmodified":"success";if(R=="success"){try{V=o.httpData(J,M.dataType,M)}catch(Z){R="parsererror"}}if(R=="success"){var Y;try{Y=J.getResponseHeader("Last-Modified")}catch(Z){}if(M.ifModified&&Y){o.lastModified[M.url]=Y}if(!W){I()}}else{o.handleError(M,J,R)}L();if(X){J.abort()}if(M.async){J=null}}}};if(M.async){var P=setInterval(N,13);if(M.timeout>0){setTimeout(function(){if(J&&!K){N("timeout")}},M.timeout)}}try{J.send(M.data)}catch(S){o.handleError(M,J,null,S)}if(!M.async){N()}function I(){if(M.success){M.success(V,R)}if(M.global){o.event.trigger("ajaxSuccess",[J,M])}}function L(){if(M.complete){M.complete(J,R)}if(M.global){o.event.trigger("ajaxComplete",[J,M])}if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}return J},handleError:function(F,H,E,G){if(F.error){F.error(H,E,G)}if(F.global){o.event.trigger("ajaxError",[H,F,G])}},active:0,httpSuccess:function(F){try{return !F.status&&location.protocol=="file:"||(F.status>=200&&F.status<300)||F.status==304||F.status==1223}catch(E){}return false},httpNotModified:function(G,E){try{var H=G.getResponseHeader("Last-Modified");return G.status==304||H==o.lastModified[E]}catch(F){}return false},httpData:function(J,H,G){var F=J.getResponseHeader("content-type"),E=H=="xml"||!H&&F&&F.indexOf("xml")>=0,I=E?J.responseXML:J.responseText;if(E&&I.documentElement.tagName=="parsererror"){throw"parsererror"}if(G&&G.dataFilter){I=G.dataFilter(I,H)}if(typeof I==="string"){if(H=="script"){o.globalEval(I)}if(H=="json"){I=l["eval"]("("+I+")")}}return I},param:function(E){var G=[];function H(I,J){G[G.length]=encodeURIComponent(I)+"="+encodeURIComponent(J)}if(o.isArray(E)||E.jquery){o.each(E,function(){H(this.name,this.value)})}else{for(var F in E){if(o.isArray(E[F])){o.each(E[F],function(){H(F,this)})}else{H(F,o.isFunction(E[F])?E[F]():E[F])}}}return G.join("&").replace(/%20/g,"+")}});var m={},n,d=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];function t(F,E){var G={};o.each(d.concat.apply([],d.slice(0,E)),function(){G[this]=F});return G}o.fn.extend({show:function(J,L){if(J){return this.animate(t("show",3),J,L)}else{for(var H=0,F=this.length;H").appendTo("body");K=I.css("display");if(K==="none"){K="block"}I.remove();m[G]=K}o.data(this[H],"olddisplay",K)}}for(var H=0,F=this.length;H=0;H--){if(G[H].elem==this){if(E){G[H](true)}G.splice(H,1)}}});if(!E){this.dequeue()}return this}});o.each({slideDown:t("show",1),slideUp:t("hide",1),slideToggle:t("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(E,F){o.fn[E]=function(G,H){return this.animate(F,G,H)}});o.extend({speed:function(G,H,F){var E=typeof G==="object"?G:{complete:F||!F&&H||o.isFunction(G)&&G,duration:G,easing:F&&H||H&&!o.isFunction(H)&&H};E.duration=o.fx.off?0:typeof E.duration==="number"?E.duration:o.fx.speeds[E.duration]||o.fx.speeds._default;E.old=E.complete;E.complete=function(){if(E.queue!==false){o(this).dequeue()}if(o.isFunction(E.old)){E.old.call(this)}};return E},easing:{linear:function(G,H,E,F){return E+F*G},swing:function(G,H,E,F){return((-Math.cos(G*Math.PI)/2)+0.5)*F+E}},timers:[],fx:function(F,E,G){this.options=E;this.elem=F;this.prop=G;if(!E.orig){E.orig={}}}});o.fx.prototype={update:function(){if(this.options.step){this.options.step.call(this.elem,this.now,this)}(o.fx.step[this.prop]||o.fx.step._default)(this);if((this.prop=="height"||this.prop=="width")&&this.elem.style){this.elem.style.display="block"}},cur:function(F){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null)){return this.elem[this.prop]}var E=parseFloat(o.css(this.elem,this.prop,F));return E&&E>-10000?E:parseFloat(o.curCSS(this.elem,this.prop))||0},custom:function(I,H,G){this.startTime=e();this.start=I;this.end=H;this.unit=G||this.unit||"px";this.now=this.start;this.pos=this.state=0;var E=this;function F(J){return E.step(J)}F.elem=this.elem;if(F()&&o.timers.push(F)&&!n){n=setInterval(function(){var K=o.timers;for(var J=0;J=this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;var E=true;for(var F in this.options.curAnim){if(this.options.curAnim[F]!==true){E=false}}if(E){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;this.elem.style.display=this.options.display;if(o.css(this.elem,"display")=="none"){this.elem.style.display="block"}}if(this.options.hide){o(this.elem).hide()}if(this.options.hide||this.options.show){for(var I in this.options.curAnim){o.attr(this.elem.style,I,this.options.orig[I])}}this.options.complete.call(this.elem)}return false}else{var J=G-this.startTime;this.state=J/this.options.duration;this.pos=o.easing[this.options.easing||(o.easing.swing?"swing":"linear")](this.state,J,0,1,this.options.duration);this.now=this.start+((this.end-this.start)*this.pos);this.update()}return true}};o.extend(o.fx,{speeds:{slow:600,fast:200,_default:400},step:{opacity:function(E){o.attr(E.elem.style,"opacity",E.now)},_default:function(E){if(E.elem.style&&E.elem.style[E.prop]!=null){E.elem.style[E.prop]=E.now+E.unit}else{E.elem[E.prop]=E.now}}}});if(document.documentElement.getBoundingClientRect){o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}var G=this[0].getBoundingClientRect(),J=this[0].ownerDocument,F=J.body,E=J.documentElement,L=E.clientTop||F.clientTop||0,K=E.clientLeft||F.clientLeft||0,I=G.top+(self.pageYOffset||o.boxModel&&E.scrollTop||F.scrollTop)-L,H=G.left+(self.pageXOffset||o.boxModel&&E.scrollLeft||F.scrollLeft)-K;return{top:I,left:H}}}else{o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}o.offset.initialized||o.offset.initialize();var J=this[0],G=J.offsetParent,F=J,O=J.ownerDocument,M,H=O.documentElement,K=O.body,L=O.defaultView,E=L.getComputedStyle(J,null),N=J.offsetTop,I=J.offsetLeft;while((J=J.parentNode)&&J!==K&&J!==H){M=L.getComputedStyle(J,null);N-=J.scrollTop,I-=J.scrollLeft;if(J===G){N+=J.offsetTop,I+=J.offsetLeft;if(o.offset.doesNotAddBorder&&!(o.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(J.tagName))){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}F=G,G=J.offsetParent}if(o.offset.subtractsBorderForOverflowNotVisible&&M.overflow!=="visible"){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}E=M}if(E.position==="relative"||E.position==="static"){N+=K.offsetTop,I+=K.offsetLeft}if(E.position==="fixed"){N+=Math.max(H.scrollTop,K.scrollTop),I+=Math.max(H.scrollLeft,K.scrollLeft)}return{top:N,left:I}}}o.offset={initialize:function(){if(this.initialized){return}var L=document.body,F=document.createElement("div"),H,G,N,I,M,E,J=L.style.marginTop,K='
';M={position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"};for(E in M){F.style[E]=M[E]}F.innerHTML=K;L.insertBefore(F,L.firstChild);H=F.firstChild,G=H.firstChild,I=H.nextSibling.firstChild.firstChild;this.doesNotAddBorder=(G.offsetTop!==5);this.doesAddBorderForTableAndCells=(I.offsetTop===5);H.style.overflow="hidden",H.style.position="relative";this.subtractsBorderForOverflowNotVisible=(G.offsetTop===-5);L.style.marginTop="1px";this.doesNotIncludeMarginInBodyOffset=(L.offsetTop===0);L.style.marginTop=J;L.removeChild(F);this.initialized=true},bodyOffset:function(E){o.offset.initialized||o.offset.initialize();var G=E.offsetTop,F=E.offsetLeft;if(o.offset.doesNotIncludeMarginInBodyOffset){G+=parseInt(o.curCSS(E,"marginTop",true),10)||0,F+=parseInt(o.curCSS(E,"marginLeft",true),10)||0}return{top:G,left:F}}};o.fn.extend({position:function(){var I=0,H=0,F;if(this[0]){var G=this.offsetParent(),J=this.offset(),E=/^body|html$/i.test(G[0].tagName)?{top:0,left:0}:G.offset();J.top-=j(this,"marginTop");J.left-=j(this,"marginLeft");E.top+=j(G,"borderTopWidth");E.left+=j(G,"borderLeftWidth");F={top:J.top-E.top,left:J.left-E.left}}return F},offsetParent:function(){var E=this[0].offsetParent||document.body;while(E&&(!/^body|html$/i.test(E.tagName)&&o.css(E,"position")=="static")){E=E.offsetParent}return o(E)}});o.each(["Left","Top"],function(F,E){var G="scroll"+E;o.fn[G]=function(H){if(!this[0]){return null}return H!==g?this.each(function(){this==l||this==document?l.scrollTo(!F?H:o(l).scrollLeft(),F?H:o(l).scrollTop()):this[G]=H}):this[0]==l||this[0]==document?self[F?"pageYOffset":"pageXOffset"]||o.boxModel&&document.documentElement[G]||document.body[G]:this[0][G]}});o.each(["Height","Width"],function(I,G){var E=I?"Left":"Top",H=I?"Right":"Bottom",F=G.toLowerCase();o.fn["inner"+G]=function(){return this[0]?o.css(this[0],F,false,"padding"):null};o.fn["outer"+G]=function(K){return this[0]?o.css(this[0],F,false,K?"margin":"border"):null};var J=G.toLowerCase();o.fn[J]=function(K){return this[0]==l?document.compatMode=="CSS1Compat"&&document.documentElement["client"+G]||document.body["client"+G]:this[0]==document?Math.max(document.documentElement["client"+G],document.body["scroll"+G],document.documentElement["scroll"+G],document.body["offset"+G],document.documentElement["offset"+G]):K===g?(this.length?o.css(this[0],J):null):this.css(J,typeof K==="string"?K:K+"px")}})})(); +/* + * jQuery UI 1.7.1 + * + * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * http://docs.jquery.com/UI + */ jQuery.ui||(function(c){var i=c.fn.remove,d=c.browser.mozilla&&(parseFloat(c.browser.version)<1.9);c.ui={version:"1.7.1",plugin:{add:function(k,l,n){var m=c.ui[k].prototype;for(var j in n){m.plugins[j]=m.plugins[j]||[];m.plugins[j].push([l,n[j]])}},call:function(j,l,k){var n=j.plugins[l];if(!n||!j.element[0].parentNode){return}for(var m=0;m0){return true}m[j]=1;l=(m[j]>0);m[j]=0;return l},isOverAxis:function(k,j,l){return(k>j)&&(k<(j+l))},isOver:function(o,k,n,m,j,l){return c.ui.isOverAxis(o,n,j)&&c.ui.isOverAxis(k,m,l)},keyCode:{BACKSPACE:8,CAPS_LOCK:20,COMMA:188,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38}};if(d){var f=c.attr,e=c.fn.removeAttr,h="http://www.w3.org/2005/07/aaa",a=/^aria-/,b=/^wairole:/;c.attr=function(k,j,l){var m=l!==undefined;return(j=="role"?(m?f.call(this,k,j,"wairole:"+l):(f.apply(this,arguments)||"").replace(b,"")):(a.test(j)?(m?k.setAttributeNS(h,j.replace(a,"aaa:"),l):f.call(this,k,j.replace(a,"aaa:"))):f.apply(this,arguments)))};c.fn.removeAttr=function(j){return(a.test(j)?this.each(function(){this.removeAttributeNS(h,j.replace(a,""))}):e.call(this,j))}}c.fn.extend({remove:function(){c("*",this).add(this).each(function(){c(this).triggerHandler("remove")});return i.apply(this,arguments)},enableSelection:function(){return this.attr("unselectable","off").css("MozUserSelect","").unbind("selectstart.ui")},disableSelection:function(){return this.attr("unselectable","on").css("MozUserSelect","none").bind("selectstart.ui",function(){return false})},scrollParent:function(){var j;if((c.browser.msie&&(/(static|relative)/).test(this.css("position")))||(/absolute/).test(this.css("position"))){j=this.parents().filter(function(){return(/(relative|absolute|fixed)/).test(c.curCSS(this,"position",1))&&(/(auto|scroll)/).test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0)}else{j=this.parents().filter(function(){return(/(auto|scroll)/).test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0)}return(/fixed/).test(this.css("position"))||!j.length?c(document):j}});c.extend(c.expr[":"],{data:function(l,k,j){return !!c.data(l,j[3])},focusable:function(k){var l=k.nodeName.toLowerCase(),j=c.attr(k,"tabindex");return(/input|select|textarea|button|object/.test(l)?!k.disabled:"a"==l||"area"==l?k.href||!isNaN(j):!isNaN(j))&&!c(k)["area"==l?"parents":"closest"](":hidden").length},tabbable:function(k){var j=c.attr(k,"tabindex");return(isNaN(j)||j>=0)&&c(k).is(":focusable")}});function g(m,n,o,l){function k(q){var p=c[m][n][q]||[];return(typeof p=="string"?p.split(/,?\s+/):p)}var j=k("getter");if(l.length==1&&typeof l[0]=="string"){j=j.concat(k("getterSetter"))}return(c.inArray(o,j)!=-1)}c.widget=function(k,j){var l=k.split(".")[0];k=k.split(".")[1];c.fn[k]=function(p){var n=(typeof p=="string"),o=Array.prototype.slice.call(arguments,1);if(n&&p.substring(0,1)=="_"){return this}if(n&&g(l,k,p,o)){var m=c.data(this[0],k);return(m?m[p].apply(m,o):undefined)}return this.each(function(){var q=c.data(this,k);(!q&&!n&&c.data(this,k,new c[l][k](this,p))._init());(q&&n&&c.isFunction(q[p])&&q[p].apply(q,o))})};c[l]=c[l]||{};c[l][k]=function(o,n){var m=this;this.namespace=l;this.widgetName=k;this.widgetEventPrefix=c[l][k].eventPrefix||k;this.widgetBaseClass=l+"-"+k;this.options=c.extend({},c.widget.defaults,c[l][k].defaults,c.metadata&&c.metadata.get(o)[k],n);this.element=c(o).bind("setData."+k,function(q,p,r){if(q.target==o){return m._setData(p,r)}}).bind("getData."+k,function(q,p){if(q.target==o){return m._getData(p)}}).bind("remove",function(){return m.destroy()})};c[l][k].prototype=c.extend({},c.widget.prototype,j);c[l][k].getterSetter="option"};c.widget.prototype={_init:function(){},destroy:function(){this.element.removeData(this.widgetName).removeClass(this.widgetBaseClass+"-disabled "+this.namespace+"-state-disabled").removeAttr("aria-disabled")},option:function(l,m){var k=l,j=this;if(typeof l=="string"){if(m===undefined){return this._getData(l)}k={};k[l]=m}c.each(k,function(n,o){j._setData(n,o)})},_getData:function(j){return this.options[j]},_setData:function(j,k){this.options[j]=k;if(j=="disabled"){this.element[k?"addClass":"removeClass"](this.widgetBaseClass+"-disabled "+this.namespace+"-state-disabled").attr("aria-disabled",k)}},enable:function(){this._setData("disabled",false)},disable:function(){this._setData("disabled",true)},_trigger:function(l,m,n){var p=this.options[l],j=(l==this.widgetEventPrefix?l:this.widgetEventPrefix+l);m=c.Event(m);m.type=j;if(m.originalEvent){for(var k=c.event.props.length,o;k;){o=c.event.props[--k];m[o]=m.originalEvent[o]}}this.element.trigger(m,n);return !(c.isFunction(p)&&p.call(this.element[0],m,n)===false||m.isDefaultPrevented())}};c.widget.defaults={disabled:false};c.ui.mouse={_mouseInit:function(){var j=this;this.element.bind("mousedown."+this.widgetName,function(k){return j._mouseDown(k)}).bind("click."+this.widgetName,function(k){if(j._preventClickEvent){j._preventClickEvent=false;k.stopImmediatePropagation();return false}});if(c.browser.msie){this._mouseUnselectable=this.element.attr("unselectable");this.element.attr("unselectable","on")}this.started=false},_mouseDestroy:function(){this.element.unbind("."+this.widgetName);(c.browser.msie&&this.element.attr("unselectable",this._mouseUnselectable))},_mouseDown:function(l){l.originalEvent=l.originalEvent||{};if(l.originalEvent.mouseHandled){return}(this._mouseStarted&&this._mouseUp(l));this._mouseDownEvent=l;var k=this,m=(l.which==1),j=(typeof this.options.cancel=="string"?c(l.target).parents().add(l.target).filter(this.options.cancel).length:false);if(!m||j||!this._mouseCapture(l)){return true}this.mouseDelayMet=!this.options.delay;if(!this.mouseDelayMet){this._mouseDelayTimer=setTimeout(function(){k.mouseDelayMet=true},this.options.delay)}if(this._mouseDistanceMet(l)&&this._mouseDelayMet(l)){this._mouseStarted=(this._mouseStart(l)!==false);if(!this._mouseStarted){l.preventDefault();return true}}this._mouseMoveDelegate=function(n){return k._mouseMove(n)};this._mouseUpDelegate=function(n){return k._mouseUp(n)};c(document).bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate);(c.browser.safari||l.preventDefault());l.originalEvent.mouseHandled=true;return true},_mouseMove:function(j){if(c.browser.msie&&!j.button){return this._mouseUp(j)}if(this._mouseStarted){this._mouseDrag(j);return j.preventDefault()}if(this._mouseDistanceMet(j)&&this._mouseDelayMet(j)){this._mouseStarted=(this._mouseStart(this._mouseDownEvent,j)!==false);(this._mouseStarted?this._mouseDrag(j):this._mouseUp(j))}return !this._mouseStarted},_mouseUp:function(j){c(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate);if(this._mouseStarted){this._mouseStarted=false;this._preventClickEvent=(j.target==this._mouseDownEvent.target);this._mouseStop(j)}return false},_mouseDistanceMet:function(j){return(Math.max(Math.abs(this._mouseDownEvent.pageX-j.pageX),Math.abs(this._mouseDownEvent.pageY-j.pageY))>=this.options.distance)},_mouseDelayMet:function(j){return this.mouseDelayMet},_mouseStart:function(j){},_mouseDrag:function(j){},_mouseStop:function(j){},_mouseCapture:function(j){return true}};c.ui.mouse.defaults={cancel:null,distance:1,delay:0}})(jQuery);;/* + * jQuery UI Draggable 1.7.1 + * + * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * http://docs.jquery.com/UI/Draggables + * + * Depends: + * ui.core.js + */ (function(a){a.widget("ui.draggable",a.extend({},a.ui.mouse,{_init:function(){if(this.options.helper=="original"&&!(/^(?:r|a|f)/).test(this.element.css("position"))){this.element[0].style.position="relative"}(this.options.addClasses&&this.element.addClass("ui-draggable"));(this.options.disabled&&this.element.addClass("ui-draggable-disabled"));this._mouseInit()},destroy:function(){if(!this.element.data("draggable")){return}this.element.removeData("draggable").unbind(".draggable").removeClass("ui-draggable ui-draggable-dragging ui-draggable-disabled");this._mouseDestroy()},_mouseCapture:function(b){var c=this.options;if(this.helper||c.disabled||a(b.target).is(".ui-resizable-handle")){return false}this.handle=this._getHandle(b);if(!this.handle){return false}return true},_mouseStart:function(b){var c=this.options;this.helper=this._createHelper(b);this._cacheHelperProportions();if(a.ui.ddmanager){a.ui.ddmanager.current=this}this._cacheMargins();this.cssPosition=this.helper.css("position");this.scrollParent=this.helper.scrollParent();this.offset=this.element.offset();this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left};a.extend(this.offset,{click:{left:b.pageX-this.offset.left,top:b.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()});this.originalPosition=this._generatePosition(b);this.originalPageX=b.pageX;this.originalPageY=b.pageY;if(c.cursorAt){this._adjustOffsetFromHelper(c.cursorAt)}if(c.containment){this._setContainment()}this._trigger("start",b);this._cacheHelperProportions();if(a.ui.ddmanager&&!c.dropBehaviour){a.ui.ddmanager.prepareOffsets(this,b)}this.helper.addClass("ui-draggable-dragging");this._mouseDrag(b,true);return true},_mouseDrag:function(b,d){this.position=this._generatePosition(b);this.positionAbs=this._convertPositionTo("absolute");if(!d){var c=this._uiHash();this._trigger("drag",b,c);this.position=c.position}if(!this.options.axis||this.options.axis!="y"){this.helper[0].style.left=this.position.left+"px"}if(!this.options.axis||this.options.axis!="x"){this.helper[0].style.top=this.position.top+"px"}if(a.ui.ddmanager){a.ui.ddmanager.drag(this,b)}return false},_mouseStop:function(c){var d=false;if(a.ui.ddmanager&&!this.options.dropBehaviour){d=a.ui.ddmanager.drop(this,c)}if(this.dropped){d=this.dropped;this.dropped=false}if((this.options.revert=="invalid"&&!d)||(this.options.revert=="valid"&&d)||this.options.revert===true||(a.isFunction(this.options.revert)&&this.options.revert.call(this.element,d))){var b=this;a(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,10),function(){b._trigger("stop",c);b._clear()})}else{this._trigger("stop",c);this._clear()}return false},_getHandle:function(b){var c=!this.options.handle||!a(this.options.handle,this.element).length?true:false;a(this.options.handle,this.element).find("*").andSelf().each(function(){if(this==b.target){c=true}});return c},_createHelper:function(c){var d=this.options;var b=a.isFunction(d.helper)?a(d.helper.apply(this.element[0],[c])):(d.helper=="clone"?this.element.clone():this.element);if(!b.parents("body").length){b.appendTo((d.appendTo=="parent"?this.element[0].parentNode:d.appendTo))}if(b[0]!=this.element[0]&&!(/(fixed|absolute)/).test(b.css("position"))){b.css("position","absolute")}return b},_adjustOffsetFromHelper:function(b){if(b.left!=undefined){this.offset.click.left=b.left+this.margins.left}if(b.right!=undefined){this.offset.click.left=this.helperProportions.width-b.right+this.margins.left}if(b.top!=undefined){this.offset.click.top=b.top+this.margins.top}if(b.bottom!=undefined){this.offset.click.top=this.helperProportions.height-b.bottom+this.margins.top}},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var b=this.offsetParent.offset();if(this.cssPosition=="absolute"&&this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0])){b.left+=this.scrollParent.scrollLeft();b.top+=this.scrollParent.scrollTop()}if((this.offsetParent[0]==document.body)||(this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()=="html"&&a.browser.msie)){b={top:0,left:0}}return{top:b.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:b.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if(this.cssPosition=="relative"){var b=this.element.position();return{top:b.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:b.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}else{return{top:0,left:0}}},_cacheMargins:function(){this.margins={left:(parseInt(this.element.css("marginLeft"),10)||0),top:(parseInt(this.element.css("marginTop"),10)||0)}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var e=this.options;if(e.containment=="parent"){e.containment=this.helper[0].parentNode}if(e.containment=="document"||e.containment=="window"){this.containment=[0-this.offset.relative.left-this.offset.parent.left,0-this.offset.relative.top-this.offset.parent.top,a(e.containment=="document"?document:window).width()-this.helperProportions.width-this.margins.left,(a(e.containment=="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top]}if(!(/^(document|window|parent)$/).test(e.containment)&&e.containment.constructor!=Array){var c=a(e.containment)[0];if(!c){return}var d=a(e.containment).offset();var b=(a(c).css("overflow")!="hidden");this.containment=[d.left+(parseInt(a(c).css("borderLeftWidth"),10)||0)+(parseInt(a(c).css("paddingLeft"),10)||0)-this.margins.left,d.top+(parseInt(a(c).css("borderTopWidth"),10)||0)+(parseInt(a(c).css("paddingTop"),10)||0)-this.margins.top,d.left+(b?Math.max(c.scrollWidth,c.offsetWidth):c.offsetWidth)-(parseInt(a(c).css("borderLeftWidth"),10)||0)-(parseInt(a(c).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left,d.top+(b?Math.max(c.scrollHeight,c.offsetHeight):c.offsetHeight)-(parseInt(a(c).css("borderTopWidth"),10)||0)-(parseInt(a(c).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top]}else{if(e.containment.constructor==Array){this.containment=e.containment}}},_convertPositionTo:function(f,h){if(!h){h=this.position}var c=f=="absolute"?1:-1;var e=this.options,b=this.cssPosition=="absolute"&&!(this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,g=(/(html|body)/i).test(b[0].tagName);return{top:(h.top+this.offset.relative.top*c+this.offset.parent.top*c-(a.browser.safari&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():(g?0:b.scrollTop()))*c)),left:(h.left+this.offset.relative.left*c+this.offset.parent.left*c-(a.browser.safari&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():g?0:b.scrollLeft())*c))}},_generatePosition:function(e){var h=this.options,b=this.cssPosition=="absolute"&&!(this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,i=(/(html|body)/i).test(b[0].tagName);if(this.cssPosition=="relative"&&!(this.scrollParent[0]!=document&&this.scrollParent[0]!=this.offsetParent[0])){this.offset.relative=this._getRelativeOffset()}var d=e.pageX;var c=e.pageY;if(this.originalPosition){if(this.containment){if(e.pageX-this.offset.click.leftthis.containment[2]){d=this.containment[2]+this.offset.click.left}if(e.pageY-this.offset.click.top>this.containment[3]){c=this.containment[3]+this.offset.click.top}}if(h.grid){var g=this.originalPageY+Math.round((c-this.originalPageY)/h.grid[1])*h.grid[1];c=this.containment?(!(g-this.offset.click.topthis.containment[3])?g:(!(g-this.offset.click.topthis.containment[2])?f:(!(f-this.offset.click.left
').css({width:this.offsetWidth+"px",height:this.offsetHeight+"px",position:"absolute",opacity:"0.001",zIndex:1000}).css(a(this).offset()).appendTo("body")})},stop:function(b,c){a("div.ui-draggable-iframeFix").each(function(){this.parentNode.removeChild(this)})}});a.ui.plugin.add("draggable","opacity",{start:function(c,d){var b=a(d.helper),e=a(this).data("draggable").options;if(b.css("opacity")){e._opacity=b.css("opacity")}b.css("opacity",e.opacity)},stop:function(b,c){var d=a(this).data("draggable").options;if(d._opacity){a(c.helper).css("opacity",d._opacity)}}});a.ui.plugin.add("draggable","scroll",{start:function(c,d){var b=a(this).data("draggable");if(b.scrollParent[0]!=document&&b.scrollParent[0].tagName!="HTML"){b.overflowOffset=b.scrollParent.offset()}},drag:function(d,e){var c=a(this).data("draggable"),f=c.options,b=false;if(c.scrollParent[0]!=document&&c.scrollParent[0].tagName!="HTML"){if(!f.axis||f.axis!="x"){if((c.overflowOffset.top+c.scrollParent[0].offsetHeight)-d.pageY=0;v--){var s=g.snapElements[v].left,n=s+g.snapElements[v].width,m=g.snapElements[v].top,A=m+g.snapElements[v].height;if(!((s-y=p&&n<=k)||(m>=p&&m<=k)||(nk))&&((e>=g&&e<=c)||(d>=g&&d<=c)||(ec));break;default:return false;break}};a.ui.ddmanager={current:null,droppables:{"default":[]},prepareOffsets:function(e,g){var b=a.ui.ddmanager.droppables[e.options.scope];var f=g?g.type:null;var h=(e.currentItem||e.element).find(":data(droppable)").andSelf();droppablesLoop:for(var d=0;d').css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(),top:this.element.css("top"),left:this.element.css("left")}));this.element=this.element.parent().data("resizable",this.element.data("resizable"));this.elementIsWrapper=true;this.element.css({marginLeft:this.originalElement.css("marginLeft"),marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom")});this.originalElement.css({marginLeft:0,marginTop:0,marginRight:0,marginBottom:0});this.originalResizeStyle=this.originalElement.css("resize");this.originalElement.css("resize","none");this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"}));this.originalElement.css({margin:this.originalElement.css("margin")});this._proportionallyResize()}this.handles=j.handles||(!c(".ui-resizable-handle",this.element).length?"e,s,se":{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne",nw:".ui-resizable-nw"});if(this.handles.constructor==String){if(this.handles=="all"){this.handles="n,e,s,w,se,sw,ne,nw"}var k=this.handles.split(",");this.handles={};for(var f=0;f');if(/sw|se|ne|nw/.test(h)){g.css({zIndex:++j.zIndex})}if("se"==h){g.addClass("ui-icon ui-icon-gripsmall-diagonal-se")}this.handles[h]=".ui-resizable-"+h;this.element.append(g)}}this._renderAxis=function(p){p=p||this.element;for(var m in this.handles){if(this.handles[m].constructor==String){this.handles[m]=c(this.handles[m],this.element).show()}if(this.elementIsWrapper&&this.originalElement[0].nodeName.match(/textarea|input|select|button/i)){var n=c(this.handles[m],this.element),o=0;o=/sw|ne|nw|se|n|s/.test(m)?n.outerHeight():n.outerWidth();var l=["padding",/ne|nw|n/.test(m)?"Top":/se|sw|s/.test(m)?"Bottom":/^e$/.test(m)?"Right":"Left"].join("");p.css(l,o);this._proportionallyResize()}if(!c(this.handles[m]).length){continue}}};this._renderAxis(this.element);this._handles=c(".ui-resizable-handle",this.element).disableSelection();this._handles.mouseover(function(){if(!e.resizing){if(this.className){var i=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i)}e.axis=i&&i[1]?i[1]:"se"}});if(j.autoHide){this._handles.hide();c(this.element).addClass("ui-resizable-autohide").hover(function(){c(this).removeClass("ui-resizable-autohide");e._handles.show()},function(){if(!e.resizing){c(this).addClass("ui-resizable-autohide");e._handles.hide()}})}this._mouseInit()},destroy:function(){this._mouseDestroy();var d=function(f){c(f).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing").removeData("resizable").unbind(".resizable").find(".ui-resizable-handle").remove()};if(this.elementIsWrapper){d(this.element);var e=this.element;e.parent().append(this.originalElement.css({position:e.css("position"),width:e.outerWidth(),height:e.outerHeight(),top:e.css("top"),left:e.css("left")})).end().remove()}this.originalElement.css("resize",this.originalResizeStyle);d(this.originalElement)},_mouseCapture:function(e){var f=false;for(var d in this.handles){if(c(this.handles[d])[0]==e.target){f=true}}return this.options.disabled||!!f},_mouseStart:function(f){var i=this.options,e=this.element.position(),d=this.element;this.resizing=true;this.documentScroll={top:c(document).scrollTop(),left:c(document).scrollLeft()};if(d.is(".ui-draggable")||(/absolute/).test(d.css("position"))){d.css({position:"absolute",top:e.top,left:e.left})}if(c.browser.opera&&(/relative/).test(d.css("position"))){d.css({position:"relative",top:"auto",left:"auto"})}this._renderProxy();var j=b(this.helper.css("left")),g=b(this.helper.css("top"));if(i.containment){j+=c(i.containment).scrollLeft()||0;g+=c(i.containment).scrollTop()||0}this.offset=this.helper.offset();this.position={left:j,top:g};this.size=this._helper?{width:d.outerWidth(),height:d.outerHeight()}:{width:d.width(),height:d.height()};this.originalSize=this._helper?{width:d.outerWidth(),height:d.outerHeight()}:{width:d.width(),height:d.height()};this.originalPosition={left:j,top:g};this.sizeDiff={width:d.outerWidth()-d.width(),height:d.outerHeight()-d.height()};this.originalMousePosition={left:f.pageX,top:f.pageY};this.aspectRatio=(typeof i.aspectRatio=="number")?i.aspectRatio:((this.originalSize.width/this.originalSize.height)||1);var h=c(".ui-resizable-"+this.axis).css("cursor");c("body").css("cursor",h=="auto"?this.axis+"-resize":h);d.addClass("ui-resizable-resizing");this._propagate("start",f);return true},_mouseDrag:function(d){var g=this.helper,f=this.options,l={},p=this,i=this.originalMousePosition,m=this.axis;var q=(d.pageX-i.left)||0,n=(d.pageY-i.top)||0;var h=this._change[m];if(!h){return false}var k=h.apply(this,[d,q,n]),j=c.browser.msie&&c.browser.version<7,e=this.sizeDiff;if(this._aspectRatio||d.shiftKey){k=this._updateRatio(k,d)}k=this._respectSize(k,d);this._propagate("resize",d);g.css({top:this.position.top+"px",left:this.position.left+"px",width:this.size.width+"px",height:this.size.height+"px"});if(!this._helper&&this._proportionallyResizeElements.length){this._proportionallyResize()}this._updateCache(k);this._trigger("resize",d,this.ui());return false},_mouseStop:function(g){this.resizing=false;var h=this.options,l=this;if(this._helper){var f=this._proportionallyResizeElements,d=f.length&&(/textarea/i).test(f[0].nodeName),e=d&&c.ui.hasScroll(f[0],"left")?0:l.sizeDiff.height,j=d?0:l.sizeDiff.width;var m={width:(l.size.width-j),height:(l.size.height-e)},i=(parseInt(l.element.css("left"),10)+(l.position.left-l.originalPosition.left))||null,k=(parseInt(l.element.css("top"),10)+(l.position.top-l.originalPosition.top))||null;if(!h.animate){this.element.css(c.extend(m,{top:k,left:i}))}l.helper.height(l.size.height);l.helper.width(l.size.width);if(this._helper&&!h.animate){this._proportionallyResize()}}c("body").css("cursor","auto");this.element.removeClass("ui-resizable-resizing");this._propagate("stop",g);if(this._helper){this.helper.remove()}return false},_updateCache:function(d){var e=this.options;this.offset=this.helper.offset();if(a(d.left)){this.position.left=d.left}if(a(d.top)){this.position.top=d.top}if(a(d.height)){this.size.height=d.height}if(a(d.width)){this.size.width=d.width}},_updateRatio:function(g,f){var h=this.options,i=this.position,e=this.size,d=this.axis;if(g.height){g.width=(e.height*this.aspectRatio)}else{if(g.width){g.height=(e.width/this.aspectRatio)}}if(d=="sw"){g.left=i.left+(e.width-g.width);g.top=null}if(d=="nw"){g.top=i.top+(e.height-g.height);g.left=i.left+(e.width-g.width)}return g},_respectSize:function(k,f){var i=this.helper,h=this.options,q=this._aspectRatio||f.shiftKey,p=this.axis,s=a(k.width)&&h.maxWidth&&(h.maxWidthk.width),r=a(k.height)&&h.minHeight&&(h.minHeight>k.height);if(g){k.width=h.minWidth}if(r){k.height=h.minHeight}if(s){k.width=h.maxWidth}if(l){k.height=h.maxHeight}var e=this.originalPosition.left+this.originalSize.width,n=this.position.top+this.size.height;var j=/sw|nw|w/.test(p),d=/nw|ne|n/.test(p);if(g&&j){k.left=e-h.minWidth}if(s&&j){k.left=e-h.maxWidth}if(r&&d){k.top=n-h.minHeight}if(l&&d){k.top=n-h.maxHeight}var m=!k.width&&!k.height;if(m&&!k.left&&k.top){k.top=null}else{if(m&&!k.top&&k.left){k.left=null}}return k},_proportionallyResize:function(){var j=this.options;if(!this._proportionallyResizeElements.length){return}var f=this.helper||this.element;for(var e=0;e');var d=c.browser.msie&&c.browser.version<7,f=(d?1:0),g=(d?2:-1);this.helper.addClass(this._helper).css({width:this.element.outerWidth()+g,height:this.element.outerHeight()+g,position:"absolute",left:this.elementOffset.left-f+"px",top:this.elementOffset.top-f+"px",zIndex:++h.zIndex});this.helper.appendTo("body").disableSelection()}else{this.helper=this.element}},_change:{e:function(f,e,d){return{width:this.originalSize.width+e}},w:function(g,e,d){var i=this.options,f=this.originalSize,h=this.originalPosition;return{left:h.left+e,width:f.width-e}},n:function(g,e,d){var i=this.options,f=this.originalSize,h=this.originalPosition;return{top:h.top+d,height:f.height-d}},s:function(f,e,d){return{height:this.originalSize.height+d}},se:function(f,e,d){return c.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[f,e,d]))},sw:function(f,e,d){return c.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[f,e,d]))},ne:function(f,e,d){return c.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[f,e,d]))},nw:function(f,e,d){return c.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[f,e,d]))}},_propagate:function(e,d){c.ui.plugin.call(this,e,[d,this.ui()]);(e!="resize"&&this._trigger(e,d,this.ui()))},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}}));c.extend(c.ui.resizable,{version:"1.7.1",eventPrefix:"resize",defaults:{alsoResize:false,animate:false,animateDuration:"slow",animateEasing:"swing",aspectRatio:false,autoHide:false,cancel:":input,option",containment:false,delay:0,distance:1,ghost:false,grid:false,handles:"e,s,se",helper:false,maxHeight:null,maxWidth:null,minHeight:10,minWidth:10,zIndex:1000}});c.ui.plugin.add("resizable","alsoResize",{start:function(e,f){var d=c(this).data("resizable"),g=d.options;_store=function(h){c(h).each(function(){c(this).data("resizable-alsoresize",{width:parseInt(c(this).width(),10),height:parseInt(c(this).height(),10),left:parseInt(c(this).css("left"),10),top:parseInt(c(this).css("top"),10)})})};if(typeof(g.alsoResize)=="object"&&!g.alsoResize.parentNode){if(g.alsoResize.length){g.alsoResize=g.alsoResize[0];_store(g.alsoResize)}else{c.each(g.alsoResize,function(h,i){_store(h)})}}else{_store(g.alsoResize)}},resize:function(f,h){var e=c(this).data("resizable"),i=e.options,g=e.originalSize,k=e.originalPosition;var j={height:(e.size.height-g.height)||0,width:(e.size.width-g.width)||0,top:(e.position.top-k.top)||0,left:(e.position.left-k.left)||0},d=function(l,m){c(l).each(function(){var p=c(this),q=c(this).data("resizable-alsoresize"),o={},n=m&&m.length?m:["width","height","top","left"];c.each(n||["width","height","top","left"],function(r,t){var s=(q[t]||0)+(j[t]||0);if(s&&s>=0){o[t]=s||null}});if(/relative/.test(p.css("position"))&&c.browser.opera){e._revertToRelativePosition=true;p.css({position:"absolute",top:"auto",left:"auto"})}p.css(o)})};if(typeof(i.alsoResize)=="object"&&!i.alsoResize.nodeType){c.each(i.alsoResize,function(l,m){d(l,m)})}else{d(i.alsoResize)}},stop:function(e,f){var d=c(this).data("resizable");if(d._revertToRelativePosition&&c.browser.opera){d._revertToRelativePosition=false;el.css({position:"relative"})}c(this).removeData("resizable-alsoresize-start")}});c.ui.plugin.add("resizable","animate",{stop:function(h,m){var n=c(this).data("resizable"),i=n.options;var g=n._proportionallyResizeElements,d=g.length&&(/textarea/i).test(g[0].nodeName),e=d&&c.ui.hasScroll(g[0],"left")?0:n.sizeDiff.height,k=d?0:n.sizeDiff.width;var f={width:(n.size.width-k),height:(n.size.height-e)},j=(parseInt(n.element.css("left"),10)+(n.position.left-n.originalPosition.left))||null,l=(parseInt(n.element.css("top"),10)+(n.position.top-n.originalPosition.top))||null;n.element.animate(c.extend(f,l&&j?{top:l,left:j}:{}),{duration:i.animateDuration,easing:i.animateEasing,step:function(){var o={width:parseInt(n.element.css("width"),10),height:parseInt(n.element.css("height"),10),top:parseInt(n.element.css("top"),10),left:parseInt(n.element.css("left"),10)};if(g&&g.length){c(g[0]).css({width:o.width,height:o.height})}n._updateCache(o);n._propagate("resize",h)}})}});c.ui.plugin.add("resizable","containment",{start:function(e,q){var s=c(this).data("resizable"),i=s.options,k=s.element;var f=i.containment,j=(f instanceof c)?f.get(0):(/parent/.test(f))?k.parent().get(0):f;if(!j){return}s.containerElement=c(j);if(/document/.test(f)||f==document){s.containerOffset={left:0,top:0};s.containerPosition={left:0,top:0};s.parentData={element:c(document),left:0,top:0,width:c(document).width(),height:c(document).height()||document.body.parentNode.scrollHeight}}else{var m=c(j),h=[];c(["Top","Right","Left","Bottom"]).each(function(p,o){h[p]=b(m.css("padding"+o))});s.containerOffset=m.offset();s.containerPosition=m.position();s.containerSize={height:(m.innerHeight()-h[3]),width:(m.innerWidth()-h[1])};var n=s.containerOffset,d=s.containerSize.height,l=s.containerSize.width,g=(c.ui.hasScroll(j,"left")?j.scrollWidth:l),r=(c.ui.hasScroll(j)?j.scrollHeight:d);s.parentData={element:j,left:n.left,top:n.top,width:g,height:r}}},resize:function(f,p){var s=c(this).data("resizable"),h=s.options,e=s.containerSize,n=s.containerOffset,l=s.size,m=s.position,q=s._aspectRatio||f.shiftKey,d={top:0,left:0},g=s.containerElement;if(g[0]!=document&&(/static/).test(g.css("position"))){d=n}if(m.left<(s._helper?n.left:0)){s.size.width=s.size.width+(s._helper?(s.position.left-n.left):(s.position.left-d.left));if(q){s.size.height=s.size.width/h.aspectRatio}s.position.left=h.helper?n.left:0}if(m.top<(s._helper?n.top:0)){s.size.height=s.size.height+(s._helper?(s.position.top-n.top):s.position.top);if(q){s.size.width=s.size.height*h.aspectRatio}s.position.top=s._helper?n.top:0}s.offset.left=s.parentData.left+s.position.left;s.offset.top=s.parentData.top+s.position.top;var k=Math.abs((s._helper?s.offset.left-d.left:(s.offset.left-d.left))+s.sizeDiff.width),r=Math.abs((s._helper?s.offset.top-d.top:(s.offset.top-n.top))+s.sizeDiff.height);var j=s.containerElement.get(0)==s.element.parent().get(0),i=/relative|absolute/.test(s.containerElement.css("position"));if(j&&i){k-=s.parentData.left}if(k+s.size.width>=s.parentData.width){s.size.width=s.parentData.width-k;if(q){s.size.height=s.size.width/s.aspectRatio}}if(r+s.size.height>=s.parentData.height){s.size.height=s.parentData.height-r;if(q){s.size.width=s.size.height*s.aspectRatio}}},stop:function(e,m){var p=c(this).data("resizable"),f=p.options,k=p.position,l=p.containerOffset,d=p.containerPosition,g=p.containerElement;var i=c(p.helper),q=i.offset(),n=i.outerWidth()-p.sizeDiff.width,j=i.outerHeight()-p.sizeDiff.height;if(p._helper&&!f.animate&&(/relative/).test(g.css("position"))){c(this).css({left:q.left-d.left-l.left,width:n,height:j})}if(p._helper&&!f.animate&&(/static/).test(g.css("position"))){c(this).css({left:q.left-d.left-l.left,width:n,height:j})}}});c.ui.plugin.add("resizable","ghost",{start:function(f,g){var d=c(this).data("resizable"),h=d.options,e=d.size;d.ghost=d.originalElement.clone();d.ghost.css({opacity:0.25,display:"block",position:"relative",height:e.height,width:e.width,margin:0,left:0,top:0}).addClass("ui-resizable-ghost").addClass(typeof h.ghost=="string"?h.ghost:"");d.ghost.appendTo(d.helper)},resize:function(e,f){var d=c(this).data("resizable"),g=d.options;if(d.ghost){d.ghost.css({position:"relative",height:d.size.height,width:d.size.width})}},stop:function(e,f){var d=c(this).data("resizable"),g=d.options;if(d.ghost&&d.helper){d.helper.get(0).removeChild(d.ghost.get(0))}}});c.ui.plugin.add("resizable","grid",{resize:function(d,l){var n=c(this).data("resizable"),g=n.options,j=n.size,h=n.originalSize,i=n.originalPosition,m=n.axis,k=g._aspectRatio||d.shiftKey;g.grid=typeof g.grid=="number"?[g.grid,g.grid]:g.grid;var f=Math.round((j.width-h.width)/(g.grid[0]||1))*(g.grid[0]||1),e=Math.round((j.height-h.height)/(g.grid[1]||1))*(g.grid[1]||1);if(/^(se|s|e)$/.test(m)){n.size.width=h.width+f;n.size.height=h.height+e}else{if(/^(ne)$/.test(m)){n.size.width=h.width+f;n.size.height=h.height+e;n.position.top=i.top-e}else{if(/^(sw)$/.test(m)){n.size.width=h.width+f;n.size.height=h.height+e;n.position.left=i.left-f}else{n.size.width=h.width+f;n.size.height=h.height+e;n.position.top=i.top-e;n.position.left=i.left-f}}}}});var b=function(d){return parseInt(d,10)||0};var a=function(d){return !isNaN(parseInt(d,10))}})(jQuery);;/* + * jQuery UI Selectable 1.7.1 + * + * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * http://docs.jquery.com/UI/Selectables + * + * Depends: + * ui.core.js + */ (function(a){a.widget("ui.selectable",a.extend({},a.ui.mouse,{_init:function(){var b=this;this.element.addClass("ui-selectable");this.dragged=false;var c;this.refresh=function(){c=a(b.options.filter,b.element[0]);c.each(function(){var d=a(this);var e=d.offset();a.data(this,"selectable-item",{element:this,$element:d,left:e.left,top:e.top,right:e.left+d.outerWidth(),bottom:e.top+d.outerHeight(),startselected:false,selected:d.hasClass("ui-selected"),selecting:d.hasClass("ui-selecting"),unselecting:d.hasClass("ui-unselecting")})})};this.refresh();this.selectees=c.addClass("ui-selectee");this._mouseInit();this.helper=a(document.createElement("div")).css({border:"1px dotted black"}).addClass("ui-selectable-helper")},destroy:function(){this.element.removeClass("ui-selectable ui-selectable-disabled").removeData("selectable").unbind(".selectable");this._mouseDestroy()},_mouseStart:function(d){var b=this;this.opos=[d.pageX,d.pageY];if(this.options.disabled){return}var c=this.options;this.selectees=a(c.filter,this.element[0]);this._trigger("start",d);a(c.appendTo).append(this.helper);this.helper.css({"z-index":100,position:"absolute",left:d.clientX,top:d.clientY,width:0,height:0});if(c.autoRefresh){this.refresh()}this.selectees.filter(".ui-selected").each(function(){var e=a.data(this,"selectable-item");e.startselected=true;if(!d.metaKey){e.$element.removeClass("ui-selected");e.selected=false;e.$element.addClass("ui-unselecting");e.unselecting=true;b._trigger("unselecting",d,{unselecting:e.element})}});a(d.target).parents().andSelf().each(function(){var e=a.data(this,"selectable-item");if(e){e.$element.removeClass("ui-unselecting").addClass("ui-selecting");e.unselecting=false;e.selecting=true;e.selected=true;b._trigger("selecting",d,{selecting:e.element});return false}})},_mouseDrag:function(i){var c=this;this.dragged=true;if(this.options.disabled){return}var e=this.options;var d=this.opos[0],h=this.opos[1],b=i.pageX,g=i.pageY;if(d>b){var f=b;b=d;d=f}if(h>g){var f=g;g=h;h=f}this.helper.css({left:d,top:h,width:b-d,height:g-h});this.selectees.each(function(){var j=a.data(this,"selectable-item");if(!j||j.element==c.element[0]){return}var k=false;if(e.tolerance=="touch"){k=(!(j.left>b||j.rightg||j.bottomd&&j.righth&&j.bottom=0;b--){this.items[b].item.removeData("sortable-item")}},_mouseCapture:function(e,f){if(this.reverting){return false}if(this.options.disabled||this.options.type=="static"){return false}this._refreshItems(e);var d=null,c=this,b=a(e.target).parents().each(function(){if(a.data(this,"sortable-item")==c){d=a(this);return false}});if(a.data(e.target,"sortable-item")==c){d=a(e.target)}if(!d){return false}if(this.options.handle&&!f){var g=false;a(this.options.handle,d).find("*").andSelf().each(function(){if(this==e.target){g=true}});if(!g){return false}}this.currentItem=d;this._removeCurrentsFromItems();return true},_mouseStart:function(e,f,b){var g=this.options,c=this;this.currentContainer=this;this.refreshPositions();this.helper=this._createHelper(e);this._cacheHelperProportions();this._cacheMargins();this.scrollParent=this.helper.scrollParent();this.offset=this.currentItem.offset();this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left};this.helper.css("position","absolute");this.cssPosition=this.helper.css("position");a.extend(this.offset,{click:{left:e.pageX-this.offset.left,top:e.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()});this.originalPosition=this._generatePosition(e);this.originalPageX=e.pageX;this.originalPageY=e.pageY;if(g.cursorAt){this._adjustOffsetFromHelper(g.cursorAt)}this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]};if(this.helper[0]!=this.currentItem[0]){this.currentItem.hide()}this._createPlaceholder();if(g.containment){this._setContainment()}if(g.cursor){if(a("body").css("cursor")){this._storedCursor=a("body").css("cursor")}a("body").css("cursor",g.cursor)}if(g.opacity){if(this.helper.css("opacity")){this._storedOpacity=this.helper.css("opacity")}this.helper.css("opacity",g.opacity)}if(g.zIndex){if(this.helper.css("zIndex")){this._storedZIndex=this.helper.css("zIndex")}this.helper.css("zIndex",g.zIndex)}if(this.scrollParent[0]!=document&&this.scrollParent[0].tagName!="HTML"){this.overflowOffset=this.scrollParent.offset()}this._trigger("start",e,this._uiHash());if(!this._preserveHelperProportions){this._cacheHelperProportions()}if(!b){for(var d=this.containers.length-1;d>=0;d--){this.containers[d]._trigger("activate",e,c._uiHash(this))}}if(a.ui.ddmanager){a.ui.ddmanager.current=this}if(a.ui.ddmanager&&!g.dropBehaviour){a.ui.ddmanager.prepareOffsets(this,e)}this.dragging=true;this.helper.addClass("ui-sortable-helper");this._mouseDrag(e);return true},_mouseDrag:function(f){this.position=this._generatePosition(f);this.positionAbs=this._convertPositionTo("absolute");if(!this.lastPositionAbs){this.lastPositionAbs=this.positionAbs}if(this.options.scroll){var g=this.options,b=false;if(this.scrollParent[0]!=document&&this.scrollParent[0].tagName!="HTML"){if((this.overflowOffset.top+this.scrollParent[0].offsetHeight)-f.pageY=0;d--){var e=this.items[d],c=e.item[0],h=this._intersectsWithPointer(e);if(!h){continue}if(c!=this.currentItem[0]&&this.placeholder[h==1?"next":"prev"]()[0]!=c&&!a.ui.contains(this.placeholder[0],c)&&(this.options.type=="semi-dynamic"?!a.ui.contains(this.element[0],c):true)){this.direction=h==1?"down":"up";if(this.options.tolerance=="pointer"||this._intersectsWithSides(e)){this._rearrange(f,e)}else{break}this._trigger("change",f,this._uiHash());break}}this._contactContainers(f);if(a.ui.ddmanager){a.ui.ddmanager.drag(this,f)}this._trigger("sort",f,this._uiHash());this.lastPositionAbs=this.positionAbs;return false},_mouseStop:function(c,d){if(!c){return}if(a.ui.ddmanager&&!this.options.dropBehaviour){a.ui.ddmanager.drop(this,c)}if(this.options.revert){var b=this;var e=b.placeholder.offset();b.reverting=true;a(this.helper).animate({left:e.left-this.offset.parent.left-b.margins.left+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollLeft),top:e.top-this.offset.parent.top-b.margins.top+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollTop)},parseInt(this.options.revert,10)||500,function(){b._clear(c)})}else{this._clear(c,d)}return false},cancel:function(){var b=this;if(this.dragging){this._mouseUp();if(this.options.helper=="original"){this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper")}else{this.currentItem.show()}for(var c=this.containers.length-1;c>=0;c--){this.containers[c]._trigger("deactivate",null,b._uiHash(this));if(this.containers[c].containerCache.over){this.containers[c]._trigger("out",null,b._uiHash(this));this.containers[c].containerCache.over=0}}}if(this.placeholder[0].parentNode){this.placeholder[0].parentNode.removeChild(this.placeholder[0])}if(this.options.helper!="original"&&this.helper&&this.helper[0].parentNode){this.helper.remove()}a.extend(this,{helper:null,dragging:false,reverting:false,_noFinalSort:null});if(this.domPosition.prev){a(this.domPosition.prev).after(this.currentItem)}else{a(this.domPosition.parent).prepend(this.currentItem)}return true},serialize:function(d){var b=this._getItemsAsjQuery(d&&d.connected);var c=[];d=d||{};a(b).each(function(){var e=(a(d.item||this).attr(d.attribute||"id")||"").match(d.expression||(/(.+)[-=_](.+)/));if(e){c.push((d.key||e[1]+"[]")+"="+(d.key&&d.expression?e[1]:e[2]))}});return c.join("&")},toArray:function(d){var b=this._getItemsAsjQuery(d&&d.connected);var c=[];d=d||{};b.each(function(){c.push(a(d.item||this).attr(d.attribute||"id")||"")});return c},_intersectsWith:function(m){var e=this.positionAbs.left,d=e+this.helperProportions.width,k=this.positionAbs.top,j=k+this.helperProportions.height;var f=m.left,c=f+m.width,n=m.top,i=n+m.height;var o=this.offset.click.top,h=this.offset.click.left;var g=(k+o)>n&&(k+o)f&&(e+h)m[this.floating?"width":"height"])){return g}else{return(f0?"down":"up")},_getDragHorizontalDirection:function(){var b=this.positionAbs.left-this.lastPositionAbs.left;return b!=0&&(b>0?"right":"left")},refresh:function(b){this._refreshItems(b);this.refreshPositions()},_connectWith:function(){var b=this.options;return b.connectWith.constructor==String?[b.connectWith]:b.connectWith},_getItemsAsjQuery:function(b){var l=this;var g=[];var e=[];var h=this._connectWith();if(h&&b){for(var d=h.length-1;d>=0;d--){var k=a(h[d]);for(var c=k.length-1;c>=0;c--){var f=a.data(k[c],"sortable");if(f&&f!=this&&!f.options.disabled){e.push([a.isFunction(f.options.items)?f.options.items.call(f.element):a(f.options.items,f.element).not(".ui-sortable-helper"),f])}}}}e.push([a.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):a(this.options.items,this.element).not(".ui-sortable-helper"),this]);for(var d=e.length-1;d>=0;d--){e[d][0].each(function(){g.push(this)})}return a(g)},_removeCurrentsFromItems:function(){var d=this.currentItem.find(":data(sortable-item)");for(var c=0;c=0;e--){var m=a(l[e]);for(var d=m.length-1;d>=0;d--){var g=a.data(m[d],"sortable");if(g&&g!=this&&!g.options.disabled){f.push([a.isFunction(g.options.items)?g.options.items.call(g.element[0],b,{item:this.currentItem}):a(g.options.items,g.element),g]);this.containers.push(g)}}}}for(var e=f.length-1;e>=0;e--){var k=f[e][1];var c=f[e][0];for(var d=0,n=c.length;d=0;d--){var e=this.items[d];if(e.instance!=this.currentContainer&&this.currentContainer&&e.item[0]!=this.currentItem[0]){continue}var c=this.options.toleranceElement?a(this.options.toleranceElement,e.item):e.item;if(!b){e.width=c.outerWidth();e.height=c.outerHeight()}var f=c.offset();e.left=f.left;e.top=f.top}if(this.options.custom&&this.options.custom.refreshContainers){this.options.custom.refreshContainers.call(this)}else{for(var d=this.containers.length-1;d>=0;d--){var f=this.containers[d].element.offset();this.containers[d].containerCache.left=f.left;this.containers[d].containerCache.top=f.top;this.containers[d].containerCache.width=this.containers[d].element.outerWidth();this.containers[d].containerCache.height=this.containers[d].element.outerHeight()}}},_createPlaceholder:function(d){var b=d||this,e=b.options;if(!e.placeholder||e.placeholder.constructor==String){var c=e.placeholder;e.placeholder={element:function(){var f=a(document.createElement(b.currentItem[0].nodeName)).addClass(c||b.currentItem[0].className+" ui-sortable-placeholder").removeClass("ui-sortable-helper")[0];if(!c){f.style.visibility="hidden"}return f},update:function(f,g){if(c&&!e.forcePlaceholderSize){return}if(!g.height()){g.height(b.currentItem.innerHeight()-parseInt(b.currentItem.css("paddingTop")||0,10)-parseInt(b.currentItem.css("paddingBottom")||0,10))}if(!g.width()){g.width(b.currentItem.innerWidth()-parseInt(b.currentItem.css("paddingLeft")||0,10)-parseInt(b.currentItem.css("paddingRight")||0,10))}}}}b.placeholder=a(e.placeholder.element.call(b.element,b.currentItem));b.currentItem.after(b.placeholder);e.placeholder.update(b,b.placeholder)},_contactContainers:function(d){for(var c=this.containers.length-1;c>=0;c--){if(this._intersectsWith(this.containers[c].containerCache)){if(!this.containers[c].containerCache.over){if(this.currentContainer!=this.containers[c]){var h=10000;var g=null;var e=this.positionAbs[this.containers[c].floating?"left":"top"];for(var b=this.items.length-1;b>=0;b--){if(!a.ui.contains(this.containers[c].element[0],this.items[b].item[0])){continue}var f=this.items[b][this.containers[c].floating?"left":"top"];if(Math.abs(f-e)this.containment[2]){d=this.containment[2]+this.offset.click.left}if(e.pageY-this.offset.click.top>this.containment[3]){c=this.containment[3]+this.offset.click.top}}if(h.grid){var g=this.originalPageY+Math.round((c-this.originalPageY)/h.grid[1])*h.grid[1];c=this.containment?(!(g-this.offset.click.topthis.containment[3])?g:(!(g-this.offset.click.topthis.containment[2])?f:(!(f-this.offset.click.left=0;c--){if(a.ui.contains(this.containers[c].element[0],this.currentItem[0])&&!e){f.push((function(g){return function(h){g._trigger("receive",h,this._uiHash(this))}}).call(this,this.containers[c]));f.push((function(g){return function(h){g._trigger("update",h,this._uiHash(this))}}).call(this,this.containers[c]))}}}for(var c=this.containers.length-1;c>=0;c--){if(!e){f.push((function(g){return function(h){g._trigger("deactivate",h,this._uiHash(this))}}).call(this,this.containers[c]))}if(this.containers[c].containerCache.over){f.push((function(g){return function(h){g._trigger("out",h,this._uiHash(this))}}).call(this,this.containers[c]));this.containers[c].containerCache.over=0}}if(this._storedCursor){a("body").css("cursor",this._storedCursor)}if(this._storedOpacity){this.helper.css("opacity",this._storedOpacity)}if(this._storedZIndex){this.helper.css("zIndex",this._storedZIndex=="auto"?"":this._storedZIndex)}this.dragging=false;if(this.cancelHelperRemoval){if(!e){this._trigger("beforeStop",d,this._uiHash());for(var c=0;c *",opacity:false,placeholder:false,revert:false,scroll:true,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1000}})})(jQuery);;/* + * jQuery UI Accordion 1.7.1 + * + * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * http://docs.jquery.com/UI/Accordion + * + * Depends: + * ui.core.js + */ (function(a){a.widget("ui.accordion",{_init:function(){var d=this.options,b=this;this.running=0;if(d.collapsible==a.ui.accordion.defaults.collapsible&&d.alwaysOpen!=a.ui.accordion.defaults.alwaysOpen){d.collapsible=!d.alwaysOpen}if(d.navigation){var c=this.element.find("a").filter(d.navigationFilter);if(c.length){if(c.filter(d.header).length){this.active=c}else{this.active=c.parent().parent().prev();c.addClass("ui-accordion-content-active")}}}this.element.addClass("ui-accordion ui-widget ui-helper-reset");if(this.element[0].nodeName=="UL"){this.element.children("li").addClass("ui-accordion-li-fix")}this.headers=this.element.find(d.header).addClass("ui-accordion-header ui-helper-reset ui-state-default ui-corner-all").bind("mouseenter.accordion",function(){a(this).addClass("ui-state-hover")}).bind("mouseleave.accordion",function(){a(this).removeClass("ui-state-hover")}).bind("focus.accordion",function(){a(this).addClass("ui-state-focus")}).bind("blur.accordion",function(){a(this).removeClass("ui-state-focus")});this.headers.next().addClass("ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom");this.active=this._findActive(this.active||d.active).toggleClass("ui-state-default").toggleClass("ui-state-active").toggleClass("ui-corner-all").toggleClass("ui-corner-top");this.active.next().addClass("ui-accordion-content-active");a("").addClass("ui-icon "+d.icons.header).prependTo(this.headers);this.active.find(".ui-icon").toggleClass(d.icons.header).toggleClass(d.icons.headerSelected);if(a.browser.msie){this.element.find("a").css("zoom","1")}this.resize();this.element.attr("role","tablist");this.headers.attr("role","tab").bind("keydown",function(e){return b._keydown(e)}).next().attr("role","tabpanel");this.headers.not(this.active||"").attr("aria-expanded","false").attr("tabIndex","-1").next().hide();if(!this.active.length){this.headers.eq(0).attr("tabIndex","0")}else{this.active.attr("aria-expanded","true").attr("tabIndex","0")}if(!a.browser.safari){this.headers.find("a").attr("tabIndex","-1")}if(d.event){this.headers.bind((d.event)+".accordion",function(e){return b._clickHandler.call(b,e,this)})}},destroy:function(){var c=this.options;this.element.removeClass("ui-accordion ui-widget ui-helper-reset").removeAttr("role").unbind(".accordion").removeData("accordion");this.headers.unbind(".accordion").removeClass("ui-accordion-header ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-corner-top").removeAttr("role").removeAttr("aria-expanded").removeAttr("tabindex");this.headers.find("a").removeAttr("tabindex");this.headers.children(".ui-icon").remove();var b=this.headers.next().css("display","").removeAttr("role").removeClass("ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active");if(c.autoHeight||c.fillHeight){b.css("height","")}},_setData:function(b,c){if(b=="alwaysOpen"){b="collapsible";c=!c}a.widget.prototype._setData.apply(this,arguments)},_keydown:function(e){var g=this.options,f=a.ui.keyCode;if(g.disabled||e.altKey||e.ctrlKey){return}var d=this.headers.length;var b=this.headers.index(e.target);var c=false;switch(e.keyCode){case f.RIGHT:case f.DOWN:c=this.headers[(b+1)%d];break;case f.LEFT:case f.UP:c=this.headers[(b-1+d)%d];break;case f.SPACE:case f.ENTER:return this._clickHandler({target:e.target},e.target)}if(c){a(e.target).attr("tabIndex","-1");a(c).attr("tabIndex","0");c.focus();return false}return true},resize:function(){var e=this.options,d;if(e.fillSpace){if(a.browser.msie){var b=this.element.parent().css("overflow");this.element.parent().css("overflow","hidden")}d=this.element.parent().height();if(a.browser.msie){this.element.parent().css("overflow",b)}this.headers.each(function(){d-=a(this).outerHeight()});var c=0;this.headers.next().each(function(){c=Math.max(c,a(this).innerHeight()-a(this).height())}).height(Math.max(0,d-c)).css("overflow","auto")}else{if(e.autoHeight){d=0;this.headers.next().each(function(){d=Math.max(d,a(this).outerHeight())}).height(d)}}},activate:function(b){var c=this._findActive(b)[0];this._clickHandler({target:c},c)},_findActive:function(b){return b?typeof b=="number"?this.headers.filter(":eq("+b+")"):this.headers.not(this.headers.not(b)):b===false?a([]):this.headers.filter(":eq(0)")},_clickHandler:function(b,f){var d=this.options;if(d.disabled){return false}if(!b.target&&d.collapsible){this.active.removeClass("ui-state-active ui-corner-top").addClass("ui-state-default ui-corner-all").find(".ui-icon").removeClass(d.icons.headerSelected).addClass(d.icons.header);this.active.next().addClass("ui-accordion-content-active");var h=this.active.next(),e={options:d,newHeader:a([]),oldHeader:d.active,newContent:a([]),oldContent:h},c=(this.active=a([]));this._toggle(c,h,e);return false}var g=a(b.currentTarget||f);var i=g[0]==this.active[0];if(this.running||(!d.collapsible&&i)){return false}this.active.removeClass("ui-state-active ui-corner-top").addClass("ui-state-default ui-corner-all").find(".ui-icon").removeClass(d.icons.headerSelected).addClass(d.icons.header);this.active.next().addClass("ui-accordion-content-active");if(!i){g.removeClass("ui-state-default ui-corner-all").addClass("ui-state-active ui-corner-top").find(".ui-icon").removeClass(d.icons.header).addClass(d.icons.headerSelected);g.next().addClass("ui-accordion-content-active")}var c=g.next(),h=this.active.next(),e={options:d,newHeader:i&&d.collapsible?a([]):g,oldHeader:this.active,newContent:i&&d.collapsible?a([]):c.find("> *"),oldContent:h.find("> *")},j=this.headers.index(this.active[0])>this.headers.index(g[0]);this.active=i?a([]):g;this._toggle(c,h,e,i,j);return false},_toggle:function(b,i,g,j,k){var d=this.options,m=this;this.toShow=b;this.toHide=i;this.data=g;var c=function(){if(!m){return}return m._completed.apply(m,arguments)};this._trigger("changestart",null,this.data);this.running=i.size()===0?b.size():i.size();if(d.animated){var f={};if(d.collapsible&&j){f={toShow:a([]),toHide:i,complete:c,down:k,autoHeight:d.autoHeight||d.fillSpace}}else{f={toShow:b,toHide:i,complete:c,down:k,autoHeight:d.autoHeight||d.fillSpace}}if(!d.proxied){d.proxied=d.animated}if(!d.proxiedDuration){d.proxiedDuration=d.duration}d.animated=a.isFunction(d.proxied)?d.proxied(f):d.proxied;d.duration=a.isFunction(d.proxiedDuration)?d.proxiedDuration(f):d.proxiedDuration;var l=a.ui.accordion.animations,e=d.duration,h=d.animated;if(!l[h]){l[h]=function(n){this.slide(n,{easing:h,duration:e||700})}}l[h](f)}else{if(d.collapsible&&j){b.toggle()}else{i.hide();b.show()}c(true)}i.prev().attr("aria-expanded","false").attr("tabIndex","-1").blur();b.prev().attr("aria-expanded","true").attr("tabIndex","0").focus()},_completed:function(b){var c=this.options;this.running=b?0:--this.running;if(this.running){return}if(c.clearStyle){this.toShow.add(this.toHide).css({height:"",overflow:""})}this._trigger("change",null,this.data)}});a.extend(a.ui.accordion,{version:"1.7.1",defaults:{active:null,alwaysOpen:true,animated:"slide",autoHeight:true,clearStyle:false,collapsible:false,event:"click",fillSpace:false,header:"> li > :first-child,> :not(li):even",icons:{header:"ui-icon-triangle-1-e",headerSelected:"ui-icon-triangle-1-s"},navigation:false,navigationFilter:function(){return this.href.toLowerCase()==location.href.toLowerCase()}},animations:{slide:function(j,h){j=a.extend({easing:"swing",duration:300},j,h);if(!j.toHide.size()){j.toShow.animate({height:"show"},j);return}if(!j.toShow.size()){j.toHide.animate({height:"hide"},j);return}var c=j.toShow.css("overflow"),g,d={},f={},e=["height","paddingTop","paddingBottom"],b;var i=j.toShow;b=i[0].style.width;i.width(parseInt(i.parent().width(),10)-parseInt(i.css("paddingLeft"),10)-parseInt(i.css("paddingRight"),10)-(parseInt(i.css("borderLeftWidth"),10)||0)-(parseInt(i.css("borderRightWidth"),10)||0));a.each(e,function(k,m){f[m]="hide";var l=(""+a.css(j.toShow[0],m)).match(/^([\d+-.]+)(.*)$/);d[m]={value:l[1],unit:l[2]||"px"}});j.toShow.css({height:0,overflow:"hidden"}).show();j.toHide.filter(":hidden").each(j.complete).end().filter(":visible").animate(f,{step:function(k,l){if(l.prop=="height"){g=(l.now-l.start)/(l.end-l.start)}j.toShow[0].style[l.prop]=(g*d[l.prop].value)+d[l.prop].unit},duration:j.duration,easing:j.easing,complete:function(){if(!j.autoHeight){j.toShow.css("height","")}j.toShow.css("width",b);j.toShow.css({overflow:c});j.complete()}})},bounceslide:function(b){this.slide(b,{easing:b.down?"easeOutBounce":"swing",duration:b.down?1000:200})},easeslide:function(b){this.slide(b,{easing:"easeinout",duration:700})}}})})(jQuery);;/* + * jQuery UI Dialog 1.7.1 + * + * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * http://docs.jquery.com/UI/Dialog + * + * Depends: + * ui.core.js + * ui.draggable.js + * ui.resizable.js + */ (function(c){var b={dragStart:"start.draggable",drag:"drag.draggable",dragStop:"stop.draggable",maxHeight:"maxHeight.resizable",minHeight:"minHeight.resizable",maxWidth:"maxWidth.resizable",minWidth:"minWidth.resizable",resizeStart:"start.resizable",resize:"drag.resizable",resizeStop:"stop.resizable"},a="ui-dialog ui-widget ui-widget-content ui-corner-all ";c.widget("ui.dialog",{_init:function(){this.originalTitle=this.element.attr("title");var l=this,m=this.options,j=m.title||this.originalTitle||" ",e=c.ui.dialog.getTitleId(this.element),k=(this.uiDialog=c("
")).appendTo(document.body).hide().addClass(a+m.dialogClass).css({position:"absolute",overflow:"hidden",zIndex:m.zIndex}).attr("tabIndex",-1).css("outline",0).keydown(function(n){(m.closeOnEscape&&n.keyCode&&n.keyCode==c.ui.keyCode.ESCAPE&&l.close(n))}).attr({role:"dialog","aria-labelledby":e}).mousedown(function(n){l.moveToTop(false,n)}),g=this.element.show().removeAttr("title").addClass("ui-dialog-content ui-widget-content").appendTo(k),f=(this.uiDialogTitlebar=c("
")).addClass("ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix").prependTo(k),i=c('').addClass("ui-dialog-titlebar-close ui-corner-all").attr("role","button").hover(function(){i.addClass("ui-state-hover")},function(){i.removeClass("ui-state-hover")}).focus(function(){i.addClass("ui-state-focus")}).blur(function(){i.removeClass("ui-state-focus")}).mousedown(function(n){n.stopPropagation()}).click(function(n){l.close(n);return false}).appendTo(f),h=(this.uiDialogTitlebarCloseText=c("")).addClass("ui-icon ui-icon-closethick").text(m.closeText).appendTo(i),d=c("").addClass("ui-dialog-title").attr("id",e).html(j).prependTo(f);f.find("*").add(f).disableSelection();(m.draggable&&c.fn.draggable&&this._makeDraggable());(m.resizable&&c.fn.resizable&&this._makeResizable());this._createButtons(m.buttons);this._isOpen=false;(m.bgiframe&&c.fn.bgiframe&&k.bgiframe());(m.autoOpen&&this.open())},destroy:function(){(this.overlay&&this.overlay.destroy());this.uiDialog.hide();this.element.unbind(".dialog").removeData("dialog").removeClass("ui-dialog-content ui-widget-content").hide().appendTo("body");this.uiDialog.remove();(this.originalTitle&&this.element.attr("title",this.originalTitle))},close:function(e){var d=this;if(false===d._trigger("beforeclose",e)){return}(d.overlay&&d.overlay.destroy());d.uiDialog.unbind("keypress.ui-dialog");(d.options.hide?d.uiDialog.hide(d.options.hide,function(){d._trigger("close",e)}):d.uiDialog.hide()&&d._trigger("close",e));c.ui.dialog.overlay.resize();d._isOpen=false},isOpen:function(){return this._isOpen},moveToTop:function(f,e){if((this.options.modal&&!f)||(!this.options.stack&&!this.options.modal)){return this._trigger("focus",e)}if(this.options.zIndex>c.ui.dialog.maxZ){c.ui.dialog.maxZ=this.options.zIndex}(this.overlay&&this.overlay.$el.css("z-index",c.ui.dialog.overlay.maxZ=++c.ui.dialog.maxZ));var d={scrollTop:this.element.attr("scrollTop"),scrollLeft:this.element.attr("scrollLeft")};this.uiDialog.css("z-index",++c.ui.dialog.maxZ);this.element.attr(d);this._trigger("focus",e)},open:function(){if(this._isOpen){return}var e=this.options,d=this.uiDialog;this.overlay=e.modal?new c.ui.dialog.overlay(this):null;(d.next().length&&d.appendTo("body"));this._size();this._position(e.position);d.show(e.show);this.moveToTop(true);(e.modal&&d.bind("keypress.ui-dialog",function(h){if(h.keyCode!=c.ui.keyCode.TAB){return}var g=c(":tabbable",this),i=g.filter(":first")[0],f=g.filter(":last")[0];if(h.target==f&&!h.shiftKey){setTimeout(function(){i.focus()},1)}else{if(h.target==i&&h.shiftKey){setTimeout(function(){f.focus()},1)}}}));c([]).add(d.find(".ui-dialog-content :tabbable:first")).add(d.find(".ui-dialog-buttonpane :tabbable:first")).add(d).filter(":first").focus();this._trigger("open");this._isOpen=true},_createButtons:function(g){var f=this,d=false,e=c("
").addClass("ui-dialog-buttonpane ui-widget-content ui-helper-clearfix");this.uiDialog.find(".ui-dialog-buttonpane").remove();(typeof g=="object"&&g!==null&&c.each(g,function(){return !(d=true)}));if(d){c.each(g,function(h,i){c('').addClass("ui-state-default ui-corner-all").text(h).click(function(){i.apply(f.element[0],arguments)}).hover(function(){c(this).addClass("ui-state-hover")},function(){c(this).removeClass("ui-state-hover")}).focus(function(){c(this).addClass("ui-state-focus")}).blur(function(){c(this).removeClass("ui-state-focus")}).appendTo(e)});e.appendTo(this.uiDialog)}},_makeDraggable:function(){var d=this,f=this.options,e;this.uiDialog.draggable({cancel:".ui-dialog-content",handle:".ui-dialog-titlebar",containment:"document",start:function(){e=f.height;c(this).height(c(this).height()).addClass("ui-dialog-dragging");(f.dragStart&&f.dragStart.apply(d.element[0],arguments))},drag:function(){(f.drag&&f.drag.apply(d.element[0],arguments))},stop:function(){c(this).removeClass("ui-dialog-dragging").height(e);(f.dragStop&&f.dragStop.apply(d.element[0],arguments));c.ui.dialog.overlay.resize()}})},_makeResizable:function(g){g=(g===undefined?this.options.resizable:g);var d=this,f=this.options,e=typeof g=="string"?g:"n,e,s,w,se,sw,ne,nw";this.uiDialog.resizable({cancel:".ui-dialog-content",alsoResize:this.element,maxWidth:f.maxWidth,maxHeight:f.maxHeight,minWidth:f.minWidth,minHeight:f.minHeight,start:function(){c(this).addClass("ui-dialog-resizing");(f.resizeStart&&f.resizeStart.apply(d.element[0],arguments))},resize:function(){(f.resize&&f.resize.apply(d.element[0],arguments))},handles:e,stop:function(){c(this).removeClass("ui-dialog-resizing");f.height=c(this).height();f.width=c(this).width();(f.resizeStop&&f.resizeStop.apply(d.element[0],arguments));c.ui.dialog.overlay.resize()}}).find(".ui-resizable-se").addClass("ui-icon ui-icon-grip-diagonal-se")},_position:function(i){var e=c(window),f=c(document),g=f.scrollTop(),d=f.scrollLeft(),h=g;if(c.inArray(i,["center","top","right","bottom","left"])>=0){i=[i=="right"||i=="left"?i:"center",i=="top"||i=="bottom"?i:"middle"]}if(i.constructor!=Array){i=["center","middle"]}if(i[0].constructor==Number){d+=i[0]}else{switch(i[0]){case"left":d+=0;break;case"right":d+=e.width()-this.uiDialog.outerWidth();break;default:case"center":d+=(e.width()-this.uiDialog.outerWidth())/2}}if(i[1].constructor==Number){g+=i[1]}else{switch(i[1]){case"top":g+=0;break;case"bottom":g+=e.height()-this.uiDialog.outerHeight();break;default:case"middle":g+=(e.height()-this.uiDialog.outerHeight())/2}}g=Math.max(g,h);this.uiDialog.css({top:g,left:d})},_setData:function(e,f){(b[e]&&this.uiDialog.data(b[e],f));switch(e){case"buttons":this._createButtons(f);break;case"closeText":this.uiDialogTitlebarCloseText.text(f);break;case"dialogClass":this.uiDialog.removeClass(this.options.dialogClass).addClass(a+f);break;case"draggable":(f?this._makeDraggable():this.uiDialog.draggable("destroy"));break;case"height":this.uiDialog.height(f);break;case"position":this._position(f);break;case"resizable":var d=this.uiDialog,g=this.uiDialog.is(":data(resizable)");(g&&!f&&d.resizable("destroy"));(g&&typeof f=="string"&&d.resizable("option","handles",f));(g||this._makeResizable(f));break;case"title":c(".ui-dialog-title",this.uiDialogTitlebar).html(f||" ");break;case"width":this.uiDialog.width(f);break}c.widget.prototype._setData.apply(this,arguments)},_size:function(){var e=this.options;this.element.css({height:0,minHeight:0,width:"auto"});var d=this.uiDialog.css({height:"auto",width:e.width}).height();this.element.css({minHeight:Math.max(e.minHeight-d,0),height:e.height=="auto"?"auto":Math.max(e.height-d,0)})}});c.extend(c.ui.dialog,{version:"1.7.1",defaults:{autoOpen:true,bgiframe:false,buttons:{},closeOnEscape:true,closeText:"close",dialogClass:"",draggable:true,hide:null,height:"auto",maxHeight:false,maxWidth:false,minHeight:150,minWidth:150,modal:false,position:"center",resizable:true,show:null,stack:true,title:"",width:300,zIndex:1000},getter:"isOpen",uuid:0,maxZ:0,getTitleId:function(d){return"ui-dialog-title-"+(d.attr("id")||++this.uuid)},overlay:function(d){this.$el=c.ui.dialog.overlay.create(d)}});c.extend(c.ui.dialog.overlay,{instances:[],maxZ:0,events:c.map("focus,mousedown,mouseup,keydown,keypress,click".split(","),function(d){return d+".dialog-overlay"}).join(" "),create:function(e){if(this.instances.length===0){setTimeout(function(){c(document).bind(c.ui.dialog.overlay.events,function(f){var g=c(f.target).parents(".ui-dialog").css("zIndex")||0;return(g>c.ui.dialog.overlay.maxZ)})},1);c(document).bind("keydown.dialog-overlay",function(f){(e.options.closeOnEscape&&f.keyCode&&f.keyCode==c.ui.keyCode.ESCAPE&&e.close(f))});c(window).bind("resize.dialog-overlay",c.ui.dialog.overlay.resize)}var d=c("
").appendTo(document.body).addClass("ui-widget-overlay").css({width:this.width(),height:this.height()});(e.options.bgiframe&&c.fn.bgiframe&&d.bgiframe());this.instances.push(d);return d},destroy:function(d){this.instances.splice(c.inArray(this.instances,d),1);if(this.instances.length===0){c([document,window]).unbind(".dialog-overlay")}d.remove()},height:function(){if(c.browser.msie&&c.browser.version<7){var e=Math.max(document.documentElement.scrollHeight,document.body.scrollHeight);var d=Math.max(document.documentElement.offsetHeight,document.body.offsetHeight);if(e
");if(!c.values){c.values=[this._valueMin(),this._valueMin()]}if(c.values.length&&c.values.length!=2){c.values=[c.values[0],c.values[0]]}}else{this.range=a("
")}this.range.appendTo(this.element).addClass("ui-slider-range");if(c.range=="min"||c.range=="max"){this.range.addClass("ui-slider-range-"+c.range)}this.range.addClass("ui-widget-header")}if(a(".ui-slider-handle",this.element).length==0){a('
').appendTo(this.element).addClass("ui-slider-handle")}if(c.values&&c.values.length){while(a(".ui-slider-handle",this.element).length').appendTo(this.element).addClass("ui-slider-handle")}}this.handles=a(".ui-slider-handle",this.element).addClass("ui-state-default ui-corner-all");this.handle=this.handles.eq(0);this.handles.add(this.range).filter("a").click(function(d){d.preventDefault()}).hover(function(){a(this).addClass("ui-state-hover")},function(){a(this).removeClass("ui-state-hover")}).focus(function(){a(".ui-slider .ui-state-focus").removeClass("ui-state-focus");a(this).addClass("ui-state-focus")}).blur(function(){a(this).removeClass("ui-state-focus")});this.handles.each(function(d){a(this).data("index.ui-slider-handle",d)});this.handles.keydown(function(i){var f=true;var e=a(this).data("index.ui-slider-handle");if(b.options.disabled){return}switch(i.keyCode){case a.ui.keyCode.HOME:case a.ui.keyCode.END:case a.ui.keyCode.UP:case a.ui.keyCode.RIGHT:case a.ui.keyCode.DOWN:case a.ui.keyCode.LEFT:f=false;if(!b._keySliding){b._keySliding=true;a(this).addClass("ui-state-active");b._start(i,e)}break}var g,d,h=b._step();if(b.options.values&&b.options.values.length){g=d=b.values(e)}else{g=d=b.value()}switch(i.keyCode){case a.ui.keyCode.HOME:d=b._valueMin();break;case a.ui.keyCode.END:d=b._valueMax();break;case a.ui.keyCode.UP:case a.ui.keyCode.RIGHT:if(g==b._valueMax()){return}d=g+h;break;case a.ui.keyCode.DOWN:case a.ui.keyCode.LEFT:if(g==b._valueMin()){return}d=g-h;break}b._slide(i,e,d);return f}).keyup(function(e){var d=a(this).data("index.ui-slider-handle");if(b._keySliding){b._stop(e,d);b._change(e,d);b._keySliding=false;a(this).removeClass("ui-state-active")}});this._refreshValue()},destroy:function(){this.handles.remove();this.range.remove();this.element.removeClass("ui-slider ui-slider-horizontal ui-slider-vertical ui-slider-disabled ui-widget ui-widget-content ui-corner-all").removeData("slider").unbind(".slider");this._mouseDestroy()},_mouseCapture:function(d){var e=this.options;if(e.disabled){return false}this.elementSize={width:this.element.outerWidth(),height:this.element.outerHeight()};this.elementOffset=this.element.offset();var h={x:d.pageX,y:d.pageY};var j=this._normValueFromMouse(h);var c=this._valueMax()-this._valueMin()+1,f;var k=this,i;this.handles.each(function(l){var m=Math.abs(j-k.values(l));if(c>m){c=m;f=a(this);i=l}});if(e.range==true&&this.values(1)==e.min){f=a(this.handles[++i])}this._start(d,i);k._handleIndex=i;f.addClass("ui-state-active").focus();var g=f.offset();var b=!a(d.target).parents().andSelf().is(".ui-slider-handle");this._clickOffset=b?{left:0,top:0}:{left:d.pageX-g.left-(f.width()/2),top:d.pageY-g.top-(f.height()/2)-(parseInt(f.css("borderTopWidth"),10)||0)-(parseInt(f.css("borderBottomWidth"),10)||0)+(parseInt(f.css("marginTop"),10)||0)};j=this._normValueFromMouse(h);this._slide(d,i,j);return true},_mouseStart:function(b){return true},_mouseDrag:function(d){var b={x:d.pageX,y:d.pageY};var c=this._normValueFromMouse(b);this._slide(d,this._handleIndex,c);return false},_mouseStop:function(b){this.handles.removeClass("ui-state-active");this._stop(b,this._handleIndex);this._change(b,this._handleIndex);this._handleIndex=null;this._clickOffset=null;return false},_detectOrientation:function(){this.orientation=this.options.orientation=="vertical"?"vertical":"horizontal"},_normValueFromMouse:function(d){var c,h;if("horizontal"==this.orientation){c=this.elementSize.width;h=d.x-this.elementOffset.left-(this._clickOffset?this._clickOffset.left:0)}else{c=this.elementSize.height;h=d.y-this.elementOffset.top-(this._clickOffset?this._clickOffset.top:0)}var f=(h/c);if(f>1){f=1}if(f<0){f=0}if("vertical"==this.orientation){f=1-f}var e=this._valueMax()-this._valueMin(),i=f*e,b=i%this.options.step,g=this._valueMin()+i-b;if(b>(this.options.step/2)){g+=this.options.step}return parseFloat(g.toFixed(5))},_start:function(d,c){var b={handle:this.handles[c],value:this.value()};if(this.options.values&&this.options.values.length){b.value=this.values(c);b.values=this.values()}this._trigger("start",d,b)},_slide:function(f,e,d){var g=this.handles[e];if(this.options.values&&this.options.values.length){var b=this.values(e?0:1);if((e==0&&d>=b)||(e==1&&d<=b)){d=b}if(d!=this.values(e)){var c=this.values();c[e]=d;var h=this._trigger("slide",f,{handle:this.handles[e],value:d,values:c});var b=this.values(e?0:1);if(h!==false){this.values(e,d,(f.type=="mousedown"&&this.options.animate),true)}}}else{if(d!=this.value()){var h=this._trigger("slide",f,{handle:this.handles[e],value:d});if(h!==false){this._setData("value",d,(f.type=="mousedown"&&this.options.animate))}}}},_stop:function(d,c){var b={handle:this.handles[c],value:this.value()};if(this.options.values&&this.options.values.length){b.value=this.values(c);b.values=this.values()}this._trigger("stop",d,b)},_change:function(d,c){var b={handle:this.handles[c],value:this.value()};if(this.options.values&&this.options.values.length){b.value=this.values(c);b.values=this.values()}this._trigger("change",d,b)},value:function(b){if(arguments.length){this._setData("value",b);this._change(null,0)}return this._value()},values:function(b,e,c,d){if(arguments.length>1){this.options.values[b]=e;this._refreshValue(c);if(!d){this._change(null,b)}}if(arguments.length){if(this.options.values&&this.options.values.length){return this._values(b)}else{return this.value()}}else{return this._values()}},_setData:function(b,d,c){a.widget.prototype._setData.apply(this,arguments);switch(b){case"orientation":this._detectOrientation();this.element.removeClass("ui-slider-horizontal ui-slider-vertical").addClass("ui-slider-"+this.orientation);this._refreshValue(c);break;case"value":this._refreshValue(c);break}},_step:function(){var b=this.options.step;return b},_value:function(){var b=this.options.value;if(bthis._valueMax()){b=this._valueMax()}return b},_values:function(b){if(arguments.length){var c=this.options.values[b];if(cthis._valueMax()){c=this._valueMax()}return c}else{return this.options.values}},_valueMin:function(){var b=this.options.min;return b},_valueMax:function(){var b=this.options.max;return b},_refreshValue:function(c){var f=this.options.range,d=this.options,l=this;if(this.options.values&&this.options.values.length){var i,h;this.handles.each(function(p,n){var o=(l.values(p)-l._valueMin())/(l._valueMax()-l._valueMin())*100;var m={};m[l.orientation=="horizontal"?"left":"bottom"]=o+"%";a(this).stop(1,1)[c?"animate":"css"](m,d.animate);if(l.options.range===true){if(l.orientation=="horizontal"){(p==0)&&l.range.stop(1,1)[c?"animate":"css"]({left:o+"%"},d.animate);(p==1)&&l.range[c?"animate":"css"]({width:(o-lastValPercent)+"%"},{queue:false,duration:d.animate})}else{(p==0)&&l.range.stop(1,1)[c?"animate":"css"]({bottom:(o)+"%"},d.animate);(p==1)&&l.range[c?"animate":"css"]({height:(o-lastValPercent)+"%"},{queue:false,duration:d.animate})}}lastValPercent=o})}else{var j=this.value(),g=this._valueMin(),k=this._valueMax(),e=k!=g?(j-g)/(k-g)*100:0;var b={};b[l.orientation=="horizontal"?"left":"bottom"]=e+"%";this.handle.stop(1,1)[c?"animate":"css"](b,d.animate);(f=="min")&&(this.orientation=="horizontal")&&this.range.stop(1,1)[c?"animate":"css"]({width:e+"%"},d.animate);(f=="max")&&(this.orientation=="horizontal")&&this.range[c?"animate":"css"]({width:(100-e)+"%"},{queue:false,duration:d.animate});(f=="min")&&(this.orientation=="vertical")&&this.range.stop(1,1)[c?"animate":"css"]({height:e+"%"},d.animate);(f=="max")&&(this.orientation=="vertical")&&this.range[c?"animate":"css"]({height:(100-e)+"%"},{queue:false,duration:d.animate})}}}));a.extend(a.ui.slider,{getter:"value values",version:"1.7.1",eventPrefix:"slide",defaults:{animate:false,delay:0,distance:0,max:100,min:0,orientation:"horizontal",range:false,step:1,value:0,values:null}})})(jQuery);;/* + * jQuery UI Tabs 1.7.1 + * + * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * http://docs.jquery.com/UI/Tabs + * + * Depends: + * ui.core.js + */ (function(a){a.widget("ui.tabs",{_init:function(){if(this.options.deselectable!==undefined){this.options.collapsible=this.options.deselectable}this._tabify(true)},_setData:function(b,c){if(b=="selected"){if(this.options.collapsible&&c==this.options.selected){return}this.select(c)}else{this.options[b]=c;if(b=="deselectable"){this.options.collapsible=c}this._tabify()}},_tabId:function(b){return b.title&&b.title.replace(/\s/g,"_").replace(/[^A-Za-z0-9\-_:\.]/g,"")||this.options.idPrefix+a.data(b)},_sanitizeSelector:function(b){return b.replace(/:/g,"\\:")},_cookie:function(){var b=this.cookie||(this.cookie=this.options.cookie.name||"ui-tabs-"+a.data(this.list[0]));return a.cookie.apply(null,[b].concat(a.makeArray(arguments)))},_ui:function(c,b){return{tab:c,panel:b,index:this.anchors.index(c)}},_cleanup:function(){this.lis.filter(".ui-state-processing").removeClass("ui-state-processing").find("span:data(label.tabs)").each(function(){var b=a(this);b.html(b.data("label.tabs")).removeData("label.tabs")})},_tabify:function(n){this.list=this.element.children("ul:first");this.lis=a("li:has(a[href])",this.list);this.anchors=this.lis.map(function(){return a("a",this)[0]});this.panels=a([]);var p=this,d=this.options;var c=/^#.+/;this.anchors.each(function(r,o){var q=a(o).attr("href");var s=q.split("#")[0],u;if(s&&(s===location.toString().split("#")[0]||(u=a("base")[0])&&s===u.href)){q=o.hash;o.href=q}if(c.test(q)){p.panels=p.panels.add(p._sanitizeSelector(q))}else{if(q!="#"){a.data(o,"href.tabs",q);a.data(o,"load.tabs",q.replace(/#.*$/,""));var w=p._tabId(o);o.href="#"+w;var v=a("#"+w);if(!v.length){v=a(d.panelTemplate).attr("id",w).addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").insertAfter(p.panels[r-1]||p.list);v.data("destroy.tabs",true)}p.panels=p.panels.add(v)}else{d.disabled.push(r)}}});if(n){this.element.addClass("ui-tabs ui-widget ui-widget-content ui-corner-all");this.list.addClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all");this.lis.addClass("ui-state-default ui-corner-top");this.panels.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom");if(d.selected===undefined){if(location.hash){this.anchors.each(function(q,o){if(o.hash==location.hash){d.selected=q;return false}})}if(typeof d.selected!="number"&&d.cookie){d.selected=parseInt(p._cookie(),10)}if(typeof d.selected!="number"&&this.lis.filter(".ui-tabs-selected").length){d.selected=this.lis.index(this.lis.filter(".ui-tabs-selected"))}d.selected=d.selected||0}else{if(d.selected===null){d.selected=-1}}d.selected=((d.selected>=0&&this.anchors[d.selected])||d.selected<0)?d.selected:0;d.disabled=a.unique(d.disabled.concat(a.map(this.lis.filter(".ui-state-disabled"),function(q,o){return p.lis.index(q)}))).sort();if(a.inArray(d.selected,d.disabled)!=-1){d.disabled.splice(a.inArray(d.selected,d.disabled),1)}this.panels.addClass("ui-tabs-hide");this.lis.removeClass("ui-tabs-selected ui-state-active");if(d.selected>=0&&this.anchors.length){this.panels.eq(d.selected).removeClass("ui-tabs-hide");this.lis.eq(d.selected).addClass("ui-tabs-selected ui-state-active");p.element.queue("tabs",function(){p._trigger("show",null,p._ui(p.anchors[d.selected],p.panels[d.selected]))});this.load(d.selected)}a(window).bind("unload",function(){p.lis.add(p.anchors).unbind(".tabs");p.lis=p.anchors=p.panels=null})}else{d.selected=this.lis.index(this.lis.filter(".ui-tabs-selected"))}this.element[d.collapsible?"addClass":"removeClass"]("ui-tabs-collapsible");if(d.cookie){this._cookie(d.selected,d.cookie)}for(var g=0,m;(m=this.lis[g]);g++){a(m)[a.inArray(g,d.disabled)!=-1&&!a(m).hasClass("ui-tabs-selected")?"addClass":"removeClass"]("ui-state-disabled")}if(d.cache===false){this.anchors.removeData("cache.tabs")}this.lis.add(this.anchors).unbind(".tabs");if(d.event!="mouseover"){var f=function(o,i){if(i.is(":not(.ui-state-disabled)")){i.addClass("ui-state-"+o)}};var j=function(o,i){i.removeClass("ui-state-"+o)};this.lis.bind("mouseover.tabs",function(){f("hover",a(this))});this.lis.bind("mouseout.tabs",function(){j("hover",a(this))});this.anchors.bind("focus.tabs",function(){f("focus",a(this).closest("li"))});this.anchors.bind("blur.tabs",function(){j("focus",a(this).closest("li"))})}var b,h;if(d.fx){if(a.isArray(d.fx)){b=d.fx[0];h=d.fx[1]}else{b=h=d.fx}}function e(i,o){i.css({display:""});if(a.browser.msie&&o.opacity){i[0].style.removeAttribute("filter")}}var k=h?function(i,o){a(i).closest("li").removeClass("ui-state-default").addClass("ui-tabs-selected ui-state-active");o.hide().removeClass("ui-tabs-hide").animate(h,h.duration||"normal",function(){e(o,h);p._trigger("show",null,p._ui(i,o[0]))})}:function(i,o){a(i).closest("li").removeClass("ui-state-default").addClass("ui-tabs-selected ui-state-active");o.removeClass("ui-tabs-hide");p._trigger("show",null,p._ui(i,o[0]))};var l=b?function(o,i){i.animate(b,b.duration||"normal",function(){p.lis.removeClass("ui-tabs-selected ui-state-active").addClass("ui-state-default");i.addClass("ui-tabs-hide");e(i,b);p.element.dequeue("tabs")})}:function(o,i,q){p.lis.removeClass("ui-tabs-selected ui-state-active").addClass("ui-state-default");i.addClass("ui-tabs-hide");p.element.dequeue("tabs")};this.anchors.bind(d.event+".tabs",function(){var o=this,r=a(this).closest("li"),i=p.panels.filter(":not(.ui-tabs-hide)"),q=a(p._sanitizeSelector(this.hash));if((r.hasClass("ui-tabs-selected")&&!d.collapsible)||r.hasClass("ui-state-disabled")||r.hasClass("ui-state-processing")||p._trigger("select",null,p._ui(this,q[0]))===false){this.blur();return false}d.selected=p.anchors.index(this);p.abort();if(d.collapsible){if(r.hasClass("ui-tabs-selected")){d.selected=-1;if(d.cookie){p._cookie(d.selected,d.cookie)}p.element.queue("tabs",function(){l(o,i)}).dequeue("tabs");this.blur();return false}else{if(!i.length){if(d.cookie){p._cookie(d.selected,d.cookie)}p.element.queue("tabs",function(){k(o,q)});p.load(p.anchors.index(this));this.blur();return false}}}if(d.cookie){p._cookie(d.selected,d.cookie)}if(q.length){if(i.length){p.element.queue("tabs",function(){l(o,i)})}p.element.queue("tabs",function(){k(o,q)});p.load(p.anchors.index(this))}else{throw"jQuery UI Tabs: Mismatching fragment identifier."}if(a.browser.msie){this.blur()}});this.anchors.bind("click.tabs",function(){return false})},destroy:function(){var b=this.options;this.abort();this.element.unbind(".tabs").removeClass("ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible").removeData("tabs");this.list.removeClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all");this.anchors.each(function(){var c=a.data(this,"href.tabs");if(c){this.href=c}var d=a(this).unbind(".tabs");a.each(["href","load","cache"],function(e,f){d.removeData(f+".tabs")})});this.lis.unbind(".tabs").add(this.panels).each(function(){if(a.data(this,"destroy.tabs")){a(this).remove()}else{a(this).removeClass(["ui-state-default","ui-corner-top","ui-tabs-selected","ui-state-active","ui-state-hover","ui-state-focus","ui-state-disabled","ui-tabs-panel","ui-widget-content","ui-corner-bottom","ui-tabs-hide"].join(" "))}});if(b.cookie){this._cookie(null,b.cookie)}},add:function(e,d,c){if(c===undefined){c=this.anchors.length}var b=this,g=this.options,i=a(g.tabTemplate.replace(/#\{href\}/g,e).replace(/#\{label\}/g,d)),h=!e.indexOf("#")?e.replace("#",""):this._tabId(a("a",i)[0]);i.addClass("ui-state-default ui-corner-top").data("destroy.tabs",true);var f=a("#"+h);if(!f.length){f=a(g.panelTemplate).attr("id",h).data("destroy.tabs",true)}f.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide");if(c>=this.lis.length){i.appendTo(this.list);f.appendTo(this.list[0].parentNode)}else{i.insertBefore(this.lis[c]);f.insertBefore(this.panels[c])}g.disabled=a.map(g.disabled,function(k,j){return k>=c?++k:k});this._tabify();if(this.anchors.length==1){i.addClass("ui-tabs-selected ui-state-active");f.removeClass("ui-tabs-hide");this.element.queue("tabs",function(){b._trigger("show",null,b._ui(b.anchors[0],b.panels[0]))});this.load(0)}this._trigger("add",null,this._ui(this.anchors[c],this.panels[c]))},remove:function(b){var d=this.options,e=this.lis.eq(b).remove(),c=this.panels.eq(b).remove();if(e.hasClass("ui-tabs-selected")&&this.anchors.length>1){this.select(b+(b+1=b?--g:g});this._tabify();this._trigger("remove",null,this._ui(e.find("a")[0],c[0]))},enable:function(b){var c=this.options;if(a.inArray(b,c.disabled)==-1){return}this.lis.eq(b).removeClass("ui-state-disabled");c.disabled=a.grep(c.disabled,function(e,d){return e!=b});this._trigger("enable",null,this._ui(this.anchors[b],this.panels[b]))},disable:function(c){var b=this,d=this.options;if(c!=d.selected){this.lis.eq(c).addClass("ui-state-disabled");d.disabled.push(c);d.disabled.sort();this._trigger("disable",null,this._ui(this.anchors[c],this.panels[c]))}},select:function(b){if(typeof b=="string"){b=this.anchors.index(this.anchors.filter("[href$="+b+"]"))}else{if(b===null){b=-1}}if(b==-1&&this.options.collapsible){b=this.options.selected}this.anchors.eq(b).trigger(this.options.event+".tabs")},load:function(e){var c=this,g=this.options,b=this.anchors.eq(e)[0],d=a.data(b,"load.tabs");this.abort();if(!d||this.element.queue("tabs").length!==0&&a.data(b,"cache.tabs")){this.element.dequeue("tabs");return}this.lis.eq(e).addClass("ui-state-processing");if(g.spinner){var f=a("span",b);f.data("label.tabs",f.html()).html(g.spinner)}this.xhr=a.ajax(a.extend({},g.ajaxOptions,{url:d,success:function(i,h){a(c._sanitizeSelector(b.hash)).html(i);c._cleanup();if(g.cache){a.data(b,"cache.tabs",true)}c._trigger("load",null,c._ui(c.anchors[e],c.panels[e]));try{g.ajaxOptions.success(i,h)}catch(j){}c.element.dequeue("tabs")}}))},abort:function(){this.element.queue([]);this.panels.stop(false,true);if(this.xhr){this.xhr.abort();delete this.xhr}this._cleanup()},url:function(c,b){this.anchors.eq(c).removeData("cache.tabs").data("load.tabs",b)},length:function(){return this.anchors.length}});a.extend(a.ui.tabs,{version:"1.7.1",getter:"length",defaults:{ajaxOptions:null,cache:false,cookie:null,collapsible:false,disabled:[],event:"click",fx:null,idPrefix:"ui-tabs-",panelTemplate:"
",spinner:"Loading…",tabTemplate:'
  • #{label}
  • '}});a.extend(a.ui.tabs.prototype,{rotation:null,rotate:function(d,f){var b=this,g=this.options;var c=b._rotate||(b._rotate=function(h){clearTimeout(b.rotation);b.rotation=setTimeout(function(){var i=g.selected;b.select(++i')}$.extend(Datepicker.prototype,{markerClassName:"hasDatepicker",log:function(){if(this.debug){console.log.apply("",arguments)}},setDefaults:function(settings){extendRemove(this._defaults,settings||{});return this},_attachDatepicker:function(target,settings){var inlineSettings=null;for(var attrName in this._defaults){var attrValue=target.getAttribute("date:"+attrName);if(attrValue){inlineSettings=inlineSettings||{};try{inlineSettings[attrName]=eval(attrValue)}catch(err){inlineSettings[attrName]=attrValue}}}var nodeName=target.nodeName.toLowerCase();var inline=(nodeName=="div"||nodeName=="span");if(!target.id){target.id="dp"+(++this.uuid)}var inst=this._newInst($(target),inline);inst.settings=$.extend({},settings||{},inlineSettings||{});if(nodeName=="input"){this._connectDatepicker(target,inst)}else{if(inline){this._inlineDatepicker(target,inst)}}},_newInst:function(target,inline){var id=target[0].id.replace(/([:\[\]\.])/g,"\\\\$1");return{id:id,input:target,selectedDay:0,selectedMonth:0,selectedYear:0,drawMonth:0,drawYear:0,inline:inline,dpDiv:(!inline?this.dpDiv:$('
    '))}},_connectDatepicker:function(target,inst){var input=$(target);inst.trigger=$([]);if(input.hasClass(this.markerClassName)){return}var appendText=this._get(inst,"appendText");var isRTL=this._get(inst,"isRTL");if(appendText){input[isRTL?"before":"after"](''+appendText+"")}var showOn=this._get(inst,"showOn");if(showOn=="focus"||showOn=="both"){input.focus(this._showDatepicker)}if(showOn=="button"||showOn=="both"){var buttonText=this._get(inst,"buttonText");var buttonImage=this._get(inst,"buttonImage");inst.trigger=$(this._get(inst,"buttonImageOnly")?$("").addClass(this._triggerClass).attr({src:buttonImage,alt:buttonText,title:buttonText}):$('').addClass(this._triggerClass).html(buttonImage==""?buttonText:$("").attr({src:buttonImage,alt:buttonText,title:buttonText})));input[isRTL?"before":"after"](inst.trigger);inst.trigger.click(function(){if($.datepicker._datepickerShowing&&$.datepicker._lastInput==target){$.datepicker._hideDatepicker()}else{$.datepicker._showDatepicker(target)}return false})}input.addClass(this.markerClassName).keydown(this._doKeyDown).keypress(this._doKeyPress).bind("setData.datepicker",function(event,key,value){inst.settings[key]=value}).bind("getData.datepicker",function(event,key){return this._get(inst,key)});$.data(target,PROP_NAME,inst)},_inlineDatepicker:function(target,inst){var divSpan=$(target);if(divSpan.hasClass(this.markerClassName)){return}divSpan.addClass(this.markerClassName).append(inst.dpDiv).bind("setData.datepicker",function(event,key,value){inst.settings[key]=value}).bind("getData.datepicker",function(event,key){return this._get(inst,key)});$.data(target,PROP_NAME,inst);this._setDate(inst,this._getDefaultDate(inst));this._updateDatepicker(inst);this._updateAlternate(inst)},_dialogDatepicker:function(input,dateText,onSelect,settings,pos){var inst=this._dialogInst;if(!inst){var id="dp"+(++this.uuid);this._dialogInput=$('');this._dialogInput.keydown(this._doKeyDown);$("body").append(this._dialogInput);inst=this._dialogInst=this._newInst(this._dialogInput,false);inst.settings={};$.data(this._dialogInput[0],PROP_NAME,inst)}extendRemove(inst.settings,settings||{});this._dialogInput.val(dateText);this._pos=(pos?(pos.length?pos:[pos.pageX,pos.pageY]):null);if(!this._pos){var browserWidth=window.innerWidth||document.documentElement.clientWidth||document.body.clientWidth;var browserHeight=window.innerHeight||document.documentElement.clientHeight||document.body.clientHeight;var scrollX=document.documentElement.scrollLeft||document.body.scrollLeft;var scrollY=document.documentElement.scrollTop||document.body.scrollTop;this._pos=[(browserWidth/2)-100+scrollX,(browserHeight/2)-150+scrollY]}this._dialogInput.css("left",this._pos[0]+"px").css("top",this._pos[1]+"px");inst.settings.onSelect=onSelect;this._inDialog=true;this.dpDiv.addClass(this._dialogClass);this._showDatepicker(this._dialogInput[0]);if($.blockUI){$.blockUI(this.dpDiv)}$.data(this._dialogInput[0],PROP_NAME,inst);return this},_destroyDatepicker:function(target){var $target=$(target);var inst=$.data(target,PROP_NAME);if(!$target.hasClass(this.markerClassName)){return}var nodeName=target.nodeName.toLowerCase();$.removeData(target,PROP_NAME);if(nodeName=="input"){inst.trigger.remove();$target.siblings("."+this._appendClass).remove().end().removeClass(this.markerClassName).unbind("focus",this._showDatepicker).unbind("keydown",this._doKeyDown).unbind("keypress",this._doKeyPress)}else{if(nodeName=="div"||nodeName=="span"){$target.removeClass(this.markerClassName).empty()}}},_enableDatepicker:function(target){var $target=$(target);var inst=$.data(target,PROP_NAME);if(!$target.hasClass(this.markerClassName)){return}var nodeName=target.nodeName.toLowerCase();if(nodeName=="input"){target.disabled=false;inst.trigger.filter("button").each(function(){this.disabled=false}).end().filter("img").css({opacity:"1.0",cursor:""})}else{if(nodeName=="div"||nodeName=="span"){var inline=$target.children("."+this._inlineClass);inline.children().removeClass("ui-state-disabled")}}this._disabledInputs=$.map(this._disabledInputs,function(value){return(value==target?null:value)})},_disableDatepicker:function(target){var $target=$(target);var inst=$.data(target,PROP_NAME);if(!$target.hasClass(this.markerClassName)){return}var nodeName=target.nodeName.toLowerCase();if(nodeName=="input"){target.disabled=true;inst.trigger.filter("button").each(function(){this.disabled=true}).end().filter("img").css({opacity:"0.5",cursor:"default"})}else{if(nodeName=="div"||nodeName=="span"){var inline=$target.children("."+this._inlineClass);inline.children().addClass("ui-state-disabled")}}this._disabledInputs=$.map(this._disabledInputs,function(value){return(value==target?null:value)});this._disabledInputs[this._disabledInputs.length]=target},_isDisabledDatepicker:function(target){if(!target){return false}for(var i=0;i-1)}},_showDatepicker:function(input){input=input.target||input;if(input.nodeName.toLowerCase()!="input"){input=$("input",input.parentNode)[0]}if($.datepicker._isDisabledDatepicker(input)||$.datepicker._lastInput==input){return}var inst=$.datepicker._getInst(input);var beforeShow=$.datepicker._get(inst,"beforeShow");extendRemove(inst.settings,(beforeShow?beforeShow.apply(input,[input,inst]):{}));$.datepicker._hideDatepicker(null,"");$.datepicker._lastInput=input;$.datepicker._setDateFromField(inst);if($.datepicker._inDialog){input.value=""}if(!$.datepicker._pos){$.datepicker._pos=$.datepicker._findPos(input);$.datepicker._pos[1]+=input.offsetHeight}var isFixed=false;$(input).parents().each(function(){isFixed|=$(this).css("position")=="fixed";return !isFixed});if(isFixed&&$.browser.opera){$.datepicker._pos[0]-=document.documentElement.scrollLeft;$.datepicker._pos[1]-=document.documentElement.scrollTop}var offset={left:$.datepicker._pos[0],top:$.datepicker._pos[1]};$.datepicker._pos=null;inst.rangeStart=null;inst.dpDiv.css({position:"absolute",display:"block",top:"-1000px"});$.datepicker._updateDatepicker(inst);offset=$.datepicker._checkOffset(inst,offset,isFixed);inst.dpDiv.css({position:($.datepicker._inDialog&&$.blockUI?"static":(isFixed?"fixed":"absolute")),display:"none",left:offset.left+"px",top:offset.top+"px"});if(!inst.inline){var showAnim=$.datepicker._get(inst,"showAnim")||"show";var duration=$.datepicker._get(inst,"duration");var postProcess=function(){$.datepicker._datepickerShowing=true;if($.browser.msie&&parseInt($.browser.version,10)<7){$("iframe.ui-datepicker-cover").css({width:inst.dpDiv.width()+4,height:inst.dpDiv.height()+4})}};if($.effects&&$.effects[showAnim]){inst.dpDiv.show(showAnim,$.datepicker._get(inst,"showOptions"),duration,postProcess)}else{inst.dpDiv[showAnim](duration,postProcess)}if(duration==""){postProcess()}if(inst.input[0].type!="hidden"){inst.input[0].focus()}$.datepicker._curInst=inst}},_updateDatepicker:function(inst){var dims={width:inst.dpDiv.width()+4,height:inst.dpDiv.height()+4};var self=this;inst.dpDiv.empty().append(this._generateHTML(inst)).find("iframe.ui-datepicker-cover").css({width:dims.width,height:dims.height}).end().find("button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a").bind("mouseout",function(){$(this).removeClass("ui-state-hover");if(this.className.indexOf("ui-datepicker-prev")!=-1){$(this).removeClass("ui-datepicker-prev-hover")}if(this.className.indexOf("ui-datepicker-next")!=-1){$(this).removeClass("ui-datepicker-next-hover")}}).bind("mouseover",function(){if(!self._isDisabledDatepicker(inst.inline?inst.dpDiv.parent()[0]:inst.input[0])){$(this).parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover");$(this).addClass("ui-state-hover");if(this.className.indexOf("ui-datepicker-prev")!=-1){$(this).addClass("ui-datepicker-prev-hover")}if(this.className.indexOf("ui-datepicker-next")!=-1){$(this).addClass("ui-datepicker-next-hover")}}}).end().find("."+this._dayOverClass+" a").trigger("mouseover").end();var numMonths=this._getNumberOfMonths(inst);var cols=numMonths[1];var width=17;if(cols>1){inst.dpDiv.addClass("ui-datepicker-multi-"+cols).css("width",(width*cols)+"em")}else{inst.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width("")}inst.dpDiv[(numMonths[0]!=1||numMonths[1]!=1?"add":"remove")+"Class"]("ui-datepicker-multi");inst.dpDiv[(this._get(inst,"isRTL")?"add":"remove")+"Class"]("ui-datepicker-rtl");if(inst.input&&inst.input[0].type!="hidden"&&inst==$.datepicker._curInst){$(inst.input[0]).focus()}},_checkOffset:function(inst,offset,isFixed){var dpWidth=inst.dpDiv.outerWidth();var dpHeight=inst.dpDiv.outerHeight();var inputWidth=inst.input?inst.input.outerWidth():0;var inputHeight=inst.input?inst.input.outerHeight():0;var viewWidth=(window.innerWidth||document.documentElement.clientWidth||document.body.clientWidth)+$(document).scrollLeft();var viewHeight=(window.innerHeight||document.documentElement.clientHeight||document.body.clientHeight)+$(document).scrollTop();offset.left-=(this._get(inst,"isRTL")?(dpWidth-inputWidth):0);offset.left-=(isFixed&&offset.left==inst.input.offset().left)?$(document).scrollLeft():0;offset.top-=(isFixed&&offset.top==(inst.input.offset().top+inputHeight))?$(document).scrollTop():0;offset.left-=(offset.left+dpWidth>viewWidth&&viewWidth>dpWidth)?Math.abs(offset.left+dpWidth-viewWidth):0;offset.top-=(offset.top+dpHeight>viewHeight&&viewHeight>dpHeight)?Math.abs(offset.top+dpHeight+inputHeight*2-viewHeight):0;return offset},_findPos:function(obj){while(obj&&(obj.type=="hidden"||obj.nodeType!=1)){obj=obj.nextSibling}var position=$(obj).offset();return[position.left,position.top]},_hideDatepicker:function(input,duration){var inst=this._curInst;if(!inst||(input&&inst!=$.data(input,PROP_NAME))){return}if(inst.stayOpen){this._selectDate("#"+inst.id,this._formatDate(inst,inst.currentDay,inst.currentMonth,inst.currentYear))}inst.stayOpen=false;if(this._datepickerShowing){duration=(duration!=null?duration:this._get(inst,"duration"));var showAnim=this._get(inst,"showAnim");var postProcess=function(){$.datepicker._tidyDialog(inst)};if(duration!=""&&$.effects&&$.effects[showAnim]){inst.dpDiv.hide(showAnim,$.datepicker._get(inst,"showOptions"),duration,postProcess)}else{inst.dpDiv[(duration==""?"hide":(showAnim=="slideDown"?"slideUp":(showAnim=="fadeIn"?"fadeOut":"hide")))](duration,postProcess)}if(duration==""){this._tidyDialog(inst)}var onClose=this._get(inst,"onClose");if(onClose){onClose.apply((inst.input?inst.input[0]:null),[(inst.input?inst.input.val():""),inst])}this._datepickerShowing=false;this._lastInput=null;if(this._inDialog){this._dialogInput.css({position:"absolute",left:"0",top:"-100px"});if($.blockUI){$.unblockUI();$("body").append(this.dpDiv)}}this._inDialog=false}this._curInst=null},_tidyDialog:function(inst){inst.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar")},_checkExternalClick:function(event){if(!$.datepicker._curInst){return}var $target=$(event.target);if(($target.parents("#"+$.datepicker._mainDivId).length==0)&&!$target.hasClass($.datepicker.markerClassName)&&!$target.hasClass($.datepicker._triggerClass)&&$.datepicker._datepickerShowing&&!($.datepicker._inDialog&&$.blockUI)){$.datepicker._hideDatepicker(null,"")}},_adjustDate:function(id,offset,period){var target=$(id);var inst=this._getInst(target[0]);if(this._isDisabledDatepicker(target[0])){return}this._adjustInstDate(inst,offset+(period=="M"?this._get(inst,"showCurrentAtPos"):0),period);this._updateDatepicker(inst)},_gotoToday:function(id){var target=$(id);var inst=this._getInst(target[0]);if(this._get(inst,"gotoCurrent")&&inst.currentDay){inst.selectedDay=inst.currentDay;inst.drawMonth=inst.selectedMonth=inst.currentMonth;inst.drawYear=inst.selectedYear=inst.currentYear}else{var date=new Date();inst.selectedDay=date.getDate();inst.drawMonth=inst.selectedMonth=date.getMonth();inst.drawYear=inst.selectedYear=date.getFullYear()}this._notifyChange(inst);this._adjustDate(target)},_selectMonthYear:function(id,select,period){var target=$(id);var inst=this._getInst(target[0]);inst._selectingMonthYear=false;inst["selected"+(period=="M"?"Month":"Year")]=inst["draw"+(period=="M"?"Month":"Year")]=parseInt(select.options[select.selectedIndex].value,10);this._notifyChange(inst);this._adjustDate(target)},_clickMonthYear:function(id){var target=$(id);var inst=this._getInst(target[0]);if(inst.input&&inst._selectingMonthYear&&!$.browser.msie){inst.input[0].focus()}inst._selectingMonthYear=!inst._selectingMonthYear},_selectDay:function(id,month,year,td){var target=$(id);if($(td).hasClass(this._unselectableClass)||this._isDisabledDatepicker(target[0])){return}var inst=this._getInst(target[0]);inst.selectedDay=inst.currentDay=$("a",td).html();inst.selectedMonth=inst.currentMonth=month;inst.selectedYear=inst.currentYear=year;if(inst.stayOpen){inst.endDay=inst.endMonth=inst.endYear=null}this._selectDate(id,this._formatDate(inst,inst.currentDay,inst.currentMonth,inst.currentYear));if(inst.stayOpen){inst.rangeStart=this._daylightSavingAdjust(new Date(inst.currentYear,inst.currentMonth,inst.currentDay));this._updateDatepicker(inst)}},_clearDate:function(id){var target=$(id);var inst=this._getInst(target[0]);inst.stayOpen=false;inst.endDay=inst.endMonth=inst.endYear=inst.rangeStart=null;this._selectDate(target,"")},_selectDate:function(id,dateStr){var target=$(id);var inst=this._getInst(target[0]);dateStr=(dateStr!=null?dateStr:this._formatDate(inst));if(inst.input){inst.input.val(dateStr)}this._updateAlternate(inst);var onSelect=this._get(inst,"onSelect");if(onSelect){onSelect.apply((inst.input?inst.input[0]:null),[dateStr,inst])}else{if(inst.input){inst.input.trigger("change")}}if(inst.inline){this._updateDatepicker(inst)}else{if(!inst.stayOpen){this._hideDatepicker(null,this._get(inst,"duration"));this._lastInput=inst.input[0];if(typeof(inst.input[0])!="object"){inst.input[0].focus()}this._lastInput=null}}},_updateAlternate:function(inst){var altField=this._get(inst,"altField");if(altField){var altFormat=this._get(inst,"altFormat")||this._get(inst,"dateFormat");var date=this._getDate(inst);dateStr=this.formatDate(altFormat,date,this._getFormatConfig(inst));$(altField).each(function(){$(this).val(dateStr)})}},noWeekends:function(date){var day=date.getDay();return[(day>0&&day<6),""]},iso8601Week:function(date){var checkDate=new Date(date.getFullYear(),date.getMonth(),date.getDate());var firstMon=new Date(checkDate.getFullYear(),1-1,4);var firstDay=firstMon.getDay()||7;firstMon.setDate(firstMon.getDate()+1-firstDay);if(firstDay<4&&checkDatenew Date(checkDate.getFullYear(),12-1,28)){firstDay=new Date(checkDate.getFullYear()+1,1-1,4).getDay()||7;if(firstDay>4&&(checkDate.getDay()||7)0&&iValue="0"&&value.charAt(iValue)<="9"){num=num*10+parseInt(value.charAt(iValue++),10);size--}if(size==origSize){throw"Missing number at position "+iValue}return num};var getName=function(match,shortNames,longNames){var names=(lookAhead(match)?longNames:shortNames);var size=0;for(var j=0;j0&&iValue-1){month=1;day=doy;do{var dim=this._getDaysInMonth(year,month-1);if(day<=dim){break}month++;day-=dim}while(true)}var date=this._daylightSavingAdjust(new Date(year,month-1,day));if(date.getFullYear()!=year||date.getMonth()+1!=month||date.getDate()!=day){throw"Invalid date"}return date},ATOM:"yy-mm-dd",COOKIE:"D, dd M yy",ISO_8601:"yy-mm-dd",RFC_822:"D, d M y",RFC_850:"DD, dd-M-y",RFC_1036:"D, d M y",RFC_1123:"D, d M yy",RFC_2822:"D, d M yy",RSS:"D, d M y",TIMESTAMP:"@",W3C:"yy-mm-dd",formatDate:function(format,date,settings){if(!date){return""}var dayNamesShort=(settings?settings.dayNamesShort:null)||this._defaults.dayNamesShort;var dayNames=(settings?settings.dayNames:null)||this._defaults.dayNames;var monthNamesShort=(settings?settings.monthNamesShort:null)||this._defaults.monthNamesShort;var monthNames=(settings?settings.monthNames:null)||this._defaults.monthNames;var lookAhead=function(match){var matches=(iFormat+1=0;m--){doy+=this._getDaysInMonth(date.getFullYear(),m)}output+=formatNumber("o",doy,3);break;case"m":output+=formatNumber("m",date.getMonth()+1,2);break;case"M":output+=formatName("M",date.getMonth(),monthNamesShort,monthNames);break;case"y":output+=(lookAhead("y")?date.getFullYear():(date.getYear()%100<10?"0":"")+date.getYear()%100);break;case"@":output+=date.getTime();break;case"'":if(lookAhead("'")){output+="'"}else{literal=true}break;default:output+=format.charAt(iFormat)}}}}return output},_possibleChars:function(format){var chars="";var literal=false;for(var iFormat=0;iFormatmaxDate?maxDate:date);return date},_determineDate:function(date,defaultDate){var offsetNumeric=function(offset){var date=new Date();date.setDate(date.getDate()+offset);return date};var offsetString=function(offset,getDaysInMonth){var date=new Date();var year=date.getFullYear();var month=date.getMonth();var day=date.getDate();var pattern=/([+-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g;var matches=pattern.exec(offset);while(matches){switch(matches[2]||"d"){case"d":case"D":day+=parseInt(matches[1],10);break;case"w":case"W":day+=parseInt(matches[1],10)*7;break;case"m":case"M":month+=parseInt(matches[1],10);day=Math.min(day,getDaysInMonth(year,month));break;case"y":case"Y":year+=parseInt(matches[1],10);day=Math.min(day,getDaysInMonth(year,month));break}matches=pattern.exec(offset)}return new Date(year,month,day)};date=(date==null?defaultDate:(typeof date=="string"?offsetString(date,this._getDaysInMonth):(typeof date=="number"?(isNaN(date)?defaultDate:offsetNumeric(date)):date)));date=(date&&date.toString()=="Invalid Date"?defaultDate:date);if(date){date.setHours(0);date.setMinutes(0);date.setSeconds(0);date.setMilliseconds(0)}return this._daylightSavingAdjust(date)},_daylightSavingAdjust:function(date){if(!date){return null}date.setHours(date.getHours()>12?date.getHours()+2:0);return date},_setDate:function(inst,date,endDate){var clear=!(date);var origMonth=inst.selectedMonth;var origYear=inst.selectedYear;date=this._determineDate(date,new Date());inst.selectedDay=inst.currentDay=date.getDate();inst.drawMonth=inst.selectedMonth=inst.currentMonth=date.getMonth();inst.drawYear=inst.selectedYear=inst.currentYear=date.getFullYear();if(origMonth!=inst.selectedMonth||origYear!=inst.selectedYear){this._notifyChange(inst)}this._adjustInstDate(inst);if(inst.input){inst.input.val(clear?"":this._formatDate(inst))}},_getDate:function(inst){var startDate=(!inst.currentYear||(inst.input&&inst.input.val()=="")?null:this._daylightSavingAdjust(new Date(inst.currentYear,inst.currentMonth,inst.currentDay)));return startDate},_generateHTML:function(inst){var today=new Date();today=this._daylightSavingAdjust(new Date(today.getFullYear(),today.getMonth(),today.getDate()));var isRTL=this._get(inst,"isRTL");var showButtonPanel=this._get(inst,"showButtonPanel");var hideIfNoPrevNext=this._get(inst,"hideIfNoPrevNext");var navigationAsDateFormat=this._get(inst,"navigationAsDateFormat");var numMonths=this._getNumberOfMonths(inst);var showCurrentAtPos=this._get(inst,"showCurrentAtPos");var stepMonths=this._get(inst,"stepMonths");var stepBigMonths=this._get(inst,"stepBigMonths");var isMultiMonth=(numMonths[0]!=1||numMonths[1]!=1);var currentDate=this._daylightSavingAdjust((!inst.currentDay?new Date(9999,9,9):new Date(inst.currentYear,inst.currentMonth,inst.currentDay)));var minDate=this._getMinMaxDate(inst,"min",true);var maxDate=this._getMinMaxDate(inst,"max");var drawMonth=inst.drawMonth-showCurrentAtPos;var drawYear=inst.drawYear;if(drawMonth<0){drawMonth+=12;drawYear--}if(maxDate){var maxDraw=this._daylightSavingAdjust(new Date(maxDate.getFullYear(),maxDate.getMonth()-numMonths[1]+1,maxDate.getDate()));maxDraw=(minDate&&maxDrawmaxDraw){drawMonth--;if(drawMonth<0){drawMonth=11;drawYear--}}}inst.drawMonth=drawMonth;inst.drawYear=drawYear;var prevText=this._get(inst,"prevText");prevText=(!navigationAsDateFormat?prevText:this.formatDate(prevText,this._daylightSavingAdjust(new Date(drawYear,drawMonth-stepMonths,1)),this._getFormatConfig(inst)));var prev=(this._canAdjustMonth(inst,-1,drawYear,drawMonth)?''+prevText+"":(hideIfNoPrevNext?"":''+prevText+""));var nextText=this._get(inst,"nextText");nextText=(!navigationAsDateFormat?nextText:this.formatDate(nextText,this._daylightSavingAdjust(new Date(drawYear,drawMonth+stepMonths,1)),this._getFormatConfig(inst)));var next=(this._canAdjustMonth(inst,+1,drawYear,drawMonth)?''+nextText+"":(hideIfNoPrevNext?"":''+nextText+""));var currentText=this._get(inst,"currentText");var gotoDate=(this._get(inst,"gotoCurrent")&&inst.currentDay?currentDate:today);currentText=(!navigationAsDateFormat?currentText:this.formatDate(currentText,gotoDate,this._getFormatConfig(inst)));var controls=(!inst.inline?'":"");var buttonPanel=(showButtonPanel)?'
    '+(isRTL?controls:"")+(this._isInRange(inst,gotoDate)?'":"")+(isRTL?"":controls)+"
    ":"";var firstDay=parseInt(this._get(inst,"firstDay"),10);firstDay=(isNaN(firstDay)?0:firstDay);var dayNames=this._get(inst,"dayNames");var dayNamesShort=this._get(inst,"dayNamesShort");var dayNamesMin=this._get(inst,"dayNamesMin");var monthNames=this._get(inst,"monthNames");var monthNamesShort=this._get(inst,"monthNamesShort");var beforeShowDay=this._get(inst,"beforeShowDay");var showOtherMonths=this._get(inst,"showOtherMonths");var calculateWeek=this._get(inst,"calculateWeek")||this.iso8601Week;var endDate=inst.endDay?this._daylightSavingAdjust(new Date(inst.endYear,inst.endMonth,inst.endDay)):currentDate;var defaultDate=this._getDefaultDate(inst);var html="";for(var row=0;row'+(/all|left/.test(cornerClass)&&row==0?(isRTL?next:prev):"")+(/all|right/.test(cornerClass)&&row==0?(isRTL?prev:next):"")+this._generateMonthYearHeader(inst,drawMonth,drawYear,minDate,maxDate,selectedDate,row>0||col>0,monthNames,monthNamesShort)+'';var thead="";for(var dow=0;dow<7;dow++){var day=(dow+firstDay)%7;thead+="=5?' class="ui-datepicker-week-end"':"")+'>'+dayNamesMin[day]+""}calender+=thead+"";var daysInMonth=this._getDaysInMonth(drawYear,drawMonth);if(drawYear==inst.selectedYear&&drawMonth==inst.selectedMonth){inst.selectedDay=Math.min(inst.selectedDay,daysInMonth)}var leadDays=(this._getFirstDayOfMonth(drawYear,drawMonth)-firstDay+7)%7;var numRows=(isMultiMonth?6:Math.ceil((leadDays+daysInMonth)/7));var printDate=this._daylightSavingAdjust(new Date(drawYear,drawMonth,1-leadDays));for(var dRow=0;dRow";var tbody="";for(var dow=0;dow<7;dow++){var daySettings=(beforeShowDay?beforeShowDay.apply((inst.input?inst.input[0]:null),[printDate]):[true,""]);var otherMonth=(printDate.getMonth()!=drawMonth);var unselectable=otherMonth||!daySettings[0]||(minDate&&printDatemaxDate);tbody+='";printDate.setDate(printDate.getDate()+1);printDate=this._daylightSavingAdjust(printDate)}calender+=tbody+""}drawMonth++;if(drawMonth>11){drawMonth=0;drawYear++}calender+="
    =currentDate.getTime()&&printDate.getTime()<=endDate.getTime()?" "+this._currentClass:"")+(printDate.getTime()==today.getTime()?" ui-datepicker-today":""))+'"'+((!otherMonth||showOtherMonths)&&daySettings[2]?' title="'+daySettings[2]+'"':"")+(unselectable?"":" onclick=\"DP_jQuery.datepicker._selectDay('#"+inst.id+"',"+drawMonth+","+drawYear+', this);return false;"')+">"+(otherMonth?(showOtherMonths?printDate.getDate():" "):(unselectable?''+printDate.getDate()+"":'=currentDate.getTime()&&printDate.getTime()<=endDate.getTime()?" ui-state-active":"")+'" href="#">'+printDate.getDate()+""))+"
    "+(isMultiMonth?""+((numMonths[0]>0&&col==numMonths[1]-1)?'
    ':""):"");group+=calender}html+=group}html+=buttonPanel+($.browser.msie&&parseInt($.browser.version,10)<7&&!inst.inline?'':"");inst._keyEvent=false;return html},_generateMonthYearHeader:function(inst,drawMonth,drawYear,minDate,maxDate,selectedDate,secondary,monthNames,monthNamesShort){minDate=(inst.rangeStart&&minDate&&selectedDate "}else{var inMinYear=(minDate&&minDate.getFullYear()==drawYear);var inMaxYear=(maxDate&&maxDate.getFullYear()==drawYear);monthHtml+='"}if(!showMonthAfterYear){html+=monthHtml+((secondary||changeMonth||changeYear)&&(!(changeMonth&&changeYear))?" ":"")}if(secondary||!changeYear){html+=''+drawYear+""}else{var years=this._get(inst,"yearRange").split(":");var year=0;var endYear=0;if(years.length!=2){year=drawYear-10;endYear=drawYear+10}else{if(years[0].charAt(0)=="+"||years[0].charAt(0)=="-"){year=drawYear+parseInt(years[0],10);endYear=drawYear+parseInt(years[1],10)}else{year=parseInt(years[0],10);endYear=parseInt(years[1],10)}}year=(minDate?Math.max(year,minDate.getFullYear()):year);endYear=(maxDate?Math.min(endYear,maxDate.getFullYear()):endYear);html+='"}if(showMonthAfterYear){html+=(secondary||changeMonth||changeYear?" ":"")+monthHtml}html+="";return html},_adjustInstDate:function(inst,offset,period){var year=inst.drawYear+(period=="Y"?offset:0);var month=inst.drawMonth+(period=="M"?offset:0);var day=Math.min(inst.selectedDay,this._getDaysInMonth(year,month))+(period=="D"?offset:0);var date=this._daylightSavingAdjust(new Date(year,month,day));var minDate=this._getMinMaxDate(inst,"min",true);var maxDate=this._getMinMaxDate(inst,"max");date=(minDate&&datemaxDate?maxDate:date);inst.selectedDay=date.getDate();inst.drawMonth=inst.selectedMonth=date.getMonth();inst.drawYear=inst.selectedYear=date.getFullYear();if(period=="M"||period=="Y"){this._notifyChange(inst)}},_notifyChange:function(inst){var onChange=this._get(inst,"onChangeMonthYear");if(onChange){onChange.apply((inst.input?inst.input[0]:null),[inst.selectedYear,inst.selectedMonth+1,inst])}},_getNumberOfMonths:function(inst){var numMonths=this._get(inst,"numberOfMonths");return(numMonths==null?[1,1]:(typeof numMonths=="number"?[1,numMonths]:numMonths))},_getMinMaxDate:function(inst,minMax,checkRange){var date=this._determineDate(this._get(inst,minMax+"Date"),null);return(!checkRange||!inst.rangeStart?date:(!date||inst.rangeStart>date?inst.rangeStart:date))},_getDaysInMonth:function(year,month){return 32-new Date(year,month,32).getDate()},_getFirstDayOfMonth:function(year,month){return new Date(year,month,1).getDay()},_canAdjustMonth:function(inst,offset,curYear,curMonth){var numMonths=this._getNumberOfMonths(inst);var date=this._daylightSavingAdjust(new Date(curYear,curMonth+(offset<0?offset:numMonths[1]),1));if(offset<0){date.setDate(this._getDaysInMonth(date.getFullYear(),date.getMonth()))}return this._isInRange(inst,date)},_isInRange:function(inst,date){var newMinDate=(!inst.rangeStart?null:this._daylightSavingAdjust(new Date(inst.selectedYear,inst.selectedMonth,inst.selectedDay)));newMinDate=(newMinDate&&inst.rangeStart=minDate)&&(!maxDate||date<=maxDate))},_getFormatConfig:function(inst){var shortYearCutoff=this._get(inst,"shortYearCutoff");shortYearCutoff=(typeof shortYearCutoff!="string"?shortYearCutoff:new Date().getFullYear()%100+parseInt(shortYearCutoff,10));return{shortYearCutoff:shortYearCutoff,dayNamesShort:this._get(inst,"dayNamesShort"),dayNames:this._get(inst,"dayNames"),monthNamesShort:this._get(inst,"monthNamesShort"),monthNames:this._get(inst,"monthNames")}},_formatDate:function(inst,day,month,year){if(!day){inst.currentDay=inst.selectedDay;inst.currentMonth=inst.selectedMonth;inst.currentYear=inst.selectedYear}var date=(day?(typeof day=="object"?day:this._daylightSavingAdjust(new Date(year,month,day))):this._daylightSavingAdjust(new Date(inst.currentYear,inst.currentMonth,inst.currentDay)));return this.formatDate(this._get(inst,"dateFormat"),date,this._getFormatConfig(inst))}});function extendRemove(target,props){$.extend(target,props);for(var name in props){if(props[name]==null||props[name]==undefined){target[name]=props[name]}}return target}function isArray(a){return(a&&(($.browser.safari&&typeof a=="object"&&a.length)||(a.constructor&&a.constructor.toString().match(/\Array\(\)/))))}$.fn.datepicker=function(options){if(!$.datepicker.initialized){$(document).mousedown($.datepicker._checkExternalClick).find("body").append($.datepicker.dpDiv);$.datepicker.initialized=true}var otherArgs=Array.prototype.slice.call(arguments,1);if(typeof options=="string"&&(options=="isDisabled"||options=="getDate")){return $.datepicker["_"+options+"Datepicker"].apply($.datepicker,[this[0]].concat(otherArgs))}return this.each(function(){typeof options=="string"?$.datepicker["_"+options+"Datepicker"].apply($.datepicker,[this].concat(otherArgs)):$.datepicker._attachDatepicker(this,options)})};$.datepicker=new Datepicker();$.datepicker.initialized=false;$.datepicker.uuid=new Date().getTime();$.datepicker.version="1.7.1";window.DP_jQuery=$})(jQuery);;/* + * jQuery UI Progressbar 1.7.1 + * + * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * http://docs.jquery.com/UI/Progressbar + * + * Depends: + * ui.core.js + */ (function(a){a.widget("ui.progressbar",{_init:function(){this.element.addClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").attr({role:"progressbar","aria-valuemin":this._valueMin(),"aria-valuemax":this._valueMax(),"aria-valuenow":this._value()});this.valueDiv=a('
    ').appendTo(this.element);this._refreshValue()},destroy:function(){this.element.removeClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").removeAttr("role").removeAttr("aria-valuemin").removeAttr("aria-valuemax").removeAttr("aria-valuenow").removeData("progressbar").unbind(".progressbar");this.valueDiv.remove();a.widget.prototype.destroy.apply(this,arguments)},value:function(b){arguments.length&&this._setData("value",b);return this._value()},_setData:function(b,c){switch(b){case"value":this.options.value=c;this._refreshValue();this._trigger("change",null,{});break}a.widget.prototype._setData.apply(this,arguments)},_value:function(){var b=this.options.value;if(bthis._valueMax()){b=this._valueMax()}return b},_valueMin:function(){var b=0;return b},_valueMax:function(){var b=100;return b},_refreshValue:function(){var b=this.value();this.valueDiv[b==this._valueMax()?"addClass":"removeClass"]("ui-corner-right");this.valueDiv.width(b+"%");this.element.attr("aria-valuenow",b)}});a.extend(a.ui.progressbar,{version:"1.7.1",defaults:{value:0}})})(jQuery);;/* + * jQuery UI Effects 1.7.1 + * + * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * http://docs.jquery.com/UI/Effects/ + */ jQuery.effects||(function(d){d.effects={version:"1.7.1",save:function(g,h){for(var f=0;f');var j=f.parent();if(f.css("position")=="static"){j.css({position:"relative"});f.css({position:"relative"})}else{var i=f.css("top");if(isNaN(parseInt(i,10))){i="auto"}var h=f.css("left");if(isNaN(parseInt(h,10))){h="auto"}j.css({position:f.css("position"),top:i,left:h,zIndex:f.css("z-index")}).show();f.css({position:"relative",top:0,left:0})}j.css(g);return j},removeWrapper:function(f){if(f.parent().is(".ui-effects-wrapper")){return f.parent().replaceWith(f)}return f},setTransition:function(g,i,f,h){h=h||{};d.each(i,function(k,j){unit=g.cssUnit(j);if(unit[0]>0){h[j]=unit[0]*f+unit[1]}});return h},animateClass:function(h,i,k,j){var f=(typeof k=="function"?k:(j?j:null));var g=(typeof k=="string"?k:null);return this.each(function(){var q={};var o=d(this);var p=o.attr("style")||"";if(typeof p=="object"){p=p.cssText}if(h.toggle){o.hasClass(h.toggle)?h.remove=h.toggle:h.add=h.toggle}var l=d.extend({},(document.defaultView?document.defaultView.getComputedStyle(this,null):this.currentStyle));if(h.add){o.addClass(h.add)}if(h.remove){o.removeClass(h.remove)}var m=d.extend({},(document.defaultView?document.defaultView.getComputedStyle(this,null):this.currentStyle));if(h.add){o.removeClass(h.add)}if(h.remove){o.addClass(h.remove)}for(var r in m){if(typeof m[r]!="function"&&m[r]&&r.indexOf("Moz")==-1&&r.indexOf("length")==-1&&m[r]!=l[r]&&(r.match(/color/i)||(!r.match(/color/i)&&!isNaN(parseInt(m[r],10))))&&(l.position!="static"||(l.position=="static"&&!r.match(/left|top|bottom|right/)))){q[r]=m[r]}}o.animate(q,i,g,function(){if(typeof d(this).attr("style")=="object"){d(this).attr("style")["cssText"]="";d(this).attr("style")["cssText"]=p}else{d(this).attr("style",p)}if(h.add){d(this).addClass(h.add)}if(h.remove){d(this).removeClass(h.remove)}if(f){f.apply(this,arguments)}})})}};function c(g,f){var i=g[1]&&g[1].constructor==Object?g[1]:{};if(f){i.mode=f}var h=g[1]&&g[1].constructor!=Object?g[1]:(i.duration?i.duration:g[2]);h=d.fx.off?0:typeof h==="number"?h:d.fx.speeds[h]||d.fx.speeds._default;var j=i.callback||(d.isFunction(g[1])&&g[1])||(d.isFunction(g[2])&&g[2])||(d.isFunction(g[3])&&g[3]);return[g[0],i,h,j]}d.fn.extend({_show:d.fn.show,_hide:d.fn.hide,__toggle:d.fn.toggle,_addClass:d.fn.addClass,_removeClass:d.fn.removeClass,_toggleClass:d.fn.toggleClass,effect:function(g,f,h,i){return d.effects[g]?d.effects[g].call(this,{method:g,options:f||{},duration:h,callback:i}):null},show:function(){if(!arguments[0]||(arguments[0].constructor==Number||(/(slow|normal|fast)/).test(arguments[0]))){return this._show.apply(this,arguments)}else{return this.effect.apply(this,c(arguments,"show"))}},hide:function(){if(!arguments[0]||(arguments[0].constructor==Number||(/(slow|normal|fast)/).test(arguments[0]))){return this._hide.apply(this,arguments)}else{return this.effect.apply(this,c(arguments,"hide"))}},toggle:function(){if(!arguments[0]||(arguments[0].constructor==Number||(/(slow|normal|fast)/).test(arguments[0]))||(arguments[0].constructor==Function)){return this.__toggle.apply(this,arguments)}else{return this.effect.apply(this,c(arguments,"toggle"))}},addClass:function(g,f,i,h){return f?d.effects.animateClass.apply(this,[{add:g},f,i,h]):this._addClass(g)},removeClass:function(g,f,i,h){return f?d.effects.animateClass.apply(this,[{remove:g},f,i,h]):this._removeClass(g)},toggleClass:function(g,f,i,h){return((typeof f!=="boolean")&&f)?d.effects.animateClass.apply(this,[{toggle:g},f,i,h]):this._toggleClass(g,f)},morph:function(f,h,g,j,i){return d.effects.animateClass.apply(this,[{add:h,remove:f},g,j,i])},switchClass:function(){return this.morph.apply(this,arguments)},cssUnit:function(f){var g=this.css(f),h=[];d.each(["em","px","%","pt"],function(j,k){if(g.indexOf(k)>0){h=[parseFloat(g),k]}});return h}});d.each(["backgroundColor","borderBottomColor","borderLeftColor","borderRightColor","borderTopColor","color","outlineColor"],function(g,f){d.fx.step[f]=function(h){if(h.state==0){h.start=e(h.elem,f);h.end=b(h.end)}h.elem.style[f]="rgb("+[Math.max(Math.min(parseInt((h.pos*(h.end[0]-h.start[0]))+h.start[0],10),255),0),Math.max(Math.min(parseInt((h.pos*(h.end[1]-h.start[1]))+h.start[1],10),255),0),Math.max(Math.min(parseInt((h.pos*(h.end[2]-h.start[2]))+h.start[2],10),255),0)].join(",")+")"}});function b(g){var f;if(g&&g.constructor==Array&&g.length==3){return g}if(f=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(g)){return[parseInt(f[1],10),parseInt(f[2],10),parseInt(f[3],10)]}if(f=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(g)){return[parseFloat(f[1])*2.55,parseFloat(f[2])*2.55,parseFloat(f[3])*2.55]}if(f=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(g)){return[parseInt(f[1],16),parseInt(f[2],16),parseInt(f[3],16)]}if(f=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(g)){return[parseInt(f[1]+f[1],16),parseInt(f[2]+f[2],16),parseInt(f[3]+f[3],16)]}if(f=/rgba\(0, 0, 0, 0\)/.exec(g)){return a.transparent}return a[d.trim(g).toLowerCase()]}function e(h,f){var g;do{g=d.curCSS(h,f);if(g!=""&&g!="transparent"||d.nodeName(h,"body")){break}f="backgroundColor"}while(h=h.parentNode);return b(g)}var a={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0],transparent:[255,255,255]};d.easing.jswing=d.easing.swing;d.extend(d.easing,{def:"easeOutQuad",swing:function(g,h,f,j,i){return d.easing[d.easing.def](g,h,f,j,i)},easeInQuad:function(g,h,f,j,i){return j*(h/=i)*h+f},easeOutQuad:function(g,h,f,j,i){return -j*(h/=i)*(h-2)+f},easeInOutQuad:function(g,h,f,j,i){if((h/=i/2)<1){return j/2*h*h+f}return -j/2*((--h)*(h-2)-1)+f},easeInCubic:function(g,h,f,j,i){return j*(h/=i)*h*h+f},easeOutCubic:function(g,h,f,j,i){return j*((h=h/i-1)*h*h+1)+f},easeInOutCubic:function(g,h,f,j,i){if((h/=i/2)<1){return j/2*h*h*h+f}return j/2*((h-=2)*h*h+2)+f},easeInQuart:function(g,h,f,j,i){return j*(h/=i)*h*h*h+f},easeOutQuart:function(g,h,f,j,i){return -j*((h=h/i-1)*h*h*h-1)+f},easeInOutQuart:function(g,h,f,j,i){if((h/=i/2)<1){return j/2*h*h*h*h+f}return -j/2*((h-=2)*h*h*h-2)+f},easeInQuint:function(g,h,f,j,i){return j*(h/=i)*h*h*h*h+f},easeOutQuint:function(g,h,f,j,i){return j*((h=h/i-1)*h*h*h*h+1)+f},easeInOutQuint:function(g,h,f,j,i){if((h/=i/2)<1){return j/2*h*h*h*h*h+f}return j/2*((h-=2)*h*h*h*h+2)+f},easeInSine:function(g,h,f,j,i){return -j*Math.cos(h/i*(Math.PI/2))+j+f},easeOutSine:function(g,h,f,j,i){return j*Math.sin(h/i*(Math.PI/2))+f},easeInOutSine:function(g,h,f,j,i){return -j/2*(Math.cos(Math.PI*h/i)-1)+f},easeInExpo:function(g,h,f,j,i){return(h==0)?f:j*Math.pow(2,10*(h/i-1))+f},easeOutExpo:function(g,h,f,j,i){return(h==i)?f+j:j*(-Math.pow(2,-10*h/i)+1)+f},easeInOutExpo:function(g,h,f,j,i){if(h==0){return f}if(h==i){return f+j}if((h/=i/2)<1){return j/2*Math.pow(2,10*(h-1))+f}return j/2*(-Math.pow(2,-10*--h)+2)+f},easeInCirc:function(g,h,f,j,i){return -j*(Math.sqrt(1-(h/=i)*h)-1)+f},easeOutCirc:function(g,h,f,j,i){return j*Math.sqrt(1-(h=h/i-1)*h)+f},easeInOutCirc:function(g,h,f,j,i){if((h/=i/2)<1){return -j/2*(Math.sqrt(1-h*h)-1)+f}return j/2*(Math.sqrt(1-(h-=2)*h)+1)+f},easeInElastic:function(g,i,f,m,l){var j=1.70158;var k=0;var h=m;if(i==0){return f}if((i/=l)==1){return f+m}if(!k){k=l*0.3}if(h").css({position:"absolute",visibility:"visible",left:-d*(g/e),top:-f*(c/k)}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:g/e,height:c/k,left:l.left+d*(g/e)+(b.options.mode=="show"?(d-Math.floor(e/2))*(g/e):0),top:l.top+f*(c/k)+(b.options.mode=="show"?(f-Math.floor(k/2))*(c/k):0),opacity:b.options.mode=="show"?0:1}).animate({left:l.left+d*(g/e)+(b.options.mode=="show"?0:(d-Math.floor(e/2))*(g/e)),top:l.top+f*(c/k)+(b.options.mode=="show"?0:(f-Math.floor(k/2))*(c/k)),opacity:b.options.mode=="show"?1:0},b.duration||500)}}setTimeout(function(){b.options.mode=="show"?h.css({visibility:"visible"}):h.css({visibility:"visible"}).hide();if(b.callback){b.callback.apply(h[0])}h.dequeue();a("div.ui-effects-explode").remove()},b.duration||500)})}})(jQuery);;/* + * jQuery UI Effects Fold 1.7.1 + * + * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * http://docs.jquery.com/UI/Effects/Fold + * + * Depends: + * effects.core.js + */ (function(a){a.effects.fold=function(b){return this.queue(function(){var e=a(this),k=["position","top","left"];var h=a.effects.setMode(e,b.options.mode||"hide");var o=b.options.size||15;var n=!(!b.options.horizFirst);var g=b.duration?b.duration/2:a.fx.speeds._default/2;a.effects.save(e,k);e.show();var d=a.effects.createWrapper(e).css({overflow:"hidden"});var i=((h=="show")!=n);var f=i?["width","height"]:["height","width"];var c=i?[d.width(),d.height()]:[d.height(),d.width()];var j=/([0-9]+)%/.exec(o);if(j){o=parseInt(j[1],10)/100*c[h=="hide"?0:1]}if(h=="show"){d.css(n?{height:0,width:o}:{height:o,width:0})}var m={},l={};m[f[0]]=h=="show"?c[0]:o;l[f[1]]=h=="show"?c[1]:0;d.animate(m,g,b.options.easing).animate(l,g,b.options.easing,function(){if(h=="hide"){e.hide()}a.effects.restore(e,k);a.effects.removeWrapper(e);if(b.callback){b.callback.apply(e[0],arguments)}e.dequeue()})})}})(jQuery);;/* + * jQuery UI Effects Highlight 1.7.1 + * + * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * http://docs.jquery.com/UI/Effects/Highlight + * + * Depends: + * effects.core.js + */ (function(a){a.effects.highlight=function(b){return this.queue(function(){var e=a(this),d=["backgroundImage","backgroundColor","opacity"];var h=a.effects.setMode(e,b.options.mode||"show");var c=b.options.color||"#ffff99";var g=e.css("backgroundColor");a.effects.save(e,d);e.show();e.css({backgroundImage:"none",backgroundColor:c});var f={backgroundColor:g};if(h=="hide"){f.opacity=0}e.animate(f,{queue:false,duration:b.duration,easing:b.options.easing,complete:function(){if(h=="hide"){e.hide()}a.effects.restore(e,d);if(h=="show"&&a.browser.msie){this.style.removeAttribute("filter")}if(b.callback){b.callback.apply(this,arguments)}e.dequeue()}})})}})(jQuery);;/* + * jQuery UI Effects Pulsate 1.7.1 + * + * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * http://docs.jquery.com/UI/Effects/Pulsate + * + * Depends: + * effects.core.js + */ (function(a){a.effects.pulsate=function(b){return this.queue(function(){var d=a(this);var g=a.effects.setMode(d,b.options.mode||"show");var f=b.options.times||5;var e=b.duration?b.duration/2:a.fx.speeds._default/2;if(g=="hide"){f--}if(d.is(":hidden")){d.css("opacity",0);d.show();d.animate({opacity:1},e,b.options.easing);f=f-2}for(var c=0;c').appendTo(document.body).addClass(b.options.className).css({top:d.top,left:d.left,height:f.innerHeight(),width:f.innerWidth(),position:"absolute"}).animate(g,b.duration,b.options.easing,function(){c.remove();(b.callback&&b.callback.apply(f[0],arguments));f.dequeue()})})}})(jQuery);; +$j = jQuery.noConflict(); + + return true; +}); + diff --git a/src/ChatPage/chat/BypassLoginForm/js/LanguageOverride.js b/src/ChatPage/chat/BypassLoginForm/js/LanguageOverride.js new file mode 100755 index 0000000..5e64662 --- /dev/null +++ b/src/ChatPage/chat/BypassLoginForm/js/LanguageOverride.js @@ -0,0 +1,79 @@ +/************************************************************************************************ +* This file contains Javascript code authored by Interactive Intelligence, Inc. * +* * +* The contents of this file are warranted to function as intended, provided they are not * +* modified in any way by customers, end users, or other parties. * +* * +* During the course of this product's support lifecycle, Interactive Intelligence, Inc. may * +* publish updates to this file at any time, via an SU or similar process. If other * +* modifications are made to this file, these modifications may therefore be overwritten. * +* * +* Customers are encouraged to extend the functionality provided in this file, by creating * +* additional file(s) which use this file as an API. * +************************************************************************************************/ + + +/* Interaction Center 4.0 SU5 */ +var ININ_Web_Chat_LanguageOverride_Fileversion = "4.0005.0017.422"; + +define([], function() +{ + // Helper function to override which L10N will be fetched + function setLanguage(language) + { + requirejs.config( + { + config: + { + 'i18n': + { + locale: language + } + } + }); + } + + // Override it if it has been intentionally overridden + if (getUserSelectedLanguageOverride()) + { + setLanguage(getUserSelectedLanguageOverride()); + } else + { + var languageCode = (navigator.language || navigator.userLanguage || "").toLowerCase(); + if (languageCode) + { + var oldLanguageCode = languageCode; + + // We don't distribute a general "pt" file, just "pt-br". If someone requests "pt", + // it's better to give them "pt-br" rather than something non-Portuguese. + languageCode = languageCode.replace(/pt[-\w]*/, "pt-br"); + + // We don't distribute a general "en" file, just "en-us". Same as above. + languageCode = languageCode.replace(/en[-\w]*/, "en-us"); + + // Norwegian Bokmal and Norwegian Nynorsk map to Norwegian. + languageCode = languageCode.replace(/nb[-\w]*/, "no"); + languageCode = languageCode.replace(/nn[-\w]*/, "no"); + + // We distribute zh-cn (simplified Chinese) and zh-tw (traditional Chinese). + // But, browsers may send any of: zh-Hans, zh-sg (Singapore), zh-Hant, zh-hk (Hong Kong), zh-mo (Macau), zh-tw (Taiwan), zh. + // Map them to simplified/traditional as appropriate. + languageCode = languageCode.replace("zh-hans", "zh-cn"); + languageCode = languageCode.replace("zh-sg", "zh-cn"); + languageCode = languageCode.replace("zh-hk", "zh-tw"); + languageCode = languageCode.replace("zh-hant", "zh-tw"); + languageCode = languageCode.replace("zh-mo", "zh-tw"); + languageCode = languageCode.replace(/zh$/, "zh-cn"); + languageCode = languageCode.replace(/zh([^-])/, "zh-cn$1"); + + if (languageCode != oldLanguageCode) + { + setLanguage(languageCode); + } + } + } + + return {}; +}); + + diff --git a/src/ChatPage/chat/BypassLoginForm/js/UI.js b/src/ChatPage/chat/BypassLoginForm/js/UI.js new file mode 100755 index 0000000..1535a28 --- /dev/null +++ b/src/ChatPage/chat/BypassLoginForm/js/UI.js @@ -0,0 +1,8639 @@ +/************************************************************************************************ +* This file contains Javascript code authored by Interactive Intelligence, Inc. * +* * +* The contents of this file are warranted to function as intended, provided they are not * +* modified in any way by customers, end users, or other parties. * +* * +* During the course of this product's support lifecycle, Interactive Intelligence, Inc. may * +* publish updates to this file at any time, via an SU or similar process. If other * +* modifications are made to this file, these modifications may therefore be overwritten. * +* * +* Customers are encouraged to extend the functionality provided in this file, by creating * +* additional file(s) which use this file as an API. * +************************************************************************************************/ + + +/* Interaction Center 4.0 SU5 */ +var ININ_Web_Chat_UI_Fileversion = "4.0005.0017.422"; + +define(['i18n!nls/localization', 'external', 'WebServices', 'common'], function(localization, external, webservices, common) +{ + var ui = common.Type.registerLocalNamespace("ui"); + + + +// Register namespaces +ui.registerChildNamespace("Interfaces"); + +/** + * IStatusManager interface + * Provides methods: clearStatus(), setErrorStatus(), setStatus(), setBusy(), clearBusy(). + */ +ui.Interfaces.IStatusManager = new common.Interface('ui.Interfaces.IStatusManager', ['clearStatus', 'setErrorStatus', 'setStatus', 'setBusy', 'clearBusy']); + +// Register namespaces +ui.registerChildNamespace("ConfigConversions"); + +/** + * ConfigConversions class + * This class provides methods to convert the values found in config.js into the values + * desired by ui.Page.load(). + */ +ui.ConfigConversions.ChatInteractionType = "Chat"; +ui.ConfigConversions.CallbackInteractionType = "Callback"; + +// The following method is here for expansion purposes only, and is not supported at this time. +/* + * This method converts the value of "TextType" in config.js to a value that is acceptable for use as the + * useHtmlEditor parameter to Page.load(). + * + * Config.js contains either: + * TextType: Plain + * or: + * TextType: Html + * + * This method simply returns true if "Html" was specified, and false otherwise. + * It is case-insensitive. + * + * @param textType Either "Plain" or "Html" + * +ui.ConfigConversions.convertTextTypeToUseHtmlEditor = function(textType) +{ + if (null == textType) + { + return false; + } + if("plain" == textType.toLowerCase()) + { + return false; + } + if("html" == textType.toLowerCase()) + { + return true; + } + + return false; +}; +*/ + +/** + * This method converts the value of "InteractionTypes" in config.js to a value that is acceptable for use as the + * pageMode parameter to Page.load(). + * + * @param interactionTypes A specification of which interaction type(s) to allow in this session, in the format used by config.js, i.e. ["chat","callback"] + * @return A bitmap representing the specified interaction types + */ +ui.ConfigConversions.convertInteractionTypesToPageMode = function(interactionTypes) +{ + if(!interactionTypes) + { + return ui.PageModes.CHAT_AND_CALLBACK; + } + + if(common.Utilities.isType(interactionTypes, Array)) + { + return ui.ConfigConversions.convertInteractionTypeArrayToPageMode(interactionTypes); + } + + return ui.ConfigConversions.convertInteractionTypeScalarToPageMode(interactionTypes); +}; + +/** + * This method converts a scalar value of "InteractionTypes" in config.js to a value that is acceptable for use as the + * pageMode parameter to Page.load(). + * @see convertInteractionTypesToPageMode + * + * @param interactionType A specification of which interaction type to allow in this session, in the format used by config.js, i.e. "callback" + * @return A bitmap representing the specified interaction type + */ +ui.ConfigConversions.convertInteractionTypeScalarToPageMode = function(interactionType) +{ + if(interactionType == ui.ConfigConversions.ChatInteractionType) + { + return ui.PageModes.CHAT; + } + + if(interactionType == ui.ConfigConversions.CallbackInteractionType) + { + return ui.PageModes.CALLBACK; + } + + return ui.PageModes.CHAT_AND_CALLBACK; +}; + +/** + * This method converts an array value of "InteractionTypes" in config.js to a value that is acceptable for use as the + * pageMode parameter to Page.load(). + * @see convertInteractionTypesToPageMode + * + * @param interactionTypes A specification of which interaction types to allow in this session, in the format used by config.js, i.e. ["chat","callback"] + * @return A bitmap representing the specified interaction types + */ +ui.ConfigConversions.convertInteractionTypeArrayToPageMode = function(interactionTypes) +{ + var isChatEnabled = webservices.Utilities.doesArrayHaveElement(interactionTypes, ui.ConfigConversions.ChatInteractionType); + var isCallbackEnabled = webservices.Utilities.doesArrayHaveElement(interactionTypes, ui.ConfigConversions.CallbackInteractionType); + if(isChatEnabled && isCallbackEnabled) + { + return ui.PageModes.CHAT_AND_CALLBACK; + } + if(isChatEnabled) + { + return ui.PageModes.CHAT; + } + if(isCallbackEnabled) + { + return ui.PageModes.CALLBACK; + } + + return ui.PageModes.CHAT_AND_CALLBACK; +}; + +/** + * Returns the URI fragment which the webserver has been configured to treat as a reverse proxy to + * the primary IC server. + * Since AJAX requests can only be made to the originating server, it is necessary to configure a + * reverse proxy in order for the requests to get to the IC server(s). If this Javascript was accessed from + * http://this-server/somePage.html, then it cannot make AJAX requests to + * http://IC-server-1:8114/..., even if there weren't a firewall in the way. So, perhaps + * this-server was configured in a way such that: + * http://this-server/I3Root/Server1/websvcs/serverConfiguration reverse proxies to + * http://IC-server-1:8114/websvcs/serverConfiguration. + * In that case, "I3Root/Server1" is the "URI Fragment". + * + * @param icServerCount How many IC servers exist in this configuration + * @return The URI fragment that will result in a reverse proxy to the primary IC server. + */ +ui.ConfigConversions.convertICServerCountToCurrentUriFragment = function(icServerCount) +{ + return "I3Root/Server1"; +}; + +/** + * Returns the set of URI fragments which map to all of the IC servers. Currently only 1 or 2 IC servers + * are supported. + * + * @param icServerCount How many IC servers exist in this configuration + * @return The URI fragments that will result in reverse proxies to the IC servers. + */ +ui.ConfigConversions.convertICServerCountToUriFragments = function(icServerCount) +{ + if(icServerCount == 2) + { + return ["I3Root/Server1", "I3Root/Server2"]; + } + + return ["I3Root/Server1"]; +}; + +// Register namespaces +ui.registerChildNamespace("FormFieldTypes"); + +/** + * ui.FormFieldTypes enum + * Represents the various types of form fields that may be present in a Form. + */ +ui.FormFieldTypes.MIN = 1; + +ui.FormFieldTypes.Username = 1; +ui.FormFieldTypes.Password = 2; +ui.FormFieldTypes.ConfirmPassword = 3; +ui.FormFieldTypes.FirstName = 4; +ui.FormFieldTypes.MiddleName = 5; +ui.FormFieldTypes.LastName = 6; +ui.FormFieldTypes.HomeStreetAddress = 7; +ui.FormFieldTypes.HomeCity = 8; +ui.FormFieldTypes.HomeState = 9; +ui.FormFieldTypes.HomePostalCode = 10; +ui.FormFieldTypes.HomeCountry = 11; +ui.FormFieldTypes.HomeEmail = 12; +ui.FormFieldTypes.HomePhone = 13; +ui.FormFieldTypes.HomePhone2 = 14; +ui.FormFieldTypes.HomeFax = 15; +ui.FormFieldTypes.HomePager = 16; +ui.FormFieldTypes.HomeMobile = 17; +ui.FormFieldTypes.HomeUrl = 18; +ui.FormFieldTypes.Department = 19; +ui.FormFieldTypes.Company = 20; +ui.FormFieldTypes.JobTitle = 21; +ui.FormFieldTypes.AssistantName = 22; +ui.FormFieldTypes.AssistantPhone = 23; +ui.FormFieldTypes.BusinessStreetAddress = 24; +ui.FormFieldTypes.BusinessCity = 25; +ui.FormFieldTypes.BusinessState = 26; +ui.FormFieldTypes.BusinessPostalCode = 27; +ui.FormFieldTypes.BusinessCountry = 28; +ui.FormFieldTypes.BusinessEmail = 29; +ui.FormFieldTypes.BusinessPhone = 30; +ui.FormFieldTypes.BusinessPhone2 = 31; +ui.FormFieldTypes.BusinessFax = 32; +ui.FormFieldTypes.BusinessPager = 33; +ui.FormFieldTypes.BusinessMobile = 34; +ui.FormFieldTypes.BusinessUrl = 35; +ui.FormFieldTypes.Remarks = 36; +ui.FormFieldTypes.Name = 37; +ui.FormFieldTypes.Subject = 38; +ui.FormFieldTypes.Telephone = 39; + +ui.FormFieldTypes.MAX = 39; + +// Register namespaces +ui.registerChildNamespace("Interfaces"); + +/** + * IFormField interface + * Represents a field within an ui.IFormSection within an ui.Form. + * The various types of fields are enumerated in ui.FormFieldTypes. + * A field simply represents its FormFieldType. + */ +ui.Interfaces.IFormField = new common.Interface('ui.Interfaces.IFormField', ['get_type']); + +/** + * FormField class + * Implementation of IFormField interface. + */ +ui.FormField = Class.create(common.InterfaceImplementation, +{ + /** + * constructor + * @param type - The FormFieldType of this FormField. + */ + initialize:function($super, type) + { + this._validateType(type); + + $super(); + + this.addImplementedInterface(ui.Interfaces.IFormField, ui); + + this._type = type; + }, + + // methods + + /** + * Throws an exception if the supplied param is not one of the values enumerated in ui.FormFieldTypes. + * (Private method) + * + * @param type - A value which the caller wishes to determine is or is not a valid form field type. + */ + _validateType : function(type) + { + common.ParameterValidation.validate([type], [{"type": Number, "required": true}] ); + + if((type < ui.FormFieldTypes.MIN) || + (type > ui.FormFieldTypes.MAX)) + { + throw common.ExceptionFactory.createException("Not a valid type: " + type); + } + }, + + /** + * Returns the type of this FormField. + */ + get_type : function() + { + return this._type; + } +}); + +// Register namespaces +ui.registerChildNamespace("Interfaces"); + +/** + * IFormSection interface + * A Form is composed of zero or more IFormSections. + * An IFormSection is composed of a name, and zero or more IFormFields. + */ +ui.Interfaces.IFormSection = new common.Interface('ui.Interfaces.IFormSection', ['get_name', 'get_fields', 'addFieldByFieldType', 'addField']); + +/** + * FormSection class + * Implementation of IFormSection interface. + */ +ui.FormSection = Class.create(common.InterfaceImplementation, +{ + /** + * constructor + * @param name - Optional. The name of this section of the form. + * @param fields - Optional. The fields within this section of the form. + */ + initialize:function($super, name, fields) + { + common.ParameterValidation.validate([name, fields], [ {"type": String, "required": false, "allowEmpty": true}, {"type": Array, "required": false} ]); + + this._validateArrayElements(fields); + + $super(); + + this.addImplementedInterface(ui.Interfaces.IFormSection, ui); + + this._name = name; + this._fields = []; + + if(fields && fields.length > 0) + { + this._fields = fields; + } + }, + + // methods + + /** + * Ensures that each object in the supplied array is a valid IFormField. + * (Private method) + */ + _validateArrayElements : function(fields) + { + if(fields) + { + for(var i = 0; i < fields.length; ++i) + { + common.Interface.ensureImplements(fields[i], ui.Interfaces.IFormField); + } + } + }, + + /** + * Returns the name of this FormSection + */ + get_name : function() + { + return this._name; + }, + + /** + * Returns an array containing the IFormFields in this FormSection + */ + get_fields : function() + { + return this._fields; + }, + + /** + * Adds a new FormField of the specified type to this FormSection + * + * @param fieldType - The type of FormField to add. Must be a member of ui.FormFieldTypes. + * @returns The modified FormSection, to allow for chaining, such as: + * myFormSection.addFieldByFieldType(aFieldType).addFieldByFieldType(anotherFieldType) + */ + addFieldByFieldType : function(fieldType) + { + return this.addField(new ui.FormField(fieldType)); + }, + + /** + * Adds a new IFormField to this FormSection. + * + * @param field - An instance of any class which implements the IFormField interface. + * @returns The modified FormSection, to allow for chaining, such as: + * myFormSection.addField(aField).addField(anotherField) + */ + addField : function(field) + { + common.Interface.ensureImplements(field, ui.Interfaces.IFormField); + + this._fields.push(field); + return this; + } +}); + +/** + * Form class + * A Form simply represents a collection of IFormSections. + */ +ui.Form = Class.create( +{ + /** + * constructor + * @param sections - Optional. An array of instances of IFormSection. + */ + initialize:function(sections) + { + common.ParameterValidation.validate([sections], [ {"type": Array, "required": false} ]); + + this._sections = []; + + if(sections && sections.length > 0) + { + this._sections = sections; + } + }, + + // methods + + /** + * Returns the IFormSections in this form. + */ + get_sections : function() + { + return this._sections; + }, + + /** + * Adds an IFormSections to this Form. + * + * @param section The IFormSection to add to this Form. + */ + addSection : function(section) + { + common.Interface.ensureImplements(section, ui.Interfaces.IFormSection); + + this._sections.push(section); + return this; + } +}); + +// Register namespaces +ui.registerChildNamespace("_Internal"); + +/** + * _ErrorDisplayTextBuilder class + * This is a singleton, which may be referenced as ui.ErrorDisplayTextBuilder. + * This class handles converting error codes into human-readable messages. + */ +ui._Internal._ErrorDisplayTextBuilder = Class.create( +{ + /** + * Builds an error message from an error code (from webservices.ErrorCodes) and/or a string. + * If only the string is present, it will be returned. + * If only the error code is present, a human-readable translation of it will be returned. + * If both are present, the return value will be the string, followed by " - ", followed by the human-readable translation + * of the error code. + * If neither is present, '' will be returned. + * + * @param error An webservices.ErrorCode + * @param mainErrorText A string + */ + build : function(error, mainErrorText) + { + var builtText = ''; + if(mainErrorText) + { + builtText = mainErrorText; + } + + if(error) + { + if(builtText.length > 0) + { + builtText += ' - '; + } + + builtText += this.buildError(error); + } + + return builtText; + }, + + /** + * Takes an error code (from webservices.ErrorCodes) and returns its meaning in a human-readable format. + * + * @param error An webservices.ErrorCode + */ + buildError : function(error) + { + common.Interface.ensureImplements(error, webservices.Interfaces.IError); + + switch(error.get_errorSource()) + { + case webservices.ErrorCodes.WEBSVC: + return this.buildWebSvcError(error); + case webservices.ErrorCodes.HTTP: + return this.buildHttpError(error); + default: + return this.buildGeneralError(error); + } + }, + + /** + * Takes an error code whose source is webservices.ErrorCodes.WEBSVC and returns its meaning in + * a human-readable format. Unless you are sure that it's a WEBSVC error code, it would be safer to call build(). + * + * @param error An webservices.ErrorCode + */ + buildWebSvcError : function(error) + { + common.Interface.ensureImplements(error, webservices.Interfaces.IError); + + switch(error.get_errorType()) + { + case webservices.ErrorCodes.GENERAL: + return this.buildGeneralError(error); + case webservices.ErrorCodes.CONTENTTYPE: + return this.buildContentTypeError(error); + case webservices.ErrorCodes.CONTENT: + return this.buildContentError(error); + case webservices.ErrorCodes.UNKNOWNENTITY: + return this.buildUnknownEntityError(error); + case webservices.ErrorCodes.USERDB: + return this.buildUserDbError(error); + default: + return this.buildGeneralError(error); + } + }, + + /** + * Takes an error code whose source is not webservices.HTTP, and whose source is not + * webservices.WEBSVC unless its error type is webservices.ErrorCodes.GENERAL. + * Returns its meaning in a human-readable format. + * Unless you are sure of the error code (and error type, if error code is WEBSVC), it would be safer to call build(). + * + * @param error An webservices.ErrorCode + */ + buildGeneralError : function(error) + { + common.Interface.ensureImplements(error, webservices.Interfaces.IError); + + return localization.GeneralError; + }, + + /** + * Takes an error code whose source is webservices.ErrorCodes.WEBSVC and whose error type is + * webservices.ErrorCodes.CONTENTTYPE. + * Returns its meaning in a human-readable format. + * Unless you are sure that it's a WEBSVC error code, or if its error type, it would be safer to call build(). + * + * @param error An webservices.ErrorCode + */ + buildContentTypeError : function(error) + { + common.Interface.ensureImplements(error, webservices.Interfaces.IError); + + switch(error.get_subErrorType()) + { + case webservices.ErrorCodes.INVALIDCHARSET: + return localization.InvalidCharSetError; + case webservices.ErrorCodes.INVALIDCONTENTTYPE: + return localization.InvalidContentTypeError; + default: + return localization.InvalidContentTypeError; + } + }, + + /** + * Takes an error code whose source is webservices.ErrorCodes.WEBSVC and whose error type is + * webservices.ErrorCodes.CONTENT. + * Returns its meaning in a human-readable format. + * Unless you are sure that it's a WEBSVC error code, or if its error type, it would be safer to call build(). + * + * @param error An webservices.ErrorCode + */ + buildContentError : function(error) + { + common.Interface.ensureImplements(error, webservices.Interfaces.IError); + + switch(error.get_subErrorType()) + { + case webservices.ErrorCodes.INVALID: + return this.buildContentInvalidError(error); + default: + return localization.ContentError; + } + }, + + /** + * Takes an error code whose source is webservices.ErrorCodes.WEBSVC and whose error type is + * webservices.ErrorCodes.CONTENT and whose sub error type is INVALID. + * Returns its meaning in a human-readable format. + * Unless you are sure that it's a WEBSVC error code, with error type of CONTENT and sub error type of INVALID, + * it would be safer to call build(). + * + * @param error An webservices.ErrorCode + */ + buildContentInvalidError : function(error) + { + common.Interface.ensureImplements(error, webservices.Interfaces.IError); + + switch(error.get_token(4)) + { + case webservices.ErrorCodes.MISSINGDATA: + return localization.MissingDataError; + default: + return localization.ContentError; + } + }, + + /** + * Takes an error code whose source is webservices.ErrorCodes.WEBSVC and whose error type is + * webservices.ErrorCodes.UNKNOWNENTITY. + * Returns its meaning in a human-readable format. + * Unless you are sure that it's a WEBSVC error code, or if its error type, it would be safer to call build(). + * + * @param error An webservices.ErrorCode + */ + buildUnknownEntityError : function(error) + { + common.Interface.ensureImplements(error, webservices.Interfaces.IError); + + switch(error.get_subErrorType()) + { + case webservices.ErrorCodes.SESSION: + return localization.UnknownSessionError; + case webservices.ErrorCodes.PARTICIPANT: + return localization.UnknownParticipantError; + case webservices.ErrorCodes.BADTARGET: + return localization.BadTargetError; + default: + return localization.UnknownEntityError; + } + }, + + /** + * Takes an error code whose source is webservices.ErrorCodes.WEBSVC and whose error type is + * webservices.ErrorCodes.USERDB. + * Returns its meaning in a human-readable format. + * Unless you are sure that it's a WEBSVC error code, or if its error type, it would be safer to call build(). + * + * @param error An webservices.ErrorCode + */ + buildUserDbError : function(error) + { + common.Interface.ensureImplements(error, webservices.Interfaces.IError); + + switch(error.get_subErrorType()) + { + case webservices.ErrorCodes.NOTONLINE: + return localization.UserNotOnline; + case webservices.ErrorCodes.BADCREDENTIALS: + return localization.BadCredentialsError; + case webservices.ErrorCodes.ACCOUNTEXISTS: + return localization.AccountExistsError; + default: + return localization.UserDbError; + } + }, + + /** + * Takes an error code whose source is webservices.ErrorCodes.HTTP and returns its meaning in + * a human-readable format. Unless you are sure that it's an HTTP error code, it would be safer to call build(). + * + * @param error An webservices.ErrorCode + */ + buildHttpError : function(error) + { + common.Interface.ensureImplements(error, webservices.Interfaces.IError); + + return localization.ErrorConnectingToServer; + } +}); + +/** + * Singleton instance of the _ErrorDisplayTextBuilder class. + */ +ui.ErrorDisplayTextBuilder = new ui._Internal._ErrorDisplayTextBuilder(); + +/** + * Control class + * Parent class for various GUI classes in this web application. Each instance of this class wraps a DOM object. + * Also provides convenience methods for working with DOM objects. + */ +ui.Control = Class.create(common.InterfaceImplementation, +{ + /** + * Constructor + * + * @param domObject the DOM object which the browser will use to display this Control. In the default + * implementation, this is treated as an abstract class, and its subclasses each have a method called + * _buildDomObject(). These subclasses then simply include the following in their constructors: + * $super(_buildDomObject()); + * In turn, _buildDomObject() makes use of createElement(), createChildElement(), and/or createHiddenChildElement(). + */ + initialize:function($super, domObject) + { + var numArgs = 2; + if(arguments.length != numArgs) + { + throw common.ExceptionFactory.createException("Control constructor called with " + arguments.length + " arguments, but expected " + numArgs + "."); + } + + if(!domObject) + { + throw common.ExceptionFactory.createException("Control constructor called with a null/undefined dom object paramter."); + } + + $super(); + + this._domObject = domObject; + }, + + /** + * destructor + */ + destroy : function() + { + if(this._domObject) + { + if(this._domObject.destroy) + { + this._domObject.destroy(); + } + + delete this._domObject; + this._domObject = null; + } + + common.InterfaceImplementation.prototype.destroy.call(this); + }, + + // public methods + + /** + * Returns the DOM object which the browser will use to display this Control. + */ + get_domObject : function() + { + return this._domObject; + }, + + /** + * Enables (by passing true) or disables (by passing false) this Control. + * + * @param enabled If true, this Control will be enabled. If false, this Control will be disabled. + */ + enable : function(enabled) + { + this._domObject.disabled = !enabled; + }, + + /** + * Returns a boolean indicating whether this Control is visible or not. + * + * @return Boolean + */ + isVisible : function() + { + return this._domObject.visible(); + }, + + /** + * Causes this Control to become visible in the browser. + */ + show : function() + { + Element.show(this._domObject); + }, + + /** + * Causes this control to become hidden in the browser. + */ + hide : function() + { + Element.hide(this._domObject); + }, + + /** + * Creates a DOM element. + * + * @param tag Which type of HTML tag the new element should have: a, br, table, etc. + * @param id The ID of the new element (optional) + * @param attributes The attributes of the new element + * @param styles The CSS to apply to the new element (optional) + * @param innerHTML The inner HTML of the element (optional, and for compatible tags only) + * Example: + * var a = createElement('a', 'myanchor', { 'class': 'foo', href: '/foo.html' }, + * { backgroundColor: '#900', fontSize: '12px' }, 'my link to foo'); + */ + createElement : function(tag, id, attributes, styles, innerHTML) + { + var element = new Element(tag, attributes); + + if(id) + { + element.id = id; + } + + if(styles) + { + Element.setStyle(element, styles); + } + + if(innerHTML) + { + element.innerHTML = innerHTML; + } + + return element; + }, + + /** + * Creates a DOM element, as a child of the supplied DOM element + * + * @param parent An existing DOM element which will become the parent of the new DOM element + * @param tag Which type of HTML tag the new element should have: a, br, table, etc. + * @param id The ID of the new element (optional) + * @param attributes The attributes of the new element + * @param styles The CSS to apply to the new element (optional) + * @param innerHTML The inner HTML of the element (optional, and for compatible tags only) + * Example: + * var a = createChildElement(parentOfA, 'a', 'myanchor', { 'class': 'foo', href: '/foo.html' }, + * { backgroundColor: '#900', fontSize: '12px' }, 'my link to foo'); + */ + createChildElement : function(parent, tag, id, attributes, styles, innerHTML) + { + if(!parent) + { + throw common.ExceptionFactory.createException("createChildElement: parent parameter is not defined"); + } + + var element = this.createElement(tag, id, attributes, styles, innerHTML); + parent.appendChild(element); + return element; + }, + + /** + * Creates a DOM element, as a child of the supplied DOM element. The new DOM element will initially be hidden. + * + * @param parent An existing DOM element which will become the parent of the new DOM element + * @param tag Which type of HTML tag the new element should have: a, br, table, etc. + * @param id The ID of the new element (optional) + * @param attributes The attributes of the new element + * @param styles The CSS to apply to the new element (optional) + * @param innerHTML The inner HTML of the element (optional, and for compatible tags only) + */ + createHiddenChildElement : function(parent, tag, id, attributes, styles, innerHTML) + { + var element = this.createElement(tag, id, attributes, styles, innerHTML); + $j(element).hide(); + parent.appendChild(element); + return element; + }, + + // private/protected methods + + /** + * Finds all HTML elements of the specified type and CSS class, within the supplied element, + * and makes them be the same width (the width of whichever + * is currently widest). This is useful because a phrase in + * one language may require more width than the same phrase in + * another language. + * + * @param tagName The type of HTML tags that are to be aligned. Example: "label" + * @param className Optional. If supplied, only tags with this CSS class will be affected. + * @param parent The DOM object on which to perform the work. Optional - if not supplied, the main DOM object for the control will be used. + */ + _alignTags : function(tagName, className, parent) + { + var maxWidth = 0; + if (!parent) + { + parent = this.get_domObject(); + if (!parent) + { + return; // Control hasn't been drawn yet + } + } + var elements = parent.getElementsByTagName(tagName); + for (var i=0; i 0)) + { + elements[i].style.width = maxWidth + "px"; + } + } + } + + return maxWidth; + }, + + /** + * Convenience method to return what the value of domElement.offsetRight would + * be, if browsers supported it. + * + * @param domElement Any DOM element + */ + _getOffsetRight : function(domElement) + { + return domElement.offsetLeft + domElement.offsetWidth; + } +}); + +/** + * FormPanelBase class + * Base class for UI representation of forms. + */ +ui.FormPanelBase = Class.create(ui.Control, +{ + /** + * constructor + * + * @param statusManager - An instance of a class which implements ui.Interfaces.IStatusManager + * @param registerFormContainer - The Panel that contains the registration form. Must have + * a showRegisterForm() method. + * @param submitButtonText - The text that should be displayed on the form's submit button + * @param formPanelClass - The CSS selector of the form. The leading dot should not be included. + * @param requiredFields - An array of FormFieldTypes, indicating which fields are required on this form. Optional. + */ + initialize : function($super, statusManager, registerFormContainer, submitButtonText, formPanelClass, requiredFields) + { + if((arguments.length != 5) && (arguments.length != 6)) + { + throw common.ExceptionFactory.createException("FormPanelBase constructor called with " + arguments.length + " arguments, but expected 5 or 6."); + } + + if(requiredFields) + { + this._requiredFields = requiredFields; + } + else + { + this._requiredFields = this.getRequiredFields(); + } + + this._submitButtonText = submitButtonText; + this._formPanelClass = formPanelClass; + this._statusManager = statusManager; + this._registerFormContainer = registerFormContainer; + + var domObject = this._buildDomObject(); + this._validateDomObject(); + + $super(domObject); + }, + + /** + * destructor + */ + destroy : function() + { + this._statusManager = null; + + ui.Control.prototype.destroy.call(this); + }, + + // methods + + /** + * Abstract method, should be overridden by subclass. + * Gets a list of fields which the user is required to fill in. + * + * @return An array of ui.FormFieldTypes indicating the required fields of this form. + */ + getRequiredFields : function() + { + return []; + }, + + /** + * Returns 1 and displays an error if the field is required but blank. + * Returns 0 otherwise. + */ + _validateField : function(value, textbox, fieldType) + { + if(textbox) + { + if(webservices.Utilities.doesArrayHaveElement(this._requiredFields, fieldType)) + { + if(!value) + { + this._showFieldError(textbox, localization.FieldIsRequired); + return 1; + } + } + + this._hideFieldError(textbox); + } + + return 0; + }, + + /** + * Hides the div in which error messages go. + */ + _hideFieldError : function(textbox) + { + var errorDiv = this._getTextBoxErrorDivFromTextBox(textbox); + Element.hide(errorDiv); + }, + + /** + * Puts a message into the div in which error messages go, and ensures that the div is visible. + * + * @param textbox - The textbox to which the error pertains (so that an indicator may be shown next to it) + * @param errorText - the error message to display + */ + _showFieldError : function(textbox, errorText) + { + this._setFieldError(textbox, errorText); + var errorDiv = this._getTextBoxErrorDivFromTextBox(textbox); + Element.show(errorDiv); + }, + + /** + * Given a textbox in the field, returns the corresponding error div, or null if not found. + */ + _getTextBoxErrorDivFromTextBox : function(textbox) + { + var parentDiv = Element.up(textbox, 'div'); + var divs = Element.select(parentDiv, 'span.iwc-formfielderror'); + if(divs && divs.length > 0) + { + return divs[0]; + } + + return null; + }, + + /** + * Sets the value of the textbox's error span to the supplied text. + * @param textbox - the textbox to which the error pertains + * @param errorText - the error message + */ + _setFieldError : function(textbox, errorText) + { + var errorDiv = this._getTextBoxErrorDivFromTextBox(textbox); + var spans = Element.select(errorDiv, 'span'); + if(spans && spans.length > 0) + { + var span = spans[0]; + if(span) + { + span.innerHTML = errorText; + } + } + }, + + /** + * Erases the text in a textbox + */ + _clearTextboxIfAvailable : function(textbox) + { + if(textbox) + { + textbox.value = ''; + } + + return null; + }, + + /** + * Ensures that this is a valid form panel. Subclasses may override - this implementation simply + * checks if the form has a submit button. + */ + _validateDomObject : function() + { + if(!this._submitButton) + { + throw common.ExceptionFactory.createException("Submit button not found"); + } + }, + + /** + * Builds the DOM representation of the form, so the browser can display it. + * @see _buildInnerPanel() in subclasses + */ + _buildDomObject : function() + { + var div = this.createElement('div', null, {'class': 'iwc-form-panel ' + this._formPanelClass}); + var form = this.createChildElement(div, 'form', null, {'action': '#', 'class': 'iwc-form'}); + form.onsubmit = function() { return false; } + form.appendChild(this._buildInnerPanel()); + return div; + }, + + /** + * Builds the DOM representation of the div that contains this form's submit button. + */ + _buildButtonPanel : function() + { + var div = this.createElement('div', null, {'class': 'iwc-form-button-div'}); + this._submitButton = this.createChildElement(div, 'input', null, {'type': 'submit', 'value': this._submitButtonText}); + Element.observe(this._submitButton, 'click', this._onClickSubmitButton.bindAsEventListener(this)); + return div; + }, + + /** + * Adds a div to the form into which error messages can be placed. + */ + _addErrorDiv : function(div) + { + var divError = this.createChildElement(div, 'span', null, { 'class': 'iwc-formfielderror' }, { 'display': 'none' }); + this.createChildElement(divError, 'img', null, { 'src': 'img/error.png' }); + this.createChildElement(divError, 'span'); + }, + + /** + * Handler for the submit button being clicked. + */ + _onClickSubmitButton : function() + { + // override this in derived class + }, + + /** + * Returns the text that is currently in the supplied textbox, if any. Returns null otherwise. + */ + _getValueIfAvailable : function(textbox) + { + if(textbox) + { + return textbox.value; + } + + return null; + } +}); + +/** + * LoginFormPanelBase class + * + * UI representation of a login form. May be subclassed for specific purposes. + */ +ui.LoginFormPanelBase = Class.create(ui.FormPanelBase, +{ + AUTHENTICATION_NONE : 0, // Should not actually be used, but is useful for variable initialization before ORing the other values. + AUTHENTICATION_ANONYMOUS : 1, + AUTHENTICATION_TRACKER : 2, + //AUTHENTICATION_STS : 4, + //... + + /** + * constructor + * + * @param statusManager - An instance of a class which implements ui.Interfaces.IStatusManager + * @param registerFormContainer - The Panel that contains the registration form. Must have + * a showRegisterForm() method. + * @param submitButtonText - The text that should be displayed on the form's submit button + * @param formPanelClass - The CSS selector of the form. The leading dot should not be included. + * @param allowedAccessTypes - A logical OR of one or more of the AUTHENTICATION_* constants above. + * @param requiredFields - An array of FormFieldTypes, indicating which fields are required on this form. Optional. + * @param strings - An object may be used to override some or all of the strings used in this control. Optional. + */ + initialize : function($super, statusManager, registerFormContainer, submitButtonText, formPanelClass, allowedAccessTypes, requiredFields, strings) + { + var minArgs = 6; + var maxArgs = 8; + if((arguments.length < minArgs) || (arguments.length > maxArgs)) + { + throw common.ExceptionFactory.createException("LoginFormPanel constructor called with " + arguments.length + " arguments, but expected between " + minArgs + " and " + maxArgs); + } + + this._initializeStrings(strings); + this._allowedAccessTypes = allowedAccessTypes; + + $super(statusManager, registerFormContainer, submitButtonText, formPanelClass, requiredFields); + }, + + /** + * destructor + */ + destroy : function() + { + ui.FormPanelBase.prototype.destroy.call(this); + }, + + // methods + + /** + * Determines which form field should get focus when the overall form does. + */ + focus : function() + { + try + { + if(this._authenticatedRadio && this._anonymousRadio) + { + if(this._isAuthenticatedRadioClicked()) + { + this._authenticatedIdentifierTextBox.focus(); + } + else + { + this._anonymousIdentifierTextBox.focus(); + } + } + else if(this._authenticatedIdentifierTextBox) + { + this._authenticatedIdentifierTextBox.focus(); + } + else if(this._anonymousIdentifierTextBox) + { + this._anonymousIdentifierTextBox.focus(); + } + } catch (e) + { + } + this._alignTags("label"); + this._alignSubmitButton(); + }, + + /** + * Clears the fields of the form + */ + reset : function() + { + this._onClickAuthenticatedRadio(); + this._clearTextboxIfAvailable(this._anonymousIdentifierTextBox); + this._clearTextboxIfAvailable(this._authenticatedIdentifierTextBox); + this._clearTextboxIfAvailable(this._authenticatedCredentialsTextBox); + }, + + /** + * Overrides the UI control to which the submit button should dynamically align. + * In LTR languages, the right edge of the button will align with the right edge of this control. + * If this method is never called, the target will default to the name/username + * textbox (see getSubmitButtonAlignmentTarget()). + * + * @param element Any DOM element on the screen. + */ + setSubmitButtonAlignmentTarget : function(element) + { + this._submitButtonAlignmentTarget = element; + }, + + /** + * Gets the UI control to which the submit button should dynamically align. + * In LTR languages, the right edge of the button will align with the right edge of this control. + * If setSubmitButtonAlignmentTarget() was ever called, the return value will be the parameter + * that was passed to that method. Otherwise, the return value will be the name/username + * textbox. + * + * @return A DOM element on the screen. + */ + getSubmitButtonAlignmentTarget : function() + { + if (this._submitButtonAlignmentTarget) + { + return this._submitButtonAlignmentTarget; + } + + if(this._authenticatedRadio && this._anonymousRadio) + { + if(this._isAuthenticatedRadioClicked()) + { + return this._authenticatedIdentifierTextBox; + } + else + { + return this._anonymousIdentifierTextBox; + } + } + else if(this._authenticatedIdentifierTextBox) + { + return this._authenticatedIdentifierTextBox; + } + else if(this._anonymousIdentifierTextBox) + { + return this._anonymousIdentifierTextBox; + } + }, + + /** + * Sets up the strings which will be used in this UI. + * + * @param defaultOverrides Optional. May be used to override the strings that would be used by default. + */ + _initializeStrings : function(defaultOverrides) + { + this._strings = defaultOverrides; + if (!this._strings) + { + this._strings = new Object(); + } + + if (!this._strings.authenticatedRadioLabel) + { + this._strings.authenticatedRadioLabel = localization.IHaveAnAccount; + } + + if (!this._strings.anonymousRadioLabel) + { + this._strings.anonymousRadioLabel = localization.IDontHaveAnAccount; + } + + if (!this._strings.createAccountLinkLabel) + { + this._strings.createAccountLinkLabel = localization.CreateAnAccount; + } + + if (!this._strings.authenticatedIdentifierLabel) + { + this._strings.authenticatedIdentifierLabel = localization.UserNameLabel; + } + + if (!this._strings.authenticatedCredentialsLabel) + { + this._strings.authenticatedCredentialsLabel = localization.PasswordLabel; + } + + if (!this._strings.anonymousIdentifierLabel) + { + this._strings.anonymousIdentifierLabel = localization.NameLabel; + } + + // Comparison is different here: if caller passes in nothing, the default value is used. If caller passes + // in some other string, that string is used. If caller passes in false, the label is not displayed at all. + if (!this._strings.anonymousIdentifierQualifierLabel && false !== this._strings.anonymousIdentifierQualifierLabel) + { + this._strings.anonymousIdentifierQualifierLabel = localization.OptionalTag; + } + }, + + _buildInnerPanel : function() + { + var div = this.createElement('div'); + + if (this._multipleAccessTypesAllowed()) + { + div.appendChild(this._buildRadioButtonPanel()); + } + + if (this._allowedAccessTypes & this.AUTHENTICATION_TRACKER) + { + this._authenticatedCredentialsDiv = this._buildAuthenticatedCredentialsPanel(); + div.appendChild(this._authenticatedCredentialsDiv); + } + + if (this._allowedAccessTypes & this.AUTHENTICATION_ANONYMOUS) + { + this._anonymousCredentialsDiv = this._buildAnonymousCredentialsPanel(); + div.appendChild(this._anonymousCredentialsDiv); + } + + div.appendChild(this._buildExtraFormFields()); + this._buttonPanel = this._buildButtonPanel() + div.appendChild(this._buttonPanel); + + this._onClickAuthenticatedRadio(); + + return div; + }, + + /** + * Returns true if the number of access types (anonymous, + * tracker, etc.) is >= 2. Returns false otherwise. + */ + _multipleAccessTypesAllowed : function() + { + var alreadyFoundAOne = false; + var bits = this._allowedAccessTypes; + while (0 != bits) + { + if (bits & 1) + { + if (alreadyFoundAOne) + { + return true; + } + alreadyFoundAOne = true; + } + bits >>= 1; + } + return false; + }, + + _buildRadioButtonPanel : function() + { + var div = this.createElement('div'); + var childDiv = null; + + if (this._allowedAccessTypes & this.AUTHENTICATION_TRACKER) + { + childDiv = this.createChildElement(div, 'div', null, {'class': 'iwc-form-field-div'}); + this._authenticatedRadio = this.createChildElement(childDiv, 'input', null, {'type': 'radio', 'defaultChecked': true, 'checked': true}); + var span = this.createChildElement(childDiv, 'span', null, { 'class': 'iwc-account-radio-button-label'}, null, this._strings.authenticatedRadioLabel); + Element.observe(this._authenticatedRadio, 'click', this._onClickAuthenticatedRadio.bindAsEventListener(this)); + Element.observe(span, 'click', this._onClickAuthenticatedRadio.bindAsEventListener(this)); + } + + if (this._allowedAccessTypes & this.AUTHENTICATION_ANONYMOUS) + { + childDiv = this.createChildElement(div, 'div', null, {'class': 'iwc-form-field-div'}); + this._anonymousRadio = this.createChildElement(childDiv, 'input', null, {'type': 'radio'}); + var span = this.createChildElement(childDiv, 'span', null, { 'class': 'iwc-account-radio-button-label'}, null, this._strings.anonymousRadioLabel); + Element.observe(this._anonymousRadio, 'click', this._onClickAnonymousRadio.bindAsEventListener(this)); + Element.observe(span, 'click', this._onClickAnonymousRadio.bindAsEventListener(this)); + } + + return div; + }, + + _onClickAuthenticatedRadio : function() + { + // if this form doesn't support both authentication types, these radio buttons won't be here + if(this._authenticatedRadio && this._anonymousRadio) + { + this._authenticatedRadio.checked = true; + this._anonymousRadio.checked = false; + + Element.show(this._authenticatedCredentialsDiv); + Element.hide(this._anonymousCredentialsDiv); + } + + this._alignTags("label"); + this._alignSubmitButton(); + }, + + _onClickAnonymousRadio : function() + { + // if this form doesn't support both authentication types, these radio buttons won't be here + if(this._authenticatedRadio && this._anonymousRadio) + { + this._authenticatedRadio.checked = false; + this._anonymousRadio.checked = true; + + Element.hide(this._authenticatedCredentialsDiv); + Element.show(this._anonymousCredentialsDiv); + + try + { + this._anonymousIdentifierTextBox.focus(); + } catch (e) + { + } + } + + this._alignTags("label"); + this._alignSubmitButton(); + }, + + _onClickCreateAccount : function() + { + this._registerFormContainer.showRegisterForm(); + }, + + _shouldAddCreateAccountLink : function() + { + var tabVisibility = webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.TabVisibility); + if (tabVisibility.hideRegisterNewAccountTab()) + { + return false; + } + + return webservices.CapabilityRepository.isTrackerRegistrationCapabilityEnabled(); + + }, + + _onClickCreateAccount : function() + { + this._registerFormContainer.showRegisterForm(); + }, + + _shouldAddCreateAccountLink : function() + { + var tabVisibility = webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.TabVisibility); + if (tabVisibility.hideRegisterNewAccountTab()) + { + return false; + } + + return webservices.CapabilityRepository.isTrackerRegistrationCapabilityEnabled(); + }, + + _buildAuthenticatedCredentialsPanel : function() + { + var maximumFieldLengths = webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.MaximumFieldLengths); + var div = this.createElement('div', null, { 'class': 'iwc-account-indented-panel'}); + + // authenticatedIdentifier (e.g. username) div + var authenticatedIdentifierDiv = this.createChildElement(div, 'div', null, {'class': 'iwc-form-field-div'}); + this.createChildElement(authenticatedIdentifierDiv, 'label', null, { 'class': 'iwc-label'}, null, this._strings.authenticatedIdentifierLabel); + this._authenticatedIdentifierTextBox = this.createChildElement(authenticatedIdentifierDiv, 'input', null, { 'type': 'text', 'class': 'iwc-textbox', 'maxlength': maximumFieldLengths.get_nameMaximumLength()}); + this._addErrorDiv(authenticatedIdentifierDiv); + + // authenticated credentials (e.g. password) div + var authenticatedCredentialsDiv = this.createChildElement(div, 'div', null, {'class': 'iwc-form-field-div'}); + this.createChildElement(authenticatedCredentialsDiv, 'label', null, { 'class': 'iwc-label'}, null, this._strings.authenticatedCredentialsLabel); + this._authenticatedCredentialsTextBox = this.createChildElement(authenticatedCredentialsDiv, 'input', null, { 'type': 'password', 'class': 'iwc-textbox', 'maxlength': maximumFieldLengths.get_passwordMaximumLength()}); + this._addErrorDiv(authenticatedCredentialsDiv); + + return div; + }, + + _buildAnonymousCredentialsPanel : function() + { + var maximumFieldLengths = webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.MaximumFieldLengths); + var div = this.createElement('div', null, { 'class': 'iwc-account-indented-panel'}); + + // anonymous identifier (e.g. name) div + var anonymousIdentifierDiv = this.createChildElement(div, 'div', null, {'class': 'iwc-form-field-div'}); + this.createChildElement(anonymousIdentifierDiv, 'label', null, { 'class': 'iwc-label' }, null, this._strings.anonymousIdentifierLabel); + this._anonymousIdentifierTextBox = this.createChildElement(anonymousIdentifierDiv, 'input', null, { 'type': 'text', 'class': 'iwc-textbox', 'maxlength': maximumFieldLengths.get_nameMaximumLength()}); + + if (this._strings.anonymousIdentifierQualifierLabel) + { + this.createChildElement(anonymousIdentifierDiv, 'span', null, { 'class': 'iwc-label iwc-optional-label' }, null, this._strings.anonymousIdentifierQualifierLabel); + } + + this._addErrorDiv(anonymousIdentifierDiv); + + return div; + }, + + _buildExtraFormFields : function() + { + // overload this in a derived class if you need to add extra form fields + return this.createElement('div'); + }, + + _isAuthenticatedRadioClicked : function() + { + return this._authenticatedRadio && this._authenticatedRadio.checked; + }, + + _alignSubmitButton : function() + { + var target = this.getSubmitButtonAlignmentTarget(); + + if (localization.TextDirection == "rtl") + { + this._buttonPanel.style.left = this._getOffsetRight(target) + "px"; + } else + { + this._buttonPanel.style.width = this._getOffsetRight(target) + "px"; + } + } +}); + +/** + * CallbackLoginFormPanel class + * Implements the panel in which the user types their name, phone number, subject of their callback request, etc. + */ +ui.CallbackLoginFormPanel = Class.create(ui.LoginFormPanelBase, +{ + /** + * constructor + * + * @param callbackManager An instance of a subclass of webservices.CallbackManagerBase + * @param statusManager An instance of a class which implements ui.Interfaces.IStatusManager + * @param registerFormContainer The Panel that contains the registration form. Must have + * a showRegisterForm() method. + * @param callbackParameters An instance of CallbackParameters + * @param requiredFields Optional parameter. An array of FormFieldTypes, indicating which fields are required on this form. + */ + initialize : function($super, callbackManager, statusManager, registerFormContainer, callbackParameters, requiredFields) + { + this._callbackManager = callbackManager; + this._callbackParameters = callbackParameters; + + this._maxSubjectLen = webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.MaximumFieldLengths).get_subjectMaximumLength(); + + var allowedAccessTypes = this.AUTHENTICATION_NONE; + if (webservices.CapabilityRepository.isCallbackTrackerAuthenticationCapabilityEnabled()) + { + allowedAccessTypes |= this.AUTHENTICATION_TRACKER; + } + if (webservices.CapabilityRepository.isCallbackAnonymousAuthenticationCapabilityEnabled()) + { + allowedAccessTypes |= this.AUTHENTICATION_ANONYMOUS; + } + $super(statusManager, registerFormContainer, localization.StartCallbackButton, 'iwc-callback-form-panel', allowedAccessTypes, requiredFields); + + this.addImplementedInterface(webservices.Interfaces.ICallbackCreationFailureNotification, webservices); + webservices.NotificationRegistry.registerObserver(this, webservices.Interfaces.ICallbackCreationFailureNotification); + }, + + /** + * destructor + */ + destroy : function() + { + this._callbackManager = null; + + ui.LoginFormPanelBase.prototype.destroy.call(this); + }, + + // methods + + /** + * Clears the fields of the login form. + */ + reset : function() + { + this._onClickAuthenticatedRadio(); + this._clearTextboxIfAvailable(this._anonymousIdentifierTextBox); + this._clearTextboxIfAvailable(this._authenticatedIdentifierTextBox); + this._clearTextboxIfAvailable(this._authenticatedCredentialsTextBox); + this._clearTextboxIfAvailable(this._subjectTextBox); + this._clearTextboxIfAvailable(this._telephoneTextBox); + this._statusManager.clearBusy(); + }, + + /** + * Gets a list of fields which the user is required to fill in. + * + * @return An array of FormFieldTypes + */ + getRequiredFields : function() + { + return [ui.FormFieldTypes.Username, ui.FormFieldTypes.Password, + ui.FormFieldTypes.Telephone, ui.FormFieldTypes.Subject]; + }, + + /** + * Respond to notification that an attempt to create a Callback has failed. + * + * @param notification Contains an error indicating the reason for the failure. + */ + processCallbackCreationFailureNotification : function(notification) + { + common.Interface.ensureImplements(notification, webservices.Interfaces.ICallbackCreationFailureNotification); + + var error = notification.get_error(); + var text = ui.ErrorDisplayTextBuilder.build(error, localization.CallbackFailed); + this._statusManager.setErrorStatus(text); + this._statusManager.clearBusy(); + }, + + // private methods + + _buildInnerPanel : function() + { + var div = this.createElement('div'); + + // container div + this._containerDiv = this.createChildElement(div, 'div'); + this._containerDiv.appendChild(ui.LoginFormPanelBase.prototype._buildInnerPanel.call(this)); + + return div; + }, + + _buildExtraFormFields : function() + { + // overload this in a derived class if you need to add extra form fields + return this._buildCallbackDetailsPanel(); + }, + + _buildCallbackDetailsPanel : function() + { + var div = this.createElement('div', null, { 'class': 'iwc-account-indented-panel'}); + + // telephone div + var telephoneDiv = this.createChildElement(div, 'div', null, {'class': 'iwc-form-field-div'}); + this.createChildElement(telephoneDiv, 'label', null, { 'class': 'iwc-label'}, null, localization.TelephoneLabel); + var maximumFieldLengths = webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.MaximumFieldLengths); + this._telephoneTextBox = this.createChildElement(telephoneDiv, 'input', null, { 'type': 'text', 'class': 'iwc-textbox', 'maxlength': maximumFieldLengths.get_telephoneMaximumLength()}); + this._addErrorDiv(telephoneDiv); + + // subject div + var subjectDiv = this.createChildElement(div, 'div', null, {'class': 'iwc-form-field-div'}); + this.createChildElement(subjectDiv, 'label', null, { 'class': 'iwc-label iwc-description-label'}, null, localization.DescriptionLabel); + this._subjectTextBox = this.createChildElement(subjectDiv, 'textarea', null, { 'class': 'iwc-description-textarea'}); + Element.observe(this._subjectTextBox, 'keyup', this._onKeyUpTextArea.bindAsEventListener(this)); + this._addErrorDiv(subjectDiv); + + this.setSubmitButtonAlignmentTarget(this._subjectTextBox); // Make the submit button align with the textarea + + return div; + }, + + _onClickSubmitButton : function() + { + this._statusManager.clearStatus(); + + var additionalErrorTextArray = []; + + var name = this._getValueIfAvailable(this._anonymousIdentifierTextBox); + var username = this._getValueIfAvailable(this._authenticatedIdentifierTextBox); + var password = this._getValueIfAvailable(this._authenticatedCredentialsTextBox); + var subject = this._getValueIfAvailable(this._subjectTextBox); + var telephone = this._getValueIfAvailable(this._telephoneTextBox); + + var numErrors = 0; + if(this._isAuthenticatedRadioClicked()) + { + numErrors += this._validateField(username, this._authenticatedIdentifierTextBox, ui.FormFieldTypes.Username); + numErrors += this._validateField(password, this._authenticatedCredentialsTextBox, ui.FormFieldTypes.Password); + } + + numErrors += this._validateField(subject, this._subjectTextBox, ui.FormFieldTypes.Subject); + numErrors += this._validateField(telephone, this._telephoneTextBox, ui.FormFieldTypes.Telephone); + + // if errors are present, show the status and return + if(numErrors > 0) + { + var errorText; + + if(numErrors == 1) + { + errorText = localization.OneErrorWithCallbackData; + } + else + { + errorText = localization.MultipleErrorsWithCallbackData.replace('%0', numErrors); + } + + for(var i = 0; i < additionalErrorTextArray.length; ++i) + { + errorText += '
    '; + errorText += additionalErrorTextArray[i]; + } + + this._statusManager.setErrorStatus(errorText); + } + else + { + this._callbackParameters.set_subject(subject); + this._callbackParameters.set_telephone(telephone); + + if(this._isAuthenticatedRadioClicked()) + { + this._callbackParameters.set_participantName(username); + this._callbackParameters.set_participantCredentials(password); + this._createCallback(); + } + else + { + if(!name) + { + name = localization.AnonymousUser; + } + + this._callbackParameters.set_participantName(name); + this._callbackParameters.set_participantCredentials(null); + this._createCallback(); + } + } + }, + + _createCallback : function() + { + try + { + this._statusManager.setBusy(); + this._callbackManager.createCallback(this._callbackParameters); + } + catch(ex) + { + this._statusManager.clearBusy(); + common.Debug.traceError("Caught unhandled exception:\n" + ex); + common.Debug.alert("Caught unhandled exception:\n" + ex); + webservices.ProblemReporter.sendProblemReport(ex, "CallbackLoginFormPanel._createCallback()"); + } + }, + + /** + * Key handler. Enforces maximum length, since the HTML textarea tag has no "maxlength" attribute. + */ + _onKeyUpTextArea : function(evt) + { + // This method will be called for special keys in Firefox, but not in IE. Just ignore them. + if (evt.keyCode == 37 || // Left arrow + evt.keyCode == 38 || // Up arrow + evt.keyCode == 39 || // Right arrow + evt.keyCode == 40 || // Down arrow + evt.keyCode == 8 || // Backspace + evt.keyCode == 93 || // Right-click menu key + evt.keyCode == 91 || // Window key + evt.keyCode == 45 || // Insert + evt.keyCode == 46 || // Delete + evt.keyCode == 35 || // End + evt.keyCode == 36 || // Home + evt.keyCode == 33 || // PgUp + evt.keyCode == 34 || // PgDn + evt.keyCode == 116 || // Ctrl+F5 (reload) + evt.keyCode == 192) // Alt, when used in combination with another key. For instance, + // if Alt-LeftArrow is pressed to go back, this method will be + // called twice, once with 192 and then again with 37. If Alt is + // pressed by itself, this method will not be called. + { + return; + } + + var len = this._subjectTextBox.value.length; + + if (len >= this._maxSubjectLen) + { + evt.stop(); + + // Handle the case in which the user just pasted a large block of text + this._subjectTextBox.value = this._subjectTextBox.value.substr(0, this._maxSubjectLen); + } + }, + + _onClickAuthenticatedRadio : function() + { + ui.LoginFormPanelBase.prototype._onClickAuthenticatedRadio.call(this); + }, + + + _onClickAnonymousRadio : function() + { + ui.LoginFormPanelBase.prototype._onClickAnonymousRadio.call(this); + } +}); + +/** + * CallbackCreationSuccessPanel class + * Implements the panel in which the user is informed that a Callback was created successfully. + */ +ui.CallbackCreationSuccessPanel = Class.create(ui.Control, +{ + /** + * constructor + */ + initialize : function($super) + { + this._formPanelClass = 'iwc-callback-creation-success-panel'; + + var domObject = this._buildDomObject(); + this._validateDomObject(); + + $super(domObject); + }, + + /** + * destructor + */ + destroy : function() + { + }, + + // methods + + /** + * Shows the panel + */ + show : function() + { + Element.show(this._domObject); + }, + + /** + * Hides the panel + */ + hide : function() + { + Element.hide(this._domObject); + }, + + // private methods + + _buildDomObject : function() + { + var div = this.createElement('div', null, {'class': this._formPanelClass}, {'display': 'none'}); + this.createChildElement(div, 'img', null, {'src':'img/check.png'}); + this._messageSpan = this.createChildElement(div, 'span', null, {'class': 'iwc-label'}, null, localization.CallbackSucceeded); + return div; + }, + + _validateDomObject : function() + { + if(!this._messageSpan) + { + throw common.ExceptionFactory.createException("CallbackCreationSuccessPanel not built properly!"); + } + } +}); + +/** + * CallbackStatusPanel class + * Implements the panel in which the user manages previously-created Callbacks. + * This is an abstract class. @see WebServicesCallbackStatusPanel. + */ +ui.CallbackStatusPanel = Class.create(ui.Control, +{ + /** + * constructor + * + * @param statusManager An instance of a class which implements ui.Interfaces.IStatusManager + */ + initialize : function($super, statusManager) + { + this._statusManager = statusManager; + this._cssClass = 'iwc-callback-status-panel'; + + var domObject = this._buildDomObject(); + this._validateDomObject(); + + $super(domObject); + + if (this._isStatusSupported()) + { + this._statusFieldsDisplay = webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.StatusFieldsDisplay); + } + }, + + /** + * destructor + */ + destroy : function() + { + this._statusFieldsDisplay = null; + }, + + // methods + + /** + * What to do when the panel gets focus. + * Currently does nothing, since there are no textboxes, radio buttons, etc. + */ + focus : function() + { + // Nothing to do + }, + + /** + * Resets the panel to its initial appearance. + */ + reset : function() + { + this._callbackCreationSuccessPanel.hide(); + this.showDisconnectButtonPanel(); + this.hideAgentPhoto(); + this.hideCallbackParameters(); + this.hideStatus(); + this._statusManager.clearBusy(); + }, + + /** + * Show the panel indicating that a callback has been created successfully + */ + showCallbackCreationSuccessPanel : function() + { + this._callbackCreationSuccessPanel.show(); + }, + + /** + * Show the panel containing the disconnect button (and hide the disconnect failure panel) + */ + showDisconnectButtonPanel : function() + { + if (this._isDisconnectSupported()) + { + Element.hide(this._disconnectFailurePanel); + Element.show(this._disconnectButtonPanel); + } + }, + + /** + * Show the disconnect failure panel. + * The disconnect button panel is not hidden, so that the user may re-attempt the disconnect. + */ + showDisconnectFailurePanel : function() + { + if (this._isDisconnectSupported()) + { + this.setStatusIndicator(null); + Element.hide(this._statusFailurePanel); + Element.show(this._disconnectFailurePanel); + this._statusManager.clearBusy(); + } + }, + + /** + * Abstract method, implemented in subclass. + * Does the work of disconnecting the callback. + */ + disconnect : function() + { + throw common.ExceptionFactory.createException("CallbackStatusPanel.disconnect(): Abstract method not overridden by child class!"); + }, + + /** + * Sets the displayed participant name, telephone number, subject, and creation date/time of the callback. + * + * @param participantName The name or username of the web user who has created the callback request + * @param telephone The telephone number at which the participant indicated they would like to be called + * @param subject The subject of the callback (which the web user entered in the CallbackLoginPanel) + * @param creationDateTime A Javascript Date object containing the timestamp of when the callback request was created + */ + showCallbackParameters : function(participantName, telephone, subject, creationDateTime) + { + if (!this._isStatusSupported()) + { + return; + } + + if (this._statusFieldsDisplay.get_showSubject()) + { + this._subjectSpan.innerHTML = subject; + Element.show(this._subjectSpan); + } + else + { + Element.hide(this._subjectSpan); + } + + var showingAtLeastOneField = false; // (Excluding the subject) + + if (this._statusFieldsDisplay.get_showName()) + { + this._participantNameField.innerHTML = participantName; + showingAtLeastOneField = true; + } + + if (this._statusFieldsDisplay.get_showTelephone()) + { + this._telephoneNumberField.innerHTML = telephone; + showingAtLeastOneField = true; + } + + if (this._statusFieldsDisplay.get_showCreationDateTime()) + { + this._creationDateTimeField.innerHTML = creationDateTime.toLocaleString(); + showingAtLeastOneField = true; + } + + if (showingAtLeastOneField) + { + Element.show(this._parameterFieldsContainerDiv); + } + }, + + /** + * Hides the displayed subject of the callback + */ + hideCallbackParameters : function() + { + if (this._isStatusSupported()) + { + Element.hide(this._parameterFieldsContainerDiv); + Element.hide(this._subjectSpan); + } + }, + + /** + * Sets the status indicator (quick visual reference of what state the callback is in). + * + * @param status TODO + */ + setStatusIndicator : function(statusKey) + { + if (!this._isStatusSupported()) + { + return; + } + + if (statusKey) + { + try + { + var status = localization[statusKey]; + this._statusIndicator.innerHTML = status; + Element.show(this._statusIndicator); + } catch (e) + { + common.Debug.traceWarning('Received status indicator "' + statusKey + '" is not a resource file key: ' + e); + } + } + else + { + Element.hide(this._statusIndicator); + } + }, + + /** + * Sets the status fields and makes the panel visible + * @params A Javascript object containing any or all of the following fields: + * assignedAgentName, interactionState, estimatedCallbackTime (specified in seconds after now), + * queueWaitTime (in seconds), queuePosition, queueName, longestWaitTime (in seconds), + * interactionsWaitingCount, loggedInAgentsCount, availableAgentsCount + */ + showStatus : function(params) + { + if (!this._isStatusSupported()) + { + return; + } + + Element.hide(this._statusFailurePanel); + + if (this._statusFieldsDisplay.get_showAssignedAgentName() && params.assignedAgentName) + { + this._assignedAgentField.innerHTML = params.assignedAgentName; + Element.show(this._assignedAgentDiv); + } + else + { + Element.hide(this._assignedAgentDiv); + } + + if (this._statusFieldsDisplay.get_showInteractionState() && params.interactionState && !(params.interactionState.match(/\%\d+\%/))) + { + this._callbackStateField.innerHTML = params.interactionState; + Element.show(this._callbackStateDiv); + } + else + { + Element.hide(this._callbackStateDiv); + } + + if (this._statusFieldsDisplay.get_showEstimatedCallbackTime() && params.estimatedCallbackTime && params.estimatedCallbackTime > 0) + { + this._estimatedCallbackTimeField.innerHTML = this._statusFieldsDisplay.formatTimeDuration("TimeDuration", params.estimatedCallbackTime); + Element.show(this._estimatedCallbackTimeDiv); + } + else + { + Element.hide(this._estimatedCallbackTimeDiv); + } + + if (this._statusFieldsDisplay.get_showQueueWaitTime() && params.queueWaitTime && params.queueName && params.queueWaitTime > 0) + { + this._queueWaitTimeLabel.innerHTML = localization.WaitTimeLabel; + this._queueWaitTimeField.innerHTML = this._statusFieldsDisplay.formatTimeDuration("TimeDuration", params.queueWaitTime); + Element.show(this._queueWaitTimeDiv); + } + else + { + Element.hide(this._queueWaitTimeDiv); + } + + if (this._statusFieldsDisplay.get_showQueuePosition() && params.queuePosition && params.queueName && params.queuePosition > 0) + { + this._queuePositionLabel.innerHTML = localization.QueuePositionLabel.replace('%0', params.queueName); + this._queuePositionField.innerHTML = params.queuePosition; + Element.show(this._queuePositionDiv); + } + else + { + Element.hide(this._queuePositionDiv); + } + + if (this._statusFieldsDisplay.get_showLongestWaitTime() && params.longestWaitTime && params.longestWaitTime > 0) + { + this._longestWaitTimeField.innerHTML = this._statusFieldsDisplay.formatTimeDuration("TimeDuration", params.longestWaitTime); + Element.show(this._longestWaitTimeDiv); + } + else + { + Element.hide(this._longestWaitTimeDiv); + } + + if (this._statusFieldsDisplay.get_showInteractionsWaitingCount() && params.interactionsWaitingCount && params.interactionsWaitingCount >= 0) + { + this._interactionsWaitingCountField.innerHTML = params.interactionsWaitingCount; + Element.show(this._interactionsWaitingCountDiv); + } + else + { + Element.hide(this._interactionsWaitingCountDiv); + } + + if (this._statusFieldsDisplay.get_showLoggedInAgentsCount() && params.loggedInAgentsCount && params.loggedInAgentsCount >= 0) + { + this._loggedInAgentsCountField.innerHTML = params.loggedInAgentsCount; + Element.show(this._loggedInAgentsCountDiv); + } + else + { + Element.hide(this._loggedInAgentsCountDiv); + } + + if (this._statusFieldsDisplay.get_showAvailableAgentsCount() && params.availableAgentsCount && params.availableAgentsCount >= 0) + { + this._availableAgentsCountField.innerHTML = params.availableAgentsCount; + Element.show(this._availableAgentsCountDiv); + } + else + { + Element.hide(this._availableAgentsCountDiv); + } + + Element.show(this._statusFieldsContainerDiv); + }, + + /** + * Hides the status fields of the callback + */ + hideStatus : function() + { + if (this._isStatusSupported()) + { + Element.hide(this._statusFieldsContainerDiv); + } + }, + + /** + * Shows the failure message + */ + showStatusFailure : function() + { + if (this._isStatusSupported()) + { + Element.hide(this._statusFieldsContainerDiv); + this.setStatusIndicator(null); + this.hideAgentPhoto(); + Element.show(this._statusFailurePanel); + Element.show(this._domObject); + } + }, + + /** + * Displays the photo at the supplied URL as the agent photo. + * + * @param url The URL of the agent's photo + */ + showAgentPhoto : function(url) + { + if (this._isStatusSupported()) + { + this._assignedAgentPhoto.src = "/" + webservices.Servers.CurrentUriFragment + url; + Element.show(this._assignedAgentPhoto); + } + }, + + /** + * Hides the agent's photo + */ + hideAgentPhoto : function() + { + if (this._isStatusSupported()) + { + Element.hide(this._assignedAgentPhoto); + this._assignedAgentPhoto.src = ""; + } + }, + + /** + * Abstract method, implemented in subclass. + * Does the work of querying the status. + */ + queryStatus : function() + { + throw common.ExceptionFactory.createException("CallbackStatusPanel.queryStatus(): Abstract method not overridden by child class!"); + }, + + // private methods + + _buildDomObject : function() + { + // Root panel + this._panelContainerDiv = this.createElement('div', null, {'class': 'containsFloatingChild ' + this._cssClass}, { 'display': 'none' }); + + this._addCallbackCreationSuccessPanel(); + + this._addStatusIndicatorAndButtonContainerDiv(); + + this._addAssignedAgentPhotoDiv(); + + this._addStatusAndFailureContainerDiv(); + + return this._panelContainerDiv; + }, + + _addCallbackCreationSuccessPanel : function() + { + panel = new ui.CallbackCreationSuccessPanel(); + this._panelContainerDiv.appendChild(panel.get_domObject()); + this._callbackCreationSuccessPanel = panel; + }, + + _addStatusIndicatorAndButtonContainerDiv : function() + { + if (this._isStatusSupported() || this._isDisconnectSupported()) + { + this._statusIndicatorAndButtonContainerDiv = this.createChildElement(this._panelContainerDiv, 'div', null, {'class': 'iwc-callback-status-indicator-and-button-container'}); + this._addStatusIndicator(); + this._addDisconnectButtonPanel(); + } + }, + + _addStatusIndicator : function() + { + if (this._isStatusSupported()) + { + this._statusIndicator = this.createChildElement(this._statusIndicatorAndButtonContainerDiv, 'div', null, {'class': 'iwc-callback-status-indicator'}, { 'display': 'none'}); + } + }, + + _addDisconnectButtonPanel : function() + { + if (this._isDisconnectSupported()) + { + // The panel containing the button to initiate the disconnect + this._disconnectButtonPanel = this.createChildElement(this._statusIndicatorAndButtonContainerDiv, 'div', null, {'class': 'iwc-callback-disconnect-button-panel'}, {'display': 'none'}); + var disconnectButton = this.createChildElement(this._disconnectButtonPanel, 'input', null, { 'type': 'button', 'class': 'iwc-callback-disconnect-button', 'value': localization.DisconnectCallback }); + Element.observe(disconnectButton, 'click', this._onClickDisconnectButton.bindAsEventListener(this)); + } + }, + + _addAssignedAgentPhotoDiv : function() + { + if (this._isStatusSupported()) + { + this._assignedAgentPhoto = this.createChildElement(this._panelContainerDiv, 'img', null, { 'class': 'iwc-callback-participant-avatar'}, {'display': 'none'}); + } + }, + + _addStatusAndFailureContainerDiv : function() + { + if (this._isStatusSupported() || this._isDisconnectSupported()) + { + this._statusAndFailureContainerDiv = this.createChildElement(this._panelContainerDiv, 'div', null, {'class': 'iwc-callback-status-and-failure-container'}); + } + this._addStatusPanel(); + this._addFailureContainerDiv(); + }, + + _addStatusPanel : function() + { + if (!this._isStatusSupported()) + { + return; + } + + this._statusPanel = this.createChildElement(this._statusAndFailureContainerDiv, 'div', null, {'class': 'iwc-callback-status'}); + + // Subject + var subjectDiv = this.createChildElement(this._statusPanel, 'div', null, {'class': 'iwc-callback-status-subject-div'}); + this._subjectSpan = this.createChildElement(subjectDiv, 'span', null, { 'class': 'iwc-callback-status-subject' }); + + // Container for fields that the web user entered him/herself + this._parameterFieldsContainerDiv = this.createChildElement(this._statusPanel, 'div', null, {'class': 'iwc-callback-status-fields-container-div'}, {'display': 'none'}); + + // Web user's name or username + this._participantNameDiv = this.createChildElement(this._parameterFieldsContainerDiv, 'div', null, {'class': 'iwc-form-field-div'}); + this.createChildElement(this._participantNameDiv, 'label', null, { 'class': 'iwc-key'}, null, localization.CallbackCreatorNameLabel); + this._participantNameField = this.createChildElement(this._participantNameDiv, 'label', null, { 'class': 'iwc-label'}); + + // Creation date/time + this._creationDateTimeDiv = this.createChildElement(this._parameterFieldsContainerDiv, 'div', null, {'class': 'iwc-form-field-div'}); + this.createChildElement(this._creationDateTimeDiv, 'label', null, { 'class': 'iwc-key'}, null, localization.CallbackCreationDateTimeLabel); + this._creationDateTimeField = this.createChildElement(this._creationDateTimeDiv, 'label', null, { 'class': 'iwc-label'}); + + // Telephone number + this._telephoneNumberDiv = this.createChildElement(this._parameterFieldsContainerDiv, 'div', null, {'class': 'iwc-form-field-div'}); + this.createChildElement(this._telephoneNumberDiv, 'label', null, { 'class': 'iwc-key'}, null, localization.TelephoneLabel); + this._telephoneNumberField = this.createChildElement(this._telephoneNumberDiv, 'label', null, { 'class': 'iwc-label'}); + + // Container for status fields obtained from IC + this._statusFieldsContainerDiv = this.createChildElement(this._statusPanel, 'div', null, {'class': 'iwc-callback-status-fields-container-div'}, {'display': 'none'}); + + // Assigned agent + this._assignedAgentDiv = this.createChildElement(this._statusFieldsContainerDiv, 'div', null, {'class': 'iwc-form-field-div'}); + this.createChildElement(this._assignedAgentDiv, 'label', null, { 'class': 'iwc-key'}, null, localization.AssignedAgentLabel); + this._assignedAgentField = this.createChildElement(this._assignedAgentDiv, 'label', null, { 'class': 'iwc-label'}); + + // Interaction state + this._callbackStateDiv = this.createChildElement(this._statusFieldsContainerDiv, 'div', null, {'class': 'iwc-form-field-div'}); + this.createChildElement(this._callbackStateDiv, 'label', null, { 'class': 'iwc-key'}, null, localization.CallbackStateLabel); + this._callbackStateField = this.createChildElement(this._callbackStateDiv, 'label', null, { 'class': 'iwc-label'}); + + // Queue wait time + this._queueWaitTimeDiv = this.createChildElement(this._statusFieldsContainerDiv, 'div', null, {'class': 'iwc-form-field-div'}); + this._queueWaitTimeLabel = this.createChildElement(this._queueWaitTimeDiv, 'label', null, { 'class': 'iwc-key'}); + this._queueWaitTimeField = this.createChildElement(this._queueWaitTimeDiv, 'label', null, { 'class': 'iwc-label'}); + + // Estimated callback time + this._estimatedCallbackTimeDiv = this.createChildElement(this._statusFieldsContainerDiv, 'div', null, {'class': 'iwc-form-field-div'}); + this.createChildElement(this._estimatedCallbackTimeDiv, 'label', null, { 'class': 'iwc-key'}, null, localization.EstimatedCallbackTimeLabel); + this._estimatedCallbackTimeField = this.createChildElement(this._estimatedCallbackTimeDiv, 'label', null, { 'class': 'iwc-label'}); + + // Queue position + this._queuePositionDiv = this.createChildElement(this._statusFieldsContainerDiv, 'div', null, {'class': 'iwc-form-field-div'}); + this._queuePositionLabel = this.createChildElement(this._queuePositionDiv, 'label', null, { 'class': 'iwc-key'}); + this._queuePositionField = this.createChildElement(this._queuePositionDiv, 'label', null, { 'class': 'iwc-label'}); + + // Longest wait time + this._longestWaitTimeDiv = this.createChildElement(this._statusFieldsContainerDiv, 'div', null, {'class': 'iwc-form-field-div'}); + this._longestWaitTimeLabel = this.createChildElement(this._longestWaitTimeDiv, 'label', null, { 'class': 'iwc-key'}, null, localization.LongestWaitTimeLabel); + this._longestWaitTimeField = this.createChildElement(this._longestWaitTimeDiv, 'label', null, { 'class': 'iwc-label'}); + + // Calls waiting count + this._interactionsWaitingCountDiv = this.createChildElement(this._statusFieldsContainerDiv, 'div', null, {'class': 'iwc-form-field-div'}); + this._interactionsWaitingCountLabel = this.createChildElement(this._interactionsWaitingCountDiv, 'label', null, { 'class': 'iwc-key'}, null, localization.InteractionsWaitingCountLabel); + this._interactionsWaitingCountField = this.createChildElement(this._interactionsWaitingCountDiv, 'label', null, { 'class': 'iwc-label'}); + + // Logged in agents count + this._loggedInAgentsCountDiv = this.createChildElement(this._statusFieldsContainerDiv, 'div', null, {'class': 'iwc-form-field-div'}); + this._loggedInAgentsCountLabel = this.createChildElement(this._loggedInAgentsCountDiv, 'label', null, { 'class': 'iwc-key'}, null, localization.LoggedInAgentsCountLabel); + this._loggedInAgentsCountField = this.createChildElement(this._loggedInAgentsCountDiv, 'label', null, { 'class': 'iwc-label'}); + + // Available agents count + this._availableAgentsCountDiv = this.createChildElement(this._statusFieldsContainerDiv, 'div', null, {'class': 'iwc-form-field-div'}); + this._availableAgentsCountLabel = this.createChildElement(this._availableAgentsCountDiv, 'label', null, { 'class': 'iwc-key'}, null, localization.AvailableAgentsCountLabel); + this._availableAgentsCountField = this.createChildElement(this._availableAgentsCountDiv, 'label', null, { 'class': 'iwc-label'}); + }, + + _addFailureContainerDiv : function() + { + if (this._isStatusSupported() || this._isDisconnectSupported()) + { + this._failureContainerDiv = this.createChildElement(this._statusAndFailureContainerDiv, 'div', null, {'class': 'iwc-callback-status-failure-container'}); + } + this._addStatusFailurePanel(); + this._addDisconnectFailurePanel(); + }, + + _addStatusFailurePanel : function() + { + if (this._isStatusSupported()) + { + this._statusFailurePanel = this.createChildElement(this._failureContainerDiv, 'div', null, {'class': 'iwc-callback-status-failure'}, {'display': 'none'}); + this.createChildElement(this._statusFailurePanel, 'img', null, {'src':'img/error.png'}); + this.createChildElement(this._statusFailurePanel, 'span', null, {'class': 'iwc-label'}, null, localization.CallbackStatusFailed); + } + }, + + _addDisconnectFailurePanel : function() + { + if (this._isDisconnectSupported()) + { + // The panel that informs the user that the disconnect has failed + this._disconnectFailurePanel = this.createChildElement(this._failureContainerDiv, 'div', null, {'class': 'iwc-callback-disconnect-failure-panel'}, {'display': 'none'}); + this.createChildElement(this._disconnectFailurePanel, 'img', null, {'src':'img/error.png'}); + this.createChildElement(this._disconnectFailurePanel, 'span', null, {'class': 'iwc-label'}, null, localization.DisconnectCallbackFailed); + } + }, + + _validateDomObject : function() + { + if((this._isDisconnectSupported() && !this._disconnectFailurePanel) || + (this._isStatusSupported() && !this._statusFailurePanel) || + (!this._callbackCreationSuccessPanel)) + { + throw common.ExceptionFactory.createException("CallbackStatusPanel not built properly!"); + } + }, + + _isStatusSupported : function() + { + return webservices.CapabilityRepository.isCallbackStatusCapabilityEnabled(); + }, + + _isDisconnectSupported : function() + { + return webservices.CapabilityRepository.isDisconnectCallbackCapabilityEnabled(); + }, + + _isReconnectSupported : function() + { + return webservices.CapabilityRepository.isReconnectCallbackCapabilityEnabled(); + }, + + _onClickDisconnectButton : function() + { + this._statusManager.setBusy(); + this.disconnect(); // Abstract method + } +}); + +/** + * WebServicesCallbackStatusPanel class + * + * Handles the logic of the panel in which the web user may view the status of a callback. For the + * UI, @see CallbackStatusPanel. + */ +ui.WebServicesCallbackStatusPanel = Class.create(ui.CallbackStatusPanel, +{ + /** + * When a callback is created, this determines how + * long (in milliseconds) before its status is polled. + * After that, it is polled every SUBSEQUENT_POLL_INTERVAL milliseconds. + */ + INITIAL_POLL_INTERVAL : 1500, + + /** + * This determines how often (in milliseconds) a Callback's + * status should be polled. But, after one is initially created, + * the first poll is done after INITIAL_POLL_INTERVAL milliseconds. + */ + SUBSEQUENT_POLL_INTERVAL : 10000, + + m_initialPollTimerId : null, + + /** + * Constructor + * + * @param callbackManager An instance of a subclass of webservices.CallbackManagerBase + * @param statusManager An instance of a class which implements ui.Interfaces.IStatusManager + */ + initialize : function($super, callbackManager, statusManager) + { + this._callbackManager = callbackManager; + this._statusManager = statusManager; + this._statusPollTimer = null; + this._participantId = null; + + $super(statusManager); + + this.addImplementedInterface(webservices.Interfaces.ICallbackCreationNotification, webservices); + webservices.NotificationRegistry.registerObserver(this, webservices.Interfaces.ICallbackCreationNotification); + + if (this._isDisconnectSupported()) + { + this.addImplementedInterface(webservices.Interfaces.ICallbackDisconnectNotificationObserver, webservices); + this.addImplementedInterface(webservices.Interfaces.ICallbackDisconnectFailureNotificationObserver, webservices); + webservices.NotificationRegistry.registerObserver(this, webservices.Interfaces.ICallbackDisconnectNotification); + webservices.NotificationRegistry.registerObserver(this, webservices.Interfaces.ICallbackDisconnectFailureNotification); + } + + if (this._isReconnectSupported()) + { + this.addImplementedInterface(webservices.Interfaces.ICallbackReconnectNotification, webservices); + webservices.NotificationRegistry.registerObserver(this, webservices.Interfaces.ICallbackReconnectNotification); + } + + if (this._isStatusSupported()) + { + this.addImplementedInterface(webservices.Interfaces.ICallbackStatusNotificationObserver, webservices); + this.addImplementedInterface(webservices.Interfaces.ICallbackStatusFailureNotificationObserver, webservices); + this.addImplementedInterface(webservices.Interfaces.IPartyInfoNotificationObserver, webservices); + webservices.NotificationRegistry.registerObserver(this, webservices.Interfaces.ICallbackStatusNotification); + webservices.NotificationRegistry.registerObserver(this, webservices.Interfaces.ICallbackStatusFailureNotification); + webservices.NotificationRegistry.registerObserver(this, webservices.Interfaces.IPartyInfoNotification); + } + }, + + /** + * destructor + */ + destroy : function() + { + this._destroyTimer(); + + this._callbackManager = null; + }, + + // methods + + /** + * Disconnects the callback. + */ + disconnect : function() + { + if (!this._isDisconnectSupported()) + { + throw common.ExceptionFactory.createException("WebServicesCallbackStatusPanel.disconnect(): Disconnect capability not supported by server!"); + } + this._callbackManager.disconnect(this._participantId); + }, + + /** + * Queries the status of the callback. + */ + queryStatus : function() + { + if (!this._isStatusSupported()) + { + throw common.ExceptionFactory.createException("WebServicesCallbackStatusPanel.queryStatus(): QueryStatus capability not supported by server!"); + } + + common.Debug.traceMethodEntered("WebServicesCallbackStatusPanel.queryStatus()"); + try + { + this._callbackManager.queryStatus(this._participantId); + } + catch(e) + { + common.Debug.traceWarning("WebServicesCallbackStatusPanel._doInitialStatusQuery(): Caught exception: " + e); + } + common.Debug.traceMethodExited("WebServicesCallbackStatusPanel.queryStatus()"); + }, + + /** + * This method is called when the recurring status poll timer fires. + * Do not call it directly. + * The status poll timer only runs when there is a callback to poll. + */ + onStatusPollTimer : function() + { + common.Debug.traceMethodEntered("WebServicesCallbackStatusPanel.onStatusPollTimer()"); + this.queryStatus(); + common.Debug.traceMethodExited("WebServicesCallbackStatusPanel.onStatusPollTimer()"); + }, + + /** + * Respond to notification that a Callback was created successfully. + * + * @param callbackCreationNotification An implementation of ICallbackCreationNotification + */ + processCallbackCreationNotification : function(notification) + { + common.Debug.traceMethodEntered("WebServicesCallbackStatusPanel.processCallbackCreationNotification()"); + common.Interface.ensureImplements(notification, webservices.Interfaces.ICallbackCreationNotification); + + this._participantId = notification.get_participantId(); + // Wait a bit before polling its status, so that ACD can start to route it. + var _self = this; + m_initialPollTimerId = window.setTimeout(_self._doInitialStatusQuery.bind(_self), this.INITIAL_POLL_INTERVAL); + this.showCallbackCreationSuccessPanel(); + this.showDisconnectButtonPanel(); + var participantName = notification.get_participantName(); + var telephone = notification.get_telephone(); + var subject = notification.get_subject() || notification.get_callbackId(); + var creationDateTime = notification.get_creationDateTime(); + this.showCallbackParameters(participantName, telephone, subject, creationDateTime); + + common.Debug.traceMethodExited("WebServicesCallbackStatusPanel.processCallbackCreationNotification()"); + }, + + /** + * Respond to notification that a Callback has been disconnected + * @param notification An implementation of ICallbackDisconnectNotification + */ + processCallbackDisconnectNotification : function(notification) + { + common.Debug.traceMethodEntered("WebServicesCallbackStatusPanel.processCallbackDisconnectNotification()"); + common.Interface.ensureImplements(notification, webservices.Interfaces.ICallbackDisconnectNotification); + + if (m_initialPollTimerId) + { + // If the callback was canceled within INITIAL_POLL_INTERVAL milliseconds of creation, + // there is still a pending call to _doInitialStatusQuery() which must be stopped. + window.clearTimeout(m_initialPollTimerId); + } + + var participantId = notification.get_participantId(); + if (participantId == this._participantId) + { + this._destroyTimer(); + this.reset(); + this._participantId = null; + } + + common.Debug.traceMethodExited("WebServicesCallbackStatusPanel.processCallbackDisconnectNotification()"); + }, + + /** + * Respond to notification that a Callback was reconnected successfully. + * + * @param callbackReconnectNotification An implementation of ICallbackReconnectNotification + */ + processCallbackReconnectNotification : function(notification) + { + common.Debug.traceMethodEntered("WebServicesCallbackStatusPanel.processCallbackReconnectNotification()"); + common.Interface.ensureImplements(notification, webservices.Interfaces.ICallbackReconnectNotification); + + this._participantId = notification.get_participantId(); + common.Debug.traceNote("WebServicesCallbackStatusPanel.processCallbackReconnectNotification(): participant ID changed to: " + this._participantId); + m_initialPollTimerId = window.setTimeout(_self._doInitialStatusQuery.bind(_self), this.INITIAL_POLL_INTERVAL); + this.showCallbackCreationSuccessPanel(); + this.showDisconnectButtonPanel(); + + // TODO: Show participantName, telephone, subject, and creationDateTime? + + common.Debug.traceMethodExited("WebServicesCallbackStatusPanel.processCallbackReconnectNotification()"); + }, + + /** + * Respond to notification that an attempt to disconnect a Callback has failed + * @param notification CallbackDisconnectFailureNotification + */ + processCallbackDisconnectFailureNotification : function(notification) + { + common.Debug.traceMethodEntered("WebServicesCallbackStatusPanel.processCallbackDisconnectFailureNotification()"); + common.Interface.ensureImplements(notification, webservices.Interfaces.ICallbackDisconnectFailureNotification); + common.Debug.traceError("WebServicesCallbackDisconnectPanel.processCallbackDisconnectFailureNotification() received error: " + notification.get_error().get_errorCode()); + + this.showDisconnectFailurePanel(); + + common.Debug.traceMethodExited("WebServicesCallbackStatusPanel.processCallbackDisconnectFailureNotification()"); + }, + + /** + * Respond to notification that a Callback's status has been received + * @param notification CallbackStatusNotification + */ + processCallbackStatusNotification : function(notification) + { + common.Debug.traceMethodEntered("WebServicesCallbackStatusPanel.processCallbackStatusNotification()"); + common.Interface.ensureImplements(notification, webservices.Interfaces.ICallbackStatusNotification); + + var webVisitorParticipantId = notification.get_participantId(); + if (webVisitorParticipantId == this._participantId) + { + var params = new Object(); + params.assignedAgentName = notification.get_assignedAgentName(); + params.interactionState = notification.get_interactionState(); + params.estimatedCallbackTime = notification.get_estimatedCallbackTime(); + params.queueWaitTime = notification.get_queueWaitTime(); + params.queuePosition = notification.get_queuePosition(); + params.queueName = notification.get_queueName(); + params.longestWaitTime = notification.get_longestWaitTime(); + params.interactionsWaitingCount = notification.get_interactionsWaitingCount(); + params.loggedInAgentsCount = notification.get_loggedInAgentsCount(); + params.availableAgentsCount = notification.get_availableAgentsCount(); + var indicator = notification.get_statusIndicator(); + + this.showStatus(params); + this.setStatusIndicator(indicator); + this._alignTags('label', 'iwc-key'); + } + common.Debug.traceMethodExited("WebServicesCallbackStatusPanel.processCallbackStatusNotification()"); + }, + + /** + * Respond to notification that an attempt to get a Callback's status has failed + * @param notification CallbackStatusFailureNotification + */ + processCallbackStatusFailureNotification : function(notification) + { + common.Debug.traceMethodEntered("WebServicesCallbackStatusPanel.processCallbackStatusFailureNotification()"); + common.Interface.ensureImplements(notification, webservices.Interfaces.ICallbackStatusFailureNotification); + + common.Debug.traceError("WebServicesCallbackStatusPanel.processCallbackStatusFailureNotification() received error: " + notification.get_error().get_errorCode()); + + if (this._participantId) + { + this.showStatusFailure(); + } + // else there was a race condition between the status request and the callback being disconnected, so no need to show the status failure message. + + common.Debug.traceMethodExited("WebServicesCallbackStatusPanel.processCallbackStatusFailureNotification()"); + }, + + /** + * Respond to receipt of information (name, photo location) about a party involved in a Callback + * + * @param notification + */ + processPartyInfoNotification : function(notification) + { + common.Debug.traceMethodEntered("WebServicesCallbackStatusPanel.processPartyInfoNotification()"); + common.Interface.ensureImplements(notification, webservices.Interfaces.IPartyInfoNotification); + + if (notification.get_localParticipantId() == this._participantId) + { + var agentPhoto = notification.get_photo(); + if (agentPhoto) + { + this.showAgentPhoto(agentPhoto); + } + else + { + this.hideAgentPhoto(); + } + } + + common.Debug.traceMethodExited("WebServicesCallbackStatusPanel.processPartyInfoNotification()"); + }, + + /** + * Called when the parent container is shown. + * Resumes status polling, if appropriate + */ + parentShown : function() + { + common.Debug.traceMethodEntered("WebServicesCallbackStatusPanel.parentShown()"); + if (this._statusPollTimer && !this._statusPollTimer.isRunning()) + { + // The timer was stopped by a previous call to parentHidden() + this._doInitialStatusQuery(); + } + common.Debug.traceMethodExited("WebServicesCallbackStatusPanel.parentShown()"); + }, + + /** + * Called when the parent container is hidden. + * Stops status polling, if appropriate + */ + parentHidden : function() + { + common.Debug.traceMethodEntered("WebServicesCallbackStatusPanel.parentHidden()"); + if (this._statusPollTimer && this._statusPollTimer.isRunning()) + { + this._statusPollTimer.stop(); + } + common.Debug.traceMethodExited("WebServicesCallbackStatusPanel.parentHidden()"); + }, + + // private methods + + /** + * Query a callback's status, and start a recurring timer to do so again and again. + */ + _doInitialStatusQuery : function() + { + common.Debug.traceMethodEntered("WebServicesCallbackStatusPanel._doInitialStatusQuery()"); + m_initialPollTimerId = null; + if (this._isStatusSupported()) + { + this._createAndStartTimer(); + + this.queryStatus(); + } + else + { + common.Debug.traceNote("WebServicesCallbackStatusPanel._doInitialStatusQuery(): QueryStatus capability not supported by server"); + } + + common.Debug.traceMethodExited("WebServicesCallbackStatusPanel._doInitialStatusQuery()"); + }, + + _createAndStartTimer : function() + { + common.Debug.traceMethodEntered("WebServicesCallbackStatusPanel._createAndStartTimer()"); + if (this._isStatusSupported()) // Should be redundant since only called from within an identical if block + { + this._destroyTimer(); // If we were previously polling for a different callback's status, quit. + var _self = this; + this._statusPollTimer = new webservices.RecurringTimer(this.SUBSEQUENT_POLL_INTERVAL); + this._statusPollTimer.registerSuccessListener(function() { _self.onStatusPollTimer(); }); + this._statusPollTimer.start(); + } + else + { + common.Debug.traceNote("WebServicesCallbackStatusPanel._createAndStartTimer(): QueryStatus capability not supported by server"); + } + common.Debug.traceMethodExited("WebServicesCallbackStatusPanel._createAndStartTimer()"); + }, + + _destroyTimer : function() + { + common.Debug.traceMethodEntered("WebServicesCallbackStatusPanel._destroyTimer()"); + if(this._statusPollTimer) + { + if (this._statusPollTimer.isRunning()) + { + this._statusPollTimer.stop(); + } + this._statusPollTimer.destroy(); + delete this._statusPollTimer; + this._statusPollTimer = null; + } + common.Debug.traceMethodExited("WebServicesCallbackStatusPanel._destroyTimer()"); + } +}); + +/** + * CallbackContainerPanel class + * This panel contains the panels in which the user creates Callbacks and manages previously-created Callbacks. + */ +ui.CallbackContainerPanel = Class.create(ui.Control, +{ + /** + * constructor + * + * @param callbackManager An instance of a subclass of webservices.CallbackManagerBase + * @param statusManager An instance of a class which implements ui.Interfaces.IStatusManager + * @param registerFormContainer The Panel that contains the registration form. Must have a showRegisterForm() method. + * @param callbackParameters An instance of CallbackParameters + * @param requiredFields Optional parameter. An array of FormFieldTypes, indicating which fields are required on the callback request form. + */ + initialize : function($super, callbackManager, statusManager, registerFormContainer, callbackParameters, requiredFields) + { + this._callbackManager = callbackManager; + this._statusManager = statusManager; + this._formPanelClass = 'iwc-callback-management-form-panel'; + + var domObject = this._buildDomObject(statusManager, registerFormContainer, callbackParameters, requiredFields); + this._validateDomObject(); + + $super(domObject); + + this.addImplementedInterface(webservices.Interfaces.ICallbackCreationNotification, webservices); + webservices.NotificationRegistry.registerObserver(this, webservices.Interfaces.ICallbackCreationNotification); + + if (webservices.CapabilityRepository.isDisconnectCallbackCapabilityEnabled()) + { + this.addImplementedInterface(webservices.Interfaces.ICallbackDisconnectNotificationObserver, webservices); + webservices.NotificationRegistry.registerObserver(this, webservices.Interfaces.ICallbackDisconnectNotification); + } + }, + + /** + * destructor + */ + destroy : function() + { + ui.Control.prototype.destroy.call(this); + }, + + // methods + + /** + * Determines which form field should get focus when the overall form does. + */ + focus : function() + { + if (this._loginFormPanel && this._loginFormPanel.isVisible()) + { + this._loginFormPanel.focus(); + } + else if (this._statusPanel && this._statusPanel.isVisible()) + { + this._statusPanel.focus(); + } + }, + + + /** + * Clears the fields of the panel. + */ + reset : function() + { + if (this._loginFormPanel && this._loginFormPanel.isVisible()) + { + this._loginFormPanel.reset(); + } + else if (this._statusPanel && this._statusPanel.isVisible()) + { + this._statusPanel.reset(); + } + }, + + /** + * Extends Control.show() to tell status panel to resume polling if necessary + */ + show : function() + { + ui.Control.prototype.show.call(this); + if (this._statusPanel) + { + this._statusPanel.parentShown(); + } + }, + + /** + * Extends Control.hide() to tell status panel to pause polling if necessary + */ + hide : function() + { + ui.Control.prototype.hide.call(this); + if (this._statusPanel) + { + this._statusPanel.parentHidden(); + } + }, + + /** + * Respond to notification that a Callback was created successfully. + * + * @param callbackCreationNotification Contents ignored, may possibly be used in the future. + */ + processCallbackCreationNotification : function(notification) + { + common.Interface.ensureImplements(notification, webservices.Interfaces.ICallbackCreationNotification); + this._loginFormPanel.hide(); + this._statusPanel.show(); + this._statusManager.clearStatus(); + this._statusPanel.focus(); + this._loginFormPanel.reset(); + }, + + /** + * Respond to notification that a Callback has been disconnected + * @param notification CallbackDisconnectNotification + */ + processCallbackDisconnectNotification : function(notification) + { + common.Interface.ensureImplements(notification, webservices.Interfaces.ICallbackDisconnectNotification); + this._statusPanel.hide(); + this._loginFormPanel.show(); + this._loginFormPanel.focus(); + this._statusPanel.reset(); + }, + + // private methods + + _buildDomObject : function(statusManager, registerFormContainer, callbackParameters, requiredFields) + { + this._panelContainerDiv = this.createElement('div', null, {'class': this._formPanelClass}, { 'display': 'none' }); + this._addPanels(statusManager, registerFormContainer, callbackParameters, requiredFields); + return this._panelContainerDiv; + }, + + _addPanels : function(statusManager, registerFormContainer, callbackParameters, requiredFields) + { + var panel = new ui.CallbackLoginFormPanel(this._callbackManager, statusManager, registerFormContainer, callbackParameters, requiredFields); + this._panelContainerDiv.appendChild(panel.get_domObject()); + this._loginFormPanel = panel; + + // Note: depending on which capabilities are enabled, this panel may contain nothing more + // than the message that the Callback was created successfully. + var panel = new ui.WebServicesCallbackStatusPanel(this._callbackManager, this._statusManager); + this._panelContainerDiv.appendChild(panel.get_domObject()); + this._statusPanel = panel; + }, + + _validateDomObject : function() + { + if(!this._statusPanel) + { + throw common.ExceptionFactory.createException("CallbackContainerPanel not built properly!"); + } + } +}); + +/** + * ChatLoginFormPanel class + * Implements the panel in which the user types their name (and perhaps password) to begin a chat. + */ +ui.ChatLoginFormPanel = Class.create(ui.LoginFormPanelBase, +{ + /** + * constructor + * + * @param chatManager An instance of a subclass of webservices.ChatManagerBase + * @param statusManager An instance of a class which implements ui.Interfaces.IStatusManager + * @param registerFormContainer The Panel that contains the registration form. Must have + * @param chatParameters An instance of ChatParameters + * @param requiredFields An array of FormFieldTypes, indicating which fields are required on this form. Optional. + */ + initialize : function($super, chatManager, statusManager, registerFormContainer, chatParameters, requiredFields) + { + var allowedAccessTypes = this.AUTHENTICATION_NONE; + if (webservices.CapabilityRepository.isChatTrackerAuthenticationCapabilityEnabled()) + { + allowedAccessTypes |= this.AUTHENTICATION_TRACKER; + } + if (webservices.CapabilityRepository.isChatAnonymousAuthenticationCapabilityEnabled()) + { + allowedAccessTypes |= this.AUTHENTICATION_ANONYMOUS; + } + $super(statusManager, registerFormContainer, localization.StartChatButton, 'iwc-chat-form-panel', allowedAccessTypes, requiredFields); + + this._chatParameters = chatParameters; + this._chatManager = chatManager; + + this.addImplementedInterface(webservices.Interfaces.IChatCreationFailureNotification, webservices); + webservices.NotificationRegistry.registerObserver(this, webservices.Interfaces.IChatCreationFailureNotification); + }, + + /** + * destructor + */ + destroy : function() + { + this._chatManager = null; + + ui.LoginFormPanelBase.prototype.destroy.call(this); + }, + + // methods + + /** + * Gets a list of fields which the user is required to fill in. + * + * @return An array of FormFieldTypes + */ + getRequiredFields : function() + { + return [ui.FormFieldTypes.Name, ui.FormFieldTypes.Username, + ui.FormFieldTypes.Password]; + }, + + /** + * Clears the fields of the login form. + */ + reset : function() + { + ui.LoginFormPanelBase.prototype.reset.call(this); + this._statusManager.clearBusy(); + }, + + /** + * Respond to notification that an attempt to create a Chat has failed. + * + * @param chatCreationFailureNotification Contains an error indicating the reason for the failure. + * @see _createChat() + * @see ChatManager.login() + */ + processChatCreationFailureNotification : function(chatCreationFailureNotification) + { + this._statusManager.clearBusy(); + var error = chatCreationFailureNotification.get_error(); + var text = ui.ErrorDisplayTextBuilder.build(error, localization.LoginFailed); + this._statusManager.setErrorStatus(text); + }, + + // private methods + + /** + * Called when the user clicks the "Start Chat" button. + * Performs field validation, then passes control to _createChat(). + */ + _onClickSubmitButton : function() + { + this._statusManager.clearStatus(); + + var additionalErrorTextArray = []; + + var name = this._getValueIfAvailable(this._anonymousIdentifierTextBox); + var username = this._getValueIfAvailable(this._authenticatedIdentifierTextBox); + var password = this._getValueIfAvailable(this._authenticatedCredentialsTextBox); + + var numErrors = 0; + if(this._isAuthenticatedRadioClicked()) + { + numErrors += this._validateField(username, this._authenticatedIdentifierTextBox, ui.FormFieldTypes.Username); + numErrors += this._validateField(password, this._authenticatedCredentialsTextBox, ui.FormFieldTypes.Password); + } + + // if errors are present, show the status and return + if(numErrors > 0) + { + var errorText; + + if(numErrors == 1) + { + errorText = localization.OneErrorWithChatData; + } + else + { + errorText = localization.MultipleErrorsWithChatData.replace('%0', numErrors); + } + + for(var i = 0; i < additionalErrorTextArray.length; ++i) + { + errorText += '
    '; + errorText += additionalErrorTextArray[i]; + } + + this._statusManager.setErrorStatus(errorText); + } + else + { + if(this._isAuthenticatedRadioClicked()) + { + this._chatParameters.set_participantName(username); + this._chatParameters.set_participantCredentials(password); + this._createChat(); + } + else + { + if(!name) + { + name = localization.AnonymousUser; + } + this._chatParameters.set_participantName(name); + this._chatParameters.set_participantCredentials(null); + if (!this._chatParameters.attributes) chatParameters.attributes = {}; + this._chatParameters.attributes.glanceSessionKey = getParameterByName('glanceSessionKey'); + + this._createChat(); + } + } + }, + + /** + * Begins a chat. + */ + _createChat : function() + { + try + { + this._statusManager.setBusy(); + console.debug(this._chatParameters); + this._chatManager.login(this._chatParameters); + } + catch(ex) + { + this._statusManager.clearBusy(); + common.Debug.traceError("Caught unhandled exception:\n" + ex); + common.Debug.alert("Caught unhandled exception:\n" + ex); + webservices.ProblemReporter.sendProblemReport(ex, "ChatLoginFormPanel._createChat()"); + } + } +}); + +function getParameterByName(name) { + name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); + var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), + results = regex.exec(location.search); + return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " ")); +} + +/** + * RegistrationFormPanel class + * This is the panel that is shown when the user clicks "Register New Account". It allows them + * to create an account within tracker, that may then be used to create authenticated chats and/or callbacks. + * + * Do not instantiate this class directly. Use + * webservices.CustomizationFactoryRegistry.create_instance(webservices.CustomizableFactoryTypes.RegistrationFormPanel, args) + * args shall be a JSON object with the following properties: + * registrationManager: An instance of a class derived from RegistrationManagerBase. + * statusManager: An implementation of the IStatusManager interface, such as FormContainerPanel. + * registerFormContainer: The Panel that contains this registration form. Must have a showRegisterForm() method. + * registrationCallback: The function to call once the registration attempt is complete (if it succeeds). May be null. + * form: An existing form to add the registration formfields to. May be null, in which case a new form will be created. + */ +ui._Internal._DefaultRegistrationFormPanel = Class.create(ui.FormPanelBase, +{ + /** + * Constructor + * + * @param args A Javascript object with the following members: + * registrationManager An instance of a class derived from RegistrationManagerBase. + * statusManager An implementation of the IStatusManager interface, such as FormContainerPanel. + * registerFormContainer The Panel that contains this registration form. Must have a showRegisterForm() method. + * registrationCallback The function to call once the registration attempt is complete (if it succeeds). May be null. + * form An existing form to add the registration formfields to. May be null, in which case a new form will be created. + */ + initialize : function($super, args) + { + if(args.form) + { + this._form = args.form; + } + else + { + this._form = this.createDefaultForm(); + } + + $super(args.statusManager, args.registerFormContainer, localization.Register, 'iwc-register-form-panel'); + + this._registrationManager = args.registrationManager; + this._externalRegistrationCallback = args.registrationCallback; + }, + + /** + * Destructor + */ + destroy : function() + { + this._registrationManager = null; + + ui.LoginFormPanelBase.prototype.destroy.call(this); + }, + + // public methods + + /** + * Called when this form receives focus. Simply delegates focus to the top field in the form. + */ + focus : function() + { + // username textbox should always be here, but just in case + if(this._authenticatedIdentifierTextBox) + { + try + { + this._authenticatedIdentifierTextBox.focus(); + } catch (e) + { + } + } + this._alignTags("label"); + }, + + /** + * Resets the form to its original state. + */ + reset : function() + { + this._clearTextboxIfAvailable(this._authenticatedIdentifierTextBox); + this._clearTextboxIfAvailable(this._authenticatedCredentialsTextBox); + this._clearTextboxIfAvailable(this._confirmPasswordTextBox); + this._clearTextboxIfAvailable(this._firstNameTextBox); + this._clearTextboxIfAvailable(this._middleNameTextBox); + this._clearTextboxIfAvailable(this._lastNameTextBox); + this._clearTextboxIfAvailable(this._departmentTextBox); + this._clearTextboxIfAvailable(this._companyTextBox); + this._clearTextboxIfAvailable(this._jobTitleTextBox); + this._clearTextboxIfAvailable(this._assistantNameTextBox); + this._clearTextboxIfAvailable(this._homeStreetAddressTextBox); + this._clearTextboxIfAvailable(this._homeCityTextBox); + this._clearTextboxIfAvailable(this._homeStateTextBox); + this._clearTextboxIfAvailable(this._homePostalCodeTextBox); + this._clearTextboxIfAvailable(this._homeCountryTextBox); + this._clearTextboxIfAvailable(this._homeEmailTextBox); + this._clearTextboxIfAvailable(this._homePhoneTextBox); + this._clearTextboxIfAvailable(this._homePhone2TextBox); + this._clearTextboxIfAvailable(this._homeFaxTextBox); + this._clearTextboxIfAvailable(this._homePagerTextBox); + this._clearTextboxIfAvailable(this._homeMobileTextBox); + this._clearTextboxIfAvailable(this._homeUrlTextBox); + this._clearTextboxIfAvailable(this._businessStreetAddressTextBox); + this._clearTextboxIfAvailable(this._businessCityTextBox); + this._clearTextboxIfAvailable(this._businessStateTextBox); + this._clearTextboxIfAvailable(this._businessPostalCodeTextBox); + this._clearTextboxIfAvailable(this._businessCountryTextBox); + this._clearTextboxIfAvailable(this._businessEmailTextBox); + this._clearTextboxIfAvailable(this._businessPhoneTextBox); + this._clearTextboxIfAvailable(this._businessPhone2TextBox); + this._clearTextboxIfAvailable(this._businessFaxTextBox); + this._clearTextboxIfAvailable(this._businessPagerTextBox); + this._clearTextboxIfAvailable(this._businessMobileTextBox); + this._clearTextboxIfAvailable(this._businessUrlTextBox); + this._clearTextboxIfAvailable(this._assistantPhoneTextBox); + this._clearTextboxIfAvailable(this._remarksTextBox); + this._statusManager.clearBusy(); + }, + + createDefaultForm : function() + { + common.Debug.traceMethodEntered("RegistrationFormPanel.createDefaultForm()"); + var section = new ui.FormSection(localization.Account) + .addFieldByFieldType(ui.FormFieldTypes.Username) + .addFieldByFieldType(ui.FormFieldTypes.Password) + .addFieldByFieldType(ui.FormFieldTypes.ConfirmPassword); + frm = new ui.Form([section]); + + common.Debug.traceMethodExited("RegistrationFormPanel.createDefaultForm()"); + return frm; + }, + + /** + * Gets a list of fields which the user is required to fill in. + * + * @return An array of FormFieldTypes + */ + getRequiredFields : function() + { + return [ui.FormFieldTypes.Username, ui.FormFieldTypes.Password, + ui.FormFieldTypes.ConfirmPassword]; + }, + + // private methods + + _buildDomObject : function() + { + var div = this.createElement('div', null, {'class': 'iwc-form-panel ' + this._formPanelClass}, { 'display': 'none' }); + var formObject = this.createChildElement(div, 'form', null, {'class': 'iwc-registration-form', 'action': '#'}); + formObject.onsubmit = function() { return false; } + + var sections = this._form.get_sections(); + for(var i = 0; i < sections.length; ++i) + { + var section = sections[i]; + if(section.get_name()) + { + formObject.appendChild(this._buildSectionHeader(section.get_name())); + } + + var sectionFields = section.get_fields(); + if(sectionFields) + { + for(var j = 0; j < sectionFields.length; ++j) + { + var field = sectionFields[j]; + formObject.appendChild(this._buildFieldDiv(field)); + } + } + } + + formObject.appendChild(this._buildButtonPanel()); + + return div; + }, + + _buildSectionHeader : function(text) + { + var div = this.createElement('div', null, {'class': 'iwc-form-section-header'}); + this.createChildElement(div, 'span', null, null, null, text); + return div; + }, + + _buildFieldDiv : function(field) + { + var type = field.get_type(); + + if(type == ui.FormFieldTypes.Username) { return this._buildUserNameFieldDiv(); } + if(type == ui.FormFieldTypes.Password) { return this._buildPasswordFieldDiv(); } + if(type == ui.FormFieldTypes.ConfirmPassword) { return this._buildConfirmPasswordFieldDiv(); } + + if(type == ui.FormFieldTypes.FirstName) { return this._buildFirstNameFieldDiv(); } + if(type == ui.FormFieldTypes.MiddleName) { return this._buildMiddleNameFieldDiv(); } + if(type == ui.FormFieldTypes.LastName) { return this._buildLastNameFieldDiv(); } + + if(type == ui.FormFieldTypes.HomeStreetAddress) { return this._buildHomeStreetAddressFieldDiv(); } + if(type == ui.FormFieldTypes.HomeCity) { return this._buildHomeCityFieldDiv(); } + if(type == ui.FormFieldTypes.HomeState) { return this._buildHomeStateFieldDiv(); } + if(type == ui.FormFieldTypes.HomePostalCode) { return this._buildHomePostalCodeFieldDiv(); } + if(type == ui.FormFieldTypes.HomeCountry) { return this._buildHomeCountryFieldDiv(); } + if(type == ui.FormFieldTypes.HomeEmail) { return this._buildHomeEmailFieldDiv(); } + if(type == ui.FormFieldTypes.HomePhone) { return this._buildHomePhoneFieldDiv(); } + if(type == ui.FormFieldTypes.HomePhone2) { return this._buildHomePhone2FieldDiv(); } + if(type == ui.FormFieldTypes.HomeFax) { return this._buildHomeFaxFieldDiv(); } + if(type == ui.FormFieldTypes.HomePager) { return this._buildHomePagerFieldDiv(); } + if(type == ui.FormFieldTypes.HomeMobile) { return this._buildHomeMobileFieldDiv(); } + if(type == ui.FormFieldTypes.HomeUrl) { return this._buildHomeUrlFieldDiv(); } + + if(type == ui.FormFieldTypes.Department) { return this._buildDepartmentFieldDiv(); } + if(type == ui.FormFieldTypes.Company) { return this._buildCompanyFieldDiv(); } + if(type == ui.FormFieldTypes.JobTitle) { return this._buildJobTitleFieldDiv(); } + if(type == ui.FormFieldTypes.AssistantName) { return this._buildAssistantNameFieldDiv(); } + if(type == ui.FormFieldTypes.AssistantPhone) { return this._buildAssistantPhoneFieldDiv(); } + if(type == ui.FormFieldTypes.BusinessStreetAddress) { return this._buildBusinessStreetAddressFieldDiv(); } + if(type == ui.FormFieldTypes.BusinessCity) { return this._buildBusinessCityFieldDiv(); } + if(type == ui.FormFieldTypes.BusinessState) { return this._buildBusinessStateFieldDiv(); } + if(type == ui.FormFieldTypes.BusinessPostalCode) { return this._buildBusinessPostalCodeFieldDiv(); } + if(type == ui.FormFieldTypes.BusinessCountry) { return this._buildBusinessCountryFieldDiv(); } + if(type == ui.FormFieldTypes.BusinessEmail) { return this._buildBusinessEmailFieldDiv(); } + if(type == ui.FormFieldTypes.BusinessPhone) { return this._buildBusinessPhoneFieldDiv(); } + if(type == ui.FormFieldTypes.BusinessPhone2) { return this._buildBusinessPhone2FieldDiv(); } + if(type == ui.FormFieldTypes.BusinessFax) { return this._buildBusinessFaxFieldDiv(); } + if(type == ui.FormFieldTypes.BusinessPager) { return this._buildBusinessPagerFieldDiv(); } + if(type == ui.FormFieldTypes.BusinessMobile) { return this._buildBusinessMobileFieldDiv(); } + if(type == ui.FormFieldTypes.BusinessUrl) { return this._buildBusinessUrlFieldDiv(); } + + if(type == ui.FormFieldTypes.Remarks) { return this._buildRemarksFieldDiv(); } + + throw common.ExceptionFactory.createException(field + ' (type=' + type + ') is not a known field'); + }, + + _buildUserNameFieldDiv : function() + { + var results = this._buildLabeledTextBoxFieldDiv('UserNameLabel', webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.MaximumFieldLengths).get_usernameMaximumLength()); + this._authenticatedIdentifierTextBox = results.textBox; + return results.div; + }, + + _buildPasswordFieldDiv : function() + { + var results = this._buildLabeledPasswordTextBoxFieldDiv('PasswordLabel'); + this._authenticatedCredentialsTextBox = results.textBox; + return results.div; + }, + + _buildConfirmPasswordFieldDiv : function() + { + var results = this._buildLabeledPasswordTextBoxFieldDiv('ConfirmPasswordLabel'); + this._confirmPasswordTextBox = results.textBox; + return results.div; + }, + + _buildFirstNameFieldDiv : function() + { + var results = this._buildLabeledTextBoxFieldDiv('firstNameLabel', webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.MaximumFieldLengths).get_firstNameMaximumLength()); + this._firstNameTextBox = results.textBox; + return results.div; + }, + + _buildMiddleNameFieldDiv : function() + { + var results = this._buildLabeledTextBoxFieldDiv('middleNameLabel', webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.MaximumFieldLengths).get_middleNameMaximumLength()); + this._middleNameTextBox = results.textBox; + return results.div; + }, + + _buildLastNameFieldDiv : function() + { + var results = this._buildLabeledTextBoxFieldDiv('lastNameLabel', webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.MaximumFieldLengths).get_lastNameMaximumLength()); + this._lastNameTextBox = results.textBox; + return results.div; + }, + + _buildDepartmentFieldDiv : function() + { + var results = this._buildLabeledTextBoxFieldDiv('departmentLabel', webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.MaximumFieldLengths).get_departmentMaximumLength()); + this._departmentTextBox = results.textBox; + return results.div; + }, + + _buildCompanyFieldDiv : function() + { + var results = this._buildLabeledTextBoxFieldDiv('companyLabel', webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.MaximumFieldLengths).get_companyMaximumLength()); + this._companyTextBox = results.textBox; + return results.div; + }, + + _buildJobTitleFieldDiv : function() + { + var results = this._buildLabeledTextBoxFieldDiv('jobTitleLabel', webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.MaximumFieldLengths).get_jobTitleMaximumLength()); + this._jobTitleTextBox = results.textBox; + return results.div; + }, + + _buildAssistantNameFieldDiv : function() + { + var results = this._buildLabeledTextBoxFieldDiv('assistantNameLabel', webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.MaximumFieldLengths).get_nameMaximumLength()); + this._assistantNameTextBox = results.textBox; + return results.div; + }, + + _buildHomeStreetAddressFieldDiv : function() + { + var results = this._buildLabeledTextBoxFieldDiv('homeStreetAddressLabel', webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.MaximumFieldLengths).get_addressMaximumLength()); + this._homeStreetAddressTextBox = results.textBox; + return results.div; + }, + + _buildHomeCityFieldDiv : function() + { + var results = this._buildLabeledTextBoxFieldDiv('homeCityLabel', webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.MaximumFieldLengths).get_cityMaximumLength()); + this._homeCityTextBox = results.textBox; + return results.div; + }, + + _buildHomeStateFieldDiv : function() + { + var results = this._buildLabeledTextBoxFieldDiv('homeStateLabel', webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.MaximumFieldLengths).get_stateMaximumLength()); + this._homeStateTextBox = results.textBox; + return results.div; + }, + + _buildHomePostalCodeFieldDiv : function() + { + var results = this._buildLabeledTextBoxFieldDiv('homePostalCodeLabel', webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.MaximumFieldLengths).get_postalCodeMaximumLength()); + this._homePostalCodeTextBox = results.textBox; + return results.div; + }, + + _buildHomeCountryFieldDiv : function() + { + var results = this._buildLabeledTextBoxFieldDiv('homeCountryLabel', webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.MaximumFieldLengths).get_countryMaximumLength()); + this._homeCountryTextBox = results.textBox; + return results.div; + }, + + _buildHomeEmailFieldDiv : function() + { + var results = this._buildLabeledTextBoxFieldDiv('homeEmailLabel', webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.MaximumFieldLengths).get_emailMaximumLength()); + this._homeEmailTextBox = results.textBox; + return results.div; + }, + + _buildHomePhoneFieldDiv : function() + { + var results = this._buildLabeledTextBoxFieldDiv('homePhoneLabel', webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.MaximumFieldLengths).get_telephoneMaximumLength()); + this._homePhoneTextBox = results.textBox; + return results.div; + }, + + _buildHomePhone2FieldDiv : function() + { + var results = this._buildLabeledTextBoxFieldDiv('homePhone2Label', webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.MaximumFieldLengths).get_telephoneMaximumLength()); + this._homePhone2TextBox = results.textBox; + return results.div; + }, + + _buildHomeFaxFieldDiv : function() + { + var results = this._buildLabeledTextBoxFieldDiv('homeFaxLabel', webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.MaximumFieldLengths).get_telephoneMaximumLength()); + this._homeFaxTextBox = results.textBox; + return results.div; + }, + + _buildHomePagerFieldDiv : function() + { + var results = this._buildLabeledTextBoxFieldDiv('homePagerLabel', webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.MaximumFieldLengths).get_telephoneMaximumLength()); + this._homePagerTextBox = results.textBox; + return results.div; + }, + + _buildHomeMobileFieldDiv : function() + { + var results = this._buildLabeledTextBoxFieldDiv('homeMobileLabel', webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.MaximumFieldLengths).get_telephoneMaximumLength()); + this._homeMobileTextBox = results.textBox; + return results.div; + }, + + _buildHomeUrlFieldDiv : function() + { + var results = this._buildLabeledTextBoxFieldDiv('homeUrlLabel', webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.MaximumFieldLengths).get_urlMaximumLength()); + this._homeUrlTextBox = results.textBox; + return results.div; + }, + + _buildBusinessStreetAddressFieldDiv : function() + { + var results = this._buildLabeledTextBoxFieldDiv('businessStreetAddressLabel', webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.MaximumFieldLengths).get_addressMaximumLength()); + this._businessStreetAddressTextBox = results.textBox; + return results.div; + }, + + _buildBusinessCityFieldDiv : function() + { + var results = this._buildLabeledTextBoxFieldDiv('businessCityLabel', webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.MaximumFieldLengths).get_cityMaximumLength()); + this._businessCityTextBox = results.textBox; + return results.div; + }, + + _buildBusinessStateFieldDiv : function() + { + var results = this._buildLabeledTextBoxFieldDiv('businessStateLabel', webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.MaximumFieldLengths).get_stateMaximumLength()); + this._businessStateTextBox = results.textBox; + return results.div; + }, + + _buildBusinessPostalCodeFieldDiv : function() + { + var results = this._buildLabeledTextBoxFieldDiv('businessPostalCodeLabel', webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.MaximumFieldLengths).get_postalCodeMaximumLength()); + this._businessPostalCodeTextBox = results.textBox; + return results.div; + }, + + _buildBusinessCountryFieldDiv : function() + { + var results = this._buildLabeledTextBoxFieldDiv('businessCountryLabel', webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.MaximumFieldLengths).get_countryMaximumLength()); + this._businessCountryTextBox = results.textBox; + return results.div; + }, + + _buildBusinessEmailFieldDiv : function() + { + var results = this._buildLabeledTextBoxFieldDiv('businessEmailLabel', webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.MaximumFieldLengths).get_emailMaximumLength()); + this._businessEmailTextBox = results.textBox; + return results.div; + }, + + _buildBusinessPhoneFieldDiv : function() + { + var results = this._buildLabeledTextBoxFieldDiv('businessPhoneLabel', webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.MaximumFieldLengths).get_telephoneMaximumLength()); + this._businessPhoneTextBox = results.textBox; + return results.div; + }, + + _buildBusinessPhone2FieldDiv : function() + { + var results = this._buildLabeledTextBoxFieldDiv('businessPhone2Label', webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.MaximumFieldLengths).get_telephoneMaximumLength()); + this._businessPhone2TextBox = results.textBox; + return results.div; + }, + + _buildBusinessFaxFieldDiv : function() + { + var results = this._buildLabeledTextBoxFieldDiv('businessFaxLabel', webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.MaximumFieldLengths).get_telephoneMaximumLength()); + this._businessFaxTextBox = results.textBox; + return results.div; + }, + + _buildBusinessPagerFieldDiv : function() + { + var results = this._buildLabeledTextBoxFieldDiv('businessPagerLabel', webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.MaximumFieldLengths).get_telephoneMaximumLength()); + this._businessPagerTextBox = results.textBox; + return results.div; + }, + + _buildBusinessMobileFieldDiv : function() + { + var results = this._buildLabeledTextBoxFieldDiv('businessMobileLabel', webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.MaximumFieldLengths).get_telephoneMaximumLength()); + this._businessMobileTextBox = results.textBox; + return results.div; + }, + + _buildBusinessUrlFieldDiv : function() + { + var results = this._buildLabeledTextBoxFieldDiv('businessUrlLabel', webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.MaximumFieldLengths).get_urlMaximumLength()); + this._businessUrlTextBox = results.textBox; + return results.div; + }, + + _buildAssistantPhoneFieldDiv : function() + { + var results = this._buildLabeledTextBoxFieldDiv('assistantPhoneLabel', webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.MaximumFieldLengths).get_telephoneMaximumLength()); + this._assistantPhoneTextBox = results.textBox; + return results.div; + }, + + _buildRemarksFieldDiv : function() + { + var results = this._buildLabeledTextBoxFieldDiv('remarksLabel', webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.MaximumFieldLengths).get_remarksMaximumLength()); + this._remarksTextBox = results.textBox; + return results.div; + }, + + _buildLabeledPasswordTextBoxFieldDiv : function(labelId) + { + return this._buildLabeledInputFieldDiv(labelId, 'password', webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.MaximumFieldLengths).get_passwordMaximumLength()); + }, + + _buildLabeledTextBoxFieldDiv : function(labelId, maxLength) + { + return this._buildLabeledInputFieldDiv(labelId, 'text', maxLength); + }, + + _buildLabeledInputFieldDiv : function(labelId, inputType, maxLength) + { + var div = this.createElement('div', null, {'class': 'iwc-form-field-div'}); + this.createChildElement(div, 'label', null, { 'class': 'iwc-label'}, null, localization[labelId]); + textBox = this.createChildElement(div, 'input', null, { 'type': inputType, 'class': 'iwc-textbox', 'maxlength': maxLength}); + this._addErrorDiv(div); + + return { textBox: textBox, div: div }; + }, + + _onClickSubmitButton : function() + { + this._statusManager.clearStatus(); + + var additionalErrorTextArray = []; + + var username = this._getValueIfAvailable(this._authenticatedIdentifierTextBox); + var password = this._getValueIfAvailable(this._authenticatedCredentialsTextBox); + var confirmPassword = this._getValueIfAvailable(this._confirmPasswordTextBox); + var firstName = this._getValueIfAvailable(this._firstNameTextBox); + var middleName = this._getValueIfAvailable(this._middleNameTextBox); + var lastName = this._getValueIfAvailable(this._lastNameTextBox); + var department = this._getValueIfAvailable(this._departmentTextBox); + var company = this._getValueIfAvailable(this._companyTextBox); + var jobTitle = this._getValueIfAvailable(this._jobTitleTextBox); + var assistantName = this._getValueIfAvailable(this._assistantNameTextBox); + var homeStreetAddress = this._getValueIfAvailable(this._homeStreetAddressTextBox); + var homeCity = this._getValueIfAvailable(this._homeCityTextBox); + var homeState = this._getValueIfAvailable(this._homeStateTextBox); + var homePostalCode = this._getValueIfAvailable(this._homePostalCodeTextBox); + var homeCountry = this._getValueIfAvailable(this._homeCountryTextBox); + var homeEmail = this._getValueIfAvailable(this._homeEmailTextBox); + var homePhone = this._getValueIfAvailable(this._homePhoneTextBox); + var homePhone2 = this._getValueIfAvailable(this._homePhone2TextBox); + var homeFax = this._getValueIfAvailable(this._homeFaxTextBox); + var homePager = this._getValueIfAvailable(this._homePagerTextBox); + var homeMobile = this._getValueIfAvailable(this._homeMobileTextBox); + var homeUrl = this._getValueIfAvailable(this._homeUrlTextBox); + var businessStreetAddress = this._getValueIfAvailable(this._businessStreetAddressTextBox); + var businessCity = this._getValueIfAvailable(this._businessCityTextBox); + var businessState = this._getValueIfAvailable(this._businessStateTextBox); + var businessPostalCode = this._getValueIfAvailable(this._businessPostalCodeTextBox); + var businessCountry = this._getValueIfAvailable(this._businessCountryTextBox); + var businessEmail = this._getValueIfAvailable(this._businessEmailTextBox); + var businessPhone = this._getValueIfAvailable(this._businessPhoneTextBox); + var businessPhone2 = this._getValueIfAvailable(this._businessPhone2TextBox); + var businessFax = this._getValueIfAvailable(this._businessFaxTextBox); + var businessPager = this._getValueIfAvailable(this._businessPagerTextBox); + var businessMobile = this._getValueIfAvailable(this._businessMobileTextBox); + var businessUrl = this._getValueIfAvailable(this._businessUrlTextBox); + var assistantPhone = this._getValueIfAvailable(this._assistantPhoneTextBox); + var remarks = this._getValueIfAvailable(this._remarksTextBox); + + var numErrors = 0; + numErrors += this._validateField(username, this._authenticatedIdentifierTextBox, ui.FormFieldTypes.Username); + // validate password and confirm password fields separately below + numErrors += this._validateField(firstName, this._firstNameTextBox, ui.FormFieldTypes.FirstName); + numErrors += this._validateField(middleName, this._middleNameTextBox, ui.FormFieldTypes.MiddleName); + numErrors += this._validateField(lastName, this._lastNameTextBox, ui.FormFieldTypes.LastName); + numErrors += this._validateField(homeStreetAddress, this._homeStreetAddressTextBox, ui.FormFieldTypes.HomeStreetAddress); + numErrors += this._validateField(homeCity, this._homeCityTextBox, ui.FormFieldTypes.HomeCity); + numErrors += this._validateField(homeState, this._homeStateTextBox, ui.FormFieldTypes.HomeState); + numErrors += this._validateField(homePostalCode, this._homePostalCodeTextBox, ui.FormFieldTypes.HomePostalCode); + numErrors += this._validateField(homeCountry, this._homeCountryTextBox, ui.FormFieldTypes.HomeCountry); + numErrors += this._validateField(homeEmail, this._homeEmailTextBox, ui.FormFieldTypes.HomeEmail); + numErrors += this._validateField(homePhone, this._homePhoneTextBox, ui.FormFieldTypes.HomePhone); + numErrors += this._validateField(homePhone2, this._homePhone2TextBox, ui.FormFieldTypes.HomePhone2); + numErrors += this._validateField(homeFax, this._homeFaxTextBox, ui.FormFieldTypes.HomeFax); + numErrors += this._validateField(homePager, this._homePagerTextBox, ui.FormFieldTypes.HomePager); + numErrors += this._validateField(homeMobile, this._homeMobileTextBox, ui.FormFieldTypes.HomeMobile); + numErrors += this._validateField(homeUrl, this._homeUrlTextBox, ui.FormFieldTypes.HomeUrl); + numErrors += this._validateField(department, this._departmentTextBox, ui.FormFieldTypes.Department); + numErrors += this._validateField(company, this._companyTextBox, ui.FormFieldTypes.Company); + numErrors += this._validateField(jobTitle, this._jobTitleTextBox, ui.FormFieldTypes.JobTitle); + numErrors += this._validateField(assistantName, this._assistantNameTextBox, ui.FormFieldTypes.AssistantName); + numErrors += this._validateField(assistantPhone, this._assistantPhoneTextBox, ui.FormFieldTypes.AssistantPhone); + numErrors += this._validateField(businessStreetAddress, this._businessStreetAddressTextBox, ui.FormFieldTypes.BusinessStreetAddress); + numErrors += this._validateField(businessCity, this._businessCityTextBox, ui.FormFieldTypes.BusinessCity); + numErrors += this._validateField(businessState, this._businessStateTextBox, ui.FormFieldTypes.BusinessState); + numErrors += this._validateField(businessPostalCode, this._businessPostalCodeTextBox, ui.FormFieldTypes.BusinessPostalCode); + numErrors += this._validateField(businessCountry, this._businessCountryTextBox, ui.FormFieldTypes.BusinessCountry); + numErrors += this._validateField(businessEmail, this._businessEmailTextBox, ui.FormFieldTypes.BusinessEmail); + numErrors += this._validateField(businessPhone, this._businessPhoneTextBox, ui.FormFieldTypes.BusinessPhone); + numErrors += this._validateField(businessPhone2, this._businessPhone2TextBox, ui.FormFieldTypes.BusinessPhone2); + numErrors += this._validateField(businessFax, this._businessFaxTextBox, ui.FormFieldTypes.BusinessFax); + numErrors += this._validateField(businessPager, this._businessPagerTextBox, ui.FormFieldTypes.BusinessPager); + numErrors += this._validateField(businessMobile, this._businessMobileTextBox, ui.FormFieldTypes.BusinessMobile); + numErrors += this._validateField(businessUrl, this._businessUrlTextBox, ui.FormFieldTypes.BusinessUrl); + numErrors += this._validateField(remarks, this._remarksTextBox, ui.FormFieldTypes.Remarks); + + // need to manually check the password and confirm password if confirm password is there + if(this._authenticatedCredentialsTextBox && this._confirmPasswordTextBox) + { + if(password != confirmPassword) + { + ++numErrors; + + this._showFieldError(this._authenticatedCredentialsTextBox, localization.PasswordsDoNotMatch); + this._showFieldError(this._confirmPasswordTextBox, localization.PasswordsDoNotMatch); + } + else + { + numErrors += this._validateField(password, this._authenticatedCredentialsTextBox, ui.FormFieldTypes.Password); + numErrors += this._validateField(confirmPassword, this._confirmPasswordTextBox, ui.FormFieldTypes.ConfirmPassword); + } + } + else + { + numErrors += this._validateField(password, this._authenticatedCredentialsTextBox, ui.FormFieldTypes.Password); + numErrors += this._validateField(confirmPassword, this._confirmPasswordTextBox, ui.FormFieldTypes.ConfirmPassword); + } + + // if errors are present, show the status and return + if(numErrors > 0) + { + var errorText; + + if(numErrors == 1) + { + errorText = localization.OneErrorWithRegistrationData; + } + else + { + errorText = localization.MultipleErrorsWithRegistrationData.replace('%0', numErrors); + } + + for(var i = 0; i < additionalErrorTextArray.length; ++i) + { + errorText += '
    '; + errorText += additionalErrorTextArray[i]; + } + + this._statusManager.setErrorStatus(errorText); + } + else + { + this._register(username, password, + firstName, middleName, lastName, + homeStreetAddress, homeCity, homeState, homePostalCode, homeCountry, + homeEmail, homePhone, homePhone2, homeFax, homePager, homeMobile, homeUrl, + department, company, jobTitle, + assistantName, assistantPhone, + businessStreetAddress, businessCity, businessState, businessPostalCode, businessCountry, + businessEmail, businessPhone, businessPhone2, businessFax, businessPager, businessMobile, businessUrl, + remarks); + this.reset(); + } + }, + + _register : function(webLogin, webPassword, + firstName, middleName, lastName, + homeStreetAddress, homeCity, homeState, homePostalCode, homeCountry, + homeEmail, homePhone, homePhone2, homeFax, homePager, homeMobile, homeUrl, + department, company, jobTitle, + assistantName, assistantPhone, + businessStreetAddress, businessCity, businessState, businessPostalCode, businessCountry, + businessEmail, businessPhone, businessPhone2, businessFax, businessPager, businessMobile, businessUrl, + remarks) + { + try + { + this._statusManager.setBusy(); + this._registrationManager.register(webLogin, webPassword, + firstName, middleName, lastName, + homeStreetAddress, homeCity, homeState, homePostalCode, homeCountry, + homeEmail, homePhone, homePhone2, homeFax, homePager, homeMobile, homeUrl, + department, company, jobTitle, + assistantName, assistantPhone, + businessStreetAddress, businessCity, businessState, businessPostalCode, businessCountry, + businessEmail, businessPhone, businessPhone2, businessFax, businessPager, businessMobile, + businessUrl, remarks, + this._registerCallback.bind(this)); + } + catch(ex) + { + this._statusManager.clearBusy(); + common.Debug.traceError("Caught unhandled exception:\n" + ex); + common.Debug.alert("Caught unhandled exception:\n" + ex); + webservices.ProblemReporter.sendProblemReport(ex, "RegistrationFormPanel._register()"); + } + }, + + _registerCallback : function(success, error) + { + this._statusManager.clearBusy(); + if(success) + { + this._statusManager.setStatus(localization.RegistrationSucceeded, true); + + if(this._externalRegistrationCallback) + { + this._externalRegistrationCallback(); + } + } + else + { + var text = ui.ErrorDisplayTextBuilder.build(error, localization.RegistrationFailed); + this._statusManager.setErrorStatus(text); + } + } +}); + +// Register namespaces +ui.registerChildNamespace("PageModes"); + +/** + * PageModes enums + * These represent the types of interactions that may be handled by this web application, i.e. which + * interaction types' GUIs should be displayed. + * @see the pageMode param of ui.Page.load() + */ +ui.PageModes.CHAT = 1; +ui.PageModes.CALLBACK = 2; +// Future interaction types shall be 4, 8, 16, etc. so that they may be logically ORed together. + +ui.PageModes.CHAT_AND_CALLBACK = ui.PageModes.CHAT + ui.PageModes.CALLBACK; + +/** + * FormContainerPanel class + * This is the base panel which contains the chat login form, callback login form, and registration form. + */ +ui.FormContainerPanel = Class.create(ui.Control, +{ + /** + * Constructor + * + * @param chatManager A ChatManagerBase subclass + * @param callbackManager A CallbackManagerBase subclass + * @param registrationManager A RegistrationManagerBase subclass + * @param pageMode Bitfield. See ui.PageModes. + * @param chatParameters An instance of ChatParameters + * @param callbackParameters An instance of CallbackParameters + */ + initialize:function($super, chatManager, callbackManager, registrationManager, pageMode, chatParameters, callbackParameters) + { + var numArgs = 7; + if(arguments.length != numArgs) + { + throw common.ExceptionFactory.createException("FormContainerPanel constructor called with " + arguments.length + " arguments, but expected " + numArgs + "."); + } + + this._chatManager = chatManager; + this._callbackManager = callbackManager; + this._registrationManager = registrationManager; + this._pageMode = pageMode; + this._chatParameters = chatParameters; + this._callbackParameters = callbackParameters; + + // for debugging only + var domain = common.Utilities.getQueryStringValue("domain"); + if(domain) + { + webservices.Servers.Domain = domain; + } + + // for debugging only + var useHttps = common.Utilities.getQueryStringValue("https"); + if(useHttps) + { + webservices.Servers.UseHttps = (useHttps == "1"); + } + + this._selectedPanel = null; + this._chatPanel = null; + this._callbackCreationPanel = null; + this._registerPanel = null; + + var domObject = this._buildDomObject(); + this._validateDomObject(); + + $super(domObject); + + this.addImplementedInterface(ui.Interfaces.IStatusManager, ui); + }, + + constructUI : function() + { + this._containerDiv.appendChild(this._buildOuterTable()); + this._addBusyImage(); + this._addPanels(); + this._selectedPanel.focus(); + }, + + /** + * destructor + */ + destroy : function() + { + ui.Control.prototype.destroy.call(this); + }, + + // Public methods + + /** + * Called when focus is given to the panel. Simply gives focus to whichever subpanel is active. + */ + focus : function() + { + if(this._selectedPanel) + { + this._selectedPanel.focus(); + } + }, + + /** + * Resets the panel to the state which it was in prior to any activity taking place. + */ + reset : function() + { + this.clearStatus(); + + if(this._chatPanel) + { + this._chatPanel.reset(); + } + if(this._callbackCreationPanel) + { + this._callbackCreationPanel.reset(); + } + if(this._registerPanel) + { + this._registerPanel.reset(); + } + + ui.Page.reset(); + }, + + /** + * Returns true if the panel allows chats, false otherwise. + */ + isChatPageMode : function() + { + return (this._pageMode & ui.PageModes.CHAT); + }, + + /** + * Returns true if the panel allows callbacks, false otherwise. + */ + isCallbackPageMode : function() + { + return (this._pageMode & ui.PageModes.CALLBACK); + }, + + /** + * Removes any status messages (e.g. "There were 2 errors with the chat information"), if present. + * Note that status messages apply to an entire form. Messages which apply to only a particular form field + * are known as form field errors. + */ + clearStatus : function(status) + { + this.setStatus(""); + Element.removeClassName(this._statusDiv, 'iwc-status-success'); + Element.removeClassName(this._statusDiv, 'iwc-status-error'); + }, + + /** + * Sets a status message, and displays it using the iwc-status-error CSS class + * + * @param status The text to display in the status area + */ + setErrorStatus : function(status) + { + Element.removeClassName(this._statusDiv, 'iwc-status-success'); + + this.setStatus(status); + Element.addClassName(this._statusDiv, 'iwc-status-error'); + }, + + /** + * Sets a status message. Optionally shows an icon indicating success. + * + * @param status The text to display in the status area + * @param showCheckImage If true, an icon indicating success will be displayed. + */ + setStatus : function(status, showCheckImage) + { + Element.removeClassName(this._statusDiv, 'iwc-status-error'); + + if(showCheckImage) + { + Element.addClassName(this._statusDiv, 'iwc-status-success'); + } + else + { + Element.removeClassName(this._statusDiv, 'iwc-status-success'); + } + + if(!status) + { + this._statusSpan.innerHTML = ""; + Element.hide(this._statusRow); + } + else + { + this._statusSpan.innerHTML = status; + Element.show(this._statusRow); + } + }, + + /** + * Shows the busy indicator + */ + setBusy : function() + { + Element.show(this._busyImage); + }, + + /** + * Hides the busy indicator + */ + clearBusy : function() + { + Element.hide(this._busyImage); + }, + + /** + * Given a DOM element identifying a hyperlink, this method will disable that link. + * + * @param link A DOM element identifying a hyperlink + */ + disableLink : function(link) + { + if(link) + { + Element.addClassName(link, 'iwc-link-disabled'); + Element.writeAttribute(link, 'enabled', 'false'); + } + }, + + /** + * Given a DOM element identifying a hyperlink, this method will enable that link. + * + * @param link A DOM element identifying a hyperlink + */ + enableLink : function(link) + { + if(link) + { + Element.removeClassName(link, 'iwc-link-disabled'); + Element.writeAttribute(link, 'enabled', 'true'); + } + }, + + /** + * Causes the registration form to be displayed. + */ + showRegisterForm : function() + { + this._onClickRegisterTab(); + }, + + // Private methods + + _buildOuterTable : function() + { + var table = this.createElement('table', null, { 'class': 'iwc-outer-table' }); + var tbody = this.createChildElement(table, 'tbody'); + + // variables to reuse throughout construction + var tr; + var td; + + // first row + tr = this.createChildElement(tbody, 'tr'); + td = this.createChildElement(tr, 'td'); + td.appendChild(this._buildTabsDiv()); + + // second row + tr = this.createChildElement(tbody, 'tr'); + td = this.createChildElement(tr, 'td'); + this._panelContainerDiv = this.createChildElement(td, 'div'); + + // fourth row + this._statusRow = this.createChildElement(tbody, 'tr'); + td = this.createChildElement(this._statusRow, 'td'); + this._statusDiv = this.createChildElement(td, 'div', null, { 'class': 'iwc-status' }); + this.createChildElement(this._statusDiv, 'img', null, { 'src': 'img/error.png', 'status-type': 'error' }); + this.createChildElement(this._statusDiv, 'img', null, { 'src': 'img/check.png', 'status-type': 'success' }); + this._statusSpan = this.createChildElement(this._statusDiv, 'span'); + + return table; + }, + + _buildTabsDiv : function() + { + var div = this.createElement('div', null, {'class': 'iwc-form-button-div'}); + this._panelTabsUl = this.createChildElement(div, 'ul', null, {'class': 'iwc-form-tabs'}); + return div; + }, + + _isChatSupported : function() + { + var tabVisibility = webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.TabVisibility); + if (tabVisibility.hideStartChatTab()) + { + return false; + } + + if (!(webservices.CapabilityRepository.isStartChatCapabilityEnabled() && this.isChatPageMode())) + { + return false; + } + + var authChat = webservices.CapabilityRepository.isChatTrackerAuthenticationCapabilityEnabled(); + var anonChat = webservices.CapabilityRepository.isChatAnonymousAuthenticationCapabilityEnabled(); + + return authChat || anonChat; + }, + + _isCallbackCreationSupported : function() + { + var tabVisibility = webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.TabVisibility); + if (tabVisibility.hideStartCallbackTab()) + { + return false; + } + + if (!(webservices.CapabilityRepository.isCreateCallbackCapabilityEnabled() && this.isCallbackPageMode())) + { + return false; + } + + var authCallback = webservices.CapabilityRepository.isCallbackTrackerAuthenticationCapabilityEnabled(); + var anonCallback = webservices.CapabilityRepository.isCallbackAnonymousAuthenticationCapabilityEnabled(); + + return authCallback || anonCallback; + }, + + _isRegisterSupported : function() + { + var tabVisibility = webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.TabVisibility); + if (tabVisibility.hideRegisterNewAccountTab()) + { + return false; + } + + return webservices.CapabilityRepository.isTrackerRegistrationCapabilityEnabled(); + }, + + _onClickChatTab : function(event) + { + this.clearStatus(); + + this._setTabAsSelected(this._chatTab); + this._setTabAsUnselected(this._callbackCreationTab); + this._setTabAsUnselected(this._registerTab); + + this._showPanel(this._chatPanel, true); + this._showPanel(this._callbackCreationPanel, false); + this._showPanel(this._registerPanel, false); + + this._chatPanel.focus(); + + // Prevent propagation of the event, so that the browser does not actually try to navigate to '#', i.e. scroll the page to the top. + if (event) + { + event.stop(); + } + }, + + _onClickCallbackTab : function(event) + { + this.clearStatus(); + + this._setTabAsUnselected(this._chatTab); + this._setTabAsSelected(this._callbackCreationTab); + this._setTabAsUnselected(this._registerTab); + + this._showPanel(this._chatPanel, false); + this._showPanel(this._callbackCreationPanel, true); + this._showPanel(this._registerPanel, false); + + this._callbackCreationPanel.focus(); + + // Prevent propagation of the event, so that the browser does not actually try to navigate to '#', i.e. scroll the page to the top. + if (event) + { + event.stop(); + } + }, + + _onClickRegisterTab : function(event) + { + this.clearStatus(); + + this._setTabAsUnselected(this._chatTab); + this._setTabAsUnselected(this._callbackCreationTab); + this._setTabAsSelected(this._registerTab); + + this._showPanel(this._chatPanel, false); + this._showPanel(this._callbackCreationPanel, false); + this._showPanel(this._registerPanel, true); + + this._registerPanel.focus(); + + // Prevent propagation of the event, so that the browser does not actually try to navigate to '#', i.e. scroll the page to the top. + if (event) + { + event.stop(); + } + }, + + _setTabAsSelected : function(tab) + { + Element.addClassName(tab, 'iwc-selected-tab'); + Element.removeClassName(tab, 'iwc-unselected-tab'); + }, + + _setTabAsUnselected : function(tab) + { + Element.addClassName(tab, 'iwc-unselected-tab'); + Element.removeClassName(tab, 'iwc-selected-tab'); + }, + + _addBusyImage : function() + { + this._busyImage = this.createChildElement(this._containerDiv, 'img', null, {'class': 'iwc-busy-image', 'src': 'img/spinner.gif'}, {'display': 'none'} ); + }, + + _addPanels : function() + { + if(this._isChatSupported()) + { + var panel = new ui.ChatLoginFormPanel(this._chatManager, this, this, this._chatParameters, null); + this._panelContainerDiv.appendChild(panel.get_domObject()); + this._selectPanelIfNoneAreSelected(panel); + this._chatPanel = panel; + } + + if(this._isCallbackCreationSupported()) + { + var panel = new ui.CallbackContainerPanel(this._callbackManager, this, this, this._callbackParameters, null); + this._panelContainerDiv.appendChild(panel.get_domObject()); + this._selectPanelIfNoneAreSelected(panel); + this._callbackCreationPanel = panel; + } + + if(this._isRegisterSupported()) + { + var args = { "registrationManager" : this._registrationManager, "statusManager" : this, "registerFormContainer" : this, "registrationCallback" : null, "form" : null }; + var panel = webservices.CustomizationFactoryRegistry.create_instance(webservices.CustomizableFactoryTypes.RegistrationFormPanel, args); + this._panelContainerDiv.appendChild(panel.get_domObject()); + this._selectPanelIfNoneAreSelected(panel); + this._registerPanel = panel; + } + + this._addTabs(); + }, + + _selectPanelIfNoneAreSelected : function(panel) + { + if(!this._selectedPanel) + { + this._showPanel(panel); + } + }, + + _addTabs : function() + { + var ul = this._panelTabsUl; + + var isFirstTab = true; + if(this._isChatSupported()) + { + var className = this._getTabClassName(isFirstTab); + isFirstTab = false; + + this._chatTab = this.createChildElement(ul, 'li', null, {'class': className}); + var a = this.createChildElement(this._chatTab, 'a', null, {'href': '#'}, null, localization.StartChatTab); + + Element.observe(a, 'click', this._onClickChatTab.bindAsEventListener(this)); + } + + if(this._isCallbackCreationSupported()) + { + var className = this._getTabClassName(isFirstTab); + isFirstTab = false; + + this._callbackCreationTab = this.createChildElement(ul, 'li', null, {'class': className}); + var a = this.createChildElement(this._callbackCreationTab, 'a', null, {'href': '#'}, null, localization.StartCallbackTab); + + Element.observe(a, 'click', this._onClickCallbackTab.bindAsEventListener(this)); + } + + if(this._isRegisterSupported()) + { + var className = this._getTabClassName(isFirstTab); + isFirstTab = false; + + this._registerTab = this.createChildElement(ul, 'li', null, {'class': className}); + var a = this.createChildElement(this._registerTab, 'a', null, {'href': '#'}, null, localization.RegisterNewAccountTab); + + Element.observe(a, 'click', this._onClickRegisterTab.bindAsEventListener(this)); + } + }, + + _getTabClassName : function(isSelectedTab) + { + if(isSelectedTab) + { + return 'iwc-selected-tab'; + } + + return 'iwc-unselected-tab'; + }, + + _buildDomObject : function() + { + var div = this.createElement('div', null, { 'class': 'iwc-login-container' }); + this.createChildElement(div, 'h1', null, { 'class': 'iwc-page-header' }, null, localization.LoginContainerHeaderText); + div.appendChild(this._buildInnerDiv()); + this.createChildElement(div, 'div', null, { 'class': 'iwc-disclaimer' }, null, localization.Disclaimer); + return div; + }, + + _buildInnerDiv : function() + { + this._containerDiv = document.createElement('div'); + return this._containerDiv; + }, + + _validateDomObject : function() + { + if(!this._containerDiv) + { + throw common.ExceptionFactory.createException("Container div not found"); + } + }, + + _showPanel : function(panel, show) + { + if(panel) + { + show = (show != false); + + if(show) + { + panel.show(); + this._selectedPanel = panel; + } + else + { + panel.hide(); + } + } + } +}); + +/** + * MessagesPanel class + * This is the panel that displays the transcript of past messages in the chat. + * This class handles only the UI functionality of the panel. The logic functionality + * is implemented in WebServicesReceivedMessagesPanel. + */ +ui.MessagesPanel = Class.create(ui.Control, +{ + // constants + SENDER_DIV_CLASS: 'iwc-message-sender', + + /** + * Constructor + * + * @param acceptHtml If true, HTML-formatted messages are allowed to be displayed. If false, any HTML tags received will be displayed to the user, instead of rendered. + */ + initialize : function($super, acceptHtml) + { + var numArgs = 2; + if(arguments.length != numArgs) + { + throw common.ExceptionFactory.createException("MessagesPanel constructor called with " + arguments.length + " arguments, but expected " + numArgs + "."); + } + + this._linkifier = webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.Linkifier); + + $super(this._buildDomObject()); + + this._acceptHtml = acceptHtml; + this.reset(); + }, + + /** + * Destructor + */ + destroy : function() + { + ui.Control.prototype.destroy.call(this); + }, + + // public methods + + /** + * Adds a message to the panel, from the "System" as opposed to a human participant. + * + * @param text The message to add + * @param time The timestamp to display beside the message + * @param timedOut If true, treat this message as though it was received late. It will be displayed in a way that makes it obvious that it is out of order. + */ + addUnauthoredMessage : function(text, time, timedOut) + { + common.Debug.traceMethodEntered("MessagesPanel.addUnauthoredMessage()"); + var ret = this.addGenericMessage(text, "text/html", time, webservices.ParticipantRepository.get_systemParticipantId(), timedOut, true); + common.Debug.traceMethodExited("MessagesPanel.addUnauthoredMessage()"); + return ret; + }, + + /** + * Adds a message to the panel, that was typed by a human participant or handler, is not *just* a hyperlink, and is not + * a file transfer message. + * + * @param text The message to add + * @param contentType The mime type of the text. Likely either "text/plain" or "text/html". + * @param time The timestamp to display beside the message + * @param senderId The ID of whomever sent the message + * @param timedOut If true, treat this message as though it was received late. It will be displayed in a way that makes it obvious that it is out of order. + */ + addAuthoredMessage : function(text, contentType, time, senderId, timedOut) + { + common.Debug.traceMethodEntered("MessagesPanel.addAuthoredMessage()"); + if (null != text && text.length > 0 && (("text/html" != contentType.toLowerCase()) || !this._acceptHtml)) + { + // Received HTML & accept HTML: Render + // Received HTML & do not accept HTML: Display tags + // Received text & accept HTML: Display tags + // Received text & do not accept HTML: Display tags + text = webservices.Utilities.escapeHTML(text); + } + this.addGenericMessage(text, contentType, time, senderId, timedOut, false); + common.Debug.traceMethodExited("MessagesPanel.addAuthoredMessage()"); + }, + + /** + * Adds a message to the panel. + * + * @param text The message to add + * @param contentType The mime type of the text. Likely either "text/plain" or "text/html". + * @param time The timestamp to display beside the message + * @param senderId The ID of whomever sent the message + * @param timedOut If true, treat this message as though it was received late. It will be displayed in a way that makes it obvious that it is out of order. + * @param skipLinkifier If true, the Linkifier will not be used to turn URLs into hyperlinks. Useful for messages that contain HTML which should be rendered for the user. + */ + addGenericMessage : function(text, contentType, time, senderId, timedOut, skipLinkifier) + { + common.Debug.traceMethodEntered("MessagesPanel.addGenericMessage()"); + + if(timedOut) + { + this.addUnauthoredMessage(localization.OutOfOrderMessage, new Date()); + this._lastSenderId = null; + this._lastUl = null; + + var lateMsg = this._addMessage(text, time, senderId, skipLinkifier); + + Element.addClassName(lateMsg, 'iwc-late-message'); + + // reset state + this._lastSenderId = null; + this._lastUl = null; + } + else + { + this._addMessage(text, time, senderId, skipLinkifier); + } + common.Debug.traceMethodExited("MessagesPanel.addGenericMessage()"); + }, + + /** + * Adds a message to the panel where the message consists of only a hyperlink + * + * @param text The clickable text + * @param url The URL to display when the text is clicked + * @param time The timestamp to display beside the link + * @param senderId The ID of whomever sent the link + * @param timedOut If true, treat this link as though it was received late. It will be displayed in a way that makes it obvious that it is out of order. + */ + addAuthoredLinkMessage : function(text, url, time, senderId, timedOut) + { + common.Debug.traceMethodEntered("MessagesPanel.addAuthoredLinkMessage()"); + + if (text != url) + { + // Trust that caller knows what it's doing. + var linkHtml = this._linkifier.createLink(url, text); + // Since the tag was just added above, tell addGenericMessage() to skip calling the linkifier. + this.addGenericMessage(linkHtml, "text/html", time, senderId, timedOut, true); + } + else + { + // This may be something like "http://www.somewhere.com Try this link". + // Don't simply wrap an tag around it here - let the linkifier scan it + // and put the tags in the appropriate places. + this.addGenericMessage(text, "text/html", time, senderId, timedOut, false); + } + + common.Debug.traceMethodExited("MessagesPanel.addAuthoredLinkMessage()"); + }, + + /** + * Changes the name displayed for a group of messages + * + * @param newName The new name to display + * @param senderId The id of chat participant who sent the messages to be affected + */ + changeMessageGroupsName : function(newName, senderId) + { + var liArray = this._getLIsWithSenderId(senderId); + if(liArray && (liArray.length > 0)) + { + for(var i = 0; i < liArray.length; ++i) + { + this._changeMessageGroupName(liArray[i], newName); + } + } + }, + + /** + * Resets this panel to its original state + */ + reset : function() + { + // initialize members + this._lastSenderId = null; + this._lastUl = null; + this._lastTime = null; + + // remove all list items + if (this._messagesUl.hasChildNodes()) + { + while (this._messagesUl.childNodes.length >= 1) + { + this._messagesUl.removeChild(this._messagesUl.firstChild); + } + } + }, + + // private methods + + _buildDomObject : function() + { + var messagesDiv = this.createElement('div', 'messagesPanel', { 'class': 'iwc-received-messages-panel' }); + this._messagesUl = this.createChildElement(messagesDiv, 'ul'); + return messagesDiv; + }, + + _buildSenderDiv : function(senderId) + { + return this.createElement('div', null, { 'class': this.SENDER_DIV_CLASS }, null, webservices.ParticipantDisplayNameFormatter.formatDisplayNameFromId(senderId)); + }, + + _buildMessage : function(text, time, addDash, skipLinkifier) + { + var li = this.createElement('li', null, { 'class': 'iwc-message' }); + if(time) + { + var e = this._buildMessageTimeDiv(time); + li.appendChild(e); + + if(!this._shouldFadeTimeElement(time)) + { + Element.addClassName(e, 'iwc-message-time-faded'); + } + } + li.appendChild(this._buildMessageTextDiv(text, addDash, skipLinkifier)); + return li; + }, + + _shouldFadeTimeElement : function(time) + { + return !this._isSameMinute(this._lastTime, time); + }, + + _isSameMinute : function(time1, time2) + { + common.ParameterValidation.validate([time1, time2], [ {"type": Date}, {"type": Date} ]); + + if(!time1) + { + return false; + } + + if(!time2) + { + return false; + } + + return ((time1.getFullYear() == time2.getFullYear()) && + (time1.getMonth() == time2.getMonth()) && + (time1.getDate() == time2.getDate()) && + (time1.getHours() == time2.getHours()) && + (time1.getMinutes() == time2.getMinutes())); + }, + + _isSameDay : function(time1, time2) + { + common.ParameterValidation.validate([time1, time2], [ {"type": Date}, {"type": Date} ]); + + if(!time1) + { + return false; + } + + if(!time2) + { + return false; + } + + return ((time1.getFullYear() == time2.getFullYear()) && + (time1.getMonth() == time2.getMonth()) && + (time1.getDate() == time2.getDate())); + }, + + _buildMessageTextDiv : function(text, addDash, skipLinkifier) + { + if (!skipLinkifier) + { + text = this._linkifier.linkifyText(text); + } + + // We want to turn plaintext newlines into HTML, so that the + // web user can see line breaks included in sent messages. + text = text.replace(/\r?\n/g, "
    "); + var element = this.createElement('div', null, { 'class': 'iwc-message-text' }, null, text); + if(addDash) + { + Element.addClassName(element, ((webservices.Utilities.isBrowserIE() && document.body.dir=='rtl') ? 'iwc-arrow-IE-fix' : 'iwc-arrow')); + } + return element; + }, + + _buildMessageTimeDiv : function(time) + { + var div = this.createElement('div', null, { 'class': 'iwc-message-time' }, null, ui.DateTimeFormatter.formatTimeForDisplay(time, !this._isSameDay(this._lastTime, time))); + $j(div).hide(); + + return div; + }, + + _showHiddenMessage : function(message) + { + $j(message).show(); + this._onMessageShown(message); + }, + + _onMessageShown : function(liTag) + { + common.Debug.traceMethodEntered("MessagesPanel._onMessageShown()"); + /* Full HTML structure contains two li tags: + *
      - List of all messages in the chat + *
    • - Container for one or more consecutive messages from the same party. CSS class may be: iwc-system-message-group, iwc-message-from-agent, or iwc-message-from-self. + *
      - Container + *
      Name of sender
      (Not present for system text) + *
        - List of one or more consecutive messages from the same party + *
      • - A single message. CSS class is iwc-message. + *
        - The timestamp of a single message. CSS class is iwc-message-time. + *
        - The text of a single message. CSS class is iwc-message-text. + * Depending on whether the "if" or the "else" in _addMessage() was executed higher in the call stack, the liTag parameter may be either the inner or the outer li tag. + * When a message is added that is from the same party as the previous message, the liTag parameter will be a newly-added inner li. + * When a message is added that is from a different party as the previous message, the old inner ul is closed out, a new outer li is created, and the liTag parameter will be that new outer li. + */ + + var innerLiTag = (liTag.hasClassName('iwc-message') ? liTag : Element.select(liTag, '.iwc-message').pop()); + var textDiv = Element.select(innerLiTag, '.iwc-message-text')[0]; + var timeDiv = Element.select(innerLiTag, '.iwc-message-time')[0]; + + if (textDiv.offsetWidth + timeDiv.scrollWidth > innerLiTag.offsetWidth) + { + common.Debug.traceNote("MessagesPanel._onMessageShown(): Overflow. Inserting space(s)."); + var longestWordLength = this._getLongestWordLength(textDiv.innerHTML); + + // Estimate how many characters will fit in innerLiTag, and subtract 5 just to be safe. + var allowableWordLength = Math.floor((longestWordLength * (innerLiTag.offsetWidth - timeDiv.scrollWidth)) / + textDiv.offsetWidth) - 5; + + // Don't bother breaking words if the user has their browser ridiculously small + if (allowableWordLength > 5) + { + textDiv.innerHTML = this._enforceMaximumWordLength(textDiv.innerHTML, allowableWordLength); + } + } + + // Yield to the browser, so that it can update the UI *before* we scroll to the bottom. Otherwise, due to threading, we may scroll to what *was* the bottom before _enforceMaximumWordLength() inserted a carriage return. + window.setTimeout(this._scrollToBottom.bind(this), 10); + + common.Debug.traceMethodExited("MessagesPanel._onMessageShown()"); + }, + + _scrollToBottom : function() { + this._messagesUl.scrollTop = this._messagesUl.scrollHeight; // Scroll to the bottom, now that the new message is visible + }, + + _getLongestWordLength : function(str) + { + var words = str.split(" "); + + // Find longest word and get length of that. + var longestWordLength = 0; + for (var i=0; i longestWordLength) + { + longestWordLength = words[i].length; + } + } + return longestWordLength; + }, + + _enforceMaximumWordLength : function(str, allowableWordLength) + { + // At this point, special characters that were typed by chat parties have already been converted to HTML entities, e.g. if + // the agent said "your payment must be received in < 2 weeks" then str will contain "<" instead of "<". + // But if the agent said "Go to www.company.com" then str will contain "
        www.company.com". + + if (-1 == str.indexOf('<')) + { + // Easy case - no HTML + var pattern = new RegExp("(\\S{" + allowableWordLength + "})(\\S)"); + var bChanged = true; + while (bChanged) + { + var oldStr = str; + str = str.replace(pattern,"$1 $2"); + bChanged = (oldStr != str); + } + return str; + } else + { + // Harder case - HTML is present + var firstTagBeginIdx = str.indexOf('<'); + var firstTagEndIdx = str.indexOf('>') + 1; + var beforeTag = str.substring(0, firstTagBeginIdx); + var tag = str.substring(firstTagBeginIdx, firstTagEndIdx); + var afterTag = str.substring(firstTagEndIdx); + return this._enforceMaximumWordLength(beforeTag, allowableWordLength) // The part before the first HTML tag. Recurse; will be the easy case. + + tag // The tag itself + + this._enforceMaximumWordLength(afterTag, allowableWordLength); // Recurse on the part after the first HTML tag; may be easy or harder. + } + }, + + _showHiddenTimeElement : function(message) + { + var timeElement = this._getTimeElement(message); + if(timeElement) + { + $j(timeElement).show(); + } + }, + + _getTimeElement : function(message) + { + var timeElements = Element.select(message, '.iwc-message-time'); + if(timeElements && timeElements.length == 1) + { + return timeElements[0]; + } + + return null; + }, + + _addMessage : function(text, time, senderId, skipLinkifier) + { + common.Debug.traceMethodEntered("MessagesPanel._addMessage()"); + var newOuterElement; + var message; + if(!this._lastSenderId || (this._lastSenderId != senderId)) + { + var originalName = null; + if(senderId != webservices.ParticipantRepository.get_systemParticipantId()) + { + originalName = webservices.ParticipantDisplayNameFormatter.formatDisplayNameFromId(senderId); + } + + var messageLi = this.createHiddenChildElement(this._messagesUl, 'li', null, {'senderId': senderId, 'originalName': originalName}); + if (senderId == webservices.ParticipantRepository.get_currentParticipantId()) + { + Element.addClassName(messageLi, 'iwc-message-from-self'); + } + else if(senderId == webservices.ParticipantRepository.get_systemParticipantId()) + { + Element.addClassName(messageLi, 'iwc-system-message-group'); + } + else + { + Element.addClassName(messageLi, 'iwc-message-from-agent'); + } + + var wrapperDiv = this.createChildElement(messageLi, 'div'); + if(senderId != webservices.ParticipantRepository.get_systemParticipantId()) + { + wrapperDiv.appendChild(this._buildSenderDiv(senderId)); + } + + var ul = this.createChildElement(wrapperDiv, 'ul'); + message = this._buildMessage(text, time, (senderId != webservices.ParticipantRepository.get_systemParticipantId()), skipLinkifier); + ul.appendChild(message); + this._showHiddenMessage(messageLi); + + // save the members + this._lastSenderId = senderId; + this._lastUl = ul; + + newOuterElement = messageLi; + } + else + { + message = this._buildMessage(text, time, (senderId != webservices.ParticipantRepository.get_systemParticipantId()), skipLinkifier); + + $j(message).hide(); + this._lastUl.appendChild(message); + this._showHiddenMessage(message); + + newOuterElement = message; + } + + // save this message's time stamp + this._lastTime = time; + + this._showHiddenTimeElement(message); + + common.Debug.traceMethodExited("MessagesPanel._addMessage()"); + return newOuterElement; + }, + + _getLIsWithSenderId : function(senderId) + { + return Element.select(this._messagesUl, '[senderId="' + senderId + '"]'); + }, + + _changeMessageGroupName : function(li, newName) + { + var originalName = this._getOriginalNameFromMessageGroupLi(li); + if(originalName != newName) + { + var senderDiv = this._getSenderDivFromMessageGroupLi(li); + if(senderDiv) + { + this._setNameOnSenderDiv(senderDiv, newName, originalName); + } + } + }, + + _getOriginalNameFromMessageGroupLi : function(li) + { + return Element.readAttribute(li, 'originalName'); + }, + + _getSenderDivFromMessageGroupLi : function(li) + { + var divArray = Element.select(li, 'div[class="' + this.SENDER_DIV_CLASS + '"]'); + if(divArray && (divArray.length > 0)) + { + return divArray[0]; + } + + return null; + }, + + _setNameOnSenderDiv : function(senderDiv, newName, originalName) + { + if(senderDiv) + { + senderDiv.innerHTML = newName + "(" + originalName + ")"; + } + } +}); + +/** + * WebServicesReceivedMessagesPanel class + * This class handles the logic functionality of the panel that displays the transcript + * of past messages in the chat. The UI functionality is implemented in MessagesPanel. + * + */ +ui.WebServicesReceivedMessagesPanel = Class.create(ui.MessagesPanel, +{ + /** + * Constructor + * + * @param acceptHtml If true, HTML-formatted messages are allowed to be displayed. If false, any HTML tags received will be displayed to the user, instead of rendered. + */ + initialize:function($super, acceptHtml) + { + var numArgs = 2; + if(arguments.length != numArgs) + { + throw common.ExceptionFactory.createException("WebServicesReceivedMessagesPanel constructor called with " + arguments.length + " arguments, but expected " + numArgs + "."); + } + + $super(acceptHtml); + + this.addImplementedInterface(webservices.Interfaces.IParticipantJoinedNotificationObserver, webservices); + this.addImplementedInterface(webservices.Interfaces.IParticipantLeftNotificationObserver, webservices); + this.addImplementedInterface(webservices.Interfaces.IParticipantVoicemailNotificationObserver, webservices); + this.addImplementedInterface(webservices.Interfaces.IReceivedTextNotificationObserver, webservices); + this.addImplementedInterface(webservices.Interfaces.IReceivedUrlNotificationObserver, webservices); + this.addImplementedInterface(webservices.Interfaces.IReceivedFileNotificationObserver, webservices); + this.addImplementedInterface(webservices.Interfaces.IFailoverUINotificationObserver, webservices); + this.addImplementedInterface(webservices.Interfaces.IResumedPollingNotificationObserver, webservices); + this.addImplementedInterface(webservices.Interfaces.IChatReconnectFailureNotificationObserver, webservices); + this.addImplementedInterface(webservices.Interfaces.IRefreshPageNotificationObserver, webservices); + + this._isConnected = true; + this._showMoreMessages = true; + this._isFirstAttempt = true; + }, + + // public methods + + /** + * Implementation of IParticipantJoinedNotificationObserver + * Currently does nothing - stubbed here for future enhancement. + * + * @param notification Something that implements of IParticipantJoinedNotification + */ + processParticipantJoinedNotification : function(notification) + { + common.Interface.ensureImplements(notification, webservices.Interfaces.IParticipantJoinedNotification); + }, + + /** + * Implementation of IParticipantLeftNotificationObserver + * Adds a message to the panel indicating that the participant has left the chat. + * + * @param notification Something that implements IParticipantLeftNotification + */ + processParticipantLeftNotification : function(notification) + { + common.Interface.ensureImplements(notification, webservices.Interfaces.IParticipantLeftNotification); + + if(this._showMoreMessages) + { + if(notification.get_participantId() == webservices.ParticipantRepository.get_currentParticipantId()) + { + this._addSystemMessage(localization.DisconnectedFromChat, notification.get_dateTime(), notification.get_isTimedOut()); + } + } + }, + + /** + * Implementation of IReceivedTextNotificationObserver + * Adds the received message to the panel. + * + * @param notification Something that implements IReceivedTextNotification + */ + processReceivedTextNotification : function(notification) + { + common.Interface.ensureImplements(notification, webservices.Interfaces.IReceivedTextNotification); + + if(this._showMoreMessages) + { + this.addAuthoredMessage(notification.get_messageText(), notification.get_contentType(), notification.get_dateTime(), notification.get_participantId(), notification.get_isTimedOut()); + } + }, + + /** + * Implementation of IReceivedUrlNotificationObserver + * Adds the received URL to the panel. + * + * @param notification Something that implements IReceivedUrlNotification + */ + processReceivedUrlNotification : function(notification) + { + common.Interface.ensureImplements(notification, webservices.Interfaces.IReceivedUrlNotification); + + if(this._showMoreMessages) + { + this.addAuthoredLinkMessage(notification.get_messageUrl(), notification.get_messageUrl(), notification.get_dateTime(), notification.get_participantId(), notification.get_isTimedOut()); + } + }, + + /** + * Implementation of IReceivedFileNotificationObserver + * Adds a link to the received file to the panel. + * + * @param notification Something that implements IReceivedFileNotification + */ + processReceivedFileNotification : function(notification) + { + common.Interface.ensureImplements(notification, webservices.Interfaces.IReceivedFileNotification); + + if(this._showMoreMessages) + { + this.addAuthoredLinkMessage(this._getFileName(notification.get_messageRelativeUrl()), this._createFullUrl(notification.get_messageRelativeUrl()), notification.get_dateTime(), notification.get_participantId(), notification.get_isTimedOut()); + } + }, + + /** + * Implementation of IFailoverUINotificationObserver + * Adds a message to the panel indicating there was an error connecting to the server. + * + * @param notification Something that implements IFailoverUINotification + */ + processFailoverUINotification : function(notification) + { + common.Interface.ensureImplements(notification, webservices.Interfaces.IFailoverUINotification); + + if(this._showMoreMessages) + { + if(this._isConnected) + { + this._isConnected = false; + } + } + }, + + /** + * Implementation of IResumedPollingNotificationObserver + * Adds a message to the panel indicating successful resumption of polling + * + * @param notification Something that implements IResumedPollingNotification + */ + processResumedPollingNotification : function(notification) + { + common.Interface.ensureImplements(notification, webservices.Interfaces.IResumedPollingNotification); + + if(this._showMoreMessages) + { + if(!this._isConnected) + { + this._isConnected = true; + if(!this._isFirstAttempt) + { + this._addSystemMessage(localization.SuccessfullyReconnectedServer, new Date(), false); + this._isFirstAttempt = true; //reset the status + } + + } + } + }, + + /** + * Implementation of IChatReconnectFailureNotificationObserver + * Adds a message to the panel indicating failure to reconnect a chat to the server + * + * @param notification Something that implements IChatReconnectFailureNotification + */ + processChatReconnectFailureNotification : function(notification) + { + common.Interface.ensureImplements(notification, webservices.Interfaces.IChatReconnectFailureNotification); +/* + this._isFirstAttempt = false; + if(this._showMoreMessages) + { + this._addSystemMessage(localization.CouldNotConnectServerRetry, new Date(), false); + } +*/ + }, + + /** + * Implementation of IRefreshPageNotificationObserver + * Adds a message to the panel instructing the user to refresh the page to begin a new chat. + * + * @param notification Something that implements IRefreshPageNotification + */ + processRefreshPageNotification : function(notification) + { + common.Interface.ensureImplements(notification, webservices.Interfaces.IRefreshPageNotification); + + if(this._showMoreMessages) + { + var url; + + if(notification.get_newUriFragment()) + { + url = webservices.Utilities.appendQueryStringParameterToUrl(location.href, "server", notification.get_newUriFragment()); + } + else + { + url = webservices.Utilities.removeEndingPoundCharacter(location.href); + } + + var msg = localization.NeedPageRefresh_Format.replace("{0}", url); + this._addSystemMessage(msg, new Date(), false); + + // if we got this notification, no matter what else we get, we can't act on it + this._showMoreMessages = false; + } + }, + + /** + * Implementation of IParticipantVoicemailNotificationObserver + * Adds a message to the panel instructing the user to leave a message, since no agents are available. + * + * @param notification Something that implements IParticipantVoicemailNotification + */ + processParticipantVoicemailNotification : function(notification) + { + common.Interface.ensureImplements(notification, webservices.Interfaces.IParticipantVoicemailNotification); + + if(this._showMoreMessages) + { + var text = localization.PleaseLeaveMessage.replace('%0', this._formatName(notification.get_participantId())); + this._addSystemMessage(text, notification.get_dateTime(), notification.get_isTimedOut()); + } + }, + + // private methods + + _formatName : function(id, name) + { + if(!name) + { + var participant = webservices.ParticipantRepository.get_participant(id); + if(participant) + { + name = participant.get_name(); + } + } + + return webservices.ParticipantDisplayNameFormatter.formatDisplayNameFromIdAndName(id, name); + }, + + _getFileName : function(relativeUrl) + { + return webservices.Utilities.getFileNameFromUrl(relativeUrl); + }, + + _createFullUrl : function(relativeUrl) + { + return webservices.Servers.buildUrl(webservices.Servers.CurrentUriFragment, relativeUrl); + }, + + _addSystemMessage : function(text, dateTime, get_isTimedOut) + { + this.addUnauthoredMessage(text, dateTime, get_isTimedOut); + } +}); + +// Register namespaces +ui.registerChildNamespace("_Internal"); + +/** + * (UI) LanguageCodeConverter class + * This extends webservices.LanguageCodeConverter (note the package name) to + * provide additional language code conversion functionality specific to the UI implementation. At this time, + * that includes only functionality pertaining to the CK Editor. + * + * Provides methods pertaining to the use of language codes (aka IETF Tags). Examples of these tags are: + * en-US = English as spoken in the US + * en-GB = English as spoken in Great Britain + * de-CH = German as spoken in Switzerland + * ...etc. + * + * Note that generally the region portion of a language code is capitalized, but this is merely a convention, and + * this web application does not follow that convention. + * + * There is no need to instantiate this class - a singleton instance called ui.LanguageCodeConverter is available. + */ +ui._Internal.LanguageCodeConverter = Class.create(webservices.LanguageCodeConverter, { + /** + * Constructor does nothing because all the functionality is essentially static + */ + initialize : function() { + }, + + /** + * Returns the language code which CKEditor expects, given a language code used by IC. + * If CKEditor doesn't use the exact same language codes as IC, this is the place to convert them. + * + * @param languageCode The language code in use by IC. + */ + convertLanguageCodeToCKEditorLanguageCode : function(languageCode) + { + if (!languageCode) + { + return "en"; + } + + // All string comparisons below should be case-insensitive, so convert languageCode + // to lower case. Any future language codes or tokens that are added below should + // be added in lower case! + languageCode = languageCode.toLowerCase(); + + var firstToken = this.getFirstToken(languageCode); + + if(firstToken == "af") return "af"; + if(firstToken == "ar") return "ar"; + if(firstToken == "bg") return "bg"; + if(firstToken == "bn") return "bn"; + if(firstToken == "bs") return "bs"; + if(firstToken == "ca") return "ca"; + if(firstToken == "cs") return "cs"; + if(firstToken == "da") return "da"; + if(firstToken == "de") return "de"; + if(firstToken == "el") return "el"; + if(languageCode == "en-au") return "en-au"; + if(languageCode == "en-ca") return "en-ca"; + if(languageCode == "en-uk") return "en-uk"; + if(firstToken == "en") return "en"; + if(firstToken == "eo") return "eo"; + if(firstToken == "es") return "es"; + if(firstToken == "et") return "et"; + if(firstToken == "eu") return "eu"; + if(firstToken == "fa") return "fa"; + if(firstToken == "fi") return "fi"; + if(firstToken == "fo") return "fo"; + if(languageCode == "fr-ca") return "fr-ca"; + if(firstToken == "fr") return "fr"; + if(firstToken == "gl") return "gl"; + if(firstToken == "gu") return "gu"; + if(firstToken == "he") return "he"; + if(firstToken == "hi") return "hi"; + if(firstToken == "hr") return "hr"; + if(firstToken == "hu") return "hu"; + if(firstToken == "is") return "is"; + if(firstToken == "it") return "it"; + if(firstToken == "ja") return "ja"; + if(firstToken == "km") return "km"; + if(firstToken == "ko") return "ko"; + if(firstToken == "lt") return "lt"; + if(firstToken == "lv") return "lv"; + if(firstToken == "mn") return "mn"; + if(firstToken == "ms") return "ms"; + if(firstToken == "nb") return "nb"; + if(firstToken == "nl") return "nl"; + if(firstToken == "no") return "no"; + if(firstToken == "pl") return "pl"; + if(languageCode == "pt-br") return "pt-br"; + if(firstToken == "pt") return "pt"; + if(firstToken == "ro") return "ro"; + if(firstToken == "ru") return "ru"; + if(firstToken == "sk") return "sk"; + if(firstToken == "sl") return "sl"; + if(languageCode == "sr-latn") return "sr-latn"; + if(firstToken == "sr") return "sr"; + if(firstToken == "sv") return "sv"; + if(firstToken == "th") return "th"; + if(firstToken == "tr") return "tr"; + if(firstToken == "uk") return "uk"; + if(firstToken == "vi") return "vi"; + if(languageCode == "zh-cn") return "zh-cn"; + if(firstToken == "zh") return "zh"; + + return "en"; + }, + + currentLanguageIsRightToLeft : function() + { + return ("rtl" == localization.TextDirection); + } +}); +ui.LanguageCodeConverter = new ui._Internal.LanguageCodeConverter(); + +/** + * ComposeMessagePanel class + * + * Handles the UI of the panel in which the web user may type messages to the agent. For the + * logic, @see WebServicesComposeMessagePanel. + */ +ui.ComposeMessagePanel = Class.create(ui.Control, +{ + /** + * Constructor + * + * @param useHtmlEditor If true, WYSIWYG controls will be displayed. If false, only a plain text editor will be displayed. Note that the WYSIWYG editor is not supported at this time. + * @param languageCode The code (i.e. fr-ca) that specifies which language the editor's tooltips, etc. should appear in. Case-insensitive. + */ + initialize : function($super, useHtmlEditor, languageCode) + { + if((arguments.length < 1) || (arguments.length > 3)) + { + throw common.ExceptionFactory.createException("ComposeMessagePanel constructor called with " + arguments.length + " arguments, but expected 1 - 3."); + } + + // initialize members + this._editor = null; + this._useHtmlEditor = (useHtmlEditor === true); + this._languageCode = languageCode; + this._isSendingEnabled = true; + + $super(this._buildDomObject()); + this._validateDomObject(); + + this._enableSendButton(false); + + this._attachHandlers(); + }, + + /** + * destructor + */ + destroy : function() + { + ui.Control.prototype.destroy.call(this); + }, + + // public methods + + /** + * Returns true if WYSIWYG controls are/will be displayed. Returns false if only a plain text editor is/will be displayed. + * Note that the WYSIWYG editor is not supported at this time. + */ + get_useHtmlEditor : function() + { + return this._useHtmlEditor; + }, + + /** + * Enable or disable the panel. + * + * @param enabled If true, the panel will be enabled. If false, the panel will be disabled. + */ + enable : function(enabled) + { + this._textBoxDomObject.disabled = !enabled; + + if (enabled) + { + this._enableSendButtonTimer.start(); + } else + { + this._enableSendButtonTimer.stop(); + } + + if(this._isMessageTextEmpty()) + { + this._buttonDomObject.disabled = true; + } + else + { + this._buttonDomObject.disabled = !enabled; + } + }, + + /** + * Resets the panel back to its original state. Text will be cleared and the send button will be disabled. + */ + reset : function() + { + this._clearMessageText(); + this._enableSendButton(false); + }, + + /** + * Should be called when the panel receives focus. Simply diverts focus to the text entry field within the panel. + */ + focus : function() + { + this._focusEditTextBox(); + }, + + /** + * Enable the panel to send text, or disable the panel from sending text. + * If the parameter is true AND text is present in the text box, the send button will be enabled. + * Otherwise the send button will be disabled. + * + * @param enable If true, sending will be enabled. If false, sending will be disabled. + */ + enableSending : function(enable) + { + this._isSendingEnabled = enable; + this._enableSendButton(!this._isMessageTextEmpty()); + }, + + // private methods + + _attachHandlers : function() + { + Event.observe(this._buttonDomObject, 'click', this._onClickSendButton.bindAsEventListener(this)); + + if(!this._useHtmlEditor) + { + this._textBoxDomObject.observe('keyup', this._onKeyUpTextBox.bindAsEventListener(this)); + } + + this._enableSendButtonTimer = new webservices.RecurringTimer(300); + var _self = this; + this._enableSendButtonTimer.registerSuccessListener(function() { _self._onEnableSendButtonTimer(); }); + + this._sendOnEnterCheckboxDomObject.observe('change', this._onChangeSendOnEnterCheckbox.bindAsEventListener(this)); + }, + + _onEnableSendButtonTimer: function() + { + this._enableSendButton(!this._isMessageTextEmpty()); + }, + + _onKeyUpTextBox : function(evt) + { + if(this._isEnterKey(evt)) + { + if(!this._isMessageTextEmpty()) + { + if (this._sendOnEnterCheckboxDomObject.checked) + { + this._onClickSendButton(); + } + } + else + { + this._clearMessageText(); + } + } + }, + + _isEnterKey : function(evt) + { + if(this._useHtmlEditor) + { + return (!evt.data.$.shiftKey && (evt.data.getKey() == 13)); + } + else + { + return !evt.shiftKey && (evt.keyCode == 13); + } + }, + + _onClickSendButton : function() + { + if(this._isSendingEnabled) + { + var text = this._getMessageText(); + if(text) + { + this._clearMessageText(); + this._enableSendButton(false); + this._sendMessage(text); + this._focusEditTextBox(); + } + } + }, + + _onChangeSendOnEnterCheckbox : function(evt) + { + evt.stop(); + this._focusEditTextBox(); + }, + + _onMessageSendFailed : function(errorMessage, messageThatFailedToSend) + { + common.Debug.traceMethodEntered("ComposeMessagePanel._onMessageSendFailed()"); + /* + if (errorMessage) + { + // TODO: Make MainPanel implement IStatusManager and call setErrorStatus() on it. Also make _onClickSendButton() call that.clearStatus() + } + */ + + if (messageThatFailedToSend) + { + this._setMessageText(messageThatFailedToSend); + } + + this._enableSendButton(true); + this._focusEditTextBox(); + common.Debug.traceMethodExited("ComposeMessagePanel._onMessageSendFailed()"); + }, + + _sendMessage : function(text) + { + window.alert("Due to a programmatic error, this message can not be sent. A problem report will be created.\n" + text); + webservices.ProblemReporter.sendProblemReport("Programmer error - abstract method not overridden. This code should be unreachable.", "ComposeMessagePanel._sendMessage()"); + }, + + _enableSendButton : function(enabled) + { + if(!this._isSendingEnabled) + { + enabled = false; + } + + this._buttonDomObject.disabled = !enabled; + }, + + _getMessageText : function() + { + var text = ''; + + if(this._useHtmlEditor) + { + var editor = this._getEditor(); + if(editor) + { + text = editor.getData(); + text = this._cleanHtmlOutput(text); + } + } + else + { + text = this._textBoxDomObject.value; + text = this._cleanPlainTextOutput(text); + } + + return text; + }, + + _cleanHtmlOutput : function(text) + { + var strippedText = text; + + var whitespaceArray = ['

        \n\t 

        ', '\n', '\r', '\t', ' ', '

        ', '

        ', '
        ', '
        ']; + strippedText = this._removeAllPrefixes(strippedText, whitespaceArray); + strippedText = this._removeAllSuffixes(strippedText, whitespaceArray); + + return strippedText; + }, + + _removeAllPrefixes : function(text, prefixArray) + { + var strippedText = text; + + var originalText = null; + do + { + originalText = strippedText; + strippedText = this._removePrefixes(originalText, prefixArray); + } + while(strippedText != originalText); + + return strippedText; + }, + + _removePrefixes : function(text, prefixArray) + { + for(var i = 0; i < prefixArray.length; ++i) + { + text = this._removePrefix(text, prefixArray[i]); + } + + return text; + }, + + _removePrefix : function(text, prefix) + { + if(text.length >= prefix.length) + { + if(text.substr(0, prefix.length) == prefix) + { + return text.substr(prefix.length, text.length - prefix.length); + } + } + + return text; + }, + + _removeAllSuffixes : function(text, suffixArray) + { + var strippedText = text; + + var originalText = null; + do + { + originalText = strippedText; + strippedText = this._removeSuffixes(originalText, suffixArray); + } + while(strippedText != originalText); + + return strippedText; + }, + + _removeSuffixes : function(text, suffixArray) + { + for(var i = 0; i < suffixArray.length; ++i) + { + text = this._removeSuffix(text, suffixArray[i]); + } + + return text; + }, + + _removeSuffix : function(text, suffix) + { + if(text.length >= suffix.length) + { + if(text.substr(text.length - suffix.length, suffix.length) == suffix) + { + return text.substr(0, text.length - suffix.length); + } + } + + return text; + }, + + _cleanPlainTextOutput : function(text) + { + if((text.length > 0) && (text[0] == '\n')) + { + text = text.substring(1); + } + return text; + }, + + _clearMessageText : function() + { + this._setMessageText(''); + }, + + _setMessageText : function(text) + { + if(this._useHtmlEditor) + { + var editor = this._getEditor(); + if(editor) + { + this._setEditorText(text); + } + } + else + { + this._textBoxDomObject.value = text; + } + }, + + _setEditorText : function(text) + { + // this works, but it seems to undo the keyup handler and can't put it back + // editor.setData(text); + + var iframe = window.frames[0]; + if(iframe.document) + { + var bodies = iframe.document.getElementsByTagName('body'); + if(bodies && bodies.length == 1) + { + var body = bodies[0]; + body.innerHTML = text; + } + } + }, + + _isMessageTextEmpty : function() + { + if (this._getMessageText().length === 0) + { + return true; + } + return this._getMessageText().strip().length === 0; + }, + + _getEditor : function() + { + return this._editor; +// return CKEDITOR.instances['inputPanel']; + }, + + _focusEditTextBox : function() + { + try + { + if(this._useHtmlEditor) + { + this._getEditor().focus(); + } + else + { + this._textBoxDomObject.focus(); + } + } catch (e) + { + } + }, + + _validateDomObject : function() + { + if(!this._textBoxDomObject) + { + throw common.ExceptionFactory.createException("Text box row not found;"); + } + + if(!this._buttonDomObject) + { + throw common.ExceptionFactory.createException("Button not found;"); + } + }, + + _buildDomObject : function() + { + var domObject = this.createElement('div', null, { 'class': 'iwc-compose-message-panel-container' }); + var panel = this.createChildElement(domObject, 'div', null, { 'class': 'iwc-compose-message-panel', 'useHtmlEditor': (this._useHtmlEditor ? 'true' : 'false') }); + var formObject = this.createChildElement(panel, 'form', null, {'class': 'iwc-compose-message-form', 'action': '#'}); + formObject.onsubmit = function() { return false; } + this._textBoxDomObject = this.createChildElement(formObject, 'textarea', 'inputPanel', { 'class': 'iwc-input' }); + this._buttonDomObject = this.createChildElement(formObject, 'input', 'sendButton', { 'type': 'button', 'value': localization.Send, 'class': 'iwc-send-button' }); + + if(this._useHtmlEditor) + { + this._useCkEditor(); + } + + var sendOnEnterContainer = this.createChildElement(domObject, 'div', null, { 'class': 'iwc-send-on-enter-container' }); + this.createChildElement(sendOnEnterContainer, 'label', null, { 'class': 'iwc-send-on-enter-checkbox-label' }, null, localization.SendOnEnter); + var defaultSendOnEnter = true; + try + { + defaultSendOnEnter = webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.SendOnEnter).getSendOnEnterByDefault(this._languageCode); + } catch (e) + { + common.Debug.traceError('Caught exception trying to get default send-on-enter behaviour: ' + e); + // Just let it be true + } + this._sendOnEnterCheckboxDomObject = this.createChildElement(sendOnEnterContainer, 'input', null, { 'type': 'checkbox', 'checked': defaultSendOnEnter, 'class': 'iwc-send-on-enter-checkbox' }); + this._sendOnEnterCheckboxDomObject.checked = defaultSendOnEnter; // Specifying checked in the attributes above fails for IE8 + + return domObject; + }, + + _useCkEditor : function() + { + var options = { skin : 'office2003', + width: '602px', + height: '60px', + toolbar : + [ + [ 'FontSize', 'Bold', 'Italic', '-', 'TextColor', 'BGColor' ] + ] + }; + + if(this._languageCode) + { + options.language = ui.LanguageCodeConverter.convertLanguageCodeToCKEditorLanguageCode(this._languageCode); + } + + this._editor = CKEDITOR.replace(this._textBoxDomObject, options); + + this._editor.config.resize_enabled = false; + this._editor.config.toolbarCanCollapse = false; + + // even though the document is supposed to be loaded, we still need to give CKEditor a little extra time + // before attaching handlers + this._setCKActionsTimeout(); + }, + + _setCKActionsTimeout : function() + { + window.setTimeout(this._postTimeoutCKActions.bindAsEventListener(this), 1000); + }, + + _postTimeoutCKActions : function() + { + if(!this._readyForCKActions()) + { + this._setCKActionsTimeout(); + } + else + { + this._attachCKHandlers(); + this._hideStatusBar(); + } + }, + + _readyForCKActions : function() + { + var editor = this._getEditor(); + if(editor) + { + return !(!(editor.document)); + } + + return false; + }, + + _hideStatusBar : function() + { + var ckeditorArray = Element.select(this.get_domObject(), '.cke_editor'); + if(ckeditorArray && ckeditorArray.length == 1) + { + var tableRowArray = Element.select(ckeditorArray[0], 'tr'); + if(tableRowArray && tableRowArray.length == 3) + { + Element.setStyle(tableRowArray[2], {display: 'none'}); + } + } + }, + + _attachCKHandlers : function() + { + var editor = this._getEditor(); + var editorDocument = editor.document; + var self = this; + editorDocument.on('keyup', function(event) + { + self._onKeyUpTextBox(event); + }); + } +}); + +/** + * WebServicesComposeMessagePanel class + * + * Handles the logic of the panel in which the web user may type messages to the agent. For the + * UI, @see ComposeMessagePanel. + */ +ui.WebServicesComposeMessagePanel = Class.create(ui.ComposeMessagePanel, +{ + /** + * Constructor + * + * @param typingIndicator An instance of a class derived from webservicesTypingIndicatorBase, which will be notified when the user starts or stops typing. + * @param useHtmlEditor If true, a WYSIWYG editor will be displayed. If false, a plain text editor will be displayed. Note that the WYSIWYG editor is not supported at this time. + * @param languageCode The code (i.e. fr-ca) that specifies which language the editor's tooltips, etc. should appear in. Case-insensitive. + */ + initialize : function($super, typingIndicator, useHtmlEditor, languageCode) + { + if((arguments.length == 0) || (arguments.length > 4)) + { + throw common.ExceptionFactory.createException("WebServicesComposeMessagePanel constructor called with " + arguments.length + " arguments, but expected 1-3."); + } + + $super(useHtmlEditor, languageCode); + + this.addImplementedInterface(webservices.Interfaces.IChatCreationNotificationObserver, webservices); + this.addImplementedInterface(webservices.Interfaces.IParticipantLeftNotificationObserver, webservices); + this.addImplementedInterface(webservices.Interfaces.IFailoverUINotificationObserver, webservices); + this.addImplementedInterface(webservices.Interfaces.IResumedPollingNotificationObserver, webservices); + this.addImplementedInterface(webservices.Interfaces.IRefreshPageNotificationObserver, webservices); + + this._typingIndicator = typingIndicator; + this._awaitingReconnect = false; + this._neverReenable = false; + }, + + // public methods + + /** + * Setter for the chat manager + * + * @param chatManager An instance of a subclass of webservices.ChatManagerBase + */ + set_chatManager : function(chatManager) + { + this._chatManager = chatManager; + }, + + /** + * Disables this panel when the current user ceases being a participant in the chat. + * + * @param notification Something that implements webservices.Interfaces.IParticipantLeftNotification + */ + processParticipantLeftNotification : function(notification) + { + common.Interface.ensureImplements(notification, webservices.Interfaces.IParticipantLeftNotification); + if(notification.get_participantId() == webservices.ParticipantRepository.get_currentParticipantId()) + { + this.enable(false); + } + }, + + /** + * Event listener for failovers + * + * @param notification Something that implements webservices.Interfaces.IFailoverNotification + */ + processFailoverUINotification : function(notification) + { + common.Interface.ensureImplements(notification, webservices.Interfaces.IFailoverUINotification); + + this._awaitingReconnect = true; + this.enable(false); + this.enableSending(false); + }, + + /** + * Event listener for resumption of polling following reconnection of the chat + * + * @param notification Something that implements webservices.Interfaces.IResumedPollingNotification + */ + processResumedPollingNotification : function(notification) + { + common.Interface.ensureImplements(notification, webservices.Interfaces.IResumedPollingNotification); + + if(!this._neverReenable) + { + this._awaitingReconnect = false; + this.enable(true); + this.enableSending(true); + } + }, + + /** + * Event listener for creation of a chat + * + * @param notification Something that implements webservices.Interfaces.IChatCreationNotification + */ + processChatCreationNotification : function(notification) + { + common.Interface.ensureImplements(notification, webservices.Interfaces.IChatCreationNotification); + + if(!this._neverReenable) + { + this._awaitingReconnect = false; + this.enableSending(true); + } + }, + + /** + * Event listener. Will be called if the user refreshes the page. Disables the panel AND makes it so that it + * can never be enabled again, since it is no longer visible. + * + * @param notification Something that implements IRefreshPageNotification + */ + processRefreshPageNotification : function(notification) + { + common.Interface.ensureImplements(notification, webservices.Interfaces.IRefreshPageNotification); + + this.enable(false); + this.enableSending(false); + + // if we got this notification, no matter what else we get, we can't act on it + this._neverReenable = true; + }, + + // private methods + + /** + * This class is a subclass of ui.ComposeMessagePanel. This method essentially calls + * super.enable(). However, if the chat is awaiting reconnection or in a state in which it must never + * be re-enabled, the parameter to super.enable() will be false, regardless of what is passed to this method. + * + * @param enabled If true AND the chat is connected, the UI panel will be enabled. If false OR the chat is not connected, the UI panel will be disabled. + */ + enable : function(enabled) + { + if(this._awaitingReconnect || this._neverReenable) + { + enabled = false; + } + ui.ComposeMessagePanel.prototype.enable.call(this, enabled); + }, + + /** + * This class is a subclass of ui.ComposeMessagePanel. This method essentially calls + * super.enableSending(). However, if the chat is not connected, the parameter to super.enableSending() will be + * false, regardless of what is passed to this method. + * + * @param enabled If true AND the chat is connected, the UI panel will indicate that sending is enabled. If false OR the chat is not connected, the UI panel will indicate that sending is disabled. + */ + enableSending : function(enabled) + { + if(this._awaitingReconnect || this._neverReenable) + { + enabled = false; + } + ui.ComposeMessagePanel.prototype.enableSending.call(this, enabled); + }, + + _onKeyUpTextBox : function(evt) + { + try + { + this._typingIndicator.keyPressed(); + ui.ComposeMessagePanel.prototype._onKeyUpTextBox.call(this, evt); // Allow superclass to also handle the event + } + catch(ex) + { + common.Debug.traceError(ex.message); + common.Debug.alert(ex.message); + webservices.ProblemReporter.sendProblemReport(ex, "WebServicesComposeMessagePanel._onKeyUpTextBox()"); + } + }, + + _sendMessage : function(text) + { + this._chatManager.sendMessage(text, this.get_useHtmlEditor(), this._onMessageSendAttemptCompleted.bind(this, text)); + }, + + /** + * Called when an attempt to send a message has finished, whether successful or not. + * + * @param text The message that we attempted to send + * @param success Whether the attempt succeeded + * @param response An instance of ChatResponse. All the events, etc. will have already been handled before getting to this point - it is included here only to allow any error condition to be examined. + */ + _onMessageSendAttemptCompleted : function(text, success, response) + { + common.Debug.traceMethodEntered("WebServicesComposeMessagePanel._onMessageSendAttemptCompleted()"); + if(!success) + { + common.Debug.traceError(ui.ErrorDisplayTextBuilder.build(response.get_statusReason(), 'Failed to send message')); + webservices.ProblemReporter.sendProblemReport(response, "WebServicesComposeMessagePanel._sendCallback()"); + + this._onMessageSendFailed(localization.FailedToSendMessage, text); + } + common.Debug.traceMethodExited("WebServicesComposeMessagePanel._onMessageSendAttemptCompleted()"); + } +}); + +/** + * ParticipantsPanel class + * This is the panel that displays the list of participants that are currently members of the chat. + */ +ui.ParticipantsPanel = Class.create(ui.Control, +{ + /** + * Constructor + */ + initialize:function($super) + { + var numArgs = 1; + if(arguments.length != numArgs) + { + throw common.ExceptionFactory.createException("ParticipantsPanel constructor called with " + arguments.length + " arguments, but expected " + numArgs + "."); + } + + $super(this._buildDomObject()); + this._validateDomObject(); + }, + + /** + * Destructor + */ + destroy : function() + { + ui.Control.prototype.destroy.call(this); + }, + + // public methods + + /** + * This method resets the panel to its original state. It clears the list of participants. + */ + reset : function() + { + this._list.innerHTML = ''; + }, + + // private methods + + _buildParticipantDomObjectId : function(participantId) + { + return 'iwc-participant-' + participantId; + }, + + _getParticipantDomObject : function(participantId) + { + return document.getElementById(this._buildParticipantDomObjectId(participantId)); + }, + + _getParticipantNameElements : function(participantId) + { + var domObject = this._getParticipantDomObject(participantId); + if(domObject) + { + return Element.select(domObject, '[participant-name=true]'); + } + + return null; + }, + + _getParticipantImageDivs : function(participantId) + { + var domObject = this._getParticipantDomObject(participantId); + if(domObject) + { + return Element.select(domObject, '[participant-avatar=true]'); + } + + return null; + }, + + _getParticipantSupplementalInfo : function(participantId) + { + var domObject = this._getParticipantDomObject(participantId); + if(!domObject) + { + common.Debug.traceWarning('No participant with id: ' + participantId); + } + else + { + var elementArray = Element.select(domObject, '.iwc-chat-participant-popover-supplementalInfo'); + if(elementArray && elementArray.length == 1) + { + return elementArray[0]; + } + } + return null; + }, + + _getParticipantPopover : function(participantId) + { + var domObject = this._getParticipantDomObject(participantId); + if(!domObject) + { + common.Debug.traceWarning('No participant with id: ' + participantId); + } + else + { + var elementArray = Element.select(domObject, 'div.iwc-chat-participant-popover'); + if(elementArray && elementArray.length == 1) + { + return elementArray[0]; + } + } + return null; + }, + + _addParticipantSelf : function(participantId, participantName) + { + var participantLi = this._addParticipant(participantId, participantName); + Element.addClassName(participantLi, 'iwc-participant-self'); + }, + + _addParticipantOther : function(participantId, participantName) + { + var participantLi = this._addParticipant(participantId, participantName); + + // Add popover div + var popoverDiv = this.createChildElement(participantLi, 'div', null, { 'class': 'iwc-chat-participant-popover iwc-rounded-box' }, null); + var table = this.createChildElement(popoverDiv, 'table', null, {'cellpadding': '0', 'cellspacing': '0'}); + var tbody = this.createChildElement(table, 'tbody'); + var tr = this.createChildElement(tbody, 'tr'); + + // image td + var leftTd = this.createChildElement(tr, 'td'); + var leftTdDiv = this.createChildElement(leftTd, 'div', null, { 'class': 'iwc-chat-participant-popover-avatar-div' }, null); + this.createChildElement(leftTdDiv, 'div', null, { 'participant-avatar':'true', 'class': 'iwc-chat-participant-popover-avatar' }); + + // text td + var rightTd = this.createChildElement(tr, 'td'); + this.createChildElement(rightTd, 'span', null, { 'participant-name':'true', 'class': 'iwc-chat-participant-popover-name' }, null, webservices.ParticipantDisplayNameFormatter.formatDisplayNameFromIdAndName(participantId, participantName)); + this.createChildElement(rightTd, 'br'); + this.createChildElement(rightTd, 'span', null, { 'class': 'iwc-chat-participant-popover-supplementalInfo' }); + + Element.hide(popoverDiv); + var _self = this; + Element.observe(participantLi, 'mouseover', this._showParticipantPopover.bind(_self, participantId)); + Element.observe(participantLi, 'mouseout', this._hideParticipantPopover.bind(_self, participantId)); + }, + + _addParticipant : function(participantId, participantName) + { + var participantLi = this.createChildElement(this._list, 'li', this._buildParticipantDomObjectId(participantId)); + var table = this.createChildElement(participantLi, 'table', null, {'cellpadding': '0', 'cellspacing': '0'}); + var tbody = this.createChildElement(table, 'tbody'); + var tr = this.createChildElement(tbody, 'tr'); + + // avatar td + var leftTd = this.createChildElement(tr, 'td'); + var imgDiv = this.createChildElement(leftTd, 'div'); + this.createChildElement(imgDiv, 'div', null, { 'participant-avatar':'true', 'class': 'iwc-chat-participant-avatar' }); + + // typing indicator image td + var middleTd = this.createChildElement(tr, 'td'); + var imgDiv = this.createChildElement(middleTd, 'div', null, { 'class':'iwc-participant-typing-indicator-img' }); + + // text td + var rightTd = this.createChildElement(tr, 'td'); + this.createChildElement(rightTd, 'span', null, { 'participant-name':'true', 'class': 'iwc-participant-name' }, null, webservices.ParticipantDisplayNameFormatter.formatDisplayNameFromIdAndName(participantId, participantName)); + this.createChildElement(rightTd, 'span', null, { 'class': 'iwc-participant-typing-status' }, null, localization.Typing); + + return participantLi; + }, + + _removeParticipant : function(participantId) + { + var domObject = this._getParticipantDomObject(participantId); + if(domObject) + { + Element.remove(domObject); + } + }, + + _setParticipantName : function(participantId, newParticipantName) + { + var elementArray = this._getParticipantNameElements(participantId); + if(!elementArray || elementArray.length < 1) + { + common.Debug.traceWarning('No name tag for participant with id: ' + participantId); + } + else + { + for (var i=0; i' + + ' ' + + localization.ChatHistory + + '' + + '' + + '' + + '' + + '' + + localization.Print + + '' + + ''; + + var post = '
        ' + + '' + + localization.Print + + '' + + '
        ' + + localization.LinkDisclaimer + + '
        ' + + '' + + ''; + + + var content = ''; + this._lastMessageDate = null; + for(var i = 0; i < messages.length; ++i) + { + var message = messages[i]; + content += this._formatMessageIntoHtml(message); + } + + return pre + content + post; + }, + + _formatMessageIntoHtml : function(message) + { + common.Interface.ensureImplements(message, webservices.Interfaces.IMessageData); + + var html = '' + + ui.DateTimeFormatter.formatTimeForDisplay(message.get_date(), (this._lastMessageDate != message.get_date().getDate())) + + '' + message.get_name() + ':' + + webservices.Utilities.escapeHTML(message.get_text()) + ''; + + this._lastMessageDate = message.get_date().getDate(); + + return html; + }, + + _popUpWindow : function(html) + { + common.Debug.traceMethodEntered("ParticipantsPanel._popUpWindow()"); + var windowTitle = 'Chat_History'; + var windowFeatures = 'width=' + (screen.width * 3/4) + ', height=' + (screen.height * 3/4) + ', dependent=no, toolbar=no, location=no, directories=no, status=no, menubar=yes, scrollbars=yes, resizable=yes'; + + try + { + var newWin = window.open('', windowTitle, windowFeatures); + newWin.document.write(html); + newWin.document.close(); + } + catch(ex) + { + common.Debug.traceError("Caught unhandled exception:\n" + ex); + common.Debug.alert("Caught unhandled exception:\n" + ex); + webservices.ProblemReporter.sendProblemReport(ex, "ParticipantsPanel._popUpWindow()"); + window.alert(localization.ErrorOpeningWindow); + } + common.Debug.traceMethodExited("ParticipantsPanel._popUpWindow()"); + }, + + _validateDomObject : function() + { + if(!this._list) + { + throw common.ExceptionFactory.createException("Participant list not found"); + } + } +}); + +/** + * WebServicesParticipantsPanel class + * Listens for notifications that a chat participant has joined the conversation, left the conversation, put the + * conversation on hold, etc. and then calls the appropriate methods of the superclass. + * Basically, this class is the "glue" between NotificationRegistry and ParticipantsPanel. + */ +ui.WebServicesParticipantsPanel = Class.create(ui.ParticipantsPanel, +{ + /** + * constructor + */ + initialize:function($super) + { + var numArgs = 1; + if(arguments.length != numArgs) + { + throw common.ExceptionFactory.createException("WebServicesParticipantsPanel constructor called with " + arguments.length + " arguments, but expected " + numArgs + "."); + } + + $super(); + + this.addImplementedInterface(webservices.Interfaces.IParticipantJoinedNotificationObserver, webservices); + this.addImplementedInterface(webservices.Interfaces.IParticipantLeftNotificationObserver, webservices); + this.addImplementedInterface(webservices.Interfaces.IParticipantNameChangedNotificationObserver, webservices); + this.addImplementedInterface(webservices.Interfaces.IParticipantStartedTypingNotificationObserver, webservices); + this.addImplementedInterface(webservices.Interfaces.IParticipantStoppedTypingNotificationObserver, webservices); + this.addImplementedInterface(webservices.Interfaces.IChatReconnectUINotificationObserver, webservices); + this.addImplementedInterface(webservices.Interfaces.IPartyInfoNotificationObserver, webservices); + + // For the popover + this.addImplementedInterface(webservices.Interfaces.IReceivedTextNotificationObserver, webservices); + this.addImplementedInterface(webservices.Interfaces.IReceivedUrlNotificationObserver, webservices); + this.addImplementedInterface(webservices.Interfaces.IReceivedFileNotificationObserver, webservices); + + // for debugging only + this.addImplementedInterface(webservices.Interfaces.IParticipantActiveNotificationObserver, webservices); + this.addImplementedInterface(webservices.Interfaces.IParticipantHeldNotificationObserver, webservices); + // for debugging only + }, + + // public methods + + /** + * Called when a participant has joined the chat. Calls whatever is necessary to make the UI reflect this. + * + * @param notification Something that implements IParticipantJoinedNotification + */ + processParticipantJoinedNotification : function(notification) + { + common.Interface.ensureImplements(notification, webservices.Interfaces.IParticipantJoinedNotification); + + var id = notification.get_participantId(); + var name = webservices.ParticipantRepository.get_participant(id).get_name(); + if (webservices.ParticipantRepository.get_currentParticipantId() == id) + { + this._addParticipantSelf(id, name); + } + else + { + this._addParticipantOther(id, name); + } + }, + + /** + * Event listener for reconnection of the chat + * + * @param notification Something that implements webservices.Interfaces.IChatReconnectUINotification + */ + processChatReconnectUINotification : function(notification) + { + common.Interface.ensureImplements(notification, webservices.Interfaces.IChatReconnectUINotification); + + this.reset(); + }, + + /** + * Called when a participant has left the chat. Calls whatever is necessary to make the UI reflect this. + * + * @param notification Something that implements IParticipantLeftNotification + */ + processParticipantLeftNotification : function(notification) + { + common.Interface.ensureImplements(notification, webservices.Interfaces.IParticipantLeftNotification); + this._removeParticipant(notification.get_participantId()); + }, + + /** + * Called when a participant's name has changed. Calls whatever is necessary to make the UI reflect this. + * + * @param notification Something that implements IParticipantNameChangedNotification + */ + processParticipantNameChangedNotification : function(notification) + { + common.Interface.ensureImplements(notification, webservices.Interfaces.IParticipantNameChangedNotification); + this._setParticipantName(notification.get_participantId(), notification.get_newParticipantName()); + }, + + /** + * For debugging only + * @param notification Something that implements IParticipantActiveNotification + */ + processParticipantActiveNotification : function(notification) + { + common.Interface.ensureImplements(notification, webservices.Interfaces.IParticipantActiveNotification); + this._setParticipantActive(notification.get_participantId()); + }, + + /** + * Called when a participant has placed the chat on hold. Calls whatever is necessary to make the UI reflect this. + * + * @param notification Something that implements IParticipantHeldNotification + */ + processParticipantHeldNotification : function(notification) + { + common.Interface.ensureImplements(notification, webservices.Interfaces.IParticipantHeldNotification); + this._setParticipantHeld(notification.get_participantId()); + }, + + /** + * Called when a participant has started typing. Calls whatever is necessary to make the UI reflect this. + * + * @param notification Something that implements IParticipantStartedTypingNotification + */ + processParticipantStartedTypingNotification : function(notification) + { + common.Interface.ensureImplements(notification, webservices.Interfaces.IParticipantStartedTypingNotification); + this._setParticipantStartedTyping(notification.get_participantId()); + }, + + /** + * Called when a participant has stopped typing. Calls whatever is necessary to make the UI reflect this. + * + * @param notification Something that implements IParticipantStoppedTypingNotification + */ + processParticipantStoppedTypingNotification : function(notification) + { + common.Interface.ensureImplements(notification, webservices.Interfaces.IParticipantStoppedTypingNotification); + this._setParticipantStoppedTyping(notification.get_participantId()); + }, + + /** + * Respond to receipt of information (name, photo location) about a + * party involved in an interaction (not necessarily this interaction!) + * + * @param notification + */ + processPartyInfoNotification : function(notification) + { + common.Debug.traceMethodEntered("WebServicesParticipantsPanel.processPartyInfoNotification()"); + common.Interface.ensureImplements(notification, webservices.Interfaces.IPartyInfoNotification); + + if (notification.get_localParticipantId() == webservices.ParticipantRepository.get_currentParticipantId()) + { + var agentPhoto = notification.get_photo(); + if (agentPhoto) + { + this._setParticipantPhoto(notification.get_remoteParticipantId(), agentPhoto); + } + else + { + this._setParticipantPhoto(notification.get_remoteParticipantId(), null); + } + } + + common.Debug.traceMethodExited("WebServicesParticipantsPanel.processPartyInfoNotification()"); + }, + + /** + * Implementation of IReceivedTextNotificationObserver + * Updates the last response time in the participant's popover + * + * @param notification Something that implements IReceivedTextNotification + */ + processReceivedTextNotification : function(notification) + { + common.Interface.ensureImplements(notification, webservices.Interfaces.IReceivedTextNotification); + + this._participantResponded(notification.get_participantId(), notification.get_dateTime()); + }, + + /** + * Implementation of IReceivedUrlNotificationObserver + * Adds the received URL to the panel. + * + * @param notification Something that implements IReceivedUrlNotification + */ + processReceivedUrlNotification : function(notification) + { + common.Interface.ensureImplements(notification, webservices.Interfaces.IReceivedUrlNotification); + + this._participantResponded(notification.get_participantId(), notification.get_dateTime()); + }, + + /** + * Implementation of IReceivedFileNotificationObserver + * Adds a link to the received file to the panel. + * + * @param notification Something that implements IReceivedFileNotification + */ + processReceivedFileNotification : function(notification) + { + common.Interface.ensureImplements(notification, webservices.Interfaces.IReceivedFileNotification); + + this._participantResponded(notification.get_participantId(), notification.get_dateTime()); + }, + + /** + * Returns true if the supplied participantId specifies a participant whose photo is known. + * Returns false otherwise. + * + * @param participantId A GUID identifying a participant + * @return boolean + */ + isPhotoAvailable : function(participantId) + { + var agent = webservices.ParticipantRepository.get_participant(participantId); + if (agent) + { + if (agent.get_photo()) + { + return true; + } + } + return false; + }, + + // private methods + + _participantResponded : function(participantId, dateTime) + { + if (webservices.ParticipantRepository.get_currentParticipantId() != participantId && + webservices.ParticipantRepository.get_systemParticipantId() != participantId) + { + this._setParticipantLastRespondedTime(participantId, dateTime); + } + } +}); + +/** + * MainPanel class + * This is the panel that is shown when a chat is in progress. + */ +ui.MainPanel = Class.create(ui.Control, +{ + /** + * Constructor + * + * @param useHtmlEditor If true, a WYSIWYG editor will be used. If false, a simple textbox will be used. Note that the WYSIWYG editor is not supported at this time. + * @param languageCode The code (i.e. fr-ca) that specifies which language the editor's tooltips, etc. should appear in. Case-insensitive. + */ + initialize:function($super, useHtmlEditor, languageCode) + { + if((arguments.length < 2) || (arguments.length > 3)) + { + throw common.ExceptionFactory.createException("MainPanel constructor called with " + arguments.length + " arguments, but expected 2 or 3."); + } + + // Assumption: ((want to display HTML editor) == (allow displaying received messages as HTML)). If this becomes false, + // stop using the variable "useHtmlEditor" for both purposes, and have 2 variables instead. + this._receivedMessagesPanel = new ui.WebServicesReceivedMessagesPanel(useHtmlEditor); + this._composeMessagePanel = new ui.WebServicesComposeMessagePanel(webservices.Json.TypingIndicator, useHtmlEditor, languageCode); + this._participantsPanel = new ui.WebServicesParticipantsPanel(); + + // If false, user will be asked to confirm that they really do want to exit the chat. If true, + // the confirmation step will be skipped. + this._exitButtonShouldOnlyExit = false; + + var domObject = this._buildDomObject(); + $super(domObject); + + this.addImplementedInterface(webservices.Interfaces.IParticipantLeftNotificationObserver, webservices); + this.addImplementedInterface(webservices.Interfaces.IChatCompletionFailureNotification, webservices); + + this._bindControls(); + }, + + /** + * Destructor + */ + destroy : function() + { + this._receivedMessagesPanel.destroy(); + delete this._receivedMessagesPanel; + this._receivedMessagesPanel = null; + + this._composeMessagePanel.destroy(); + delete this._composeMessagePanel; + this._composeMessagePanel = null; + + this._participantsPanel.destroy(); + delete this._participantsPanel; + this._participantsPanel = null; + + ui.Control.prototype.destroy.call(this); + }, + + // public methods + + /** + * Sets the ChatManager, for both this panel and the WebServicesComposeMessagePanel which it contains. + * + * @param chatManager An instance of a subclass of webservices.ChatManagerBase + */ + set_chatManager : function(chatManager) + { + this._chatManager = chatManager; + this._composeMessagePanel.set_chatManager(chatManager); + }, + + /** + * Causes this panel (and all sub-panels) to become enabled or disabled. + * + * @param enabled If true, this panel will be enabled. If false, this panel will be disabled. + */ + enable : function(enabled) + { + this._receivedMessagesPanel.enable(enabled); + this._participantsPanel.enable(enabled); + this._composeMessagePanel.enable(enabled); + }, + + /** + * Resets this panel back to its initial state. + */ + reset : function() + { + this._receivedMessagesPanel.reset(); + this._participantsPanel.reset(); + this._composeMessagePanel.reset(); + this._exitButtonShouldOnlyExit = false; + }, + + /** + * Called when the panel receives focus. Simply delegates the focus to the WebServicesComposeMessagePanel. + */ + focus : function() + { + this._composeMessagePanel.focus(); + }, + + /** + * Called whenever a participant leaves the chat. + * This implements the webservices.Interfaces.IParticipantLeftNotificationObserver interface. + * Checks to see if the participant who left is the web user whose browser is running this code. If so, calls + * ChatManager to free resources, and disables the prompt given by the Exit button. + * + * @param notification An implementation of IParticipantLeftNotification, so that the ID of the participant who left can be known. + */ + processParticipantLeftNotification : function(notification) + { + common.Debug.traceMethodEntered("MainPanel.processParticipantLeftNotification()"); + common.Interface.ensureImplements(notification, webservices.Interfaces.IParticipantLeftNotification); + + if(notification.get_participantId() == webservices.ParticipantRepository.get_currentParticipantId()) + { + common.Debug.traceNote("The current participant has left."); + // since the normal logout wasn't done, need to call into chat manager manually + this._chatManager.onExitChatSuccess(null); + + // the current user was forced out, so change the exit button from logout to just plain exit + this._exitButtonShouldOnlyExit = true; + } + common.Debug.traceMethodExited("MainPanel.processParticipantLeftNotification()"); + }, + + /** + * Respond to notification that an attempt to exit a chat has failed. + * + * @param chatCompletionFailureNotification Contains an error indicating the reason for the failure. + * @see _createChat() + * @see ChatManager.login() + */ + processChatCompletionFailureNotification : function(chatCompletionFailureNotification) + { + var error = chatCompletionFailureNotification.get_error(); + common.Debug.traceError("Failed to exit chat: " + error.get_errorCode()); + }, + + // private methods + + _onClickExitButton : function() + { + common.Debug.traceMethodEntered("MainPanel._onClickExitButton()"); + if(this._exitButtonShouldOnlyExit) + { + // Force a notification + webservices.NotificationRegistry.process(webservices.NotificationFactory.createChatCompletionNotification()); + } + else + { + common.Debug.traceNote("Displaying Javascript confirmation dialog."); + if(window.confirm(localization.ExitChatWarning)) + { + this._chatManager.exitChat(); + } + } + common.Debug.traceMethodExited("MainPanel._onClickExitButton()"); + }, + + _bindControls : function() + { + webservices.NotificationRegistry.registerObserver(this._composeMessagePanel, webservices.Interfaces.IParticipantLeftNotification); + webservices.NotificationRegistry.registerObserver(this._composeMessagePanel, webservices.Interfaces.IFailoverUINotification); + webservices.NotificationRegistry.registerObserver(this._composeMessagePanel, webservices.Interfaces.IResumedPollingNotification); + webservices.NotificationRegistry.registerObserver(this._composeMessagePanel, webservices.Interfaces.IChatCreationNotification); + webservices.NotificationRegistry.registerObserver(this._composeMessagePanel, webservices.Interfaces.IRefreshPageNotification); + + webservices.NotificationRegistry.registerObserver(this._receivedMessagesPanel, webservices.Interfaces.IParticipantJoinedNotification); + webservices.NotificationRegistry.registerObserver(this._receivedMessagesPanel, webservices.Interfaces.IParticipantLeftNotification); + webservices.NotificationRegistry.registerObserver(this._receivedMessagesPanel, webservices.Interfaces.IReceivedTextNotification); + webservices.NotificationRegistry.registerObserver(this._receivedMessagesPanel, webservices.Interfaces.IReceivedUrlNotification); + webservices.NotificationRegistry.registerObserver(this._receivedMessagesPanel, webservices.Interfaces.IReceivedFileNotification); + webservices.NotificationRegistry.registerObserver(this._receivedMessagesPanel, webservices.Interfaces.IParticipantVoicemailNotification); + webservices.NotificationRegistry.registerObserver(this._receivedMessagesPanel, webservices.Interfaces.IFailoverUINotification); + webservices.NotificationRegistry.registerObserver(this._receivedMessagesPanel, webservices.Interfaces.IResumedPollingNotification); + webservices.NotificationRegistry.registerObserver(this._receivedMessagesPanel, webservices.Interfaces.IChatReconnectFailureNotification); + webservices.NotificationRegistry.registerObserver(this._receivedMessagesPanel, webservices.Interfaces.IRefreshPageNotification); + + webservices.NotificationRegistry.registerObserver(this._participantsPanel, webservices.Interfaces.IParticipantJoinedNotification); + webservices.NotificationRegistry.registerObserver(this._participantsPanel, webservices.Interfaces.IParticipantLeftNotification); + webservices.NotificationRegistry.registerObserver(this._participantsPanel, webservices.Interfaces.IParticipantNameChangedNotification); + webservices.NotificationRegistry.registerObserver(this._participantsPanel, webservices.Interfaces.IParticipantStartedTypingNotification); + webservices.NotificationRegistry.registerObserver(this._participantsPanel, webservices.Interfaces.IParticipantStoppedTypingNotification); + webservices.NotificationRegistry.registerObserver(this._participantsPanel, webservices.Interfaces.IChatReconnectUINotification); + webservices.NotificationRegistry.registerObserver(this._participantsPanel, webservices.Interfaces.IPartyInfoNotification); + + // For the popover only + webservices.NotificationRegistry.registerObserver(this._participantsPanel, webservices.Interfaces.IReceivedTextNotification); + webservices.NotificationRegistry.registerObserver(this._participantsPanel, webservices.Interfaces.IReceivedUrlNotification); + webservices.NotificationRegistry.registerObserver(this._participantsPanel, webservices.Interfaces.IReceivedFileNotification); + + // for debugging only + webservices.NotificationRegistry.registerObserver(this._participantsPanel, webservices.Interfaces.IParticipantActiveNotification); + webservices.NotificationRegistry.registerObserver(this._participantsPanel, webservices.Interfaces.IParticipantHeldNotification); + // for debugging only + + // this needs to be last since it calls the actual exit chat functionality + // (or create a CurrentParticipantLeftNotification to eliminate this condition) + webservices.NotificationRegistry.registerObserver(this, webservices.Interfaces.IParticipantLeftNotification); + webservices.NotificationRegistry.registerObserver(this, webservices.Interfaces.IChatCompletionFailureNotification); + }, + + _buildDomObject : function() + { + var outerDiv = this.createElement('div', 'mainPanel', { 'class': 'iwc-main-panel' }); + outerDiv.appendChild(this._buildTitleHeader()); + outerDiv.appendChild(this._buildInnerDiv()); + return outerDiv; + }, + + _buildTitleHeader : function() + { + return this.createElement('h1', null, { 'class': 'iwc-page-header' }, null, localization.MainPanelHeaderText); + }, + + _buildInnerDiv : function() + { + var div = document.createElement('div'); + div.appendChild(this._buildTopRow()); + div.appendChild(this._buildBottomRow()); + return div; + }, + + _buildTopRow : function() + { + var div = document.createElement('div'); + div.appendChild(this._participantsPanel.get_domObject()); + div.appendChild(this._receivedMessagesPanel.get_domObject()); + return div; + }, + + _buildBottomRow : function() + { + var div = document.createElement('div'); + div.appendChild(this._composeMessagePanel.get_domObject()); + + var button = this.createChildElement(div, 'input', 'exitButton', { 'type': 'button', 'value': localization.Exit, 'class': 'iwc-exit-button' }); + Element.observe(button, 'click', this._onClickExitButton.bindAsEventListener(this)); + + return div; + } +}); + +// Register namespaces +ui.registerChildNamespace("_Internal"); + +/** + * _DateTimeFormatter class + * + * Formats dates and times according to formats which adhere to: + * http://msdn.microsoft.com/en-us/library/dd317787%28v=vs.85%29.aspx + * http://msdn.microsoft.com/en-us/library/dd318148%28v=vs.85%29.aspx + */ +ui._Internal._DateTimeFormatter = Class.create( +{ + /** + * Constructor. Does nothing. + */ + initialize : function(dateFormat, timeFormat) + { + if (null == dateFormat) + { + // If an SU1 (or higher) Javascript client starts a chat with a GA webserver, + // dateFormat and timeFormat will be null. Use the value from the resource file + // as a fallback. + dateFormat = localization.FallbackDateFormat; + } + this.set_dateFormat(dateFormat); + + if (null == timeFormat) + { + timeFormat = localization.FallbackTimeFormat; + } + this.set_timeFormat(timeFormat); + + if ("1" == localization.OverrideDateTimeFormats) + { + try + { + // Allow the date format to be overridden by the custom resource file. + this.set_dateFormat(localization.DateFormat); + } catch (ex) + { + // get() already displayed an error to the console. + } + + try + { + this.set_timeFormat(localization.TimeFormat); + } catch (ex) + { + // get() already displayed an error to the console. + } + } + }, + + /** + * Sets the date format + * + * @param dateFormat The format in which dates will be displayed + */ + set_dateFormat : function(dateFormat) + { + this._dateFormat = dateFormat; + }, + + /** + * Sets the time format + * + * @param timeFormat The format in which times will be displayed + */ + set_timeFormat : function(timeFormat) + { + this._timeFormat = timeFormat; + }, + + /** + * Formats a date/timestamp for display + * + * @param dateTime A Javascript Date object representing the date/timestamp to format + * @param showDate A boolean. If true, the date and time will be included. If false, only the time will be included. + */ + formatTimeForDisplay : function(dateTime, showDate) + { + try + { + if (showDate) + { + // Include the date + return this.formatDate(dateTime) + " " + this.formatTime(dateTime); + } + else + { + // Do not include the date + return this.formatTime(dateTime); + } + } catch (ex) + { + common.Debug.traceError(ex.message); + return dateTime.toString() + ex.message; + } + }, + + /** + * Formats a date + * + * @param date A date to format + */ + formatDate : function(date) + { + var ret = this._dateFormat; // Example: "M/d/yyyy" + + // Example: It is Wednesday the 3rd, and this._dateFormat = "dddd d". Ignore month and year for now. + // If we do /dddd/ first and /d/ second, we'll get "We3nes3ay 3". + // But, if we do /d/ first and /dddd/ second, we'll get "3333 3". + // The solution is to replace dddd with a placeholder, then do /d/, then change the + // placeholder back to dddd, and finally do /dddd/. + var placeholder1 = "#####"; + var placeholder2 = "!!!!!"; + var placeholder3 = "-----"; + var placeholder4 = "_____"; + var placeholder5 = "====="; + var placeholder6 = "~~~~~"; + + ret = ret.replace(/dddd/g, placeholder1); + ret = ret.replace(/ddd/g, placeholder2); + ret = ret.replace(/MMMM/g, placeholder3); + ret = ret.replace(/MMM/g, placeholder4); + ret = ret.replace(/gg/g, placeholder5); + ret = ret.replace(/g/g, placeholder6); + + ret = ret.replace(/yyyyy?/g, date.getFullYear()); + var twoDigitYear = date.getFullYear() % 100; + ret = ret.replace(/yy/g, twoDigitYear >= 10 ? twoDigitYear : "0" + twoDigitYear); + ret = ret.replace(/y/g, date.getFullYear() % 10); + + var day = date.getDate(); + ret = ret.replace(/dd/g, (day >= 10 ? day : "0" + day)); + ret = ret.replace(/d/g, day); + + var month = date.getMonth() + 1; // getMonth() returns 0 for January ... 11 for December + ret = ret.replace(/MM/g, (month >= 10 ? month : "0" + month)); + ret = ret.replace(/M/g, month); + + ret = ret.replace(new RegExp(placeholder1, "g"), localization["DayOfWeek" + date.getDay()]); + ret = ret.replace(new RegExp(placeholder2, "g"), localization["AbbreviatedDayOfWeek" + date.getDay()]); + ret = ret.replace(new RegExp(placeholder3, "g"), localization["Month" + month]); + ret = ret.replace(new RegExp(placeholder4, "g"), localization["AbbreviatedMonth" + month]); + ret = ret.replace(new RegExp(placeholder5, "g"), localization.Era); + ret = ret.replace(new RegExp(placeholder6, "g"), localization.AbbreviatedEra); + + return ret; + }, + + /** + * Formats a time + * + * @param time A time to format + */ + formatTime : function(time) + { + var ret = this._timeFormat; // Example: "h:mm:ss tt" + + var hours = time.getHours(); // 0...23 + var hoursOnTwelveHourClock = hours % 12; + if (hoursOnTwelveHourClock == 0) + { + hoursOnTwelveHourClock = 12; + } + ret = ret.replace(/HH/g, (hours >= 10 ? hours : "0" + hours)); + ret = ret.replace(/H/g, hours); + ret = ret.replace(/hh/g, (hoursOnTwelveHourClock >= 10 ? hoursOnTwelveHourClock : "0" + hoursOnTwelveHourClock)); + ret = ret.replace(/h/g, hoursOnTwelveHourClock); + + var minutes = time.getMinutes(); + ret = ret.replace(/mm/g, (minutes >= 10 ? minutes : "0" + minutes)); + ret = ret.replace(/m/g, minutes); + + var seconds = time.getSeconds(); + ret = ret.replace(/ss/g, (seconds >= 10 ? seconds : "0" + seconds)); + ret = ret.replace(/s/g, seconds); + + var ampm = (hours < 12 ? "AM" : "PM"); + ret = ret.replace(/tt/g, localization[ampm]); + ret = ret.replace(/t/g, localization["Abbreviated" + ampm]); + + return ret; + } +}); + +/** + * WebChat class + * The main object of the UI side of the chat. + */ +ui.WebChat = Class.create(ui.Control, +{ + /** + * Constructor + * + * @param chatManager A ChatManagerBase subclass + * @param callbackManager A CallbackManagerBase subclass + * @param registrationManager A RegistrationManagerBase subclass + * @param pageMode Bitfield. See ui.PageModes. + * @param chatParameters An instance of ChatParameters + * @param callbackParameters An instance of CallbackParameters + * @param shouldWarnOnClose If true, clicking the Exit button during a chat will result in a prompt for confirmation. + * If false, no confirmation prompt will be given, and the exit button will immediately exit the chat. + * @param useHtmlEditor If true, the web user will be shown an editor which allows him/her to change font, text + * size, color, etc. If false, the web user will only be able to enter plain text. + * Note that the HTML editor is not supported at this time. + * @param languageCode An IETF Language Tag to indicate which spoken language will be used for the + * chats/callbacks. For instance, pass "en-us" for U.S. English, or "de-ch" for German as + * spoken in Switzerland. + * @param chatFollowupUrl Optional. If included, a new browser will be launched to display this URL upon completion + * of a chat. The URL will not be displayed upon creation of a callback. + */ + initialize:function($super, chatManager, callbackManager, registrationManager, pageMode, chatParameters, callbackParameters, shouldWarnOnClose, useHtmlEditor, languageCode, chatFollowupUrl) + { + common.Debug.traceMethodEntered("WebChat.initialize()"); + if((arguments.length < 1) || (arguments.length > 11)) + { + throw common.ExceptionFactory.createException("WebChat constructor called with " + arguments.length + " arguments, but expected between 1 and 11."); + } + + // check our dependencies + common.DependencyValidators.requirePrototypeVersion("1.6.1"); + common.DependencyValidators.requireJQueryVersion("1.3.2"); + + // check the browser version + // TODO: What versions of Safari are we supporting? or does it matter? + // TODO: Are we supporting Chrome? + if (common.Browser.isFireFox() && (common.Browser.getFireFoxVersion(navigator.userAgent).isLessThan(new common.Version('3.5')))) + { + window.alert(localization.FireFoxVersionError); + return; + } + if (common.Browser.isIE() && (common.Browser.getIEVersion(navigator.userAgent).isLessThan(new common.Version('7')))) + { + window.alert(localization.IEVersionError); + return; + } + + // initialize members + this._isChatConnected = false; + this._chatFollowupUrl = chatFollowupUrl; + + // save members + this._shouldWarnOnClose = shouldWarnOnClose; + this._chatManager = chatManager; + this._callbackManager = callbackManager; + this._registrationManager = registrationManager; + + // initialize ui controls: login panel (if necessary, i.e. if the information collected + // on that panel is not already known from some external source) and main panel. + var loginInfoSource = webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.LoginInfoSource); + var chatParticipantName = loginInfoSource.get_chatUsername(); + if (common.Utilities.isNullOrEmptyString(chatParticipantName)) + { + var callbackParticipantName = loginInfoSource.get_callbackUsername(); + if ((null != callbackParticipantName) && (0 < callbackParticipantName.length)) + { + var participantCredentials = loginInfoSource.get_callbackPassword(); + var telephone = loginInfoSource.get_callbackTelephone(); + var subject = loginInfoSource.get_callbackDescription(); + + // If telephone and/or subject are blank, don't bother the WebProcessorBridge. + if (!(telephone && subject)) + { + var error = webservices.ErrorCodes.ERROR + "." + + webservices.ErrorCodes.WEBSVC + "." + + webservices.ErrorCodes.CONTENT + "." + + webservices.ErrorCodes.MISSINGDATA; + webservices.NotificationRegistry.process(webservices.NotificationFactory.createCallbackCreationFailureNotification(new webservices.Error(error))); + } + else + { + callbackParameters.set_telephone(telephone); + callbackParameters.set_subject(subject); + callbackParameters.set_participantName(callbackParticipantName); + callbackParameters.set_participantCredentials(participantCredentials); + this._callbackManager.createCallback(callbackParameters); + } + } + else + { + this._loginContainerPanel = new ui.FormContainerPanel(this._chatManager, this._callbackManager, + this._registrationManager, + pageMode, chatParameters, + callbackParameters); + } + } + this._mainPanel = new ui.MainPanel(useHtmlEditor, languageCode); + + // build and validate the DOM + var domObject = this._buildDomObject(); + this._validateDomObject(); + + $super(domObject); + + this.addImplementedInterface(webservices.Interfaces.IPageBeforeUnloadNotificationObserver, webservices); + this.addImplementedInterface(webservices.Interfaces.IChatCreationNotificationObserver, webservices); + this.addImplementedInterface(webservices.Interfaces.IChatCompletionNotificationObserver, webservices); + webservices.NotificationRegistry.registerObserver(this, webservices.Interfaces.IPageBeforeUnloadNotification); + webservices.NotificationRegistry.registerObserver(this, webservices.Interfaces.IChatCreationNotification); + webservices.NotificationRegistry.registerObserver(this, webservices.Interfaces.IChatCompletionNotification); + + this._closeWindowMessageIsEnabled = false; + this._enableControls(false); + this._mainPanel.hide(); + + if (!common.Utilities.isNullOrEmptyString(chatParticipantName)) + { + chatParameters.set_participantName(chatParticipantName); + chatParameters.set_participantCredentials(loginInfoSource.get_chatPassword()); + if (!chatParameters.attributes) chatParameters.attributes = {}; + chatParameters.attributes.glanceSessionKey = getParameterByName('glanceSessionKey'); + this._chatManager.login(chatParameters); + } + + common.Debug.traceMethodExited("WebChat.initialize()"); + }, + + /** + * Destructor + */ + destroy : function() + { + common.Debug.traceMethodEntered("WebChat.destroy()"); + if (this._isChatConnected) + { + this._showChatFollowupUrl(); + } + + if (this._loginContainerPanel) + { + this._loginContainerPanel.destroy(); + delete this._loginContainerPanel; + this._loginContainerPanel = null; + } + + this._mainPanel.destroy(); + delete this._mainPanel; + this._mainPanel = null; + + webservices.WebServicesInitialization.destroy(); + + ui.Control.prototype.destroy.call(this); + common.Debug.traceMethodExited("WebChat.destroy()"); + }, + + // public methods + + getLoginContainerPanel : function() + { + return this._loginContainerPanel; + }, + + /** + * Called when the chat receives focus. Simply delegates focus to a sub-panel. + */ + focus : function() + { + if (this._loginContainerPanel) + { + this._loginContainerPanel.focus(); + // Could just as easily do main panel here as well. + } + else + { + this._mainPanel.focus(); + } + }, + + /** + * Callback that is called when a response to the request for server configuration is received. + */ + onServerConfigurationRetrieved : function() + { + if (this._loginContainerPanel) + { + this._loginContainerPanel.onServerConfigurationRetrieved(); + } + }, + + /** + * Called at cleanup time. + */ + onUnload : function() + { + common.Debug.traceMethodEntered("WebChat.onUnload()"); + if(this._isChatConnected) + { + common.Debug.traceAlways("Exiting the chat!"); + this._chatManager.exitChat(); + } + common.Debug.traceMethodExited("WebChat.onUnload()"); + }, + + /** + * Respond to notification that a Chat was created successfully. + * + * @param chatCreationNotification Contains the ID of the current participant (i.e. the one whose browser is currently executing this code, and who is attempting to log in) + */ + processChatCreationNotification : function(chatCreationNotification) + { + common.Debug.traceMethodEntered("WebChat.processChatCreationNotification()"); + var participantID = chatCreationNotification.get_currentParticipantId(); + var chatID = chatCreationNotification.get_currentChatId(); + + if (ui.DateTimeFormatter) + { + delete ui.DateTimeFormatter; + } + ui.DateTimeFormatter = new ui._Internal._DateTimeFormatter(chatCreationNotification.get_dateFormat(), chatCreationNotification.get_timeFormat()); + + this._chatManager.startChat(chatID, participantID); + + if (this._loginContainerPanel) + { + this._loginContainerPanel.hide(); + } + + this._mainPanel.set_chatManager(this._chatManager); + this._mainPanel.enable(true); + this._mainPanel.show(); + + if(this._shouldWarnOnClose) + { + this._enableCloseWindowMessage(); + } + + this._enableControls(true); + + this._mainPanel.focus(); + + this._isChatConnected = true; + common.Debug.traceMethodExited("WebChat.processChatCreationNotification()"); + }, + + /** + * Respond to notification that a Chat was exited. + * + * @param chatCompletionNotification Notification object. Contents ignored. + */ + processChatCompletionNotification : function(chatCompletionNotification) + { + common.Debug.traceMethodEntered("WebChat.processChatCompletionNotification()"); + this._isChatConnected = false; + + this._disableCloseWindowMessage(); + + this._showChatFollowupUrl(); + + this._mainPanel.set_chatManager(null); + this._mainPanel.hide(); + this._mainPanel.enable(false); + this._mainPanel.reset(); + + if (this._loginContainerPanel) + { + this._loginContainerPanel.reset(); + this._loginContainerPanel.show(); + } + + this._enableControls(false); + + if (this._loginContainerPanel) + { + this._loginContainerPanel.focus(); + } + common.Debug.traceMethodExited("WebChat.processChatCompletionNotification()"); + }, + + /** + * Implementation of webservices.Interfaces.IPageBeforeUnloadNotificationObserver + * + * @param notification IPageBeforeUnloadNotification + */ + processPageBeforeUnloadNotification : function(notification) + { + if (this._closeWindowMessageIsEnabled) + { + return localization.ClosePageWarning; + } + }, + + // private methods + + _enableCloseWindowMessage : function() + { + this._closeWindowMessageIsEnabled = true; + }, + + _disableCloseWindowMessage : function() + { + this._closeWindowMessageIsEnabled = false; + }, + + /** + * Display the chat followup URL (i.e. for a survey or something), if one has been set. + * TODO: Is there value in appending something like "&interactionID=1234" to the followup URL? + */ + _showChatFollowupUrl : function() + { + if (this._chatFollowupUrl) + { + window.open(this._chatFollowupUrl, "_blank"); + } + }, + + _enableControls : function(enabled) + { + this._mainPanel.enable(enabled); + }, + + _validateDomObject : function() + { + var loginInfoSource = webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.LoginInfoSource); + if(!this._loginContainerPanel && + !loginInfoSource.get_chatUsername() && + !loginInfoSource.get_callbackUsername()) + { + // The login container panel should exist, but does not. + throw common.ExceptionFactory.createException("Login panel not found"); + } + + if(!this._mainPanel) + { + throw common.ExceptionFactory.createException("Main panel not found"); + } + }, + + _buildDomObject : function() + { + var outerDiv = this.createElement('div', null, { 'class': 'iwc-web-chat' }); + if (this._loginContainerPanel) + { + outerDiv.appendChild(this._loginContainerPanel.get_domObject()); + } + outerDiv.appendChild(this._mainPanel.get_domObject()); + return outerDiv; + } +}); + +// Register namespaces +ui.registerChildNamespace("_Internal"); + +/** + * Page class + * Represents the root UI container for the Chat/Callback web app. + * Do not instantiate - use the singleton, ui.Page. + */ +ui._Internal._Page = Class.create(common.InterfaceImplementation, +{ + /** + * Default constructor. This object is a singleton (see declaration immediately after this class), + * so no need to call this directly. + */ + initialize:function($super) + { + common.Debug.traceMethodEntered("Page.initialize()"); + var numArgs = 1; + if(arguments.length != numArgs) + { + throw common.ExceptionFactory.createException("Page constructor called with " + arguments.length + " arguments, but expected " + numArgs + "."); + } + + this._numServerConfigRequestFailovers = 0; + + $super(); + + common.Debug.traceMethodExited("Page.initialize()"); + }, + + // methods + + /** + * When called, this method will display the ININ Chat/Callback UI. + * + * @param params A Javascript object containing key/value pairs as specified below. All are + * required unless otherwise specified: + * + * currentUriFragment The URI fragment that reverse proxies to the preferred xIC server. + * See description of next param. + * uriFragments An array of URI fragments identifying the xIC server(s). Since AJAX requests can only be + * made to the originating server, it is necessary to configure a reverse proxy in order for + * the requests to get to the xIC server(s). If this Javascript was accessed from + * http://this-server/somePage.html, then it cannot make AJAX requests to + * http://xIC-server-1:8114/..., even if there weren't a firewall in the way. So, perhaps + * this-server was configured in a way such that: + * http://this-server/something/websvcs/serverConfiguration reverse proxies to + * http://xIC-server-1:8114/websvcs/serverConfiguration, and + * http://this-server/somethingElse/websvcs/serverConfiguration reverse proxies to + * http://xIC-server-2:8114/websvcs/serverConfiguration. In that case, the value passed for + * this param should be [ "something", "somethingElse" ]. + * pageMode Bitfield. See ui.PageModes. + * chatTarget The name of the queue to which chats should be sent. + * May be null if chats were not included in pageMode param. + * chatTargetType "Workgroup" or "User", depending on the queue type of the previous param. + * May be null if chats were not included in pageMode param. + * callbackTarget The name of the queue to which callbacks should be sent. + * May be null if callbacks were not included in pageMode param. + * callbackTargetType "Workgroup" or "User", depending on the queue type of the previous param. + * May be null if callbacks were not included in pageMode param. + * customInfo Customers wishing to customize chats may set this to any data. It will be set as + * the value of the CUSTOM_INFO attribute on the interaction. + * defaultLanguageCode An IETF Language Tag to indicate which spoken language will be used as the default for the + * chats/callbacks. For instance, pass "en-us" for U.S. English, or "de-ch" for German as + * spoken in Switzerland. This can be overriden, in order from highest to lowest, + * by: 1. directly overriding by calling Bootloader.setLanguage(), 2. web user's + * browser settings, 3. web user's OS settings. + * useHttps true/false value indicating whether HTTPS shall be used for the communication between the + * web browser and web server. This is distinct from the issue of whether HTTPS should be used + * between the web server and the xIC server - that is determined by the reverse proxy + * configuration. If not supplied, a warning will be logged and true will be assumed. + * chatFollowupUrl Optional. If included, a new browser will be launched to display this URL upon completion + * of a chat. The URL will not be displayed upon creation of a callback. + * callbackAttributes Optional. An object containing key/value pairs. If supplied, all + * keys and values must be strings. These fields will be passed to WebProcessorBridge + * and set as attributes on the Callback (but each key will be prefixed with a constant + * to form the actual attribute name). + * callbackRoutingContexts Optional. An instance of webservices.RoutingContexts that specifies how + * Callbacks should be routed. + */ + load : function(params) + { + common.Debug.traceMethodEntered("Page.load()"); + + if(arguments.length != 1) + { + throw common.ExceptionFactory.createException("Page.load() called with " + arguments.length + " arguments, but expected 1. Make sure there is not a version mismatch between the HTML and Javascript files."); + } + + params = this._verifyLoadParameters(params); + + this._defaultLanguageCode = params.defaultLanguageCode; + + // initialize API + webservices.WebServicesInitialization.initialize(params.currentUriFragment, params.uriFragments, params.useHttps); + + this._partyManager = new webservices.Json.PartyManager( + webservices.Json.GenericResponseBuilder, + webservices.CapabilityRepository, + webservices.Json.FailoverHandler); + + this._pageMode = params.pageMode; + + if (params.pageMode & ui.PageModes.CHAT) + { + this._chatParameters = new webservices.ChatParameters(params.chatTarget, params.chatTargetType, params.customInfo); + this._chatFollowupUrl = params.chatFollowupUrl; + this._useHtmlEditor = false; // Only plaintext is supported in 4.0 GA, maybe we will support HTML chat in a future release. + this._chatManager = new webservices.Json.ChatManager( + webservices.Json.GenericResponseBuilder, + webservices.CapabilityRepository, + webservices.Json.TypingIndicator, + webservices.Json.FailoverHandler, + this._useHtmlEditor); + this._chatManager.set_partyManager(this._partyManager); + } + + if (params.pageMode & ui.PageModes.CALLBACK) + { + this._callbackParameters = new webservices.CallbackParameters(params.callbackTarget, params.callbackTargetType, params.customInfo, params.callbackAttributes, params.callbackRoutingContexts); + this._callbackManager = new webservices.Json.CallbackManager( + webservices.Json.GenericResponseBuilder, + webservices.CapabilityRepository, + webservices.Json.FailoverHandler); + this._callbackManager.set_partyManager(this._partyManager); + } + + this._registrationManager = new webservices.Json.RegistrationManager( + webservices.Json.GenericResponseBuilder, + webservices.CapabilityRepository, + webservices.Json.FailoverHandler); + + this.addImplementedInterface(webservices.Interfaces.IPageUnloadNotificationObserver, webservices); + webservices.NotificationRegistry.registerObserver(this, webservices.Interfaces.IPageUnloadNotification); + + window.onbeforeunload = function(evt) + { + var returnValue = webservices.NotificationRegistry.process(webservices.NotificationFactory.createPageBeforeUnloadNotification()); + if (null != returnValue) + { + common.Debug.traceNote("Displaying prompt: " + returnValue); + if (evt) + { + evt.returnValue = returnValue; // IE, Firefox 3 and earlier + } + return returnValue; // Safari + } + }; + window.onunload = function() + { + common.Debug.traceMethodEntered("Page anonymous window.onunload handler"); + webservices.NotificationRegistry.process(webservices.NotificationFactory.createPageUnloadNotification()); + common.Debug.traceMethodExited("Page anonymous window.onunload handler"); + }; + + webservices.Json.ServerConfigurationManager.getServerConfiguration(this._serverConfigurationCallback.bind(this)); + common.Debug.traceMethodExited("Page.load()"); + }, + + /** + * Gets the Element to which the chat UI should be added. + * Initially searches for something whose id is "iwc-web-chat-container", but + * if that is not found, just returns the element representing the page body. + */ + getContainingElement : function() + { + var parent = document.getElementById('iwc-web-chat-container'); + if (!parent) + { + parent = this.getBody(); + } + return parent; + }, + + /** + * Convenience method to get the DOM element representing the body tag of the HTML page containing the + * chat/callback UI. + */ + getBody : function() + { + return document.getElementsByTagName('body')[0]; + }, + + processPageUnloadNotification : function(notification) + { + common.Debug.traceMethodEntered("Page.processPageUnloadNotification()"); + common.Interface.ensureImplements(notification, webservices.Interfaces.IPageUnloadNotification); + this.destroy(); + common.Debug.traceMethodExited("Page.processPageUnloadNotification()"); + }, + + /** + * Cleans up the application's resources. + */ + destroy : function() + { + common.Debug.traceMethodEntered("Page.destroy()"); + try + { + if(this._webChat) + { + this._webChat.onUnload(); + this._webChat.destroy(); + delete this._webChat; + this._webChat = null; + } + + if(this._chatManager) + { + this._chatManager.destroy(); + delete this._chatManager; + this._chatManager = null; + } + + if(this._callbackManager) + { + this._callbackManager.destroy(); + delete this._callbackManager; + this._callbackManager = null; + } + + if(this._registrationManager) + { + this._registrationManager.destroy(); + delete this._registrationManager; + this._registrationManager = null; + } + + if(this._partyManager) + { + this._partyManager.destroy(); + delete this._partyManager; + this._partyManager = null; + } + + common.InterfaceImplementation.prototype.destroy.call(this); + } + catch(ex) + { + common.Debug.traceError(ex.message); + common.Debug.breakpoint(); + common.Debug.alert(ex.message); + } + common.Debug.traceMethodExited("Page.destroy()"); + }, + + /** + * Resets the Page to the state which it was in prior to any activity taking place. + */ + reset : function() + { + this._numServerConfigRequestFailovers = 0; + }, + + // private methods + + _verifyLoadParameters : function(params) + { + if (!params) + { + throw common.ExceptionFactory.createException("No parameters specified to Page.load()!"); + } + + if (!params.currentUriFragment) + { + throw common.ExceptionFactory.createException("No current URI fragment specified!"); + } + + if (!params.uriFragments) + { + throw common.ExceptionFactory.createException("No URI fragments specified"); + } + + if (!params.pageMode) + { + throw common.ExceptionFactory.createException("Page mode not specified"); + } + + if (params.pageMode & ui.PageModes.CHAT) + { + if (!params.chatTarget) + { + throw common.ExceptionFactory.createException("Chat target not specified"); + } + + if (!params.chatTargetType) + { + common.Debug.traceWarning('Chat target type not specified, assuming "Workgroup".'); + params.chatTargetType = "Workgroup"; + } + } + + if (params.pageMode & ui.PageModes.CALLBACK) + { + if (!params.callbackTarget) + { + throw common.ExceptionFactory.createException("Callback target not specified"); + } + + if (!params.callbackTargetType) + { + common.Debug.traceWarning('Callback target type not specified, assuming "Workgroup".'); + params.callbackTargetType = "Workgroup"; + } + } + + if (!params.defaultLanguageCode) + { + common.Debug.traceWarning("Default language not specified, assuming en-us"); + params.defaultLanguageCode = "en-us"; + } + + if (!(params.useHttps === true || params.useHttps === false)) + { + common.Debug.traceWarning("useHttps not specified, assuming true"); + params.useHttps = true; + } + + return params; + }, + + _serverConfigurationCallback : function(success) + { + common.Debug.traceMethodEntered("Page._serverConfigurationCallback()"); + try + { + if(success) + { + common.Debug.traceStatus("Server configuration obtained successfully."); + this._constructUI(); + } + else + { + if(this._shouldSwitchoverAndTryToGetServerConfigurationAgain()) + { + common.Debug.traceStatus("Going to switch over, and try again to obtain server configuration."); + webservices.Servers.switchCurrentServer(); + this._numServerConfigRequestFailovers++; + webservices.Json.ServerConfigurationManager.getServerConfiguration(this._serverConfigurationCallback.bind(this)); + } + else + { + common.Debug.traceStatus("Failed to obtain server configuration."); + this._constructFailureUI(); + } + } + } + catch(ex) + { + common.Debug.breakpoint(); + common.Debug.traceError(ex.message); + if (ex.stack) + { + common.Debug.traceStatus(ex.stack); + } + common.Debug.alert(ex.message); + webservices.ProblemReporter.sendProblemReport(ex, "Page._serverConfigurationCallback()"); + } + common.Debug.traceMethodExited("Page._serverConfigurationCallback()"); + }, + + _shouldSwitchoverAndTryToGetServerConfigurationAgain : function() + { + if (!webservices.Servers.isConfiguredForSwitchover()) + { + // In this case, the retry logic was already handled + // in AjaxManagerBase._shouldRequestBeRetriedBasedOnMessageTypeAndRetryCount(). + return false; + } + + // Adding 1 because retryCounts maintains the number of times to REtry. So, for instance, + // initial try + 3 retries = 4 total tries. + var retryCounts = webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.RetryCounts); + var numTimesToTry = webservices.Servers.get_numberOfServers() * + (1 + retryCounts.get_serverConfigurationRetries()); + + return (this._numServerConfigRequestFailovers < numTimesToTry); + }, + + _constructUI : function() + { + common.Debug.traceMethodEntered("Page._constructUI()"); + + var lang = localization.LanguageCode; + document.body.dir = localization.TextDirection; + + // initialize ui controls + this._webChat = new ui.WebChat(this._chatManager, this._callbackManager, this._registrationManager, this._pageMode, this._chatParameters, this._callbackParameters, true, this._useHtmlEditor, lang, this._chatFollowupUrl); + + // add the controls to the page + Element.insert(this.getContainingElement(), { bottom: this._webChat.get_domObject() }); + + this._webChat.focus(); + + var loginContainerPanel = this._webChat.getLoginContainerPanel(); + if (loginContainerPanel) // Will not exist if bypassing the login form + { + loginContainerPanel.constructUI(); + } + + common.Debug.traceMethodExited("Page._constructUI()"); + }, + + _constructFailureUI : function() + { + common.Debug.traceMethodEntered("Page._constructFailureUI()"); + this.getContainingElement().appendChild(this._buildErrorPanel()); + common.Debug.traceMethodExited("Page._constructFailureUI()"); + }, + + _buildErrorPanel : function() + { + common.Debug.traceMethodEntered("Page._buildErrorPanel()"); + var msg = "There was an error connecting to the server"; + try + { + msg = localization.ErrorConnectingToServer; + } + catch (ex) + { + /* + * If we're in this method, we've failed to get server configuration. + * Javascript can't "see" the web browser's language settings, so we rely on the server configuration response + * to include the value of the HTTP Accept-Language parameter. Since we didn't receive that, localization has + * not been loaded yet, so we will certainly land in this catch clause. So, this message will be in English. + */ + } + var divError = new Element('div', { 'class': 'iwc-load-error' }); + divError.appendChild(new Element('img', { 'src': 'img/error.png' })); + var msgElement = new Element('span', null); + msgElement.innerHTML = msg; + divError.appendChild(msgElement); + common.Debug.traceMethodExited("Page._buildErrorPanel()"); + return divError; + } +}); + +/** + * Singleton instance of _Page class. + */ +ui.Page = new ui._Internal._Page(); + +// Register namespaces +ui.registerChildNamespace("_Internal"); + +/** + * _DefaultLoginInfoSource class + * + * Do not instantiate this class directly. Use + * webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.LoginInfoSource) + * + * In the default installation, a page is shown which allows the user to + * select between tabs for Chat, Callback, and Registration. What goes on + * "behind the scenes" is that ui.LoginInfoSourceFactory is called + * to get a source for login info (username, password, Callback telephone number, + * Callback subject). An instance of this class is returned. However, this class + * has no knowledge of what the login info should be, so all of its methods return + * null. Therefore, the page with the three tabs is shown, to allow the user to + * enter the login info. + * + * However, if the user's information is already known (i.e., if it exists + * in some external source, such as a cookie, database, the form submission + * data from some external form, etc.) then the page with the three tabs may + * be bypassed. In this case the user may be sent directly into a Chat, or + * a Callback may be created without interaction from the User. + * + * To do this, simply create a subclass of this class, and implement the various + * methods to return the login info that is obtained from the external source. + * Then edit the customizations.LoginInfoSourceFactory class, to + * return an instance of the subclass, rather than an instance of this class. + * + * Please see also webservices.Notification. This class + * contains Notifications that are sent upon creation of a Chat or Callback, + * completion of a Chat, and failure to create a Chat or Callback. Customizations + * which subclass _DefaultLoginInfoSource will likely also want to add + * process*Notification() events for these Notification types, so that they + * may perform appropriate UI tasks for those events. + */ +ui._Internal._DefaultLoginInfoSource = Class.create( +{ + /** + * Constructor. Does nothing. + */ + initialize : function() + { + }, + + /** + * A subclass may override this method to skip the login page, and begin a Chat + * right away using a username obtained from some other source (for instance, + * a cookie, form data posted from a previous page, etc.) + */ + get_chatUsername : function() + { + return null; + }, + + /** + * If a subclass overrides get_chatUsername() to return non-null, this method may + * optionally be overridden to return the password of that user. If an anonymous Chat is + * desired, simply override get_chatUsername() but not get_chatPassword(). + */ + get_chatPassword : function() + { + return null; + }, + + /** + * A subclass may override this method (and others below) to skip the login + * page, and begin a Callback right away using a username obtained from + * some other source (for instance, a cookie, form data posted from a + * previous page, etc.) + * + * Note that if get_chatUsername() is also overridden and returns a non-null + * value, that will take priority and a Chat will be started, not a Callback. + */ + get_callbackUsername : function() + { + return null; + }, + + /** + * If a subclass overrides get_callbackUsername() to return non-null, this method may + * optionally be overridden to return the password of that user. If an anonymous Callback is + * desired, simply override get_callbackUsername() but not get_callbackPassword(). + */ + get_callbackPassword : function() + { + return null; + }, + + /** + * If a subclass overrides get_callbackUsername() to return non-null, this method shall + * be overridden to return the telephone number of that user. + */ + get_callbackTelephone : function() + { + return null; + }, + + /** + * If a subclass overrides get_callbackUsername() to return non-null, this method shall + * be overridden to return the subject which that user wishes to discuss. + */ + get_callbackDescription : function() + { + return null; + } +}); + +// Register namespaces +ui.registerChildNamespace("_Internal"); + +/** + * DefaultMaximumFieldLengths class + * + * Do not instantiate this class directly. Use + * webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.MaximumFieldLengths) + * + * In the default installation, each text field within the 3 tabs will allow the user + * to enter up to the maximum number of characters that Tracker will support for that + * data type. + * + * If it is desired to have a different maximum length for one or more fields, the + * following steps may be taken: + * 1. Create a subclass of this class. Override one or methods to return a different number. + * Note that it is not advisable to increase the returned values, as they are by default + * set to the maximum data length which Tracker can handle. + * Also note that this will have no effect on the pixel width of these fields - that can + * be changed by editing the ".iwc-text-box" selector of the CSS. + * 2. Change the line in customizations.MaximumFieldLengthsFactory that + * instantiates a new ui._Internal._DefaultMaximumFieldLengths. + * Make that line instead create an instance of the subclass from step 1. + */ +ui._Internal._DefaultMaximumFieldLengths = Class.create( +{ + // Do not change these values (unless the Tracker DB Schema changes) + TRACKER_USERNAME_MAXIMUM_LENGTH : 100, + TRACKER_PASSWORD_MAXIMUM_LENGTH : 64, + TRACKER_FIRST_NAME_MAXIMUM_LENGTH : 50, + TRACKER_MIDDLE_NAME_MAXIMUM_LENGTH : 50, + TRACKER_LAST_NAME_MAXIMUM_LENGTH : 50, + TRACKER_NAME_MAXIMUM_LENGTH : 128, + TRACKER_TELEPHONE_MAXIMUM_LENGTH : 255, + TRACKER_SUBJECT_MAXIMUM_LENGTH : 2000, + TRACKER_ADDRESS_MAXIMUM_LENGTH : 255, + TRACKER_CITY_MAXIMUM_LENGTH : 50, + TRACKER_STATE_MAXIMUM_LENGTH : 50, + TRACKER_POSTAL_CODE_MAXIMUM_LENGTH : 20, + TRACKER_COUNTRY_MAXIMUM_LENGTH : 50, + TRACKER_EMAIL_MAXIMUM_LENGTH : 255, + TRACKER_URL_MAXIMUM_LENGTH : 255, + TRACKER_DEPARTMENT_MAXIMUM_LENGTH : 50, + TRACKER_COMPANY_MAXIMUM_LENGTH : 100, + TRACKER_JOB_TITLE_MAXIMUM_LENGTH : 100, + TRACKER_REMARKS_MAXIMUM_LENGTH : 2000, + + /** + * Constructor. Does nothing. + */ + initialize : function() + { + }, + + /** + * Override this method with one that returns a different number to alter the maximum + * number of characters that a web user is allowed to type into a username field + */ + get_usernameMaximumLength : function() + { + return this.TRACKER_USERNAME_MAXIMUM_LENGTH; + }, + + /** + * Override this method with one that returns a different number to alter the maximum + * number of characters that a web user is allowed to type into a password field + */ + get_passwordMaximumLength : function() + { + return this.TRACKER_PASSWORD_MAXIMUM_LENGTH; + }, + + /** + * Override this method with one that returns a different number to alter the maximum + * number of characters that a web user is allowed to type into a first name field + */ + get_firstNameMaximumLength : function() + { + return this.TRACKER_FIRST_NAME_MAXIMUM_LENGTH; + }, + + /** + * Override this method with one that returns a different number to alter the maximum + * number of characters that a web user is allowed to type into a middle name field + */ + get_middleNameMaximumLength : function() + { + return this.TRACKER_MIDDLE_NAME_MAXIMUM_LENGTH; + }, + + /** + * Override this method with one that returns a different number to alter the maximum + * number of characters that a web user is allowed to type into a last name field + */ + get_lastNameMaximumLength : function() + { + return this.TRACKER_LAST_NAME_MAXIMUM_LENGTH; + }, + + /** + * Override this method with one that returns a different number to alter the maximum + * number of characters that a web user is allowed to type into a name field (currently + * used in two places: the name field of the web user when they choose "I don't have + * an account", and the "Assistant Name" field). + */ + get_nameMaximumLength : function() + { + return this.TRACKER_NAME_MAXIMUM_LENGTH; + }, + + /** + * Override this method with one that returns a different number to alter the maximum + * number of characters that a web user is allowed to type into a telephone (or fax, etc.) number field + */ + get_telephoneMaximumLength : function() + { + return this.TRACKER_TELEPHONE_MAXIMUM_LENGTH; + }, + + /** + * Override this method with one that returns a different number to alter the maximum + * number of characters that a web user is allowed to type into a callback subject field + */ + get_subjectMaximumLength : function() + { + return this.TRACKER_SUBJECT_MAXIMUM_LENGTH; + }, + + /** + * Override this method with one that returns a different number to alter the maximum + * number of characters that a web user is allowed to type into a street address field + */ + get_addressMaximumLength : function() + { + return this.TRACKER_ADDRESS_MAXIMUM_LENGTH; + }, + + /** + * Override this method with one that returns a different number to alter the maximum + * number of characters that a web user is allowed to type into a city name field + */ + get_cityMaximumLength : function() + { + return this.TRACKER_CITY_MAXIMUM_LENGTH; + }, + + /** + * Override this method with one that returns a different number to alter the maximum + * number of characters that a web user is allowed to type into a state (or province/territory) field + */ + get_stateMaximumLength : function() + { + return this.TRACKER_STATE_MAXIMUM_LENGTH; + }, + + /** + * Override this method with one that returns a different number to alter the maximum + * number of characters that a web user is allowed to type into a postal code field + */ + get_postalCodeMaximumLength : function() + { + return this.TRACKER_POSTAL_CODE_MAXIMUM_LENGTH; + }, + + /** + * Override this method with one that returns a different number to alter the maximum + * number of characters that a web user is allowed to type into a country field + */ + get_countryMaximumLength : function() + { + return this.TRACKER_COUNTRY_MAXIMUM_LENGTH; + }, + + /** + * Override this method with one that returns a different number to alter the maximum + * number of characters that a web user is allowed to type into an email field + */ + get_emailMaximumLength : function() + { + return this.TRACKER_EMAIL_MAXIMUM_LENGTH; + }, + + /** + * Override this method with one that returns a different number to alter the maximum + * number of characters that a web user is allowed to type into a URL field + */ + get_urlMaximumLength : function() + { + return this.TRACKER_URL_MAXIMUM_LENGTH; + }, + + /** + * Override this method with one that returns a different number to alter the maximum + * number of characters that a web user is allowed to type into a department name field + */ + get_departmentMaximumLength : function() + { + return this.TRACKER_DEPARTMENT_MAXIMUM_LENGTH; + }, + + /** + * Override this method with one that returns a different number to alter the maximum + * number of characters that a web user is allowed to type into a company name field + */ + get_companyMaximumLength : function() + { + return this.TRACKER_COMPANY_MAXIMUM_LENGTH; + }, + + /** + * Override this method with one that returns a different number to alter the maximum + * number of characters that a web user is allowed to type into a job title field + */ + get_jobTitleMaximumLength : function() + { + return this.TRACKER_JOB_TITLE_MAXIMUM_LENGTH; + }, + + /** + * Override this method with one that returns a different number to alter the maximum + * number of characters that a web user is allowed to type into a remarks field + */ + get_remarksMaximumLength : function() + { + return this.TRACKER_REMARKS_MAXIMUM_LENGTH; + } + +}); + +// Register namespaces +ui.registerChildNamespace("_Internal"); + +/** + * _DefaultTabVisibility class + * + * Do not instantiate this class directly. Use + * webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.TabVisibility) + * + * By default: + * + * 1. The "Start Chat" tab is displayed if the Web Processor Bridge + * includes "start" and ("supportChatAuthenticationTracker" or "supportChatAuthenticationAnonymous") + * in the list of chat capabilities (part of the server configuration response). + * + * 2. The "Start Callback" tab is displayed if "create" and ("supportCallbackAuthenticationTracker" + * or "supportCallbackAuthenticationAnonymous") is included in the list of callback capabilities. + * + * 3. The "Register New Account" tab is displayed if "supportRegistrationTracker" + * is included in the list of common capabilities. + * + * However, currently the Web Processor Bridge always includes all of the above. + * Therefore, this class (or a subclass thereof, depending on what + * customizations.TabVisibilityFactory returns) is queried to determine whether + * each tab should be shown or not. + * + * To prevent certain tabs from displayed: + * 1. Create a subclass of this class which overrides one or more methods in this class. + * 2. Modify TabVisibilityFactory to return an instance of the new subclass instead + * of an instance of this class. + */ +ui._Internal._DefaultTabVisibility = Class.create( +{ + /** + * Constructor. Does nothing. + */ + initialize : function() + { + }, + + /** + * If a subclass overrides this return value to true, the "Start Chat" tab will + * not be displayed. + */ + hideStartChatTab : function() + { + return false; + }, + + /** + * If a subclass overrides this return value to true, the "Start Callback" tab will + * not be displayed. + */ + hideStartCallbackTab : function() + { + return false; + }, + + /** + * If a subclass overrides this return value to true, the "Register New Account" tab will + * not be displayed, and the "Create an account" link on the other two tabs will also be hidden. + */ + hideRegisterNewAccountTab : function() + { + return false; + }, + + /** + * If this method returns false, the link to display a printable chat transcript + * will be displayed. If it returns true, the link will not be displayed. + * + * In the default implementation, false is returned. However, subclasses may + * override this method if the link is not (always) desired. + * + * If true is returned, the resource strings "ClosePageWarning" and "ExitPageWarning" + * should be reworded, since they mention the ability to print a transcript. + * + * @return boolean indicating whether the printable chat history link should be hidden. + */ + disablePrintableChatHistory : function() + { + return false; + } +}); + +// Register namespaces +ui.registerChildNamespace("_Internal"); + +/** + * _DefaultStatusFieldsDisplay class + * + * Do not instantiate this class directly. Use + * webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.StatusFieldsDisplay) + * + * Controls whether the following fields are displayed in the Callback Status Panel. By default, all are displayed. + * Assigned Agent Name + * Interaction State + * Estimated Callback Time + * Queue Wait Time + * Queue Position + * Longest Wait Time + * Interactions Waiting Count + * Logged In Agents Count + * Available Agents Count + * Subject (entered by web user) + * Creation Time (time the callback request was submitted by web user) + * Web User's name (if anonymous) or username (if authenticated) + * Web user's telephone number + */ +ui._Internal._DefaultStatusFieldsDisplay = Class.create( +{ + /** + * Constructor. Does nothing. + */ + initialize : function() + { + }, + + /** + * This method returns whether the assigned agent's name should be displayed + * in the callback status panel. + * + * @return Boolean + */ + get_showAssignedAgentName : function() + { + return true; + }, + + /** + * This method returns whether the interaction state should be displayed + * in the callback status panel. + * + * @return Boolean + */ + get_showInteractionState : function() + { + return true; + }, + + /** + * This method returns whether the assigned estimated callback time should be displayed + * in the callback status panel. + * + * @return Boolean + */ + get_showEstimatedCallbackTime : function() + { + return true; + }, + + /** + * This method returns whether the queue wait time should be displayed + * in the callback status panel. + * + * @return Boolean + */ + get_showQueueWaitTime : function() + { + return true; + }, + + /** + * This method returns whether the callback's position in the queue should be displayed + * in the callback status panel. + * + * @return Boolean + */ + get_showQueuePosition : function() + { + return true; + }, + + /** + * This method returns whether the longest wait time of interactions in the queue should be displayed + * in the callback status panel. + * + * @return Boolean + */ + get_showLongestWaitTime : function() + { + return true; + }, + + /** + * This method returns whether the number of interactions waiting on + * the queue should be displayed in the callback status panel. + * + * @return Boolean + */ + get_showInteractionsWaitingCount : function() + { + return true; + }, + + /** + * This method returns whether the number of agents logged in should be displayed + * in the callback status panel. + * + * @return Boolean + */ + get_showLoggedInAgentsCount : function() + { + return true; + }, + + /** + * This method returns whether the number of available agents should be displayed + * in the callback status panel. + * + * @return Boolean + */ + get_showAvailableAgentsCount : function() + { + return true; + }, + + /** + * This method returns whether the callback's subject (as entered by the web user) + * should be displayed in the callback status panel. + * + * @return Boolean + */ + get_showSubject : function() + { + return true; + }, + + /** + * This method returns whether the creation date/time of the callback should be displayed + * in the callback status panel. + * + * @return Boolean + */ + get_showCreationDateTime : function() + { + return true; + }, + + /** + * This method returns whether the web user's name (if anonymous) or username (if authenticated) + * should be displayed in the callback status panel. + * + * @return Boolean + */ + get_showName : function() + { + return true; + }, + + /** + * This method returns whether the web user's telephone number should be displayed + * in the callback status panel. + * + * @return Boolean + */ + get_showTelephone : function() + { + return true; + }, + + /** + * Takes a prefix common to several resource keys, and a number of seconds, and + * returns a localized string displaying that time duration. + * + * This is a customization point, to allow customers to tweak the number + * of seconds before display. This could be used to make the shortest displayed + * time be 5 minutes, or to build in some over- or under-estimation, or to display + * only increments of 5 minutes, etc. for instance. + * + * In this implementation, if seconds represents... + * ...zero to 89 seconds, the returned value will be the resource + * whose key is: resourcePrefix + "_Minute" + * ...between 90 seconds and 45 minutes, the returned value will be the + * rounded number of minutes substituted into the resource whose + * key is: resourcePrefix + "_Minutes" + * ...between 46 and 89 minutes, the returned value will be the resource + * whose key is: resourcePrefix + "_Hour" + * ...between 90 minutes and 20 hours, the returned value will be the rounded number + * of hours substituted into the resource whose key is: resourcePrefix + "_Hours" + * ...at least 20 hours but less than 36 hours, the returned value will be the + * resource whose key is: resourcePrefix + "_Day" + * ...at least 36 hours, the returned value will be the rounded number of days substituted + * into the resource whose key is: resourcePrefix + "_Hours" + * + * In a later SU, this method will be changed to correctly handle the special rules for writing + * plural numbers in languages such as Russian and Polish. + * + * @param resourcePrefix - A prefix common to several keys in the resource file. This method may append "_Minute", "_Minutes", "_Hour", "_Hours". + * @param seconds - integer number of seconds + * @return Localized string + */ + formatTimeDuration : function(resourcePrefix, seconds) + { + var timeDuration = new webservices.TimeDuration(seconds); + var resourceSuffix = ""; + + if (timeDuration.getTotalSeconds() <= 89) + { + return localization[resourcePrefix + "_Minute"]; + } + else if (timeDuration.getTotalMinutes() <= 45) + { + var nMinutesToDisplay = timeDuration.getRoundedMinutes(); + return localization[resourcePrefix + "_Minutes"].replace('%0', nMinutesToDisplay); + } + else if (timeDuration.getTotalMinutes() <= 89) + { + return localization[resourcePrefix + "_Hour"]; + } + else if (timeDuration.getTotalHours() <= 20) + { + var nHoursToDisplay = timeDuration.getRoundedHours(); + return localization[resourcePrefix + "_Hours"].replace('%0', nHoursToDisplay); + } + else if (timeDuration.getTotalHours() <= 36) + { + return localization[resourcePrefix + "_Day"]; + } + else + { + nDaysToDisplay = timeDuration.getRoundedDays(); + return localization[resourcePrefix + "_Days"].replace('%0', nDaysToDisplay); + } + } +}); + + return ui; +}); + + diff --git a/src/ChatPage/chat/BypassLoginForm/js/WebServices.js b/src/ChatPage/chat/BypassLoginForm/js/WebServices.js new file mode 100755 index 0000000..c161b77 --- /dev/null +++ b/src/ChatPage/chat/BypassLoginForm/js/WebServices.js @@ -0,0 +1,16711 @@ +/************************************************************************************************ +* This file contains Javascript code authored by Interactive Intelligence, Inc. * +* * +* The contents of this file are warranted to function as intended, provided they are not * +* modified in any way by customers, end users, or other parties. * +* * +* During the course of this product's support lifecycle, Interactive Intelligence, Inc. may * +* publish updates to this file at any time, via an SU or similar process. If other * +* modifications are made to this file, these modifications may therefore be overwritten. * +* * +* Customers are encouraged to extend the functionality provided in this file, by creating * +* additional file(s) which use this file as an API. * +************************************************************************************************/ + + +/* Interaction Center 4.0 SU5 */ +var ININ_Web_Chat_WebServices_Fileversion = "4.0005.0017.422"; + +define(['i18n!nls/localization', 'common'], function(localization, common) +{ + var webservices = common.Type.registerLocalNamespace("webservices"); + +// Register namespaces +webservices.registerChildNamespace("Interfaces"); + +/** + * ICapability interface + * Provides methods: get_relativeUrl(), get_method() + */ +webservices.Interfaces.ICapability = new common.Interface('webservices.Interfaces.ICapability', ['get_relativeUrl', 'get_method']); + +// ICapability derived interfaces + +/** + * IStartCapability interface, derived from ICapability + * Provides additional methods: none + */ +webservices.Interfaces.IStartCapability = new common.Interface('webservices.Interfaces.IStartCapability', [], ['webservices.Interfaces.ICapability'], webservices); + +/** + * IPollCapability interface, derived from ICapability + * Provides additional methods: none + */ +webservices.Interfaces.IPollCapability = new common.Interface('webservices.Interfaces.IPollCapability', [], ['webservices.Interfaces.ICapability'], webservices); + +/** + * ISendMessageCapability interface, derived from ICapability + * Provides additional methods: none + */ +webservices.Interfaces.ISendMessageCapability = new common.Interface('webservices.Interfaces.ISendMessageCapability', [], ['webservices.Interfaces.ICapability'], webservices); + +/** + * ISetTypingStateCapability interface, derived from ICapability + * Provides additional methods: none + */ +webservices.Interfaces.ISetTypingStateCapability = new common.Interface('webservices.Interfaces.ISetTypingStateCapability', [], ['webservices.Interfaces.ICapability'], webservices); + +/** + * IExitCapability interface, derived from ICapability + * Provides additional methods: none + */ +webservices.Interfaces.IExitCapability = new common.Interface('webservices.Interfaces.IExitCapability', [], ['webservices.Interfaces.ICapability'], webservices); + +// Register namespaces +webservices.registerChildNamespace("Interfaces"); + +// may need an interface is a CapabilitiesObserver so if any are changed... +// Register namespaces +webservices.registerChildNamespace("Interfaces"); + +/** + * ICustomizationSingletonFactory interface + * Provides methods: get_instance() + */ +webservices.Interfaces.ICustomizationSingletonFactory = new common.Interface('webservices.Interfaces.ICustomizationFactory', ['get_instance']); + +/** + * ICustomizationFactory interface + * Provides methods: create_instance() + */ +webservices.Interfaces.ICustomizationFactory = new common.Interface('webservices.Interfaces.ICustomizationFactory', ['create_instance']); + +// Register namespaces +webservices.registerChildNamespace("Interfaces"); + +/** + * IError interface + * Provides methods: get_token(), get_errorType(), get_subErrorType(), get_errorCode() + */ +webservices.Interfaces.IError = new common.Interface('webservices.Interfaces.IError', ['get_token', 'get_errorType', 'get_subErrorType', 'get_errorCode']); + +// Register namespaces +webservices.registerChildNamespace("Interfaces"); + +/** + * INotification interface + * Provides methods: none + */ +webservices.Interfaces.INotification = new common.Interface('webservices.Interfaces.INotification', []); + + +// Register namespaces +webservices.registerChildNamespace("Interfaces"); + +/** + * INotificationProcessor interface + * Provides method: process() + */ +webservices.Interfaces.INotificationProcessor = new common.Interface('webservices.Interfaces.INotificationProcessor', ['process']); + +// Register namespaces +webservices.registerChildNamespace("Interfaces"); + +// INotification derived interfaces + +/** + * IQueueStatusNotification interface, derived from INotification + * Provides additional methods: get_agentsAvailable, get_estimatedWaitTime + */ +webservices.Interfaces.IQueueStatusNotification = new common.Interface('webservices.Interfaces.IQueueStatusNotification', [ 'get_agentsAvailable, get_estimatedWaitTime' ], ['webservices.Interfaces.INotification'], webservices); + +/** + * IQueueStatusFailureNotification interface, derived from INotification + * Provides additional methods: get_error + */ +webservices.Interfaces.IQueueStatusFailureNotification = new common.Interface('webservices.Interfaces.IQueueStatusFailureNotification', [ 'get_error' ], ['webservices.Interfaces.INotification'], webservices); + +// Register namespaces +webservices.registerChildNamespace("Interfaces"); + +/** + * IResponse interface + * Provides methods: get_statusType(), set_statusReason(), isSuccessful() + */ +webservices.Interfaces.IResponse = new common.Interface('webservices.Interfaces.IResponse', ['get_statusType', 'set_statusReason', 'isSuccessful']); + +// IResponse derived interfaces + +/** + * IChatResponse interface, derived from IResponse + * Provides additional methods: get_pollWaitSuggestion(), set_pollWaitSuggestion(), get_events(), addEvent() + */ +webservices.Interfaces.IChatResponse = new common.Interface('webservices.Interfaces.IChatResponse', ['get_pollWaitSuggestion', 'set_pollWaitSuggestion', 'get_events', 'addEvent'], ['webservices.Interfaces.IResponse'], webservices); + +/** + * ICallbackResponse interface, derived from IResponse + * Provides additional methods: none + */ +webservices.Interfaces.ICallbackResponse = new common.Interface('webservices.Interfaces.ICallbackResponse', [], ['webservices.Interfaces.IResponse'], webservices); + +/** + * ICallbackCreateResponse interface, derived from ICallbackResponse + * Provides additional methods: get_participantId(), set_participantId() + */ +webservices.Interfaces.ICallbackCreateResponse = new common.Interface('webservices.Interfaces.ICallbackCreateResponse', ['get_participantId', 'set_participantId'], ['webservices.Interfaces.ICallbackResponse'], webservices); + +/** + * ICallbackStatusResponse interface, derived from ICallbackResponse + * Provides additional methods: get_queueWaitTime(), set_queueWaitTime(), + * get_assignedAgentName(), get_assignedAgentParticipantId(), set_assignedAgentName(), set_assignedAgentParticipantId(), + * get_interactionState(), set_interactionState(), get_estimatedCallbackTime(), set_estimatedCallbackTime(), + * get_queuePosition(), set_queuePosition(), get_queueName(), set_queueName(), + * get_longestWaitTime(), set_longestWaitTime(), get_interactionsWaitingCount(), set_interactionsWaitingCount(), + * get_loggedInAgentsCount(), set_loggedInAgentsCount(), get_availableAgentsCount(), set_availableAgentsCount() + */ +webservices.Interfaces.ICallbackStatusResponse = new common.Interface('webservices.Interfaces.ICallbackStatusResponse', [ 'get_queueWaitTime', 'set_queueWaitTime', 'get_assignedAgentName', 'get_assignedAgentParticipantId', 'set_assignedAgentName', 'set_assignedAgentParticipantId', 'get_interactionState', 'set_interactionState', 'get_estimatedCallbackTime', 'set_estimatedCallbackTime', 'get_queuePosition', 'set_queuePosition', 'get_queueName', 'set_queueName', 'get_longestWaitTime', 'set_longestWaitTime', 'get_interactionsWaitingCount', 'set_interactionsWaitingCount', 'get_loggedInAgentsCount', 'set_loggedInAgentsCount', 'get_availableAgentsCount', 'set_availableAgentsCount' ], ['webservices.Interfaces.ICallbackResponse'], webservices); + +/** + * ICallbackReconnectResponse interface, derived from ICallbackResponse + * Provides additional method: get_participantId(), set_participantId() + */ +webservices.Interfaces.ICallbackReconnectResponse = new common.Interface('webservices.Interfaces.ICallbackReconnectResponse', ['get_participantId', 'set_participantId'], ['webservices.Interfaces.ICallbackResponse'], webservices); + +/** + * IQueueQueryResponse interface, derived from IResponse + * Provides additional methods: get_agentsAvailable(), set_agentsAvailable(), get_estimatedWaitTime(), set_estimatedWaitTime() + */ +webservices.Interfaces.IQueueQueryResponse = new common.Interface('webservices.Interfaces.IQueueQueryResponse', ['get_agentsAvailable', 'set_agentsAvailable', 'get_estimatedWaitTime', 'set_estimatedWaitTime'], ['webservices.Interfaces.IResponse'], webservices); + +/** + * IRegistrationResponse interface, derived from IResponse + * Provides additional methods: none + */ +webservices.Interfaces.IRegistrationResponse = new common.Interface('webservices.Interfaces.IRegistrationResponse', [], ['webservices.Interfaces.IResponse'], webservices); + +/** + * IServerConfigurationResponse interface, derived from IResponse + * Provides additional methods: get_commonCapabilities(), addCommonCapability(), set_commonCapabilities(), get_chatCapabilities(), addChatCapability(), set_chatCapabilities(), get_callbackCapabilities(), addCallbackCapability(), set_callbackCapabilities() + */ +webservices.Interfaces.IServerConfigurationResponse = new common.Interface('webservices.Interfaces.IServerConfigurationResponse', ['get_commonCapabilities', 'addCommonCapability', 'set_commonCapabilities', 'get_chatCapabilities', 'addChatCapability', 'set_chatCapabilities', 'get_callbackCapabilities', 'addCallbackCapability', 'set_callbackCapabilities'], ['webservices.Interfaces.IResponse'], webservices); + +/** + * IPartyInfoResponse interface, derived from IResponse + * Provides additional methods: get_name(), set_name(), get_photo(), set_photo() + */ +webservices.Interfaces.IPartyInfoResponse = new common.Interface('webservices.Interfaces.IPartyInfoResponse', ['get_name', 'set_name', 'get_photo', 'set_photo'], ['webservices.Interfaces.IResponse'], webservices); + +// Register namespaces +webservices.registerChildNamespace("_Internal"); + +/** + * NotificationBase class + * + * Base class of objects which other objects may listen for. + * Created by NotificationFactory, upon receipt of Events. + */ +webservices._Internal.NotificationBase = Class.create(common.InterfaceImplementation, +{ + _className : "webservices._Internal.NotificationBase", + + /** + * Constructor + */ + initialize : function($super) + { + if(arguments.length != 1) + { + throw common.ExceptionFactory.createException("NotificationBase constructor called with " + arguments.length + " arguments, but expected 1."); + } + + $super(); + + this.addImplementedInterface(webservices.Interfaces.INotification, webservices); + }, + + /** + * Destructor + */ + destroy : function() + { + common.InterfaceImplementation.prototype.destroy.call(this); + }, + + get_className : function() + { + return this._className; + } +}); + + +// Register namespaces +webservices.registerChildNamespace("_Internal"); + +/** + * A Notification that the status of a queue has been retrieved. + */ +webservices.QueueStatusNotification = Class.create(webservices._Internal.NotificationBase, +{ + _className : "webservices.QueueStatusNotification", + + /** + * Constructor + * + * @param agentsAvailable How many agents are currently available on this queue + * @param estimatedWaitTime How long in seconds it is estimated that it will take for an agent to answer an interaction, if it were placed onto the queue now. + */ + initialize : function($super, agentsAvailable, estimatedWaitTime) + { + if(arguments.length != 3) + { + throw common.ExceptionFactory.createException("QueueStatusNotification constructor called with " + arguments.length + " arguments, but expected 3."); + } + + $super(); + + this.addImplementedInterface(webservices.Interfaces.IQueueStatusNotification, webservices); + + this._agentsAvailable = agentsAvailable; + this._estimatedWaitTime = estimatedWaitTime; + }, + + /** + * Returns the number of agents available on this queue + * + * @return The number of agents available on this queue + */ + get_agentsAvailable : function() + { + return this._agentsAvailable; + }, + + /** + * Returns the queue's estimated wait time, in seconds. + * + * @param estimatedWaitTime How long in seconds it is estimated that it will take for an agent to answer an interaction, if it were placed onto the queue now. + */ + get_estimatedWaitTime : function() + { + return this._estimatedWaitTime; + } +}); + +/** + * A Notification that an attempt to retrieve the status of a queue has failed. + */ +webservices.QueueStatusFailureNotification = Class.create(webservices._Internal.NotificationBase, +{ + _className : "webservices.QueueStatusFailureNotification", + + /** + * Constructor + * + * @param error The error that caused the failure + */ + initialize : function($super, error) + { + if(arguments.length != 2) + { + throw common.ExceptionFactory.createException("QueueStatusFailureNotification constructor called with " + arguments.length + " arguments, but expected 2."); + } + + $super(); + + this.addImplementedInterface(webservices.Interfaces.IQueueStatusFailureNotification, webservices); + + this._error = error; + }, + + /** + * Returns the error that caused this failure. + * + * @return The error that caused this failure. + */ + get_error : function() + { + return this._error; + } +}); + +// Register namespaces +webservices.registerChildNamespace("_Internal"); + +/** + * NotificationRegistry class + * + * Allows interested objects to register as observers to receive the various types of notifications. + * Receives notifications and forwards them to the interested observers. + */ +webservices._Internal._NotificationRegistry = Class.create(common.InterfaceImplementation, +{ + /** + * Constructor + */ + initialize : function($super, notificationFactory) + { + // validate arguments + var numArgs = 2; + if(arguments.length != numArgs) + { + throw common.ExceptionFactory.createException("NotificationRegistry constructor called with " + arguments.length + " arguments, but expected " + numArgs + "."); + } + + $super(); + + this.addImplementedInterface(webservices.Interfaces.INotificationProcessor, webservices); + + // initialize private members + + this._notificationFactory = notificationFactory; + + /* + * Map of "simple" notification name (just a string, not the full class/interface) to array of observer method(s) + * to call when a notification of that type is passed to process(). For instance, may contain: + * { + * "FooNotification" -> [ someClass.processFooNotification, someOtherClass.processFooNotification ], + * "BarNotification" -> [ someClass.processBarNotification ] + * } + */ + this._notificationSimpleNameToObserverMethods = {}; + }, + + /** + * Destructor + */ + destroy : function() + { + for (var notificationSimpleName in this._notificationSimpleNameToObserverMethods) + { + delete this._notificationSimpleNameToObserverMethods[notificationSimpleName]; + } + this._notificationSimpleNameToObserverMethods = {}; + + common.InterfaceImplementation.prototype.destroy.call(this); + }, + + /** + * This method makes the NotificationRegistry know about a particular + * Notification type: how to register observers of it, how to create an instance + * of it, how to process an instance of it. It also creates an observer interface, which observers of the Notification must implement. + * It relies on a certain convention. To create and use a notification called "MyNotification": + * There shall be an interface IMyNotification, optionally in some namespace. It must be an instance of common.Interface.INotification. + * There shall be a class MyNotification, optionally in some namespace. It must have webservices._Internal.NotificationBase as an ancestor. + * MyNotification shall have a data member called _className, whose value is the name of the class (including any package name). + * + * This method will dynamically create the following: + * NotificationRegistry.registerMyNotificationObserver() (deprecated - call registerObserver() directly) + * NotificationFactory.createMyNotification() - just calls constructorFunction + * webservices.Interfaces.IMyNotificationObserver - contains a single method, processMyNotificationObserver(). Objects which wish to listen for MyNotification must implement this interface. + * + * @param notificationInterface The interface which the notification type must implement. + * @param constructorFunction A function which will construct an instance of the Notification. + * + * Example: + * MyPackage.Interfaces.IMyNotification = new common.Interface('MyPackage.Interfaces.IMyNotification', [], ['webservices.Interfaces.INotification']); + * MyPackage.MyNotification = Class.create(webservices._Internal.NotificationBase, + * { + * _className : "MyPackage.MyNotification", // This must exist! + * initialize($super, ...) + * { + * $super(); + * this.addImplementedInterface(MyPackage.Interfaces.IMyNotification); + * ...do stuff + * }, + * ...other methods + * }; + * + * function buildMyNotification() { return new MyPackage.MyNotification(...); } + * webservices.NotificationRegistry.registerNotificationType(MyPackage.Interfaces.IMyNotification, buildMyNotification); + * myListenerObject.addImplementedInterface(MyPackage.Interfaces.IMyNotification); // myListenerObject has a method processMyNotification() + * webservices.NotificationRegistry.registerObserver(myListenerObject, MyPackage.Interfaces.IMyNotification); + * ...when something happens... + * var anInstanceOfMyNotification = webservices.NotificationFactory.createMyNotification(...); // This method is created dynamically by the call to registerNotificationType(), and + * // will call buildMyNotification(). + * webservices.NotificationRegistry.process(anInstanceOfMyNotification); // This method is created dynamically by the call to registerNotificationType(). + * + * Note that a registerMyNotificationObserver(observer) method is created dynamically by the call to registerNotificationType(), but this form is deprecated. + * Use the equivalent registerObserver(observer, MyPackage.Interfaces.IMyNotification). + */ + registerNotificationType : function(notificationInterface, constructorFunction) + { + var notificationSimpleName = this._getNotificationSimpleName(notificationInterface); + + // Set up array of observers, initially empty + this._notificationSimpleNameToObserverMethods[notificationSimpleName] = []; + + // Create a register___NotificationObserver() method in this class + this["register" + notificationSimpleName + "Observer"] = function(observer) { + common.Debug.traceWarning("Deprecated method register" + notificationSimpleName + "Observer(observer) called. Please switch to the new form, registerObserver(observer, I" + notificationSimpleName + ")"); + this.registerObserver(observer, notificationInterface); + }; + + // Create the observer interface, which any object(s) wishing to observe the new Notification type must implement. + webservices.Interfaces["I" + notificationSimpleName + "Observer"] = new common.Interface('webservices.Interfaces.I' + notificationSimpleName + 'Observer', + ['process' + notificationSimpleName]); + + // Create the factory method + this._notificationFactory['create' + notificationSimpleName] = constructorFunction; + }, + + /** + * Cause the observer passed to this method to begin receiving notifications which implement the supplied interface. + * + * @param observer An object (not a method of that object!) that implements the observer interface that corresponds to notificationInterface. + * @param notificationInterface An interface (not an instance that implements that interface!) of a particular Notification type. + * + * Example: + * registerObserver(myClass, webservices.Interfaces.IFooNotification); + * In this example, myClass must implement IFooNotificationObserver, which means it must have a method processFooNotification(). + */ + registerObserver : function(observer, notificationInterface) + { + if (!observer) + { + throw common.ExceptionFactory.createException("observer is null"); + } + + if (!notificationInterface) + { + throw common.ExceptionFactory.createException("notificationInterface is null"); + } + + common.Interface.ensureImplements(observer, notificationInterface); + + var notificationSimpleName = this._getNotificationSimpleName(notificationInterface); + + if (!this._notificationSimpleNameToObserverMethods[notificationSimpleName]) + { + this._notificationSimpleNameToObserverMethods[notificationSimpleName] = []; + } + + this._notificationSimpleNameToObserverMethods[notificationSimpleName].push(this._getObserverFunction(observer, notificationSimpleName).bind(observer)); + }, + + /** + * Receives notifications and dispatches them to observers' methods specific to each notification type. + * + * @param notification A notification. + */ + process : function(notification) + { + common.Debug.traceMethodEntered("processNotification"); + + if (!notification) + { + throw common.ExceptionFactory.createException("notification is null"); + } + + var notificationSimpleName = this._getNotificationSimpleName(notification); + var observers = this._notificationSimpleNameToObserverMethods[notificationSimpleName]; + if (observers) + { + var exceptions = new Array(); + for(var i = 0; i < observers.length; ++i) + { + try + { + observers[i](notification); + } + catch(e) + { + common.Debug.traceError("Caught unhandled exception in NotificationRegistry:\n" + e); + exceptions.push(e); // Save for later. Continue notifying listeners. + } + } + if (exceptions.length == 1) + { + throw exceptions[0]; + } else if (exceptions.length > 1) + { + var masterException = new common.ExceptionFactory.createException("Multiple exceptions."); + masterException.exceptionList = exceptions; + throw masterException; + } + } + + common.Debug.traceMethodExited("processNotification"); + }, + + // Private methods + + /** + * Uses reflection to get the method .process + * + * @param observer A method that observes this type of notification + * @param notificationSimpleName A string like "FooNotification" + */ + _getObserverFunction : function(observer, notificationSimpleName) + { + var observerFunctionName = "process" + notificationSimpleName; + return observer[observerFunctionName]; + }, + + /** + * Gets the simple name of a notification type, given its class or interface. + * Examples: + * _getNotificationSimpleName(webservices.Interfaces.IFooNotification) returns "FooNotification". + * _getNotificationSimpleName(webservices.BarNotification, false) returns "BarNotification". + * + * @param notificationType The class or interface for a particular notification. This is not a string. + */ + _getNotificationSimpleName : function(notificationTypeOrInterface) + { + if (notificationTypeOrInterface instanceof webservices._Internal.NotificationBase) + { + var namespaceParts = notificationTypeOrInterface.get_className().split('.'); + var lastNamespacePart = namespaceParts[namespaceParts.length-1]; + return lastNamespacePart; + } else + { + var namespaceParts = notificationTypeOrInterface.get_name().split('.'); + var lastNamespacePart = namespaceParts[namespaceParts.length-1]; + return lastNamespacePart.substring(1); // Chop off the "I" + } + } +}); + +// Register namespaces +webservices.registerChildNamespace("_Internal"); + +/** + * QueueNotificationFactory class + * + * Creates Notification objects pertaining to queues which other objects may listen for. + */ +webservices._Internal.QueueNotificationFactory = Class.create(common.InterfaceImplementation, +{ + /** + * Constructor + */ + initialize : function($super) + { + if(arguments.length != 1) + { + throw common.ExceptionFactory.createException("QueueNotificationFactory constructor called with " + arguments.length + " arguments, but expected 1."); + } + + $super(); + }, + + /** + * Secondary initializer, to break circular dependency. + * webservices.NotificationRegistry.registerNotificationType() requires + * webservices.NotificationFactory to already have been constructed. + */ + delayedInit : function() + { + webservices.QueueNotificationRegistry.registerNotificationType(webservices.Interfaces.IQueueStatusNotification, + /** + * Creates a QueueStatusNotification, which contains the number of available agents and estimated wait time. + * + * @param agentsAvailable How many agents are currently available on this queue + * @param estimatedWaitTime How long in seconds it is estimated that it will take for an agent to answer an interaction, if it were placed onto the queue now. + * @return QueueStatusNotification + */ + function(agentsAvailable, estimatedWaitTime) + { + return new webservices.QueueStatusNotification(agentsAvailable, estimatedWaitTime); + }); + + webservices.QueueNotificationRegistry.registerNotificationType(webservices.Interfaces.IQueueStatusFailureNotification, + /** + * Creates a QueueStatusFailureNotification, which indicates that an attempt to get a queue's status has failed. + * + * @param error The error that caused the failure + * @return QueueStatusFailureNotification + */ + function(error) + { + return new webservices.QueueStatusFailureNotification(error); + }); + } +}); + +// Register namespaces +webservices.registerChildNamespace("Utilities"); + +/** + * Utilities class + * Provides miscellaneous functionality. + */ + +/** + * Creates a GUID + * + * @return A GUID + */ +webservices.Utilities.generateGuid = function() +{ + return (webservices.Utilities._generateGuidToken() + + webservices.Utilities._generateGuidToken() + + "-" + + webservices.Utilities._generateGuidToken() + + "-" + + webservices.Utilities._generateGuidToken() + + "-" + + webservices.Utilities._generateGuidToken() + + webservices.Utilities._generateGuidToken() + + webservices.Utilities._generateGuidToken()); +}; + +// private method +webservices.Utilities._generateGuidToken = function() +{ + return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1); +}; + +// Helper for parseOutBaseUrl(), which is currently not used. +// Given "http://www.somewhere.com/someDir/someSubDir/someFile.ext" and "http", will return "http://www.somewhere.com" +webservices.Utilities.parseOutBaseUrlByProtocol = function(url, protocol) +{ + var prefix; + if(protocol == "file") + { + prefix = protocol + ":///"; + } + else + { + prefix = protocol + "://"; + } + + if(url.length > prefix.length) + { + if(url.substr(0, prefix.length) == prefix) + { + var tokens = url.split("/"); + for(var i = 1; i < tokens.length; ++i) + { + if(tokens[i]) + { + return prefix + tokens[i]; + } + } + } + } + + return null; +}; + +// Currently not used. +// Given "http://www.somewhere.com/someDir/someSubDir/someFile.ext", will return "http://www.somewhere.com" +// Likewise for https and file URLs. Case-sensitive, though. +webservices.Utilities.parseOutBaseUrl = function(url) +{ + var baseUrl = webservices.Utilities.parseOutBaseUrlByProtocol(url, "http"); + if(baseUrl) + { + return baseUrl; + } + + var baseUrl = webservices.Utilities.parseOutBaseUrlByProtocol(url, "https"); + if(baseUrl) + { + return baseUrl; + } + + var baseUrl = webservices.Utilities.parseOutBaseUrlByProtocol(url, "file"); + if(baseUrl) + { + return baseUrl; + } + + return url; +}; + +/** + * Builds a full URL from the various parts. + * + * @param protocol "http", "https", "file", etc. + * @param domain The hostname, such as "www.mycompany.com". + * @param port The port on which the server process is running. Usually 80 for http and 443 for https. + * @param uriFragment See lengthy definition in the documentation for the Servers class. + * @param relativeUrl The part of the URL that comes after the URI fragment. In this application, it is likely to be one of the constants defined in the CapabilityUrls class. + * @return The full URL formed by these parts. + */ +webservices.Utilities.buildUrlWithProtocolDomainPortUrlFragmentAndRelativeUrl = function(protocol, domain, port, uriFragment, relativeUrl) +{ + return webservices.Utilities.buildUrlWithProtocolDomainPortAndRelativeUrl(protocol, domain, port, webservices.Utilities.combineUriTokens(uriFragment, relativeUrl)); +}; + +/** + * Builds a full URL from the various parts. + * + * @param protocol "http", "https", "file", etc. + * @param domain The hostname, such as "www.mycompany.com". + * @param port The port on which the server process is running. Usually 80 for http and 443 for https. + * @param relativeUrl The part of the URL that comes after the hostname, such as "someDir/someSubDir/someFile.ext". + * @return The full URL formed by these parts. + */ +webservices.Utilities.buildUrlWithProtocolDomainPortAndRelativeUrl = function(protocol, domain, port, relativeUrl) +{ + return webservices.Utilities.combineUriTokens(protocol + "://" + domain + ":" + port, relativeUrl); +}; + +/** + * Combines two parts of a URI. For instance, when passed "http://www.mycompany.com" and "/someDir/someSubDir/someFile.ext", it will + * return "http://www.mycompany.com/someDir/someSubDir/someFile.ext". Or when passed "someDir" and "someSubDir", it + * will return "someDir/someSubDir". + * + * @param uri1 Part of a URI + * @param uri2 Part of a URI + * @return A concatenation of the two parameters, with a "/" inserted between them if necessary. + */ +webservices.Utilities.combineUriTokens = function(uri1, uri2) +{ + // Either string could be empty. If so, just return the other one. + if (!uri1 || 0 === uri1.length) + { + return uri2; + } + if (!uri2 || 0 === uri2.length) + { + return uri1; + } + + var url = uri1; + + // Insert a slash if neither uri1 nor uri2 contains one + if((uri2.substring(0, 1) != "/") && (url[url.length - 1] != "/")) + { + url += "/"; + } + + // uri1 could end with a slash and/or uri2 could begin with a slash. Browser probably + // won't care about a doubled slash, but avoid the possibility anyway. + if((uri2.substring(0, 1) == "/") && (url[url.length - 1] == "/")) + { + url = url.substring(0, url.length - 1); + } + url += uri2; + return url; +}; + +/** + * Given a URL, returns the name of the file specified. + * For instance, when passed "http://www.mycompany.com/someDir/someSubDir/someFile.ext", it will return "someFile.ext". + * When passed "http://www.mycompany.com/someDir/someSubDir/", it will return null. + * + * @param url A URL + * @return The file specified by the URL, with the protocol, domain, and path removed. + */ +webservices.Utilities.getFileNameFromUrl = function(url) +{ + var tokens = url.split("/"); + if(tokens && (tokens.length > 0)) + { + return tokens[tokens.length - 1]; + } + + return null; +}; + +/** + * Indicates whether elevated browser privileges are needed to send an AJAX request. + * + * @return boolean indicating whether elevated privileges are needed to send an AJAX request. + */ +webservices.Utilities.needsElevatedPrivileges = function() +{ + return webservices.Utilities.isFireFoxVersionOrHigher(3.5) && + (location.protocol == "file:"); +}; + +/** + * Returns true if the web browser is FireFox, and the version number is higher than the value passed. + * Note that only the major and minor version numbers are used here. Passing a value with two decimal points, such as "3.6.13" + * is not supported. + * + * @param requiredVersion An integer or floating point number, such as 3 or 3.6 + * @return false if the web browser is not FireFox. If the web browser is FireFox, false if the version is less than the value passed, and true if the version is equal to or greater than the version passed. + */ +webservices.Utilities.isFireFoxVersionOrHigher = function (requiredVersion) +{ + // test for Firefox/x.x or Firefox x.x (ignoring remaining digits) + if (/Firefox[\/\s](\d+\.\d+)/.test(navigator.userAgent)) + { + // capture x.x portion and store as a number + var version = RegExp.$1; + version = parseFloat(version); + return (version >= requiredVersion); + } + + return false; +}; + +/** + * Makes a copy of an array + * + * @param array An array + * @return A different array, containing the same elements. + */ +webservices.Utilities.copyArray = function(array) +{ + var newArray = []; + for(var i = 0; i < array.length; ++i) + { + newArray.push(array[i]); + } + return newArray; +}; + +/** + * Takes an array, and returns an array containing the same items but in a random order. + * + * @param array An array + * @return The same array, but with its items reordered randomly. + */ +webservices.Utilities.randomizeArray = function(array) +{ + var i = array.length; + if(i > 0) + { + while (--i) + { + var j = Math.floor(Math.random() * (i + 1)); + var tempi = array[i]; + var tempj = array[j]; + array[i] = tempj; + array[j] = tempi; + } + } + return array; +}; + +/** + * Takes an array, and returns an webservices.Queue containing the same items. + * The first item in the array will be the first one pushed into the Queue. So, + * buildQueueFromArray([1, 2, 3]).pop() will return 3. + * + * @param array An array + * @return A Queue containing the same items as the array. + */ +webservices.Utilities.buildQueueFromArray = function(array) +{ + var queue = new webservices.Queue(); + for(var i = 0; i < array.length; ++i) + { + queue.push(array[i]); + } + return queue; +}; + +/** + * Returns true if the passed array contains the passed item. + * + * @param array An array + * @param element Something that may or may not be in the array + * @return True if array contains element. False if array does not contain element. False if array is null. + */ +webservices.Utilities.doesArrayHaveElement = function(array, element) +{ + if(!array) + { + return false; + } + + for(var i = 0; i < array.length; ++i) + { + if(array[i] == element) + { + return true; + } + } + + return false; +}; + +/** + * Takes a URL and a name/value pair. Returns the same URL, but with the name/value pair added to the end as a + * query string. Works whether or not other name/value pairs are already at the end of the URL. + * Example: appendQueryStringParameterToUrl("http://www.mycompany.com/someDir/someFile", "firstThree", "abc") + * will return "http://www.mycompany.com/someDir/someFile?firstThree=abc". + * Example: appendQueryStringParameterToUrl("http://www.mycompany.com/someDir/someFile?firstThree=abc", "lastThree", "xyz") + * will return "http://www.mycompany.com/someDir/someFile?firstThree=abc&lastThree=xyz". + * + * @param url A URL + * @param name The name portion of a name/value pair + * @param value The value portion of a name/value pair + */ +webservices.Utilities.appendQueryStringParameterToUrl = function(url, name, value) +{ + // copy the whole url except for the ending # char if it's there + var newUrl = webservices.Utilities.removeEndingPoundCharacter(url); + + // if this is the first query string parameter, need to add the ? char + // else this is just another query string parameter, need to add the & char + if(url.indexOf("?") == -1) + { + newUrl += "?"; + } + else + { + newUrl += "&"; + } + + // finally add the query string name and value + newUrl += name + "=" + value; + + return newUrl; +}; + +/** + * Takes a URL (or any string). If it does not end with a '#' character, returns the same string. If it + * does end with a pound character, the return value will be the string with the '#' character removed. + * + * @param url A URL (or any string) + * @return The URL, but with the ending '#' character removed. If the passed URL did not end with '#', then the passed URL will be returned as-is. + */ +webservices.Utilities.removeEndingPoundCharacter = function(url) +{ + if(url[url.length - 1] == "#") + { + return url.substr(0, url.length - 1); + } + + return url; +}; + +/** + * Escapes HTML, by doing the following: + * Converts ampersands to & + * Converts less-than signs to < + * Converts greater-than signs to > + * + * @param str The string which may contain HTML. + * @return The same string, but with the HTML escaped. + */ +webservices.Utilities.escapeHTML = function(str) +{ + str = str.replace(/&/g, "&"); + str = str.replace(//g, ">"); + return str; +}; + +/** + * Returns a random integer in the supplied range. + * + * @param min The minimum number to return + * @param max The maximum number to return + */ +webservices.Utilities.randomInRange = function(min, max) +{ + // Check to see if caller specified the params in the wrong order + if (max < min) + { + return webservices.Utilities.randomInRange(max, min); + } + + return min + Math.round(Math.random() * (max-min)); +}; + +/** + * Returns true if the browser is Microsoft Internet Explorer. Returns false otherwise. + * + * @return Boolean + */ +webservices.Utilities.isBrowserIE = function() +{ + return (navigator.appName == 'Microsoft Internet Explorer'); +}; + + +/** + * Class to implement a basic Queue. + */ +webservices.Queue = Class.create({ + + /** + * Constructor + */ + initialize : function() + { + this._nextIndex = 0; + this._map = new common.Map(); + }, + + /** + * Destructor + */ + destroy : function() + { + this._map.destroy(); + }, + + /** + * Push something onto the queue + * + * @param value This will be pushed onto the queue + */ + push : function(value) + { + this._map.put(this._nextIndex++, value); + }, + + /** + * Pop something off of the queue + * + * @param key Ignored + */ + pop : function(key) + { + if(this._map.isEmpty()) + { + return null; + } + + var key = this._map.firstKey(); + var value = this._map.firstObject(); + this._map.remove(key); + return value; + } +}); + +/** + * Class to implement a basic Collection. + */ +webservices.Collection = Class.create({ + /** + * Constructor + * + * @param array Optional. If supplied, the items in the array will be added to the newly-constructed Collection. + */ + initialize : function(array) + { + common.ParameterValidation.validate([array], [ {"type": Array, "required": false, "allowEmpty": true} ]); + + this._nextIndex = 0; + this._map = new common.Map(); + + if(array) + { + for(var i = 0; i < array.length; ++i) + { + this.add(array[i]); + } + } + }, + + /** + * Destructor + */ + destroy : function() + { + this._map.destroy(); + }, + + /** + * Adds an item to the Collection + * + * @param item The item to add to the Collection + */ + add : function(item) + { + this._map.put(item, 1); + }, + + /** + * Removes an item from the Collection + * + * @param item The item to remove from the Collection + */ + remove : function(item) + { + if(this._map.isEmpty()) + { + return null; + } + + if(this._map.containsKey(item)) + { + this._map.remove(item); + } + else + { + throw common.ExceptionFactory.createException(item); + } + }, + + /** + * Returns the size of the Collection + * + * @return The number of items in the collection + */ + size : function() + { + return this._map.size(); + } +}); + +/** + * A class to implement mutual exclusion. + * Based on Lamport's bakery algorithm + */ +webservices.Mutex = Class.create({ + s_waitInterval: 50, + + m_map: new common.Map(), + + /** + * Constructor + * + * @param whatToDo A callback to a function that acts as the critical section, i.e. will be executed with mutual exclusion + */ + initialize: function(whatToDo/*, callbackToCallWhenItsDone*/) { + common.Debug.traceNote("In webservices.Mutex.initialize()"); + this.execute = whatToDo; + /*this.onCompleted = callbackToCallWhenItsDone;*/ + this.id = ++(webservices.Mutex.s_maxIDSoFar); + common.Debug.traceNote("Created new Mutex, assigned id: " + this.id); + + // Get number. Instead of global arrays for entering and number as + // described in the algorithm, use member variables. The various "threads"' values for number + // don't need to be consecutive integers...they just need to be pretty close to unique, and able to be ordered. + this.entering = 1; + this.number = new Date().getTime(); + common.Debug.traceNote("Thread #" + this.id + " picked a number: " + this.number); + this.entering = 0; + + this.m_map.put(this.id, this); + this._waitOnLowerThreadsThenDoCriticalSection(this.m_map.firstKey()); + }, + + // private methods + + /** + * In the algorithm, there are places where we wait for other "threads" to do something. + * Busy-waiting is a bad idea in Javascript, and the language has no sleep() or yield(). + * So, this is implemented in the form of a continuation. + */ + _waitOnLowerThreadsThenDoCriticalSection: function(nextID) { + common.Debug.traceNote("Thread #" + this.id + " entering waitOnLowerThreadsThenDoCriticalSection(" + nextID + ")"); + for (var j = nextID; j !== null; j = this.m_map.nextKey(j)) { + common.Debug.traceNote("Thread #" + this.id + " in for loop, j=" + j); + var jthThread = this.m_map.get(j); + if ((jthThread.entering) || ((jthThread.number !== 0) && + ((jthThread.number < this.number) || + ((jthThread.number == this.number) && (jthThread.id < this.id))))) { + common.Debug.traceNote("Thread #" + this.id + " going to wait for " + j + "'th thread to do its thing"); + // jthThread is doing its critical section. Wait until it's done. + // Since this is Javascript, that means using setTimeout to call a + // continuation of the current state. + + // Can't do setTimeout("waitOnLowerThreadsThenDoCriticalSection(" + j + ")", this.s_waitInterval). + // This is because setTimeout() doesn't work well with object methods. But, we can create a + // locally-scoped variable, which the passed function will still be able to see. + var _self = this; + window.setTimeout(function() { return _self.waitOnLowerThreadsThenDoCriticalSection(j); }, this.s_waitInterval); + return; // Don't allow the critical section code below to run right now. + } + } + + common.Debug.traceNote("Thread #" + this.id + " gained exclusivity"); + // do critical section + var returnValue = this.execute(); + + // clean up, and release the lock + this.m_map.remove(this.id); + this.number = 0; + + common.Debug.traceNote("Thread #" + this.id + " finished critical section, return value was: " + returnValue); + + /* + // if caller wants to be informed of the return value, do that + if (this.onCompleted) { + this.onCompleted(returnValue); + } + */ + } +}); + +/** + * The highest ID given out so far to any webservices.MutexCriticalSection. + */ +webservices.Mutex.s_maxIDSoFar = -1; + +// Register namespaces +webservices.registerChildNamespace("Servers.Current"); + +/** + * Servers class + * + * Keeps track of webservers and URLs used. + * + * The term "URI Fragment" is used frequently here. Most customers do not want their IC servers to be visible to the public. They will + * keep them behind a firewall. So in order for the Javascript client to send requests to an IC server (specifically the + * WebProcessorBridge.exe process), the Javascript is served from a webserver outside the firewall, and firewall rules are configured + * to allow that webserver to act as a reverse proxy by sending requests from clients to WebProcessorBridge. + * "URI fragment" refers to the portion of a URL which the webserver has been configured to recognize to indicate that the request + * should be reverse proxied. + * Example: + * The IC server is running on ic.mycompany.com. WebProcessorBridge is listening on port 8114 of this machine. + * The webserver is www.mycompany.com. The reverse proxy is configured such that requests of the form + * http://www.mycompany.com/I3Root/Server1/* will be reverse proxied to http://ic.mycompany.com:8114/* + * For instance, if the client requests http://www.mycompany.com/I3Root/Server1/websvcs/serverConfiguration, the webserver will + * then request http://ic.mycompany.com:8114/websvcs/serverConfiguration from the IC server, wait for the IC server's response, and + * then send the IC server's response to the client. + * In this example, "I3Root/Server1" is termed the "URI fragment". + */ + +/** Whether the communcation between the browser and the webserver should use HTTPS. If false, HTTP will be used. */ +webservices.Servers.UseHttps = false; + +/** The domain (i.e. hostname) of the webserver that served this Javascript. */ +webservices.Servers.Domain = document.domain; + +/** The port of the webserver that served this Javascript. If a nonstandard one like 8888 is used, use that. Otherwise use 80 for HTTP or 443 for HTTPS. */ +webservices.Servers.Port = document.location.port ? document.location.port : ( document.location.protocol == "http:" ? 80 : 443 ); + +/** The string that was the URI fragment (see above for definition) at the start of this session. */ +webservices.Servers.OriginalUriFragment = ""; + +/** The URI fragment that is currently in use. May change if a switchover occurs. */ +webservices.Servers.CurrentUriFragment = ""; + +/** + * The set of possible URI fragments that may be used. Should match the URI fragments that are configured on the webserver + * to trigger reverse proxying to IC servers. + */ +webservices.Servers.UriFragments = []; + +/** + * Builds the full URL at which to access some Capability + * + * @param uriFragment A URI fragment. See above for definition of the term. + * @param relativeUrl The portion of the URI that identifies the Capability being accessed. Comes after the URI fragment described above. See definition in the Capability class. + */ +webservices.Servers.buildUrl = function(uriFragment, relativeUrl) +{ + var protocol; + if(webservices.Servers.UseHttps) + { + protocol = "https"; + } + else + { + protocol = "http"; + } + + return webservices.Utilities.buildUrlWithProtocolDomainPortUrlFragmentAndRelativeUrl(protocol, webservices.Servers.Domain, webservices.Servers.Port, uriFragment, relativeUrl); +}; + +/** + * Returns how many IC servers the client knows about. + * For now, this will always either return 1 or 2. + */ +webservices.Servers.get_numberOfServers = function() +{ + return webservices.Servers.UriFragments.length; +} + +/** + * Returns a boolean indicating whether or not a switchover pair has been configured. + * + * @return true if this server knows about 2 different URI fragments. False otherwise. + */ +webservices.Servers.isConfiguredForSwitchover = function() +{ + return (2 == webservices.Servers.get_numberOfServers()); +}; + +/** + * Switches which URI fragment will be used for requests to the IC server + */ +webservices.Servers.switchCurrentServer = function() +{ + if(webservices.Servers.isConfiguredForSwitchover()) + { + if(webservices.Servers.CurrentUriFragment == webservices.Servers.UriFragments[0]) + { + webservices.Servers.CurrentUriFragment = webservices.Servers.UriFragments[1]; + } + else + { + webservices.Servers.CurrentUriFragment = webservices.Servers.UriFragments[0]; + } + } +}; + +/** + * Indicates whether a switchover has occurred. + * + * @return true if switchover has occurred (i.e. URI fragment currently in use is not the one that was in use at the start of the session), false otherwise. + */ +webservices.Servers.isCurrentServerTheOriginalServer = function() +{ + return (webservices.Servers.CurrentUriFragment == webservices.Servers.OriginalUriFragment); +}; + + +// Register namespaces +webservices.registerChildNamespace("HttpMethods"); + +/** + * Constant to represent the HTTP "GET" method + */ +webservices.HttpMethods.GET = "GET"; + +/** + * Constant to represent the HTTP "POST" method + */ +webservices.HttpMethods.POST = "POST"; + +/** + * Constant to represent the HTTP "HEAD" method + */ +webservices.HttpMethods.HEAD = "HEAD"; + +/** + * Constant to represent the HTTP "DELETE" method + */ +webservices.HttpMethods.DELETE = "DELETE"; + +// Register namespace +webservices.registerChildNamespace("CapabilityUrls.Common"); + +// Constants for the Common Capability URLs + +/** + * Constant for the server configuration URL. + * The complete URL is: http://(server)/(uriFragment)/(this_constant) + */ +webservices.CapabilityUrls.Common.SERVERCONFIGURATION = "websvcs/serverConfiguration"; + +/** + * Constant for the tracker registration URL. + * The complete URL is: http://(server)/(uriFragment)/(this_constant) + */ +webservices.CapabilityUrls.Common.TRACKERREGISTRATION = "websvcs/register"; + +/** + * Constant for the URL to get info (name and picture) about an agent + * The complete URL is: http://(server)/(uriFragment)/(this_constant) + */ +webservices.CapabilityUrls.Common.PARTYINFO = "websvcs/partyInfo"; + +// Register namespace +webservices.registerChildNamespace("CapabilityUrls.QueueQuery"); + +// Constant for the Queue Query Capability URL + +/** + * Constant for the queue query URL. + * The complete URL is: http://(server)/(uriFragment)/(this_constant) + */ +webservices.CapabilityUrls.QueueQuery.QUERY = "websvcs/queue/query"; + +// Register namespace +webservices.registerChildNamespace("CapabilityUrls.Chat"); + +// Constants for the Chat Capability URLs + +/** + * Constant for the URL to start a chat. + * The complete URL is: http://(server)/(uriFragment)/(this_constant) + */ +webservices.CapabilityUrls.Chat.START = "websvcs/chat/start"; + +/** + * Constant for the URL to reconnect to a chat. + * The complete URL is: http://(server)/(uriFragment)/(this_constant) + */ +webservices.CapabilityUrls.Chat.RECONNECT = "websvcs/chat/reconnect"; + +/** + * Constant for the URL to poll a chat for new messages. + * The complete URL is: http://(server)/(uriFragment)/(this_constant) + */ +webservices.CapabilityUrls.Chat.POLL = "websvcs/chat/poll"; + +/** + * Constant for the URL to send a message within a chat. + * The complete URL is: http://(server)/(uriFragment)/(this_constant) + */ +webservices.CapabilityUrls.Chat.SENDMESSAGE = "websvcs/chat/sendMessage"; + +/** + * Constant for the URL to send a typing indicator within a chat. + * The complete URL is: http://(server)/(uriFragment)/(this_constant) + */ +webservices.CapabilityUrls.Chat.SETTYPINGSTATE = "websvcs/chat/setTypingState"; + +/** + * Constant for the URL to exit a chat. + * The complete URL is: http://(server)/(uriFragment)/(this_constant) + */ +webservices.CapabilityUrls.Chat.EXIT = "websvcs/chat/exit"; + +/** + * Constant for the URL to retrieve a file within a chat. + * The complete URL is: http://(server)/(uriFragment)/(this_constant) + */ +webservices.CapabilityUrls.Chat.GETFILE = "websvcs/chat/getFile"; + +/** + * Constant for the URL to send a report of the chat's problem to WebProcessorBridge + * The complete URL is: http://(server)/(uriFragment)/(this_constant) + */ +webservices.CapabilityUrls.Common.PROBLEMREPORT = "websvcs/problemReport"; + +// Register namespace +webservices.registerChildNamespace("CapabilityUrls.Callback"); + +// Constants for the Callback Capability URLs + +/** + * Constant for the URL to create a callback. + * The complete URL is: http://(server)/(uriFragment)/(this_constant) + */ +webservices.CapabilityUrls.Callback.CREATE = "websvcs/callback/create"; + +/** + * Constant for the URL to query the status of a callback. + * The complete URL is: http://(server)/(uriFragment)/(this_constant) + */ +webservices.CapabilityUrls.Callback.STATUS = "websvcs/callback/status"; + +/** + * Constant for the URL to reconnect to a callback. In other words, bring a + * previously-created callback into the current web session so it can be modified, + * queried, or disconnected. + * The complete URL is: http://(server)/(uriFragment)/(this_constant) + */ +webservices.CapabilityUrls.Callback.RECONNECT = "websvcs/callback/reconnect"; + +/** + * Constant for the URL to disconnect a callback (in other words, remove the interaction from the queue, + * so the visitor will not get called back). + * The complete URL is: http://(server)/(uriFragment)/(this_constant) + */ +webservices.CapabilityUrls.Callback.DISCONNECT = "websvcs/callback/disconnect"; + +/** + * Capability class + * + * A Capability represents a piece of functionality that the client and server both know how to do. + * For instance, sending a message within a chat, or creating a callback. + * The client queries the server for its capabilities, and then knows to only utilize the capabilities which both client and server share. + * Each capability can be reached by sending an HTTP GET, POST, HEAD, or DELETE request to a certain URL of the format + * http://(server)/(uriFragment)/(relative URL indicating the type of capability) + * For instance, http://www.company.com/I3Root/Server1/websvcs/callback/create + * This class maintains a pairing of the relative URL (defined in CapabilityUrls) and the HTTP method. + */ +webservices.Capability = Class.create(common.InterfaceImplementation, +{ + /** + * Constructor + * + * @param relativeUrl The relative URL that is used to invoke this capability + * @param method Whether the URL should be requested via an HTTP GET, POST, HEAD, or DELETE request + */ + initialize : function($super, relativeUrl, method) + { + var numArgs = 3; + if(arguments.length != numArgs) + { + throw common.ExceptionFactory.createException("Capability constructor called with " + arguments.length + " arguments, but expected " + numArgs + "."); + } + + $super(); + + this.addImplementedInterface(webservices.Interfaces.ICapability, webservices); + + this._validateUrl(relativeUrl); + this._relativeUrl = relativeUrl; + this._validateMethod(method); + this._method = method; + }, + + /** + * Destructor + */ + destroy : function() + { + this._relativeUrl = null; + this._method = null; + + common.InterfaceImplementation.prototype.destroy.call(this); + }, + + // public methods + + /** + * Getter for the relative URL property + * @return A string. In the default implementation, this will always be one of the constants defined in CapabilityUrls. + */ + get_relativeUrl : function() + { + return this._relativeUrl; + }, + + /** + * Getter for the method property + * + * @return A constant (from webservices.HttpMethods) indicating whether an HTTP GET, POST, HEAD, or DELETE shall be used to request the relative URL. + */ + get_method : function() + { + return this._method; + }, + + toString : function() + { + return "<Capability: " + this._method + ", " + this._relativeUrl + ">"; + }, + + // private methods + + _validateUrl : function(url) + { + if((url != webservices.CapabilityUrls.Common.SERVERCONFIGURATION) && + (url != webservices.CapabilityUrls.Common.TRACKERREGISTRATION) && + (url != webservices.CapabilityUrls.Common.PROBLEMREPORT) && + (url != webservices.CapabilityUrls.Common.PARTYINFO) && + (url != webservices.CapabilityUrls.QueueQuery.QUERY) && + (url != webservices.CapabilityUrls.Chat.START) && + (url != webservices.CapabilityUrls.Chat.RECONNECT) && + (url != webservices.CapabilityUrls.Chat.POLL) && + (url != webservices.CapabilityUrls.Chat.SENDMESSAGE) && + (url != webservices.CapabilityUrls.Chat.SETTYPINGSTATE) && + (url != webservices.CapabilityUrls.Chat.EXIT) && + (url != webservices.CapabilityUrls.Chat.GETFILE) && + (url != webservices.CapabilityUrls.Callback.CREATE) && + (url != webservices.CapabilityUrls.Callback.STATUS) && + (url != webservices.CapabilityUrls.Callback.RECONNECT) && + (url != webservices.CapabilityUrls.Callback.DISCONNECT)) + { + throw common.ExceptionFactory.createException(url + " is not a capability url"); + } + }, + + _validateMethod : function(method) + { + if((method != webservices.HttpMethods.GET) && + (method != webservices.HttpMethods.POST) && + (method != webservices.HttpMethods.HEAD) && + (method != webservices.HttpMethods.DELETE)) + { + throw common.ExceptionFactory.createException(method + " is not a valid method"); + } + } +}); + +// Register namespaces +webservices.registerChildNamespace("_Internal._CapabilityRepository"); +webservices.registerChildNamespace("CapabilityRepository"); + +/** + * CapabilityRepository class + * + * Keeps track of which Capabilities are enabled or disabled, and provides getter methods for the various Capabilities. + */ +webservices._Internal._CapabilityRepository = Class.create(common.InterfaceImplementation, +{ + /** + * Constructor + */ + initialize : function($super) + { + var numArgs = 1; + if(arguments.length != numArgs) + { + throw common.ExceptionFactory.createException("CapabilityRepository constructor called with " + arguments.length + " arguments, but expected " + numArgs + "."); + } + + $super(); + + // initialization + this._chatTrackerAuthenticationCapability = false; + this._chatStsAuthenticationCapability = false; + this._chatAnonymousAuthenticationCapability = false; + this._callbackTrackerAuthenticationCapability = false; + this._callbackStsAuthenticationCapability = false; + this._callbackAnonymousAuthenticationCapability = false; + this._queueQueryTrackerAuthenticationCapability = null; + this._queueQueryStsAuthenticationCapability = null; + this._queueQueryAnonymousAuthenticationCapability = null; + + this._serverConfigurationCapability = null; + this._trackerRegistrationCapability = null; + this._partyInfoCapability = null; + this._startChatCapability = null; + this._reconnectChatCapability = null; + this._pollCapability = null; + this._sendMessageCapability = null; + this._setTypingStateCapability = null; + this._exitCapability = null; + this._problemReportCapability = null; + this._createCallbackCapability = null; + this._callbackStatusCapability = null; + this._reconnectCallbackCapability = null; + this._disconnectCallbackCapability = null; + + // hardcode the Server Configuration Capability + this._serverConfigurationCapability = new webservices.Capability(webservices.CapabilityUrls.Common.SERVERCONFIGURATION, + webservices.HttpMethods.GET); + }, + + /** + * Destructor + */ + destroy : function() + { + if(this._serverConfigurationCapability) + { + this._serverConfigurationCapability.destroy(); + delete this._serverConfigurationCapability; + this._serverConfigurationCapability = null; + } + + if(this._startChatCapability) + { + this._startChatCapability.destroy(); + delete this._startChatCapability; + this._startChatCapability = null; + } + + if(this._reconnectChatCapability) + { + this._reconnectChatCapability.destroy(); + delete this._reconnectChatCapability; + this._reconnectChatCapability = null; + } + + if(this._pollCapability) + { + this._pollCapability.destroy(); + delete this._pollCapability; + this._pollCapability = null; + } + + if(this._sendMessageCapability) + { + this._sendMessageCapability.destroy(); + delete this._sendMessageCapability; + this._sendMessageCapability = null; + } + + if(this._setTypingStateCapability) + { + this._setTypingStateCapability.destroy(); + delete this._setTypingStateCapability; + this._setTypingStateCapability = null; + } + + if(this._exitCapability) + { + this._exitCapability.destroy(); + delete this._exitCapability; + this._exitCapability = null; + } + + if(this._problemReportCapability) + { + this._problemReportCapability.destroy(); + delete this._problemReportCapability; + this._problemReportCapability = null; + } + + if(this._createCallbackCapability) + { + this._createCallbackCapability.destroy(); + delete this._createCallbackCapability; + this._createCallbackCapability = null; + } + + if(this._callbackStatusCapability) + { + this._callbackStatusCapability.destroy(); + delete this._callbackStatusCapability; + this._callbackStatusCapability = null; + } + + if(this._reconnectCallbackCapability) + { + this._reconnectCallbackCapability.destroy(); + delete this._reconnectCallbackCapability; + this._reconnectCallbackCapability = null; + } + + if(this._disconnectCallbackCapability) + { + this._disconnectCallbackCapability.destroy(); + delete this._disconnectCallbackCapability; + this._disconnectCallbackCapability = null; + } + + if(this._queueQueryTrackerAuthenticationCapability) + { + this._queueQueryTrackerAuthenticationCapability.destroy(); + delete this._queueQueryTrackerAuthenticationCapability; + this._queueQueryTrackerAuthenticationCapability = null; + } + + if(this._queueQueryStsAuthenticationCapability) + { + this._queueQueryStsAuthenticationCapability.destroy(); + delete this._queueQueryStsAuthenticationCapability; + this._queueQueryStsAuthenticationCapability = null; + } + + if(this._queueQueryAnonymousAuthenticationCapability) + { + this._queueQueryAnonymousAuthenticationCapability.destroy(); + delete this._queueQueryAnonymousAuthenticationCapability; + this._queueQueryAnonymousAuthenticationCapability = null; + } + + if(this._trackerRegistrationCapability) + { + this._trackerRegistrationCapability.destroy(); + delete this._trackerRegistrationCapability; + this._trackerRegistrationCapability = null; + } + + if(this._partyInfoCapability) + { + this._partyInfoCapability.destroy(); + delete this._partyInfoCapability; + this._partyInfoCapability = null; + } + + this._chatTrackerAuthenticationCapability = null; + this._chatStsAuthenticationCapability = null; + this._chatAnonymousAuthenticationCapability = null; + this._callbackTrackerAuthenticationCapability = null; + this._callbackStsAuthenticationCapability = null; + this._callbackAnonymousAuthenticationCapability = null; + + common.InterfaceImplementation.prototype.destroy.call(this); + }, + + // public methods + + /** + * Getter for the Capability for querying the server's capabilities. + * Always available, so there are no enableServerConfigurationCapability() or disableServerConfigurationCapability() methods. + * + * @return Capability, or null + */ + get_serverConfigurationCapability : function() + { + return this._serverConfigurationCapability; + }, + + /** + * Returns true or false, depending on whether the Capability to start a Chat is enabled. + * + * @return True if Chats can be started, false otherwise. + */ + isStartChatCapabilityEnabled : function() + { + return (this._startChatCapability !== null); + }, + + /** + * Getter for the Capability for starting a chat + * + * @return Capability, or null + */ + get_startChatCapability : function() + { + return this._startChatCapability; + }, + + /** + * Enables the Capability to start a chat + */ + enableStartChatCapability : function() + { + if(!this._startChatCapability) + { + this._startChatCapability = new webservices.Capability(webservices.CapabilityUrls.Chat.START, + webservices.HttpMethods.POST); + } + }, + + /** + * Disables the Capability to start a chat + */ + disableStartChatCapability : function() + { + this._startChatCapability = null; + }, + + /** + * Getter for the Capability for reconnecting to a chat + * + * @return Capability, or null + */ + get_reconnectChatCapability : function() + { + return this._reconnectChatCapability; + }, + + /** + * Enables the Capability to reconnect to a chat + */ + enableReconnectChatCapability : function() + { + if(!this._reconnectChatCapability) + { + this._reconnectChatCapability = new webservices.Capability(webservices.CapabilityUrls.Chat.RECONNECT, + webservices.HttpMethods.POST); + } + }, + + /** + * Disables the Capability to reconnect to a chat + */ + disableReconnectChatCapability : function() + { + this._reconnectChatCapability = null; + }, + + /** + * Getter for the Capability for polling a chat (i.e. checking for new messages or events) + * + * @return Capability, or null + */ + get_pollCapability : function() + { + return this._pollCapability; + }, + + /** + * Enables the Capability to poll a chat + */ + enablePollCapability : function() + { + if(!this._pollCapability) + { + this._pollCapability = new webservices.Capability(webservices.CapabilityUrls.Chat.POLL, + webservices.HttpMethods.GET); + } + }, + + /** + * Disables the Capability to poll a chat + */ + disablePollCapability : function() + { + this._pollCapability = null; + }, + + /** + * Getter for the Capability for sending a message within a chat + * + * @return Capability, or null + */ + get_sendMessageCapability : function() + { + return this._sendMessageCapability; + }, + + /** + * Enables the Capability to send a message within a chat + */ + enableSendMessageCapability : function() + { + if(!this._sendMessageCapability) + { + this._sendMessageCapability = new webservices.Capability(webservices.CapabilityUrls.Chat.SENDMESSAGE, + webservices.HttpMethods.POST); + } + }, + + /** + * Disables the Capability to send a message within a chat + */ + disableSendMessageCapability : function() + { + this._sendMessageCapability = null; + }, + + /** + * Getter for the Capability for setting a typing indicator within a chat + * + * @return Capability, or null + */ + get_setTypingStateCapability : function() + { + return this._setTypingStateCapability; + }, + + /** + * Enables the Capability to set a typing indicator within a chat + */ + enableSetTypingStateCapability : function() + { + if(!this._setTypingStateCapability) + { + this._setTypingStateCapability = new webservices.Capability(webservices.CapabilityUrls.Chat.SETTYPINGSTATE, + webservices.HttpMethods.POST); + } + }, + + /** + * Disables the Capability to set a typing indicator within a chat + */ + disableSetTypingStateCapability : function() + { + this._setTypingStateCapability = null; + }, + + /** + * Getter for the Capability for exiting a chat + * + * @return Capability, or null + */ + get_exitCapability : function() + { + return this._exitCapability; + }, + + /** + * Enables the Capability to exit a chat + */ + enableExitCapability : function() + { + if(!this._exitCapability) + { + this._exitCapability = new webservices.Capability(webservices.CapabilityUrls.Chat.EXIT, + webservices.HttpMethods.POST); + } + }, + + /** + * Disables the Capability to exit a chat + */ + disableExitCapability : function() + { + this._exitCapability = null; + }, + + /** + * Returns true or false, depending on whether the Capability to send a problem report is enabled. + * + * @return True if problem reports can be sent, false otherwise. + */ + isProblemReportCapabilityEnabled : function() + { + return (this._problemReportCapability !== null); + }, + + /** + * Gets the capability for sending a problem report to the IC server + */ + get_problemReportCapability : function() + { + return this._problemReportCapability; + }, + + /** + * Enables the capability of sending a problem report to the IC server + */ + enableProblemReportCapability : function() + { + if(!this._problemReportCapability) + { + this._problemReportCapability = new webservices.Capability(webservices.CapabilityUrls.Common.PROBLEMREPORT, + webservices.HttpMethods.POST); + } + }, + + /** + * Disables the Capability of sending a problem report to the IC server + */ + disableProblemReportCapability : function() + { + this._problemReportCapability = null; + }, + + /** + * Returns true or false, depending on whether the Capability to create a Callback is enabled. + * + * @return True if Callbacks can be created, false otherwise. + */ + isCreateCallbackCapabilityEnabled : function() + { + return (this._createCallbackCapability !== null); + }, + + /** + * Getter for the Capability for creating a Callback + * + * @return Capability, or null + */ + get_createCallbackCapability : function() + { + return this._createCallbackCapability; + }, + + /** + * Enables the Capability to create a Callback + */ + enableCreateCallbackCapability : function() + { + if(!this._createCallbackCapability) + { + this._createCallbackCapability = new webservices.Capability(webservices.CapabilityUrls.Callback.CREATE, + webservices.HttpMethods.POST); + } + }, + + /** + * Disables the Capability to create a Callback + */ + disableCreateCallbackCapability : function() + { + this._createCallbackCapability = null; + }, + + /** + * Returns true or false, depending on whether the Capability to query the status of a Callback is enabled. + * + * @return True if Callbacks' status can be queried, false otherwise. + */ + isCallbackStatusCapabilityEnabled : function() + { + return (this._callbackStatusCapability !== null); + }, + + /** + * Getter for the Capability for querying the status of a Callback + * + * @return Capability, or null + */ + get_callbackStatusCapability : function() + { + return this._callbackStatusCapability; + }, + + /** + * Enables the Capability to query the status of a Callback + */ + enableCallbackStatusCapability : function() + { + if(!this._callbackStatusCapability) + { + this._callbackStatusCapability = new webservices.Capability(webservices.CapabilityUrls.Callback.STATUS, + webservices.HttpMethods.GET); + } + }, + + /** + * Disables the Capability to query the status of a Callback + */ + disableCallbackStatusCapability : function() + { + this._callbackStatusCapability = null; + }, + + /** + * Returns true or false, depending on whether the Capability to reconnect a Callback is enabled. + * Reconnecting means to bring a previously-created Callback into the current web session, + * so that operations may be performed upon it. + * + * @return True if Callbacks can be reconnected, false otherwise. + */ + isReconnectCallbackCapabilityEnabled : function() + { + return (this._reconnectCallbackCapability !== null); + }, + + /** + * Getter for the Capability for reconnecting a Callback + * + * @return Capability, or null + */ + get_reconnectCallbackCapability : function() + { + return this._reconnectCallbackCapability; + }, + + /** + * Enables the Capability to reconnect a Callback + */ + enableReconnectCallbackCapability : function() + { + if(!this._reconnectCallbackCapability) + { + this._reconnectCallbackCapability = new webservices.Capability(webservices.CapabilityUrls.Callback.RECONNECT, + webservices.HttpMethods.POST); + } + }, + + /** + * Disables the Capability to reconnect a Callback + */ + disableReconnectCallbackCapability : function() + { + this._reconnectCallbackCapability = null; + }, + + /** + * Returns true or false, depending on whether the Capability to disconnect a Callback is enabled. + * + * @return True if Callbacks can be disconnected, false otherwise. + */ + isDisconnectCallbackCapabilityEnabled : function() + { + return (this._disconnectCallbackCapability !== null); + }, + + /** + * Getter for the Capability for disconnecting a Callback + * + * @return Capability, or null + */ + get_disconnectCallbackCapability : function() + { + return this._disconnectCallbackCapability; + }, + + /** + * Enables the Capability to disconnect a Callback + */ + enableDisconnectCallbackCapability : function() + { + if(!this._disconnectCallbackCapability) + { + this._disconnectCallbackCapability = new webservices.Capability(webservices.CapabilityUrls.Callback.DISCONNECT, + webservices.HttpMethods.POST); + } + }, + + /** + * Disables the Capability to disconnect a Callback + */ + disableDisconnectCallbackCapability : function() + { + this._disconnectCallbackCapability = null; + }, + + /** + * Returns true or false, depending on whether the Capability to query a queue anonymously is enabled. + * + * @return True if querying a queue anonymously is possible, false otherwise. + */ + isQueueQueryAnonymousAuthenticationCapabilityEnabled : function() + { + return (this._queueQueryAnonymousAuthenticationCapability !== null); + }, + + /** + * Getter for the Capability for querying a queue anonymously + * + * @return Capability, or null + */ + get_queueQueryAnonymousAuthenticationCapability : function() + { + return this._queueQueryAnonymousAuthenticationCapability; + }, + + /** + * Enables the Capability to query a queue anonymously + */ + enableQueueQueryAnonymousAuthenticationCapability : function() + { + if(!this._queueQueryAnonymousAuthenticationCapability) + { + this._queueQueryAnonymousAuthenticationCapability = new webservices.Capability(webservices.CapabilityUrls.QueueQuery.QUERY, + webservices.HttpMethods.POST); + } + }, + + /** + * Disables the Capability to query a queue anonymously + */ + disableQueueQueryAnonymousAuthenticationCapability : function() + { + this._queueQueryAnonymousAuthenticationCapability = null; + }, + + /** + * Returns true or false, depending on whether the Capability to query a queue with Tracker is enabled. + * + * @return True if querying a queue with Tracker is possible, false otherwise. + */ + isQueueQueryTrackerAuthenticationCapabilityEnabled : function() + { + return (this._queueQueryTrackerAuthenticationCapability !== null); + }, + + /** + * Getter for the Capability for querying a queue via Tracker + * + * @return Capability, or null + */ + get_queueQueryTrackerAuthenticationCapability : function() + { + return this._queueQueryTrackerAuthenticationCapability; + }, + + /** + * Enables the Capability to query a queue via Tracker + */ + enableQueueQueryTrackerAuthenticationCapability : function() + { + if(!this._queueQueryTrackerAuthenticationCapability) + { + this._queueQueryTrackerAuthenticationCapability = new webservices.Capability(webservices.CapabilityUrls.QueueQuery.QUERY, + webservices.HttpMethods.POST); + } + }, + + /** + * Disables the Capability to query a queue via Tracker + */ + disableQueueQueryTrackerAuthenticationCapability : function() + { + this._queueQueryTrackerAuthenticationCapability = null; + }, + + /** + * Returns true or false, depending on whether the Capability to query a queue with STS is enabled. + * + * @return True if querying a queue with STS is possible, false otherwise. + */ + isQueueQueryStsAuthenticationCapabilityEnabled : function() + { + return (this._queueQueryStsAuthenticationCapability !== null); + }, + + /** + * Getter for the Capability for querying a queue via STS + * + * @return Capability, or null + */ + get_queueQueryStsAuthenticationCapability : function() + { + return this._queueQueryStsAuthenticationCapability; + }, + + /** + * Enables the Capability to query a queue via STS + */ + enableQueueQueryStsAuthenticationCapability : function() + { + if(!this._queueQueryStsAuthenticationCapability) + { + this._queueQueryStsAuthenticationCapability = new webservices.Capability(webservices.CapabilityUrls.QueueQuery.QUERY, + webservices.HttpMethods.POST); + } + }, + + /** + * Disables the Capability to query a queue via STS + */ + disableQueueQueryStsAuthenticationCapability : function() + { + this._queueQueryStsAuthenticationCapability = null; + }, + + /** + * Returns true or false, depending on whether the Capability to query party info (name, photo) is enabled. + * + * @return True if querying party info is possible, false otherwise. + */ + isPartyInfoCapabilityEnabled : function() + { + return (this._partyInfoCapability !== null); + }, + + /** + * Getter for the Capability for querying party info + * + * @return Capability, or null + */ + get_partyInfoCapability : function() + { + return this._partyInfoCapability; + }, + + /** + * Enables the Capability to query party info + */ + enablePartyInfoCapability : function() + { + if(!this._partyInfoCapability) + { + this._partyInfoCapability = new webservices.Capability(webservices.CapabilityUrls.Common.PARTYINFO, + webservices.HttpMethods.POST); + } + }, + + /** + * Disables the Capability to query party info + */ + disablePartyInfoCapability : function() + { + this._partyInfoCapability = null; + }, + + /** + * Returns true or false, depending on whether the Capability to register with Tracker is enabled. + * + * @return True if Tracker registration is possible, false otherwise. + */ + isTrackerRegistrationCapabilityEnabled : function() + { + return (this._trackerRegistrationCapability !== null); + }, + + /** + * Getter for the Capability for registering with Tracker + * + * @return Capability, or null + */ + get_trackerRegistrationCapability : function() + { + return this._trackerRegistrationCapability; + }, + + /** + * Enables the Capability to register with Tracker + */ + enableTrackerRegistrationCapability : function() + { + if(!this._trackerRegistrationCapability) + { + this._trackerRegistrationCapability = new webservices.Capability(webservices.CapabilityUrls.Common.TRACKERREGISTRATION, + webservices.HttpMethods.POST); + } + }, + + /** + * Disables the Capability to register with Tracker + */ + disableTrackerRegistrationCapability : function() + { + this._trackerRegistrationCapability = null; + }, + + /** + * Returns true or false, depending on whether the Capability to authenticate a Chat with Tracker is enabled. + * + * @return True if Tracker authentication of Chats is possible, false otherwise. + */ + isChatTrackerAuthenticationCapabilityEnabled : function() + { + return this._chatTrackerAuthenticationCapability; + }, + + /** + * Enables the Capability to authenticate a Chat with Tracker + */ + enableChatTrackerAuthenticationCapability : function() + { + this._chatTrackerAuthenticationCapability = true; + }, + + /** + * Disables the Capability to authenticate a Chat with Tracker + */ + disableChatTrackerAuthenticationCapability : function() + { + this._chatTrackerAuthenticationCapability = false; + }, + + /** + * Returns true or false, depending on whether the Capability to authenticate a Chat with STS is enabled. + * + * @return True if STS Chat authentication is possible, false otherwise. + */ + isChatStsAuthenticationCapabilityEnabled : function() + { + return this._chatStsAuthenticationCapability; + }, + + /** + * Enables the Capability to authenticate a Chat with STS + */ + enableChatStsAuthenticationCapability : function() + { + this._chatStsAuthenticationCapability = true; + }, + + /** + * Disables the Capability to authenticate a Chat with STS + */ + disableChatStsAuthenticationCapability : function() + { + this._chatStsAuthenticationCapability = false; + }, + + /** + * Returns true or false, depending on whether the Capability to authenticate a Chat anonymously is enabled. + * + * @return True if anonymous Chat authentication is possible, false otherwise. + */ + isChatAnonymousAuthenticationCapabilityEnabled : function() + { + return this._chatAnonymousAuthenticationCapability; + }, + + /** + * Enables the Capability to authenticate a Chat anonymously + */ + enableChatAnonymousAuthenticationCapability : function() + { + this._chatAnonymousAuthenticationCapability = true; + }, + + /** + * Disables the Capability to authenticate a Chat anonymously + */ + disableChatAnonymousAuthenticationCapability : function() + { + this._chatAnonymousAuthenticationCapability = false; + }, + + /** + * Returns true or false, depending on whether the Capability to authenticate a Callback with Tracker is enabled. + * + * @return True if Tracker authentication of Callbacks is possible, false otherwise. + */ + isCallbackTrackerAuthenticationCapabilityEnabled : function() + { + return this._callbackTrackerAuthenticationCapability; + }, + + /** + * Enables the Capability to authenticate a Callback with Tracker + */ + enableCallbackTrackerAuthenticationCapability : function() + { + this._callbackTrackerAuthenticationCapability = true; + }, + + /** + * Disables the Capability to authenticate a Callback with Tracker + */ + disableCallbackTrackerAuthenticationCapability : function() + { + this._callbackTrackerAuthenticationCapability = false; + }, + + /** + * Returns true or false, depending on whether the Capability to authenticate a Callback with STS is enabled. + * + * @return True if STS authentication of Callbacks is possible, false otherwise. + */ + isCallbackStsAuthenticationCapabilityEnabled : function() + { + return this._callbackStsAuthenticationCapability; + }, + + /** + * Enables the Capability to authenticate a Callback with STS + */ + enableCallbackStsAuthenticationCapability : function() + { + this._callbackStsAuthenticationCapability = true; + }, + + /** + * Disables the Capability to authenticate a Callback with STS + */ + disableCallbackStsAuthenticationCapability : function() + { + this._callbackStsAuthenticationCapability = false; + }, + + /** + * Returns true or false, depending on whether the Capability to authenticate Callbacks anonymously is enabled. + * + * @return True if anonymous authentication of Callbacks is possible, false otherwise. + */ + isCallbackAnonymousAuthenticationCapabilityEnabled : function() + { + return this._callbackAnonymousAuthenticationCapability; + }, + + /** + * Enables the Capability to authenticate a Callback anonymously + */ + enableCallbackAnonymousAuthenticationCapability : function() + { + this._callbackAnonymousAuthenticationCapability = true; + }, + + /** + * Disables the Capability to authenticate a Callback anonymously + */ + disableCallbackAnonymousAuthenticationCapability : function() + { + this._callbackAnonymousAuthenticationCapability = false; + }, + + /** + * Disables all Capabilities + */ + reset : function() + { + this.disableStartChatCapability(); + this.disableReconnectChatCapability(); + this.disablePollCapability(); + this.disableSendMessageCapability(); + this.disableSetTypingStateCapability(); + this.disableExitCapability(); + this.disableProblemReportCapability(); + this.disableCreateCallbackCapability(); + this.disableCallbackStatusCapability(); + this.disableReconnectCallbackCapability(); + this.disableDisconnectCallbackCapability(); + this.disableQueueQueryAnonymousAuthenticationCapability(); + this.disableQueueQueryTrackerAuthenticationCapability(); + this.disableQueueQueryStsAuthenticationCapability(); + this.disablePartyInfoCapability(); + this.disableTrackerRegistrationCapability(); + this.disableChatTrackerAuthenticationCapability(); + this.disableChatStsAuthenticationCapability(); + this.disableChatAnonymousAuthenticationCapability(); + this.disableCallbackTrackerAuthenticationCapability(); + this.disableCallbackStsAuthenticationCapability(); + this.disableCallbackAnonymousAuthenticationCapability(); + } +}); + +// Register namespaces +webservices.registerChildNamespace("_Internal"); + +/** + * ServerConfigurationProcessorBase class + * + * When the application is loaded, the client queries the IC server for a list of the server's capabilities. This class processes + * the response to that request. Capabilities common to both client and server will be stored in the CapabilityRepository. + */ +webservices._Internal._ServerConfigurationProcessorBase = Class.create(common.InterfaceImplementation, +{ + /** + * Constructor + * + * @param capabilityRepository Where to store the capabilities common to client and server. + */ + initialize: function($super, capabilityRepository) + { + common.ParameterValidation.validate([capabilityRepository], [ {"required": true}, {"required": true} ]); + + $super(); + + this._capabilityRepository = capabilityRepository; + this._lastServerConfigurationVersion = null; + }, + + /** + * Destructor + */ + destroy : function() + { + webservices.ResponseBase.prototype.destroy.call(this); + }, + + // public methods + + /** + * Process the response to a request for the IC server's capabilities. + * + * @param response An instance of webservices.ServerConfigurationResponse + */ + process : function(response) + { + common.Interface.ensureImplements(response, webservices.Interfaces.IServerConfigurationResponse); + + // reset the capability repository since we're processing a whole new server configuration + this._capabilityRepository.reset(); + + if(response) + { + this._lastServerConfigurationVersion = response.get_serverConfigVersion(); + this._enableCommonCapabilities(response.get_commonCapabilities()); + this._enableChatCapabilities(response.get_chatCapabilities()); + this._enableCallbackCapabilities(response.get_callbackCapabilities()); + this._enableQueueQueryCapabilities(response.get_queueQueryCapabilities()); + + if (webservices.ProblemReporter) + { + webservices.ProblemReporter.set_regEx(response.get_problemReportRegEx()); + } + } + }, + + /** + * The server configuration response JSON contains a field "cfgVer". This is + * incremented each time certain properties are changed in IA. This method + * returns the most recent value for this field that has been received from + * the server. + * + * @return An integer indicating the most recent configuration version received from the server. + */ + get_lastServerConfigurationVersion : function() + { + return this._lastServerConfigurationVersion; + }, + + /** + * Resets the most recently obtained server configuration version number, so that the next + * poll will definitely trigger a server configuration request. + */ + resetServerConfigurationVersion : function() + { + this._lastServerConfigurationVersion = null; + }, + + // private methods + + _enableCommonCapabilities : function(capabilities) + { + if(capabilities.indexOf(webservices.ServerConfigurationCapabilities.Common.SUPPORT_REGISTRATION_TRACKER) != -1) + { + this._capabilityRepository.enableTrackerRegistrationCapability(); + } + if(capabilities.indexOf(webservices.ServerConfigurationCapabilities.Common.PROBLEM_REPORT) != -1) + { + this._capabilityRepository.enableProblemReportCapability(); + } + + if(capabilities.indexOf(webservices.ServerConfigurationCapabilities.Common.PARTY_INFO) != -1) + { + this._capabilityRepository.enablePartyInfoCapability(); + } + }, + + _enableChatCapabilities : function(capabilities) + { + if(capabilities.indexOf(webservices.ServerConfigurationCapabilities.Chat.SUPPORT_AUTHENTICATION_TRACKER) != -1) + { + this._capabilityRepository.enableChatTrackerAuthenticationCapability(); + } + + if(capabilities.indexOf(webservices.ServerConfigurationCapabilities.Chat.SUPPORT_AUTHENTICATION_STS) != -1) + { + this._capabilityRepository.enableChatStsAuthenticationCapability(); + } + + if(capabilities.indexOf(webservices.ServerConfigurationCapabilities.Chat.SUPPORT_AUTHENTICATION_ANONYMOUS) != -1) + { + this._capabilityRepository.enableChatAnonymousAuthenticationCapability(); + } + + if(capabilities.indexOf(webservices.ServerConfigurationCapabilities.Chat.START) != -1) + { + this._capabilityRepository.enableStartChatCapability(); + } + + if(capabilities.indexOf(webservices.ServerConfigurationCapabilities.Chat.RECONNECT) != -1) + { + this._capabilityRepository.enableReconnectChatCapability(); + } + + if(capabilities.indexOf(webservices.ServerConfigurationCapabilities.Chat.POLL) != -1) + { + this._capabilityRepository.enablePollCapability(); + } + + if(capabilities.indexOf(webservices.ServerConfigurationCapabilities.Chat.SENDMESSAGE) != -1) + { + this._capabilityRepository.enableSendMessageCapability(); + } + + if(capabilities.indexOf(webservices.ServerConfigurationCapabilities.Chat.SETTYPINGSTATE) != -1) + { + this._capabilityRepository.enableSetTypingStateCapability(); + } + + if(capabilities.indexOf(webservices.ServerConfigurationCapabilities.Chat.EXIT) != -1) + { + this._capabilityRepository.enableExitCapability(); + } + }, + + _enableCallbackCapabilities : function(capabilities) + { + if(capabilities.indexOf(webservices.ServerConfigurationCapabilities.Callback.SUPPORT_AUTHENTICATION_TRACKER) != -1) + { + this._capabilityRepository.enableCallbackTrackerAuthenticationCapability(); + } + + if(capabilities.indexOf(webservices.ServerConfigurationCapabilities.Callback.SUPPORT_AUTHENTICATION_STS) != -1) + { + this._capabilityRepository.enableCallbackStsAuthenticationCapability(); + } + + if(capabilities.indexOf(webservices.ServerConfigurationCapabilities.Callback.SUPPORT_AUTHENTICATION_ANONYMOUS) != -1) + { + this._capabilityRepository.enableCallbackAnonymousAuthenticationCapability(); + } + + if(capabilities.indexOf(webservices.ServerConfigurationCapabilities.Callback.CREATE) != -1) + { + this._capabilityRepository.enableCreateCallbackCapability(); + } + + if(capabilities.indexOf(webservices.ServerConfigurationCapabilities.Callback.STATUS) != -1) + { + this._capabilityRepository.enableCallbackStatusCapability(); + } + + if(capabilities.indexOf(webservices.ServerConfigurationCapabilities.Callback.RECONNECT) != -1) + { + this._capabilityRepository.enableReconnectCallbackCapability(); + } + + if(capabilities.indexOf(webservices.ServerConfigurationCapabilities.Callback.DISCONNECT) != -1) + { + this._capabilityRepository.enableDisconnectCallbackCapability(); + } + }, + + _enableQueueQueryCapabilities : function(capabilities) + { + if(capabilities.indexOf(webservices.ServerConfigurationCapabilities.QueueQuery.SUPPORT_AUTHENTICATION_TRACKER) != -1) + { + this._capabilityRepository.enableQueueQueryTrackerAuthenticationCapability(); + } + if(capabilities.indexOf(webservices.ServerConfigurationCapabilities.QueueQuery.SUPPORT_AUTHENTICATION_STS) != -1) + { + this._capabilityRepository.enableQueueQueryStsAuthenticationCapability(); + } + if(capabilities.indexOf(webservices.ServerConfigurationCapabilities.QueueQuery.SUPPORT_AUTHENTICATION_ANONYMOUS) != -1) + { + this._capabilityRepository.enableQueueQueryAnonymousAuthenticationCapability(); + } + } +}); + +/** + * An abstract class representing anything that can have listeners listening for success/failure events. + */ +webservices.ListenableBase = Class.create(common.InterfaceImplementation, +{ + /** + * Constructor + */ + initialize : function($super) + { + common.Debug.traceMethodEntered("ListenableBase.initialize()"); + + $super(); + + this._successListeners = []; + this._failureListeners = []; + + common.Debug.traceMethodExited("ListenableBase.initialize()"); + }, + + /** + * Destructor + */ + destroy : function() + { + common.InterfaceImplementation.prototype.destroy.call(this); + }, + + /** + * Erases the lists of listeners for success and failure events. + */ + reset : function() + { + this._successListeners = []; + this._failureListeners = []; + }, + + /** + * Register a listener to be called when an operation succeeds. + */ + registerSuccessListener : function(listener) + { + common.Debug.traceMethodEntered("ListenableBase.registerSuccessListener()"); + this._successListeners.push(listener); + common.Debug.traceMethodExited("ListenableBase.registerSuccessListener()"); + }, + + /** + * Register a listener to be called when an operation fails. + */ + registerFailureListener: function(listener) + { + common.Debug.traceMethodEntered("ListenableBase.registerFailureListener()"); + this._failureListeners.push(listener); + common.Debug.traceMethodExited("ListenableBase.registerFailureListener()"); + }, + + /** + * Loop through the listeners that are listening for notification of success, and notify them + * that the operation has succeeded. + * + * @param obj This object will be passed to each listener's callback method. + */ + notifyListenersOfSuccess: function(obj) + { + this._notifyListeners(this._successListeners, obj); + }, + + /** + * Loop through the listeners that are listening for notification of failure, and notify them + * that the operation has failed. + * + * @param obj This object will be passed to each listener's callback method. + */ + notifyListenersOfFailure : function(obj) + { + this._notifyListeners(this._failureListeners, obj); + }, + + // private methods + + _notifyListeners : function(listenerList, obj) + { + var exceptions = new Array(); + for (var i = 0; i < listenerList.length; ++i) + { + var listener = listenerList[i]; + try + { + listener(obj); + } catch (e) + { + exceptions.push(e); // Save for later. Continue notifying listeners. + } + } + if (exceptions.length == 1) + { + throw exceptions[0]; + } else if (exceptions.length > 1) + { + var masterException = new common.ExceptionFactory.createException("Multiple exceptions."); + masterException.exceptionList = exceptions; + throw masterException; + } + } +}); + +// Register namespaces +webservices.registerChildNamespace("_Internal"); + +/** + * Timer class + * Constructor takes integer specifying how many milliseconds to wait before firing. + * After construction, caller can register as a listener to the timer. + */ +webservices.Timer = Class.create(webservices.ListenableBase, +{ + /** + * Constructor + * + * @param duration How long before the timer should go off, in milliseconds. + */ + initialize : function($super, duration) + { + common.Debug.traceMethodEntered("Timer.initialize()"); + + $super(); + this._duration = duration; + this._windowTimeoutId = null; + + common.Debug.traceMethodExited("Timer.initialize()"); + }, + + /** + * Destructor + */ + destroy : function() + { + webservices.ListenableBase.prototype.destroy.call(this); + + this._windowTimeoutId = null; + }, + + /** + * Function to start the timer, i.e. calling this will cause notifyListenersOfSuccess() to be + * called after duration milliseconds + */ + start : function() + { + common.Debug.traceVerboseNote("Starting a " + this._duration + "ms timer..."); + var _self = this; + this._windowTimeoutId = window.setTimeout(function() { _self.onTimer(); }, this._duration); + common.Debug.traceVerboseNote("...timer started. _windowTimeoutId=" + this._windowTimeoutId); + }, + + /** + * Returns true if this timer is running, false otherwise (i.e. if it hasn't been started yet, or was + * started and already fired and hasn't been started again since firing). + */ + isRunning : function() + { + return (this._windowTimeoutId !== null); + }, + + /** + * "Public" function to cancel the timer. + * Just creates a webservices.Mutex to execute _cancelImpl() as an atomic operation relative to any other + * methods called in this way. + * + * It is possible that the timer could go off in the short + * amount of time between the caller calling this method, and + * the implementation of this method actually canceling the + * timer. If the timer is successfully canceled, it will call + * notifyListenersOfFailure(). If not, the timer + * will go off, and call notifyListenersOfSuccess + * as normal. + */ + cancel : function() + { + common.Debug.traceMethodEntered("Timer.cancel()"); + if (null === this._windowTimeoutId) + { + common.Debug.traceNote("Nothing to do!"); + } + else + { + common.Debug.traceNote("Now creating mutex to cancel timer: " + this._windowTimeoutId); + + // Can't do new webservices.Mutex(this._cancelImpl). + // Also can't do new webservices.Mutex("this._cancelImpl()"). + // This is because setTimeout() doesn't work well with object methods. But, we can create a locally-scoped + // variable, which the passed function will still be able to see. + var _self = this; + new webservices.Mutex(function() { return _self._cancelImpl(); }); + } + common.Debug.traceMethodExited("Timer.cancel()"); + }, + + /** + * "Private" function to cancel the timer. + */ + _cancelImpl : function() + { + common.Debug.traceMethodEntered("Timer._cancelImpl()"); + common.Debug.traceNote("This._windowTimeoutId=" + this._windowTimeoutId); + if (this.isRunning()) + { + window.clearTimeout(this._windowTimeoutId); + common.Debug.traceNote("_cancelImpl() canceled timer #" + this._windowTimeoutId + "...notifying listeners of timer's failure to go off"); + this.notifyListenersOfFailure(); + common.Debug.traceNote("_cancelImpl() finished notifying listeners of timer #" + this._windowTimeoutId + "'s failure to go off."); + this._windowTimeoutId = null; + } + else + { + common.Debug.traceNote("_cancelImpl() failed to cancel timer"); + } + common.Debug.traceMethodExited("Timer._cancelImpl()"); + }, + + /** + * "Public" function to restart a timer. + * + * If a timer is running, but hasn't fired yet, this method will cause the timer to "start over" with the + * originally-specified duration. + * + * Example: At 1:00:00, a timer is set with duration = 0:30. notifyListenersOfSuccess() + * will be fired at 1:00:30. + * But, if restart() is called at 1:00:20, then notifyListenersOfSuccess() will not get + * called until 1:00:50. + * + * Note that if a timer was set and has already fired, and it is desired that the timer start again, this + * method is not the appropriate one to call. In this scenario, simply call start(). + * restart is only meant to be called on a currently-running timer, and will have no effect if + * called on a timer that is not currently running. + * + * Implementation-wise, this function just creates a webservices.Mutex to execute _restartImpl() as an atomic + * operation relative to any other methods called in this way. + * + * @param duration Optional. If specified, this will be the new duration of the timer, in milliseconds + */ + restart : function(duration) + { + common.Debug.traceMethodEntered("Timer.restart()"); + + if (null != duration) + { + this._duration = duration; + } + + common.Debug.traceNote("Now creating mutex to restart timer: " + this._windowTimeoutId); + // Can't do new webservices.Mutex(this._restartImpl). + // Also can't do new webservices.Mutex("this._restartImpl()"). + // This is because setTimeout() doesn't work well with object methods. But, we can create a locally-scoped + // variable, which the passed function will still be able to see. + var _self = this; + new webservices.Mutex(function() { return _self._restartImpl(); }); + common.Debug.traceMethodExited("Timer.restart()"); + }, + + /** + * "Private" function to restart the timer. + */ + _restartImpl : function() + { + common.Debug.traceMethodEntered("Timer._restartImpl()"); + var retVal = false; + if (this.isRunning()) + { + common.Debug.traceNote("_restartImpl indicates that timer is already running, so canceling"); + this._cancelImpl(); + this.start(); + retVal = true; + } + common.Debug.traceMethodExited("Timer._restartImpl()"); + return retVal; + }, + + /** + * Erases the _windowTimeoutId, and calls notifyListenersOfSuccess(). + */ + onTimer : function() + { + common.Debug.traceVerboseNote("Timer firing. ID was: " + this._windowTimeoutId); + this.notifyListenersOfSuccess(); + this._windowTimeoutId = null; + }, + + /** + * Returns the duration of the timer, in milliseconds + * + * @return The duration of the timer, in milliseconds + */ + get_duration : function() + { + return this._duration; + } +}); + +/** + * RecurringTimer class + * + * This class represents a timer which goes off after a certain time period, and every time it + * goes off, it re-sets it self to go off again once that time period elapses again. + */ +webservices.RecurringTimer = Class.create(webservices.Timer, +{ + /** + * Constructor + * + * @param duration How often the timer should go off, in milliseconds. + */ + initialize: function($super, duration) + { + common.Debug.traceMethodEntered("RecurringTimer.initialize()"); + $super(duration); + + this._isRunning = false; + + common.Debug.traceMethodExited("RecurringTimer.initialize()"); + }, + + /** + * Destructor + */ + destroy : function() + { + webservices.Timer.prototype.destroy.call(this); + }, + + // methods + + /** + * Start the timer + */ + start : function() + { + this._isRunning = true; + webservices.Timer.prototype.start.call(this); + }, + + /** + * Stop the timer + */ + stop : function() + { + this.cancel(); + this._isRunning = false; + }, + + /** + * This method is called when the timer goes off. Do not call it directly. + * This method will notify this timer's listeners, and then re-start the timer. + * + * @see ListenableBase.notifyListenersOfSuccess() This method provides the mechanism by which the timer's listeners are notified. + */ + onTimer : function() + { + webservices.Timer.prototype.onTimer.call(this); + + if(this._isRunning) + { + common.Debug.traceVerboseNote("Restarting the timer"); + this.start(); + } + } +}); + +/** + * AJAX request functionality class + * Wrapper for Prototype's Ajax.Request functionality. + */ +webservices.AjaxRequest = Class.create( +{ + /** + * Constructor + * + * @param url The URL that is being requested + * @param options Defined at http://www.prototypejs.org/api/ajax/options + */ + initialize : function(url, options) + { + this._url = url; + this._options = options; + }, + + /** + * Destructor + */ + destroy : function() + { + this._url = null; + this._options = null; + }, + + // methods + + /** + * Sends the AjaxRequest + */ + send : function() + { + common.Debug.traceAlways("REQUEST: " + this._url); + common.Debug.traceNote("Asynchronous: " + this._options.asynchronous); + + this._options.onCreate = this.onCreateAjaxRequest.bind(this); + this._options.onComplete = this.onCompleteAjaxRequest.bind(this); + + new Ajax.Request(this._url, this._options); + }, + + /** + * This method is called any time an AJAX request is created. + * It sets a timer, which is cleared by onCompleteAjaxRequest(). Thus, if the timer + * ever goes off, that means that the AJAX request timed out. + * + * @param ajaxRequest An AJAX request + */ + onCreateAjaxRequest : function(ajaxRequest) + { + common.Debug.traceMethodEntered("AjaxRequest.onCreateAjaxRequest()"); + this._ajaxRequest = ajaxRequest; + var retryCounts = webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.RetryCounts); + var duration = retryCounts.get_ajaxTimeoutMilliseconds(); + var d = new Date(); + this._ajaxRequest.request.options.ININ_Timeout_ID = window.setTimeout(this.onAjaxRequestTimeout.bind(this), duration); + common.Debug.traceNote("Set timeout #" + this._ajaxRequest.request.options.ININ_Timeout_ID + " for " + duration + "ms at: " + d.toTimeString()); + common.Debug.traceMethodExited("AjaxRequest.onCreateAjaxRequest()"); + }, + + /** + * AJAX does not have a built-in mechanism for limiting the time to wait on a request. + * So, this method implements that. + * + * @param ajaxRequest An AJAX request. Passed automatically by AJAX, but is ignored because it isn't always passed in IE. this._ajaxRequest is used instead. + */ + onAjaxRequestTimeout : function(ajaxRequest) + { + common.Debug.traceMethodEntered("AjaxRequest anonymous timeout function"); + var d = new Date(); + common.Debug.traceWarning("AJAX request with timeout #" + this._ajaxRequest.request.options.ININ_Timeout_ID + " timed out at " + d.toTimeString() + "!"); + if (1 == this._ajaxRequest.transport.readyState || 2 == this._ajaxRequest.transport.readyState || 3 == this._ajaxRequest.transport.readyState) + { + this._ajaxRequest.transport.onreadystatechange = Prototype.emptyFunction; + this._ajaxRequest.transport.abort(); + Ajax.activeRequestCount--; + this._ajaxRequest.request.options.onFailure(this._ajaxRequest); + if (webservices.ProblemReporter) + { + webservices.ProblemReporter.recordTimedOutRequest(this._ajaxRequest); + } + } + common.Debug.traceMethodExited("AjaxRequest anonymous timeout function"); + }, + + /** + * This method is called when an AJAX request completes. + * It clears the timer that was set by onCreateAjaxRequest(). + * + * @param ajaxRequest An AJAX request. Passed automatically by AJAX, but is ignored. + */ + onCompleteAjaxRequest : function(ajaxRequest) + { + common.Debug.traceMethodEntered("AjaxRequest.onCompleteAjaxRequest()"); + window.clearTimeout(this._ajaxRequest.request.options.ININ_Timeout_ID); + common.Debug.traceNote("Cleared timeout: " + this._ajaxRequest.request.options.ININ_Timeout_ID); + common.Debug.traceMethodExited("AjaxRequest.onCompleteAjaxRequest()"); + } +}); + +/** + * Base class which handles AJAX connection. + * Don't use this directly, use a derived class instead. + */ +webservices.AjaxManagerBase = Class.create(webservices.ListenableBase, +{ + // constants + CONTENT_TYPE_HEADER: 'content-type', + + /** + * Constructor + * + * @param capability A Capability object representing what this AjaxManager is intended to do (i.e. poll, send message, etc.) + * @param serverUriFragment The URI fragment that reverse proxies to the IC server. + */ + initialize: function($super, capability, serverUriFragment) + { + common.Debug.traceMethodEntered("AjaxManagerBase.initialize()"); + if(!capability) + { + common.Debug.traceError("null capability"); + common.Debug.breakpoint(); + } else + { + common.Debug.traceNote("Capability=" + capability.toString()); + } + + $super(); + + common.Interface.ensureImplements(capability, webservices.Interfaces.ICapability); + this._capability = capability; + + if(serverUriFragment) + { + this._serverUriFragment = serverUriFragment; + } + else + { + this._serverUriFragment = webservices.Servers.CurrentUriFragment; + } + + /** How many times the current AJAX request has been retried */ + this.retriesSoFar = 0; + common.Debug.traceMethodExited("AjaxManagerBase.initialize()"); + }, + + // public methods + + /** + * Send the AJAX request (to the url specified in the capability passed to the initialize(), using the HTTP method + * also specified in the capability). If HTTP method is POST, the arg to this is used to supply what data + * should be POSTed. Otherwise, the arg is ignored. + * + * @param dataToPost If it is a POST request, this is the data that shall be POSTed. Otherwise, ignored. + * @param participantIdToAppend If non-null, (a slash and) the participantID will be appended onto the end of the URL. A participantID identifies a participant within the context of a particular interaction. If the web user (the person whose browser is running this code) has several interactions, he/she will have the same number of participantIds. This should be one of the participantIds representing the web user - it should NOT be a participantId representing an agent. + * @param useAsynchronous If true, the request will be sent asynchronously. If false OR NULL, it will be sent synchronously. + */ + sendRequest: function(dataToPost, participantIdToAppend, useAsynchronous) + { + common.Debug.traceMethodEntered("AjaxManagerBase.sendRequest()"); + common.Interface.ensureImplements(this._capability, webservices.Interfaces.ICapability); + + if(common.Browser.isFireFox() && webservices.Utilities.needsElevatedPrivileges()) + { + common.Debug.traceScopeEntered("enablePrivilegeFunction"); + + try + { + netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect"); + } + catch (ex) + { + common.Debug.traceError(ex.message); + if (webservices.ProblemReporter) + { + webservices.ProblemReporter.sendProblemReport("Browser security settings error: " + ex, "AjaxManagerBase.sendRequest()"); + } + window.alert(localization.BrowserSecuritySettingsError); + + common.Debug.traceScopeExited("enablePrivilegeFunction"); + + return; + } + + common.Debug.traceScopeExited("enablePrivilegeFunction"); + } + + common.Debug.traceNote("AjaxManagerBase.sendRequest...dataToPost (which is allowed to be undefined) is: " + dataToPost); + var _self = this; + var dataToPostCached = dataToPost; + var participantIdToAppendCached = participantIdToAppend; + var sendTimestamp = new Date(); + var options = + { + method: this._capability.get_method(), + contentType: this.buildContentTypeValue(), + onSuccess: function(xmlHttpRequest) + { + common.Debug.traceNote("AjaxManagerBase.sendRequest() succeeded"); + + try + { + common.Debug.traceAlways("RESPONSE STATUS: " + xmlHttpRequest.status); + common.Debug.traceAlways("RESPONSE: " + xmlHttpRequest.responseText); + + if(xmlHttpRequest && xmlHttpRequest.status == 200) + { + _self.onTransportSuccess(xmlHttpRequest); + } + else + { + _self.onTransportFailure(xmlHttpRequest, dataToPostCached, participantIdToAppendCached); + } + } + catch(ex) + { + common.Debug.traceError(ex.message); + common.Debug.alert(ex.message); + common.Debug.breakpoint(); + if (webservices.ProblemReporter) + { + webservices.ProblemReporter.sendProblemReport(ex, "AjaxManagerBase.sendRequest().onSuccess()"); + } + } + }, + onFailure: function(xmlHttpRequest) + { + common.Debug.traceError("AjaxManagerBase.sendRequest() failed: " + xmlHttpRequest.request.url); + + try + { + xmlHttpRequest.sendTimestamp = sendTimestamp; + _self.onTransportFailure(xmlHttpRequest, dataToPostCached, participantIdToAppendCached); + } + catch(ex) + { + common.Debug.traceError(ex.message); + common.Debug.alert(ex.message); + common.Debug.breakpoint(); + if (webservices.ProblemReporter) + { + webservices.ProblemReporter.sendProblemReport(ex, "AjaxManagerBase.sendRequest().onFailure()"); + } + } + }, + onException : function(xmlHttpRequest, ex) + { + common.Debug.traceError("AjaxManagerBase.sendRequest() threw an exception: " + ex + ". URL: " + xmlHttpRequest.request.url); + + try + { + xmlHttpRequest.sendTimestamp = sendTimestamp; + _self.onTransportFailure(xmlHttpRequest, dataToPostCached, participantIdToAppendCached); + } + catch(ex) + { + common.Debug.traceError(ex.message); + common.Debug.alert(ex.message); + common.Debug.breakpoint(); + if (webservices.ProblemReporter) + { + webservices.ProblemReporter.sendProblemReport(ex, "AjaxManagerBase.sendRequest().onException()"); + } + } + }, + requestHeaders: + { + Accept: this.buildContentTypeValue() + }, + postBody: dataToPost, + asynchronous: !!useAsynchronous + }; + + var url = this._buildUrl(this._capability.get_relativeUrl(), participantIdToAppend); + common.Debug.traceStatus("URL = " + url); + + var request = new webservices.AjaxRequest(url, options); + request.send(); + common.Debug.traceNote("AjaxManagerBase.sendRequest() has sent the AJAX request."); + + request.destroy(); + delete request; + request = null; + + common.Debug.traceMethodExited("AjaxManagerBase.sendRequest()"); + }, + + /** + * Called when the AJAX request returns successfully (i.e. the transport was successful - we may have + * been successfully delivered a response from the server indicating that the server failed somehow in + * processing the request). + * It does the following: + * 1) Sets the retry counter to 0 + * 2) Parses AJAX response (using subclass' buildResponse() method). + * 3a) If AJAX response indicates failure, notifies listeners + * 3b) If AJAX response indicates success, notifies listeners + * Note: xmlHttpRequest contains properties responseText and responseXML. + * This method should not be called directly by clients of the API. + * + * @param xmlHttpRequest Standard xmlHttpRequest object representing the response from the webserver + */ + onTransportSuccess: function(xmlHttpRequest) + { + common.Debug.traceMethodEntered("AjaxManagerBase.onTransportSuccess()"); + + this.retriesSoFar = 0; // Clear it out for next time + this.validateContentType(xmlHttpRequest.getHeader(this.CONTENT_TYPE_HEADER)); + + var responseObject = this.buildResponse(xmlHttpRequest); + if (responseObject.isSuccessful()) + { + common.Debug.traceNote("onTransportSuccess() notifying listeners of successful building of response object"); + this.notifyListenersOfSuccess(responseObject); + common.Debug.traceNote("onTransportSuccess() done notifying listeners of successful building of response object"); + } + else + { + common.Debug.traceNote("Notifying listeners of failureful building of response object"); + this.notifyListenersOfFailure(responseObject); + common.Debug.traceNote("Done notifying listeners of failureful building of response object"); + } + + responseObject.destroy(); + delete responseObject; + responseObject = null; + + common.Debug.traceMethodExited("AjaxManagerBase.onTransportSuccess()"); + }, + + /** + * + * Called when the AJAX request returns unsuccessfully (i.e. the transport was unsuccessful). + * It calls the listeners. + * This method should not be called directly by clients of the API. + * + * @param xmlHttpRequest Standard xmlHttpRequest object representing the response from the webserver + * @param dataThatWasPosted If it was a POST request, this is the data that was POSTed. Otherwise, ignore. + * @param participantIdToAppend If non-null, (a slash and) the ID will be appended onto the end of the URL. + */ + onTransportFailure: function(xmlHttpRequest, dataThatWasPosted, participantIdToAppend) + { + common.Debug.traceMethodEntered("AjaxManagerBase.onTransportFailure()"); + common.Debug.traceNote("dataThatWasPosted: " + dataThatWasPosted + " Upon entry, have retried " + this.retriesSoFar + " times."); + // HTTP status code is in xmlHttpRequest.status + + var responseObject = this.buildResponse(xmlHttpRequest); + responseObject.xmlHttpRequest = xmlHttpRequest; // Give the ProblemReporter and any failure listeners access to the HTTP status code, etc. + if (webservices.ProblemReporter) + { + webservices.ProblemReporter.recordFailedRequest(responseObject); + } + if (this._shouldRequestBeRetried(responseObject)) + { + ++this.retriesSoFar; + this.sendRequest(dataThatWasPosted, participantIdToAppend, xmlHttpRequest.request.options.asynchronous); + } + else + { + this.retriesSoFar = 0; // Clear it out for next time + + common.Debug.traceNote("Processing response..."); + + common.Debug.traceNote("onTransportFailure notifying listeners of failure."); + this.notifyListenersOfFailure(responseObject); + common.Debug.traceNote("onTransportFailure done notifying listeners of failure."); + } + common.Debug.traceMethodExited("AjaxManagerBase.onTransportFailure()"); + }, + + // private methods + + _buildUrl : function(relativeUrl, participantIdToAppend) + { + var url = webservices.Servers.buildUrl(this._serverUriFragment, relativeUrl); + + if(participantIdToAppend) + { + url = url + "/" + participantIdToAppend; + } + + return url; + }, + + _shouldRequestBeRetried : function(response) + { + return this._shouldRequestBeRetriedBasedOnMessageTypeAndRetryCount(this._capability.get_relativeUrl(), this.retriesSoFar) && + this._shouldRequestBeRetriedBasedOnError(response.get_statusReason()); + }, + + _shouldRequestBeRetriedBasedOnMessageTypeAndRetryCount : function(capabilityUrl, retriesSoFar) + { + common.Debug.traceMethodEntered("AjaxManagerBase._shouldRequestBeRetriedBasedOnMessageTypeAndRetryCount()"); + var returnVal; + var retryCounts = webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.RetryCounts); + + if(capabilityUrl == webservices.CapabilityUrls.Chat.POLL) + { + returnVal = (retriesSoFar < retryCounts.get_pollRetries()); + } + else if(capabilityUrl == webservices.CapabilityUrls.Chat.EXIT) + { + returnVal = (retriesSoFar < retryCounts.get_exitRetries()); + } + else if(capabilityUrl == webservices.CapabilityUrls.Common.SERVERCONFIGURATION) + { + if (webservices.Servers.isConfiguredForSwitchover()) + { + returnVal = false; + // To clarify: + // With two servers, A and B, rather than trying A, A, A, A, B, B, B, B, + // we want to retry in a different order which is determined by the caller, + // such as A, B, A, B, A, B, A, B. So false is returned here, simply to give + // the caller the opportunity to switch to the other server. + } + else + { + returnVal = (retriesSoFar < retryCounts.get_serverConfigurationRetries()); + } + } + else if(capabilityUrl == webservices.CapabilityUrls.Chat.START) + { + returnVal = (retriesSoFar < retryCounts.get_startChatRetries()); + } + else if(capabilityUrl == webservices.CapabilityUrls.Chat.RECONNECT) + { + returnVal = (retriesSoFar < retryCounts.get_reconnectRetries()); + } + else if(capabilityUrl == webservices.CapabilityUrls.Common.TRACKERREGISTRATION) + { + returnVal = (retriesSoFar < retryCounts.get_trackerRegistrationRetries()); + } + else if(capabilityUrl == webservices.CapabilityUrls.Chat.GETFILE) + { + returnVal = (retriesSoFar < retryCounts.get_getFileRetries()); + } + else if(capabilityUrl == webservices.CapabilityUrls.Callback.CREATE) + { + returnVal = (retriesSoFar < retryCounts.get_createCallbackRetries()); + } + else if(capabilityUrl == webservices.CapabilityUrls.Chat.SENDMESSAGE) + { + returnVal = (retriesSoFar < retryCounts.get_sendMessageRetries()); + } + else if(capabilityUrl == webservices.CapabilityUrls.Chat.SETTYPINGSTATE) + { + returnVal = (retriesSoFar < retryCounts.get_setTypingStateRetries()); + } + else if(capabilityUrl == webservices.CapabilityUrls.Common.PROBLEMREPORT) + { + returnVal = (retriesSoFar < retryCounts.get_problemReportRetries()); + } + else + { + // Unrecognized capability! + common.Debug.traceWarning("Unrecognized capability: " + capabilityUrl); + returnVal = true; + } + common.Debug.traceMethodExited("AjaxManagerBase._shouldRequestBeRetriedBasedOnMessageTypeAndRetryCount()"); + return returnVal; + }, + + _shouldRequestBeRetriedBasedOnError : function(error) + { + common.Debug.traceMethodEntered("AjaxManagerBase._shouldRequestBeRetriedBasedOnError()"); + var returnVal; + if(error.get_token(1) == webservices.ErrorCodes.WEBSVC) + { + var webSvcError = error.get_token(2); + if((!webSvcError) || (webSvcError == webservices.ErrorCodes.GENERAL)) + { + // generic error gets retried + returnVal = true; + } + else + { + // any web service error other than general means that something is wrong with + // the content of the message or the entities it references, so a retry won�t help + returnVal = false; + } + } + else if(error.get_token(1) == webservices.ErrorCodes.HTTP) + { + var httpError = error.get_token(2); + + if(!httpError) + { + // generic error gets retried + returnVal = true; + } + else if (httpError.length == 0) + { + // Unrecognized error! + common.Debug.traceWarning("Unrecognized HTTP error"); + returnVal = false; + } + else if (httpError == webservices.ErrorCodes.BADGATEWAY) + { + common.Debug.traceWarning("Received error 502 - perhaps the reverse proxy is not configured correctly on the webserver, or the IC server is not responding."); + returnVal = false; + } + else if (httpError == webservices.ErrorCodes.SERVICEUNAVAILABLE) + { + common.Debug.traceWarning("Received error 503 - perhaps this server is currently the backup in a switchover pair."); + returnVal = false; + } + else if(httpError[0] == '5') + { + // these 500 level errors may be temporary, so a retry is warranted + common.Debug.traceWarning("Received error " + httpError + ". Retrying."); + returnVal = true; + } + else + { + // a 100, 200, 300 or 400 level error would do no good to retry + // (exception: status code 200 indicates success and therefore wouldn't get here) + // (exception: status code 404 MAY indicate the server is attempting to beat a DoS attack. Don't retry, though, else + // that will be contributing to the attack). + common.Debug.traceWarning("Received error " + httpError + ". Not retrying."); + returnVal = false; + } + } + else + { + // default to true giving a retry attempt the benefit of the doubt + returnVal = true; + } + common.Debug.traceNote("Returning: " + returnVal); + common.Debug.traceMethodExited("AjaxManagerBase._shouldRequestBeRetriedBasedOnError()"); + return returnVal; + } +}); + +// Register namespaces + +webservices.registerChildNamespace("_Internal"); + +/** + * This class is the main brains of managing the server configuration, but is abstract - use derived class instead + */ +webservices.ServerConfigurationManagerBase = Class.create(webservices.ListenableBase, +{ + /** + * Constructor + * + * @param genericResponseBuilder An object to build Response objects (i.e. objects which implement ResponseBase or its subclasses) from the IC server's replies to AJAX requests. + * @param capabilityRepository An object to keep track of which Capabilities are enabled or disabled, and provide getter methods for the various Capabilities. + * @param serverConfigurationProcessor An object to parse the IC server's capabilities + */ + initialize : function($super, genericResponseBuilder, capabilityRepository, serverConfigurationProcessor) + { + common.Debug.traceMethodEntered("ServerConfigurationManagerBase.initialize()"); + + var numArgs = 4; + if(arguments.length != numArgs) + { + throw common.ExceptionFactory.createException("ServerConfigurationManagerBase constructor called with " + arguments.length + " arguments, but expected " + numArgs + "."); + } + +// common.ParameterValidation.validate(arguments, [ {}, {"required": true} ]); + + $super(); + + this._genericResponseBuilder = genericResponseBuilder; + this._capabilityRepository = capabilityRepository; + this._serverConfigurationProcessor = serverConfigurationProcessor; + + common.Debug.traceMethodExited("ServerConfigurationManagerBase.initialize()"); + }, + + /** + * Destructor + */ + destroy : function() + { + common.Debug.traceMethodEntered("ServerConfigurationManagerBase.destroy()"); + + this._genericResponseBuilder = null; + this._serverConfigurationProcessor = null; + this._capabilityRepository = null; + + common.Debug.traceMethodExited("ServerConfigurationManagerBase.destroy()"); + }, + + // public methods + + /** + * Queries the IC server for its configuration/capabilities. + * + * @param callback This function will be called when a response is received from the IC server. This function should take 1 argument. It will be passed true if the query succeeded, and false if it failed. + */ + getServerConfiguration : function(callback) + { + common.Debug.traceMethodEntered("ServerConfigurationManagerBase.getServerConfiguration()"); + + // create the get server configuration capability and then get the server configuration + var capability = this._capabilityRepository.get_serverConfigurationCapability(); + var ajax = this.createAjaxManager(capability); + var _self = this; + ajax.registerSuccessListener(function(response) + { + common.Debug.traceNote("ServerConfigurationManagerBase.getServerConfiguration() succeeded: " + response); + _self._processServerConfiguration(response); + if (callback) + { + callback(true); + } + }); + ajax.registerFailureListener(function(response) + { + common.Debug.traceError("ServerConfigurationManagerBase.getServerConfiguration() failed: " + response); + if (callback) + { + callback(false, response.get_statusReason()); + } + }); + ajax.sendRequest(null, false, true); + + common.Debug.traceMethodExited("ServerConfigurationManagerBase.getServerConfiguration()"); + }, + + // private methods + + _processServerConfiguration : function(response) + { + this._serverConfigurationProcessor.process(response); + } +}); + +// Register namespaces +webservices.registerChildNamespace("ErrorCodes"); + +/** + * webservices.ErrorCodes enums + */ + +// (from systest\eic\main\products\eic\src\WebProcessor\ExternalBridge\error_codes.h) +// TODO: Make the codes be hierarchical, such as webservices.ERROR.CONTENTTYPE.INVALIDCHARSET, as in the .h file + +// Codes/namespaces within the root namespace: + +/** Namespace to contain all other error codes */ +webservices.ErrorCodes.ERROR = "error"; + +// Codes/namespaces within the error namespace + +/** Namespace to contain errors not pertaining to HTTP transport */ +webservices.ErrorCodes.WEBSVC = "websvc"; + +/** Namespace to contain errors pertaining to HTTP transport */ +webservices.ErrorCodes.HTTP = "http"; + +// Codes/namespaces within the error.websvc namespace + +/** Generic error */ +webservices.ErrorCodes.GENERAL = "general"; + +/** Namespace for errors indicating that the type of something is wrong */ +webservices.ErrorCodes.CONTENTTYPE = "contentType"; + +/** Namespace for errors indicating that the content of something is wrong */ +webservices.ErrorCodes.CONTENT = "content"; + +/** Namespace for errors indicating that the specified object was not found */ +webservices.ErrorCodes.UNKNOWNENTITY = "unknownEntity"; + +/** Namespace for errors associated with registration, authentication, authorization */ +webservices.ErrorCodes.USERDB = "userdb"; + +// Codes within the error.websvc.contentType namespace + +/** Error indicating that the wrong character set was used */ +webservices.ErrorCodes.INVALIDCHARSET = "invalidCharset"; + +/** Error indicating that contentType is not what was desired */ +webservices.ErrorCodes.INVALIDCONTENTTYPE = "invalidContentType"; + +// Codes/namespaces within the error.websvc.content namespace + +/** Illegal JSON sequence, XML document, etc */ +webservices.ErrorCodes.INVALID = "invalid"; + +// Codes within the error.websvc.content.invalid namespace + +/** Mising a required data element */ +webservices.ErrorCodes.MISSINGDATA = "missingData"; + +// Codes within the error.websvc.unknownEntity namespace + +/** Error indicating an attempt to access a session that does not exist */ +webservices.ErrorCodes.SESSION = "session"; + +/** Error indicating a participant does not exist */ +webservices.ErrorCodes.PARTICIPANT = "participant"; + +/** Error indicating a target (i.e. queue) does not exist */ +webservices.ErrorCodes.BADTARGET = "badTarget"; + +// Codes within the error.websvc.userdb namespace + +/** Error indicating that a user is not online */ +webservices.ErrorCodes.NOTONLINE = "notOnline"; + +/** Error indicating that the authentication credentials given were unacceptable */ +webservices.ErrorCodes.BADCREDENTIALS = "badCredentials"; + +/** Error indicating that an account name (that someone is trying to register) already exists */ +webservices.ErrorCodes.ACCOUNTEXISTS = "accountExists"; + +// Codes within the error.http namespace + +/** Error indicating that an attempt was made to send an HTTP request to a hostname that is not valid, such as www.nonexistantcompany.com */ +webservices.ErrorCodes.INVALIDHOST = "0"; + +/** Error representing a standard HTTP 301 error, given when a document has moved permanently */ +webservices.ErrorCodes.MOVEDPERMANENTLY = "301"; + +/** Error representing a standard HTTP 403 error, given when the requestor is not authorized to see the requested document */ +webservices.ErrorCodes.FORBIDDEN = "403"; + +/** Error representing a standard HTTP 404 error, given when a request was made for a document that does not exist */ +webservices.ErrorCodes.NOTFOUND = "404"; + +/** Error representing a standard HTTP 500 error, given when an error occurs internal to the webserver */ +webservices.ErrorCodes.INTERNALSERVERERROR = "500"; + +/** Error representing a standard HTTP 502 error, given when a proxy error occurs. This may happen when, for instance, a page at http://somewhere makes an AJAX request for a page at https://somewhere (note the protocol change) or http://somewhere_else */ +webservices.ErrorCodes.BADGATEWAY = "502"; + +/** Error representing a standard HTTP 503 error, given when a webserver is temporarily unable to fulfill a request (due to maintenance, overloading, etc.) */ +webservices.ErrorCodes.SERVICEUNAVAILABLE = "503"; + +/** + * Error class + * + * Represents an error, which contains a source, type, and subtype. + */ +webservices.Error = Class.create(common.InterfaceImplementation, +{ + /** + * Constructor + */ + initialize : function($super, errorCode) + { + common.ParameterValidation.validate([errorCode], [ {"type": String, "required": true, "allowEmpty": false} ]); + + $super(); + + this.addImplementedInterface(webservices.Interfaces.IError, webservices); + + this._errorCode = errorCode; + this._tokens = this._buildParsedTokenArray(errorCode); + }, + + // public methods + + /** + * Returns part of the Error. + * If index is 1, the error source is returned. If index is 2, the error type is returned. If index is 3, the + * error's subtype is returned. But it would likely be better to access these via get_errorSource(), get_errorType(), + * and get_subErrorType() respectively. + * + * @param index Specifies which piece of data about the Error to return + * @return A string containing the Error's source, type, or subtype, depending on the value of index. + */ + get_token : function(index) + { + return this._tokens[index]; + }, + + /** + * Gets this error's source + * + * @return A string indicating the source of the error + */ + get_errorSource : function() + { + return this.get_token(1); + }, + + /** + * Gets this error's type + * + * @return A string indicating the type of the error + */ + get_errorType : function() + { + return this.get_token(2); + }, + + /** + * Gets this error's subtype + * + * @return A string indicating the subtype of the error + */ + get_subErrorType : function() + { + return this.get_token(3); + }, + + /** + * Gets this error's textual representation. + * + * @return A string representing the error. + */ + get_errorCode : function() + { + return this._errorCode; + }, + + // private methods + + _buildParsedTokenArray : function(str) + { + return str.split('.'); + } +}); + +// Register namespaces +webservices.registerChildNamespace("_Internal"); + +/** + * ResponseBase + * + * Base class to represent responses received from the IC server + */ +webservices.ResponseBase = Class.create(common.InterfaceImplementation, +{ + EXCEPTION_INVALID_INTERACTION_STATE: "Invalid interaction state", + + /** + * Constructor + */ + initialize: function($super) + { + common.Debug.traceMethodEntered("ResponseBase.initalize()"); + + $super(); + + this.addImplementedInterface(webservices.Interfaces.IResponse, webservices); + + this._statusType = null; + this._statusReason = null; + + common.Debug.traceMethodExited("ResponseBase.initalize()"); + }, + + /** + * Destructor + */ + destroy : function() + { + this._statusType = null; + this._statusReason = null; + }, + + // methods + + /** + * Returns whether the status of this response indicates success or failure + * + * @return webservices.ResponseBase.STATUS_TYPE_SUCCESS or webservices.ResponseBase.STATUS_TYPE_FAILURE + */ + get_statusType : function() + { + return this._statusType; + }, + + /** + * Sets whether the status of this response indicates success or failure + * + * @param statusType webservices.ResponseBase.STATUS_TYPE_SUCCESS or webservices.ResponseBase.STATUS_TYPE_FAILURE + */ + set_statusType : function(statusType) + { + if (statusType != webservices.ResponseBase.STATUS_TYPE_SUCCESS && + statusType != webservices.ResponseBase.STATUS_TYPE_FAILURE) + { + throw common.ExceptionFactory.createException("Invalid status type: " + statusType); + } + + this._statusType = statusType; + }, + + /** + * Returns whether the response indicates success + * + * @return true if the response indicates success, false if it indicates a failure + */ + isSuccessful : function() + { + return webservices.ResponseBase.STATUS_TYPE_SUCCESS == this._statusType; + }, + + /** + * Gets the reason for the failure. There is no reason to call this on a successful response. + * + * @return An instance of Error indicating why the operation failed. Null if the operation succeeded. + */ + get_statusReason : function() + { + return this._statusReason; + }, + + /** + * Sets the reason for the failure. There is no reason to call this on a successful response. + * + * @param statusReason An instance of Error indicating why the operation failed. + */ + set_statusReason : function(statusReason) + { + common.Interface.ensureImplements(statusReason, webservices.Interfaces.IError); + + this._statusReason = statusReason; + }, + + /** + * Returns a representation of this object as a string. Useful for debugging purposes. + * + * @return string + */ + toString : function() + { + var msg = ""; + + if(this._statusType) + { + msg += "TYPE: "; + msg += this._statusType; + msg += ", "; + } + + if(this._statusReason) + { + msg += "ERROR CODE: "; + msg += this._statusReason.get_errorCode(); + } + + return msg; + } +}); + +/** Status type indicating success */ +webservices.ResponseBase.STATUS_TYPE_SUCCESS = "success"; + +/** Status type indicating failure */ +webservices.ResponseBase.STATUS_TYPE_FAILURE = "failure"; + +// Register namespaces +webservices.registerChildNamespace("ServerConfigurationCapabilities"); +webservices.registerChildNamespace("ServerConfigurationCapabilities.Common"); +webservices.registerChildNamespace("ServerConfigurationCapabilities.Chat"); +webservices.registerChildNamespace("ServerConfigurationCapabilities.Callback"); +webservices.registerChildNamespace("ServerConfigurationCapabilities.QueueQuery"); + +// List of all capabilities that this client knows about. The JSON/XML received from the IC server will be parsed to look for these. +// This list may change as future SUs are released. + +/** Capability to send a problem report */ +webservices.ServerConfigurationCapabilities.Common.PROBLEM_REPORT = "problemReport"; + +/** Capability to get party info */ +webservices.ServerConfigurationCapabilities.Common.PARTY_INFO = "partyInfo"; + +/** Capability to register via tracker */ +webservices.ServerConfigurationCapabilities.Common.SUPPORT_REGISTRATION_TRACKER = "supportRegistrationTracker"; + +/** Capability to authenticate to a chat using tracker */ +webservices.ServerConfigurationCapabilities.Chat.SUPPORT_AUTHENTICATION_TRACKER = "supportAuthenticationTracker"; + +/** Capability to authenticate to a chat using STS */ +webservices.ServerConfigurationCapabilities.Chat.SUPPORT_AUTHENTICATION_STS = "supportAuthenticationSTS"; + +/** Capability to authenticate to a chat anonymously */ +webservices.ServerConfigurationCapabilities.Chat.SUPPORT_AUTHENTICATION_ANONYMOUS = "supportAuthenticationAnonymous"; + +/** Capability to start a chat */ +webservices.ServerConfigurationCapabilities.Chat.START = "start"; + +/** Capability to reconnect to a chat */ +webservices.ServerConfigurationCapabilities.Chat.RECONNECT = "reconnect"; + +/** Capability to poll a chat for new messages/events */ +webservices.ServerConfigurationCapabilities.Chat.POLL = "poll"; + +/** Capability to send a message in a chat */ +webservices.ServerConfigurationCapabilities.Chat.SENDMESSAGE = "sendMessage"; + +/** Capability to send typing indicators */ +webservices.ServerConfigurationCapabilities.Chat.SETTYPINGSTATE = "setTypingState"; + +/** Capability to exit a chat */ +webservices.ServerConfigurationCapabilities.Chat.EXIT = "exit"; + +/** Capability to create a callback interaction */ +webservices.ServerConfigurationCapabilities.Callback.CREATE = "create"; + +/** Capability to query the status of a callback interaction */ +webservices.ServerConfigurationCapabilities.Callback.STATUS = "status"; + +/** Capability to reconnect a callback interaction */ +webservices.ServerConfigurationCapabilities.Callback.RECONNECT = "reconnect"; + +/** Capability to disconnect a callback interaction */ +webservices.ServerConfigurationCapabilities.Callback.DISCONNECT = "disconnect"; + +/** Capability to authenticate to a callback using tracker */ +webservices.ServerConfigurationCapabilities.Callback.SUPPORT_AUTHENTICATION_TRACKER = "supportAuthenticationTracker"; + +/** Capability to authenticate to a callback using STS */ +webservices.ServerConfigurationCapabilities.Callback.SUPPORT_AUTHENTICATION_STS = "supportAuthenticationSTS"; + +/** Capability to authenticate to a callback anonymously */ +webservices.ServerConfigurationCapabilities.Callback.SUPPORT_AUTHENTICATION_ANONYMOUS = "supportAuthenticationAnonymous"; + +/** Capability to authenticate to a queue query using tracker */ +webservices.ServerConfigurationCapabilities.QueueQuery.SUPPORT_AUTHENTICATION_TRACKER = "supportAuthenticationTracker"; + +/** Capability to authenticate to a queue query using STS */ +webservices.ServerConfigurationCapabilities.QueueQuery.SUPPORT_AUTHENTICATION_STS = "supportAuthenticationSTS"; + +/** Capability to authenticate to a queue query anonymously */ +webservices.ServerConfigurationCapabilities.QueueQuery.SUPPORT_AUTHENTICATION_ANONYMOUS = "supportAuthenticationAnonymous"; + + +// Register namespaces + + +/** + * ServerConfigurationResponse class + * + * When an AJAX request is made to the IC server to get the server configuration, ServerConfigurationResponseBuilder + * translates the IC server's JSON/XML reply into a ServerConfigurationResponse. + * + * It contains information about the IC server's Capabilities - callbacks, typing indicators, etc. This allows + * interoperability between clients and servers that are running different SUs. + */ +webservices.ServerConfigurationResponse = Class.create(webservices.ResponseBase, +{ + /** + * Constructor + */ + initialize: function($super) + { + common.Debug.traceMethodEntered("ServerConfigurationResponse.initalize()"); + + $super(); + + this.addImplementedInterface(webservices.Interfaces.IServerConfigurationResponse, webservices); + + this._commonCapabilities = []; + this._chatCapabilities = []; + this._callbackCapabilities = []; + this._queueQueryCapabilities = []; + this._problemReportRegEx = null; + + common.Debug.traceMethodExited("ServerConfigurationResponse.initalize()"); + }, + + /** + * Destructor + */ + destroy : function() + { + if(this._commonCapabilities) + { + delete this._commonCapabilities; + this._commonCapabilities = null; + } + if(this._chatCapabilities) + { + delete this._chatCapabilities; + this._chatCapabilities = null; + } + if(this._callbackCapabilities) + { + delete this._callbackCapabilities; + this._callbackCapabilities = null; + } + if(this._queueQueryCapabilities) + { + delete this._queueQueryCapabilities; + this._queueQueryCapabilities = null; + } + + webservices.ResponseBase.prototype.destroy.call(this); + }, + + // public methods + + /** + * Gets the version number of this server configuration response. + * + * @return Version number of server configuration + */ + get_serverConfigVersion : function() + { + return this._cfgVer; + }, + + /** + * Sets the version number of this server configuration response + * + * @param cfgVer Version number of server configuration + */ + set_serverConfigVersion : function(cfgVer) + { + this._cfgVer = cfgVer; + }, + + /** + * Returns a list of "common" capabilities. This does not mean capabilities that are shared between the client and + * the IC server. It refers to capabilities that are common to the various interaction types (chat, callback, etc.). + * See the constants beginning with webservices.ServerConfigurationCapabilities.Common above. + * + * @return JSON/XML string + */ + get_commonCapabilities : function() + { + return this._commonCapabilities; + }, + + /** + * Adds a common (to all interaction types) capability to the list of common capabilities. + * + * @param capability One of the ServerConfigurationCapability constants defined above. + */ + addCommonCapability : function(capability) + { + this._validateCapability(capability); + + // NOTE: Can't check for non-common capabilities, since this needs to support + // future common capabilities and they could be anything + + this._commonCapabilities.push(capability); + }, + + /** + * Sets the entire list of common (to all interaction types) capabilities, overwriting the previous list. + * + * @param capabilities Array of zero or more of the ServerConfigurationCapability constants defined above. + */ + set_commonCapabilities : function(capabilities) + { + this._commonCapabilities = capabilities; + }, + + /** + * Returns a list of chat capabilities. + * See the constants beginning with webservices.ServerConfigurationCapabilities.Chat above. + * + * @return JSON/XML string + */ + get_chatCapabilities : function() + { + return this._chatCapabilities; + }, + + /** + * Adds a chat capability to the list of chat capabilities. + * + * @param capability One of the ServerConfigurationCapability constants defined above. + */ + addChatCapability : function(capability) + { + this._validateCapability(capability); + + // NOTE: Can't check for non-chat capabilities, since this needs to support + // future chat capabilities and they could be anything + + this._chatCapabilities.push(capability); + }, + + /** + * Sets the entire list of chat capabilities, overwriting the previous list. + * + * @param capabilities Array of zero or more of the ServerConfigurationCapability constants defined above. + */ + set_chatCapabilities : function(capabilities) + { + this._chatCapabilities = capabilities; + }, + + /** + * Returns a list of callback capabilities. + * See the constants beginning with webservices.ServerConfigurationCapabilities.Callback above. + * + * @return JSON/XML string + */ + get_callbackCapabilities : function() + { + return this._callbackCapabilities; + }, + + /** + * Adds a callback capability to the list of callback capabilities. + * + * @param capability One of the ServerConfigurationCapability constants defined above. + */ + addCallbackCapability : function(capability) + { + this._validateCapability(capability); + + // NOTE: Can't check for non-callback capabilities, since this needs to support + // future callback capabilities and they could be anything + + this._callbackCapabilities.push(capability); + }, + + /** + * Sets the entire list of callback capabilities, overwriting the previous list. + * + * @param capabilities Array of zero or more of the ServerConfigurationCapability constants defined above. + */ + set_callbackCapabilities : function(capabilities) + { + this._callbackCapabilities = capabilities; + }, + + /** + * Gets the regular expression which problem reports must match if they are to be sent. + * If this is null, AND if the "sendProblemReport" capability is enabled, problem reports will be sent if problems occur. + * If this is non-null, AND if the "sendProblemReport" capability is enabled, problem reports will be sent if problems occur provided + * that the problem report that is generated matches this regular expression. + * If the "sendProblemReport" capability is disabled, then there is no point in calling get_problemReportRegEx() as its return + * value should be ignored. + */ + get_problemReportRegEx : function() + { + return this._problemReportRegEx; + }, + + /** + * Specifies a regular expression which problem reports must match if they are to be sent. + * If this is not set, or set to null, AND if the "sendProblemReport" capability is enabled, problem reports will be sent if problems occur. + * If this is set, AND if the "sendProblemReport" capability is enabled, problem reports will be sent if problems occur provided + * that the problem report that is generated matches this regular expression. + * If the "sendProblemReport" capability is disabled, then there is likely not much point in calling this method. The value passed will + * be stored, but problem reports will not be sent. + */ + set_problemReportRegEx : function(regEx) + { + this._problemReportRegEx = regEx; + }, + + /** + * Returns a list of queue query capabilities. + * See the constants beginning with webservices.ServerConfigurationCapabilities.QueueQuery above. + * + * @return JSON/XML string + */ + get_queueQueryCapabilities : function() + { + return this._queueQueryCapabilities; + }, + + /** + * Adds a queue query capability to the list of queue query capabilities. + * + * @param capability One of the ServerConfigurationCapability constants defined above. + */ + addQueueQueryCapability : function(capability) + { + this._validateCapability(capability); + + // NOTE: Can't check for non-queue query capabilities, since this needs to support + // future queue query capabilities and they could be anything + + this._queueQueryCapabilities.push(capability); + }, + + /** + * Sets the entire list of queue query capabilities, overwriting the previous list. + * + * @param capabilities Array of zero or more of the ServerConfigurationCapability constants defined above. + */ + set_queueQueryCapabilities : function(capabilities) + { + this._queueQueryCapabilities = capabilities; + }, + + // private methods + + _validateCapability : function(capability) + { + common.ParameterValidation.validate([capability], [ {"type": String, "required": true, "allowEmpty": false} ]); + } + +}); + + + +// Register namespaces +webservices.registerChildNamespace("Json"); + +/** + * Class which handles AJAX connection. + * To use this: + * 1) Pass a webservices.Interfaces.ICapability to the constructor, to represent + * what this AJAX manager is supposed to do (e.g. chat, send a message, etc.) + * 2) Register one or more callbacks for success and failure using registerSuccessListener() and + * registerFailureListener(). (These methods are inherited from ListenableBase) + * 3) Call sendRequest() at some point. If doing a POST, the data to post shall be the + * arg to that function. + * 4) The callback that you passed in step #2 will be called. It will have one parameter, which will be a + * webservices.ResponseBase (or a subclass thereof). + */ +webservices.Json.AjaxManager = Class.create(webservices.AjaxManagerBase, +{ + // constants + JSON_CONTENT_TYPE_VALUE: 'application/json', + UTF8_CONTENT_TYPE_VALUE: 'charset=utf-8', + + /** + * Constructor + * + * @param genericResponseBuilder The object that shall be used to translate the IC server's HTTP reply into a ResponseBase (or subclass thereof) + * @param capability A Capability object representing what this AjaxManager is intended to do (i.e. poll, send message, etc.) + * @param serverUriFragment The URI fragment that reverse proxies to the IC server. (optional - if not specified, the current one will be used.) + */ + initialize : function($super, genericResponseBuilder, capability, serverUriFragment) + { + common.Debug.traceMethodEntered("Json.AjaxManager.initialize()"); + $super(capability, serverUriFragment); + + this._genericResponseBuilder = genericResponseBuilder; + + common.Debug.traceMethodExited("Json.AjaxManager.initialize()"); + }, + + // public methods + + /** + * Returns the content type of the HTTP requests that this AjaxManager will make. + * + * @return String containing the content type of HTTP requests that this AjaxManager will make. + */ + buildContentTypeValue : function() + { + return this.JSON_CONTENT_TYPE_VALUE + "; " + this.UTF8_CONTENT_TYPE_VALUE; + }, + + /** + * When an AJAX request is sent and the HTTP server replies, this method is used to ensure + * that the content type of the response is one that this AjaxManager knows how to handle. + * + * @param contentType The value of the Content-Type field of an HTTP response that was received + */ + validateContentType : function(contentType) + { + common.Debug.traceNote('Content-type to validate: ' + contentType); + if(!contentType) + { + common.Debug.traceError('content-type received is null/empty'); + } + else if(!contentType.include(this.JSON_CONTENT_TYPE_VALUE) && !contentType.include(this.UTF8_CONTENT_TYPE_VALUE)) + { + common.Debug.traceError('Bad content-type received: ' + contentType); + } + }, + + /** + * Takes an AJAX reply from the IC server, and returns a ResponseBase or subclass thereof. + * + * @param xmlHttpRequest What was received from the IC server when an AJAX request was made + * @return A ResponseBase, or subclass thereof. + */ + buildResponse : function(xmlHttpRequest) + { + common.Debug.traceMethodEntered("Json.AjaxManager.buildResponse()"); + var retVal = null; + try + { + retVal = this._genericResponseBuilder.buildResponseFromRequest(xmlHttpRequest); + } + catch (ex) + { + common.Debug.traceError("Caught unhandled exception:\n" + ex); + } + common.Debug.traceMethodExited("Json.AjaxManager.buildResponse()"); + return retVal; + } +}); + + + +// Register namespaces +webservices.registerChildNamespace("Json"); + +/** + * ResponseBuilderBase class + * ChatResponseBuilder, CallbackResponseBuilder, etc. are derived from this. + */ +webservices.Json.ResponseBuilderBase = Class.create( +{ + /** + * Constructor + */ + initialize : function() + { }, + + /** + * Destructor + */ + destroy : function() + { }, + + /** + * Parses the "status" portion of the JSON protocol, and sets the appropriate properties on the + * response. + * + * Effectively a protected method. + * + * @param status The "status" portion of the JSON object that was received from the IC server in the HTTP reply. + * @param response A ResponseBase, or subclass thereof. Its statusType and statusReason properties may be set, depending on + * the value of the status param. + */ + _parseStatus : function(status, response) + { + common.Debug.traceMethodEntered("ResponseBaseBuilder.parseStatus()"); + + if(status) + { + common.Debug.traceNote("status type: " + status.type); + if(status.type) + { + response.set_statusType(status.type); + } + + common.Debug.traceNote("status reason: " + status.reason); + if(status.reason) + { + var error = null; + try + { + error = new webservices.Error(status.reason); + } + catch(ex) + { + common.Debug.traceError(ex.message); + common.Debug.traceWarning("Invalid status reason: " + status.reason); + } + + if(error) + { + response.set_statusReason(error); + } + } + } + + common.Debug.traceMethodExited("ResponseBaseBuilder.parseStatus()"); + }, + + // TODO: This method seems to be dead code. Confirm and remove. + parseStatus1 : function(status, response) + { + common.Debug.traceMethodEntered("Json.RegistrationResponseBuilder.buildRegistrationResponse()"); + common.Debug.traceStatus("jsonStr is: " + jsonStr); + + var response = null; + + if (jsonStr) + { + response = new webservices.RegistrationResponse(); + + var json = null; + try + { + json = jsonStr.evalJSON(); + } + catch (e) + { + common.Debug.traceError("Caught exception calling JSON.parse or string.evalJSON:\n" + e); + common.Debug.alert("Caught exception calling JSON.parse or string.evalJSON:\n" + e); + } + + if(json.registration) + { + if(json.registration.status) + { + if(json.registration.status.type) + { + response.set_statusType(json.registration.status.type); + } + + response._statusReason = json.registration.status.reason; + } + } + } + + common.Debug.traceMethodExited("Json.RegistrationResponseBuilder.buildRegistrationResponse()"); + + return response; + } +}); + + + +// Register namespaces +webservices.registerChildNamespace("Json._Internal"); + +/** + * ServerConfigurationProcessor class + * + * Placeholder for any future JSON-specific functionality relating to processing an IServerConfigurationResponse, etc. + */ +webservices.Json._Internal.ServerConfigurationProcessor = Class.create(webservices._Internal._ServerConfigurationProcessorBase, +{ + /** + * Constructor + * + * @param capabilityRepository An instance of CapabilityRepository, in which the capabilities will be stored. + */ + initialize: function($super, capabilityRepository) + { + $super(capabilityRepository); + }, + + /** + * Destructor + */ + destroy : function() + { + webservices._Internal._ServerConfigurationProcessorBase.prototype.destroy.call(this); + } +}); + + + +// Register namespaces +webservices.registerChildNamespace("Json._Internal"); + +/** + * ServerConfigurationResponseBuilder class + * When an AJAX request is made to the IC server to get the server configuration, this class translates the IC server's + * JSON reply into a response object that implements IServerConfigurationResponse. + */ +webservices.Json._Internal.ServerConfigurationResponseBuilder = Class.create(webservices.Json.ResponseBuilderBase, +{ + /** + * Constructor + */ + initialize : function($super) + { + $super(); + }, + + /** + * Destructor + */ + destroy : function() + { + webservices.Json.ResponseBuilderBase.prototype.destroy.call(this); + }, + + // methods + + /** + * Translates jsonStr into an implementation of IServerConfigurationResponse, for use by + * + * @param jsonStr The IC server's reply to the AJAX request for server configuration + * @returns An implementation of IServerConfigurationResponse + */ + buildServerConfigurationResponse : function(jsonStr) + { + common.Debug.traceMethodEntered("Json.ServerConfigurationResponseBuilder.buildServerConfigurationResponse()"); + common.Debug.traceStatus("jsonStr is: " + jsonStr); + + var response = null; + + if (jsonStr) + { + response = new webservices.ServerConfigurationResponse(); + + // default to failure until at least some point in the JSON is reached + response.set_statusType(webservices.ResponseBase.STATUS_TYPE_FAILURE); + + var json = null; + try + { + json = jsonStr.evalJSON(); + } + catch (e) + { + common.Debug.traceError("Caught exception calling JSON.parse or string.evalJSON:\n" + e); + } + + if(json) + { + if (common.Utilities.isType(json, Array) && json.length >= 1) + { + if(json[0].serverConfiguration) + { + response.set_statusType(webservices.ResponseBase.STATUS_TYPE_SUCCESS); + response.set_serverConfigVersion(json[0].serverConfiguration.cfgVer); + + // we'll say that every node under serverConfiguration is optional + var jsonCapabilities = json[0].serverConfiguration.capabilities; + if(jsonCapabilities) + { + if(jsonCapabilities.common) + { + response.set_commonCapabilities(jsonCapabilities.common); + } + + if(jsonCapabilities.chat) + { + response.set_chatCapabilities(jsonCapabilities.chat); + } + + if(jsonCapabilities.callback) + { + response.set_callbackCapabilities(jsonCapabilities.callback); + } + + if(jsonCapabilities.queueQuery) + { + response.set_queueQueryCapabilities(jsonCapabilities.queueQuery); + } + } + + if (json[0].serverConfiguration.problemReportRegEx) + { + response.set_problemReportRegEx(json[0].serverConfiguration.problemReportRegEx); + } + } + } + } + } + + common.Debug.traceMethodExited("Json.ServerConfigurationResponseBuilder.buildServerConfigurationResponse()"); + + return response; + } +}); + + + +// Register namespaces +webservices.registerChildNamespace("Json._Internal"); + +/** + * GenericResponseBuilder class + * This class is in charge of building responses (i.e. objects which implement ResponseBase or its subclasses) from the IC server's + * replies to AJAX requests. + * In the case of an HTTP error (i.e. a 404, etc.) or an HTTP reply that is successful but empty, this class builds the + * response object itself. Otherwise, it will delgate to one of the other *ResponseBuilder classes. + */ +webservices.Json._Internal.GenericResponseBuilder = Class.create( +{ + /** + * Constructor + * + * @param chatResponseBuilder Object to which the task of building chat responses should be delgated. + * @param callbackResponseBuilder Object to which the task of building callback responses should be delegated. + * @param registrationResponseBuilder Object to which the task of building registration responses should be delegated. + * @param serverConfigurationResponseBuilder Object to which the task of building server configuration responses should be delegated. + * @param partyInfoResponseBuilder Object to which the task of building party info responses should be delegated. + * @param queueQueryResponseBuilder Object to which the task of building queue query responses should be delegated. + */ + initialize: function(chatResponseBuilder, callbackResponseBuilder, registrationResponseBuilder, serverConfigurationResponseBuilder, + partyInfoResponseBuilder, queueQueryResponseBuilder) + { + this._chatResponseBuilder = chatResponseBuilder; + this._callbackResponseBuilder = callbackResponseBuilder; + this._registrationResponseBuilder = registrationResponseBuilder; + this._serverConfigurationResponseBuilder = serverConfigurationResponseBuilder; + this._partyInfoResponseBuilder = partyInfoResponseBuilder; + this._queueQueryResponseBuilder = queueQueryResponseBuilder; + }, + + /** + * Destructor + */ + destroy : function() + { }, + + // methods + + /** + * Takes an AJAX reply from the IC server, and returns a ResponseBase or subclass thereof. + * + * @param xmlHttpRequest What was received from the IC server when an AJAX request was made + * @return A ResponseBase, or subclass thereof. + */ + buildResponseFromRequest : function(xmlHttpRequest) + { + var response = null; + + // need to always return some kind of response for consumers to process + if(xmlHttpRequest) + { + if(xmlHttpRequest.status == 200) + { + response = this._buildResponseFromResponseText(xmlHttpRequest.responseText, xmlHttpRequest.request.url); + } + else + { + var errorCode = webservices.ErrorCodes.ERROR + '.' + webservices.ErrorCodes.HTTP; + if(xmlHttpRequest.status == 0) + { + errorCode += '.' + webservices.ErrorCodes.INVALIDHOST; + } + else if(xmlHttpRequest.status == 403) + { + errorCode += '.' + webservices.ErrorCodes.FORBIDDEN; + } + else if(xmlHttpRequest.status == 404) + { + errorCode += '.' + webservices.ErrorCodes.NOTFOUND; + } + else if(xmlHttpRequest.status == 500) + { + errorCode += '.' + webservices.ErrorCodes.INTERNALSERVERERROR; + } + else if(xmlHttpRequest.status == 503) + { + errorCode += '.' + webservices.ErrorCodes.SERVICEUNAVAILABLE; + } + else + { + errorCode += '.' + xmlHttpRequest.status; + } + + response = new webservices.ResponseBase(); + response.set_statusType(webservices.ResponseBase.STATUS_TYPE_FAILURE); + response.set_statusReason(new webservices.Error(errorCode)); + } + } + else + { + response = new webservices.ResponseBase(); + response.set_statusType(webservices.ResponseBase.STATUS_TYPE_FAILURE); + response.set_statusReason(new webservices.Error(webservices.ErrorCodes.ERROR + '.' + webservices.ErrorCodes.HTTP)); + } + + + return response; + }, + + // private methods + + _buildResponseFromResponseText : function(jsonStr, url) + { + common.Debug.traceMethodEntered("Json.GenericResponseBuilder.buildResponse()"); + common.Debug.traceStatus("jsonStr is: " + jsonStr); + common.Debug.traceStatus("url is: " + url); + + var response = null; + + if (jsonStr) + { + var json = null; + try + { + json = jsonStr.evalJSON(); + } + catch (e) + { + common.Debug.traceError("Caught exception calling JSON.parse or string.evalJSON:\n" + e); + } + + if(json) + { + if(json.chat) + { + response = this._chatResponseBuilder.buildChatResponse(jsonStr); + } + else if(json.callback) + { + response = this._callbackResponseBuilder.buildCallbackResponse(jsonStr, url); + } + else if(json.registration) + { + response = this._registrationResponseBuilder.buildRegistrationResponse(jsonStr); + } + else if(json.partyInfo) + { + response = this._partyInfoResponseBuilder.buildPartyInfoResponse(jsonStr); + } + else if(json.queue) + { + response = this._queueQueryResponseBuilder.buildQueueQueryResponse(jsonStr); + } + else if(common.Utilities.isType(json, Array) && json.length >= 1 && json[0].serverConfiguration) + { + response = this._serverConfigurationResponseBuilder.buildServerConfigurationResponse(jsonStr); + } + } + } + + // if it got to this point, then the http part was ok, but the web processor bridge is hosed + if(!response) + { + response = new webservices.ResponseBase(); + response.set_statusType(webservices.ResponseBase.STATUS_TYPE_FAILURE); + response.set_statusReason(new webservices.Error(webservices.ErrorCodes.ERROR + '.' + webservices.ErrorCodes.WEBSVC)); + } + + common.Debug.traceMethodExited("Json.GenericResponseBuilder.buildResponse()"); + + return response; + } +}); + + + +// Register namespaces +webservices.registerChildNamespace("Json._Internal"); + +/** + * ServerConfigurationManager class + * Extends ServerConfigurationManagerBase with JSON-specific functionality + */ +webservices.Json._Internal._ServerConfigurationManager = Class.create(webservices.ServerConfigurationManagerBase, +{ + /** + * Constructor + * + * @param genericResponseBuilder An instance of GenericResponseBuilder, to turn the JSON received from the IC server into a ResponseBase or subclass thereof + * @param capabilityRepository An instance of CapabilityRepository, in which the capabilities are stored. + * @param serverConfigurationProcessor An instance of (a subclass of) ServerConfigurationProcessorBase, such as ServerConfigurationProcessor + */ + initialize: function($super, genericResponseBuilder, capabilityRepository, serverConfigurationProcessor) + { + common.Debug.traceMethodEntered("Json.ServerConfigurationManager.initialize()"); + + $super(genericResponseBuilder, capabilityRepository, serverConfigurationProcessor); + + common.Debug.traceMethodExited("Json.ServerConfigurationManager.initialize()"); + }, + + // public methods + + /** + * Gets an JSON-specific instance of webservices.AjaxManagerBase + * This method should not be called directly by clients of the API. + * + * @param capability A Capability object representing what this AjaxManager object is intended to do (i.e. poll, send a message, etc.) + * @return AjaxManager + */ + createAjaxManager : function(capability) + { + common.Debug.traceMethodEntered("Json.ServerConfigurationManager.createAjaxManager()"); + var mgr = new webservices.Json.AjaxManager(this._genericResponseBuilder, capability); + common.Debug.traceMethodExited("Json.ServerConfigurationManager.createAjaxManager()"); + return mgr; + } +}); + +// Register namespaces +webservices.registerChildNamespace("_Internal"); + +/** + * (WebServices) LanguageCodeConverter class + * + * Provides methods pertaining to the use of language codes (aka IETF Tags). Examples of these tags are: + * en-US = English as spoken in the US + * en-GB = English as spoken in Great Britain + * de-CH = German as spoken in Switzerland + * ...etc. + * + * Note that generally the region portion of a language code is capitalized, but this is merely a convention, and + * this web application does not follow that convention. + * + * There is no need to instantiate this class - a singleton instance called webservices.LanguageCodeConverter is available. + */ +webservices._Internal.LanguageCodeConverter = Class.create({ + /** + * Constructor does nothing because all the functionality is essentially static + */ + initialize : function() { + }, + + /** + * Attempts to convert a language code into one for which ININ has created a resource file. + * It will not convert from one language to another, it just affects regional dialects of langauges. + * + * Note that LanguageList has functionality where if "xx-YY" is added, it will automatically add the base "XX" to + * the list as well. LanguageCodeConverter is intended for trickier situations than that. + * + * For instance, if passed "en-AU", it will return "en-US" since we publish a resource file for US English + * but not one for Australian English nor one for general English ("en"). + * + * Note, however, this means that if someone's language preference order is { "en-AU" (which we don't have), + * "fr" (which we do have), "en-US" (which we do have) }, this will result in them getting "en-US" since it's + * our designated substitute for their first choice. But such situations will be rare. + * + * @param languageCode A language code (presumably, one in which the user would like to chat, or would like to see the user interface, etc.) + * @param A language code to use instead of the one that was passed in + */ + convert : function(languageCode) + { + if (null == languageCode || "" == languageCode) { + common.Debug.traceError("Received empty languageCode"); + common.Debug.breakpoint(); + return languageCode; + } + + // We don't distribute a general "pt" file, just "pt-BR". If someone requests "pt", + // it's better to give them "pt-BR" rather than something non-Portuguese. + if (languageCode.match(/^pt/i) && !languageCode.match(/^pt-BR/i)) { + common.Debug.traceStatus('Substituting "pt-br" for "' + languageCode + '"'); + return "pt-br"; + } + + if (languageCode.match(/^en/i) && !languageCode.match(/^en-US/i)) { + common.Debug.traceStatus('Substituting "en-us" for "' + languageCode + '"'); + return "en-us"; + } + + // Norwegian Bokmal and Norwegian Nynorsk map to Norwegian. + if (languageCode.match(/^nb/i) || languageCode.match(/^nn/i)) { + common.Debug.traceStatus('Substituting "no" for "' + languageCode + '"'); + return "no"; + } + + // We distribute zh-Hans (simplified Chinese) and zh-Hant (traditional Chinese). + // But, browsers may send any of: zh-Hans, zh-SG (Singapore), zh-Hant, + // zh-HK (Hong Kong), zh-MO (Macau), zh-TW (Taiwan), zh + if (languageCode.match(/^zh$/i) || languageCode.match(/^zh-CN/i) || languageCode.match(/^zh-SG/i)) + { + common.Debug.traceStatus('Substituting "zh-Hans" for "' + languageCode + '"'); + return "zh-Hans"; + } + + if (languageCode.match(/^zh-HK/i) || languageCode.match(/^zh-TW$/i) || languageCode.match(/^zh-MO/i)) + { + common.Debug.traceStatus('Substituting "zh-Hant" for "' + languageCode + '"'); + return "zh-Hant"; + } + + return languageCode; + }, + + /** + * Returns the first token of a language code. For instance, if the parmameter is "en-US", then "en" will be returned. + * + * @param languageCode The language code whose first token is to be returned. + */ + getFirstToken : function(languageCode) + { + if(!languageCode) + { + return ""; + } + + var tokens = languageCode.split("-"); + return tokens[0]; + } +}); +webservices.LanguageCodeConverter = new webservices._Internal.LanguageCodeConverter(); + +/** + * An ordered list of unique language codes. Adding a language multiple times will have the same effect as adding it only once. + */ +webservices.LanguageList = Class.create( +{ + /** + * Constructor + */ + initialize: function() + { + this._array = new Array(); + }, + + /** + * Destructor + */ + destroy : function() + { + delete this._array; + }, + + /** + * Determines whether the list already contains a certain language code. Case-insensitive. + * + * @param languageCode A string whose value is a language code, i.e. "en-US". + * @return true if the language code is contained in the list. False if it is empty or not contained in the list. + */ + contains: function(languageCode) + { + if (null == languageCode || languageCode.length == 0) + { + return false; + } + + return (-1 != this._array.indexOf(languageCode)); + }, + + /** + * Adds a language code to the list. Case-insensitive. Attempts to add null or the empty string will have no effect. + * + * @param languageCode A string whose value is a language code, i.e. "en-US". + */ + push: function(languageCode) + { + common.Debug.traceMethodEntered("LanguageList.push()"); + if (null == languageCode || languageCode.length <= 0) + { + return; + } + + languageCode = languageCode.toLowerCase(); + var convertedLanguageCode = webservices.LanguageCodeConverter.convert(languageCode); + var extraLogInfo = ""; + if (languageCode != convertedLanguageCode) + { + extraLogInfo = ' (in lieu of "' + languageCode + '")'; + } + + if (this.contains(convertedLanguageCode)) + { + common.Debug.traceStatus('Already added language "' + convertedLanguageCode + '"' + extraLogInfo); + } else + { + this._array.push(convertedLanguageCode); + common.Debug.traceStatus('Added language "' + convertedLanguageCode + '"' + extraLogInfo); + + var hyphenIdx = languageCode.lastIndexOf('-'); + if (hyphenIdx > 0) + { + var baseLanguageCode = languageCode.substr(0, hyphenIdx) + common.Debug.traceStatus('Recursing to add base language, "' + baseLanguageCode + '"'); + this.push(baseLanguageCode); + } + } + common.Debug.traceMethodExited("LanguageList.push()"); + }, + + /** + * Returns an Array containing the language codes in this list. The Array is a copy, so the caller may + * alter the array without affecting this LanguageList. + * + * @return Array of strings, each of which holds a language code. + */ + toArray: function() + { + return this._array.clone(); + } +}); + +// Register namespaces +webservices.registerChildNamespace("_Internal"); + +/** + * LanguagePreferenceRepository class + * + * Many factors influence which language is used for chat. This class manages those. + * + * In priority order, the factors are: + * 1. Whether the language to use for this chat has been explicitly specified. To do this, a customer may create a customization + * in which the user can click a French flag, or a link that says "Chat in English", etc. which results in a call to + * Bootloader.setLanguage(). + * 2. The language(s) specified by the user's web browser settings. Javascript can't directly "see" this, only server-side code. So + * it is included in the server configuration response so that the client side can use it. Multiple may be specified, with + * priorities included. + * 3. The language specified by the user's OS settings. + * 4. The language specified in config.js + */ +webservices._Internal._LanguagePreferenceRepository = Class.create( +{ + /** + * Constructor + */ + initialize: function() + { + this._specifiedLanguage = null; + this._browserLanguages = null; + }, + + /** + * Destructor + */ + destroy : function() + { + }, + + /** + * Returns an array of languages, in the order in which the application should attempt to use them. + * + * @return Array of language codes + */ + getLanguagePreferenceOrder : function() + { + common.Debug.traceMethodEntered("LanguagePreferenceRepository.getLanguagePreferenceOrder()"); + + var languagePreferenceOrder = new webservices.LanguageList(); + + // 1. Explicitly-specified language + if (this._getSpecifiedLanguage()) + { + common.Debug.traceStatus("Specified language is: " + this._getSpecifiedLanguage()); + languagePreferenceOrder.push(this._getSpecifiedLanguage()); + } + + // 2. The language(s) specified by the user's web browser settings. + if (this._getBrowserLanguages()) + { + var browserLanguages = this._getBrowserLanguages(); + common.Debug.traceStatus("Browser language(s) are: " + browserLanguages.join()); + for (var i=0; i 0) + { + common.Debug.traceStatus("Searching for highest-preference language of: " + languages.length); + var maxPreference = -1; + var maxPreferenceLanguage = null; + var maxPreferenceLanguageIndex = -1; + for (var i=0; i webservices.CustomizableFactoryTypes.MAX) + { + throw common.ExceptionFactory.createException('"' + type + '" is not a valid CustomizableFactoryType.'); + } + }, + + _validateSingletonType : function(type) + { + if (type < webservices.CustomizableSingletonFactoryTypes.MIN || + type > webservices.CustomizableSingletonFactoryTypes.MAX) + { + throw common.ExceptionFactory.createException('"' + type + '" is not a valid CustomizableFactoryType.'); + } + }, + + // Contains/reduces the boilerplate that was previously located in Customizations.js + _createFactories : function() + { + for (name in ININ.Web.Chat.Customizations.singletonImplementations) + { + (function(type) { + ININ.Web.Chat.Customizations[type + "Factory"] = Class.create(common.InterfaceImplementation, + { + _instance : null, + + /** + * Constructor + */ + initialize : function($super) + { + if(arguments.length != 1) + { + throw common.ExceptionFactory.createException(type + "Factory constructor called with " + arguments.length + " arguments, but expected 1."); + } + + $super(); + + this.addImplementedInterface(webservices.Interfaces.ICustomizationFactory, webservices); + }, + + get_instance: function(args) + { + if (null == this._instance) + { + this._instance = new ININ.Web.Chat.Customizations.singletonImplementations[type](args); + } + return this._instance; + } + }); + })(name); + } + + // Factory for non-singleton objects + + for (name in ININ.Web.Chat.Customizations.nonSingletonImplementations) + { + (function(type) { + ININ.Web.Chat.Customizations[type + "Factory"] = Class.create(common.InterfaceImplementation, + { + /** + * Constructor + */ + initialize : function($super) + { + if(arguments.length != 1) + { + throw common.ExceptionFactory.createException(type + "Factory constructor called with " + arguments.length + " arguments, but expected 1."); + } + + $super(); + + this.addImplementedInterface(webservices.Interfaces.ICustomizationFactory, webservices); + }, + + create_instance: function(args) + { + return new ININ.Web.Chat.Customizations.nonSingletonImplementations[type](args); + } + }); + })(name); + } + } +}); + +/** + * A class representing a duration of time. Granularity is currently 1 second. + * Provides methods whose names fall into three categories: get___(), getTotal___(), and getRounded___(). + * + * Example 1: + * 270150 seconds = 3 days, 3 hours, 2 minutes, and 30 seconds. For a TimeDuration object representing + * this duration: + * getSeconds() will return 30, whereas getTotalSeconds() will return 270150. There is no getRoundedSeconds() method. + * getMinutes() will return 2, whereas getTotalMinutes() will return 4502. + * getRoundedMinutes() will return 3, since 2:30 is closer to 3:00 than to 2:00. + * getHours() will return 3, whereas getTotalHours() will return 75. + * getRoundedHours() will return 3. + * getDays() and getRoundedDays() will both return 3. + * + * Example 2: + * For an object representing 5 days, 14 hours, 45 minutes, and 18 seconds: + * getRoundedDays() will return 6. + * getRoundedHours() will return 15. + * getRoundedMinutes() will return 45. + * + * Example 3: + * For an object representing 1 hour, 29 minutes, and 30 seconds: + * getRoundedMinutes() will return 30. + * getRoundedHours() will return 1, because this is based on the unrounded number of minutes, not the rounded number of minutes. + * + * Example 4: + * For an object representing 1 day, 23 hours, 59 minutes, and 59 seconds: + * getRoundedDays() will return 2. + * getRoundedHours() will return 0, not 24! + * getRoundedMinutes() will return 0, not 60! + * + * Note that all methods return integers! + * For 179 seconds (which is 2:59), getMinutes() will return 2. Not 2.98333, and not 3. + * There is no getTotalDays() method. Since this class does not handle longer durations (weeks, + * months, years, etc.) getTotalDays() would always return the same as getDays(), so there is no need for it. + */ +webservices.TimeDuration = Class.create( +{ + /** + * Constructor + * + * @params How many seconds this TimeDuration represents + */ + initialize: function(seconds) + { + this.set(seconds); + }, + + /** + * Destructor + */ + destroy : function() + { + }, + + /** + * Makes this TimeDuration object represent a duration of 0. + */ + reset : function() + { + this._seconds = this._minutes = this._hours = this._days = 0; + this._totalSeconds = this._totalMinutes = this._totalHours = 0; + }, + + /** + * Changes the amount of time that this object represents. + * + * @params How many seconds this TimeDuration now represents + */ + set : function(seconds) + { + this.reset(); + + this._totalSeconds = seconds; + this._seconds = this._totalSeconds % 60; + + if (this._totalSeconds > 60) + { + this._totalMinutes = Math.floor(this._totalSeconds / 60); + this._minutes = this._totalMinutes % 60; + + if (this._totalMinutes > 60) + { + this._totalHours = Math.floor(this._totalMinutes / 60); + this._hours = this._totalHours % 24; + + if (this._totalHours > 24) + { + this._days = Math.floor(this._totalHours / 24); + } + } + } + }, + + /** + * Returns how many seconds this TimeDuration object represents, excluding the minutes, hours, and days portion. + * For instance, after calling set(275), which is 4 minutes and 35 seconds, this method will return 35. + * + * @return integer, 0 through 59 + */ + getSeconds : function() + { + return this._seconds; + }, + + /** + * Returns how many seconds this TimeDuration object represents, including the minutes, hours, and days portion. + * For instance, after calling set(275), which is 4 minutes and 35 seconds, this method will return 275. + * + * @return integer + */ + getTotalSeconds : function() + { + return this._totalSeconds; + }, + + /** + * Returns how many minutes this TimeDuration object represents, excluding the seconds, hours, and days portion. + * For instance, after calling set(7475), which is 2 hours, 4 minutes and 35 seconds, this method will return 4. + * + * @return integer, 0 through 59 + */ + getMinutes : function() + { + return this._minutes; + }, + + /** + * Returns how many minutes this TimeDuration object represents, if the seconds are rounded to the nearest minute. + * For instance, after calling set(275), which is 4 minutes and 35 seconds, this method will return 5. + * Another example: after calling set(7399), which is 1 hour, 59 minutes, and 59 seconds, this method will + * return 0 since that rounds to 2 hours 0 minutes. + * + * @return integer, 0 through 59 + */ + getRoundedMinutes : function() + { + if (this.getSeconds() < 30) + { + return this.getMinutes(); + } + else + { + return 1 + this.getMinutes(); + } + }, + + /** + * Returns how many minutes this TimeDuration object represents, including the hours and days portion. + * For instance, after calling set(7475), which is 2 hours, 4 minutes and 35 seconds, which + * equals 124 minutes and 35 seconds, this method will return 124. + * + * @return integer + */ + getTotalMinutes : function() + { + return this._totalMinutes; + }, + + /** + * Returns how many hours this TimeDuration object represents, excluding the seconds, minutes, and days portion. + * For instance, after calling set(356521), which is 4 days, 3 hours, 2 minutes, and 1 second, this method will return 3. + * + * @return integer, 0 through 23 + */ + getHours : function() + { + return this._hours; + }, + + /** + * Returns how many hours this TimeDuration object represents, if the minutes are rounded to the nearest hour. + * For instance, after calling set(7399), which is 1 hour, 59 minutes, and 59 seconds, this method will + * return 2 since that rounds to 2 hours 0 minutes. Days are ignored: if this TimeDuration object represented + * 8 days, 1 hour, 59 minutes, and 59 seconds, this method would still return 2. + * + * @return integer, 0 through 59 + */ + getRoundedHours : function() + { + if (this.getMinutes() < 30) + { + return this.getHours(); + } + else + { + return 1 + this.getHours(); + } + }, + + /** + * Returns how many hours this TimeDuration object represents, including the days portion. + * For instance, after calling set(356521), which is 4 days, 3 hours, 2 minutes, and 1 second, which + * equals 99 hours, 2 minutes, and 1 second, this method will return 99. + * + * @return integer + */ + getTotalHours : function() + { + return this._totalHours; + }, + + /** + * Returns how many days this TimeDuration object represents, excluding the hours, minutes, and seconds portion. + * For instance, after calling set(356521), which is 4 days, 3 hours, 2 minutes, and 1 second, this method will + * return 4. + * + * @return integer + */ + getDays : function() + { + return this._days; + }, + + /** + * Returns how many days this TimeDuration object represents, if the hours are rounded to the nearest day. + * For instance, if this object represents 5 days and 12 hours, this method will return 6. + * + * Note that if this object represents 5 days, 11 hours, 58 minutes, and 30 seconds, this method will return 5. + * In other words, this method does NOT round the seconds (5d, 11h, 59m), then round the minutes (5d, 12h), then round + * the days. + * + * @return integer + */ + getRoundedDays : function() + { + if (this.getHours() < 12) + { + return this.getDays(); + } + else + { + return 1 + this.getDays(); + } + } +}); + +/** + * Superclass of CallbackManagerBase, ChatManagerBase, RegistrationManagerBase. + * All of those are abstract - use their derived classes instead + */ +webservices.InteractionManagerBase = Class.create(webservices.ListenableBase, +{ + /** + * Constructor + * + * @param genericResponseBuilder An object to build Response objects (i.e. objects which implement ResponseBase or its subclasses) from the IC server's replies to AJAX requests. + * @param capabilityRepository An object to keep track of which Capabilities are enabled or disabled, and provide getter methods for the various Capabilities. + * @param failoverHandler In charge of connecting to the other server if the current one goes down for some reason. + */ + initialize : function($super, genericResponseBuilder, capabilityRepository, failoverHandler) + { + common.Debug.traceMethodEntered("InteractionManagerBase.initialize()"); + + var numArgs = 4; + if(arguments.length != numArgs) + { + throw common.ExceptionFactory.createException("InteractionManagerBase constructor called with " + arguments.length + " arguments, but expected " + numArgs + "."); + } + +// common.ParameterValidation.validate(arguments, [ {}, {"required": true} ]); + + $super(); + + this._genericResponseBuilder = genericResponseBuilder; + this._capabilityRepository = capabilityRepository; + this._failoverHandler = failoverHandler; + + common.Debug.traceMethodExited("InteractionManagerBase.initialize()"); + }, + + /** + * Destructor + */ + destroy : function() + { + common.Debug.traceMethodEntered("InteractionManagerBase.destroy()"); + + this._genericResponseBuilder = null; + this._capabilityRepository = null; + this._failoverHandler = null; + + common.Debug.traceMethodExited("InteractionManagerBase.destroy()"); + }, + + // public methods + + /** + * Handle a successful response to any AJAX request: + * 1) Process the response + * 2) Alert listeners + * This method should not be called directly by clients of the API. + * + * @param response An instance of a subclass of ResponseBase, which represents the response to an AJAX request received from the IC server + */ + onAJAXSuccess: function(response) { + common.Debug.traceMethodEntered("InteractionManagerBase.onAJAXSuccess()"); + + common.Debug.traceNote("InteractionManagerBase.onAJAXSuccess calling notifyListenersOfSuccess()"); + this.notifyListenersOfSuccess(); + common.Debug.traceNote("InteractionManagerBase.onAJAXSuccess back from notifyListenersOfSuccess()"); + common.Debug.traceMethodExited("InteractionManagerBase.onAJAXSuccess()"); + }, + + /** + * Handle an unsuccessful response to any AJAX request. + * If response is supplied, that indicates that the transport succeeded, but the content + * of the response indicated a failure. + * If response is not supplied, that indicates that the transport of the request failed. + * This method should not be called directly by clients of the API. + * + * @param response An instance of a subclass of ResponseBase, which represents the response to an AJAX request received from the IC server + */ + onAJAXFailure: function(response) { + common.Debug.traceMethodEntered("InteractionManagerBase.onAJAXFailure()"); + + common.Debug.traceNote("InteractionManagerBase.onAJAXFailure calling handleFailedRequest()"); + this._handleFailedRequest(response); + + common.Debug.traceNote("InteractionManagerBase.onAJAXFailure calling notifyListenersOfFailure()"); + this.notifyListenersOfFailure(response.get_statusReason()); + common.Debug.traceNote("InteractionManagerBase.onAJAXFailure back from notifyListenersOfFailure()"); + common.Debug.traceMethodExited("InteractionManagerBase.onAJAXFailure()"); + }, + + // private methods + + _handleFailedRequest : function(response) + { + common.Debug.traceMethodEntered("InteractionManagerBase._handleFailedRequest()"); + if(this._isFailoverCondition(response)) + { + this._handleFailover(); + } + else if(this._isInvalidSession(response)) + { + this._handleInvalidSession(response); + } + common.Debug.traceMethodExited("InteractionManagerBase._handleFailedRequest()"); + }, + + _isFailoverCondition : function(response) + { + return !response.isSuccessful() && (response.get_statusReason().get_errorSource() == webservices.ErrorCodes.HTTP); + }, + + _handleFailover : function() + { + common.Debug.traceMethodEntered("InteractionManagerBase._handleFailover()"); + common.Debug.traceAlways("InteractionManagerBase._handleFailover: Creating Failover notifications..."); + + webservices.NotificationRegistry.process(webservices.NotificationFactory.createFailoverUINotification()); + webservices.NotificationRegistry.process(webservices.NotificationFactory.createFailoverNotification()); + common.Debug.traceMethodExited("InteractionManagerBase._handleFailover()"); + }, + + _isInvalidSession : function(response) + { + var unknownSessionCode = webservices.ErrorCodes.ERROR + "." + + webservices.ErrorCodes.WEBSVC + "." + + webservices.ErrorCodes.UNKNOWNENTITY + "." + + webservices.ErrorCodes.SESSION; + return !response.isSuccessful() && (response.get_statusReason().get_errorCode() == unknownSessionCode); + }, + + _handleInvalidSession : function(response) + { + // Override in subclasses if it is desired for anything to be done. + } +}); + +/** + * This class is the main brains of working with a queue, but is abstract - use derived class instead + */ +webservices.QueueManagerBase = Class.create(webservices.InteractionManagerBase, +{ + /** + * Constructor + * + * @param genericResponseBuilder An object to build Response objects (i.e. objects which implement ResponseBase or its subclasses) from the IC server's replies to AJAX requests. + * @param capabilityRepository An object to keep track of which Capabilities are enabled or disabled, and provide getter methods for the various Capabilities. + * @param failoverHandler An instance of webservices.Json.FailoverHandler + */ + initialize : function($super, genericResponseBuilder, capabilityRepository, failoverHandler) + { + common.Debug.traceMethodEntered("QueueManagerBase.initialize()"); + + var numArgs = 4; + if(arguments.length != numArgs) + { + throw common.ExceptionFactory.createException("QueueManagerBase constructor called with " + arguments.length + " arguments, but expected " + numArgs + "."); + } + + $super(genericResponseBuilder, capabilityRepository, failoverHandler); + + common.Debug.traceMethodExited("QueueManagerBase.initialize()"); + }, + + /** + * Destructor + */ + destroy : function() + { + }, + + // public methods + + /** + * Query the status of a queue. + * + * @param username The username of the person attempting to query a queue + * @param userCredentials The password of the person attempting to query a queue (may be empty) + * @param queueName The name of the queue being queried + * @param queueType The type of the queue being queried (currently only "Workgroup" is supported) + */ + queueQuery : function(username, userCredentials, queueName, queueType) + { + common.Debug.traceMethodEntered("ChatManagerBase.queueQuery()"); + + if (userCredentials == null) + { + userCredentials = ""; + } + + var anonymousOk = (userCredentials == "") && webservices.CapabilityRepository.isQueueQueryAnonymousAuthenticationCapabilityEnabled(); + var trackerOk = (userCredentials != "") && webservices.CapabilityRepository.isQueueQueryTrackerAuthenticationCapabilityEnabled(); + + if (!(anonymousOk || trackerOk)) + { + webservices.QueueNotificationRegistry.process(webservices.QueueNotificationFactory.createQueueStatusFailureNotification("error.websvc.unsupportedOperation")); + return; + } + + // create the queueQuery capability and then query the queue. The URL is the same regardless of the authentication method. + var ajax = this.createAjaxManager(this._capabilityRepository.get_queueQueryAnonymousAuthenticationCapability()); + var _self = this; + ajax.registerSuccessListener(function(response) + { + common.Debug.traceNote("ChatManagerBase.queueQuery() succeeded: " + response); + webservices.QueueNotificationRegistry.process(webservices.QueueNotificationFactory.createQueueStatusNotification(response.get_agentsAvailable(), response.get_estimatedWaitTime())); + _self.onAJAXSuccess(response); + }); + ajax.registerFailureListener(function(response) + { + common.Debug.traceError("ChatManagerBase.queueQuery() failed: " + response); + webservices.QueueNotificationRegistry.process(webservices.QueueNotificationFactory.createQueueStatusFailureNotification(response.get_statusReason())); + _self.onAJAXFailure(response); + }); + + ajax.sendRequest(this.serializeQueueQueryPostData(username, userCredentials, queueName, queueType)); + + common.Debug.traceMethodExited("ChatManagerBase.queueQuery()"); + } +}); + +/** + * QueueQueryResponse class + * + * When an AJAX request is made to the IC server to query a queue, QueueQueryResponseBuilder + * translates the IC server's JSON/XML reply into a QueueQueryResponse. + */ +webservices.QueueQueryResponse = Class.create(webservices.ResponseBase, +{ + /** + * Constructor + */ + initialize: function($super) + { + common.Debug.traceMethodEntered("QueueQueryResponse.initalize()"); + + $super(); + + this._estimatedWaitTime = null; + this._agentsAvailable = null; + + this.addImplementedInterface(webservices.Interfaces.IQueueQueryResponse, webservices); + + common.Debug.traceMethodExited("QueueQueryResponse.initalize()"); + }, + + /** + * Destructor + */ + destroy : function() + { + webservices.ResponseBase.prototype.destroy.call(this); + }, + + // public methods + + get_agentsAvailable: function() + { + return this._agentsAvailable; + }, + + set_agentsAvailable: function(agentsAvailable) + { + this._agentsAvailable = agentsAvailable; + }, + + get_estimatedWaitTime: function() + { + return this._estimatedWaitTime; + }, + + set_estimatedWaitTime: function(estimatedWaitTime) + { + this._estimatedWaitTime = estimatedWaitTime; + } +}); + + + +// Register namespaces +webservices.registerChildNamespace("Json._Internal"); + +/** + * QueueQueryResponseBuilder class + * Handles translating the JSON received as the IC server's reply to an AJAX request for querying a queue + * into an webservices.QueueQueryResponse object. + */ +webservices.Json._Internal.QueueQueryResponseBuilder = Class.create(webservices.Json.ResponseBuilderBase, +{ + /** + * Constructor + */ + initialize : function($super) + { + $super(); + }, + + /** + * Destructor + */ + destroy : function() + { + webservices.Json.ResponseBuilderBase.prototype.destroy.call(this); + }, + + /** + * Handles translating the JSON received as the IC servers reply to an AJAX request + * into an webservices.QueueQueryResponse object. + * + * @param jsonStr JSON received from the IC server. This should have already been vetted to ensure that is not empty, its status indicates success, etc. In the default implementation, that is done in GenericResponseBuilder. + * @return webservices.QueueQueryResponse + */ + buildQueueQueryResponse : function(jsonStr) + { + common.Debug.traceMethodEntered("Json.QueueQueryResponseBuilder.buildQueueQueryResponse()"); + common.Debug.traceStatus("jsonStr is: " + jsonStr); + + var response = null; + + if (jsonStr) + { + response = new webservices.QueueQueryResponse(); + + var json = null; + try + { + json = jsonStr.evalJSON(); + } + catch (e) + { + common.Debug.traceError("Caught exception calling JSON.parse or string.evalJSON:\n" + e); + common.Debug.alert("Caught exception calling JSON.parse or string.evalJSON:\n" + e); + } + + if(json.queue) + { + response.set_agentsAvailable(json.queue.agentsAvailable); + response.set_estimatedWaitTime(json.queue.estimatedWaitTime); // seconds + + if(json.queue.status) + { + if(json.queue.status.type) + { + response.set_statusType(json.queue.status.type); + } + + if(json.queue.status.reason) + { + var error = null; + try + { + error = new webservices.Error(json.queue.status.reason); + } + catch(ex) + { + common.Debug.traceError(ex.message); + common.Debug.traceWarning("Invalid status reason: " + json.chat.status.reason); + } + + if(error) + { + response.set_statusReason(error); + } + } + } + } + } + + common.Debug.traceMethodExited("Json.QueueQueryResponseBuilder.buildQueueQueryResponse()"); + + return response; + } +}); + + + +// Register namespaces +webservices.registerChildNamespace("Json"); + +/** + * QueueManager class + * Extends QueueManagerBase with JSON-specific functionality + */ +webservices.Json.QueueManager = Class.create(webservices.QueueManagerBase, +{ + /** + * Constructor + * + * @param genericResponseBuilder An instance of GenericResponseBuilder, to turn the JSON received from the IC server into a ResponseBase or subclass thereof + * @param capabilityRepository An instance of CapabilityRepository, in which the capabilities are stored. + * @param failoverHandler An instance of webservices.Json.FailoverHandler + */ + initialize: function($super, genericResponseBuilder, capabilityRepository, failoverHandler) + { + common.Debug.traceMethodEntered("Json.QueueManager.initialize()"); + + $super(genericResponseBuilder, capabilityRepository, failoverHandler); + + common.Debug.traceMethodExited("Json.QueueManager.initialize()"); + }, + + // public methods + + /** + * Gets an JSON-specific instance of webservices.AjaxManagerBase + * This method should not be called directly by clients of the API. + * + * @param capability A Capability object representing what this AjaxManager object is intended to do (i.e. poll, send a message, etc.) + * @return AjaxManager + */ + createAjaxManager : function(capability) + { + common.Debug.traceMethodEntered("Json.QueueManager.createAjaxManager()"); + var mgr = new webservices.Json.AjaxManager(this._genericResponseBuilder, capability); + common.Debug.traceMethodExited("Json.QueueManager.createAjaxManager()"); + return mgr; + }, + + /** + * Takes data necessary to query a queue, and puts it into the appropriate JSON format for sending to the IC server. + * @param username The username of the person attempting to query a queue + * @param password The password of the person attempting to query a queue + * @param queueName The name of the queue being queried + * @param queueType The type of the queue being queried (currently only "Workgroup" is supported) + */ + serializeQueueQueryPostData : function(username, password, queueName, queueType) + { + var json = { }; + + json.queueName = queueName; + json.queueType = queueType; + json.participant = { }; + json.participant.username = username; + json.participant.password = password; + + return Object.toJSON(json); + } +}); + +// Register namespaces +webservices.registerChildNamespace("QueueServicesInitialization"); + +/** + * Create objects necessary for querying the queue + * + * @param currentUriFragment The URI fragment currently in use to reverse proxy to the IC server. See Servers class. + * @param uriFragments The set of URI fragments that are used to reverse proxy to the IC server(s). See Servers class. + * @param useHttps If true, AJAX requests will be made via HTTPS. If false, they will be made via HTTP. + */ +webservices.QueueServicesInitialization.initialize = function(currentUriFragment, uriFragments, useHttps) +{ + // create singletons + webservices.QueueNotificationFactory = new webservices._Internal.QueueNotificationFactory(); + webservices.QueueNotificationRegistry = new webservices._Internal._NotificationRegistry(webservices.QueueNotificationFactory); + webservices.QueueNotificationFactory.delayedInit(); + webservices.CapabilityRepository = new webservices._Internal._CapabilityRepository(); + webservices.Json.GenericResponseBuilder = new webservices.Json._Internal.GenericResponseBuilder( + null, + null, + null, + new webservices.Json._Internal.ServerConfigurationResponseBuilder(), + null, + new webservices.Json._Internal.QueueQueryResponseBuilder()); + webservices.Json.ServerConfigurationProcessor = new webservices.Json._Internal.ServerConfigurationProcessor(webservices.CapabilityRepository, webservices.Json.FailoverHandler); + webservices.Json.ServerConfigurationManager = new webservices.Json._Internal._ServerConfigurationManager(webservices.Json.GenericResponseBuilder, webservices.CapabilityRepository, webservices.Json.ServerConfigurationProcessor); + + // Set up protocol/servers + webservices.Servers.UriFragments = uriFragments; + webservices.Servers.UseHttps = (useHttps == true); + + // use the server as the current uri fragment if it's specified in the query string, else use the one loaded from the page + var server = common.Utilities.getQueryStringValue("server"); + if(server) + { + webservices.Servers.CurrentUriFragment = server; + } + else + { + webservices.Servers.CurrentUriFragment = currentUriFragment; + } + + // Factories which can be overridden for customization + webservices.CustomizationFactoryRegistry = new webservices._Internal._CustomizationFactoryRegistry(); + webservices.CustomizationFactoryRegistry.registerSingletonFactory(webservices.CustomizableSingletonFactoryTypes.RetryCounts, new ININ.Web.Chat.Customizations.RetryCountsFactory()); +}; + +webservices.QueueServicesInitialization.uninitialize = function() +{ +}; + +/** + * Clean up objects used by the queue. + */ +webservices.QueueServicesInitialization.destroy = function() +{ +}; + + +/** + * This class provides the entry point for customers who wish to provide queue querying + * on their pages without doing heavy customization. + * This can be used by: + * var uriFragments = [ "/i3root/Server1", "/i3root/Server2"]; // Or just the first if not using switchover + * var currentUriFragment = uriFragments[0]; + * var useHttps = true; // Or false, depending on whether the page is access via HTTPS or HTTP + * var queueQueryer = new webservices.EasyQueueQueryer(currentUriFragment, uriFragments, useHttps); + * queueQueryer.getQueueInfo(queueName, userId, userCredentials); + * + * The caller should also register for QueueStatusNotification and QueueStatusFailureNotification. + */ +webservices.EasyQueueQueryer = Class.create( +{ + initialize: function(currentUriFragment, uriFragments, useHttps) + { + this._currentUriFragment = currentUriFragment; + this._uriFragments = uriFragments; + this._useHttps = useHttps; + this._numServerConfigRequestFailovers = 0; + webservices.QueueServicesInitialization.initialize(this._currentUriFragment, this._uriFragments, this._useHttps); + }, + + /** + * This method allows you to query the status of an ACD queue. + * + * @param queueName The name of the ACD queue to query, i.e. "Marketing" + * @param userId The user id (in Tracker) of the user who is doing the querying. For an anonymous user, any string may be passed, such as "Anonymous User". + * @param userCredentials The credentials (i.e. password) of the user who is doing the querying. For an anonymous user, pass "". + */ + getQueueInfo: function(queueName, userId, userCredentials) + { + // ServerConfigurationManager expects a callback function that it can just pass (success, failureReason). + // But the implementation below may need (queueName, userId, userCredentials) also, if it has + // to fail over. So, create a closure to give _serverConfigurationCallback() all 5 args that it needs without + // bothering getServerConfiguration() + webservices.Json.ServerConfigurationManager.getServerConfiguration(this._serverConfigurationCallback.bind(this, userId, userCredentials, queueName)); + }, + + _serverConfigurationCallback : function(userId, userCredentials, queueName, success, failureReason) + { + if (success) + { + var queueMgr = new webservices.Json.QueueManager( + webservices.Json.GenericResponseBuilder, + webservices.CapabilityRepository, + webservices.Json.FailoverHandler); + queueMgr.queueQuery(userId, userCredentials, queueName, "Workgroup"); + } else + { + if (this._shouldSwitchoverAndTryToGetServerConfigurationAgain()) + { + common.Debug.traceStatus("Going to switch over, and try again to obtain server configuration."); + webservices.Servers.switchCurrentServer(); + this._numServerConfigRequestFailovers++; + webservices.Json.ServerConfigurationManager.getServerConfiguration(this._serverConfigurationCallback.bind(this, userId, userCredentials, queueName)); + } else + { + webservices.QueueNotificationRegistry.process(webservices.QueueNotificationFactory.createQueueStatusFailureNotification(failureReason.get_errorCode())); + } + } + }, + + _shouldSwitchoverAndTryToGetServerConfigurationAgain : function() + { + if (!webservices.Servers.isConfiguredForSwitchover()) + { + // In this case, the retry logic was already handled + // in AjaxManagerBase._shouldRequestBeRetriedBasedOnMessageTypeAndRetryCount(). + return false; + } + + // Adding 1 because retryCounts maintains the number of times to REtry. So, for instance, + // initial try + 3 retries = 4 total tries. + var retryCounts = webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.RetryCounts); + var numTimesToTry = webservices.Servers.get_numberOfServers() * + (1 + retryCounts.get_serverConfigurationRetries()); + + return (this._numServerConfigRequestFailovers < numTimesToTry); + } +}); + +common.Type.registerNamespace("ININ.Web.Chat.Customizations"); + +ININ.Web.Chat.Customizations.singletonImplementations = +{ + "RetryCounts" : webservices._Internal._DefaultRetryCounts +}; + +// Register namespaces +webservices.registerChildNamespace("Interfaces"); + +/** + * ISequenceable interface + * Provides methods: get_sequenceNumber(), get_dateTime() + */ +webservices.Interfaces.ISequenceable = new common.Interface('webservices.Interfaces.ISequenceable', ['get_sequenceNumber', 'get_dateTime']); + +// Register namespaces +webservices.registerChildNamespace("Interfaces"); + +/** + * IEvent interface, derived from ISequenceable + * Provides method: get_participantId() + */ +webservices.Interfaces.IEvent = new common.Interface('webservices.Interfaces.IEvent', ['get_participantId'], ['webservices.Interfaces.ISequenceable'], webservices); + +// IEvent derived interfaces + +/** + * IParticipantStateChangedEvent interface, derived from IEvent + * Provides additional methods: get_participantName(), get_state() + */ +webservices.Interfaces.IParticipantStateChangedEvent = new common.Interface('webservices.Interfaces.IParticipantStateChangedEvent', ['get_participantName', 'get_state'], ['webservices.Interfaces.IEvent'], webservices); + +/** + * IParticipantSetTypingEvent interface, derived from IEvent + * Provides additional methods: get_isTyping() + */ +webservices.Interfaces.IParticipantSetTypingEvent = new common.Interface('webservices.Interfaces.IParticipantSetTypingEvent', ['get_isTyping'], ['webservices.Interfaces.IEvent'], webservices); + +/** + * IReceivedTextEvent interface, derived from IEvent + * Provides additional methods: get_messageText() + */ +webservices.Interfaces.IReceivedTextEvent = new common.Interface('webservices.Interfaces.IReceivedTextEvent', ['get_messageText'], ['webservices.Interfaces.IEvent'], webservices); + +/** + * IReceivedCommandEvent interface, derived from IEvent + * Provides additional methods: get_command() + */ +webservices.Interfaces.IReceivedCommandEvent = new common.Interface('webservices.Interfaces.IReceivedCommandEvent', ['get_command'], ['webservices.Interfaces.IEvent'], webservices); + +/** + * IReceivedUrlEvent interface, derived from IEvent + * Provides additional methods: get_messageUrl() + */ +webservices.Interfaces.IReceivedUrlEvent = new common.Interface('webservices.Interfaces.IReceivedUrlEvent', ['get_messageUrl'], ['webservices.Interfaces.IEvent'], webservices); + +/** + * IReceivedFileEvent interface, derived from IEvent + * Provides additional methods: get_messageRelativeUrl() + */ +webservices.Interfaces.IReceivedFileEvent = new common.Interface('webservices.Interfaces.IReceivedFileEvent', ['get_messageRelativeUrl'], ['webservices.Interfaces.IEvent'], webservices); + +// Register namespaces +webservices.registerChildNamespace("Interfaces"); + +// INotification derived interfaces + +/** + * IParticipantNotification interface, derived from INotification + * Provides additional methods: get_participantId(), get_dateTime(), get_isTimedOut(), set_isTimedOut() + */ +webservices.Interfaces.IParticipantNotification = new common.Interface('webservices.Interfaces.IParticipantNotification', ['get_participantId', 'get_dateTime', 'get_isTimedOut', 'set_isTimedOut'], ['webservices.Interfaces.INotification'], webservices); + +/** + * IFailoverNotification interface, derived from INotification + * Provides additional methods: none + */ +webservices.Interfaces.IFailoverNotification = new common.Interface('webservices.Interfaces.IFailoverNotification', [], ['webservices.Interfaces.INotification'], webservices); + +/** + * IFailoverUINotification interface, derived from INotification + * Provides additional methods: none + */ +webservices.Interfaces.IFailoverUINotification = new common.Interface('webservices.Interfaces.IFailoverUINotification', [], ['webservices.Interfaces.INotification'], webservices); + +/** + * IChatReconnectNotification interface, derived from INotification + * Provides additional methods: none + */ +webservices.Interfaces.IChatReconnectNotification = new common.Interface('webservices.Interfaces.IChatReconnectNotification', [], ['webservices.Interfaces.INotification'], webservices); + + +/** + * IResumedPollingNotification interface, derived from INotification + * Provides additional methods: none + * + * There is no "IStoppedPollingNotification", because IFailoverNotification servers this purpose. The sequence will be: + * IFailoverNotification indicates that connectivity to the server has been lost. + * IReconnectNotification indicates that a server configuration request has completed successfully. + * IResumedPollingNotification indicates that the in-progress chat (if any) is polling again. + * If network connectivity has been lost for long enough that the server has purged the chat, then IResumedPollingNotification will not be sent. + * + */ +webservices.Interfaces.IResumedPollingNotification = new common.Interface('webservices.Interfaces.IResumedPollingNotification', [], ['webservices.Interfaces.INotification'], webservices); + +/** + * IChatReconnectFailureNotification interface, derived from INotification + * Provides additional methods: none + */ +webservices.Interfaces.IChatReconnectFailureNotification = new common.Interface('webservices.Interfaces.IChatReconnectFailureNotification', [], ['webservices.Interfaces.INotification'], webservices); + +/** + * IRefreshPageNotification interface, derived from INotification + * Provides additional methods: get_newUriFragment() + */ +webservices.Interfaces.IRefreshPageNotification = new common.Interface('webservices.Interfaces.IRefreshPageNotification', ['get_newUriFragment'], ['webservices.Interfaces.INotification'], webservices); + +// IParticipantNotification derived interfaces + +/** + * INewParticipantNotification interface, derived from IParticipantNotification + * Provides additional methods: get_participantName(), get_interactionType() + */ +webservices.Interfaces.INewParticipantNotification = new common.Interface('webservices.Interfaces.INewParticipantNotification', ['get_participantName', 'get_interactionType'], ['webservices.Interfaces.IParticipantNotification'], webservices); + +/** + * IParticipantJoinedNotification interface, derived from IParticipantNotification + * Provides additional methods: get_participantName() + */ +webservices.Interfaces.IParticipantJoinedNotification = new common.Interface('webservices.Interfaces.IParticipantJoinedNotification', ['get_participantName'], ['webservices.Interfaces.IParticipantNotification'], webservices); + +/** + * IParticipantLeftNotification interface, derived from IParticipantNotification + * Provides additional methods: none + */ +webservices.Interfaces.IParticipantLeftNotification = new common.Interface('webservices.Interfaces.IParticipantLeftNotification', [], ['webservices.Interfaces.IParticipantNotification'], webservices); + +/** + * IParticipantNameChangedNotification interface, derived from IParticipantNotification + * Provides additional methods: get_newParticipantName() + */ +webservices.Interfaces.IParticipantNameChangedNotification = new common.Interface('webservices.Interfaces.IParticipantNameChangedNotification', ['get_newParticipantName'], ['webservices.Interfaces.IParticipantNotification'], webservices); + +/** + * IParticipantInitializingNotification interface, derived from IParticipantNotification + * Provides additional methods: get_participantName() + */ +webservices.Interfaces.IParticipantInitializingNotification = new common.Interface('webservices.Interfaces.IParticipantInitializingNotification', ['get_participantName'], ['webservices.Interfaces.IParticipantNotification'], webservices); + +/** + * IParticipantAlertingNotification interface, derived from IParticipantNotification + * Provides additional methods: get_participantName() + */ +webservices.Interfaces.IParticipantAlertingNotification = new common.Interface('webservices.Interfaces.IParticipantAlertingNotification', ['get_participantName'], ['webservices.Interfaces.IParticipantNotification'], webservices); + +/** + * IParticipantActiveNotification interface, derived from IParticipantNotification + * Provides additional methods: none + */ +webservices.Interfaces.IParticipantActiveNotification = new common.Interface('webservices.Interfaces.IParticipantActiveNotification', [], ['webservices.Interfaces.IParticipantNotification'], webservices); + +/** + * IParticipantHeldNotification interface, derived from IParticipantNotification + * Provides additional methods: none + */ +webservices.Interfaces.IParticipantHeldNotification = new common.Interface('webservices.Interfaces.IParticipantHeldNotification', [], ['webservices.Interfaces.IParticipantNotification'], webservices); + +/** + * IParticipantVoicemailNotification interface, derived from IParticipantNotification + * Provides additional methods: none + */ +webservices.Interfaces.IParticipantVoicemailNotification = new common.Interface('webservices.Interfaces.IParticipantVoicemailNotification', [], ['webservices.Interfaces.IParticipantNotification'], webservices); + +/** + * IParticipantDisconnectedNotification interface, derived from IParticipantNotification + * Provides additional methods: none + */ +webservices.Interfaces.IParticipantDisconnectedNotification = new common.Interface('webservices.Interfaces.IParticipantDisconnectedNotification', [], ['webservices.Interfaces.IParticipantNotification'], webservices); + +/** + * IParticipantStartedTypingNotification interface, derived from IParticipantNotification + * Provides additional methods: none + */ +webservices.Interfaces.IParticipantStartedTypingNotification = new common.Interface('webservices.Interfaces.IParticipantStartedTypingNotification', [], ['webservices.Interfaces.IParticipantNotification'], webservices); + +/** + * IParticipantStoppedTypingNotification interface, derived from IParticipantNotification + * Provides additional methods: none + */ +webservices.Interfaces.IParticipantStoppedTypingNotification = new common.Interface('webservices.Interfaces.IParticipantStoppedTypingNotification', [], ['webservices.Interfaces.IParticipantNotification'], webservices); + +/** + * IReceivedTextNotification interface, derived from IParticipantNotification + * Provides additional methods: get_messageText() + */ +webservices.Interfaces.IReceivedTextNotification = new common.Interface('webservices.Interfaces.IReceivedTextNotification', ['get_messageText'], ['webservices.Interfaces.IParticipantNotification'], webservices); + +/** + * IReceivedCommandNotification interface, derived from IParticipantNotification + * Provides additional methods: get_command() + */ +webservices.Interfaces.IReceivedCommandNotification = new common.Interface('webservices.Interfaces.IReceivedCommandNotification', ['get_command'], ['webservices.Interfaces.IParticipantNotification'], webservices); + +/** + * IReceivedUrlNotification interface, derived from IParticipantNotification + * Provides additional methods: get_messageUrl() + */ +webservices.Interfaces.IReceivedUrlNotification = new common.Interface('webservices.Interfaces.IReceivedUrlNotification', ['get_messageUrl'], ['webservices.Interfaces.IParticipantNotification'], webservices); + +/** + * IReceivedFileNotification interface, derived from IParticipantNotification + * Provides additional methods: get_messageRelativeUrl() + */ +webservices.Interfaces.IReceivedFileNotification = new common.Interface('webservices.Interfaces.IReceivedFileNotification', ['get_messageRelativeUrl'], ['webservices.Interfaces.IParticipantNotification'], webservices); + +/** + * ICurrentParticipantIdChangedNotification interface, derived from IParticipantNotification + * Provides additional methods: none + */ +webservices.Interfaces.ICurrentParticipantIdChangedNotification = new common.Interface('webservices.Interfaces.ICurrentParticipantIdChangedNotification', [], ['webservices.Interfaces.IParticipantNotification'], webservices); + +/** + * IPageNotification interface, derived from INotification + * Provides additional methods: none + */ +webservices.Interfaces.IPageNotification = new common.Interface('webservices.Interfaces.IPageNotification', [], ['webservices.Interfaces.INotification'], webservices); + +/** + * IPageUnloadNotification interface, derived from IPageNotification + * Provides additional methods: none + */ +webservices.Interfaces.IPageUnloadNotification = new common.Interface('webservices.Interfaces.IPageUnloadNotification', [], ['webservices.Interfaces.IPageNotification'], webservices); + +/** + * IPageBeforeUnloadNotification interface, derived from IPageNotification + * Provides additional methods: none + */ +webservices.Interfaces.IPageBeforeUnloadNotification = new common.Interface('webservices.Interfaces.IPageBeforeUnloadNotification', [], ['webservices.Interfaces.IPageNotification'], webservices); + +/** + * IChatCreationNotification interface, derived from INotification + * Provides additional methods: get_currentParticipantId(), get_dateFormat(), get_timeFormat() + */ +webservices.Interfaces.IChatCreationNotification = new common.Interface('webservices.Interfaces.IChatCreationNotification', [ 'get_currentChatId', 'get_currentParticipantId', 'get_dateFormat', 'get_timeFormat' ], ['webservices.Interfaces.INotification'], webservices); + +/** + * IChatCreationFailureNotification interface, derived from INotification + * Provides additional methods: get_error() + */ +webservices.Interfaces.IChatCreationFailureNotification = new common.Interface('webservices.Interfaces.IChatCreationFailureNotification', [ 'get_error' ], ['webservices.Interfaces.INotification'], webservices); + +/** + * IChatCompletionNotification interface, derived from INotification + * Provides additional methods: none + */ +webservices.Interfaces.IChatCompletionNotification = new common.Interface('webservices.Interfaces.IChatCompletionNotification', [], ['webservices.Interfaces.INotification'], webservices); + + +/** + * IChatCompletionFailureNotification interface, derived from INotification + * Provides additional methods: get_error() + */ +webservices.Interfaces.IChatCompletionFailureNotification = new common.Interface('webservices.Interfaces.IChatCompletionFailureNotification', [ 'get_error' ], ['webservices.Interfaces.INotification'], webservices); + +/** + * ICallbackCreationNotification interface, derived from INotification + * Provides additional methods: get_participantId(), get_callbackId(), get_userIdentityId(), get_participantName(), get_telephone(), get_subject(), get_creationDateTime() + */ +webservices.Interfaces.ICallbackCreationNotification = new common.Interface('webservices.Interfaces.ICallbackCreationNotification', [ 'get_participantId', 'get_callbackId', 'get_userIdentityId', 'get_participantName', 'get_telephone', 'get_subject', 'get_creationDateTime' ], ['webservices.Interfaces.INotification'], webservices); + +/** + * ICallbackCreationFailureNotification interface, derived from INotification + * Provides additional methods: get_error() + */ +webservices.Interfaces.ICallbackCreationFailureNotification = new common.Interface('webservices.Interfaces.ICallbackCreationFailureNotification', [ 'get_error' ], ['webservices.Interfaces.INotification'], webservices); + +/** + * IChatReconnectUINotification interface, derived from INotification + * Provides additional methods: none + */ +webservices.Interfaces.IChatReconnectUINotification = new common.Interface('webservices.Interfaces.IChatReconnectUINotification', [], ['webservices.Interfaces.INotification'], webservices); + + +/** + * ICallbackStatusNotification interface, derived from INotification + * Provides additional methods: get_participantId(), get_queueWaitTime(), get_assignedAgentName(), get_assignedAgentParticipantId(), get_interactionState(), get_estimatedCallbackTime(), get_queuePosition(), get_queueName(), get_longestWaitTime(), get_interactionsWaitingCount(), get_loggedInAgentsCount(), get_availableAgentsCount(), get_statusIndicator() + */ +webservices.Interfaces.ICallbackStatusNotification = new common.Interface('webservices.Interfaces.ICallbackStatusNotification', [ 'get_participantId', 'get_queueWaitTime', 'get_assignedAgentName', 'get_assignedAgentParticipantId', 'get_interactionState', 'get_estimatedCallbackTime', 'get_queuePosition', 'get_queueName', 'get_longestWaitTime', 'get_interactionsWaitingCount', 'get_loggedInAgentsCount', 'get_availableAgentsCount', 'get_statusIndicator' ], ['webservices.Interfaces.INotification'], webservices); + +/** + * ICallbackStatusFailureNotification interface, derived from INotification + * Provides additional methods: get_error() + */ +webservices.Interfaces.ICallbackStatusFailureNotification = new common.Interface('webservices.Interfaces.ICallbackStatusFailureNotification', [ 'get_error' ], ['webservices.Interfaces.INotification'], webservices); + +/** + * ICallbackReconnectNotification interface, derived from INotification + * Provides additional method: get_participantId() + */ +webservices.Interfaces.ICallbackReconnectNotification = new common.Interface('webservices.Interfaces.ICallbackReconnectNotification', ['get_participantId'], ['webservices.Interfaces.INotification'], webservices); + +/** + * ICallbackReconnectFailureNotification interface, derived from INotification + * Provides additional method: get_error() + */ +webservices.Interfaces.ICallbackReconnectFailureNotification = new common.Interface('webservices.Interfaces.ICallbackReconnectFailureNotification', ['get_error'], ['webservices.Interfaces.INotification'], webservices); + +/** + * ICallbackDisconnectNotification interface, derived from INotification + * Provides additional methods: get_participantId() + */ +webservices.Interfaces.ICallbackDisconnectNotification = new common.Interface('webservices.Interfaces.ICallbackDisconnectNotification', [ 'get_participantId' ], ['webservices.Interfaces.INotification'], webservices); + +/** + * ICallbackDisconnectFailureNotification interface, derived from INotification + * Provides additional methods: get_error() + */ +webservices.Interfaces.ICallbackDisconnectFailureNotification = new common.Interface('webservices.Interfaces.ICallbackDisconnectFailureNotification', [ 'get_error' ], ['webservices.Interfaces.INotification'], webservices); + +/** + * IPartyInfoNotification interface, derived from INotification + * Provides additional methods: get_name(), get_photo(), get_localParticipantId(), get_remoteParticipantId() + */ +webservices.Interfaces.IPartyInfoNotification = new common.Interface('webservices.Interfaces.IPartyInfoNotification', ['get_name', 'get_photo', 'get_localParticipantId', 'get_remoteParticipantId'], ['webservices.Interfaces.INotification'], webservices); + +/** + * IPartyInfoFailureNotification interface, derived from INotification + * Provides additional methods: get_error() + */ +webservices.Interfaces.IPartyInfoFailureNotification = new common.Interface('webservices.Interfaces.IPartyInfoFailureNotification', ['get_error'], ['webservices.Interfaces.INotification'], webservices); + + +// Register namespaces +webservices.registerChildNamespace("Interfaces"); + +// Chat Property Update Observer interfaces + +/** + * IPollWaitSuggestionUpdateObserver interface + * Provides method: processPollWaitSuggestionUpdate() + */ +webservices.Interfaces.IPollWaitSuggestionUpdateObserver = new common.Interface('webservices.Interfaces.IPollWaitSuggestionUpdateObserver', ['processPollWaitSuggestionUpdate']); + +// Register namespaces +webservices.registerChildNamespace("Interfaces"); + +/** + * IParticipant interface + * Provides methods: get_id(), get_name(), get_interactionType(), get_photo(), set_photo() + */ +webservices.Interfaces.IParticipant = new common.Interface('webservices.Interfaces.IParticipant', ['get_id', 'get_name', 'get_interactionType', 'get_photo', 'set_photo']); + +// Register namespaces +webservices.registerChildNamespace("Interfaces"); + +/** + * IParticipantRepository interface + * Provides methods: get_currentParticipantId(), addParticipant(), removeParticipant(), changeParticipantName(), markParticipantAsActive(), markParticipantAsInactive(), get_participants(), get_participant(), reset() + */ +webservices.Interfaces.IParticipantRepository = new common.Interface('webservices.Interfaces.IParticipantRepository', ['get_currentParticipantId', 'addParticipant', 'removeParticipant', 'changeParticipantName', 'markParticipantAsActive', 'markParticipantAsInactive', 'get_participants', 'get_participant', 'reset']); + +// Register namespaces +webservices.registerChildNamespace("Interfaces"); + +/** + * IMessageData interface + * Provides methods: get_date(), get_name(), get_text() + */ +webservices.Interfaces.IMessageData = new common.Interface('webservices.Interfaces.IMessageData', ['get_date', 'get_name', 'get_text']); + +// Register namespaces +webservices.registerChildNamespace("Interfaces"); + +/** + * ISequenceableProcessor interface + * Provides method: process() + */ +webservices.Interfaces.ISequenceableProcessor = new common.Interface('webservices.Interfaces.ISequenceableProcessor', ['process']); + +/** + * StringPosition class + * Represents a slice (i.e. beginning and ending indices) of a string, but does not store the string itself. + */ +webservices.StringPosition = Class.create( +{ + /** + * Constructor + * This initializes to non-useful values, and should be considered to be a private constructor. + * Please use either buildFromStartAndLength() or buildFromStartAndStop(). + */ + initialize : function() + { + this._startPosition = -1; + this._length = -1; + }, + + // public methods + + /** + * Gets the starting index of the string slice + * @return integer + */ + get_startPosition : function() + { + return this._startPosition; + }, + + /** + * Gets the ending index of the string slice + * @return integer + */ + get_stopPosition : function() + { + return this._startPosition + this._length; + }, + + /** + * Gets the length of the string slice + * @return integer + */ + get_length : function() + { + return this._length; + } +}); + +/** + * Creates a StringPosition, given the starting index and the number of characters to include in the slice. + * + * @param start Starting index of the string slice + * @param length Length of the string slice + */ +webservices.StringPosition.buildFromStartAndLength = function(start, length) +{ + var sp = new webservices.StringPosition(); + sp._startPosition = start; + sp._length = length; + return sp; +}; + +/** + * Creates a StringPosition, given the starting and ending indices. + * + * @param start Starting index of the string slice + * @param stop Ending index of the string slice + */ +webservices.StringPosition.buildFromStartAndStop = function(start, stop) +{ + var sp = new webservices.StringPosition(); + sp._startPosition = start; + sp._length = stop - start; + return sp; +}; + +// Register namespaces +webservices.registerChildNamespace("_Internal"); + +/** + * _DefaultLinkifier class + * + * Do not instantiate this class directly. Use + * webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableFactoryTypes.Linkifier) + * + * Scans text for URIs (http, https, ftp, file, mailto) and inserts the appropriate HTML to make them become links + * Note that this will NOT linkify "www.somewhere.com" - the scheme part (for instance, "http://") is + * necessary for linkification. This may be corrected in a future SU. + */ +webservices._Internal._DefaultLinkifier = Class.create( +{ + _linkOpeningTagPrefix : '', + _linkClosingTag : '', + _hideSchemeFromUser : true, + + // The following regular expression will match URLs specified with a + // scheme (e.g. http://www.inin.com or http://www.inin.com/directory/file?key=value + // or ftp://www.inin.com or mailto:support@inin.com). + // It will also match URLs with no scheme specified (e.g. www.inin.com), IF they + // are of the form "www dot something" + // ( scheme )(URL) |( www... ) + _urlMatchingRegularExpression: /(?:(?:((?:(?:https?|ftp):\/\/)|(?:mailto:))(\S+))|(www\.[^\.\s]\S+))/ig, + + /** + * Constructor + */ + initialize : function() + { + }, + + // public methods + + /** + * Scans text for URIs (http, https, ftp, file, mailto) and inserts the appropriate HTML to make them become links + * + * @param text The text to "linkify" + * @return The text with URLs converted to links + */ + linkifyText : function(text) + { + return text.replace(this.getUrlMatchingRegularExpression(), this._onMatch.bind(this)); + }, + + /** + * Creates a hyperlink, using this class' defined tags. + * + * Example: Depending on the values of this._linkOpeningTagPrefix, etc., + * createLink("http://www.inin.com", "Interactive Intelligence") may return + * Interactive Intelligence + * + * @param url The URL that the link points to + * @param text The text for the user to click on. If not specified, will default to the value of url. + * @return string containing an HTML "a" tag (opening tag, text for the user to click on, and closing tag) + */ + createLink : function(url, text) + { + return this.getLinkOpeningTagPrefix() + url + this.getLinkOpeningTagSuffix() + (text || url) + this.getLinkClosingTag(); + }, + + /** + * The Linkifier inserts HTML "a" tags into the text. + * This method returns the portion of the "a" tag that comes before the URL. + * + * @return string + */ + getLinkOpeningTagPrefix : function() + { + return this._linkOpeningTagPrefix; + }, + + /** + * The Linkifier inserts HTML "a" tags into the text. + * This method returns the portion of the "a" tag that comes after the URL. + * + * @return string + */ + getLinkOpeningTagSuffix : function() + { + return this._linkOpeningTagSuffix; + }, + + /** + * The Linkifier inserts HTML "a" tags into the text. + * This method returns the "/a" tag + * + * @return string, by default "" + */ + getLinkClosingTag : function() + { + return this._linkClosingTag; + }, + + /** + * Returns the regular expression used to identify URLs in text. + * + * @return regular expression + */ + getUrlMatchingRegularExpression : function() + { + return this._urlMatchingRegularExpression; + }, + + /** + * Returns whether to hide "http://" and "mailto:" from + * the user when displaying clickable URLs + * + * @return Boolean + */ + getHideSchemeFromUser : function() + { + return this._hideSchemeFromUser; + }, + + // Private methods + + /** + * If we've found a URL that was specified with a scheme, e.g. "https://www.inin.com", then: + * fullURL = URL including scheme, e.g. "https://www.inin.com" + * scheme = scheme, e.g. "https://" + * afterScheme = URL without scheme, e.g. "www.inin.com" + * + * But if we've found a URL that was specified without a scheme, e.g. "www.inin.com", then: + * fullURL = what was found, e.g. "www.inin.com" + * scheme = null + * afterScheme = ALSO NULL! + */ + _onMatch : function(fullURL, scheme, afterScheme) + { + if (!scheme) + { + // Found a URL that was specified without a scheme, e.g. "www.inin.com" + // Fix up the values so that they are what they'd be if scheme had been provided. + scheme = "http://"; + afterScheme = fullURL; + fullURL = scheme + afterScheme; + } + + // Only allow hiding of scheme from user if it is http or mailto. Still show it if it is https or ftp. + if (this.getHideSchemeFromUser() && ("http://" == scheme.toLowerCase() || "mailto:" == scheme.toLowerCase())) + { + return this.createLink(fullURL, afterScheme); + } + else + { + return this.createLink(fullURL); + } + } +}); + +// Register namespaces +webservices.registerChildNamespace("_Internal"); + +/** + * _DefaultSendOnEnter class + * + * Do not instantiate this class directly. Use + * webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableFactoryTypes.SendOnEnter) + * + * Implements the rule that the "Send on Enter" checkbox will default to OFF for users of Arabic, Hebrew, Japanese, Korean, Russian, + * Serbian, Turkish, and Chinese, and will default to ON for all other supported languages. + * + * Note that customers who choose to translate our resource file into another language on their own, should consider whether to add + * those language code(s) to this class as well. + */ +webservices._Internal._DefaultSendOnEnter = Class.create( +{ + /** + * Constructor + */ + initialize : function() + { + }, + + // public methods + + /** + * For a given language, gets whether pressing the Enter key should send the message or not. + * + * @param language The language in which the user is typing + * @return True if the default behaviour for that language should be for the message to be sent, + * or false if the default behaviour (due to IME, for instance) should not be for the + * message to be sent. + */ + getSendOnEnterByDefault : function(language) + { + if (!language) + { + return true; + } + return !webservices.Utilities.doesArrayHaveElement(this._languagesThatUseIME, language.toLowerCase()); + }, + + // private members + + _languagesThatUseIME : [ 'ja', 'zh-cn', 'zh-tw' ] +}); + +// Register namespaces +webservices.registerChildNamespace("InteractionState"); + +/** + * State to represent interactions that are initializing + */ +webservices.InteractionState.INITIALIZING = "initializing"; + +/** + * State to represent interactions that are alerting + */ +webservices.InteractionState.ALERTING = "alerting"; + +/** + * State to represent interactions that are active + */ +webservices.InteractionState.ACTIVE = "active"; + +/** + * State to represent interactions that are held + */ +webservices.InteractionState.HELD = "held"; + +/** + * State to represent interactions that are in the voicemail state + */ +webservices.InteractionState.VOICEMAIL = "voicemail"; + +/** + * State to represent interactions that are disconnected + */ +webservices.InteractionState.DISCONNECTED = "disconnected"; + +// Register namespaces +webservices.registerChildNamespace("_Internal"); + +/** + * InteractionTypes class + * Contains constants that represent the types of interactions that may be handled by this web application, + * and a method to verify whether something is one of those. + */ +webservices._Internal._InteractionTypes = Class.create({ + /** + * Interaction type representing a text chat + */ + CHAT : 1, + + /** + * Interaction type representing a callback request + */ + CALLBACK : 2, + + /** + * Validates whether something is a recognized interaction type. + * + * @param type Something that may or may not be one of the interaction types enumerated above + * @return True if type is one of the known interaction types, false otherwise. + */ + validate : function(type) + { + if (this.CHAT == type || + this.CALLBACK == type) + { + return true; + } + else + { + return false; + } + } +}); + + +webservices.InteractionTypes = new webservices._Internal._InteractionTypes(); + +// Register namespaces +webservices.registerChildNamespace("_Internal"); + +/** + * SequenceManagerBase class + * + * Abstract class - use a subclass. + * + * If network congestion or other issues occur, AJAX responses could be received from the IC server in the wrong order. + * This class handles putting them back into the correct order. It does this by observing the sequence number of all + * received events. If a sequence number is larger than expected, indicating that one or more events are missing, it + * queues the events that were received too early, until the preceding event(s) are received or time out. + */ +webservices._Internal._SequenceManagerBase = Class.create(common.InterfaceImplementation, +{ + /** + * Constructor + * + * @param sequenceObjectProcessor Once the events are in the proper order, they are handed off to this object for processing. + * @param timeOutMilliseconds How long before a message is considered to be lost. Begins at the time when a message with a higher sequence number is received. + */ + initialize:function($super, sequenceObjectProcessor, timeOutMilliseconds) + { + var numArgs = 3; + if(arguments.length != numArgs) + { + throw common.ExceptionFactory.createException("_SequenceManagerBase constructor called with " + arguments.length + " arguments, but expected " + numArgs + "."); + } + + $super(); + + this._sequenceObjectProcessor = sequenceObjectProcessor; + this._timeOutMilliseconds = timeOutMilliseconds; + this._nextExpectedSequenceNumber = 0; + this._queue = new common.Map(); + }, + + /** + * Destructor + */ + destroy : function() + { + this._sequenceObjectProcessor = null; + + this._queue.destroy(); + delete this._queue; + this._queue = null; + + common.InterfaceImplementation.prototype.destroy.call(this); + }, + + // public methods + + /** + * Called when an object is received. If it was received in the correct order, it will be immediately processed. + * If not, it will be queued until all objects that were supposed to be received before it have either been + * received or timed out. + * + * @param seqObject The object to process or queue + */ + addSequenceableObject : function(seqObject) + { + common.Debug.traceMethodEntered("SequenceManagerBase.addSequenceableObject"); + common.Interface.ensureImplements(seqObject, webservices.Interfaces.ISequenceable); + + var success = false; + + common.Debug.traceStatus("Got sequenceable object #" + seqObject.get_sequenceNumber()); + common.Debug.traceStatus("Next expected sequence #" + this._nextExpectedSequenceNumber); + if(this._nextExpectedSequenceNumber == seqObject.get_sequenceNumber()) + { + // TODO: does this need to be locked? + // 64-bit floating point value and as such the largest integral value it can represent precisely is 2^53, + // BUT some operators fail for numbers > ((2^31)-1). That's still a couple billion, though. + ++this._nextExpectedSequenceNumber; + common.Debug.traceStatus("Incremented next expected sequence number to: " + this._nextExpectedSequenceNumber); + + this._processSequenceableObject(seqObject, false); // Can result in a call to reset() + + this._processUnprocessedObjectsInQueue(); + success = true; + } + else if(seqObject.get_sequenceNumber() < this._nextExpectedSequenceNumber) + { + common.Debug.traceError("Sequence number '" + seqObject.get_sequenceNumber() + "' has already been processed. Expected: " + this._nextExpectedSequenceNumber); + common.Debug.breakpoint(); + } + else + { + if(this._queue.containsKey(seqObject.get_sequenceNumber())) + { + common.Debug.traceError("Sequence number '" + seqObject.get_sequenceNumber() + "' is already in the queue."); + common.Debug.breakpoint(); + } + else + { + common.Debug.traceWarning("Queueing object #" + seqObject.get_sequenceNumber()); + common.Debug.breakpoint(); + + this._queue.put(seqObject.get_sequenceNumber(), seqObject); + success = true; + } + } + + this._processTimedOutObjectsInQueue(); + + common.Debug.traceMethodExited("SequenceManagerBase.addSequenceableObject"); + + return success; + }, + + /** + * Resets the object to its default state + */ + reset : function() + { + common.Debug.traceMethodEntered("SequenceManagerBase.reset"); + common.Debug.traceNote("SequenceManagerBase::reset"); + + this._nextExpectedSequenceNumber = 0; + this._queue.removeAll(); + common.Debug.traceMethodExited("SequenceManagerBase.reset"); + }, + + /** + * Returns the number of queued objects. An object is queued if it is received before another object which it + * should have been received after. For example, if objects with sequence numbers 1, 2, 3, 5, 6, and 8 are received, + * then 3 of them (5, 6, 8) are queued. Then, if the object with sequence number 4 is received, 4, 5, and 6 will be + * processed and only 8 will remain in the queue. + * + * @return The number of objects in the queue + */ + get_queuedCount : function() + { + return this._queue.size(); + }, + + /** + * Returns a boolean indicating whether or not the queue is empty. + * + * @return True if the queue is empty, false otherwise. + */ + isEmpty : function() + { + return (this.get_queuedCount() === 0); + }, + + // private methods + + _processSequenceableObject : function(seqObject, timedOut) + { + common.Debug.traceMethodEntered("SequenceManagerBase._processSequenceableObject"); + this._sequenceObjectProcessor.process(seqObject, timedOut); + common.Debug.traceMethodExited("SequenceManagerBase._processSequenceableObject"); + }, + + _processUnprocessedObjectsInQueue : function() + { + common.Debug.traceMethodEntered("SequenceManagerBase._processUnprocessedObjectsInQueue"); + + // TODO: does this need to be locked? + while(!this._queue.isEmpty() && this._queue.containsKey(this._nextExpectedSequenceNumber)) + { + var seqObject = this._queue.get(this._nextExpectedSequenceNumber); + this._processSequenceableObject(seqObject, false); + this._queue.remove(this._nextExpectedSequenceNumber); + + // TODO: is integer overflow possible? + ++this._nextExpectedSequenceNumber; + common.Debug.traceStatus("Incremented next expected sequence number to: " + this._nextExpectedSequenceNumber); + } + + common.Debug.traceMethodExited("SequenceManagerBase._processUnprocessedObjectsInQueue"); + }, + + _processTimedOutObjectsInQueue : function() + { + common.Debug.traceMethodEntered("SequenceManagerBase._processTimedOutObjectsInQueue"); + + // TODO: does this need to be locked? + + var timedOutSeqObjects = this._getTimedOutObjectsInQueue(); + if(timedOutSeqObjects && timedOutSeqObjects.length > 0) + { + this._orderList(timedOutSeqObjects); + + for(var i = 0; i < timedOutSeqObjects.length; ++i) + { + this._processSequenceableObject(timedOutSeqObjects[i], true); + } + + // TODO: lock? + this._nextExpectedSequenceNumber = timedOutSeqObjects[timedOutSeqObjects.length - 1].get_sequenceNumber() + 1; + common.Debug.traceStatus("Set next expected sequence number to: " + this._nextExpectedSequenceNumber); + } + + common.Debug.traceMethodExited("SequenceManagerBase._processTimedOutObjectsInQueue"); + }, + + _getTimedOutObjectsInQueue : function() + { + common.Debug.traceMethodEntered("SequenceManagerBase._getTimedOutObjectsInQueue"); + var timedOutSeqObjects = []; + + // TODO: does this need to be locked? + var seqObject = this._queue.firstObject(); + while(seqObject) + { + var nextSeqObject = this._queue.nextObject(seqObject.get_sequenceNumber()); + + if(this._hasSequenceableObjectTimedOut(seqObject)) + { + timedOutSeqObjects.push(seqObject); + this._queue.remove(seqObject.get_sequenceNumber()); + } + + seqObject = nextSeqObject; + } + + common.Debug.traceMethodExited("SequenceManagerBase._getTimedOutObjectsInQueue"); + return timedOutSeqObjects; + }, + + _orderList : function(list) + { + list.sort(this._orderSeqObjects) + }, + + _orderSeqObjects : function(a, b) + { + if(a.get_sequenceNumber() < b.get_sequenceNumber()) + { + return -1; + } + else if(a.get_sequenceNumber() > b.get_sequenceNumber()) + { + return 1; + } + + return 0; + }, + + _hasSequenceableObjectTimedOut : function(seqObject) + { + return this._comesAfter(this._createTimeOutDate(), seqObject.get_dateTime()); + }, + + _comesAfter : function(dateA, dateB) + { + return (dateA > dateB); + }, + + _createTimeOutDate : function() + { + var currentDate = new Date(); + return new Date(currentDate.getTime() - this._timeOutMilliseconds); + } +}); + +/** + * Event class + * + * Events, as opposed to Notifications, directly mirror the JSON/XML format of the responses received from the IC server. + */ +webservices.Event = Class.create(common.InterfaceImplementation, +{ + /** + * Constructor + * + * @param participantId ID of the person who took whatever action the event is telling us about + * @param sequenceNumber Sequence number of this event. The first event in the chat is sequence number 0, and they increment thereafter. + * @param dateTime Timestamp for the event. Javascript Date object. + */ + initialize:function($super, participantId, sequenceNumber, dateTime) + { + var numArgs = 4; + if(arguments.length != numArgs) + { + throw common.ExceptionFactory.createException("Event constructor called with " + arguments.length + " arguments, but expected " + numArgs + "."); + } + + $super(); + + this.addImplementedInterface(webservices.Interfaces.IEvent, webservices); + + this._participantId = participantId; + this._sequenceNumber = sequenceNumber; + this._dateTime = dateTime; + }, + + /** + * Destructor + */ + destroy : function() + { + this._dateTime = null; + + common.InterfaceImplementation.prototype.destroy.call(this); + }, + + // public methods + + /** + * Returns the ID of the participant who took whatever action the event is telling us about. + * + * @return ID of the participant + */ + get_participantId : function() + { + return this._participantId; + }, + + /** + * Returns the sequence number of this event. + * + * @return integer + */ + get_sequenceNumber : function() + { + return this._sequenceNumber; + }, + + /** + * Returns the timestamp of this event + * + * @return Javascript Date object + */ + get_dateTime : function() + { + return this._dateTime; + }, + + /** + * Returns a string representation of this event, useful for debugging. + * + * @return string + */ + toString : function() + { + return "<Event: " + this._participantId + ", " + this._sequenceNumber + ", " + this._dateTime + ">"; + } +}); + +/** + * ParticipantStateChangedEvent class + * + * Events indicating that a participant has changed state. + */ +webservices.ParticipantStateChangedEvent = Class.create(webservices.Event, +{ + /** + * Constructor + * + * @param participantId ID of the person who changed state. + * @param sequenceNumber Sequence number of this event. The first event in the chat is sequence number 0, and they increment thereafter. + * @param dateTime Timestamp for the event. Javascript Date object. + * @param state The state to which the participant changed + * @param participantName The name of the participant + */ + initialize:function($super, participantId, sequenceNumber, dateTime, state, participantName) + { + var numArgs = 6; + if(arguments.length != numArgs) + { + throw common.ExceptionFactory.createException("ParticipantStateChangedEvent constructor called with " + arguments.length + " arguments, but expected " + numArgs + "."); + } + + $super(participantId, sequenceNumber, dateTime); + + this.addImplementedInterface(webservices.Interfaces.IParticipantStateChangedEvent, webservices); + + this._validateState(state); + this._state = state; + this._participantName = participantName; + }, + + // public methods + + /** + * Returns the state to which the participant changed + * + * @return State string as specified in JSON/XML protocol + */ + get_state : function() + { + return this._state; + }, + + /** + * Returns the name of the participant + * + * @return The name of the participant + */ + get_participantName : function() + { + return this._participantName; + }, + + /** + * Returns a string representation of this event, useful for debugging. + * + * @return string + */ + toString : function() + { + return "<ParticipantStateChangedEvent: " + this._participantId + ", " + this._participantName + ", " + this._sequenceNumber + ", " + this._dateTime + ", " + this._state + ">"; + }, + + // private methods + + _validateState : function(state) + { + if(!state) + { + throw common.ExceptionFactory.createException("state is null/undefined"); + } + + if((state != webservices.InteractionState.INITIALIZING) && + (state != webservices.InteractionState.ALERTING) && + (state != webservices.InteractionState.ACTIVE) && + (state != webservices.InteractionState.HELD) && + (state != webservices.InteractionState.VOICEMAIL) && + (state != webservices.InteractionState.DISCONNECTED)) + { + throw common.ExceptionFactory.createException("Not a valid state: " + state); + } + } +}); + +/** + * ParticipantSetTypingEvent class + * + * Events indicating that a participant has started or stopped typing. + */ +webservices.ParticipantSetTypingEvent = Class.create(webservices.Event, +{ + /** + * Constructor + * + * @param participantId ID of the person who started or stopped typing. + * @param sequenceNumber Sequence number of this event. The first event in the chat is sequence number 0, and they increment thereafter. + * @param dateTime Timestamp for the event. Javascript Date object. + * @param isTyping True if the event is to indicate that the participant has started typing, false if the event is to indicate that the participant has stopped typing. + */ + initialize:function($super, participantId, sequenceNumber, dateTime, isTyping) + { + var numArgs = 5; + if(arguments.length != numArgs) + { + throw common.ExceptionFactory.createException("ParticipantSetTypingEvent constructor called with " + arguments.length + " arguments, but expected " + numArgs + "."); + } + + $super(participantId, sequenceNumber, dateTime); + + this.addImplementedInterface(webservices.Interfaces.IParticipantSetTypingEvent, webservices); + + this._isTyping = isTyping; + }, + + // public methods + + /** + * Returns whether the participant started or stopped typing. + * + * @return isTyping True if the event is to indicate that the participant has started typing, false if the event is to indicate that the participant has stopped typing. + */ + get_isTyping : function() + { + return this._isTyping; + }, + + /** + * Returns a string representation of this event, useful for debugging. + * + * @return string + */ + toString : function() + { + return "<ParticipantSetTypingEvent: " + this._participantId + ", " + this._sequenceNumber + ", " + this._dateTime + ", " + this._isTyping + ">"; + } +}); + +/** + * ReceivedTextEvent class + * + * Events indicating that someone has typed something in the chat. + */ +webservices.ReceivedTextEvent = Class.create(webservices.Event, +{ + /** + * Constructor + * + * @param participantId ID of the person who sent the message. + * @param sequenceNumber Sequence number of this event. The first event in the chat is sequence number 0, and they increment thereafter. + * @param dateTime Timestamp for the event. Javascript Date object. + * @param messageText What the participant typed + * @param conversationSequenceNumber Separate sequence number tracking only received text, URLs, and files. If 5 ParticipantStateChangedEvents are sent, followed by 8 ReceivedTextEvents and 2 ReceivedUrlEvents, the last event received will have sequenceNumber 14 and conversationSequenceNumber 9. + * @param contentType The mime type of messageText. Likely either "text/plain" or "text/html". + */ + initialize:function($super, participantId, sequenceNumber, dateTime, messageText, conversationSequenceNumber, contentType) + { + var numArgs = 7; + if(arguments.length != numArgs) + { + throw common.ExceptionFactory.createException("ReceivedTextEvent constructor called with " + arguments.length + " arguments, but expected " + numArgs + "."); + } + + $super(participantId, sequenceNumber, dateTime); + + this.addImplementedInterface(webservices.Interfaces.IReceivedTextEvent, webservices); + + this._messageText = messageText; + this._contentType = contentType; + this._conversationSequenceNumber = conversationSequenceNumber; + }, + + // public methods + + /** + * Gets the message that the participant typed. + * + * @return string + */ + get_messageText : function() + { + return this._messageText; + }, + + /** + * Gets the mime type of the message that the participant typed. + * + * @return string + */ + get_contentType : function() + { + return this._contentType; + }, + + /** + * Gets the conversation sequence number. + * This is a separate sequence number tracking only received text, URLs, and files. + * If 5 ParticipantStateChangedEvents are sent, followed by 8 ReceivedTextEvents and 2 ReceivedUrlEvents, the + * last event received will have sequenceNumber 14 and conversationSequenceNumber 9. + * + * @return integer + */ + get_conversationSequenceNumber : function() + { + return this._conversationSequenceNumber; + }, + + /** + * Returns a string representation of this event, useful for debugging. + * + * @return string + */ + toString : function() + { + return "<ReceivedTextEvent: " + this._participantId + ", " + this._sequenceNumber + ", " + this._dateTime + ", " + this._messageText + ", " + this._contentType + ">"; + } +}); + +/** + * ReceivedCommandEvent class + * + * Events indicating that someone has typed a special string that represents a command to make the application do something, for instance + * change the tracing level. + */ +webservices.ReceivedCommandEvent = Class.create(webservices.Event, +{ + /** + * Constructor + * + * @param participantId ID of the person who sent the command. + * @param sequenceNumber Sequence number of this event. The first event in the chat is sequence number 0, and they increment thereafter. + * @param dateTime Timestamp for the event. Javascript Date object. + * @param command The command the participant typed + * @param conversationSequenceNumber Separate sequence number tracking only received text, URLs, and files. If 5 ParticipantStateChangedEvents are sent, followed by 8 ReceivedTextEvents and 2 ReceivedUrlEvents, the last event received will have sequenceNumber 14 and conversationSequenceNumber 9. + */ + initialize:function($super, participantId, sequenceNumber, dateTime, command, conversationSequenceNumber) + { + var numArgs = 6; + if(arguments.length != numArgs) + { + throw common.ExceptionFactory.createException("ReceivedCommandEvent constructor called with " + arguments.length + " arguments, but expected " + numArgs + "."); + } + + $super(participantId, sequenceNumber, dateTime); + + this.addImplementedInterface(webservices.Interfaces.IReceivedCommandEvent, webservices); + + this._command = command; + this._conversationSequenceNumber = conversationSequenceNumber; + }, + + // public methods + + /** + * Gets the command that the participant typed. + * + * @return string + */ + get_command : function() + { + return this._command; + }, + + /** + * Gets the conversation sequence number. + * This is a separate sequence number tracking only received text, URLs, and files. + * If 5 ParticipantStateChangedEvents are sent, followed by 8 ReceivedTextEvents and 2 ReceivedUrlEvents, the + * last event received will have sequenceNumber 14 and conversationSequenceNumber 9. + * + * @return integer + */ + get_conversationSequenceNumber : function() + { + return this._conversationSequenceNumber; + }, + + /** + * Returns a string representation of this event, useful for debugging. + * + * @return string + */ + toString : function() + { + return "<ReceivedCommandEvent: " + this._participantId + ", " + this._sequenceNumber + ", " + this._dateTime + ", " + this._command + ">"; + } +}); + +/** + * ReceivedUrlEvent class + * + * Events indicating that someone in the chat has sent a URL. + */ +webservices.ReceivedUrlEvent = Class.create(webservices.Event, +{ + /** + * Constructor + * + * @param participantId ID of the person who sent the message. + * @param sequenceNumber Sequence number of this event. The first event in the chat is sequence number 0, and they increment thereafter. + * @param dateTime Timestamp for the event. Javascript Date object. + * @param messageUrl The URL that the participant sent. + * @param conversationSequenceNumber Separate sequence number tracking only received text, URLs, and files. If 5 ParticipantStateChangedEvents are sent, followed by 8 ReceivedTextEvents and 2 ReceivedUrlEvents, the last event received will have sequenceNumber 14 and conversationSequenceNumber 9. + */ + initialize:function($super, participantId, sequenceNumber, dateTime, messageUrl, conversationSequenceNumber) + { + var numArgs = 6; + if(arguments.length != numArgs) + { + throw common.ExceptionFactory.createException("ReceivedUrlEvent constructor called with " + arguments.length + " arguments, but expected " + numArgs + "."); + } + + $super(participantId, sequenceNumber, dateTime); + + this.addImplementedInterface(webservices.Interfaces.IReceivedUrlEvent, webservices); + + this._messageUrl = messageUrl; + this._conversationSequenceNumber = conversationSequenceNumber; + }, + + // public methods + + /** + * Gets the URL that the participant sent. + * + * @return string + */ + get_messageUrl : function() + { + return this._messageUrl; + }, + + /** + * Gets the conversation sequence number. + * This is a separate sequence number tracking only received text, URLs, and files. + * If 5 ParticipantStateChangedEvents are sent, followed by 8 ReceivedTextEvents and 2 ReceivedUrlEvents, the + * last event received will have sequenceNumber 14 and conversationSequenceNumber 9. + * + * @return integer + */ + get_conversationSequenceNumber : function() + { + return this._conversationSequenceNumber; + }, + + /** + * Returns a string representation of this event, useful for debugging. + * + * @return string + */ + toString : function() + { + return "<ReceivedUrlEvent: " + this._participantId + ", " + this._sequenceNumber + ", " + this._dateTime + ", " + this._messageUrl + ">"; + } +}); + +/** + * ReceivedFileEvent class + * + * Events indicating that someone in the chat has sent a file. + */ +webservices.ReceivedFileEvent = Class.create(webservices.Event, +{ + /** + * Constructor + * + * @param participantId ID of the person who sent the message. + * @param sequenceNumber Sequence number of this event. The first event in the chat is sequence number 0, and they increment thereafter. + * @param dateTime Timestamp for the event. Javascript Date object. + * @param messageRelativeUrl The URL that the web user can use to retrieve the file. + * @param conversationSequenceNumber Separate sequence number tracking only received text, URLs, and files. If 5 ParticipantStateChangedEvents are sent, followed by 8 ReceivedTextEvents and 2 ReceivedUrlEvents, the last event received will have sequenceNumber 14 and conversationSequenceNumber 9. + */ + initialize:function($super, participantId, sequenceNumber, dateTime, messageRelativeUrl, conversationSequenceNumber) + { + var numArgs = 6; + if(arguments.length != numArgs) + { + throw common.ExceptionFactory.createException("ReceivedFileEvent constructor called with " + arguments.length + " arguments, but expected " + numArgs + "."); + } + + $super(participantId, sequenceNumber, dateTime); + + this.addImplementedInterface(webservices.Interfaces.IReceivedFileEvent, webservices); + + this._messageRelativeUrl = messageRelativeUrl; + this._conversationSequenceNumber = conversationSequenceNumber; + }, + + // public methods + + /** + * Gets the URL that the web user can use to retrieve the file that the participant sent. + * + * @return string + */ + get_messageRelativeUrl : function() + { + return this._messageRelativeUrl; + }, + + /** + * Gets the conversation sequence number. + * This is a separate sequence number tracking only received text, URLs, and files. + * If 5 ParticipantStateChangedEvents are sent, followed by 8 ReceivedTextEvents and 2 ReceivedUrlEvents, the + * last event received will have sequenceNumber 14 and conversationSequenceNumber 9. + * + * @return integer + */ + get_conversationSequenceNumber : function() + { + return this._conversationSequenceNumber; + }, + + /** + * Returns a string representation of this event, useful for debugging. + * + * @return string + */ + toString : function() + { + return "<ReceivedFileEvent: " + this._participantId + ", " + this._sequenceNumber + ", " + this._dateTime + ", " + this._messageRelativeUrl + ">"; + } +}); + + + +// Register namespaces +webservices.registerChildNamespace("Json.EventTypes"); + +// private constants for the event names +webservices.Json.EventTypes.PARTICIPANT_STATE_CHANGED = "participantStateChanged"; +webservices.Json.EventTypes.TYPINGINDICATOR = "typingIndicator"; +webservices.Json.EventTypes.TEXT = "text"; +webservices.Json.EventTypes.URL = "url"; +webservices.Json.EventTypes.FILE = "file"; + +webservices.registerChildNamespace("Json"); + +/** + * EventFactory class + * When an AJAX request is made to the IC server, the IC server sends a reply in JSON (or XML) format. + * This class handles translation of the "event" part of a JSON reply into an webservices.*Event object. + */ +webservices.Json.EventFactory = Class.create(common.InterfaceImplementation, +{ + // public methods + + /** + * Translates part of a JSON reply from the IC server into an instance of one of the webservices.*Event classes + * @param jsonEvent The "event" portion of a JSON reply from the IC server + * @return An instance of one of the webservices.*Event classes + */ + createEvent : function(jsonEvent) + { + if(!jsonEvent) + { + throw common.ExceptionFactory.createException("JSON event is null, undefined or empty"); + } + + common.ParameterValidation.validate([jsonEvent.type, jsonEvent.participantID, jsonEvent.sequenceNumber], [ {"type": String, "allowEmpty": false, "required": true}, {"type": String, "allowEmpty": false, "required": true}, {"type": Number, "required": true}]); + + if(jsonEvent.type == webservices.Json.EventTypes.PARTICIPANT_STATE_CHANGED) + { + common.ParameterValidation.validate([jsonEvent.participantName, jsonEvent.state], [ {"type": String, "allowEmpty": true, "required": false}, {"type": String, "allowEmpty": false, "required": true} ]); + + return new webservices.ParticipantStateChangedEvent(jsonEvent.participantID, jsonEvent.sequenceNumber, new Date(), jsonEvent.state, jsonEvent.participantName); + } + if(jsonEvent.type == webservices.Json.EventTypes.TYPINGINDICATOR) + { + common.ParameterValidation.validate([jsonEvent.value], [ {"type": Boolean, "required": true} ]); + + return new webservices.ParticipantSetTypingEvent(jsonEvent.participantID, jsonEvent.sequenceNumber, new Date(), jsonEvent.value); + } + if(jsonEvent.type == webservices.Json.EventTypes.TEXT) + { + common.ParameterValidation.validate([jsonEvent.value, jsonEvent.conversationSequenceNumber], [ {"type": String, "allowEmpty": false, "required": true}, {"type": Number, "required": true} ]); + + if (webservices.CommandRepository.isCommand(jsonEvent.value)) + { + return new webservices.ReceivedCommandEvent(jsonEvent.participantID, jsonEvent.sequenceNumber, new Date(), jsonEvent.value, jsonEvent.conversationSequenceNumber); + } + else + { + // Necessary kludge for an SU3 ES. Will be resolved in a better way in the normal development timeline. + if (jsonEvent.participantID == webservices.ParticipantRepository.SYSTEM_SENDER_ID && jsonEvent.displayName) + { + webservices.ParticipantRepository.changeParticipantName(jsonEvent.participantID, jsonEvent.displayName); + } + + return new webservices.ReceivedTextEvent(jsonEvent.participantID, jsonEvent.sequenceNumber, new Date(), jsonEvent.value, jsonEvent.conversationSequenceNumber, jsonEvent.contentType); + } + } + if(jsonEvent.type == webservices.Json.EventTypes.URL) + { + common.ParameterValidation.validate([jsonEvent.value, jsonEvent.conversationSequenceNumber], [ {"type": String, "allowEmpty": false, "required": true}, {"type": Number, "required": true} ]); + + return new webservices.ReceivedUrlEvent(jsonEvent.participantID, jsonEvent.sequenceNumber, new Date(), jsonEvent.value, jsonEvent.conversationSequenceNumber); + } + if(jsonEvent.type == webservices.Json.EventTypes.FILE) + { + common.ParameterValidation.validate([jsonEvent.value, jsonEvent.conversationSequenceNumber], [ {"type": String, "allowEmpty": false, "required": true}, {"type": Number, "required": true} ]); + + return new webservices.ReceivedFileEvent(jsonEvent.participantID, jsonEvent.sequenceNumber, new Date(), jsonEvent.value, jsonEvent.conversationSequenceNumber); + } + + throw common.ExceptionFactory.createException("Unknown JSON event object"); + } +}); + +// Register namespaces +webservices.registerChildNamespace("_Internal"); + +/** + * EventProcessor class + * Consumes events (webservices.Interfaces.I*Event), uses NotificationFactory to create the appropriate + * notifications (webservices.*Notification) for those events, and then uses notification processor + * (webservices.Interfaces.INotificationProcessor) to dispatch those notifications to the + * appropriate observers (webservices.Interfaces.I*Observer). + */ +webservices._Internal._EventProcessor = Class.create(common.InterfaceImplementation, +{ + /** + * Constructor + * + * @param notificationProcessor Something that implements INotificationProcessor (such as NotificationRegistry) + * @param participantRepository Keeps track of which participants are in the chat, their names, etc. + */ + initialize : function($super, notificationProcessor, participantRepository) + { + // validate arguments + var numArgs = 3; + if(arguments.length != numArgs) + { + throw common.ExceptionFactory.createException("EventProcessor constructor called with " + arguments.length + " arguments, but expected " + numArgs + "."); + } + + // validate argument interfaces + common.Interface.ensureImplements(notificationProcessor, webservices.Interfaces.INotificationProcessor); + common.Interface.ensureImplements(participantRepository, webservices.Interfaces.IParticipantRepository); + + $super(); + + this.addImplementedInterface(webservices.Interfaces.ISequenceableProcessor, webservices); + + // initialize private members + this._isStarted = false; + this._notificationProcessor = notificationProcessor; + this._participantRepository = participantRepository; + }, + + /** + * Destructor + */ + destroy : function() + { + common.InterfaceImplementation.prototype.destroy.call(this); + }, + + // public methods + + /** + * Starts the EventProcessor + */ + start : function() + { + common.Debug.traceMethodEntered("EventProcessor.start()"); + this._isStarted = true; + common.Debug.traceMethodExited("EventProcessor.start()"); + }, + + /** + * Stops the EventProcessor + */ + stop : function() + { + common.Debug.traceMethodEntered("EventProcessor.stop()"); + this._isStarted = false; + common.Debug.traceMethodExited("EventProcessor.stop()"); + }, + + /** + * Processes an event, by sending the appropriate notification(s) to the interested observer(s). + * + * @param evt An implementation of webservices.Interfaces.IEvent. + * @param isTimedOut Whether the event was received late. If so, perhaps the observer will do something special to indicate this. + */ + process : function(evt, timedOut) + { + common.Debug.traceMethodEntered("EventProcessor.process()"); + try + { + if(!this._isStarted) + { + common.Debug.traceWarning("EventProcessor hasn't been started. Ignoring event"); + } + else + { + common.Interface.ensureImplements(evt, webservices.Interfaces.ISequenceable); + common.Interface.ensureImplements(evt, webservices.Interfaces.IEvent); + + common.Debug.traceStatus("Processing event #" + evt.get_sequenceNumber()); + + if(common.Interface.doesImplement(evt, webservices.Interfaces.IParticipantStateChangedEvent)) + { + // see if this is the first time we've seen this participant + if(!this._doesParticipantExist(evt.get_participantId())) + { + this._processNotification(webservices.NotificationFactory.createNewParticipantNotification(evt.get_participantId(), evt.get_participantName(), webservices.InteractionTypes.CHAT, evt.get_dateTime(), timedOut)); + } + } + + if(common.Interface.doesImplement(evt, webservices.Interfaces.IParticipantStateChangedEvent)) + { + // see if this participant's name changed + if(this._didParticipantsNameChange(evt)) + { + this._processNotification(webservices.NotificationFactory.createParticipantNameChangedNotification(evt, timedOut)); + } + + // see if this participant has joined or left the chat + if(!this._isParticipantAlreadyActive(evt.get_participantId())) + { + // Participant is not in the chat. See if they're in a new state that indicates that they have joined + if((evt.get_state() == webservices.InteractionState.ACTIVE) || + (evt.get_state() == webservices.InteractionState.HELD) || + (evt.get_state() == webservices.InteractionState.VOICEMAIL)) + { + this._processNotification(webservices.NotificationFactory.createParticipantJoinedNotification(evt, timedOut)); + } + } + else + { + // Participant is in the chat. See if they're in a new state that indicates that they have left + if(evt.get_state() == webservices.InteractionState.DISCONNECTED) + { + this._processNotification(webservices.NotificationFactory.createParticipantLeftNotification(evt, timedOut)); + } + } + + // process individual state changes + if(evt.get_state() == webservices.InteractionState.INITIALIZING) + { + this._processNotification(webservices.NotificationFactory.createParticipantInitializingNotification(evt, timedOut)); + } + else if(evt.get_state() == webservices.InteractionState.ACTIVE) + { + this._processNotification(webservices.NotificationFactory.createParticipantActiveNotification(evt, timedOut)); + } + else if(evt.get_state() == webservices.InteractionState.ALERTING) + { + this._processNotification(webservices.NotificationFactory.createParticipantAlertingNotification(evt, timedOut)); + } + else if(evt.get_state() == webservices.InteractionState.HELD) + { + this._processNotification(webservices.NotificationFactory.createParticipantHeldNotification(evt, timedOut)); + } + else if(evt.get_state() == webservices.InteractionState.VOICEMAIL) + { + this._processNotification(webservices.NotificationFactory.createParticipantVoicemailNotification(evt, timedOut)); + } + else if(evt.get_state() == webservices.InteractionState.DISCONNECTED) + { + this._processNotification(webservices.NotificationFactory.createParticipantDisconnectedNotification(evt, timedOut)); + } + } + if(common.Interface.doesImplement(evt, webservices.Interfaces.IParticipantSetTypingEvent)) + { + if(evt.get_isTyping()) + { + this._processNotification(webservices.NotificationFactory.createParticipantStartedTypingNotification(evt, timedOut)); + } + else + { + this._processNotification(webservices.NotificationFactory.createParticipantStoppedTypingNotification(evt, timedOut)); + } + } + if(common.Interface.doesImplement(evt, webservices.Interfaces.IReceivedTextEvent)) + { + this._processNotification(webservices.NotificationFactory.createReceivedTextNotification(evt, timedOut)); + } + if(common.Interface.doesImplement(evt, webservices.Interfaces.IReceivedCommandEvent)) + { + this._processNotification(webservices.NotificationFactory.createReceivedCommandNotification(evt, timedOut)); + } + if(common.Interface.doesImplement(evt, webservices.Interfaces.IReceivedUrlEvent)) + { + this._processNotification(webservices.NotificationFactory.createReceivedUrlNotification(evt, timedOut)); + } + if(common.Interface.doesImplement(evt, webservices.Interfaces.IReceivedFileEvent)) + { + this._processNotification(webservices.NotificationFactory.createReceivedFileNotification(evt, timedOut)); + } + } + } + catch(ex) + { + common.Debug.traceError(ex.message); + common.Debug.alert(ex.message); + common.Debug.breakpoint(); + webservices.ProblemReporter.sendProblemReport(ex, "EventProcessor.process()"); + } + common.Debug.traceMethodExited("EventProcessor.process()"); + }, + + // private methods + + _doesParticipantExist : function(participantId) + { + var participant = this._participantRepository.get_participant(participantId); + if(participant) + { + return true; + } + + return false; + }, + + _isParticipantAlreadyActive : function(participantId) + { + var participant = this._participantRepository.get_participant(participantId); + if(participant) + { + return participant.get_isActive(); + } + + return false; + }, + + _didParticipantsNameChange : function(evt) + { + var newName = evt.get_participantName(); + if(newName) + { + var participant = this._participantRepository.get_participant(evt.get_participantId()); + if(participant) + { + if(participant.get_name() != newName) + { + return true; + } + } + } + + return false; + }, + + _processNotification : function(notification) + { + this._notificationProcessor.process(notification); + } +}); + +// Register namespaces +webservices.registerChildNamespace("_Internal"); + +/** + * EventSequenceManager class + * Keeps the events received from the IC server in the correct chronological sequence. + */ +webservices._Internal._EventSequenceManager = Class.create(webservices._Internal._SequenceManagerBase, +{ + // constants + TIMEOUT_MILLISECONDS : (5000), // 5 seconds + + /** + * Constructor + * + * @param eventProcessor An instance of webservices.EventProcessor + */ + initialize : function($super, eventProcessor) + { + var numArgs = 2; + if(arguments.length != numArgs) + { + throw common.ExceptionFactory.createException("EventSequenceManager constructor called with " + arguments.length + " arguments, but expected " + numArgs + "."); + } + + $super(eventProcessor, this.TIMEOUT_MILLISECONDS); + } +}); + +// Register namespaces +webservices.registerChildNamespace("_Internal"); + +/** + * ParticipantNotificationBase class + * + * Base class of objects which other objects may listen for, which indicate that something has occurred pertaining + * to a participant in the chat. + */ +webservices._Internal.ParticipantNotificationBase = Class.create(webservices._Internal.NotificationBase, +{ + _className : "webservices._Internal.ParticipantNotificationBase", + + /** + * Constructor + * + * @param participantId ID of the person who took whatever action the notification is telling us about + * @param dateTime Timestamp for the notification. Javascript Date object. + * @param isTimedOut Whether the notification is for an event that was received late. If so, perhaps the GUI will do something special to indicate that it is displayed out of sequence, etc.. + */ + initialize : function($super, participantId, dateTime, isTimedOut) + { + if((arguments.length != 3) && (arguments.length != 4)) + { + throw common.ExceptionFactory.createException("ParticipantNotificationBase constructor called with " + arguments.length + " arguments, but expected 3 or 4."); + } + + common.ParameterValidation.validate([participantId, dateTime, isTimedOut], [ {"type": String, "allowEmpty": false, "required": true}, {"type": Date, "required": true}, {"type": Boolean, "required": false}]); + + $super(); + + this.addImplementedInterface(webservices.Interfaces.IParticipantNotification, webservices); + + this._participantId = participantId; + this._dateTime = dateTime; + this._hasTimedOut = (isTimedOut == true); + }, + + /** + * Destructor + */ + destroy : function() + { + this._dateTime = null; + + webservices._Internal.NotificationBase.prototype.destroy.call(this); + }, + + // public methods + + /** + * Returns the ID of the participant who took whatever action the notification indicates. + * + * @return ID of the participant + */ + get_participantId : function() + { + return this._participantId; + }, + + /** + * Returns the timestamp of the event that this notification indicates. + * + * @return Javascript Date object + */ + get_dateTime : function() + { + return this._dateTime; + }, + + /** + * Indicates whether the event was received late. + * If so, perhaps the GUI will do something special to indicate that it is displayed out of sequence, etc.. + * + * @return true if the event was received late, false if not. + */ + get_isTimedOut : function() + { + return this._hasTimedOut; + }, + + /** + * Sets whether the event was received late. + * If so, perhaps the GUI will do something special to indicate that it is displayed out of sequence, etc.. + * + * @param value true if the event was received late, false if not. + */ + set_isTimedOut : function(value) + { + common.ParameterValidation.validate([value], {"type": Boolean, "required": true}); + + this._hasTimedOut = value; + } +}); + +/** + * NewParticipantNotification class + * + * Notification indicating that there is a new participant in the chat (though they may not be active yet). + */ +webservices.NewParticipantNotification = Class.create(webservices._Internal.ParticipantNotificationBase, +{ + _className : "webservices.NewParticipantNotification", + + /** + * Constructor + * + * @param participantId ID of the person who is now in the chat. + * @param participantName The name of the participant who is now in the chat. + * @param interactionType The type of interaction in which the person is participating. A constant defined in webservices.InteractionTypes. + * @param dateTime Timestamp for the notification. Javascript Date object. + * @param isTimedOut Whether the event was received late. If so, perhaps the GUI will do something special to indicate that it is displayed out of sequence, etc.. + */ + initialize:function($super, participantId, participantName, interactionType, dateTime, isTimedOut) + { + if((arguments.length != 5) && (arguments.length != 6)) + { + throw common.ExceptionFactory.createException("NewParticipantNotification constructor called with " + arguments.length + " arguments, but expected 5 or 6."); + } + + $super(participantId, dateTime, isTimedOut); + + this.addImplementedInterface(webservices.Interfaces.INewParticipantNotification, webservices); + + this._participantName = participantName; + this._interactionType = interactionType; + }, + + // public methods + + /** + * Returns the name of the participant to whom this notification pertains. + * + * @return The name of the participant + */ + get_participantName : function() + { + return this._participantName; + }, + + /** + * Gets the type of interaction in which the participant is participating + * + * @return A constant defined in webservices.InteractionTypes. + */ + get_interactionType : function() + { + return this._interactionType; + } +}); + +/** + * ParticipantJoinedNotification class + * + * Notification indicating that a participant has jonied the chat. + */ +webservices.ParticipantJoinedNotification = Class.create(webservices._Internal.ParticipantNotificationBase, +{ + _className : "webservices.ParticipantJoinedNotification", + + /** + * Constructor + * + * @param participantId ID of the person who has joined. + * @param participantName The name of the participant who has joined. + * @param dateTime Timestamp for the notification. Javascript Date object. + * @param isTimedOut Whether the event was received late. If so, perhaps the GUI will do something special to indicate that it is displayed out of sequence, etc.. + */ + initialize:function($super, participantId, participantName, dateTime, isTimedOut) + { + if((arguments.length != 4) && (arguments.length != 5)) + { + throw common.ExceptionFactory.createException("ParticipantJoinedNotification constructor called with " + arguments.length + " arguments, but expected 4 or 5."); + } + + $super(participantId, dateTime, isTimedOut); + + this.addImplementedInterface(webservices.Interfaces.IParticipantJoinedNotification, webservices); + + this._participantName = participantName; + }, + + // public methods + + /** + * Returns the name of the participant to whom this notification pertains. + * + * @return The name of the participant + */ + get_participantName : function() + { + return this._participantName; + } +}); + +/** + * ParticipantLeftNotification class + * + * Notification indicating that a participant has left the chat. + */ +webservices.ParticipantLeftNotification = Class.create(webservices._Internal.ParticipantNotificationBase, +{ + _className : "webservices.ParticipantLeftNotification", + + /** + * Constructor + * + * @param participantId ID of the person who has left. + * @param dateTime Timestamp for the notification. Javascript Date object. + * @param isTimedOut Whether the event was received late. If so, perhaps the GUI will do something special to indicate that it is displayed out of sequence, etc.. + */ + initialize:function($super, participantId, dateTime, isTimedOut) + { + if((arguments.length != 3) && (arguments.length != 4)) + { + throw common.ExceptionFactory.createException("ParticipantLeftNotification constructor called with " + arguments.length + " arguments, but expected 3 or 4."); + } + + $super(participantId, dateTime, isTimedOut); + + this.addImplementedInterface(webservices.Interfaces.IParticipantLeftNotification, webservices); + } +}); + +/** + * ParticipantNameChangedNotification class + * + * Notification indicating that the name of a participant in the chat has changed. + */ +webservices.ParticipantNameChangedNotification = Class.create(webservices._Internal.ParticipantNotificationBase, +{ + _className : "webservices.ParticipantNameChangedNotification", + + /** + * Constructor + * + * @param participantId ID of the person whose name has changed. + * @param newParticipantName The new name of the participant. + * @param dateTime Timestamp for the notification. Javascript Date object. + * @param isTimedOut Whether the event was received late. If so, perhaps the GUI will do something special to indicate that it is displayed out of sequence, etc.. + */ + initialize:function($super, participantId, newParticipantName, dateTime, isTimedOut) + { + if((arguments.length != 4) && (arguments.length != 5)) + { + throw common.ExceptionFactory.createException("ParticipantNameChangedNotification constructor called with " + arguments.length + " arguments, but expected 4 or 5."); + } + + $super(participantId, dateTime, isTimedOut); + + this.addImplementedInterface(webservices.Interfaces.IParticipantNameChangedNotification, webservices); + + this._newParticipantName = newParticipantName; + }, + + // public methods + + /** + * Returns the new name of the participant whose name has changed. + * + * @return The new name of the participant + */ + get_newParticipantName : function() + { + return this._newParticipantName; + } +}); + +/** + * ParticipantInitializingNotification class + * + * Notification indicating that a participant in the chat is initializing. + */ +webservices.ParticipantInitializingNotification = Class.create(webservices._Internal.ParticipantNotificationBase, +{ + _className : "webservices.ParticipantInitializingNotification", + + /** + * Constructor + * + * @param participantId ID of the person who is initializing. + * @param participantName The name of the participant who is initializing. + * @param dateTime Timestamp for the notification. Javascript Date object. + * @param isTimedOut Whether the event was received late. If so, perhaps the GUI will do something special to indicate that it is displayed out of sequence, etc.. + */ + initialize:function($super, participantId, participantName, dateTime, isTimedOut) + { + if((arguments.length != 4) && (arguments.length != 5)) + { + throw common.ExceptionFactory.createException("ParticipantInitializingNotification constructor called with " + arguments.length + " arguments, but expected 4 or 5."); + } + + $super(participantId, dateTime, isTimedOut); + + this.addImplementedInterface(webservices.Interfaces.IParticipantInitializingNotification, webservices); + + this._participantName = participantName; + }, + + // public methods + + /** + * Returns the name of the participant who is initializing. + * + * @return The name of the participant + */ + get_participantName : function() + { + return this._participantName; + } +}); + +/** + * ParticipantAlertingNotification class + * + * Notification indicating that a participant in the chat is alerting. + */ +webservices.ParticipantAlertingNotification = Class.create(webservices._Internal.ParticipantNotificationBase, +{ + _className : "webservices.ParticipantAlertingNotification", + + /** + * Constructor + * + * @param participantId ID of the person who is alerting. + * @param participantName The name of the participant who is alerting. + * @param dateTime Timestamp for the notification. Javascript Date object. + * @param isTimedOut Whether the event was received late. If so, perhaps the GUI will do something special to indicate that it is displayed out of sequence, etc.. + */ + initialize:function($super, participantId, participantName, dateTime, isTimedOut) + { + if((arguments.length != 4) && (arguments.length != 5)) + { + throw common.ExceptionFactory.createException("ParticipantAlertingNotification constructor called with " + arguments.length + " arguments, but expected 4 or 5."); + } + + $super(participantId, dateTime, isTimedOut); + + this.addImplementedInterface(webservices.Interfaces.IParticipantAlertingNotification, webservices); + + this._participantName = participantName; + }, + + // public methods + + /** + * Returns the name of the participant who is alerting. + * + * @return The name of the participant + */ + get_participantName : function() + { + return this._participantName; + } +}); + +/** + * ParticipantActiveNotification class + * + * Notification indicating that a participant in the chat has become active. + */ +webservices.ParticipantActiveNotification = Class.create(webservices._Internal.ParticipantNotificationBase, +{ + _className : "webservices.ParticipantActiveNotification", + + /** + * Constructor + * + * @param participantId ID of the person who has become active. + * @param dateTime Timestamp for the notification. Javascript Date object. + * @param isTimedOut Whether the event was received late. If so, perhaps the GUI will do something special to indicate that it is displayed out of sequence, etc.. + */ + initialize:function($super, participantId, dateTime, isTimedOut) + { + if((arguments.length != 3) && (arguments.length != 4)) + { + throw common.ExceptionFactory.createException("ParticipantActiveNotification constructor called with " + arguments.length + " arguments, but expected 3 or 4."); + } + + $super(participantId, dateTime, isTimedOut); + + this.addImplementedInterface(webservices.Interfaces.IParticipantActiveNotification, webservices); + } +}); + +/** + * ParticipantHeldNotification class + * + * Notification indicating that a participant in the chat has put the chat on hold. + */ +webservices.ParticipantHeldNotification = Class.create(webservices._Internal.ParticipantNotificationBase, +{ + _className : "webservices.ParticipantHeldNotification", + + /** + * Constructor + * + * @param participantId ID of the person who has put the chat on hold. + * @param dateTime Timestamp for the notification. Javascript Date object. + * @param isTimedOut Whether the event was received late. If so, perhaps the GUI will do something special to indicate that it is displayed out of sequence, etc.. + */ + initialize:function($super, participantId, dateTime, isTimedOut) + { + if((arguments.length != 3) && (arguments.length != 4)) + { + throw common.ExceptionFactory.createException("ParticipantHeldNotification constructor called with " + arguments.length + " arguments, but expected 3 or 4."); + } + + $super(participantId, dateTime, isTimedOut); + + this.addImplementedInterface(webservices.Interfaces.IParticipantHeldNotification, webservices); + } +}); + +/** + * ParticipantVoicemailNotification class + * + * Notification indicating that a participant in the chat has gone to a participant's voicemail. + */ +webservices.ParticipantVoicemailNotification = Class.create(webservices._Internal.ParticipantNotificationBase, +{ + _className : "webservices.ParticipantVoicemailNotification", + + /** + * Constructor + * + * @param participantId The chat has gone to the voicemail of the participant with this ID. + * @param dateTime Timestamp for the notification. Javascript Date object. + * @param isTimedOut Whether the event was received late. If so, perhaps the GUI will do something special to indicate that it is displayed out of sequence, etc.. + */ + initialize:function($super, participantId, dateTime, isTimedOut) + { + if((arguments.length != 3) && (arguments.length != 4)) + { + throw common.ExceptionFactory.createException("ParticipantVoicemailNotification constructor called with " + arguments.length + " arguments, but expected 3 or 4."); + } + + $super(participantId, dateTime, isTimedOut); + + this.addImplementedInterface(webservices.Interfaces.IParticipantVoicemailNotification, webservices); + } +}); + +/** + * ParticipantDisconnectedNotification class + * + * Notification indicating that a participant in the chat has disconnected. + */ +webservices.ParticipantDisconnectedNotification = Class.create(webservices._Internal.ParticipantNotificationBase, +{ + _className : "webservices.ParticipantDisconnectedNotification", + + /** + * Constructor + * + * @param participantId ID of the person who has disconnected. + * @param dateTime Timestamp for the notification. Javascript Date object. + * @param isTimedOut Whether the event was received late. If so, perhaps the GUI will do something special to indicate that it is displayed out of sequence, etc.. + */ + initialize:function($super, participantId, dateTime, isTimedOut) + { + if((arguments.length != 3) && (arguments.length != 4)) + { + throw common.ExceptionFactory.createException("ParticipantDisconnectedNotification constructor called with " + arguments.length + " arguments, but expected 3 or 4."); + } + + $super(participantId, dateTime, isTimedOut); + + this.addImplementedInterface(webservices.Interfaces.IParticipantDisconnectedNotification, webservices); + } +}); + +/** + * ParticipantStartedTypingNotification class + * + * Notification indicating that a participant in the chat has started typing. + */ +webservices.ParticipantStartedTypingNotification = Class.create(webservices._Internal.ParticipantNotificationBase, +{ + _className : "webservices.ParticipantStartedTypingNotification", + + /** + * Constructor + * + * @param participantId ID of the person who has started typing. + * @param dateTime Timestamp for the notification. Javascript Date object. + * @param isTimedOut Whether the event was received late. If so, perhaps the GUI will do something special to indicate that it is displayed out of sequence, etc.. + */ + initialize:function($super, participantId, dateTime, isTimedOut) + { + if((arguments.length != 3) && (arguments.length != 4)) + { + throw common.ExceptionFactory.createException("ParticipantStartedTypingNotification constructor called with " + arguments.length + " arguments, but expected 3 or 4."); + } + + $super(participantId, dateTime, isTimedOut); + + this.addImplementedInterface(webservices.Interfaces.IParticipantStartedTypingNotification, webservices); + } +}); + +/** + * ParticipantStoppedTypingNotification class + * + * Notification indicating that a participant in the chat has stopped typing. + */ +webservices.ParticipantStoppedTypingNotification = Class.create(webservices._Internal.ParticipantNotificationBase, +{ + _className : "webservices.ParticipantStoppedTypingNotification", + + /** + * Constructor + * + * @param participantId ID of the person who has stopped typing. + * @param dateTime Timestamp for the notification. Javascript Date object. + * @param isTimedOut Whether the event was received late. If so, perhaps the GUI will do something special to indicate that it is displayed out of sequence, etc.. + */ + initialize:function($super, participantId, dateTime, isTimedOut) + { + if((arguments.length != 3) && (arguments.length != 4)) + { + throw common.ExceptionFactory.createException("ParticipantStoppedTypingNotification constructor called with " + arguments.length + " arguments, but expected 3 or 4."); + } + + $super(participantId, dateTime, isTimedOut); + + this.addImplementedInterface(webservices.Interfaces.IParticipantStoppedTypingNotification, webservices); + } +}); + +/** + * ReceivedTextNotification class + * + * Notification indicating that a participant in the chat has typed something. + */ +webservices.ReceivedTextNotification = Class.create(webservices._Internal.ParticipantNotificationBase, +{ + _className : "webservices.ReceivedTextNotification", + + /** + * Constructor + * + * @param participantId ID of the person who sent a message. + * @param dateTime Timestamp for the notification. Javascript Date object. + * @param conversationSequenceNumber Separate sequence number tracking only received text, URLs, and files. If 5 ParticipantStateChangedEvents are sent, followed by 8 ReceivedTextEvents and 2 ReceivedUrlEvents, the last event received will have sequenceNumber 14 and conversationSequenceNumber 9. + * @param messageText What the participant typed + * @param contentType The mime type of messageText. Likely either "text/plain" or "text/html". + * @param isTimedOut Whether the event was received late. If so, perhaps the GUI will do something special to indicate that it is displayed out of sequence, etc.. + */ + initialize:function($super, participantId, dateTime, conversationSequenceNumber, messageText, contentType, isTimedOut) + { + if((arguments.length != 6) && (arguments.length != 7)) + { + throw common.ExceptionFactory.createException("ReceivedTextNotification constructor called with " + arguments.length + " arguments, but expected 6 or 7."); + } + + $super(participantId, dateTime, isTimedOut); + + this.addImplementedInterface(webservices.Interfaces.IReceivedTextNotification, webservices); + + this._conversationSequenceNumber = conversationSequenceNumber; + this._messageText = messageText; + this._contentType = contentType; + }, + + // public methods + + /** + * Gets the conversation sequence number. + * This is a separate sequence number tracking only received text, URLs, and files. + * If 5 ParticipantStateChangedEvents are sent, followed by 8 ReceivedTextEvents and 2 ReceivedUrlEvents, the + * last event received will have sequenceNumber 14 and conversationSequenceNumber 9. + * + * @return integer + */ + get_conversationSequenceNumber : function() + { + return this._conversationSequenceNumber; + }, + + /** + * Gets the message that the participant typed. + * + * @return string + */ + get_messageText : function() + { + return this._messageText; + }, + + /** + * Gets the mime type of the message that the participant typed. + * + * @return string + */ + get_contentType : function() + { + return this._contentType; + } +}); + +/** + * ReceivedCommandNotification class + * + * Notification indicating that a participant in the chat has typed a special string that should cause the application to + * do something, rather than be displayed to the other participants. + */ +webservices.ReceivedCommandNotification = Class.create(webservices._Internal.ParticipantNotificationBase, +{ + _className : "webservices.ReceivedCommandNotification", + + /** + * Constructor + * + * @param participantId ID of the person who typed a command. + * @param dateTime Timestamp for the notification. Javascript Date object. + * @param conversationSequenceNumber Separate sequence number tracking only received text, URLs, and files. If 5 ParticipantStateChangedEvents are sent, followed by 8 ReceivedTextEvents and 2 ReceivedUrlEvents, the last event received will have sequenceNumber 14 and conversationSequenceNumber 9. + * @param command What the participant typed + * @param isTimedOut Whether the event was received late. If so, perhaps the GUI will do something special to indicate that it is displayed out of sequence, etc.. + */ + initialize:function($super, participantId, dateTime, conversationSequenceNumber, command, isTimedOut) + { + if((arguments.length != 5) && (arguments.length != 6)) + { + throw common.ExceptionFactory.createException("ReceivedCommandNotification constructor called with " + arguments.length + " arguments, but expected 5 or 6."); + } + + $super(participantId, dateTime, isTimedOut); + + this.addImplementedInterface(webservices.Interfaces.IReceivedCommandNotification, webservices); + + this._conversationSequenceNumber = conversationSequenceNumber; + this._command = command; + }, + + // public methods + + /** + * Gets the conversation sequence number. + * This is a separate sequence number tracking only received text, URLs, and files. + * If 5 ParticipantStateChangedEvents are sent, followed by 8 ReceivedTextEvents and 2 ReceivedUrlEvents, the + * last event received will have sequenceNumber 14 and conversationSequenceNumber 9. + * + * @return integer + */ + get_conversationSequenceNumber : function() + { + return this._conversationSequenceNumber; + }, + + /** + * Gets the message that the participant typed. + * + * @return string + */ + get_command : function() + { + return this._command; + } +}); + +/** + * ReceivedUrlNotification class + * + * Notification indicating that a participant in the chat has sent a URL. + */ +webservices.ReceivedUrlNotification = Class.create(webservices._Internal.ParticipantNotificationBase, +{ + _className : "webservices.ReceivedUrlNotification", + + /** + * Constructor + * + * @param participantId ID of the person who sent a message. + * @param dateTime Timestamp for the notification. Javascript Date object. + * @param conversationSequenceNumber Separate sequence number tracking only received text, URLs, and files. If 5 ParticipantStateChangedEvents are sent, followed by 8 ReceivedTextEvents and 2 ReceivedUrlEvents, the last event received will have sequenceNumber 14 and conversationSequenceNumber 9. + * @param messageUrl The URL that the participant sent. + * @param isTimedOut Whether the event was received late. If so, perhaps the GUI will do something special to indicate that it is displayed out of sequence, etc.. + */ + initialize:function($super, participantId, dateTime, conversationSequenceNumber, messageUrl, isTimedOut) + { + if((arguments.length != 5) && (arguments.length != 6)) + { + throw common.ExceptionFactory.createException("ReceivedUrlNotification constructor called with " + arguments.length + " arguments, but expected 5 or 6."); + } + + $super(participantId, dateTime, isTimedOut); + + this.addImplementedInterface(webservices.Interfaces.IReceivedUrlNotification, webservices); + + this._conversationSequenceNumber = conversationSequenceNumber; + this._messageUrl = messageUrl; + }, + + // public methods + + /** + * Gets the conversation sequence number. + * This is a separate sequence number tracking only received text, URLs, and files. + * If 5 ParticipantStateChangedEvents are sent, followed by 8 ReceivedTextEvents and 2 ReceivedUrlEvents, the + * last event received will have sequenceNumber 14 and conversationSequenceNumber 9. + * + * @return integer + */ + get_conversationSequenceNumber : function() + { + return this._conversationSequenceNumber; + }, + + /** + * Gets the URL that the participant sent. + * + * @return string + */ + get_messageUrl : function() + { + return this._messageUrl; + } +}); + +/** + * ReceivedFileNotification class + * + * Notification indicating that a participant in the chat has sent a file. + */ +webservices.ReceivedFileNotification = Class.create(webservices._Internal.ParticipantNotificationBase, +{ + _className : "webservices.ReceivedFileNotification", + + /** + * Constructor + * + * @param participantId ID of the person who sent a message. + * @param dateTime Timestamp for the notification. Javascript Date object. + * @param conversationSequenceNumber Separate sequence number tracking only received text, URLs, and files. If 5 ParticipantStateChangedEvents are sent, followed by 8 ReceivedTextEvents and 2 ReceivedUrlEvents, the last event received will have sequenceNumber 14 and conversationSequenceNumber 9. + * @param messageRelativeUrl The URL that the web user can use to retrieve the file. + * @param isTimedOut Whether the event was received late. If so, perhaps the GUI will do something special to indicate that it is displayed out of sequence, etc.. + */ + initialize:function($super, participantId, dateTime, conversationSequenceNumber, messageRelativeUrl, isTimedOut) + { + if((arguments.length != 5) && (arguments.length != 6)) + { + throw common.ExceptionFactory.createException("ReceivedFileNotification constructor called with " + arguments.length + " arguments, but expected 5 or 6."); + } + + $super(participantId, dateTime, isTimedOut); + + this.addImplementedInterface(webservices.Interfaces.IReceivedFileNotification, webservices); + + this._conversationSequenceNumber = conversationSequenceNumber; + this._messageRelativeUrl = messageRelativeUrl; + }, + + // public methods + + /** + * Gets the conversation sequence number. + * This is a separate sequence number tracking only received text, URLs, and files. + * If 5 ParticipantStateChangedEvents are sent, followed by 8 ReceivedTextEvents and 2 ReceivedUrlEvents, the + * last event received will have sequenceNumber 14 and conversationSequenceNumber 9. + * + * @return integer + */ + get_conversationSequenceNumber : function() + { + return this._conversationSequenceNumber; + }, + + /** + * Gets the URL that the web user can use to retrieve the file that the participant sent. + * + * @return string + */ + get_messageRelativeUrl : function() + { + return this._messageRelativeUrl; + } +}); + + +/** + * FailoverNotification class + * + * Notification indicating that a failover has occurred. + */ +webservices.FailoverNotification = Class.create(webservices._Internal.NotificationBase, +{ + _className : "webservices.FailoverNotification", + + /** + * Constructor + */ + initialize : function($super) + { + if(arguments.length != 1) + { + throw common.ExceptionFactory.createException("FailoverNotification constructor called with " + arguments.length + " arguments, but expected 1."); + } + + $super(); + + this.addImplementedInterface(webservices.Interfaces.IFailoverNotification, webservices); + } +}); + + +/** + * FailoverUINotification class + * + * Notification indicating (to the UI) that a failover has occurred. + */ +webservices.FailoverUINotification = Class.create(webservices._Internal.NotificationBase, +{ + _className : "webservices.FailoverUINotification", + + /** + * Constructor + */ + initialize : function($super) + { + if(arguments.length != 1) + { + throw common.ExceptionFactory.createException("FailoverUINotification constructor called with " + arguments.length + " arguments, but expected 1."); + } + + $super(); + + this.addImplementedInterface(webservices.Interfaces.IFailoverUINotification, webservices); + } +}); + + +/** + * ChatReconnectNotification class + * + * Notification indicating that a chat has reconnected. + */ +webservices.ChatReconnectNotification = Class.create(webservices._Internal.NotificationBase, +{ + _className : "webservices.ChatReconnectNotification", + + /** + * Constructor + */ + initialize : function($super) + { + if(arguments.length != 1) + { + throw common.ExceptionFactory.createException("ChatReconnectNotification constructor called with " + arguments.length + " arguments, but expected 1."); + } + + $super(); + + this.addImplementedInterface(webservices.Interfaces.IChatReconnectNotification, webservices); + } +}); + + +/** + * ResumedPollingNotification class + * + * Notification indicating that a reconnect has occurred. + */ +webservices.ResumedPollingNotification = Class.create(webservices._Internal.NotificationBase, +{ + _className : "webservices.ResumedPollingNotification", + + /** + * Constructor + */ + initialize : function($super) + { + if(arguments.length != 1) + { + throw common.ExceptionFactory.createException("ResumedPollingNotification constructor called with " + arguments.length + " arguments, but expected 1."); + } + + $super(); + + this.addImplementedInterface(webservices.Interfaces.IResumedPollingNotification, webservices); + } +}); + + +/** + * ChatReconnectFailureNotification class + * + * Notification indicating that a chat has failed to reconnect. + */ +webservices.ChatReconnectFailureNotification = Class.create(webservices._Internal.NotificationBase, +{ + _className : "webservices.ChatReconnectFailureNotification", + + /** + * Constructor + */ + initialize : function($super) + { + if(arguments.length != 1) + { + throw common.ExceptionFactory.createException("ChatReconnectFailureNotification constructor called with " + arguments.length + " arguments, but expected 1."); + } + + $super(); + + this.addImplementedInterface(webservices.Interfaces.IChatReconnectFailureNotification, webservices); + } +}); + + +/** + * RefreshPageNotification class + * + * Notification indicating that the user needs to refresh the page to start a new chat. + */ +webservices.RefreshPageNotification = Class.create(webservices._Internal.NotificationBase, +{ + _className : "webservices.RefreshPageNotification", + + /** + * Constructor + * + * @param newUriFragment The URI fragment that should now be used to reverse proxy requests through the webserver to the IC server. + */ + initialize : function($super, newUriFragment) + { + if((arguments.length != 1) && (arguments.length != 2)) + { + throw common.ExceptionFactory.createException("RefreshPageNotification constructor called with " + arguments.length + " arguments, but expected 1 or 2."); + } + + $super(); + + this.addImplementedInterface(webservices.Interfaces.IRefreshPageNotification, webservices); + + this._newUriFragment = newUriFragment; + }, + + // public methods + + /** + * Returns the new URI fragment to use to reverse proxy requests through the webserver to the IC server. + * + * @return The URI fragment that should now be used. + */ + get_newUriFragment : function() + { + return this._newUriFragment; + } +}); + + +/** + * CurrentParticipantIdChangedNotification class + * + * Notification indicating that the ID of the current participant (i.e. the person whose web browser is running this code) has changed. + */ +webservices.CurrentParticipantIdChangedNotification = Class.create(webservices._Internal.ParticipantNotificationBase, +{ + _className : "webservices.CurrentParticipantIdChangedNotification", + + /** + * Constructor + * + * @param participantId The new ID of the current participant + * @param dateTime Timestamp for the notification. Javascript Date object. + * @param isTimedOut Whether the event was received late. If so, perhaps the GUI will do something special to indicate that it is displayed out of sequence, etc.. + */ + initialize:function($super, participantId, dateTime, isTimedOut) + { + if((arguments.length != 3) && (arguments.length != 4)) + { + throw common.ExceptionFactory.createException("CurrentParticipantIdChangedNotification constructor called with " + arguments.length + " arguments, but expected 3 or 4."); + } + + $super(participantId, dateTime, isTimedOut); + + this.addImplementedInterface(webservices.Interfaces.ICurrentParticipantIdChangedNotification, webservices); + } +}); + +/** + * PageNotificationBase class + * + * Base class of objects which other objects may listen for, which indicate that something has occurred pertaining + * to the browser loading or unloading the page. These notifications are not received from the IC server. + */ +webservices._Internal.PageNotificationBase = Class.create(webservices._Internal.NotificationBase, +{ + _className : "webservices._Internal.PageNotificationBase", + + /** + * Constructor + */ + initialize : function($super) + { + if(arguments.length != 1) + { + throw common.ExceptionFactory.createException("PageNotificationBase constructor called with " + arguments.length + " arguments, but expected 1."); + } + + $super(); + + this.addImplementedInterface(webservices.Interfaces.IPageNotification, webservices); + } +}); + +/** + * PageBeforeUnloadNotification class + * + * Notification indicating that web browser is about to unload the web page. + */ +webservices.PageBeforeUnloadNotification = Class.create(webservices._Internal.PageNotificationBase, +{ + _className : "webservices.PageBeforeUnloadNotification", + + /** + * Constructor + */ + initialize : function($super) + { + if(arguments.length != 1) + { + throw common.ExceptionFactory.createException("PageBeforeUnloadNotification constructor called with " + arguments.length + " arguments, but expected 1."); + } + + $super(); + + this.addImplementedInterface(webservices.Interfaces.IPageBeforeUnloadNotification, webservices); + } +}); + +/** + * PageUnloadNotification class + * + * Notification indicating that web browser has completed unloading the web page. + */ +webservices.PageUnloadNotification = Class.create(webservices._Internal.PageNotificationBase, +{ + _className : "webservices.PageUnloadNotification", + + /** + * Constructor + */ + initialize : function($super) + { + if(arguments.length != 1) + { + throw common.ExceptionFactory.createException("PageUnloadNotification constructor called with " + arguments.length + " arguments, but expected 1."); + } + + $super(); + + this.addImplementedInterface(webservices.Interfaces.IPageUnloadNotification, webservices); + } +}); + +/** + * ChatCreationNotification class + * + * Notification indicating that a chat has been created. + */ +webservices.ChatCreationNotification = Class.create(webservices._Internal.NotificationBase, +{ + _className : "webservices.ChatCreationNotification", + + /** + * Constructor + * + * @param currentParticipantId An identifier which will be used to refer to the party that created the chat. + * @param dateFormat A string specifying how dates should be formatted, as described in http://msdn.microsoft.com/en-us/library/dd317787%28v=vs.85%29.aspx + * @param timeFormat A string specifying how times should be formatted, as described in http://msdn.microsoft.com/en-us/library/dd318148%28v=vs.85%29.aspx + */ + initialize : function($super, currentChatId, currentParticipantId, dateFormat, timeFormat) + { + if(arguments.length != 5) + { + throw common.ExceptionFactory.createException("ChatCreationNotification constructor called with " + arguments.length + " arguments, but expected 5."); + } + + $super(); + + this.addImplementedInterface(webservices.Interfaces.IChatCreationNotification, webservices); + + this._currentChatId = currentChatId; + this._currentParticipantId = currentParticipantId; + this._dateFormat = dateFormat; + this._timeFormat = timeFormat; + }, + + /** + * Returns the ID that will be used to refer to this chat. + * + * @returns GUID + */ + get_currentChatId : function() + { + return this._currentChatId; + }, + + /** + * Returns the ID that will be used to refer to this participant for the remainder of the chat. + * + * @return GUID + */ + get_currentParticipantId : function() + { + return this._currentParticipantId; + }, + + /** + * Returns the format that will be used to display dates + * + * @return String as described in http://msdn.microsoft.com/en-us/library/dd317787%28v=vs.85%29.aspx + */ + get_dateFormat : function() + { + return this._dateFormat; + }, + + /** + * Returns the format that will be used to display times + * + * @return String as described in http://msdn.microsoft.com/en-us/library/dd318148%28v=vs.85%29.aspx + */ + get_timeFormat : function() + { + return this._timeFormat; + } +}); + +/** + * ChatCreationFailureNotification class + * + * Notification indicating that an attempt to create a chat has failed. + */ +webservices.ChatCreationFailureNotification = Class.create(webservices._Internal.NotificationBase, +{ + _className : "webservices.ChatCreationFailureNotification", + + /** + * Constructor + * + * @param error The error that caused the failure + */ + initialize : function($super, error) + { + if(arguments.length != 2) + { + throw common.ExceptionFactory.createException("ChatCreationFailureNotification constructor called with " + arguments.length + " arguments, but expected 2."); + } + + $super(); + + this.addImplementedInterface(webservices.Interfaces.IChatCreationFailureNotification, webservices); + + this._error = error; + }, + + /** + * Returns the error that caused this failure. + */ + get_error : function() + { + return this._error; + } +}); + +/** + * ChatCompletionNotification class + * + * Notification indicating that a chat has completed. + */ +webservices.ChatCompletionNotification = Class.create(webservices._Internal.NotificationBase, +{ + _className : "webservices.ChatCompletionNotification", + + /** + * Constructor + */ + initialize : function($super) + { + if(arguments.length != 1) + { + throw common.ExceptionFactory.createException("ChatCompletionNotification constructor called with " + arguments.length + " arguments, but expected 1."); + } + + $super(); + + this.addImplementedInterface(webservices.Interfaces.IChatCompletionNotification, webservices); + } +}); + +/** + * ChatCompletionFailureNotification class + * + * Notification indicating that an attempt to exit a chat has failed. + */ +webservices.ChatCompletionFailureNotification = Class.create(webservices._Internal.NotificationBase, +{ + _className : "webservices.ChatCompletionFailureNotification", + + /** + * Constructor + * + * @param error The error that caused the failure + */ + initialize : function($super, error) + { + if(arguments.length != 2) + { + throw common.ExceptionFactory.createException("ChatCompletionFailureNotification constructor called with " + arguments.length + " arguments, but expected 2."); + } + + $super(); + + this.addImplementedInterface(webservices.Interfaces.IChatCompletionFailureNotification, webservices); + + this._error = error; + }, + + /** + * Returns the error that caused this failure. + */ + get_error : function() + { + return this._error; + } +}); + +/** + * ChatReconnectUINotification class + * + * Notification indicating (to the UI) that a chat has reconnected. + */ +webservices.ChatReconnectUINotification = Class.create(webservices._Internal.NotificationBase, +{ + _className : "webservices.ChatReconnectUINotification", + + /** + * Constructor + */ + initialize : function($super) + { + if(arguments.length != 1) + { + throw common.ExceptionFactory.createException("ChatReconnectUINotification constructor called with " + arguments.length + " arguments, but expected 1."); + } + + $super(); + + this.addImplementedInterface(webservices.Interfaces.IChatReconnectUINotification, webservices); + } +}); + +/** + * CallbackCreationNotification class + * + * Notification indicating that a callback has been created. + */ +webservices.CallbackCreationNotification = Class.create(webservices._Internal.NotificationBase, +{ + _className : "webservices.CallbackCreationNotification", + + /** + * Constructor + * @param participantId An identifier which will be used to refer to the party that created the callback for the duration of the current web session. + * @param callbackId ID of the callback. It may be used for the duration of the callback to bring the callback into a then-current web session. If the IVR permits, it may also be entered into the IVR to manage the callback via telephone. Will be null if the IC server does not supply it. + * @param userIdentityId ID of the user who created this callback. It may be used for the duration of the callback to bring the callback into a then-current web session. If the IVR permits, it may also be entered into the IVR to manage the callback via telephone. Will be null if the IC server does not supply it. + * @param participantName The name or username of the web user who has created the callback request + * @param telephone The telephone number at which the participant indicated they would like to be called + * @param subject The topic which the participant wishes to discuss with the agent + * @param creationDateTime A Javascript Date object containing the timestamp of when the callback request was created + */ + initialize : function($super, participantId, callbackId, userIdentityId, participantName, telephone, subject, creationDateTime) + { + var numArgs = 8; + if(arguments.length != numArgs) + { + throw common.ExceptionFactory.createException("CallbackCreationNotification constructor called with " + arguments.length + " arguments, but expected " + numArgs); + } + + $super(); + + this.addImplementedInterface(webservices.Interfaces.ICallbackCreationNotification, webservices); + + this._participantId = participantId; + this._callbackId = callbackId; + this._userIdentityId = userIdentityId; + this._participantName = participantName; + this._telephone = telephone; + this._subject = subject; + this._creationDateTime = creationDateTime; + }, + + /** + * Returns the ID that will be used to refer to this participant for the callback. + * + * @return GUID + */ + get_participantId : function() + { + return this._participantId; + }, + + /** + * Gets the ID of the callback, so it may be reconnected + * (i.e. brought into a then-current web session) later. + * This ID's lifespan is that of the Callback. + * + * @return ID of the callback + */ + get_callbackId : function() + { + return this._callbackId; + }, + + /** + * Gets the ID of the user who created this callback + * This ID's lifespan is that of the Callback. + * + * @return ID of the user + */ + get_userIdentityId : function() + { + return this._userIdentityId; + }, + + /** + * Gets the name or username of the user who created this callback request. + * + * @return String containing the name or username + */ + get_participantName : function() + { + return this._participantName; + }, + + /** + * Gets the telephone number at which the participant indicated they would like to be called + * + * @return String containing the telephone number + */ + get_telephone : function() + { + return this._telephone; + }, + + /** + * Gets the subject/description of the callback. + * This was supplied by the user when the callback was created. + * + * @return String containing the subject + */ + get_subject : function() + { + return this._subject; + }, + + /** + * Gets the date and time when this callback request was sent. + * + * @return Javascript Date object + */ + get_creationDateTime : function() + { + return this._creationDateTime; + } +}); + +/** + * CallbackCreationFailureNotification class + * + * Notification indicating that an attempt to create a callback has failed. + */ +webservices.CallbackCreationFailureNotification = Class.create(webservices._Internal.NotificationBase, +{ + _className : "webservices.CallbackCreationFailureNotification", + + /** + * Constructor + * + * @param error The error that caused the failure + */ + initialize : function($super, error) + { + if(arguments.length != 2) + { + throw common.ExceptionFactory.createException("CallbackCreationFailureNotification constructor called with " + arguments.length + " arguments, but expected 2."); + } + + $super(); + + this.addImplementedInterface(webservices.Interfaces.ICallbackCreationFailureNotification, webservices); + + this._error = error; + }, + + /** + * Returns the error that caused this failure. + */ + get_error : function() + { + return this._error; + } +}); + +/** + * CallbackStatusNotification class + * + * Notification containing a callback's status + */ +webservices.CallbackStatusNotification = Class.create(webservices._Internal.NotificationBase, +{ + _className : "webservices.CallbackStatusNotification", + + /** + * Constructor + * + * @param participantId The identifier which associates the web visitor with the callback that has been queried. + * @param queueWaitTime number of seconds + * @param assignedAgentName Agent's name + * @param assignedAgentParticipantId ID identifying the agent + * @param interactionState The state of the interaction + * @param estimatedCallbackTime number of seconds before it is estimated that the callback will be made + * @param queuePosition integer indicating the callback's position in the queue + * @param queueName the name of the queue + * @param longestWaitTime The longest amount of time (in seconds) an interaction waited before being connected on the queue to which the current callback is targetted. + * @param interactionsWaitingCount The number of calls waiting + * @param loggedInAgentsCount the number of logged in agents who meet this callback's routing critieria + * @param availableAgentsCount the number of available agents who meet this callback's routing criteria + * @param statusIndicator A key to quickly indicate the status of the callback + */ + initialize : function($super, participantId, queueWaitTime, assignedAgentName, assignedAgentParticipantId, + interactionState, estimatedCallbackTime, queuePosition, queueName, + longestWaitTime, interactionsWaitingCount, loggedInAgentsCount, availableAgentsCount, + statusIndicator) + { + var numArgs = 14; + if(arguments.length != numArgs) + { + throw common.ExceptionFactory.createException("CallbackStatusNotification constructor called with " + arguments.length + " arguments, but expected " + numArgs); + } + + $super(); + + this.addImplementedInterface(webservices.Interfaces.ICallbackStatusNotification, webservices); + + this._participantId = participantId; + this._queueWaitTime = queueWaitTime; + this._assignedAgentName = assignedAgentName; + this._assignedAgentParticipantId = assignedAgentParticipantId; + this._interactionState = interactionState; + this._estimatedCallbackTime = estimatedCallbackTime; + this._queuePosition = queuePosition; + this._queueName = queueName; + this._longestWaitTime = longestWaitTime; + this._interactionsWaitingCount = interactionsWaitingCount; + this._loggedInAgentsCount = loggedInAgentsCount; + this._availableAgentsCount = availableAgentsCount; + this._statusIndicator = statusIndicator; + }, + + /** + * Returns the ID that is used to associate the web visitor with the callback that has been queried. + * + * @return GUID + */ + get_participantId : function() + { + return this._participantId; + }, + + /** + * Gets the queue's wait time + * + * @return number of seconds + */ + get_queueWaitTime : function() + { + return this._queueWaitTime; + }, + + /** + * Gets the assigned agent's name + * + * @return Agent's name + */ + get_assignedAgentName : function() + { + return this._assignedAgentName; + }, + + /** + * Gets the assigned agent's participant ID + * + * @return ID identifying the agent + */ + get_assignedAgentParticipantId : function() + { + return this._assignedAgentParticipantId; + }, + + /** + * Gets the interaction state + * + * @return interaction state + */ + get_interactionState : function() + { + return this._interactionState; + }, + + /** + * Gets the estimated callback time, expressed in "seconds after now" + * + * @return number of seconds before it is estimated that the callback will be made + */ + get_estimatedCallbackTime : function() + { + return this._estimatedCallbackTime; + }, + + /** + * Gets the callback's position in the queue + * + * @return integer indicating the callback's position in the queue + */ + get_queuePosition : function() + { + return this._queuePosition; + }, + + /** + * Gets the name of the queue + * + * @return the name of the queue + */ + get_queueName : function() + { + return this._queueName; + }, + + /** + * Gets the longest amount of time (in seconds) an interaction waited before being connected on the queue to which the current callback is targetted. + * + * @return longestWaitTime + */ + get_longestWaitTime : function() + { + return this._longestWaitTime; + }, + + /** + * Gets the number of calls waiting + * + * @return the number of calls waiting + */ + get_interactionsWaitingCount : function() + { + return this._interactionsWaitingCount; + }, + + /** + * Gets the number of logged in agents who meet this callback's routing criteria + * + * @return the number of logged in agents who meet this callback's routing critieria + */ + get_loggedInAgentsCount : function() + { + return this._loggedInAgentsCount; + }, + + /** + * Gets the number of available agents who meet this callback's routing criteria + * + * @return the number of available agents who meet this callback's routing criteria + */ + get_availableAgentsCount : function() + { + return this._availableAgentsCount; + }, + + /** + * Returns a key to indicate the status of the callback + * + * @return statusIndicator string + */ + get_statusIndicator : function() + { + return this._statusIndicator; + } +}); + +/** + * CallbackStatusFailureNotification class + * + * Notification indicating that an attempt to query the status of a callback has failed. + */ +webservices.CallbackStatusFailureNotification = Class.create(webservices._Internal.NotificationBase, +{ + _className : "webservices.CallbackStatusFailureNotification", + + /** + * Constructor + * + * @param error The error that caused the failure + */ + initialize : function($super, error) + { + if(arguments.length != 2) + { + throw common.ExceptionFactory.createException("CallbackStatusFailureNotification constructor called with " + arguments.length + " arguments, but expected 2."); + } + + $super(); + + this.addImplementedInterface(webservices.Interfaces.ICallbackStatusFailureNotification, webservices); + + this._error = error; + }, + + /** + * Returns the error that caused this failure. + * + * @return The error that caused this failure. + */ + get_error : function() + { + return this._error; + } +}); + +/** + * CallbackDisconnectNotification class + * + * Notification indicating that a callback interaction has been disconnected (i.e. cancelled) + */ +webservices.CallbackDisconnectNotification = Class.create(webservices._Internal.NotificationBase, +{ + _className : "webservices.CallbackDisconnectNotification", + + /** + * Constructor + * @param participantId The identifier which associated the web visitor with the callback that has been disconnected. This identifier is no longer valid, and is included so that consumers of this notification may remove it from their data structures, user interfaces, etc. + */ + initialize : function($super, participantId) + { + if(arguments.length != 2) + { + throw common.ExceptionFactory.createException("CallbackDisconnectNotification constructor called with " + arguments.length + " arguments, but expected 2."); + } + + $super(); + + this.addImplementedInterface(webservices.Interfaces.ICallbackDisconnectNotification, webservices); + + this._participantId = participantId; + }, + + /** + * Returns the ID that was used to associate the web visitor with the callback that has been disconnected. This identifier is no longer valid, and is made available so that consumers of this notification may remove it from their data structures, user interfaces, etc. + * + * @return GUID + */ + get_participantId : function() + { + return this._participantId; + } +}); + +/** + * CallbackDisconnectFailureNotification class + * + * Notification indicating that an attempt to disconnect a callback has failed. + */ +webservices.CallbackDisconnectFailureNotification = Class.create(webservices._Internal.NotificationBase, +{ + _className : "webservices.CallbackDisconnectFailureNotification", + + /** + * Constructor + * + * @param error The error that caused the failure + */ + initialize : function($super, error) + { + if(arguments.length != 2) + { + throw common.ExceptionFactory.createException("CallbackDisconnectFailureNotification constructor called with " + arguments.length + " arguments, but expected 2."); + } + + $super(); + + this.addImplementedInterface(webservices.Interfaces.ICallbackDisconnectFailureNotification, webservices); + + this._error = error; + }, + + /** + * Returns the error that caused this failure. + * + * @return The error that caused this failure. + */ + get_error : function() + { + return this._error; + } +}); + +/** + * CallbackReconnectNotification class + * + * Notification indicating that a callback interaction has been reconnected (i.e. brought into the + * current web session) + */ +webservices.CallbackReconnectNotification = Class.create(webservices._Internal.NotificationBase, +{ + _className : "webservices.CallbackReconnectNotification", + + /** + * Constructor + * + * @param participantId An identifier which will be used to refer to the party that created the callback. + */ + initialize : function($super, participantId) + { + var numExpectedArgs = 2; + if(arguments.length != numExpectedArgs) + { + throw common.ExceptionFactory.createException("CallbackReconnectNotification constructor called with " + arguments.length + " arguments, but expected " + numExpectedArgs); + } + + $super(); + + this._participantId = participantId; + + this.addImplementedInterface(webservices.Interfaces.ICallbackReconnectNotification, webservices); + }, + + /** + * Returns the ID that will be used to refer to this participant for the callback. + * + * @return GUID + */ + get_participantId : function() + { + return this._participantId; + } +}); + +/** + * CallbackReconnectFailureNotification class + * + * Notification indicating that an attempt to reconnect a callback has failed. + */ +webservices.CallbackReconnectFailureNotification = Class.create(webservices._Internal.NotificationBase, +{ + _className : "webservices.CallbackReconnectFailureNotification", + + /** + * Constructor + * + * @param error The error that caused the failure + */ + initialize : function($super, error) + { + var numExpectedArgs = 2; + if(arguments.length != numExpectedArgs) + { + throw common.ExceptionFactory.createException("CallbackReconnectFailureNotification constructor called with " + arguments.length + " arguments, but expected " + numExpectedArgs); + } + + $super(); + + this.addImplementedInterface(webservices.Interfaces.ICallbackReconnectFailureNotification, webservices); + + this._error = error; + }, + + /** + * Returns the error that caused this failure. + * + * @return The error that caused this failure. + */ + get_error : function() + { + return this._error; + } +}); + +/** + * PartyInfoNotification class + * + * Notification containing information pertaining to a party in an interaction, such as their name and photograph. + */ +webservices.PartyInfoNotification = Class.create(webservices._Internal.NotificationBase, +{ + _className : "webservices.PartyInfoNotification", + + /** + * Constructor + * + * @param localParticipantId The participantId of the agent whose info is being queried. + * @param remoteParticipantId The participantId of the agent whose info is being queried. + * @param name The name of the participant + * @param photo Location of the photo of the participant + */ + initialize : function($super, localParticipantId, remoteParticipantId, name, photo) + { + var minExpectedArgs = 4; + var maxExpectedArgs = 5; + if(arguments.length < minExpectedArgs || arguments.length > maxExpectedArgs) + { + throw common.ExceptionFactory.createException("PartyInfoNotification constructor called with " + arguments.length + " arguments, but expected between " + minExpectedArgs + " and " + maxExpectedArgs); + } + + $super(); + + this.addImplementedInterface(webservices.Interfaces.IPartyInfoNotification, webservices); + + this._name = name; + this._photo = photo; + this._localParticipantId = localParticipantId; + this._remoteParticipantId = remoteParticipantId; + }, + + /** + * Gets the name of the participant to whom this response pertains + * + * @return name of the participant + */ + get_name : function() + { + return this._name; + }, + + /** + * Gets the location of the photo of the participant to whom this response pertains + * + * @return Location of the photo of the participant + */ + get_photo : function() + { + return this._photo; + }, + + /** + * Returns the ID of the local participant (i.e. the one whose web browser is running this code) + * + * @return GUID + */ + get_localParticipantId : function() + { + return this._localParticipantId; + }, + + /** + * Returns the ID of the remote participant (i.e. the one whose name and photo are supplied by this Notification) + * + * @return GUID + */ + get_remoteParticipantId : function() + { + return this._remoteParticipantId; + } +}); + +/** + * PartyInfoFailureNotification class + * + * Notification indicating that an attempt to get information about a party in an interaction has failed. + */ +webservices.PartyInfoFailureNotification = Class.create(webservices._Internal.NotificationBase, +{ + _className : "webservices.PartyInfoFailureNotification", + + /** + * Constructor + * + * @param error The error that caused the failure + */ + initialize : function($super, error) + { + var numExpectedArgs = 2; + if(arguments.length != numExpectedArgs) + { + throw common.ExceptionFactory.createException("PartyInfoFailureNotification constructor called with " + arguments.length + " arguments, but expected " + numExpectedArgs); + } + + $super(); + + this.addImplementedInterface(webservices.Interfaces.IPartyInfoFailureNotification, webservices); + + this._error = error; + }, + + /** + * Returns the error that caused this failure. + * + * @return The error that caused this failure. + */ + get_error : function() + { + return this._error; + } +}); + +// Register namespaces +webservices.registerChildNamespace("_Internal"); + +/** + * NotificationFactory class + * + * Creates Notification objects which other objects may listen for. + */ +webservices._Internal.NotificationFactory = Class.create(common.InterfaceImplementation, +{ + /** + * Constructor + */ + initialize : function($super) + { + if(arguments.length != 1) + { + throw common.ExceptionFactory.createException("NotificationFactory constructor called with " + arguments.length + " arguments, but expected 1."); + } + + $super(); + }, + + /** + * Secondary initializer, to break circular dependency. + * webservices.NotificationRegistry.registerNotificationType() requires + * webservices.NotificationFactory to already have been constructed. + */ + delayedInit : function() + { + webservices.NotificationRegistry.registerNotificationType(webservices.Interfaces.IParticipantJoinedNotification, + /** + * Creates a ParticipantJoinedNotification, to indicate that someone has joined the chat. + * + * @param evt A ParticipantStateChangedEvent + * @param isTimedOut Whether the event was received late. If so, perhaps the GUI will do something special to indicate that it is displayed out of sequence, etc.. + * @return ParticipantJoinedNotification + */ + function(evt, isTimedOut) + { + return new webservices.ParticipantJoinedNotification(evt.get_participantId(), evt.get_participantName(), evt.get_dateTime(), isTimedOut); + }); + + webservices.NotificationRegistry.registerNotificationType(webservices.Interfaces.IParticipantLeftNotification, + /** + * Creates a ParticipantLeftNotification, to indicate that someone has left the chat. + * + * @param evt A ParticipantStateChangedEvent + * @param isTimedOut Whether the event was received late. If so, perhaps the GUI will do something special to indicate that it is displayed out of sequence, etc.. + * @return ParticipantLeftNotification + */ + function(evt, isTimedOut) + { + return new webservices.ParticipantLeftNotification(evt.get_participantId(), evt.get_dateTime(), isTimedOut); + }); + + webservices.NotificationRegistry.registerNotificationType(webservices.Interfaces.INewParticipantNotification, + /** + * Creates a NewParticipantNotification, to indicate that someone has joined an interaction. + * + * @param participantId ID of the person who is now in the interaction. + * @param participantName The name of the participant who is now in the interaction. + * @param interactionType The type of interaction in which the person is participating. A constant defined in webservices.InteractionTypes. + * @param dateTime Timestamp for the notification. Javascript Date object. + * @param isTimedOut Whether the event was received late. If so, perhaps the GUI will do something special to indicate that it is displayed out of sequence, etc. + * @return NewParticipantNotification + */ + function(participantId, participantName, interactionType, dateTime, isTimedOut) + { + return new webservices.NewParticipantNotification(participantId, participantName, interactionType, dateTime, isTimedOut); + }); + + webservices.NotificationRegistry.registerNotificationType(webservices.Interfaces.IParticipantNameChangedNotification, + /** + * Creates a ParticipantNameChangedNotification, to indicate that the name of someone in the chat has changed. + * + * @param evt A ParticipantStateChangedEvent + * @param isTimedOut Whether the event was received late. If so, perhaps the GUI will do something special to indicate that it is displayed out of sequence, etc.. + * @return ParticipantNameChangedNotification + */ + function(evt, isTimedOut) + { + return new webservices.ParticipantNameChangedNotification(evt.get_participantId(), evt.get_participantName(), evt.get_dateTime(), isTimedOut); + }); + + webservices.NotificationRegistry.registerNotificationType(webservices.Interfaces.IParticipantInitializingNotification, + /** + * Creates a ParticipantInitializingNotification, to indicate that someone in the chat is initializing. + * + * @param evt A ParticipantStateChangedEvent + * @param isTimedOut Whether the event was received late. If so, perhaps the GUI will do something special to indicate that it is displayed out of sequence, etc.. + * @return ParticipantInitializingNotification + */ + function(evt, isTimedOut) + { + return new webservices.ParticipantInitializingNotification(evt.get_participantId(), evt.get_participantName(), evt.get_dateTime(), isTimedOut); + }); + + webservices.NotificationRegistry.registerNotificationType(webservices.Interfaces.IParticipantAlertingNotification, + /** + * Creates a ParticipantAlertingNotification, to indicate that someone in the chat is alerting. + * + * @param evt A ParticipantStateChangedEvent + * @param isTimedOut Whether the event was received late. If so, perhaps the GUI will do something special to indicate that it is displayed out of sequence, etc.. + * @return ParticipantAlertingNotification + */ + function(evt, isTimedOut) + { + return new webservices.ParticipantAlertingNotification(evt.get_participantId(), evt.get_participantName(), evt.get_dateTime(), isTimedOut); + }); + + webservices.NotificationRegistry.registerNotificationType(webservices.Interfaces.IParticipantActiveNotification, + /** + * Creates a ParticipantActiveNotification, to indicate that someone in the chat has become active. + * + * @param evt A ParticipantStateChangedEvent + * @param isTimedOut Whether the event was received late. If so, perhaps the GUI will do something special to indicate that it is displayed out of sequence, etc.. + * @return ParticipantActiveNotification + */ + function(evt, isTimedOut) + { + return new webservices.ParticipantActiveNotification(evt.get_participantId(), evt.get_dateTime(), isTimedOut); + }); + + webservices.NotificationRegistry.registerNotificationType(webservices.Interfaces.IParticipantHeldNotification, + /** + * Creates a ParticipantHeldNotification, to indicate that someone in the chat has put the chat on hold. + * + * @param evt A ParticipantStateChangedEvent + * @param isTimedOut Whether the event was received late. If so, perhaps the GUI will do something special to indicate that it is displayed out of sequence, etc.. + * @return ParticipantHeldNotification + */ + function(evt, isTimedOut) + { + return new webservices.ParticipantHeldNotification(evt.get_participantId(), evt.get_dateTime(), isTimedOut); + }); + + webservices.NotificationRegistry.registerNotificationType(webservices.Interfaces.IParticipantVoicemailNotification, + /** + * Creates a ParticipantVoicemailNotification, to indicate that someone in the chat has entered the voicemail state. + * + * @param evt A ParticipantStateChangedEvent + * @param isTimedOut Whether the event was received late. If so, perhaps the GUI will do something special to indicate that it is displayed out of sequence, etc.. + * @return ParticipantVoicemailNotification + */ + function(evt, isTimedOut) + { + return new webservices.ParticipantVoicemailNotification(evt.get_participantId(), evt.get_dateTime(), isTimedOut); + }); + + webservices.NotificationRegistry.registerNotificationType(webservices.Interfaces.IParticipantDisconnectedNotification, + /** + * Creates a ParticipantDisconnectedNotification, to indicate that someone has disconnected from the chat + * + * @param evt A ParticipantStateChangedEvent + * @param isTimedOut Whether the event was received late. If so, perhaps the GUI will do something special to indicate that it is displayed out of sequence, etc.. + * @return ParticipantDisconnectedNotification + */ + function(evt, isTimedOut) + { + return new webservices.ParticipantDisconnectedNotification(evt.get_participantId(), evt.get_dateTime(), isTimedOut); + }); + + webservices.NotificationRegistry.registerNotificationType(webservices.Interfaces.IParticipantStartedTypingNotification, + /** + * Creates a ParticipantStartedTypingNotification, to indicate that someone in the chat has begun typing + * + * @param evt A ParticipantSetTypingEvent + * @param isTimedOut Whether the event was received late. If so, perhaps the GUI will do something special to indicate that it is displayed out of sequence, etc.. + * @return ParticipantStartedTypingNotification + */ + function(evt, isTimedOut) + { + return new webservices.ParticipantStartedTypingNotification(evt.get_participantId(), evt.get_dateTime(), isTimedOut); + }); + + webservices.NotificationRegistry.registerNotificationType(webservices.Interfaces.IParticipantStoppedTypingNotification, + /** + * Creates a ParticipantStoppedTypingNotification, to indicate that someone in the chat has stopped typing + * + * @param evt A ParticipantSetTypingEvent + * @param isTimedOut Whether the event was received late. If so, perhaps the GUI will do something special to indicate that it is displayed out of sequence, etc.. + * @return ParticipantStoppedTypingNotification + */ + function(evt, isTimedOut) + { + return new webservices.ParticipantStoppedTypingNotification(evt.get_participantId(), evt.get_dateTime(), isTimedOut); + }); + + webservices.NotificationRegistry.registerNotificationType(webservices.Interfaces.IReceivedTextNotification, + /** + * Creates a ReceivedTextNotification, to indicate that a message has been received + * + * @param evt A ReceivedTextEvent + * @param isTimedOut Whether the event was received late. If so, perhaps the GUI will do something special to indicate that it is displayed out of sequence, etc.. + * @return ReceivedTextNotification + */ + function(evt, isTimedOut) + { + return new webservices.ReceivedTextNotification(evt.get_participantId(), evt.get_dateTime(), evt.get_conversationSequenceNumber(), evt.get_messageText(), evt.get_contentType(), isTimedOut); + }); + + webservices.NotificationRegistry.registerNotificationType(webservices.Interfaces.IReceivedCommandNotification, + /** + * Creates a ReceivedCommandNotification, to indicate that a command has been received + * + * @param evt A ReceivedCommandEvent + * @param isTimedOut Whether the event was received late. If so, perhaps the GUI will do something special to indicate that it is displayed out of sequence, etc.. + * @return ReceivedCommandNotification + */ + function(evt, isTimedOut) + { + return new webservices.ReceivedCommandNotification(evt.get_participantId(), evt.get_dateTime(), evt.get_conversationSequenceNumber(), evt.get_command(), isTimedOut); + }); + + webservices.NotificationRegistry.registerNotificationType(webservices.Interfaces.IReceivedUrlNotification, + /** + * Creates a ReceivedUrlNotification, to indicate that a URL has been received + * + * @param evt A ReceivedUrlEvent + * @param isTimedOut Whether the event was received late. If so, perhaps the GUI will do something special to indicate that it is displayed out of sequence, etc.. + * @return ReceivedUrlNotification + */ + function(evt, isTimedOut) + { + return new webservices.ReceivedUrlNotification(evt.get_participantId(), evt.get_dateTime(), evt.get_conversationSequenceNumber(), evt.get_messageUrl(), isTimedOut); + }); + + webservices.NotificationRegistry.registerNotificationType(webservices.Interfaces.IReceivedFileNotification, + /** + * Creates a ReceivedFileNotification, to indicate that a file has been received + * + * @param evt A ReceivedFileEvent + * @param isTimedOut Whether the event was received late. If so, perhaps the GUI will do something special to indicate that it is displayed out of sequence, etc.. + * @return ReceivedFileNotification + */ + function(evt, isTimedOut) + { + return new webservices.ReceivedFileNotification(evt.get_participantId(), evt.get_dateTime(), evt.get_conversationSequenceNumber(), evt.get_messageRelativeUrl(), isTimedOut); + }); + + webservices.NotificationRegistry.registerNotificationType(webservices.Interfaces.IFailoverNotification, + /** + * Creates a FailoverNotification, to indicate that a failover has occurred + * @return FailoverNotification + */ + function() + { + return new webservices.FailoverNotification(); + }); + + webservices.NotificationRegistry.registerNotificationType(webservices.Interfaces.IFailoverUINotification, + /** + * Creates a FailoverUINotification, to indicate (to the UI) that a failover has occurred + * @return FailoverUINotification + */ + function() + { + return new webservices.FailoverUINotification(); + }); + + webservices.NotificationRegistry.registerNotificationType(webservices.Interfaces.IChatReconnectUINotification, + /** + * Creates a ChatReconnectUINotification, to indicate (to the UI) that a reconnect has occurred + * @return ChatReconnectUINotification + */ + function() + { + return new webservices.ChatReconnectUINotification(); + }); + + webservices.NotificationRegistry.registerNotificationType(webservices.Interfaces.IResumedPollingNotification, + /** + * Creates a ResumedPollingNotification, to indicate that a reconnect has occurred + * @return ResumedPollingNotification + */ + function() + { + return new webservices.ResumedPollingNotification(); + }); + + webservices.NotificationRegistry.registerNotificationType(webservices.Interfaces.IChatReconnectNotification, + /** + * Creates a ChatReconnectNotification, to indicate that a reconnect has occurred + * @return ChatReconnectNotification + */ + function() + { + return new webservices.ChatReconnectNotification(); + }); + + webservices.NotificationRegistry.registerNotificationType(webservices.Interfaces.IChatReconnectFailureNotification, + /** + * Creates a ChatReconnectFailureNotification, to indicate that a reconnect has failed to occur + * @return ChatReconnectFailureNotification + */ + function() + { + return new webservices.ChatReconnectFailureNotification(); + }); + + webservices.NotificationRegistry.registerNotificationType(webservices.Interfaces.IRefreshPageNotification, + /** + * Creates a RefreshPageNotification, to indicate that the user needs to refresh the + * page to start a new chat. + * + * @param newUriFragment The URI fragment that should now be used to reverse proxy requests through the webserver to the IC server. + * @return RefreshPageNotification + */ + function(newUriFragment) + { + return new webservices.RefreshPageNotification(newUriFragment); + }); + + webservices.NotificationRegistry.registerNotificationType(webservices.Interfaces.IPageBeforeUnloadNotification, + /** + * Creates a PageBeforeUnloadNotification, to indicate that the web browser is about to unload the web page + * + * @return PageBeforeUnloadNotification + */ + function() + { + return new webservices.PageBeforeUnloadNotification(); + }); + + webservices.NotificationRegistry.registerNotificationType(webservices.Interfaces.IPageUnloadNotification, + /** + * Creates a PageUnloadNotification, to indicate that the web browser has completed unloading the web page + * + * @return PageUnloadNotification + */ + function() + { + return new webservices.PageUnloadNotification(); + }); + + webservices.NotificationRegistry.registerNotificationType(webservices.Interfaces.IChatCreationNotification, + /** + * Creates a ChatCreationNotification, to indicate that a chat has been created + * + * @param currentParticipantId An identifier which will be used to refer to the party that created the chat. + * @param dateFormat A string specifying how dates should be formatted, as described in http://msdn.microsoft.com/en-us/library/dd317787%28v=vs.85%29.aspx + * @param timeFormat A string specifying how times should be formatted, as described in http://msdn.microsoft.com/en-us/library/dd318148%28v=vs.85%29.aspx + * @return ChatCreationNotification + */ + function(currentChatId, currentParticipantId, dateFormat, timeFormat) + { + return new webservices.ChatCreationNotification(currentChatId, currentParticipantId, dateFormat, timeFormat); + }); + + webservices.NotificationRegistry.registerNotificationType(webservices.Interfaces.IChatCreationFailureNotification, + /** + * Creates a ChatCreationFailureNotification, to indicate that an attempt to create a chat has failed + * + * @param error The error that caused the failure + * @return ChatCreationFailureNotification + */ + function(error) + { + return new webservices.ChatCreationFailureNotification(error); + }); + + webservices.NotificationRegistry.registerNotificationType(webservices.Interfaces.IChatCompletionNotification, + /** + * Creates a ChatCompletionNotification, to indicate that a chat has completed + * @return ChatCompletionNotification + */ + function() + { + return new webservices.ChatCompletionNotification(); + }); + + webservices.NotificationRegistry.registerNotificationType(webservices.Interfaces.IChatCompletionFailureNotification, + /** + * Creates a ChatCompletionFailureNotification, to indicate that an attempt to exit a chat has failed + * + * @param error The error that caused the failure + * @return ChatCompletionNotification + */ + function(error) + { + return new webservices.ChatCompletionFailureNotification(error); + }); + + webservices.NotificationRegistry.registerNotificationType(webservices.Interfaces.ICallbackCreationNotification, + /** + * Creates a CallbackCreationNotification, to indicate that a callback has been created + * + * @param participantId An identifier which will be used to refer to the party that created the callback for the duration of the current web session. + * @param callbackId ID of the callback. It may be used for the duration of the callback to bring the callback into a then-current web session. If the IVR permits, it may also be entered into the IVR to manage the callback via telephone. Will be null if the IC server does not supply it. + * @param userIdentityId ID of the user who created this callback. It may be used for the duration of the callback to bring the callback into a then-current web session. If the IVR permits, it may also be entered into the IVR to manage the callback via telephone. Will be null if the IC server does not supply it. + * @param participantName The name or username of the web user who has created the callback request + * @param telephone The telephone number at which the participant indicated they would like to be called + * @param subject The topic which the participant wishes to discuss with the agent + * @param creationDateTime A Javascript Date object containing the timestamp of when the callback request was created + * @return CallbackCreationNotification + */ + function(participantId, callbackId, userIdentityId, participantName, telephone, subject, creationDateTime) + { + return new webservices.CallbackCreationNotification(participantId, callbackId, userIdentityId, participantName, telephone, subject, creationDateTime); + }); + + webservices.NotificationRegistry.registerNotificationType(webservices.Interfaces.ICallbackCreationFailureNotification, + /** + * Creates a CallbackCreationFailureNotification, to indicate that an attempt to create a callback has failed + * + * @param error The error that caused the failure + * @return CallbackCreationFailureNotification + */ + function(error) + { + return new webservices.CallbackCreationFailureNotification(error); + }); + + webservices.NotificationRegistry.registerNotificationType(webservices.Interfaces.ICallbackStatusNotification, + /** + * Creates a CallbackStatusNotification, which contains information about a callback. + * + * @param participantId The identifier which associates the web visitor with the callback that has been queried. + * @param queueWaitTime number of seconds + * @param assignedAgentName Agent's name + * @param assignedAgentParticipantId ID identifying the agent + * @param interactionState The state of the interaction + * @param estimatedCallbackTime number of seconds before it is estimated that the callback will be made + * @param queuePosition integer indicating the callback's position in the queue + * @param queueName the name of the queue + * @param longestWaitTime TODO write what this is! + * @param interactionsWaitingCount The number of calls waiting + * @param loggedInAgentsCount the number of logged in agents who meet this callback's routing critieria + * @param availableAgentsCount the number of available agents who meet this callback's routing criteria + * @param statusIndicator A key to quickly indicate the status of the callback + * @return CallbackStatusNotification + */ + function(participantId, queueWaitTime, assignedAgentName, assignedAgentParticipantId, + interactionState, estimatedCallbackTime, + queuePosition, queueName, + longestWaitTime, interactionsWaitingCount, loggedInAgentsCount, availableAgentsCount, + statusIndicator) + { + return new webservices.CallbackStatusNotification(participantId, queueWaitTime, + assignedAgentName, assignedAgentParticipantId, + interactionState, estimatedCallbackTime, + queuePosition, queueName, + longestWaitTime, interactionsWaitingCount, + loggedInAgentsCount, availableAgentsCount, + statusIndicator); + }); + + webservices.NotificationRegistry.registerNotificationType(webservices.Interfaces.ICallbackStatusFailureNotification, + /** + * Creates a CallbackStatusFailureNotification, to indicate that a request to get a Callback's status has failed. + * + * @param error The error that caused the failure + * @return CallbackStatusFailureNotification + */ + function(error) + { + return new webservices.CallbackStatusFailureNotification(error); + }); + + webservices.NotificationRegistry.registerNotificationType(webservices.Interfaces.ICallbackDisconnectNotification, + /** + * Creates a CallbackDisconnectNotification, which indicates that a Callback has been disconnected + * + * @param participantId The identifier which associated the web visitor with the callback that has been disconnected. This identifier is no longer valid, and is included so that consumers of this notification may remove it from their data structures, user interfaces, etc. + * @return CallbackDisconnectNotification + */ + function(participantId) + { + return new webservices.CallbackDisconnectNotification(participantId); + }); + + webservices.NotificationRegistry.registerNotificationType(webservices.Interfaces.ICallbackDisconnectFailureNotification, + /** + * Creates a CallbackDisconnectFailureNotification, which indicates that a request to disconnect a Callback has failed. + * + * @param error The error that caused the failure + * @return CallbackDisconnectFailureNotification + */ + function(error) + { + return new webservices.CallbackDisconnectFailureNotification(error); + }); + + webservices.NotificationRegistry.registerNotificationType(webservices.Interfaces.ICallbackReconnectNotification, + /** + * Creates a CallbackReconnectNotification, which indicates that a Callback has + * been reconnected (i.e. brought into the current web session) + * + * @param participantId An identifier which will be used to refer to the party that created the callback. + * @return CallbackReconnectNotification + */ + function(participantId) + { + return new webservices.CallbackReconnectNotification(participantId); + }); + + webservices.NotificationRegistry.registerNotificationType(webservices.Interfaces.ICallbackReconnectFailureNotification, + /** + * Creates a CallbackReconnectFailureNotification, which indicates that a request to reconnect a Callback has failed. + * + * @param error The error that caused the failure + * @return CallbackReconnectFailureNotification + */ + function(error) + { + return new webservices.CallbackReconnectFailureNotification(error); + }); + + webservices.NotificationRegistry.registerNotificationType(webservices.Interfaces.IPartyInfoNotification, + /** + * Creates a PartyInfoNotification, which contains the name and photo location of a party involved in + * an interaction. + * + * @param localParticipantId The participantId of the agent whose info is being queried. + * @param remoteParticipantId The participantId of the agent whose info is being queried. + * @param name The name of the remote participant + * @param photo Location of the photo of the remote participant + * @return PartyInfoNotification + */ + function(localParticipantId, remoteParticipantId, name, photo) + { + return new webservices.PartyInfoNotification(localParticipantId, remoteParticipantId, name, photo); + }); + + webservices.NotificationRegistry.registerNotificationType(webservices.Interfaces.IPartyInfoFailureNotification, + /** + * Creates a PartyInfoFailureNotification, which indicates that a request to reconnect a Callback has failed. + * + * @param error The error that caused the failure + * @return PartyInfoFailureNotification + */ + function(error) + { + return new webservices.PartyInfoFailureNotification(error); + }); + + webservices.NotificationRegistry.registerNotificationType(webservices.Interfaces.ICurrentParticipantIdChangedNotification, + /** + * Creates a CurrentParticipantIdChangedNotification, which indicates that the GUID used to identify the web user has changed to another GUID. + * + * @param participantId The new ID of the current participant + * @param dateTime Timestamp for the notification. Javascript Date object. + * @param isTimedOut Whether the event was received late. If so, perhaps the GUI will do something special to indicate that it is displayed out of sequence, etc.. + * @return CurrentParticipantIdChangedNotification + */ + function(participantId, dateTime, isTimedOut) + { + return new webservices.CurrentParticipantIdChangedNotification(participantId, dateTime, isTimedOut); + }); + } +}); + +// Register namespaces +webservices.registerChildNamespace("_Internal"); + +/** + * FailoverHandlerBase class + * + * In charge of connecting to the other server if the current one goes down for some reason. + */ +webservices._Internal._FailoverHandlerBase = Class.create(common.InterfaceImplementation, +{ + /** + * Constructor + * + * @param capabilityRepository Instance of CapabilityRepository, so we'll know how to get the new server's Capabilities + */ + initialize : function($super, capabilityRepository) + { + common.Debug.traceMethodEntered("FailoverHandlerBase.initialize()"); + var numArgs = 2; + if(arguments.length != numArgs) + { + throw common.ExceptionFactory.createException("FailoverHandlerBase constructor called with " + arguments.length + " arguments, but expected " + numArgs + "."); + } + + common.ParameterValidation.validate([capabilityRepository], [ {"required": true} ]); + + $super(); + + this.addImplementedInterface(webservices.Interfaces.IFailoverNotificationObserver, webservices); + this.addImplementedInterface(webservices.Interfaces.IChatReconnectFailureNotificationObserver, webservices); + + // Observe additional events, so that we know which type(s) of interaction to attempt to reconnect + this.addImplementedInterface(webservices.Interfaces.IChatCreationNotification, webservices); + this.addImplementedInterface(webservices.Interfaces.IChatCompletionNotification, webservices); + this.addImplementedInterface(webservices.Interfaces.ICallbackCreationNotification, webservices); + this.addImplementedInterface(webservices.Interfaces.ICallbackDisconnectNotification, webservices); + webservices.NotificationRegistry.registerObserver(this, webservices.Interfaces.IChatCreationNotification); + webservices.NotificationRegistry.registerObserver(this, webservices.Interfaces.IChatCompletionNotification); + webservices.NotificationRegistry.registerObserver(this, webservices.Interfaces.ICallbackCreationNotification); + webservices.NotificationRegistry.registerObserver(this, webservices.Interfaces.ICallbackDisconnectNotification); + + this._capabilityRepository = capabilityRepository; + this._chatManager = null; + this._callbackManager = null; + this._attemptingReconnectToFirstServer = true; + this._attemptingReconnect = false; + this._foundServerToUseForReconnect = false; + this._shouldAttemptToReconnectChat = false; + this._shouldAttemptToReconnectCallback = false; + this._reconnectAttempts = 0; + + this.RECONNECT_MAX_ATTEMPTS = 12; // per server + this.RECONNECT_INTERVAL_MILLISECONDS = 2000; + + common.Debug.traceMethodExited("FailoverHandlerBase.initialize()"); + }, + + /** + * Destructor + */ + destroy : function() + { + common.Debug.traceMethodEntered("FailoverHandlerBase.destroy()"); + common.InterfaceImplementation.prototype.destroy.call(this); + common.Debug.traceMethodExited("FailoverHandlerBase.destroy()"); + }, + + // public methods + + /** + * Setter for the ChatManager property + * + * @param chatManager An instance of a subclass of ChatManagerBase + */ + set_chatManager : function(chatManager) + { + this._chatManager = chatManager; + }, + + /** + * Setter for the CallbackManager property + * + * @param callbackManager An instance of a subclass of CallbackManagerBase + */ + set_callbackManager : function(callbackManager) + { + this._callbackManager = callbackManager; + }, + + /** + * Listener for FailoverNotifications. If one is received, this method + * will attempt to reconnect the interaction(s) to other servers. + * + * @param notification A FailoverNotification + */ + processFailoverNotification : function(notification) + { + common.Debug.traceMethodEntered("FailoverHandlerBase.processFailoverNotification()"); + common.Interface.ensureImplements(notification, webservices.Interfaces.IFailoverNotification); + + this.attemptReconnectingToAllAvailableServers(); + + common.Debug.traceMethodExited("FailoverHandlerBase.processFailoverNotification()"); + }, + + /** + * Listener for ChatReconnectFailureNotifications. If one is received, this method will set a timer so that another attempt will + * be made once the timer fires. + * + * @param notification A ChatReconnectFailureNotification + */ + processChatReconnectFailureNotification : function(notification) + { + common.Debug.traceMethodEntered("FailoverHandlerBase.processChatReconnectFailureNotification()"); + common.Interface.ensureImplements(notification, webservices.Interfaces.IChatReconnectFailureNotification); + + // attempt a connection + // for switchover, this means each server will be tried on every other timeout + // for single IC systems, this means the server will be tried on every timeout + var retryCounts = webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.RetryCounts); + var minDelay = retryCounts.get_reconnectTimeoutMillisecondsMinimum(); + var maxDelay = retryCounts.get_reconnectTimeoutMillisecondsMaximum(); + var delay = webservices.Utilities.randomInRange(minDelay, maxDelay); + common.Debug.traceNote("Delaying for " + delay + " milliseconds before attemptReconnectingToAllAvailableServers"); + window.setTimeout(this.onTimerExpired.bindAsEventListener(this), delay); + common.Debug.traceMethodExited("FailoverHandlerBase.processChatReconnectFailureNotification()"); + }, + + /** + * Callback that is called when the timer that was set in processChatReconnectFailureNotification() fires. + */ + onTimerExpired : function() + { + common.Debug.traceMethodEntered("FailoverHandlerBase.onTimerExpired()"); + this.attemptReconnectingToAllAvailableServers(); + common.Debug.traceMethodExited("FailoverHandlerBase.onTimerExpired()"); + }, + + /** + * Begins the process of looping through the known servers and attempting to reconnect to each one, until one succeeds. + */ + attemptReconnectingToAllAvailableServers : function() + { + common.Debug.traceMethodEntered("FailoverHandlerBase.attemptReconnectingToAllAvailableServers()"); + // there is another server to try so try connecting to it + if(!this._attemptingReconnect) + { + this._attemptingReconnect = true; + this._attemptingReconnectToFirstServer = true; + this._reconnectAttempts = 0; + this._requestServerConfiguration(); + } + common.Debug.traceMethodExited("FailoverHandlerBase.attemptReconnectingToAllAvailableServers()"); + }, + + // private methods + + _requestServerConfiguration : function() + { + common.Debug.traceMethodEntered("FailoverHandlerBase._requestServerConfiguration()"); + common.Debug.traceAlways("Attempting reconnect to: " + webservices.Servers.CurrentUriFragment); + this._requestServerConfigurationAsync(); + common.Debug.traceMethodExited("FailoverHandlerBase._requestServerConfiguration()"); + }, + + _requestServerConfigurationAsync : function() + { + common.Debug.traceMethodEntered("FailoverHandlerBase._requestServerConfigurationAsync()"); + webservices.Json.ServerConfigurationManager.getServerConfiguration(this._serverConfigurationCallback.bind(this)); + common.Debug.traceMethodExited("FailoverHandlerBase._requestServerConfigurationAsync()"); + }, + + /** + * Called upon completion of an attempt to reconnect a Chat, + * whether the attempt was successful or not. + */ + _onChatReconnectAttemptComplete : function (success, response) + { + this._attemptingReconnect = false; + + if(success) + { + // let the application know that we've reconnected + var notification = webservices.NotificationFactory.createChatReconnectNotification(); + setTimeout(function(){webservices.NotificationRegistry.process(notification);}, 10); + } + else + { + //the reconnect attempt gave an error; give up. + var notification = webservices.NotificationFactory.createChatReconnectFailureNotification(response.get_statusReason()); + webservices.NotificationRegistry.process(notification); + + common.Debug.traceError("Error reconnecting to the chat. Displaying refresh notification."); + webservices.NotificationRegistry.process(webservices.NotificationFactory.createRefreshPageNotification(webservices.Servers.CurrentUriFragment)); + } + }, + + /** + * Called upon completion of an attempt to reconnect a Callback, + * whether the attempt was successful or not. + */ + _onCallbackReconnectAttemptComplete : function (success, response) + { + this._attemptingReconnect = false; + + if(success) + { + // let the application know that we've reconnected + var notification = webservices.NotificationFactory.createCallbackReconnectNotification(); + setTimeout(function(){webservices.NotificationRegistry.process(notification);}, 10); + } + else + { + //the reconnect attempt gave an error; give up. + var notification = webservices.NotificationFactory.createCallbackReconnectFailureNotification(response.get_statusReason()); + webservices.NotificationRegistry.process(notification); + + common.Debug.traceError("Error reconnecting to the callback. Displaying refresh notification."); + webservices.NotificationRegistry.process(webservices.NotificationFactory.createRefreshPageNotification(webservices.Servers.CurrentUriFragment)); + } + }, + + _serverConfigurationCallback : function(success, failureReason) + { + common.Debug.traceMethodEntered("FailoverHandlerBase._serverConfigurationCallback()"); + if(this._shouldUseServerForReconnect(success, failureReason)) + { + common.Debug.traceAlways("Using " + webservices.Servers.CurrentUriFragment + " for reconnect..."); + + _self = this; + + if (! this._attemptingReconnectToFirstServer) + { + // We have successfully switched to the new server. + common.Debug.traceAlways("Failover completed successfully"); + + // TODO: Implement the infrastructure for this: + //webservices.NotificationRegistry.process(webservices.NotificationFactory.createFailoverCompletedNotification()); + } + + this._reconnectAttempts = 0; + + //call the reconnect(s) + if (this._shouldAttemptToReconnectChat) { + this._chatManager.reconnect(function(success, response){_self._onChatReconnectAttemptComplete(success, response);}); + } + if (this._shouldAttemptToReconnectCallback) { + this._callbackManager.reconnect(function(success, response){_self._onCallbackReconnectAttemptComplete(success, response);}); + } + } + else + { + common.Debug.traceAlways("Not using " + webservices.Servers.CurrentUriFragment + " for reconnect..."); + + if(this._attemptingReconnectToFirstServer && webservices.Servers.isConfiguredForSwitchover()) + { + common.Debug.traceAlways("Tried connecting to first server and failed. It's switchover so try the other server after " + this.RECONNECT_INTERVAL_MILLISECONDS + "ms."); + webservices.Servers.switchCurrentServer(); + this._attemptingReconnectToFirstServer = false; + window.setTimeout(this._requestServerConfiguration.bind(this), this.RECONNECT_INTERVAL_MILLISECONDS); + } + else + { + if(webservices.Servers.isConfiguredForSwitchover()) + { + common.Debug.traceAlways("Failed to connect to both IC servers in the switchover pair"); + // switch back to original server for next reconnect attempt if we're switchover + webservices.Servers.switchCurrentServer(); + } + else + { + common.Debug.traceAlways("Failed to connect to the IC server"); + } + + if (this._reconnectAttempts < this.RECONNECT_MAX_ATTEMPTS) + { + this._attemptingReconnect = false; + this._reconnectAttempts++; + common.Debug.traceWarning("Reconnect attempt #" + this._reconnectAttempts + " failed. Cycling after " + this.RECONNECT_INTERVAL_MILLISECONDS + "ms."); + window.setTimeout(this.attemptReconnectingToAllAvailableServers.bind(this), this.RECONNECT_INTERVAL_MILLISECONDS); + } +/* + else + { + // It's a total failure + common.Debug.traceNote("Sending out reconnect failure notification."); + } +*/ + } + } + common.Debug.traceMethodExited("FailoverHandlerBase._serverConfigurationCallback()"); + }, + + _shouldUseServerForReconnect : function(success, failureReason) + { + common.Debug.traceMethodEntered("FailoverHandlerBase._shouldUserServerForReconnect()"); + // for a server to be considered for the reconnect: + // 1) the request had to have succeeded + // 2) the server should have the minimum server config necessary + var returnValue = success && this._doesServerHaveMinimumServerConfig(); + + common.Debug.traceNote("Returning: " + returnValue); + common.Debug.traceMethodExited("FailoverHandlerBase._shouldUserServerForReconnect()"); + return returnValue; + }, + + _doesServerHaveMinimumServerConfig : function() + { + common.Debug.traceMethodEntered("FailoverHandlerBase._doesServerHaveMinimumServerConfig()"); + + var returnValue = ( webservices.CapabilityRepository.get_reconnectChatCapability() && + webservices.CapabilityRepository.get_reconnectCallbackCapability()); + + common.Debug.traceNote("Returning: " + returnValue); + common.Debug.traceMethodExited("FailoverHandlerBase._doesServerHaveMinimumServerConfig()"); + return returnValue; + }, + + processChatCreationNotification : function(chatCreationNotification) + { + this._shouldAttemptToReconnectChat = true; + }, + + processChatCompletionNotification : function(chatCompletionNotification) + { + this._shouldAttemptToReconnectChat = false; + }, + + processCallbackCreationNotification : function(callbackCreationNotification) + { + this._shouldAttemptToReconnectCallback = true; + }, + + processCallbackDisconnectNotification : function(callbackDisconnectNotification) + { + this._shouldAttemptToReconnectCallback = false; + } +}); + + + +// Register namespaces +webservices.registerChildNamespace("Json._Internal"); + +/** + * FailoverHandler class + * JSON-specific subclass of FailoverHandlerBase. Ensures the use of webservices.Json.AjaxManager. + */ +webservices.Json._Internal._FailoverHandler = Class.create(webservices._Internal._FailoverHandlerBase, +{ + /** + * Constructor + * + * @param genericResponseBuilder The object that shall be used to translate the IC server's HTTP reply into a ResponseBase (or subclass thereof) + * @param capabilityRepository An instance of CapabilityRepository, in which the capabilities are stored. + */ + initialize: function($super, genericResponseBuilder, capabilityRepository) + { + $super(capabilityRepository); + + this._genericResponseBuilder = genericResponseBuilder; + }, + + /** + * Destructor + */ + destroy : function() + { + webservices._Internal._FailoverHandlerBase.prototype.destroy.call(this); + }, + + // methods + + /** + * Overload of FailoverHandlerBase.set_chatManager(). + * + * @param chatManager The ChatManager currently in use + */ + set_chatManager : function(chatManager) + { + webservices._Internal._FailoverHandlerBase.prototype.set_chatManager.call(this, chatManager); + }, + + /** + * FailoverHandlerBase will call this, to get an instance of (a subclass of) AjaxManagerBase. + * + * @param capability A Capability object representing what this AjaxManager is intended to do (i.e. poll, send message, etc.) + * @param serverUriFragment The URI fragment that reverse proxies to the IC server. + * @return AjaxManager + */ + _createAjaxManager : function(capability, serverUriFragment) + { + return new webservices.Json.AjaxManager(this._genericResponseBuilder, capability, serverUriFragment); + }, + + /** + * Overload of FailoverHandlerBase.processFailoverNotification(). + * + * @param notification Something that implements IFailoverNotification + */ + processFailoverNotification : function(notification) + { + webservices._Internal._FailoverHandlerBase.prototype.processFailoverNotification.call(this, notification); + } +}); + +/** + * MessageData class + * Represents something that someone has typed in a chat. Consists of the name of the person + * who typed something, what they typed, and when they typed it. + */ +webservices.MessageData = Class.create(common.InterfaceImplementation, +{ + /** + * constructor + * @param date - When the message was typed + * @param name - Who typed the message + * @param text - What was typed + */ + initialize : function($super, date, name, text) + { + var numArgs = 4; + if(arguments.length != numArgs) + { + throw common.ExceptionFactory.createException("MessageData constructor called with " + arguments.length + " arguments, but expected " + numArgs + "."); + } + + $super(); + + this.addImplementedInterface(webservices.Interfaces.IMessageData, webservices); + + this._date = date; + this._name = name; + this._text = text; + }, + + // public methods + + /** + * Returns the timestamp of when the message was typed + */ + get_date : function() + { + return this._date; + }, + + /** + * Returns the name of the person who typed the message + */ + get_name : function() + { + return this._name; + }, + + /** + * Returns the message that was typed + */ + get_text : function() + { + return this._text; + } +}); + +// Register namespaces +webservices.registerChildNamespace("_Internal"); + +/** + * ReceivedMessageRepository class + * + * This class stores all the messages sent during the chat. Only one instance is needed. + */ +webservices._Internal._ReceivedMessageRepository = Class.create(common.InterfaceImplementation, +{ + /** + * Constructor + */ + initialize:function($super) + { + var numArgs = 1; + if(arguments.length != numArgs) + { + throw common.ExceptionFactory.createException("ReceivedMessageRepository constructor called with " + arguments.length + " arguments, but expected " + numArgs + "."); + } + + $super(); + + this.addImplementedInterface(webservices.Interfaces.IReceivedTextNotificationObserver, webservices); + this.addImplementedInterface(webservices.Interfaces.IReceivedUrlNotificationObserver, webservices); + this.addImplementedInterface(webservices.Interfaces.IReceivedFileNotificationObserver, webservices); + + this._receivedMessages = []; + this._lastConversationSequenceNumber = null; + this._linkifier = webservices.CustomizationFactoryRegistry.get_instance(webservices.CustomizableSingletonFactoryTypes.Linkifier); + }, + + /** + * Destructor + */ + destroy : function() + { + if(this._receivedMessages) + { + delete this._receivedMessages; + this._receivedMessages = null; + } + + common.InterfaceImplementation.prototype.destroy.call(this); + }, + + // public methods + + /** + * Called when the web user receives a message from an agent + * + * @param notification An instance of ReceivedTextNotification + */ + processReceivedTextNotification : function(notification) + { + common.Interface.ensureImplements(notification, webservices.Interfaces.IReceivedTextNotification); + this.addMessage(notification.get_dateTime(), notification.get_participantId(), this._linkifier.linkifyText(notification.get_messageText())); + this._lastConversationSequenceNumber = notification.get_conversationSequenceNumber(); + }, + + /** + * Called when the web user receives a URL from an agent + * + * @param notification An instance of ReceivedUrlNotification + */ + processReceivedUrlNotification : function(notification) + { + common.Interface.ensureImplements(notification, webservices.Interfaces.IReceivedUrlNotification); + this.addMessage(notification.get_dateTime(), notification.get_participantId(), this._linkifier.linkifyText(notification.get_messageUrl())); + this._lastConversationSequenceNumber = notification.get_conversationSequenceNumber(); + }, + + /** + * Called when the web user receives a file from an agent + * + * @param notification An instance of ReceivedFileNotification + */ + processReceivedFileNotification : function(notification) + { + common.Interface.ensureImplements(notification, webservices.Interfaces.IReceivedFileNotification); + var text = this._getFileName(notification.get_messageRelativeUrl()); + var url = this._createFullUrl(notification.get_messageRelativeUrl()); + var message = '' + text + ''; + this.addMessage(notification.get_dateTime(), notification.get_participantId(), message); + this._lastConversationSequenceNumber = notification.get_conversationSequenceNumber(); + }, + + /** + * Each message (text, file, or URL) in the chat has a sequence number, so that they may be kept in the proper order. + * This method returns the highest sequence number that has been used so far. + */ + get_lastConversationSequenceNumber : function() + { + return this._lastConversationSequenceNumber; + }, + + /** + * Adds a message to the repository. + * + * @param dateTime When the message was added + * @param participantId Who sent the text, file, or URL + * @param text The text to store in the repository. For instance, if a file was received, this could be HTML code to provide a link to the file. + */ + addMessage : function(dateTime, participantId, text) + { + // can't just check if(!text), since we want to let empty strings through + if(text === undefined) + { + throw common.ExceptionFactory.createException("text parameter to addMessage is undefined"); + } + if(text === null) + { + throw common.ExceptionFactory.createException("text parameter to addMessage is null"); + } + if(typeof text != "string") + { + throw common.ExceptionFactory.createException("text parameter to addMessage is not a string"); + } + + var participantName = this._getParticipantName(participantId); + this._receivedMessages.push(new webservices.MessageData(dateTime, participantName, text)); + }, + + /** + * Get the list of messages that are in the repository + * + * @return An array of MessageData objects. + */ + get_messages : function() + { + return this._receivedMessages; + }, + + /** + * Resets the repository to its default state. + */ + reset : function() + { + this._receivedMessages = []; + }, + + // private methods + + _getFileName : function(relativeUrl) + { + return webservices.Utilities.getFileNameFromUrl(relativeUrl); + }, + + _createFullUrl : function(relativeUrl) + { + return webservices.Servers.buildUrl(webservices.Servers.CurrentUriFragment, relativeUrl); + }, + + _getParticipantName : function(participantId) + { + var participant = webservices.ParticipantRepository.get_participant(participantId); + if(participant) + { + return participant.get_name(); + } + + return "IC"; + } +}); + +// Register namespaces +webservices.registerChildNamespace("_Internal"); + +/** + * ReceivedUrlRepository class + * + * A place to store the URLs which are sent to the web user by the agent(s) + */ +webservices._Internal._ReceivedUrlRepository = Class.create(common.InterfaceImplementation, +{ + /** + * Constructor + */ + initialize:function($super) + { + var numArgs = 1; + if(arguments.length != numArgs) + { + throw common.ExceptionFactory.createException("ReceivedUrlRepository constructor called with " + arguments.length + " arguments, but expected " + numArgs + "."); + } + + $super(); + + this.addImplementedInterface(webservices.Interfaces.IReceivedUrlNotificationObserver, webservices); + + this._receivedUrls = []; + }, + + /** + * Destructor + */ + destroy : function() + { + if(this._receivedUrls) + { + delete this._receivedUrls; + this._receivedUrls = null; + } + + common.InterfaceImplementation.prototype.destroy.call(this); + }, + + // public methods + + /** + * Called when the web user receives a URL from an agent + * + * @param notification An instance of ReceivedUrlNotification + */ + processReceivedUrlNotification : function(notification) + { + common.Interface.ensureImplements(notification, webservices.Interfaces.IReceivedUrlNotification); + this.addUrl(notification.get_messageUrl()); + }, + + /** + * Adds a URL to the repository. Mostly exists as a helper to processReceivedUrlNotification(). + * + * @param url The URL to add to the repository. + */ + addUrl : function(url) + { + if(!url) + { + throw common.ExceptionFactory.createException("Url parameter to addUrl is null/undefined"); + } + if(url.length === 0) + { + throw common.ExceptionFactory.createException("Url parameter to addUrl is empty"); + } + if(typeof url != "string") + { + throw common.ExceptionFactory.createException("Url parameter to addUrl is not a string"); + } + + this._receivedUrls.push(url); + }, + + /** + * Gets the set of URLs that are in the repository + * + * @return list of URLs + */ + get_urls : function() + { + return this._receivedUrls; + }, + + /** + * Resets the repository to its default state. Removes all URLs. + */ + reset : function() + { + this._receivedUrls = []; + } +}); + +/** + * ChatPropertyUpdateRegistry class + * + * Notifies interested parties about changes to properties of the chat. Currently this only includes the poll wait time suggestion. + */ +webservices.ChatPropertyUpdateRegistry = (function() +{ + // private + var _pollWaitSuggestionUpdateObservers = []; + + // public methods + return { + + /** + * Destructor + */ + destroy : function() + { + delete _pollWaitSuggestionUpdateObservers; + _pollWaitSuggestionUpdateObservers = null; + }, + + /** + * Parties (i.e. other objects) interested in being notified about changes to poll wait time suggestion + * may call this method. If they do so, their processPollWaitSuggestionUpdate() method will be + * called when this property changes. + * + * @param pollWaitSuggestionUpdateObserver An object with a processPollWaitSuggestionUpdate() method + */ + registerPollWaitSuggestionUpdateObserver : function(pollWaitSuggestionUpdateObserver) + { + if(!pollWaitSuggestionUpdateObserver) + { + throw common.ExceptionFactory.createException("pollWaitSuggestionUpdateObserver is null"); + } + + common.Interface.ensureImplements(pollWaitSuggestionUpdateObserver, webservices.Interfaces.IPollWaitSuggestionUpdateObserver); + + _pollWaitSuggestionUpdateObservers.push(pollWaitSuggestionUpdateObserver); + }, + + /** + * This will be called when the poll wait time suggestion changes. It will notify each interested party (i.e. object) + * of this by calling its processPollWaitSuggestionUpdate() method. + * + * @param pollWaitSuggestion A number (or string containing a number) indicating the number of milliseconds to wait before the next poll. + */ + processPollWaitSuggestionUpdate : function(pollWaitSuggestion) + { + if(!pollWaitSuggestion) + { + throw common.ExceptionFactory.createException("pollWaitSuggestion is null"); + } + + if(typeof pollWaitSuggestion == "string") + { + if(pollWaitSuggestion.length === 0) + { + throw common.ExceptionFactory.createException("pollWaitSuggestion is not an empty string"); + } + + // convert it to a number and let the number branch handle it + pollWaitSuggestion = parseInt(pollWaitSuggestion, 10); + } + + if(isNaN(pollWaitSuggestion)) + { + throw common.ExceptionFactory.createException("pollWaitSuggestion is not a number"); + } + + if(typeof pollWaitSuggestion != "number") + { + throw common.ExceptionFactory.createException("pollWaitSuggestion is not a number"); + } + + for(var i = 0; i < _pollWaitSuggestionUpdateObservers.length; ++i) + { + var observer = _pollWaitSuggestionUpdateObservers[i]; + observer.processPollWaitSuggestionUpdate(pollWaitSuggestion); + } + } + }; + +})(); + +/** + * Participant class + * This class represents a participant in an interaction. + * + * If a person is participating in two interactions, there will be two + * different Participant instances created to represent that situation. + */ +webservices.Participant = Class.create(common.InterfaceImplementation, +{ + // public methods + + /** + * Constructor + * + * @param id A unique ID representing this person participating in this interaction. + * @param name The name of this participant. + * @param interactionType The type of interaction in which the person is participating. A constant defined in webservices.InteractionTypes. + * @param isActive Whether this participant is currently active in the chat. + */ + initialize: function($super, id, name, interactionType, isActive) + { + common.ParameterValidation.validate([id, name, isActive], [ {"type": String, "allowEmpty": false, "required": true}, {"type": String, "allowEmpty": true, "required": false}, {"type": Boolean, "required": false}]); + + $super(); + + this.addImplementedInterface(webservices.Interfaces.IParticipant, webservices); + + this._id = id; + this._name = name; + this._interactionType = interactionType; + this._photo = null; + + if(isActive === undefined) + { + this._isActive = true; + } + else + { + this._isActive = isActive; + } + }, + + /** + * Gets the ID of the participant + * + * @return The ID of the participant. + */ + get_id : function() + { + return this._id; + }, + + /** + * Gets the name of the participant + * + * @return The name of the participant. + */ + get_name : function() + { + return this._name; + }, + + /** + * Sets the name of the participant + * + * @param newName The name of the participant. + */ + set_name : function(newName) + { + this._name = newName; + }, + + /** + * Gets the type of interaction in which the participant is participating + * + * @return A constant defined in webservices.InteractionTypes. + */ + get_interactionType : function() + { + return this._interactionType; + }, + + /** + * Gets the photo of the participant + * + * @return The photo of the participant. + */ + get_photo : function() + { + return this._photo; + }, + + /** + * Sets the photo of the participant + * + * @param photo The photo of the participant. + */ + set_photo : function(photo) + { + this._photo = photo; + }, + + /** + * Gets whether the participant is currently active in the chat. + * + * @return True if the participant is active, false if the participant is inactive. + */ + get_isActive : function() + { + return this._isActive; + }, + + /** + * Sets whether the participant is currently active in the chat. + * + * @param active True if the participant is active, false if the participant is inactive. + */ + set_isActive : function(active) + { + this._isActive = active; + } +}); + + +/** + * ParticipantRepositoryBase class + * + * Abstract class. Keeps track of which participants are participating in an + * interaction (i.e. a chat, callback, etc.), their status, and which one + * is the current chat participant (i.e. the one whose web browser is running this code). + */ +webservices.ParticipantRepositoryBase = Class.create(common.InterfaceImplementation, +{ + // constants + + /** + * The participant ID that is used for messages from the IC system itself. + */ + SYSTEM_SENDER_ID : '00000000-0000-0000-0000-000000000000', + + /** + * Constructor + */ + initialize : function($super) + { + var numArgs = 1; + if(arguments.length != numArgs) + { + throw common.ExceptionFactory.createException("ParticipantsRepositoryBase constructor called with " + arguments.length + " arguments, but expected " + numArgs + "."); + } + + $super(); + + this.addImplementedInterface(webservices.Interfaces.IParticipantRepository, webservices); + + // initialize private members + this._currentParticipantId = null; + this._participants = []; + }, + + /** + * Destructor + */ + destroy : function() + { + common.InterfaceImplementation.prototype.destroy.call(this); + + delete this._participants; + this._participants = null; + }, + + // public methods + + /** + * Gets the ID of the current participant (i.e. the one whose web browser is running this code) + * + * @return ID of the current participant + */ + get_currentParticipantId : function() + { + return this._currentParticipantId; + }, + + /** + * Gets the participant ID that is used for messages from the IC system itself. + * + * @return ID of the "system" participant + */ + get_systemParticipantId : function() + { + return this.SYSTEM_SENDER_ID; + }, + + /** + * Add another participant to the repository + * + * @param participant An instance of webservices.Participant + */ + addParticipant : function(participant) + { + common.Debug.traceMethodEntered("ParticipantRepositoryBase.addParticipant()"); + + if(!participant) + { + throw common.ExceptionFactory.createException("Participant parameter to addParticipant is null/undefined"); + } + if(typeof participant != "object") + { + throw common.ExceptionFactory.createException("Participant parameter to addParticipant is not an object"); + } + + common.Interface.ensureImplements(participant, webservices.Interfaces.IParticipant); + + // Don't want the web user's username to be an HTML form, a Javascript function call, etc.! + participant.set_name(webservices.Utilities.escapeHTML(participant.get_name())); + + // TODO: this should probably be locked + this._participants.push(participant); + common.Debug.traceMethodExited("ParticipantRepositoryBase.addParticipant()"); + }, + + /** + * Remove a participant from the repository + * + * @param participantId The ID of a participant already involved in an interaction + * @return true If the participant was found and removed, false if the ID did not match any participant involved in any interaction + */ + removeParticipant : function(participantId) + { + if(!participantId) + { + throw common.ExceptionFactory.createException("participantId parameter to removeParticipant is null/undefined"); + } + if(typeof participantId != "string") + { + throw common.ExceptionFactory.createException("participantId parameter to removeParticipant is not a string"); + } + + // TODO: this should be locked + for(var i = 0; i < this._participants.length; ++i) + { + var participant = this._participants[i]; + + common.Interface.ensureImplements(participant, webservices.Interfaces.IParticipant); + if(participantId == participant.get_id()) + { + this._participants.splice(i, 1); + return true; + } + } + + return false; + }, + + /** + * Removes all participants of a particular interaction type + * + * @param interactionType A constant defined in webservices.InteractionTypes + */ + removeAllParticipantsForInteractionType : function(interactionType) + { + if (!webservices.InteractionTypes.validate(interactionType)) + { + throw common.ExceptionFactory.createException("interactionType parameter to removeAllParticipantsForInteractionType is not valid"); + } + + var i=0; + // TODO: this should be locked + while (i < this._participants.length) + { + var participant = this._participants[i]; + + common.Interface.ensureImplements(participant, webservices.Interfaces.IParticipant); + if (interactionType == participant.get_interactionType()) + { + this._participants.splice(i, 1); + } + else + { + ++i; + } + } + }, + + /** + * Change the name of a participant in an interaction + * + * @param participantId The ID of the participant whose name has changed. + * @param newParticipantName The new name of the participant identified by participantId + */ + changeParticipantName : function(participantId, newParticipantName) + { + common.Debug.traceMethodEntered("ParticipantRepositoryBase.changeParticipantName()"); + if(!participantId) + { + throw common.ExceptionFactory.createException("participantId parameter to changeParticipantName is null/undefined"); + } + if(typeof participantId != "string") + { + throw common.ExceptionFactory.createException("participantId parameter to changeParticipantName is not a string"); + } + + // Don't want the web user's username to be an HTML form, a Javascript function call, etc.! + newParticipantName = webservices.Utilities.escapeHTML(newParticipantName); + + // TODO: this should be locked + for(var i = 0; i < this._participants.length; ++i) + { + var participant = this._participants[i]; + + common.Interface.ensureImplements(participant, webservices.Interfaces.IParticipant); + if(participantId == participant.get_id()) + { + participant.set_name(newParticipantName); + break; + } + } + common.Debug.traceMethodExited("ParticipantRepositoryBase.changeParticipantName()"); + }, + + /** + * Participants in a chat can be active or inactive. This method may be used to mark a participant as active. + * Currently not used for any other interaction type. + * + * @param participantId The ID of the participant who has just become active + */ + markParticipantAsActive : function(participantId) + { + var participant = this.get_participant(participantId); + if(participant) + { + participant.set_isActive(true); + } + }, + + /** + * Participants in a chat can be active or inactive. This method may be used to mark a participant as inactive. + * Currently not used for any other interaction type. + * + * @param participantId The ID of the participant who has just become inactive + */ + markParticipantAsInactive : function(participantId) + { + var participant = this.get_participant(participantId); + if(participant) + { + participant.set_isActive(false); + } + }, + + /** + * Returns an array of all the participants in the repository + * + * @return array of webservices.Participant + */ + get_participants : function() + { + return this._participants; + }, + + /** + * Gets a participant, given the participant's ID + * + * @param participantId The ID of a participant in an interaction. + * @return An webservices.Participant, if the ID belongs to someone in an interaction. Null otherwise. + */ + get_participant : function(participantId) + { + if(!participantId) + { + throw common.ExceptionFactory.createException("participantId parameter to get_participant is null/undefined"); + } + if(typeof participantId != "string") + { + throw common.ExceptionFactory.createException("participantId parameter to get_participant is not a string"); + } + + // TODO: lock this with a mutex + for(var i = 0; i < this._participants.length; ++i) + { + var participant = this._participants[i]; + if (participant.get_id() == participantId) + { + return participant; + } + } + + return null; + }, + + /** + * Resets the participant repository to its initial state. + */ + reset : function() + { + this._participants = []; + }, + + // private methods + + _debug : function() + { + common.Debug.traceNote("ParticipantRepositoryBase.debug(): Repository contains:"); + for(var i = 0; i < this._participants.length; ++i) + { + var participant = this._participants[i]; + common.Debug.traceNote(" ID:" + participant.get_id() + " Name:" + participant.get_name() + " Active:" + participant.get_isActive()); + } + } +}); + +// Register namespaces +webservices.registerChildNamespace("_Internal"); + +/** + * ParticipantRepository class + * + * Extends ParticipantRepositoryBase, to use Observer/Notification interfaces. + */ +webservices._Internal._ParticipantRepository = Class.create(webservices.ParticipantRepositoryBase, +{ + /** + * Constructor + */ + initialize : function($super) + { + var numArgs = 1; + if(arguments.length != numArgs) + { + throw common.ExceptionFactory.createException("ParticipantsRepository constructor called with " + arguments.length + " arguments, but expected " + numArgs + "."); + } + + $super(); + + this.addImplementedInterface(webservices.Interfaces.ICurrentParticipantIdChangedNotificationObserver, webservices); + this.addImplementedInterface(webservices.Interfaces.INewParticipantNotificationObserver, webservices); + this.addImplementedInterface(webservices.Interfaces.IParticipantJoinedNotificationObserver, webservices); + this.addImplementedInterface(webservices.Interfaces.IParticipantLeftNotificationObserver, webservices); + this.addImplementedInterface(webservices.Interfaces.IParticipantNameChangedNotificationObserver, webservices); + }, + + /** + * Constructor + */ + destroy : function() + { + webservices.ParticipantRepositoryBase.prototype.destroy.call(this); + }, + + // public methods + + /** + * Changes the ID of the current participant (i.e. the participant whose web browser this code is running in) + * + * @param notification An ICurrentParticipantIdChangedNotification + */ + processCurrentParticipantIdChangedNotification : function(notification) + { + common.Debug.traceMethodEntered("ParticipantRepository.processCurrentParticipantIdChangedNotification"); + common.Interface.ensureImplements(notification, webservices.Interfaces.ICurrentParticipantIdChangedNotification); + this._currentParticipantId = notification.get_participantId(); + common.Debug.traceMethodExited("ParticipantRepository.processCurrentParticipantIdChangedNotification"); + }, + + /** + * Adds a participant to the repository + * + * @param notification An INewParticipantNotification + */ + processNewParticipantNotification : function(notification) + { + common.Debug.traceMethodEntered("ParticipantRepository.processNewParticipantNotification"); + common.Interface.ensureImplements(notification, webservices.Interfaces.INewParticipantNotification); + this.addParticipant(this._convertNewParticipantNotificationToParticipant(notification)); + common.Debug.traceMethodExited("ParticipantRepository.processNewParticipantNotification"); + }, + + /** + * Marks a participant as active in the chat + * + * @param notification An IParticipantJoinedNotification + */ + processParticipantJoinedNotification : function(notification) + { + common.Debug.traceMethodEntered("ParticipantRepository.processParticipantJoinedNotification"); + common.Interface.ensureImplements(notification, webservices.Interfaces.IParticipantJoinedNotification); + this.markParticipantAsActive(notification.get_participantId()); + common.Debug.traceMethodExited("ParticipantRepository.processParticipantJoinedNotification"); + }, + + /** + * Marks a participant as inactive in the chat + * + * @param notification An IParticipantLeftNotification + */ + processParticipantLeftNotification : function(notification) + { + common.Debug.traceMethodEntered("ParticipantRepository.processParticipantLeftNotification"); + common.Interface.ensureImplements(notification, webservices.Interfaces.IParticipantLeftNotification); + this.markParticipantAsInactive(notification.get_participantId()); + common.Debug.traceMethodExited("ParticipantRepository.processParticipantLeftNotification"); + }, + + /** + * Changes the name of a participant + * + * @param notification An IParticipantNameChangedNotification + */ + processParticipantNameChangedNotification : function(notification) + { + common.Debug.traceMethodEntered("ParticipantRepository.processParticipantNameChangedNotification"); + common.Interface.ensureImplements(notification, webservices.Interfaces.IParticipantNameChangedNotification); + this.changeParticipantName(notification.get_participantId(), notification.get_newParticipantName()); + common.Debug.traceMethodEntered("ParticipantRepository.processParticipantNameChangedNotification"); + }, + + // private methods + + _convertNewParticipantNotificationToParticipant : function(notification) + { + common.Interface.ensureImplements(notification, webservices.Interfaces.INewParticipantNotification); + return new webservices.Participant(notification.get_participantId(), notification.get_participantName(), notification.get_interactionType(), false); + } +}); + +// Register namespaces +webservices.registerChildNamespace("_Internal"); + +/** + * Polling manager class + * + * This class should be viewed as a singleton. + */ +webservices._Internal._PollManager = Class.create(webservices.ListenableBase, +{ + // constants + + /** By default, this is how long the client will wait between polling requests. Units = milliseconds. */ + DEFAULT_POLL_INTERVAL : 500, + + /** + * Constructor + */ + initialize: function($super) + { + common.Debug.traceMethodEntered("PollManager.initialize()"); + $super(); + + this.addImplementedInterface(webservices.Interfaces.IPollWaitSuggestionUpdateObserver, webservices); + this.addImplementedInterface(webservices.Interfaces.IFailoverNotificationObserver, webservices); + this.addImplementedInterface(webservices.Interfaces.IChatReconnectNotificationObserver, webservices); + this.addImplementedInterface(webservices.Interfaces.IRefreshPageNotificationObserver, webservices); + + this.reset(); + common.Debug.traceMethodExited("PollManager.initialize()"); + }, + + /** + * Destructor + */ + destroy : function() + { + common.Debug.traceMethodEntered("PollManager.destroy()"); + webservices.ListenableBase.prototype.destroy.call(this); + + this._chatManager = null; + + if(this._timer) + { + this._timer.destroy(); + delete this._timer; + this._timer = null; + } + common.Debug.traceMethodExited("PollManager.destroy()"); + }, + + // public methods + + /** + * Resets the PollManager to its default state + */ + reset : function() + { + common.Debug.traceMethodEntered("PollManager.reset()"); + + webservices.ListenableBase.prototype.reset.call(this); + + // initialize data + this._isActive = false; + this._fireResumedPollingNotificationAfterNextPoll = false; // Fires upon first poll after recovery from network problems. Does not fire upon first poll of a new, healthy chat. + + if(this._timer) + { + this._timer.destroy(); + delete this._timer; + this._timer = null; + } + + // Create a timer to do the polling. + common.Debug.traceNote("Creating poll manager's timer, but not going to start it yet."); + this._timer = new webservices.RecurringTimer(this.DEFAULT_POLL_INTERVAL); + + var _self = this; + this._timer.registerSuccessListener(function() { _self.onTimeToPoll(); }); + + common.Debug.traceMethodExited("PollManager.reset()"); + }, + + /** + * Setter for the chat manager property + * + * @param chatManager The ChatManager singleton + */ + set_chatManager : function(chatManager) + { + common.Debug.traceMethodEntered("PollManager.set_chatManager()"); + this._chatManager = chatManager; + common.Debug.traceMethodExited("PollManager.set_chatManager()"); + }, + + /** + * Update the duration of the polling timer (in response to the IC server's suggestion to do so) + * + * @param pollWaitSuggestion The new suggested polling interval, in milliseconds. + */ + processPollWaitSuggestionUpdate : function(pollWaitSuggestion) + { + common.Debug.traceMethodEntered("PollManager.processPollWaitSuggestionUpdate()"); + this.changeDuration(pollWaitSuggestion); + common.Debug.traceMethodExited("PollManager.processPollWaitSuggestionUpdate()"); + }, + + /** + * Listener for notification that a failover has occurred. Stops polling, since the chat is dead. + * + * @param notification IFailoverNotification + */ + processFailoverNotification : function(notification) + { + common.Debug.traceMethodEntered("PollManager.processFailoverNotification()"); + common.Interface.ensureImplements(notification, webservices.Interfaces.IFailoverNotification); + + common.Debug.traceAlways("PollManager received failover notification. Stopping polling."); + this.stop(); + common.Debug.traceMethodExited("PollManager.processFailoverNotification()"); + }, + + /** + * Listener for notification that the chat has reconnected. Resumes polling. + * + * @param notification IChatReconnectNotification + */ + processChatReconnectNotification : function(notification) + { + common.Debug.traceMethodEntered("PollManager.processChatReconnectNotification()"); + common.Interface.ensureImplements(notification, webservices.Interfaces.IChatReconnectNotification); + + common.Debug.traceAlways("PollManager received chat reconnect notification. Starting polling."); + this._fireResumedPollingNotificationAfterNextPoll = true; + this.start(); + + common.Debug.traceMethodExited("PollManager.processChatReconnectNotification()"); + }, + + /** + * Listener for notification that the user needs to refresh the page to start a new chat. + * Stops polling, since the chat is dead. + * + * @param notification IRefreshPageNotification + */ + processRefreshPageNotification : function(notification) + { + common.Debug.traceMethodEntered("PollManager.processRefreshPageNotification()"); + common.Interface.ensureImplements(notification, webservices.Interfaces.IRefreshPageNotification); + + common.Debug.traceAlways("PollManager received refresh page notification. Stopping polling."); + + // if we need to refresh the page to start over, make sure we've stopped polling + this.stop(); + common.Debug.traceMethodExited("PollManager.processRefreshPageNotification()"); + }, + + /** + * Start polling. Called when client app is done initializing. + */ + start : function() + { + common.Debug.traceMethodEntered("PollManager.start()"); + + this._isActive = true; + + if (this._timer) + { + common.Debug.traceNote("In PollManager.start(), timer exists, so stopping it"); + this._timer.stop(); // Just in case it's already running. Won't do any harm if not. + } + + if(this._isActive) + { + common.Debug.traceStatus("Performing first poll"); + this.poll(); + + common.Debug.traceNote("Starting PollManager's timer."); + this._timer.start(); + } + + common.Debug.traceMethodExited("PollManager.start()"); + }, + + /** + * Stop polling. Called when the chat is finished. + */ + stop : function() + { + common.Debug.traceMethodEntered("PollManager.stop()"); + this._isActive = false; + if (this._timer) + { + common.Debug.traceNote("In PollManager.stop(), timer exists, so stopping it"); + this._timer.stop(); + } + common.Debug.traceMethodExited("PollManager.stop()"); + }, + + /** + * Polling is triggered by a recurring timer. This method changes the interval at which this timer fires. + * + * @param duration The new interval between poll requests, in milliseconds. + */ + changeDuration : function(duration) + { + common.Debug.traceMethodEntered("PollManager.changeDuration()"); + common.Debug.traceStatus("PollManager setting timer duration to: " + duration + "ms"); + if (this._timer) + { + if(duration != this._timer.get_duration()) + { + this._timer.restart(duration); + } + } + common.Debug.traceMethodExited("PollManager.changeDuration()"); + }, + + /** + * Indicates whether the poll manager is active (i.e. a poll + * request is pending right now, or the poll manager is + * currently waiting for the timer to fire again). + * + * @return true if the poll manager is active, false if it is not + */ + isActive : function() + { + return this._isActive; + }, + + /** + * Called when the timer goes off, indicating it's time to poll now. + * Create AJAX mgr, register this as a listener of the AJAX mgr, and send polling request. + * This method should not be called directly by clients of the API. + */ + onTimeToPoll : function() + { + common.Debug.traceMethodEntered("PollManager.onTimeToPoll()"); + + if(!this._isActive) + { + common.Debug.traceNote("No longer active, so ignoring this poll timer firing"); + } + else + { + this.poll(); + } + + common.Debug.traceMethodExited("PollManager.onTimeToPoll()"); + }, + + /** + * Creates and sends a poll request + */ + poll : function() + { + common.Debug.traceMethodEntered("PollManager.poll()"); + + var ajax = this._chatManager.createAjaxManager(webservices.CapabilityRepository.get_pollCapability()); + var _self = this; + ajax.registerSuccessListener(function(response) + { + common.Debug.traceNote("PollManager.poll succeeded: " + response); + if (_self._fireResumedPollingNotificationAfterNextPoll) + { + _self._fireResumedPollingNotificationAfterNextPoll = false; + webservices.NotificationRegistry.process(webservices.NotificationFactory.createResumedPollingNotification()); + } + _self.notifyListenersOfSuccess(response); + }); + ajax.registerFailureListener(function(response) + { + common.Debug.traceError("PollManager.poll failed: " + response); + _self.notifyListenersOfFailure(response); + }); + common.Debug.traceNote("Sending new poll request."); + var participantId = webservices.ParticipantRepository.get_currentParticipantId(); + ajax.sendRequest(null, participantId, true); + + common.Debug.traceMethodExited("PollManager.poll()"); + } +}); + +/** + * InteractionParameters class + * This class contains parameters that will be necessary to create a Interaction. + */ +webservices.InteractionParameters = Class.create( +{ + /** + * Constructor + * + * This constructor allows for all parameters to be specified at once. But, it is + * also valid to populate this object gradually and then create the interaction once + * it is fully-populated. + * + * @param target The name of the queue to which the interaction should be sent. + * @param targetType "Workgroup" or "User", depending on the queue type of the previous param. + * @param customInfo Customers wishing to customize interactions may set this to any data. It will be set as + * the value of the CUSTOM_INFO attribute on the interaction. + * @param routingContexts Optional. An instance of webservices.RoutingContexts that specifies how + * interactions should be routed. + * @param participantName The name of the participant who wants to interact with an agent. + * @param participantCredentials The credentials (i.e. password, certificate, etc.) of the participant who wants to interact with an agent + * @param Javascript Date object + */ + initialize:function(target, targetType, customInfo, routingContexts, participantName, participantCredentials, creationDateTime) + { + this.set_target(target); + this.set_targetType(targetType); + this.set_customInfo(customInfo); + this.set_routingContexts(routingContexts); + this.set_participantName(participantName); + this.set_participantCredentials(participantCredentials); + this.set_creationDateTime(creationDateTime); + }, + + // methods + + /** + * Returns the interaction's target. + * @return The name of the queue to which interactions should be sent. + */ + get_target : function() + { + return this._target; + }, + + /** + * Sets the interaction's target. + * @param target The name of the queue to which interactions should be sent. + */ + set_target : function(target) + { + this._target = target; + }, + + /** + * Returns the interaction's target type. + * @return "Workgroup" or "User", depending on the queue type of the target. + */ + get_targetType : function() + { + return this._targetType; + }, + + /** + * Sets the interaction's target type. + * @param targetType "Workgroup" or "User", depending on the queue type of the target. + */ + set_targetType : function(targetType) + { + this._targetType = targetType; + }, + + /** + * Returns the custom info (if any). + * @return Customers wishing to customize may set this to any data. It will be set as + * the value of the CUSTOM_INFO attribute on the interaction. + */ + get_customInfo : function() + { + return this._customInfo; + }, + + /** + * Sets the custom info (if any). + * @param customInfo A string, or null. + */ + set_customInfo : function(customInfo) + { + this._customInfo = customInfo; + }, + + /** + * Returns the routing contexts (if any). + * @return An instance of RoutingContexts, or null. + */ + get_routingContexts : function() + { + return this._routingContexts; + }, + + /** + * Sets the custom info (if any). + * @param routingContexts Customers wishing to customize may set this to an instance of webservices.RoutingContexts. + */ + set_routingContexts : function(routingContexts) + { + this._routingContexts = routingContexts; + }, + + /** + * Returns the participant's name. + * @return The name of the participant who wants to interact with an agent. + */ + get_participantName : function() + { + return this._participantName; + }, + + /** + * Sets the participant's name. + * @param participantName The name of the participant who wants to interact with an agent. + */ + set_participantName : function(participantName) + { + this._participantName = participantName; + }, + + /** + * Returns the participant's credentials (if any). + * @return The credentials (i.e. password, certificate, etc.) of the participant + */ + get_participantCredentials : function() + { + return this._participantCredentials; + }, + + /** + * Sets the participant's credentials (if any). + * @param participantCredentials The credentials (i.e. password, certificate, etc.) of the participant + */ + set_participantCredentials : function(participantCredentials) + { + if(!participantCredentials) + { + participantCredentials = ""; + } + this._participantCredentials = participantCredentials; + }, + + /** + * Gets the creation date/time of this interaction + * + * @return Javascript Date object + */ + get_creationDateTime : function() + { + return this._creationDateTime; + }, + + /** + * Sets the creation date/time of this interaction + * + * @param creationDateTime Javascript Date object + */ + set_creationDateTime : function(creationDateTime) + { + this._creationDateTime = creationDateTime; + } +}); + +/** + * ChatParameters class + * This class contains parameters that will be necessary to create a Chat. + */ +webservices.ChatParameters = Class.create(webservices.InteractionParameters, +{ + /** + * Constructor + * + * This constructor allows for all parameters to be specified at once. But, it is + * also valid to populate this object gradually and then create the chat once + * it is fully-populated. + * + * @param target The name of the queue to which chats should be sent. + * @param targetType "Workgroup" or "User", depending on the queue type of the previous param. + * @param customInfo Customers wishing to customize chats may set this to any data. It will be set as + * the value of the CUSTOM_INFO attribute on the interaction. + * @param routingContexts Optional. An instance of webservices.RoutingContexts that specifies how + * chats should be routed. + * @param participantName The name of the participant who wants to chat with an agent. + * @param participantCredentials The credentials (i.e. password, certificate, etc.) of the participant trying to log in + * @param Javascript Date object + */ + initialize:function($super, target, targetType, customInfo, routingContexts, participantName, participantCredentials, creationDateTime) + { + $super(target, targetType, customInfo, routingContexts, participantName, participantCredentials, creationDateTime); + } +}); + +/** + * Routing Contexts class + * This allows the specification of routing information for the interaction. + * A "routing context" is a tuple of a "category" and a "context". This class + * represents one or more such tuples. + */ +webservices.RoutingContexts = Class.create( +{ + /** + * Constructor + * Creates a set of routing contexts, containing one routing context. + * Additional ones may be added with the add() method. + * @param category The category of what is being specified, i.e. "Product" + * @param context The specification of something within that category, i.e. "NewAutoInsurancePolicy" + */ + initialize: function(category, context) + { + common.Debug.traceMethodEntered("RoutingContexts.initalize()"); + + this._routingContexts = new Hash(); + this.add(category, context); + + common.Debug.traceMethodExited("RoutingContexts.initalize()"); + }, + + /** + * Destructor + */ + destroy : function() + { + delete this._routingContexts; + this._routingContexts = null; + }, + + /** + * Adds another routing context. + * @param category The category of what is being specified, i.e. "Product" + * @param context The specification of something within that category, i.e. "NewAutoInsurancePolicy" + */ + add : function(category, context) + { + common.Debug.traceMethodEntered("RoutingContexts.add()"); + common.Debug.traceNote("category: " + category + ", context: " + context); + + if (typeof category != "string") + { + throw common.ExceptionFactory.createException("category must be a string."); + } + + if (typeof context != "string") + { + throw common.ExceptionFactory.createException("context must be a string."); + } + + this._routingContexts.set(category, context); + + common.Debug.traceMethodExited("RoutingContexts.add()"); + }, + + /** + * Returns true if this routing context is empty. + * @return Boolean + */ + isEmpty : function() + { + return (this._routingContexts == null || this._routingContexts.length <= 0); + }, + + /** + * Returns the set of categories that have been added. + * @return Array of strings + */ + categories : function() + { + if (this._routingContexts == null) + { + return null; + } + return this._routingContexts.keys(); + }, + + /** + * Gets the context of a particular category. + * @param category string + * @return string + */ + getContext : function(category) + { + if (this._routingContexts == null) + { + return null; + } + return this._routingContexts.get(category); + } +}); + +/** + * CallbackParameters class + * This class contains parameters that will be necessary to create a Callback. + */ +webservices.CallbackParameters = Class.create(webservices.InteractionParameters, +{ + /** + * Constructor + * + * This constructor allows for all parameters to be specified at once. But, it is + * also valid to populate this object gradually and then create the callback once + * it is fully-populated. + * + * @param target The name of the queue to which callbacks should be sent. + * @param targetType "Workgroup" or "User", depending on the queue type of the previous param. + * @param customInfo Customers wishing to customize callbacks may set this to any data. It will be set as + * the value of the CUSTOM_INFO attribute on the interaction. + * @param attributes Optional parameter. An object containing key/value pairs. If supplied, all + * keys and values must be strings. These fields will be passed to WebProcessorBridge + * and set as attributes on the Callback (but each key will be prefixed with a constant + * to form the actual attribute name). + * @param routingContexts Optional. An instance of webservices.RoutingContexts that specifies how + * Callbacks should be routed. + * @param participantName The name of the participant who wants to callback with an agent. + * @param participantCredentials The credentials (i.e. password, certificate, etc.) of the participant trying to log in + * @param Javascript Date object + * @param telephone The telephone number at which the participant wishes to be called + * @param subject The topic which the participant wishes to discuss with the agent + */ + initialize:function($super, target, targetType, customInfo, attributes, routingContexts, + participantName, participantCredentials, creationDateTime, telephone, subject) + { + $super(target, targetType, customInfo, routingContexts, participantName, participantCredentials, creationDateTime); + this.set_telephone(telephone); + this.set_subject(subject); + this.set_attributes(attributes); + }, + + // methods + + /** + * Returns the callback telephone number + * @return The telephone number at which the participant wishes to be called + */ + get_telephone : function() + { + return this._telephone; + }, + + /** + * Sets the callback's telephone number + * @param telephone The telephone number at which the participant wishes to be called + */ + set_telephone : function(telephone) + { + this._telephone = telephone; + }, + /** + * Returns the callback's subject (what the participant wants to talk about). + * @return The topic which the participant wishes to discuss with the agent + */ + get_subject : function() + { + return this._subject; + }, + + /** + * Sets the callback's subject. + * @param subject The topic which the participant wishes to discuss with the agent + */ + set_subject : function(subject) + { + this._subject = subject; + }, + + /** + * Returns the callback's optional attributes. + * @return The attributes to set on the callback interaction. + */ + get_attributes : function() + { + return this._attributes; + }, + + /** + * Sets the callback's optional attributes. + * @param attributes An object containing key/value pairs. If supplied, all + * keys and values must be strings. These fields will be passed to WebProcessorBridge + * and set as attributes on the Callback (but each key will be prefixed with a constant + * to form the actual attribute name). + */ + set_attributes : function(attributes) + { + this._attributes = attributes; + } +}); + +// Register namespaces +webservices.registerChildNamespace("TypingIndicator"); + +/** Default duration of the typing indicator, in milliseconds */ +webservices.TypingIndicator.DEFAULT_DURATION_MS = 3000; + +webservices.registerChildNamespace("_Internal"); + +/** + * Implements the Typing Indicator functionality. + * This class should be viewed as abstract, and its subclasses each as a singleton. + * When a user starts typing (i.e. presses a key when their typing indicator status is false), will set the + * indicator to true. After that, if a certain amount of time passes and the user doen't hit another key, + * will set the indicator to false. + */ +webservices.TypingIndicatorBase = Class.create(webservices.ListenableBase, +{ + /** + * Constructor + */ + initialize: function($super) + { + common.Debug.traceMethodEntered("TypingIndicatorBase.initialize"); + $super(); + + this.addImplementedInterface(webservices.Interfaces.IFailoverNotificationObserver, webservices); + this.addImplementedInterface(webservices.Interfaces.IResumedPollingNotificationObserver, webservices); + webservices.NotificationRegistry.registerObserver(this, webservices.Interfaces.IFailoverNotification); + webservices.NotificationRegistry.registerObserver(this, webservices.Interfaces.IResumedPollingNotification); + + this.reset(); + common.Debug.traceMethodExited("TypingIndicatorBase.initialize"); + }, + + /** + * Destructor + */ + destroy : function() + { + if(this._timer) + { + this._timer.destroy(); + delete this._timer; + this._timer = null; + } + + webservices.ListenableBase.prototype.destroy.call(this); + }, + + // public methods + + /** + * Setter for the chat manager property. + * + * @param chatManager An instance of (a subclass of abstract class) ChatManagerBase + */ + set_chatManager : function(chatManager) + { + common.Debug.traceMethodEntered("TypingIndicatorBase.set_chatManager()"); + this._chatManager = chatManager; + common.Debug.traceMethodExited("TypingIndicatorBase.set_chatManager()"); + }, + + /** + * Change the amount of time that must pass after a keystroke before a user is considered to no longer be typing. + * + * @param duration New time limit, in milliseconds. + */ + set_duration : function(duration) + { + common.Debug.traceMethodEntered("TypingIndicatorBase.set_duration()"); + if (this._timer) + { + if(this._timer.get_duration() != duration) + { + this._timer.restart(duration); + } + } + common.Debug.traceMethodExited("TypingIndicatorBase.set_duration()"); + }, + + /** + * Returns whether the web user is typing. + * + * @return True if the web user is typing, false otherwise. + */ + isTyping : function() + { + return this._typingState; + }, + + /** + * If typing indicator is false, calls set() + * If typing indicator is true, will reset the timer to the full time interval. + */ + keyPressed : function() + { + if (!this.isTyping()) + { + this.set(true); + } + else + { + this._timer.restart(); + } + }, + + /** + * Sets this object's typing state to true or false depending on the arg that's passed in, and sends + * an AJAX request to set typing state on the WebProcessor to match. + * Called by keyPressed(), with arg=true when user presses a key. + * Called when the timer times out with arg=false. + * Note: If arg=true, then in onAJAXSuccess(), timer will be + * started to set typingState back to false after a certain period of time. + * This should be viewed as a private method. + * + * @param true if the web user is typing, false otherwise + */ + set : function(typingState) + { + if(this._running) + { + this._typingState = typingState; + var ajax = this._chatManager.createAjaxManager(webservices.CapabilityRepository.get_setTypingStateCapability()); + var _self = this; + ajax.registerSuccessListener(function(response) + { + common.Debug.traceNote("TypingIndicator.set succeeded: " + response); + _self.onAJAXSuccess(response, typingState); + }); + ajax.registerFailureListener(function(response) + { + common.Debug.traceError("TypingIndicator.set failed: " + response); + _self._typingState = false; // So a failed AJAX request doesn't result in it getting stuck at "true", thus preventing future indicators from being sent. + _self.notifyListenersOfFailure(response); + }); + var participantId = webservices.ParticipantRepository.get_currentParticipantId(); + ajax.sendRequest(this.serializeDataToPost(), participantId, true); + } + }, + + /** + * This method is called when the AJAX message succeeded. It does the following: + * 1) If the typing state was just set to true, this sets a timer to set it back to + * false if no more typing occurs for a while. + * 2) Notifies listeners. + * This method should not be called directly by clients of the API. + * + * @param response The IC server's response to the AJAX request. + * @param typingState True or false, whichever the typing state was set to by the AJAX request. + */ + onAJAXSuccess : function(response, typingState) + { + common.Debug.traceMethodEntered("TypingIndicatorBase.onAJAXSuccess()"); + + // Could have used this.typingState, and made this method not take any arguments, but + // that could open the door to thread-safety issues. + if (typingState) + { + this._timer.start(); // Set timer to clear typing state afer a while + } + this.notifyListenersOfSuccess(response); + + common.Debug.traceMethodExited("TypingIndicatorBase.onAJAXSuccess()"); + }, + + /** + * Make this object active. If stopped, calls to set() will have no effect. + */ + start : function() + { + this._running = true; + }, + + /** + * Make this object inactive. If stopped, calls to set() will have no effect. + * Note that this will restart the timer so that it will run one last time. + */ + stop : function() + { + this._running = false; + this._timer.restart(); // Is this really necessary? If user starts typing and this is called, do we care to receive the stopped typing notification? + }, + + /** + * Resets the object to its default "off" state. + */ + reset : function() + { + common.Debug.traceMethodEntered("TypingIndicatorBase.reset"); + + webservices.ListenableBase.prototype.reset.call(this); + + this._running = false; + this._typingState = false; + + if(this._timer) + { + this._timer.destroy(); + delete this._timer; + this._timer = null; + } + + // A timer to use to know when to set the typing indicator back to false + common.Debug.traceNote("About to create a timer for typing indicator...but not going to start it yet"); + this._timer = new webservices.Timer(webservices.TypingIndicator.DEFAULT_DURATION_MS); + common.Debug.traceNote("Created timer."); + + var _self = this; + this._timer.registerSuccessListener(function() { _self.set(false); }); + + common.Debug.traceMethodExited("TypingIndicatorBase.reset"); + }, + + /** + * Listener for notification that a failover has occurred. Stops polling, since the chat is dead. + * + * @param notification IFailoverNotification + */ + processFailoverNotification : function(notification) + { + common.Debug.traceMethodEntered("TypingIndicatorBase.processFailoverNotification()"); + common.Interface.ensureImplements(notification, webservices.Interfaces.IFailoverNotification); + + this.stop(); + + common.Debug.traceMethodExited("TypingIndicatorBase.processFailoverNotification()"); + }, + + /** + * Event listener for reconnection of the chat + * + * @param notification Something that implements webservices.Interfaces.IResumedPollingNotification + */ + processResumedPollingNotification : function(notification) + { + common.Debug.traceMethodEntered("TypingIndicatorBase.processResumedPollingNotification()"); + common.Interface.ensureImplements(notification, webservices.Interfaces.IResumedPollingNotification); + + this.start(); + + common.Debug.traceMethodExited("TypingIndicatorBase.processResumedPollingNotification()"); + } +}); + +/** + * This class is the main brains of the chat, but is abstract - use derived class instead + */ +webservices.ChatManagerBase = Class.create(webservices.InteractionManagerBase, +{ + /** + * The amount of time that must elapse after a keystroke before the web user is considered to be no longer typing. + * Specified in milliseconds. + */ + typingIndicatorDuration: webservices.TypingIndicator.DEFAULT_DURATION_MS, + + /** + * Constructor + * + * @param genericResponseBuilder An object to build Response objects (i.e. objects which implement ResponseBase or its subclasses) from the IC server's replies to AJAX requests. + * @param capabilityRepository An object to keep track of which Capabilities are enabled or disabled, and provide getter methods for the various Capabilities. + * @param typingIndicator An object to track whether the web user is typing or not + * @param failoverHandler In charge of connecting to the other server if the current one goes down for some reason. + */ + initialize : function($super, genericResponseBuilder, capabilityRepository, typingIndicator, failoverHandler) + { + common.Debug.traceMethodEntered("ChatManagerBase.initialize()"); + + var numArgs = 5; + if(arguments.length != numArgs) + { + throw common.ExceptionFactory.createException("ChatManagerBase constructor called with " + arguments.length + " arguments, but expected " + numArgs + "."); + } + +// common.ParameterValidation.validate(arguments, [ {}, {"required": true} ]); + + $super(genericResponseBuilder, capabilityRepository, failoverHandler); + + this._typingIndicator = typingIndicator; + this._failoverHandler = failoverHandler; + this._isReconnecting = false; + this._partyManager = null; + + this.addImplementedInterface(webservices.Interfaces.IChatCompletionNotification, webservices); + this.addImplementedInterface(webservices.Interfaces.IParticipantActiveNotificationObserver, webservices); + webservices.NotificationRegistry.registerObserver(this, webservices.Interfaces.IChatCompletionNotification); + webservices.NotificationRegistry.registerObserver(this, webservices.Interfaces.IParticipantActiveNotification); + + common.Debug.traceMethodExited("ChatManagerBase.initialize()"); + }, + + /** + * Destructor + */ + destroy : function() + { + common.Debug.traceMethodEntered("ChatManagerBase.destroy()"); + + this._typingIndicator = null; + + common.Debug.traceMethodExited("ChatManagerBase.destroy()"); + }, + + // public methods + + /** + * Sets the party manager, which can be used to get agent's photos. + * + * @param partyManager An instance of a subclass of webservices.PartyManagerBase + */ + set_partyManager : function(partyManager) + { + this._partyManager = partyManager; + }, + + /** + * Start a chat. This method does not return anything, but will cause + * either a ChatCreationNotification or a ChatCreationFailureNotification to be + * sent to observers of these Notification types. + * + * @param parameters An instance of ChatParameters + */ + login : function(parameters) + { + console.debug(parameters); + common.Debug.traceMethodEntered("ChatManagerBase.login()"); + + var anonymousOk = webservices.CapabilityRepository.isChatAnonymousAuthenticationCapabilityEnabled(); + if (!anonymousOk && !(parameters.get_participantName() && parameters.get_participantCredentials())) { + webservices.NotificationRegistry.process(webservices.NotificationFactory.createChatCreationFailureNotification(new webservices.Error("error.websvc.unsupportedOperation"))); + } else + { + // create the start chat capability and start the chat + var ajax = this.createAjaxManager(this._capabilityRepository.get_startChatCapability()); + var _self = this; + ajax.registerSuccessListener(function(response) + { + common.Debug.traceNote("ChatManagerBase.login() succeeded: " + response); + webservices.Servers.OriginalUriFragment = webservices.Servers.CurrentUriFragment; + webservices.NotificationRegistry.process(webservices.NotificationFactory.createChatCreationNotification(response.chatID, response.currentParticipantID, response.dateFormat, response.timeFormat)); + _self.processResponse(response); + _self.onAJAXSuccess(response); + }); + ajax.registerFailureListener(function(response) + { + common.Debug.traceError("ChatManagerBase.login() failed: " + response); + _self.onAJAXFailure(response); + webservices.NotificationRegistry.process(webservices.NotificationFactory.createChatCreationFailureNotification(response.get_statusReason())); + }); + + // Necessary kludge for an SU3 ES. Will be resolved in a better way in the normal development timeline. + webservices.NotificationRegistry.process(webservices.NotificationFactory.createNewParticipantNotification(webservices.ParticipantRepository.SYSTEM_SENDER_ID, "IC", webservices.InteractionTypes.CHAT, new Date(), false)); + + console.debug(this.serializeLoginPostData(parameters)); + ajax.sendRequest(this.serializeLoginPostData(parameters), null, true); + } + + common.Debug.traceMethodExited("ChatManagerBase.login()"); + }, + + /** + * The UI should call this method when the user presses a key, so that a typing indicator may be sent to the IC server. + */ + keyPressed: function() + { + common.Debug.traceMethodEntered("ChatManagerBase.keyPressed()"); + if (this._typingIndicator) { + this._typingIndicator.keyPressed(); + } + common.Debug.traceMethodExited("ChatManagerBase.keyPressed()"); + }, + + /** + * Sends a message during the course of a chat. + * + * @param str The message that the web user typed + * @param isContentHtml True if the message is in HTML format, false if it is in plain text format. + * @param callback This function will be called when a response is received from the IC server. This function should take 2 arguments. The first argument will be a boolean indicating whether the send attempt was successful. The second will be the failure reason (not applicable if the send attempt succeeded). + */ + sendMessage: function(str, isContentHtml, callback) + { + common.Debug.traceMethodEntered("ChatManagerBase.sendMessage()"); + var ajax = this.createAjaxManager(this._capabilityRepository.get_sendMessageCapability()); + var _self = this; + ajax.registerSuccessListener(function(response) + { + common.Debug.traceNote("ChatManagerBase.sendMessage() succeeded: " + response); + _self.processResponse(response); + _self.onAJAXSuccess(response); + callback(true, response); + }); + ajax.registerFailureListener(function(response) + { + common.Debug.traceError("ChatManagerBase.sendMessage() failed: " + response); + _self.onAJAXFailure(response); + callback(false, response); + }); + var participantId = webservices.ParticipantRepository.get_currentParticipantId(); + ajax.sendRequest(this.serializeMessageToSend(str, isContentHtml), participantId, true); + common.Debug.traceMethodExited("ChatManagerBase.sendMessage()"); + }, + + /** + * Ends the chat. This method does not return anything, but will cause + * either a ChatCompletionNotification or a ChatCompletionFailureNotification to be + * sent to observers of these Notification types. + */ + exitChat: function() + { + common.Debug.traceMethodEntered("ChatManagerBase.exitChat()"); + var ajax = this.createAjaxManager(this._capabilityRepository.get_exitCapability()); + var _self = this; + ajax.registerSuccessListener(function(response) + { + common.Debug.traceNote("ChatManagerBase.exitChat() succeeded: " + response); + _self.processResponse(response); + _self.onAJAXSuccess(response); + webservices.NotificationRegistry.process(webservices.NotificationFactory.createChatCompletionNotification()); + webservices.NotificationRegistry.process(webservices.NotificationFactory.createCurrentParticipantIdChangedNotification(null, new Date())); + }); + ajax.registerFailureListener(function(response) + { + common.Debug.traceError("ChatManagerBase.exitChat() failed: " + response); + response.wasExitRequest = true; + _self.onAJAXFailure(response); + webservices.NotificationRegistry.process(webservices.NotificationFactory.createChatCompletionFailureNotification(response.get_statusReason())); + }); + var participantId = webservices.ParticipantRepository.get_currentParticipantId(); + ajax.sendRequest(null, participantId, false); + common.Debug.traceMethodExited("ChatManagerBase.exitChat()"); + }, + + /** + * Ends the chat. No confirmation will be given to the caller. + */ + exitChatAsync: function() + { + common.Debug.traceMethodEntered("ChatManagerBase.exitChatAsync()"); + var ajax = this.createAjaxManager(this._capabilityRepository.get_exitCapability()); + var participantId = webservices.ParticipantRepository.get_currentParticipantId(); + ajax.sendRequest(null, participantId, true); + common.Debug.traceMethodExited("ChatManagerBase.exitChatAsync()"); + }, + + /** + * Reconnect to a chat for which connectivity was lost. + * + * @param callback This function will be called when a response is received from the IC server. This function should take 2 arguments. The first argument will be a boolean indicating whether the send attempt was successful. The second will be the ChatResponse representing the response that was received from IC. + */ + reconnect : function(callback) + { + //var seqNum = webservices.ReceivedMessageRepository.get_lastMessageSequenceNumber(); + var ajax = this.createAjaxManager(this._capabilityRepository.get_reconnectChatCapability()); + var _self = this; + + if(this._isReconnecting == true) + { + return; + } + + this._isReconnecting = true; + + ajax.registerSuccessListener(function(response) + { + + common.Debug.traceNote("ChatManagerBase.reconnect() succeeded: " + response); + webservices.NotificationRegistry.process(webservices.NotificationFactory.createChatReconnectUINotification()); + webservices.EventSequenceManager.reset(); + //var previousParticipants = webservices.ParticipantRepository.get_participants(); + webservices.ParticipantRepository.reset(); + + webservices.NotificationRegistry.process(webservices.NotificationFactory.createCurrentParticipantIdChangedNotification(response.currentParticipantID, new Date())); + + _self._isReconnecting = false; + _self.processResponse(response); + _self.onAJAXSuccess(response); + callback(true, response); + }); + ajax.registerFailureListener(function(response) + { + common.Debug.traceError("ChatManagerBase.reconnect() failed: " + response); + callback(false, response); + _self._isReconnecting = false; + }); + + ajax.sendRequest(this.serializeReconnectPostData(this.chatID), false, true); + }, + + /** + * Handle a successful response to a request to start a chat. + * This involves creating the typing indicator and the poll manager, and + * then using onAJAXSuccess() to handle the response. + * This method should not be called directly by clients of the API. + * + * @param currentParticipantID The ID of the current participant (i.e. the person whose web browser is running this code) + */ + startChat: function(currentChatID, currentParticipantID) + { + common.Debug.traceMethodEntered("ChatManagerBase.startChat()"); + var _self = this; + this.chatID = currentChatID; + + try + { + // configure + if(currentParticipantID) + { + webservices.NotificationRegistry.process(webservices.NotificationFactory.createCurrentParticipantIdChangedNotification(currentParticipantID, new Date())); + } + + // start the event processor + webservices.EventProcessor.start(); + + // configure the failover handler + this._failoverHandler.set_chatManager(this); + + // configure the poll manager + webservices.PollManager.set_chatManager(this); + webservices.PollManager.registerSuccessListener(function(response) + { + common.Debug.traceNote("PollManager.SuccessListener: " + response); + _self.processResponse(response); + _self.onAJAXSuccess(response); + }); + webservices.PollManager.registerFailureListener(function(response) + { + common.Debug.traceError("PollManager.FailureListener: " + response); + _self.onAJAXFailure(response); + }); + webservices.PollManager.start(); + + // configure the typing indicator + this._typingIndicator.set_chatManager(this); + this._typingIndicator.set_duration(this.typingIndicatorDuration); + this._typingIndicator.registerSuccessListener(function(response) + { + common.Debug.traceNote("TypingIndicator.SuccessListener: " + response); + _self.processResponse(response); + _self.onAJAXSuccess(response); + }); + this._typingIndicator.registerFailureListener(function(response) + { + common.Debug.traceError("TypingIndicator.FailureListener: " + response); + _self.onAJAXFailure(response); + }); + this._typingIndicator.start(); + } + catch(e) + { + common.Debug.traceError("Caught unhandled exception:\n" + e); + common.Debug.alert("Caught unhandled exception:\n" + e); + webservices.ProblemReporter.sendProblemReport(e, "ChatManagerBase.startChat()"); + } + common.Debug.traceMethodExited("ChatManagerBase.startChat()"); + }, + + /** + * This method will be called when a chat is exited successfully. + * + * @see exitChat() + * @param chatCompletionNotification Notification object. Contents ignored. + */ + processChatCompletionNotification : function(chatCompletionNotification) + { + common.Debug.traceMethodEntered("ChatManagerBase.processChatCompletionNotification()"); + + this._tearDownChat(); + + common.Debug.traceMethodExited("ChatManagerBase.processChatCompletionNotification()"); + }, + + /** + * Set chat's state data, based on response's state data + * + * @param response An instance of a subclass of ResponseBase, which represents the response to an AJAX request received from the IC server + */ + processResponse : function(response) + { + common.Debug.traceMethodEntered("ChatManagerBase.processResponse()"); + + try + { + common.Interface.ensureImplements(response, webservices.Interfaces.IChatResponse); + + if (response.get_pollWaitSuggestion()) + { + webservices.ChatPropertyUpdateRegistry.processPollWaitSuggestionUpdate(response.get_pollWaitSuggestion()); + } + + var events = response.get_events(); + if(events) + { + for(var i = 0; i < events.length; ++i) + { + //webservices.EventSequenceManager.addSequenceableObject(events[i]); // temporarily removed and added the following line instead. + webservices.EventProcessor.process(events[i], false); + } + } + + var cfgVer = response.get_serverConfigVersion(); + if (cfgVer != webservices.Json.ServerConfigurationProcessor.get_lastServerConfigurationVersion()) + { + webservices.Json.ServerConfigurationManager.getServerConfiguration(); + } + } catch (e) + { + webservices.ProblemReporter.sendProblemReport(e, "ChatManagerBase.processResponse()"); + } + + common.Debug.traceMethodExited("ChatManagerBase.processResponse()"); + }, + + /** + * Called when a participant has joined the chat. Queries the agent's photo. + * + * @param notification Something that implements IParticipantActiveNotification + */ + processParticipantActiveNotification : function(notification) + { + common.Interface.ensureImplements(notification, webservices.Interfaces.IParticipantActiveNotification); + + if (this._partyManager && webservices.CapabilityRepository.isPartyInfoCapabilityEnabled()) + { + var currentParticipantId = webservices.ParticipantRepository.get_currentParticipantId(); + var otherPartyId = notification.get_participantId(); + + // Don't query the photo of the web visitor - there won't be one. + if (otherPartyId != currentParticipantId) + { + var otherParty = webservices.ParticipantRepository.get_participant(otherPartyId); + + // If otherParty's photo is not known, request it + if (null == otherParty || null == otherParty.get_photo()) + { + this._partyManager.getPartyInfo(currentParticipantId, otherPartyId); + } + } + } + }, + + _tearDownChat : function() + { + common.Debug.traceMethodEntered("ChatManagerBase._tearDownChat()"); + // immediately stop the sub systems that are running + webservices.EventProcessor.stop(); + webservices.PollManager.stop(); + this._typingIndicator.stop(); + + // Remove chat participants, but leave callback participants intact + webservices.ParticipantRepository.removeAllParticipantsForInteractionType(webservices.InteractionTypes.CHAT); + + // reset the subsystems back to original state + webservices.PollManager.reset(); + webservices.EventSequenceManager.reset(); + webservices.ReceivedMessageRepository.reset(); + webservices.ReceivedUrlRepository.reset(); + this._typingIndicator.reset(); + webservices.ProblemReporter.reset(); + webservices.Json.ServerConfigurationProcessor.resetServerConfigurationVersion(); + + common.Debug.traceMethodExited("ChatManagerBase._tearDownChat()"); + }, + + _handleInvalidSession : function(response) + { + common.Debug.traceMethodEntered("ChatManagerBase._handleInvalidSession()"); + if (response.wasExitRequest) + { + webservices.NotificationRegistry.process(webservices.NotificationFactory.createChatCompletionNotification()); + } else + { + this._tearDownChat(); + } + common.Debug.traceMethodExited("ChatManagerBase._handleInvalidSession()"); + } +}); + +// Register namespaces +webservices.registerChildNamespace("_Internal"); + +/** + * This class handles reporting problems back to IC + */ +webservices._Internal._ProblemReporterBase = Class.create(common.InterfaceImplementation, +{ + MAX_RECORDS_TO_KEEP: 8, + + /** + * Constructor + */ + initialize : function($super) + { + common.Debug.traceMethodEntered("ProblemReporterBase.initialize()"); + + var numArgs = 1; + if(arguments.length != numArgs) + { + throw common.ExceptionFactory.createException("ProblemReporterBase constructor called with " + arguments.length + " arguments, but expected " + numArgs + "."); + } + + $super(); + + this.addImplementedInterface(webservices.Interfaces.IFailoverNotificationObserver, webservices); + this.addImplementedInterface(webservices.Interfaces.IResumedPollingNotificationObserver, webservices); + webservices.NotificationRegistry.registerObserver(this, webservices.Interfaces.IFailoverNotification); + webservices.NotificationRegistry.registerObserver(this, webservices.Interfaces.IResumedPollingNotification); + + this._failedRequestHistory = new Array(); + this._timedOutRequestHistory = new Array(); + this._connectivityFailureHistory = new Array(); + this._regEx = null; + + common.Debug.traceMethodExited("ProblemReporterBase.initialize()"); + }, + + /** + * Destructor + */ + destroy : function() + { + common.Debug.traceMethodEntered("ProblemReporterBase.destroy()"); + + this._failedRequestHistory = null; + this._timedOutRequestHistory = null; + this._connectivityFailureHistory = null; + this._regEx = null; + + common.Debug.traceMethodExited("ProblemReporterBase.destroy()"); + }, + + /** + * Sets to default state + */ + reset : function() + { + this._failedRequestHistory = new Array(); + this._timedOutRequestHistory = new Array(); + this._connectivityFailureHistory = new Array(); + this._regEx = null; + }, + + // public methods + + /** + * Send a "problem report" to WebProcessorBridge, since browser-side tracing will be difficult to obtain in a production scenario. + */ + sendProblemReport : function(reason, attemptedWork, giveResetLink) + { + common.Debug.traceMethodEntered("ChatManagerBase.sendProblemReport()"); + var participantId = webservices.ParticipantRepository.get_currentParticipantId(); + + if (webservices.CapabilityRepository.isProblemReportCapabilityEnabled() && (null != participantId)) + { + var problemReport = this._buildProblemReport(reason, attemptedWork); + var problemReportStr = this.serializeProblemReport(problemReport); + + if ((null == this.get_regEx()) || (new RegExp(this.get_regEx()).test(problemReportStr))) + { + common.Debug.traceWarning("Sending problem report."); + var ajax = this.createAjaxManager(); + ajax.sendRequest(problemReportStr, participantId, true); + } else + { + common.Debug.traceNote("Problem report did not match regular expression."); + } + } else + { + common.Debug.traceNote("Problem report capability is not enabled, or participant ID has not been set yet."); + } + + /* TODO: Give the user a link to refresh the page + if (giveResetLink) + { + webservices.NotificationRegistry.process(webservices.NotificationFactory.createResetUINotification(webservices.Servers.CurrentUriFragment)); + } + */ + + common.Debug.traceMethodExited("ChatManagerBase.sendProblemReport()"); + }, + + /** + * Records the failed request (by creating an object representing the failure + * and adding it to a collection) so that this may be attached to the + * next problem report to be send. + * + * @param response A subclass of ResponseBase representing the message that failed to be send properly + */ + recordFailedRequest : function(response) + { + common.Debug.traceMethodEntered("ProblemReporterBase.recordFailedRequest()"); + try + { + var url = response.xmlHttpRequest.request.url; + if (url.indexOf(webservices.CapabilityUrls.Common.PROBLEMREPORT) == -1) + { + var sendTimestamp = response.xmlHttpRequest.sendTimestamp; + var duration = new Date() - sendTimestamp; + var status = response.xmlHttpRequest.transport.status; + var failedRequestInfo = { "status": status, + "url": url, + "sendTimestamp": sendTimestamp, + "duration": duration + }; + this._failedRequestHistory.push(failedRequestInfo); + common.Debug.traceWarning("Failed request: " + failedRequestInfo); + + while (this._failedRequestHistory.length > this.MAX_RECORDS_TO_KEEP) + { + this._failedRequestHistory.shift(); + } + + this.sendProblemReport("Failed request", response.xmlHttpRequest.request.url); + } + } catch (e) + { + common.Debug.traceError("Unable to record failed request due to exception: " + e); + } + common.Debug.traceMethodExited("ProblemReporterBase.recordFailedRequest()"); + }, + + /** + * Records that an AJAX request has timed out (by creating an object representing the failure + * and adding it to a collection) so that this may be attached to the next problem + * report to send. + * + * @param ajaxRequest An instance of AJAXRequest. + */ + recordTimedOutRequest : function(ajaxRequest) + { + common.Debug.traceMethodEntered("ProblemReporterBase.recordTimedOutRequest()"); + try + { + var url = ajaxRequest.request.url; + if (url.indexOf(webservices.CapabilityUrls.Common.PROBLEMREPORT) == -1) + { + var sendTimestamp = ajaxRequest.sendTimestamp; + var duration = new Date() - sendTimestamp; + var status = ajaxRequest.transport.status; + var timedOutRequestInfo = { "status": status, + "url": url, + "sendTimestamp": sendTimestamp, + "duration": duration + }; + this._timedOutRequestHistory.push(timedOutRequestInfo); + common.Debug.traceWarning("Timed-out request: " + timedOutRequestInfo); + + while (this._timedOutRequestHistory.length > this.MAX_RECORDS_TO_KEEP) + { + this._timedOutRequestHistory.shift(); + } + } + } catch (e) + { + common.Debug.traceError("Unable to record timed out request due to exception: " + e); + } + common.Debug.traceMethodExited("ProblemReporterBase.recordTimedOutRequest()"); + }, + + /** + * Listener for notification that a failover has occurred. Records the failure, to be included in the next problem report. + * + * @param notification IFailoverNotification + */ + processFailoverNotification : function(notification) + { + common.Debug.traceMethodEntered("ProblemReporterBase.processFailoverNotification()"); + common.Interface.ensureImplements(notification, webservices.Interfaces.IFailoverNotification); + common.Debug.traceWarning("ProblemReporterBase received failover notification."); + var timestamp = new Date(); + var connectivityFailureInfo = { + "activity": "failover", + "timestamp": timestamp + }; + this._connectivityFailureHistory.push(connectivityFailureInfo); + + while (this._connectivityFailureHistory.length > this.MAX_RECORDS_TO_KEEP) + { + this._connectivityFailureHistory.shift(); + } + + common.Debug.traceMethodExited("ProblemReporterBase.processFailoverNotification()"); + }, + + /** + * Listener for notification that polling has resumed after a previous failover. + * Records the information to be included in the next problem report. + * + * @param notification IResumedPollingNotification + */ + processResumedPollingNotification : function(notification) + { + common.Debug.traceMethodEntered("ProblemReporterBase.processResumedPollingNotification()"); + common.Interface.ensureImplements(notification, webservices.Interfaces.IResumedPollingNotification); + common.Debug.traceWarning("ProblemReporterBase received resumed polling notification."); + var timestamp = new Date(); + var connectivityFailureInfo = { + "activity": "resumedPolling", + "timestamp": timestamp + }; + this._connectivityFailureHistory.push(connectivityFailureInfo); + + while (this._connectivityFailureHistory.length > this.MAX_RECORDS_TO_KEEP) + { + this._connectivityFailureHistory.shift(); + } + + this.sendProblemReport("Resumed polling", "Sending accumulated problem report info that may not have been transmitted before."); + + common.Debug.traceMethodExited("ProblemReporterBase.processResumedPollingNotification()"); + }, + + /** + * Gets the regular expression which problem reports must match if they are to be sent. + * If this is null, AND if the "sendProblemReport" capability is enabled, problem reports will be sent if problems occur. + * If this is non-null, AND if the "sendProblemReport" capability is enabled, problem reports will be sent if problems occur provided + * that the problem report that is generated matches this regular expression. + * If the "sendProblemReport" capability is disabled, then there is no point in calling get_problemReportRegEx() as its return + * value should be ignored. + */ + get_regEx : function() + { + return this._regEx; + }, + + /** + * Specifies a regular expression which problem reports must match if they are to be sent. + * If this is not set, or set to null, AND if the "sendProblemReport" capability is enabled, problem reports will be sent if problems occur. + * If this is set, AND if the "sendProblemReport" capability is enabled, problem reports will be sent if problems occur provided + * that the problem report that is generated matches this regular expression. + * If the "sendProblemReport" capability is disabled, then there is likely not much point in calling this method. The value passed will + * be stored, but problem reports will not be sent. + */ + set_regEx : function(regEx) + { + this._regEx = regEx; + }, + + // private methods + + _buildProblemReport : function(reason, attemptedWork) + { + var problemReportObject = + { + "version": 1, + "attemptedWork": attemptedWork, + "userAgent": navigator.userAgent, + "failedRequestHistory": this._failedRequestHistory, + "timedOutRequestHistory": this._timedOutRequestHistory, + "connectivityFailureHistory": this._connectivityFailureHistory, + "exceptions": reason + }; + + if (ININ_Web_Common_Fileversion) + { + problemReportObject.bootloaderFileVersion = ININ_Web_Common_Fileversion; + } + + if (ININ_Web_Chat_WebServices_Fileversion) + { + problemReportObject.webServicesFileVersion = ININ_Web_Chat_WebServices_Fileversion; + } + + // This will not be defined if the customer created a custom UI + if (ININ_Web_Chat_UI_Fileversion) + { + problemReportObject.uiFileVersion = ININ_Web_Chat_UI_Fileversion; + } + + return problemReportObject; + } +}); + +/** + * This class is the main brains of callbacks, but is abstract - use derived class instead + */ +webservices.CallbackManagerBase = Class.create(webservices.InteractionManagerBase, +{ + /** + * Constructor + * + * @param genericResponseBuilder An object to build Response objects (i.e. objects which implement ResponseBase or its subclasses) from the IC server's replies to AJAX requests. + * @param capabilityRepository An object to keep track of which Capabilities are enabled or disabled, and provide getter methods for the various Capabilities. + * @param failoverHandler In charge of connecting to the other server if the current one goes down for some reason. + */ + initialize : function($super, genericResponseBuilder, capabilityRepository, failoverHandler) + { + common.Debug.traceMethodEntered("CallbackManagerBase.initialize()"); + + var numArgs = 4; + if(arguments.length != numArgs) + { + throw common.ExceptionFactory.createException("CallbackManagerBase constructor called with " + arguments.length + " arguments, but expected " + numArgs + "."); + } + +// common.ParameterValidation.validate(arguments, [ {}, {"required": true} ]); + + $super(genericResponseBuilder, capabilityRepository, failoverHandler); + + this._failoverHandler = failoverHandler; + this._partyManager = null; + this._callbackID = null; + + this.addImplementedInterface(webservices.Interfaces.ICallbackStatusNotificationObserver, webservices); + webservices.NotificationRegistry.registerObserver(this, webservices.Interfaces.ICallbackStatusNotification); + + common.Debug.traceMethodExited("CallbackManagerBase.initialize()"); + }, + + /** + * Destructor + */ + destroy : function() + { + common.Debug.traceMethodEntered("CallbackManagerBase.destroy()"); + + common.Debug.traceMethodExited("CallbackManagerBase.destroy()"); + }, + + // public methods + + /** + * Sets the party manager, which can be used to get agent's photos. + * + * @param partyManager An instance of a subclass of webservices.PartyManagerBase + */ + set_partyManager : function(partyManager) + { + this._partyManager = partyManager; + }, + + /** + * Create a callback interaction. This method does not return anything, but will cause + * either a CallbackCreationNotification or a CallbackCreationFailureNotification to be + * sent to observers of these Notification types. + * + * @param parameters An instance of CallbackParameters + */ + createCallback : function(parameters) + { + common.Debug.traceMethodEntered("CallbackManagerBase.createCallback()"); + + var anonymousOk = webservices.CapabilityRepository.isCallbackAnonymousAuthenticationCapabilityEnabled(); + if (!anonymousOk && !(parameters.get_participantName() && parameters.get_participantCredentials())) { + webservices.NotificationRegistry.process(webservices.NotificationFactory.createCallbackCreationFailureNotification(new webservices.Error("error.websvc.unsupportedOperation"))); + } else + { + // configure the failover handler + this._failoverHandler.set_callbackManager(this); + + // create the create callback capability and create the callback + var ajax = this.createAjaxManager(this._capabilityRepository.get_createCallbackCapability()); + var _self = this; + ajax.registerSuccessListener(function(response) + { + common.Interface.ensureImplements(response, webservices.Interfaces.ICallbackResponse); + common.Debug.traceNote("CallbackManagerBase.createCallback() succeeded: " + response); + _self._callbackID = response.get_callbackId(); + webservices.NotificationRegistry.process(webservices.NotificationFactory.createCallbackCreationNotification(response.get_participantId(), response.get_callbackId(), response.get_userIdentityId(), parameters.get_participantName(), parameters.get_telephone(), parameters.get_subject(), parameters.get_creationDateTime())); + _self.onAJAXSuccess(response); + }); + ajax.registerFailureListener(function(response) + { + common.Debug.traceError("CallbackManagerBase.createCallback() failed: " + response); + webservices.NotificationRegistry.process(webservices.NotificationFactory.createCallbackCreationFailureNotification(response.get_statusReason())); + _self.onAJAXFailure(response); + }); + + parameters.set_creationDateTime(new Date()); + ajax.sendRequest(this.serializeCreateCallbackPostData(parameters)); + } + + common.Debug.traceMethodExited("CallbackManagerBase.createCallback()"); + }, + + /** + * Query the status of a Callback + * + * @param participantId Identifies the web visitor (the person whose browser is running this code) in the context of a callback. In this case, the callback is the one whose status the web visitor wishes to query. + */ + queryStatus : function(participantId) + { + common.Debug.traceMethodEntered("CallbackManagerBase.queryStatus()"); + + if (null == participantId) + { + throw common.ExceptionFactory.createException("CallbackManagerBase.queryStatus(): participantId must be specified"); + } + + // create the callback status capability + var ajax = this.createAjaxManager(this._capabilityRepository.get_callbackStatusCapability()); + var _self = this; + + ajax.registerSuccessListener(function(response) + { + common.Interface.ensureImplements(response, webservices.Interfaces.ICallbackResponse); + common.Debug.traceNote("CallbackManagerBase.queryStatus() succeeded: " + response); + webservices.NotificationRegistry.process( + webservices.NotificationFactory.createCallbackStatusNotification( + participantId, + response.get_queueWaitTime(), + response.get_assignedAgentName(), + response.get_assignedAgentParticipantId(), + response.get_interactionState(), + response.get_estimatedCallbackTime(), + response.get_queuePosition(), + response.get_queueName(), + response.get_longestWaitTime(), + response.get_interactionsWaitingCount(), + response.get_loggedInAgentsCount(), + response.get_availableAgentsCount(), + response.get_statusIndicator())); + _self.onAJAXSuccess(response); + }); + ajax.registerFailureListener(function(response) + { + common.Debug.traceError("CallbackManagerBase.queryStatus() failed: " + response); + webservices.NotificationRegistry.process(webservices.NotificationFactory.createCallbackStatusFailureNotification(response.get_statusReason())); + _self.onAJAXFailure(response); + }); + + ajax.sendRequest(null, participantId, true); + common.Debug.traceMethodExited("CallbackManagerBase.queryStatus()"); + }, + + /** + * Disconnect a Callback + * + * @param participantId Identifies the web visitor (the person whose browser is running this code) in the context of a callback. In this case, the callback is the one that the web visitor wishes to disconnect. + */ + disconnect : function(participantId) + { + common.Debug.traceMethodEntered("CallbackManagerBase.disconnect()"); + if (null == participantId) + { + throw common.ExceptionFactory.createException("CallbackManagerBase.disconnect(): participantId must be specified"); + } + + // create the disconnect callback capability + var ajax = this.createAjaxManager(this._capabilityRepository.get_disconnectCallbackCapability()); + var _self = this; + + ajax.registerSuccessListener(function(response) + { + common.Interface.ensureImplements(response, webservices.Interfaces.ICallbackResponse); + common.Debug.traceNote("CallbackManagerBase.disconnect() succeeded: " + response); + _self._callbackID = null; + webservices.NotificationRegistry.process(webservices.NotificationFactory.createCallbackDisconnectNotification(participantId)); + _self.onAJAXSuccess(response); + }); + ajax.registerFailureListener(function(response) + { + common.Debug.traceError("CallbackManagerBase.disconnect() failed: " + response); + webservices.NotificationRegistry.process(webservices.NotificationFactory.createCallbackDisconnectFailureNotification(response.get_statusReason())); + _self.onAJAXFailure(response); + }); + + ajax.sendRequest(null, participantId, true); + common.Debug.traceMethodExited("CallbackManagerBase.disconnect()"); + }, + + /** + * Reconnect to a callback for which connectivity was lost. + * + * @param callbackFunction This function will be called when a response is received from the IC server. + * This function should take 2 arguments. The first argument will be a boolean indicating whether the + * reconnection attempt was successful. The second will be the CallbackReconnectResponse representing + * the response that was received from IC. + */ + reconnect : function(callbackFunction) + { + common.Debug.traceMethodEntered("CallbackManagerBase.reconnect()"); + var ajax = this.createAjaxManager(this._capabilityRepository.get_reconnectCallbackCapability()); + var _self = this; + + if(this._isReconnecting) + { + return; + } + + this._isReconnecting = true; + + ajax.registerSuccessListener(function(response) + { + + common.Debug.traceNote("CallbackManagerBase.reconnect() succeeded: " + response); + + webservices.NotificationRegistry.process(webservices.NotificationFactory.createCallbackReconnectNotification(response.get_participantId())); + + callbackFunction(true, response); + _self._isReconnecting = false; + _self.onAJAXSuccess(response); + }); + ajax.registerFailureListener(function(response) + { + common.Debug.traceError("CallbackManagerBase.reconnect() failed: " + response); + callbackFunction(false, response); + _self._isReconnecting = false; + }); + + ajax.sendRequest(this.serializeReconnectPostData(this._callbackID), false); + common.Debug.traceMethodExited("CallbackManagerBase.reconnect()"); + }, + + /* + * In the future, we could receive callback creation and reconnect notifications, and add the local participant + * to the ParticipantRepository. But currently there is no benefit in doing so. + * The notifications would have to be modified to provide the local participant's name. + * webservices.NotificationRegistry.process(webservices.NotificationFactory.createNewParticipantNotification(notification.get_participantId(), notification.get_participantName(), webservices.InteractionTypes.CALLBACK, null, false)); + */ + + /** + * Respond to notification that a Callback's status has been received + * This just checks to see if the agent's photo is known, and if not, requests it. + * @param notification CallbackStatusNotification + */ + processCallbackStatusNotification : function(notification) + { + common.Debug.traceMethodEntered("CallbackManagerBase.processCallbackStatusNotification()"); + common.Interface.ensureImplements(notification, webservices.Interfaces.ICallbackStatusNotification); + + var agentParticipantId = notification.get_assignedAgentParticipantId(); + if (null != agentParticipantId) + { + // If not there already, put the agent into the participant repository + var agent = webservices.ParticipantRepository.get_participant(agentParticipantId); + if (null == agent) + { + webservices.NotificationRegistry.process(webservices.NotificationFactory.createNewParticipantNotification(agentParticipantId, notification.get_assignedAgentName(), webservices.InteractionTypes.CALLBACK, null, false)); + var agent = webservices.ParticipantRepository.get_participant(agentParticipantId); + } + + // If agent's photo is not known, request it + if ((null == agent.get_photo()) && this._partyManager && webservices.CapabilityRepository.isPartyInfoCapabilityEnabled()) + { + var webVisitorParticipantId = notification.get_participantId(); + this._partyManager.getPartyInfo(webVisitorParticipantId, agentParticipantId); + } + } + common.Debug.traceMethodExited("CallbackManagerBase.processCallbackStatusNotification()"); + }, + + // private methods + + _handleInvalidSession : function(response) + { + common.Debug.traceMethodEntered("CallbackManagerBase._handleInvalidSession()"); + webservices.NotificationRegistry.process(webservices.NotificationFactory.createCallbackDisconnectNotification(participantId)); + common.Debug.traceMethodExited("CallbackManagerBase._handleInvalidSession()"); + } +}); + +// Register namespaces +webservices.registerChildNamespace("_Internal"); + +/** + * This class is the main brains of tracker registration, but is abstract - use derived class instead + */ +webservices.RegistrationManagerBase = Class.create(webservices.InteractionManagerBase, +{ + /** + * Constructor + * + * @param genericResponseBuilder An object to build Response objects (i.e. objects which implement ResponseBase or its subclasses) from the IC server's replies to AJAX requests. + * @param capabilityRepository An object to keep track of which Capabilities are enabled or disabled, and provide getter methods for the various Capabilities. + * @param failoverHandler In charge of connecting to the other server if the current one goes down for some reason. + */ + initialize : function($super, genericResponseBuilder, capabilityRepository, failoverHandler) + { + common.Debug.traceMethodEntered("RegistrationManagerBase.initialize()"); + + var numArgs = 4; + if(arguments.length != numArgs) + { + throw common.ExceptionFactory.createException("RegistrationManagerBase constructor called with " + arguments.length + " arguments, but expected " + numArgs + "."); + } + +// common.ParameterValidation.validate(arguments, [ {}, {"required": true} ]); + + $super(genericResponseBuilder, capabilityRepository, failoverHandler); + + common.Debug.traceMethodExited("RegistrationManagerBase.initialize()"); + }, + + /** + * Destructor + */ + destroy : function() + { + common.Debug.traceMethodEntered("RegistrationManagerBase.destroy()"); + + common.Debug.traceMethodExited("RegistrationManagerBase.destroy()"); + }, + + // public methods + + /** + * Register with tracker + * + * @param username The username of the person attempting to register + * @param password The password of the person attempting to register + * @param firstName The first name of the person attempting to register + * @param middleName The middle name of the person attempting to register + * @param lastName The last name of the person attempting to register + * @param homeStreetAddress The street address of the person attempting to register + * @param homeCity The city of the person attempting to register + * @param homeState The state/province/territory of the person attempting to register + * @param homePostalCode The postal code of the person attempting to register + * @param homeCountry The country of the person attempting to register + * @param homeEmail The email address of the person attempting to register + * @param homePhone The home telephone number of the person attempting to register + * @param homePhone2 The secondary home telephone number of the person attempting to register + * @param homeFax The home fax number of the person attempting to register + * @param homePager The home pager number of the person attempting to register + * @param homeMobile The personal mobile telephone number of the person attempting to register + * @param homeUrl The personal URL of the person attempting to register + * @param department The department (of their company) of the person attempting to register + * @param company The company for which the person attempting to register works + * @param jobTitle The job title of the person attempting to register + * @param assistantName The name of the assistant of the person attempting to register + * @param assistantPhone The telephone number of the assistant of the person attempting to register + * @param businessStreetAddress The street address of the company + * @param businessCity The city in which the company is located + * @param businessState The state/province/territory in which the company is located + * @param businessPostalCode The postal code of the company + * @param businessCountry The country in which the company is located + * @param businessEmail The business email address of the person attempting to register + * @param businessPhone The business telephone number of the person attempting to register + * @param businessPhone2 The secondary business telephone number of the person attempting to register + * @param businessFax The business fax number of the person attempting to register + * @param businessPager The business pager number of the person attempting to register + * @param businessMobile The business mobile telephone number of the person attempting to register + * @param businessUrl The business URL of the person attempting to register + * @param remarks Freeform string + * @param callback This function will be called when a response is received from the IC server. This function should take 3 arguments. The first argument will be a boolean indicating whether the registration attempt was successful. The second will be the failure reason (not applicable if the registration attempt succeeded). The third will be the ID of the current participant (i.e. the person in whose web browser this code is running) and is not applicable if the registration attempt failed. + */ + register : function(username, password, + firstName, middleName, lastName, + homeStreetAddress, homeCity, homeState, homePostalCode, homeCountry, + homeEmail, homePhone, homePhone2, homeFax, homePager, homeMobile, homeUrl, + department, company, jobTitle, + assistantName, assistantPhone, + businessStreetAddress, businessCity, businessState, businessPostalCode, businessCountry, + businessEmail, businessPhone, businessPhone2, businessFax, businessPager, businessMobile, businessUrl, + remarks, callback) + { + common.Debug.traceMethodEntered("RegistrationManagerBase.register()"); + + // create the register capability and then register + var ajax = this.createAjaxManager(this._capabilityRepository.get_trackerRegistrationCapability()); + var _self = this; + ajax.registerSuccessListener(function(response) + { + common.Interface.ensureImplements(response, webservices.Interfaces.IRegistrationResponse); + common.Debug.traceNote("RegistrationManagerBase.register() succeeded: " + response); + callback(response.isSuccessful(), response.get_statusReason(), response.currentParticipantID); + _self.onAJAXSuccess(response); + }); + ajax.registerFailureListener(function(response) + { + common.Debug.traceError("RegistrationManagerBase.register() failed: " + response); + callback(false, response.get_statusReason()); + _self.onAJAXFailure(response); + }); + + ajax.sendRequest(this.serializeRegistrationPostData(username, password, + firstName, middleName, lastName, + homeStreetAddress, homeCity, homeState, homePostalCode, homeCountry, + homeEmail, homePhone, homePhone2, homeFax, homePager, homeMobile, homeUrl, + department, company, jobTitle, + assistantName, assistantPhone, + businessStreetAddress, businessCity, businessState, businessPostalCode, businessCountry, + businessEmail, businessPhone, businessPhone2, businessFax, businessPager, businessMobile, businessUrl, + remarks)); + + common.Debug.traceMethodExited("RegistrationManagerBase.register()"); + } +}); + +/** + * This class is the main brains pertaining to information about parties in interactions, but is abstract - use derived class instead + */ +webservices.PartyManagerBase = Class.create(webservices.InteractionManagerBase, +{ + /** + * Constructor + * + * @param genericResponseBuilder An object to build Response objects (i.e. objects which implement ResponseBase or its subclasses) from the IC server's replies to AJAX requests. + * @param capabilityRepository An object to keep track of which Capabilities are enabled or disabled, and provide getter methods for the various Capabilities. + * @param failoverHandler In charge of connecting to the other server if the current one goes down for some reason. + */ + initialize : function($super, genericResponseBuilder, capabilityRepository, failoverHandler) + { + common.Debug.traceMethodEntered("PartyManagerBase.initialize()"); + + var numArgs = 4; + if(arguments.length != numArgs) + { + throw common.ExceptionFactory.createException("PartyManagerBase constructor called with " + arguments.length + " arguments, but expected " + numArgs + "."); + } + +// common.ParameterValidation.validate(arguments, [ {}, {"required": true} ]); + + $super(genericResponseBuilder, capabilityRepository, failoverHandler); + + this.addImplementedInterface(webservices.Interfaces.IPartyInfoNotificationObserver, webservices); + webservices.NotificationRegistry.registerObserver(this, webservices.Interfaces.IPartyInfoNotification); + + common.Debug.traceMethodExited("PartyManagerBase.initialize()"); + }, + + /** + * Destructor + */ + destroy : function() + { + common.Debug.traceMethodEntered("PartyManagerBase.destroy()"); + + common.Debug.traceMethodExited("PartyManagerBase.destroy()"); + }, + + // public methods + + /** + * Queries a party's info (name, photo). This method does not return anything, but will cause + * either a PartyInfoNotification or a PartyInfoFailureNotification to be + * sent to observers of these Notification types. + * + * @param localParticipantId The participantId of the agent whose info is being queried. + * @param remoteParticipantId The participantId of the agent whose info is being queried. + */ + getPartyInfo : function(localParticipantId, remoteParticipantId) + { + common.Debug.traceMethodEntered("PartyManagerBase.getPartyInfo()"); + + if (!webservices.CapabilityRepository.isPartyInfoCapabilityEnabled()) + { + throw common.ExceptionFactory.createException("PartyManagerBase.getPartyInfo(): The party info capability is not enabled!"); + } + + // create the get party info capability + var ajax = this.createAjaxManager(this._capabilityRepository.get_partyInfoCapability()); + var _self = this; + ajax.registerSuccessListener(function(response) + { + common.Interface.ensureImplements(response, webservices.Interfaces.PartyResponse); + common.Debug.traceNote("PartyManagerBase.getPartyInfo() succeeded: " + response); + webservices.NotificationRegistry.process(webservices.NotificationFactory.createPartyInfoNotification(localParticipantId, remoteParticipantId, response.get_name(), response.get_photo())); + _self.onAJAXSuccess(response); + }); + ajax.registerFailureListener(function(response) + { + common.Debug.traceError("PartyManagerBase.getPartyInfo() failed: " + response); + webservices.NotificationRegistry.process(webservices.NotificationFactory.createPartyInfoFailureNotification(response.get_statusReason())); + _self.onAJAXFailure(response); + }); + + ajax.sendRequest(this.serializeGetPartyInfoPostData(remoteParticipantId), localParticipantId, true); + + common.Debug.traceMethodExited("PartyManagerBase.getPartyInfo()"); + }, + + /** + * Respond to receipt of information (name, photo location) about a party involved in an interaction + * by setting the photo property of the appropriate participant. + * + * @param notification + */ + processPartyInfoNotification : function(notification) + { + common.Debug.traceMethodEntered("PartyManagerBase.processPartyInfoNotification()"); + common.Interface.ensureImplements(notification, webservices.Interfaces.IPartyInfoNotification); + + if (notification.get_photo()) + { + common.Debug.traceNote("PartyManagerBase.processPartyInfoNotification(): agent photo is: " + + notification.get_photo() + " [in " + webservices.Servers.CurrentUriFragment + "]"); + var agentParticipantId = notification.get_remoteParticipantId(); + var agent = webservices.ParticipantRepository.get_participant(agentParticipantId); + if (!agent) + { + throw common.ExceptionFactory.createException("Received party info for unrecognized agent!"); + } + agent.set_photo(notification.get_photo()); + } + common.Debug.traceMethodExited("PartyManagerBase.processPartyInfoNotification()"); + } +}); + +/** + * Chat Response Base class + * AjaxManager will call ResponseBuilder to create these when HTTP JSON/XML relies are received from the IC server that pertain to chats. + */ +webservices.ChatResponse = Class.create(webservices.ResponseBase, +{ + EXCEPTION_INVALID_INTERACTION_STATE: "Invalid interaction state", + + /** + * Constructor + */ + initialize: function($super) + { + common.Debug.traceMethodEntered("ChatResponse.initalize()"); + + $super(); + + this.addImplementedInterface(webservices.Interfaces.IChatResponse, webservices); + + this._events = []; + this._pollWaitSuggestion = null; + + common.Debug.traceMethodExited("ChatResponse.initalize()"); + }, + + /** + * Destructor + */ + destroy : function() + { + if(this._events) + { + while(this._events.length > 0) + { + var event = this._events.pop(); + event.destroy(); + } + delete this._events; + this._events = null; + } + this._pollWaitSuggestion = null; + + webservices.ResponseBase.prototype.destroy.call(this); + }, + + // methods + + /** + * Gets the number of milliseconds that the IC server suggests waiting before polling again + * + * @return An integer representing how many milliseconds the IC server suggests waiting before polling again + */ + get_pollWaitSuggestion : function() + { + return this._pollWaitSuggestion; + }, + + /** + * Sets the number of milliseconds that the IC server suggests waiting before polling again + * + * @param pollWaitSuggestion An integer representing how many milliseconds the IC server suggests waiting before polling again + */ + set_pollWaitSuggestion : function(pollWaitSuggestion) + { + this._pollWaitSuggestion = pollWaitSuggestion; + }, + + /** + * Gets the list of events (e.g. participant joined, etc.) contained in this response + * + * @return A list of events. Each shall be an instance of one of the webservices.*Event classes. + */ + get_events : function() + { + return this._events; + }, + + /** + * Adds an event to the list of events (e.g. participant joined, etc.) contained in this response + * + * @param event An instance of one of the webservices.*Event classes. + */ + addEvent : function(event) + { + this._events.push(event); + }, + + /** + * Gets the config version that the IC server instructed this client to fetch, if any. + * + * @return Version number of server configuration + */ + get_serverConfigVersion : function() + { + return this._cfgVer; + }, + + /** + * Sets the config version that the IC server instructed this client to fetch. + * + * @param cfgVer Version number of server configuration + */ + set_serverConfigVersion : function(cfgVer) + { + this._cfgVer = cfgVer; + } +}); + +/** + * Callback Response Base class + * AjaxManager will call ResponseBuilder to create these when HTTP JSON/XML relies are received + * from the IC server that pertain to callbacks. + */ +webservices.CallbackResponseBase = Class.create(webservices.ResponseBase, +{ + /** + * Constructor + */ + initialize: function($super) + { + common.Debug.traceMethodEntered("CallbackResponseBase.initalize()"); + + $super(); + + this.addImplementedInterface(webservices.Interfaces.ICallbackResponse, webservices); + + common.Debug.traceMethodExited("CallbackResponseBase.initalize()"); + }, + + /** + * Destructor + */ + destroy : function() + { + webservices.ResponseBase.prototype.destroy.call(this); + } +}); + +/** + * Callback Creation Response class + * AjaxManager will call ResponseBuilder to create these when HTTP JSON/XML relies are received + * from the IC server that pertain to the creation of callbacks. + */ +webservices.CallbackCreateResponse = Class.create(webservices.CallbackResponseBase, +{ + /** + * Constructor + */ + initialize: function($super) + { + common.Debug.traceMethodEntered("CallbackCreateResponse.initalize()"); + + $super(); + + this.addImplementedInterface(webservices.Interfaces.ICallbackCreateResponse, webservices); + + this._participantId = null; + this._callbackId = null; + this._userIdentityId = null; + + common.Debug.traceMethodExited("CallbackCreateResponse.initalize()"); + }, + + /** + * Destructor + */ + destroy : function() + { + this._participantId = null; + this._callbackId = null; + this._userIdentityId = null; + + webservices.ResponseBase.prototype.destroy.call(this); + }, + + // methods + + /** + * Gets the ID of the participant to whom this response pertains. + * This ID's lifespan is that of the web session. + * + * @return ID of the participant + */ + get_participantId : function() + { + return this._participantId; + }, + + /** + * Sets the ID of the participant to whom this response pertains + * + * @param participantId ID of the participant + */ + set_participantId : function(participantId) + { + this._participantId = participantId; + }, + + /** + * Gets the ID of the callback, so it may be reconnected + * (i.e. brought into a then-current web session) later. + * This ID's lifespan is that of the Callback. + * + * @return ID of the callback + */ + get_callbackId : function() + { + return this._callbackId; + }, + + /** + * Sets the ID of the callback + * + * @param callbackId ID of the callback + */ + set_callbackId : function(callbackId) + { + this._callbackId = callbackId; + }, + + /** + * Gets the ID of the user who created this callback + * This ID's lifespan is that of the Callback. + * + * @return ID of the user + */ + get_userIdentityId : function() + { + return this._userIdentityId; + }, + + /** + * Sets the ID of the user who created this callback + * + * @param userIdentityId ID of the user who created this callback + */ + set_userIdentityId : function(userIdentityId) + { + this._userIdentityId = userIdentityId; + } +}); + +/** + * Callback Status Response class + * AjaxManager will call ResponseBuilder to create these when HTTP JSON/XML relies are received + * from the IC server that pertain to querying the status of callbacks. + */ +webservices.CallbackStatusResponse = Class.create(webservices.CallbackResponseBase, +{ + /** + * Constructor + */ + initialize: function($super) + { + common.Debug.traceMethodEntered("CallbackStatusResponse.initalize()"); + + $super(); + + this.addImplementedInterface(webservices.Interfaces.ICallbackStatusResponse, webservices); + + this._queueWaitTime = null; + this._assignedAgentName = null; + this._assignedAgentParticipantId = null; + this._interactionState = null; + this._estimatedCallbackTime = null; + this._queuePosition = null; + this._queueName = null; + this._longestWaitTime = null; + this._interactionsWaitingCount = null; + this._loggedInAgentsCount = null; + this._availableAgentsCount = null; + this._statusIndicator = null; + + common.Debug.traceMethodExited("CallbackStatusResponse.initalize()"); + }, + + /** + * Destructor + */ + destroy : function() + { + this._queueWaitTime = null; + this._assignedAgentName = null; + this._assignedAgentParticipantId = null; + this._interactionState = null; + this._estimatedCallbackTime = null; + this._queuePosition = null; + this._queueName = null; + this._longestWaitTime = null; + this._interactionsWaitingCount = null; + this._loggedInAgentsCount = null; + this._availableAgentsCount = null; + this._statusIndicator = null; + + webservices.ResponseBase.prototype.destroy.call(this); + }, + + // methods + + /** + * Gets the queue's wait time + * + * @return number of seconds + */ + get_queueWaitTime : function() + { + return this._queueWaitTime; + }, + + /** + * Sets the queue's wait time + * + * @param queueWaitTime number of seconds + */ + set_queueWaitTime : function(queueWaitTime) + { + this._queueWaitTime = queueWaitTime; + }, + + /** + * Gets the assigned agent's name + * + * @return Agent's name + */ + get_assignedAgentName : function() + { + return this._assignedAgentName; + }, + + /** + * Sets the assigned agent's name + * + * @param assignedAgentName Agent's name + */ + set_assignedAgentName : function(assignedAgentName) + { + this._assignedAgentName = assignedAgentName; + }, + + /** + * Gets the assigned agent's participant ID + * + * @return ID identifying the agent + */ + get_assignedAgentParticipantId : function() + { + return this._assignedAgentParticipantId; + }, + + /** + * Sets the assigned agent + * + * @param assignedAgentParticipantId ID identifying the agent + */ + set_assignedAgentParticipantId : function(assignedAgentParticipantId) + { + this._assignedAgentParticipantId = assignedAgentParticipantId; + }, + + /** + * Gets the interaction state + * + * @return interaction state + */ + get_interactionState : function() + { + return this._interactionState; + }, + + /** + * Sets the interaction state + * + * @param interactionState The state of the interaction + */ + set_interactionState : function(interactionState) + { + this._interactionState = interactionState; + }, + + /** + * Gets the estimated callback time, expressed in "seconds after now" + * + * @return number of seconds before it is estimated that the callback will be made + */ + get_estimatedCallbackTime : function() + { + return this._estimatedCallbackTime; + }, + + /** + * Sets the estimated callback time, expressed in "seconds after now" + * + * @param estimatedCallbackTime number of seconds before it is estimated that the callback will be made + */ + set_estimatedCallbackTime : function(estimatedCallbackTime) + { + this._estimatedCallbackTime = estimatedCallbackTime; + }, + + /** + * Gets the callback's position in the queue + * + * @return integer indicating the callback's position in the queue + */ + get_queuePosition : function() + { + return this._queuePosition; + }, + + /** + * Sets the callback's position in the queue + * + * @param queuePosition integer indicating the callback's position in the queue + */ + set_queuePosition : function(queuePosition) + { + this._queuePosition = queuePosition; + }, + + /** + * Gets the name of the queue + * + * @return the name of the queue + */ + get_queueName : function() + { + return this._queueName; + }, + + /** + * Sets the name of the queue + * + * @param queueName the name of the queue + */ + set_queueName : function(queueName) + { + this._queueName = queueName; + }, + + /** + * Gets TODO write this comment block! + * + * @return + */ + get_longestWaitTime : function() + { + return this._longestWaitTime; + }, + + /** + * Sets TODO write this comment block! + * + * @param longestWaitTime + */ + set_longestWaitTime : function(longestWaitTime) + { + this._longestWaitTime = longestWaitTime; + }, + + /** + * Gets the number of calls waiting + * + * @return the number of calls waiting + */ + get_interactionsWaitingCount : function() + { + return this._interactionsWaitingCount; + }, + + /** + * Sets the number of calls waiting + * + * @param interactionsWaitingCount The number of calls waiting + */ + set_interactionsWaitingCount : function(interactionsWaitingCount) + { + this._interactionsWaitingCount = interactionsWaitingCount; + }, + + /** + * Gets the number of logged in agents who meet this callback's routing criteria + * + * @return the number of logged in agents who meet this callback's routing critieria + */ + get_loggedInAgentsCount : function() + { + return this._loggedInAgentsCount; + }, + + /** + * Sets the number of logged in agents who meet this callback's routing critieria + * + * @param loggedInAgentsCount the number of logged in agents who meet this callback's routing critieria + */ + set_loggedInAgentsCount : function(loggedInAgentsCount) + { + this._loggedInAgentsCount = loggedInAgentsCount; + }, + + /** + * Gets the number of available agents who meet this callback's routing criteria + * + * @return the number of available agents who meet this callback's routing criteria + */ + get_availableAgentsCount : function() + { + return this._availableAgentsCount; + }, + + /** + * Sets the number of available agents who meet this callback's routing criteria + * + * @param availableAgentsCount the number of available agents who meet this callback's routing criteria + */ + set_availableAgentsCount : function(availableAgentsCount) + { + this._availableAgentsCount = availableAgentsCount; + }, + + /** + * Returns a key to indicate the status of the callback + * + * @return statusIndicator string + */ + get_statusIndicator : function() + { + return this._statusIndicator; + }, + + /** + * Sets a key to indicate the status of the callback + * + * @param statusIndicator string + */ + set_statusIndicator : function(statusIndicator) + { + this._statusIndicator = statusIndicator; + } +}); + +/** + * Callback Reconnection Response class + * AjaxManager will call ResponseBuilder to create these when HTTP JSON/XML relies are received + * from the IC server that pertain to the reconnection of callbacks. + */ +webservices.CallbackReconnectResponse = Class.create(webservices.CallbackResponseBase, +{ + /** + * Constructor + */ + initialize: function($super) + { + common.Debug.traceMethodEntered("CallbackReconnectResponse.initalize()"); + + $super(); + + this.addImplementedInterface(webservices.Interfaces.ICallbackReconnectResponse, webservices); + + this._participantId = null; + + common.Debug.traceMethodExited("CallbackReconnectResponse.initalize()"); + }, + + /** + * Destructor + */ + destroy : function() + { + this._participantId = null; + + webservices.ResponseBase.prototype.destroy.call(this); + }, + + // methods + + /** + * Gets the ID of the participant to whom this response pertains + * + * @return ID of the participant + */ + get_participantId : function() + { + return this._participantId; + }, + + /** + * Sets the ID of the participant to whom this response pertains + * + * @param participantId ID of the participant + */ + set_participantId : function(participantId) + { + this._participantId = participantId; + } +}); + +/** + * PartyInfoResponse class + * + * When an AJAX request is made to the IC server to get party info (i.e. the name and photo of a + * party in a chat or a callback), PartyInfoResponseBuilder translates the IC server's + * JSON/XML reply into a PartyInfoResponse. + */ +webservices.PartyInfoResponse = Class.create(webservices.ResponseBase, +{ + /** + * Constructor + */ + initialize: function($super) + { + common.Debug.traceMethodEntered("PartyInfoResponse.initalize()"); + + $super(); + + this.addImplementedInterface(webservices.Interfaces.IPartyInfoResponse, webservices); + + common.Debug.traceMethodExited("PartyInfoResponse.initalize()"); + }, + + /** + * Destructor + */ + destroy : function() + { + webservices.ResponseBase.prototype.destroy.call(this); + }, + + // public methods + + /** + * Gets the name of the participant to whom this response pertains + * + * @return name of the participant + */ + get_name : function() + { + return this._name; + }, + + /** + * Sets the name of the participant to whom this response pertains + * + * @param name The name of the participant + */ + set_name : function(name) + { + this._name = name; + }, + + /** + * Gets the location of the photo of the participant to whom this response pertains + * + * @return Location of the photo of the participant + */ + get_photo : function() + { + return this._photo; + }, + + /** + * Sets the location of the photo of the participant to whom this response pertains + * + * @param photo Location of the photo of the participant + */ + set_photo : function(photo) + { + this._photo = photo; + } +}); + +/** + * Registration Response Base class + * AjaxManager will call ResponseBuilder to create these when HTTP JSON/XML relies are received + * from the IC server that pertain to registration. + */ +webservices.RegistrationResponse = Class.create(webservices.ResponseBase, +{ + /** + * Constructor + */ + initialize: function($super) + { + common.Debug.traceMethodEntered("RegistrationResponse.initalize()"); + + $super(); + + this.addImplementedInterface(webservices.Interfaces.IRegistrationResponse, webservices); + + common.Debug.traceMethodExited("RegistrationResponse.initalize()"); + }, + + /** + * Destructor + */ + destroy : function() + { + webservices.ResponseBase.prototype.destroy.call(this); + } +}); + + + +// Register namespaces +webservices.registerChildNamespace("Json._Internal"); + +/** + * ChatResponseBuilder class + * Handles translating the JSON received as the IC server's reply to an AJAX request + * into an webservices.ChatResponse object. + */ +webservices.Json._Internal.ChatResponseBuilder = Class.create(webservices.Json.ResponseBuilderBase, +{ + /** + * Constructor + * + * @param eventFactory An instance of EventFactory, so that the "event" part(s) of the JSON may be translated into webservices.*Event objects. + */ + initialize : function($super, eventFactory) + { + $super(); + + this._eventFactory = eventFactory; + }, + + /** + * Destructor + */ + destroy : function() + { + webservices.Json.ResponseBuilderBase.prototype.destroy.call(this); + }, + + /** + * Handles translating the JSON received as the IC servers reply to an AJAX request + * into an webservices.ChatResponse object. + * + * @param jsonStr JSON received from the IC server. This should have already been vetted to ensure that is not empty, its status indicates success, etc. In the default implementation, that is done in GenericResponseBuilder. + * @return webservices.ChatResponse + */ + buildChatResponse : function(jsonStr) + { + common.Debug.traceMethodEntered("Json.ChatResponseBuilder.buildChatResponse()"); + common.Debug.traceStatus("jsonStr is: " + jsonStr); + + var response = null; + + if (jsonStr) + { + response = new webservices.ChatResponse(); + + var json = null; + try + { + json = jsonStr.evalJSON(); + } + catch (e) + { + common.Debug.traceError("Caught exception calling JSON.parse or string.evalJSON:\n" + e); + common.Debug.alert("Caught exception calling JSON.parse or string.evalJSON:\n" + e); + } + + if(json.chat) + { + this._parseStatus(json.chat.status, response); + + common.Debug.traceNote("status type has been set."); + if (response.isSuccessful()) + { + common.Debug.traceNote("Successful response."); + + this._parseEvents(json.chat.events, response); + + common.Debug.traceNote("Participant/Chat ID: " + json.chat.participantID); + response.chatID = json.chat.chatID; + response.currentParticipantID = json.chat.participantID; + response.dateFormat = json.chat.dateFormat; + response.timeFormat = json.chat.timeFormat; + + if (json.chat.cfgVer) + { + response.set_serverConfigVersion(json.chat.cfgVer); + } + } + else + { + common.Debug.traceNote("Unsuccessful response"); + } + + if(json.chat.pollWaitSuggestion) + { + response.set_pollWaitSuggestion(json.chat.pollWaitSuggestion); + } + } + } + + common.Debug.traceMethodExited("Json.ChatResponseBuilder.buildChatResponse()"); + + return response; + }, + + // private methods + + _parseEvents : function(events, response) + { + if (events) + { + common.Debug.traceNote("Creating events..."); + for (var i = 0; i < events.length; ++i) + { + var jsonEvent = events[i]; + + common.Debug.traceNote("Creating event: " + jsonEvent.type); + response.addEvent(this._eventFactory.createEvent(jsonEvent)); + } + common.Debug.traceNote("Done creating events."); + } + } +}); + + + +// Register namespaces +webservices.registerChildNamespace("Json._Internal"); + +/** + * CallbackResponseBuilder class + * Handles translating the JSON received as the IC server's reply to an AJAX request + * into a webservices.CallbackResponseBase object (or subclass thereof). + */ +webservices.Json._Internal.CallbackResponseBuilder = Class.create(webservices.Json.ResponseBuilderBase, +{ + /** + * Constructor + */ + initialize : function($super) + { + $super(); + }, + + /** + * Destructor + */ + destroy : function() + { + webservices.Json.ResponseBuilderBase.prototype.destroy.call(this); + }, + + /** + * Handles translating the JSON received as the IC servers reply to an AJAX request + * into a subclass of an webservices.CallbackResponseBase object. + * + * @param jsonStr JSON received from the IC server. This should have already been vetted to ensure that is not empty, its status indicates success, etc. In the default implementation, that is done in GenericResponseBuilder. + * @return webservices.CallbackResponseBase subclass + */ + buildCallbackResponse : function(jsonStr, url) + { + common.Debug.traceMethodEntered("Json.CallbackResponseBuilder.buildCallbackResponse()"); + common.Debug.traceStatus("jsonStr is: " + jsonStr); + common.Debug.traceStatus("url is: " + url); + + var response = null; + + if (!jsonStr) + { + common.Debug.traceError("Missing jsonStr!"); + } + else if (!url) + { + common.Debug.traceError("Missing URL!"); + } + else + { + var json = null; + try + { + json = jsonStr.evalJSON(); + } + catch (e) + { + common.Debug.traceError("Caught exception calling JSON.parse or string.evalJSON:\n" + e); + common.Debug.alert("Caught exception calling JSON.parse or string.evalJSON:\n" + e); + } + + if(!json.callback) + { + common.Debug.traceError("JSON has no 'callback' element!"); + common.Debug.breakpoint(); + return response; + } + + if (url.include(webservices.CapabilityUrls.Callback.CREATE)) + { + response = new webservices.CallbackCreateResponse(); + } else if (url.include(webservices.CapabilityUrls.Callback.STATUS)) + { + response = new webservices.CallbackStatusResponse(); + } else if (url.include(webservices.CapabilityUrls.Callback.DISCONNECT)) + { + response = new webservices.CallbackResponseBase(); + } else if (url.include(webservices.CapabilityUrls.Callback.RECONNECT)) + { + response = new webservices.CallbackReconnectResponse(); + } else + { + common.Debug.traceError("Unrecognized response type!"); + common.Debug.breakpoint(); + return response; + } + + this._parseStatus(json.callback.status, response); + + common.Debug.traceNote("status type has been set."); + if (response.isSuccessful()) + { + common.Debug.traceNote("Successful response."); + + if (common.Interface.doesImplement(response,webservices.Interfaces.ICallbackCreateResponse)) + { + common.Debug.traceNote("Participant/Callback ID: " + json.callback.participantID); + response.set_participantId(json.callback.participantID); + if (json.callback.callbackID) + { + response.set_callbackId(json.callback.callbackID); + } + if (json.callback.userIdentityID) + { + response.set_userIdentityId(json.callback.userIdentityID); + } + } else if (common.Interface.doesImplement(response,webservices.Interfaces.ICallbackReconnectResponse)) + { + common.Debug.traceNote("Participant/Callback ID: " + json.callback.participantID); + response.set_participantId(json.callback.participantID); + } else if (common.Interface.doesImplement(response,webservices.Interfaces.ICallbackStatusResponse)) + { + if (json.callback.assignedAgent) + { + response.set_assignedAgentName(json.callback.assignedAgent.name); + response.set_assignedAgentParticipantId(json.callback.assignedAgent.participantID); + response.set_statusIndicator("InProcess"); + } + else + { + response.set_statusIndicator("Waiting"); + } + + if (json.callback.acdStatus) + { + response.set_queueWaitTime(json.callback.acdStatus.queueWaitTime); + response.set_interactionState(json.callback.acdStatus.interactionState); + response.set_estimatedCallbackTime(json.callback.acdStatus.estimatedCallbackTime); + response.set_queuePosition(json.callback.acdStatus.queuePosition); + response.set_queueName(json.callback.acdStatus.queueName); + response.set_longestWaitTime(json.callback.acdStatus.longestWaitTime); + response.set_interactionsWaitingCount(json.callback.acdStatus.callsWaitingCount); // TODO change + response.set_loggedInAgentsCount(json.callback.acdStatus.loggedInAgentsCount); + response.set_availableAgentsCount(json.callback.acdStatus.availableAgentsCount); + } + } + // else { nothing to do, since Disconnect response has no state. } + } + else + { + common.Debug.traceNote("Unsuccessful response"); + } + } + + common.Debug.traceMethodExited("Json.CallbackResponseBuilder.buildCallbackResponse()"); + + return response; + } +}); + + + +// Register namespaces +webservices.registerChildNamespace("Json._Internal"); + +/** + * RegistrationResponseBuilder class + * Handles translating the JSON received as the IC server's reply to an AJAX request for registration + * into an webservices.RegistrationResponse object. + */ +webservices.Json._Internal.RegistrationResponseBuilder = Class.create(webservices.Json.ResponseBuilderBase, +{ + /** + * Constructor + */ + initialize : function($super) + { + $super(); + }, + + /** + * Destructor + */ + destroy : function() + { + webservices.Json.ResponseBuilderBase.prototype.destroy.call(this); + }, + + /** + * Handles translating the JSON received as the IC servers reply to an AJAX request + * into an webservices.RegistrationResponse object. + * + * @param jsonStr JSON received from the IC server. This should have already been vetted to ensure that is not empty, its status indicates success, etc. In the default implementation, that is done in GenericResponseBuilder. + * @return webservices.RegistrationResponse + */ + buildRegistrationResponse : function(jsonStr) + { + common.Debug.traceMethodEntered("Json.RegistrationResponseBuilder.buildRegistrationResponse()"); + common.Debug.traceStatus("jsonStr is: " + jsonStr); + + var response = null; + + if (jsonStr) + { + response = new webservices.RegistrationResponse(); + + var json = null; + try + { + json = jsonStr.evalJSON(); + } + catch (e) + { + common.Debug.traceError("Caught exception calling JSON.parse or string.evalJSON:\n" + e); + common.Debug.alert("Caught exception calling JSON.parse or string.evalJSON:\n" + e); + } + + if(json.registration) + { + if(json.registration.status) + { + if(json.registration.status.type) + { + response.set_statusType(json.registration.status.type); + } + + if(json.registration.status.reason) + { + var error = null; + try + { + error = new webservices.Error(json.registration.status.reason); + } + catch(ex) + { + common.Debug.traceError(ex.message); + common.Debug.traceWarning("Invalid status reason: " + json.chat.status.reason); + } + + if(error) + { + response.set_statusReason(error); + } + } + } + } + } + + common.Debug.traceMethodExited("Json.RegistrationResponseBuilder.buildRegistrationResponse()"); + + return response; + } +}); + + + +// Register namespaces +webservices.registerChildNamespace("Json._Internal"); + +/** + * PartyInfoResponseBuilder class + * Handles translating the JSON received as the IC server's reply to an AJAX request + * into a webservices.PartyInfoResponse object + */ +webservices.Json._Internal.PartyInfoResponseBuilder = Class.create(webservices.Json.ResponseBuilderBase, +{ + /** + * Constructor + */ + initialize : function($super) + { + $super(); + }, + + /** + * Destructor + */ + destroy : function() + { + webservices.Json.ResponseBuilderBase.prototype.destroy.call(this); + }, + + /** + * Handles translating the JSON received as the IC servers reply to an AJAX request + * into a webservices.PartyInfoResponse object. + * + * @param jsonStr JSON received from the IC server. This should have already been vetted to ensure that is not empty, its status indicates success, etc. In the default implementation, that is done in GenericResponseBuilder. + * @return webservices.PartyInfoResponse + */ + buildPartyInfoResponse : function(jsonStr) + { + common.Debug.traceMethodEntered("Json.PartyInfoResponseBuilder.buildPartyInfoResponse()"); + common.Debug.traceStatus("jsonStr is: " + jsonStr); + + var response = null; + + if (!jsonStr) + { + common.Debug.traceError("Missing jsonStr!"); + } + else + { + var json = null; + try + { + json = jsonStr.evalJSON(); + } + catch (e) + { + common.Debug.traceError("Caught exception calling JSON.parse or string.evalJSON:\n" + e); + common.Debug.alert("Caught exception calling JSON.parse or string.evalJSON:\n" + e); + } + + if(!json.partyInfo) + { + common.Debug.traceError("JSON has no 'partyInfo' element!"); + common.Debug.breakpoint(); + return response; + } + + response = new webservices.PartyInfoResponse(); + + this._parseStatus(json.partyInfo.status, response); + + common.Debug.traceNote("status type has been set."); + if (response.isSuccessful()) + { + common.Debug.traceNote("Successful response."); + + response.set_name(json.partyInfo.name); + response.set_photo(json.partyInfo.photo); + } + else + { + common.Debug.traceNote("Unsuccessful response"); + } + } + + common.Debug.traceMethodExited("Json.PartyInfoResponseBuilder.buildPartyInfoResponse()"); + + return response; + } +}); + + + +// Register namespaces +webservices.registerChildNamespace("Json._Internal"); + +/** + * webservices.Json._Internal._TypingIndicator class + * This class extends TypingIndicatorBase, to provide a JSON-specific implementation of serializeDataToPost(), + * which is the method that creates the textual representation of the typing indicator to send to the IC server. + * + */ +webservices.Json._Internal._TypingIndicator = Class.create(webservices.TypingIndicatorBase, +{ + /** + * Constructor + */ + initialize: function($super) + { + common.Debug.traceMethodEntered("Json.TypingIndicator.initialize"); + $super(); + common.Debug.traceMethodExited("Json.TypingIndicator.initialize"); + }, + + /** + * Create a textual representation of the typing indicator to send to the server in the AJAX request. + * (JSON flavor of this method) + */ + serializeDataToPost: function() + { + return "{ \"typingIndicator\": " + this._typingState + "}"; + } +}); + + + +// Register namespaces +webservices.registerChildNamespace("Json"); + +/** + * ChatManager class + * Extends ChatManagerBase with JSON-specific functionality + */ +webservices.Json.ChatManager = Class.create(webservices.ChatManagerBase, +{ + // constants + HTML_MIME_TYPE_VALUE: 'text/html', + PLAIN_TEXT_MIME_TYPE_VALUE: 'text/plain', + + /** + * Constructor + * + * @param genericResponseBuilder An instance of GenericResponseBuilder, to turn the JSON received from the IC server into a ResponseBase or subclass thereof + * @param capabilityRepository An instance of CapabilityRepository, in which the capabilities are stored. + * @param typingIndicator An instance of webservices.Json.TypingIndicator + * @param failoverHandler An instance of webservices.Json.FailoverHandler + * @param acceptHtml If true, this indicates that the client is willing to receive messages from WebProcessorBridge that contain HTML. Otherwise, only plain text messages are desired. + */ + initialize: function($super, genericResponseBuilder, capabilityRepository, typingIndicator, failoverHandler, acceptHtml) + { + common.Debug.traceMethodEntered("Json.ChatManager.initialize()"); + + // fields will overwrite or append to the corresponding fields in the chat state. + $super(genericResponseBuilder, capabilityRepository, typingIndicator, failoverHandler); + + this._mimeType = acceptHtml ? this.HTML_MIME_TYPE_VALUE + : this.PLAIN_TEXT_MIME_TYPE_VALUE; + + common.Debug.traceMethodExited("Json.ChatManager.initialize()"); + }, + + // public methods + + /** + * Attempt to log in the specified participant + * + * @param parameters An instance of ChatParameters + */ + login: function(parameters) + { + common.Debug.traceMethodEntered("Json.ChatManager.login()"); + + // The idea here is that "chat state" conceptually contains the same data as a response may, + // so we can just use a response object to represent it. Each time a response comes in, its + // fields will overwrite or append to the corresponding fields in the chat state. + try + { + common.Debug.traceNote("Creating an empty WebSvcsJSONCommonRequest, to hold webservices.Json.ChatManager's state"); + } + catch (e) + { + common.Debug.traceError("Caught unhandled exception:\n" + e); + common.Debug.alert("Caught unhandled exception:\n" + e); + } + + webservices.ChatManagerBase.prototype.login.call(this, parameters); + + common.Debug.traceMethodExited("Json.ChatManager.login()"); + }, + + /** + * Gets an JSON-specific instance of webservices.AjaxManagerBase + * This method should not be called directly by clients of the API. + * + * @param capability A Capability object representing what this AjaxManager object is intended to do (i.e. poll, send a message, etc.) + * @return AjaxManager + */ + createAjaxManager : function(capability) + { + common.Debug.traceMethodEntered("Json.ChatManager.createAjaxManager()"); + + if(!capability) + { + common.Debug.traceError("null capability"); + common.Debug.breakpoint(); + webservices.ProblemReporter.sendProblemReport("null capability", "ChatManager.createAjaxManager()"); + return; + } + + common.Debug.traceNote("capability=" + capability.toString()); + + var mgr = new webservices.Json.AjaxManager(this._genericResponseBuilder, capability); + common.Debug.traceMethodExited("Json.ChatManager.createAjaxManager()"); + return mgr; + }, + + /** + * Takes data necessary for a login, and puts it into the appropriate JSON format for sending to the IC server. + * + * @param parameters An instance of ChatParameters + * @return String in JSON format, appropriate for sending to IC server + */ + serializeLoginPostData : function(parameters) + { + common.Debug.traceMethodEntered("Json.ChatManager.serializeLoginPostData()"); + + var json = {}; + + json.supportedContentTypes = this._mimeType; + + json.participant = {}; + json.participant.name = parameters.get_participantName(); + json.participant.credentials = parameters.get_participantCredentials(); + + json.target = parameters.get_target(); + json.targetType = parameters.get_targetType(); + + if (parameters.get_customInfo()) + { + json.customInfo = parameters.get_customInfo(); + } + + if (localization.LanguageCode) + { + // The language whose resource file got loaded. + json.language = localization.LanguageCode; + } + else + { + common.Debug.traceWarning("Starting a chat, but no language has been specified."); + } + + json.clientToken = "deprecated"; + + if (parameters.attributes) { + json.attributes = {}; + for (var attr in parameters.attributes) { + json.attributes[attr] = parameters.attributes[attr]; + } + } + console.debug(json); + common.Debug.traceMethodExited("Json.ChatManager.serializeLoginPostData()"); + return Object.toJSON(json); + }, + + serializeReconnectPostData : function(chatId) + { + common.Debug.traceMethodEntered("Json.ChatManager.serializeReconnectPostData()"); + + var json = {}; + + json.chatID = chatId; + + common.Debug.traceMethodExited("Json.ChatManager.serializeReconnectPostData()"); + return Object.toJSON(json); + }, + + /** + * Convert Javascript problem report to JSON + * @param problemReport An object containing arbitrary data about a problem encountered by the client + * @return That object, represented as a JSON string + */ + serializeProblemReport : function(problemReport) + { + return Object.toJSON(problemReport); + }, + + /** + * Puts a message that the web user typed during a chat into the appropriate JSON format for sending to the IC server. + * + * @param str The message that the web user typed + * @param isContentHtml True if the message is in HTML format, false if it is in plain text format. + * @return String in JSON format, appropriate for sending to IC server + */ + serializeMessageToSend : function(str, isContentHtml) + { + var contentType = (isContentHtml ? this.HTML_MIME_TYPE_VALUE : this.PLAIN_TEXT_MIME_TYPE_VALUE); + return Object.toJSON( { "message": str, "contentType": contentType } ); + } +}); + + + +// Register namespaces +webservices.registerChildNamespace("Json"); + +/** + * CallbackManager class + * Extends CallbackManagerBase with JSON-specific functionality + */ +webservices.Json.CallbackManager = Class.create(webservices.CallbackManagerBase, +{ + /** + * Constructor + * + * @param genericResponseBuilder An instance of GenericResponseBuilder, to turn the JSON received from the IC server into a ResponseBase or subclass thereof + * @param capabilityRepository An instance of CapabilityRepository, in which the capabilities are stored. + * @param failoverHandler An instance of webservices.Json.FailoverHandler + */ + initialize: function($super, genericResponseBuilder, capabilityRepository, failoverHandler) + { + common.Debug.traceMethodEntered("Json.CallbackManager.initialize()"); + + $super(genericResponseBuilder, capabilityRepository, failoverHandler); + + common.Debug.traceMethodExited("Json.CallbackManager.initialize()"); + }, + + // public methods + + /** + * Gets an JSON-specific instance of webservices.AjaxManagerBase + * This method should not be called directly by clients of the API. + * + * @param capability A Capability object representing what this AjaxManager object is intended to do (i.e. poll, send a message, etc.) + * @return AjaxManager + */ + createAjaxManager : function(capability) + { + common.Debug.traceMethodEntered("Json.CallbackManager.createAjaxManager()"); + var mgr = new webservices.Json.AjaxManager(this._genericResponseBuilder, capability); + common.Debug.traceMethodExited("Json.CallbackManager.createAjaxManager()"); + return mgr; + }, + + /** + * Takes data necessary to create a callback, and puts it into the appropriate JSON format for sending to the IC server. + * + * @param parameters An instance of CallbackParameters + * @return String in JSON format, appropriate for sending to IC server + */ + serializeCreateCallbackPostData : function(parameters) + { + common.Debug.traceMethodEntered("Json.CallbackManager.serializeCreateCallbackPostData()"); + var json = { }; + + json.target = parameters.get_target(); + json.targettype = parameters.get_targetType(); + json.subject = parameters.get_subject(); + + json.participant = {}; + json.participant.name = parameters.get_participantName(); + json.participant.credentials = parameters.get_participantCredentials(); + json.participant.telephone = parameters.get_telephone(); + + if (parameters.get_customInfo()) + { + json.customInfo = parameters.get_customInfo(); + } + + var attributes = parameters.get_attributes(); + if (attributes) + { + json.attributes = {}; + for (key in attributes) + { + var value = attributes[key]; + if (key.constructor === String && (value == null || value.constructor === String)) + { + json.attributes[key] = value; + } + } + } + + var routingContexts = parameters.get_routingContexts(); + if (routingContexts && !routingContexts.isEmpty()) + { + json.routingContexts = new Array(); + var categories = routingContexts.categories(); + for (var i=0; i 1) + { + // Iterate + namespaceObject.registerChildNamespace(namespaceParts.slice(1).join('.')); + } + return namespaceObject; + }; + + // public methods + var typeClass = + { + registerNamespace : function(namespacePath) + { + var rootObject = window; + var namespaceParts = namespacePath.split('.'); + + for (var i = 0; i < namespaceParts.length; ++i) + { + var currentPart = namespaceParts[i]; + var namespaceObject = rootObject[currentPart]; + + // if the namespace object exists, make sure it's tagged as a namespace + if (namespaceObject && !namespaceObject.__isNamespace) + { + throw ININ.Web.Common.ExceptionFactory.createException("Part of the namespace " + namespacePath + " is already an object: " + namespaceParts[i]); + } + + if (!namespaceObject) + { + // create the object, add new members on to the existing object and verify the object + var name = _buildNamespaceName(namespaceParts, i); + namespaceObject = rootObject[currentPart] = {}; + _addNewMembers(namespaceObject, name); + _verifyNamespaceObjectWithName(namespaceObject); + } + + // save this namespace object as the root for the next iteration + rootObject = namespaceObject; + } + }, + + registerLocalNamespace : function(name) + { + var namespaceObject = {}; + _addNewMembers(namespaceObject, name); + namespaceObject.registerChildNamespace = _registerChildNamespace.bind(namespaceObject); + return namespaceObject; + } + + }; + + // register the namespace for this object and then assign the namespace object itself + typeClass.registerNamespace("ININ.Web.Common.Type"); + ININ.Web.Common.Type = typeClass; +})(); + +/*global ININ: true, Error: true, Prototype: true, alert: true, console: true, firebug: true */ + +ININ.Web.Common.Type.registerNamespace("ININ.Web.Common"); + +ININ.Web.Common.Type.registerNamespace("ININ.Web.Common.TraceLevels"); + +// TraceLevel constants +ININ.Web.Common.TraceLevels.ALL = 100; +ININ.Web.Common.TraceLevels.VERBOSE_NOTE = 99; +ININ.Web.Common.TraceLevels.NOTE = 80; +ININ.Web.Common.TraceLevels.STATUS = 60; +ININ.Web.Common.TraceLevels.WARNING = 40; +ININ.Web.Common.TraceLevels.ERROR = 20; +ININ.Web.Common.TraceLevels.CRITICAL_ERROR = 10; +ININ.Web.Common.TraceLevels.ALWAYS = 0; + +// Debug class +ININ.Web.Common.Debug = function() +{ + // private members + var _enabled = false; + var _currentTraceLevel = ININ.Web.Common.TraceLevels.STATUS; + var _scopeTraceLevel = ININ.Web.Common.TraceLevels.NOTE; + + var _isFirebugLite = function() + { + return typeof(firebug) != "undefined"; + }; + + var _shouldLevelBeTraced = function(level) + { + if(ININ.Web.Common.Utilities.isType(level, Number)) + { + return level <= _currentTraceLevel; + } + + return true; + }; + + var _beginManualScope = function(text) + { + ININ.Web.Common.Debug.traceNote(text); + }; + + var _endManualScope = function(text) + { + ININ.Web.Common.Debug.traceNote(text); + }; + + var _beginScope = function(prefix, scopeText) + { + if(_shouldLevelBeTraced(_scopeTraceLevel)) + { + if(_isFirebugLite()) + { + // for firebug lite + _beginManualScope(prefix + scopeText); + } + else + { + try + { + console.group(scopeText); + } + catch(ex) + { + // for IE developer tools + _beginManualScope(prefix + scopeText); + } + } + } + }; + + var _endScope = function(prefix, scopeText) + { + if(_shouldLevelBeTraced(_scopeTraceLevel)) + { + if(_isFirebugLite()) + { + // for firebug lite + _endManualScope(prefix + scopeText); + } + else + { + try + { + console.groupEnd(scopeText); + } + catch(ex) + { + // for IE developer tools + _endManualScope(prefix + scopeText); + } + } + } + }; + + var _logMessage = function(msg, level) + { + if(_shouldLevelBeTraced(level)) + { + try + { + console.log(msg); + } + catch(ex) + { + // silently fail + } + } + }; + + var _logWarning = function(msg) + { + if(_shouldLevelBeTraced(ININ.Web.Common.TraceLevels.WARNING)) + { + try + { + console.warn(msg); + } + catch(ex) + { + // silently fail + } + } + }; + + var _logError = function(msg) + { + if(_shouldLevelBeTraced(ININ.Web.Common.TraceLevels.ERROR)) + { + try + { + console.error(msg); + } + catch(ex) + { + // silently fail + } + } + }; + + // public methods + return { + enable : function() + { + _enabled = true; + }, + + disable : function() + { + _enabled = false; + }, + + isEnabled : function() + { + return _enabled; + }, + + setTraceLevel : function(level) + { + if((level != ININ.Web.Common.TraceLevels.ALL) && + (level != ININ.Web.Common.TraceLevels.VERBOSE_NOTE) && + (level != ININ.Web.Common.TraceLevels.NOTE) && + (level != ININ.Web.Common.TraceLevels.STATUS) && + (level != ININ.Web.Common.TraceLevels.WARNING) && + (level != ININ.Web.Common.TraceLevels.ERROR) && + (level != ININ.Web.Common.TraceLevels.CRITICAL_ERROR)) + { + throw new Error("Level specified is not a value in ININ.Web.Common.TraceLevels"); + } + + _currentTraceLevel = level; + }, + + setScopeTraceLevel : function(level) + { + if((level != ININ.Web.Common.TraceLevels.ALL) && + (level != ININ.Web.Common.TraceLevels.VERBOSE_NOTE) && + (level != ININ.Web.Common.TraceLevels.NOTE) && + (level != ININ.Web.Common.TraceLevels.STATUS) && + (level != ININ.Web.Common.TraceLevels.WARNING) && + (level != ININ.Web.Common.TraceLevels.ERROR) && + (level != ININ.Web.Common.TraceLevels.CRITICAL_ERROR)) + { + throw new Error("Level specified is not a value in ININ.Web.Common.TraceLevels"); + } + + _scopeTraceLevel = level; + }, + + traceMethodEntered : function(methodName) + { + _beginScope("Entering method: ", methodName); + }, + + traceMethodExited : function(methodName) + { + _endScope("Exiting method: ", methodName); + }, + + traceScopeEntered : function(scopeName) + { + _beginScope("Entering scope: ", scopeName); + }, + + traceScopeExited : function(scopeName) + { + _endScope("Exiting scope: ", scopeName); + }, + + traceAlways : function(msg) + { + _logMessage(msg, ININ.Web.Common.TraceLevels.ALWAYS); + }, + + traceVerboseNote : function(msg) + { + _logMessage(msg, ININ.Web.Common.TraceLevels.VERBOSE_NOTE); + }, + + traceNote : function(msg) + { + _logMessage(msg, ININ.Web.Common.TraceLevels.NOTE); + }, + + traceStatus : function(msg) + { + _logMessage(msg, ININ.Web.Common.TraceLevels.STATUS); + }, + + traceWarning : function(msg) + { + _logWarning(msg); + }, + + traceError : function(msg, level) + { + _logError(msg); + }, + + traceCriticalError : function(msg, level) + { + _logError(msg, ININ.Web.Common.TraceLevels.CRITICAL_ERROR); + }, + + alert : function(msg) + { + if(_enabled) + { + alert(msg); + } + }, + + breakpoint : function(msg) + { + if(_enabled) + { + eval('debugger'); + } + } + }; +}(); + +/*global ININ: true */ + +// register namespaces +ININ.Web.Common.Type.registerNamespace("ININ.Web.Common.Utilities"); + +// Utilities class +ININ.Web.Common.Utilities = (function() +{ + // public methods + return { + isNullOrUndefined : function(object) + { + return (typeof(object) === "undefined") || (object === null); + }, + + isNullOrEmptyString : function(str) + { + return ((null == str) || (str.length == 0)); + }, + + isType : function(value, type) + { + if(typeof type != "function") + { + throw ININ.Web.Common.ExceptionFactory.createException("Invalid type sent into isType()"); + } + + if(this.isNullOrUndefined(value)) + { + return false; + } + + if(value instanceof type) + { + return true; + } + + return (value.constructor === type); + }, + + getQueryStringValue : function(field) + { + var queryString = window.location.search.substring(1); + var fieldValuePairs = queryString.split("&"); + for (var i = 0; i < fieldValuePairs.length; ++i) + { + var parts = fieldValuePairs[i].split("="); + if (parts[0] == field) + { + return parts[1]; + } + } + + return null; + } + }; +})(); + +ININ.Web.Common.QueryStringDebug = ININ.Web.Common.Utilities.getQueryStringValue('debug'); +if(ININ.Web.Common.QueryStringDebug) +{ + ININ.Web.Common.Debug.enable(); +} + +ININ.Web.Common.QueryStringTraceLevel = ININ.Web.Common.Utilities.getQueryStringValue('traceLevel'); +if(ININ.Web.Common.QueryStringTraceLevel) +{ + ININ.Web.Common.Debug.setTraceLevel(ININ.Web.Common.QueryStringTraceLevel); +} + +/*global ININ: true, Prototype: true, navigator: true */ + +// register namespaces +ININ.Web.Common.Type.registerNamespace("ININ.Web.Common"); + +// Browser class +ININ.Web.Common.Browser = (function() +{ + // private methods + var _detectChromeUserAgent = function() + { + return (navigator.userAgent.indexOf('Chrome/') > -1); + }; + + // public methods + return { + isIE : function() + { + return Prototype.Browser.IE; + }, + + isOpera: function() + { + return Prototype.Browser.Opera; + }, + + isFireFox : function() + { + return Prototype.Browser.Gecko; + }, + + isWebKit : function() + { + return Prototype.Browser.WebKit; + }, + + isSafari : function() + { + return Prototype.Browser.WebKit && !_detectChromeUserAgent(); + }, + + isMobileSafari : function() + { + return Prototype.Browser.MobileSafari; + }, + + isChrome : function() + { + return Prototype.Browser.WebKit && _detectChromeUserAgent(); + }, + + getFireFoxVersion : function() + { + // example: Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2 (.NET CLR 3.5.30729) + + var userAgent; + if(arguments.length == 0) + { + userAgent = navigator.userAgent; + } + else if(arguments.length == 1) + { + userAgent = arguments[0]; + } + + var tokens = userAgent.split(' '); + if (tokens && tokens.length > 0) + { + for(var i = 0; i < tokens.length; ++i) + { + if(tokens[i].startsWith('Firefox')) + { + var version; + + var browserTokens = tokens[i].split('/'); + if (browserTokens && browserTokens.length > 1) + { + version = new ININ.Web.Common.Version(browserTokens[1]); + } + + return version; + } + } + } + + return undefined; + }, + + getIEVersion : function() + { + // example: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; InfoPath.2; .NET CLR 1.1.4322; .NET CLR 3.5.21022; + + var userAgent; + if(arguments.length == 0) + { + userAgent = navigator.userAgent; + } + else if(arguments.length == 1) + { + userAgent = arguments[0]; + } + + var tokens = userAgent.split('; '); + if (tokens && tokens.length > 0) + { + for(var i = 0; i < tokens.length; ++i) + { + if(tokens[i].startsWith('MSIE')) + { + var version; + + var browserTokens = tokens[i].split(' '); + if (browserTokens && browserTokens.length > 1) + { + version = new ININ.Web.Common.Version(browserTokens[1]); + } + + return version; + } + } + } + + return undefined; + } + + }; +})(); + +/*global ININ: true, Class: true */ + +// register namespaces +ININ.Web.Common.Type.registerNamespace("ININ.Web.Common"); + +// Version class +// Supports versions of type X.X.X with an arbitrary long number of X's, where X is a number +ININ.Web.Common.Version = Class.create( +{ + // public methods + initialize:function() + { + this._versionParts = []; + + // check number of arguments sent in constructor + if(arguments.length === 0) + { + throw ININ.Web.Common.ExceptionFactory.createException("Version constructor called with 0 arguments, but expected at least 1."); + } + + // check for multiple arguments + if(arguments.length > 1) + { + this._assignVersionNumbers(arguments); + return; + } + + // check to see if the single argument is a string (and might need to processed) + var arg = arguments[0]; + if(Object.isString(arg)) + { + if(arg.blank()) + { + throw ININ.Web.Common.ExceptionFactory.createException("Version constructor called with a zero length argument."); + } + + if(arg.indexOf(".") != -1) + { + this._parseVersionString(arg); + return; + } + } + + this._versionParts[0] = this._getValidArgument(arg); + }, + + get_majorVersion : function() + { + return this.get_versionPart(0); + }, + + get_minorVersion : function() + { + return this.get_versionPart(1); + }, + + get_buildVersion : function() + { + return this.get_versionPart(2); + }, + + get_revisionVersion : function() + { + return this.get_versionPart(3); + }, + + get_versionPart : function(index) + { + if(index >= this._versionParts.length) + { + return 0; + } + + return this._versionParts[index]; + }, + + equals : function(version) + { + var maxNumParts = Math.max(this._versionParts.length, version._versionParts.length); + for(var i = 0; i < maxNumParts; ++i) + { + if(this.get_versionPart(i) != version.get_versionPart(i)) + { + return false; + } + } + + // this means that every single piece of both versions were equal, so return true + return true; + }, + + isGreaterThan : function(version) + { + var maxNumParts = Math.max(this._versionParts.length, version._versionParts.length); + for(var i = 0; i < maxNumParts; ++i) + { + if(this.get_versionPart(i) > version.get_versionPart(i)) + { + return true; + } + if(this.get_versionPart(i) < version.get_versionPart(i)) + { + return false; + } + } + + // this means they're equal, so return false + return false; + }, + + isLessThan : function(version) + { + var maxNumParts = Math.max(this._versionParts.length, version._versionParts.length); + for(var i = 0; i < maxNumParts; ++i) + { + if(this.get_versionPart(i) < version.get_versionPart(i)) + { + return true; + } + if(this.get_versionPart(i) > version.get_versionPart(i)) + { + return false; + } + } + + // this means they're equal, so return false + return false; + }, + + isGreaterThanOrEqualTo : function(version) + { + return (this.isGreaterThan(version) || this.equals(version)); + }, + + isLessThanOrEqualTo : function(version) + { + return (this.isLessThan(version) || this.equals(version)); + }, + + // private methods + _parseVersionString : function(versionString) + { + var versionParts = versionString.split('.'); + this._assignVersionNumbers(versionParts); + }, + + _assignVersionNumbers : function(versionParts) + { + // make sure every part is a number + for(var i = 0; i < versionParts.length; ++i) + { + var part = this._getValidArgument(versionParts[i]); + this._versionParts[i] = part; + } + }, + + _getValidArgument : function(arg) + { + var validArg; + + if(ININ.Web.Common.Utilities.isNullOrUndefined(arg)) + { + throw ININ.Web.Common.ExceptionFactory.createException("Version constructor called with null/undefined argument."); + } + + if(Object.isString(arg)) + { + if(arg.blank()) + { + throw ININ.Web.Common.ExceptionFactory.createException("Version constructor called with a zero length argument."); + } + + // convert it to a number and let the number branch handle it + arg = parseInt(arg, 10); + } + + if(Object.isNumber(arg)) + { + if(isNaN(arg)) + { + throw ININ.Web.Common.ExceptionFactory.createException("Version constructor called with Nan argument."); + } + + if(arg == Infinity) + { + throw ININ.Web.Common.ExceptionFactory.createException("Version constructor called with Infinity argument."); + } + + if(arg < 0) + { + throw ININ.Web.Common.ExceptionFactory.createException("Version constructor called with a negative number argument."); + } + + return arg; + } + + throw ININ.Web.Common.ExceptionFactory.createException("Version constructor called with an argument that is not of type String or Number."); + }, + + toString : function() + { + var output = ''; + + for (var i = 0; i < this._versionParts.length; ++i) + { + if (output) + { + output += '.'; + } + + output += this._versionParts[i]; + } + + return output; + } + +}); + +/*global ININ: true, Error: true, Prototype: true, alert: true */ + +ININ.Web.Common.Type.registerNamespace("ININ.Web.Common"); + +// Debugging class +ININ.Web.Common.ParameterValidation = function() +{ + // private methods + var _validateType = function(type, argument) + { + return ININ.Web.Common.Utilities.isType(argument, type); + }; + + var _validateAllowEmpty = function(allowEmpty, argument) + { + // if we're checking whether it's empty, then it really needs to be a string + if(!ININ.Web.Common.Utilities.isType(argument, String)) + { + return false; + } + + if(allowEmpty) + { + // any value is ok + return true; + } + + return !argument.blank(); + }; + + var _validateRequired = function(isRequired, argument) + { + if(!isRequired) + { + // not required, so this validator will pass no matter what + return true; + } + + return (!ININ.Web.Common.Utilities.isNullOrUndefined(argument)); + }; + + // public methods + return { + validate : function(args, validators) + { + if(ININ.Web.Common.Debug.isEnabled()) + { + if(ININ.Web.Common.Utilities.isNullOrUndefined(validators)) + { + throw ININ.Web.Common.ExceptionFactory.createException("Validators are null/undefined."); + } + + if(!ININ.Web.Common.Utilities.isNullOrUndefined(args)) + { + if(args.length > validators.length) + { + throw ININ.Web.Common.ExceptionFactory.createException("More args than validators."); + } + + for(var i = 0; i < validators.length; ++i) + { + var validator = validators[i]; + var argument = args[i]; + + if(ININ.Web.Common.Utilities.isNullOrUndefined(validator)) + { + throw ININ.Web.Common.ExceptionFactory.createException("Validator at index " + i + " is null/undefined."); + } + + // validate required, if provided + if(validator.required) + { + if(!_validateRequired(validator.required, argument)) + { + throw ININ.Web.Common.ExceptionFactory.createException("Argument at index " + i + " is required, but is not provided"); + } + } + + // got past the required check, so continue checking if it's actually defined + if(!ININ.Web.Common.Utilities.isNullOrUndefined(argument)) + { + // validate the type, if provided + if(validator.type) + { + if(!_validateType(validator.type, argument)) + { + throw ININ.Web.Common.ExceptionFactory.createException("Argument at index " + i + " is not of type " + validator.type); + } + } + + // validate allowEmpty, if provided + if(validator.allowEmpty && (typeof validator.allowEmpty != "boolean")) + { + throw ININ.Web.Common.ExceptionFactory.createException("AllowEmpty value is not a boolean but '" + validator.allowEmpty + "'"); + } + + if(ININ.Web.Common.Utilities.isType(argument, String) && (typeof validator.allowEmpty == "boolean") && !validator.allowEmpty) + { + if(!_validateAllowEmpty(validator.allowEmpty, argument)) + { + throw ININ.Web.Common.ExceptionFactory.createException("Argument at index " + i + " is not allowed to be empty"); + } + } + + if(ININ.Web.Common.Utilities.isType(argument, Array) && (validator.elementType)) + { + for(var j = 0; j < argument.length; ++j) + { + if(!_validateType(validator.elementType, argument[j])) + { + throw ININ.Web.Common.ExceptionFactory.createException("Element " + j + " in argument at index " + i + " is not of type " + validator.elementType); + } + } + } + } + } + } + } + } + }; +}(); + +// register namespaces +ININ.Web.Common.Type.registerNamespace("ININ.Web.Common.DependencyValidators"); + +// DependencyValidators class +ININ.Web.Common.DependencyValidators = (function() +{ + // private methods + var _checkForPrototype = function() + { + if(!Prototype) + { + throw ININ.Web.Common.ExceptionFactory.createException("Prototype does not exist"); + } + }; + + var _checkForPrototypeVersion = function(requiredVersionString) + { + var requiredVersion = new ININ.Web.Common.Version(requiredVersionString); + var providedVersion = new ININ.Web.Common.Version(Prototype.Version); + if(!providedVersion.isGreaterThanOrEqualTo(requiredVersion)) + { + throw ININ.Web.Common.ExceptionFactory.createException("Prototype is of an unsupported version"); + } + }; + + var _checkForJQuery = function() + { + if(!jQuery) + { + throw ININ.Web.Common.ExceptionFactory.createException("jQuery does not exist"); + } + }; + + var _checkForJQueryVersion = function(requiredVersionString) + { + var requiredVersion = new ININ.Web.Common.Version(requiredVersionString); + var providedVersion = new ININ.Web.Common.Version(jQuery.prototype.jquery); + if(!providedVersion.isGreaterThanOrEqualTo(requiredVersion)) + { + throw ININ.Web.Common.ExceptionFactory.createException("JQuery is of an unsupported version"); + } + }; + + // public methods + return { + requirePrototypeVersion : function(requiredVersion) + { + _checkForPrototype(); + _checkForPrototypeVersion(requiredVersion); + }, + + requireJQueryVersion : function(requiredVersion) + { + _checkForJQuery(); + _checkForJQueryVersion(requiredVersion); + } + }; + +})(); + +/*global ININ: true, Error: true, Prototype: true, alert: true */ + +ININ.Web.Common.Type.registerNamespace("ININ.Web.Common"); + +// ExceptionFactory class +ININ.Web.Common.ExceptionFactory = function() +{ + // private methods + var _popStackFrame = function(error) + { + if (ININ.Web.Common.Utilities.isNullOrUndefined(error) || + ININ.Web.Common.Utilities.isNullOrUndefined(error.stack) || + ININ.Web.Common.Utilities.isNullOrUndefined(error.fileName) || + ININ.Web.Common.Utilities.isNullOrUndefined(error.lineNumber)) + { + return; + } + + var stackFrames = error.stack.split("\n"); + var currentFrame = stackFrames[0]; + var pattern = error.fileName + ":" + error.lineNumber; + while(!ININ.Web.Common.Utilities.isNullOrUndefined(currentFrame) && (currentFrame.indexOf(pattern) === -1)) + { + stackFrames.shift(); + currentFrame = stackFrames[0]; + } + + var nextFrame = stackFrames[1]; + if (ININ.Web.Common.Utilities.isNullOrUndefined(nextFrame)) + { + return; + } + + var nextFrameParts = nextFrame.match(/@(.*):(\d+)$/); + if (ININ.Web.Common.Utilities.isNullOrUndefined(nextFrameParts)) + { + return; + } + + error.fileName = nextFrameParts[1]; + error.lineNumber = parseInt(nextFrameParts[2], 10); + stackFrames.shift(); + error.stack = stackFrames.join("\n"); + }; + + var _getGeneratedFileNameFromError = function(error) + { + // check the firefox variant, then safari variant + return error.fileName || error.sourceURL || null; + }; + + var _getGeneratedLineNumberFromError = function(error) + { + // check the firefox variant, then safari variant + return error.lineNumber || error.line || null; + }; + + var _setBrowserSpecificMembers = function(error, fileName, lineNumber) + { + if(ININ.Web.Common.Browser.isFireFox()) + { + error.fileName = fileName; + error.lineNumber = lineNumber; + } + else if(ININ.Web.Common.Browser.isSafari()) + { + // Safari gets mad if you assign it an undefined value + // it won't assign the real source URL when the error actually gets thrown + // maybe uses a dirty flag or something to mark that it's been changed (even change to another undefined) + if(fileName) + { + error.sourceURL = fileName; + } + if(lineNumber) + { + error.line = lineNumber; + } + } + }; + + // public methods + return { + createException : function(message, fileName, lineNumber) + { + ININ.Web.Common.ParameterValidation.validate(arguments, [ {"required": true, "type": String, "allowEmpty": false}, {"type": String, "allowEmpty": false}, {"type": Number} ]); + + // create the error + var error = new Error(message); + + // pop the stack frame so that the error shows the correct place if file name and line number aren't specified + _popStackFrame(error); + + // get the line number from error (since browsers like firefox and webkit will set that for you) + if(!lineNumber) + { + lineNumber = _getGeneratedLineNumberFromError(error); + } + + // get the file name from error (since browsers like firefox and webkit will set that for you) + if(!fileName) + { + fileName = _getGeneratedFileNameFromError(error); + } + + // attach custom members + error._message = message; + error._fileName = fileName; + error._lineNumber = lineNumber; + + // set browser specific members + _setBrowserSpecificMembers(error, fileName, lineNumber); + + // add methods + error.alert = function() { alert(this._message); }; + error.get_message = function() { return this._message; }; + error.get_lineNumber = function() + { + // webkit browsers set this members when the error gets thrown + // so we can't set it on creation + if(this.line) + { + return this.line; + } + + return this._lineNumber; + }; + error.get_fileName = function() + { + // webkit browsers set this members when the error gets thrown + // so we can't set it on creation + if(this.sourceURL) + { + return this.sourceURL; + } + + return this._fileName; + }; + error.get_displayMessage = function() + { + var msg; + + if(this._message) + { + msg = "Message: '" + this._message + "'"; + } + if(error._fileName) + { + msg = msg + " File: '" + this._fileName + "'"; + } + if(error._lineNumber) + { + msg = msg + " Line:" + this._lineNumber; + } + + return msg; + }; + + // log the error if enabled + ININ.Web.Common.Debug.traceError("Exception created: " + error.get_displayMessage()); + + return error; + } + }; +}(); + +// register namespaces +ININ.Web.Common.Type.registerNamespace("ININ.Web.Common"); + +// check for prototype and version +ININ.Web.Common.DependencyValidators.requirePrototypeVersion("1.6.1"); + +// Interface class +ININ.Web.Common.Interface = Class.create( +{ + // public methods + initialize:function(name, methods, bases, namespaceRoot) + { + // check number of arguments sent in constructor + if((arguments.length < 2) || (arguments.length > 4)) + { + throw ININ.Web.Common.ExceptionFactory.createException("Interface constructor called with " + arguments.length + + "arguments, but expected exactly 2, 3, or 4."); + } + + // initialize the object + this.methods = []; + this._baseInterfaceNames = []; + var namespace = namespaceRoot || window; + + // set the name + this.name = name; + + // keep all base interface names + this._pushBaseInterfaceNames(bases); + + // keep all base interface methods + this._pushBaseInterfaceMethods(bases, namespaceRoot); + + // keep all parameter methods + this._pushMethods(methods); + }, + + get_name : function() + { + return this.name; + }, + + get_methods : function() + { + return this.methods; + }, + + get_baseInterfaceNames : function() + { + return this._baseInterfaceNames; + }, + + // private methods + _pushBaseInterfaceNames : function(baseInterfaceNames) + { + if(baseInterfaceNames) + { + for(var i = 0, len = baseInterfaceNames.length; i < len; ++i) + { + var baseName = baseInterfaceNames[i]; + if(typeof baseName !== 'string') + { + throw ININ.Web.Common.ExceptionFactory.createException("Interface constructor expects base interface names to be passed in as a string."); + } + + // make sure we don't already have it, then add it + if(this._baseInterfaceNames.indexOf(baseName) == -1) + { + this._baseInterfaceNames.push(baseName); + } + } + } + }, + + _pushBaseInterfaceMethods : function(baseInterfaceNames, namespaceRoot) + { + if(baseInterfaceNames) + { + for(var i = 0, len = baseInterfaceNames.length; i < len; ++i) + { + var baseName = baseInterfaceNames[i]; + if(typeof baseName !== 'string') + { + throw ININ.Web.Common.ExceptionFactory.createException("Interface constructor expects base interface names to be passed in as a string."); + } + if(!baseName) + { + throw ININ.Web.Common.ExceptionFactory.createException("Empty base interface name passed into Interface constructor."); + } + + // save the base interface's methods + var baseInterface = ININ.Web.Common.Interface.getInterface(baseName, namespaceRoot); + if(!baseInterface) + { + throw ININ.Web.Common.ExceptionFactory.createException("Unknown base interface passed into Interface constructor: " + baseName); + } + this._pushMethods(baseInterface.methods); + } + } + }, + + _pushMethods : function(methods) + { + for(var i = 0, len = methods.length; i < len; ++i) + { + var method = methods[i]; + + // make sure the method is the right type + if(typeof method !== 'string') + { + throw ININ.Web.Common.ExceptionFactory.createException("Interface constructor expects method names to be passed in as a string."); + } + + // make sure the method has a name + if(method.length === 0) + { + throw ININ.Web.Common.ExceptionFactory.createException("Interface constructor expects method names to be non-empty."); + } + + // make sure we don't already have it, then add it + if(this.methods.indexOf(method) == -1) + { + this.methods.push(method); + } + } + } +}); + +// public static methods +ININ.Web.Common.Interface.isInterface = function(object) +{ + return (object.get_methods() && object.get_baseInterfaceNames() && object.get_name()); +}; + +// This method is meant for ensuring an object implements a class. It will throw an exception if it does not. +// It is meant only for debugging and will only be run when debugging is enabled. +ININ.Web.Common.Interface.ensureImplements = function(object, interfaces, onlyCheckMethods) +{ + if(ININ.Web.Common.Debug.isEnabled()) + { + // check the number of arguments + if((arguments.length != 2) && (arguments.length != 3)) + { + throw ININ.Web.Common.ExceptionFactory.createException("Function Interface.ensureImplements called with " + + arguments.length + " arguments, but expected exactly 2 or 3."); + } + + var errorMsg = ININ.Web.Common.Interface._ensureImplementsReturnErrorMessage(object, interfaces, onlyCheckMethods); + if(errorMsg) + { + throw ININ.Web.Common.ExceptionFactory.createException(errorMsg); + } + } +}; + +// This method check to see if an object implements a class. It will return false if it does not. +// It will be run no matter if debugging is enabled or not. +ININ.Web.Common.Interface.doesImplement = function(object, interfaces, onlyCheckMethods) +{ + // check the number of arguments + if((arguments.length != 2) && (arguments.length != 3)) + { + throw ININ.Web.Common.ExceptionFactory.createException("Function Interface.doesImplement called with " + + arguments.length + " arguments, but expected exactly 2 or 3."); + } + + if(ININ.Web.Common.Interface._ensureImplementsReturnErrorMessage(object, interfaces, onlyCheckMethods)) + { + return false; + } + + return true; +}; + +ININ.Web.Common.Interface.getInterface = function(namespacePath, namespaceRoot) +{ + var namespace = namespaceRoot || window; + + var namespaceParts = namespacePath.split('.'); + + if (namespace != window) + { + namespaceParts.shift(); + } + + for (var i = 0; i < namespaceParts.length; ++i) + { + var currentPart = namespaceParts[i]; + namespace = namespace[currentPart]; + } + + return namespace; +}; + +// private static methods +ININ.Web.Common.Interface._checkInterfaceParameterForNullUndefinedAndObject = function(param) +{ + var validArg; + + if(param === null) + { + throw ININ.Web.Common.ExceptionFactory.createException("Interface parameter is null."); + } + + if(param === undefined) + { + throw ININ.Web.Common.ExceptionFactory.createException("Interface parameter is undefined."); + } + + if(typeof param != "object") + { + throw ININ.Web.Common.ExceptionFactory.createException("Interface parameter is not of type object."); + } +} + +ININ.Web.Common.Interface._ensureImplementsReturnErrorMessage = function(object, interfaces, onlyCheckMethods) +{ + ININ.Web.Common.Interface._checkInterfaceParameterForNullUndefinedAndObject(interfaces); + + // convert interfaces parameter to array if not already, so we can support both arrays and non-arrays + if(typeof interfaces.length == "undefined") + { + var singleInterface = interfaces; + ININ.Web.Common.Interface._checkInterfaceParameterForNullUndefinedAndObject(singleInterface); + + interfaces = []; + interfaces.push(singleInterface); + } + + // make sure the array has something in it + if(interfaces.length == 0) + { + throw ININ.Web.Common.ExceptionFactory.createException("No interfaces passed in to check."); + } + + // iterate over the interface argument + for(var i = 0, len = interfaces.length; i < len; ++i) + { + var curInterface = interfaces[i]; + ININ.Web.Common.Interface._checkInterfaceParameterForNullUndefinedAndObject(curInterface); + + // make sure the interface we have is an interface + if(curInterface.constructor !== ININ.Web.Common.Interface) + { + return "Function Interface.ensureImplements expects interfaces to be instances of Interface."; + } + + if(!onlyCheckMethods) + { + // make sure the class implements interfaces + if(!object.implementsInterface) + { + return "Function Interface.ensureImplements: object does not implement any interfaces."; + } + + // make sure the class implements the interface's name + if(!object.implementsInterface(curInterface)) + { + return "Function Interface.ensureImplements: object does not implement the " + + curInterface.get_name() + " interface."; + } + } + + // iterate over the interface's methods and make sure the class implements it + for(var j = 0, methodsLen = curInterface.methods.length; j < methodsLen; j++) + { + var method = curInterface.methods[j]; + if((!object[method]) || (typeof object[method] !== 'function')) + { + return "Function Interface.ensureImplements: object does not implement the " + + curInterface.get_name() + " interface. Method " + method + " was not found."; + } + } + } + + return null; +}; + +// register namespaces +ININ.Web.Common.Type.registerNamespace("ININ.Web.Common"); + +// check for prototype and version +ININ.Web.Common.DependencyValidators.requirePrototypeVersion("1.6.1"); + +// InterfaceImplementation class +ININ.Web.Common.InterfaceImplementation = Class.create( +{ + // constructor + initialize : function() + { + this._interfaceNames = []; + }, + + // destructor + destroy : function() + { + delete this._interfaceNames; + this._interfaceNames = null; + }, + + // public methods + addImplementedInterface : function(interfaceObject, namespaceRoot) + { + if(!interfaceObject) + { + throw ININ.Web.Common.ExceptionFactory.createException("interface is null/undefined"); + } + + namespaceRoot = namespaceRoot || window; + + // add the interface + this._addImplementedInterfaceName(interfaceObject.name, namespaceRoot); + + // add the interface's base interfaces + var baseInterfaceNames = interfaceObject.get_baseInterfaceNames(); + if(baseInterfaceNames) + { + for(var i = 0; i < baseInterfaceNames.length; ++i) + { + this.addImplementedInterface(ININ.Web.Common.Interface.getInterface(baseInterfaceNames[i], namespaceRoot), namespaceRoot); + } + } + }, + + implementsInterface : function(interfaceObject, namespaceRoot) + { + return (this._interfaceNames.indexOf(interfaceObject.name) != -1); + }, + + // private methods + _addImplementedInterfaceName : function(interfaceName, namespaceRoot) + { + // make sure interface is actually implemented first + ININ.Web.Common.Interface.ensureImplements(this, ININ.Web.Common.Interface.getInterface(interfaceName, namespaceRoot), true); + + // add interface + if(this._interfaceNames.indexOf(interfaceName) == -1) + { + this._interfaceNames.push(interfaceName); + } + } + +}); + +/*global ININ: true, Class: true */ + +// Register namespaces +ININ.Web.Common.Type.registerNamespace("ININ.Web.Common"); + + +// Map class +ININ.Web.Common.Map = Class.create( +{ + // constructor + initialize : function() + { + this._map = {}; + }, + + // destructor + destroy : function() + { + this.removeAll(); + delete this._map; + this._map = null; + }, + + // methods + put : function(key, value) + { + this._map[key] = value; + }, + + get : function(key) + { + return this._map[key]; + }, + + remove : function(key) + { + delete this._map[key]; + }, + + containsKey : function(soughtKey) + { + var foundKey = this.get(soughtKey); + if (foundKey) + { + return true; + } + else + { + return false; + } + }, + + containsValue : function(soughtValue) + { + var key = this.nextKey(); + while (key) + { + if (soughtValue == this.get(key)) + { + return true; + } + key = this.nextKey(key); + } + return false; + }, + + removeAll : function() + { + for (var key in this._map) + { + if(this._map.hasOwnProperty(key)) + { + this.remove(key); + } + } + }, + + size : function() + { + var count = 0; + for (var key in this._map) + { + if(this._map.hasOwnProperty(key)) + { + count++; + } + } + return count; + }, + + isEmpty : function() + { + return this.size() === 0; + }, + + firstKey : function() + { + return this.nextKey(); + }, + + firstObject : function() + { + return this.get(this.firstKey()); + }, + + nextKey : function(returnTheKeyAfterThisOne) + { + var readyToReturn = false; + if (!returnTheKeyAfterThisOne) + { + // If no arg supplied, just return the key found in the first iteration of the loop! + readyToReturn = true; + } + + for (var key in this._map) + { + if(this._map.hasOwnProperty(key)) + { + if (readyToReturn) + { + return key; + } + else if (returnTheKeyAfterThisOne == key) + { + readyToReturn = true; + } + } + } + return null; + }, + + nextObject : function(key) + { + return this.get(this.nextKey(key)); + } +}); + +/*global ININ: true */ + +ININ.Web.Common.Type.registerNamespace("ININ.Web.Common"); + +// Ioc class +ININ.Web.Common.IoC = function() +{ + // private member + var _typeMap = new ININ.Web.Common.Map(); + var _singletonMap = new ININ.Web.Common.Map(); + + // public methods + return { + register : function(type, implementationType) + { + if(!type) + { + throw ININ.Web.Common.ExceptionFactory.createException("Type sent in to IoC.register is null or undefined: " + type); + } + + if(!ININ.Web.Common.Interface.isInterface(type)) + { + throw ININ.Web.Common.ExceptionFactory.createException("Type sent in to IoC.register is not an interface"); + } + + if(_typeMap.get(type.get_name())) + { + throw ININ.Web.Common.ExceptionFactory.createException("Type " + type + " already exists in IoC"); + } + + _typeMap.put(type.get_name(), implementationType); + }, + + registerSingleton : function(type, implementationType) + { + if(!type) + { + throw ININ.Web.Common.ExceptionFactory.createException("Type sent in to IoC.registerSingleton is null or undefined: " + type); + } + + if(!ININ.Web.Common.Interface.isInterface(type)) + { + throw ININ.Web.Common.ExceptionFactory.createException("Type sent in to IoC.registerSingleton is not an interface"); + } + + if(_singletonMap.get(type.get_name())) + { + throw ININ.Web.Common.ExceptionFactory.createException("Type " + type + " already exists in IoC"); + } + + _singletonMap.put(type.get_name(), new implementationType()); + }, + + registerSingletonInstance : function(type, instance) + { + if(!type) + { + throw ININ.Web.Common.ExceptionFactory.createException("Type sent in to IoC.registerSingleton is null or undefined: " + type); + } + + if(!ININ.Web.Common.Interface.isInterface(type)) + { + throw ININ.Web.Common.ExceptionFactory.createException("Type sent in to IoC.registerSingleton is not an interface"); + } + + if(_singletonMap.get(type.get_name())) + { + throw ININ.Web.Common.ExceptionFactory.createException("Type " + type + " already exists in IoC"); + } + + ININ.Web.Common.Interface.ensureImplements(instance, [type]); + + _singletonMap.put(type.get_name(), instance); + }, + + get : function(type) + { + var implementationType = _typeMap.get(type.get_name()); + if(ININ.Web.Common.Utilities.isNullOrUndefined(implementationType)) + { + throw ININ.Web.Common.ExceptionFactory.createException("Could not find implementationType for " + type.get_name()); + } + + return new implementationType(); + }, + + getSingleton : function(type) + { + var singleton = _singletonMap.get(type.get_name()); + if(ININ.Web.Common.Utilities.isNullOrUndefined(singleton)) + { + throw ININ.Web.Common.ExceptionFactory.createException("Could not find singleton for " + type.get_name()); + } + + return singleton; + }, + + remove : function(type) + { + var typeName = type.get_name(); + if(_typeMap.containsKey(typeName)) + { + _typeMap.remove(typeName); + } + }, + + removeSingleton : function(type) + { + var typeName = type.get_name(); + if(_singletonMap.containsKey(typeName)) + { + _singletonMap.remove(typeName); + } + } + }; +}(); + + return ININ.Web.Common; +}); + + diff --git a/src/ChatPage/chat/BypassLoginForm/js/config.js b/src/ChatPage/chat/BypassLoginForm/js/config.js new file mode 100755 index 0000000..fe221ba --- /dev/null +++ b/src/ChatPage/chat/BypassLoginForm/js/config.js @@ -0,0 +1,10 @@ +/*global ININ: true */ + +// Register namespaces +ININ.Web.Common.Type.registerNamespace("ININ.Web.Chat"); + +// Config class +ININ.Web.Chat.Config = +{"ChatTarget":"Chat","CallbackTarget":"Marketing","ChatTargetType":"Workgroup","CallbackTargetType":"Workgroup","InteractionTypes":["chat","callback"],"UseEncryption":true,"ICServerCount":1,"DefaultLanguageCode":"en-us","ConfigVersion":0}; + + diff --git a/src/ChatPage/chat/BypassLoginForm/js/i18n.js b/src/ChatPage/chat/BypassLoginForm/js/i18n.js new file mode 100755 index 0000000..77f93c0 --- /dev/null +++ b/src/ChatPage/chat/BypassLoginForm/js/i18n.js @@ -0,0 +1,183 @@ +/** + * @license RequireJS i18n 2.0.4 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved. + * Available via the MIT or new BSD license. + * see: http://github.com/requirejs/i18n for details + */ +/*jslint regexp: true */ +/*global require: false, navigator: false, define: false */ + +/** + * This plugin handles i18n! prefixed modules. It does the following: + * + * 1) A regular module can have a dependency on an i18n bundle, but the regular + * module does not want to specify what locale to load. So it just specifies + * the top-level bundle, like "i18n!nls/colors". + * + * This plugin will load the i18n bundle at nls/colors, see that it is a root/master + * bundle since it does not have a locale in its name. It will then try to find + * the best match locale available in that master bundle, then request all the + * locale pieces for that best match locale. For instance, if the locale is "en-us", + * then the plugin will ask for the "en-us", "en" and "root" bundles to be loaded + * (but only if they are specified on the master bundle). + * + * Once all the bundles for the locale pieces load, then it mixes in all those + * locale pieces into each other, then finally sets the context.defined value + * for the nls/colors bundle to be that mixed in locale. + * + * 2) A regular module specifies a specific locale to load. For instance, + * i18n!nls/fr-fr/colors. In this case, the plugin needs to load the master bundle + * first, at nls/colors, then figure out what the best match locale is for fr-fr, + * since maybe only fr or just root is defined for that locale. Once that best + * fit is found, all of its locale pieces need to have their bundles loaded. + * + * Once all the bundles for the locale pieces load, then it mixes in all those + * locale pieces into each other, then finally sets the context.defined value + * for the nls/fr-fr/colors bundle to be that mixed in locale. + */ +(function () { + 'use strict'; + + //regexp for reconstructing the master bundle name from parts of the regexp match + //nlsRegExp.exec("foo/bar/baz/nls/en-ca/foo") gives: + //["foo/bar/baz/nls/en-ca/foo", "foo/bar/baz/nls/", "/", "/", "en-ca", "foo"] + //nlsRegExp.exec("foo/bar/baz/nls/foo") gives: + //["foo/bar/baz/nls/foo", "foo/bar/baz/nls/", "/", "/", "foo", ""] + //so, if match[5] is blank, it means this is the top bundle definition. + var nlsRegExp = /(^.*(^|\/)nls(\/|$))([^\/]*)\/?([^\/]*)/; + + //Helper function to avoid repeating code. Lots of arguments in the + //desire to stay functional and support RequireJS contexts without having + //to know about the RequireJS contexts. + function addPart(locale, master, needed, toLoad, prefix, suffix) { + if (master[locale]) { + needed.push(locale); + if (master[locale] === true || master[locale] === 1) { + toLoad.push(prefix + locale + '/' + suffix); + } + } + } + + function addIfExists(req, locale, toLoad, prefix, suffix) { + var fullName = prefix + locale + '/' + suffix; + if (require._fileExists(req.toUrl(fullName + '.js'))) { + toLoad.push(fullName); + } + } + + /** + * Simple function to mix in properties from source into target, + * but only if target does not already have a property of the same name. + * This is not robust in IE for transferring methods that match + * Object.prototype names, but the uses of mixin here seem unlikely to + * trigger a problem related to that. + */ + function mixin(target, source, force) { + var prop; + for (prop in source) { + if (source.hasOwnProperty(prop) && (!target.hasOwnProperty(prop) || force)) { + target[prop] = source[prop]; + } else if (typeof source[prop] === 'object') { + if (!target[prop] && source[prop]) { + target[prop] = {}; + } + mixin(target[prop], source[prop], force); + } + } + } + + define(['module'], function (module) { + var masterConfig = module.config ? module.config() : {}; + + return { + version: '2.0.4', + /** + * Called when a dependency needs to be loaded. + */ + load: function (name, req, onLoad, config) { + config = config || {}; + + if (config.locale) { + masterConfig.locale = config.locale; + } + + var masterName, + match = nlsRegExp.exec(name), + prefix = match[1], + locale = match[4], + suffix = match[5], + parts = locale.split("-"), + toLoad = [], + value = {}, + i, part, current = ""; + + //If match[5] is blank, it means this is the top bundle definition, + //so it does not have to be handled. Locale-specific requests + //will have a match[4] value but no match[5] + if (match[5]) { + //locale-specific bundle + prefix = match[1]; + masterName = prefix + suffix; + } else { + //Top-level bundle. + masterName = name; + suffix = match[4]; + locale = masterConfig.locale; + if (!locale) { + locale = masterConfig.locale = + typeof navigator === "undefined" ? "root" : + (navigator.language || + navigator.userLanguage || "root").toLowerCase(); + } + parts = locale.split("-"); + } + + if (config.isBuild) { + //Check for existence of all locale possible files and + //require them if exist. + toLoad.push(masterName); + addIfExists(req, "root", toLoad, prefix, suffix); + for (i = 0; i < parts.length; i++) { + part = parts[i]; + current += (current ? "-" : "") + part; + addIfExists(req, current, toLoad, prefix, suffix); + } + + req(toLoad, function () { + onLoad(); + }); + } else { + //First, fetch the master bundle, it knows what locales are available. + req([masterName], function (master) { + //Figure out the best fit + var needed = [], + part; + + //Always allow for root, then do the rest of the locale parts. + addPart("root", master, needed, toLoad, prefix, suffix); + for (i = 0; i < parts.length; i++) { + part = parts[i]; + current += (current ? "-" : "") + part; + addPart(current, master, needed, toLoad, prefix, suffix); + } + + //Load all the parts missing. + req(toLoad, function () { + var i, partBundle, part; + for (i = needed.length - 1; i > -1 && needed[i]; i--) { + part = needed[i]; + partBundle = master[part]; + if (partBundle === true || partBundle === 1) { + partBundle = req(prefix + part + '/' + suffix); + } + mixin(value, partBundle); + } + + //All done, notify the loader. + onLoad(value); + }); + }); + } + } + }; + }); +}()); diff --git a/src/ChatPage/chat/BypassLoginForm/js/main.js b/src/ChatPage/chat/BypassLoginForm/js/main.js new file mode 100755 index 0000000..c9c68e1 --- /dev/null +++ b/src/ChatPage/chat/BypassLoginForm/js/main.js @@ -0,0 +1,97 @@ +requirejs.config( +{ + shim: + { + 'prototype': + { + exports: 'Prototype' + }, + 'config': + { + deps: ['common'], + exports: 'ININ.Web.Chat.Config' + }, + 'i18n': + { + deps: ['LanguageOverride'] + } + } +}); + +// Customizations aren't used by this file, but do need to be loaded before the app starts running. +define(['ui', 'config', 'customizations'], function(ui, config, customizations) +{ + var intervalID = null; + function checkVisible() { + if (window.outerWidth !== 0) { + // Either visible from the start, or was initially invisible and then the interval fired + if (null != intervalID) { + // If it was that the interval fired, clear the interval + window.clearInterval(intervalID); + intervalID = null; + } + loadUI(); + } else if (null == intervalID) { + // Initially invisible + intervalID = window.setInterval(checkVisible, 500); + } + } + + var loadUI = function() + { + ui.Page.load(setInteractionWebToolsParams(config, ui)); + }; + + checkVisible(); +}); + +/** + * Displays a message to the web user indicating that a certain module is taking too long to load. + * + * Since this may be called before localization has been loaded, this method contains hard-coded English string(s). + * + * @param err Contains information about the error that has occurred + */ +requirejs.onError = function (err) +{ + console.log("Load failure. requireType=" + err.requireType + ", requireModules=" + err.requireModules); + + // We only try to load one thing at a time, so any previous error is no longer relevant. It should have + // been removed already, but this is just to be safe. + removeLoadError(); + + var parent = document.getElementById('iwc-web-chat-container'); + if (!parent) + { + parent = this.getBody(); + } + var errorDiv = new Element('div', { 'class': 'iwc-load-error' }); + errorDiv.id = 'iwc-load-error'; + var errorImg = new Element('img', { 'src': 'img/error.png' }); + var errorMsg = new Element('span', null); + errorMsg.innerHTML = "The attempt to load " + err.requireModules + " is taking a long time. Attempting to load it will continue, but may not ever succeed."; + errorDiv.appendChild(errorImg); + errorDiv.appendChild(errorMsg); + parent.appendChild(errorDiv); +}; + +/** + * Removes the error message created by addLoadError() + */ +removeLoadError = function() +{ + var errorDiv = document.getElementById('iwc-load-error'); + if (null != errorDiv) + { + var parent = errorDiv.parentNode; + if (null != parent) + { + parent.removeChild(errorDiv); + } + } +}; + +// Bootloader is no longer necessary, but its onLoadedConfig() +// method is still called from the config file. Stub that out. +Bootloader = new Object(); +Bootloader.onLoadedConfig = function() { }; diff --git a/src/ChatPage/chat/BypassLoginForm/js/nls/en-us/localization.js b/src/ChatPage/chat/BypassLoginForm/js/nls/en-us/localization.js new file mode 100755 index 0000000..423317b --- /dev/null +++ b/src/ChatPage/chat/BypassLoginForm/js/nls/en-us/localization.js @@ -0,0 +1,201 @@ +define({ + +"LoginContainerHeaderText": "Interaction Web Tools", +"MainPanelHeaderText": "Interaction Web Tools", +"ClosePageWarning": "Are you sure you want to disconnect your current chat? Your option to receive a transcript will be lost.", +"ExitChatWarning": "Are you sure you want to disconnect your current chat? Your option to receive a transcript will be lost.", +"OutOfOrderMessage": "This message have been delivered out of order:", +"StartChatButton": "Start Chat", +"StartChatTab": "Chat", +"StartCallbackButton": "Start Callback", +"StartCallbackTab": "Callback", +"RegisterNewAccountTab": "Create an Account", +"Login": "Login", +"Register": "Register", +"IHaveAnAccount": "I have an account", +"IDontHaveAnAccount": "I don't have an account", +"CreateAnAccount": "Create an account", +"OptionalTag": "(Optional)", +"NameLabel": "Name:", +"DescriptionLabel": "Description:", +"TelephoneLabel": "Telephone:", +"PasswordLabel": "Password:", +"ConfirmPasswordLabel": "Confirm Password:", +"UserNameLabel": "Username:", +"lastNameLabel": "Last Name:", +"firstNameLabel": "First Name:", +"middleNameLabel": "Middle Name:", +"departmentLabel": "Department:", +"companyLabel": "Company:", +"jobTitleLabel": "Job Title:", +"assistantNameLabel": "Assistant Name:", +"webLoginLabel": "Web Login:", +"webPasswordLabel": "Web Password:", +"homeStreetAddressLabel": "Street Address:", +"homeCityLabel": "City:", +"homeStateLabel": "State:", +"homePostalCodeLabel": "Zip:", +"homeCountryLabel": "Country:", +"homeEmailLabel": "E-mail:", +"homePhoneLabel": "Phone:", +"homePhone2Label": "Additional Phone:", +"homeFaxLabel": "Fax:", +"homePagerLabel": "Pager:", +"homeMobileLabel": "Mobile:", +"homeUrlLabel": "URL:", +"businessStreetAddressLabel": "Street Address:", +"businessCityLabel": "City:", +"businessStateLabel": "State:", +"businessPostalCodeLabel": "Zip:", +"businessCountryLabel": "Country:", +"businessEmailLabel": "E-mail:", +"businessPhoneLabel": "Phone:", +"businessPhone2Label": "Additional Phone:", +"businessFaxLabel": "Fax:", +"businessPagerLabel": "Pager:", +"businessMobileLabel": "Mobile:", +"businessUrlLabel": "URL:", +"assistantPhoneLabel": "Assistant Phone:", +"remarksLabel": "Remarks:", +"LoginFailed": "Login failed", +"RegistrationSucceeded": "Registration succeeded", +"RegistrationFailed": "Registration failed", +"CallbackSucceeded": "An agent will call you back as soon as possible", +"CallbackFailed": "Callback failed", +"CreateCallback": "Submit", +"InProcess": "In Process", +"Waiting": "Waiting", +"CallbackCreatorNameLabel": "Requestor:", +"CallbackCreationDateTimeLabel": "Requested at:", +"AssignedAgentLabel": "Assigned Agent:", +"CallbackStateLabel": "State:", +"EstimatedCallbackTimeLabel": "Estimated callback time:", +"WaitTimeLabel": "Average wait time:", +"QueuePositionLabel": "Position in %0 queue:", +"LongestWaitTimeLabel": "Longest wait time:", +"TimeDuration_Minute": "About a minute", +"TimeDuration_Minutes": "About %0 minutes", +"TimeDuration_Minutes_AlternatePlural": "About %0 minutes", +"TimeDuration_Hour": "About an hour", +"TimeDuration_Hours": "About %0 hours", +"TimeDuration_Hours_AlternatePlural": "About %0 hours", +"TimeDuration_Day": "About a day", +"TimeDuration_Days": "About %0 days", +"TimeDuration_Days_AlternatePlural": "About %0 days", +"InteractionsWaitingCountLabel": "Interactions waiting:", +"LoggedInAgentsCountLabel": "Logged in agents:", +"AvailableAgentsCountLabel": "Available agents:", +"Callback": "Callback", +"CallbackStatusFailed": "Couldn't get the status of the callback.", +"UserIdLabel": "User ID:", +"DisconnectCallback": "Cancel", +"DisconnectCallbackSucceeded": "The callback request has been canceled.", +"DisconnectCallbackFailed": "Failed to cancel this callback request", +"EnterNameIfDesired": "Enter name if desired", +"AnonymousUser": "Anonymous User", +"Account": "Account", +"Name": "Name", +"Home": "Home", +"Business": "Business", +"OneErrorWithChatData": "There was one error with the chat information.", +"MultipleErrorsWithChatData": "There were %0 errors with the chat information.", +"OneErrorWithRegistrationData": "There was one error with the registration information.", +"MultipleErrorsWithRegistrationData": "There were %0 errors with the registration information.", +"OneErrorWithCallbackData": "There was one error with the callback information.", +"MultipleErrorsWithCallbackData": "There were %0 errors with the callback information.", +"FieldIsRequired": "Field is required", +"PasswordsDoNotMatch": "Passwords do not match", +"GeneralError": "General error", +"InvalidCharSetError": "Character set of content is invalid", +"InvalidContentTypeError": "Type of content is invalid", +"ContentError": "Content is invalid", +"MissingDataError": "Content is missing required data", +"UnknownEntityError": "Unknown entity", +"UnknownSessionError": "Unknown session", +"UnknownParticipantError": "Unknown participant", +"BadTargetError": "Unknown ACD target", +"UserDbError": "User account error", +"UserNotOnline": "User is not online", +"BadCredentialsError": "User name or password is incorrect", +"AccountExistsError": "Account already exists", +"FailedToSendMessage": "Failed to send message", +"ErrorConnectingToServer": "There was an error connecting to the server", +"SendOnEnter": "Press Enter to send message", +"Send": "Send", +"Exit": "Exit", +"DisconnectedFromChat": "You have been disconnected from the chat", +"ErrorConnectingServer": "Error connecting to the server. Please wait while we try to reconnect...", +"SuccessfullyReconnectedServer": "Successfully reconnected to the server.", +"CouldNotConnectServerRetry": "Could not connect to any servers. Will retry in a moment.", +"PleaseLeaveMessage": "Please leave a message for %0", +"PrintableChatHistory": "Printable chat history", +"ChatHistory": "Chat History", +"Print": "Print", +"LinkDisclaimer": "*Some links may not be valid after the chat ends.", +/* Note that the following line uses single quotes, to avoid having nested double quotes. */ +"NeedPageRefresh_Format": 'Sorry for the inconvenience, but we have encountered a system failure that will require you to start a new chat.', +"BrowserSecuritySettingsError": "Web browser security settings will not allow Interaction Web Tools to run.", +"FireFoxVersionError": "FireFox versions prior to 3.5 are not supported", +"IEVersionError": "Internet Explorer versions prior to 7 are not supported", +"ErrorOpeningWindow": "There was an error opening the window.", + +"Typing": "(typing)", +"LastRespondedTime": "Last response sent at: %0", +"Disclaimer": "Disclaimer: Subject of callbacks and content of chats might be monitored, recorded, or viewed by agents and supervisors for quality monitoring and routing and distribution purposes.", + +"DayOfWeek0": "Sunday", +"DayOfWeek1": "Monday", +"DayOfWeek2": "Tuesday", +"DayOfWeek3": "Wednesday", +"DayOfWeek4": "Thursday", +"DayOfWeek5": "Friday", +"DayOfWeek6": "Saturday", +"AbbreviatedDayOfWeek0": "Sun", +"AbbreviatedDayOfWeek1": "Mon", +"AbbreviatedDayOfWeek2": "Tue", +"AbbreviatedDayOfWeek3": "Wed", +"AbbreviatedDayOfWeek4": "Thu", +"AbbreviatedDayOfWeek5": "Fri", +"AbbreviatedDayOfWeek6": "Sat", + +"Month1": "January", +"Month2": "February", +"Month3": "March", +"Month4": "April", +"Month5": "May", +"Month6": "June", +"Month7": "July", +"Month8": "August", +"Month9": "September", +"Month10": "October", +"Month11": "November", +"Month12": "December", +"AbbreviatedMonth1": "Jan", +"AbbreviatedMonth2": "Feb", +"AbbreviatedMonth3": "Mar", +"AbbreviatedMonth4": "Apr", +"AbbreviatedMonth5": "May", +"AbbreviatedMonth6": "Jun", +"AbbreviatedMonth7": "Jul", +"AbbreviatedMonth8": "Aug", +"AbbreviatedMonth9": "Sep", +"AbbreviatedMonth10": "Oct", +"AbbreviatedMonth11": "Nov", +"AbbreviatedMonth12": "Dec", + +"AM": "a.m.", +"PM": "p.m.", +"AbbreviatedAM": "a", +"AbbreviatedPM": "p", + +"Era": "CE", +"AbbreviatedEra": "CE", + +"FallbackDateFormat": "d MMM yyyy", +"FallbackTimeFormat": "h:mm tt", + +"OverrideDateTimeFormats": "0", + +"LanguageCode": "en-us", +"TextDirection": "ltr" +}); diff --git a/src/ChatPage/chat/BypassLoginForm/js/nls/localization.js b/src/ChatPage/chat/BypassLoginForm/js/nls/localization.js new file mode 100755 index 0000000..d2cb933 --- /dev/null +++ b/src/ChatPage/chat/BypassLoginForm/js/nls/localization.js @@ -0,0 +1,33 @@ +var g_defaultLanguageResourceFilePath = null; + +function languageCodeToResourceFilePath(languageCode) { + return 'js/nls/' + languageCode + '/localization.js'; +} + +define(['config'], function(config) { + g_defaultLanguageResourceFilePath = languageCodeToResourceFilePath(config.DefaultLanguageCode) || 'en-us'; + + return { + "root": true, + "ar": true, + "da": true, + "de": true, + "en-us": true, + "es": true, + "fr": true, + "he": true, + "it": true, + "ja": true, + "ko": true, + "nl": true, + "no": true, + "pl": true, + "pt-br": true, + "ru": true, + "sr": true, + "sv": true, + "tr": true, + "zh-cn": true, + "zh-tw": true + }; +}); diff --git a/src/ChatPage/chat/BypassLoginForm/js/nls/root/localization.js b/src/ChatPage/chat/BypassLoginForm/js/nls/root/localization.js new file mode 100755 index 0000000..300d5c2 --- /dev/null +++ b/src/ChatPage/chat/BypassLoginForm/js/nls/root/localization.js @@ -0,0 +1,5 @@ +// The "?1" is to create a unique filename. Otherwise, we are asking RequireJS to load a file +// which its I18N plugin has already loaded. This causes RequireJS to give a timeout error. +define([g_defaultLanguageResourceFilePath+"?1"], function(defaultLanguageResources) { + return defaultLanguageResources; +}); diff --git a/src/ChatPage/chat/BypassLoginForm/js/prototype.js b/src/ChatPage/chat/BypassLoginForm/js/prototype.js new file mode 100755 index 0000000..100773e --- /dev/null +++ b/src/ChatPage/chat/BypassLoginForm/js/prototype.js @@ -0,0 +1,4874 @@ +/* Prototype JavaScript framework, version 1.6.1 + * (c) 2005-2009 Sam Stephenson + * + * Prototype is freely distributable under the terms of an MIT-style license. + * For details, see the Prototype web site: http://www.prototypejs.org/ + * + *--------------------------------------------------------------------------*/ + +var Prototype = { + Version: '1.6.1', + + Browser: (function(){ + var ua = navigator.userAgent; + var isOpera = Object.prototype.toString.call(window.opera) == '[object Opera]'; + return { + IE: !!window.attachEvent && !isOpera, + Opera: isOpera, + WebKit: ua.indexOf('AppleWebKit/') > -1, + Gecko: ua.indexOf('Gecko') > -1 && ua.indexOf('KHTML') === -1, + MobileSafari: /Apple.*Mobile.*Safari/.test(ua) + } + })(), + + BrowserFeatures: { + XPath: !!document.evaluate, + SelectorsAPI: !!document.querySelector, + ElementExtensions: (function() { + var constructor = window.Element || window.HTMLElement; + return !!(constructor && constructor.prototype); + })(), + SpecificElementExtensions: (function() { + if (typeof window.HTMLDivElement !== 'undefined') + return true; + + var div = document.createElement('div'); + var form = document.createElement('form'); + var isSupported = false; + + if (div['__proto__'] && (div['__proto__'] !== form['__proto__'])) { + isSupported = true; + } + + div = form = null; + + return isSupported; + })() + }, + + ScriptFragment: ']*>([\\S\\s]*?)<\/script>', + JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/, + + emptyFunction: function() { }, + K: function(x) { return x } +}; + +if (Prototype.Browser.MobileSafari) + Prototype.BrowserFeatures.SpecificElementExtensions = false; + + +var Abstract = { }; + + +var Try = { + these: function() { + var returnValue; + + for (var i = 0, length = arguments.length; i < length; i++) { + var lambda = arguments[i]; + try { + returnValue = lambda(); + break; + } catch (e) { } + } + + return returnValue; + } +}; + +/* Based on Alex Arnell's inheritance implementation. */ + +var Class = (function() { + function subclass() {}; + function create() { + var parent = null, properties = $A(arguments); + if (Object.isFunction(properties[0])) + parent = properties.shift(); + + function klass() { + this.initialize.apply(this, arguments); + } + + Object.extend(klass, Class.Methods); + klass.superclass = parent; + klass.subclasses = []; + + if (parent) { + subclass.prototype = parent.prototype; + klass.prototype = new subclass; + parent.subclasses.push(klass); + } + + for (var i = 0; i < properties.length; i++) + klass.addMethods(properties[i]); + + if (!klass.prototype.initialize) + klass.prototype.initialize = Prototype.emptyFunction; + + klass.prototype.constructor = klass; + return klass; + } + + function addMethods(source) { + var ancestor = this.superclass && this.superclass.prototype; + var properties = Object.keys(source); + + if (!Object.keys({ toString: true }).length) { + if (source.toString != Object.prototype.toString) + properties.push("toString"); + if (source.valueOf != Object.prototype.valueOf) + properties.push("valueOf"); + } + + for (var i = 0, length = properties.length; i < length; i++) { + var property = properties[i], value = source[property]; + if (ancestor && Object.isFunction(value) && + value.argumentNames().first() == "$super") { + var method = value; + value = (function(m) { + return function() { return ancestor[m].apply(this, arguments); }; + })(property).wrap(method); + + value.valueOf = method.valueOf.bind(method); + value.toString = method.toString.bind(method); + } + this.prototype[property] = value; + } + + return this; + } + + return { + create: create, + Methods: { + addMethods: addMethods + } + }; +})(); +(function() { + + var _toString = Object.prototype.toString; + + function extend(destination, source) { + for (var property in source) + destination[property] = source[property]; + return destination; + } + + function inspect(object) { + try { + if (isUndefined(object)) return 'undefined'; + if (object === null) return 'null'; + return object.inspect ? object.inspect() : String(object); + } catch (e) { + if (e instanceof RangeError) return '...'; + throw e; + } + } + + function toJSON(object) { + var type = typeof object; + switch (type) { + case 'undefined': + case 'function': + case 'unknown': return; + case 'boolean': return object.toString(); + } + + if (object === null) return 'null'; + if (object.toJSON) return object.toJSON(); + if (isElement(object)) return; + + var results = []; + for (var property in object) { + var value = toJSON(object[property]); + if (!isUndefined(value)) + results.push(property.toJSON() + ': ' + value); + } + + return '{' + results.join(', ') + '}'; + } + + function toQueryString(object) { + return $H(object).toQueryString(); + } + + function toHTML(object) { + return object && object.toHTML ? object.toHTML() : String.interpret(object); + } + + function keys(object) { + var results = []; + for (var property in object) + results.push(property); + return results; + } + + function values(object) { + var results = []; + for (var property in object) + results.push(object[property]); + return results; + } + + function clone(object) { + return extend({ }, object); + } + + function isElement(object) { + return !!(object && object.nodeType == 1); + } + + function isArray(object) { + return _toString.call(object) == "[object Array]"; + } + + + function isHash(object) { + return object instanceof Hash; + } + + function isFunction(object) { + return typeof object === "function"; + } + + function isString(object) { + return _toString.call(object) == "[object String]"; + } + + function isNumber(object) { + return _toString.call(object) == "[object Number]"; + } + + function isUndefined(object) { + return typeof object === "undefined"; + } + + extend(Object, { + extend: extend, + inspect: inspect, + toJSON: toJSON, + toQueryString: toQueryString, + toHTML: toHTML, + keys: keys, + values: values, + clone: clone, + isElement: isElement, + isArray: isArray, + isHash: isHash, + isFunction: isFunction, + isString: isString, + isNumber: isNumber, + isUndefined: isUndefined + }); +})(); +Object.extend(Function.prototype, (function() { + var slice = Array.prototype.slice; + + function update(array, args) { + var arrayLength = array.length, length = args.length; + while (length--) array[arrayLength + length] = args[length]; + return array; + } + + function merge(array, args) { + array = slice.call(array, 0); + return update(array, args); + } + + function argumentNames() { + var names = this.toString().match(/^[\s\(]*function[^(]*\(([^)]*)\)/)[1] + .replace(/\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g, '') + .replace(/\s+/g, '').split(','); + return names.length == 1 && !names[0] ? [] : names; + } + + function bind(context) { + if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this; + var __method = this, args = slice.call(arguments, 1); + return function() { + var a = merge(args, arguments); + return __method.apply(context, a); + } + } + + function bindAsEventListener(context) { + var __method = this, args = slice.call(arguments, 1); + return function(event) { + var a = update([event || window.event], args); + return __method.apply(context, a); + } + } + + function curry() { + if (!arguments.length) return this; + var __method = this, args = slice.call(arguments, 0); + return function() { + var a = merge(args, arguments); + return __method.apply(this, a); + } + } + + function delay(timeout) { + var __method = this, args = slice.call(arguments, 1); + timeout = timeout * 1000 + return window.setTimeout(function() { + return __method.apply(__method, args); + }, timeout); + } + + function defer() { + var args = update([0.01], arguments); + return this.delay.apply(this, args); + } + + function wrap(wrapper) { + var __method = this; + return function() { + var a = update([__method.bind(this)], arguments); + return wrapper.apply(this, a); + } + } + + function methodize() { + if (this._methodized) return this._methodized; + var __method = this; + return this._methodized = function() { + var a = update([this], arguments); + return __method.apply(null, a); + }; + } + + return { + argumentNames: argumentNames, + bind: bind, + bindAsEventListener: bindAsEventListener, + curry: curry, + delay: delay, + defer: defer, + wrap: wrap, + methodize: methodize + } +})()); + + +Date.prototype.toJSON = function() { + return '"' + this.getUTCFullYear() + '-' + + (this.getUTCMonth() + 1).toPaddedString(2) + '-' + + this.getUTCDate().toPaddedString(2) + 'T' + + this.getUTCHours().toPaddedString(2) + ':' + + this.getUTCMinutes().toPaddedString(2) + ':' + + this.getUTCSeconds().toPaddedString(2) + 'Z"'; +}; + + +RegExp.prototype.match = RegExp.prototype.test; + +RegExp.escape = function(str) { + return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1'); +}; +var PeriodicalExecuter = Class.create({ + initialize: function(callback, frequency) { + this.callback = callback; + this.frequency = frequency; + this.currentlyExecuting = false; + + this.registerCallback(); + }, + + registerCallback: function() { + this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + execute: function() { + this.callback(this); + }, + + stop: function() { + if (!this.timer) return; + clearInterval(this.timer); + this.timer = null; + }, + + onTimerEvent: function() { + if (!this.currentlyExecuting) { + try { + this.currentlyExecuting = true; + this.execute(); + this.currentlyExecuting = false; + } catch(e) { + this.currentlyExecuting = false; + throw e; + } + } + } +}); +Object.extend(String, { + interpret: function(value) { + return value == null ? '' : String(value); + }, + specialChar: { + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '\\': '\\\\' + } +}); + +Object.extend(String.prototype, (function() { + + function prepareReplacement(replacement) { + if (Object.isFunction(replacement)) return replacement; + var template = new Template(replacement); + return function(match) { return template.evaluate(match) }; + } + + function gsub(pattern, replacement) { + var result = '', source = this, match; + replacement = prepareReplacement(replacement); + + if (Object.isString(pattern)) + pattern = RegExp.escape(pattern); + + if (!(pattern.length || pattern.source)) { + replacement = replacement(''); + return replacement + source.split('').join(replacement) + replacement; + } + + while (source.length > 0) { + if (match = source.match(pattern)) { + result += source.slice(0, match.index); + result += String.interpret(replacement(match)); + source = source.slice(match.index + match[0].length); + } else { + result += source, source = ''; + } + } + return result; + } + + function sub(pattern, replacement, count) { + replacement = prepareReplacement(replacement); + count = Object.isUndefined(count) ? 1 : count; + + return this.gsub(pattern, function(match) { + if (--count < 0) return match[0]; + return replacement(match); + }); + } + + function scan(pattern, iterator) { + this.gsub(pattern, iterator); + return String(this); + } + + function truncate(length, truncation) { + length = length || 30; + truncation = Object.isUndefined(truncation) ? '...' : truncation; + return this.length > length ? + this.slice(0, length - truncation.length) + truncation : String(this); + } + + function strip() { + return this.replace(/^\s+/, '').replace(/\s+$/, ''); + } + + function stripTags() { + return this.replace(/<\w+(\s+("[^"]*"|'[^']*'|[^>])+)?>|<\/\w+>/gi, ''); + } + + function stripScripts() { + return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); + } + + function extractScripts() { + var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); + var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); + return (this.match(matchAll) || []).map(function(scriptTag) { + return (scriptTag.match(matchOne) || ['', ''])[1]; + }); + } + + function evalScripts() { + return this.extractScripts().map(function(script) { return eval(script) }); + } + + function escapeHTML() { + return this.replace(/&/g,'&').replace(//g,'>'); + } + + function unescapeHTML() { + return this.stripTags().replace(/</g,'<').replace(/>/g,'>').replace(/&/g,'&'); + } + + + function toQueryParams(separator) { + var match = this.strip().match(/([^?#]*)(#.*)?$/); + if (!match) return { }; + + return match[1].split(separator || '&').inject({ }, function(hash, pair) { + if ((pair = pair.split('='))[0]) { + var key = decodeURIComponent(pair.shift()); + var value = pair.length > 1 ? pair.join('=') : pair[0]; + if (value != undefined) value = decodeURIComponent(value); + + if (key in hash) { + if (!Object.isArray(hash[key])) hash[key] = [hash[key]]; + hash[key].push(value); + } + else hash[key] = value; + } + return hash; + }); + } + + function toArray() { + return this.split(''); + } + + function succ() { + return this.slice(0, this.length - 1) + + String.fromCharCode(this.charCodeAt(this.length - 1) + 1); + } + + function times(count) { + return count < 1 ? '' : new Array(count + 1).join(this); + } + + function camelize() { + var parts = this.split('-'), len = parts.length; + if (len == 1) return parts[0]; + + var camelized = this.charAt(0) == '-' + ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1) + : parts[0]; + + for (var i = 1; i < len; i++) + camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1); + + return camelized; + } + + function capitalize() { + return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase(); + } + + function underscore() { + return this.replace(/::/g, '/') + .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2') + .replace(/([a-z\d])([A-Z])/g, '$1_$2') + .replace(/-/g, '_') + .toLowerCase(); + } + + function dasherize() { + return this.replace(/_/g, '-'); + } + + function inspect(useDoubleQuotes) { + var escapedString = this.replace(/[\x00-\x1f\\]/g, function(character) { + if (character in String.specialChar) { + return String.specialChar[character]; + } + return '\\u00' + character.charCodeAt().toPaddedString(2, 16); + }); + if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"'; + return "'" + escapedString.replace(/'/g, '\\\'') + "'"; + } + + function toJSON() { + return this.inspect(true); + } + + function unfilterJSON(filter) { + return this.replace(filter || Prototype.JSONFilter, '$1'); + } + + function isJSON() { + var str = this; + if (str.blank()) return false; + str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''); + return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str); + } + + function evalJSON(sanitize) { + var json = this.unfilterJSON(); + try { + if (!sanitize || json.isJSON()) return eval('(' + json + ')'); + } catch (e) { } + throw new SyntaxError('Badly formed JSON string: ' + this.inspect()); + } + + function include(pattern) { + return this.indexOf(pattern) > -1; + } + + function startsWith(pattern) { + return this.indexOf(pattern) === 0; + } + + function endsWith(pattern) { + var d = this.length - pattern.length; + return d >= 0 && this.lastIndexOf(pattern) === d; + } + + function empty() { + return this == ''; + } + + function blank() { + return /^\s*$/.test(this); + } + + function interpolate(object, pattern) { + return new Template(this, pattern).evaluate(object); + } + + return { + gsub: gsub, + sub: sub, + scan: scan, + truncate: truncate, + strip: String.prototype.trim ? String.prototype.trim : strip, + stripTags: stripTags, + stripScripts: stripScripts, + extractScripts: extractScripts, + evalScripts: evalScripts, + escapeHTML: escapeHTML, + unescapeHTML: unescapeHTML, + toQueryParams: toQueryParams, + parseQuery: toQueryParams, + toArray: toArray, + succ: succ, + times: times, + camelize: camelize, + capitalize: capitalize, + underscore: underscore, + dasherize: dasherize, + inspect: inspect, + toJSON: toJSON, + unfilterJSON: unfilterJSON, + isJSON: isJSON, + evalJSON: evalJSON, + include: include, + startsWith: startsWith, + endsWith: endsWith, + empty: empty, + blank: blank, + interpolate: interpolate + }; +})()); + +var Template = Class.create({ + initialize: function(template, pattern) { + this.template = template.toString(); + this.pattern = pattern || Template.Pattern; + }, + + evaluate: function(object) { + if (object && Object.isFunction(object.toTemplateReplacements)) + object = object.toTemplateReplacements(); + + return this.template.gsub(this.pattern, function(match) { + if (object == null) return (match[1] + ''); + + var before = match[1] || ''; + if (before == '\\') return match[2]; + + var ctx = object, expr = match[3]; + var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/; + match = pattern.exec(expr); + if (match == null) return before; + + while (match != null) { + var comp = match[1].startsWith('[') ? match[2].replace(/\\\\]/g, ']') : match[1]; + ctx = ctx[comp]; + if (null == ctx || '' == match[3]) break; + expr = expr.substring('[' == match[3] ? match[1].length : match[0].length); + match = pattern.exec(expr); + } + + return before + String.interpret(ctx); + }); + } +}); +Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; + +var $break = { }; + +var Enumerable = (function() { + function each(iterator, context) { + var index = 0; + try { + this._each(function(value) { + iterator.call(context, value, index++); + }); + } catch (e) { + if (e != $break) throw e; + } + return this; + } + + function eachSlice(number, iterator, context) { + var index = -number, slices = [], array = this.toArray(); + if (number < 1) return array; + while ((index += number) < array.length) + slices.push(array.slice(index, index+number)); + return slices.collect(iterator, context); + } + + function all(iterator, context) { + iterator = iterator || Prototype.K; + var result = true; + this.each(function(value, index) { + result = result && !!iterator.call(context, value, index); + if (!result) throw $break; + }); + return result; + } + + function any(iterator, context) { + iterator = iterator || Prototype.K; + var result = false; + this.each(function(value, index) { + if (result = !!iterator.call(context, value, index)) + throw $break; + }); + return result; + } + + function collect(iterator, context) { + iterator = iterator || Prototype.K; + var results = []; + this.each(function(value, index) { + results.push(iterator.call(context, value, index)); + }); + return results; + } + + function detect(iterator, context) { + var result; + this.each(function(value, index) { + if (iterator.call(context, value, index)) { + result = value; + throw $break; + } + }); + return result; + } + + function findAll(iterator, context) { + var results = []; + this.each(function(value, index) { + if (iterator.call(context, value, index)) + results.push(value); + }); + return results; + } + + function grep(filter, iterator, context) { + iterator = iterator || Prototype.K; + var results = []; + + if (Object.isString(filter)) + filter = new RegExp(RegExp.escape(filter)); + + this.each(function(value, index) { + if (filter.match(value)) + results.push(iterator.call(context, value, index)); + }); + return results; + } + + function include(object) { + if (Object.isFunction(this.indexOf)) + if (this.indexOf(object) != -1) return true; + + var found = false; + this.each(function(value) { + if (value == object) { + found = true; + throw $break; + } + }); + return found; + } + + function inGroupsOf(number, fillWith) { + fillWith = Object.isUndefined(fillWith) ? null : fillWith; + return this.eachSlice(number, function(slice) { + while(slice.length < number) slice.push(fillWith); + return slice; + }); + } + + function inject(memo, iterator, context) { + this.each(function(value, index) { + memo = iterator.call(context, memo, value, index); + }); + return memo; + } + + function invoke(method) { + var args = $A(arguments).slice(1); + return this.map(function(value) { + return value[method].apply(value, args); + }); + } + + function max(iterator, context) { + iterator = iterator || Prototype.K; + var result; + this.each(function(value, index) { + value = iterator.call(context, value, index); + if (result == null || value >= result) + result = value; + }); + return result; + } + + function min(iterator, context) { + iterator = iterator || Prototype.K; + var result; + this.each(function(value, index) { + value = iterator.call(context, value, index); + if (result == null || value < result) + result = value; + }); + return result; + } + + function partition(iterator, context) { + iterator = iterator || Prototype.K; + var trues = [], falses = []; + this.each(function(value, index) { + (iterator.call(context, value, index) ? + trues : falses).push(value); + }); + return [trues, falses]; + } + + function pluck(property) { + var results = []; + this.each(function(value) { + results.push(value[property]); + }); + return results; + } + + function reject(iterator, context) { + var results = []; + this.each(function(value, index) { + if (!iterator.call(context, value, index)) + results.push(value); + }); + return results; + } + + function sortBy(iterator, context) { + return this.map(function(value, index) { + return { + value: value, + criteria: iterator.call(context, value, index) + }; + }).sort(function(left, right) { + var a = left.criteria, b = right.criteria; + return a < b ? -1 : a > b ? 1 : 0; + }).pluck('value'); + } + + function toArray() { + return this.map(); + } + + function zip() { + var iterator = Prototype.K, args = $A(arguments); + if (Object.isFunction(args.last())) + iterator = args.pop(); + + var collections = [this].concat(args).map($A); + return this.map(function(value, index) { + return iterator(collections.pluck(index)); + }); + } + + function size() { + return this.toArray().length; + } + + function inspect() { + return '#'; + } + + + + + + + + + + return { + each: each, + eachSlice: eachSlice, + all: all, + every: all, + any: any, + some: any, + collect: collect, + map: collect, + detect: detect, + findAll: findAll, + select: findAll, + filter: findAll, + grep: grep, + include: include, + member: include, + inGroupsOf: inGroupsOf, + inject: inject, + invoke: invoke, + max: max, + min: min, + partition: partition, + pluck: pluck, + reject: reject, + sortBy: sortBy, + toArray: toArray, + entries: toArray, + zip: zip, + size: size, + inspect: inspect, + find: detect + }; +})(); +function $A(iterable) { + if (!iterable) return []; + if ('toArray' in Object(iterable)) return iterable.toArray(); + var length = iterable.length || 0, results = new Array(length); + while (length--) results[length] = iterable[length]; + return results; +} + +function $w(string) { + if (!Object.isString(string)) return []; + string = string.strip(); + return string ? string.split(/\s+/) : []; +} + +Array.from = $A; + + +(function() { + var arrayProto = Array.prototype, + slice = arrayProto.slice, + _each = arrayProto.forEach; // use native browser JS 1.6 implementation if available + + function each(iterator) { + for (var i = 0, length = this.length; i < length; i++) + iterator(this[i]); + } + if (!_each) _each = each; + + function clear() { + this.length = 0; + return this; + } + + function first() { + return this[0]; + } + + function last() { + return this[this.length - 1]; + } + + function compact() { + return this.select(function(value) { + return value != null; + }); + } + + function flatten() { + return this.inject([], function(array, value) { + if (Object.isArray(value)) + return array.concat(value.flatten()); + array.push(value); + return array; + }); + } + + function without() { + var values = slice.call(arguments, 0); + return this.select(function(value) { + return !values.include(value); + }); + } + + function reverse(inline) { + return (inline !== false ? this : this.toArray())._reverse(); + } + + function uniq(sorted) { + return this.inject([], function(array, value, index) { + if (0 == index || (sorted ? array.last() != value : !array.include(value))) + array.push(value); + return array; + }); + } + + function intersect(array) { + return this.uniq().findAll(function(item) { + return array.detect(function(value) { return item === value }); + }); + } + + + function clone() { + return slice.call(this, 0); + } + + function size() { + return this.length; + } + + function inspect() { + return '[' + this.map(Object.inspect).join(', ') + ']'; + } + + function toJSON() { + var results = []; + this.each(function(object) { + var value = Object.toJSON(object); + if (!Object.isUndefined(value)) results.push(value); + }); + return '[' + results.join(', ') + ']'; + } + + function indexOf(item, i) { + i || (i = 0); + var length = this.length; + if (i < 0) i = length + i; + for (; i < length; i++) + if (this[i] === item) return i; + return -1; + } + + function lastIndexOf(item, i) { + i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1; + var n = this.slice(0, i).reverse().indexOf(item); + return (n < 0) ? n : i - n - 1; + } + + function concat() { + var array = slice.call(this, 0), item; + for (var i = 0, length = arguments.length; i < length; i++) { + item = arguments[i]; + if (Object.isArray(item) && !('callee' in item)) { + for (var j = 0, arrayLength = item.length; j < arrayLength; j++) + array.push(item[j]); + } else { + array.push(item); + } + } + return array; + } + + Object.extend(arrayProto, Enumerable); + + if (!arrayProto._reverse) + arrayProto._reverse = arrayProto.reverse; + + Object.extend(arrayProto, { + _each: _each, + clear: clear, + first: first, + last: last, + compact: compact, + flatten: flatten, + without: without, + reverse: reverse, + uniq: uniq, + intersect: intersect, + clone: clone, + toArray: clone, + size: size, + inspect: inspect, + toJSON: toJSON + }); + + var CONCAT_ARGUMENTS_BUGGY = (function() { + return [].concat(arguments)[0][0] !== 1; + })(1,2) + + if (CONCAT_ARGUMENTS_BUGGY) arrayProto.concat = concat; + + if (!arrayProto.indexOf) arrayProto.indexOf = indexOf; + if (!arrayProto.lastIndexOf) arrayProto.lastIndexOf = lastIndexOf; +})(); +function $H(object) { + return new Hash(object); +}; + +var Hash = Class.create(Enumerable, (function() { + function initialize(object) { + this._object = Object.isHash(object) ? object.toObject() : Object.clone(object); + } + + function _each(iterator) { + for (var key in this._object) { + var value = this._object[key], pair = [key, value]; + pair.key = key; + pair.value = value; + iterator(pair); + } + } + + function set(key, value) { + return this._object[key] = value; + } + + function get(key) { + if (this._object[key] !== Object.prototype[key]) + return this._object[key]; + } + + function unset(key) { + var value = this._object[key]; + delete this._object[key]; + return value; + } + + function toObject() { + return Object.clone(this._object); + } + + function keys() { + return this.pluck('key'); + } + + function values() { + return this.pluck('value'); + } + + function index(value) { + var match = this.detect(function(pair) { + return pair.value === value; + }); + return match && match.key; + } + + function merge(object) { + return this.clone().update(object); + } + + function update(object) { + return new Hash(object).inject(this, function(result, pair) { + result.set(pair.key, pair.value); + return result; + }); + } + + function toQueryPair(key, value) { + if (Object.isUndefined(value)) return key; + return key + '=' + encodeURIComponent(String.interpret(value)); + } + + function toQueryString() { + return this.inject([], function(results, pair) { + var key = encodeURIComponent(pair.key), values = pair.value; + + if (values && typeof values == 'object') { + if (Object.isArray(values)) + return results.concat(values.map(toQueryPair.curry(key))); + } else results.push(toQueryPair(key, values)); + return results; + }).join('&'); + } + + function inspect() { + return '#'; + } + + function toJSON() { + return Object.toJSON(this.toObject()); + } + + function clone() { + return new Hash(this); + } + + return { + initialize: initialize, + _each: _each, + set: set, + get: get, + unset: unset, + toObject: toObject, + toTemplateReplacements: toObject, + keys: keys, + values: values, + index: index, + merge: merge, + update: update, + toQueryString: toQueryString, + inspect: inspect, + toJSON: toJSON, + clone: clone + }; +})()); + +Hash.from = $H; +Object.extend(Number.prototype, (function() { + function toColorPart() { + return this.toPaddedString(2, 16); + } + + function succ() { + return this + 1; + } + + function times(iterator, context) { + $R(0, this, true).each(iterator, context); + return this; + } + + function toPaddedString(length, radix) { + var string = this.toString(radix || 10); + return '0'.times(length - string.length) + string; + } + + function toJSON() { + return isFinite(this) ? this.toString() : 'null'; + } + + function abs() { + return Math.abs(this); + } + + function round() { + return Math.round(this); + } + + function ceil() { + return Math.ceil(this); + } + + function floor() { + return Math.floor(this); + } + + return { + toColorPart: toColorPart, + succ: succ, + times: times, + toPaddedString: toPaddedString, + toJSON: toJSON, + abs: abs, + round: round, + ceil: ceil, + floor: floor + }; +})()); + +function $R(start, end, exclusive) { + return new ObjectRange(start, end, exclusive); +} + +var ObjectRange = Class.create(Enumerable, (function() { + function initialize(start, end, exclusive) { + this.start = start; + this.end = end; + this.exclusive = exclusive; + } + + function _each(iterator) { + var value = this.start; + while (this.include(value)) { + iterator(value); + value = value.succ(); + } + } + + function include(value) { + if (value < this.start) + return false; + if (this.exclusive) + return value < this.end; + return value <= this.end; + } + + return { + initialize: initialize, + _each: _each, + include: include + }; +})()); + + + +var Ajax = { + getTransport: function() { + return Try.these( + function() {return new XMLHttpRequest()}, + function() {return new ActiveXObject('Msxml2.XMLHTTP')}, + function() {return new ActiveXObject('Microsoft.XMLHTTP')} + ) || false; + }, + + activeRequestCount: 0 +}; + +Ajax.Responders = { + responders: [], + + _each: function(iterator) { + this.responders._each(iterator); + }, + + register: function(responder) { + if (!this.include(responder)) + this.responders.push(responder); + }, + + unregister: function(responder) { + this.responders = this.responders.without(responder); + }, + + dispatch: function(callback, request, transport, json) { + this.each(function(responder) { + if (Object.isFunction(responder[callback])) { + try { + responder[callback].apply(responder, [request, transport, json]); + } catch (e) { } + } + }); + } +}; + +Object.extend(Ajax.Responders, Enumerable); + +Ajax.Responders.register({ + onCreate: function() { Ajax.activeRequestCount++ }, + onComplete: function() { Ajax.activeRequestCount-- } +}); +Ajax.Base = Class.create({ + initialize: function(options) { + this.options = { + method: 'post', + asynchronous: true, + contentType: 'application/x-www-form-urlencoded', + encoding: 'UTF-8', + parameters: '', + evalJSON: true, + evalJS: true + }; + Object.extend(this.options, options || { }); + + this.options.method = this.options.method.toLowerCase(); + + if (Object.isString(this.options.parameters)) + this.options.parameters = this.options.parameters.toQueryParams(); + else if (Object.isHash(this.options.parameters)) + this.options.parameters = this.options.parameters.toObject(); + } +}); +Ajax.Request = Class.create(Ajax.Base, { + _complete: false, + + initialize: function($super, url, options) { + $super(options); + this.transport = Ajax.getTransport(); + this.request(url); + }, + + request: function(url) { + this.url = url; + this.method = this.options.method; + var params = Object.clone(this.options.parameters); + + if (!['get', 'post'].include(this.method)) { + params['_method'] = this.method; + this.method = 'post'; + } + + this.parameters = params; + + if (params = Object.toQueryString(params)) { + if (this.method == 'get') + this.url += (this.url.include('?') ? '&' : '?') + params; + else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) + params += '&_='; + } + + try { + var response = new Ajax.Response(this); + if (this.options.onCreate) this.options.onCreate(response); + Ajax.Responders.dispatch('onCreate', this, response); + + this.transport.open(this.method.toUpperCase(), this.url, + this.options.asynchronous); + + if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1); + + this.transport.onreadystatechange = this.onStateChange.bind(this); + this.setRequestHeaders(); + + this.body = this.method == 'post' ? (this.options.postBody || params) : null; + this.transport.send(this.body); + + /* Force Firefox to handle ready state 4 for synchronous requests */ + if (!this.options.asynchronous && this.transport.overrideMimeType) + this.onStateChange(); + + } + catch (e) { + this.dispatchException(e); + } + }, + + onStateChange: function() { + var readyState = this.transport.readyState; + if (readyState > 1 && !((readyState == 4) && this._complete)) + this.respondToReadyState(this.transport.readyState); + }, + + setRequestHeaders: function() { + var headers = { + 'X-Requested-With': 'XMLHttpRequest', + 'X-Prototype-Version': Prototype.Version, + 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' + }; + + if (this.method == 'post') { + headers['Content-type'] = this.options.contentType + + (this.options.encoding ? '; charset=' + this.options.encoding : ''); + + /* Force "Connection: close" for older Mozilla browsers to work + * around a bug where XMLHttpRequest sends an incorrect + * Content-length header. See Mozilla Bugzilla #246651. + */ + if (this.transport.overrideMimeType && + (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) + headers['Connection'] = 'close'; + } + + if (typeof this.options.requestHeaders == 'object') { + var extras = this.options.requestHeaders; + + if (Object.isFunction(extras.push)) + for (var i = 0, length = extras.length; i < length; i += 2) + headers[extras[i]] = extras[i+1]; + else + $H(extras).each(function(pair) { headers[pair.key] = pair.value }); + } + + for (var name in headers) + this.transport.setRequestHeader(name, headers[name]); + }, + + success: function() { + var status = this.getStatus(); + return !status || (status >= 200 && status < 300); + }, + + getStatus: function() { + try { + return this.transport.status || 0; + } catch (e) { return 0 } + }, + + respondToReadyState: function(readyState) { + var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this); + + if (state == 'Complete') { + try { + this._complete = true; + (this.options['on' + response.status] + || this.options['on' + (this.success() ? 'Success' : 'Failure')] + || Prototype.emptyFunction)(response, response.headerJSON); + } catch (e) { + this.dispatchException(e); + } + + var contentType = response.getHeader('Content-type'); + if (this.options.evalJS == 'force' + || (this.options.evalJS && this.isSameOrigin() && contentType + && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i))) + this.evalResponse(); + } + + try { + (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON); + Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON); + } catch (e) { + this.dispatchException(e); + } + + if (state == 'Complete') { + this.transport.onreadystatechange = Prototype.emptyFunction; + } + }, + + isSameOrigin: function() { + var m = this.url.match(/^\s*https?:\/\/[^\/]*/); + return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({ + protocol: location.protocol, + domain: document.domain, + port: location.port ? ':' + location.port : '' + })); + }, + + getHeader: function(name) { + try { + return this.transport.getResponseHeader(name) || null; + } catch (e) { return null; } + }, + + evalResponse: function() { + try { + return eval((this.transport.responseText || '').unfilterJSON()); + } catch (e) { + this.dispatchException(e); + } + }, + + dispatchException: function(exception) { + (this.options.onException || Prototype.emptyFunction)(this, exception); + Ajax.Responders.dispatch('onException', this, exception); + } +}); + +Ajax.Request.Events = + ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; + + + + + + + + +Ajax.Response = Class.create({ + initialize: function(request){ + this.request = request; + var transport = this.transport = request.transport, + readyState = this.readyState = transport.readyState; + + if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) { + this.status = this.getStatus(); + this.statusText = this.getStatusText(); + this.responseText = String.interpret(transport.responseText); + this.headerJSON = this._getHeaderJSON(); + } + + if(readyState == 4) { + var xml = transport.responseXML; + this.responseXML = Object.isUndefined(xml) ? null : xml; + this.responseJSON = this._getResponseJSON(); + } + }, + + status: 0, + + statusText: '', + + getStatus: Ajax.Request.prototype.getStatus, + + getStatusText: function() { + try { + return this.transport.statusText || ''; + } catch (e) { return '' } + }, + + getHeader: Ajax.Request.prototype.getHeader, + + getAllHeaders: function() { + try { + return this.getAllResponseHeaders(); + } catch (e) { return null } + }, + + getResponseHeader: function(name) { + return this.transport.getResponseHeader(name); + }, + + getAllResponseHeaders: function() { + return this.transport.getAllResponseHeaders(); + }, + + _getHeaderJSON: function() { + var json = this.getHeader('X-JSON'); + if (!json) return null; + json = decodeURIComponent(escape(json)); + try { + return json.evalJSON(this.request.options.sanitizeJSON || + !this.request.isSameOrigin()); + } catch (e) { + this.request.dispatchException(e); + } + }, + + _getResponseJSON: function() { + var options = this.request.options; + if (!options.evalJSON || (options.evalJSON != 'force' && + !(this.getHeader('Content-type') || '').include('application/json')) || + this.responseText.blank()) + return null; + try { + return this.responseText.evalJSON(options.sanitizeJSON || + !this.request.isSameOrigin()); + } catch (e) { + this.request.dispatchException(e); + } + } +}); + +Ajax.Updater = Class.create(Ajax.Request, { + initialize: function($super, container, url, options) { + this.container = { + success: (container.success || container), + failure: (container.failure || (container.success ? null : container)) + }; + + options = Object.clone(options); + var onComplete = options.onComplete; + options.onComplete = (function(response, json) { + this.updateContent(response.responseText); + if (Object.isFunction(onComplete)) onComplete(response, json); + }).bind(this); + + $super(url, options); + }, + + updateContent: function(responseText) { + var receiver = this.container[this.success() ? 'success' : 'failure'], + options = this.options; + + if (!options.evalScripts) responseText = responseText.stripScripts(); + + if (receiver = $(receiver)) { + if (options.insertion) { + if (Object.isString(options.insertion)) { + var insertion = { }; insertion[options.insertion] = responseText; + receiver.insert(insertion); + } + else options.insertion(receiver, responseText); + } + else receiver.update(responseText); + } + } +}); + +Ajax.PeriodicalUpdater = Class.create(Ajax.Base, { + initialize: function($super, container, url, options) { + $super(options); + this.onComplete = this.options.onComplete; + + this.frequency = (this.options.frequency || 2); + this.decay = (this.options.decay || 1); + + this.updater = { }; + this.container = container; + this.url = url; + + this.start(); + }, + + start: function() { + this.options.onComplete = this.updateComplete.bind(this); + this.onTimerEvent(); + }, + + stop: function() { + this.updater.options.onComplete = undefined; + clearTimeout(this.timer); + (this.onComplete || Prototype.emptyFunction).apply(this, arguments); + }, + + updateComplete: function(response) { + if (this.options.decay) { + this.decay = (response.responseText == this.lastText ? + this.decay * this.options.decay : 1); + + this.lastText = response.responseText; + } + this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency); + }, + + onTimerEvent: function() { + this.updater = new Ajax.Updater(this.container, this.url, this.options); + } +}); + + + +function $(element) { + if (arguments.length > 1) { + for (var i = 0, elements = [], length = arguments.length; i < length; i++) + elements.push($(arguments[i])); + return elements; + } + if (Object.isString(element)) + element = document.getElementById(element); + return Element.extend(element); +} + +if (Prototype.BrowserFeatures.XPath) { + document._getElementsByXPath = function(expression, parentElement) { + var results = []; + var query = document.evaluate(expression, $(parentElement) || document, + null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); + for (var i = 0, length = query.snapshotLength; i < length; i++) + results.push(Element.extend(query.snapshotItem(i))); + return results; + }; +} + +/*--------------------------------------------------------------------------*/ + +if (!window.Node) var Node = { }; + +if (!Node.ELEMENT_NODE) { + Object.extend(Node, { + ELEMENT_NODE: 1, + ATTRIBUTE_NODE: 2, + TEXT_NODE: 3, + CDATA_SECTION_NODE: 4, + ENTITY_REFERENCE_NODE: 5, + ENTITY_NODE: 6, + PROCESSING_INSTRUCTION_NODE: 7, + COMMENT_NODE: 8, + DOCUMENT_NODE: 9, + DOCUMENT_TYPE_NODE: 10, + DOCUMENT_FRAGMENT_NODE: 11, + NOTATION_NODE: 12 + }); +} + + +(function(global) { + + var SETATTRIBUTE_IGNORES_NAME = (function(){ + var elForm = document.createElement("form"); + var elInput = document.createElement("input"); + var root = document.documentElement; + elInput.setAttribute("name", "test"); + elForm.appendChild(elInput); + root.appendChild(elForm); + var isBuggy = elForm.elements + ? (typeof elForm.elements.test == "undefined") + : null; + root.removeChild(elForm); + elForm = elInput = null; + return isBuggy; + })(); + + var element = global.Element; + global.Element = function(tagName, attributes) { + attributes = attributes || { }; + tagName = tagName.toLowerCase(); + var cache = Element.cache; + if (SETATTRIBUTE_IGNORES_NAME && attributes.name) { + tagName = '<' + tagName + ' name="' + attributes.name + '">'; + delete attributes.name; + return Element.writeAttribute(document.createElement(tagName), attributes); + } + if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName)); + return Element.writeAttribute(cache[tagName].cloneNode(false), attributes); + }; + Object.extend(global.Element, element || { }); + if (element) global.Element.prototype = element.prototype; +})(this); + +Element.cache = { }; +Element.idCounter = 1; + +Element.Methods = { + visible: function(element) { + return $(element).style.display != 'none'; + }, + + toggle: function(element) { + element = $(element); + Element[Element.visible(element) ? 'hide' : 'show'](element); + return element; + }, + + + hide: function(element) { + element = $(element); + element.style.display = 'none'; + return element; + }, + + show: function(element) { + element = $(element); + element.style.display = ''; + return element; + }, + + remove: function(element) { + element = $(element); + element.parentNode.removeChild(element); + return element; + }, + + update: (function(){ + + var SELECT_ELEMENT_INNERHTML_BUGGY = (function(){ + var el = document.createElement("select"), + isBuggy = true; + el.innerHTML = ""; + if (el.options && el.options[0]) { + isBuggy = el.options[0].nodeName.toUpperCase() !== "OPTION"; + } + el = null; + return isBuggy; + })(); + + var TABLE_ELEMENT_INNERHTML_BUGGY = (function(){ + try { + var el = document.createElement("table"); + if (el && el.tBodies) { + el.innerHTML = "test"; + var isBuggy = typeof el.tBodies[0] == "undefined"; + el = null; + return isBuggy; + } + } catch (e) { + return true; + } + })(); + + var SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING = (function () { + var s = document.createElement("script"), + isBuggy = false; + try { + s.appendChild(document.createTextNode("")); + isBuggy = !s.firstChild || + s.firstChild && s.firstChild.nodeType !== 3; + } catch (e) { + isBuggy = true; + } + s = null; + return isBuggy; + })(); + + function update(element, content) { + element = $(element); + + if (content && content.toElement) + content = content.toElement(); + + if (Object.isElement(content)) + return element.update().insert(content); + + content = Object.toHTML(content); + + var tagName = element.tagName.toUpperCase(); + + if (tagName === 'SCRIPT' && SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING) { + element.text = content; + return element; + } + + if (SELECT_ELEMENT_INNERHTML_BUGGY || TABLE_ELEMENT_INNERHTML_BUGGY) { + if (tagName in Element._insertionTranslations.tags) { + while (element.firstChild) { + element.removeChild(element.firstChild); + } + Element._getContentFromAnonymousElement(tagName, content.stripScripts()) + .each(function(node) { + element.appendChild(node) + }); + } + else { + element.innerHTML = content.stripScripts(); + } + } + else { + element.innerHTML = content.stripScripts(); + } + + content.evalScripts.bind(content).defer(); + return element; + } + + return update; + })(), + + replace: function(element, content) { + element = $(element); + if (content && content.toElement) content = content.toElement(); + else if (!Object.isElement(content)) { + content = Object.toHTML(content); + var range = element.ownerDocument.createRange(); + range.selectNode(element); + content.evalScripts.bind(content).defer(); + content = range.createContextualFragment(content.stripScripts()); + } + element.parentNode.replaceChild(content, element); + return element; + }, + + insert: function(element, insertions) { + element = $(element); + + if (Object.isString(insertions) || Object.isNumber(insertions) || + Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML))) + insertions = {bottom:insertions}; + + var content, insert, tagName, childNodes; + + for (var position in insertions) { + content = insertions[position]; + position = position.toLowerCase(); + insert = Element._insertionTranslations[position]; + + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) { + insert(element, content); + continue; + } + + content = Object.toHTML(content); + + tagName = ((position == 'before' || position == 'after') + ? element.parentNode : element).tagName.toUpperCase(); + + childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); + + if (position == 'top' || position == 'after') childNodes.reverse(); + childNodes.each(insert.curry(element)); + + content.evalScripts.bind(content).defer(); + } + + return element; + }, + + wrap: function(element, wrapper, attributes) { + element = $(element); + if (Object.isElement(wrapper)) + $(wrapper).writeAttribute(attributes || { }); + else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes); + else wrapper = new Element('div', wrapper); + if (element.parentNode) + element.parentNode.replaceChild(wrapper, element); + wrapper.appendChild(element); + return wrapper; + }, + + inspect: function(element) { + element = $(element); + var result = '<' + element.tagName.toLowerCase(); + $H({'id': 'id', 'className': 'class'}).each(function(pair) { + var property = pair.first(), attribute = pair.last(); + var value = (element[property] || '').toString(); + if (value) result += ' ' + attribute + '=' + value.inspect(true); + }); + return result + '>'; + }, + + recursivelyCollect: function(element, property) { + element = $(element); + var elements = []; + while (element = element[property]) + if (element.nodeType == 1) + elements.push(Element.extend(element)); + return elements; + }, + + ancestors: function(element) { + return Element.recursivelyCollect(element, 'parentNode'); + }, + + descendants: function(element) { + return Element.select(element, "*"); + }, + + firstDescendant: function(element) { + element = $(element).firstChild; + while (element && element.nodeType != 1) element = element.nextSibling; + return $(element); + }, + + immediateDescendants: function(element) { + if (!(element = $(element).firstChild)) return []; + while (element && element.nodeType != 1) element = element.nextSibling; + if (element) return [element].concat($(element).nextSiblings()); + return []; + }, + + previousSiblings: function(element) { + return Element.recursivelyCollect(element, 'previousSibling'); + }, + + nextSiblings: function(element) { + return Element.recursivelyCollect(element, 'nextSibling'); + }, + + siblings: function(element) { + element = $(element); + return Element.previousSiblings(element).reverse() + .concat(Element.nextSiblings(element)); + }, + + match: function(element, selector) { + if (Object.isString(selector)) + selector = new Selector(selector); + return selector.match($(element)); + }, + + up: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return $(element.parentNode); + var ancestors = Element.ancestors(element); + return Object.isNumber(expression) ? ancestors[expression] : + Selector.findElement(ancestors, expression, index); + }, + + down: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return Element.firstDescendant(element); + return Object.isNumber(expression) ? Element.descendants(element)[expression] : + Element.select(element, expression)[index || 0]; + }, + + previous: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element)); + var previousSiblings = Element.previousSiblings(element); + return Object.isNumber(expression) ? previousSiblings[expression] : + Selector.findElement(previousSiblings, expression, index); + }, + + next: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element)); + var nextSiblings = Element.nextSiblings(element); + return Object.isNumber(expression) ? nextSiblings[expression] : + Selector.findElement(nextSiblings, expression, index); + }, + + + select: function(element) { + var args = Array.prototype.slice.call(arguments, 1); + return Selector.findChildElements(element, args); + }, + + adjacent: function(element) { + var args = Array.prototype.slice.call(arguments, 1); + return Selector.findChildElements(element.parentNode, args).without(element); + }, + + identify: function(element) { + element = $(element); + var id = Element.readAttribute(element, 'id'); + if (id) return id; + do { id = 'anonymous_element_' + Element.idCounter++ } while ($(id)); + Element.writeAttribute(element, 'id', id); + return id; + }, + + readAttribute: function(element, name) { + element = $(element); + if (Prototype.Browser.IE) { + var t = Element._attributeTranslations.read; + if (t.values[name]) return t.values[name](element, name); + if (t.names[name]) name = t.names[name]; + if (name.include(':')) { + return (!element.attributes || !element.attributes[name]) ? null : + element.attributes[name].value; + } + } + return element.getAttribute(name); + }, + + writeAttribute: function(element, name, value) { + element = $(element); + var attributes = { }, t = Element._attributeTranslations.write; + + if (typeof name == 'object') attributes = name; + else attributes[name] = Object.isUndefined(value) ? true : value; + + for (var attr in attributes) { + name = t.names[attr] || attr; + value = attributes[attr]; + if (t.values[attr]) name = t.values[attr](element, value); + if (value === false || value === null) + element.removeAttribute(name); + else if (value === true) + element.setAttribute(name, name); + else element.setAttribute(name, value); + } + return element; + }, + + getHeight: function(element) { + return Element.getDimensions(element).height; + }, + + getWidth: function(element) { + return Element.getDimensions(element).width; + }, + + classNames: function(element) { + return new Element.ClassNames(element); + }, + + hasClassName: function(element, className) { + if (!(element = $(element))) return; + var elementClassName = element.className; + return (elementClassName.length > 0 && (elementClassName == className || + new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName))); + }, + + addClassName: function(element, className) { + if (!(element = $(element))) return; + if (!Element.hasClassName(element, className)) + element.className += (element.className ? ' ' : '') + className; + return element; + }, + + removeClassName: function(element, className) { + if (!(element = $(element))) return; + element.className = element.className.replace( + new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip(); + return element; + }, + + toggleClassName: function(element, className) { + if (!(element = $(element))) return; + return Element[Element.hasClassName(element, className) ? + 'removeClassName' : 'addClassName'](element, className); + }, + + cleanWhitespace: function(element) { + element = $(element); + var node = element.firstChild; + while (node) { + var nextNode = node.nextSibling; + if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) + element.removeChild(node); + node = nextNode; + } + return element; + }, + + empty: function(element) { + return $(element).innerHTML.blank(); + }, + + descendantOf: function(element, ancestor) { + element = $(element), ancestor = $(ancestor); + + if (element.compareDocumentPosition) + return (element.compareDocumentPosition(ancestor) & 8) === 8; + + if (ancestor.contains) + return ancestor.contains(element) && ancestor !== element; + + while (element = element.parentNode) + if (element == ancestor) return true; + + return false; + }, + + scrollTo: function(element) { + element = $(element); + var pos = Element.cumulativeOffset(element); + window.scrollTo(pos[0], pos[1]); + return element; + }, + + getStyle: function(element, style) { + element = $(element); + style = style == 'float' ? 'cssFloat' : style.camelize(); + var value = element.style[style]; + if (!value || value == 'auto') { + var css = document.defaultView.getComputedStyle(element, null); + value = css ? css[style] : null; + } + if (style == 'opacity') return value ? parseFloat(value) : 1.0; + return value == 'auto' ? null : value; + }, + + getOpacity: function(element) { + return $(element).getStyle('opacity'); + }, + + setStyle: function(element, styles) { + element = $(element); + var elementStyle = element.style, match; + if (Object.isString(styles)) { + element.style.cssText += ';' + styles; + return styles.include('opacity') ? + element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element; + } + for (var property in styles) + if (property == 'opacity') element.setOpacity(styles[property]); + else + elementStyle[(property == 'float' || property == 'cssFloat') ? + (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') : + property] = styles[property]; + + return element; + }, + + setOpacity: function(element, value) { + element = $(element); + element.style.opacity = (value == 1 || value === '') ? '' : + (value < 0.00001) ? 0 : value; + return element; + }, + + getDimensions: function(element) { + element = $(element); + var display = Element.getStyle(element, 'display'); + if (display != 'none' && display != null) // Safari bug + return {width: element.offsetWidth, height: element.offsetHeight}; + + var els = element.style; + var originalVisibility = els.visibility; + var originalPosition = els.position; + var originalDisplay = els.display; + els.visibility = 'hidden'; + if (originalPosition != 'fixed') // Switching fixed to absolute causes issues in Safari + els.position = 'absolute'; + els.display = 'block'; + var originalWidth = element.clientWidth; + var originalHeight = element.clientHeight; + els.display = originalDisplay; + els.position = originalPosition; + els.visibility = originalVisibility; + return {width: originalWidth, height: originalHeight}; + }, + + makePositioned: function(element) { + element = $(element); + var pos = Element.getStyle(element, 'position'); + if (pos == 'static' || !pos) { + element._madePositioned = true; + element.style.position = 'relative'; + if (Prototype.Browser.Opera) { + element.style.top = 0; + element.style.left = 0; + } + } + return element; + }, + + undoPositioned: function(element) { + element = $(element); + if (element._madePositioned) { + element._madePositioned = undefined; + element.style.position = + element.style.top = + element.style.left = + element.style.bottom = + element.style.right = ''; + } + return element; + }, + + makeClipping: function(element) { + element = $(element); + if (element._overflow) return element; + element._overflow = Element.getStyle(element, 'overflow') || 'auto'; + if (element._overflow !== 'hidden') + element.style.overflow = 'hidden'; + return element; + }, + + undoClipping: function(element) { + element = $(element); + if (!element._overflow) return element; + element.style.overflow = element._overflow == 'auto' ? '' : element._overflow; + element._overflow = null; + return element; + }, + + cumulativeOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + return Element._returnOffset(valueL, valueT); + }, + + positionedOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + if (element) { + if (element.tagName.toUpperCase() == 'BODY') break; + var p = Element.getStyle(element, 'position'); + if (p !== 'static') break; + } + } while (element); + return Element._returnOffset(valueL, valueT); + }, + + absolutize: function(element) { + element = $(element); + if (Element.getStyle(element, 'position') == 'absolute') return element; + + var offsets = Element.positionedOffset(element); + var top = offsets[1]; + var left = offsets[0]; + var width = element.clientWidth; + var height = element.clientHeight; + + element._originalLeft = left - parseFloat(element.style.left || 0); + element._originalTop = top - parseFloat(element.style.top || 0); + element._originalWidth = element.style.width; + element._originalHeight = element.style.height; + + element.style.position = 'absolute'; + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.width = width + 'px'; + element.style.height = height + 'px'; + return element; + }, + + relativize: function(element) { + element = $(element); + if (Element.getStyle(element, 'position') == 'relative') return element; + + element.style.position = 'relative'; + var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); + var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); + + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.height = element._originalHeight; + element.style.width = element._originalWidth; + return element; + }, + + cumulativeScrollOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.scrollTop || 0; + valueL += element.scrollLeft || 0; + element = element.parentNode; + } while (element); + return Element._returnOffset(valueL, valueT); + }, + + getOffsetParent: function(element) { + if (element.offsetParent) return $(element.offsetParent); + if (element == document.body) return $(element); + + while ((element = element.parentNode) && element != document.body) + if (Element.getStyle(element, 'position') != 'static') + return $(element); + + return $(document.body); + }, + + viewportOffset: function(forElement) { + var valueT = 0, valueL = 0; + + var element = forElement; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + + if (element.offsetParent == document.body && + Element.getStyle(element, 'position') == 'absolute') break; + + } while (element = element.offsetParent); + + element = forElement; + do { + if (!Prototype.Browser.Opera || (element.tagName && (element.tagName.toUpperCase() == 'BODY'))) { + valueT -= element.scrollTop || 0; + valueL -= element.scrollLeft || 0; + } + } while (element = element.parentNode); + + return Element._returnOffset(valueL, valueT); + }, + + clonePosition: function(element, source) { + var options = Object.extend({ + setLeft: true, + setTop: true, + setWidth: true, + setHeight: true, + offsetTop: 0, + offsetLeft: 0 + }, arguments[2] || { }); + + source = $(source); + var p = Element.viewportOffset(source); + + element = $(element); + var delta = [0, 0]; + var parent = null; + if (Element.getStyle(element, 'position') == 'absolute') { + parent = Element.getOffsetParent(element); + delta = Element.viewportOffset(parent); + } + + if (parent == document.body) { + delta[0] -= document.body.offsetLeft; + delta[1] -= document.body.offsetTop; + } + + if (options.setLeft) element.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; + if (options.setTop) element.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; + if (options.setWidth) element.style.width = source.offsetWidth + 'px'; + if (options.setHeight) element.style.height = source.offsetHeight + 'px'; + return element; + } +}; + +Object.extend(Element.Methods, { + getElementsBySelector: Element.Methods.select, + + childElements: Element.Methods.immediateDescendants +}); + +Element._attributeTranslations = { + write: { + names: { + className: 'class', + htmlFor: 'for' + }, + values: { } + } +}; + +if (Prototype.Browser.Opera) { + Element.Methods.getStyle = Element.Methods.getStyle.wrap( + function(proceed, element, style) { + switch (style) { + case 'left': case 'top': case 'right': case 'bottom': + if (proceed(element, 'position') === 'static') return null; + case 'height': case 'width': + if (!Element.visible(element)) return null; + + var dim = parseInt(proceed(element, style), 10); + + if (dim !== element['offset' + style.capitalize()]) + return dim + 'px'; + + var properties; + if (style === 'height') { + properties = ['border-top-width', 'padding-top', + 'padding-bottom', 'border-bottom-width']; + } + else { + properties = ['border-left-width', 'padding-left', + 'padding-right', 'border-right-width']; + } + return properties.inject(dim, function(memo, property) { + var val = proceed(element, property); + return val === null ? memo : memo - parseInt(val, 10); + }) + 'px'; + default: return proceed(element, style); + } + } + ); + + Element.Methods.readAttribute = Element.Methods.readAttribute.wrap( + function(proceed, element, attribute) { + if (attribute === 'title') return element.title; + return proceed(element, attribute); + } + ); +} + +else if (Prototype.Browser.IE) { + Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap( + function(proceed, element) { + element = $(element); + try { element.offsetParent } + catch(e) { return $(document.body) } + var position = element.getStyle('position'); + if (position !== 'static') return proceed(element); + element.setStyle({ position: 'relative' }); + var value = proceed(element); + element.setStyle({ position: position }); + return value; + } + ); + + $w('positionedOffset viewportOffset').each(function(method) { + Element.Methods[method] = Element.Methods[method].wrap( + function(proceed, element) { + element = $(element); + try { element.offsetParent } + catch(e) { return Element._returnOffset(0,0) } + var position = element.getStyle('position'); + if (position !== 'static') return proceed(element); + var offsetParent = element.getOffsetParent(); + if (offsetParent && offsetParent.getStyle('position') === 'fixed') + offsetParent.setStyle({ zoom: 1 }); + element.setStyle({ position: 'relative' }); + var value = proceed(element); + element.setStyle({ position: position }); + return value; + } + ); + }); + + Element.Methods.cumulativeOffset = Element.Methods.cumulativeOffset.wrap( + function(proceed, element) { + try { element.offsetParent } + catch(e) { return Element._returnOffset(0,0) } + return proceed(element); + } + ); + + Element.Methods.getStyle = function(element, style) { + element = $(element); + style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize(); + var value = element.style[style]; + if (!value && element.currentStyle) value = element.currentStyle[style]; + + if (style == 'opacity') { + if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/)) + if (value[1]) return parseFloat(value[1]) / 100; + return 1.0; + } + + if (value == 'auto') { + if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none')) + return element['offset' + style.capitalize()] + 'px'; + return null; + } + return value; + }; + + Element.Methods.setOpacity = function(element, value) { + function stripAlpha(filter){ + return filter.replace(/alpha\([^\)]*\)/gi,''); + } + element = $(element); + var currentStyle = element.currentStyle; + if ((currentStyle && !currentStyle.hasLayout) || + (!currentStyle && element.style.zoom == 'normal')) + element.style.zoom = 1; + + var filter = element.getStyle('filter'), style = element.style; + if (value == 1 || value === '') { + (filter = stripAlpha(filter)) ? + style.filter = filter : style.removeAttribute('filter'); + return element; + } else if (value < 0.00001) value = 0; + style.filter = stripAlpha(filter) + + 'alpha(opacity=' + (value * 100) + ')'; + return element; + }; + + Element._attributeTranslations = (function(){ + + var classProp = 'className'; + var forProp = 'for'; + + var el = document.createElement('div'); + + el.setAttribute(classProp, 'x'); + + if (el.className !== 'x') { + el.setAttribute('class', 'x'); + if (el.className === 'x') { + classProp = 'class'; + } + } + el = null; + + el = document.createElement('label'); + el.setAttribute(forProp, 'x'); + if (el.htmlFor !== 'x') { + el.setAttribute('htmlFor', 'x'); + if (el.htmlFor === 'x') { + forProp = 'htmlFor'; + } + } + el = null; + + return { + read: { + names: { + 'class': classProp, + 'className': classProp, + 'for': forProp, + 'htmlFor': forProp + }, + values: { + _getAttr: function(element, attribute) { + return element.getAttribute(attribute); + }, + _getAttr2: function(element, attribute) { + return element.getAttribute(attribute, 2); + }, + _getAttrNode: function(element, attribute) { + var node = element.getAttributeNode(attribute); + return node ? node.value : ""; + }, + _getEv: (function(){ + + var el = document.createElement('div'); + el.onclick = Prototype.emptyFunction; + var value = el.getAttribute('onclick'); + var f; + + if (String(value).indexOf('{') > -1) { + f = function(element, attribute) { + attribute = element.getAttribute(attribute); + if (!attribute) return null; + attribute = attribute.toString(); + attribute = attribute.split('{')[1]; + attribute = attribute.split('}')[0]; + return attribute.strip(); + }; + } + else if (value === '') { + f = function(element, attribute) { + attribute = element.getAttribute(attribute); + if (!attribute) return null; + return attribute.strip(); + }; + } + el = null; + return f; + })(), + _flag: function(element, attribute) { + return $(element).hasAttribute(attribute) ? attribute : null; + }, + style: function(element) { + return element.style.cssText.toLowerCase(); + }, + title: function(element) { + return element.title; + } + } + } + } + })(); + + Element._attributeTranslations.write = { + names: Object.extend({ + cellpadding: 'cellPadding', + cellspacing: 'cellSpacing' + }, Element._attributeTranslations.read.names), + values: { + checked: function(element, value) { + element.checked = !!value; + }, + + style: function(element, value) { + element.style.cssText = value ? value : ''; + } + } + }; + + Element._attributeTranslations.has = {}; + + $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' + + 'encType maxLength readOnly longDesc frameBorder').each(function(attr) { + Element._attributeTranslations.write.names[attr.toLowerCase()] = attr; + Element._attributeTranslations.has[attr.toLowerCase()] = attr; + }); + + (function(v) { + Object.extend(v, { + href: v._getAttr2, + src: v._getAttr2, + type: v._getAttr, + action: v._getAttrNode, + disabled: v._flag, + checked: v._flag, + readonly: v._flag, + multiple: v._flag, + onload: v._getEv, + onunload: v._getEv, + onclick: v._getEv, + ondblclick: v._getEv, + onmousedown: v._getEv, + onmouseup: v._getEv, + onmouseover: v._getEv, + onmousemove: v._getEv, + onmouseout: v._getEv, + onfocus: v._getEv, + onblur: v._getEv, + onkeypress: v._getEv, + onkeydown: v._getEv, + onkeyup: v._getEv, + onsubmit: v._getEv, + onreset: v._getEv, + onselect: v._getEv, + onchange: v._getEv + }); + })(Element._attributeTranslations.read.values); + + if (Prototype.BrowserFeatures.ElementExtensions) { + (function() { + function _descendants(element) { + var nodes = element.getElementsByTagName('*'), results = []; + for (var i = 0, node; node = nodes[i]; i++) + if (node.tagName !== "!") // Filter out comment nodes. + results.push(node); + return results; + } + + Element.Methods.down = function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return element.firstDescendant(); + return Object.isNumber(expression) ? _descendants(element)[expression] : + Element.select(element, expression)[index || 0]; + } + })(); + } + +} + +else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) { + Element.Methods.setOpacity = function(element, value) { + element = $(element); + element.style.opacity = (value == 1) ? 0.999999 : + (value === '') ? '' : (value < 0.00001) ? 0 : value; + return element; + }; +} + +else if (Prototype.Browser.WebKit) { + Element.Methods.setOpacity = function(element, value) { + element = $(element); + element.style.opacity = (value == 1 || value === '') ? '' : + (value < 0.00001) ? 0 : value; + + if (value == 1) + if(element.tagName.toUpperCase() == 'IMG' && element.width) { + element.width++; element.width--; + } else try { + var n = document.createTextNode(' '); + element.appendChild(n); + element.removeChild(n); + } catch (e) { } + + return element; + }; + + Element.Methods.cumulativeOffset = function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + if (element.offsetParent == document.body) + if (Element.getStyle(element, 'position') == 'absolute') break; + + element = element.offsetParent; + } while (element); + + return Element._returnOffset(valueL, valueT); + }; +} + +if ('outerHTML' in document.documentElement) { + Element.Methods.replace = function(element, content) { + element = $(element); + + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) { + element.parentNode.replaceChild(content, element); + return element; + } + + content = Object.toHTML(content); + var parent = element.parentNode, tagName = parent.tagName.toUpperCase(); + + if (Element._insertionTranslations.tags[tagName]) { + var nextSibling = element.next(); + var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); + parent.removeChild(element); + if (nextSibling) + fragments.each(function(node) { parent.insertBefore(node, nextSibling) }); + else + fragments.each(function(node) { parent.appendChild(node) }); + } + else element.outerHTML = content.stripScripts(); + + content.evalScripts.bind(content).defer(); + return element; + }; +} + +Element._returnOffset = function(l, t) { + var result = [l, t]; + result.left = l; + result.top = t; + return result; +}; + +Element._getContentFromAnonymousElement = function(tagName, html) { + var div = new Element('div'), t = Element._insertionTranslations.tags[tagName]; + if (t) { + div.innerHTML = t[0] + html + t[1]; + t[2].times(function() { div = div.firstChild }); + } else div.innerHTML = html; + return $A(div.childNodes); +}; + +Element._insertionTranslations = { + before: function(element, node) { + element.parentNode.insertBefore(node, element); + }, + top: function(element, node) { + element.insertBefore(node, element.firstChild); + }, + bottom: function(element, node) { + element.appendChild(node); + }, + after: function(element, node) { + element.parentNode.insertBefore(node, element.nextSibling); + }, + tags: { + TABLE: ['', '
        ', 1], + TBODY: ['', '
        ', 2], + TR: ['', '
        ', 3], + TD: ['
        ', '
        ', 4], + SELECT: ['', 1] + } +}; + +(function() { + var tags = Element._insertionTranslations.tags; + Object.extend(tags, { + THEAD: tags.TBODY, + TFOOT: tags.TBODY, + TH: tags.TD + }); +})(); + +Element.Methods.Simulated = { + hasAttribute: function(element, attribute) { + attribute = Element._attributeTranslations.has[attribute] || attribute; + var node = $(element).getAttributeNode(attribute); + return !!(node && node.specified); + } +}; + +Element.Methods.ByTag = { }; + +Object.extend(Element, Element.Methods); + +(function(div) { + + if (!Prototype.BrowserFeatures.ElementExtensions && div['__proto__']) { + window.HTMLElement = { }; + window.HTMLElement.prototype = div['__proto__']; + Prototype.BrowserFeatures.ElementExtensions = true; + } + + div = null; + +})(document.createElement('div')) + +Element.extend = (function() { + + function checkDeficiency(tagName) { + if (typeof window.Element != 'undefined') { + var proto = window.Element.prototype; + if (proto) { + var id = '_' + (Math.random()+'').slice(2); + var el = document.createElement(tagName); + proto[id] = 'x'; + var isBuggy = (el[id] !== 'x'); + delete proto[id]; + el = null; + return isBuggy; + } + } + return false; + } + + function extendElementWith(element, methods) { + for (var property in methods) { + var value = methods[property]; + if (Object.isFunction(value) && !(property in element)) + element[property] = value.methodize(); + } + } + + var HTMLOBJECTELEMENT_PROTOTYPE_BUGGY = checkDeficiency('object'); + + if (Prototype.BrowserFeatures.SpecificElementExtensions) { + if (HTMLOBJECTELEMENT_PROTOTYPE_BUGGY) { + return function(element) { + if (element && typeof element._extendedByPrototype == 'undefined') { + var t = element.tagName; + if (t && (/^(?:object|applet|embed)$/i.test(t))) { + extendElementWith(element, Element.Methods); + extendElementWith(element, Element.Methods.Simulated); + extendElementWith(element, Element.Methods.ByTag[t.toUpperCase()]); + } + } + return element; + } + } + return Prototype.K; + } + + var Methods = { }, ByTag = Element.Methods.ByTag; + + var extend = Object.extend(function(element) { + if (!element || typeof element._extendedByPrototype != 'undefined' || + element.nodeType != 1 || element == window) return element; + + var methods = Object.clone(Methods), + tagName = element.tagName.toUpperCase(); + + if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]); + + extendElementWith(element, methods); + + element._extendedByPrototype = Prototype.emptyFunction; + return element; + + }, { + refresh: function() { + if (!Prototype.BrowserFeatures.ElementExtensions) { + Object.extend(Methods, Element.Methods); + Object.extend(Methods, Element.Methods.Simulated); + } + } + }); + + extend.refresh(); + return extend; +})(); + +Element.hasAttribute = function(element, attribute) { + if (element.hasAttribute) return element.hasAttribute(attribute); + return Element.Methods.Simulated.hasAttribute(element, attribute); +}; + +Element.addMethods = function(methods) { + var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag; + + if (!methods) { + Object.extend(Form, Form.Methods); + Object.extend(Form.Element, Form.Element.Methods); + Object.extend(Element.Methods.ByTag, { + "FORM": Object.clone(Form.Methods), + "INPUT": Object.clone(Form.Element.Methods), + "SELECT": Object.clone(Form.Element.Methods), + "TEXTAREA": Object.clone(Form.Element.Methods) + }); + } + + if (arguments.length == 2) { + var tagName = methods; + methods = arguments[1]; + } + + if (!tagName) Object.extend(Element.Methods, methods || { }); + else { + if (Object.isArray(tagName)) tagName.each(extend); + else extend(tagName); + } + + function extend(tagName) { + tagName = tagName.toUpperCase(); + if (!Element.Methods.ByTag[tagName]) + Element.Methods.ByTag[tagName] = { }; + Object.extend(Element.Methods.ByTag[tagName], methods); + } + + function copy(methods, destination, onlyIfAbsent) { + onlyIfAbsent = onlyIfAbsent || false; + for (var property in methods) { + var value = methods[property]; + if (!Object.isFunction(value)) continue; + if (!onlyIfAbsent || !(property in destination)) + destination[property] = value.methodize(); + } + } + + function findDOMClass(tagName) { + var klass; + var trans = { + "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph", + "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList", + "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading", + "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote", + "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION": + "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD": + "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR": + "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET": + "FrameSet", "IFRAME": "IFrame" + }; + if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element'; + if (window[klass]) return window[klass]; + klass = 'HTML' + tagName + 'Element'; + if (window[klass]) return window[klass]; + klass = 'HTML' + tagName.capitalize() + 'Element'; + if (window[klass]) return window[klass]; + + var element = document.createElement(tagName); + var proto = element['__proto__'] || element.constructor.prototype; + element = null; + return proto; + } + + var elementPrototype = window.HTMLElement ? HTMLElement.prototype : + Element.prototype; + + if (F.ElementExtensions) { + copy(Element.Methods, elementPrototype); + copy(Element.Methods.Simulated, elementPrototype, true); + } + + if (F.SpecificElementExtensions) { + for (var tag in Element.Methods.ByTag) { + var klass = findDOMClass(tag); + if (Object.isUndefined(klass)) continue; + copy(T[tag], klass.prototype); + } + } + + Object.extend(Element, Element.Methods); + delete Element.ByTag; + + if (Element.extend.refresh) Element.extend.refresh(); + Element.cache = { }; +}; + + +document.viewport = { + + getDimensions: function() { + return { width: this.getWidth(), height: this.getHeight() }; + }, + + getScrollOffsets: function() { + return Element._returnOffset( + window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft, + window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop); + } +}; + +(function(viewport) { + var B = Prototype.Browser, doc = document, element, property = {}; + + function getRootElement() { + if (B.WebKit && !doc.evaluate) + return document; + + if (B.Opera && window.parseFloat(window.opera.version()) < 9.5) + return document.body; + + return document.documentElement; + } + + function define(D) { + if (!element) element = getRootElement(); + + property[D] = 'client' + D; + + viewport['get' + D] = function() { return element[property[D]] }; + return viewport['get' + D](); + } + + viewport.getWidth = define.curry('Width'); + + viewport.getHeight = define.curry('Height'); +})(document.viewport); + + +Element.Storage = { + UID: 1 +}; + +Element.addMethods({ + getStorage: function(element) { + if (!(element = $(element))) return; + + var uid; + if (element === window) { + uid = 0; + } else { + if (typeof element._prototypeUID === "undefined") + element._prototypeUID = [Element.Storage.UID++]; + uid = element._prototypeUID[0]; + } + + if (!Element.Storage[uid]) + Element.Storage[uid] = $H(); + + return Element.Storage[uid]; + }, + + store: function(element, key, value) { + if (!(element = $(element))) return; + + if (arguments.length === 2) { + Element.getStorage(element).update(key); + } else { + Element.getStorage(element).set(key, value); + } + + return element; + }, + + retrieve: function(element, key, defaultValue) { + if (!(element = $(element))) return; + var hash = Element.getStorage(element), value = hash.get(key); + + if (Object.isUndefined(value)) { + hash.set(key, defaultValue); + value = defaultValue; + } + + return value; + }, + + clone: function(element, deep) { + if (!(element = $(element))) return; + var clone = element.cloneNode(deep); + clone._prototypeUID = void 0; + if (deep) { + var descendants = Element.select(clone, '*'), + i = descendants.length; + while (i--) { + descendants[i]._prototypeUID = void 0; + } + } + return Element.extend(clone); + } +}); +/* Portions of the Selector class are derived from Jack Slocum's DomQuery, + * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style + * license. Please see http://www.yui-ext.com/ for more information. */ + +var Selector = Class.create({ + initialize: function(expression) { + this.expression = expression.strip(); + + if (this.shouldUseSelectorsAPI()) { + this.mode = 'selectorsAPI'; + } else if (this.shouldUseXPath()) { + this.mode = 'xpath'; + this.compileXPathMatcher(); + } else { + this.mode = "normal"; + this.compileMatcher(); + } + + }, + + shouldUseXPath: (function() { + + var IS_DESCENDANT_SELECTOR_BUGGY = (function(){ + var isBuggy = false; + if (document.evaluate && window.XPathResult) { + var el = document.createElement('div'); + el.innerHTML = '
        '; + + var xpath = ".//*[local-name()='ul' or local-name()='UL']" + + "//*[local-name()='li' or local-name()='LI']"; + + var result = document.evaluate(xpath, el, null, + XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); + + isBuggy = (result.snapshotLength !== 2); + el = null; + } + return isBuggy; + })(); + + return function() { + if (!Prototype.BrowserFeatures.XPath) return false; + + var e = this.expression; + + if (Prototype.Browser.WebKit && + (e.include("-of-type") || e.include(":empty"))) + return false; + + if ((/(\[[\w-]*?:|:checked)/).test(e)) + return false; + + if (IS_DESCENDANT_SELECTOR_BUGGY) return false; + + return true; + } + + })(), + + shouldUseSelectorsAPI: function() { + if (!Prototype.BrowserFeatures.SelectorsAPI) return false; + + if (Selector.CASE_INSENSITIVE_CLASS_NAMES) return false; + + if (!Selector._div) Selector._div = new Element('div'); + + try { + Selector._div.querySelector(this.expression); + } catch(e) { + return false; + } + + return true; + }, + + compileMatcher: function() { + var e = this.expression, ps = Selector.patterns, h = Selector.handlers, + c = Selector.criteria, le, p, m, len = ps.length, name; + + if (Selector._cache[e]) { + this.matcher = Selector._cache[e]; + return; + } + + this.matcher = ["this.matcher = function(root) {", + "var r = root, h = Selector.handlers, c = false, n;"]; + + while (e && le != e && (/\S/).test(e)) { + le = e; + for (var i = 0; i"; + } +}); + +if (Prototype.BrowserFeatures.SelectorsAPI && + document.compatMode === 'BackCompat') { + Selector.CASE_INSENSITIVE_CLASS_NAMES = (function(){ + var div = document.createElement('div'), + span = document.createElement('span'); + + div.id = "prototype_test_id"; + span.className = 'Test'; + div.appendChild(span); + var isIgnored = (div.querySelector('#prototype_test_id .test') !== null); + div = span = null; + return isIgnored; + })(); +} + +Object.extend(Selector, { + _cache: { }, + + xpath: { + descendant: "//*", + child: "/*", + adjacent: "/following-sibling::*[1]", + laterSibling: '/following-sibling::*', + tagName: function(m) { + if (m[1] == '*') return ''; + return "[local-name()='" + m[1].toLowerCase() + + "' or local-name()='" + m[1].toUpperCase() + "']"; + }, + className: "[contains(concat(' ', @class, ' '), ' #{1} ')]", + id: "[@id='#{1}']", + attrPresence: function(m) { + m[1] = m[1].toLowerCase(); + return new Template("[@#{1}]").evaluate(m); + }, + attr: function(m) { + m[1] = m[1].toLowerCase(); + m[3] = m[5] || m[6]; + return new Template(Selector.xpath.operators[m[2]]).evaluate(m); + }, + pseudo: function(m) { + var h = Selector.xpath.pseudos[m[1]]; + if (!h) return ''; + if (Object.isFunction(h)) return h(m); + return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m); + }, + operators: { + '=': "[@#{1}='#{3}']", + '!=': "[@#{1}!='#{3}']", + '^=': "[starts-with(@#{1}, '#{3}')]", + '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']", + '*=': "[contains(@#{1}, '#{3}')]", + '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]", + '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]" + }, + pseudos: { + 'first-child': '[not(preceding-sibling::*)]', + 'last-child': '[not(following-sibling::*)]', + 'only-child': '[not(preceding-sibling::* or following-sibling::*)]', + 'empty': "[count(*) = 0 and (count(text()) = 0)]", + 'checked': "[@checked]", + 'disabled': "[(@disabled) and (@type!='hidden')]", + 'enabled': "[not(@disabled) and (@type!='hidden')]", + 'not': function(m) { + var e = m[6], p = Selector.patterns, + x = Selector.xpath, le, v, len = p.length, name; + + var exclusion = []; + while (e && le != e && (/\S/).test(e)) { + le = e; + for (var i = 0; i= 0)]"; + return new Template(predicate).evaluate({ + fragment: fragment, a: a, b: b }); + } + } + } + }, + + criteria: { + tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;', + className: 'n = h.className(n, r, "#{1}", c); c = false;', + id: 'n = h.id(n, r, "#{1}", c); c = false;', + attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;', + attr: function(m) { + m[3] = (m[5] || m[6]); + return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m); + }, + pseudo: function(m) { + if (m[6]) m[6] = m[6].replace(/"/g, '\\"'); + return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m); + }, + descendant: 'c = "descendant";', + child: 'c = "child";', + adjacent: 'c = "adjacent";', + laterSibling: 'c = "laterSibling";' + }, + + patterns: [ + { name: 'laterSibling', re: /^\s*~\s*/ }, + { name: 'child', re: /^\s*>\s*/ }, + { name: 'adjacent', re: /^\s*\+\s*/ }, + { name: 'descendant', re: /^\s/ }, + + { name: 'tagName', re: /^\s*(\*|[\w\-]+)(\b|$)?/ }, + { name: 'id', re: /^#([\w\-\*]+)(\b|$)/ }, + { name: 'className', re: /^\.([\w\-\*]+)(\b|$)/ }, + { name: 'pseudo', re: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/ }, + { name: 'attrPresence', re: /^\[((?:[\w-]+:)?[\w-]+)\]/ }, + { name: 'attr', re: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/ } + ], + + assertions: { + tagName: function(element, matches) { + return matches[1].toUpperCase() == element.tagName.toUpperCase(); + }, + + className: function(element, matches) { + return Element.hasClassName(element, matches[1]); + }, + + id: function(element, matches) { + return element.id === matches[1]; + }, + + attrPresence: function(element, matches) { + return Element.hasAttribute(element, matches[1]); + }, + + attr: function(element, matches) { + var nodeValue = Element.readAttribute(element, matches[1]); + return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]); + } + }, + + handlers: { + concat: function(a, b) { + for (var i = 0, node; node = b[i]; i++) + a.push(node); + return a; + }, + + mark: function(nodes) { + var _true = Prototype.emptyFunction; + for (var i = 0, node; node = nodes[i]; i++) + node._countedByPrototype = _true; + return nodes; + }, + + unmark: (function(){ + + var PROPERTIES_ATTRIBUTES_MAP = (function(){ + var el = document.createElement('div'), + isBuggy = false, + propName = '_countedByPrototype', + value = 'x' + el[propName] = value; + isBuggy = (el.getAttribute(propName) === value); + el = null; + return isBuggy; + })(); + + return PROPERTIES_ATTRIBUTES_MAP ? + function(nodes) { + for (var i = 0, node; node = nodes[i]; i++) + node.removeAttribute('_countedByPrototype'); + return nodes; + } : + function(nodes) { + for (var i = 0, node; node = nodes[i]; i++) + node._countedByPrototype = void 0; + return nodes; + } + })(), + + index: function(parentNode, reverse, ofType) { + parentNode._countedByPrototype = Prototype.emptyFunction; + if (reverse) { + for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) { + var node = nodes[i]; + if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++; + } + } else { + for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++) + if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++; + } + }, + + unique: function(nodes) { + if (nodes.length == 0) return nodes; + var results = [], n; + for (var i = 0, l = nodes.length; i < l; i++) + if (typeof (n = nodes[i])._countedByPrototype == 'undefined') { + n._countedByPrototype = Prototype.emptyFunction; + results.push(Element.extend(n)); + } + return Selector.handlers.unmark(results); + }, + + descendant: function(nodes) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) + h.concat(results, node.getElementsByTagName('*')); + return results; + }, + + child: function(nodes) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) { + for (var j = 0, child; child = node.childNodes[j]; j++) + if (child.nodeType == 1 && child.tagName != '!') results.push(child); + } + return results; + }, + + adjacent: function(nodes) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + var next = this.nextElementSibling(node); + if (next) results.push(next); + } + return results; + }, + + laterSibling: function(nodes) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) + h.concat(results, Element.nextSiblings(node)); + return results; + }, + + nextElementSibling: function(node) { + while (node = node.nextSibling) + if (node.nodeType == 1) return node; + return null; + }, + + previousElementSibling: function(node) { + while (node = node.previousSibling) + if (node.nodeType == 1) return node; + return null; + }, + + tagName: function(nodes, root, tagName, combinator) { + var uTagName = tagName.toUpperCase(); + var results = [], h = Selector.handlers; + if (nodes) { + if (combinator) { + if (combinator == "descendant") { + for (var i = 0, node; node = nodes[i]; i++) + h.concat(results, node.getElementsByTagName(tagName)); + return results; + } else nodes = this[combinator](nodes); + if (tagName == "*") return nodes; + } + for (var i = 0, node; node = nodes[i]; i++) + if (node.tagName.toUpperCase() === uTagName) results.push(node); + return results; + } else return root.getElementsByTagName(tagName); + }, + + id: function(nodes, root, id, combinator) { + var targetNode = $(id), h = Selector.handlers; + + if (root == document) { + if (!targetNode) return []; + if (!nodes) return [targetNode]; + } else { + if (!root.sourceIndex || root.sourceIndex < 1) { + var nodes = root.getElementsByTagName('*'); + for (var j = 0, node; node = nodes[j]; j++) { + if (node.id === id) return [node]; + } + } + } + + if (nodes) { + if (combinator) { + if (combinator == 'child') { + for (var i = 0, node; node = nodes[i]; i++) + if (targetNode.parentNode == node) return [targetNode]; + } else if (combinator == 'descendant') { + for (var i = 0, node; node = nodes[i]; i++) + if (Element.descendantOf(targetNode, node)) return [targetNode]; + } else if (combinator == 'adjacent') { + for (var i = 0, node; node = nodes[i]; i++) + if (Selector.handlers.previousElementSibling(targetNode) == node) + return [targetNode]; + } else nodes = h[combinator](nodes); + } + for (var i = 0, node; node = nodes[i]; i++) + if (node == targetNode) return [targetNode]; + return []; + } + return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : []; + }, + + className: function(nodes, root, className, combinator) { + if (nodes && combinator) nodes = this[combinator](nodes); + return Selector.handlers.byClassName(nodes, root, className); + }, + + byClassName: function(nodes, root, className) { + if (!nodes) nodes = Selector.handlers.descendant([root]); + var needle = ' ' + className + ' '; + for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) { + nodeClassName = node.className; + if (nodeClassName.length == 0) continue; + if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle)) + results.push(node); + } + return results; + }, + + attrPresence: function(nodes, root, attr, combinator) { + if (!nodes) nodes = root.getElementsByTagName("*"); + if (nodes && combinator) nodes = this[combinator](nodes); + var results = []; + for (var i = 0, node; node = nodes[i]; i++) + if (Element.hasAttribute(node, attr)) results.push(node); + return results; + }, + + attr: function(nodes, root, attr, value, operator, combinator) { + if (!nodes) nodes = root.getElementsByTagName("*"); + if (nodes && combinator) nodes = this[combinator](nodes); + var handler = Selector.operators[operator], results = []; + for (var i = 0, node; node = nodes[i]; i++) { + var nodeValue = Element.readAttribute(node, attr); + if (nodeValue === null) continue; + if (handler(nodeValue, value)) results.push(node); + } + return results; + }, + + pseudo: function(nodes, name, value, root, combinator) { + if (nodes && combinator) nodes = this[combinator](nodes); + if (!nodes) nodes = root.getElementsByTagName("*"); + return Selector.pseudos[name](nodes, value, root); + } + }, + + pseudos: { + 'first-child': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + if (Selector.handlers.previousElementSibling(node)) continue; + results.push(node); + } + return results; + }, + 'last-child': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + if (Selector.handlers.nextElementSibling(node)) continue; + results.push(node); + } + return results; + }, + 'only-child': function(nodes, value, root) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (!h.previousElementSibling(node) && !h.nextElementSibling(node)) + results.push(node); + return results; + }, + 'nth-child': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root); + }, + 'nth-last-child': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root, true); + }, + 'nth-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root, false, true); + }, + 'nth-last-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root, true, true); + }, + 'first-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, "1", root, false, true); + }, + 'last-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, "1", root, true, true); + }, + 'only-of-type': function(nodes, formula, root) { + var p = Selector.pseudos; + return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root); + }, + + getIndices: function(a, b, total) { + if (a == 0) return b > 0 ? [b] : []; + return $R(1, total).inject([], function(memo, i) { + if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i); + return memo; + }); + }, + + nth: function(nodes, formula, root, reverse, ofType) { + if (nodes.length == 0) return []; + if (formula == 'even') formula = '2n+0'; + if (formula == 'odd') formula = '2n+1'; + var h = Selector.handlers, results = [], indexed = [], m; + h.mark(nodes); + for (var i = 0, node; node = nodes[i]; i++) { + if (!node.parentNode._countedByPrototype) { + h.index(node.parentNode, reverse, ofType); + indexed.push(node.parentNode); + } + } + if (formula.match(/^\d+$/)) { // just a number + formula = Number(formula); + for (var i = 0, node; node = nodes[i]; i++) + if (node.nodeIndex == formula) results.push(node); + } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b + if (m[1] == "-") m[1] = -1; + var a = m[1] ? Number(m[1]) : 1; + var b = m[2] ? Number(m[2]) : 0; + var indices = Selector.pseudos.getIndices(a, b, nodes.length); + for (var i = 0, node, l = indices.length; node = nodes[i]; i++) { + for (var j = 0; j < l; j++) + if (node.nodeIndex == indices[j]) results.push(node); + } + } + h.unmark(nodes); + h.unmark(indexed); + return results; + }, + + 'empty': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + if (node.tagName == '!' || node.firstChild) continue; + results.push(node); + } + return results; + }, + + 'not': function(nodes, selector, root) { + var h = Selector.handlers, selectorType, m; + var exclusions = new Selector(selector).findElements(root); + h.mark(exclusions); + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (!node._countedByPrototype) results.push(node); + h.unmark(exclusions); + return results; + }, + + 'enabled': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (!node.disabled && (!node.type || node.type !== 'hidden')) + results.push(node); + return results; + }, + + 'disabled': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (node.disabled) results.push(node); + return results; + }, + + 'checked': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (node.checked) results.push(node); + return results; + } + }, + + operators: { + '=': function(nv, v) { return nv == v; }, + '!=': function(nv, v) { return nv != v; }, + '^=': function(nv, v) { return nv == v || nv && nv.startsWith(v); }, + '$=': function(nv, v) { return nv == v || nv && nv.endsWith(v); }, + '*=': function(nv, v) { return nv == v || nv && nv.include(v); }, + '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); }, + '|=': function(nv, v) { return ('-' + (nv || "").toUpperCase() + + '-').include('-' + (v || "").toUpperCase() + '-'); } + }, + + split: function(expression) { + var expressions = []; + expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) { + expressions.push(m[1].strip()); + }); + return expressions; + }, + + matchElements: function(elements, expression) { + var matches = $$(expression), h = Selector.handlers; + h.mark(matches); + for (var i = 0, results = [], element; element = elements[i]; i++) + if (element._countedByPrototype) results.push(element); + h.unmark(matches); + return results; + }, + + findElement: function(elements, expression, index) { + if (Object.isNumber(expression)) { + index = expression; expression = false; + } + return Selector.matchElements(elements, expression || '*')[index || 0]; + }, + + findChildElements: function(element, expressions) { + expressions = Selector.split(expressions.join(',')); + var results = [], h = Selector.handlers; + for (var i = 0, l = expressions.length, selector; i < l; i++) { + selector = new Selector(expressions[i].strip()); + h.concat(results, selector.findElements(element)); + } + return (l > 1) ? h.unique(results) : results; + } +}); + +if (Prototype.Browser.IE) { + Object.extend(Selector.handlers, { + concat: function(a, b) { + for (var i = 0, node; node = b[i]; i++) + if (node.tagName !== "!") a.push(node); + return a; + } + }); +} + +function $$() { + return Selector.findChildElements(document, $A(arguments)); +} + +var Form = { + reset: function(form) { + form = $(form); + form.reset(); + return form; + }, + + serializeElements: function(elements, options) { + if (typeof options != 'object') options = { hash: !!options }; + else if (Object.isUndefined(options.hash)) options.hash = true; + var key, value, submitted = false, submit = options.submit; + + var data = elements.inject({ }, function(result, element) { + if (!element.disabled && element.name) { + key = element.name; value = $(element).getValue(); + if (value != null && element.type != 'file' && (element.type != 'submit' || (!submitted && + submit !== false && (!submit || key == submit) && (submitted = true)))) { + if (key in result) { + if (!Object.isArray(result[key])) result[key] = [result[key]]; + result[key].push(value); + } + else result[key] = value; + } + } + return result; + }); + + return options.hash ? data : Object.toQueryString(data); + } +}; + +Form.Methods = { + serialize: function(form, options) { + return Form.serializeElements(Form.getElements(form), options); + }, + + getElements: function(form) { + var elements = $(form).getElementsByTagName('*'), + element, + arr = [ ], + serializers = Form.Element.Serializers; + for (var i = 0; element = elements[i]; i++) { + arr.push(element); + } + return arr.inject([], function(elements, child) { + if (serializers[child.tagName.toLowerCase()]) + elements.push(Element.extend(child)); + return elements; + }) + }, + + getInputs: function(form, typeName, name) { + form = $(form); + var inputs = form.getElementsByTagName('input'); + + if (!typeName && !name) return $A(inputs).map(Element.extend); + + for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) { + var input = inputs[i]; + if ((typeName && input.type != typeName) || (name && input.name != name)) + continue; + matchingInputs.push(Element.extend(input)); + } + + return matchingInputs; + }, + + disable: function(form) { + form = $(form); + Form.getElements(form).invoke('disable'); + return form; + }, + + enable: function(form) { + form = $(form); + Form.getElements(form).invoke('enable'); + return form; + }, + + findFirstElement: function(form) { + var elements = $(form).getElements().findAll(function(element) { + return 'hidden' != element.type && !element.disabled; + }); + var firstByIndex = elements.findAll(function(element) { + return element.hasAttribute('tabIndex') && element.tabIndex >= 0; + }).sortBy(function(element) { return element.tabIndex }).first(); + + return firstByIndex ? firstByIndex : elements.find(function(element) { + return /^(?:input|select|textarea)$/i.test(element.tagName); + }); + }, + + focusFirstElement: function(form) { + form = $(form); + form.findFirstElement().activate(); + return form; + }, + + request: function(form, options) { + form = $(form), options = Object.clone(options || { }); + + var params = options.parameters, action = form.readAttribute('action') || ''; + if (action.blank()) action = window.location.href; + options.parameters = form.serialize(true); + + if (params) { + if (Object.isString(params)) params = params.toQueryParams(); + Object.extend(options.parameters, params); + } + + if (form.hasAttribute('method') && !options.method) + options.method = form.method; + + return new Ajax.Request(action, options); + } +}; + +/*--------------------------------------------------------------------------*/ + + +Form.Element = { + focus: function(element) { + $(element).focus(); + return element; + }, + + select: function(element) { + $(element).select(); + return element; + } +}; + +Form.Element.Methods = { + + serialize: function(element) { + element = $(element); + if (!element.disabled && element.name) { + var value = element.getValue(); + if (value != undefined) { + var pair = { }; + pair[element.name] = value; + return Object.toQueryString(pair); + } + } + return ''; + }, + + getValue: function(element) { + element = $(element); + var method = element.tagName.toLowerCase(); + return Form.Element.Serializers[method](element); + }, + + setValue: function(element, value) { + element = $(element); + var method = element.tagName.toLowerCase(); + Form.Element.Serializers[method](element, value); + return element; + }, + + clear: function(element) { + $(element).value = ''; + return element; + }, + + present: function(element) { + return $(element).value != ''; + }, + + activate: function(element) { + element = $(element); + try { + element.focus(); + if (element.select && (element.tagName.toLowerCase() != 'input' || + !(/^(?:button|reset|submit)$/i.test(element.type)))) + element.select(); + } catch (e) { } + return element; + }, + + disable: function(element) { + element = $(element); + element.disabled = true; + return element; + }, + + enable: function(element) { + element = $(element); + element.disabled = false; + return element; + } +}; + +/*--------------------------------------------------------------------------*/ + +var Field = Form.Element; + +var $F = Form.Element.Methods.getValue; + +/*--------------------------------------------------------------------------*/ + +Form.Element.Serializers = { + input: function(element, value) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + return Form.Element.Serializers.inputSelector(element, value); + default: + return Form.Element.Serializers.textarea(element, value); + } + }, + + inputSelector: function(element, value) { + if (Object.isUndefined(value)) return element.checked ? element.value : null; + else element.checked = !!value; + }, + + textarea: function(element, value) { + if (Object.isUndefined(value)) return element.value; + else element.value = value; + }, + + select: function(element, value) { + if (Object.isUndefined(value)) + return this[element.type == 'select-one' ? + 'selectOne' : 'selectMany'](element); + else { + var opt, currentValue, single = !Object.isArray(value); + for (var i = 0, length = element.length; i < length; i++) { + opt = element.options[i]; + currentValue = this.optionValue(opt); + if (single) { + if (currentValue == value) { + opt.selected = true; + return; + } + } + else opt.selected = value.include(currentValue); + } + } + }, + + selectOne: function(element) { + var index = element.selectedIndex; + return index >= 0 ? this.optionValue(element.options[index]) : null; + }, + + selectMany: function(element) { + var values, length = element.length; + if (!length) return null; + + for (var i = 0, values = []; i < length; i++) { + var opt = element.options[i]; + if (opt.selected) values.push(this.optionValue(opt)); + } + return values; + }, + + optionValue: function(opt) { + return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text; + } +}; + +/*--------------------------------------------------------------------------*/ + + +Abstract.TimedObserver = Class.create(PeriodicalExecuter, { + initialize: function($super, element, frequency, callback) { + $super(callback, frequency); + this.element = $(element); + this.lastValue = this.getValue(); + }, + + execute: function() { + var value = this.getValue(); + if (Object.isString(this.lastValue) && Object.isString(value) ? + this.lastValue != value : String(this.lastValue) != String(value)) { + this.callback(this.element, value); + this.lastValue = value; + } + } +}); + +Form.Element.Observer = Class.create(Abstract.TimedObserver, { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.Observer = Class.create(Abstract.TimedObserver, { + getValue: function() { + return Form.serialize(this.element); + } +}); + +/*--------------------------------------------------------------------------*/ + +Abstract.EventObserver = Class.create({ + initialize: function(element, callback) { + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + if (this.element.tagName.toLowerCase() == 'form') + this.registerFormCallbacks(); + else + this.registerCallback(this.element); + }, + + onElementEvent: function() { + var value = this.getValue(); + if (this.lastValue != value) { + this.callback(this.element, value); + this.lastValue = value; + } + }, + + registerFormCallbacks: function() { + Form.getElements(this.element).each(this.registerCallback, this); + }, + + registerCallback: function(element) { + if (element.type) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + Event.observe(element, 'click', this.onElementEvent.bind(this)); + break; + default: + Event.observe(element, 'change', this.onElementEvent.bind(this)); + break; + } + } + } +}); + +Form.Element.EventObserver = Class.create(Abstract.EventObserver, { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.EventObserver = Class.create(Abstract.EventObserver, { + getValue: function() { + return Form.serialize(this.element); + } +}); +(function() { + + var Event = { + KEY_BACKSPACE: 8, + KEY_TAB: 9, + KEY_RETURN: 13, + KEY_ESC: 27, + KEY_LEFT: 37, + KEY_UP: 38, + KEY_RIGHT: 39, + KEY_DOWN: 40, + KEY_DELETE: 46, + KEY_HOME: 36, + KEY_END: 35, + KEY_PAGEUP: 33, + KEY_PAGEDOWN: 34, + KEY_INSERT: 45, + + cache: {} + }; + + var docEl = document.documentElement; + var MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED = 'onmouseenter' in docEl + && 'onmouseleave' in docEl; + + var _isButton; + if (Prototype.Browser.IE) { + var buttonMap = { 0: 1, 1: 4, 2: 2 }; + _isButton = function(event, code) { + return event.button === buttonMap[code]; + }; + } else if (Prototype.Browser.WebKit) { + _isButton = function(event, code) { + switch (code) { + case 0: return event.which == 1 && !event.metaKey; + case 1: return event.which == 1 && event.metaKey; + default: return false; + } + }; + } else { + _isButton = function(event, code) { + return event.which ? (event.which === code + 1) : (event.button === code); + }; + } + + function isLeftClick(event) { return _isButton(event, 0) } + + function isMiddleClick(event) { return _isButton(event, 1) } + + function isRightClick(event) { return _isButton(event, 2) } + + function element(event) { + event = Event.extend(event); + + var node = event.target, type = event.type, + currentTarget = event.currentTarget; + + if (currentTarget && currentTarget.tagName) { + if (type === 'load' || type === 'error' || + (type === 'click' && currentTarget.tagName.toLowerCase() === 'input' + && currentTarget.type === 'radio')) + node = currentTarget; + } + + if (node.nodeType == Node.TEXT_NODE) + node = node.parentNode; + + return Element.extend(node); + } + + function findElement(event, expression) { + var element = Event.element(event); + if (!expression) return element; + var elements = [element].concat(element.ancestors()); + return Selector.findElement(elements, expression, 0); + } + + function pointer(event) { + return { x: pointerX(event), y: pointerY(event) }; + } + + function pointerX(event) { + var docElement = document.documentElement, + body = document.body || { scrollLeft: 0 }; + + return event.pageX || (event.clientX + + (docElement.scrollLeft || body.scrollLeft) - + (docElement.clientLeft || 0)); + } + + function pointerY(event) { + var docElement = document.documentElement, + body = document.body || { scrollTop: 0 }; + + return event.pageY || (event.clientY + + (docElement.scrollTop || body.scrollTop) - + (docElement.clientTop || 0)); + } + + + function stop(event) { + Event.extend(event); + event.preventDefault(); + event.stopPropagation(); + + event.stopped = true; + } + + Event.Methods = { + isLeftClick: isLeftClick, + isMiddleClick: isMiddleClick, + isRightClick: isRightClick, + + element: element, + findElement: findElement, + + pointer: pointer, + pointerX: pointerX, + pointerY: pointerY, + + stop: stop + }; + + + var methods = Object.keys(Event.Methods).inject({ }, function(m, name) { + m[name] = Event.Methods[name].methodize(); + return m; + }); + + if (Prototype.Browser.IE) { + function _relatedTarget(event) { + var element; + switch (event.type) { + case 'mouseover': element = event.fromElement; break; + case 'mouseout': element = event.toElement; break; + default: return null; + } + return Element.extend(element); + } + + Object.extend(methods, { + stopPropagation: function() { this.cancelBubble = true }, + preventDefault: function() { this.returnValue = false }, + inspect: function() { return '[object Event]' } + }); + + Event.extend = function(event, element) { + if (!event) return false; + if (event._extendedByPrototype) return event; + + event._extendedByPrototype = Prototype.emptyFunction; + var pointer = Event.pointer(event); + + Object.extend(event, { + target: event.srcElement || element, + relatedTarget: _relatedTarget(event), + pageX: pointer.x, + pageY: pointer.y + }); + + return Object.extend(event, methods); + }; + } else { + Event.prototype = window.Event.prototype || document.createEvent('HTMLEvents').__proto__; + Object.extend(Event.prototype, methods); + Event.extend = Prototype.K; + } + + function _createResponder(element, eventName, handler) { + var registry = Element.retrieve(element, 'prototype_event_registry'); + + if (Object.isUndefined(registry)) { + CACHE.push(element); + registry = Element.retrieve(element, 'prototype_event_registry', $H()); + } + + var respondersForEvent = registry.get(eventName); + if (Object.isUndefined(respondersForEvent)) { + respondersForEvent = []; + registry.set(eventName, respondersForEvent); + } + + if (respondersForEvent.pluck('handler').include(handler)) return false; + + var responder; + if (eventName.include(":")) { + responder = function(event) { + if (Object.isUndefined(event.eventName)) + return false; + + if (event.eventName !== eventName) + return false; + + Event.extend(event, element); + handler.call(element, event); + }; + } else { + if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED && + (eventName === "mouseenter" || eventName === "mouseleave")) { + if (eventName === "mouseenter" || eventName === "mouseleave") { + responder = function(event) { + Event.extend(event, element); + + var parent = event.relatedTarget; + while (parent && parent !== element) { + try { parent = parent.parentNode; } + catch(e) { parent = element; } + } + + if (parent === element) return; + + handler.call(element, event); + }; + } + } else { + responder = function(event) { + Event.extend(event, element); + handler.call(element, event); + }; + } + } + + responder.handler = handler; + respondersForEvent.push(responder); + return responder; + } + + function _destroyCache() { + for (var i = 0, length = CACHE.length; i < length; i++) { + Event.stopObserving(CACHE[i]); + CACHE[i] = null; + } + } + + var CACHE = []; + + if (Prototype.Browser.IE) + window.attachEvent('onunload', _destroyCache); + + if (Prototype.Browser.WebKit) + window.addEventListener('unload', Prototype.emptyFunction, false); + + + var _getDOMEventName = Prototype.K; + + if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED) { + _getDOMEventName = function(eventName) { + var translations = { mouseenter: "mouseover", mouseleave: "mouseout" }; + return eventName in translations ? translations[eventName] : eventName; + }; + } + + function observe(element, eventName, handler) { + element = $(element); + + var responder = _createResponder(element, eventName, handler); + + if (!responder) return element; + + if (eventName.include(':')) { + if (element.addEventListener) + element.addEventListener("dataavailable", responder, false); + else { + element.attachEvent("ondataavailable", responder); + element.attachEvent("onfilterchange", responder); + } + } else { + var actualEventName = _getDOMEventName(eventName); + + if (element.addEventListener) + element.addEventListener(actualEventName, responder, false); + else + element.attachEvent("on" + actualEventName, responder); + } + + return element; + } + + function stopObserving(element, eventName, handler) { + element = $(element); + + var registry = Element.retrieve(element, 'prototype_event_registry'); + + if (Object.isUndefined(registry)) return element; + + if (eventName && !handler) { + var responders = registry.get(eventName); + + if (Object.isUndefined(responders)) return element; + + responders.each( function(r) { + Element.stopObserving(element, eventName, r.handler); + }); + return element; + } else if (!eventName) { + registry.each( function(pair) { + var eventName = pair.key, responders = pair.value; + + responders.each( function(r) { + Element.stopObserving(element, eventName, r.handler); + }); + }); + return element; + } + + var responders = registry.get(eventName); + + if (!responders) return; + + var responder = responders.find( function(r) { return r.handler === handler; }); + if (!responder) return element; + + var actualEventName = _getDOMEventName(eventName); + + if (eventName.include(':')) { + if (element.removeEventListener) + element.removeEventListener("dataavailable", responder, false); + else { + element.detachEvent("ondataavailable", responder); + element.detachEvent("onfilterchange", responder); + } + } else { + if (element.removeEventListener) + element.removeEventListener(actualEventName, responder, false); + else + element.detachEvent('on' + actualEventName, responder); + } + + registry.set(eventName, responders.without(responder)); + + return element; + } + + function fire(element, eventName, memo, bubble) { + element = $(element); + + if (Object.isUndefined(bubble)) + bubble = true; + + if (element == document && document.createEvent && !element.dispatchEvent) + element = document.documentElement; + + var event; + if (document.createEvent) { + event = document.createEvent('HTMLEvents'); + event.initEvent('dataavailable', true, true); + } else { + event = document.createEventObject(); + event.eventType = bubble ? 'ondataavailable' : 'onfilterchange'; + } + + event.eventName = eventName; + event.memo = memo || { }; + + if (document.createEvent) + element.dispatchEvent(event); + else + element.fireEvent(event.eventType, event); + + return Event.extend(event); + } + + + Object.extend(Event, Event.Methods); + + Object.extend(Event, { + fire: fire, + observe: observe, + stopObserving: stopObserving + }); + + Element.addMethods({ + fire: fire, + + observe: observe, + + stopObserving: stopObserving + }); + + Object.extend(document, { + fire: fire.methodize(), + + observe: observe.methodize(), + + stopObserving: stopObserving.methodize(), + + loaded: false + }); + + if (window.Event) Object.extend(window.Event, Event); + else window.Event = Event; +})(); + +(function() { + /* Support for the DOMContentLoaded event is based on work by Dan Webb, + Matthias Miller, Dean Edwards, John Resig, and Diego Perini. */ + + var timer; + + function fireContentLoadedEvent() { + if (document.loaded) return; + if (timer) window.clearTimeout(timer); + document.loaded = true; + document.fire('dom:loaded'); + } + + function checkReadyState() { + if (document.readyState === 'complete') { + document.stopObserving('readystatechange', checkReadyState); + fireContentLoadedEvent(); + } + } + + function pollDoScroll() { + try { document.documentElement.doScroll('left'); } + catch(e) { + timer = pollDoScroll.defer(); + return; + } + fireContentLoadedEvent(); + } + + if (document.addEventListener) { + document.addEventListener('DOMContentLoaded', fireContentLoadedEvent, false); + } else { + document.observe('readystatechange', checkReadyState); + if (window == top) + timer = pollDoScroll.defer(); + } + + Event.observe(window, 'load', fireContentLoadedEvent); +})(); + +Element.addMethods(); + +/*------------------------------- DEPRECATED -------------------------------*/ + +Hash.toQueryString = Object.toQueryString; + +var Toggle = { display: Element.toggle }; + +Element.Methods.childOf = Element.Methods.descendantOf; + +var Insertion = { + Before: function(element, content) { + return Element.insert(element, {before:content}); + }, + + Top: function(element, content) { + return Element.insert(element, {top:content}); + }, + + Bottom: function(element, content) { + return Element.insert(element, {bottom:content}); + }, + + After: function(element, content) { + return Element.insert(element, {after:content}); + } +}; + +var $continue = new Error('"throw $continue" is deprecated, use "return" instead'); + +var Position = { + includeScrollOffsets: false, + + prepare: function() { + this.deltaX = window.pageXOffset + || document.documentElement.scrollLeft + || document.body.scrollLeft + || 0; + this.deltaY = window.pageYOffset + || document.documentElement.scrollTop + || document.body.scrollTop + || 0; + }, + + within: function(element, x, y) { + if (this.includeScrollOffsets) + return this.withinIncludingScrolloffsets(element, x, y); + this.xcomp = x; + this.ycomp = y; + this.offset = Element.cumulativeOffset(element); + + return (y >= this.offset[1] && + y < this.offset[1] + element.offsetHeight && + x >= this.offset[0] && + x < this.offset[0] + element.offsetWidth); + }, + + withinIncludingScrolloffsets: function(element, x, y) { + var offsetcache = Element.cumulativeScrollOffset(element); + + this.xcomp = x + offsetcache[0] - this.deltaX; + this.ycomp = y + offsetcache[1] - this.deltaY; + this.offset = Element.cumulativeOffset(element); + + return (this.ycomp >= this.offset[1] && + this.ycomp < this.offset[1] + element.offsetHeight && + this.xcomp >= this.offset[0] && + this.xcomp < this.offset[0] + element.offsetWidth); + }, + + overlap: function(mode, element) { + if (!mode) return 0; + if (mode == 'vertical') + return ((this.offset[1] + element.offsetHeight) - this.ycomp) / + element.offsetHeight; + if (mode == 'horizontal') + return ((this.offset[0] + element.offsetWidth) - this.xcomp) / + element.offsetWidth; + }, + + + cumulativeOffset: Element.Methods.cumulativeOffset, + + positionedOffset: Element.Methods.positionedOffset, + + absolutize: function(element) { + Position.prepare(); + return Element.absolutize(element); + }, + + relativize: function(element) { + Position.prepare(); + return Element.relativize(element); + }, + + realOffset: Element.Methods.cumulativeScrollOffset, + + offsetParent: Element.Methods.getOffsetParent, + + page: Element.Methods.viewportOffset, + + clone: function(source, target, options) { + options = options || { }; + return Element.clonePosition(target, source, options); + } +}; + +/*--------------------------------------------------------------------------*/ + +if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){ + function iter(name) { + return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name + " ')]"; + } + + instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ? + function(element, className) { + className = className.toString().strip(); + var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(className); + return cond ? document._getElementsByXPath('.//*' + cond, element) : []; + } : function(element, className) { + className = className.toString().strip(); + var elements = [], classNames = (/\s/.test(className) ? $w(className) : null); + if (!classNames && !className) return elements; + + var nodes = $(element).getElementsByTagName('*'); + className = ' ' + className + ' '; + + for (var i = 0, child, cn; child = nodes[i]; i++) { + if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) || + (classNames && classNames.all(function(name) { + return !name.toString().blank() && cn.include(' ' + name + ' '); + })))) + elements.push(Element.extend(child)); + } + return elements; + }; + + return function(className, parentElement) { + return $(parentElement || document.body).getElementsByClassName(className); + }; +}(Element.Methods); + +/*--------------------------------------------------------------------------*/ + +Element.ClassNames = Class.create(); +Element.ClassNames.prototype = { + initialize: function(element) { + this.element = $(element); + }, + + _each: function(iterator) { + this.element.className.split(/\s+/).select(function(name) { + return name.length > 0; + })._each(iterator); + }, + + set: function(className) { + this.element.className = className; + }, + + add: function(classNameToAdd) { + if (this.include(classNameToAdd)) return; + this.set($A(this).concat(classNameToAdd).join(' ')); + }, + + remove: function(classNameToRemove) { + if (!this.include(classNameToRemove)) return; + this.set($A(this).without(classNameToRemove).join(' ')); + }, + + toString: function() { + return $A(this).join(' '); + } +}; + +Object.extend(Element.ClassNames.prototype, Enumerable); + +/*--------------------------------------------------------------------------*/ diff --git a/src/ChatPage/chat/BypassLoginForm/js/require.js b/src/ChatPage/chat/BypassLoginForm/js/require.js new file mode 100755 index 0000000..29acced --- /dev/null +++ b/src/ChatPage/chat/BypassLoginForm/js/require.js @@ -0,0 +1,35 @@ +/* + RequireJS 2.1.5 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved. + Available via the MIT or new BSD license. + see: http://github.com/jrburke/requirejs for details +*/ +var requirejs,require,define; +(function(aa){function I(b){return"[object Function]"===L.call(b)}function J(b){return"[object Array]"===L.call(b)}function y(b,c){if(b){var d;for(d=0;dthis.depCount&&!this.defined){if(I(n)){if(this.events.error)try{e=i.execCb(c,n,b,e)}catch(d){a=d}else e=i.execCb(c,n,b,e);this.map.isDefine&&((b=this.module)&&void 0!==b.exports&&b.exports!==this.exports?e=b.exports:void 0===e&&this.usingExports&&(e=this.exports));if(a)return a.requireMap=this.map,a.requireModules=[this.map.id],a.requireType="define",v(this.error= +a)}else e=n;this.exports=e;if(this.map.isDefine&&!this.ignore&&(q[c]=e,l.onResourceLoad))l.onResourceLoad(i,this.map,this.depMaps);x(c);this.defined=!0}this.defining=!1;this.defined&&!this.defineEmitted&&(this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete=!0)}}else this.fetch()}},callPlugin:function(){var a=this.map,b=a.id,d=j(a.prefix);this.depMaps.push(d);t(d,"defined",u(this,function(e){var n,d;d=this.map.name;var g=this.map.parentMap?this.map.parentMap.name:null,h= +i.makeRequire(a.parentMap,{enableBuildCallback:!0});if(this.map.unnormalized){if(e.normalize&&(d=e.normalize(d,function(a){return c(a,g,!0)})||""),e=j(a.prefix+"!"+d,this.map.parentMap),t(e,"defined",u(this,function(a){this.init([],function(){return a},null,{enabled:!0,ignore:!0})})),d=m(p,e.id)){this.depMaps.push(e);if(this.events.error)d.on("error",u(this,function(a){this.emit("error",a)}));d.enable()}}else n=u(this,function(a){this.init([],function(){return a},null,{enabled:!0})}),n.error=u(this, +function(a){this.inited=!0;this.error=a;a.requireModules=[b];G(p,function(a){0===a.map.id.indexOf(b+"_unnormalized")&&x(a.map.id)});v(a)}),n.fromText=u(this,function(e,c){var d=a.name,g=j(d),C=O;c&&(e=c);C&&(O=!1);r(g);s(k.config,b)&&(k.config[d]=k.config[b]);try{l.exec(e)}catch(ca){return v(B("fromtexteval","fromText eval for "+b+" failed: "+ca,ca,[b]))}C&&(O=!0);this.depMaps.push(g);i.completeLoad(d);h([d],n)}),e.load(a.name,h,n,k)}));i.enable(d,this);this.pluginMaps[d.id]=d},enable:function(){V[this.map.id]= +this;this.enabling=this.enabled=!0;y(this.depMaps,u(this,function(a,b){var c,e;if("string"===typeof a){a=j(a,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap);this.depMaps[b]=a;if(c=m(N,a.id)){this.depExports[b]=c(this);return}this.depCount+=1;t(a,"defined",u(this,function(a){this.defineDep(b,a);this.check()}));this.errback&&t(a,"error",this.errback)}c=a.id;e=p[c];!s(N,c)&&(e&&!e.enabled)&&i.enable(a,this)}));G(this.pluginMaps,u(this,function(a){var b=m(p,a.id);b&&!b.enabled&&i.enable(a, +this)}));this.enabling=!1;this.check()},on:function(a,b){var c=this.events[a];c||(c=this.events[a]=[]);c.push(b)},emit:function(a,b){y(this.events[a],function(a){a(b)});"error"===a&&delete this.events[a]}};i={config:k,contextName:b,registry:p,defined:q,urlFetched:U,defQueue:H,Module:Z,makeModuleMap:j,nextTick:l.nextTick,onError:v,configure:function(a){a.baseUrl&&"/"!==a.baseUrl.charAt(a.baseUrl.length-1)&&(a.baseUrl+="/");var b=k.pkgs,c=k.shim,e={paths:!0,config:!0,map:!0};G(a,function(a,b){e[b]? +"map"===b?(k.map||(k.map={}),R(k[b],a,!0,!0)):R(k[b],a,!0):k[b]=a});a.shim&&(G(a.shim,function(a,b){J(a)&&(a={deps:a});if((a.exports||a.init)&&!a.exportsFn)a.exportsFn=i.makeShimExports(a);c[b]=a}),k.shim=c);a.packages&&(y(a.packages,function(a){a="string"===typeof a?{name:a}:a;b[a.name]={name:a.name,location:a.location||a.name,main:(a.main||"main").replace(ja,"").replace(ea,"")}}),k.pkgs=b);G(p,function(a,b){!a.inited&&!a.map.unnormalized&&(a.map=j(b))});if(a.deps||a.callback)i.require(a.deps||[], +a.callback)},makeShimExports:function(a){return function(){var b;a.init&&(b=a.init.apply(aa,arguments));return b||a.exports&&ba(a.exports)}},makeRequire:function(a,f){function d(e,c,h){var g,k;f.enableBuildCallback&&(c&&I(c))&&(c.__requireJsBuild=!0);if("string"===typeof e){if(I(c))return v(B("requireargs","Invalid require call"),h);if(a&&s(N,e))return N[e](p[a.id]);if(l.get)return l.get(i,e,a,d);g=j(e,a,!1,!0);g=g.id;return!s(q,g)?v(B("notloaded",'Module name "'+g+'" has not been loaded yet for context: '+ +b+(a?"":". Use require([])"))):q[g]}L();i.nextTick(function(){L();k=r(j(null,a));k.skipMap=f.skipMap;k.init(e,c,h,{enabled:!0});D()});return d}f=f||{};R(d,{isBrowser:A,toUrl:function(b){var d,f=b.lastIndexOf("."),g=b.split("/")[0];if(-1!==f&&(!("."===g||".."===g)||1h.attachEvent.toString().indexOf("[native code"))&&!Y?(O=!0,h.attachEvent("onreadystatechange",b.onScriptLoad)):(h.addEventListener("load",b.onScriptLoad,!1),h.addEventListener("error",b.onScriptError,!1)),h.src=d,K=h,D?x.insertBefore(h,D):x.appendChild(h),K=null,h;if(da)try{importScripts(d),b.completeLoad(c)}catch(j){b.onError(B("importscripts","importScripts failed for "+c+" at "+d,j,[c]))}};A&&M(document.getElementsByTagName("script"),function(b){x||(x= +b.parentNode);if(t=b.getAttribute("data-main"))return r.baseUrl||(E=t.split("/"),Q=E.pop(),fa=E.length?E.join("/")+"/":"./",r.baseUrl=fa,t=Q),t=t.replace(ea,""),r.deps=r.deps?r.deps.concat(t):[t],!0});define=function(b,c,d){var l,h;"string"!==typeof b&&(d=c,c=b,b=null);J(c)||(d=c,c=[]);!c.length&&I(d)&&d.length&&(d.toString().replace(la,"").replace(ma,function(b,d){c.push(d)}),c=(1===d.length?["require"]:["require","exports","module"]).concat(c));if(O){if(!(l=K))P&&"interactive"===P.readyState||M(document.getElementsByTagName("script"), +function(b){if("interactive"===b.readyState)return P=b}),l=P;l&&(b||(b=l.getAttribute("data-requiremodule")),h=F[l.getAttribute("data-requirecontext")])}(h?h.defQueue:T).push([b,c,d])};define.amd={jQuery:!0};l.exec=function(b){return eval(b)};l(r)}})(this); diff --git a/src/ChatPage/chat/BypassLoginForm/page.css b/src/ChatPage/chat/BypassLoginForm/page.css new file mode 100755 index 0000000..24954c7 --- /dev/null +++ b/src/ChatPage/chat/BypassLoginForm/page.css @@ -0,0 +1,17 @@ +body +{ + background-color:#FBCA48; + background-image: url('img/WebChatHeader.png'); + background-repeat:no-repeat; + background-position: left top; + margin-top:0px; + margin-left:auto; + margin-right:auto; + padding-top:0px; +} + +body[dir=rtl] +{ + background-image: url('img/WebChatHeader_rtl.png'); + background-position: right top; +} diff --git a/src/ChatPage/chat/BypassLoginForm/printableHistory.css b/src/ChatPage/chat/BypassLoginForm/printableHistory.css new file mode 100755 index 0000000..bc464ef --- /dev/null +++ b/src/ChatPage/chat/BypassLoginForm/printableHistory.css @@ -0,0 +1,55 @@ +body +{ + color: #000; + background-color: #fff +} + +ul > li +{ + list-style-type:none; +} + +.linkDisclaimerDiv +{ + margin-top: 8px; +} + +td +{ + vertical-align:top; +} + +td.time +{ + min-width:160px; + white-space:nowrap; +} + +[dir=ltr] td.time +{ + text-align:right; + padding-right:10px; +} + +[dir=rtl] td.time +{ + text-align:left; + padding-left:10px; +} + +td.name +{ + white-space:nowrap; +} + +[dir=ltr] td.name +{ + text-align:right; + padding-right:10px; +} + +[dir=rtl] td.name +{ + text-align:left; + padding-left:10px; +} diff --git a/src/ChatPage/chat/BypassLoginForm/readme.html b/src/ChatPage/chat/BypassLoginForm/readme.html new file mode 100755 index 0000000..d7125ae --- /dev/null +++ b/src/ChatPage/chat/BypassLoginForm/readme.html @@ -0,0 +1,44 @@ + + + Readme + + +

        + In this example, the information necessary to start a chat or create a callback is + obtained from an external source, and therefore the default login page is bypassed. +

        + +

        + The following files are different from the default install:
        +

          +
        • + Customizations.js - LoginInfoSourceFactory was changed to return a new subclass of + ui._Internal._DefaultLoginInfoSource. + This subclass, customizations.CustomLoginInfoSource, + appears later in this file, and overrides the methods of its parent class.
          + Also, a new class, customizations.CustomLifecycleEventsObserver, + was created. It listens for Notifications that a chat was created, a chat + completed, a callback was created, or an attempt to do any of these operations + has failed. It is instantiated in CustomLoginInfoSource, but could just + as easily be instantiated elsewhere instead. +
        • +
        • + chatOrCallback.html - This is the default index.html page, renamed. The content of the file + has not been changed at all. +
        • +
        • + index.html - This page contains a simple form into which the user may enter their username, password, + telephone number, and callback subject. It is meant to represent any web page, cookie, database, or + other source for the information necessary to create a chat or callback. For instance, a page displaying + information about a particular product could display a "chat with a representative" link that is constructed + using cookie data. +
        • +
        +

        + +

        + Important: Before using this example, please copy your "config.js" file into this example's + "js" directory. +

        + + diff --git a/src/ChatPage/chat/BypassLoginForm/webchat.css b/src/ChatPage/chat/BypassLoginForm/webchat.css new file mode 100755 index 0000000..c2d7ca4 --- /dev/null +++ b/src/ChatPage/chat/BypassLoginForm/webchat.css @@ -0,0 +1,1271 @@ +/* BEGIN reset */ +.iwc-web-chat +{ + margin:0px; + padding:0px; +} +/* END reset */ + + + +/* BEGIN regular element override styles */ +.iwc-web-chat h1.iwc-page-header +{ + font-family: Arial, Helvetica, Sans-Serif; + color:#523b10; + margin-top:39px; + font-size:1.6em; + font-weight: normal; + margin-bottom: 12px; + display: block; +} + +.iwc-web-chat .iwc-link +{ + font-size:.8em; + color:#674703; + text-decoration:none; +} + +.iwc-web-chat .iwc-link:hover +{ + text-decoration:underline; +} + +.iwc-web-chat .iwc-link-disabled +{ + color:#333; +} + +.iwc-web-chat .iwc-link-disabled:hover +{ + text-decoration:none; + cursor:default; +} + +.iwc-web-chat .iwc-error > span +{ + font-size: 1.2em; + color: #f00; +} + +.iwc-web-chat .iwc-error > img +{ + vertical-align:middle; +} + +[dir=ltr] .iwc-web-chat .iwc-error > img +{ + padding-right:4px; +} + +[dir=rtl] .iwc-web-chat .iwc-error > img +{ + padding-left:4px; +} + +.iwc-load-error +{ + margin-top: 45px; +} + +[dir=ltr] .iwc-load-error +{ + margin-left: 30px; +} + +[dir=rtl] .iwc-load-error +{ + margin-right: 30px; +} + +.iwc-load-error > span +{ + font-size: 1.2em; + color: #f00; +} + +.iwc-load-error > img +{ + vertical-align:middle; +} + +[dir=ltr] .iwc-load-error > img +{ + padding-right:4px; +} + +[dir=rtl] .iwc-load-error > img +{ + padding-left:4px; +} + +.iwc-web-chat input[type='button'] +{ + font-size:0.8em; +} +/* END regular element override styles */ + +/* BEGIN .iwc-login-container */ +[dir=ltr] .iwc-login-container +{ + margin:0px; + margin-left:30px; +} + +[dir=rtl] .iwc-login-container +{ + margin:0px; + margin-right:30px; +} + +.iwc-login-container .iwc-page-header +{ +} + +.iwc-login-container .iwc-status > img +{ + display:none; + vertical-align:middle; +} + +[dir=ltr] .iwc-login-container .iwc-status > img +{ + padding:2px 5px 2px 2px; +} + +[dir=rtl] .iwc-login-container .iwc-status > img +{ + padding:2px 2px 2px 5px; +} + +.iwc-login-container .iwc-status > span +{ + color: #444; + font-weight:bold; + font-size:1.0em; + vertical-align:middle; +} + +.iwc-login-container .iwc-status-success > img[status-type='error'], +.iwc-login-container .iwc-status-error > img[status-type='success'] +{ + display:none; +} + +.iwc-login-container .iwc-status-success > img[status-type='success'], +.iwc-login-container .iwc-status-error > img[status-type='error'] +{ + display:inline; +} + +.iwc-login-container .iwc-status-error > span +{ + color: #f00; +} + +.iwc-callback-panel-content +{ + margin-top:10px; +} + +.iwc-disclaimer +{ + color: #674703; + font-size: 0.8em; +} + +/* END .iwc-login-container */ + + + +/* BEGIN .iwc-form-tabs */ +[dir=ltr] .iwc-form-tabs +{ + font: bold 11px verdana,arial,sans-serif; + list-style-type: none; + margin: 1em 0 1em 0; + padding: 0px 10px 25px 10px; + text-align: left; + border-bottom: 1px solid #555; +} + +[dir=rtl] .iwc-form-tabs +{ + font: bold 11px verdana,arial,sans-serif; + list-style-type: none; + margin: 1em 0 1em 0; + padding: 0px 10px 25px 10px; + text-align: right; + border-bottom: 1px solid #555; +} + +.iwc-form-tabs .iwc-unselected-tab, +.iwc-form-tabs .iwc-selected-tab +{ + display: block; +} + +[dir=ltr] .iwc-form-tabs .iwc-unselected-tab, +[dir=ltr] .iwc-form-tabs .iwc-selected-tab +{ + margin-right:3px; + float: left; +} + +[dir=rtl] .iwc-form-tabs .iwc-unselected-tab, +[dir=rtl] .iwc-form-tabs .iwc-selected-tab +{ + margin-left:3px; + float: right; +} + +.iwc-form-tabs .iwc-unselected-tab > a +{ + border-style: solid solid none; + border-width: 1px 1px medium; + padding: 3px 4px; + text-decoration: none; + border-color: #666; + background-color: #DD9609; + color: #704D18; + display: block; + height: 18px; +} + +[dir=ltr] .iwc-form-tabs .iwc-unselected-tab > a +{ + margin-right: 0pt; +} + +[dir=rtl] .iwc-form-tabs .iwc-unselected-tab > a +{ + margin-left: 0pt; +} + +.iwc-form-tabs .iwc-selected-tab > a +{ + border-color: #666 #666 #FBCA48; + background-color: #FBCA48; + border-width: 1px; + text-decoration: none; + border-style: solid solid none; + padding: 4px 4px 3px; + color: #523B10; + position: relative; + display: block; + height: 18px; +} + +[dir=ltr] .iwc-form-tabs .iwc-selected-tab > a +{ + margin-right: 0pt; +} + +[dir=rtl] .iwc-form-tabs .iwc-selected-tab > a +{ + margin-left: 0pt; +} +/* END .iwc-form-tabs */ + + + +/* BEGIN .iwc-form-panel */ +.iwc-form-panel +{ + width:600px; +} + +.iwc-form-panel input[type='button'], +.iwc-callback-management-form-panel input[type='button'] +{ + height:26px; +} + +.iwc-callback-management-form-panel .iwc-callback-status-indicator-and-button-container +{ + width: 74px; +} + +[dir=ltr] .iwc-callback-management-form-panel .iwc-callback-status-indicator-and-button-container +{ + float: left; +} + +[dir=rtl] .iwc-callback-management-form-panel .iwc-callback-status-indicator-and-button-container +{ + float: right; +} + +[dir=ltr] .iwc-callback-disconnect-button +{ + margin-left: -1px; + min-width: 76px; + max-width: 76px; +} + +[dir=rtl] .iwc-callback-disconnect-button +{ + margin-right: -1px; + min-width: 76px; + max-width: 76px; +} + +.iwc-callback-management-form-panel .iwc-callback-status-indicator +{ + color: #FBCA48; + background-color: #674703; + padding: 4px; + width: 66px; + text-align: center; + margin-bottom: 2px; + font-size: 11px; +} + +[dir=ltr] .iwc-callback-management-form-panel .iwc-callback-status-and-failure-container +{ + float: left; + margin-left: 8px; +} + +[dir=rtl] .iwc-callback-management-form-panel .iwc-callback-status-and-failure-container +{ + float: right; + margin-right: 8px; +} + +.iwc-callback-management-form-panel .iwc-callback-status-subject +{ + color: #674703; + font-size: 1em; + font-weight: bold; + line-height: 24px; + vertical-align: middle; +} + +.iwc-callback-status-subject-div +{ + max-width: 430px; +} + +[dir=ltr] .iwc-callback-status-subject-div +{ + max-width: 430px; +} + +[dir=ltr] .iwc-callback-management-form-panel .iwc-callback-status-fields-container-div +{ + margin-left: 30px; + max-width: 400px; +} + +[dir=rtl] .iwc-callback-management-form-panel .iwc-callback-status-fields-container-div +{ + margin-right: 30px; + max-width: 400px; +} + +.iwc-callback-management-form-panel .iwc-callback-status-failure-container +{ +} + +[dir=ltr] .iwc-form-panel .iwc-account-indented-panel +{ + margin-left:7px; +} + +[dir=rtl] .iwc-form-panel .iwc-account-indented-panel +{ + margin-right:7px; +} + +.iwc-form-panel .iwc-form-field-div +{ + margin-bottom:4px; +} + +[dir=ltr] .iwc-form-panel .iwc-form-button-div +{ + text-align:right; +} + +[dir=rtl] .iwc-form-panel .iwc-form-button-div +{ + text-align:left; +} + +.iwc-callback-participant-avatar +{ + width:80px; + border:none 0px; + padding:0px; +} + +[dir=ltr] .iwc-callback-participant-avatar +{ + float:left; + margin-left:8px; +} + +[dir=rtl] .iwc-callback-participant-avatar +{ + float:right; + margin-right:8px; +} + +.iwc-chat-form-panel .iwc-form-button-div +{ + width:285px; +} + +.iwc-chat-form-panel .iwc-label, +.iwc-callback-form-panel .iwc-label +{ + min-width: 70px; +} + +[dir=ltr] .iwc-chat-form-panel .iwc-label, +[dir=ltr] .iwc-callback-form-panel .iwc-label +{ + margin-right: 4px; +} + +[dir=rtl] .iwc-chat-form-panel .iwc-label, +[dir=rtl] .iwc-callback-form-panel .iwc-label +{ + margin-left: 4px; +} + +.iwc-callback-management-form-panel .iwc-key +{ + min-width: 113px; + font-weight:bold; +} + +[dir=ltr] .iwc-callback-management-form-panel .iwc-key +{ + margin-right: 6px; +} + +[dir=rtl] .iwc-callback-management-form-panel .iwc-key +{ + margin-left: 6px; +} + +.iwc-register-form-panel .iwc-label +{ + min-width: 150px; +} + +[dir=ltr] .iwc-register-form-panel .iwc-label +{ + margin-right: 4px; +} + +[dir=rtl] .iwc-register-form-panel .iwc-label +{ + margin-left: 4px; +} + +.iwc-callback-form-panel .iwc-form-button-div, +.iwc-callback-management-form-panel .iwc-form-button-div +{ + width:386px; +} + +.iwc-register-form-panel .iwc-form-button-div +{ + width:359px; +} + +.iwc-form-panel .iwc-account-radio-button-label +{ + font-size:.8em; + color:#674703; + cursor:default; +} + +.iwc-form-panel .iwc-create-account-link +{ + font-size:.8em; + color:#674703; + text-decoration:underline; +} + +[dir=ltr] .iwc-form-panel .iwc-create-account-link +{ + margin-left:12px; +} + +[dir=rtl] .iwc-form-panel .iwc-create-account-link +{ + margin-right:12px; +} + +.iwc-callback-disconnect-link +{ + font-size:.8em; + color:#674703; + text-decoration:underline; +} + +[dir=ltr] .iwc-callback-disconnect-link +{ + margin-left:7px; +} + +[dir=rtl] .iwc-callback-disconnect-link +{ + margin-right:7px; +} + +.iwc-form-panel .iwc-label, +.iwc-callback-management-form-panel .iwc-label, +.iwc-callback-management-form-panel .iwc-key +{ + display: inline-block; + color: #674703; + font-size: .8em; +} + +.iwc-form-panel .iwc-description-label +{ + vertical-align:top; + margin-top:5px; +} + +[dir=ltr] .iwc-form-panel .iwc-optional-label +{ + margin-left:4px; +} + +[dir=rtl] .iwc-form-panel .iwc-optional-label +{ + margin-right:4px; +} + +.iwc-form-panel .iwc-textbox +{ + width: 200px; +} + +.iwc-form-panel .iwc-textarea +{ + width: 400px; + height: 100px; +} + +.iwc-form-panel .iwc-button, +.iwc-exit-button +{ + color: #32352e; + padding-left: 8px; + padding-right: 8px; +} + +.iwc-form-panel .iwc-description-textarea +{ + height:80px; + width:300px; +} + +[dir=ltr] .iwc-callback-management-form-panel .iwc-callback-status-panel +{ + width:600px; +} + +[dir=rtl] .iwc-callback-management-form-panel .iwc-callback-status-panel +{ + width:600px; +} + +.iwc-callback-management-form-panel .iwc-callback-creation-success-panel, +.iwc-callback-management-form-panel .iwc-callback-status-panel +{ + margin-bottom: 12px; +} + +.iwc-callback-management-form-panel .iwc-callback-creation-success-panel img, +.iwc-callback-management-form-panel .iwc-callback-disconnect-link-panel img, +.iwc-callback-management-form-panel .iwc-callback-disconnect-confirmation-panel img, +.iwc-callback-management-form-panel .iwc-callback-disconnect-failure-panel img, +.iwc-callback-management-form-panel .iwc-callback-status-failure img +{ + vertical-align:middle; +} + +.iwc-callback-management-form-panel .iwc-callback-creation-success-panel span +{ + color: #444; + font-weight:bold; + font-size:1.0em; + vertical-align:middle; + line-height:24px; +} + +[dir=ltr] .iwc-callback-management-form-panel .iwc-callback-creation-success-panel span +{ + margin-left:7px; +} + +[dir=rtl] .iwc-callback-management-form-panel .iwc-callback-creation-success-panel span +{ + margin-right:7px; +} + +.iwc-callback-management-form-panel .iwc-callback-status-header +{ + border: solid 1px #CFA73B; + background-color:#EBBD43; + margin-bottom:6px; +} + +[dir=ltr] .iwc-callback-management-form-panel .iwc-callback-status-header +{ + padding: 2px 0px 2px 2px; +} + +[dir=rtl] .iwc-callback-management-form-panel .iwc-callback-status-header +{ + padding: 2px 2px 2px 0px; +} + +.iwc-form-panel .iwc-form-section-header +{ + border: solid 1px #CFA73B; + background-color:#EBBD43; + margin-bottom:6px; + width:352px; +} + +[dir=ltr] .iwc-form-panel .iwc-form-section-header +{ + padding: 2px 0px 2px 2px; +} + +[dir=rtl] .iwc-form-panel .iwc-form-section-header +{ + padding: 2px 2px 2px 0px; +} + +.iwc-form-panel .iwc-form-section-header span, +.iwc-callback-management-form-panel .iwc-callback-status-header span +{ + font-size:.9em; + font-weight:bold; + color:#000; +} + +[dir=ltr] .iwc-form-panel .iwc-formfielderror +{ + margin-left:4px; +} + +[dir=rtl] .iwc-form-panel .iwc-formfielderror +{ + margin-right:4px; +} + +.iwc-form-panel .iwc-formfielderror > img +{ + height:16px; + width:16px; + vertical-align:top; + margin-top:4px; +} + +[dir=ltr] .iwc-form-panel .iwc-formfielderror > img +{ + padding-right:4px; +} + +[dir=rtl] .iwc-form-panel .iwc-formfielderror > img +{ + padding-left:4px; +} + +.iwc-form-panel .iwc-formfielderror > span +{ + color:#f00; + font-size:.8em; + font-weight:bold; + vertical-align:top; + margin-top:4px; + display:inline-block; +} + +.iwc-busy-image +{ + border: none; + z-index: 100; + height:110px; + width:110px; + position:absolute; + top:160px; +} + +[dir=ltr] .iwc-busy-image +{ + left:180px; +} + +[dir=rtl] .iwc-busy-image +{ + right:180px; +} + +/* END .iwc-form-panel */ + + + + +/* BEGIN .iwc-main-panel */ +[dir=ltr] .iwc-main-panel +{ + margin-left: 30px; +} + +[dir=rtl] .iwc-main-panel +{ + margin-right: 30px; +} + +.iwc-main-panel .iwc-page-header +{ +} + + +/* END .iwc-main-panel */ + + + +/* BEGIN .iwc-received-messages-panel */ +.iwc-received-messages-panel +{ + padding:1px; + width: 600px; + height: 300px; + border:solid 1px #666; + background-color:#fff; +} + +[dir=ltr] .iwc-received-messages-panel +{ + margin:0px 4px 0px 0px; +} + +[dir=rtl] .iwc-received-messages-panel +{ + margin:0px 0px 0px 4px; +} + +.iwc-received-messages-panel > ul +{ + list-style-type:none; + margin:0px; + padding:0px; + overflow:auto; + overflow-x:hidden; + height:300px; +} + +.iwc-received-messages-panel > ul > li +{ + width:auto; + border: none 0px; + margin:2px; +} + +.iwc-received-messages-panel > ul > li .iwc-message-sender +{ + margin: 0px 8px 0px 4px; + font-family:Verdana; + font-size:0.8em; + color:#555; + display:inline; +} + +.iwc-received-messages-panel > ul .iwc-late-message +{ + background-color:#ff6666; +} + +.iwc-received-messages-panel > ul > li ul +{ + list-style-type:none; +} + +[dir=ltr] .iwc-received-messages-panel > ul > li ul +{ + padding-left:10px; + margin-left:8px; +} + +[dir=rtl] .iwc-received-messages-panel > ul > li ul +{ + padding-right:10px; + margin-right:8px; +} + +.iwc-message .iwc-arrow +{ + background-repeat:no-repeat; +} + +[dir=ltr] .iwc-message .iwc-arrow +{ + background-image: url('img/message_indicator.png'); + background-position:left; + padding-left:15px; +} + +[dir=rtl] .iwc-message .iwc-arrow +{ + background-image: url('img/message_indicator_rtl.png'); + background-position:right; + padding-right:15px; +} + +/* In RTL, IE will swap padding-left and padding-right. */ +[dir=rtl] .iwc-message .iwc-arrow-IE-fix +{ + background-image: url('img/message_indicator_rtl.png'); + background-position:right; + padding-left:15px; + background-repeat:no-repeat; +} + +.iwc-message > div, +.iwc-message > div > div +{ + display:inline; +} + +.iwc-message .iwc-message-text +{ + font-family:Verdana; + font-size:0.8em; +} + +.iwc-message .iwc-message-time +{ + font-family:Verdana; + font-size:0.7em; + color:#555; + display: block; +} + +[dir=ltr] .iwc-message .iwc-message-time +{ + margin:3px 4px 0px 0px; + float:right; + padding-left:14px; +} + +[dir=rtl] .iwc-message .iwc-message-time +{ + margin:3px 0px 0px 4px; + float:left; + padding-right:14px; +} + +.iwc-message .iwc-message-time-faded +{ + color: #ddd; +} + +.iwc-system-message-group +{ + margin: 5px 5px 11px -14px; + background-color:#f5f5f5; +} + +.iwc-system-message-group .iwc-message > div +{ + color:#555; + font-style:italic; + font-size:0.7em; +} + +.iwc-message-link a:link, +.iwc-message-link a:visited, +.iwc-message-link a:active, +.iwc-message-link a:hover +{ + color:#00f; +} +/* END .iwc-received-messages-panel */ + + + +/* BEGIN .iwc-participants-panel */ +.iwc-participants-panel +{ + border:solid 1px #666; + background-color: #fff; + margin:0px; + width:600px; + min-height:34px; + padding:1px; +} + +[dir=ltr] .iwc-participants-panel +{ + left:4px; +} + +[dir=rtl] .iwc-participants-panel +{ + right:4px; +} + +.iwc-participants-panel > ul +{ + margin:0px; + list-style-type: none; +} + +[dir=ltr] .iwc-participants-panel > ul +{ + padding:0px 0px 0px 4px; +} + +[dir=rtl] .iwc-participants-panel > ul +{ + padding:0px 4px 0px 0px; +} + +.iwc-participants-panel > ul > li table, +.iwc-participants-panel > ul > li tbody, +.iwc-participants-panel > ul > li tr, +.iwc-participants-panel > ul > li td, +.iwc-participants-panel > ul > li .iwc-participant-name +{ + width:auto; + height:32px; + margin:0px; + border:0px; + padding:0px; + outline:0px; +} + +.iwc-participants-panel > ul > li +{ + width:auto; + height:32px; + margin:2px 0px; + padding:0px; + outline:0px; +} + +.iwc-send-on-enter-container { + width: 600px; + padding: 1px; +} + +.iwc-send-on-enter-checkbox-label { + float: right; +} + +.iwc-send-on-enter-checkbox { + float: right; +} + +.iwc-participant-name, +.iwc-chat-participant-popover-name +{ + font-size:1.2em; + font-family:Verdana; + border: none 0px; + color:#222; +} + +.iwc-participant-name +{ + vertical-align:middle; +} + +.iwc-chat-participant-popover-name +{ + font-weight: bold; +} + +[dir=ltr] .iwc-chat-participant-popover-name +{ + margin-left: 8px; +} + +[dir=rtl] .iwc-chat-participant-popover-name +{ + margin-right: 8px; +} + +.iwc-participants-panel > ul > li.iwc-participant-held .iwc-participant-name +{ + font-style:italic; +} + +.iwc-participants-panel > ul > li .iwc-participant-typing-indicator-img +{ + width:16px; + height:32px; + border: none 0px; + padding:0px; + vertical-align:middle; + background-position:center; + background-repeat:no-repeat; +} + +[dir=ltr] .iwc-participants-panel > ul > li .iwc-participant-typing-indicator-img +{ + margin:0px 4px 0px 0px; +} + +[dir=rtl] .iwc-participants-panel > ul > li .iwc-participant-typing-indicator-img +{ + margin:0px 0px 0px 4px; +} + +.iwc-chat-participant-avatar +{ + background-size: cover; + width:32px; + height:32px; + border:none 0px; + padding:0px; + -webkit-background-size: cover; + -moz-background-size: cover; + -o-background-size: cover; +} + +[dir=ltr] .iwc-chat-participant-avatar +{ + filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='img/user.png', sizingMethod='scale'); + -ms-filter: "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='img/user.png', sizingMethod='scale')"; + background-image: url('img/user.png'); +} + +[dir=rtl] .iwc-chat-participant-avatar +{ + filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='img/user_rtl.png', sizingMethod='scale'); + -ms-filter: "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='img/user_rtl.png', sizingMethod='scale')"; + background-image: url('img/user_rtl.png'); +} + +.iwc-chat-participant-popover +{ + position: absolute; + z-index: 100; + border: 1px solid black; + margin-top: -16px; + padding: 8px; + background-color: #FDE4A3; +} + +[dir=ltr] .iwc-chat-participant-popover +{ + margin-left: 36px; +} + +[dir=rtl] .iwc-chat-participant-popover +{ + margin-right: 36px; +} + +.iwc-chat-participant-popover td +{ + vertical-align: top; +} + +.iwc-chat-participant-popover-supplementalInfo +{ + font-weight: 0.8em; + color: #555555; +} + +[dir=ltr] .iwc-chat-participant-popover-supplementalInfo +{ + margin-left: 8px; +} + +[dir=rtl] .iwc-chat-participant-popover-supplementalInfo +{ + margin-right: 8px; +} + +[dir=ltr] .iwc-chat-participant-popover-avatar-div +{ + margin:0px 4px 0px 0px; +} + +[dir=rtl] .iwc-chat-participant-popover-avatar-div +{ + margin:0px 0px 0px 4px; +} + +.iwc-chat-participant-popover-avatar +{ + width:300px; + height:300px; + background-size: cover; +} + +.iwc-rounded-box +{ + -webkit-border-radius: 8px; + -moz-border-radius: 8px; + border-radius: 8px; +} + +[dir=ltr] .iwc-participants-panel > ul > li.iwc-participant-typing .iwc-participant-typing-indicator-img +{ + background-image: url('img/typing_16x32.png'); +} + +[dir=rtl] .iwc-participants-panel > ul > li.iwc-participant-typing .iwc-participant-typing-indicator-img +{ + background-image: url('img/typing_16x32_rtl.png'); +} + +.iwc-participants-panel > ul > li .iwc-participant-typing-status +{ + display:none; +} + +.iwc-participants-panel > ul > li.iwc-participant-typing .iwc-participant-typing-status +{ + display:inline; + color:#999; + font-family:Verdana; + font-size:.7em; +} + +[dir=ltr] .iwc-participants-panel > ul > li.iwc-participant-typing .iwc-participant-typing-status +{ + margin-left:4px; +} + +[dir=rtl] .iwc-participants-panel > ul > li.iwc-participant-typing .iwc-participant-typing-status +{ + margin-right:4px; +} + +[dir=ltr] .iwc-print-div +{ + float:right; +} + +[dir=rtl] .iwc-print-div +{ + float:left; +} + +.iwc-print-div a +{ + font-size:.8em; + color:#674703; + text-decoration:underline; + font-size:.8em; +} + +[dir=ltr] .iwc-print-div a +{ + margin-right:5px; +} + +[dir=rtl] .iwc-print-div a +{ + margin-left:5px; +} +/* END .iwc-participants-panel */ + + + +/* BEGIN .iwc-compose-message-panel */ +.iwc-compose-message-panel +{ + margin:0px; + width: 602px; + background-color:#fff; + border: solid 1px #666; + padding:0px; +} + +.iwc-compose-message-panel .iwc-input +{ + position:absolute; + margin: 0px; + padding: 0px; + height: 61px; + width: 517px; + display: block; + vertical-align: top; + overflow:auto; + border:0px; + top:0; +} + +[dir=ltr] .iwc-compose-message-panel .iwc-input +{ + left:0; + margin-right: 4px; +} + +[dir=rtl] .iwc-compose-message-panel .iwc-input +{ + right:0; + margin-left: 4px; +} + +.iwc-compose-message-panel[usehtmleditor='false'] +{ + height:61px; +} + +.iwc-compose-message-panel[usehtmleditor='false'] .iwc-input +{ + resize: none; /* for safari and chrome */ +} + +.iwc-compose-message-panel[usehtmleditor='true'] .cke_editor .cke_contents +{ + height:92px; + padding-right:83px; + background-color:#fff; +} + +.iwc-compose-message-panel +{ + position:relative; +} + +.iwc-compose-message-panel .iwc-send-button +{ + position:absolute; + margin: 0px; + padding: 0px; + height: 57px; + width: 80px; + display: block; + vertical-align: top; + bottom:2px; +} + +[dir=ltr] .iwc-compose-message-panel .iwc-send-button +{ + right:2px; +} + +[dir=rtl] .iwc-compose-message-panel .iwc-send-button +{ + left:2px; +} +/* END .iwc-compose-message-panel */ + +.containsFloatingChild:after { + clear: both; + visibility: hidden; + display: block; + content: " "; + height: 0px; +} diff --git a/src/ChatPage/chathost.html b/src/ChatPage/chathost.html new file mode 100755 index 0000000..2ec2d09 --- /dev/null +++ b/src/ChatPage/chathost.html @@ -0,0 +1,220 @@ + + + + + + + + Web Chat + + + + +
        +
        + +
        +
        +
        Cobrowse with an agent
        +
        +
        + +
        +
        + + \ No newline at end of file diff --git a/src/ChatPage/favicon.ico b/src/ChatPage/favicon.ico new file mode 100755 index 0000000..82ce973 Binary files /dev/null and b/src/ChatPage/favicon.ico differ diff --git a/src/ChatPage/img/logo.png b/src/ChatPage/img/logo.png new file mode 100755 index 0000000..691a260 Binary files /dev/null and b/src/ChatPage/img/logo.png differ diff --git a/src/ChatPage/scripts/config.js b/src/ChatPage/scripts/config.js new file mode 100755 index 0000000..fe3069d --- /dev/null +++ b/src/ChatPage/scripts/config.js @@ -0,0 +1,27 @@ +// Define the link options here +var glanceLinkOptions = [ + { + "text": "Demo Page", + "description": "A page for demoing Glance Cobrowse.", + "url": "https://domain.com/path/to/page.html" + }, + { + "text": "Another Page", + "description": "Another page that's the same as the demo page, but different.", + "url": "https://domain.com/path/to/page2.html" + }, + { + "text": "Another Other Page", + "description": "Another page that's the same as the demo page, but different.", + "url": "https://domain.com/path/to/page3.html" + } + ]; + +// Glance settings +var glanceGroupId = 1234; +var glanceScreenDomain = 'https://domain.com'; + +// If these variables are specified elsewhere, remove them from the config file +var glanceGuestName = 'Website Guest'; +var glanceGuestEmail = 'fake@domain.com'; +var glanceGuestPhone = '555-555-5555'; \ No newline at end of file diff --git a/src/ChatPage/scripts/jquery-2.1.1.min.js b/src/ChatPage/scripts/jquery-2.1.1.min.js new file mode 100755 index 0000000..e5ace11 --- /dev/null +++ b/src/ChatPage/scripts/jquery-2.1.1.min.js @@ -0,0 +1,4 @@ +/*! jQuery v2.1.1 | (c) 2005, 2014 jQuery Foundation, Inc. | jquery.org/license */ +!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l=a.document,m="2.1.1",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return n.each(this,a,b)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){return!n.isArray(a)&&a-parseFloat(a)>=0},isPlainObject:function(a){return"object"!==n.type(a)||a.nodeType||n.isWindow(a)?!1:a.constructor&&!j.call(a.constructor.prototype,"isPrototypeOf")?!1:!0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=l.createElement("script"),b.text=a,l.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:g.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(e=d.call(arguments,2),f=function(){return a.apply(b||this,e.concat(d.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:k}),n.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+-new Date,v=a.document,w=0,x=0,y=gb(),z=gb(),A=gb(),B=function(a,b){return a===b&&(l=!0),0},C="undefined",D=1<<31,E={}.hasOwnProperty,F=[],G=F.pop,H=F.push,I=F.push,J=F.slice,K=F.indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]===a)return b;return-1},L="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",M="[\\x20\\t\\r\\n\\f]",N="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",O=N.replace("w","w#"),P="\\["+M+"*("+N+")(?:"+M+"*([*^$|!~]?=)"+M+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+O+"))|)"+M+"*\\]",Q=":("+N+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+P+")*)|.*)\\)|)",R=new RegExp("^"+M+"+|((?:^|[^\\\\])(?:\\\\.)*)"+M+"+$","g"),S=new RegExp("^"+M+"*,"+M+"*"),T=new RegExp("^"+M+"*([>+~]|"+M+")"+M+"*"),U=new RegExp("="+M+"*([^\\]'\"]*?)"+M+"*\\]","g"),V=new RegExp(Q),W=new RegExp("^"+O+"$"),X={ID:new RegExp("^#("+N+")"),CLASS:new RegExp("^\\.("+N+")"),TAG:new RegExp("^("+N.replace("w","w*")+")"),ATTR:new RegExp("^"+P),PSEUDO:new RegExp("^"+Q),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+L+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ab=/[+~]/,bb=/'|\\/g,cb=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),db=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)};try{I.apply(F=J.call(v.childNodes),v.childNodes),F[v.childNodes.length].nodeType}catch(eb){I={apply:F.length?function(a,b){H.apply(a,J.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fb(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],!a||"string"!=typeof a)return d;if(1!==(k=b.nodeType)&&9!==k)return[];if(p&&!e){if(f=_.exec(a))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return I.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName&&b.getElementsByClassName)return I.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=9===k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(bb,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+qb(o[l]);w=ab.test(a)&&ob(b.parentNode)||b,x=o.join(",")}if(x)try{return I.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function gb(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function hb(a){return a[u]=!0,a}function ib(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function jb(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function kb(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||D)-(~a.sourceIndex||D);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function lb(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function mb(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function nb(a){return hb(function(b){return b=+b,hb(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function ob(a){return a&&typeof a.getElementsByTagName!==C&&a}c=fb.support={},f=fb.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fb.setDocument=function(a){var b,e=a?a.ownerDocument||a:v,g=e.defaultView;return e!==n&&9===e.nodeType&&e.documentElement?(n=e,o=e.documentElement,p=!f(e),g&&g!==g.top&&(g.addEventListener?g.addEventListener("unload",function(){m()},!1):g.attachEvent&&g.attachEvent("onunload",function(){m()})),c.attributes=ib(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ib(function(a){return a.appendChild(e.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(e.getElementsByClassName)&&ib(function(a){return a.innerHTML="
        ",a.firstChild.className="i",2===a.getElementsByClassName("i").length}),c.getById=ib(function(a){return o.appendChild(a).id=u,!e.getElementsByName||!e.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if(typeof b.getElementById!==C&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){var c=typeof a.getAttributeNode!==C&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return typeof b.getElementsByTagName!==C?b.getElementsByTagName(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return typeof b.getElementsByClassName!==C&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(e.querySelectorAll))&&(ib(function(a){a.innerHTML="",a.querySelectorAll("[msallowclip^='']").length&&q.push("[*^$]="+M+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+M+"*(?:value|"+L+")"),a.querySelectorAll(":checked").length||q.push(":checked")}),ib(function(a){var b=e.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+M+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ib(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",Q)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===e||a.ownerDocument===v&&t(v,a)?-1:b===e||b.ownerDocument===v&&t(v,b)?1:k?K.call(k,a)-K.call(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,f=a.parentNode,g=b.parentNode,h=[a],i=[b];if(!f||!g)return a===e?-1:b===e?1:f?-1:g?1:k?K.call(k,a)-K.call(k,b):0;if(f===g)return kb(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?kb(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},e):n},fb.matches=function(a,b){return fb(a,null,null,b)},fb.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fb(b,n,null,[a]).length>0},fb.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fb.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&E.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fb.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fb.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fb.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fb.selectors={cacheLength:50,createPseudo:hb,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(cb,db),a[3]=(a[3]||a[4]||a[5]||"").replace(cb,db),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fb.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fb.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(cb,db).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+M+")"+a+"("+M+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||typeof a.getAttribute!==C&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fb.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fb.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?hb(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=K.call(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:hb(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?hb(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:hb(function(a){return function(b){return fb(a,b).length>0}}),contains:hb(function(a){return function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:hb(function(a){return W.test(a||"")||fb.error("unsupported lang: "+a),a=a.replace(cb,db).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:nb(function(){return[0]}),last:nb(function(a,b){return[b-1]}),eq:nb(function(a,b,c){return[0>c?c+b:c]}),even:nb(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:nb(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:nb(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:nb(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function rb(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function sb(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function tb(a,b,c){for(var d=0,e=b.length;e>d;d++)fb(a,b[d],c);return c}function ub(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function vb(a,b,c,d,e,f){return d&&!d[u]&&(d=vb(d)),e&&!e[u]&&(e=vb(e,f)),hb(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||tb(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ub(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ub(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?K.call(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ub(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):I.apply(g,r)})}function wb(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=rb(function(a){return a===b},h,!0),l=rb(function(a){return K.call(b,a)>-1},h,!0),m=[function(a,c,d){return!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d))}];f>i;i++)if(c=d.relative[a[i].type])m=[rb(sb(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return vb(i>1&&sb(m),i>1&&qb(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&wb(a.slice(i,e)),f>e&&wb(a=a.slice(e)),f>e&&qb(a))}m.push(c)}return sb(m)}function xb(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=G.call(i));s=ub(s)}I.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&fb.uniqueSort(i)}return k&&(w=v,j=t),r};return c?hb(f):f}return h=fb.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wb(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xb(e,d)),f.selector=a}return f},i=fb.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(cb,db),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(cb,db),ab.test(j[0].type)&&ob(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qb(j),!a)return I.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,ab.test(a)&&ob(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ib(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ib(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||jb("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ib(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||jb("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ib(function(a){return null==a.getAttribute("disabled")})||jb(L,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fb}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=n.expr.match.needsContext,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^.[^:#\[\.,]*$/;function x(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(w.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return g.call(b,a)>=0!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(x(this,a||[],!1))},not:function(a){return this.pushStack(x(this,a||[],!0))},is:function(a){return!!x(this,"string"==typeof a&&u.test(a)?n(a):a||[],!1).length}});var y,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=n.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||y).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:l,!0)),v.test(c[1])&&n.isPlainObject(b))for(c in b)n.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}return d=l.getElementById(c[2]),d&&d.parentNode&&(this.length=1,this[0]=d),this.context=l,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof y.ready?y.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};A.prototype=n.fn,y=n(l);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};n.extend({dir:function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=u.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.unique(f):f)},index:function(a){return a?"string"==typeof a?g.call(n(a),this[0]):g.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.unique(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return n.dir(a,"parentNode")},parentsUntil:function(a,b,c){return n.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return n.dir(a,"nextSibling")},prevAll:function(a){return n.dir(a,"previousSibling")},nextUntil:function(a,b,c){return n.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return n.dir(a,"previousSibling",c)},siblings:function(a){return n.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return n.sibling(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(C[a]||n.unique(e),B.test(a)&&e.reverse()),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return n.each(a.match(E)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):n.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(b=a.memory&&l,c=!0,g=e||0,e=0,f=h.length,d=!0;h&&f>g;g++)if(h[g].apply(l[0],l[1])===!1&&a.stopOnFalse){b=!1;break}d=!1,h&&(i?i.length&&j(i.shift()):b?h=[]:k.disable())},k={add:function(){if(h){var c=h.length;!function g(b){n.each(b,function(b,c){var d=n.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&g(c)})}(arguments),d?f=h.length:b&&(e=c,j(b))}return this},remove:function(){return h&&n.each(arguments,function(a,b){var c;while((c=n.inArray(b,h,c))>-1)h.splice(c,1),d&&(f>=c&&f--,g>=c&&g--)}),this},has:function(a){return a?n.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],f=0,this},disable:function(){return h=i=b=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,b||k.disable(),this},locked:function(){return!i},fireWith:function(a,b){return!h||c&&!i||(b=b||[],b=[a,b.slice?b.slice():b],d?i.push(b):j(b)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!c}};return k},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&n.isFunction(a.promise)?e:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(H.resolveWith(l,[n]),n.fn.triggerHandler&&(n(l).triggerHandler("ready"),n(l).off("ready"))))}});function I(){l.removeEventListener("DOMContentLoaded",I,!1),a.removeEventListener("load",I,!1),n.ready()}n.ready.promise=function(b){return H||(H=n.Deferred(),"complete"===l.readyState?setTimeout(n.ready):(l.addEventListener("DOMContentLoaded",I,!1),a.addEventListener("load",I,!1))),H.promise(b)},n.ready.promise();var J=n.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)n.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f};n.acceptData=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function K(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=n.expando+Math.random()}K.uid=1,K.accepts=n.acceptData,K.prototype={key:function(a){if(!K.accepts(a))return 0;var b={},c=a[this.expando];if(!c){c=K.uid++;try{b[this.expando]={value:c},Object.defineProperties(a,b)}catch(d){b[this.expando]=c,n.extend(a,b)}}return this.cache[c]||(this.cache[c]={}),c},set:function(a,b,c){var d,e=this.key(a),f=this.cache[e];if("string"==typeof b)f[b]=c;else if(n.isEmptyObject(f))n.extend(this.cache[e],b);else for(d in b)f[d]=b[d];return f},get:function(a,b){var c=this.cache[this.key(a)];return void 0===b?c:c[b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=this.key(a),g=this.cache[f];if(void 0===b)this.cache[f]={};else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in g?d=[b,e]:(d=e,d=d in g?[d]:d.match(E)||[])),c=d.length;while(c--)delete g[d[c]]}},hasData:function(a){return!n.isEmptyObject(this.cache[a[this.expando]]||{})},discard:function(a){a[this.expando]&&delete this.cache[a[this.expando]]}};var L=new K,M=new K,N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(O,"-$1").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}M.set(a,b,c)}else c=void 0;return c}n.extend({hasData:function(a){return M.hasData(a)||L.hasData(a)},data:function(a,b,c){return M.access(a,b,c)},removeData:function(a,b){M.remove(a,b) +},_data:function(a,b,c){return L.access(a,b,c)},_removeData:function(a,b){L.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=M.get(f),1===f.nodeType&&!L.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d])));L.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){M.set(this,a)}):J(this,function(b){var c,d=n.camelCase(a);if(f&&void 0===b){if(c=M.get(f,a),void 0!==c)return c;if(c=M.get(f,d),void 0!==c)return c;if(c=P(f,d,void 0),void 0!==c)return c}else this.each(function(){var c=M.get(this,d);M.set(this,d,b),-1!==a.indexOf("-")&&void 0!==c&&M.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){M.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=L.get(a,b),c&&(!d||n.isArray(c)?d=L.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return L.get(a,c)||L.access(a,c,{empty:n.Callbacks("once memory").add(function(){L.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthx",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var U="undefined";k.focusinBubbles="onfocusin"in a;var V=/^key/,W=/^(?:mouse|pointer|contextmenu)|click/,X=/^(?:focusinfocus|focusoutblur)$/,Y=/^([^.]*)(?:\.(.+)|)$/;function Z(){return!0}function $(){return!1}function _(){try{return l.activeElement}catch(a){}}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return typeof n!==U&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(E)||[""],j=b.length;while(j--)h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g,!1)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.hasData(a)&&L.get(a);if(r&&(i=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&(delete r.handle,L.remove(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,m,o,p=[d||l],q=j.call(b,"type")?b.type:b,r=j.call(b,"namespace")?b.namespace.split("."):[];if(g=h=d=d||l,3!==d.nodeType&&8!==d.nodeType&&!X.test(q+n.event.triggered)&&(q.indexOf(".")>=0&&(r=q.split("."),q=r.shift(),r.sort()),k=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=r.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:n.makeArray(c,[b]),o=n.event.special[q]||{},e||!o.trigger||o.trigger.apply(d,c)!==!1)){if(!e&&!o.noBubble&&!n.isWindow(d)){for(i=o.delegateType||q,X.test(i+q)||(g=g.parentNode);g;g=g.parentNode)p.push(g),h=g;h===(d.ownerDocument||l)&&p.push(h.defaultView||h.parentWindow||a)}f=0;while((g=p[f++])&&!b.isPropagationStopped())b.type=f>1?i:o.bindType||q,m=(L.get(g,"events")||{})[b.type]&&L.get(g,"handle"),m&&m.apply(g,c),m=k&&g[k],m&&m.apply&&n.acceptData(g)&&(b.result=m.apply(g,c),b.result===!1&&b.preventDefault());return b.type=q,e||b.isDefaultPrevented()||o._default&&o._default.apply(p.pop(),c)!==!1||!n.acceptData(d)||k&&n.isFunction(d[q])&&!n.isWindow(d)&&(h=d[k],h&&(d[k]=null),n.event.triggered=q,d[q](),n.event.triggered=void 0,h&&(d[k]=h)),b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(L.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(g.namespace))&&(a.handleObj=g,a.data=g.data,e=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==e&&(a.result=e)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!==this;i=i.parentNode||this)if(i.disabled!==!0||"click"!==a.type){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>=0:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]*)\/>/gi,bb=/<([\w:]+)/,cb=/<|&#?\w+;/,db=/<(?:script|style|link)/i,eb=/checked\s*(?:[^=]|=\s*.checked.)/i,fb=/^$|\/(?:java|ecma)script/i,gb=/^true\/(.*)/,hb=/^\s*\s*$/g,ib={option:[1,""],thead:[1,"","
        "],col:[2,"","
        "],tr:[2,"","
        "],td:[3,"","
        "],_default:[0,"",""]};ib.optgroup=ib.option,ib.tbody=ib.tfoot=ib.colgroup=ib.caption=ib.thead,ib.th=ib.td;function jb(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function kb(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function lb(a){var b=gb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function mb(a,b){for(var c=0,d=a.length;d>c;c++)L.set(a[c],"globalEval",!b||L.get(b[c],"globalEval"))}function nb(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(L.hasData(a)&&(f=L.access(a),g=L.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)n.event.add(b,e,j[e][c])}M.hasData(a)&&(h=M.access(a),i=n.extend({},h),M.set(b,i))}}function ob(a,b){var c=a.getElementsByTagName?a.getElementsByTagName(b||"*"):a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function pb(a,b){var c=b.nodeName.toLowerCase();"input"===c&&T.test(a.type)?b.checked=a.checked:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}n.extend({clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=n.contains(a.ownerDocument,a);if(!(k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(g=ob(h),f=ob(a),d=0,e=f.length;e>d;d++)pb(f[d],g[d]);if(b)if(c)for(f=f||ob(a),g=g||ob(h),d=0,e=f.length;e>d;d++)nb(f[d],g[d]);else nb(a,h);return g=ob(h,"script"),g.length>0&&mb(g,!i&&ob(a,"script")),h},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k=b.createDocumentFragment(),l=[],m=0,o=a.length;o>m;m++)if(e=a[m],e||0===e)if("object"===n.type(e))n.merge(l,e.nodeType?[e]:e);else if(cb.test(e)){f=f||k.appendChild(b.createElement("div")),g=(bb.exec(e)||["",""])[1].toLowerCase(),h=ib[g]||ib._default,f.innerHTML=h[1]+e.replace(ab,"<$1>")+h[2],j=h[0];while(j--)f=f.lastChild;n.merge(l,f.childNodes),f=k.firstChild,f.textContent=""}else l.push(b.createTextNode(e));k.textContent="",m=0;while(e=l[m++])if((!d||-1===n.inArray(e,d))&&(i=n.contains(e.ownerDocument,e),f=ob(k.appendChild(e),"script"),i&&mb(f),c)){j=0;while(e=f[j++])fb.test(e.type||"")&&c.push(e)}return k},cleanData:function(a){for(var b,c,d,e,f=n.event.special,g=0;void 0!==(c=a[g]);g++){if(n.acceptData(c)&&(e=c[L.expando],e&&(b=L.cache[e]))){if(b.events)for(d in b.events)f[d]?n.event.remove(c,d):n.removeEvent(c,d,b.handle);L.cache[e]&&delete L.cache[e]}delete M.cache[c[M.expando]]}}}),n.fn.extend({text:function(a){return J(this,function(a){return void 0===a?n.text(this):this.empty().each(function(){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&(this.textContent=a)})},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=jb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=jb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?n.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||n.cleanData(ob(c)),c.parentNode&&(b&&n.contains(c.ownerDocument,c)&&mb(ob(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(n.cleanData(ob(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return J(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!db.test(a)&&!ib[(bb.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(ab,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(ob(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,n.cleanData(ob(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,m=this,o=l-1,p=a[0],q=n.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&eb.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(c=n.buildFragment(a,this[0].ownerDocument,!1,this),d=c.firstChild,1===c.childNodes.length&&(c=d),d)){for(f=n.map(ob(c,"script"),kb),g=f.length;l>j;j++)h=c,j!==o&&(h=n.clone(h,!0,!0),g&&n.merge(f,ob(h,"script"))),b.call(this[j],h,j);if(g)for(i=f[f.length-1].ownerDocument,n.map(f,lb),j=0;g>j;j++)h=f[j],fb.test(h.type||"")&&!L.access(h,"globalEval")&&n.contains(i,h)&&(h.src?n._evalUrl&&n._evalUrl(h.src):n.globalEval(h.textContent.replace(hb,"")))}return this}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=[],e=n(a),g=e.length-1,h=0;g>=h;h++)c=h===g?this:this.clone(!0),n(e[h])[b](c),f.apply(d,c.get());return this.pushStack(d)}});var qb,rb={};function sb(b,c){var d,e=n(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:n.css(e[0],"display");return e.detach(),f}function tb(a){var b=l,c=rb[a];return c||(c=sb(a,b),"none"!==c&&c||(qb=(qb||n("