Connect the agent, go in ready state and manage a call
For this example we will provide to the user a very simple interface allowing an agent :
- manage the agent state between paused and ready,
- take an incoming call,
- hangup a call,
- qualify a call,
- call a manual number
The interface definition is :
<!-- Panel used to manage general agent state --> <div class="Panel"> <table style="width:100%"> <tr> <td><button id="BtReady" disabled onclick="OnReadyClick(event)">READY</button></td> <td><button id="BtPause" disabled onclick="OnPauseClick(event)">PAUSE</button></td> <td style="width:100%;text-align:right"><span id="GeneralState">OFF</span></td> </tr> </table> </div> <!-- Panel used to manage telephony context state --> <div class="Panel"> <table style="width:100%"> <tr> <td><button id="BtChooseCampaign" disabled onclick="OnChooseCampaignClick(event)">CHOOSE CAMPAIGN</button></td> <td><button id="BtMakeCall" disabled onclick="OnMakeCallClick(event)">MAKE CALL</button></td> <td><button id="BtHangup" disabled onclick="OnHangupClick(event)">HANGUP</button></td> <td><button id="BtStatus" disabled onclick="OnStatusClick(event)">CALL STATUS</button></td> <td><button id="BtNextCall" disabled onclick="OnNextCallClick(event)">NEXT CALL</button></td> <td style="width:100%;text-align:right"<>span id="TelephonyState">OFF</span></td> </tr> </table> </div> <!-- Panel used to display call informations --> <div class="Panel"> <table style="width:100%"> <tr> <td><span id="CallPhoneNumberTitle">INDICE : </span></td> <td style="width:100%;text-align:right"><span id="CallIndiceValue"></span></td> </tr> <tr> <td><span id="CallDnisTitle">PHONE : </span></td> <td style="width:100%;text-align:right"><span id="CallPhoneNumberValue"></span></td> </tr> <tr> <td><span id="CallCampaignTitle">CAMPAIGN : </span></td> <td style="width:100%;text-align:right"><span id="CallCampaignValue"></span></td> </tr> </table> </div>
The First step is to instantiate the AgentLink object :
//Define the path where the Flash will be loaded agentlink_object_path = "http://www.yourdomain.com/flash/"; //Define the default protocol mode to WEBSOCKET agentlink_default_protocol = "WEBSOCKET"; //Define the websocket mode to unsecure websock_protocol = "ws"; //Define the websocket port websock_port = 443; //ID of the div where the flash componant will be placed var container = document.getElementById("YourDivId"); //Define the Log function. Use the browser console to display them function Log(logger, level, text) { console.log(level + " - " + logger + " - " + text); } //Instantiate the Flash component var agentlink = new AgentLinkClass(container);
When the object has been created, define the events you need to listen and the javascript function to call with the function attachEvent (here are attached only the events used for this example) :
agentlink.attachEvent("OnConnect", Connect); agentlink.attachEvent("OnUserIdentification", UserIdentification); agentlink.attachEvent("OnAgentStateChange", AgentStateChange); agentlink.attachEvent("OnSessionOpen", OnSessionOpen); agentlink.attachEvent("OnSessionClose", OnSessionClose); agentlink.attachEvent("OnSessionStateChange", OnSessionStateChange); agentlink.attachEvent("OnSessionUpdate", OnSessionUpdate);
Set properties needed before connecting to CTIProxy :
agentlink.AgentProxy = "127.0.0.1"; agentlink.Port = 9000; agentlink.CustomerId = 1; agentlink.Locale = "en-US"; agentlink.AdminUrl = "http://127.0.0.1/hermes_net_v5/admin/"; agentlink.OnMediaWebService = "http://127.0.0.1/hermes_net_v5/PlateformPublication/OnMedia/Web_Service/MailService.asmx?OMS=127.0.0.1:5002"; agentlink.CrmUrl = "http://127.0.0.1/hermes_net_v5/CRM/";
Start connecting to the CTIProxy by calling the Connect function :
agentlink.Connect();
When the "OnConnect" event is fired, login with the correct agent id and extension :
function Connect() {
//Connect the agent with ID 1000, with extension id 1
agentlink.Login(1000, "password", "1");
}
The OnConnect event is not fired if the connection failed.
When the "OnUserIdentification" event is fired, verify that the agent is correctly logged in. Else, log an error. After a call to the method Logout, this event will also be called to notify of the logout state.
function UserIdentification(userId, userName, station, defaultContext) {
if (!agentlink.LoggedIn) {
Log("INTEGRATOR", "ERROR", "Agent has logged out or the logged in operation did not succeed");
}
// else : agent is now logged in
}
We have now to manage the interface elements : states indicator and buttons.
For this tasks, the better way is to use the event OnAgentStateChange that is fired each times the state is changing and each times the agent possible action list is changing.
In this example we will use this event to perform three differents tasks :
- Display the current agent states and the current states on the telephony context
- Manage the availability of the buttons of the command bar
- Manage in an automatic way the start of the telephony context (the default management way is to let the agent starting and stopping the context)
function AgentStateChange(contextType, stateCode, stateString) { contextType = parseInt(contextType, 10); /** 1. DISPLAY STATES **/ if (contextType == ContextTypes.Global) { var generalStateSpan = document.getElementById("GeneralState"); generalStateSpan.innerHTML = stateCode + " - " + stateString; } else if (contextType == ContextTypes.Telephony) { var telephonyStateSpan = document.getElementById("TelephonyState"); telephonyStateSpan.innerHTML = stateCode + " - " + stateString; } else { //Other contexts types are not managed on this example } /** 2. MANAGE COMMAND BAR **/ //Test on global level if ready is allowed var btReady = document.getElementById("BtReady"); btReady.disabled = !agentlink.ActionIsReady(AgentActionAllowed.Ready); //Test on global level if pause is allowed var btPause = document.getElementById("BtPause"); btPause.disabled = !agentlink.ActionIsReady(AgentActionAllowed.Pause); //Test on telephony level if manual call is allowed var btCall = document.getElementById("BtMakeCall"); btCall.disabled = !agentlink.Telephony.ActionIsReady(ContextActionAllowed.ManualCall); //Test on telephony level if choosing the outbound campaign is allowed var btChooseCamp = document.getElementById("BtChooseCampaign"); btChooseCamp.disabled = !agentlink.Telephony.ActionIsReady(ContextActionAllowed.ChangeAffectationOutbound); //For Hangup and set call status button : first test if a call exist or not var btHangup = document.getElementById("BtHangup"); var btStatus = document.getElementById("BtStatus"); var btCloseSession = document.getElementById("BtNextCall"); var session = agentlink.Telephony.GetSession(); if (typeof(session) == "undefined") { //There is no active call : disable the session buttons //Else : the state will be managed on event "OnSessionStateChange" handler btHangup.disabled = true; btStatus.disabled = true; btCloseSession.disabled = true; } /** 3. MANAGE AUTO-STARTING TELEPHONY CONTEXTS If Global state is waiting, and telephony context is stopped - start it We do this task only on the telephony target event because this event will be fired for each differents contexts where the agent can work **/ if (contextType == ContextTypes.Telephony) { ManageAutoStartTelephony(); } } function ManageAutoStartTelephony() { //Manage auto start if the telephony context is stopped //or if the detail state is waiting on outbound when some inbound campaigns exists var is_waiting_inbound = ((agentlink.Telephony.ContextState == ContextStates.Waiting) && ((agentlink.Telephony.ContextStateDetail == ContextWaitingDetails.WaitingInbound) || (agentlink.Telephony.ContextStateDetail == ContextWaitingDetails.WaitingBlended))); var auto_start_inbound = ((agentlink.Telephony.ContextState == ContextStates.Stopped) || (!is_waiting_inbound)); if ((agentlink.AgentState == AgentGlobalStates.Waiting) && (auto_start_inbound)) { //First verify that the agent has rights for starting the inbound context : //this rights is managed on the Hermes.Net Admin side if (agentlink.Telephony.ActionIsReady(ContextActionAllowed.EnableContextInbound)) { //If agent can choose queues : select all queues, //else, only start the context. //Nb : starting context does not start any queue when agent is allowed to choose queue if (agentlink.Telephony.ActionIsReady(ContextActionAllowed.ChangeAffectationInbound)) { //Enable all the queues of the context var queues = new Array(); //Get all telephnony queues for (var i = 0; i < agentlink.Telephony.Queues.Count; i++) { var cqueue = agentlink.Telephony.Queues.Item(i); if (!cqueue.IsTelephonyOutbound) { queues.push(cqueue.QueueId); } } agentlink.StartQueue(ContextTypes.Telephony, queues); } else { //Start the context without choosing queues agentlink.StartContext(ContextTypes.Telephony, TelephonyModes.Inbound); } } else { Log("INTEGRATOR", "INFO", "Agent has no rights for starting the telephony inbound context"); } //The outbound context will be started only when the user wants to choose the campaign } }To manage the availability of a button, we use the method ActionIsReady on the Agent level, the method ActionIsReady on the Telephony level (depending of the button to test).
To start a list of inbound queues, we use the function StartQueue and to start all the inbound context, we use the function StartContext.
Now that the Global and telephony context state are managed, we will focus on managing the state when
and active call is on.
For this we are listening four events :
- OnSessionOpen : fired when a new call is coming. We will use it to display the session informations.
- OnSessionClose : fired when the active call is closed. We will use it to remove the session informations.
- OnSessionStateChange : fired when the state of the session change. It's the event to use to listen for the session states and the allowed action for the session. We will use this event to manage the availability of the buttons linked to a session : Hangup, Set call status and Next call.
- OnSessionUpdate : when the type of session change. On telephony, this event is fired when a search mode session is updated on real call session. As we don't manage the search mode on this example, we will not manage this event.
function OnSessionOpen(contextType, sessionId) { if (contextType == ContextTypes.Telephony) { //Get the current telephony session. For telephony, //there is only one session at a time var session = agentlink.Telephony.GetSession(); /** display call informations **/ document.getElementById("CallIndiceValue").innerHTML = session.Indice; document.getElementById("CallPhoneNumberValue").innerHTML = session.ContactNumber; document.getElementById("CallCampaignValue").innerHTML = session.CampaignId; } else { Log("INTEGRATOR", "INFO", "Ignore sessions for others context than telephony"); } }
function OnSessionClose(contextType, sessionId) {
if (contextType == ContextTypes.Telephony) {
document.getElementById("CallIndiceValue").innerHTML = "";
document.getElementById("CallPhoneNumberValue").innerHTML = "";
document.getElementById("CallCampaignValue").innerHTML = "";
//The telephony state will be updated by the OnAgentStateChange event.
}
}
function OnSessionStateChange(contextType, sessionId) { if (contextType == ContextTypes.Telephony) { var session = agentlink.Telephony.GetSession(); /** First : display the session state */ var telephonyStateSpan = document.getElementById("TelephonyState"); telephonyStateSpan.innerHTML = session.SessionState + " - " + session.SessionStateLabel; /** Second : manage the session's buttons states : when the session is opening, the event "OnAgentStateChange" is not fired again **/ var btHangup = document.getElementById("BtHangup"); btHangup.disabled = !session.ActionIsReady(TelephonySessionActionAllowed.Hangup); var btStatus = document.getElementById("BtStatus"); btStatus.disabled = !session.ActionIsReady(TelephonySessionActionAllowed.SetCallStatus); var btCloseSession = document.getElementById("BtNextCall"); btCloseSession.disabled = !session.ActionIsReady(TelephonySessionActionAllowed.CloseSession); } }
function OnSessionUpdate(contextType, sessionId) { Log("INTEGRATOR", "INFO", "Ignore OnSessionUpdate in this example"); }
When user click on "Ready" button : we simply go out of pause. For this, we will use the RequestReady function.
function OnReadyClick(e) { agentlink.RequestReady(); }
When user click on "Pause" button, we create a div and add a select with all pause codes on it.
To list the codes, we use the property PauseCodes.
This list should be listed every times the agent need it to be sure to display last codes added in the Admin.
function OnPauseClick(e) { /*** CREATE THE DOM. SEE THE FULL HTML EXAMPLE TO SEE THE DOM CODE ***/ /**** FILL THE SELECT WITH ALL PAUSES CODES ***/ var select = document.createElement("select"); select.style.width = "190px"; select.id = "SELECT_PAUSE"; for (var i = 0; i < agentlink.PauseCodes.Count; i++) { var pcode = agentlink.PauseCodes.Item(i); var opt = new Option(pcode.Description, pcode.Code); select.add(opt); } /** END OF DOM CREATION. SEE THE FULL HTML EXAMPLE ***/ }
When the user choose the Pause code, we send it to the CTIProxy with the function RequestPause.
Before sending the pause code to the proxy, we verify that the agent can go in pause : the agent state could change during the time the agent choose the code. Or, you can avoid this by calling the function RequestTempNotReady when opening the pause code list interface.
function SelectPause(e) { // First we verify that the agent can always go in pause if (agentlink.ActionIsReady(AgentActionAllowed.Pause)) { //Send the pause code to proxy var pauseCode = parseInt(document.getElementById("SELECT_PAUSE").value, 10); agentlink.RequestPause(pauseCode, 0); //Close the pause div ClosePauseDiv(e); } }
When user click on "CHOOSE CAMPAIGN", we create a div and add a select with all outbound campaigns on it.
To list the campaigns, we use the property Campaigns and
we test on each Campaign the type.
We also verify if the campaign is
open before adding it to the list.
Please note that the list contains only the active campaigns.
function OnChooseCampaignClick(e) { /*** CREATE THE DOM. SEE THE FULL HTML EXAMPLE TO SEE THE DOM CODE ***/ /**** FILL THE SELECT WITH ALL CAMPAIGNS ID ***/ var select = document.createElement("select"); select.style.width = "190px"; select.id = "SELECT_CAMPAIGNS"; for (var i = 0; i < agentlink.Campaigns.Count; i++) { var campaign = agentlink.Campaigns.Item(i); if (campaign.Type == CampaignTypes.Outbound) { var opt = new Option(campaign.Description, campaign.Queue); //If the campaign is closed (out of service or holiday), //add it, but set the option disabled if ((campaign.State == CampaignStates.Close) || (campaign.EnabledBy == QueueEnabledBy.Disabled) || (campaign.EnabledBy == QueueEnabledBy.DisabledBySupervisor)) { opt.disabled = true; } select.add(opt); } } /** END OF DOM CREATION. SEE THE FULL HTML EXAMPLE ***/ }
When the user choose the outbound campaign, we verify that this is allowed by the current agent state : the agent state could change during the time the agent choose the campaign. Before sending the outbound queue, we call the StopQueue function to stop the current queue started (this operation is required). After this, we call the StartQueue function with the selected queue.
function SelectCampaign(e) { /** First we verify that the agent can always choose the campaign : the agent state can change while the agent was choosing a campaign **/ if (agentlink.Telephony.ActionIsReady(ContextActionAllowed.ChangeAffectationOutbound)) { var select = document.getElementById("SELECT_CAMPAIGNS"); var campQueue = parseInt(select.value, 10); //First stop all other queues var arrayQueuesToStop = new Array(); for (var i = 0; i < select.options.length; i++) { if (select.options[i].value != campQueue) { arrayQueuesToStop.push(select.options[i].value); } } agentlink.StopQueue(ContextTypes.Telephony, arrayQueuesToStop); //Second start the selected queue var arrayQueues = new Array(); arrayQueues.push(campQueue); agentlink.StartQueue(ContextTypes.Telephony, arrayQueues); //Close the pause div CloseCampaignDiv(e); } }
When user click on "MAKE CALL", we create a div, and add :
- a text input to let the user enter the phone number
- the list of manual campaing to use for the call
function OnMakeCallClick(e) { /*** CREATE THE DOM. SEE THE FULL HTML EXAMPLE TO SEE THE DOM CODE ***/ var tr = table.insertRow(-1); var td = tr.insertCell(-1); td.innerHTML = "Phone : "; td.colSpan = 2; td.style.textAlign = "center"; var input = document.createElement("input"); input.type = "text"; input.style.width = "120px"; input.id = "INPUT_CALL"; td.appendChild(input); var tr = table.insertRow(-1); var td = tr.insertCell(-1); td.innerHTML = "Campaign : "; td.colSpan = 2; td.style.textAlign = "center"; /**** FILL THE SELECT WITH ALL MANUAL CAMPAIGNS ***/ var select = document.createElement("select"); select.style.width = "120px"; select.id = "SELECT_CALL"; for (var i = 0; i < agentlink.ManualCampaigns.Count; i++) { var camp = agentlink.ManualCampaigns.Item(i); var opt = new Option(camp.Description, camp.CampaignId); select.add(opt); } td.appendChild(select); /** END OF DOM CREATION. SEE THE FULL HTML EXAMPLE ***/ }
When the user validate the phonenumber to dial, we verify that the action is always allowed by the current agent state : the agent state could change during the time the agent was typing the phonenumber.
To dial, we use the ManualCall function and for this
example we use only External phone number type.
function MakeCall(e) {
/** First we verify that the agent can always make manual call :
the agent state can change while the agent was typing the number **/
if (agentlink.Telephony.ActionIsReady(ContextActionAllowed.ManualCall)) {
var phoneNumber = document.getElementById("INPUT_CALL").value;
var campaignId = document.getElementById("SELECT_CALL").value;
if ((phoneNumber != "") && (campaignId != null)) {
agentlink.Telephony.ManualCall(campaignId, phoneNumber, PhoneNumberTypes.External, "");
} else {
Log("INTEGRATOR", "INFO", "Enter the phone number to dial");
}
}
CloseCallDiv(null);
}
When the user click on the Hangup button, we call the Hangup function on the active session call.
function OnHangupClick(e) { var session = agentlink.Telephony.GetSession(); session.Hangup(); }
The last operation to manage on this example is to close the session to allow the agent to be ready for another one.
If a qualification group is set on the campaign of the call, the qualification has to be send to the CTIProxy before closing the call.
So on the NEXT CALL button, we have to verify if the call is qualified or not (with the function MustQualify). If the call has to be qualified, we first open the status div in the same way as when the user click on the button CALL STATUS. If the call is already qualified, we close the session with the function CloseSession.
Nb : the call status can be send during the call and multiple times. If the call status is send more than one times, only the last is used.
In this example, we use a global variable to know wich action to do after sending the call status (with the function SetCallStatus) :
- Closing the call session, if the status panel is open for a click on "NEXT CALL"
- Nothing, if the status panel is open for a click on "CALL STATUS"
var setReadyOnStatus = false; function OnNextCallClick(e) { var session = agentlink.Telephony.GetSession(); /** The CloseSession is allowed at the end of the call. It is the integrator responsability to check if the call status has to be send. For it, we use the function MustQualify. Nb : the call status can be send during the call and multiple times. If the call status is send more than one times, only the last is used. */ if (session.MustQualify()) { setReadyOnStatus = true; DisplayCallStatusPopup(e); } else { //The call status has already be sended, send the CloseSession command session.CloseSession(); } } function OnStatusClick(e) { setReadyOnStatus = false; DisplayCallStatusPopup(e); }
To list the call status available to the agent, we use the QualificationCodes function with the session QualificationGroup property as argument.
The interface we provide contains two selects : one for the status code, and the other one for the status code detail (note that all status code does not have a detail). When the user will choose a status code, we will populate the second list with all details for the selected code.
function DisplayCallStatusPopup(e) { var session = agentlink.Telephony.GetSession(); /*** CREATE THE DOM. SEE THE FULL HTML EXAMPLE TO SEE THE DOM CODE ***/ //Add a select with all status code var select = document.createElement("select"); select.style.width = "120px"; select.id = "SELECT_STATUS_CODE"; select.onchange = PopulateSubStatus; //Add a first option used to ask the user to choose a code var opt = new Option("Status codes" , "-1"); select.add(opt); //Add all status available for the current call var codes = agentlink.QualificationCodes(session.QualificationGroup); if (codes == null) { Log("INTEGRATOR", "WARN", "No qualifications in group " + session.QualificationGroup); CloseStatusDiv(); return; } for (var i = 0; i < codes.Count; i++) { var current = codes.Item(i); opt = new Option(current.Code + " -" + current.Description, current.Code); select.add(opt); } td.appendChild(select); var select = document.createElement("select"); select.style.width = "120px"; select.id = "SELECT_SUB_STATUS"; select.disabled = true; td.appendChild(select); /** END OF DOM CREATION. SEE THE FULL HTML EXAMPLE ***/ } function PopulateSubStatus(e) { //Get the current status selected var mainSelect = document.getElementById("SELECT_STATUS_CODE"); var statusCode = parseInt(mainSelect.value, 10); //First remove all options from the subSelect var subSelect = document.getElementById("SELECT_SUB_STATUS"); for (var i = subSelect.options.length - 1; i >= 0; i--) { subSelect.remove(i); } //Populate the list only if a status code is selected if (statusCode != -1) { var session = agentlink.Telephony.GetSession(); var codes = agentlink.QualificationCodes(session.QualificationGroup); for (var i = 0; i < codes.Count; i++) { var current = codes.Item(i); //Get the item selected if (current.Code == statusCode) { for (var j = 0; j < current.Details.Count; j++) { var currentDetail = current.Details.Item(j); var opt = new Option(currentDetail.Code + " -" + currentDetail.Description, currentDetail.Code); subSelect.add(opt); subSelect.disabled = false; } if (current.Details.Count == 0) { subSelect.disabled = true; } } } } else { subSelect.disabled = true; } }
When the user validate the call status panel, we send the status selected to the CTIProxy with
the function SetCallStatus.
If the status
is callback or general callback (code 94 or 95) we simply program the callback in +1 hour (to simplify the example).
After we send the status, we test if the panel has been opened for closing the session. If yes, we close the session by calling the CloseSession function.
function SendCallStatus(e) { //Get the current status selected var mainSelect = document.getElementById("SELECT_STATUS_CODE"); var statusCode = parseInt(mainSelect.value, 10); if (statusCode == -1) { Log("INTEGRATOR", "WARN", "No status is selected"); return; } //Get the substatus selected var subSelect = document.getElementById("SELECT_SUB_STATUS"); var statusDetail = 0; //If there is no substatus, keep "0" as default value if (!subSelect.disabled) { statusDetail = parseInt(subSelect.value, 10); } //Manage personal callback and callbacks var strDateRappel = ""; var strTelRappel = ""; var strValidityRappel = ""; var session = agentlink.Telephony.GetSession(); if (statusCode == 94 || statusCode == 95) { //For this example, as we do not provide any interface //to permit the agent to select the callback date, we simply //program the callback in one hour var dte = new Date(); dte.setTime(dte.getTime() + 60000); strDateRappel = dte.getFullYear() + LZ(dte.getMonth() + 1) + LZ(dte.getDate()) + LZ(dte.getHours()) + LZ(dte.getMinutes()); //use the current contact phone number to program the callback strTelRappel = session.ContactNumber; //set the validity as 1 hour strValidityRappel = 720; } //Send the callstatus to Proxy session.SetCallStatus(statusCode, statusDetail, strDateRappel, strTelRappel, strValidityRappel, ""); //If the status has been asked by the Next call button, //send the CloseSession just after the status if (setReadyOnStatus) { session.CloseSession(); } //Close the div CloseStatusDiv(); }
When the session is closing, the agent telephony state is "waiting for a new call", or "pause", if the agent choose to go in pause during the call.