LnkWcb Framework

LnkWcb  2.5.0

LnkWcb Framework > LnkWcb > bouton-jquery.js (source view)
Search:
 
Filters
/*!
 * 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>&lt;form&gt;</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>&lt;input&gt;</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>&lt;input&gt;</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>&lt;ul&gt;</code> list is created into the user error element.
		 * Each string is put into an <code>&lt;li&gt;</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&lt;Array&lt;String&gt;&gt;}
		 * @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&lt;Array&lt;String&gt;&gt;} 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&lt;Array&lt;String&gt;&gt;} 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&lt;Array&lt;String&gt;&gt;} 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&lt;Array&lt;String&gt;&gt;} 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'));
		});
	})();
})();

Copyright © 2010 Linkeo.com All rights reserved.