/*!
* LNK WCB Framework v2.5
* (c) 2010 Linkeo.com
* Use and redistribution are permitted. No warranty.
*
* Advanced Bouton
*/
// LnkWcb.BoutonJquery
// - tel/date basic validation
// - full internationalization (frontal msg translated)
// - call status display (all frontal status), fully customizable
// - relevant error display (translated frontal msg), fully customizable
// - customizable fallbacks (incl. back-office cfg msg)
var LnkLog, LnkWcb; // if already defined, log won't get clobbered just defining it
LnkLog = LnkLog || {};
LnkLog.log = LnkLog.log || function () {}; // if not defined, use a safe default value instead
LnkWcb = LnkWcb || {};
/**
* @module LnkWcb
*/
/**
* Advanced Bouton
* @class BoutonJquery
* @extends LnkWcb.Bouton
* @namespace LnkWcb
* @constructor
* @param cfg {Object} the initial configuration settings
* @public
*/
(function () {
var L = LnkWcb,
U = L.util,
B = L.Bouton, BJQ, BJQPROTO,
INTL = L.intl,
$ = L.jQuery,
defaultSettings = U.putAll(B.prototype.getDefaultSettings(), {
/**
* A CSS selector that locates the form container. This container element might not necessarily
* be a <code><form></code> element. It is just a root element in which other elements
* will be looked for.
* @config formSel
* @type String
* @default #LnkWcbForm
* @public
*/
formSel: '#LnkWcbForm',
// the 4 following settings are relative selectors (relative to the form root)
/**
* The relative CSS selector that locates the websurfer status element.
* Relative to the form root as specified by <a href="#config_formSel"><code>formSel</code></a>.
* @config userStatusElemRelSel
* @type String
* @default .user-status
* @public
*/
userStatusElemRelSel: '.user-status',
/**
* The relative CSS selector that locates the websurfer errors element.
* Relative to the form root as specified by <a href="#config_formSel"><code>formSel</code></a>.
* @config userErrorsElemRelSel
* @type String
* @default .user-errors
* @public
*/
userErrorsElemRelSel: '.user-errors',
/**
* The relative CSS selector that locates the websurfer input element for his/her telephone number.
* Relative to the form root as specified by <a href="#config_formSel"><code>formSel</code></a>.
* Might not necessarily be an <code><input></code> element. It can be a container that will
* be highlighted (see <a href="#config_errorClass"><code>errorClass</code></a> config) on error.
* @config calleeInputRelSel
* @type String
* @default input[name="callee"]
* @public
*/
calleeInputRelSel: 'input[name="callee"]',
/**
* The relative CSS selector that locates the websurfer input element for the date at which the delayed call is to be planned.
* Relative to the form root as specified by <a href="#config_formSel"><code>formSel</code></a>.
* Might not necessarily be an <code><input></code> element. It can be a container that will
* be highlighted (see <a href="#config_errorClass"><code>errorClass</code></a> config) on error.
* @config dateInputRelSel
* @type String
* @default .LnkWcbDateDiff
* @public
*/
dateInputRelSel: '.LnkWcbDateDiff',
/**
* The CSS class that will be added to input elements when they are in error state.
* @config errorClass
* @type String
* @default error
* @protected
*/
errorClass: 'error',
forceFallbacks: true // override
}),
renouvelez = "Merci de renouveler votre demande.",
differez = "Choisissez la date et l'heure à laquelle nous pouvons vous rappeler.",
autreMoyen = "Merci de nous joindre par un autre moyen.",
defaultRsc = { // French locale by default
err: {
blankTel: "Précisez le numéro de téléphone.",
invalidTel: "Ce numéro de téléphone [{tel}] n'est pas correct.",
blankDate: "La date [{date}] est incorrecte. Elle doit respecter le format 'JJ/MM/AAAA hh:mm'."
},
msg: {
helloChannelState: "Bonjour, le canal est {channelStatus}",
callStatus: "Votre téléphone : {userPhoneStatus}, votre correspondant : {agentPhoneStatus}.",
delayedCallAccepted: "Votre demande a bien été prise en compte.",
okThanks: "Nous vous remercions de votre appel.",
koPleaseCallLater: "{koReason}",
fallback: "Tous nos conseillers sont en ligne. Merci de rappeler ultérieurement."
},
channelStatus: {
INACTIF: "désactivé. "+autreMoyen,
HORS_LIMITES: "provisoirement bloqué. "+autreMoyen,
SATURE: "malheureusement saturé. "+differez,
OUVERT: "disponible. Saisissez votre numéro de téléphone et nous vous appelons immédiatement.",
FERME: "fermé. "+differez,
FERIE: "exceptionnellement fermé aujourd'hui. "+differez
},
userPhoneStatus: {
INCONNU: "sera bientôt appelé", // dialing not yet started
APPEL_EN_COURS: "sonne", // Processing, Dialing, Ringing
MESSAGE_BIENVENUE: "nous vous souhaitons la bienvenue", // Welcome message (only for user, not for agent)
MUSIQUE_ATTENTE: "merci de patienter", // MusicOnHold (only for user in direct mode, and only for agent in reverse mode)
COMMUNICATION_EN_COURS: "est en ligne", // Up and not bridged
COMMUNICATION_ETABLIE: "est connecté", // Up and bridged
TOUCHE_APPUYEE: "a la touche [{uDigit}] appuyée"
},
agentPhoneStatus: {
INCONNU: "sera bientôt appelé", // dialing not yet started
APPEL_EN_COURS: "est appelé", // Processing, Dialing, Ringing
MUSIQUE_ATTENTE: "est en attente", // MusicOnHold (only for user in direct mode, and only for agent in reverse mode)
COMMUNICATION_EN_COURS: "est en ligne", // Up and not bridged
COMMUNICATION_ETABLIE: "est connecté", // Up and bridged
TOUCHE_APPUYEE: "prend l'appel"
},
koReason: {
user: {
RACCROCHE_INTERNAUTE_AVANT_AGENT: "Vous avez raccroché avant que nous ne puissions vous mettre en relation. "+renouvelez,
INVERSE_INABOUTI_INTERNAUTE: "Votre téléphone ne répond pas. "+renouvelez, // délai expiré sur 2e (et dernier) appel
INABOUTI_INTERNAUTE: "Votre téléphone ne répond pas. "+renouvelez, // délai expiré sur 1er appel
MACHINE: "Votre téléphone est sur répondeur. "+renouvelez // ne peut survenir que si la détection des répondeurs internautes est activée
},
CAUSE_INCONNUE: "Une erreur technique est survenue. "+renouvelez
/* // non useful message codes because forceFallbacks=true -> end handler never deals with fallbacks then
,
INABOUTI_AGENT: "Tous nos conseillers sont en ligne. "+differez, // la ligne ne répond pas. délai expiré sur dernier appel (numéro agent ou débordement autre numéro)
INVERSE_INABOUTI_AGENT: "Tous nos conseillers sont en ligne. "+differez, // la ligne ne répon pas. Délai expiré sur 1er appel
NON_CONFIRMATION_APPEL_AGENT: "Tous nos conseillers sont en ligne. "+differez, // aucun conseiller ne prend l'appel
RACCROCHE_AGENT_AVANT_INTERNAUTE: "Tous nos conseillers sont en ligne. "+differez, // le conseiller décroche puis raccroche
*/
},
errors: {
userErrorTelephone: "Le numéro de téléphone saisi est incorrect.",
userErrorDate: "La date saisie est incorrecte.",
userErrorDuplicateCall: "Un appel est déjà en cours pour ce numéro de téléphone.",
techError: "Une erreur technique est survenue (code {errCode}). Nous faisons tout notre possible pour rétablir le service."
/* // non useful message codes because forceFallbacks=true -> error handler never deals with fallbacks then
,
debErrorBlocked: "Ce bouton n'accepte plus aucune demande de rappel. "+autreMoyen,
debErrorClosedHour: "Nos bureaux sont actuellement fermés. "+differez,
debErrorClosedDay: "Nos bureaux sont exceptionnellement fermés ce jour. "+differez,
debErrorOverwhelmed: "Toutes nos lignes sont occupées. "+differez
*/
},
fallbacks: {
CAUSE_DEBORDE: "Toutes nos conseillers sont en ligne. "+differez,
CAUSE_FERME: "Nos bureaux sont actuellement fermés. "+differez,
CAUSE_FERIE: "Nos bureaux sont exceptionnellement fermés ce jour. "+differez,
CAUSE_BLOQUE: "Ce bouton n'accepte plus aucune demande de rappel. "+autreMoyen, // canal désactivé
CAUSE_SATURE: "Toutes nos lignes sont occupées. "+differez // ne peut survenir que si la limite d'appel simultanés est paramétrée
}
};
BJQ = L.BoutonJquery = function (cfg) {
var that = this;
B.call(that);
U.putAll(U.putAll(that.cfg, defaultSettings), cfg); // force our default settings over LnkWcb.Bouton ones
/**
* The current resource used.
* @property rsc
* @type Object
* @public
*/
that.rsc = INTL.getRsc('bouton-jquery', INTL.getLang()) || defaultRsc;
INTL.onLangChanged(function (lang) {
that.rsc = INTL.getRsc('bouton-jquery', lang) || defaultRsc;
});
that.fieldsInError = {};
BJQ.onCreate.fire(that);
};
BJQPROTO = BJQ.prototype = U.object(B.prototype);
BJQPROTO.constructor = BJQ;
BJQ.superclass = B.prototype;
/**
* Class event. Fires when an <code>LnkWcb.BoutonJquery</code> instance is just created.
* @event onCreate
* @param bouton {Object} the new instance
* @static
* @protected
*/
BJQ.onCreate = new L.Event(BJQ);
// public class methods
U.putAll(BJQPROTO, {
/**
* Display a text message into the user status element.
* The one specified with the <a href="#config_userStatusElemRelSel"><code>userStatusElemRelSel</code></a> config.
* @method showUserStatus
* @param msg {String} the text message to display
* @protected
* @see userStatusElemRelSel and formSel config
*/
showUserStatus: function (msg) {
$(this.cfg.formSel).find(this.cfg.userStatusElemRelSel).text(msg);
},
/**
* Empty the user error element.
* @method resetUserErrors
* @protected
* @see userErrorsElemRelSel, formSel and errorClass config
*/
resetUserErrors: function () {
var cfg = this.cfg, fields = this.fieldsInError,
form, field;
form = $(cfg.formSel);
form.find(cfg.userErrorsElemRelSel).empty();
for (field in fields) {
if (fields[field] !== undefined) {
form.find(field).removeClass(cfg.errorClass);
delete fields[field];
}
}
},
/**
* Display errors. They are strings, grouped in arrays, indexed by their field CSS (relative) selectors.
* <p>
* A <code><ul></code> list is created into the user error element.
* Each string is put into an <code><li></code> item of this list.
* <p>
* The names in the <code>errors</code> arguments actually are CSS selectors
* (relative to the form root specified by the <a href="#config_formSel"><code>formSel</code></a>)
* to find some element on which the error CSS class (see the
* <a href="#config_errorClass"><code>errorClass</code></a> config) will be added.
*
* @method showUserErrors
* @param errors {Object<Array<String>>}
* @protected
*/
showUserErrors: function (errors) {
var cfg = this.cfg,
form, errorsElem, errorsMkup, field, i;
form = $(cfg.formSel);
errorsElem = form.find(cfg.userErrorsElemRelSel);
if (!errorsElem.length) {
return;
}
errorsMkup = [];
for (field in errors) {
for (i = 0; i < errors[field].length; ++i) {
errorsMkup.push({ li: errors[field][i] });
}
this.fieldsInError[field] = true;
form.find(field).addClass(cfg.errorClass);
}
if (errorsMkup.length) {
errorsElem.append(U.createMarkupNode({ ul: [ {}, errorsMkup ] }));
}
},
// class events handlers
/*
onSendCallAction: function (args, attrs) {
var planedDate, date;
planedDate = attrs.planedDate;
if (typeof planedDate === 'object' && planedDate !== null) { // fixup date from { date: .., hours: .., minutes: .. } object to "DD/MM/YYYY hh:mm" string
date = planedDate.date;
date.setHours(planedDate.hours);
date.setMinutes(planedDate.minutes);
attrs.planedDate = U.formatDateTime(date);
}
},
*/
/**
* The default listener action for
* the <a href="LnkWcb.Bouton.html#event_onChannelState"><code>onChannelState</code></a> event.
* <p>
* Override this method to change the default behavior.
* <p>
* Calls the <a href="#method_showUserStatus"><code>showUserStatus</code></a> method
* with the formated <code>msg.helloChannelState</code> message from the resource.
* @method onChannelStateAction
* @param etat {Object} the server response
* @param etatOuverture {String} a digest channel status (recommended)
* @protected
*/
onChannelStateAction: function (etat, etatOuverture) {
var attrs = {
channelStatus: this.rsc.channelStatus[etatOuverture]
};
this.showUserStatus(INTL.fmt(this.rsc.msg.helloChannelState, attrs));
},
/**
* The default listener action for
* the <a href="LnkWcb.Bouton.html#event_onStatus"><code>onStatus</code></a> event.
* <p>
* Override this method to change the default behavior.
* <p>
* Calls the <a href="#method_showUserStatus"><code>showUserStatus</code></a> method
* with the formated <code>msg.callStatus</code> message from the resource.
* @method onStatusAction
* @param status {Object} the response object
* @param params {Object<Array<String>>} the request parameters
* @protected
*/
onStatusAction: function (status, params) {
var rsc = this.rsc,
attrs = {
userPhoneStatus: rsc.userPhoneStatus[status.uStatus],
agentPhoneStatus: rsc.agentPhoneStatus[status.aStatus]
},
msg = INTL.fmt(rsc.msg.callStatus, attrs, status);
this.showUserStatus(msg);
},
/**
* The default listener action for
* the <a href="LnkWcb.Bouton.html#event_onEnded"><code>onEnded</code></a> event.
* <p>
* Override this method to change the default behavior.
* <p>
* Calls the <a href="#method_showUserStatus"><code>showUserStatus</code></a> method
* with the formated message from the resource.
* <ul>
* <li><code>msg.delayedCallAccepted</code> in case of an accepted delayed call</li>
* <li><code>msg.okThanks</code> in case the call ends with an OK final status</li>
* <li>The formated <code>msg.koPleaseCallLater</code> message in case of a KO ending</li>
* </ul>
* If the KO ending was caused by the websurfer, then
* the <a href="#method_showUserErrors"><code>showUserErrors</code></a> method is called instead.
* @method onEndedAction
* @param status {Object} the response object
* @param params {Object<Array<String>>} the request parameters
* @protected
*/
onEndedAction: function (status, params) {
var rsc = this.rsc,
attrs = {}, msg, koReason;
if (String(params.t) === 'delay') { // errors directly go to the error handler, not here (because serverSideManagedFallbacks=false)
msg = rsc.msg.delayedCallAccepted;
}
else if (status.status === 'OK') {
msg = rsc.msg.okThanks;
}
else { // status.status === 'KO' // KO ends that are fallbacks directly go to the fallback handler, not here (because forceFallbacks=true)
koReason = rsc.koReason.user[status.cause];
if (koReason) { // KO calls due to user error are presented as user errors
attrs.koReason = koReason;
this.resetUserErrors();
this.showUserErrors({ msg: [ INTL.fmt(rsc.msg.koPleaseCallLater, attrs, status) ] });
this.showUserStatus("");
return;
}
attrs.koReason = rsc.koReason[status.cause];
msg = INTL.fmt(rsc.msg.koPleaseCallLater, attrs, status);
}
this.showUserStatus(msg);
},
/**
* The default listener action for
* the <a href="LnkWcb.Bouton.html#event_onFallback"><code>onFallback</code></a> event.
* <p>
* Override this method to change the default behavior.
* <p>
* Calls the <a href="#method_showUserStatus"><code>showUserStatus</code></a> method
* with the formated message from the resource.
* @method onFallbackAction
* @param status {Object} the response object
* @param params {Object<Array<String>>} the request parameters
* @protected
*/
onFallbackAction: function (status, params) {
// Note: Client fallbacks and server fallbacks always set the 'status.debordementCause' attribute.
// Except server-side fallbacks when ended="1". But we use client-side fallbacks here.
this.showUserStatus(this.rsc.fallbacks[status.debordementCause]);
},
/**
* The default listener action for
* the <a href="LnkWcb.Bouton.html#event_onErrorDigest"><code>onErrorDigest</code></a> event.
* <p>
* Override this method to change the default behavior.
* <p>
* Calls the <a href="#method_showUserStatus"><code>showUserStatus</code></a> method
* with the formated message from the resource.
* When the KO ending was caused by the websurfer, then
* the <a href="#method_showUserErrors"><code>showUserErrors</code></a> method
* is called instead.
* @method onErrorDigestAction
* @param msgCode {String} a message code for the error
* @param errCode {String} an error code for the error
* @param status {Object} the response object
* @param params {Object<Array<String>>} the request parameters
* @protected
*/
onErrorDigestAction: function (msgCode, errCode, status, params) {
var message = INTL.fmt(this.rsc.errors[msgCode], { errCode: errCode });
if (/^U/.test(errCode)) {
this.resetUserErrors();
this.showUserErrors({ msg: [ message ] }); // present to user only his/her errors
}
else {
this.showUserStatus(message); // technical errors presented as status
}
}
});
// custom instance validation
/**
* The default <a href="#config_validator">validator</a> for this <code>LnkWcb.BoutonJquery</code> class.
* <p>
* If some error is detected, the
* <a href="#method_showUserErrors"><code>showUserErrors</code></a> method is called.
* @method validator
* @param telephone {String} the callee telephone number
* @param date {Date} an optional date at which the delayed call should occur
* @return {Boolean} <code>true</code> if inputs are valid, <code>false</code> otherwise
* @private
*/
defaultSettings.validator = function (tel, date) {
var BLANK = /^\s+$/,
TEL = /^(?:\+[1-9]|0)\d{9,}$/,
DATE = /^[0-3]\d\/[01]\d\/\d{4} [0-2]\d:[0-5]\d$/,
cfg = this.cfg,
errors = {};
this.resetUserErrors();
if (!tel || BLANK.test(tel)) {
U.accu(errors, cfg.calleeInputRelSel, this.rsc.err.blankTel);
}
else if (!TEL.test(tel)) {
U.accu(errors, cfg.calleeInputRelSel, INTL.fmt(this.rsc.err.invalidTel, { tel: tel }));
}
if (date !== undefined && !BLANK.test(date) && !DATE.test(date)) {
U.accu(errors, cfg.dateInputRelSel, INTL.fmt(this.rsc.err.blankDate, { date: date }));
}
this.showUserErrors(errors);
return U.countDefined(errors) === 0;
};
// customize instance behavior with class events handlers
(function () {
function PROXY(method) {
return function () {
this[method].apply(this, arguments);
};
}
BJQ.onCreate.register(function (that) {
// that.onSendCall(PROXY('onSendCallAction'));
that.onChannelState(PROXY('onChannelStateAction'));
that.onStatus(PROXY('onStatusAction'));
//that.onDigit(PROXY('onDigitAction'));
that.onEnded(PROXY('onEndedAction'));
that.onFallback(PROXY('onFallbackAction'));
that.onErrorDigest(PROXY('onErrorDigestAction'));
});
})();
})();