1 /** 2 * Cisco Finesse - JavaScript Library 3 * Version 12.0(1) 4 * Cisco Systems, Inc. 5 * http://www.cisco.com/ 6 * 7 * Portions created or assigned to Cisco Systems, Inc. are 8 * Copyright (c) 2019 Cisco Systems, Inc. or its affiliated entities. All Rights Reserved. 9 */ 10 /** 11 * This JavaScript library is made available to Cisco partners and customers as 12 * a convenience to help minimize the cost of Cisco Finesse customizations. 13 * This library can be used in Cisco Finesse deployments. Cisco does not 14 * permit the use of this library in customer deployments that do not include 15 * Cisco Finesse. Support for the JavaScript library is provided on a 16 * "best effort" basis via CDN. Like any custom deployment, it is the 17 * responsibility of the partner and/or customer to ensure that the 18 * customization works correctly and this includes ensuring that the Cisco 19 * Finesse JavaScript is properly integrated into 3rd party applications. 20 * Cisco reserves the right to make changes to the JavaScript code and 21 * corresponding API as part of the normal Cisco Finesse release cycle. The 22 * implication of this is that new versions of the JavaScript might be 23 * incompatible with applications built on older Finesse integrations. That 24 * said, it is Cisco's intention to ensure JavaScript compatibility across 25 * versions as much as possible and Cisco will make every effort to clearly 26 * document any differences in the JavaScript across versions in the event 27 * that a backwards compatibility impacting change is made. 28 */ 29 (function (root, factory) { 30 if (typeof define === 'function' && define.amd) { 31 define('finesse', [], factory); 32 } else { 33 root.finesse = factory(); 34 } 35 }(this, function () { 36 /** 37 * @license almond 0.2.9 Copyright (c) 2011-2014, The Dojo Foundation All Rights Reserved. 38 * Available via the MIT or new BSD license. 39 * see: http://github.com/jrburke/almond for details 40 */ 41 //Going sloppy to avoid 'use strict' string cost, but strict practices should 42 //be followed. 43 /*jslint sloppy: true */ 44 /*global setTimeout: false */ 45 46 var requirejs, require, define; 47 (function (undef) { 48 var main, req, makeMap, handlers, 49 defined = {}, 50 waiting = {}, 51 config = {}, 52 defining = {}, 53 hasOwn = Object.prototype.hasOwnProperty, 54 aps = [].slice, 55 jsSuffixRegExp = /\.js$/; 56 57 function hasProp(obj, prop) { 58 return hasOwn.call(obj, prop); 59 } 60 61 /** 62 * Given a relative module name, like ./something, normalize it to 63 * a real name that can be mapped to a path. 64 * @param {String} name the relative name 65 * @param {String} baseName a real name that the name arg is relative 66 * to. 67 * @returns {String} normalized name 68 */ 69 function normalize(name, baseName) { 70 var nameParts, nameSegment, mapValue, foundMap, lastIndex, 71 foundI, foundStarMap, starI, i, j, part, 72 baseParts = baseName && baseName.split("/"), 73 map = config.map, 74 starMap = (map && map['*']) || {}; 75 76 //Adjust any relative paths. 77 if (name && name.charAt(0) === ".") { 78 //If have a base name, try to normalize against it, 79 //otherwise, assume it is a top-level require that will 80 //be relative to baseUrl in the end. 81 if (baseName) { 82 //Convert baseName to array, and lop off the last part, 83 //so that . matches that "directory" and not name of the baseName's 84 //module. For instance, baseName of "one/two/three", maps to 85 //"one/two/three.js", but we want the directory, "one/two" for 86 //this normalization. 87 baseParts = baseParts.slice(0, baseParts.length - 1); 88 name = name.split('/'); 89 lastIndex = name.length - 1; 90 91 // Node .js allowance: 92 if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) { 93 name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, ''); 94 } 95 96 name = baseParts.concat(name); 97 98 //start trimDots 99 for (i = 0; i < name.length; i += 1) { 100 part = name[i]; 101 if (part === ".") { 102 name.splice(i, 1); 103 i -= 1; 104 } else if (part === "..") { 105 if (i === 1 && (name[2] === '..' || name[0] === '..')) { 106 //End of the line. Keep at least one non-dot 107 //path segment at the front so it can be mapped 108 //correctly to disk. Otherwise, there is likely 109 //no path mapping for a path starting with '..'. 110 //This can still fail, but catches the most reasonable 111 //uses of .. 112 break; 113 } else if (i > 0) { 114 name.splice(i - 1, 2); 115 i -= 2; 116 } 117 } 118 } 119 //end trimDots 120 121 name = name.join("/"); 122 } else if (name.indexOf('./') === 0) { 123 // No baseName, so this is ID is resolved relative 124 // to baseUrl, pull off the leading dot. 125 name = name.substring(2); 126 } 127 } 128 129 //Apply map config if available. 130 if ((baseParts || starMap) && map) { 131 nameParts = name.split('/'); 132 133 for (i = nameParts.length; i > 0; i -= 1) { 134 nameSegment = nameParts.slice(0, i).join("/"); 135 136 if (baseParts) { 137 //Find the longest baseName segment match in the config. 138 //So, do joins on the biggest to smallest lengths of baseParts. 139 for (j = baseParts.length; j > 0; j -= 1) { 140 mapValue = map[baseParts.slice(0, j).join('/')]; 141 142 //baseName segment has config, find if it has one for 143 //this name. 144 if (mapValue) { 145 mapValue = mapValue[nameSegment]; 146 if (mapValue) { 147 //Match, update name to the new value. 148 foundMap = mapValue; 149 foundI = i; 150 break; 151 } 152 } 153 } 154 } 155 156 if (foundMap) { 157 break; 158 } 159 160 //Check for a star map match, but just hold on to it, 161 //if there is a shorter segment match later in a matching 162 //config, then favor over this star map. 163 if (!foundStarMap && starMap && starMap[nameSegment]) { 164 foundStarMap = starMap[nameSegment]; 165 starI = i; 166 } 167 } 168 169 if (!foundMap && foundStarMap) { 170 foundMap = foundStarMap; 171 foundI = starI; 172 } 173 174 if (foundMap) { 175 nameParts.splice(0, foundI, foundMap); 176 name = nameParts.join('/'); 177 } 178 } 179 180 return name; 181 } 182 183 function makeRequire(relName, forceSync) { 184 return function () { 185 //A version of a require function that passes a moduleName 186 //value for items that may need to 187 //look up paths relative to the moduleName 188 return req.apply(undef, aps.call(arguments, 0).concat([relName, forceSync])); 189 }; 190 } 191 192 function makeNormalize(relName) { 193 return function (name) { 194 return normalize(name, relName); 195 }; 196 } 197 198 function makeLoad(depName) { 199 return function (value) { 200 defined[depName] = value; 201 }; 202 } 203 204 function callDep(name) { 205 if (hasProp(waiting, name)) { 206 var args = waiting[name]; 207 delete waiting[name]; 208 defining[name] = true; 209 main.apply(undef, args); 210 } 211 212 if (!hasProp(defined, name) && !hasProp(defining, name)) { 213 throw new Error('No ' + name); 214 } 215 return defined[name]; 216 } 217 218 //Turns a plugin!resource to [plugin, resource] 219 //with the plugin being undefined if the name 220 //did not have a plugin prefix. 221 function splitPrefix(name) { 222 var prefix, 223 index = name ? name.indexOf('!') : -1; 224 if (index > -1) { 225 prefix = name.substring(0, index); 226 name = name.substring(index + 1, name.length); 227 } 228 return [prefix, name]; 229 } 230 231 /** 232 * Makes a name map, normalizing the name, and using a plugin 233 * for normalization if necessary. Grabs a ref to plugin 234 * too, as an optimization. 235 */ 236 makeMap = function (name, relName) { 237 var plugin, 238 parts = splitPrefix(name), 239 prefix = parts[0]; 240 241 name = parts[1]; 242 243 if (prefix) { 244 prefix = normalize(prefix, relName); 245 plugin = callDep(prefix); 246 } 247 248 //Normalize according 249 if (prefix) { 250 if (plugin && plugin.normalize) { 251 name = plugin.normalize(name, makeNormalize(relName)); 252 } else { 253 name = normalize(name, relName); 254 } 255 } else { 256 name = normalize(name, relName); 257 parts = splitPrefix(name); 258 prefix = parts[0]; 259 name = parts[1]; 260 if (prefix) { 261 plugin = callDep(prefix); 262 } 263 } 264 265 //Using ridiculous property names for space reasons 266 return { 267 f: prefix ? prefix + '!' + name : name, //fullName 268 n: name, 269 pr: prefix, 270 p: plugin 271 }; 272 }; 273 274 function makeConfig(name) { 275 return function () { 276 return (config && config.config && config.config[name]) || {}; 277 }; 278 } 279 280 handlers = { 281 require: function (name) { 282 return makeRequire(name); 283 }, 284 exports: function (name) { 285 var e = defined[name]; 286 if (typeof e !== 'undefined') { 287 return e; 288 } else { 289 return (defined[name] = {}); 290 } 291 }, 292 module: function (name) { 293 return { 294 id: name, 295 uri: '', 296 exports: defined[name], 297 config: makeConfig(name) 298 }; 299 } 300 }; 301 302 main = function (name, deps, callback, relName) { 303 var cjsModule, depName, ret, map, i, 304 args = [], 305 callbackType = typeof callback, 306 usingExports; 307 308 //Use name if no relName 309 relName = relName || name; 310 311 //Call the callback to define the module, if necessary. 312 if (callbackType === 'undefined' || callbackType === 'function') { 313 //Pull out the defined dependencies and pass the ordered 314 //values to the callback. 315 //Default to [require, exports, module] if no deps 316 deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps; 317 for (i = 0; i < deps.length; i += 1) { 318 map = makeMap(deps[i], relName); 319 depName = map.f; 320 321 //Fast path CommonJS standard dependencies. 322 if (depName === "require") { 323 args[i] = handlers.require(name); 324 } else if (depName === "exports") { 325 //CommonJS module spec 1.1 326 args[i] = handlers.exports(name); 327 usingExports = true; 328 } else if (depName === "module") { 329 //CommonJS module spec 1.1 330 cjsModule = args[i] = handlers.module(name); 331 } else if (hasProp(defined, depName) || 332 hasProp(waiting, depName) || 333 hasProp(defining, depName)) { 334 args[i] = callDep(depName); 335 } else if (map.p) { 336 map.p.load(map.n, makeRequire(relName, true), makeLoad(depName), {}); 337 args[i] = defined[depName]; 338 } else { 339 throw new Error(name + ' missing ' + depName); 340 } 341 } 342 343 ret = callback ? callback.apply(defined[name], args) : undefined; 344 345 if (name) { 346 //If setting exports via "module" is in play, 347 //favor that over return value and exports. After that, 348 //favor a non-undefined return value over exports use. 349 if (cjsModule && cjsModule.exports !== undef && 350 cjsModule.exports !== defined[name]) { 351 defined[name] = cjsModule.exports; 352 } else if (ret !== undef || !usingExports) { 353 //Use the return value from the function. 354 defined[name] = ret; 355 } 356 } 357 } else if (name) { 358 //May just be an object definition for the module. Only 359 //worry about defining if have a module name. 360 defined[name] = callback; 361 } 362 }; 363 364 requirejs = require = req = function (deps, callback, relName, forceSync, alt) { 365 if (typeof deps === "string") { 366 if (handlers[deps]) { 367 //callback in this case is really relName 368 return handlers[deps](callback); 369 } 370 //Just return the module wanted. In this scenario, the 371 //deps arg is the module name, and second arg (if passed) 372 //is just the relName. 373 //Normalize module name, if it contains . or .. 374 return callDep(makeMap(deps, callback).f); 375 } else if (!deps.splice) { 376 //deps is a config object, not an array. 377 config = deps; 378 if (config.deps) { 379 req(config.deps, config.callback); 380 } 381 if (!callback) { 382 return; 383 } 384 385 if (callback.splice) { 386 //callback is an array, which means it is a dependency list. 387 //Adjust args if there are dependencies 388 deps = callback; 389 callback = relName; 390 relName = null; 391 } else { 392 deps = undef; 393 } 394 } 395 396 //Support require(['a']) 397 callback = callback || function () {}; 398 399 //If relName is a function, it is an errback handler, 400 //so remove it. 401 if (typeof relName === 'function') { 402 relName = forceSync; 403 forceSync = alt; 404 } 405 406 //Simulate async callback; 407 if (forceSync) { 408 main(undef, deps, callback, relName); 409 } else { 410 //Using a non-zero value because of concern for what old browsers 411 //do, and latest browsers "upgrade" to 4 if lower value is used: 412 //http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html#dom-windowtimers-settimeout: 413 //If want a value immediately, use require('id') instead -- something 414 //that works in almond on the global level, but not guaranteed and 415 //unlikely to work in other AMD implementations. 416 setTimeout(function () { 417 main(undef, deps, callback, relName); 418 }, 4); 419 } 420 421 return req; 422 }; 423 424 /** 425 * Just drops the config on the floor, but returns req in case 426 * the config return value is used. 427 */ 428 req.config = function (cfg) { 429 return req(cfg); 430 }; 431 432 /** 433 * Expose module registry for debugging and tooling 434 */ 435 requirejs._defined = defined; 436 437 define = function (name, deps, callback) { 438 439 //This module may not have dependencies 440 if (!deps.splice) { 441 //deps is not an array, so probably means 442 //an object literal or factory function for 443 //the value. Adjust args. 444 callback = deps; 445 deps = []; 446 } 447 448 if (!hasProp(defined, name) && !hasProp(waiting, name)) { 449 waiting[name] = [name, deps, callback]; 450 } 451 }; 452 453 define.amd = { 454 jQuery: true 455 }; 456 }()); 457 define("../thirdparty/almond", function(){}); 458 459 /* Simple JavaScript Inheritance 460 * By John Resig http://ejohn.org/ 461 * MIT Licensed. 462 */ 463 // Inspired by base2 and Prototype 464 define('../thirdparty/Class',[], function () { 465 var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/; 466 // The base Class implementation (does nothing) 467 /** @private */ 468 Class = function(){}; 469 470 // Create a new Class that inherits from this class 471 /** @private */ 472 Class.extend = function(prop) { 473 var _super = this.prototype; 474 475 // Instantiate a base class (but only create the instance, 476 // don't run the init constructor) 477 initializing = true; 478 var prototype = new this(); 479 initializing = false; 480 481 // Copy the properties over onto the new prototype 482 for (var name in prop) { 483 // Check if we're overwriting an existing function 484 prototype[name] = typeof prop[name] == "function" && 485 typeof _super[name] == "function" && fnTest.test(prop[name]) ? 486 (function(name, fn){ 487 return function() { 488 var tmp = this._super; 489 490 // Add a new ._super() method that is the same method 491 // but on the super-class 492 this._super = _super[name]; 493 494 // The method only need to be bound temporarily, so we 495 // remove it when we're done executing 496 var ret = fn.apply(this, arguments); 497 this._super = tmp; 498 499 return ret; 500 }; 501 })(name, prop[name]) : 502 prop[name]; 503 } 504 505 // The dummy class constructor 506 /** @private */ 507 function Class() { 508 // All construction is actually done in the init method 509 if ( !initializing && this.init ) 510 this.init.apply(this, arguments); 511 } 512 513 // Populate our constructed prototype object 514 Class.prototype = prototype; 515 516 // Enforce the constructor to be what we expect 517 Class.prototype.constructor = Class; 518 519 // And make this class extendable 520 Class.extend = arguments.callee; 521 522 return Class; 523 }; 524 return Class; 525 }); 526 527 /** 528 * JavaScript base object that all finesse objects should inherit 529 * from because it encapsulates and provides the common functionality. 530 * 531 * Note: This javascript class requires the "inhert.js" to be included 532 * (which simplifies the class inheritance). 533 * 534 * 535 * @requires finesse.utilities.Logger 536 */ 537 538 /** The following comment is to prevent jslint errors about 539 * using variables before they are defined. 540 */ 541 /*global Class */ 542 define('FinesseBase', ["../thirdparty/Class"], function (Class) { 543 var FinesseBase = Class.extend({ 544 init: function () { 545 } 546 }); 547 548 window.finesse = window.finesse || {}; 549 window.finesse.FinesseBase = FinesseBase; 550 551 return FinesseBase; 552 }); 553 554 /** 555 * A collection of conversion utilities. 556 * Last modified 07-06-2011, Cisco Systems 557 * 558 */ 559 /** @private */ 560 define('utilities/../../thirdparty/util/converter',[], function () { 561 /** 562 * @class 563 * Contains a collection of utility functions. 564 * @private 565 */ 566 var Converter = (function () { 567 return { 568 /* This work is licensed under Creative Commons GNU LGPL License. 569 570 License: http://creativecommons.org/licenses/LGPL/2.1/ 571 Version: 0.9 572 Author: Stefan Goessner/2006 573 Web: http://goessner.net/ 574 575 2013-09-16 Modified to remove use of XmlNode.innerHTML in the innerXml function by Cisco Systems, Inc. 576 */ 577 xml2json: function (xml, tab) { 578 var X = { 579 toObj: function (xml) { 580 var o = {}; 581 if (xml.nodeType === 1) { 582 // element node .. 583 if (xml.attributes.length) 584 // element with attributes .. 585 for (var i = 0; i < xml.attributes.length; i++) 586 o["@" + xml.attributes[i].nodeName] = (xml.attributes[i].nodeValue || "").toString(); 587 if (xml.firstChild) { 588 // element has child nodes .. 589 var textChild = 0, 590 cdataChild = 0, 591 hasElementChild = false; 592 for (var n = xml.firstChild; n; n = n.nextSibling) { 593 if (n.nodeType == 1) hasElementChild = true; 594 else if (n.nodeType == 3 && n.nodeValue.match(/[^ \f\n\r\t\v]/)) textChild++; 595 // non-whitespace text 596 else if (n.nodeType == 4) cdataChild++; 597 // cdata section node 598 } 599 if (hasElementChild) { 600 if (textChild < 2 && cdataChild < 2) { 601 // structured element with evtl. a single text or/and cdata node .. 602 X.removeWhite(xml); 603 for (var n = xml.firstChild; n; n = n.nextSibling) { 604 if (n.nodeType == 3) 605 // text node 606 o["#text"] = X.escape(n.nodeValue); 607 else if (n.nodeType == 4) 608 // cdata node 609 o["#cdata"] = X.escape(n.nodeValue); 610 else if (o[n.nodeName]) { 611 // multiple occurence of element .. 612 if (o[n.nodeName] instanceof Array) 613 o[n.nodeName][o[n.nodeName].length] = X.toObj(n); 614 else 615 o[n.nodeName] = [o[n.nodeName], X.toObj(n)]; 616 } 617 else 618 // first occurence of element.. 619 o[n.nodeName] = X.toObj(n); 620 } 621 } 622 else { 623 // mixed content 624 if (!xml.attributes.length) 625 o = X.escape(X.innerXml(xml)); 626 else 627 o["#text"] = X.escape(X.innerXml(xml)); 628 } 629 } 630 else if (textChild) { 631 // pure text 632 if (!xml.attributes.length) 633 o = X.escape(X.innerXml(xml)); 634 else 635 o["#text"] = X.escape(X.innerXml(xml)); 636 } 637 else if (cdataChild) { 638 // cdata 639 if (cdataChild > 1) 640 o = X.escape(X.innerXml(xml)); 641 else 642 for (var n = xml.firstChild; n; n = n.nextSibling) 643 o["#cdata"] = X.escape(n.nodeValue); 644 } 645 } 646 if (!xml.attributes.length && !xml.firstChild) o = null; 647 } 648 else if (xml.nodeType == 9) { 649 // document.node 650 o = X.toObj(xml.documentElement); 651 } 652 else 653 throw ("unhandled node type: " + xml.nodeType); 654 return o; 655 }, 656 toJson: function(o, name, ind) { 657 var json = name ? ("\"" + name + "\"") : ""; 658 if (o instanceof Array) { 659 for (var i = 0, n = o.length; i < n; i++) 660 o[i] = X.toJson(o[i], "", ind + "\t"); 661 json += (name ? ":[": "[") + (o.length > 1 ? ("\n" + ind + "\t" + o.join(",\n" + ind + "\t") + "\n" + ind) : o.join("")) + "]"; 662 } 663 else if (o == null) 664 json += (name && ":") + "null"; 665 else if (typeof(o) == "object") { 666 var arr = []; 667 for (var m in o) 668 arr[arr.length] = X.toJson(o[m], m, ind + "\t"); 669 json += (name ? ":{": "{") + (arr.length > 1 ? ("\n" + ind + "\t" + arr.join(",\n" + ind + "\t") + "\n" + ind) : arr.join("")) + "}"; 670 } 671 else if (typeof(o) == "string") 672 json += (name && ":") + "\"" + o.toString() + "\""; 673 else 674 json += (name && ":") + o.toString(); 675 return json; 676 }, 677 innerXml: function(node) { 678 var s = ""; 679 var asXml = function(n) { 680 var s = ""; 681 if (n.nodeType == 1) { 682 s += "<" + n.nodeName; 683 for (var i = 0; i < n.attributes.length; i++) 684 s += " " + n.attributes[i].nodeName + "=\"" + (n.attributes[i].nodeValue || "").toString() + "\""; 685 if (n.firstChild) { 686 s += ">"; 687 for (var c = n.firstChild; c; c = c.nextSibling) 688 s += asXml(c); 689 s += "</" + n.nodeName + ">"; 690 } 691 else 692 s += "/>"; 693 } 694 else if (n.nodeType == 3) 695 s += n.nodeValue; 696 else if (n.nodeType == 4) 697 s += "<![CDATA[" + n.nodeValue + "]]>"; 698 return s; 699 }; 700 for (var c = node.firstChild; c; c = c.nextSibling) 701 s += asXml(c); 702 return s; 703 }, 704 escape: function(txt) { 705 return txt.replace(/[\\]/g, "\\\\") 706 .replace(/[\"]/g, '\\"') 707 .replace(/[\n]/g, '\\n') 708 .replace(/[\r]/g, '\\r'); 709 }, 710 removeWhite: function(e) { 711 e.normalize(); 712 for (var n = e.firstChild; n;) { 713 if (n.nodeType == 3) { 714 // text node 715 if (!n.nodeValue.match(/[^ \f\n\r\t\v]/)) { 716 // pure whitespace text node 717 var nxt = n.nextSibling; 718 e.removeChild(n); 719 n = nxt; 720 } 721 else 722 n = n.nextSibling; 723 } 724 else if (n.nodeType == 1) { 725 // element node 726 X.removeWhite(n); 727 n = n.nextSibling; 728 } 729 else 730 // any other node 731 n = n.nextSibling; 732 } 733 return e; 734 } 735 }; 736 if (xml.nodeType == 9) 737 // document node 738 xml = xml.documentElement; 739 var json = X.toJson(X.toObj(X.removeWhite(xml)), xml.nodeName, "\t"); 740 return "{\n" + tab + (tab ? json.replace(/\t/g, tab) : json.replace(/\t|\n/g, "")) + "\n}"; 741 }, 742 743 /* This work is licensed under Creative Commons GNU LGPL License. 744 745 License: http://creativecommons.org/licenses/LGPL/2.1/ 746 Version: 0.9 747 Author: Stefan Goessner/2006 748 Web: http://goessner.net/ 749 */ 750 json2xml: function(o, tab) { 751 var toXml = function(v, name, ind) { 752 var xml = ""; 753 if (v instanceof Array) { 754 for (var i = 0, n = v.length; i < n; i++) 755 xml += ind + toXml(v[i], name, ind + "\t") + "\n"; 756 } 757 else if (typeof(v) == "object") { 758 var hasChild = false; 759 xml += ind + "<" + name; 760 for (var m in v) { 761 if (m.charAt(0) == "@") 762 xml += " " + m.substr(1) + "=\"" + v[m].toString() + "\""; 763 else 764 hasChild = true; 765 } 766 xml += hasChild ? ">": "/>"; 767 if (hasChild) { 768 for (var m in v) { 769 if (m == "#text") 770 xml += v[m]; 771 else if (m == "#cdata") 772 xml += "<![CDATA[" + v[m] + "]]>"; 773 else if (m.charAt(0) != "@") 774 xml += toXml(v[m], m, ind + "\t"); 775 } 776 xml += (xml.charAt(xml.length - 1) == "\n" ? ind: "") + "</" + name + ">"; 777 } 778 } 779 else { 780 xml += ind + "<" + name + ">" + v.toString() + "</" + name + ">"; 781 } 782 return xml; 783 }, 784 xml = ""; 785 for (var m in o) 786 xml += toXml(o[m], m, ""); 787 return tab ? xml.replace(/\t/g, tab) : xml.replace(/\t|\n/g, ""); 788 } 789 }; 790 })(); 791 792 window.finesse = window.finesse || {}; 793 window.finesse.Converter = Converter; 794 795 return Converter; 796 }); 797 798 /** 799 * SaxParser.js: provides a simple SAX parser 800 * 801 * NONVALIDATING - this will not validate whether you have valid XML or not. It will simply report what it finds. 802 * Only supports elements, attributes, and text. No comments, cdata, processing instructions, etc. 803 */ 804 805 /** 806 * @requires 807 * @ignore 808 */ 809 // Add SaxParser to the finesse.utilities namespace 810 define('utilities/SaxParser',[], function () { 811 var SaxParser = { 812 parse: function(xml, callback) { 813 // Event callbacks 814 /** @private */ 815 var triggerEvent = function (type, data) { 816 callback.call(null, type, data); 817 }, 818 /** @private */ 819 triggerStartElement = function (name) { 820 triggerEvent("StartElement", name); 821 }, 822 /** @private */ 823 triggerEndElement = function (name) { 824 triggerEvent("EndElement", name); 825 }, 826 /** @private */ 827 triggerAttribute = function (name, value) { 828 triggerEvent("Attribute", { "name": name, "value": value }); 829 }, 830 /** @private */ 831 triggerText = function (text) { 832 triggerEvent("Text", text); 833 }, 834 835 // Parsing 836 cursor = 0, 837 xmlLength = xml.length, 838 whitespaceRegex = /^[ \t\r\n]*$/, 839 /** @private */ 840 isWhitespace = function (text) { 841 return whitespaceRegex.test(text); 842 }, 843 /** @private */ 844 moveToNonWhitespace = function () { 845 while (isWhitespace(xml.charAt(cursor))) { 846 cursor += 1; 847 } 848 }, 849 /** @private */ 850 parseAttribute = function () { 851 var nameBuffer = [], 852 valueBuffer = [], 853 valueIsQuoted = false, 854 cursorChar = ""; 855 856 nameBuffer.push(xml.charAt(cursor)); 857 858 // Get the name 859 cursor += 1; 860 while (cursor < xmlLength) { 861 cursorChar = xml.charAt(cursor); 862 if (isWhitespace(cursorChar) || cursorChar === "=") { 863 // Move on to gathering value 864 break; 865 } 866 else { 867 nameBuffer.push(cursorChar); 868 } 869 cursor += 1; 870 } 871 872 // Skip the equals sign and any whitespace 873 moveToNonWhitespace(); 874 if (cursorChar === "=") { 875 cursor += 1; 876 } else { 877 throw new Error("Did not find = following attribute name at " + cursor); 878 } 879 moveToNonWhitespace(); 880 881 // Get the value 882 valueIsQuoted = cursor !== xmlLength - 1 ? xml.charAt(cursor) === "\"": false; 883 if (valueIsQuoted) { 884 cursor += 1; 885 while (cursor < xmlLength) { 886 cursorChar = xml.charAt(cursor); 887 if (cursorChar === "\"") { 888 // Found the closing quote, so end value 889 triggerAttribute(nameBuffer.join(""), valueBuffer.join("")); 890 break; 891 } 892 else { 893 valueBuffer.push(cursorChar); 894 } 895 cursor += 1; 896 } 897 } 898 else { 899 throw new Error("Found unquoted attribute value at " + cursor); 900 } 901 }, 902 /** @private */ 903 parseEndElement = function () { 904 var elementNameBuffer = [], 905 cursorChar = ""; 906 cursor += 2; 907 while (cursor < xmlLength) { 908 cursorChar = xml.charAt(cursor); 909 if (cursorChar === ">") { 910 triggerEndElement(elementNameBuffer.join("")); 911 break; 912 } 913 else { 914 elementNameBuffer.push(cursorChar); 915 } 916 cursor += 1; 917 } 918 }, 919 /** @private */ 920 parseReference = function() { 921 var type, 922 TYPE_DEC_CHAR_REF = 1, 923 TYPE_HEX_CHAR_REF = 2, 924 TYPE_ENTITY_REF = 3, 925 buffer = ""; 926 cursor += 1; 927 // Determine the type of reference. 928 if (xml.charAt(cursor) === "#") { 929 cursor += 1; 930 if (xml.charAt(cursor) === "x") { 931 type = TYPE_HEX_CHAR_REF; 932 cursor += 1; 933 } else { 934 type = TYPE_DEC_CHAR_REF; 935 } 936 } else { 937 type = TYPE_ENTITY_REF; 938 } 939 // Read the reference into a buffer. 940 while (xml.charAt(cursor) !== ";") { 941 buffer += xml.charAt(cursor); 942 cursor += 1; 943 if (cursor >= xmlLength) { 944 throw new Error("Unterminated XML reference: " + buffer); 945 } 946 } 947 // Convert the reference to the appropriate character. 948 switch (type) { 949 case TYPE_DEC_CHAR_REF: 950 return String.fromCharCode(parseInt(buffer, 10)); 951 case TYPE_HEX_CHAR_REF: 952 return String.fromCharCode(parseInt(buffer, 16)); 953 case TYPE_ENTITY_REF: 954 switch (buffer) { 955 case "amp": 956 return "&"; 957 case "lt": 958 return "<"; 959 case "gt": 960 return ">"; 961 case "apos": 962 return "'"; 963 case "quot": 964 return "\""; 965 default: 966 throw new Error("Invalid XML entity reference: " + buffer); 967 } 968 // break; (currently unreachable) 969 } 970 }, 971 /** @private */ 972 parseElement = function () { 973 var elementNameBuffer = [], 974 textBuffer = [], 975 cursorChar = "", 976 whitespace = false; 977 978 // Get element name 979 cursor += 1; 980 while (cursor < xmlLength) { 981 cursorChar = xml.charAt(cursor); 982 whitespace = isWhitespace(cursorChar); 983 if (!whitespace && cursorChar !== "/" && cursorChar !== ">") { 984 elementNameBuffer.push(cursorChar); 985 } 986 else { 987 elementNameBuffer = elementNameBuffer.join(""); 988 triggerStartElement(elementNameBuffer); 989 break; 990 } 991 cursor += 1; 992 } 993 994 // Get attributes 995 if (whitespace) { 996 while (cursor < xmlLength) { 997 moveToNonWhitespace(); 998 cursorChar = xml.charAt(cursor); 999 if (cursorChar !== "/" && cursorChar !== ">") { 1000 // Start of attribute 1001 parseAttribute(); 1002 } 1003 cursorChar = xml.charAt(cursor); 1004 if (cursorChar === "/" || cursorChar === ">") { 1005 break; 1006 } 1007 else { 1008 cursor += 1; 1009 } 1010 } 1011 } 1012 1013 // End tag if "/>" was found, 1014 // otherwise we're at the end of the start tag and have to parse into it 1015 if (cursorChar === "/") { 1016 if (cursor !== xmlLength - 1 && xml.charAt(cursor + 1) === ">") { 1017 cursor += 1; 1018 triggerEndElement(elementNameBuffer); 1019 } 1020 } 1021 else { 1022 // cursor is on ">", so parse into element content. Assume text until we find a "<", 1023 // which could be a child element or the current element's end tag. We do not support 1024 // mixed content of text and elements as siblings unless the text is only whitespace. 1025 // Text cannot contain <, >, ", or &. They should be <, >, ", & respectively. 1026 cursor += 1; 1027 while (cursor < xmlLength) { 1028 cursorChar = xml.charAt(cursor); 1029 if (cursorChar === "<") { 1030 // Determine if end tag or element 1031 if (cursor !== xmlLength - 1 && xml.charAt(cursor + 1) === "/") { 1032 // At end tag 1033 textBuffer = textBuffer.join(""); 1034 if (!isWhitespace(textBuffer)) { 1035 triggerText(textBuffer); 1036 } 1037 parseEndElement(); 1038 break; 1039 } 1040 else { 1041 // At start tag 1042 textBuffer = textBuffer.join(""); 1043 if (!isWhitespace(textBuffer)) { 1044 triggerText(textBuffer); 1045 } 1046 parseElement(); 1047 textBuffer = []; 1048 } 1049 } else if (cursorChar === "&") { 1050 textBuffer.push(parseReference()); 1051 } 1052 else { 1053 textBuffer.push(cursorChar); 1054 } 1055 cursor += 1; 1056 } 1057 } 1058 }, 1059 /** @private */ 1060 skipXmlDeclaration = function() { 1061 if (xml.substr(0, 5) === "<?xml" && isWhitespace(xml.charAt(5))) { 1062 cursor = xml.indexOf(">") + 1; 1063 } 1064 moveToNonWhitespace(); 1065 }; 1066 1067 // Launch. 1068 skipXmlDeclaration(); 1069 parseElement(); 1070 } 1071 }; 1072 1073 window.finesse = window.finesse || {}; 1074 window.finesse.utilities = window.finesse.utilities || {}; 1075 window.finesse.utilities.SaxParser = SaxParser; 1076 1077 return SaxParser; 1078 }); 1079 1080 /** 1081 * Date.parse with progressive enhancement for ISO 8601 <https://github.com/csnover/js-iso8601> 1082 * ?? 2011 Colin Snover <http://zetafleet.com> 1083 * Released under MIT license. 1084 */ 1085 define('utilities/../../thirdparty/util/iso8601',[], function () { 1086 (function (Date, undefined) { 1087 var origParse = Date.parse, numericKeys = [ 1, 4, 5, 6, 7, 10, 11 ]; 1088 /** @private **/ 1089 Date.parse = function (date) { 1090 var timestamp, struct, minutesOffset = 0; 1091 1092 // ES5 ??15.9.4.2 states that the string should attempt to be parsed as a Date Time String Format string 1093 // before falling back to any implementation-specific date parsing, so that???s what we do, even if native 1094 // implementations could be faster 1095 // 1 YYYY 2 MM 3 DD 4 HH 5 mm 6 ss 7 msec 8 Z 9 ?? 10 tzHH 11 tzmm 1096 if ((struct = /^(\d{4}|[+\-]\d{6})(?:-(\d{2})(?:-(\d{2}))?)?(?:T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{3}))?)?(?:(Z)|([+\-])(\d{2})(?::(\d{2}))?)?)?$/.exec(date))) { 1097 // avoid NaN timestamps caused by ???undefined??? values being passed to Date.UTC 1098 for (var i = 0, k; (k = numericKeys[i]); ++i) { 1099 struct[k] = +struct[k] || 0; 1100 } 1101 1102 // allow undefined days and months 1103 struct[2] = (+struct[2] || 1) - 1; 1104 struct[3] = +struct[3] || 1; 1105 1106 if (struct[8] !== 'Z' && struct[9] !== undefined) { 1107 minutesOffset = struct[10] * 60 + struct[11]; 1108 1109 if (struct[9] === '+') { 1110 minutesOffset = 0 - minutesOffset; 1111 } 1112 } 1113 1114 timestamp = Date.UTC(struct[1], struct[2], struct[3], struct[4], struct[5] + minutesOffset, struct[6], struct[7]); 1115 } 1116 else { 1117 timestamp = origParse ? origParse(date) : NaN; 1118 } 1119 1120 return timestamp; 1121 }; 1122 }(Date)); 1123 }); 1124 1125 /*! 1126 Math.uuid.js (v1.4) 1127 http://www.broofa.com 1128 mailto:robert@broofa.com 1129 1130 Copyright (c) 2010 Robert Kieffer 1131 Dual licensed under the MIT and GPL licenses. 1132 */ 1133 1134 /* 1135 * Generate a random uuid. 1136 * 1137 * USAGE: Math.uuid(length, radix) 1138 * length - the desired number of characters 1139 * radix - the number of allowable values for each character. 1140 * 1141 * EXAMPLES: 1142 * // No arguments - returns RFC4122, version 4 ID 1143 * >>> Math.uuid() 1144 * "92329D39-6F5C-4520-ABFC-AAB64544E172" 1145 * 1146 * // One argument - returns ID of the specified length 1147 * >>> Math.uuid(15) // 15 character ID (default base=62) 1148 * "VcydxgltxrVZSTV" 1149 * 1150 * // Two arguments - returns ID of the specified length, and radix. (Radix must be <= 62) 1151 * >>> Math.uuid(8, 2) // 8 character ID (base=2) 1152 * "01001010" 1153 * >>> Math.uuid(8, 10) // 8 character ID (base=10) 1154 * "47473046" 1155 * >>> Math.uuid(8, 16) // 8 character ID (base=16) 1156 * "098F4D35" 1157 */ 1158 define('utilities/../../thirdparty/util/Math.uuid',[], function () { 1159 (function() { 1160 // Private array of chars to use 1161 var CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split(''); 1162 1163 /** @private **/ 1164 Math.uuid = function (len, radix) { 1165 var chars = CHARS, uuid = [], i; 1166 radix = radix || chars.length; 1167 1168 if (len) { 1169 // Compact form 1170 for (i = 0; i < len; i++) uuid[i] = chars[0 | Math.random()*radix]; 1171 } else { 1172 // rfc4122, version 4 form 1173 var r; 1174 1175 // rfc4122 requires these characters 1176 uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-'; 1177 uuid[14] = '4'; 1178 1179 // Fill in random data. At i==19 set the high bits of clock sequence as 1180 // per rfc4122, sec. 4.1.5 1181 for (i = 0; i < 36; i++) { 1182 if (!uuid[i]) { 1183 r = 0 | Math.random()*16; 1184 uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r]; 1185 } 1186 } 1187 } 1188 1189 return uuid.join(''); 1190 }; 1191 1192 // A more performant, but slightly bulkier, RFC4122v4 solution. We boost performance 1193 // by minimizing calls to random() 1194 /** @private **/ 1195 Math.uuidFast = function() { 1196 var chars = CHARS, uuid = new Array(36), rnd=0, r; 1197 for (var i = 0; i < 36; i++) { 1198 if (i==8 || i==13 || i==18 || i==23) { 1199 uuid[i] = '-'; 1200 } else if (i==14) { 1201 uuid[i] = '4'; 1202 } else { 1203 if (rnd <= 0x02) rnd = 0x2000000 + (Math.random()*0x1000000)|0; 1204 r = rnd & 0xf; 1205 rnd = rnd >> 4; 1206 uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r]; 1207 } 1208 } 1209 return uuid.join(''); 1210 }; 1211 1212 // A more compact, but less performant, RFC4122v4 solution: 1213 /** @private **/ 1214 Math.uuidCompact = function() { 1215 return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { 1216 var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8); 1217 return v.toString(16); 1218 }); 1219 }; 1220 })(); 1221 }); 1222 1223 /** 1224 * The following comment prevents JSLint errors concerning undefined global variables. 1225 * It tells JSLint that these identifiers are defined elsewhere. 1226 */ 1227 /*jslint bitwise:true, browser:true, nomen:true, regexp:true, sloppy:true, white:true, plusplus: true, unparam: true, forin: true */ 1228 1229 /** The following comment is to prevent jslint errors about 1230 * using variables before they are defined. 1231 */ 1232 /*global $, _prefs,_uiMsg,ciscowidgets,dojo,finesse,gadgets,hostUrl, Handlebars */ 1233 1234 /** 1235 * A collection of utility functions. 1236 * 1237 * @requires finesse.Converter 1238 */ 1239 define('utilities/Utilities',[ 1240 "../../thirdparty/util/converter", 1241 "./SaxParser", 1242 "../../thirdparty/util/iso8601", 1243 "../../thirdparty/util/Math.uuid" 1244 ], 1245 function (Converter, SaxParser) { 1246 var Utilities = /** @lends finesse.utilities.Utilities */ { 1247 1248 /** 1249 * @class 1250 * Utilities is collection of utility methods. 1251 * 1252 * @augments finesse.restservices.RestBase 1253 * @see finesse.restservices.Contacts 1254 * @constructs 1255 */ 1256 _fakeConstuctor: function () { 1257 /* This is here for jsdocs. */ 1258 }, 1259 1260 /** 1261 * @private 1262 * Retrieves the specified item from window.localStorage 1263 * @param {String} key 1264 * The key of the item to retrieve 1265 * @returns {String} 1266 * The string with the value of the retrieved item; returns 1267 * what the browser would return if not found (typically null or undefined) 1268 * Returns false if window.localStorage feature is not even found. 1269 */ 1270 getDOMStoreItem: function (key) { 1271 var store = window.localStorage; 1272 if (store) { 1273 return store.getItem(key); 1274 } 1275 }, 1276 1277 /** 1278 * @private 1279 * Sets an item into window.localStorage 1280 * @param {String} key 1281 * The key for the item to set 1282 * @param {String} value 1283 * The value to set 1284 * @returns {Boolean} 1285 * True if successful, false if window.localStorage is 1286 * not even found. 1287 */ 1288 setDOMStoreItem: function (key, value) { 1289 var store = window.localStorage; 1290 if (store) { 1291 store.setItem(key, value); 1292 return true; 1293 } 1294 return false; 1295 }, 1296 1297 /** 1298 * @private 1299 * Removes a particular item from window.localStorage 1300 * @param {String} key 1301 * The key of the item to remove 1302 * @returns {Boolean} 1303 * True if successful, false if not 1304 * Returns false if window.localStorage feature is not even found. 1305 */ 1306 removeDOMStoreItem: function (key) { 1307 var store = window.localStorage; 1308 if (store) { 1309 store.removeItem(key); 1310 return true; 1311 } 1312 return false; 1313 }, 1314 1315 /** 1316 * @private 1317 * Dumps all the contents of window.localStorage 1318 * @returns {Boolean} 1319 * True if successful, false if not. 1320 * Returns false if window.localStorage feature is not even found. 1321 */ 1322 clearDOMStore: function () { 1323 var store = window.localStorage; 1324 if (store) { 1325 store.clear(); 1326 return true; 1327 } 1328 return false; 1329 }, 1330 1331 /** 1332 * @private 1333 * Creates a message listener for window.postMessage messages. 1334 * @param {Function} callback 1335 * The callback that will be invoked with the message. The callback 1336 * is responsible for any security checks. 1337 * @param {String} [origin] 1338 * The origin to check against for security. Allows all messages 1339 * if no origin is provided. 1340 * @returns {Function} 1341 * The callback function used to register with the message listener. 1342 * This is different than the one provided as a parameter because 1343 * the function is overloaded with origin checks. 1344 * @throws {Error} If the callback provided is not a function. 1345 */ 1346 receiveMessage: function (callback, origin) { 1347 if (typeof callback !== "function") { 1348 throw new Error("Callback is not a function."); 1349 } 1350 1351 //Create a function closure to perform origin check. 1352 /** @private */ 1353 var cb = function (e) { 1354 // If an origin check is requested (provided), we'll only invoke the callback if it passes 1355 if (typeof origin !== "string" || (typeof origin === "string" && typeof e.origin === "string" && e.origin.toLowerCase() === origin.toLowerCase())) { 1356 callback(e); 1357 } 1358 }; 1359 1360 if (window.addEventListener) { //Firefox, Opera, Chrome, Safari 1361 window.addEventListener("message", cb, false); 1362 } else { //Internet Explorer 1363 window.attachEvent("onmessage", cb); 1364 } 1365 1366 //Return callback used to register with listener so that invoker 1367 //could use it to remove. 1368 return cb; 1369 }, 1370 1371 /** 1372 * @private 1373 * Sends a message to a target frame using window.postMessage. 1374 * @param {Function} message 1375 * Message to be sent to target frame. 1376 * @param {Object} [target="parent"] 1377 * An object reference to the target frame. Default us the parent. 1378 * @param {String} [targetOrigin="*"] 1379 * The URL of the frame this frame is sending the message to. 1380 */ 1381 sendMessage: function (message, target, targetOrigin) { 1382 //Default to any target URL if none is specified. 1383 targetOrigin = targetOrigin || "*"; 1384 1385 //Default to parent target if none is specified. 1386 target = target || parent; 1387 1388 //Ensure postMessage is supported by browser before invoking. 1389 if (window.postMessage) { 1390 target.postMessage(message, targetOrigin); 1391 } 1392 }, 1393 1394 /** 1395 * Returns the passed in handler, if it is a function. 1396 * @param {Function} handler 1397 * The handler to validate 1398 * @returns {Function} 1399 * The provided handler if it is valid 1400 * @throws Error 1401 * If the handler provided is invalid 1402 */ 1403 validateHandler: function (handler) { 1404 if (handler === undefined || typeof handler === "function") { 1405 return handler; 1406 } else { 1407 throw new Error("handler must be a function"); 1408 } 1409 }, 1410 1411 /** 1412 * @private 1413 * Tries to get extract the AWS error code from a 1414 * finesse.clientservices.ClientServices parsed error response object. 1415 * @param {Object} rsp 1416 * The handler to validate 1417 * @returns {String} 1418 * The error code, HTTP status code, or undefined 1419 */ 1420 getErrCode: function (rsp) { 1421 try { // Best effort to get the error code 1422 return rsp.object.ApiErrors.ApiError.ErrorType; 1423 } catch (e) { // Second best effort to get the HTTP Status code 1424 if (rsp && rsp.status) { 1425 return "HTTP " + rsp.status; 1426 } else if (rsp && rsp.isUnsent) { // If request was aborted/cancelled/timedout 1427 return "Request could not be completed"; 1428 } 1429 } // Otherwise, don't return anything (undefined) 1430 }, 1431 1432 /** 1433 * @private 1434 * Tries to get extract the AWS error data from a 1435 * finesse.clientservices.ClientServices parsed error response object. 1436 * @param {Object} rsp 1437 * The handler to validate 1438 * @returns {String} 1439 * The error data, HTTP status code, or undefined 1440 */ 1441 getErrData: function (rsp) { 1442 try { // Best effort to get the error data 1443 return rsp.object.ApiErrors.ApiError.ErrorData; 1444 } catch (e) { // Second best effort to get the HTTP Status code 1445 if (rsp && rsp.status) { 1446 return "HTTP " + rsp.status; 1447 } else if (rsp && rsp.isUnsent) { // If request was aborted/cancelled/timedout 1448 return "Request could not be completed"; 1449 } 1450 } // Otherwise, don't return anything (undefined) 1451 }, 1452 1453 /** 1454 * @private 1455 * Tries to get extract the AWS error message from a 1456 * finesse.clientservices.ClientServices parsed error response object. 1457 * @param {Object} rsp 1458 * The handler to validate 1459 * @returns {String} 1460 * The error message, HTTP status code, or undefined 1461 */ 1462 getErrMessage: function (rsp) { 1463 try { // Best effort to get the error message 1464 return rsp.object.ApiErrors.ApiError.ErrorMessage; 1465 } catch (e) { // Second best effort to get the HTTP Status code 1466 if (rsp && rsp.status) { 1467 return "HTTP " + rsp.status; 1468 } else if (rsp && rsp.isUnsent) { // If request was aborted/cancelled/timedout 1469 return "Request could not be completed"; 1470 } 1471 } // Otherwise, don't return anything (undefined) 1472 }, 1473 1474 /** 1475 * @private 1476 * Tries to get extract the AWS overrideable boolean from a 1477 * finesse.clientservices.ClientServices parsed error response object. 1478 * @param {Object} rsp 1479 * The handler to validate 1480 * @returns {String} 1481 * The overrideable boolean, HTTP status code, or undefined 1482 */ 1483 getErrOverrideable: function (rsp) { 1484 try { // Best effort to get the override boolean 1485 return rsp.object.ApiErrors.ApiError.Overrideable; 1486 } catch (e) { // Second best effort to get the HTTP Status code 1487 if (rsp && rsp.status) { 1488 return "HTTP " + rsp.status; 1489 } 1490 } // Otherwise, don't return anything (undefined) 1491 }, 1492 1493 /** 1494 * Trims leading and trailing whitespace from a string. 1495 * @param {String} str 1496 * The string to trim 1497 * @returns {String} 1498 * The trimmed string 1499 */ 1500 trim: function (str) { 1501 return str.replace(/^\s*/, "").replace(/\s*$/, ""); 1502 }, 1503 1504 /** 1505 * Utility method for getting the current time in milliseconds. 1506 * @returns {String} 1507 * The current time in milliseconds 1508 */ 1509 currentTimeMillis : function () { 1510 return (new Date()).getTime(); 1511 }, 1512 1513 /** 1514 * Gets the current drift (between client and server) 1515 * 1516 * @returns {integer} which is the current drift (last calculated; 0 if we have not calculated yet) 1517 */ 1518 getCurrentDrift : function () { 1519 var drift; 1520 1521 //Get the current client drift from localStorage 1522 drift = window.sessionStorage.getItem("clientTimestampDrift"); 1523 if (drift) { 1524 drift = parseInt(drift, 10); 1525 if (isNaN(drift)) { 1526 drift = 0; 1527 } 1528 } 1529 return drift; 1530 }, 1531 1532 /** 1533 * Converts the specified clientTime to server time by adjusting by the current drift. 1534 * 1535 * @param clientTime is the time in milliseconds 1536 * @returns {int} serverTime in milliseconds 1537 */ 1538 convertToServerTimeMillis : function(clientTime) { 1539 var drift = this.getCurrentDrift(); 1540 return (clientTime + drift); 1541 }, 1542 1543 /** 1544 * Utility method for getting the current time, 1545 * adjusted by the calculated "drift" to closely 1546 * approximate the server time. This is used 1547 * when calculating durations based on a server 1548 * timestamp, which otherwise can produce unexpected 1549 * results if the times on client and server are 1550 * off. 1551 * 1552 * @returns {String} 1553 * The current server time in milliseconds 1554 */ 1555 currentServerTimeMillis : function () { 1556 var drift = this.getCurrentDrift(); 1557 return (new Date()).getTime() + drift; 1558 }, 1559 1560 /** 1561 * Given a specified timeInMs, this method will builds a string which displays minutes and seconds. 1562 * 1563 * @param timeInMs is the time in milliseconds 1564 * @returns {String} which corresponds to minutes and seconds (e.g. 11:23) 1565 */ 1566 buildTimeString : function (timeInMs) { 1567 var min, sec, timeStr = "00:00"; 1568 1569 if (timeInMs && timeInMs !== "-1") { 1570 // calculate minutes, and seconds 1571 min = this.pad(Math.floor(timeInMs / 60000)); 1572 sec = this.pad(Math.floor((timeInMs % 60000) / 1000)); 1573 1574 // construct MM:SS time string 1575 timeStr = min + ":" + sec; 1576 } 1577 return timeStr; 1578 }, 1579 1580 /** 1581 * Given a specified timeInMs, this method will builds a string which displays minutes and seconds (and optionally hours) 1582 * 1583 * @param timeInMs is the time in milliseconds 1584 * @returns {String} which corresponds to hours, minutes and seconds (e.g. 01:11:23 or 11:23) 1585 */ 1586 buildTimeStringWithOptionalHours: function (timeInMs) { 1587 var hour, min, sec, timeStr = "00:00", optionalHour = "", timeInSecs; 1588 1589 if (timeInMs && timeInMs !== "-1") { 1590 timeInSecs = timeInMs / 1000; 1591 1592 // calculate {hours}, minutes, and seconds 1593 hour = this.pad(Math.floor(timeInSecs / 3600)); 1594 min = this.pad(Math.floor((timeInSecs % 3600) / 60)); 1595 sec = this.pad(Math.floor((timeInSecs % 3600) % 60)); 1596 1597 //Optionally add the hour if we have hours 1598 if (hour > 0) { 1599 optionalHour = hour + ":"; 1600 } 1601 1602 // construct MM:SS time string (or optionally HH:MM:SS) 1603 timeStr = optionalHour + min + ":" + sec; 1604 } 1605 return timeStr; 1606 }, 1607 1608 1609 /** 1610 * Builds a string which specifies the amount of time user has been in this state (e.g. 11:23). 1611 * 1612 * @param adjustedServerTimeInMs is integer argument which specifies the expected server time (accounting for clientdrift) 1613 * @param stateStartTimeInMs is integer argument which specifies time call entered current state 1614 * @returns {String} which is the elapsed time (MINUTES:SECONDS) 1615 * 1616 */ 1617 buildElapsedTimeString : function (adjustedServerTimeInMs, stateStartTimeInMs) { 1618 var result, delta; 1619 1620 result = "--:--"; 1621 if (stateStartTimeInMs !== 0) { 1622 delta = adjustedServerTimeInMs - stateStartTimeInMs; 1623 1624 if (delta > 0) { 1625 result = this.buildTimeString(delta); 1626 } 1627 } 1628 return result; 1629 }, 1630 1631 /** 1632 * Builds a string which specifies the amount of time user has been in this state with optional hours (e.g. 01:11:23 or 11:23). 1633 * 1634 * @param adjustedServerTimeInMs is integer argument which specifies the expected server time (accounting for clientdrift) 1635 * @param startTimeInMs is integer argument which specifies the start time 1636 * @returns {String} which is the elapsed time (MINUTES:SECONDS) or (HOURS:MINUTES:SECONDS) 1637 * 1638 */ 1639 buildElapsedTimeStringWithOptionalHours : function (adjustedServerTimeInMs, stateStartTimeInMs) { 1640 var result, delta; 1641 1642 result = "--:--"; 1643 if (stateStartTimeInMs !== 0) { 1644 delta = adjustedServerTimeInMs - stateStartTimeInMs; 1645 1646 if (delta > 0) { 1647 result = this.buildTimeStringWithOptionalHours(delta); 1648 } 1649 } 1650 return result; 1651 }, 1652 1653 1654 /** 1655 * Builds a string which displays the total call time in minutes and seconds. 1656 * 1657 * @param adjustedServerTimeInMs is integer argument which specifies the expected server time (accounting for clientdrift) 1658 * @param callStartTimeInMs is integer argument which specifies time the call started 1659 * @returns {String} which is the elapsed time [MINUTES:SECONDS] 1660 */ 1661 buildTotalTimeString : function (adjustedServerTimeInMs, callStartTimeInMs) { 1662 return this.buildElapsedTimeString(adjustedServerTimeInMs, callStartTimeInMs); 1663 }, 1664 1665 /** 1666 * Builds a string which displays the hold time in minutes and seconds. 1667 * 1668 * @param adjustedServerTimeInMs is integer argument which specifies the expected server time (accounting for clientdrift) 1669 * @param holdStartTimeInMs is integer argument which specifies time the hold started 1670 * @returns {String} which is the elapsed time [MINUTES:SECONDS] 1671 */ 1672 buildHoldTimeString : function (adjustedServerTimeInMs, holdStartTimeInMs) { 1673 return this.buildElapsedTimeString(adjustedServerTimeInMs, holdStartTimeInMs); 1674 }, 1675 1676 /** 1677 * Builds a string which displays the elapsed time the call has been in wrap up. 1678 * 1679 * @param adjustedServerTimeInMs is integer argument which specifies the expected server time (accounting for clientdrift) 1680 * @param wrapupStartTimeInMs is integer argument which specifies time call entered wrapup state 1681 * @returns {String} which is the elapsed wrapup time 1682 * 1683 */ 1684 buildWrapupTimeString : function (adjustedServerTimeInMs, wrapupStartTimeInMs) { 1685 return this.buildElapsedTimeString(adjustedServerTimeInMs, wrapupStartTimeInMs); 1686 }, 1687 1688 /** 1689 * Extracts a time from the timeStr. Note: The timeStr could be empty. In this case, the time returned will be 0. 1690 * @param timeStr is a time string in ISO8601 format (note: could be empty) 1691 * @returns {long} is the time 1692 */ 1693 extractTime : function (timeStr) { 1694 var result = 0, theDate; 1695 if (timeStr === "") { 1696 result = 0; 1697 } else if (timeStr === null) { 1698 result = 0; 1699 } else { 1700 theDate = this.parseDateStringISO8601(timeStr); 1701 result = theDate.getTime(); 1702 } 1703 return result; 1704 }, 1705 1706 /** 1707 * @private 1708 * Generates an RFC1422v4-compliant UUID using pesudorandom numbers. 1709 * @returns {String} 1710 * An RFC1422v4-compliant UUID using pesudorandom numbers. 1711 **/ 1712 generateUUID: function () { 1713 return Math.uuidCompact(); 1714 }, 1715 1716 /** @private */ 1717 xml2json: finesse.Converter.xml2json, 1718 1719 1720 /** 1721 * @private 1722 * Utility method to get the JSON parser either from gadgets.json 1723 * or from window.JSON (which will be initialized by CUIC if 1724 * browser doesn't support 1725 */ 1726 getJSONParser: function() { 1727 var _container = window.gadgets || {}, 1728 parser = _container.json || window.JSON; 1729 return parser; 1730 }, 1731 1732 /** 1733 * @private 1734 * Utility method to convert a javascript object to XML. 1735 * @param {Object} object 1736 * The object to convert to XML. 1737 * @param {Boolean} escapeFlag 1738 * If escapeFlag evaluates to true: 1739 * - XML escaping is done on the element values. 1740 * - Attributes, #cdata, and #text is not supported. 1741 * - The XML is unformatted (no whitespace between elements). 1742 * If escapeFlag evaluates to false: 1743 * - Element values are written 'as is' (no escaping). 1744 * - Attributes, #cdata, and #text is supported. 1745 * - The XML is formatted. 1746 * @returns The XML string. 1747 */ 1748 json2xml: function (object, escapeFlag) { 1749 var xml; 1750 if (escapeFlag) { 1751 xml = this._json2xmlWithEscape(object); 1752 } 1753 else { 1754 xml = finesse.Converter.json2xml(object, '\t'); 1755 } 1756 return xml; 1757 }, 1758 1759 /** 1760 * @private 1761 * Utility method to convert XML string into javascript object. 1762 */ 1763 xml2JsObj : function (event) { 1764 var parser = this.getJSONParser(); 1765 return parser.parse(finesse.Converter.xml2json(jQuery.parseXML(event), "")); 1766 }, 1767 1768 /** 1769 * @private 1770 * Utility method to convert an XML string to a javascript object. 1771 * @desc This function calls to the SAX parser and responds to callbacks 1772 * received from the parser. Entity translation is not handled here. 1773 * @param {String} xml 1774 * The XML to parse. 1775 * @returns The javascript object. 1776 */ 1777 xml2js: function (xml) { 1778 var STATES = { 1779 INVALID: 0, 1780 NEW_NODE: 1, 1781 ATTRIBUTE_NODE: 2, 1782 TEXT_NODE: 3, 1783 END_NODE: 4 1784 }, 1785 state = STATES.INVALID, 1786 rootObj = {}, 1787 newObj, 1788 objStack = [rootObj], 1789 nodeName = "", 1790 1791 /** 1792 * @private 1793 * Adds a property to the current top JSO. 1794 * @desc This is also where we make considerations for arrays. 1795 * @param {String} name 1796 * The name of the property to add. 1797 * @param (String) value 1798 * The value of the property to add. 1799 */ 1800 addProperty = function (name, value) { 1801 var current = objStack[objStack.length - 1]; 1802 if(current.hasOwnProperty(name) && current[name] instanceof Array){ 1803 current[name].push(value); 1804 }else if(current.hasOwnProperty(name)){ 1805 current[name] = [current[name], value]; 1806 }else{ 1807 current[name] = value; 1808 } 1809 }, 1810 1811 /** 1812 * @private 1813 * The callback passed to the SAX parser which processes events from 1814 * the SAX parser in order to construct the resulting JSO. 1815 * @param (String) type 1816 * The type of event received. 1817 * @param (String) data 1818 * The data received from the SAX parser. The contents of this 1819 * parameter vary based on the type of event. 1820 */ 1821 xmlFound = function (type, data) { 1822 switch (type) { 1823 case "StartElement": 1824 // Because different node types have different expectations 1825 // of parenting, we don't push another JSO until we know 1826 // what content we're getting 1827 1828 // If we're already in the new node state, we're running 1829 // into a child node. There won't be any text here, so 1830 // create another JSO 1831 if(state === STATES.NEW_NODE){ 1832 newObj = {}; 1833 addProperty(nodeName, newObj); 1834 objStack.push(newObj); 1835 } 1836 state = STATES.NEW_NODE; 1837 nodeName = data; 1838 break; 1839 case "EndElement": 1840 // If we're in the new node state, we've found no content 1841 // set the tag property to null 1842 if(state === STATES.NEW_NODE){ 1843 addProperty(nodeName, null); 1844 }else if(state === STATES.END_NODE){ 1845 objStack.pop(); 1846 } 1847 state = STATES.END_NODE; 1848 break; 1849 case "Attribute": 1850 // If were in the new node state, no JSO has yet been created 1851 // for this node, create one 1852 if(state === STATES.NEW_NODE){ 1853 newObj = {}; 1854 addProperty(nodeName, newObj); 1855 objStack.push(newObj); 1856 } 1857 // Attributes are differentiated from child elements by a 1858 // preceding "@" in the property name 1859 addProperty("@" + data.name, data.value); 1860 state = STATES.ATTRIBUTE_NODE; 1861 break; 1862 case "Text": 1863 // In order to maintain backwards compatibility, when no 1864 // attributes are assigned to a tag, its text contents are 1865 // assigned directly to the tag property instead of a JSO. 1866 1867 // If we're in the attribute node state, then the JSO for 1868 // this tag was already created when the attribute was 1869 // assigned, differentiate this property from a child 1870 // element by naming it "#text" 1871 if(state === STATES.ATTRIBUTE_NODE){ 1872 addProperty("#text", data); 1873 }else{ 1874 addProperty(nodeName, data); 1875 } 1876 state = STATES.TEXT_NODE; 1877 break; 1878 } 1879 }; 1880 SaxParser.parse(xml, xmlFound); 1881 return rootObj; 1882 }, 1883 1884 /** 1885 * @private 1886 * Traverses a plain-old-javascript-object recursively and outputs its XML representation. 1887 * @param {Object} obj 1888 * The javascript object to be converted into XML. 1889 * @returns {String} The XML representation of the object. 1890 */ 1891 js2xml: function (obj) { 1892 var xml = "", i, elem; 1893 1894 if (obj !== null) { 1895 if (obj.constructor === Object) { 1896 for (elem in obj) { 1897 if (obj[elem] === null || typeof(obj[elem]) === 'undefined') { 1898 xml += '<' + elem + '/>'; 1899 } else if (obj[elem].constructor === Array) { 1900 for (i = 0; i < obj[elem].length; i++) { 1901 xml += '<' + elem + '>' + this.js2xml(obj[elem][i]) + '</' + elem + '>'; 1902 } 1903 } else if (elem[0] !== '@') { 1904 if (this.js2xmlObjIsEmpty(obj[elem])) { 1905 xml += '<' + elem + this.js2xmlAtt(obj[elem]) + '/>'; 1906 } else if (elem === "#text") { 1907 xml += obj[elem]; 1908 } else { 1909 xml += '<' + elem + this.js2xmlAtt(obj[elem]) + '>' + this.js2xml(obj[elem]) + '</' + elem + '>'; 1910 } 1911 } 1912 } 1913 } else { 1914 xml = obj; 1915 } 1916 } 1917 1918 return xml; 1919 }, 1920 1921 /** 1922 * @private 1923 * Utility method called exclusively by js2xml() to find xml attributes. 1924 * @desc Traverses children one layer deep of a javascript object to "look ahead" 1925 * for properties flagged as such (with '@'). 1926 * @param {Object} obj 1927 * The obj to traverse. 1928 * @returns {String} Any attributes formatted for xml, if any. 1929 */ 1930 js2xmlAtt: function (obj) { 1931 var elem; 1932 1933 if (obj !== null) { 1934 if (obj.constructor === Object) { 1935 for (elem in obj) { 1936 if (obj[elem] !== null && typeof(obj[elem]) !== "undefined" && obj[elem].constructor !== Array) { 1937 if (elem[0] === '@'){ 1938 return ' ' + elem.substring(1) + '="' + obj[elem] + '"'; 1939 } 1940 } 1941 } 1942 } 1943 } 1944 1945 return ''; 1946 }, 1947 1948 /** 1949 * @private 1950 * Utility method called exclusively by js2xml() to determine if 1951 * a node has any children, with special logic for ignoring attributes. 1952 * @desc Attempts to traverse the elements in the object while ignoring attributes. 1953 * @param {Object} obj 1954 * The obj to traverse. 1955 * @returns {Boolean} whether or not the JS object is "empty" 1956 */ 1957 js2xmlObjIsEmpty: function (obj) { 1958 var elem; 1959 1960 if (obj !== null) { 1961 if (obj.constructor === Object) { 1962 for (elem in obj) { 1963 if (obj[elem] !== null) { 1964 if (obj[elem].constructor === Array){ 1965 return false; 1966 } 1967 1968 if (elem[0] !== '@'){ 1969 return false; 1970 } 1971 } else { 1972 return false; 1973 } 1974 } 1975 } else { 1976 return false; 1977 } 1978 } 1979 1980 return true; 1981 }, 1982 1983 /** 1984 * Encodes the given string into base64. 1985 *<br> 1986 * <b>NOTE:</b> {input} is assumed to be UTF-8; only the first 1987 * 8 bits of each input element are significant. 1988 * 1989 * @param {String} input 1990 * The string to convert to base64. 1991 * @returns {String} 1992 * The converted string. 1993 */ 1994 b64Encode: function (input) { 1995 var output = "", idx, data, 1996 table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; 1997 1998 for (idx = 0; idx < input.length; idx += 3) { 1999 data = input.charCodeAt(idx) << 16 | 2000 input.charCodeAt(idx + 1) << 8 | 2001 input.charCodeAt(idx + 2); 2002 2003 //assume the first 12 bits are valid 2004 output += table.charAt((data >>> 18) & 0x003f) + 2005 table.charAt((data >>> 12) & 0x003f); 2006 output += ((idx + 1) < input.length) ? 2007 table.charAt((data >>> 6) & 0x003f) : 2008 "="; 2009 output += ((idx + 2) < input.length) ? 2010 table.charAt(data & 0x003f) : 2011 "="; 2012 } 2013 2014 return output; 2015 }, 2016 2017 /** 2018 * Decodes the given base64 string. 2019 * <br> 2020 * <b>NOTE:</b> output is assumed to be UTF-8; only the first 2021 * 8 bits of each output element are significant. 2022 * 2023 * @param {String} input 2024 * The base64 encoded string 2025 * @returns {String} 2026 * Decoded string 2027 */ 2028 b64Decode: function (input) { 2029 var output = "", idx, h, data, 2030 table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; 2031 2032 for (idx = 0; idx < input.length; idx += 4) { 2033 h = [ 2034 table.indexOf(input.charAt(idx)), 2035 table.indexOf(input.charAt(idx + 1)), 2036 table.indexOf(input.charAt(idx + 2)), 2037 table.indexOf(input.charAt(idx + 3)) 2038 ]; 2039 2040 data = (h[0] << 18) | (h[1] << 12) | (h[2] << 6) | h[3]; 2041 if (input.charAt(idx + 2) === '=') { 2042 data = String.fromCharCode( 2043 (data >>> 16) & 0x00ff 2044 ); 2045 } else if (input.charAt(idx + 3) === '=') { 2046 data = String.fromCharCode( 2047 (data >>> 16) & 0x00ff, 2048 (data >>> 8) & 0x00ff 2049 ); 2050 } else { 2051 data = String.fromCharCode( 2052 (data >>> 16) & 0x00ff, 2053 (data >>> 8) & 0x00ff, 2054 data & 0x00ff 2055 ); 2056 } 2057 output += data; 2058 } 2059 2060 return output; 2061 }, 2062 2063 /** 2064 * @private 2065 * Extracts the username and the password from the Base64 encoded string. 2066 * @params {String} 2067 * A base64 encoded string containing credentials that (when decoded) 2068 * are colon delimited. 2069 * @returns {Object} 2070 * An object with the following structure: 2071 * {id:string, password:string} 2072 */ 2073 getCredentials: function (authorization) { 2074 var credObj = {}, 2075 credStr = this.b64Decode(authorization), 2076 colonIndx = credStr.indexOf(":"); 2077 2078 //Check to ensure that string is colon delimited. 2079 if (colonIndx === -1) { 2080 throw new Error("String is not colon delimited."); 2081 } 2082 2083 //Extract ID and password. 2084 credObj.id = credStr.substring(0, colonIndx); 2085 credObj.password = credStr.substring(colonIndx + 1); 2086 return credObj; 2087 }, 2088 2089 /** 2090 * Takes a string and removes any spaces within the string. 2091 * @param {String} string 2092 * The string to remove spaces from 2093 * @returns {String} 2094 * The string without spaces 2095 */ 2096 removeSpaces: function (string) { 2097 return string.split(' ').join(''); 2098 }, 2099 2100 /** 2101 * Escapes spaces as encoded " " characters so they can 2102 * be safely rendered by jQuery.text(string) in all browsers. 2103 * 2104 * (Although IE behaves as expected, Firefox collapses spaces if this function is not used.) 2105 * 2106 * @param text 2107 * The string whose spaces should be escaped 2108 * 2109 * @returns 2110 * The string with spaces escaped 2111 */ 2112 escapeSpaces: function (string) { 2113 return string.replace(/\s/g, '\u00a0'); 2114 }, 2115 2116 /** 2117 * Adds a span styled to line break at word edges around the string passed in. 2118 * @param str String to be wrapped in word-breaking style. 2119 * @private 2120 */ 2121 addWordWrapping : function (str) { 2122 return '<span style="word-wrap: break-word;">' + str + '</span>'; 2123 }, 2124 2125 /** 2126 * Takes an Object and determines whether it is an Array or not. 2127 * @param {Object} obj 2128 * The Object in question 2129 * @returns {Boolean} 2130 * true if the object is an Array, else false. 2131 */ 2132 isArray: function (obj) { 2133 return obj.constructor.toString().indexOf("Array") !== -1; 2134 }, 2135 2136 /** 2137 * @private 2138 * Takes a data object and returns an array extracted 2139 * @param {Object} data 2140 * JSON payload 2141 * 2142 * @returns {array} 2143 * extracted array 2144 */ 2145 getArray: function (data) { 2146 if (this.isArray(data)) { 2147 //Return if already an array. 2148 return data; 2149 } else { 2150 //Create an array, iterate through object, and push to array. This 2151 //should only occur with one object, and therefore one obj in array. 2152 var arr = []; 2153 arr.push(data); 2154 return arr; 2155 } 2156 }, 2157 2158 /** 2159 * @private 2160 * Extracts the ID for an entity given the Finesse REST URI. The ID is 2161 * assumed to be the last element in the URI (after the last "/"). 2162 * @param {String} uri 2163 * The Finesse REST URI to extract the ID from. 2164 * @returns {String} 2165 * The ID extracted from the REST URI. 2166 */ 2167 getId: function (uri) { 2168 if (!uri) { 2169 return ""; 2170 } 2171 var strLoc = uri.lastIndexOf("/"); 2172 return uri.slice(strLoc + 1); 2173 }, 2174 2175 /** 2176 * Compares two objects for equality. 2177 * @param {Object} obj1 2178 * First of two objects to compare. 2179 * @param {Object} obj2 2180 * Second of two objects to compare. 2181 */ 2182 getEquals: function (objA, objB) { 2183 var key; 2184 2185 for (key in objA) { 2186 if (objA.hasOwnProperty(key)) { 2187 if (!objA[key]) { 2188 objA[key] = ""; 2189 } 2190 2191 if (typeof objB[key] === 'undefined') { 2192 return false; 2193 } 2194 if (typeof objB[key] === 'object') { 2195 if (!objB[key].equals(objA[key])) { 2196 return false; 2197 } 2198 } 2199 if (objB[key] !== objA[key]) { 2200 return false; 2201 } 2202 } 2203 } 2204 return true; 2205 }, 2206 2207 /** 2208 * Regular expressions used in translating HTML and XML entities 2209 */ 2210 ampRegEx : new RegExp('&', 'gi'), 2211 ampEntityRefRegEx : new RegExp('&', 'gi'), 2212 ltRegEx : new RegExp('<', 'gi'), 2213 ltEntityRefRegEx : new RegExp('<', 'gi'), 2214 gtRegEx : new RegExp('>', 'gi'), 2215 gtEntityRefRegEx : new RegExp('>', 'gi'), 2216 xmlSpecialCharRegEx: new RegExp('[&<>"\']', 'g'), 2217 entityRefRegEx: new RegExp('&[^;]+(?:;|$)', 'g'), 2218 2219 /** 2220 * Translates between special characters and HTML entities 2221 * 2222 * @param text 2223 * The text to translate 2224 * 2225 * @param makeEntityRefs 2226 * If true, encode special characters as HTML entities; if 2227 * false, decode HTML entities back to special characters 2228 * 2229 * @private 2230 */ 2231 translateHTMLEntities: function (text, makeEntityRefs) { 2232 if (typeof(text) !== "undefined" && text !== null && text !== "") { 2233 if (makeEntityRefs) { 2234 text = text.replace(this.ampRegEx, '&'); 2235 text = text.replace(this.ltRegEx, '<'); 2236 text = text.replace(this.gtRegEx, '>'); 2237 } else { 2238 text = text.replace(this.gtEntityRefRegEx, '>'); 2239 text = text.replace(this.ltEntityRefRegEx, '<'); 2240 text = text.replace(this.ampEntityRefRegEx, '&'); 2241 } 2242 } 2243 2244 return text; 2245 }, 2246 2247 /** 2248 * Translates between special characters and XML entities 2249 * 2250 * @param text 2251 * The text to translate 2252 * 2253 * @param makeEntityRefs 2254 * If true, encode special characters as XML entities; if 2255 * false, decode XML entities back to special characters 2256 * 2257 * @private 2258 */ 2259 translateXMLEntities: function (text, makeEntityRefs) { 2260 /** @private */ 2261 var escape = function (character) { 2262 switch (character) { 2263 case "&": 2264 return "&"; 2265 case "<": 2266 return "<"; 2267 case ">": 2268 return ">"; 2269 case "'": 2270 return "'"; 2271 case "\"": 2272 return """; 2273 default: 2274 return character; 2275 } 2276 }, 2277 /** @private */ 2278 unescape = function (entity) { 2279 switch (entity) { 2280 case "&": 2281 return "&"; 2282 case "<": 2283 return "<"; 2284 case ">": 2285 return ">"; 2286 case "'": 2287 return "'"; 2288 case """: 2289 return "\""; 2290 default: 2291 if (entity.charAt(1) === "#" && entity.charAt(entity.length - 1) === ";") { 2292 if (entity.charAt(2) === "x") { 2293 return String.fromCharCode(parseInt(entity.slice(3, -1), 16)); 2294 } else { 2295 return String.fromCharCode(parseInt(entity.slice(2, -1), 10)); 2296 } 2297 } else { 2298 throw new Error("Invalid XML entity: " + entity); 2299 } 2300 } 2301 }; 2302 2303 if (typeof(text) !== "undefined" && text !== null && text !== "") { 2304 if (makeEntityRefs) { 2305 text = text.replace(this.xmlSpecialCharRegEx, escape); 2306 } else { 2307 text = text.replace(this.entityRefRegEx, unescape); 2308 } 2309 } 2310 2311 return text; 2312 }, 2313 2314 /** 2315 * @private 2316 * Utility method to pad the number with a leading 0 for single digits 2317 * @param (Number) num 2318 * the number to pad 2319 */ 2320 pad : function (num) { 2321 if (num < 10) { 2322 return "0" + num; 2323 } 2324 2325 return String(num); 2326 }, 2327 2328 /** 2329 * Pad with zeros based on a padWidth. 2330 * 2331 * @param num 2332 * @param padWidth 2333 * @returns {String} with padded zeros (based on padWidth) 2334 */ 2335 padWithWidth : function (num, padWidth) { 2336 var value, index, result; 2337 2338 result = ""; 2339 for(index=padWidth;index>1;index--) 2340 { 2341 value = Math.pow(10, index-1); 2342 2343 if (num < value) { 2344 result = result + "0"; 2345 } 2346 } 2347 result = result + num; 2348 2349 return String(result); 2350 }, 2351 2352 /** 2353 * Converts a date to an ISO date string. 2354 * 2355 * @param aDate 2356 * @returns {String} in ISO date format 2357 * 2358 * Note: Some browsers don't support this method (e.g. IE8). 2359 */ 2360 convertDateToISODateString : function (aDate) { 2361 var result; 2362 2363 result = aDate.getUTCFullYear() + "-" + this.padWithWidth(aDate.getUTCMonth()+1, 2) + "-" + this.padWithWidth(aDate.getUTCDate(), 2) + "T" + this.padWithWidth(aDate.getUTCHours(), 2) + ":" + this.padWithWidth(aDate.getUTCMinutes(), 2) + ":" + this.padWithWidth(aDate.getUTCSeconds(), 2)+ "." + this.padWithWidth(aDate.getUTCMilliseconds(), 3) + "Z"; 2364 return result; 2365 }, 2366 2367 /** 2368 * Get the date in ISO date format. 2369 * 2370 * @param aDate is the date 2371 * @returns {String} date in ISO format 2372 * 2373 * Note: see convertDateToISODateString() above. 2374 */ 2375 dateToISOString : function (aDate) { 2376 var result; 2377 2378 try { 2379 result = aDate.toISOString(); 2380 } catch (e) { 2381 result = this.convertDateToISODateString(aDate); 2382 } 2383 return result; 2384 }, 2385 2386 /** 2387 * Parse string (which is formated as ISO8601 date) into Javascript Date object. 2388 * 2389 * @param s ISO8601 string 2390 * @return {Date} 2391 * Note: Some browsers don't support Date constructor which take ISO8601 date (e.g. IE 8). 2392 */ 2393 parseDateStringISO8601 : function (s) { 2394 var i, re = /(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:.(\d+))?(Z|[+\-]\d{2})(?::(\d{2}))?/, 2395 d = s.match(re); 2396 if( !d ) { 2397 return null; 2398 } 2399 for( i in d ) { 2400 d[i] = ~~d[i]; 2401 } 2402 return new Date(Date.UTC(d[1], d[2] - 1, d[3], d[4], d[5], d[6], d[7]) + (d[8] * 60 + d[9]) * 60000); 2403 }, 2404 2405 /** 2406 * Utility method to render a timestamp value (in seconds) into HH:MM:SS format. 2407 * @param {Number} time 2408 * The timestamp in ms to render 2409 * @returns {String} 2410 * Time string in HH:MM:SS format. 2411 */ 2412 getDisplayTime : function (time) { 2413 var hour, min, sec, timeStr = "00:00:00"; 2414 2415 if (time && time !== "-1") { 2416 // calculate hours, minutes, and seconds 2417 hour = this.pad(Math.floor(time / 3600)); 2418 min = this.pad(Math.floor((time % 3600) / 60)); 2419 sec = this.pad(Math.floor((time % 3600) % 60)); 2420 // construct HH:MM:SS time string 2421 timeStr = hour + ":" + min + ":" + sec; 2422 } 2423 2424 return timeStr; 2425 }, 2426 2427 /** 2428 * Checks if the string is null. If it is, return empty string; else return 2429 * the string itself. 2430 * 2431 * @param {String} str 2432 * The string to check 2433 * @return {String} 2434 * Empty string or string itself 2435 */ 2436 convertNullToEmptyString : function (str) { 2437 return str || ""; 2438 }, 2439 2440 /** 2441 * Utility method to render a timestamp string (of format 2442 * YYYY-MM-DDTHH:MM:SSZ) into a duration of HH:MM:SS format. 2443 * 2444 * @param {String} timestamp 2445 * The timestamp to render 2446 * @param {Date} [now] 2447 * Optional argument to provide the time from which to 2448 * calculate the duration instead of using the current time 2449 * @returns {String} 2450 * Duration string in HH:MM:SS format. 2451 */ 2452 convertTsToDuration : function (timestamp, now) { 2453 return this.convertTsToDurationWithFormat(timestamp, false, now); 2454 }, 2455 2456 /** 2457 * Utility method to render a timestamp string (of format 2458 * YYYY-MM-DDTHH:MM:SSZ) into a duration of HH:MM:SS format, 2459 * with optional -1 for null or negative times. 2460 * 2461 * @param {String} timestamp 2462 * The timestamp to render 2463 * @param {Boolean} forFormat 2464 * If True, if duration is null or negative, return -1 so that the duration can be formated 2465 * as needed in the Gadget. 2466 * @param {Date} [now] 2467 * Optional argument to provide the time from which to 2468 * calculate the duration instead of using the current time 2469 * @returns {String} 2470 * Duration string in HH:MM:SS format. 2471 */ 2472 convertTsToDurationWithFormat : function (timestamp, forFormat, now) { 2473 var startTimeInMs, nowInMs, durationInSec = "-1"; 2474 2475 // Calculate duration 2476 if (timestamp && typeof timestamp === "string") { 2477 // first check it '--' for a msg in grid 2478 if (timestamp === '--' || timestamp ==="" || timestamp === "-1") { 2479 return "-1"; 2480 } 2481 // else try convert string into a time 2482 startTimeInMs = Date.parse(timestamp); 2483 if (!isNaN(startTimeInMs)) { 2484 if (!now || !(now instanceof Date)) { 2485 nowInMs = this.currentServerTimeMillis(); 2486 } else { 2487 nowInMs = this.convertToServerTimeMillis(now.getTime()); 2488 } 2489 durationInSec = Math.floor((nowInMs - startTimeInMs) / 1000); 2490 // Since currentServerTime is not exact (lag between sending and receiving 2491 // messages will differ slightly), treat a slightly negative (less than 1 sec) 2492 // value as 0, to avoid "--" showing up when a state first changes. 2493 if (durationInSec === -1) { 2494 durationInSec = 0; 2495 } 2496 2497 if (durationInSec < 0) { 2498 if (forFormat) { 2499 return "-1"; 2500 } else { 2501 return this.getDisplayTime("-1"); 2502 } 2503 } 2504 } 2505 }else { 2506 if(forFormat){ 2507 return "-1"; 2508 } 2509 } 2510 return this.getDisplayTime(durationInSec); 2511 }, 2512 2513 /** 2514 * @private 2515 * Takes the time in seconds and duration in % and return the duration in milliseconds. 2516 * 2517 * @param time in seconds 2518 * @param duration in % 2519 */ 2520 2521 getRefreshTime :function(expiryTime , duration){ 2522 var durationInMs = Math.floor((expiryTime * duration * 1000) / 100); 2523 return durationInMs; 2524 }, 2525 2526 /** 2527 * Takes a string (typically from window.location) and finds the value which corresponds to a name. For 2528 * example: http://www.company.com/?param1=value1¶m2=value2 2529 * 2530 * @param str is the string to search 2531 * @param name is the name to search for 2532 */ 2533 getParameterByName : function(str, name) { 2534 var regex, results; 2535 name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); 2536 regex = new RegExp("[\\?&]" + name + "=([^]*)"); 2537 results = regex.exec(str); 2538 return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " ")); 2539 }, 2540 2541 /** 2542 * 2543 * Returns the base64 encoded user authorization String. 2544 * @returns {String} the Authorization String 2545 * 2546 */ 2547 getUserAuthString: function () { 2548 var authString = window.sessionStorage.getItem('userFinesseAuth'); 2549 return authString; 2550 }, 2551 2552 /** 2553 * Return the user access token as JSON Object. 2554 * @returns {Object} the access token JSON object 2555 * 2556 */ 2557 getAuthTokenObj: function(){ 2558 var authTokenString = window.sessionStorage.getItem('ssoTokenObject'); 2559 return this.getJSONParser().parse(authTokenString); 2560 }, 2561 2562 /** 2563 * Returns the user access token as String. 2564 * @returns {String} the access token 2565 * 2566 */ 2567 2568 getToken: function () { 2569 var tokenString = window.sessionStorage.getItem('ssoTokenObject'), tokenObj; 2570 if (tokenString && typeof tokenString === "string") { 2571 tokenObj = this.getJSONParser().parse(tokenString); 2572 if (tokenObj.token) { 2573 return tokenObj.token; 2574 } else { 2575 throw new Error( 2576 "Unable to retrieve token : Invalid token Object in browser session"); 2577 } 2578 } 2579 }, 2580 2581 /** 2582 * The authorization header based on SSO or non SSO deployment. 2583 * Can be "Bearer " or "Basic " 2584 * @returns {String} The authorization header string. 2585 */ 2586 getAuthHeaderString : function(configObj) { 2587 var authHeader; 2588 if (configObj.systemAuthMode === this.getAuthModes().SSO) { 2589 authHeader = "Bearer " + configObj.authToken; 2590 } else if (configObj.systemAuthMode === this.getAuthModes().NONSSO) { 2591 authHeader = "Basic " + configObj.authorization; 2592 } else { 2593 throw new Error("Unknown auth mode "+configObj.systemAuthMode); 2594 } 2595 return authHeader; 2596 }, 2597 2598 /** 2599 * Can be used as a constant for auth modes 2600 * Can be "SSO" , "NON_SSO" or "HYBRID" 2601 * @returns {String} The authorization header string. 2602 */ 2603 getAuthModes : function(){ 2604 return { 2605 SSO: "SSO", 2606 NONSSO: "NON_SSO", 2607 HYBRID: "HYBRID" 2608 }; 2609 }, 2610 2611 /** 2612 * Encodes the node name 2613 */ 2614 encodeNodeName : function(node){ 2615 if (node === null){ 2616 return null; 2617 } 2618 var originalChars, encodedChars,encodedNode, i; 2619 originalChars = ["?", "@", "&","'"]; 2620 encodedChars = ["?3F", "?40", "?26","?27"]; 2621 encodedNode = node; 2622 2623 if(encodedNode.indexOf(originalChars[0]) !== -1){ 2624 encodedNode = encodedNode.replace(/\?/g, encodedChars[0]); 2625 } 2626 for (i = 1; i < originalChars.length; i++){ 2627 if(encodedNode.indexOf(originalChars[i]) !== -1){ 2628 encodedNode = encodedNode.replace(new RegExp(originalChars[i], "g"), encodedChars[i]); 2629 } 2630 } 2631 return encodedNode; 2632 }, 2633 2634 /** 2635 * @private Utility method to convert milliseconds into minutes. 2636 * @param {String} Time in milliseconds 2637 * @returns {String} Time in minutes 2638 */ 2639 convertMilliSecondsToMinutes : function(millisec){ 2640 if(!millisec || isNaN(millisec)){ 2641 throw new Error("passed argument is not a number"); 2642 }else{ 2643 var minutes = Math.floor(millisec / (1000 * 60)); 2644 return minutes; 2645 } 2646 }, 2647 2648 2649 /** 2650 * @private Adds a new cookie to the page with a default domain. 2651 * @param {String} 2652 * key the key to assign a value to 2653 * @param {String} 2654 * value the value to assign to the key 2655 * @param {Number} 2656 * days number of days (from current) until the cookie should 2657 * expire 2658 * @param {String} 2659 * domain the domain for the cookie 2660 */ 2661 addCookie : function (key, value, days, domain) { 2662 var date, expires = "", 2663 cookie = key + "=" + escape(value); 2664 if (typeof days === "number") { 2665 date = new Date(); 2666 date.setTime(date.getTime() + (days * 24 * 3600 * 1000)); 2667 cookie += "; expires=" + date.toGMTString(); 2668 } 2669 2670 if (domain) { 2671 cookie += "; domain=" + domain; 2672 } 2673 2674 document.cookie = cookie + "; path=/"; 2675 }, 2676 2677 /** 2678 * @private 2679 * Get the value of a cookie given a key. 2680 * @param {String} key 2681 * a key to lookup 2682 * @returns {String} 2683 * the value mapped to a key, null if key doesn't exist 2684 */ 2685 getCookie : function (key) { 2686 var i, pairs, pair; 2687 if (document.cookie) { 2688 pairs = document.cookie.split(";"); 2689 for (i = 0; i < pairs.length; i += 1) { 2690 pair = this.trim(pairs[i]).split("="); 2691 if (pair[0] === key) { 2692 return unescape(pair[1]); 2693 } 2694 } 2695 } 2696 return null; 2697 }, 2698 2699 getCookieEntriesThatStartsWith : function (key) { 2700 var i, pairs, pair, entries = []; 2701 if (document.cookie) { 2702 pairs = document.cookie.split(";"); 2703 for (i = 0; i < pairs.length; i += 1) { 2704 pair = this.trim(pairs[i]).split("="); 2705 if (pair[0].startsWith(key)) { 2706 entries.push({key: pair[0], value: unescape(pair[1])}); 2707 } 2708 } 2709 } 2710 return entries; 2711 }, 2712 2713 /** 2714 * @private 2715 * Deletes the cookie mapped to specified key. 2716 * @param {String} key 2717 * the key to delete 2718 */ 2719 deleteCookie : function (key, domain) { 2720 this.addCookie(key, "", -1, domain); 2721 }, 2722 2723 /** 2724 * @private 2725 * Case insensitive sort for use with arrays or Dojox stores 2726 * @param {String} a 2727 * first value 2728 * @param {String} b 2729 * second value 2730 */ 2731 caseInsensitiveSort: function (a, b) { 2732 var ret = 0, emptyString = ""; 2733 a = a + emptyString; 2734 b = b + emptyString; 2735 a = a.toLowerCase(); 2736 b = b.toLowerCase(); 2737 if (a > b) { 2738 ret = 1; 2739 } 2740 if (a < b) { 2741 ret = -1; 2742 } 2743 return ret; 2744 }, 2745 2746 /** 2747 * @private 2748 * Calls the specified function to render the dojo wijit for a gadget when the gadget first becomes visible. 2749 * 2750 * The displayWjitFunc function will be called once and only once when the div for our wijit 2751 * becomes visible for the first time. This is necessary because some dojo wijits such as the grid 2752 * throw exceptions and do not render properly if they are created in a display:none div. 2753 * If our gadget is visisble the function will be called immediately. 2754 * If our gadget is not yet visisble, then it sets a timer and waits for it to become visible. 2755 * NOTE: The timer may seem inefficent, originally I tried connecting to the tab onclick handler, but 2756 * there is a problem with dojo.connnect to an iframe's parent node in Internet Explorer. 2757 * In Firefox the click handler works OK, but it happens before the node is actually visisble, so you 2758 * end up waiting for the node to become visisble anyway. 2759 * @displayWjitFunc: A function to be called once our gadget has become visisble for th first time. 2760 */ 2761 onGadgetFirstVisible: function (displayWjitFunc) { 2762 var i, q, frameId, gadgetNbr, gadgetTitleId, panelId, panelNode, link, iterval, once = false, active = false, tabId = "#finesse-tab-selector"; 2763 try { 2764 frameId = dojo.attr(window.frameElement, "id"); // Figure out what gadget number we are by looking at our frameset 2765 gadgetNbr = frameId.match(/\d+$/)[0]; // Strip the number off the end of the frame Id, that's our gadget number 2766 gadgetTitleId = "#finesse_gadget_" + gadgetNbr + "_title"; // Create a a gadget title id from the number 2767 2768 // Loop through all of the tab panels to find one that has our gadget id 2769 dojo.query('.tab-panel', window.parent.document).some(function (node, index, arr) { 2770 q = dojo.query(gadgetTitleId, node); // Look in this panel for our gadget id 2771 if (q.length > 0) { // You found it 2772 panelNode = node; 2773 panelId = dojo.attr(panelNode, "id"); // Get panel id e.g. panel_Workgroups 2774 active = dojo.hasClass(panelNode, "active"); 2775 tabId = "#tab_" + panelId.slice(6); // Turn it into a tab id e.g.tab_Workgroups 2776 return; 2777 } 2778 }); 2779 // If panel is already active - execute the function - we're done 2780 if (active) { 2781 //?console.log(frameId + " is visible display it"); 2782 setTimeout(displayWjitFunc); 2783 } 2784 // If its not visible - wait for the active class to show up. 2785 else { 2786 //?console.log(frameId + " (" + tabId + ") is NOT active wait for it"); 2787 iterval = setInterval(dojo.hitch(this, function () { 2788 if (dojo.hasClass(panelNode, "active")) { 2789 //?console.log(frameId + " (" + tabId + ") is visible display it"); 2790 clearInterval(iterval); 2791 setTimeout(displayWjitFunc); 2792 } 2793 }), 250); 2794 } 2795 } catch (err) { 2796 //?console.log("Could not figure out what tab " + frameId + " is in: " + err); 2797 } 2798 }, 2799 2800 /** 2801 * @private 2802 * Downloads the specified url using a hidden iframe. In order to cause the browser to download rather than render 2803 * in the hidden iframe, the server code must append the header "Content-Disposition" with a value of 2804 * "attachment; filename=\"<WhateverFileNameYouWant>\"". 2805 */ 2806 downloadFile : function (url) { 2807 var iframe = document.getElementById("download_iframe"); 2808 2809 if (!iframe) 2810 { 2811 iframe = document.createElement("iframe"); 2812 $(document.body).append(iframe); 2813 $(iframe).css("display", "none"); 2814 } 2815 2816 iframe.src = url; 2817 }, 2818 2819 /** 2820 * @private 2821 * bitMask has functions for testing whether bit flags specified by integers are set in the supplied value 2822 */ 2823 bitMask: { 2824 /** @private */ 2825 isSet: function (value, mask) { 2826 return (value & mask) === mask; 2827 }, 2828 /** 2829 * Returns true if all flags in the intArray are set on the specified value 2830 * @private 2831 */ 2832 all: function (value, intArray) { 2833 var i = intArray.length; 2834 if (typeof(i) === "undefined") 2835 { 2836 intArray = [intArray]; 2837 i = 1; 2838 } 2839 while ((i = i - 1) !== -1) 2840 { 2841 if (!this.isSet(value, intArray[i])) 2842 { 2843 return false; 2844 } 2845 } 2846 return true; 2847 }, 2848 /** 2849 * @private 2850 * Returns true if any flags in the intArray are set on the specified value 2851 */ 2852 any: function (value, intArray) { 2853 var i = intArray.length; 2854 if (typeof(i) === "undefined") 2855 { 2856 intArray = [intArray]; 2857 i = 1; 2858 } 2859 while ((i = i - 1) !== -1) 2860 { 2861 if (this.isSet(value, intArray[i])) 2862 { 2863 return true; 2864 } 2865 } 2866 return false; 2867 } 2868 }, 2869 2870 /** @private */ 2871 renderDojoGridOffScreen: function (grid) { 2872 var offscreenDiv = $("<div style='position: absolute; left: -5001px; width: 5000px;'></div>")[0]; 2873 $(document.body).append(offscreenDiv); 2874 grid.placeAt(offscreenDiv); 2875 grid.startup(); 2876 document.body.removeChild(offscreenDiv); 2877 return grid; 2878 }, 2879 2880 /** @private */ 2881 initializeSearchInput: function(searchInput, changeCallback, callbackDelay, callbackScope, placeholderText) { 2882 var timerId = null, 2883 theControl = typeof(searchInput) === "string" ? $("#" + searchInput) : $(searchInput), 2884 theInputControl = theControl.find("input"), 2885 theClearButton = theControl.find("a"), 2886 inputControlWidthWithClear = 204, 2887 inputControlWidthNoClear = 230, 2888 sPreviousInput = theInputControl.val(), 2889 /** @private **/ 2890 toggleClearButton = function(){ 2891 if (theInputControl.val() === "") { 2892 theClearButton.hide(); 2893 theControl.removeClass("input-append"); 2894 theInputControl.width(inputControlWidthNoClear); 2895 } else { 2896 theInputControl.width(inputControlWidthWithClear); 2897 theClearButton.show(); 2898 theControl.addClass("input-append"); 2899 } 2900 }; 2901 2902 // set placeholder text 2903 theInputControl.attr('placeholder', placeholderText); 2904 2905 theInputControl.unbind('keyup').bind('keyup', function() { 2906 if (sPreviousInput !== theInputControl.val()) { 2907 window.clearTimeout(timerId); 2908 sPreviousInput = theInputControl.val(); 2909 timerId = window.setTimeout(function() { 2910 changeCallback.call((callbackScope || window), theInputControl.val()); 2911 theInputControl[0].focus(); 2912 }, callbackDelay); 2913 } 2914 2915 toggleClearButton(); 2916 }); 2917 2918 theClearButton.bind('click', function() { 2919 theInputControl.val(''); 2920 changeCallback.call((callbackScope || window), ''); 2921 2922 toggleClearButton(); 2923 theInputControl[0].focus(); // jquery and dojo on the same page break jquery's focus() method 2924 }); 2925 2926 theInputControl.val(""); 2927 toggleClearButton(); 2928 }, 2929 2930 DataTables: { 2931 /** @private */ 2932 createDataTable: function (options, dataTableOptions) { 2933 var grid, 2934 table = $('<table cellpadding="0" cellspacing="0" border="0" class="finesse"><thead><tr></tr></thead></table>'), 2935 headerRow = table.find("tr"), 2936 defaultOptions = { 2937 "aaData": [], 2938 "bPaginate": false, 2939 "bLengthChange": false, 2940 "bFilter": false, 2941 "bInfo": false, 2942 "sScrollY": "176", 2943 "oLanguage": { 2944 "sEmptyTable": "", 2945 "sZeroRecords": "" 2946 } 2947 }, 2948 gridOptions = $.extend({}, defaultOptions, dataTableOptions), 2949 columnDefs = [], 2950 columnFormatter; 2951 2952 // Create a header cell for each column, and set up the datatable definition for the column 2953 $(options.columns).each(function (index, column) { 2954 headerRow.append($("<th></th>")); 2955 columnDefs[index] = { 2956 "mData": column.propertyName, 2957 "sTitle": column.columnHeader, 2958 "sWidth": column.width, 2959 "aTargets": [index], 2960 "bSortable": column.sortable, 2961 "bVisible": column.visible, 2962 "mRender": column.render 2963 }; 2964 if (typeof(column.renderFunction) === "function") 2965 { 2966 /** @ignore **/ 2967 columnDefs[index].mRender = /** @ignore **/ function (value, type, dataObject) { 2968 var returnValue; 2969 2970 //Apply column render logic to value before applying extra render function 2971 if (typeof(column.render) === "function") 2972 { 2973 value = column.render.call(value, value, value); 2974 } 2975 2976 if (typeof(type) === "string") 2977 { 2978 switch (type) 2979 { 2980 case "undefined": 2981 case "sort": 2982 returnValue = value; 2983 break; 2984 case "set": 2985 throw new Error("Unsupported set data in Finesse Grid"); 2986 case "filter": 2987 case "display": 2988 case "type": 2989 returnValue = column.renderFunction.call(dataObject, value, dataObject); 2990 break; 2991 default: 2992 break; 2993 } 2994 } 2995 else 2996 { 2997 throw new Error("type param not specified in Finesse DataTable mData"); 2998 } 2999 3000 return returnValue; 3001 }; 3002 } 3003 }); 3004 gridOptions.aoColumnDefs = columnDefs; 3005 3006 // Set the height 3007 if (typeof(options.bodyHeightPixels) !== "undefined" && options.bodyHeightPixels !== null) 3008 { 3009 gridOptions.sScrollY = options.bodyHeightPixels + "px"; 3010 } 3011 3012 // Place it into the DOM 3013 if (typeof(options.container) !== "undefined" && options.container !== null) 3014 { 3015 $(options.container).append(table); 3016 } 3017 3018 // Create the DataTable 3019 table.dataTable(gridOptions); 3020 3021 return table; 3022 } 3023 }, 3024 3025 /** 3026 * @private 3027 * Sets a dojo button to the specified disable state, removing it from 3028 * the tab order if disabling, and restoring it to the tab order if enabling. 3029 * @param {Object} dojoButton Reference to the dijit.form.Button object. This is not the DOM element. 3030 * @param {bool} disabled 3031 */ 3032 setDojoButtonDisabledAttribute: function (dojoButton, disabled) { 3033 var labelNode, 3034 tabIndex; 3035 3036 dojoButton.set("disabled", disabled); 3037 3038 // Remove the tabindex attribute on disabled buttons, store it, 3039 // and replace it when it becomes enabled again 3040 labelNode = $("#" + dojoButton.id + "_label"); 3041 if (disabled) 3042 { 3043 labelNode.data("finesse:dojoButton:tabIndex", labelNode.attr("tabindex")); 3044 labelNode.removeAttr("tabindex"); 3045 } 3046 else 3047 { 3048 tabIndex = labelNode.data("finesse:dojoButton:tabIndex"); 3049 if (typeof(tabIndex) === "string") 3050 { 3051 labelNode.attr("tabindex", Number(tabIndex)); 3052 } 3053 } 3054 }, 3055 3056 /** 3057 * @private 3058 * Use this utility to disable the tab stop for a Dojo Firebug iframe within a gadget. 3059 * 3060 * Dojo sometimes adds a hidden iframe for enabling a firebug lite console in older 3061 * browsers. Unfortunately, this adds an additional tab stop that impacts accessibility. 3062 */ 3063 disableTabStopForDojoFirebugIframe: function () { 3064 var iframe = $("iframe[src*='loadFirebugConsole']"); 3065 3066 if ((iframe.length) && (iframe.attr("tabIndex") !== "-1")) { 3067 iframe.attr('tabIndex', '-1'); 3068 } 3069 }, 3070 3071 /** 3072 * @private 3073 * Measures the given text using the supplied fontFamily and fontSize 3074 * @param {string} text text to measure 3075 * @param {string} fontFamily 3076 * @param {string} fontSize 3077 * @return {number} pixel width 3078 */ 3079 measureText: function (text, fontFamily, fontSize) { 3080 var width, 3081 element = $("<div></div>").text(text).css({ 3082 "fontSize": fontSize, 3083 "fontFamily": fontFamily 3084 }).addClass("offscreen").appendTo(document.body); 3085 3086 width = element.width(); 3087 element.remove(); 3088 3089 return width; 3090 }, 3091 3092 /** 3093 * Adjusts the gadget height. Shindig's gadgets.window.adjustHeight fails when 3094 * needing to resize down in IE. This gets around that by calculating the height 3095 * manually and passing it in. 3096 * @return {undefined} 3097 */ 3098 "adjustGadgetHeight": function () { 3099 var bScrollHeight = $("body").height() + 20; 3100 gadgets.window.adjustHeight(bScrollHeight); 3101 }, 3102 3103 /** 3104 * Private helper method for converting a javascript object to xml, where the values of the elements are 3105 * appropriately escaped for XML. 3106 * This is a simple implementation that does not implement cdata or attributes. It is also 'unformatted' in that 3107 * there is no whitespace between elements. 3108 * @param object The javascript object to convert to XML. 3109 * @returns The XML string. 3110 * @private 3111 */ 3112 _json2xmlWithEscape: function(object) { 3113 var that = this, 3114 xml = "", 3115 m, 3116 /** @private **/ 3117 toXmlHelper = function(value, name) { 3118 var xml = "", 3119 i, 3120 m; 3121 if (value instanceof Array) { 3122 for (i = 0; i < value.length; ++i) { 3123 xml += toXmlHelper(value[i], name); 3124 } 3125 } 3126 else if (typeof value === "object") { 3127 xml += "<" + name + ">"; 3128 for (m in value) { 3129 if (value.hasOwnProperty(m)) { 3130 xml += toXmlHelper(value[m], m); 3131 } 3132 } 3133 xml += "</" + name + ">"; 3134 } 3135 else { 3136 // is a leaf node 3137 xml += "<" + name + ">" + that.translateHTMLEntities(value.toString(), true) + 3138 "</" + name + ">"; 3139 } 3140 return xml; 3141 }; 3142 for (m in object) { 3143 if (object.hasOwnProperty(m)) { 3144 xml += toXmlHelper(object[m], m); 3145 } 3146 } 3147 return xml; 3148 }, 3149 3150 /** 3151 * Private method for returning a sanitized version of the user agent string. 3152 * @returns the user agent string, but sanitized! 3153 * @private 3154 */ 3155 getSanitizedUserAgentString: function () { 3156 return this.translateXMLEntities(navigator.userAgent, true); 3157 }, 3158 3159 /** 3160 * Use JQuery's implementation of Promises (Deferred) to execute code when 3161 * multiple async processes have finished. An example use: 3162 * 3163 * var asyncProcess1 = $.Deferred(), 3164 * asyncProcess2 = $.Deferred(); 3165 * 3166 * finesse.utilities.Utilities.whenAllDone(asyncProcess1, asyncProcess2) // WHEN both asyncProcess1 and asyncProcess2 are resolved or rejected ... 3167 * .then( 3168 * // First function passed to then() is called when all async processes are complete, regardless of errors 3169 * function () { 3170 * console.log("all processes completed"); 3171 * }, 3172 * // Second function passed to then() is called if any async processed threw an exception 3173 * function (failures) { // Array of failure messages 3174 * console.log("Number of failed async processes: " + failures.length); 3175 * }); 3176 * 3177 * Found at: 3178 * http://stackoverflow.com/a/15094263/1244030 3179 * 3180 * Pass in any number of $.Deferred instances. 3181 * @returns {Object} 3182 */ 3183 whenAllDone: function () { 3184 var deferreds = [], 3185 result = $.Deferred(); 3186 3187 $.each(arguments, function(i, current) { 3188 var currentDeferred = $.Deferred(); 3189 current.then(function() { 3190 currentDeferred.resolve(false, arguments); 3191 }, function() { 3192 currentDeferred.resolve(true, arguments); 3193 }); 3194 deferreds.push(currentDeferred); 3195 }); 3196 3197 $.when.apply($, deferreds).then(function() { 3198 var failures = [], 3199 successes = []; 3200 3201 $.each(arguments, function(i, args) { 3202 // If we resolved with `true` as the first parameter 3203 // we have a failure, a success otherwise 3204 var target = args[0] ? failures : successes, 3205 data = args[1]; 3206 // Push either all arguments or the only one 3207 target.push(data.length === 1 ? data[0] : args); 3208 }); 3209 3210 if(failures.length) { 3211 return result.reject.apply(result, failures); 3212 } 3213 3214 return result.resolve.apply(result, successes); 3215 }); 3216 3217 return result; 3218 }, 3219 3220 /** 3221 * Private method to format a given string by replacing the place holders (like {0}) with the 3222 * corresponding supplied arguments. For example, calling this method as follows: 3223 * formatString("Hello {0}, {1} rocks!", "there", "Finesse"); 3224 * results in the following output string: 3225 * "Hello there, Finesse rocks!" 3226 * 3227 * @param {String} format - a string that holds the place holder to be replaced 3228 * 3229 * @returns {String} - string where the place holders are replaced with respective values 3230 * @private 3231 */ 3232 formatString : function(format) { 3233 if (!format || arguments.length <= 1) { 3234 return format; 3235 } 3236 3237 var i, retStr = format; 3238 for (i = 1; i < arguments.length; i += 1) { 3239 retStr = retStr.replace(new RegExp("\\{" + (i - 1) + "\\}", "g"), arguments[i]); 3240 } 3241 3242 // in order to fix French text with single quotes in it, we need to replace \' with ' 3243 return retStr.replace(/\\'/g, "'"); 3244 }, 3245 3246 /** 3247 * @private 3248 * This method is used to make the scheme and port secure when the admin gadgets are loaded in some other container. 3249 * @param {object} config - The config which is passed to client services to handle restRequest in secure or non secure mode. 3250 * 3251 * @returns {object} config - The modified config if scheme was https. 3252 */ 3253 setSchemeAndPortForHttps : function(config) { 3254 var scheme = window.location.protocol; 3255 if(scheme === "https:") { 3256 config["restScheme"] = "https"; 3257 config["localhostPort"] = "8445"; 3258 } 3259 3260 return config; 3261 }, 3262 3263 /** 3264 * 3265 * @private 3266 * If the browser tab is inactive, any calls to javascript function setTimeout(0,...) will not be executed 3267 * immediately since browser throttles events from background tabs. The delay sometimes could be about 5 seconds. 3268 * This utilitiy function provides a wrapper around ES5 promise to resolve this issue. 3269 * 3270 * @param {function} funcOther Target function that will be invoked at the end of browser events. 3271 * 3272 * @returns {Object} The promise 3273 */ 3274 executeAsync: function(funcOther) { 3275 var promise = new Promise( 3276 function( resolve, reject ) { 3277 resolve(); 3278 } 3279 ); 3280 promise.then( funcOther ); 3281 3282 return promise; 3283 }, 3284 3285 /** 3286 * Extract hostname from a given url string 3287 */ 3288 extractHostname : function (url) { 3289 var hostname; 3290 //find & remove protocol (http, ftp, etc.) and get hostname 3291 3292 if (url.indexOf("//") > -1) { 3293 hostname = url.split('/')[2]; 3294 } 3295 else { 3296 hostname = url.split('/')[0]; 3297 } 3298 3299 //find & remove port number 3300 hostname = hostname.split(':')[0]; 3301 //find & remove "?" 3302 hostname = hostname.split('?')[0]; 3303 3304 return hostname; 3305 }, 3306 3307 /** 3308 * Get the value of a querystring 3309 * @param {String} field The field to get the value of 3310 * @param {String} url The URL to get the value from (optional) 3311 * @return {String} The field value 3312 */ 3313 getQueryString : function ( field, url ) { 3314 var href = url ? url : window.location.href; 3315 var reg = new RegExp( '[?&]' + field + '=([^]*)', 'i' ); 3316 var string = reg.exec(href); 3317 return string ? decodeURIComponent(string[1]) : ''; 3318 }, 3319 3320 }; 3321 3322 window.finesse = window.finesse || {}; 3323 window.finesse.utilities = window.finesse.utilities || {}; 3324 window.finesse.utilities.Utilities = Utilities; 3325 3326 return Utilities; 3327 }); 3328 3329 /** 3330 * Allows gadgets to call the log function to publish client logging messages over the hub. 3331 * 3332 * @requires OpenAjax 3333 */ 3334 /** @private */ 3335 define('cslogger/ClientLogger',[], function () { 3336 3337 var ClientLogger = ( function () { /** @lends finesse.cslogger.ClientLogger.prototype */ 3338 var _hub, _logTopic, _originId, _sessId, _host, 3339 MONTH = { 0 : "Jan", 1 : "Feb", 2 : "Mar", 3 : "Apr", 4 : "May", 5 : "Jun", 3340 6 : "Jul", 7 : "Aug", 8 : "Sep", 9 : "Oct", 10 : "Nov", 11 : "Dec"}, 3341 3342 /** 3343 * Gets timestamp drift stored in sessionStorage 3344 * @returns drift in seconds if it is set in sessionStorage otherwise returns undefined. 3345 * @private 3346 */ 3347 getTsDrift = function() { 3348 if (window.sessionStorage.getItem('clientTimestampDrift') !== null) { 3349 return parseInt(window.sessionStorage.getItem('clientTimestampDrift'), 10); 3350 } 3351 else { 3352 return undefined; 3353 } 3354 }, 3355 3356 /** 3357 * Sets timestamp drift in sessionStorage 3358 * @param delta is the timestamp drift between server.and client. 3359 * @private 3360 */ 3361 setTsDrift = function(delta) { 3362 window.sessionStorage.setItem('clientTimestampDrift', delta.toString()); 3363 }, 3364 3365 /** 3366 * Gets Finesse server timezone offset from GMT in seconds 3367 * @returns offset in seconds if it is set in sessionStorage otherwise returns undefined. 3368 * @private 3369 */ 3370 getServerOffset = function() { 3371 if (window.sessionStorage.getItem('serverTimezoneOffset') !== null) { 3372 return parseInt(window.sessionStorage.getItem('serverTimezoneOffset'), 10); 3373 } 3374 else { 3375 return undefined; 3376 } 3377 }, 3378 3379 /** 3380 * Sets server timezone offset 3381 * @param sec is the server timezone GMT offset in seconds. 3382 * @private 3383 */ 3384 setServerOffset = function(sec) { 3385 window.sessionStorage.setItem('serverTimezoneOffset', sec.toString()); 3386 }, 3387 3388 /** 3389 * Checks to see if we have a console. 3390 * @returns Whether the console object exists. 3391 * @private 3392 */ 3393 hasConsole = function () { 3394 try { 3395 if (window.console !== undefined) { 3396 return true; 3397 } 3398 } 3399 catch (err) { 3400 // ignore and return false 3401 } 3402 3403 return false; 3404 }, 3405 3406 /** 3407 * Gets a short form (6 character) session ID from sessionStorage 3408 * @private 3409 */ 3410 getSessId = function() { 3411 if (!_sessId) { 3412 //when _sessId not defined yet, initiate it 3413 if (window.sessionStorage.getItem('enableLocalLog') === 'true') { 3414 _sessId= " "+window.sessionStorage.getItem('finSessKey'); 3415 } 3416 else { 3417 _sessId=" "; 3418 } 3419 } 3420 return _sessId; 3421 }, 3422 3423 /** 3424 * Pads a single digit number for display purposes (e.g. '4' shows as '04') 3425 * @param num is the number to pad to 2 digits 3426 * @returns a two digit padded string 3427 * @private 3428 */ 3429 padTwoDigits = function (num) 3430 { 3431 return (num < 10) ? '0' + num : num; 3432 }, 3433 3434 /** 3435 * Pads a single digit number for display purposes (e.g. '4' shows as '004') 3436 * @param num is the number to pad to 3 digits 3437 * @returns a three digit padded string 3438 * @private 3439 */ 3440 padThreeDigits = function (num) 3441 { 3442 if (num < 10) 3443 { 3444 return '00'+num; 3445 } 3446 else if (num < 100) 3447 { 3448 return '0'+num; 3449 } 3450 else 3451 { 3452 return num; 3453 } 3454 }, 3455 3456 /** 3457 * Compute the "hour" 3458 * 3459 * @param s is time in seconds 3460 * @returns {String} which is the hour 3461 * @private 3462 */ 3463 ho = function (s) { 3464 return ((s/60).toString()).split(".")[0]; 3465 }, 3466 3467 /** 3468 * Gets local timezone offset string. 3469 * 3470 * @param t is the time in seconds 3471 * @param s is the separator character between hours and minutes, e.g. ':' 3472 * @returns {String} is local timezone GMT offset in the following format: [+|-]hh[|:]MM 3473 * @private 3474 */ 3475 getGmtOffString = function (min,s) { 3476 var t, sign; 3477 if (min<0) { 3478 t = -min; 3479 sign = "-"; 3480 } 3481 else { 3482 t = min; 3483 sign = "+"; 3484 } 3485 3486 if (s===':') { 3487 return sign+padTwoDigits(ho(t))+s+padTwoDigits(t%60); 3488 } 3489 else { 3490 return sign+padTwoDigits(ho(t))+padTwoDigits(t%60); 3491 } 3492 }, 3493 3494 /** 3495 * Gets short form of a month name in English 3496 * 3497 * @param monthNum is zero-based month number 3498 * @returns {String} is short form of month name in English 3499 * @private 3500 */ 3501 getMonthShortStr = function (monthNum) { 3502 var result; 3503 try { 3504 result = MONTH[monthNum]; 3505 } 3506 catch (err) { 3507 if (hasConsole()) { 3508 window.console.log("Month must be between 0 and 11"); 3509 } 3510 } 3511 return result; 3512 }, 3513 3514 /** 3515 * Gets a timestamp. 3516 * @param aDate is a javascript Date object 3517 * @returns {String} is a timestamp in the following format: yyyy-mm-ddTHH:MM:ss.SSS [+|-]HH:MM 3518 * @private 3519 */ 3520 getDateTimeStamp = function (aDate) 3521 { 3522 var date, off, timeStr; 3523 if (aDate === null) { 3524 date = new Date(); 3525 } 3526 else { 3527 date = aDate; 3528 } 3529 off = -1*date.getTimezoneOffset(); 3530 timeStr = date.getFullYear().toString() + "-" + 3531 padTwoDigits(date.getMonth()+1) + "-" + 3532 padTwoDigits (date.getDate()) + "T"+ 3533 padTwoDigits(date.getHours()) + ":" + 3534 padTwoDigits(date.getMinutes()) + ":" + 3535 padTwoDigits(date.getSeconds())+"." + 3536 padThreeDigits(date.getMilliseconds()) + " "+ 3537 getGmtOffString(off, ':'); 3538 3539 return timeStr; 3540 }, 3541 3542 /** 3543 * Gets drift-adjusted timestamp. 3544 * @param aTimestamp is a timestamp in milliseconds 3545 * @param drift is a timestamp drift in milliseconds 3546 * @param serverOffset is a timezone GMT offset in minutes 3547 * @returns {String} is a timestamp in the Finesse server log format, e.g. Jan 07 2104 HH:MM:ss.SSS -0500 3548 * @private 3549 */ 3550 getDriftedDateTimeStamp = function (aTimestamp, drift, serverOffset) 3551 { 3552 var date, timeStr, localOffset; 3553 if (aTimestamp === null) { 3554 return "--- -- ---- --:--:--.--- -----"; 3555 } 3556 else if (drift === undefined || serverOffset === undefined) { 3557 if (hasConsole()) { 3558 window.console.log("drift or serverOffset must be a number"); 3559 } 3560 return "--- -- ---- --:--:--.--- -----"; 3561 } 3562 else { 3563 //need to get a zone diff in minutes 3564 localOffset = (new Date()).getTimezoneOffset(); 3565 date = new Date(aTimestamp+drift+(localOffset+serverOffset)*60000); 3566 timeStr = getMonthShortStr(date.getMonth()) + " "+ 3567 padTwoDigits (date.getDate())+ " "+ 3568 date.getFullYear().toString() + " "+ 3569 padTwoDigits(date.getHours()) + ":" + 3570 padTwoDigits(date.getMinutes()) + ":" + 3571 padTwoDigits(date.getSeconds())+"." + 3572 padThreeDigits(date.getMilliseconds())+" "+ 3573 getGmtOffString(serverOffset, ''); 3574 return timeStr; 3575 } 3576 }, 3577 3578 /** 3579 * Logs a message to a hidden textarea element on the page 3580 * 3581 * @param msg is the string to log. 3582 * @private 3583 */ 3584 writeToLogOutput = function (msg) { 3585 var logOutput = document.getElementById("finesseLogOutput"); 3586 3587 if (logOutput === null) 3588 { 3589 logOutput = document.createElement("textarea"); 3590 logOutput.id = "finesseLogOutput"; 3591 logOutput.style.display = "none"; 3592 document.body.appendChild(logOutput); 3593 } 3594 3595 if (logOutput.value === "") 3596 { 3597 logOutput.value = msg; 3598 } 3599 else 3600 { 3601 logOutput.value = logOutput.value + "\n" + msg; 3602 } 3603 }, 3604 3605 /* 3606 * Logs a message to console 3607 * @param str is the string to log. * @private 3608 */ 3609 logToConsole = function (str, error) 3610 { 3611 var now, msg, timeStr, driftedTimeStr, sessKey=getSessId(); 3612 now = new Date(); 3613 timeStr = getDateTimeStamp(now); 3614 if (getTsDrift() !== undefined) { 3615 driftedTimeStr = getDriftedDateTimeStamp(now.getTime(), getTsDrift(), getServerOffset()); 3616 } 3617 else { 3618 driftedTimeStr = getDriftedDateTimeStamp(null, 0, 0); 3619 } 3620 msg = timeStr + ":"+sessKey+": "+ _host + ": "+driftedTimeStr+ ": " + str; 3621 // Log to console 3622 if (hasConsole()) { 3623 if (error) { 3624 window.console.error(msg, error); 3625 } else { 3626 window.console.log(msg); 3627 } 3628 } 3629 3630 //Uncomment to print logs to hidden textarea. 3631 //writeToLogOutput(msg); 3632 3633 return msg; 3634 }; 3635 return { 3636 3637 /** 3638 * Publishes a Log Message over the hub. 3639 * 3640 * @param {String} message 3641 * The string to log. 3642 * @param {Object} error - optional 3643 * Javascript error object 3644 * @example 3645 * _clientLogger.log("This is some important message for MyGadget"); 3646 * 3647 */ 3648 log : function (message, error) { 3649 if(_hub) { 3650 _hub.publish(_logTopic, logToConsole(_originId + message, error)); 3651 } 3652 }, 3653 3654 /** 3655 * @class 3656 * Allows gadgets to call the log function to publish client logging messages over the hub. 3657 * 3658 * @constructs 3659 */ 3660 _fakeConstuctor: function () { 3661 /* This is here so we can document init() as a method rather than as a constructor. */ 3662 }, 3663 3664 /** 3665 * Initiates the client logger with a hub a gadgetId and gadget's config object. 3666 * @param {Object} hub 3667 * The hub to communicate with. 3668 * @param {String} gadgetId 3669 * A unique string to identify which gadget is doing the logging. 3670 * @param {finesse.gadget.Config} config 3671 * The config object used to get host name for that thirdparty gadget 3672 * @example 3673 * var _clientLogger = finesse.cslogger.ClientLogger; 3674 * _clientLogger.init(gadgets.Hub, "MyGadgetId", config); 3675 * 3676 */ 3677 init: function (hub, gadgetId, config) { 3678 _hub = hub; 3679 _logTopic = "finesse.clientLogging." + gadgetId; 3680 _originId = gadgetId + " : "; 3681 if ((config === undefined) || (config === "undefined")) 3682 { 3683 _host = ((finesse.container && finesse.container.Config && finesse.container.Config.host)?finesse.container.Config.host : "?.?.?.?"); 3684 } 3685 else 3686 { 3687 _host = ((config && config.host)?config.host : "?.?.?.?"); 3688 } 3689 } 3690 }; 3691 }()); 3692 3693 window.finesse = window.finesse || {}; 3694 window.finesse.cslogger = window.finesse.cslogger || {}; 3695 window.finesse.cslogger.ClientLogger = ClientLogger; 3696 3697 finesse = finesse || {}; 3698 /** @namespace Supports writing messages to a central log. */ 3699 finesse.cslogger = finesse.cslogger || {}; 3700 3701 return ClientLogger; 3702 }); 3703 3704 /** The following comment is to prevent jslint errors about 3705 * using variables before they are defined. 3706 */ 3707 /*global finesse*/ 3708 3709 /** 3710 * Initiated by the Master to create a shared BOSH connection. 3711 * 3712 * @requires Utilities 3713 */ 3714 3715 /** 3716 * @class 3717 * Establishes a shared event connection by creating a communication tunnel 3718 * with the notification server and consume events which could be published. 3719 * Public functions are exposed to register to the connection status information 3720 * and events. 3721 * @constructor 3722 * @param {String} host 3723 * The host name/ip of the Finesse server. 3724 * @throws {Error} If required constructor parameter is missing. 3725 */ 3726 /** @private */ 3727 define('clientservices/MasterTunnel',["utilities/Utilities", "cslogger/ClientLogger"], function (Utilities, ClientLogger) { 3728 var MasterTunnel = function (host, scheme) { 3729 if (typeof host !== "string" || host.length === 0) { 3730 throw new Error("Required host parameter missing."); 3731 } 3732 3733 var 3734 3735 /** 3736 * Flag to indicate whether the tunnel frame is loaded. 3737 * @private 3738 */ 3739 _isTunnelLoaded = false, 3740 3741 /** 3742 * Short reference to the Finesse utility. 3743 * @private 3744 */ 3745 _util = Utilities, 3746 3747 /** 3748 * The URL with host and port to the Finesse server. 3749 * @private 3750 */ 3751 _tunnelOrigin, 3752 3753 /** 3754 * Location of the tunnel HTML URL. 3755 * @private 3756 */ 3757 _tunnelURL, 3758 3759 /** 3760 * The port on which to connect to the Finesse server to load the eventing resources. 3761 * @private 3762 */ 3763 _tunnelOriginPort, 3764 3765 /** 3766 * Flag to indicate whether we have processed the tunnel config yet. 3767 * @private 3768 */ 3769 _isTunnelConfigInit = false, 3770 3771 /** 3772 * The tunnel frame window object. 3773 * @private 3774 */ 3775 _tunnelFrame, 3776 3777 /** 3778 * The handler registered with the object to be invoked when an event is 3779 * delivered by the notification server. 3780 * @private 3781 */ 3782 _eventHandler, 3783 3784 /** 3785 * The handler registered with the object to be invoked when presence is 3786 * delivered by the notification server. 3787 * @private 3788 */ 3789 _presenceHandler, 3790 3791 /** 3792 * The handler registered with the object to be invoked when the BOSH 3793 * connection has changed states. The object will contain the "status" 3794 * property and a "resourceID" property only if "status" is "connected". 3795 * @private 3796 */ 3797 _connInfoHandler, 3798 3799 /** 3800 * The last connection status published by the JabberWerx library. 3801 * @private 3802 */ 3803 _statusCache, 3804 3805 /** 3806 * The last event sent by notification server. 3807 * @private 3808 */ 3809 _eventCache, 3810 3811 /** 3812 * The ID of the user logged into notification server. 3813 * @private 3814 */ 3815 _id, 3816 3817 /** 3818 * The domain of the XMPP server, representing the portion of the JID 3819 * following '@': userid@domain.com 3820 * @private 3821 */ 3822 _xmppDomain, 3823 3824 /** 3825 * The password of the user logged into notification server. 3826 * @private 3827 */ 3828 _password, 3829 3830 /** 3831 * The jid of the pubsub service on the XMPP server 3832 * @private 3833 */ 3834 _pubsubDomain, 3835 3836 /** 3837 * The resource to use for the BOSH connection. 3838 * @private 3839 */ 3840 _resource, 3841 3842 /** 3843 * The resource ID identifying the client device (that we receive from the server). 3844 * @private 3845 */ 3846 _resourceID, 3847 3848 /** 3849 * The xmpp connection protocol type. 3850 * @private 3851 */ 3852 _notificationConnectionType, 3853 3854 /** 3855 * The different types of messages that could be sent to the parent frame. 3856 * The types here should be understood by the parent frame and used to 3857 * identify how the message is formatted. 3858 * @private 3859 */ 3860 _TYPES = { 3861 EVENT: 0, 3862 ID: 1, 3863 PASSWORD: 2, 3864 RESOURCEID: 3, 3865 STATUS: 4, 3866 XMPPDOMAIN: 5, 3867 PUBSUBDOMAIN: 6, 3868 SUBSCRIBE: 7, 3869 UNSUBSCRIBE: 8, 3870 PRESENCE: 9, 3871 CONNECT_REQ: 10, 3872 DISCONNECT_REQ: 11, 3873 NOTIFICATION_CONNECTION_TYPE: 12, 3874 LOGGING: 13, 3875 SUBSCRIPTIONS_REQ: 14 3876 }, 3877 3878 _handlers = { 3879 subscribe: {}, 3880 unsubscribe: {} 3881 }, 3882 3883 3884 /** 3885 * Create a connection info object. 3886 * @returns {Object} 3887 * A connection info object containing a "status" and "resourceID". 3888 * @private 3889 */ 3890 _createConnInfoObj = function () { 3891 return { 3892 status: _statusCache, 3893 resourceID: _resourceID 3894 }; 3895 }, 3896 3897 /** 3898 * Utility function which sends a message to the dynamic tunnel frame 3899 * event frame formatted as follows: "type|message". 3900 * @param {Number} type 3901 * The category type of the message. 3902 * @param {String} message 3903 * The message to be sent to the tunnel frame. 3904 * @private 3905 */ 3906 _sendMessage = function (type, message) { 3907 message = type + "|" + message; 3908 _util.sendMessage(message, _tunnelFrame, _tunnelOrigin); 3909 }, 3910 3911 /** 3912 * Utility to process the response of a subscribe request from 3913 * the tunnel frame, then invoking the stored callback handler 3914 * with the respective data (error, when applicable) 3915 * @param {String} data 3916 * The response in the format of "node[|error]" 3917 * @private 3918 */ 3919 _processSubscribeResponse = function (data) { 3920 var dataArray = data.split("|"), 3921 node = dataArray[0], 3922 err; 3923 3924 //Error is optionally the second item in the array 3925 if (dataArray.length) { 3926 err = dataArray[1]; 3927 } 3928 3929 // These response handlers are short lived and should be removed and cleaned up immediately after invocation. 3930 if (_handlers.subscribe[node]) { 3931 _handlers.subscribe[node](err); 3932 delete _handlers.subscribe[node]; 3933 } 3934 }, 3935 3936 /** 3937 * Utility to process the response of an unsubscribe request from 3938 * the tunnel frame, then invoking the stored callback handler 3939 * with the respective data (error, when applicable) 3940 * @param {String} data 3941 * The response in the format of "node[|error]" 3942 * @private 3943 */ 3944 _processUnsubscribeResponse = function (data) { 3945 var dataArray = data.split("|"), 3946 node = dataArray[0], 3947 err; 3948 3949 //Error is optionally the second item in the array 3950 if (dataArray.length) { 3951 err = dataArray[1]; 3952 } 3953 3954 // These response handlers are short lived and should be removed and cleaned up immediately after invocation. 3955 if (_handlers.unsubscribe[node]) { 3956 _handlers.unsubscribe[node](err); 3957 delete _handlers.unsubscribe[node]; 3958 } 3959 }, 3960 3961 3962 /** 3963 * Utility to process the reponse of an getSubscriptions request from 3964 * the tunnel frame, then invoking the stored callback handler 3965 * with the respective data (error, when applicable) 3966 * @param {String} data 3967 * The response containing subscriptions 3968 * @private 3969 */ 3970 _processAllSubscriptionsResponse = function (data) { 3971 var dataArray = data.split("|"), 3972 content = dataArray[0], 3973 err; 3974 3975 //Error is optionally the second item in the array 3976 if (dataArray.length) { 3977 err = dataArray[1]; 3978 } 3979 3980 // These response handlers are short lived and should be removed and cleaned up immediately after invocation. 3981 if (_handlers.subscriptions) { 3982 _handlers.subscriptions(content, err); 3983 delete _handlers.subscriptions; 3984 } 3985 }, 3986 3987 /** 3988 * Handler for messages delivered by window.postMessage. Listens for events 3989 * published by the notification server, connection status published by 3990 * the JabberWerx library, and the resource ID created when the BOSH 3991 * connection has been established. 3992 * @param {Object} e 3993 * The message object as provided by the window.postMessage feature. 3994 * @private 3995 */ 3996 _messageHandler = function (e) { 3997 var 3998 3999 //Extract the message type and message data. The expected format is 4000 //"type|data" where type is a number represented by the TYPES object. 4001 delimPos = e.data.indexOf("|"), 4002 type = Number(e.data.substr(0, delimPos)), 4003 data = e.data.substr(delimPos + 1); 4004 4005 //Accepts messages and invoke the correct registered handlers. 4006 switch (type) { 4007 case _TYPES.EVENT: 4008 _eventCache = data; 4009 if (typeof _eventHandler === "function") { 4010 _eventHandler(data); 4011 } 4012 break; 4013 case _TYPES.STATUS: 4014 _statusCache = data; 4015 4016 //A "loaded" status means that the frame is ready to accept 4017 //credentials for establishing a BOSH connection. 4018 if (data === "loaded") { 4019 _isTunnelLoaded = true; 4020 if(_resource) { 4021 _sendMessage(_TYPES.RESOURCEID, _resource); 4022 } 4023 4024 _sendMessage(_TYPES.NOTIFICATION_CONNECTION_TYPE, _notificationConnectionType); 4025 _sendMessage(_TYPES.ID, _id); 4026 _sendMessage(_TYPES.XMPPDOMAIN, _xmppDomain); 4027 _sendMessage(_TYPES.PASSWORD, _password); 4028 _sendMessage(_TYPES.PUBSUBDOMAIN, _pubsubDomain); 4029 4030 4031 } else if (typeof _connInfoHandler === "function") { 4032 _connInfoHandler(_createConnInfoObj()); 4033 } 4034 break; 4035 case _TYPES.RESOURCEID: 4036 _resourceID = data; 4037 break; 4038 case _TYPES.SUBSCRIBE: 4039 _processSubscribeResponse(data); 4040 break; 4041 case _TYPES.UNSUBSCRIBE: 4042 _processUnsubscribeResponse(data); 4043 break; 4044 case _TYPES.SUBSCRIPTIONS_REQ: 4045 _processAllSubscriptionsResponse(data); 4046 break; 4047 case _TYPES.PRESENCE: 4048 if (typeof _presenceHandler === "function") { 4049 _presenceHandler(data); 4050 } 4051 break; 4052 case _TYPES.LOGGING: 4053 ClientLogger.log(data); 4054 break; 4055 default: 4056 break; 4057 } 4058 }, 4059 4060 /** 4061 * Initialize the tunnel config so that the url can be http or https with the appropriate port 4062 * @private 4063 */ 4064 _initTunnelConfig = function () { 4065 if (_isTunnelConfigInit === true) { 4066 return; 4067 } 4068 4069 //Initialize tunnel origin 4070 //Determine tunnel origin based on host and scheme 4071 _tunnelOriginPort = (scheme && scheme.indexOf("https") !== -1) ? "7443" : "7071"; 4072 if (scheme) { 4073 _tunnelOrigin = scheme + "://" + host + ":" + _tunnelOriginPort; 4074 } else { 4075 _tunnelOrigin = "http://" + host + ":" + _tunnelOriginPort; 4076 } 4077 _tunnelURL = _tunnelOrigin + "/tunnel/"; 4078 4079 _isTunnelConfigInit = true; 4080 }, 4081 4082 /** 4083 * Create the tunnel iframe which establishes the shared BOSH connection. 4084 * Messages are sent across frames using window.postMessage. 4085 * @private 4086 */ 4087 _createTunnel = function () { 4088 var tunnelID = ((self === parent) ? "tunnel-frame" : "autopilot-tunnel-frame"), 4089 iframe = document.createElement("iframe"); 4090 iframe.style.display = "none"; 4091 iframe.setAttribute("id", tunnelID); 4092 iframe.setAttribute("name", tunnelID); 4093 iframe.setAttribute("src", _tunnelURL); 4094 document.body.appendChild(iframe); 4095 _tunnelFrame = window.frames[tunnelID]; 4096 }; 4097 4098 /** 4099 * Sends a message via postmessage to the EventTunnel to attempt to connect to the XMPP server 4100 * @private 4101 */ 4102 this.makeConnectReq = function () { 4103 _sendMessage(_TYPES.PASSWORD, _password); 4104 }; 4105 4106 /** 4107 * @private 4108 * Returns the host of the Finesse server. 4109 * @returns {String} 4110 * The host specified during the creation of the object. 4111 */ 4112 this.getHost = function () { 4113 return host; 4114 }; 4115 4116 /** 4117 * @private 4118 * The resource ID of the user who is logged into the notification server. 4119 * @returns {String} 4120 * The resource ID generated by the notification server. 4121 */ 4122 this.getResourceID = function () { 4123 return _resourceID; 4124 }; 4125 4126 /** 4127 * @private 4128 * Indicates whether the tunnel frame is loaded. 4129 * @returns {Boolean} 4130 * True if the tunnel frame is loaded, false otherwise. 4131 */ 4132 this.isTunnelLoaded = function () { 4133 return _isTunnelLoaded; 4134 }; 4135 4136 /** 4137 * @private 4138 * The location of the tunnel HTML URL. 4139 * @returns {String} 4140 * The location of the tunnel HTML URL. 4141 */ 4142 this.getTunnelURL = function () { 4143 return _tunnelURL; 4144 }; 4145 4146 /** 4147 * @private 4148 * Tunnels a subscribe request to the eventing iframe. 4149 * @param {String} node 4150 * The node to subscribe to 4151 * @param {Function} handler 4152 * Handler to invoke upon success or failure 4153 */ 4154 this.subscribe = function (node, handler) { 4155 if (handler && typeof handler !== "function") { 4156 throw new Error("Parameter is not a function."); 4157 } 4158 _handlers.subscribe[node] = handler; 4159 _sendMessage(_TYPES.SUBSCRIBE, node); 4160 }; 4161 4162 4163 /** 4164 * @private 4165 * Tunnels a get subscription request to the eventing iframe. 4166 * @param {Function} handler 4167 * Handler to invoke upon success or failure 4168 */ 4169 this.getSubscriptions = function (handler) { 4170 if (handler && typeof handler !== "function") { 4171 throw new Error("Parameter is not a function."); 4172 } 4173 _handlers.subscriptions = handler; 4174 _sendMessage(_TYPES.SUBSCRIPTIONS_REQ); 4175 }; 4176 4177 /** 4178 * @private 4179 * Tunnels an unsubscribe request to the eventing iframe. 4180 * @param {String} node 4181 * The node to unsubscribe from 4182 * @param {Function} handler 4183 * Handler to invoke upon success or failure 4184 */ 4185 this.unsubscribe = function (node, handler) { 4186 if (handler && typeof handler !== "function") { 4187 throw new Error("Parameter is not a function."); 4188 } 4189 _handlers.unsubscribe[node] = handler; 4190 _sendMessage(_TYPES.UNSUBSCRIBE, node); 4191 }; 4192 4193 /** 4194 * @private 4195 * Registers a handler to be invoked when an event is delivered. Only one 4196 * is registered at a time. If there has already been an event that was 4197 * delivered, the handler will be invoked immediately. 4198 * @param {Function} handler 4199 * Invoked when an event is delivered through the event connection. 4200 */ 4201 this.registerEventHandler = function (handler) { 4202 if (typeof handler !== "function") { 4203 throw new Error("Parameter is not a function."); 4204 } 4205 _eventHandler = handler; 4206 if (_eventCache) { 4207 handler(_eventCache); 4208 } 4209 }; 4210 4211 /** 4212 * @private 4213 * Unregisters the event handler completely. 4214 */ 4215 this.unregisterEventHandler = function () { 4216 _eventHandler = undefined; 4217 }; 4218 4219 /** 4220 * @private 4221 * Registers a handler to be invoked when a presence event is delivered. Only one 4222 * is registered at a time. 4223 * @param {Function} handler 4224 * Invoked when a presence event is delivered through the event connection. 4225 */ 4226 this.registerPresenceHandler = function (handler) { 4227 if (typeof handler !== "function") { 4228 throw new Error("Parameter is not a function."); 4229 } 4230 _presenceHandler = handler; 4231 }; 4232 4233 /** 4234 * @private 4235 * Unregisters the presence event handler completely. 4236 */ 4237 this.unregisterPresenceHandler = function () { 4238 _presenceHandler = undefined; 4239 }; 4240 4241 /** 4242 * @private 4243 * Registers a handler to be invoked when a connection status changes. The 4244 * object passed will contain a "status" property, and a "resourceID" 4245 * property, which will contain the most current resource ID assigned to 4246 * the client. If there has already been an event that was delivered, the 4247 * handler will be invoked immediately. 4248 * @param {Function} handler 4249 * Invoked when a connection status changes. 4250 */ 4251 this.registerConnectionInfoHandler = function (handler) { 4252 if (typeof handler !== "function") { 4253 throw new Error("Parameter is not a function."); 4254 } 4255 _connInfoHandler = handler; 4256 if (_statusCache) { 4257 handler(_createConnInfoObj()); 4258 } 4259 }; 4260 4261 /** 4262 * @private 4263 * Unregisters the connection information handler. 4264 */ 4265 this.unregisterConnectionInfoHandler = function () { 4266 _connInfoHandler = undefined; 4267 }; 4268 4269 /** 4270 * @private 4271 * Start listening for events and create a event tunnel for the shared BOSH 4272 * connection. 4273 * @param {String} id 4274 * The ID of the user for the notification server. 4275 * @param {String} password 4276 * The password of the user for the notification server. 4277 * @param {String} xmppDomain 4278 * The XMPP domain of the notification server 4279 * @param {String} pubsubDomain 4280 * The location (JID) of the XEP-0060 PubSub service 4281 * @param {String} resource 4282 * The resource to connect to the notification servier with. 4283 * @param {String} notificationConnectionType 4284 * The xmpp connection protocol type : websocket or BOSH. 4285 */ 4286 this.init = function (id, password, xmppDomain, pubsubDomain, resource, notificationConnectionType) { 4287 4288 if (typeof id !== "string" || typeof password !== "string" || typeof xmppDomain !== "string" || typeof pubsubDomain !== "string" || typeof notificationConnectionType !== "string") { 4289 throw new Error("Invalid or missing required parameters."); 4290 } 4291 4292 _initTunnelConfig(); 4293 4294 _id = id; 4295 _password = password; 4296 _xmppDomain = xmppDomain; 4297 _pubsubDomain = pubsubDomain; 4298 _resource = resource; 4299 _notificationConnectionType = notificationConnectionType; 4300 4301 //Attach a listener for messages sent from tunnel frame. 4302 _util.receiveMessage(_messageHandler, _tunnelOrigin); 4303 4304 //Create the tunnel iframe which will establish the shared connection. 4305 _createTunnel(); 4306 }; 4307 4308 //BEGIN TEST CODE// 4309 // /** 4310 // * Test code added to expose private functions that are used by unit test 4311 // * framework. This section of code is removed during the build process 4312 // * before packaging production code. The [begin|end]TestSection are used 4313 // * by the build to identify the section to strip. 4314 // * @ignore 4315 // */ 4316 // this.beginTestSection = 0; 4317 // 4318 // /** 4319 // * @ignore 4320 // */ 4321 // this.getTestObject = function () { 4322 // //Load mock dependencies. 4323 // var _mock = new MockControl(); 4324 // _util = _mock.createMock(finesse.utilities.Utilities); 4325 // 4326 // return { 4327 // //Expose mock dependencies 4328 // mock: _mock, 4329 // util: _util, 4330 // 4331 // //Expose internal private functions 4332 // types: _TYPES, 4333 // createConnInfoObj: _createConnInfoObj, 4334 // sendMessage: _sendMessage, 4335 // messageHandler: _messageHandler, 4336 // createTunnel: _createTunnel, 4337 // handlers: _handlers, 4338 // initTunnelConfig : _initTunnelConfig 4339 // }; 4340 // }; 4341 // 4342 // /** 4343 // * @ignore 4344 // */ 4345 // this.endTestSection = 0; 4346 // //END TEST CODE// 4347 }; 4348 4349 /** @namespace JavaScript class objects and methods to handle the subscription to Finesse events.*/ 4350 finesse.clientservices = finesse.clientservices || {}; 4351 4352 window.finesse = window.finesse || {}; 4353 window.finesse.clientservices = window.finesse.clientservices || {}; 4354 window.finesse.clientservices.MasterTunnel = MasterTunnel; 4355 4356 return MasterTunnel; 4357 4358 }); 4359 4360 /** 4361 * Contains a list of topics used for client side pubsub. 4362 * 4363 */ 4364 4365 /** @private */ 4366 define('clientservices/Topics',[], function () { 4367 4368 var Topics = (function () { 4369 4370 /** 4371 * @private 4372 * The namespace prepended to all Finesse topics. 4373 */ 4374 this.namespace = "finesse.info"; 4375 4376 /** 4377 * @private 4378 * Gets the full topic name with the Finesse namespace prepended. 4379 * @param {String} topic 4380 * The topic category. 4381 * @returns {String} 4382 * The full topic name with prepended namespace. 4383 */ 4384 var _getNSTopic = function (topic) { 4385 return this.namespace + "." + topic; 4386 }; 4387 4388 /** @scope finesse.clientservices.Topics */ 4389 return { 4390 /** 4391 * @private 4392 * Client side request channel. 4393 */ 4394 REQUESTS: _getNSTopic("requests"), 4395 4396 /** 4397 * @private 4398 * Client side response channel. 4399 */ 4400 RESPONSES: _getNSTopic("responses"), 4401 4402 /** 4403 * @private 4404 * Connection status. 4405 */ 4406 EVENTS_CONNECTION_INFO: _getNSTopic("connection"), 4407 4408 /** 4409 * @private 4410 * Presence channel 4411 */ 4412 PRESENCE: _getNSTopic("presence"), 4413 4414 /** 4415 * Topic for listening to token refresh events. 4416 * The provided callback will be invoked when the access token is refreshed. 4417 * This event is only meant for updating the access token in gadget Config object 4418 */ 4419 ACCESS_TOKEN_REFRESHED_EVENT: _getNSTopic("accessTokenRefresh"), 4420 4421 /** 4422 * @private 4423 * Convert a Finesse REST URI to a OpenAjax compatible topic name. 4424 */ 4425 getTopic: function (restUri) { 4426 //The topic should not start with '/' else it will get replaced with 4427 //'.' which is invalid. 4428 //Thus, remove '/' if it is at the beginning of the string 4429 if (restUri.indexOf('/') === 0) { 4430 restUri = restUri.substr(1); 4431 } 4432 4433 //Replace every instance of "/" with ".". This is done to follow the 4434 //OpenAjaxHub topic name convention. 4435 return restUri.replace(/\//g, "."); 4436 } 4437 }; 4438 }()); 4439 window.finesse = window.finesse || {}; 4440 window.finesse.clientservices = window.finesse.clientservices || {}; 4441 /** @private */ 4442 window.finesse.clientservices.Topics = Topics; 4443 4444 return Topics; 4445 }); 4446 /** The following comment is to prevent jslint errors about 4447 * using variables before they are defined. 4448 */ 4449 /*global finesse*/ 4450 4451 /** 4452 * Registers with the MasterTunnel to receive events, which it 4453 * could publish to the OpenAjax gadget pubsub infrastructure. 4454 * 4455 * @requires OpenAjax, finesse.clientservices.MasterTunnel, finesse.clientservices.Topics 4456 */ 4457 4458 /** @private */ 4459 define('clientservices/MasterPublisher',[ 4460 "clientservices/MasterTunnel", 4461 "clientservices/Topics", 4462 "utilities/Utilities" 4463 ], 4464 function (MasterTunnel, Topics, Utilities) { 4465 4466 var MasterPublisher = function (tunnel, hub) { 4467 if (!(tunnel instanceof MasterTunnel)) { 4468 throw new Error("Required tunnel object missing or invalid."); 4469 } 4470 4471 var 4472 4473 ClientServices = finesse.clientservices.ClientServices, 4474 4475 /** 4476 * Reference to the gadget pubsub Hub instance. 4477 * @private 4478 */ 4479 _hub = hub, 4480 4481 /** 4482 * Reference to the Topics class. 4483 * @private 4484 */ 4485 _topics = Topics, 4486 4487 /** 4488 * Reference to conversion utilities class. 4489 * @private 4490 */ 4491 _utils = Utilities, 4492 4493 /** 4494 * References to ClientServices logger methods 4495 * @private 4496 */ 4497 _logger = { 4498 log: ClientServices.log 4499 }, 4500 4501 /** 4502 * Store the passed in tunnel. 4503 * @private 4504 */ 4505 _tunnel = tunnel, 4506 4507 /** 4508 * Caches the connection info event so that it could be published if there 4509 * is a request for it. 4510 * @private 4511 */ 4512 _connInfoCache = {}, 4513 4514 /** 4515 * The types of possible request types supported when listening to the 4516 * requests channel. Each request type could result in different operations. 4517 * @private 4518 */ 4519 _REQTYPES = { 4520 CONNECTIONINFO: "ConnectionInfoReq", 4521 SUBSCRIBE: "SubscribeNodeReq", 4522 UNSUBSCRIBE: "UnsubscribeNodeReq", 4523 SUBSCRIPTIONSINFO: "SubscriptionsInfoReq", 4524 CONNECT: "ConnectionReq" 4525 }, 4526 4527 /** 4528 * Will store list of nodes that have OF subscriptions created 4529 * _nodesList[node][subscribing].reqIds[subid] 4530 * _nodesList[node][active].reqIds[subid] 4531 * _nodesList[node][unsubscribing].reqIds[subid] 4532 * _nodesList[node][holding].reqIds[subid] 4533 * @private 4534 */ 4535 _nodesList = {}, 4536 4537 /** 4538 * The states that a subscription can be in 4539 * @private 4540 */ 4541 _CHANNELSTATES = { 4542 UNINITIALIZED: "Uninitialized", 4543 PENDING: "Pending", 4544 OPERATIONAL: "Operational" 4545 }, 4546 4547 /** 4548 * Checks if the payload is JSON 4549 * @returns {Boolean} 4550 * @private 4551 */ 4552 _isJsonPayload = function(event) { 4553 var delimStart, delimEnd, retval = false; 4554 4555 try { 4556 delimStart = event.indexOf('{'); 4557 delimEnd = event.lastIndexOf('}'); 4558 4559 if ((delimStart !== -1 ) && (delimEnd === (event.length - 1))) { 4560 retval = true; //event contains JSON payload 4561 } 4562 } catch (err) { 4563 _logger.log("MasterPublisher._isJsonPayload() - Caught error: " + err); 4564 } 4565 return retval; 4566 }, 4567 4568 /** 4569 * Parses a JSON event and then publishes. 4570 * 4571 * @param {String} event 4572 * The full event payload. 4573 * @throws {Error} If the payload object is malformed. 4574 * @private 4575 */ 4576 _parseAndPublishJSONEvent = function(event) { 4577 var topic, eventObj, publishEvent, 4578 delimPos = event.indexOf("{"), 4579 node, parser, 4580 eventJson = event, 4581 returnObj = {node: null, data: null}; 4582 4583 try { 4584 //Extract and strip the node path from the message 4585 if (delimPos > 0) 4586 { 4587 //We need to decode the URI encoded node path 4588 //TODO: make sure this is kosher with OpenAjax topic naming 4589 node = decodeURI(event.substr(0, delimPos)); 4590 eventJson = event.substr(delimPos); 4591 4592 //Converting the node path to openAjaxhub topic 4593 topic = _topics.getTopic(node); 4594 4595 returnObj.node = node; 4596 returnObj.payload = eventJson; 4597 } 4598 else 4599 { 4600 _logger.log("MasterPublisher._parseAndPublishJSONEvent() - [ERROR] node is not given in postMessage: " + eventJson); 4601 throw new Error("node is not given in postMessage: " + eventJson); 4602 } 4603 4604 parser = _utils.getJSONParser(); 4605 4606 eventObj = parser.parse(eventJson); 4607 returnObj.data = eventObj; 4608 4609 } catch (err) { 4610 _logger.log("MasterPublisher._parseAndPublishJSONEvent() - [ERROR] Malformed event payload: " + err); 4611 throw new Error("Malformed event payload : " + err); 4612 } 4613 4614 _logger.log("MasterPublisher._parseAndPublishJSONEvent() - Received JSON event on node '" + node + "': " + eventJson); 4615 4616 publishEvent = {content : event, object : eventObj }; 4617 4618 //Publish event to proper event topic. 4619 if (topic && eventObj) { 4620 _hub.publish(topic, publishEvent); 4621 } 4622 }, 4623 4624 /** 4625 * Parses an XML event and then publishes. 4626 * 4627 * @param {String} event 4628 * The full event payload. 4629 * @throws {Error} If the payload object is malformed. 4630 * @private 4631 */ 4632 _parseAndPublishXMLEvent = function(event) { 4633 var topic, eventObj, publishEvent, restTopic, 4634 delimPos = event.indexOf("<"), 4635 node, 4636 eventXml = event; 4637 4638 try { 4639 //Extract and strip the node path from the message 4640 if (delimPos > 0) { 4641 //We need to decode the URI encoded node path 4642 //TODO: make sure this is kosher with OpenAjax topic naming 4643 node = decodeURI(event.substr(0, delimPos)); 4644 eventXml = event.substr(delimPos); 4645 //Converting the node path to openAjaxhub topic 4646 topic = _topics.getTopic(node); 4647 } else { 4648 _logger.log("MasterPublisher._parseAndPublishXMLEvent() - [ERROR] node is not given in postMessage: " + eventXml); 4649 throw new Error("node is not given in postMessage: " + eventXml); 4650 } 4651 4652 eventObj = _utils.xml2JsObj(eventXml); 4653 4654 } catch (err) { 4655 _logger.log("MasterPublisher._parseAndPublishXMLEvent() - [ERROR] Malformed event payload: " + err); 4656 throw new Error("Malformed event payload : " + err); 4657 } 4658 4659 _logger.log("MasterPublisher._parseAndPublishXMLEvent() - Received XML event on node '" + node + "': " + eventXml); 4660 4661 publishEvent = {content : event, object : eventObj }; 4662 4663 //Publish event to proper event topic. 4664 if (topic && eventObj) { 4665 _hub.publish(topic, publishEvent); 4666 } 4667 }, 4668 4669 /** 4670 * Publishes events to the appropriate topic. The topic name is determined 4671 * by fetching the source value from the event. 4672 * @param {String} event 4673 * The full event payload. 4674 * @throws {Error} If the payload object is malformed. 4675 * @private 4676 */ 4677 _eventHandler = function (event) { 4678 4679 //Handle JSON or XML events 4680 if (!_isJsonPayload(event)) 4681 { 4682 //XML 4683 _parseAndPublishXMLEvent(event); 4684 } 4685 else 4686 { 4687 //JSON 4688 _parseAndPublishJSONEvent(event); 4689 } 4690 }, 4691 4692 4693 /** 4694 * Handler for when presence events are sent through the MasterTunnel. 4695 * @returns {Object} 4696 * A presence xml event. 4697 * @private 4698 */ 4699 _presenceHandler = function (event) { 4700 var eventObj = _utils.xml2JsObj(event), publishEvent; 4701 4702 publishEvent = {content : event, object : eventObj}; 4703 4704 if (eventObj) { 4705 _hub.publish(_topics.PRESENCE, publishEvent); 4706 } 4707 }, 4708 4709 /** 4710 * Clone the connection info object from cache. 4711 * @returns {Object} 4712 * A connection info object containing a "status" and "resourceID". 4713 * @private 4714 */ 4715 _cloneConnInfoObj = function () { 4716 if (_connInfoCache) { 4717 return { 4718 status: _connInfoCache.status, 4719 resourceID: _connInfoCache.resourceID 4720 }; 4721 } else { 4722 return null; 4723 } 4724 }, 4725 4726 /** 4727 * Cleans up any outstanding subscribe/unsubscribe requests and notifies them of errors. 4728 * This is done if we get disconnected because we cleanup explicit subscriptions on disconnect. 4729 * @private 4730 */ 4731 _cleanupPendingRequests = function () { 4732 var node, curSubid, errObj = { 4733 error: { 4734 errorType: "Disconnected", 4735 errorMessage: "Outstanding request will never complete." 4736 } 4737 }; 4738 4739 // Iterate through all outstanding subscribe requests to notify them that it will never return 4740 for (node in _nodesList) { 4741 if (_nodesList.hasOwnProperty(node)) { 4742 for (curSubid in _nodesList[node].subscribing.reqIds) { 4743 if (_nodesList[node].subscribing.reqIds.hasOwnProperty(curSubid)) { 4744 // Notify this outstanding subscribe request to give up and error out 4745 _hub.publish(_topics.RESPONSES + "." + curSubid, errObj); 4746 } 4747 } 4748 for (curSubid in _nodesList[node].unsubscribing.reqIds) { 4749 if (_nodesList[node].unsubscribing.reqIds.hasOwnProperty(curSubid)) { 4750 // Notify this outstanding unsubscribe request to give up and error out 4751 _hub.publish(_topics.RESPONSES + "." + curSubid, errObj); 4752 } 4753 } 4754 } 4755 } 4756 }, 4757 4758 /** 4759 * Publishes the connection info to the connection info topic. 4760 * @param {Object} connInfo 4761 * The connection info object containing the status and resource ID. 4762 * @private 4763 */ 4764 _connInfoHandler = function (connInfo) { 4765 var before = _connInfoCache.status; 4766 _connInfoCache = connInfo; 4767 _logger.log("MasterPublisher._connInfoHandler() - Connection status: " + connInfo.status); 4768 _hub.publish(_topics.EVENTS_CONNECTION_INFO, _cloneConnInfoObj()); 4769 if (before === "connected" && connInfo.status !== "connected") { 4770 // Fail all pending requests when we transition to disconnected 4771 _cleanupPendingRequests(); 4772 } 4773 }, 4774 4775 /** 4776 * Get's all the subscriptions in the Openfire through EventTunnel and publishes the info to the topic 4777 * @param {String} invokeId 4778 * The request id 4779 * @private 4780 */ 4781 _getAllSubscriptions = function(invokeId) { 4782 // _logger.log("MasterPublisher._getAllSubscriptions() - Getting all subscriptions "); 4783 4784 _tunnel.getSubscriptions (function (content) { 4785 _hub.publish(_topics.RESPONSES + "." + invokeId, content); 4786 }); 4787 4788 }, 4789 4790 4791 /** 4792 * Utility method to bookkeep node subscription requests and determine 4793 * whehter it is necessary to tunnel the request to JabberWerx. 4794 * @param {String} node 4795 * The node of interest 4796 * @param {String} reqId 4797 * A unique string identifying the request/subscription 4798 * @private 4799 */ 4800 _subscribeNode = function (node, subid) { 4801 if (_connInfoCache.status !== "connected") { 4802 _hub.publish(_topics.RESPONSES + "." + subid, { 4803 error: { 4804 errorType: "Not connected", 4805 errorMessage: "Cannot subscribe without connection." 4806 } 4807 }); 4808 return; 4809 } 4810 // NODE DOES NOT YET EXIST 4811 if (!_nodesList[node]) { 4812 _nodesList[node] = { 4813 "subscribing": { 4814 "reqIds": {}, 4815 "length": 0 4816 }, 4817 "active": { 4818 "reqIds": {}, 4819 "length": 0 4820 }, 4821 "unsubscribing": { 4822 "reqIds": {}, 4823 "length": 0 4824 }, 4825 "holding": { 4826 "reqIds": {}, 4827 "length": 0 4828 } 4829 }; 4830 } 4831 if (_nodesList[node].active.length === 0) { 4832 if (_nodesList[node].unsubscribing.length === 0) { 4833 if (_nodesList[node].subscribing.length === 0) { 4834 _nodesList[node].subscribing.reqIds[subid] = true; 4835 _nodesList[node].subscribing.length += 1; 4836 4837 _logger.log("MasterPublisher._subscribeNode() - Attempting to subscribe to node '" + node + "'"); 4838 _tunnel.subscribe(node, function (err) { 4839 var errObj, curSubid; 4840 if (err) { 4841 errObj = { 4842 subscribe: { 4843 content: err 4844 } 4845 }; 4846 4847 try { 4848 errObj.subscribe.object = gadgets.json.parse((_utils.xml2json(jQuery.parseXML(err), ""))); 4849 } catch (e) { 4850 errObj.error = { 4851 errorType: "parseError", 4852 errorMessage: "Could not serialize XML: " + e 4853 }; 4854 } 4855 _logger.log("MasterPublisher._subscribeNode() - Error subscribing to node '" + node + "': " + err); 4856 } else { 4857 _logger.log("MasterPublisher._subscribeNode() - Subscribed to node '" + node + "'"); 4858 } 4859 4860 for (curSubid in _nodesList[node].subscribing.reqIds) { 4861 if (_nodesList[node].subscribing.reqIds.hasOwnProperty(curSubid)) { 4862 _hub.publish(_topics.RESPONSES + "." + curSubid, errObj); 4863 if (!err) { 4864 _nodesList[node].active.reqIds[curSubid] = true; 4865 _nodesList[node].active.length += 1; 4866 } 4867 delete _nodesList[node].subscribing.reqIds[curSubid]; 4868 _nodesList[node].subscribing.length -= 1; 4869 } 4870 } 4871 }); 4872 4873 } else { //other ids are subscribing 4874 _nodesList[node].subscribing.reqIds[subid] = true; 4875 _nodesList[node].subscribing.length += 1; 4876 } 4877 } else { //An unsubscribe request is pending, hold onto these subscribes until it is done 4878 _nodesList[node].holding.reqIds[subid] = true; 4879 _nodesList[node].holding.length += 1; 4880 } 4881 } else { // The node has active subscriptions; add this subid and return successful response 4882 _nodesList[node].active.reqIds[subid] = true; 4883 _nodesList[node].active.length += 1; 4884 _hub.publish(_topics.RESPONSES + "." + subid, undefined); 4885 } 4886 }, 4887 4888 /** 4889 * Utility method to bookkeep node unsubscribe requests and determine 4890 * whehter it is necessary to tunnel the request to JabberWerx. 4891 * @param {String} node 4892 * The node to unsubscribe from 4893 * @param {String} reqId 4894 * A unique string identifying the subscription to remove 4895 * @param {Boolean} isForceOp 4896 * Boolean flag if true then the subscription will be forefully removed even when active references to the node are nil. 4897 * @private 4898 */ 4899 _unsubscribeNode = function (node, subid, isForceOp) { 4900 if (!_nodesList[node] && !isForceOp ) { //node DNE, publish success response 4901 _hub.publish(_topics.RESPONSES + "." + subid, undefined); 4902 } else { 4903 if (_connInfoCache.status !== "connected") { 4904 _hub.publish(_topics.RESPONSES + "." + subid, { 4905 error: { 4906 errorType: "Not connected", 4907 errorMessage: "Cannot unsubscribe without connection." 4908 } 4909 }); 4910 return; 4911 } 4912 if (_nodesList[node] && _nodesList[node].active.length > 1) { 4913 delete _nodesList[node].active.reqIds[subid]; 4914 _hub.publish(_topics.RESPONSES + "." + subid, undefined); 4915 _nodesList[node].active.length -= 1; 4916 } else if (_nodesList[node] && _nodesList[node].active.length === 1 || isForceOp) { // transition subid from active category to unsubscribing category 4917 4918 if (_nodesList[node]) { 4919 _nodesList[node].unsubscribing.reqIds[subid] = true; 4920 _nodesList[node].unsubscribing.length += 1; 4921 delete _nodesList[node].active.reqIds[subid]; 4922 _nodesList[node].active.length -= 1; 4923 } 4924 4925 _logger.log("MasterPublisher._unsubscribeNode() - Attempting to unsubscribe from node '" + node + "'"); 4926 var requestId = subid; 4927 _tunnel.unsubscribe(node, function (err) { 4928 var errObj, curSubid; 4929 if (err) { 4930 errObj = { 4931 subscribe: { 4932 content: err 4933 } 4934 }; 4935 4936 try { 4937 errObj.subscribe.object = gadgets.json.parse((_utils.xml2json(jQuery.parseXML(err), ""))); 4938 } catch (e) { 4939 errObj.error = { 4940 errorType: "parseError", 4941 errorMessage: "Could not serialize XML: " + e 4942 }; 4943 } 4944 _logger.log("MasterPublisher._unsubscribeNode() - Error unsubscribing from node '" + node + "': " + err); 4945 } else { 4946 _logger.log("MasterPublisher._unsubscribeNode() - Unsubscribed from node '" + node + "'"); 4947 } 4948 4949 if (_nodesList[node]) { 4950 4951 for (curSubid in _nodesList[node].unsubscribing.reqIds) { 4952 if (_nodesList[node].unsubscribing.reqIds.hasOwnProperty(curSubid)) { 4953 // publish to all subids whether unsubscribe failed or succeeded 4954 _hub.publish(_topics.RESPONSES + "." + curSubid, errObj); 4955 if (!err) { 4956 delete _nodesList[node].unsubscribing.reqIds[curSubid]; 4957 _nodesList[node].unsubscribing.length -= 1; 4958 } else { // Just remove the subid from unsubscribing; the next subscribe request will operate with node already created 4959 delete _nodesList[node].unsubscribing.reqIds[curSubid]; 4960 _nodesList[node].unsubscribing.length -= 1; 4961 } 4962 } 4963 } 4964 4965 if (!err && _nodesList[node].holding.length > 0) { // if any subscribe requests came in while unsubscribing from OF, now transition from holding to subscribing 4966 for (curSubid in _nodesList[node].holding.reqIds) { 4967 if (_nodesList[node].holding.reqIds.hasOwnProperty(curSubid)) { 4968 delete _nodesList[node].holding.reqIds[curSubid]; 4969 _nodesList[node].holding.length -= 1; 4970 _subscribeNode(node, curSubid); 4971 } 4972 } 4973 } 4974 } else { 4975 _hub.publish(_topics.RESPONSES + "." + requestId, undefined); 4976 } 4977 }); 4978 } else { // length <= 0? 4979 _hub.publish(_topics.RESPONSES + "." + subid, undefined); 4980 } 4981 } 4982 }, 4983 4984 4985 4986 /** 4987 * Handles client requests to establish a BOSH connection. 4988 * @param {String} id 4989 * id of the xmpp user 4990 * @param {String} password 4991 * password of the xmpp user 4992 * @param {String} xmppDomain 4993 * xmppDomain of the xmpp user account 4994 * @private 4995 */ 4996 _connect = function (id, password, xmppDomain) { 4997 _tunnel.makeConnectReq(id, password, xmppDomain); 4998 }, 4999 5000 /** 5001 * Handles client requests made to the request topic. The type of the 5002 * request is described in the "type" property within the data payload. Each 5003 * type can result in a different operation. 5004 * @param {String} topic 5005 * The topic which data was published to. 5006 * @param {Object} data 5007 * The data containing requests information published by clients. 5008 * @param {String} data.type 5009 * The type of the request. Supported: "ConnectionInfoReq" 5010 * @param {Object} data.data 5011 * May contain data relevant for the particular requests. 5012 * @param {String} [data.invokeID] 5013 * The ID used to identify the request with the response. The invoke ID 5014 * will be included in the data in the publish to the topic. It is the 5015 * responsibility of the client to correlate the published data to the 5016 * request made by using the invoke ID. 5017 * @private 5018 */ 5019 _clientRequestHandler = function (topic, data) { 5020 var dataCopy; 5021 5022 //Ensure a valid data object with "type" and "data" properties. 5023 if (typeof data === "object" && 5024 typeof data.type === "string" && 5025 typeof data.data === "object") { 5026 switch (data.type) { 5027 case _REQTYPES.CONNECTIONINFO: 5028 //It is possible that Slave clients come up before the Master 5029 //client. If that is the case, the Slaves will need to make a 5030 //request for the Master to send the latest connection info to the 5031 //connectionInfo topic. 5032 dataCopy = _cloneConnInfoObj(); 5033 if (dataCopy) { 5034 if (data.invokeID !== undefined) { 5035 dataCopy.invokeID = data.invokeID; 5036 } 5037 _hub.publish(_topics.EVENTS_CONNECTION_INFO, dataCopy); 5038 } 5039 break; 5040 case _REQTYPES.SUBSCRIBE: 5041 if (typeof data.data.node === "string") { 5042 _subscribeNode(data.data.node, data.invokeID); 5043 } 5044 break; 5045 case _REQTYPES.UNSUBSCRIBE: 5046 if (typeof data.data.node === "string") { 5047 _unsubscribeNode(data.data.node, data.invokeID, data.isForceOp ); 5048 } 5049 break; 5050 case _REQTYPES.SUBSCRIPTIONSINFO: 5051 _getAllSubscriptions(data.invokeID); 5052 break; 5053 case _REQTYPES.CONNECT: 5054 // Deprecated: Disallow others (non-masters) from administering BOSH connections that are not theirs 5055 _logger.log("MasterPublisher._clientRequestHandler(): F403: Access denied. Non-master ClientService instances do not have the clearance level to make this request: makeConnectionReq"); 5056 break; 5057 default: 5058 break; 5059 } 5060 } 5061 }; 5062 5063 (function () { 5064 //Register to receive events and connection status from tunnel. 5065 _tunnel.registerEventHandler(_eventHandler); 5066 _tunnel.registerPresenceHandler(_presenceHandler); 5067 _tunnel.registerConnectionInfoHandler(_connInfoHandler); 5068 5069 //Listen to a request channel to respond to any requests made by other 5070 //clients because the Master may have access to useful information. 5071 _hub.subscribe(_topics.REQUESTS, _clientRequestHandler); 5072 }()); 5073 5074 /** 5075 * @private 5076 * Handles client requests to establish a BOSH connection. 5077 * @param {String} id 5078 * id of the xmpp user 5079 * @param {String} password 5080 * password of the xmpp user 5081 * @param {String} xmppDomain 5082 * xmppDomain of the xmpp user account 5083 */ 5084 this.connect = function (id, password, xmppDomain) { 5085 _connect(id, password, xmppDomain); 5086 }; 5087 5088 /** 5089 * @private 5090 * Resets the list of explicit subscriptions 5091 */ 5092 this.wipeout = function () { 5093 _cleanupPendingRequests(); 5094 _nodesList = {}; 5095 }; 5096 5097 //BEGIN TEST CODE// 5098 /** 5099 * Test code added to expose private functions that are used by unit test 5100 * framework. This section of code is removed during the build process 5101 * before packaging production code. The [begin|end]TestSection are used 5102 * by the build to identify the section to strip. 5103 * @ignore 5104 */ 5105 this.beginTestSection = 0; 5106 5107 /** 5108 * @ignore 5109 */ 5110 this.getTestObject = function () { 5111 //Load mock dependencies. 5112 var _mock = new MockControl(); 5113 _hub = _mock.createMock(gadgets.Hub); 5114 _tunnel = _mock.createMock(); 5115 5116 return { 5117 //Expose mock dependencies 5118 mock: _mock, 5119 hub: _hub, 5120 tunnel: _tunnel, 5121 setTunnel: function (tunnel) { 5122 _tunnel = tunnel; 5123 }, 5124 getTunnel: function () { 5125 return _tunnel; 5126 }, 5127 5128 //Expose internal private functions 5129 reqtypes: _REQTYPES, 5130 eventHandler: _eventHandler, 5131 presenceHandler: _presenceHandler, 5132 5133 subscribeNode: _subscribeNode, 5134 unsubscribeNode: _unsubscribeNode, 5135 5136 getNodeList: function () { 5137 return _nodesList; 5138 }, 5139 setNodeList: function (nodelist) { 5140 _nodesList = nodelist; 5141 }, 5142 5143 cloneConnInfoObj: _cloneConnInfoObj, 5144 connInfoHandler: _connInfoHandler, 5145 clientRequestHandler: _clientRequestHandler 5146 5147 }; 5148 }; 5149 5150 5151 /** 5152 * @ignore 5153 */ 5154 this.endTestSection = 0; 5155 //END TEST CODE// 5156 5157 }; 5158 5159 window.finesse = window.finesse || {}; 5160 window.finesse.clientservices = window.finesse.clientservices || {}; 5161 window.finesse.clientservices.MasterPublisher = MasterPublisher; 5162 5163 return MasterPublisher; 5164 }); 5165 5166 /** The following comment is to prevent jslint errors about 5167 * using variables before they are defined. 5168 */ 5169 /*global publisher:true */ 5170 5171 /** 5172 * Exposes a set of API wrappers that will hide the dirty work of 5173 * constructing Finesse API requests and consuming Finesse events. 5174 * 5175 * @requires OpenAjax, jQuery 1.5, finesse.utilities.Utilities 5176 */ 5177 5178 5179 /** 5180 * Allow clients to make Finesse API requests and consume Finesse events by 5181 * calling a set of exposed functions. The Services layer will do the dirty 5182 * work of establishing a shared BOSH connection (for designated Master 5183 * modules), consuming events for client subscriptions, and constructing API 5184 * requests. 5185 */ 5186 /** @private */ 5187 define('clientservices/ClientServices',[ 5188 "clientservices/MasterTunnel", 5189 "clientservices/MasterPublisher", 5190 "clientservices/Topics", 5191 "utilities/Utilities" 5192 ], 5193 function (MasterTunnel, MasterPublisher, Topics, Utilities) { 5194 5195 var ClientServices = (function () {/** @lends finesse.clientservices.ClientServices.prototype */ 5196 var 5197 5198 /** 5199 * Shortcut reference to the master tunnel 5200 * @private 5201 */ 5202 _tunnel, 5203 5204 _publisher, 5205 5206 /** 5207 * Shortcut reference to the finesse.utilities.Utilities singleton 5208 * This will be set by init() 5209 * @private 5210 */ 5211 _util, 5212 5213 /** 5214 * Shortcut reference to the gadgets.io object. 5215 * This will be set by init() 5216 * @private 5217 */ 5218 _io, 5219 5220 /** 5221 * Shortcut reference to the gadget pubsub Hub instance. 5222 * This will be set by init() 5223 * @private 5224 */ 5225 _hub, 5226 5227 /** 5228 * Logger object set externally by setLogger, defaults to nothing. 5229 * @private 5230 */ 5231 _logger = {}, 5232 5233 /** 5234 * Shortcut reference to the Topics class. 5235 * This will be set by init() 5236 * @private 5237 */ 5238 _topics, 5239 5240 /** 5241 * Config object needed to initialize this library 5242 * This must be set by init() 5243 * @private 5244 */ 5245 _config, 5246 5247 /** 5248 * @private 5249 * Whether or not this ClientService instance is a Master. 5250 */ 5251 _isMaster = false, 5252 5253 /** 5254 * @private 5255 * Whether the Client Services have been initiated yet. 5256 */ 5257 _inited = false, 5258 5259 /** 5260 * Stores the list of subscription IDs for all subscriptions so that it 5261 * could be retrieve for unsubscriptions. 5262 * @private 5263 */ 5264 _subscriptionID = {}, 5265 5266 /** 5267 * The possible states of the JabberWerx BOSH connection. 5268 * @private 5269 */ 5270 _STATUS = { 5271 CONNECTING: "connecting", 5272 CONNECTED: "connected", 5273 DISCONNECTED: "disconnected", 5274 DISCONNECTED_CONFLICT: "conflict", 5275 DISCONNECTED_UNAUTHORIZED: "unauthorized", 5276 DISCONNECTING: "disconnecting", 5277 RECONNECTING: "reconnecting", 5278 UNLOADING: "unloading", 5279 FAILING: "failing", 5280 RECOVERED: "recovered" 5281 }, 5282 5283 /** 5284 * Local reference for authMode enum object. 5285 * @private 5286 */ 5287 _authModes, 5288 5289 _failoverMode = false, 5290 5291 /** 5292 * Handler function to be invoked when BOSH connection is connecting. 5293 * @private 5294 */ 5295 _onConnectingHandler, 5296 5297 /** 5298 * Handler function to be invoked when BOSH connection is connected 5299 * @private 5300 */ 5301 _onConnectHandler, 5302 5303 /** 5304 * Handler function to be invoked when BOSH connection is disconnecting. 5305 * @private 5306 */ 5307 _onDisconnectingHandler, 5308 5309 /** 5310 * Handler function to be invoked when the BOSH is disconnected. 5311 * @private 5312 */ 5313 _onDisconnectHandler, 5314 5315 /** 5316 * Handler function to be invoked when the BOSH is reconnecting. 5317 * @private 5318 */ 5319 _onReconnectingHandler, 5320 5321 /** 5322 * Handler function to be invoked when the BOSH is unloading. 5323 * @private 5324 */ 5325 _onUnloadingHandler, 5326 5327 /** 5328 * Contains a cache of the latest connection info containing the current 5329 * state of the BOSH connection and the resource ID. 5330 * @private 5331 */ 5332 _connInfo, 5333 5334 /** 5335 * Keeps track of all the objects that need to be refreshed when we recover 5336 * due to our resilient connection. Only objects that we subscribe to will 5337 * be added to this list. 5338 * @private 5339 */ 5340 _refreshList = [], 5341 5342 /** 5343 * Needs to be passed as authorization header inside makeRequest wrapper function 5344 */ 5345 _authHeaderString, 5346 5347 /** 5348 * @private 5349 * Centralized logger.log method for external logger 5350 * @param {String} msg 5351 * @param {Object} [Optional] Javascript error object 5352 */ 5353 _log = function (msg, e) { 5354 // If the external logger throws up, it stops here. 5355 try { 5356 if (_logger.log) { 5357 _logger.log("[ClientServices] " + msg, e); 5358 } 5359 } catch (e) { } 5360 }, 5361 5362 /** 5363 * Go through each object in the _refreshList and call its refresh() function 5364 * @private 5365 */ 5366 _refreshObjects = function () { 5367 var i; 5368 5369 // wipe out the explicit subscription list before we refresh objects 5370 if (_publisher) { 5371 _publisher.wipeout(); 5372 } 5373 5374 // refresh each item in the refresh list 5375 for (i = _refreshList.length - 1; i >= 0; i -= 1) { 5376 try{ 5377 _log("Refreshing " + _refreshList[i].getRestUrl()); 5378 _refreshList[i].refresh(10); 5379 } 5380 catch(e){ 5381 _log("ClientServices._refreshObjects() unexpected error while refreshing object" + e); 5382 } 5383 } 5384 }, 5385 5386 /** 5387 * Handler to process connection info publishes. 5388 * @param {Object} data 5389 * The connection info data object. 5390 * @param {String} data.status 5391 * The BOSH connection status. 5392 * @param {String} data.resourceID 5393 * The resource ID for the connection. 5394 * @private 5395 */ 5396 _connInfoHandler = function (data) { 5397 5398 //Invoke registered handler depending on status received. Due to the 5399 //request topic where clients can make request for the Master to publish 5400 //the connection info, there is a chance that duplicate connection info 5401 //events may be sent, so ensure that there has been a state change 5402 //before invoking the handlers. 5403 if (_connInfo === undefined || _connInfo.status !== data.status) { 5404 _connInfo = data; 5405 switch (data.status) { 5406 case _STATUS.CONNECTING: 5407 if (_isMaster && _onConnectingHandler) { 5408 _onConnectingHandler(); 5409 } 5410 break; 5411 case _STATUS.CONNECTED: 5412 if ((_isMaster || !_failoverMode) && _onConnectHandler) { 5413 _onConnectHandler(); 5414 } 5415 break; 5416 case _STATUS.DISCONNECTED: 5417 if (_isMaster && _onDisconnectHandler) { 5418 _onDisconnectHandler(); 5419 } 5420 break; 5421 case _STATUS.DISCONNECTED_CONFLICT: 5422 if (_isMaster && _onDisconnectHandler) { 5423 _onDisconnectHandler("conflict"); 5424 } 5425 break; 5426 case _STATUS.DISCONNECTED_UNAUTHORIZED: 5427 if (_isMaster && _onDisconnectHandler) { 5428 _onDisconnectHandler("unauthorized"); 5429 } 5430 break; 5431 case _STATUS.DISCONNECTING: 5432 if (_isMaster && _onDisconnectingHandler) { 5433 _onDisconnectingHandler(); 5434 } 5435 break; 5436 case _STATUS.RECONNECTING: 5437 if (_isMaster && _onReconnectingHandler) { 5438 _onReconnectingHandler(); 5439 } 5440 break; 5441 case _STATUS.UNLOADING: 5442 if (_isMaster && _onUnloadingHandler) { 5443 _onUnloadingHandler(); 5444 } 5445 break; 5446 case _STATUS.FAILING: 5447 if (!_isMaster) { 5448 // Stop 5449 _failoverMode = true; 5450 if (_onDisconnectHandler) { 5451 _onDisconnectHandler(); 5452 } 5453 } 5454 break; 5455 case _STATUS.RECOVERED: 5456 if (!_isMaster) { 5457 _failoverMode = false; 5458 if (_onConnectHandler) { 5459 _onConnectHandler(); 5460 } 5461 } 5462 // Whenever we are recovered, we need to refresh any objects 5463 // that are stored. 5464 _refreshObjects(); 5465 break; 5466 } 5467 } 5468 }, 5469 5470 /** 5471 * Ensure that ClientServices have been inited. 5472 * @private 5473 */ 5474 _isInited = function () { 5475 if (!_inited) { 5476 throw new Error("ClientServices needs to be inited."); 5477 } 5478 }, 5479 5480 /** 5481 * Have the client become the Master by initiating a tunnel to a shared 5482 * event BOSH connection. The Master is responsible for publishing all 5483 * events to the pubsub infrastructure. 5484 * 5485 * TODO: Currently we only check the global auth mode. This code has to 5486 * handle mixed mode - in this case the user specfic SSO mode has to be 5487 * exposed via an API. 5488 * 5489 * @private 5490 */ 5491 _becomeMaster = function () { 5492 var creds , id; 5493 _tunnel = new MasterTunnel(_config.host, _config.scheme); 5494 _publisher = new MasterPublisher(_tunnel, _hub); 5495 if(_authModes.SSO === _config.systemAuthMode ) { 5496 creds = _config.authToken; 5497 } else { 5498 creds = _config.password; 5499 } 5500 _util = Utilities; 5501 id = _util.encodeNodeName(_config.id); 5502 _tunnel.init(id, creds, _config.xmppDomain, _config.pubsubDomain, _config.resource, _config.notificationConnectionType); 5503 _isMaster = true; 5504 }, 5505 5506 /** 5507 * Make a request to the request channel to have the Master publish the 5508 * connection info object. 5509 * @private 5510 */ 5511 _makeConnectionInfoReq = function () { 5512 var data = { 5513 type: "ConnectionInfoReq", 5514 data: {}, 5515 invokeID: (new Date()).getTime() 5516 }; 5517 _hub.publish(_topics.REQUESTS, data); 5518 }, 5519 5520 /** 5521 * Utility method to register a handler which is associated with a 5522 * particular connection status. 5523 * @param {String} status 5524 * The connection status string. 5525 * @param {Function} handler 5526 * The handler to associate with a particular connection status. 5527 * @throws {Error} 5528 * If the handler provided is not a function. 5529 * @private 5530 */ 5531 _registerHandler = function (status, handler) { 5532 if (typeof handler === "function") { 5533 if (_connInfo && _connInfo.status === status) { 5534 handler(); 5535 } 5536 switch (status) { 5537 case _STATUS.CONNECTING: 5538 _onConnectingHandler = handler; 5539 break; 5540 case _STATUS.CONNECTED: 5541 _onConnectHandler = handler; 5542 break; 5543 case _STATUS.DISCONNECTED: 5544 _onDisconnectHandler = handler; 5545 break; 5546 case _STATUS.DISCONNECTING: 5547 _onDisconnectingHandler = handler; 5548 break; 5549 case _STATUS.RECONNECTING: 5550 _onReconnectingHandler = handler; 5551 break; 5552 case _STATUS.UNLOADING: 5553 _onUnloadingHandler = handler; 5554 break; 5555 } 5556 5557 } else { 5558 throw new Error("Callback is not a function"); 5559 } 5560 }, 5561 5562 /** 5563 * Callback function that is called when a refresh access token event message is posted to the Hub. 5564 * 5565 * Get access token from the data and update the finesse.gadget.Config object! 5566 * 5567 * @param {String} topic 5568 * which topic the event came on (unused) 5569 * @param {Object} data 5570 * the data published with the event 5571 * @private 5572 */ 5573 _accessTokenRefreshHandler = function(topic , data){ 5574 _log("Access token refreshed - topic :" + topic + ", authToken :" + data.authToken); 5575 5576 if(data.authToken){ 5577 _config.authToken = data.authToken; 5578 if(finesse.gadget && finesse.gadget.Config){ 5579 finesse.gadget.Config.authToken = data.authToken; 5580 } 5581 } 5582 }, 5583 5584 /** 5585 * @private 5586 * Retrieves systemAuthMode from parent Finesse Container. If parent is not available, mode will be retrieved from the systemInfo rest object 5587 * @throws {Error} 5588 * If unable to retrieve systemAuthMode 5589 */ 5590 _getSystemAuthMode = function(){ 5591 var parentFinesse , sysInfo; 5592 // For gadgets hosted outside of finesse container , finesse parent object will not be available 5593 try{ 5594 parentFinesse = window.parent.finesse; 5595 } catch (e){ 5596 parentFinesse = undefined; 5597 } 5598 5599 if( parentFinesse ){ 5600 _config.systemAuthMode = parentFinesse.container.Config.systemAuthMode; 5601 } else { 5602 sysInfo = new finesse.restservices.SystemInfo({ 5603 id: _config.id, 5604 onLoad: function (systemInfo) { 5605 _config.systemAuthMode = systemInfo.getSystemAuthMode(); 5606 }, 5607 onError: function (errRsp) { 5608 throw new Error("Unable to retrieve systemAuthMode from config. Initialization failed......"); 5609 } 5610 }); 5611 5612 } 5613 }; 5614 5615 return { 5616 5617 /** 5618 * @private 5619 * Adds an item to the list to be refreshed upon reconnect 5620 * @param {RestBase} object - rest object to be refreshed 5621 */ 5622 addToRefreshList: function (object) { 5623 _refreshList.push(object); 5624 }, 5625 5626 /** 5627 * Get the current state of the Bosh/Websocket connection 5628 * returns undefined when information not available. 5629 */ 5630 getNotificationConnectionState: function () { 5631 return _connInfo && _connInfo.status; 5632 }, 5633 5634 /** 5635 * @private 5636 * Removes the given item from the refresh list 5637 * @param {RestBase} object - rest object to be removed 5638 */ 5639 removeFromRefreshList: function (object) { 5640 var i; 5641 for (i = _refreshList.length - 1; i >= 0; i -= 1) { 5642 if (_refreshList[i] === object) { 5643 _refreshList.splice(i, 1); 5644 break; 5645 } 5646 } 5647 }, 5648 5649 /** 5650 * @private 5651 * The location of the tunnel HTML URL. 5652 * @returns {String} 5653 * The location of the tunnel HTML URL. 5654 */ 5655 getTunnelURL: function () { 5656 return _tunnel.getTunnelURL(); 5657 }, 5658 5659 /** 5660 * @private 5661 * Indicates whether the tunnel frame is loaded. 5662 * @returns {Boolean} 5663 * True if the tunnel frame is loaded, false otherwise. 5664 */ 5665 isTunnelLoaded: function () { 5666 return _tunnel.isTunnelLoaded(); 5667 }, 5668 5669 /** 5670 * @private 5671 * Indicates whether the ClientServices instance is a Master. 5672 * @returns {Boolean} 5673 * True if this instance of ClientServices is a Master, false otherwise. 5674 */ 5675 isMaster: function () { 5676 return _isMaster; 5677 }, 5678 5679 /** 5680 * @private 5681 * Get the resource ID. An ID is only available if the BOSH connection has 5682 * been able to connect successfully. 5683 * @returns {String} 5684 * The resource ID string. Null if the BOSH connection was never 5685 * successfully created and/or the resource ID has not been associated. 5686 */ 5687 getResourceID: function () { 5688 if (_connInfo !== undefined) { 5689 return _connInfo.resourceID; 5690 } 5691 return null; 5692 }, 5693 5694 /* 5695 getHub: function () { 5696 return _hub; 5697 }, 5698 */ 5699 /** 5700 * @private 5701 * Add a callback to be invoked when the BOSH connection is attempting 5702 * to connect. If the connection is already trying to connect, the 5703 * callback will be invoked immediately. 5704 * @param {Function} handler 5705 * An empty param function to be invoked on connecting. Only one 5706 * handler can be registered at a time. Handlers already registered 5707 * will be overwritten. 5708 */ 5709 registerOnConnectingHandler: function (handler) { 5710 _registerHandler(_STATUS.CONNECTING, handler); 5711 }, 5712 5713 /** 5714 * @private 5715 * Removes the on connecting callback that was registered. 5716 */ 5717 unregisterOnConnectingHandler: function () { 5718 _onConnectingHandler = undefined; 5719 }, 5720 5721 /** 5722 * Add a callback to be invoked when all of the following conditions are met: 5723 * <ul> 5724 * <li>When Finesse goes IN_SERVICE</li> 5725 * <li>The BOSH connection is established</li> 5726 * <li>The Finesse user presence becomes available</li> 5727 * </ul> 5728 * If all these conditions are met at the time this function is called, then 5729 * the handler will be invoked immediately. 5730 * @param {Function} handler 5731 * An empty param function to be invoked on connect. Only one handler 5732 * can be registered at a time. Handlers already registered will be 5733 * overwritten. 5734 * @example 5735 * finesse.clientservices.ClientServices.registerOnConnectHandler(gadget.myCallback); 5736 */ 5737 registerOnConnectHandler: function (handler) { 5738 _registerHandler(_STATUS.CONNECTED, handler); 5739 }, 5740 5741 /** 5742 * @private 5743 * Removes the on connect callback that was registered. 5744 */ 5745 unregisterOnConnectHandler: function () { 5746 _onConnectHandler = undefined; 5747 }, 5748 5749 /** 5750 * Add a callback to be invoked when any of the following occurs: 5751 * <ul> 5752 * <li>Finesse is no longer IN_SERVICE</li> 5753 * <li>The BOSH connection is lost</li> 5754 * <li>The presence of the Finesse user is no longer available</li> 5755 * </ul> 5756 * If any of these conditions are met at the time this function is 5757 * called, the callback will be invoked immediately. 5758 * @param {Function} handler 5759 * An empty param function to be invoked on disconnected. Only one 5760 * handler can be registered at a time. Handlers already registered 5761 * will be overwritten. 5762 * @example 5763 * finesse.clientservices.ClientServices.registerOnDisconnectHandler(gadget.myCallback); 5764 */ 5765 registerOnDisconnectHandler: function (handler) { 5766 _registerHandler(_STATUS.DISCONNECTED, handler); 5767 }, 5768 5769 /** 5770 * @private 5771 * Removes the on disconnect callback that was registered. 5772 */ 5773 unregisterOnDisconnectHandler: function () { 5774 _onDisconnectHandler = undefined; 5775 }, 5776 5777 /** 5778 * @private 5779 * Add a callback to be invoked when the BOSH is currently disconnecting. If 5780 * the connection is already disconnecting, invoke the callback immediately. 5781 * @param {Function} handler 5782 * An empty param function to be invoked on disconnected. Only one 5783 * handler can be registered at a time. Handlers already registered 5784 * will be overwritten. 5785 */ 5786 registerOnDisconnectingHandler: function (handler) { 5787 _registerHandler(_STATUS.DISCONNECTING, handler); 5788 }, 5789 5790 /** 5791 * @private 5792 * Removes the on disconnecting callback that was registered. 5793 */ 5794 unregisterOnDisconnectingHandler: function () { 5795 _onDisconnectingHandler = undefined; 5796 }, 5797 5798 /** 5799 * @private 5800 * Add a callback to be invoked when the BOSH connection is attempting 5801 * to connect. If the connection is already trying to connect, the 5802 * callback will be invoked immediately. 5803 * @param {Function} handler 5804 * An empty param function to be invoked on connecting. Only one 5805 * handler can be registered at a time. Handlers already registered 5806 * will be overwritten. 5807 */ 5808 registerOnReconnectingHandler: function (handler) { 5809 _registerHandler(_STATUS.RECONNECTING, handler); 5810 }, 5811 5812 /** 5813 * @private 5814 * Removes the on reconnecting callback that was registered. 5815 */ 5816 unregisterOnReconnectingHandler: function () { 5817 _onReconnectingHandler = undefined; 5818 }, 5819 5820 /** 5821 * @private 5822 * Add a callback to be invoked when the BOSH connection is unloading 5823 * 5824 * @param {Function} handler 5825 * An empty param function to be invoked on connecting. Only one 5826 * handler can be registered at a time. Handlers already registered 5827 * will be overwritten. 5828 */ 5829 registerOnUnloadingHandler: function (handler) { 5830 _registerHandler(_STATUS.UNLOADING, handler); 5831 }, 5832 5833 /** 5834 * @private 5835 * Removes the on unloading callback that was registered. 5836 */ 5837 unregisterOnUnloadingHandler: function () { 5838 _onUnloadingHandler = undefined; 5839 }, 5840 5841 /** 5842 * @private 5843 * Proxy method for gadgets.io.makeRequest. The will be identical to gadgets.io.makeRequest 5844 * ClientServices will mixin the BASIC Auth string, locale, and host, since the 5845 * configuration is encapsulated in here anyways. 5846 * This removes the dependency 5847 * @param {String} url 5848 * The relative url to make the request to (the host from the passed in config will be 5849 * appended). It is expected that any encoding to the URL is already done. 5850 * @param {Function} handler 5851 * Callback handler for makeRequest to invoke when the response returns. 5852 * Completely passed through to gadgets.io.makeRequest 5853 * @param {Object} params 5854 * The params object that gadgets.io.makeRequest expects. Authorization and locale 5855 * headers are mixed in. 5856 */ 5857 makeRequest: function (url, handler, params) { 5858 var requestedScheme, scheme = "http"; 5859 5860 // ClientServices needs to be initialized with a config for restHost, auth, and locale 5861 _isInited(); 5862 5863 // Allow mixin of auth and locale headers 5864 params = params || {}; 5865 5866 // Override refresh interval to 0 instead of default 3600 as a way to workaround makeRequest 5867 // using GET http method because then the params are added to the url as query params, which 5868 // exposes the authorization string in the url. This is a placeholder until oauth comes in 5869 params[gadgets.io.RequestParameters.REFRESH_INTERVAL] = params[gadgets.io.RequestParameters.REFRESH_INTERVAL] || 0; 5870 5871 params[gadgets.io.RequestParameters.HEADERS] = params[gadgets.io.RequestParameters.HEADERS] || {}; 5872 5873 // Add Basic auth to request header 5874 params[gadgets.io.RequestParameters.HEADERS].Authorization = _util.getAuthHeaderString(_config); 5875 5876 //Locale 5877 params[gadgets.io.RequestParameters.HEADERS].locale = _config.locale; 5878 5879 //Allow clients to override the scheme: 5880 // - If not specified => we use HTTP 5881 // - If null specified => we use _config.scheme 5882 // - Otherwise => we use whatever they provide 5883 requestedScheme = params.SCHEME; 5884 if (!(requestedScheme === undefined || requestedScheme === "undefined")) { 5885 if (requestedScheme === null) { 5886 scheme = _config.scheme; 5887 } else { 5888 scheme = requestedScheme; 5889 } 5890 } 5891 scheme = _config.restScheme || scheme; 5892 5893 _log("RequestedScheme: " + requestedScheme + "; Scheme: " + scheme); 5894 gadgets.io.makeRequest(encodeURI(scheme + "://" + _config.restHost + ":" + _config.localhostPort) + url, handler, params); 5895 }, 5896 5897 /** 5898 * @private 5899 * Utility function to make a subscription to a particular topic. Only one 5900 * callback function is registered to a particular topic at any time. 5901 * @param {String} topic 5902 * The full topic name. The topic name should follow the OpenAjax 5903 * convention using dot notation (ex: finesse.api.User.1000). 5904 * @param {Function} callback 5905 * The function that should be invoked with the data when an event 5906 * is delivered to the specific topic. 5907 * @param {Function} contextId 5908 * A unique id which gets appended to the topic for storing subscription details, 5909 * when multiple subscriptions to the same topic is required. 5910 * @returns {Boolean} 5911 * True if the subscription was made successfully and the callback was 5912 * been registered. False if the subscription already exist, the 5913 * callback was not overwritten. 5914 */ 5915 subscribe: function (topic, callback, disableDuringFailover, contextId) { 5916 _isInited(); 5917 5918 var topicId = topic + (contextId === undefined ? "" : contextId); 5919 5920 //Ensure that the same subscription isn't made twice. 5921 if (!_subscriptionID[topicId]) { 5922 //Store the subscription ID using the topic name as the key. 5923 _subscriptionID[topicId] = _hub.subscribe(topic, 5924 //Invoke the callback just with the data object. 5925 function (topic, data) { 5926 if (!disableDuringFailover || _isMaster || !_failoverMode) { 5927 // Halt all intermediate event processing while the master instance attempts to rebuild the connection. This only occurs: 5928 // - For RestBase objects (which pass the disableDuringFailover flag), so that intermediary events while recovering are hidden away until everything is all good 5929 // - We shouldn't be halting anything else because we have infrastructure requests that use the hub pub sub 5930 // - Master instance will get all events regardless, because it is responsible for managing failover 5931 // - If we are not in a failover mode, everything goes 5932 // _refreshObjects will reconcile anything that was missed once we are back in action 5933 callback(data); 5934 } 5935 }); 5936 return true; 5937 } 5938 return false; 5939 }, 5940 5941 /** 5942 * @private 5943 * Unsubscribe from a particular topic. 5944 * @param {String} topic 5945 * The full topic name. 5946 * @param {String} contextId 5947 * A unique id which gets appended to the topic for storing subscription details, 5948 * when multiple subscriptions to the same topic is required. 5949 */ 5950 unsubscribe: function (topic, contextId) { 5951 _isInited(); 5952 topic = topic + (contextId === undefined ? "" : contextId); 5953 5954 //Unsubscribe from the topic using the subscription ID recorded when 5955 //the subscription was made, then delete the ID from data structure. 5956 if (_subscriptionID[topic]) { 5957 _hub.unsubscribe(_subscriptionID[topic]); 5958 delete _subscriptionID[topic]; 5959 } 5960 }, 5961 5962 5963 /** 5964 * @private 5965 * Make a request to the request channel to have the Master subscribe 5966 * to a node. 5967 * @param {String} node 5968 * The node to subscribe to. 5969 */ 5970 subscribeNode: function (node, handler) { 5971 if (handler && typeof handler !== "function") { 5972 throw new Error("ClientServices.subscribeNode: handler is not a function"); 5973 } 5974 5975 // Construct the request to send to MasterPublisher through the OpenAjax Hub 5976 var data = { 5977 type: "SubscribeNodeReq", 5978 data: {node: node}, 5979 invokeID: _util.generateUUID() 5980 }, 5981 responseTopic = _topics.RESPONSES + "." + data.invokeID, 5982 _this = this; 5983 5984 // We need to first subscribe to the response channel 5985 this.subscribe(responseTopic, function (rsp) { 5986 // Since this channel is only used for this singular request, 5987 // we are not interested anymore. 5988 // This is also critical to not leaking memory by having OpenAjax 5989 // store a bunch of orphaned callback handlers that enclose on 5990 // our entire ClientServices singleton 5991 _this.unsubscribe(responseTopic); 5992 if (handler) { 5993 handler(data.invokeID, rsp); 5994 } 5995 }); 5996 // Then publish the request on the request channel 5997 _hub.publish(_topics.REQUESTS, data); 5998 }, 5999 6000 /** 6001 * @private 6002 * Make a request to the request channel to have the Master unsubscribe 6003 * from a node. 6004 * @param {String} node 6005 * The node to unsubscribe from. 6006 */ 6007 unsubscribeNode: function (node, subid, handler) { 6008 if (handler && typeof handler !== "function") { 6009 throw new Error("ClientServices.unsubscribeNode: handler is not a function"); 6010 } 6011 6012 // Construct the request to send to MasterPublisher through the OpenAjax Hub 6013 var data = { 6014 type: "UnsubscribeNodeReq", 6015 data: { 6016 node: node, 6017 subid: subid 6018 }, 6019 isForceOp: (typeof handler.isForceOp != 'undefined') ? handler.isForceOp: false, 6020 invokeID: _util.generateUUID() 6021 }, 6022 responseTopic = _topics.RESPONSES + "." + data.invokeID, 6023 _this = this; 6024 6025 // We need to first subscribe to the response channel 6026 this.subscribe(responseTopic, function (rsp) { 6027 // Since this channel is only used for this singular request, 6028 // we are not interested anymore. 6029 // This is also critical to not leaking memory by having OpenAjax 6030 // store a bunch of orphaned callback handlers that enclose on 6031 // our entire ClientServices singleton 6032 _this.unsubscribe(responseTopic); 6033 if (handler) { 6034 handler(rsp); 6035 } 6036 }); 6037 // Then publish the request on the request channel 6038 _hub.publish(_topics.REQUESTS, data); 6039 }, 6040 6041 6042 /** 6043 * @private 6044 * Make a request to the request channel to get the list of all subscriptions for the logged in user. 6045 */ 6046 getNodeSubscriptions: function (handler) { 6047 if (handler && typeof handler !== "function") { 6048 throw new Error("ClientServices.getNodeSubscriptions: handler is not a function"); 6049 } 6050 6051 // Construct the request to send to MasterPublisher through the OpenAjax Hub 6052 var data = { 6053 type: "SubscriptionsInfoReq", 6054 data: { 6055 }, 6056 invokeID: _util.generateUUID() 6057 }, 6058 responseTopic = _topics.RESPONSES + "." + data.invokeID, 6059 _this = this; 6060 6061 // We need to first subscribe to the response channel 6062 this.subscribe(responseTopic, function (rsp) { 6063 // Since this channel is only used for this singular request, 6064 // we are not interested anymore. 6065 // This is also critical to not leaking memory by having OpenAjax 6066 // store a bunch of orphaned callback handlers that enclose on 6067 // our entire ClientServices singleton 6068 _this.unsubscribe(responseTopic); 6069 if (handler) { 6070 handler(JSON.parse(rsp)); 6071 } 6072 }); 6073 // Then publish the request on the request channel 6074 _hub.publish(_topics.REQUESTS, data); 6075 }, 6076 6077 /** 6078 * @private 6079 * Make a request to the request channel to have the Master connect to the XMPP server via BOSH 6080 */ 6081 makeConnectionReq : function () { 6082 // Disallow others (non-masters) from administering BOSH connections that are not theirs 6083 if (_isMaster && _publisher) { 6084 _publisher.connect(_config.id, _config.password, _config.xmppDomain); 6085 } else { 6086 _log("F403: Access denied. Non-master ClientService instances do not have the clearance level to make this request: makeConnectionReq"); 6087 } 6088 }, 6089 6090 /** 6091 * @private 6092 * Set's the global logger for this Client Services instance. 6093 * @param {Object} logger 6094 * Logger object with the following attributes defined:<ul> 6095 * <li><b>log:</b> function (msg) to simply log a message 6096 * </ul> 6097 */ 6098 setLogger: function (logger) { 6099 // We want to check the logger coming in so we don't have to check every time it is called. 6100 if (logger && typeof logger === "object" && typeof logger.log === "function") { 6101 _logger = logger; 6102 } else { 6103 // We are resetting it to an empty object so that _logger.log in .log is falsy. 6104 _logger = {}; 6105 } 6106 }, 6107 6108 /** 6109 * @private 6110 * Centralized logger.log method for external logger 6111 * @param {String} msg 6112 * Message to log 6113 */ 6114 log: _log, 6115 6116 /** 6117 * @class 6118 * Allow clients to make Finesse API requests and consume Finesse events by 6119 * calling a set of exposed functions. The Services layer will do the dirty 6120 * work of establishing a shared BOSH connection (for designated Master 6121 * modules), consuming events for client subscriptions, and constructing API 6122 * requests. 6123 * 6124 * @constructs 6125 */ 6126 _fakeConstuctor: function () { 6127 /* This is here so we can document init() as a method rather than as a constructor. */ 6128 }, 6129 6130 /** 6131 * Initiates the Client Services with the specified config parameters. 6132 * Enabling the Client Services as Master will trigger the establishment 6133 * of a BOSH event connection. 6134 * @param {finesse.gadget.Config} config 6135 * Configuration object containing properties used for making REST requests:<ul> 6136 * <li><b>host:</b> The Finesse server IP/host as reachable from the browser 6137 * <li><b>restHost:</b> The Finesse API IP/host as reachable from the gadget container 6138 * <li><b>id:</b> The ID of the user. This is an optional param as long as the 6139 * appropriate authorization string is provided, otherwise it is 6140 * required.</li> 6141 * <li><b>password:</b> The password belonging to the user. This is an optional param as 6142 * long as the appropriate authorization string is provided, 6143 * otherwise it is required.</li> 6144 * <li><b>authorization:</b> The base64 encoded "id:password" authentication string. This 6145 * param is provided to allow the ability to hide the password 6146 * param. If provided, the id and the password extracted from this 6147 * string will be used over the config.id and config.password.</li> 6148 * </ul> 6149 * @throws {Error} If required constructor parameter is missing. 6150 * @example 6151 * finesse.clientservices.ClientServices.init(finesse.gadget.Config); 6152 */ 6153 init: function (config) { 6154 if (!_inited) { 6155 //Validate the properties within the config object if one is provided. 6156 if (!(typeof config === "object" && 6157 typeof config.host === "string" && config.host.length > 0 && config.restHost && 6158 (typeof config.authorization === "string" || 6159 (typeof config.id === "string")))) { 6160 throw new Error("Config object contains invalid properties."); 6161 } 6162 6163 // Initialize configuration 6164 _config = config; 6165 6166 // Set shortcuts 6167 _util = Utilities; 6168 _authModes = _util.getAuthModes(); 6169 _topics = Topics; 6170 6171 //TODO: document when this is properly supported 6172 // Allows hub and io dependencies to be passed in. Currently only used for unit tests. 6173 _hub = config.hub || gadgets.Hub; 6174 _io = config.io || gadgets.io; 6175 6176 //If the authorization string is provided, then use that to 6177 //extract the ID and the password. Otherwise use the ID and 6178 //password from the respective ID and password params. 6179 if (_config.authorization) { 6180 var creds = _util.getCredentials(_config.authorization); 6181 _config.id = creds.id; 6182 _config.password = creds.password; 6183 } 6184 else { 6185 _config.authorization = _util.b64Encode( 6186 _config.id + ":" + _config.password); 6187 } 6188 6189 //In case if gadgets create their own config instance , add systemAuthMode property inside config object 6190 if(!_config.systemAuthMode || _config.systemAuthMode === ""){ 6191 _getSystemAuthMode(); 6192 } 6193 6194 if(_config.systemAuthMode === _authModes.SSO){ 6195 _accessTokenRefreshHandler(undefined , {authToken : _util.getToken()}); 6196 if(!_config.authToken){ 6197 throw new Error("ClientServices.init() - Access token is unavailable inside Config object."); 6198 } 6199 6200 if (_hub){ 6201 _hub.subscribe(_topics.ACCESS_TOKEN_REFRESHED_EVENT, _accessTokenRefreshHandler); 6202 } 6203 } 6204 6205 _inited = true; 6206 6207 if (_hub) { 6208 //Subscribe to receive connection information. Since it is possible that 6209 //the client comes up after the Master comes up, the client will need 6210 //to make a request to have the Master send the latest connection info. 6211 //It would be possible that all clients get connection info again. 6212 this.subscribe(_topics.EVENTS_CONNECTION_INFO, _connInfoHandler); 6213 _makeConnectionInfoReq(); 6214 } 6215 } 6216 6217 //Return the CS object for object chaining. 6218 return this; 6219 }, 6220 6221 /** 6222 * @private 6223 * Initializes the BOSH component of this ClientServices instance. This establishes 6224 * the BOSH connection and will trigger the registered handlers as the connection 6225 * status changes respectively:<ul> 6226 * <li>registerOnConnectingHandler</li> 6227 * <li>registerOnConnectHandler</li> 6228 * <li>registerOnDisconnectHandler</li> 6229 * <li>registerOnDisconnectingHandler</li> 6230 * <li>registerOnReconnectingHandler</li> 6231 * <li>registerOnUnloadingHandler</li> 6232 * <ul> 6233 * 6234 * @param {Object} config 6235 * An object containing the following (optional) handlers for the request:<ul> 6236 * <li><b>xmppDomain:</b> {String} The domain of the XMPP server. Available from the SystemInfo object. 6237 * This is used to construct the JID: user@domain.com</li> 6238 * <li><b>pubsubDomain:</b> {String} The pub sub domain where the pub sub service is running. 6239 * Available from the SystemInfo object. 6240 * This is used for creating or removing subscriptions.</li> 6241 * <li><b>resource:</b> {String} The resource to connect to the notification server with.</li> 6242 * </ul> 6243 */ 6244 initBosh: function (config) { 6245 //Validate the properties within the config object if one is provided. 6246 if (!(typeof config === "object" && typeof config.xmppDomain === "string" && typeof config.pubsubDomain === "string")) { 6247 throw new Error("Config object contains invalid properties."); 6248 } 6249 6250 // Mixin the required information for establishing the BOSH connection 6251 _config.xmppDomain = config.xmppDomain; 6252 _config.pubsubDomain = config.pubsubDomain; 6253 _config.resource = config.resource; 6254 6255 //Initiate Master launch sequence 6256 _becomeMaster(); 6257 }, 6258 6259 /** 6260 * @private 6261 * Sets the failover mode to either FAILING or RECOVERED. This will only occur in the master instance and is meant to be 6262 * used by a stereotypical failover monitor type module to notify non-master instances (i.e. in gadgets) 6263 * @param {Object} failoverMode 6264 * true if failing, false or something falsy when recovered 6265 */ 6266 setFailoverMode: function (failoverMode) { 6267 if (_isMaster) { 6268 _hub.publish(_topics.EVENTS_CONNECTION_INFO, { 6269 status: (failoverMode ? _STATUS.FAILING : _STATUS.RECOVERED) 6270 }); 6271 } 6272 }, 6273 6274 /** 6275 * returns the destination host where the rest calls will be made proxied through shindig. 6276 */ 6277 getRestHost: function () { 6278 return (_config && _config.restHost) || 'localhost'; 6279 }, 6280 6281 /** 6282 * @private 6283 * Private accessor used to inject mocked private dependencies for unit testing 6284 */ 6285 _getTestObj: function () { 6286 return { 6287 setPublisher: function (publisher) { 6288 _publisher = publisher; 6289 } 6290 }; 6291 } 6292 }; 6293 }()); 6294 6295 window.finesse = window.finesse || {}; 6296 window.finesse.clientservices = window.finesse.clientservices || {}; 6297 window.finesse.clientservices.ClientServices = ClientServices; 6298 6299 return ClientServices; 6300 6301 }); 6302 6303 /** 6304 * The following comment prevents JSLint errors concerning undefined global variables. 6305 * It tells JSLint that these identifiers are defined elsewhere. 6306 */ 6307 /*jslint bitwise:true, browser:true, nomen:true, regexp:true, sloppy:true, white:true */ 6308 6309 /** The following comment is to prevent jslint errors about 6310 * using variables before they are defined. 6311 */ 6312 /*global Handlebars */ 6313 6314 /** 6315 * JavaScript class to implement common notification 6316 * functionality. 6317 * 6318 * @requires Class 6319 * @requires finesse.FinesseBase 6320 */ 6321 /** @private */ 6322 define('restservices/Notifier',[ 6323 'FinesseBase', 6324 'clientservices/ClientServices' 6325 ], 6326 function (FinesseBase, ClientServices) { 6327 var Notifier = FinesseBase.extend({ 6328 /** 6329 * Initializes the notifier object. 6330 */ 6331 init : function () { 6332 this._super(); 6333 this._listenerCallback = []; 6334 }, 6335 6336 /** 6337 * Add a listener. 6338 * 6339 * @param callback_function 6340 * @param scope 6341 * is the callback function to add 6342 */ 6343 addListener : function (callback_function, scope) { 6344 var len = this._listenerCallback.length, i, cb, add = true; 6345 for (i = 0; i < len; i += 1) { 6346 cb = this._listenerCallback[i].callback; 6347 if (cb === callback_function) { 6348 // this callback already exists 6349 add = false; 6350 break; 6351 } 6352 } 6353 if (add) { 6354 this._listenerCallback.push({ "callback": this._isAFunction(callback_function), "scope": (scope || window) }); 6355 } 6356 }, 6357 6358 /** 6359 * Remove a listener. 6360 * 6361 * @param callback_function 6362 * is the callback function to remove 6363 * @return {Boolean} true if removed 6364 */ 6365 removeListener : function (callback_function) { 6366 6367 var result = false, len = this._listenerCallback.length, i, cb; 6368 for (i = len - 1; i >= 0; i -=1) { 6369 cb = this._listenerCallback[i].callback; 6370 if (cb === callback_function) { 6371 this._listenerCallback[i] = undefined; 6372 this._listenerCallback.splice(i, 1); 6373 result = true; 6374 break; 6375 } 6376 } 6377 6378 return result; 6379 }, 6380 6381 /** 6382 * Removes all listeners 6383 * @return {undefined} 6384 */ 6385 reset: function () { 6386 this._listenerCallback = []; 6387 }, 6388 6389 /** 6390 * Notify all listeners. 6391 * 6392 * @param obj 6393 * is the object that has changed 6394 */ 6395 notifyListeners : function (obj) { 6396 var len = this._listenerCallback.length, i, callbackFunction, scope; 6397 6398 for (i = 0; i < len; i += 1) { 6399 // Be sure that one bad callback does not prevent other listeners 6400 // from receiving. 6401 try { 6402 callbackFunction = this._listenerCallback[i].callback; 6403 scope = this._listenerCallback[i].scope; 6404 if (typeof callbackFunction === 'function') { 6405 callbackFunction.call(scope, obj); 6406 } 6407 } catch (err) { 6408 ClientServices.log("Notifier.js#notifyListeners: Exception caught: ", err); 6409 } 6410 } 6411 }, 6412 6413 /** 6414 * Gets a copy of the listeners. 6415 * @return changeListenerCopy (array of callbacks) 6416 */ 6417 getListeners : function () { 6418 var changeListenerCopy = [], len = this._listenerCallback.length, i; 6419 6420 for (i = 0; i < len; i += 1) { 6421 changeListenerCopy.push(this._listenerCallback[i].callback); 6422 } 6423 6424 return changeListenerCopy; 6425 }, 6426 6427 /** 6428 * Verifies that the handler is function. 6429 * @param handler to verify 6430 * @return the handler 6431 * @throws Error if not a function 6432 */ 6433 _isAFunction : function (handler) { 6434 if (handler === undefined || typeof handler === "function") { 6435 return handler; 6436 } else { 6437 throw new Error("handler must be a function"); 6438 } 6439 } 6440 }); 6441 6442 window.finesse = window.finesse || {}; 6443 window.finesse.restservices = window.finesse.restservices || {}; 6444 window.finesse.restservices.Notifier = Notifier; 6445 6446 /** @namespace JavaScript classes and methods that represent REST objects and collections. */ 6447 finesse.restservices = finesse.restservices || {}; 6448 6449 return Notifier; 6450 }); 6451 6452 /** 6453 * JavaScript base object that all REST objects should inherit 6454 * from because it encapsulates and provides the common functionality that 6455 * all REST objects need. 6456 * 6457 * @requires finesse.clientservices.ClientServices 6458 * @requires Class 6459 */ 6460 6461 /** @private */ 6462 define('restservices/RestBase',[ 6463 "FinesseBase", 6464 "utilities/Utilities", 6465 "restservices/Notifier", 6466 "clientservices/ClientServices", 6467 "clientservices/Topics" 6468 ], 6469 function (FinesseBase, Utilities, Notifier, ClientServices, Topics) { 6470 6471 var RestBase = FinesseBase.extend(/** @lends finesse.restservices.RestBase.prototype */{ 6472 6473 doNotLog: false, 6474 6475 /** 6476 * Used by _processUpdate() and restRequest(). 6477 * Maps requestIds to object-wrapped callbacks passed to restRequest(), 6478 * so that one of the callbacks can be fired when a corresponding event is 6479 * received inside _processUpdate(). 6480 * @private 6481 */ 6482 _pendingCallbacks: {}, 6483 6484 /** 6485 * Gets the REST class for the current object. This object throws an 6486 * exception because subtype must implement. 6487 * @throws {Error} because subtype must implement 6488 * @private 6489 */ 6490 getRestClass: function () { 6491 throw new Error("getRestClass(): Not implemented in subtype."); 6492 }, 6493 6494 /** 6495 * Gets the REST type for the current object. This object throws an 6496 * exception because subtype must implement. 6497 * @throws {Error} because subtype must implement. 6498 * @private 6499 */ 6500 getRestType: function () { 6501 throw new Error("getRestType(): Not implemented in subtype."); 6502 }, 6503 6504 /** 6505 * Gets the node path for the current object. 6506 * @private 6507 */ 6508 getXMPPNodePath: function () { 6509 return this.getRestUrl(); 6510 }, 6511 6512 /** 6513 * Boolean function that specifies whether the REST object supports 6514 * requests. True by default. Subclasses should override if false. 6515 * @private 6516 */ 6517 supportsRequests: true, 6518 6519 /** 6520 * Boolean function that specifies whether the REST object supports 6521 * subscriptions. True by default. Subclasses should override if false. 6522 * @private 6523 */ 6524 supportsSubscriptions: true, 6525 6526 /** 6527 * Boolean function that specifies whether the REST object should retain 6528 * a copy of the REST response. False by default. Subclasses should override if true. 6529 * @private 6530 */ 6531 keepRestResponse: false, 6532 6533 /** 6534 * Number that represents the REST Response status that is returned. Only 6535 * set if keepRestResponse is set to true. 6536 * @public 6537 */ 6538 restResponseStatus: null, 6539 6540 /** 6541 * Object to store additional headers to be sent with the REST Request, null by default. 6542 * @private 6543 */ 6544 extraHeaders: null, 6545 6546 /** 6547 * Boolean function that specifies whether the REST object explicitly 6548 * subscribes. False by default. Subclasses should override if true. 6549 * @private 6550 */ 6551 explicitSubscription: false, 6552 6553 /** 6554 * Boolean function that specifies whether subscribing should be 6555 * automatically done at construction. Defaults to true. 6556 * This be overridden at object construction, not by implementing subclasses 6557 * @private 6558 */ 6559 autoSubscribe: true, 6560 6561 /** 6562 * A unique id which gets appended to the topic for storing subscription details, 6563 * when multiple subscriptions to the same topic is required. 6564 * @private 6565 */ 6566 contextId: '', 6567 6568 /** 6569 Default ajax request timout value 6570 */ 6571 ajaxRequestTimeout : 5000, 6572 6573 /** 6574 * Private reference to default logger 6575 * @private 6576 */ 6577 _logger: { 6578 log: ClientServices.log, 6579 error: ClientServices.log 6580 }, 6581 6582 /** 6583 * @class 6584 * JavaScript representation of a REST object. Also exposes methods to operate 6585 * on the object against the server. This object is typically extended into individual 6586 * REST Objects (like Dialog, User, etc...), and shouldn't be used directly. 6587 * 6588 * @constructor 6589 * @param {String} id 6590 * The ID that uniquely identifies the REST object. 6591 * @param {Object} callbacks 6592 * An object containing callbacks for instantiation and runtime 6593 * Callback to invoke upon successful instantiation, passes in REST object. 6594 * @param {Function} callbacks.onLoad(this) 6595 * Callback to invoke upon loading the data for the first time. 6596 * @param {Function} callbacks.onChange(this) 6597 * Callback to invoke upon successful update object (PUT) 6598 * @param {Function} callbacks.onAdd(this) 6599 * Callback to invoke upon successful update to add object (POST) 6600 * @param {Function} callbacks.onDelete(this) 6601 * Callback to invoke upon successful update to delete object (DELETE) 6602 * @param {Function} callbacks.onError(rsp) 6603 * Callback to invoke on update error (refresh or event) 6604 * as passed by finesse.restservices.RestBase.restRequest() 6605 * { 6606 * status: {Number} The HTTP status code returned 6607 * content: {String} Raw string of response 6608 * object: {Object} Parsed object of response 6609 * error: {Object} Wrapped exception that was caught 6610 * error.errorType: {String} Type of error that was caught 6611 * error.errorMessage: {String} Message associated with error 6612 * } 6613 * @param {RestBase} [restObj] 6614 * A RestBase parent object which this object has an association with. 6615 * @constructs 6616 */ 6617 init: function (options, callbacks, restObj) { 6618 /** 6619 * Initialize the base class 6620 */ 6621 var _this = this; 6622 6623 this._super(); 6624 6625 if (typeof options === "object") { 6626 this._id = options.id; 6627 this._restObj = options.parentObj; 6628 this.autoSubscribe = (options.autoSubscribe === false) ? false : true; 6629 this.contextId = options.contextId ? options.contextId : this.contextId; 6630 // Pass timeout value in options object if we want to override default ajax request timeout of 5 seconds while fetching the resource 6631 this.ajaxRequestTimeout = options.timeout || this.ajaxRequestTimeout; 6632 this.doNotSubscribe = options.doNotSubscribe; 6633 this.doNotRefresh = this.doNotRefresh || options.doNotRefresh; 6634 if (typeof options.supportsRequests != "undefined") { 6635 this.supportsRequests = options.supportsRequests; 6636 } 6637 callbacks = { 6638 onLoad: options.onLoad, 6639 onChange: options.onChange, 6640 onAdd: options.onAdd, 6641 onDelete: options.onDelete, 6642 onError: options.onError 6643 }; 6644 } else { 6645 this._id = options; 6646 this._restObj = restObj; 6647 } 6648 6649 // Common stuff 6650 6651 this._data = {}; 6652 6653 //Contains the full rest response to be processed by upper layers if needed 6654 this._restResponse = undefined; 6655 6656 this._lastUpdate = {}; 6657 6658 this._util = Utilities; 6659 6660 //Should be correctly initialized in either a window OR gadget context 6661 this._config = finesse.container.Config; 6662 6663 // Setup all the notifiers - change, load and error. 6664 this._changeNotifier = new Notifier(); 6665 this._loadNotifier = new Notifier(); 6666 this._addNotifier = new Notifier(); 6667 this._deleteNotifier = new Notifier(); 6668 this._errorNotifier = new Notifier(); 6669 6670 this._loaded = false; 6671 6672 // Protect against null dereferencing of options allowing its 6673 // (nonexistent) keys to be read as undefined 6674 callbacks = callbacks || {}; 6675 6676 this.addHandler('load', callbacks.onLoad); 6677 this.addHandler('change', callbacks.onChange); 6678 this.addHandler('add', callbacks.onAdd); 6679 this.addHandler('delete', callbacks.onDelete); 6680 this.addHandler('error', callbacks.onError); 6681 6682 // Attempt to get the RestType then synchronize 6683 try { 6684 this.getRestType(); 6685 6686 // Only subscribe if this REST object supports subscriptions 6687 // and autoSubscribe was not requested to be disabled as a construction option 6688 if (this.supportsSubscriptions && this.autoSubscribe && !this.doNotSubscribe) { 6689 this.subscribe({ 6690 success: function () { 6691 //TODO: figure out how to use Function.call() or Function.apply() here... 6692 //this is exactly the same as the below else case other than the scope of "this" 6693 if (typeof options === "object" && options.data) { 6694 if (!_this._processObject(_this._normalize(options.data))) { 6695 // notify of error if we fail to construct 6696 _this._errorNotifier.notifyListeners(_this); 6697 } 6698 } else { 6699 // Only subscribe if this REST object supports requests 6700 if (_this.supportsRequests) { 6701 _this._synchronize(); 6702 } 6703 } 6704 }, 6705 error: function (err) { 6706 _this._errorNotifier.notifyListeners(err); 6707 } 6708 }); 6709 } else { 6710 if (typeof options === "object" && options.data) { 6711 if (!this._processObject(this._normalize(options.data))) { 6712 // notify of error if we fail to construct 6713 this._errorNotifier.notifyListeners(this); 6714 } 6715 } else { 6716 // Only subscribe if this REST object supports requests 6717 if (this.supportsRequests) { 6718 this._synchronize(); 6719 } 6720 } 6721 } 6722 6723 } catch (err) { 6724 this._logger.error('id=' + this._id + ': ' + err); 6725 } 6726 }, 6727 6728 /** 6729 * Determines if the object has a particular property. 6730 * @param obj is the object to examine 6731 * @param property is the property to check for 6732 * @returns {Boolean} 6733 */ 6734 hasProperty: function (obj, prop) { 6735 return (obj !== null) && (obj.hasOwnProperty(prop)); 6736 }, 6737 6738 /** 6739 * Gets a property from the object. 6740 * @param obj is the object to examine 6741 * @param property is the property to get 6742 * @returns {Property Value} or {Null} if not found 6743 */ 6744 getProperty: function (obj, property) { 6745 var result = null; 6746 6747 if (this.hasProperty(obj, property) === false) { 6748 result = null; 6749 } else { 6750 result = obj[property]; 6751 } 6752 return result; 6753 }, 6754 6755 /** 6756 * Utility to extracts the ID from the specified REST URI. This is with the 6757 * assumption that the ID is always the last element in the URI after the 6758 * "/" delimiter. 6759 * @param {String} restUri 6760 * The REST uri (i.e. /finesse/api/User/1000). 6761 * @private 6762 */ 6763 _extractId: function (restObj) { 6764 var obj, restUri = "", strLoc; 6765 for (obj in restObj) { 6766 if (restObj.hasOwnProperty(obj)) { 6767 restUri = restObj[obj].uri; 6768 break; 6769 } 6770 } 6771 return Utilities.getId(restUri); 6772 }, 6773 6774 /** 6775 * Gets the data for this object. 6776 * @returns {Object} which is contained in data 6777 */ 6778 getData: function () { 6779 return this._data; 6780 }, 6781 6782 /** 6783 * Gets the complete REST response to the request made 6784 * @returns {Object} which is contained in data 6785 * @private 6786 */ 6787 getRestResponse: function () { 6788 return this._restResponse; 6789 }, 6790 6791 /** 6792 * Gets the REST response status to the request made 6793 * @returns {Integer} response status 6794 * @private 6795 */ 6796 getRestResponseStatus: function () { 6797 return this.restResponseStatus; 6798 }, 6799 6800 /** 6801 * The REST URL in which this object can be referenced. 6802 * @return {String} 6803 * The REST URI for this object. 6804 * @private 6805 */ 6806 getRestUrl: function () { 6807 var 6808 restObj = this._restObj, 6809 restUrl = ""; 6810 6811 //Prepend the base REST object if one was provided. 6812 if (restObj instanceof RestBase) { 6813 restUrl += restObj.getRestUrl(); 6814 } 6815 //Otherwise prepend with the default webapp name. 6816 else { 6817 restUrl += "/finesse/api"; 6818 } 6819 6820 //Append the REST type. 6821 restUrl += "/" + this.getRestType(); 6822 6823 //Append ID if it is not undefined, null, or empty. 6824 if (this._id) { 6825 restUrl += "/" + this._id; 6826 } 6827 return restUrl; 6828 }, 6829 6830 /** 6831 * Getter for the id of this RestBase 6832 * @returns {String} 6833 * The id of this RestBase 6834 */ 6835 getId: function () { 6836 return this._id; 6837 }, 6838 6839 /** 6840 * Synchronize this object with the server using REST GET request. 6841 * @returns {Object} 6842 * { 6843 * abort: {function} Function that signifies the callback handler to NOT process the response of the rest request 6844 * } 6845 * @private 6846 */ 6847 _synchronize: function (retries) { 6848 // Fetch this REST object 6849 if (typeof this._id === "string") { 6850 var _this = this, isLoaded = this._loaded, _RETRY_INTERVAL = 10 * 1000; 6851 6852 return this._doGET( 6853 { 6854 success: function (rsp) { 6855 if (!_this._processResponse(rsp)) { 6856 if (retries > 0) { 6857 setTimeout(function () { 6858 _this._synchronize(retries - 1); 6859 }, _RETRY_INTERVAL); 6860 } else { 6861 _this._errorNotifier.notifyListeners(_this); 6862 } 6863 } else { 6864 // If this object was already "loaded" prior to 6865 // the _doGET request, then call the 6866 // changeNotifier 6867 if (isLoaded) { 6868 _this._changeNotifier.notifyListeners(_this); 6869 } 6870 } 6871 }, 6872 error: function (rsp) { 6873 if (retries > 0) { 6874 setTimeout(function () { 6875 _this._synchronize(retries - 1); 6876 }, _RETRY_INTERVAL); 6877 6878 } else { 6879 _this._errorNotifier.notifyListeners(rsp); 6880 } 6881 } 6882 } 6883 ); 6884 6885 } else { 6886 throw new Error("Can't construct a <" + this.getRestType() + "> due to invalid id type."); 6887 } 6888 }, 6889 6890 /** 6891 * Adds an handler to this object. 6892 * If notifierType is 'load' and the object has already loaded, the callback is invoked immediately 6893 * @param {String} notifierType 6894 * The type of notifier to add to ('load', 'change', 'add', 'delete', 'error') 6895 * @param {Function} callback 6896 * The function callback to invoke. 6897 * @example 6898 * // Handler for additions to the Dialogs collection object. 6899 * // When Dialog (a RestBase object) is created, add a change handler. 6900 * _handleDialogAdd = function(dialog) { 6901 * dialog.addHandler('change', _handleDialogChange); 6902 * } 6903 */ 6904 addHandler: function (notifierType, callback, scope) { 6905 var notifier = null; 6906 try { 6907 Utilities.validateHandler(callback); 6908 6909 notifier = this._getNotifierReference(notifierType); 6910 6911 notifier.addListener(callback, scope); 6912 6913 // If load handler is added and object has 6914 // already been loaded, invoke callback 6915 // immediately 6916 if (notifierType === 'load' && this._loaded) { 6917 callback.call((scope || window), this); 6918 } 6919 } catch (err) { 6920 this._logger.error('id=' + this._id + ': ' + err); 6921 } 6922 }, 6923 6924 /** 6925 * Removes a handler from this object. 6926 * @param {String} notifierType 6927 * The type of notifier to remove ('load', 'change', 'add', 'delete', 'error') 6928 * @param {Function} callback 6929 * The function to remove. 6930 */ 6931 removeHandler: function (notifierType, callback) { 6932 var notifier = null; 6933 try { 6934 Utilities.validateHandler(callback); 6935 6936 notifier = this._getNotifierReference(notifierType); 6937 6938 if (typeof(callback) === "undefined") 6939 { 6940 // Remove all listeners for the type 6941 notifier.reset(); 6942 } 6943 else 6944 { 6945 // Remove the specified listener 6946 finesse.utilities.Utilities.validateHandler(callback); 6947 notifier.removeListener(callback); 6948 } 6949 } catch (err) { 6950 this._logger.error('id=' + this._id + ': ' + err); 6951 } 6952 }, 6953 6954 /** 6955 * Utility method gating any operations that require complete instantiation 6956 * @throws Error 6957 * If this object was not fully instantiated yet 6958 * @returns {finesse.restservices.RestBase} 6959 * This RestBase object to allow cascading 6960 */ 6961 isLoaded: function () { 6962 if (!this._loaded) { 6963 throw new Error("Cannot operate on object that is not fully instantiated, use onLoad/onLoadError handlers"); 6964 } 6965 return this; // Allow cascading 6966 }, 6967 6968 6969 6970 /** 6971 * Force an update on this object. Since an asynchronous GET is performed, 6972 * it is necessary to have an onChange handler registered in order to be 6973 * notified when the response of this returns. 6974 * @param {Integer} retries 6975 * The number or retry attempts to make. 6976 * @returns {Object} 6977 * { 6978 * abort: {function} Function that signifies the callback handler to NOT process the response of the asynchronous request 6979 * } 6980 */ 6981 refresh: function (retries) { 6982 var _this = this; 6983 6984 if (this.explicitSubscription) { 6985 this._subscribeNode({ 6986 success: function () { 6987 //Disallow GETs if object doesn't support it. 6988 if (!_this.supportsRequests) { 6989 throw new Error("Object doesn't support request operations."); 6990 } 6991 6992 _this._synchronize(retries); 6993 6994 return this; // Allow cascading 6995 }, 6996 error: function (err) { 6997 _this._errorNotifier.notifyListeners(err); 6998 } 6999 }); 7000 } else { 7001 //Disallow GETs if object doesn't support it. 7002 if (!this.supportsRequests) { 7003 throw new Error("Object doesn't support request operations."); 7004 } 7005 7006 return this._synchronize(retries); 7007 } 7008 }, 7009 7010 /** 7011 * Utility method to validate against the known schema of this RestBase 7012 * @param {Object} obj 7013 * The object to validate 7014 * @returns {Boolean} 7015 * True if the object fits the schema of this object. This usually 7016 * means all required keys or nested objects are present. 7017 * False otherwise. 7018 * @private 7019 */ 7020 _validate: function (obj) { 7021 var valid = (typeof obj === "object" && this.hasProperty(obj, this.getRestType())); 7022 if (!valid) 7023 { 7024 this._logger.error(this.getRestType() + " failed validation! Does your JS REST class return the correct string from getRestType()?"); 7025 } 7026 return valid; 7027 }, 7028 7029 /** 7030 * Utility method to fetch this RestBase from the server 7031 * @param {finesse.interfaces.RequestHandlers} handlers 7032 * An object containing the handlers for the request 7033 * @returns {Object} 7034 * { 7035 * abort: {function} Function that signifies the callback handler to NOT process the response of the rest request 7036 * } 7037 * @private 7038 */ 7039 _doGET: function (handlers) { 7040 return this.restRequest(this.getRestUrl(), handlers); 7041 }, 7042 7043 /** 7044 * Common update event handler used by the pubsub callback closure. 7045 * Processes the update event then notifies listeners. 7046 * @param {Object} scope 7047 * An object containing callbacks to handle the asynchronous get 7048 * @param {Object} update 7049 * An object containing callbacks to handle the asynchronous get 7050 * @private 7051 */ 7052 _updateEventHandler: function (scope, update) { 7053 if (scope._processUpdate(update)) { 7054 switch (update.object.Update.event) { 7055 case "POST": 7056 scope._addNotifier.notifyListeners(scope); 7057 break; 7058 case "PUT": 7059 scope._changeNotifier.notifyListeners(scope); 7060 break; 7061 case "DELETE": 7062 scope._deleteNotifier.notifyListeners(scope); 7063 break; 7064 } 7065 } 7066 }, 7067 7068 /** 7069 * Utility method to create a callback to be given to OpenAjax to invoke when a message 7070 * is published on the topic of our REST URL (also XEP-0060 node). 7071 * This needs to be its own defined method so that subclasses can have their own implementation. 7072 * @returns {Function} callback(update) 7073 * The callback to be invoked when an update event is received. This callback will 7074 * process the update and notify listeners. 7075 * @private 7076 */ 7077 _createPubsubCallback: function () { 7078 var _this = this; 7079 return function (update) { 7080 _this._updateEventHandler(_this, update); 7081 }; 7082 }, 7083 7084 /** 7085 * Subscribe to pubsub infra using the REST URL as the topic name. 7086 * @param {finesse.interfaces.RequestHandlers} handlers 7087 * An object containing the handlers for the request 7088 * @private 7089 */ 7090 subscribe: function (callbacks) { 7091 // Only need to do a subscription to client pubsub. No need to trigger 7092 // a subscription on the Finesse server due to implicit subscribe (at 7093 // least for now). 7094 var _this = this, 7095 topic = Topics.getTopic(this.getXMPPNodePath()), 7096 handlers, 7097 successful = ClientServices.subscribe(topic, this._createPubsubCallback(), true, this.contextId); 7098 7099 callbacks = callbacks || {}; 7100 7101 handlers = { 7102 /** @private */ 7103 success: function () { 7104 // Add item to the refresh list in ClientServices to refresh if 7105 // we recover due to our resilient connection. However, do 7106 // not add if doNotRefresh flag is set. 7107 if (!_this.doNotRefresh) { 7108 ClientServices.addToRefreshList(_this); 7109 } 7110 7111 if (typeof callbacks.success === "function") { 7112 callbacks.success(); 7113 } 7114 }, 7115 /** @private */ 7116 error: function (err) { 7117 if (successful) { 7118 ClientServices.unsubscribe(topic, this.contextId); 7119 } 7120 7121 if (typeof callbacks.error === "function") { 7122 callbacks.error(err); 7123 } 7124 } 7125 }; 7126 7127 // Request a node subscription only if this object requires explicit subscriptions 7128 if (this.explicitSubscription === true) { 7129 this._subscribeNode(handlers); 7130 } else { 7131 if (successful) { 7132 this._subid = "OpenAjaxOnly"; 7133 handlers.success(); 7134 } else { 7135 handlers.error(); 7136 } 7137 } 7138 7139 return this; 7140 }, 7141 7142 /** 7143 * Unsubscribe to pubsub infra using the REST URL as the topic name. 7144 * @param {finesse.interfaces.RequestHandlers} handlers 7145 * An object containing the handlers for the request 7146 * @private 7147 */ 7148 unsubscribe: function (callbacks) { 7149 // Only need to do a subscription to client pubsub. No need to trigger 7150 // a subscription on the Finesse server due to implicit subscribe (at 7151 // least for now). 7152 var _this = this, 7153 topic = Topics.getTopic(this.getRestUrl()), 7154 handlers; 7155 7156 // no longer keep track of object to refresh on reconnect 7157 ClientServices.removeFromRefreshList(_this); 7158 7159 callbacks = callbacks || {}; 7160 7161 handlers = { 7162 /** @private */ 7163 success: function () { 7164 if (typeof callbacks.success === "function") { 7165 callbacks.success(); 7166 } 7167 }, 7168 /** @private */ 7169 error: function (err) { 7170 if (typeof callbacks.error === "function") { 7171 callbacks.error(err); 7172 } 7173 } 7174 }; 7175 7176 if (this._subid) { 7177 ClientServices.unsubscribe(topic, this.contextId); 7178 // Request a node unsubscribe only if this object requires explicit subscriptions 7179 if (this.explicitSubscription === true) { 7180 this._unsubscribeNode(handlers); 7181 } else { 7182 this._subid = undefined; 7183 handlers.success(); 7184 } 7185 } else { 7186 handlers.success(); 7187 } 7188 7189 return this; 7190 }, 7191 7192 /** 7193 * Private utility to perform node subscribe requests for explicit subscriptions 7194 * @param {finesse.interfaces.RequestHandlers} handlers 7195 * An object containing the handlers for the request 7196 * @private 7197 */ 7198 _subscribeNode: function (callbacks) { 7199 var _this = this; 7200 7201 // Protect against null dereferencing of callbacks allowing its (nonexistent) keys to be read as undefined 7202 callbacks = callbacks || {}; 7203 7204 ClientServices.subscribeNode(this.getXMPPNodePath(), function (subid, err) { 7205 if (err) { 7206 if (typeof callbacks.error === "function") { 7207 callbacks.error(err); 7208 } 7209 } else { 7210 // Store the subid on a successful subscribe 7211 _this._subid = subid; 7212 if (typeof callbacks.success === "function") { 7213 callbacks.success(); 7214 } 7215 } 7216 }); 7217 }, 7218 7219 /** 7220 * Private utility to perform node unsubscribe requests for explicit subscriptions 7221 * @param {finesse.interfaces.RequestHandlers} handlers 7222 * An object containing the handlers for the request 7223 * @private 7224 */ 7225 _unsubscribeNode: function (callbacks) { 7226 var _this = this; 7227 7228 // Protect against null dereferencing of callbacks allowing its (nonexistent) keys to be read as undefined 7229 callbacks = callbacks || {}; 7230 7231 ClientServices.unsubscribeNode(this.getXMPPNodePath(), this._subid, function (err) { 7232 _this._subid = undefined; 7233 if (err) { 7234 if (typeof callbacks.error === "function") { 7235 callbacks.error(err); 7236 } 7237 } else { 7238 if (typeof callbacks.success === "function") { 7239 callbacks.success(); 7240 } 7241 } 7242 }); 7243 }, 7244 7245 /** 7246 * Validate and store the object into the internal data store. 7247 * @param {Object} object 7248 * The JavaScript object that should match of schema of this REST object. 7249 * @returns {Boolean} 7250 * True if the object was validated and stored successfully. 7251 * @private 7252 */ 7253 _processObject: function (object) { 7254 if (this._validate(object)) { 7255 this._data = this.getProperty(object, this.getRestType()); // Should clone the object here? 7256 7257 // If loaded for the first time, call the load notifiers. 7258 if (!this._loaded) { 7259 this._loaded = true; 7260 this._loadNotifier.notifyListeners(this); 7261 } 7262 7263 return true; 7264 } 7265 return false; 7266 }, 7267 7268 /** 7269 * Normalize the object to mitigate the differences between the backend 7270 * and what this REST object should hold. For example, the backend sends 7271 * send an event with the root property name being lower case. In order to 7272 * match the GET, the property should be normalized to an upper case. 7273 * @param {Object} object 7274 * The object which should be normalized. 7275 * @returns {Object} 7276 * Return the normalized object. 7277 * @private 7278 */ 7279 _normalize: function (object) { 7280 var 7281 restType = this.getRestType(), 7282 // Get the REST object name with first character being lower case. 7283 objRestType = restType.charAt(0).toLowerCase() + restType.slice(1); 7284 7285 // Normalize payload to match REST object. The payload for an update 7286 // use a lower case object name as oppose to upper case. Only normalize 7287 // if necessary. 7288 if (!this.hasProperty(object, restType) && this.hasProperty(object, objRestType)) { 7289 //Since the object is going to be modified, clone the object so that 7290 //it doesn't affect others (due to OpenAjax publishing to other 7291 //subscriber. 7292 object = jQuery.extend(true, {}, object); 7293 7294 object[restType] = object[objRestType]; 7295 delete(object[objRestType]); 7296 } 7297 return object; 7298 }, 7299 7300 /** 7301 * Utility method to process the response of a successful get 7302 * @param {Object} rsp 7303 * The response of a successful get 7304 * @returns {Boolean} 7305 * True if the update was successfully processed (the response object 7306 * passed the schema validation) and updated the internal data cache, 7307 * false otherwise. 7308 * @private 7309 */ 7310 _processResponse: function (rsp) { 7311 try { 7312 if (this.keepRestResponse) { 7313 this._restResponse = rsp.content; 7314 this.restResponseStatus = rsp.status; 7315 } 7316 return this._processObject(rsp.object); 7317 } 7318 catch (err) { 7319 this._logger.error(this.getRestType() + ': ' + err); 7320 } 7321 return false; 7322 }, 7323 7324 /** 7325 * Method that is called at the end of _processUpdate() which by default 7326 * will just delete the requestId-to-callbacks mapping but can be overridden. 7327 * @param {String} requestId The requestId of the event 7328 */ 7329 _postProcessUpdateStrategy: function (requestId) { 7330 //Clean up _pendingCallbacks now that we fired a callback corresponding to the received requestId. 7331 delete this._pendingCallbacks[requestId]; 7332 }, 7333 7334 /** 7335 * Utility method to process the update notification. 7336 * @param {Object} update 7337 * The payload of an update notification. 7338 * @returns {Boolean} 7339 * True if the update was successfully processed (the update object 7340 * passed the schema validation) and updated the internal data cache, 7341 * false otherwise. 7342 * @private 7343 */ 7344 _processUpdate: function (update) { 7345 try { 7346 var updateObj, requestId, fakeResponse, receivedError; 7347 7348 // The backend will send the data object with a lower case. To be 7349 // consistent with what should be represented in this object, the 7350 // object name should be upper case. This will normalize the object. 7351 updateObj = this._normalize(update.object.Update.data); 7352 7353 // Store the last event. 7354 this._lastUpdate = update.object; 7355 7356 requestId = this._lastUpdate.Update ? this._lastUpdate.Update.requestId : undefined; 7357 7358 if (requestId && this._pendingCallbacks[requestId]) { 7359 7360 /* 7361 * The passed success/error callbacks are expecting to be passed an AJAX response, so construct 7362 * a simulated/"fake" AJAX response object from the information in the received event. 7363 * The constructed object should conform to the contract for response objects specified 7364 * in _createAjaxHandler(). 7365 */ 7366 fakeResponse = {}; 7367 7368 //The contract says that rsp.content should contain the raw text of the response so we simulate that here. 7369 //For some reason json2xml has trouble with the native update object, so we serialize a clone of it by 7370 //doing a parse(stringify(update)). 7371 fakeResponse.content = this._util.json2xml(gadgets.json.parse(gadgets.json.stringify(update))); 7372 7373 fakeResponse.object = {}; 7374 7375 if (updateObj.apiErrors && updateObj.apiErrors.apiError) { //Error case 7376 7377 //TODO: The lowercase -> uppercase ApiErrors translation method below is undesirable, can it be improved? 7378 receivedError = updateObj.apiErrors.apiError; 7379 fakeResponse.object.ApiErrors = {}; 7380 fakeResponse.object.ApiErrors.ApiError = {}; 7381 fakeResponse.object.ApiErrors.ApiError.ErrorData = receivedError.errorData || undefined; 7382 fakeResponse.object.ApiErrors.ApiError.ErrorMessage = receivedError.errorMessage || undefined; 7383 fakeResponse.object.ApiErrors.ApiError.ErrorType = receivedError.errorType || undefined; 7384 7385 /* 7386 * Since this is the error case, supply the error callback with a '400 BAD REQUEST' status code. We don't know what the real 7387 * status code should be since the event we're constructing fakeResponse from doesn't include a status code. 7388 * This is just to conform to the contract for the error callback in _createAjaxHandler(). 7389 **/ 7390 fakeResponse.status = 400; 7391 7392 } else { //Success case 7393 7394 fakeResponse.object = this._lastUpdate; 7395 7396 /* 7397 * Since this is the success case, supply the success callback with a '200 OK' status code. We don't know what the real 7398 * status code should be since the event we're constructing fakeResponse from doesn't include a status code. 7399 * This is just to conform to the contract for the success callback in _createAjaxHandler(). 7400 **/ 7401 fakeResponse.status = 200; 7402 } 7403 7404 try { 7405 7406 if (fakeResponse.object.ApiErrors && this._pendingCallbacks[requestId].error) { 7407 this._pendingCallbacks[requestId].error(fakeResponse); 7408 } 7409 // HTTP 202 is handled as a success, besides, we cannot infer that a non-error is a success. 7410 /*else if (this._pendingCallbacks[requestId].success) { 7411 this._pendingCallbacks[requestId].success(fakeResponse); 7412 }*/ 7413 7414 } catch (callbackErr) { 7415 7416 this._logger.error(this.getRestType() + ": Caught error while firing callback: " + callbackErr); 7417 7418 } 7419 7420 this._postProcessUpdateStrategy(requestId); 7421 7422 } 7423 7424 return this._processObject(updateObj); 7425 } 7426 catch (err) { 7427 this._logger.error(this.getRestType() + ': ' + err); 7428 } 7429 return false; 7430 }, 7431 7432 /** 7433 * Utility method to create ajax response handler closures around the 7434 * provided callbacks. Callbacks should be passed through from .ajax(). 7435 * makeRequest is responsible for garbage collecting these closures. 7436 * @param {finesse.interfaces.RequestHandlers} handlers 7437 * An object containing the handlers for the request 7438 * @returns {Object} 7439 * { 7440 * abort: {function} Function that signifies the callback handler to NOT process the response when the response returns 7441 * callback: {function} Callback handler to be invoked when the response returns 7442 * } 7443 * @private 7444 */ 7445 _createAjaxHandler: function (options) { 7446 //We should not need to check this again since it has already been done in .restRequest() 7447 //options = options || {}; 7448 7449 //Flag to indicate whether or not to process the response 7450 var abort = false, 7451 7452 //Get a reference to the parent User object 7453 _this = this; 7454 7455 return { 7456 7457 abort: function () { 7458 abort = true; 7459 }, 7460 7461 callback: function (rsp) { 7462 7463 if (abort) { 7464 // Do not process the response 7465 return; 7466 } 7467 7468 var requestId, error = false, rspObj; 7469 7470 if (options.success || options.error) { 7471 rspObj = { 7472 status: rsp.rc, 7473 content: rsp.text, 7474 isUnsent: rsp.isUnsent 7475 }; 7476 7477 if (!_this.doNotLog) { 7478 _this._logger.log(_this.getRestType() + ": requestId='" + options.uuid + "', Returned with status=" + rspObj.status + ", content='" + rspObj.content + "', isUnsent = " + rspObj.isUnsent); 7479 } 7480 7481 //Some responses may not have a body. 7482 if (rsp.text && rsp.text.length > 0) { 7483 try { 7484 rspObj.object = _this._util.xml2js(rsp.text); 7485 } catch (e) { 7486 error = true; 7487 rspObj.error = { 7488 errorType: "parseError", 7489 errorMessage: "Could not serialize XML: " + e 7490 }; 7491 } 7492 } else { 7493 rspObj.object = {}; 7494 } 7495 7496 if (!error && rspObj.status >= 200 && rspObj.status < 300) { 7497 if (options.success) { 7498 options.success(rspObj); 7499 } 7500 } else { 7501 if (options.error) { 7502 options.error(rspObj); 7503 } 7504 } 7505 7506 /* 7507 * If a synchronous error happened after a non-GET request (usually a validation error), we 7508 * need to clean up the request's entry in _pendingCallbacks since no corresponding event 7509 * will arrive later. The corresponding requestId should be present in the response headers. 7510 * 7511 * It appears Shindig changes the header keys to lower case, hence 'requestid' instead of 7512 * 'requestId' below. 7513 **/ 7514 if (rspObj.status !== 202 && rsp.headers && rsp.headers.requestid) { 7515 requestId = rsp.headers.requestid[0]; 7516 if (_this._pendingCallbacks[requestId]) { 7517 delete _this._pendingCallbacks[requestId]; 7518 } 7519 } 7520 } 7521 } 7522 }; 7523 }, 7524 7525 /** 7526 * Utility method to make an asynchronous request 7527 * @param {String} url 7528 * The unencoded URL to which the request is sent (will be encoded) 7529 * @param {Object} options 7530 * An object containing additional options for the request. 7531 * @param {Object} options.content 7532 * An object to send in the content body of the request. Will be 7533 * serialized into XML before sending. 7534 * @param {String} options.method 7535 * The type of request. Defaults to "GET" when none is specified. 7536 * @param {Function} options.success(rsp) 7537 * A callback function to be invoked for a successful request. 7538 * { 7539 * status: {Number} The HTTP status code returned 7540 * content: {String} Raw string of response 7541 * object: {Object} Parsed object of response 7542 * } 7543 * @param {Function} options.error(rsp) 7544 * A callback function to be invoked for an unsuccessful request. 7545 * { 7546 * status: {Number} The HTTP status code returned 7547 * content: {String} Raw string of response 7548 * object: {Object} Parsed object of response 7549 * error: {Object} Wrapped exception that was caught 7550 * error.errorType: {String} Type of error that was caught 7551 * error.errorMessage: {String} Message associated with error 7552 * } 7553 * @returns {Object} 7554 * { 7555 * abort: {function} Function that signifies the callback handler to NOT process the response of this asynchronous request 7556 * } 7557 * @private 7558 */ 7559 restRequest: function(url,options) { 7560 7561 7562 // Protect against null dereferencing of options 7563 // allowing its (nonexistent) keys to be read as 7564 // undefined 7565 options = options || {}; 7566 options.success = this._util 7567 .validateHandler(options.success); 7568 options.error = this._util 7569 .validateHandler(options.error); 7570 7571 // true if this should be a GET request, false 7572 // otherwise 7573 if (!options.method || options.method === "GET") { 7574 // Disable caching for GETs 7575 if (url.indexOf("?") > -1) { 7576 url += "&"; 7577 } else { 7578 url += "?"; 7579 } 7580 url += "nocache=" 7581 + this._util.currentTimeMillis(); 7582 } else { 7583 /** 7584 * If not GET, generate a requestID and add it 7585 * to the headers, then wrap callbacks into an 7586 * object and store it in _pendingCallbacks. If 7587 * we receive a synchronous error response 7588 * instead of a 202 as expected, the AJAX 7589 * handler will clean up _pendingCallbacks. 7590 */ 7591 /* 7592 * TODO: Clean up _pendingCallbacks if an entry 7593 * persists after a certain amount of time has 7594 * passed. In the block below, can store the 7595 * current time (new Date().getTime()) alongside 7596 * the callbacks in the new _pendingCallbacks 7597 * entry. Then iterate through a copty of 7598 * _pendingCallbacks, deleting all entries 7599 * inside _pendingCallbacks that are older than 7600 * a certain threshold (2 minutes for example.) 7601 * This solves a potential memory leak issue if 7602 * we never receive an event for a given stored 7603 * requestId; we don't want to store unfired 7604 * callbacks forever. 7605 */ 7606 /** @private */ 7607 options.uuid = this._util.generateUUID(); 7608 // params[gadgets.io.RequestParameters.HEADERS].requestId 7609 // = options.uuid; 7610 // By default, Shindig strips nearly all of the 7611 // response headers, but this parameter tells 7612 // Shindig 7613 // to send the headers through unmodified; we 7614 // need to be able to read the 'requestId' 7615 // header if we 7616 // get a synchronous error as a result of a 7617 // non-GET request. (See the bottom of 7618 // _createAjaxHandler().) 7619 // params[gadgets.io.RequestParameters.GET_FULL_HEADERS] 7620 // = "true"; 7621 this._pendingCallbacks[options.uuid] = {}; 7622 this._pendingCallbacks[options.uuid].success = options.success; 7623 this._pendingCallbacks[options.uuid].error = options.error; 7624 } 7625 7626 7627 /** 7628 * only checking for the host name for now. We could have extended it to scheme and port, but at this point it is not required. 7629 */ 7630 var restHost = ClientServices.getRestHost().toLowerCase(); 7631 if(restHost === "localhost" || restHost === window.location.hostname.toLowerCase()) { 7632 return this._restRequestThroughAjax(url,options); 7633 } else { 7634 return this._restRequestThroughShindig(url,options); 7635 } 7636 }, 7637 7638 _restRequestThroughAjax : function(url, options) { 7639 var encodedUrl, ajaxHandler, scheme = window.location.protocol, host = window.location.hostname, 7640 port = window.location.port, dataTypeAX, contentTypeAX, mtype, postdata = "", auth, rspObj, 7641 locale = this._config.locale, userName=this._config.id; 7642 7643 encodedUrl = encodeURI(url) 7644 + (window.errorOnRestRequest ? "ERROR" : ""); 7645 7646 ajaxHandler = this._createAjaxHandler(options); 7647 // ClientServices.makeRequest(encodedUrl, 7648 // ajaxHandler.callback, params); 7649 mtype = options.method || 'GET'; 7650 encodedUrl = scheme + "//" + host + ":" + port 7651 + encodedUrl; 7652 7653 if (typeof options.content === "object" 7654 && typeof options.content !== "undefined") { 7655 // Except get, all the other operations accepts 7656 // application/xml only. 7657 // @Consumes in all the webservices except GET 7658 // are having this. 7659 postdata = this._util.js2xml(options.content); 7660 if (postdata !== null && postdata !== "") { 7661 contentTypeAX = "application/xml"; 7662 } else { 7663 // in desktop, reason code GET request was 7664 // called with empty object content 7665 // if not able to parse any content to post 7666 // data, then it is GET only 7667 contentTypeAX = ""; 7668 dataTypeAX = "text"; 7669 } 7670 } else { 7671 // No content type for GET operation, by default 7672 // it will take text/plain || application/xml. 7673 // for dataType - GET will result plain text, 7674 // which we are taking as xml. 7675 // Queried list of @Produces from code base, all 7676 // the GET operations accepts text/plain OR 7677 // application/xml and produces application/xml 7678 contentTypeAX = ""; 7679 dataTypeAX = "text"; 7680 } 7681 auth = this._util.getAuthHeaderString(this._config); 7682 7683 if (!this.doNotLog) { 7684 this._logger.log(this.getRestType() 7685 + ": requestId='" + options.uuid 7686 + "', Making REST request: method=" 7687 + (options.method || "GET") + ", url='" 7688 + encodedUrl + "'"); 7689 } 7690 7691 $.ajax({ 7692 url : encodedUrl, 7693 headers : this.extraHeaders || {}, 7694 beforeSend : function(xhr) { 7695 xhr.setRequestHeader( 7696 "Authorization", auth); 7697 xhr.setRequestHeader("locale", 7698 locale); 7699 xhr.setRequestHeader("f_username", 7700 userName); 7701 if (options.uuid) { 7702 xhr.setRequestHeader( 7703 "requestId", 7704 options.uuid); 7705 } 7706 }, 7707 dataType : dataTypeAX, // xml or json? 7708 contentType : contentTypeAX, 7709 type : mtype, 7710 data : postdata, 7711 timeout : this.ajaxRequestTimeout, 7712 success : function(response, status, 7713 xhr) { 7714 var rspObj = { 7715 rc : xhr.status, 7716 text : response, 7717 isUnsent : xhr.readyState==0, 7718 headers : { 7719 requestid : xhr 7720 .getResponseHeader("requestId") 7721 } 7722 }; 7723 ajaxHandler.callback(rspObj); 7724 }, 7725 error : function(jqXHR, request, error) { 7726 var resObj = { 7727 rc : jqXHR.status, 7728 text : jqXHR.responseText, 7729 isUnsent : jqXHR.readyState==0, 7730 headers : { 7731 requestid : jqXHR 7732 .getResponseHeader("requestId") 7733 } 7734 }; 7735 ajaxHandler.callback(resObj); 7736 } 7737 }); 7738 return { 7739 abort : ajaxHandler.abort 7740 }; 7741 }, 7742 7743 7744 _restRequestThroughShindig: function(url, options) { 7745 var params, encodedUrl, ajaxHandler; 7746 7747 params = {}; 7748 options = options || {}; 7749 7750 params[gadgets.io.RequestParameters.HEADERS] = this.extraHeaders || {}; 7751 params[gadgets.io.RequestParameters.METHOD] = options.method; 7752 7753 7754 if (!options.method || options.method === "GET") { 7755 7756 } else { 7757 params[gadgets.io.RequestParameters.HEADERS].requestId = options.uuid; 7758 params[gadgets.io.RequestParameters.GET_FULL_HEADERS] = "true"; 7759 7760 } 7761 7762 encodedUrl = encodeURI(url) + (window.errorOnRestRequest ? "ERROR" : ""); 7763 7764 if (!this.doNotLog) { 7765 this._logger.log(this.getRestType() + ": requestId='" + options.uuid + "', Making REST request: method=" + (options.method || "GET") + ", url='" + encodedUrl + "'"); 7766 } 7767 7768 7769 if (typeof options.content === "object") { 7770 7771 params[gadgets.io.RequestParameters.HEADERS]["Content-Type"] = "application/xml"; 7772 params[gadgets.io.RequestParameters.POST_DATA] = this._util.js2xml(options.content); 7773 7774 if (!this.doNotLog) { 7775 this._logger.log(this.getRestType() + ": requestId='" + options.uuid + "', POST_DATA='" + params[gadgets.io.RequestParameters.POST_DATA] + "'"); 7776 } 7777 } 7778 7779 ajaxHandler = this._createAjaxHandler(options); 7780 ClientServices.makeRequest(encodedUrl, ajaxHandler.callback, params); 7781 7782 return { 7783 abort: ajaxHandler.abort 7784 }; 7785 }, 7786 7787 /** 7788 * Retrieves a reference to a particular notifierType. 7789 * 7790 * @param notifierType 7791 * is a string which indicates the notifier to retrieve 7792 * ('load', 'change', 'add', 'delete', 'error') 7793 * @return {Notifier} 7794 * @private 7795 */ 7796 _getNotifierReference: function (notifierType) { 7797 var notifierReference = null; 7798 if (notifierType === 'load') { 7799 notifierReference = this._loadNotifier; 7800 } else if (notifierType === 'change') { 7801 notifierReference = this._changeNotifier; 7802 } else if (notifierType === 'add') { 7803 notifierReference = this._addNotifier; 7804 } else if (notifierType === 'delete') { 7805 notifierReference = this._deleteNotifier; 7806 } else if (notifierType === 'error') { 7807 notifierReference = this._errorNotifier; 7808 } else { 7809 throw new Error("_getNotifierReference(): Trying to get unknown notifier(notifierType=" + notifierType + ")"); 7810 } 7811 7812 return notifierReference; 7813 } 7814 }); 7815 7816 window.finesse = window.finesse || {}; 7817 window.finesse.restservices = window.finesse.restservices || {}; 7818 window.finesse.restservices.RestBase = RestBase; 7819 7820 return RestBase; 7821 }); 7822 7823 /** The following comment is to prevent jslint errors about 7824 * using variables before they are defined. 7825 */ 7826 /*global finesse*/ 7827 7828 /** 7829 * JavaScript base object that all REST collection objects should 7830 * inherit from because it encapsulates and provides the common functionality 7831 * that all REST objects need. 7832 * 7833 * @requires finesse.clientservices.ClientServices 7834 * @requires Class 7835 * @requires finesse.FinesseBase 7836 * @requires finesse.restservices.RestBase 7837 */ 7838 7839 /** 7840 * @class 7841 * JavaScript representation of a REST collection object. 7842 * 7843 * @constructor 7844 * @param {Function} callbacks.onCollectionAdd(this) 7845 * Callback to invoke upon successful item addition to the collection. 7846 * @param {Function} callbacks.onCollectionDelete(this) 7847 * Callback to invoke upon successful item deletion from the collection. 7848 * @borrows finesse.restservices.RestBase as finesse.restservices.RestCollectionBase 7849 */ 7850 /** @private */ 7851 define('restservices/RestCollectionBase',[ 7852 'restservices/RestBase', 7853 'utilities/Utilities', 7854 'restservices/Notifier' 7855 ], 7856 function (RestBase, Utilities, Notifier) { 7857 var RestCollectionBase = RestBase.extend(/** @lends finesse.restservices.RestCollectionBase.prototype */{ 7858 7859 /** 7860 * Boolean function that specifies whether the collection handles subscribing 7861 * and propagation of events for the individual REST object items the 7862 * collection holds. False by default. Subclasses should override if true. 7863 * @private 7864 */ 7865 supportsRestItemSubscriptions: false, 7866 7867 /** 7868 * Gets the constructor the individual items that make of the collection. 7869 * For example, a Dialogs collection object will hold a list of Dialog items. 7870 * @throws Error because subtype must implement. 7871 * @private 7872 */ 7873 getRestItemClass: function () { 7874 throw new Error("getRestItemClass(): Not implemented in subtype."); 7875 }, 7876 7877 /** 7878 * Gets the REST type of the individual items that make of the collection. 7879 * For example, a Dialogs collection object will hold a list of Dialog items. 7880 * @throws Error because subtype must implement. 7881 * @private 7882 */ 7883 getRestItemType: function () { 7884 throw new Error("getRestItemType(): Not implemented in subtype."); 7885 }, 7886 7887 /** 7888 * The base REST URL in which items this object contains can be referenced. 7889 * @return {String} 7890 * The REST URI for items this object contains. 7891 * @private 7892 */ 7893 getRestItemBaseUrl: function () { 7894 var 7895 restUrl = "/finesse/api"; 7896 7897 //Append the REST type. 7898 restUrl += "/" + this.getRestItemType(); 7899 7900 return restUrl; 7901 }, 7902 7903 /* 7904 * Creates a new object from the given data 7905 * @param data - data object 7906 * @private 7907 */ 7908 _objectCreator: function (data) { 7909 var objectId = this._extractId(data), 7910 newRestObj = this._collection[objectId], 7911 _this = this; 7912 7913 //Prevent duplicate entries into collection. 7914 if (!newRestObj) { 7915 //Create a new REST object using the subtype defined by the 7916 //overridden method. 7917 newRestObj = new (this.getRestItemClass())({ 7918 doNotSubscribe: this.handlesItemSubscription, 7919 doNotRefresh: this.handlesItemRefresh, 7920 id: objectId, 7921 data: data, 7922 onLoad: function (newObj) { 7923 //Normalize and add REST object to collection datastore. 7924 _this._collection[objectId] = newObj; 7925 _this._collectionAddNotifier.notifyListeners(newObj); 7926 _this.length += 1; 7927 } 7928 }); 7929 } 7930 else { 7931 //If entry already exist in collection, process the new event, 7932 //and notify all change listeners since an existing object has 7933 //change. This could happen in the case when the Finesse server 7934 //cycles, and sends a snapshot of the user's calls. 7935 newRestObj._processObject(data); 7936 newRestObj._changeNotifier.notifyListeners(newRestObj); 7937 } 7938 }, 7939 7940 /* 7941 * Deletes and object and notifies its handlers 7942 * @param data - data object 7943 * @private 7944 */ 7945 _objectDeleter: function (data) { 7946 var objectId = this._extractId(data), 7947 object = this._collection[objectId]; 7948 if (object) { 7949 //Even though this is a delete, let's make sure the object we are passing has got good data 7950 object._processObject(data); 7951 //Notify listeners and delete from internal datastore. 7952 this._collectionDeleteNotifier.notifyListeners(object); 7953 delete this._collection[objectId]; 7954 this.length -= 1; 7955 } 7956 }, 7957 7958 /** 7959 * Creates an anonymous function for notifiying error listeners of a particular object 7960 * data. 7961 * @param obj - the objects whose error listeners to notify 7962 * @returns {Function} 7963 * Callback for notifying of errors 7964 * @private 7965 */ 7966 _createErrorNotifier: function (obj) { 7967 return function (err) { 7968 obj._errorNotifier.notifyListeners(err); 7969 }; 7970 }, 7971 7972 /** 7973 * Replaces the collection with a refreshed list using the passed in 7974 * data. 7975 * @param data - data object (usually this._data) 7976 * @private 7977 */ 7978 _buildRefreshedCollection: function (data) { 7979 var i, dataObject, object, objectId, dataArray, newIds = [], foundFlag; 7980 if (data && this.getProperty(data, this.getRestItemType()) !== null) { 7981 dataArray = Utilities.getArray(this.getProperty(data, this.getRestItemType())); 7982 } else { 7983 dataArray = []; 7984 } 7985 7986 // iterate through each item in the new data and add to or update collection 7987 for (i = 0; i < dataArray.length; i += 1) { 7988 dataObject = {}; 7989 dataObject[this.getRestItemType()] = dataArray[i]; 7990 objectId = this._extractId(dataObject); 7991 7992 this._objectCreator(dataObject); 7993 newIds.push(objectId); 7994 7995 // resubscribe if the object requires an explicit subscription 7996 object = this._collection[objectId]; 7997 if (this.handlesItemRefresh && object.explicitSubscription) { 7998 object._subscribeNode({ 7999 error: this._createErrorNotifier(object) 8000 }); 8001 } 8002 } 8003 8004 // now clean up items (if any) that were removed 8005 for (objectId in this._collection) { 8006 if (this._collection.hasOwnProperty(objectId)) { 8007 foundFlag = false; 8008 for (i = newIds.length - 1; i >= 0; i -= 1) { 8009 if (newIds[i] === objectId) { 8010 foundFlag = true; 8011 break; 8012 } 8013 } 8014 // did not find in updated list, so delete it 8015 if (!foundFlag) { 8016 this._objectDeleter({'data': this._collection[objectId]._data}); 8017 } 8018 } 8019 } 8020 }, 8021 8022 /** 8023 * The actual refresh operation, refactored out so we don't have to repeat code 8024 * @private 8025 */ 8026 _RESTRefresh: function () { 8027 var _this = this; 8028 this._doGET({ 8029 success: function(rsp) { 8030 if (_this._processResponse(rsp)) { 8031 _this._buildRefreshedCollection(_this._data); 8032 } else { 8033 _this._errorNotifier.notifyListeners(_this); 8034 } 8035 }, 8036 error: function(rsp) { 8037 _this._errorNotifier.notifyListeners(rsp); 8038 } 8039 }); 8040 }, 8041 8042 /** 8043 * Force an update on this object. Since an asynchronous GET is performed, 8044 * it is necessary to have an onChange handler registered in order to be 8045 * notified when the response of this returns. 8046 * @returns {finesse.restservices.RestBaseCollection} 8047 * This RestBaseCollection object to allow cascading 8048 */ 8049 refresh: function() { 8050 var _this = this, isLoaded = this._loaded; 8051 8052 // resubscribe if the collection requires an explicit subscription 8053 if (this.explicitSubscription) { 8054 this._subscribeNode({ 8055 success: function () { 8056 _this._RESTRefresh(); 8057 }, 8058 error: function (err) { 8059 _this._errorNotifier.notifyListeners(err); 8060 } 8061 }); 8062 } else { 8063 this._RESTRefresh(); 8064 } 8065 8066 return this; // Allow cascading 8067 }, 8068 8069 /** 8070 * @private 8071 * The _addHandlerCb and _deleteHandlerCb require that data be passed in the 8072 * format of an array of {(Object Type): object} objects. For example, a 8073 * queues object would return [{Queue: queue1}, {Queue: queue2}, ...]. 8074 * @param skipOuterObject If {true} is passed in for this param, then the "data" 8075 * property is returned instead of an object with the 8076 * data appended. 8077 * @return {Array} 8078 */ 8079 extractCollectionData: function (skipOuterObject) { 8080 var restObjs, 8081 obj, 8082 result = [], 8083 _this = this; 8084 8085 if (this._data) 8086 { 8087 restObjs = this._data[this.getRestItemType()]; 8088 8089 if (restObjs) 8090 { 8091 // check if there are multiple objects to pass 8092 if (!$.isArray(restObjs)) 8093 { 8094 restObjs = [restObjs]; 8095 } 8096 8097 // if so, create an object for each and add to result array 8098 $.each(restObjs, function (id, object) { 8099 if (skipOuterObject === true) 8100 { 8101 obj = object; 8102 } 8103 else 8104 { 8105 obj = {}; 8106 obj[_this.getRestItemType()] = object; 8107 } 8108 result.push(obj); 8109 }); 8110 } 8111 8112 } 8113 8114 return result; 8115 }, 8116 8117 /** 8118 * For Finesse, collections are handled uniquely on a POST and 8119 * doesn't necessary follow REST conventions. A POST on a collection 8120 * doesn't mean that the collection has been created, it means that an 8121 * item has been added to the collection. This function will generate 8122 * a closure which will handle this logic appropriately. 8123 * @param {Object} scope 8124 * The scope of where the callback should be invoked. 8125 * @private 8126 */ 8127 _addHandlerCb: function (scope) { 8128 return function (restItem) { 8129 var data = restItem.extractCollectionData(); 8130 8131 $.each(data, function (id, object) { 8132 scope._objectCreator(object); 8133 }); 8134 }; 8135 }, 8136 8137 /** 8138 * For Finesse, collections are handled uniquely on a DELETE and 8139 * doesn't necessary follow REST conventions. A DELETE on a collection 8140 * doesn't mean that the collection has been deleted, it means that an 8141 * item has been deleted from the collection. This function will generate 8142 * a closure which will handle this logic appropriately. 8143 * @param {Object} scope 8144 * The scope of where the callback should be invoked. 8145 * @private 8146 */ 8147 _deleteHandlerCb: function (scope) { 8148 return function (restItem) { 8149 var data = restItem.extractCollectionData(); 8150 8151 $.each(data, function (id, obj) { 8152 scope._objectDeleter(obj); 8153 }); 8154 }; 8155 }, 8156 8157 /** 8158 * Utility method to process the update notification for Rest Items 8159 * that are children of the collection whose events are published to 8160 * the collection's node. 8161 * @param {Object} update 8162 * The payload of an update notification. 8163 * @returns {Boolean} 8164 * True if the update was successfully processed (the update object 8165 * passed the schema validation) and updated the internal data cache, 8166 * false otherwise. 8167 * @private 8168 */ 8169 _processRestItemUpdate: function (update) { 8170 var object, objectId, updateObj = update.object.Update; 8171 8172 //Extract the ID from the source if the Update was an error. 8173 if (updateObj.data.apiErrors) { 8174 objectId = Utilities.getId(updateObj.source); 8175 } 8176 //Otherwise extract from the data object itself. 8177 else { 8178 objectId = this._extractId(updateObj.data); 8179 } 8180 8181 object = this._collection[objectId]; 8182 if (object) { 8183 if (object._processUpdate(update)) { 8184 switch (updateObj.event) { 8185 case "POST": 8186 object._addNotifier.notifyListeners(object); 8187 break; 8188 case "PUT": 8189 object._changeNotifier.notifyListeners(object); 8190 break; 8191 case "DELETE": 8192 object._deleteNotifier.notifyListeners(object); 8193 break; 8194 } 8195 } 8196 } 8197 }, 8198 8199 /** 8200 * SUBCLASS IMPLEMENTATION (override): 8201 * For collections, this callback has the additional responsibility of passing events 8202 * of collection item updates to the item objects themselves. The collection needs to 8203 * do this because item updates are published to the collection's node. 8204 * @returns {Function} 8205 * The callback to be invoked when an update event is received 8206 * @private 8207 */ 8208 _createPubsubCallback: function () { 8209 var _this = this; 8210 return function (update) { 8211 //If the source of the update is our REST URL, this means the collection itself is modified 8212 if (update.object.Update.source === _this.getRestUrl()) { 8213 _this._updateEventHandler(_this, update); 8214 } else { 8215 //Otherwise, it is safe to assume that if we got an event on our topic, it must be a 8216 //rest item update of one of our children that was published on our node (OpenAjax topic) 8217 _this._processRestItemUpdate(update); 8218 } 8219 }; 8220 }, 8221 8222 /** 8223 * @class 8224 * This is the base collection object. 8225 * 8226 * @constructs 8227 * @augments finesse.restservices.RestBase 8228 * @see finesse.restservices.Contacts 8229 * @see finesse.restservices.Dialogs 8230 * @see finesse.restservices.PhoneBooks 8231 * @see finesse.restservices.Queues 8232 * @see finesse.restservices.WorkflowActions 8233 * @see finesse.restservices.Workflows 8234 * @see finesse.restservices.WrapUpReasons 8235 */ 8236 _fakeConstuctor: function () { 8237 /* This is here to hide the real init constructor from the public docs */ 8238 }, 8239 8240 /** 8241 * @private 8242 * @param {Object} options 8243 * An object with the following properties:<ul> 8244 * <li><b>id:</b> The id of the object being constructed</li> 8245 * <li><b>onCollectionAdd(this): (optional)</b> when an object is added to this collection</li> 8246 * <li><b>onCollectionDelete(this): (optional)</b> when an object is removed from this collection</li> 8247 * <li><b>onLoad(this): (optional)</b> when the collection is successfully loaded from the server</li> 8248 * <li><b>onChange(this): (optional)</b> when an update notification of the collection is received. 8249 * This does not include adding and deleting members of the collection</li> 8250 * <li><b>onAdd(this): (optional)</b> when a notification that the collection is created is received</li> 8251 * <li><b>onDelete(this): (optional)</b> when a notification that the collection is deleted is received</li> 8252 * <li><b>onError(rsp): (optional)</b> if loading of the collection fails, invoked with the error response object:<ul> 8253 * <li><b>status:</b> {Number} The HTTP status code returned</li> 8254 * <li><b>content:</b> {String} Raw string of response</li> 8255 * <li><b>object:</b> {Object} Parsed object of response</li> 8256 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 8257 * <li><b>errorType:</b> {String} Type of error that was caught</li> 8258 * <li><b>errorMessage:</b> {String} Message associated with error</li> 8259 * </ul></li> 8260 * </ul></li> 8261 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 8262 **/ 8263 init: function (options) { 8264 8265 options = options || {}; 8266 options.id = ""; 8267 8268 //Make internal datastore collection to hold a list of objects. 8269 this._collection = {}; 8270 this.length = 0; 8271 8272 //Collections will have additional callbacks that will be invoked when 8273 //an item has been added/deleted. 8274 this._collectionAddNotifier = new Notifier(); 8275 this._collectionDeleteNotifier = new Notifier(); 8276 8277 //Initialize the base class. 8278 this._super(options); 8279 8280 this.addHandler('collectionAdd', options.onCollectionAdd); 8281 this.addHandler('collectionDelete', options.onCollectionDelete); 8282 8283 //For Finesse, collections are handled uniquely on a POST/DELETE and 8284 //doesn't necessary follow REST conventions. A POST on a collection 8285 //doesn't mean that the collection has been created, it means that an 8286 //item has been added to the collection. A DELETE means that an item has 8287 //been removed from the collection. Due to this, we are attaching 8288 //special callbacks to the add/delete that will handle this logic. 8289 this.addHandler("add", this._addHandlerCb(this)); 8290 this.addHandler("delete", this._deleteHandlerCb(this)); 8291 }, 8292 8293 /** 8294 * Returns the collection. 8295 * @returns {Object} 8296 * The collection as an object 8297 */ 8298 getCollection: function () { 8299 //TODO: is this safe? or should we instead return protected functions such as .each(function)? 8300 return this._collection; 8301 }, 8302 8303 /** 8304 * Utility method to build the internal collection data structure (object) based on provided data 8305 * @param {Object} data 8306 * The data to build the internal collection from 8307 * @private 8308 */ 8309 _buildCollection: function (data) { 8310 var i, object, objectId, dataArray; 8311 if (data && this.getProperty(data, this.getRestItemType()) !== null) { 8312 dataArray = Utilities.getArray(this.getProperty(data, this.getRestItemType())); 8313 for (i = 0; i < dataArray.length; i += 1) { 8314 8315 object = {}; 8316 object[this.getRestItemType()] = dataArray[i]; 8317 objectId = this._extractId(object); 8318 this._collection[objectId] = new (this.getRestItemClass())({ 8319 doNotSubscribe: this.handlesItemSubscription, 8320 doNotRefresh: this.handlesItemRefresh, 8321 id: objectId, 8322 data: object 8323 }); 8324 this.length += 1; 8325 } 8326 } 8327 }, 8328 8329 /** 8330 * Called to know whether to include an item in the _collection and _data. Return false to keep it, true to filter out (discard) it. 8331 * Override this in subclasses if you need only object with certain attribute values. 8332 * @param {Object} item Item to test. 8333 * @return {Boolean} False to keep, true to filter out (discard); 8334 */ 8335 _filterOutItem: function (item) { 8336 return false; 8337 }, 8338 8339 /** 8340 * Validate and store the object into the internal data store. 8341 * SUBCLASS IMPLEMENTATION (override): 8342 * Performs collection specific logic to _buildCollection internally based on provided data 8343 * @param {Object} object 8344 * The JavaScript object that should match of schema of this REST object. 8345 * @returns {Boolean} 8346 * True if the object was validated and stored successfully. 8347 * @private 8348 */ 8349 _processObject: function (object) { 8350 var i, 8351 restItemType = this.getRestItemType(), 8352 items; 8353 if (this._validate(object)) { 8354 this._data = this.getProperty(object, this.getRestType()); // Should clone the object here? 8355 8356 // If a subclass has overriden _filterOutItem then we'll need to run through the items and remove them 8357 if (this._data) 8358 { 8359 items = this._data[restItemType]; 8360 8361 if (typeof(items) !== "undefined") 8362 { 8363 if (typeof(items.length) === "undefined") 8364 { 8365 // Single object 8366 if (this._filterOutItem(items)) 8367 { 8368 this._data[restItemType] = items = []; 8369 } 8370 8371 } 8372 else 8373 { 8374 // filter out objects 8375 for (i = items.length - 1; i !== -1; i = i - 1) 8376 { 8377 if (this._filterOutItem(items[i])) 8378 { 8379 items.splice(i, 1); 8380 } 8381 } 8382 } 8383 } 8384 } 8385 8386 // If loaded for the first time, call the load notifiers. 8387 if (!this._loaded) { 8388 this._buildCollection(this._data); 8389 this._loaded = true; 8390 this._loadNotifier.notifyListeners(this); 8391 } 8392 8393 return true; 8394 8395 } 8396 return false; 8397 }, 8398 8399 /** 8400 * Retrieves a reference to a particular notifierType. 8401 * @param {String} notifierType 8402 * Specifies the notifier to retrieve (load, change, error, add, delete) 8403 * @return {Notifier} The notifier object. 8404 */ 8405 _getNotifierReference: function (notifierType) { 8406 var notifierReference; 8407 8408 try { 8409 //Use the base method to get references for load/change/error. 8410 notifierReference = this._super(notifierType); 8411 } catch (err) { 8412 //Check for add/delete 8413 if (notifierType === "collectionAdd") { 8414 notifierReference = this._collectionAddNotifier; 8415 } else if (notifierType === "collectionDelete") { 8416 notifierReference = this._collectionDeleteNotifier; 8417 } else { 8418 //Rethrow exception from base class. 8419 throw err; 8420 } 8421 } 8422 return notifierReference; 8423 } 8424 }); 8425 8426 window.finesse = window.finesse || {}; 8427 window.finesse.restservices = window.finesse.restservices || {}; 8428 window.finesse.restservices.RestCollectionBase = RestCollectionBase; 8429 8430 return RestCollectionBase; 8431 }); 8432 8433 /** 8434 * JavaScript representation of the Finesse Dialog object. 8435 * 8436 * @requires finesse.clientservices.ClientServices 8437 * @requires Class 8438 * @requires finesse.FinesseBase 8439 * @requires finesse.restservices.RestBase 8440 */ 8441 8442 /** @private */ 8443 define('restservices/DialogBase',[ 8444 'restservices/RestBase', 8445 'utilities/Utilities' 8446 ], 8447 function (RestBase, Utilities) { 8448 var DialogBase = RestBase.extend(/** @lends finesse.restservices.DialogBase.prototype */{ 8449 8450 /** 8451 * @class 8452 * A DialogBase is an attempted connection between or among multiple participants, 8453 * for example, a regular phone call, a chat, or an email. 8454 * 8455 * This object is typically extended into individual 8456 * REST Objects (like Dialog, MediaDialog, etc...), and shouldn't be used directly. 8457 * 8458 * @augments finesse.restservices.RestBase 8459 * @constructs 8460 */ 8461 _fakeConstuctor: function () { 8462 /* This is here to hide the real init constructor from the public docs */ 8463 }, 8464 8465 /** 8466 * @private 8467 * 8468 * @param {Object} options 8469 * An object with the following properties:<ul> 8470 * <li><b>id:</b> The id of the object being constructed</li> 8471 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 8472 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 8473 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 8474 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 8475 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 8476 * <li><b>status:</b> {Number} The HTTP status code returned</li> 8477 * <li><b>content:</b> {String} Raw string of response</li> 8478 * <li><b>object:</b> {Object} Parsed object of response</li> 8479 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 8480 * <li><b>errorType:</b> {String} Type of error that was caught</li> 8481 * <li><b>errorMessage:</b> {String} Message associated with error</li> 8482 * </ul></li> 8483 * </ul></li> 8484 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 8485 **/ 8486 init: function (options) { 8487 this._super(options); 8488 }, 8489 8490 /** 8491 * @private 8492 * Gets the REST class for the current object - this is the Dialog class. 8493 * @returns {Object} The Dialog class. 8494 */ 8495 getRestClass: function () { 8496 throw new Error("getRestClass(): Not implemented in subtype."); 8497 }, 8498 8499 /** 8500 * @private 8501 * The constant for agent device. 8502 */ 8503 _agentDeviceType: "AGENT_DEVICE", 8504 8505 /** 8506 * @private 8507 * Gets the REST type for the current object - this is a "Dialog". 8508 * @returns {String} The Dialog string. 8509 */ 8510 getRestType: function () { 8511 return "Dialog"; 8512 }, 8513 8514 /** 8515 * @private 8516 * Override default to indicate that this object doesn't support making 8517 * requests. 8518 */ 8519 supportsRequests: false, 8520 8521 /** 8522 * @private 8523 * Override default to indicate that this object doesn't support subscriptions. 8524 */ 8525 supportsSubscriptions: false, 8526 8527 8528 /** 8529 * Getter for the media type. 8530 * @returns {String} The media type. 8531 */ 8532 getMediaType: function () { 8533 this.isLoaded(); 8534 return this.getData().mediaType; 8535 }, 8536 8537 /** 8538 * @private 8539 * Getter for the uri. 8540 * @returns {String} The uri. 8541 */ 8542 getDialogUri: function () { 8543 this.isLoaded(); 8544 return this.getData().uri; 8545 }, 8546 8547 /** 8548 * Getter for the callType. 8549 * @deprecated Use getMediaProperties().callType instead. 8550 * @returns {String} The callType. 8551 */ 8552 getCallType: function () { 8553 this.isLoaded(); 8554 return this.getData().mediaProperties.callType; 8555 }, 8556 8557 8558 /** 8559 * Getter for the Dialog state. 8560 * @returns {String} The Dialog state. 8561 */ 8562 getState: function () { 8563 this.isLoaded(); 8564 return this.getData().state; 8565 }, 8566 8567 /** 8568 * Retrieves a list of participants within the Dialog object. 8569 * @returns {Object} Array list of participants. 8570 * Participant entity properties are as follows:<ul> 8571 * <li>state - The state of the Participant. 8572 * <li>stateCause - The state cause of the Participant. 8573 * <li>mediaAddress - The media address of the Participant. 8574 * <li>startTime - The start Time of the Participant. 8575 * <li>stateChangeTime - The time when participant state has changed. 8576 * <li>actions - These are the actions that a Participant can perform</ul> 8577 */ 8578 getParticipants: function () { 8579 this.isLoaded(); 8580 var participants = this.getData().participants.Participant; 8581 //Due to the nature of the XML->JSO converter library, a single 8582 //element in the XML array will be considered to an object instead of 8583 //a real array. This will handle those cases to ensure that an array is 8584 //always returned. 8585 8586 return Utilities.getArray(participants); 8587 }, 8588 8589 /** 8590 * This method retrieves the participant timer counters 8591 * 8592 * @param {String} participantExt Extension of participant. 8593 * @returns {Object} Array of Participants which contains properties :<ul> 8594 * <li>state - The state of the Participant. 8595 * <li>startTime - The start Time of the Participant. 8596 * <li>stateChangeTime - The time when participant state has changed.</ul> 8597 * 8598 */ 8599 getParticipantTimerCounters : function (participantExt) { 8600 var part, participantTimerCounters = {}, idx, participants; 8601 8602 participants = this.getParticipants(); 8603 8604 8605 //Loop through all the participants and find the right participant (based on participantExt) 8606 for(idx=0;idx<participants.length;idx=idx+1) 8607 { 8608 part = participants[idx]; 8609 8610 if (part.mediaAddress === participantExt) 8611 { 8612 participantTimerCounters.startTime= part.startTime; 8613 participantTimerCounters.stateChangeTime= part.stateChangeTime; 8614 participantTimerCounters.state= part.state; 8615 break; 8616 } 8617 } 8618 8619 return participantTimerCounters; 8620 }, 8621 8622 8623 /** 8624 * Retrieves a list of media properties from the dialog object. 8625 * @returns {Object} Map of call variables; names mapped to values. 8626 * Variables may include the following:<ul> 8627 * <li>dialedNumber: The number dialed. 8628 * <li>callType: The type of call. Call types include:<ul> 8629 * <li>ACD_IN 8630 * <li>PREROUTE_ACD_IN 8631 * <li>PREROUTE_DIRECT_AGENT 8632 * <li>TRANSFER 8633 * <li>OTHER_IN 8634 * <li>OUT 8635 * <li>AGENT_INSIDE 8636 * <li>CONSULT 8637 * <li>CONFERENCE 8638 * <li>SUPERVISOR_MONITOR 8639 * <li>OUTBOUND 8640 * <li>OUTBOUND_PREVIEW</ul> 8641 * <li>DNIS: The DNIS provided. For routed calls, this is the route point. 8642 * <li>wrapUpReason: A description of the call. 8643 * <li>queueNumber: Number of the agent Skill Group the call is attributed to. 8644 * <li>queueName: Name of the agent Skill Group the call is attributed to. 8645 * <li>callKeyCallId: unique number of the call routed on a particular day. 8646 * <li>callKeyPrefix: represents the day when the call is routed. 8647 * <li>callKeySequenceNum: represents the sequence number of call. 8648 * <li>Call Variables, by name. The name indicates whether it is a call variable or ECC variable. 8649 * Call variable names start with callVariable#, where # is 1-10. ECC variable names (both scalar and array) are prepended with "user". 8650 * ECC variable arrays include an index enclosed within square brackets located at the end of the ECC array name. 8651 * <li>The following call variables provide additional details about an Outbound Option call:<ul> 8652 * <li>BACampaign 8653 * <li>BAAccountNumber 8654 * <li>BAResponse 8655 * <li>BAStatus<ul> 8656 * <li>PREDICTIVE_OUTBOUND: A predictive outbound call. 8657 * <li>PROGRESSIVE_OUTBOUND: A progressive outbound call. 8658 * <li>PREVIEW_OUTBOUND_RESERVATION: Agent is reserved for a preview outbound call. 8659 * <li>PREVIEW_OUTBOUND: Agent is on a preview outbound call.</ul> 8660 * <li>BADialedListID 8661 * <li>BATimeZone 8662 * <li>BABuddyName</ul></ul> 8663 * 8664 */ 8665 getMediaProperties: function () { 8666 var mpData, currentMediaPropertiesMap, thisMediaPropertiesJQuery; 8667 8668 this.isLoaded(); 8669 8670 // We have to convert to jQuery object to do a proper compare 8671 thisMediaPropertiesJQuery = jQuery(this.getData().mediaProperties); 8672 8673 if ((this._lastMediaPropertiesJQuery !== undefined) 8674 && (this._lastMediaPropertiesMap !== undefined) 8675 && (this._lastMediaPropertiesJQuery.is(thisMediaPropertiesJQuery))) { 8676 8677 return this._lastMediaPropertiesMap; 8678 } 8679 8680 currentMediaPropertiesMap = {}; 8681 8682 mpData = this.getData().mediaProperties; 8683 8684 if (mpData) { 8685 if (mpData.callvariables && mpData.callvariables.CallVariable) { 8686 if (mpData.callvariables.CallVariable.length === undefined) { 8687 mpData.callvariables.CallVariable = [mpData.callvariables.CallVariable]; 8688 } 8689 jQuery.each(mpData.callvariables.CallVariable, function (i, callVariable) { 8690 currentMediaPropertiesMap[callVariable.name] = callVariable.value; 8691 }); 8692 } 8693 8694 jQuery.each(mpData, function (key, value) { 8695 if (key !== 'callvariables') { 8696 currentMediaPropertiesMap[key] = value; 8697 } 8698 }); 8699 } 8700 8701 this._lastMediaPropertiesMap = currentMediaPropertiesMap; 8702 this._lastMediaPropertiesJQuery = thisMediaPropertiesJQuery; 8703 8704 return this._lastMediaPropertiesMap; 8705 }, 8706 8707 8708 8709 /** 8710 * @private 8711 * Invoke a request to the server given a content body and handlers. 8712 * 8713 * @param {Object} contentBody 8714 * A JS object containing the body of the action request. 8715 * @param {finesse.interfaces.RequestHandlers} handlers 8716 * An object containing the handlers for the request 8717 */ 8718 _makeRequest: function (contentBody, handlers) { 8719 // Protect against null dereferencing of options allowing its 8720 // (nonexistent) keys to be read as undefined 8721 handlers = handlers || {}; 8722 8723 this.restRequest(this.getRestUrl(), { 8724 method: 'PUT', 8725 success: handlers.success, 8726 error: handlers.error, 8727 content: contentBody 8728 }); 8729 } 8730 8731 }); 8732 8733 window.finesse = window.finesse || {}; 8734 window.finesse.restservices = window.finesse.restservices || {}; 8735 window.finesse.restservices.DialogBase = DialogBase; 8736 8737 8738 return DialogBase; 8739 }); 8740 8741 /** 8742 * JavaScript representation of the Finesse Dialog object. 8743 * 8744 * @requires finesse.clientservices.ClientServices 8745 * @requires Class 8746 * @requires finesse.FinesseBase 8747 * @requires finesse.restservices.RestBase 8748 */ 8749 8750 /** @private */ 8751 define('restservices/Dialog',[ 8752 'restservices/DialogBase', 8753 'utilities/Utilities' 8754 ], 8755 function (DialogBase, Utilities) { 8756 var Dialog = DialogBase.extend(/** @lends finesse.restservices.Dialog.prototype */{ 8757 8758 /** 8759 * @class 8760 * A Dialog is an attempted connection between or among multiple participants, 8761 * for example, a regular phone call, a conference, or a silent monitor session. 8762 * 8763 * @augments finesse.restservices.DialogBase 8764 * @constructs 8765 */ 8766 _fakeConstuctor: function () { 8767 /* This is here to hide the real init constructor from the public docs */ 8768 }, 8769 8770 /** 8771 * @private 8772 * 8773 * @param {Object} options 8774 * An object with the following properties:<ul> 8775 * <li><b>id:</b> The id of the object being constructed</li> 8776 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 8777 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 8778 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 8779 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 8780 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 8781 * <li><b>status:</b> {Number} The HTTP status code returned</li> 8782 * <li><b>content:</b> {String} Raw string of response</li> 8783 * <li><b>object:</b> {Object} Parsed object of response</li> 8784 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 8785 * <li><b>errorType:</b> {String} Type of error that was caught</li> 8786 * <li><b>errorMessage:</b> {String} Message associated with error</li> 8787 * </ul></li> 8788 * </ul></li> 8789 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 8790 **/ 8791 init: function (options) { 8792 this._super(options); 8793 }, 8794 8795 /** 8796 * @private 8797 * Gets the REST class for the current object - this is the Dialog class. 8798 * @returns {Object} The Dialog class. 8799 */ 8800 getRestClass: function () { 8801 return Dialog; 8802 }, 8803 8804 /** 8805 * The requestId reaper timeout in ms 8806 */ 8807 REQUESTID_REAPER_TIMEOUT: 5000, 8808 8809 /** 8810 * Getter for the from address. 8811 * @returns {String} The from address. 8812 */ 8813 getFromAddress: function () { 8814 this.isLoaded(); 8815 return this.getData().fromAddress; 8816 }, 8817 8818 /** 8819 * Getter for the to address. 8820 * @returns {String} The to address. 8821 */ 8822 getToAddress: function () { 8823 this.isLoaded(); 8824 return this.getData().toAddress; 8825 }, 8826 8827 /** 8828 * Getter for the callback number without prefix. 8829 * This is required to schedule a callback if there is any dialer prefix added for direct_preview outbound calls 8830 * @returns {String} The callback number.undefined if callbackNumber is not available 8831 */ 8832 getCallbackNumber: function () { 8833 this.isLoaded(); 8834 return this.getData().callbackNumber; 8835 }, 8836 8837 /** 8838 * Getter for the secondaryId of a dialog. 8839 * A CONSULT call has two call legs (primary leg and a consult leg). 8840 * As the CONSULT call is completed (either with TRANSFER or CONFERENCE), call legs would be merged. 8841 * The surviving call's Dialog will contain the dropped call's Dialog Id in secondaryId field. 8842 * For CCE deployments, DIRECT_TRANSFER also have the secondaryId populated as mentioned above. 8843 * @returns {String} The id of the secondary dialog. 8844 * @since 11.6(1)-ES1 onwards 8845 */ 8846 getSecondaryId: function () { 8847 this.isLoaded(); 8848 return this.getData().secondaryId; 8849 }, 8850 8851 /** 8852 * gets the participant timer counters 8853 * 8854 * @param {String} participantExt Extension of participant. 8855 * @returns {Object} Array of Participants which contains properties :<ul> 8856 * <li>state - The state of the Participant. 8857 * <li>startTime - The start Time of the Participant. 8858 * <li>stateChangeTime - The time when participant state has changed.</ul> 8859 */ 8860 getParticipantTimerCounters : function (participantExt) { 8861 var part, participantTimerCounters = {}, idx, participants; 8862 8863 participants = this.getParticipants(); 8864 8865 8866 //Loop through all the participants and find the right participant (based on participantExt) 8867 for(idx=0;idx<participants.length;idx=idx+1) 8868 { 8869 part = participants[idx]; 8870 8871 if (part.mediaAddress === participantExt) 8872 { 8873 participantTimerCounters.startTime= part.startTime; 8874 participantTimerCounters.stateChangeTime= part.stateChangeTime; 8875 participantTimerCounters.state= part.state; 8876 break; 8877 } 8878 } 8879 8880 return participantTimerCounters; 8881 }, 8882 8883 /** 8884 * Determines the droppable participants. A droppable participant is a participant that is an agent extension. 8885 * (It is not a CTI Route Point, IVR Port, or the caller) 8886 * 8887 * @param {String} filterExtension used to remove a single extension from the list 8888 * @returns {Object} Array of Participants that can be dropped. 8889 * Participant entity properties are as follows:<ul> 8890 * <li>state - The state of the Participant. 8891 * <li>stateCause - The state cause of the Participant. 8892 * <li>mediaAddress - The media address of the Participant. 8893 * <li>startTime - The start Time of the Participant. 8894 * <li>stateChangeTime - The time when participant state has changed. 8895 * <li>actions - These are the actions that a Participant can perform</ul> 8896 */ 8897 getDroppableParticipants: function (filterExtension) { 8898 this.isLoaded(); 8899 var droppableParticipants = [], participants, index, idx, filterExtensionToRemove = "", callStateOk, part; 8900 8901 participants = this.getParticipants(); 8902 8903 if (filterExtension) 8904 { 8905 filterExtensionToRemove = filterExtension; 8906 } 8907 8908 //Loop through all the participants to remove non-agents & remove filterExtension 8909 //We could have removed filterExtension using splice, but we have to iterate through 8910 //the list anyway. 8911 for(idx=0;idx<participants.length;idx=idx+1) 8912 { 8913 part = participants[idx]; 8914 8915 //Skip the filterExtension 8916 if (part.mediaAddress !== filterExtensionToRemove) 8917 { 8918 callStateOk = this._isParticipantStateDroppable(part); 8919 8920 //Remove non-agents & make sure callstate 8921 if (callStateOk === true && part.mediaAddressType === this._agentDeviceType) 8922 { 8923 droppableParticipants.push(part); 8924 } 8925 } 8926 } 8927 8928 return Utilities.getArray(droppableParticipants); 8929 }, 8930 8931 _isParticipantStateDroppable : function (part) 8932 { 8933 var isParticipantStateDroppable = false; 8934 if (part.state === Dialog.ParticipantStates.ACTIVE || part.state === Dialog.ParticipantStates.ACCEPTED || part.state === Dialog.ParticipantStates.HELD) 8935 { 8936 isParticipantStateDroppable = true; 8937 } 8938 8939 return isParticipantStateDroppable; 8940 }, 8941 8942 /** 8943 * Is the participant droppable 8944 * 8945 * @param {String} participantExt Extension of participant. 8946 * @returns {Boolean} True is droppable. 8947 */ 8948 isParticipantDroppable : function (participantExt) { 8949 var droppableParticipants = null, isDroppable = false, idx, part, callStateOk; 8950 8951 droppableParticipants = this.getDroppableParticipants(); 8952 8953 if (droppableParticipants) 8954 { 8955 for(idx=0;idx<droppableParticipants.length;idx=idx+1) 8956 { 8957 part = droppableParticipants[idx]; 8958 8959 if (part.mediaAddress === participantExt) 8960 { 8961 callStateOk = this._isParticipantStateDroppable(part); 8962 8963 //Remove non-agents & make sure callstate 8964 if (callStateOk === true && part.mediaAddressType === this._agentDeviceType) 8965 { 8966 isDroppable = true; 8967 break; 8968 } 8969 } 8970 } 8971 } 8972 8973 return isDroppable; 8974 }, 8975 8976 /** 8977 * Retrieves information about the currently scheduled callback, if any. 8978 * @returns {Object} If no callback has been set, will return undefined. If 8979 * a callback has been set, it will return a map with one or more of the 8980 * following entries, depending on what values have been set. 8981 * callbackTime - the callback time, if it has been set. 8982 * callbackNumber - the callback number, if it has been set. 8983 */ 8984 getCallbackInfo: function() { 8985 this.isLoaded(); 8986 return this.getData().scheduledCallbackInfo; 8987 }, 8988 8989 /** 8990 * Invoke a consult call out to a destination. 8991 * 8992 * @param {String} mediaAddress 8993 * The media address of the user performing the consult call. 8994 * @param {String} toAddress 8995 * The destination address of the consult call. 8996 * @param {finesse.interfaces.RequestHandlers} handlers 8997 * An object containing the handlers for the request 8998 */ 8999 makeConsultCall: function (mediaAddress, toAddress, handlers) { 9000 this.isLoaded(); 9001 var contentBody = {}; 9002 contentBody[this.getRestType()] = { 9003 "targetMediaAddress": mediaAddress, 9004 "toAddress": toAddress, 9005 "requestedAction": Dialog.Actions.CONSULT_CALL 9006 }; 9007 this._makeRequest(contentBody, handlers); 9008 return this; // Allow cascading 9009 }, 9010 9011 /** 9012 * Invoke a single step transfer request. 9013 * 9014 * @param {String} mediaAddress 9015 * The media address of the user performing the single step transfer. 9016 * @param {String} toAddress 9017 * The destination address of the single step transfer. 9018 * @param {finesse.interfaces.RequestHandlers} handlers 9019 * An object containing the handlers for the request 9020 */ 9021 initiateDirectTransfer: function (mediaAddress, toAddress, handlers) { 9022 this.isLoaded(); 9023 var contentBody = {}; 9024 contentBody[this.getRestType()] = { 9025 "targetMediaAddress": mediaAddress, 9026 "toAddress": toAddress, 9027 "requestedAction": Dialog.Actions.TRANSFER_SST 9028 }; 9029 this._makeRequest(contentBody, handlers); 9030 return this; // Allow cascading 9031 }, 9032 9033 /** 9034 * Update this dialog's wrap-up reason. 9035 * 9036 * @param {String} wrapUpReason 9037 * The new wrap-up reason for this dialog 9038 * @param {finesse.interfaces.RequestHandlers} handlers 9039 * An object containing the handlers for the request 9040 */ 9041 updateWrapUpReason: function (wrapUpItems, options) 9042 { 9043 this.isLoaded(); 9044 var mediaProperties = {}; 9045 if (window.finesse.container.Config.deploymentType === 'UCCX') { 9046 mediaProperties = { 9047 "wrapUpItems": {wrapUpItem: wrapUpItems} 9048 } ; 9049 } else { 9050 mediaProperties = { 9051 "wrapUpReason": wrapUpItems 9052 }; 9053 } 9054 9055 options = options || {}; 9056 options.content = {}; 9057 options.content[this.getRestType()] = 9058 { 9059 "mediaProperties": mediaProperties, 9060 "requestedAction": Dialog.Actions.UPDATE_CALL_DATA 9061 }; 9062 options.method = "PUT"; 9063 this.restRequest(this.getRestUrl(), options); 9064 9065 return this; 9066 }, 9067 9068 /** 9069 * Invoke a request to server based on the action given. 9070 * @param {String} mediaAddress 9071 * The media address of the user performing the action. 9072 * @param {finesse.restservices.Dialog.Actions} action 9073 * The action string indicating the action to invoke on dialog. 9074 * @param {finesse.interfaces.RequestHandlers} handlers 9075 * An object containing the handlers for the request 9076 */ 9077 requestAction: function (mediaAddress, action, handlers) { 9078 this.isLoaded(); 9079 var contentBody = {}; 9080 contentBody[this.getRestType()] = { 9081 "targetMediaAddress": mediaAddress, 9082 "requestedAction": action 9083 }; 9084 this._makeRequest(contentBody, handlers); 9085 return this; // Allow cascading 9086 }, 9087 9088 /** 9089 * Wrapper around "requestAction" to request PARTICIPANT_DROP action. 9090 * 9091 * @param targetMediaAddress is the address to drop 9092 * @param {finesse.interfaces.RequestHandlers} handlers 9093 * An object containing the handlers for the request 9094 */ 9095 dropParticipant: function (targetMediaAddress, handlers) { 9096 this.requestAction(targetMediaAddress, Dialog.Actions.PARTICIPANT_DROP, handlers); 9097 }, 9098 9099 /** 9100 * Invoke a request to server to send DTMF digit tones. 9101 * @param {String} mediaAddress 9102 * @param {finesse.interfaces.RequestHandlers} handlers 9103 * An object containing the handlers for the request 9104 * @param {String} digit 9105 * The digit which causes invocation of an action on the dialog. 9106 */ 9107 sendDTMFRequest: function (mediaAddress, handlers, digit) { 9108 this.isLoaded(); 9109 var contentBody = {}; 9110 contentBody[this.getRestType()] = { 9111 "targetMediaAddress": mediaAddress, 9112 "requestedAction": "SEND_DTMF", 9113 "actionParams": { 9114 "ActionParam": { 9115 "name": "dtmfString", 9116 "value": digit 9117 } 9118 } 9119 }; 9120 this._makeRequest(contentBody, handlers); 9121 return this; // Allow cascading 9122 }, 9123 9124 /** 9125 * Invoke a request to server to set the time for a callback. 9126 * @param {String} mediaAddress 9127 * @param {String} callbackTime 9128 * The requested time for the callback, in YYYY-MM-DDTHH:MM format 9129 * (ex: 2013-12-24T23:59) 9130 * @param {finesse.interfaces.RequestHandlers} handlers 9131 * An object containing the handlers for the request 9132 */ 9133 updateCallbackTime: function (mediaAddress, callbackTime, handlers) { 9134 this.isLoaded(); 9135 var contentBody = {}; 9136 contentBody[this.getRestType()] = { 9137 "targetMediaAddress": mediaAddress, 9138 "requestedAction": Dialog.Actions.UPDATE_SCHEDULED_CALLBACK, 9139 "actionParams": { 9140 "ActionParam": { 9141 "name": "callbackTime", 9142 "value": callbackTime 9143 } 9144 } 9145 }; 9146 this._makeRequest(contentBody, handlers); 9147 return this; // Allow cascading 9148 }, 9149 9150 /** 9151 * Invoke a request to server to set the number for a callback. 9152 * @param {String} mediaAddress 9153 * @param {String} callbackNumber 9154 * The requested number to call for the callback 9155 * @param {finesse.interfaces.RequestHandlers} handlers 9156 * An object containing the handlers for the request 9157 */ 9158 updateCallbackNumber: function (mediaAddress, callbackNumber, handlers) { 9159 this.isLoaded(); 9160 var contentBody = {}; 9161 contentBody[this.getRestType()] = { 9162 "targetMediaAddress": mediaAddress, 9163 "requestedAction": Dialog.Actions.UPDATE_SCHEDULED_CALLBACK, 9164 "actionParams": { 9165 "ActionParam": { 9166 "name": "callbackNumber", 9167 "value": callbackNumber 9168 } 9169 } 9170 }; 9171 this._makeRequest(contentBody, handlers); 9172 return this; // Allow cascading 9173 }, 9174 9175 /** 9176 * Invoke a request to server to cancel a callback. 9177 * @param {String} mediaAddress 9178 * @param {finesse.interfaces.RequestHandlers} handlers 9179 * An object containing the handlers for the request 9180 */ 9181 cancelCallback: function (mediaAddress, handlers) { 9182 this.isLoaded(); 9183 var contentBody = {}; 9184 contentBody[this.getRestType()] = { 9185 "targetMediaAddress": mediaAddress, 9186 "requestedAction": Dialog.Actions.CANCEL_SCHEDULED_CALLBACK 9187 }; 9188 this._makeRequest(contentBody, handlers); 9189 return this; // Allow cascading 9190 }, 9191 9192 /** 9193 * Invoke a request to server to reclassify the call type. 9194 * @param {String} mediaAddress 9195 * The media address of the user performing the consult call. 9196 * @param {String} classification 9197 * The classification to assign to the call. Valid values are "VOICE", "FAX", 9198 * "ANS_MACHINE", "INVALID", "BUSY" (CCX only), and "DO_NOT_CALL". 9199 * @param {finesse.interfaces.RequestHandlers} handlers 9200 * An object containing the handlers for the request 9201 */ 9202 reclassifyCall: function (mediaAddress, classification, handlers) { 9203 this.isLoaded(); 9204 var contentBody = {}; 9205 contentBody[this.getRestType()] = { 9206 "targetMediaAddress": mediaAddress, 9207 "requestedAction": Dialog.Actions.RECLASSIFY, 9208 "actionParams": { 9209 "ActionParam": { 9210 "name": "outboundClassification", 9211 "value": classification 9212 } 9213 } 9214 }; 9215 this._makeRequest(contentBody, handlers); 9216 return this; // Allow cascading 9217 }, 9218 9219 /** 9220 * Utility method to create a closure containing the requestId and the Dialogs object so 9221 * that the _pendingCallbacks map can be manipulated when the timer task is executed. 9222 * @param {String} requestId The requestId of the event 9223 * @return {Function} The function to be executed by setTimeout 9224 */ 9225 _createRequestIdReaper: function (requestId) { 9226 var that = this; 9227 return function () { 9228 that._logger.log("Dialog: clearing the requestId-to-callbacks mapping for requestId=" + requestId); 9229 delete that._pendingCallbacks[requestId]; 9230 }; 9231 }, 9232 9233 /** 9234 * Overriding implementation of the one in RestBase.js 9235 * This determines the strategy that Dialogs will take after processing an event that contains a requestId. 9236 * @param {String} requestId The requestId of the event 9237 */ 9238 _postProcessUpdateStrategy: function (requestId) { 9239 this._logger.log("Dialog: determining whether to set timeout for clearing requestId-to-callbacks mapping requestId=" + requestId); 9240 var callbacksObj = this._pendingCallbacks[requestId]; 9241 if (callbacksObj && !callbacksObj.used) { 9242 this._logger.log("Dialog: setting timeout for clearing requestId-to-callbacks mapping requestId=" + requestId); 9243 setTimeout(this._createRequestIdReaper(requestId), this.REQUESTID_REAPER_TIMEOUT); 9244 callbacksObj.used = true; 9245 } 9246 } 9247 9248 }); 9249 9250 Dialog.Actions = /** @lends finesse.restservices.Dialog.Actions.prototype */ { 9251 /** 9252 * Drops the Participant from the Dialog. 9253 */ 9254 DROP: "DROP", 9255 /** 9256 * Answers a Dialog. 9257 */ 9258 ANSWER: "ANSWER", 9259 /** 9260 * Holds the Dialog. 9261 */ 9262 HOLD: "HOLD", 9263 /** 9264 * Barges into a Call Dialog. 9265 */ 9266 BARGE_CALL: "BARGE_CALL", 9267 /** 9268 * Allow as Supervisor to Drop a Participant from the Dialog. 9269 */ 9270 PARTICIPANT_DROP: "PARTICIPANT_DROP", 9271 /** 9272 * Makes a new Call Dialog. 9273 */ 9274 MAKE_CALL: "MAKE_CALL", 9275 /** 9276 * Retrieves a Dialog that is on Hold. 9277 */ 9278 RETRIEVE: "RETRIEVE", 9279 /** 9280 * Sets the time or number for a callback. Can be 9281 * either a new callback, or updating an existing one. 9282 */ 9283 UPDATE_SCHEDULED_CALLBACK: "UPDATE_SCHEDULED_CALLBACK", 9284 /** 9285 * Cancels a callback. 9286 */ 9287 CANCEL_SCHEDULED_CALLBACK: "CANCEL_SCHEDULED_CALLBACK", 9288 /** 9289 * Initiates a Consult Call. 9290 */ 9291 CONSULT_CALL: "CONSULT_CALL", 9292 /** 9293 * Initiates a Transfer of a Dialog. 9294 */ 9295 TRANSFER: "TRANSFER", 9296 /** 9297 * Initiates a Single-Step Transfer of a Dialog. 9298 */ 9299 TRANSFER_SST: "TRANSFER_SST", 9300 /** 9301 * Initiates a Conference of a Dialog. 9302 */ 9303 CONFERENCE: "CONFERENCE", 9304 /** 9305 * Changes classification for a call 9306 */ 9307 RECLASSIFY: "RECLASSIFY", 9308 /** 9309 * Updates data on a Call Dialog. 9310 */ 9311 UPDATE_CALL_DATA: "UPDATE_CALL_DATA", 9312 /** 9313 * Initiates a Recording on a Call Dialog. 9314 */ 9315 START_RECORDING : "START_RECORDING", 9316 /** 9317 * Sends DTMF (dialed digits) to a Call Dialog. 9318 */ 9319 DTMF : "SEND_DTMF", 9320 /** 9321 * Accepts a Dialog that is being Previewed. 9322 */ 9323 ACCEPT: "ACCEPT", 9324 /** 9325 * Rejects a Dialog. 9326 */ 9327 REJECT: "REJECT", 9328 /** 9329 * Closes a Dialog. 9330 */ 9331 CLOSE : "CLOSE", 9332 /** 9333 * @class Set of action constants for a Dialog. These should be used for 9334 * {@link finesse.restservices.Dialog#requestAction}. 9335 * @constructs 9336 */ 9337 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 9338 }; 9339 9340 Dialog.States = /** @lends finesse.restservices.Dialog.States.prototype */ { 9341 /** 9342 * Indicates that the call is ringing at a device. 9343 */ 9344 ALERTING: "ALERTING", 9345 /** 9346 * Indicates that the phone is off the hook at a device. 9347 */ 9348 INITIATING: "INITIATING", 9349 /** 9350 * Indicates that the dialog has a least one active participant. 9351 */ 9352 ACTIVE: "ACTIVE", 9353 /** 9354 * Indicates that the dialog has no active participants. 9355 */ 9356 DROPPED: "DROPPED", 9357 /** 9358 * Indicates that the phone is dialing at the device. 9359 */ 9360 INITIATED: "INITIATED", 9361 /** 9362 * Indicates that the dialog has failed. 9363 * @see Dialog.ReasonStates 9364 */ 9365 FAILED: "FAILED", 9366 /** 9367 * Indicates that the user has accepted an OUTBOUND_PREVIEW dialog. 9368 */ 9369 ACCEPTED: "ACCEPTED", 9370 /** 9371 * @class Possible Dialog State constants. 9372 * The State flow of a typical in-bound Dialog is as follows: INITIATING, INITIATED, ALERTING, ACTIVE, DROPPED. 9373 * @constructs 9374 */ 9375 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 9376 }; 9377 9378 Dialog.ParticipantStates = /** @lends finesse.restservices.Dialog.ParticipantStates.prototype */ { 9379 /** 9380 * Indicates that an incoming call is ringing on the device. 9381 */ 9382 ALERTING: "ALERTING", 9383 /** 9384 * Indicates that an outgoing call, not yet active, exists on the device. 9385 */ 9386 INITIATING: "INITIATING", 9387 /** 9388 * Indicates that the participant is active on the call. 9389 */ 9390 ACTIVE: "ACTIVE", 9391 /** 9392 * Indicates that the participant has dropped from the call. 9393 */ 9394 DROPPED: "DROPPED", 9395 /** 9396 * Indicates that the participant has held their connection to the call. 9397 */ 9398 HELD: "HELD", 9399 /** 9400 * Indicates that the phone is dialing at a device. 9401 */ 9402 INITIATED: "INITIATED", 9403 /** 9404 * Indicates that the call failed. 9405 * @see Dialog.ReasonStates 9406 */ 9407 FAILED: "FAILED", 9408 /** 9409 * Indicates that the participant is not in active state on the call, but is wrapping up after the participant has dropped from the call. 9410 */ 9411 WRAP_UP: "WRAP_UP", 9412 /** 9413 * Indicates that the participant has accepted the dialog. This state is applicable to OUTBOUND_PREVIEW dialogs. 9414 */ 9415 ACCEPTED: "ACCEPTED", 9416 /** 9417 * @class Possible Dialog Participant State constants. 9418 * @constructs 9419 */ 9420 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 9421 }; 9422 9423 Dialog.ReasonStates = /** @lends finesse.restservices.Dialog.ReasonStates.prototype */ { 9424 /** 9425 * Dialog was Busy. This will typically be for a Failed Dialog. 9426 */ 9427 BUSY: "BUSY", 9428 /** 9429 * Dialog reached a Bad Destination. This will typically be for a Failed Dialog. 9430 */ 9431 BAD_DESTINATION: "BAD_DESTINATION", 9432 /** 9433 * All Other Reasons. This will typically be for a Failed Dialog. 9434 */ 9435 OTHER: "OTHER", 9436 /** 9437 * The Device Resource for the Dialog was not available. 9438 */ 9439 DEVICE_RESOURCE_NOT_AVAILABLE : "DEVICE_RESOURCE_NOT_AVAILABLE", 9440 /** 9441 * @class Possible dialog state reasons code constants. 9442 * @constructs 9443 */ 9444 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 9445 }; 9446 9447 window.finesse = window.finesse || {}; 9448 window.finesse.restservices = window.finesse.restservices || {}; 9449 window.finesse.restservices.Dialog = Dialog; 9450 9451 9452 return Dialog; 9453 }); 9454 9455 /** 9456 * JavaScript representation of the Finesse Dialogs collection 9457 * object which contains a list of Dialog objects. 9458 * 9459 * @requires finesse.clientservices.ClientServices 9460 * @requires Class 9461 * @requires finesse.FinesseBase 9462 * @requires finesse.restservices.RestBase 9463 * @requires finesse.restservices.Dialog 9464 */ 9465 /** @private */ 9466 define('restservices/Dialogs',[ 9467 'restservices/RestCollectionBase', 9468 'restservices/Dialog' 9469 ], 9470 function (RestCollectionBase, Dialog) { 9471 var Dialogs = RestCollectionBase.extend(/** @lends finesse.restservices.Dialogs.prototype */{ 9472 9473 /** 9474 * @class 9475 * JavaScript representation of a Dialogs collection object. Also exposes 9476 * methods to operate on the object against the server. 9477 * @augments finesse.restservices.RestCollectionBase 9478 * @constructs 9479 * @see finesse.restservices.Dialog 9480 * @example 9481 * _dialogs = _user.getDialogs( { 9482 * onCollectionAdd : _handleDialogAdd, 9483 * onCollectionDelete : _handleDialogDelete, 9484 * onLoad : _handleDialogsLoaded 9485 * }); 9486 * 9487 * _dialogCollection = _dialogs.getCollection(); 9488 * for (var dialogId in _dialogCollection) { 9489 * if (_dialogCollection.hasOwnProperty(dialogId)) { 9490 * _dialog = _dialogCollection[dialogId]; 9491 * etc... 9492 * } 9493 * } 9494 */ 9495 _fakeConstuctor: function () { 9496 /* This is here to hide the real init constructor from the public docs */ 9497 }, 9498 9499 /** 9500 * @private 9501 * @param {Object} options 9502 * An object with the following properties:<ul> 9503 * <li><b>id:</b> The id of the object being constructed</li> 9504 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 9505 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 9506 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 9507 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 9508 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 9509 * <li><b>status:</b> {Number} The HTTP status code returned</li> 9510 * <li><b>content:</b> {String} Raw string of response</li> 9511 * <li><b>object:</b> {Object} Parsed object of response</li> 9512 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 9513 * <li><b>errorType:</b> {String} Type of error that was caught</li> 9514 * <li><b>errorMessage:</b> {String} Message associated with error</li> 9515 * </ul></li> 9516 * </ul></li> 9517 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 9518 **/ 9519 init: function (options) { 9520 this._super(options); 9521 }, 9522 9523 /** 9524 * @private 9525 * Gets the REST class for the current object - this is the Dialogs class. 9526 */ 9527 getRestClass: function () { 9528 return Dialogs; 9529 }, 9530 9531 /** 9532 * @private 9533 * Gets the REST class for the objects that make up the collection. - this 9534 * is the Dialog class. 9535 */ 9536 getRestItemClass: function () { 9537 return Dialog; 9538 }, 9539 9540 /** 9541 * @private 9542 * Gets the REST type for the current object - this is a "Dialogs". 9543 */ 9544 getRestType: function () { 9545 return "Dialogs"; 9546 }, 9547 9548 /** 9549 * @private 9550 * Gets the REST type for the objects that make up the collection - this is "Dialogs". 9551 */ 9552 getRestItemType: function () { 9553 return "Dialog"; 9554 }, 9555 9556 /** 9557 * @private 9558 * Override default to indicates that the collection doesn't support making 9559 * requests. 9560 */ 9561 supportsRequests: true, 9562 9563 /** 9564 * @private 9565 * Override default to indicates that the collection subscribes to its objects. 9566 */ 9567 supportsRestItemSubscriptions: true, 9568 9569 /** 9570 * The requestId reaper timeout in ms 9571 */ 9572 REQUESTID_REAPER_TIMEOUT: 5000, 9573 9574 /** 9575 * @private 9576 * Create a new Dialog in this collection 9577 * 9578 * @param {String} toAddress 9579 * The to address of the new Dialog 9580 * @param {String} fromAddress 9581 * The from address of the new Dialog 9582 * @param {finesse.interfaces.RequestHandlers} handlers 9583 * An object containing the (optional) handlers for the request. 9584 * @return {finesse.restservices.Dialogs} 9585 * This Dialogs object, to allow cascading. 9586 */ 9587 createNewCallDialog: function (toAddress, fromAddress, handlers) 9588 { 9589 var contentBody = {}; 9590 contentBody[this.getRestItemType()] = { 9591 "requestedAction": "MAKE_CALL", 9592 "toAddress": toAddress, 9593 "fromAddress": fromAddress 9594 }; 9595 9596 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 9597 handlers = handlers || {}; 9598 9599 this.restRequest(this.getRestUrl(), { 9600 method: 'POST', 9601 success: handlers.success, 9602 error: handlers.error, 9603 content: contentBody 9604 }); 9605 return this; // Allow cascading 9606 }, 9607 9608 /** 9609 * @private 9610 * Create a new Dialog in this collection as a result of a requested action 9611 * 9612 * @param {String} toAddress 9613 * The to address of the new Dialog 9614 * @param {String} fromAddress 9615 * The from address of the new Dialog 9616 * @param {finesse.restservices.Dialog.Actions} actionType 9617 * The associated action to request for creating this new dialog 9618 * @param {finesse.interfaces.RequestHandlers} handlers 9619 * An object containing the (optional) handlers for the request. 9620 * @return {finesse.restservices.Dialogs} 9621 * This Dialogs object, to allow cascading. 9622 */ 9623 createNewSuperviseCallDialog: function (toAddress, fromAddress, actionType, handlers) 9624 { 9625 var contentBody = {}; 9626 this._isLoaded = true; 9627 9628 contentBody[this.getRestItemType()] = { 9629 "requestedAction": actionType, 9630 "toAddress": toAddress, 9631 "fromAddress": fromAddress 9632 }; 9633 9634 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 9635 handlers = handlers || {}; 9636 9637 this.restRequest(this.getRestUrl(), { 9638 method: 'POST', 9639 success: handlers.success, 9640 error: handlers.error, 9641 content: contentBody 9642 }); 9643 return this; // Allow cascading 9644 }, 9645 9646 /** 9647 * @private 9648 * Create a new Dialog in this collection as a result of a requested action 9649 * @param {String} fromAddress 9650 * The from address of the new Dialog 9651 * @param {String} toAddress 9652 * The to address of the new Dialog 9653 * @param {finesse.restservices.Dialog.Actions} actionType 9654 * The associated action to request for creating this new dialog 9655 * @param {String} dialogUri 9656 * The associated uri of SUPERVISOR_MONITOR call 9657 * @param {finesse.interfaces.RequestHandlers} handlers 9658 * An object containing the (optional) handlers for the request. 9659 * @return {finesse.restservices.Dialogs} 9660 * This Dialogs object, to allow cascading. 9661 */ 9662 createNewBargeCall: function (fromAddress, toAddress, actionType, dialogURI, handlers) { 9663 this.isLoaded(); 9664 9665 var contentBody = {}; 9666 contentBody[this.getRestItemType()] = { 9667 "fromAddress": fromAddress, 9668 "toAddress": toAddress, 9669 "requestedAction": actionType, 9670 "associatedDialogUri": dialogURI 9671 9672 }; 9673 // (nonexistent) keys to be read as undefined 9674 handlers = handlers || {}; 9675 this.restRequest(this.getRestUrl(), { 9676 method: 'POST', 9677 success: handlers.success, 9678 error: handlers.error, 9679 content: contentBody 9680 }); 9681 return this; // Allow cascading 9682 }, 9683 9684 /** 9685 * Utility method to get the number of dialogs in this collection. 9686 * 'excludeSilentMonitor' flag is provided as an option to exclude calls with type 9687 * 'SUPERVISOR_MONITOR' from the count. 9688 * @param {Boolean} excludeSilentMonitor If true, calls with type of 'SUPERVISOR_MONITOR' will be excluded from the count. 9689 * @return {Number} The number of dialogs in this collection. 9690 */ 9691 getDialogCount: function (excludeSilentMonitor) { 9692 this.isLoaded(); 9693 9694 var dialogId, count = 0; 9695 if (excludeSilentMonitor) { 9696 for (dialogId in this._collection) { 9697 if (this._collection.hasOwnProperty(dialogId)) { 9698 if (this._collection[dialogId].getCallType() !== 'SUPERVISOR_MONITOR') { 9699 count += 1; 9700 } 9701 } 9702 } 9703 9704 return count; 9705 } else { 9706 return this.length; 9707 } 9708 }, 9709 9710 /** 9711 * Utility method to create a closure containing the requestId and the Dialogs object so 9712 * that the _pendingCallbacks map can be manipulated when the timer task is executed. 9713 * @param {String} requestId The requestId of the event 9714 * @return {Function} The function to be executed by setTimeout 9715 */ 9716 _createRequestIdReaper: function (requestId) { 9717 var that = this; 9718 return function () { 9719 that._logger.log("Dialogs: clearing the requestId-to-callbacks mapping for requestId=" + requestId); 9720 delete that._pendingCallbacks[requestId]; 9721 }; 9722 }, 9723 9724 /** 9725 * Overriding implementation of the one in RestBase.js 9726 * This determines the strategy that Dialogs will take after processing an event that contains a requestId. 9727 * @param {String} requestId The requestId of the event 9728 */ 9729 _postProcessUpdateStrategy: function (requestId) { 9730 this._logger.log("Dialogs: determining whether to set timeout for clearing requestId-to-callbacks mapping requestId=" + requestId); 9731 var callbacksObj = this._pendingCallbacks[requestId]; 9732 if (callbacksObj && !callbacksObj.used) { 9733 this._logger.log("Dialogs: setting timeout for clearing requestId-to-callbacks mapping requestId=" + requestId); 9734 setTimeout(this._createRequestIdReaper(requestId), this.REQUESTID_REAPER_TIMEOUT); 9735 callbacksObj.used = true; 9736 } 9737 } 9738 9739 }); 9740 9741 window.finesse = window.finesse || {}; 9742 window.finesse.restservices = window.finesse.restservices || {}; 9743 window.finesse.restservices.Dialogs = Dialogs; 9744 9745 return Dialogs; 9746 }); 9747 9748 /** 9749 * JavaScript representation of the Finesse ClientLog object 9750 * 9751 * @requires finesse.clientservices.ClientServices 9752 * @requires Class 9753 * @requires finesse.FinesseBase 9754 * @requires finesse.restservices.RestBase 9755 */ 9756 9757 /** The following comment is to prevent jslint errors about 9758 * using variables before they are defined. 9759 */ 9760 /** @private */ 9761 /*global finesse*/ 9762 9763 define('restservices/ClientLog',["restservices/RestBase"], function (RestBase) { 9764 9765 var ClientLog = RestBase.extend(/** @lends finesse.restservices.ClientLog.prototype */{ 9766 /** 9767 * @private 9768 * Returns whether this object supports transport logs 9769 */ 9770 doNotLog : true, 9771 9772 explicitSubscription : true, 9773 9774 /** 9775 * @class 9776 * @private 9777 * JavaScript representation of a ClientLog object. Also exposes methods to operate 9778 * on the object against the server. 9779 * 9780 * @param {Object} options 9781 * An object with the following properties:<ul> 9782 * <li><b>id:</b> The id of the object being constructed</li> 9783 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 9784 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 9785 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 9786 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 9787 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 9788 * <li><b>status:</b> {Number} The HTTP status code returned</li> 9789 * <li><b>content:</b> {String} Raw string of response</li> 9790 * <li><b>object:</b> {Object} Parsed object of response</li> 9791 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 9792 * <li><b>errorType:</b> {String} Type of error that was caught</li> 9793 * <li><b>errorMessage:</b> {String} Message associated with error</li> 9794 * </ul></li> 9795 * </ul></li> 9796 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 9797 * @constructs 9798 * @augments finesse.restservices.RestBase 9799 **/ 9800 init: function (options) { 9801 this._super({ 9802 id: "", 9803 data: {clientLog : null}, 9804 onAdd: options.onAdd, 9805 onChange: options.onChange, 9806 onLoad: options.onLoad, 9807 onError: options.onError, 9808 parentObj: options.parentObj 9809 }); 9810 }, 9811 9812 /** 9813 * @private 9814 * Gets the REST class for the current object - this is the ClientLog object. 9815 */ 9816 getRestClass: function () { 9817 return ClientLog; 9818 }, 9819 9820 /** 9821 * @private 9822 * Gets the REST type for the current object - this is a "ClientLog". 9823 */ 9824 getRestType: function () 9825 { 9826 return "ClientLog"; 9827 }, 9828 9829 /** 9830 * @private 9831 * Gets the node path for the current object 9832 * @returns {String} The node path 9833 */ 9834 getXMPPNodePath: function () { 9835 return this.getRestUrl(); 9836 }, 9837 9838 /** 9839 * @private 9840 * Utility method to fetch this object from the server, however we 9841 * override it for ClientLog to not do anything because GET is not supported 9842 * for ClientLog object. 9843 */ 9844 _doGET: function (handlers) { 9845 return; 9846 }, 9847 9848 /** 9849 * @private 9850 * Invoke a request to the server given a content body and handlers. 9851 * 9852 * @param {Object} contentBody 9853 * A JS object containing the body of the action request. 9854 * @param {Object} handlers 9855 * An object containing the following (optional) handlers for the request:<ul> 9856 * <li><b>success(rsp):</b> A callback function for a successful request to be invoked with the following 9857 * response object as its only parameter:<ul> 9858 * <li><b>status:</b> {Number} The HTTP status code returned</li> 9859 * <li><b>content:</b> {String} Raw string of response</li> 9860 * <li><b>object:</b> {Object} Parsed object of response</li></ul> 9861 * <li>A error callback function for an unsuccessful request to be invoked with the 9862 * error response object as its only parameter:<ul> 9863 * <li><b>status:</b> {Number} The HTTP status code returned</li> 9864 * <li><b>content:</b> {String} Raw string of response</li> 9865 * <li><b>object:</b> {Object} Parsed object of response (HTTP errors)</li> 9866 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 9867 * <li><b>errorType:</b> {String} Type of error that was caught</li> 9868 * <li><b>errorMessage:</b> {String} Message associated with error</li> 9869 * </ul></li> 9870 * </ul> 9871 */ 9872 sendLogs: function (contentBody, handlers) { 9873 // Protect against null dereferencing of options allowing its 9874 // (nonexistent) keys to be read as undefined 9875 handlers = handlers || {}; 9876 9877 this.restRequest(this.getRestUrl(), { 9878 method: 'POST', 9879 //success: handlers.success, 9880 error: handlers.error, 9881 content: contentBody 9882 }); 9883 } 9884 }); 9885 9886 window.finesse = window.finesse || {}; 9887 window.finesse.restservices = window.finesse.restservices || {}; 9888 window.finesse.restservices.ClientLog = ClientLog; 9889 9890 return ClientLog; 9891 }); 9892 9893 /** 9894 * JavaScript representation of the Finesse Queue object 9895 * @requires finesse.clientservices.ClientServices 9896 * @requires Class 9897 * @requires finesse.FinesseBase 9898 * @requires finesse.restservices.RestBase 9899 */ 9900 9901 /** @private */ 9902 define('restservices/Queue',[ 9903 'restservices/RestBase', 9904 'utilities/Utilities' 9905 ], 9906 function (RestBase, Utilities) { 9907 var Queue = RestBase.extend(/** @lends finesse.restservices.Queue.prototype */{ 9908 9909 /** 9910 * @class 9911 * A Queue is a list of Contacts available to a User for quick dial. 9912 * 9913 * @augments finesse.restservices.RestBase 9914 * @constructs 9915 */ 9916 _fakeConstuctor: function () { 9917 /* This is here to hide the real init constructor from the public docs */ 9918 }, 9919 9920 /** 9921 * @private 9922 * JavaScript representation of a Queue object. Also exposes methods to operate 9923 * on the object against the server. 9924 * 9925 * @constructor 9926 * @param {String} id 9927 * Not required... 9928 * @param {Object} callbacks 9929 * An object containing callbacks for instantiation and runtime 9930 * @param {Function} callbacks.onLoad(this) 9931 * Callback to invoke upon successful instantiation 9932 * @param {Function} callbacks.onLoadError(rsp) 9933 * Callback to invoke on instantiation REST request error 9934 * as passed by finesse.clientservices.ClientServices.ajax() 9935 * { 9936 * status: {Number} The HTTP status code returned 9937 * content: {String} Raw string of response 9938 * object: {Object} Parsed object of response 9939 * error: {Object} Wrapped exception that was caught 9940 * error.errorType: {String} Type of error that was caught 9941 * error.errorMessage: {String} Message associated with error 9942 * } 9943 * @param {Function} callbacks.onChange(this) 9944 * Callback to invoke upon successful update 9945 * @param {Function} callbacks.onError(rsp) 9946 * Callback to invoke on update error (refresh or event) 9947 * as passed by finesse.clientservices.ClientServices.ajax() 9948 * { 9949 * status: {Number} The HTTP status code returned 9950 * content: {String} Raw string of response 9951 * object: {Object} Parsed object of response 9952 * error: {Object} Wrapped exception that was caught 9953 * error.errorType: {String} Type of error that was caught 9954 * error.errorMessage: {String} Message associated with error 9955 * } 9956 * 9957 */ 9958 init: function (id, callbacks, restObj) { 9959 this._super(id, callbacks, restObj); 9960 }, 9961 9962 /** 9963 * @private 9964 * Gets the REST class for the current object - this is the Queue object. 9965 */ 9966 getRestClass: function () { 9967 return Queue; 9968 }, 9969 9970 /** 9971 * @private 9972 * Gets the REST type for the current object - this is a "Queue". 9973 */ 9974 getRestType: function () { 9975 return "Queue"; 9976 }, 9977 9978 /** 9979 * @private 9980 * Returns whether this object supports subscriptions 9981 */ 9982 supportsSubscriptions: function () { 9983 return true; 9984 }, 9985 9986 /** 9987 * @private 9988 * Specifies whether this object's subscriptions need to be explicitly requested 9989 */ 9990 explicitSubscription: true, 9991 9992 /** 9993 * @private 9994 * Gets the node path for the current object - this is the team Users node 9995 * @returns {String} The node path 9996 */ 9997 getXMPPNodePath: function () { 9998 return this.getRestUrl(); 9999 }, 10000 10001 /** 10002 * Getter for the queue id 10003 * @returns {String} 10004 * The id of the Queue 10005 */ 10006 getId: function () { 10007 this.isLoaded(); 10008 return this._id; 10009 }, 10010 10011 /** 10012 * Getter for the queue name 10013 * @returns {String} 10014 * The name of the Queue 10015 */ 10016 getName: function () { 10017 this.isLoaded(); 10018 return this.getData().name; 10019 }, 10020 10021 /** 10022 * Getter for the queue statistics. 10023 * Supported statistics include:<br> 10024 * - agentsBusyOther<br> 10025 * - agentsLoggedOn<br> 10026 * - agentsNotReady<br> 10027 * - agentsReady<br> 10028 * - agentsTalkingInbound<br> 10029 * - agentsTalkingInternal<br> 10030 * - agentsTalkingOutbound<br> 10031 * - agentsWrapUpNotReady<br> 10032 * - agentsWrapUpReady<br> 10033 * - callsInQueue<br> 10034 * - startTimeOfLongestCallInQueue<br> 10035 * <br> 10036 * These statistics can be accessed via dot notation:<br> 10037 * i.e.: getStatistics().callsInQueue 10038 * @returns {Object} 10039 * The Object with different statistics as properties. 10040 */ 10041 getStatistics: function () { 10042 this.isLoaded(); 10043 return this.getData().statistics; 10044 }, 10045 10046 /** 10047 * Parses a uriString to retrieve the id portion 10048 * @param {String} uriString 10049 * @return {String} id 10050 */ 10051 _parseIdFromUriString : function (uriString) { 10052 return Utilities.getId(uriString); 10053 } 10054 10055 }); 10056 10057 window.finesse = window.finesse || {}; 10058 window.finesse.restservices = window.finesse.restservices || {}; 10059 window.finesse.restservices.Queue = Queue; 10060 10061 return Queue; 10062 }); 10063 10064 /** 10065 * JavaScript representation of the Finesse Queues collection 10066 * object which contains a list of Queue objects. 10067 * @requires finesse.clientservices.ClientServices 10068 * @requires Class 10069 * @requires finesse.FinesseBase 10070 * @requires finesse.restservices.RestBase 10071 * @requires finesse.restservices.RestCollectionBase 10072 */ 10073 10074 /** 10075 * @class 10076 * JavaScript representation of a Queues collection object. 10077 * 10078 * @constructor 10079 * @borrows finesse.restservices.RestCollectionBase as finesse.restservices.Queues 10080 */ 10081 10082 /** @private */ 10083 define('restservices/Queues',[ 10084 'restservices/RestCollectionBase', 10085 'restservices/Queue' 10086 ], 10087 function (RestCollectionBase, Queue) { 10088 var Queues = RestCollectionBase.extend(/** @lends finesse.restservices.Queues.prototype */{ 10089 10090 /** 10091 * @class 10092 * JavaScript representation of a Queues collection object. 10093 * @augments finesse.restservices.RestCollectionBase 10094 * @constructs 10095 * @see finesse.restservices.Queue 10096 * @example 10097 * _queues = _user.getQueues( { 10098 * onCollectionAdd : _handleQueueAdd, 10099 * onCollectionDelete : _handleQueueDelete, 10100 * onLoad : _handleQueuesLoaded 10101 * }); 10102 * 10103 * _queueCollection = _queues.getCollection(); 10104 * for (var queueId in _queueCollection) { 10105 * if (_queueCollection.hasOwnProperty(queueId)) { 10106 * _queue = _queueCollection[queueId]; 10107 * etc... 10108 * } 10109 * } 10110 */ 10111 _fakeConstuctor: function () { 10112 /* This is here to hide the real init constructor from the public docs */ 10113 }, 10114 10115 /** 10116 * @private 10117 * JavaScript representation of a Queues object. Also exposes 10118 * methods to operate on the object against the server. 10119 * 10120 * @param {Object} options 10121 * An object with the following properties:<ul> 10122 * <li><b>id:</b> The id of the object being constructed</li> 10123 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 10124 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 10125 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 10126 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 10127 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 10128 * <li><b>status:</b> {Number} The HTTP status code returned</li> 10129 * <li><b>content:</b> {String} Raw string of response</li> 10130 * <li><b>object:</b> {Object} Parsed object of response</li> 10131 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 10132 * <li><b>errorType:</b> {String} Type of error that was caught</li> 10133 * <li><b>errorMessage:</b> {String} Message associated with error</li> 10134 * </ul></li> 10135 * </ul></li> 10136 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 10137 **/ 10138 init: function (options) { 10139 this._super(options); 10140 }, 10141 10142 /** 10143 * @private 10144 * Gets xmpp node path. 10145 */ 10146 getXMPPNodePath: function () { 10147 return this.getRestUrl(); 10148 }, 10149 10150 /** 10151 * @private 10152 * Gets the REST class for the current object - this is the Queues class. 10153 */ 10154 getRestClass: function () { 10155 return Queues; 10156 }, 10157 10158 /** 10159 * @private 10160 * Gets the REST class for the objects that make up the collection. - this 10161 * is the Queue class. 10162 */ 10163 getRestItemClass: function () { 10164 return Queue; 10165 }, 10166 10167 /** 10168 * @private 10169 * Gets the REST type for the current object - this is a "Queues". 10170 */ 10171 getRestType: function () { 10172 return "Queues"; 10173 }, 10174 10175 /** 10176 * @private 10177 * Gets the REST type for the objects that make up the collection - this is "Queue". 10178 */ 10179 getRestItemType: function () { 10180 return "Queue"; 10181 }, 10182 10183 explicitSubscription: true, 10184 10185 handlesItemRefresh: true 10186 }); 10187 10188 window.finesse = window.finesse || {}; 10189 window.finesse.restservices = window.finesse.restservices || {}; 10190 window.finesse.restservices.Queues = Queues; 10191 10192 return Queues; 10193 }); 10194 10195 /** 10196 * JavaScript representation of the Finesse WrapUpReason object. 10197 * 10198 * @requires finesse.clientservices.ClientServices 10199 * @requires Class 10200 * @requires finesse.FinesseBase 10201 * @requires finesse.restservices.RestBase 10202 */ 10203 10204 /** @private */ 10205 define('restservices/WrapUpReason',['restservices/RestBase'], function (RestBase) { 10206 10207 var WrapUpReason = RestBase.extend(/** @lends finesse.restservices.WrapUpReason.prototype */{ 10208 10209 /** 10210 * @class 10211 * A WrapUpReason is a code and description identifying a particular reason that a 10212 * User is in WORK (WrapUp) mode. 10213 * 10214 * @augments finesse.restservices.RestBase 10215 * @see finesse.restservices.User 10216 * @see finesse.restservices.User.States#WORK 10217 * @constructs 10218 */ 10219 _fakeConstuctor: function () { 10220 /* This is here to hide the real init constructor from the public docs */ 10221 }, 10222 10223 /** 10224 * @private 10225 * JavaScript representation of a WrapUpReason object. Also exposes 10226 * methods to operate on the object against the server. 10227 * 10228 * @param {Object} options 10229 * An object with the following properties:<ul> 10230 * <li><b>id:</b> The id of the object being constructed</li> 10231 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 10232 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 10233 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 10234 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 10235 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 10236 * <li><b>status:</b> {Number} The HTTP status code returned</li> 10237 * <li><b>content:</b> {String} Raw string of response</li> 10238 * <li><b>object:</b> {Object} Parsed object of response</li> 10239 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 10240 * <li><b>errorType:</b> {String} Type of error that was caught</li> 10241 * <li><b>errorMessage:</b> {String} Message associated with error</li> 10242 * </ul></li> 10243 * </ul></li> 10244 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 10245 **/ 10246 init: function (options) { 10247 this._super(options); 10248 }, 10249 10250 /** 10251 * @private 10252 * Gets the REST class for the current object - this is the WrapUpReason class. 10253 * @returns {Object} The WrapUpReason class. 10254 */ 10255 getRestClass: function () { 10256 return WrapUpReason; 10257 }, 10258 10259 /** 10260 * @private 10261 * Gets the REST type for the current object - this is a "WrapUpReason". 10262 * @returns {String} The WrapUpReason string. 10263 */ 10264 getRestType: function () { 10265 return "WrapUpReason"; 10266 }, 10267 10268 /** 10269 * @private 10270 * Gets the REST type for the current object - this is a "WrapUpReasons". 10271 * @returns {String} The WrapUpReasons string. 10272 */ 10273 getParentRestType: function () { 10274 return "WrapUpReasons"; 10275 }, 10276 10277 /** 10278 * @private 10279 * Override default to indicate that this object doesn't support making 10280 * requests. 10281 */ 10282 supportsRequests: false, 10283 10284 /** 10285 * @private 10286 * Override default to indicate that this object doesn't support subscriptions. 10287 */ 10288 supportsSubscriptions: false, 10289 10290 /** 10291 * Getter for the label. 10292 * @returns {String} The label. 10293 */ 10294 getLabel: function () { 10295 this.isLoaded(); 10296 return this.getData().label; 10297 }, 10298 10299 /** 10300 * @private 10301 * Getter for the forAll flag. 10302 * @returns {Boolean} True if global. 10303 */ 10304 getForAll: function () { 10305 this.isLoaded(); 10306 return this.getData().forAll; 10307 }, 10308 10309 /** 10310 * @private 10311 * Getter for the Uri value. 10312 * @returns {String} The Uri. 10313 */ 10314 getUri: function () { 10315 this.isLoaded(); 10316 return this.getData().uri; 10317 } 10318 }); 10319 10320 window.finesse = window.finesse || {}; 10321 window.finesse.restservices = window.finesse.restservices || {}; 10322 window.finesse.restservices.WrapUpReason = WrapUpReason; 10323 10324 return WrapUpReason; 10325 }); 10326 10327 /** 10328 * JavaScript representation of the Finesse WrapUpReasons collection 10329 * object which contains a list of WrapUpReason objects. 10330 * 10331 * @requires finesse.clientservices.ClientServices 10332 * @requires Class 10333 * @requires finesse.FinesseBase 10334 * @requires finesse.restservices.RestBase 10335 * @requires finesse.restservices.Dialog 10336 * @requires finesse.restservices.RestCollectionBase 10337 */ 10338 10339 /** @private */ 10340 define('restservices/WrapUpReasons',[ 10341 'restservices/RestCollectionBase', 10342 'restservices/WrapUpReason' 10343 ], 10344 function (RestCollectionBase, WrapUpReason) { 10345 10346 var WrapUpReasons = RestCollectionBase.extend(/** @lends finesse.restservices.WrapUpReasons.prototype */{ 10347 10348 /** 10349 * @class 10350 * JavaScript representation of a WrapUpReasons collection object. 10351 * @augments finesse.restservices.RestCollectionBase 10352 * @constructs 10353 * @see finesse.restservices.WrapUpReason 10354 * @example 10355 * _wrapUpReasons = _user.getWrapUpReasons ( { 10356 * onCollectionAdd : _handleWrapUpReasonAdd, 10357 * onCollectionDelete : _handleWrapUpReasonDelete, 10358 * onLoad : _handleWrapUpReasonsLoaded 10359 * }); 10360 * 10361 * _wrapUpReasonCollection = _wrapUpReasons.getCollection(); 10362 * for (var wrapUpReasonId in _wrapUpReasonCollection) { 10363 * if (_wrapUpReasonCollection.hasOwnProperty(wrapUpReasonId)) { 10364 * _wrapUpReason = _wrapUpReasonCollection[wrapUpReasonId]; 10365 * etc... 10366 * } 10367 * } 10368 */ 10369 _fakeConstuctor: function () { 10370 /* This is here to hide the real init constructor from the public docs */ 10371 }, 10372 10373 /** 10374 * @private 10375 * JavaScript representation of a WrapUpReasons collection object. Also exposes 10376 * methods to operate on the object against the server. 10377 * 10378 * @param {Object} options 10379 * An object with the following properties:<ul> 10380 * <li><b>id:</b> The id of the object being constructed</li> 10381 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 10382 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 10383 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 10384 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 10385 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 10386 * <li><b>status:</b> {Number} The HTTP status code returned</li> 10387 * <li><b>content:</b> {String} Raw string of response</li> 10388 * <li><b>object:</b> {Object} Parsed object of response</li> 10389 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 10390 * <li><b>errorType:</b> {String} Type of error that was caught</li> 10391 * <li><b>errorMessage:</b> {String} Message associated with error</li> 10392 * </ul></li> 10393 * </ul></li> 10394 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 10395 **/ 10396 init: function (options) { 10397 this._super(options); 10398 }, 10399 10400 /** 10401 * @private 10402 * Gets the REST class for the current object - this is the WrapUpReasons class. 10403 */ 10404 getRestClass: function () { 10405 return WrapUpReasons; 10406 }, 10407 10408 /** 10409 * @private 10410 * Gets the REST class for the objects that make up the collection. - this 10411 * is the WrapUpReason class. 10412 */ 10413 getRestItemClass: function () { 10414 return WrapUpReason; 10415 }, 10416 10417 /** 10418 * @private 10419 * Gets the REST type for the current object - this is a "WrapUpReasons". 10420 */ 10421 getRestType: function () { 10422 return "WrapUpReasons"; 10423 }, 10424 10425 /** 10426 * @private 10427 * Gets the REST type for the objects that make up the collection - this is "WrapUpReason". 10428 */ 10429 getRestItemType: function () { 10430 return "WrapUpReason"; 10431 }, 10432 10433 /** 10434 * @private 10435 * Override default to indicates that the collection supports making 10436 * requests. 10437 */ 10438 supportsRequests: true, 10439 10440 /** 10441 * @private 10442 * Override default to indicate that this object doesn't support subscriptions. 10443 */ 10444 supportsRestItemSubscriptions: false, 10445 10446 /** 10447 * @private 10448 * Retrieve the Wrap-Up Reason Codes. This call will re-query the server and refresh the collection. 10449 * 10450 * @returns {finesse.restservices.WrapUpReasons} 10451 * This ReadyReasonCodes object to allow cascading. 10452 */ 10453 get: function () { 10454 // set loaded to false so it will rebuild the collection after the get 10455 this._loaded = false; 10456 // reset collection 10457 this._collection = {}; 10458 // perform get 10459 this._synchronize(); 10460 return this; 10461 } 10462 10463 }); 10464 10465 window.finesse = window.finesse || {}; 10466 window.finesse.restservices = window.finesse.restservices || {}; 10467 window.finesse.restservices.WrapUpReasons = WrapUpReasons; 10468 10469 return WrapUpReasons; 10470 }); 10471 10472 /** 10473 * JavaScript representation of the Finesse Contact object. 10474 * @requires finesse.clientservices.ClientServices 10475 * @requires Class 10476 * @requires finesse.FinesseBase 10477 * @requires finesse.restservices.RestBase 10478 */ 10479 /** @private */ 10480 define('restservices/Contact',['restservices/RestBase'], function (RestBase) { 10481 10482 var Contact = RestBase.extend(/** @lends finesse.restservices.Contact.prototype */{ 10483 10484 /** 10485 * @class 10486 * A Contact is a single entry in a PhoneBook, consisting of a First and Last Name, 10487 * a Phone Number, and a Description. 10488 * 10489 * @augments finesse.restservices.RestBase 10490 * @see finesse.restservices.PhoneBook 10491 * @constructs 10492 */ 10493 _fakeConstuctor: function () { 10494 /* This is here to hide the real init constructor from the public docs */ 10495 }, 10496 10497 /** 10498 * @private 10499 * @param {Object} options 10500 * An object with the following properties:<ul> 10501 * <li><b>id:</b> The id of the object being constructed</li> 10502 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 10503 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 10504 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 10505 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 10506 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 10507 * <li><b>status:</b> {Number} The HTTP status code returned</li> 10508 * <li><b>content:</b> {String} Raw string of response</li> 10509 * <li><b>object:</b> {Object} Parsed object of response</li> 10510 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 10511 * <li><b>errorType:</b> {String} Type of error that was caught</li> 10512 * <li><b>errorMessage:</b> {String} Message associated with error</li> 10513 * </ul></li> 10514 * </ul></li> 10515 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 10516 **/ 10517 init: function (options) { 10518 this._super(options); 10519 }, 10520 10521 /** 10522 * @private 10523 * Gets the REST class for the current object - this is the Contact class. 10524 * @returns {Object} The Contact class. 10525 */ 10526 getRestClass: function () { 10527 return Contact; 10528 }, 10529 10530 /** 10531 * @private 10532 * Gets the REST type for the current object - this is a "Contact". 10533 * @returns {String} The Contact string. 10534 */ 10535 getRestType: function () { 10536 return "Contact"; 10537 }, 10538 10539 /** 10540 * @private 10541 * Override default to indicate that this object doesn't support making 10542 * requests. 10543 */ 10544 supportsRequests: false, 10545 10546 /** 10547 * @private 10548 * Override default to indicate that this object doesn't support subscriptions. 10549 */ 10550 supportsSubscriptions: false, 10551 10552 /** 10553 * Getter for the firstName. 10554 * @returns {String} The firstName. 10555 */ 10556 getFirstName: function () { 10557 this.isLoaded(); 10558 return this.getData().firstName; 10559 }, 10560 10561 /** 10562 * Getter for the lastName. 10563 * @returns {String} The lastName. 10564 */ 10565 getLastName: function () { 10566 this.isLoaded(); 10567 return this.getData().lastName; 10568 }, 10569 10570 /** 10571 * Getter for the phoneNumber. 10572 * @returns {String} The phoneNumber. 10573 */ 10574 getPhoneNumber: function () { 10575 this.isLoaded(); 10576 return this.getData().phoneNumber; 10577 }, 10578 10579 /** 10580 * Getter for the description. 10581 * @returns {String} The description. 10582 */ 10583 getDescription: function () { 10584 this.isLoaded(); 10585 return this.getData().description; 10586 }, 10587 10588 /** @private */ 10589 createPutSuccessHandler: function(contact, contentBody, successHandler){ 10590 return function (rsp) { 10591 // Update internal structure based on response. Here we 10592 // inject the contentBody from the PUT request into the 10593 // rsp.object element to mimic a GET as a way to take 10594 // advantage of the existing _processResponse method. 10595 rsp.object = contentBody; 10596 contact._processResponse(rsp); 10597 10598 //Remove the injected Contact object before cascading response 10599 rsp.object = {}; 10600 10601 //cascade response back to consumer's response handler 10602 successHandler(rsp); 10603 }; 10604 }, 10605 10606 /** @private */ 10607 createPostSuccessHandler: function (contact, contentBody, successHandler) { 10608 return function (rsp) { 10609 rsp.object = contentBody; 10610 contact._processResponse(rsp); 10611 10612 //Remove the injected Contact object before cascading response 10613 rsp.object = {}; 10614 10615 //cascade response back to consumer's response handler 10616 successHandler(rsp); 10617 }; 10618 }, 10619 10620 /** 10621 * Add 10622 * @private 10623 */ 10624 add: function (newValues, handlers) { 10625 // this.isLoaded(); 10626 var contentBody = {}; 10627 10628 contentBody[this.getRestType()] = { 10629 "firstName": newValues.firstName, 10630 "lastName": newValues.lastName, 10631 "phoneNumber": newValues.phoneNumber, 10632 "description": newValues.description 10633 }; 10634 10635 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 10636 handlers = handlers || {}; 10637 10638 this.restRequest(this.getRestUrl(), { 10639 method: 'POST', 10640 success: this.createPostSuccessHandler(this, contentBody, handlers.success), 10641 error: handlers.error, 10642 content: contentBody 10643 }); 10644 10645 return this; // Allow cascading 10646 }, 10647 10648 /** 10649 * Update 10650 * @private 10651 */ 10652 update: function (newValues, handlers) { 10653 this.isLoaded(); 10654 var contentBody = {}; 10655 10656 contentBody[this.getRestType()] = { 10657 "uri": this.getId(), 10658 "firstName": newValues.firstName, 10659 "lastName": newValues.lastName, 10660 "phoneNumber": newValues.phoneNumber, 10661 "description": newValues.description 10662 }; 10663 10664 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 10665 handlers = handlers || {}; 10666 10667 this.restRequest(this.getRestUrl(), { 10668 method: 'PUT', 10669 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 10670 error: handlers.error, 10671 content: contentBody 10672 }); 10673 10674 return this; // Allow cascading 10675 }, 10676 10677 10678 /** 10679 * Delete 10680 * @private 10681 */ 10682 "delete": function ( handlers) { 10683 this.isLoaded(); 10684 10685 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 10686 handlers = handlers || {}; 10687 10688 this.restRequest(this.getRestUrl(), { 10689 method: 'DELETE', 10690 success: this.createPutSuccessHandler(this, {}, handlers.success), 10691 error: handlers.error, 10692 content: undefined 10693 }); 10694 10695 return this; // Allow cascading 10696 } 10697 }); 10698 10699 window.finesse = window.finesse || {}; 10700 window.finesse.restservices = window.finesse.restservices || {}; 10701 window.finesse.restservices.Contact = Contact; 10702 10703 return Contact; 10704 }); 10705 10706 /** 10707 * JavaScript representation of the Finesse Contacts collection 10708 * object which contains a list of Contact objects. 10709 * 10710 * @requires finesse.clientservices.ClientServices 10711 * @requires Class 10712 * @requires finesse.FinesseBase 10713 * @requires finesse.restservices.RestBase 10714 * @requires finesse.restservices.Dialog 10715 * @requires finesse.restservices.RestCollectionBase 10716 */ 10717 /** @private */ 10718 define('restservices/Contacts',[ 10719 'restservices/RestCollectionBase', 10720 'restservices/Contact' 10721 ], 10722 function (RestCollectionBase, Contact) { 10723 var Contacts = RestCollectionBase.extend(/** @lends finesse.restservices.Contacts.prototype */{ 10724 10725 /** 10726 * @class 10727 * JavaScript representation of a Contacts collection object. Also exposes 10728 * methods to operate on the object against the server. 10729 * @augments finesse.restservices.RestCollectionBase 10730 * @constructs 10731 * @see finesse.restservices.Contact 10732 * @see finesse.restservices.PhoneBook 10733 * @example 10734 * _contacts = _phonebook.getContacts( { 10735 * onCollectionAdd : _handleContactAdd, 10736 * onCollectionDelete : _handleContactDelete, 10737 * onLoad : _handleContactsLoaded 10738 * }); 10739 * 10740 * _contactCollection = _contacts.getCollection(); 10741 * for (var contactId in _contactCollection) { 10742 * if (_contactCollection.hasOwnProperty(contactId)) { 10743 * _contact = _contactCollection[contactId]; 10744 * etc... 10745 * } 10746 * } 10747 */ 10748 _fakeConstuctor: function () { 10749 /* This is here to hide the real init constructor from the public docs */ 10750 }, 10751 10752 /** 10753 * @private 10754 * JavaScript representation of a Contacts collection object. Also exposes 10755 * methods to operate on the object against the server. 10756 * 10757 * @param {Object} options 10758 * An object with the following properties:<ul> 10759 * <li><b>id:</b> The id of the object being constructed</li> 10760 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 10761 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 10762 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 10763 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 10764 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 10765 * <li><b>status:</b> {Number} The HTTP status code returned</li> 10766 * <li><b>content:</b> {String} Raw string of response</li> 10767 * <li><b>object:</b> {Object} Parsed object of response</li> 10768 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 10769 * <li><b>errorType:</b> {String} Type of error that was caught</li> 10770 * <li><b>errorMessage:</b> {String} Message associated with error</li> 10771 * </ul></li> 10772 * </ul></li> 10773 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 10774 **/ 10775 init: function (options) { 10776 this._super(options); 10777 }, 10778 10779 /** 10780 * @private 10781 * Gets the REST class for the current object - this is the Contacts class. 10782 */ 10783 getRestClass: function () { 10784 return Contacts; 10785 }, 10786 10787 /** 10788 * @private 10789 * Gets the REST class for the objects that make up the collection. - this 10790 * is the Contact class. 10791 */ 10792 getRestItemClass: function () { 10793 return Contact; 10794 }, 10795 10796 /** 10797 * @private 10798 * Gets the REST type for the current object - this is a "Contacts". 10799 */ 10800 getRestType: function () { 10801 return "Contacts"; 10802 }, 10803 10804 /** 10805 * @private 10806 * Gets the REST type for the objects that make up the collection - this is "Contacts". 10807 */ 10808 getRestItemType: function () { 10809 return "Contact"; 10810 }, 10811 10812 /** 10813 * @private 10814 * Override default to indicates that the collection supports making 10815 * requests. 10816 */ 10817 supportsRequests: true, 10818 10819 /** 10820 * @private 10821 * Override default to indicates that the collection subscribes to its objects. 10822 */ 10823 supportsRestItemSubscriptions: false, 10824 10825 /** 10826 * @private 10827 * Retrieve the Contacts. This call will re-query the server and refresh the collection. 10828 * 10829 * @returns {finesse.restservices.Contacts} 10830 * This Contacts object, to allow cascading. 10831 */ 10832 get: function () { 10833 // set loaded to false so it will rebuild the collection after the get 10834 this._loaded = false; 10835 // reset collection 10836 this._collection = {}; 10837 // perform get 10838 this._synchronize(); 10839 return this; 10840 } 10841 10842 }); 10843 10844 window.finesse = window.finesse || {}; 10845 window.finesse.restservices = window.finesse.restservices || {}; 10846 window.finesse.restservices.Contacts = Contacts; 10847 10848 10849 return Contacts; 10850 }); 10851 10852 /** 10853 * JavaScript representation of the Finesse PhoneBook object. 10854 * 10855 * @requires finesse.clientservices.ClientServices 10856 * @requires Class 10857 * @requires finesse.FinesseBase 10858 * @requires finesse.restservices.RestBase 10859 */ 10860 10861 /** @private */ 10862 define('restservices/PhoneBook',[ 10863 'restservices/RestBase', 10864 'restservices/Contacts' 10865 ], 10866 function (RestBase, Contacts) { 10867 var PhoneBook = RestBase.extend(/** @lends finesse.restservices.PhoneBook.prototype */{ 10868 10869 _contacts: null, 10870 10871 /** 10872 * @class 10873 * A PhoneBook is a list of Contacts available to a User for quick dial. 10874 * 10875 * @augments finesse.restservices.RestBase 10876 * @see finesse.restservices.Contacts 10877 * @constructs 10878 */ 10879 _fakeConstuctor: function () { 10880 /* This is here to hide the real init constructor from the public docs */ 10881 }, 10882 10883 /** 10884 * @private 10885 * JavaScript representation of a PhoneBook object. Also exposes 10886 * methods to operate on the object against the server. 10887 * 10888 * @param {Object} options 10889 * An object with the following properties:<ul> 10890 * <li><b>id:</b> The id of the object being constructed</li> 10891 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 10892 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 10893 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 10894 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 10895 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 10896 * <li><b>status:</b> {Number} The HTTP status code returned</li> 10897 * <li><b>content:</b> {String} Raw string of response</li> 10898 * <li><b>object:</b> {Object} Parsed object of response</li> 10899 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 10900 * <li><b>errorType:</b> {String} Type of error that was caught</li> 10901 * <li><b>errorMessage:</b> {String} Message associated with error</li> 10902 * </ul></li> 10903 * </ul></li> 10904 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 10905 **/ 10906 init: function (options) { 10907 this._super(options); 10908 }, 10909 10910 /** 10911 * @private 10912 * Gets the REST class for the current object - this is the PhoneBook class. 10913 * @returns {Object} The PhoneBook class. 10914 */ 10915 getRestClass: function () { 10916 return PhoneBook; 10917 }, 10918 10919 /** 10920 * @private 10921 * Gets the REST type for the current object - this is a "PhoneBook". 10922 * @returns {String} The PhoneBook string. 10923 */ 10924 getRestType: function () { 10925 return "PhoneBook"; 10926 }, 10927 10928 /** 10929 * @private 10930 * Override default to indicate that this object doesn't support making 10931 * requests. 10932 */ 10933 supportsRequests: false, 10934 10935 /** 10936 * @private 10937 * Override default to indicate that this object doesn't support subscriptions. 10938 */ 10939 supportsSubscriptions: false, 10940 10941 /** 10942 * Getter for the name of the Phone Book. 10943 * @returns {String} The name. 10944 */ 10945 getName: function () { 10946 this.isLoaded(); 10947 return this.getData().name; 10948 }, 10949 10950 /** 10951 * Getter for the type flag. 10952 * @returns {String} The type. 10953 */ 10954 getType: function () { 10955 this.isLoaded(); 10956 return this.getData().type; 10957 }, 10958 10959 /** 10960 * @private 10961 * Getter for the Uri value. 10962 * @returns {String} The Uri. 10963 */ 10964 getUri: function () { 10965 this.isLoaded(); 10966 return this.getData().uri; 10967 }, 10968 10969 /** 10970 * Getter for a Contacts collection object that is associated with PhoneBook. 10971 * @param {finesse.interfaces.RequestHandlers} handlers 10972 * An object containing the handlers for the request 10973 * @returns {finesse.restservices.Contacts} 10974 * A Contacts collection object. 10975 */ 10976 getContacts: function (callbacks) { 10977 var options = callbacks || {}; 10978 options.parentObj = this; 10979 this.isLoaded(); 10980 10981 if (this._contacts === null) { 10982 this._contacts = new Contacts(options); 10983 } 10984 10985 return this._contacts; 10986 }, 10987 10988 /** 10989 * Getter for <contacts> node within PhoneBook - sometimes it's just a URI, sometimes it is a Contacts collection 10990 * @returns {String} uri to contacts 10991 * or {finesse.restservices.Contacts} collection 10992 */ 10993 getEmbeddedContacts: function(){ 10994 this.isLoaded(); 10995 return this.getData().contacts; 10996 }, 10997 10998 /** @private */ 10999 createPutSuccessHandler: function(phonebook, contentBody, successHandler){ 11000 return function (rsp) { 11001 // Update internal structure based on response. Here we 11002 // inject the contentBody from the PUT request into the 11003 // rsp.object element to mimic a GET as a way to take 11004 // advantage of the existing _processResponse method. 11005 rsp.object = contentBody; 11006 phonebook._processResponse(rsp); 11007 11008 //Remove the injected PhoneBook object before cascading response 11009 rsp.object = {}; 11010 11011 //cascade response back to consumer's response handler 11012 successHandler(rsp); 11013 }; 11014 }, 11015 11016 /** @private */ 11017 createPostSuccessHandler: function (phonebook, contentBody, successHandler) { 11018 return function (rsp) { 11019 rsp.object = contentBody; 11020 phonebook._processResponse(rsp); 11021 11022 //Remove the injected PhoneBook object before cascading response 11023 rsp.object = {}; 11024 11025 //cascade response back to consumer's response handler 11026 successHandler(rsp); 11027 }; 11028 }, 11029 11030 /** 11031 * @private 11032 * Add a PhoneBook. 11033 * @param {Object} newValues 11034 * @param {String} newValues.name Name of PhoneBook 11035 * @param {String} newValues.type Type of PhoneBook 11036 * @param {finesse.interfaces.RequestHandlers} handlers 11037 * An object containing the handlers for the request 11038 * @returns {finesse.restservices.PhoneBook} 11039 * This PhoneBook object, to allow cascading 11040 */ 11041 add: function (newValues, handlers) { 11042 // this.isLoaded(); 11043 var contentBody = {}; 11044 11045 contentBody[this.getRestType()] = { 11046 "name": newValues.name, 11047 "type": newValues.type 11048 }; 11049 11050 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 11051 handlers = handlers || {}; 11052 11053 this.restRequest(this.getRestUrl(), { 11054 method: 'POST', 11055 success: this.createPostSuccessHandler(this, contentBody, handlers.success), 11056 error: handlers.error, 11057 content: contentBody 11058 }); 11059 11060 return this; // Allow cascading 11061 }, 11062 11063 /** 11064 * @private 11065 * Update a PhoneBook. 11066 * @param {Object} newValues 11067 * @param {String} newValues.name Name of PhoneBook 11068 * @param {String} newValues.type Type of PhoneBook 11069 * @param {finesse.interfaces.RequestHandlers} handlers 11070 * An object containing the handlers for the request 11071 * @returns {finesse.restservices.PhoneBook} 11072 * This PhoneBook object, to allow cascading 11073 */ 11074 update: function (newValues, handlers) { 11075 this.isLoaded(); 11076 var contentBody = {}; 11077 11078 contentBody[this.getRestType()] = { 11079 "uri": this.getId(), 11080 "name": newValues.name, 11081 "type": newValues.type 11082 }; 11083 11084 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 11085 handlers = handlers || {}; 11086 11087 this.restRequest(this.getRestUrl(), { 11088 method: 'PUT', 11089 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 11090 error: handlers.error, 11091 content: contentBody 11092 }); 11093 11094 return this; // Allow cascading 11095 }, 11096 11097 11098 /** 11099 * Delete a PhoneBook. 11100 * @param {finesse.interfaces.RequestHandlers} handlers 11101 * An object containing the handlers for the request 11102 * @returns {finesse.restservices.PhoneBook} 11103 * This PhoneBook object, to allow cascading 11104 */ 11105 "delete": function ( handlers) { 11106 this.isLoaded(); 11107 11108 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 11109 handlers = handlers || {}; 11110 11111 this.restRequest(this.getRestUrl(), { 11112 method: 'DELETE', 11113 success: this.createPutSuccessHandler(this, {}, handlers.success), 11114 error: handlers.error, 11115 content: undefined 11116 }); 11117 11118 return this; // Allow cascading 11119 } 11120 11121 11122 11123 }); 11124 11125 window.finesse = window.finesse || {}; 11126 window.finesse.restservices = window.finesse.restservices || {}; 11127 window.finesse.restservices.PhoneBook = PhoneBook; 11128 11129 return PhoneBook; 11130 }); 11131 11132 /** 11133 * JavaScript representation of the Finesse PhoneBooks collection 11134 * object which contains a list of PhoneBook objects. 11135 * 11136 * @requires finesse.clientservices.ClientServices 11137 * @requires Class 11138 * @requires finesse.FinesseBase 11139 * @requires finesse.restservices.RestBase 11140 * @requires finesse.restservices.Dialog 11141 * @requires finesse.restservices.RestCollectionBase 11142 */ 11143 /** @private */ 11144 define('restservices/PhoneBooks',[ 11145 'restservices/RestCollectionBase', 11146 'restservices/PhoneBook' 11147 ], 11148 function (RestCollectionBase, PhoneBook) { 11149 var PhoneBooks = RestCollectionBase.extend(/** @lends finesse.restservices.PhoneBooks.prototype */{ 11150 11151 /** 11152 * @class 11153 * JavaScript representation of a PhoneBooks collection object. 11154 * @augments finesse.restservices.RestCollectionBase 11155 * @constructs 11156 * @see finesse.restservices.PhoneBook 11157 * @see finesse.restservices.Contacts 11158 * @see finesse.restservices.Contact 11159 * @example 11160 * _phoneBooks = _user.getPhoneBooks( { 11161 * onCollectionAdd : _handlePhoneBookAdd, 11162 * onCollectionDelete : _handlePhoneBookDelete, 11163 * onLoad : _handlePhoneBooksLoaded 11164 * }); 11165 * 11166 * _phoneBookCollection = _phoneBooks.getCollection(); 11167 * for (var phoneBookId in _phoneBookCollection) { 11168 * if (_phoneBookCollection.hasOwnProperty(phoneBookId)) { 11169 * _phoneBook = _phoneBookCollection[phoneBookId]; 11170 * etc... 11171 * } 11172 * } 11173 */ 11174 _fakeConstuctor: function () { 11175 /* This is here to hide the real init constructor from the public docs */ 11176 }, 11177 11178 /** 11179 * @private 11180 * 11181 * @param {Object} options 11182 * An object with the following properties:<ul> 11183 * <li><b>id:</b> The id of the object being constructed</li> 11184 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 11185 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 11186 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 11187 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 11188 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 11189 * <li><b>status:</b> {Number} The HTTP status code returned</li> 11190 * <li><b>content:</b> {String} Raw string of response</li> 11191 * <li><b>object:</b> {Object} Parsed object of response</li> 11192 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 11193 * <li><b>errorType:</b> {String} Type of error that was caught</li> 11194 * <li><b>errorMessage:</b> {String} Message associated with error</li> 11195 * </ul></li> 11196 * </ul></li> 11197 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 11198 **/ 11199 init: function (options) { 11200 // Keep the REST response for PhoneBooks to check for 206 Partial Content. 11201 this.keepRestResponse = true; 11202 // Add in the Range header which is required for PhoneBooks API. 11203 this.extraHeaders = { "Range": "objects=1-1500" }; 11204 this._super(options); 11205 }, 11206 11207 /** 11208 * @private 11209 * Gets the REST class for the current object - this is the PhoneBooks class. 11210 * @returns {Object} The PhoneBooks class. 11211 */ 11212 getRestClass: function () { 11213 return PhoneBooks; 11214 }, 11215 11216 /** 11217 * @private 11218 * Gets the REST class for the objects that make up the collection. - this is the PhoneBook class. 11219 * @returns {Object} The PhoneBook class 11220 */ 11221 getRestItemClass: function () { 11222 return PhoneBook; 11223 }, 11224 11225 /** 11226 * @private 11227 * Gets the REST type for the current object - this is a "PhoneBooks". 11228 * @returns {String} The PhoneBooks string. 11229 */ 11230 getRestType: function () { 11231 return "PhoneBooks"; 11232 }, 11233 11234 /** 11235 * @private 11236 * Gets the REST type for the objects that make up the collection - this is "PhoneBooks". 11237 * @returns {String} The PhoneBook string. 11238 */ 11239 getRestItemType: function () { 11240 return "PhoneBook"; 11241 }, 11242 11243 /** 11244 * @private 11245 * Override default to indicates that the collection supports making 11246 * requests. 11247 */ 11248 supportsRequests: true, 11249 11250 /** 11251 * @private 11252 * Override default to indicates that the collection subscribes to its objects. 11253 */ 11254 supportsRestItemSubscriptions: false, 11255 11256 /** 11257 * @private 11258 * Retrieve the PhoneBooks. This call will re-query the server and refresh the collection. 11259 * 11260 * @returns {finesse.restservices.PhoneBooks} 11261 * This PhoneBooks object, to allow cascading. 11262 */ 11263 get: function () { 11264 // set loaded to false so it will rebuild the collection after the get 11265 this._loaded = false; 11266 // reset collection 11267 this._collection = {}; 11268 // perform get 11269 this._synchronize(); 11270 return this; 11271 } 11272 11273 }); 11274 11275 window.finesse = window.finesse || {}; 11276 window.finesse.restservices = window.finesse.restservices || {}; 11277 window.finesse.restservices.PhoneBooks = PhoneBooks; 11278 11279 return PhoneBooks; 11280 }); 11281 11282 /** 11283 * JavaScript representation of the Finesse WorkflowAction object. 11284 * 11285 * @requires finesse.clientservices.ClientServices 11286 * @requires Class 11287 * @requires finesse.FinesseBase 11288 * @requires finesse.restservices.RestBase 11289 */ 11290 11291 /*jslint browser: true, nomen: true, sloppy: true, forin: true */ 11292 /*global define,finesse */ 11293 11294 /** @private */ 11295 define('restservices/WorkflowAction',['restservices/RestBase'], function (RestBase) { 11296 11297 var WorkflowAction = RestBase.extend({ 11298 11299 _contacts: null, 11300 11301 actionTypes: [ 11302 { 11303 name: 'BROWSER_POP', 11304 params: [ 11305 { 11306 name: 'windowName', 11307 type: 'text' 11308 }, 11309 { 11310 name: 'path', 11311 type: 'systemVariableSingleLineEditor' 11312 } 11313 ] 11314 }, 11315 { 11316 name: 'HTTP_REQUEST', 11317 params: [ 11318 { 11319 name: 'method', 11320 type: 'dropdown', 11321 values: ['POST', 'PUT'] 11322 }, 11323 { 11324 name: 'location', 11325 type: 'dropdown', 11326 values: ['FINESSE', 'OTHER'] 11327 }, 11328 { 11329 name: 'contentType', 11330 type: 'text' 11331 }, 11332 { 11333 name: 'path', 11334 type: 'systemVariableSingleLineEditor' 11335 }, 11336 { 11337 name: 'body', 11338 type: 'systemVariableMultiLineEditor' 11339 } 11340 ] 11341 } 11342 // more action type definitions here 11343 ], 11344 11345 /** 11346 * @class 11347 * A WorkflowAction is an action (e.g. Browser Pop, Rest Request) defined in a 11348 * Workflow and triggered by a system event (Call Received, Call Ended, etc.). 11349 * 11350 * @augments finesse.restservices.RestBase 11351 * @see finesse.restservices.Workflow 11352 * @constructs 11353 */ 11354 _fakeConstuctor: function () { 11355 /* This is here to hide the real init constructor from the public docs */ 11356 }, 11357 11358 /** 11359 * @private 11360 * JavaScript representation of a WorkflowAction object. Also exposes 11361 * methods to operate on the object against the server. 11362 * 11363 * @param {Object} options 11364 * An object with the following properties:<ul> 11365 * <li><b>id:</b> The id of the object being constructed</li> 11366 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 11367 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 11368 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 11369 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 11370 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 11371 * <li><b>status:</b> {Number} The HTTP status code returned</li> 11372 * <li><b>content:</b> {String} Raw string of response</li> 11373 * <li><b>object:</b> {Object} Parsed object of response</li> 11374 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 11375 * <li><b>errorType:</b> {String} Type of error that was caught</li> 11376 * <li><b>errorMessage:</b> {String} Message associated with error</li> 11377 * </ul></li> 11378 * </ul></li> 11379 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 11380 **/ 11381 init: function (options) { 11382 this._super(options); 11383 }, 11384 11385 /** 11386 * @private 11387 * Gets the REST class for the current object - this is the WorkflowAction class. 11388 * @returns {Object} The WorkflowAction class. 11389 */ 11390 getRestClass: function () { 11391 return finesse.restservices.WorkflowAction; 11392 }, 11393 11394 /** 11395 * @private 11396 * Gets the REST type for the current object - this is a "WorkflowAction". 11397 * @returns {String} The WorkflowAction string. 11398 */ 11399 getRestType: function () { 11400 return "WorkflowAction"; 11401 }, 11402 11403 /** 11404 * @private 11405 * Override default to indicate that this object doesn't support making 11406 * requests. 11407 */ 11408 supportsRequests: false, 11409 11410 /** 11411 * @private 11412 * Override default to indicate that this object doesn't support subscriptions. 11413 */ 11414 supportsSubscriptions: false, 11415 11416 /** 11417 * Getter for the name. 11418 * @returns {String} The name. 11419 */ 11420 getName: function () { 11421 this.isLoaded(); 11422 return this.getData().name; 11423 }, 11424 11425 /** 11426 * Getter for the type flag. 11427 * @returns {String} The type. 11428 */ 11429 getType: function () { 11430 this.isLoaded(); 11431 return this.getData().type; 11432 }, 11433 11434 /** 11435 * @private 11436 * Getter for the Uri value. 11437 * @returns {String} The Uri. 11438 */ 11439 getUri: function () { 11440 this.isLoaded(); 11441 return this.getData().uri; 11442 }, 11443 11444 /** 11445 * @private 11446 * Getter for the handledBy value. 11447 * @returns {String} handledBy. 11448 */ 11449 getHandledBy: function () { 11450 this.isLoaded(); 11451 return this.getData().handledBy; 11452 }, 11453 11454 /** 11455 * Getter for the parameters. 11456 * @returns {Object} key = param name, value = param value 11457 */ 11458 getParams: function () { 11459 var map = {}, 11460 params = this.getData().params.Param, 11461 i, 11462 param; 11463 11464 for(i=0; i<params.length; i+=1){ 11465 param = params[i]; 11466 map[param.name] = param.value || ""; 11467 } 11468 11469 return map; 11470 }, 11471 11472 /** 11473 * Getter for the ActionVariables 11474 * @returns {Object} key = action variable name, value = Object{name, type, node, testValue} 11475 */ 11476 getActionVariables: function() { 11477 var map = {}, 11478 actionVariablesParent = this.getData().actionVariables, 11479 actionVariables, 11480 i, 11481 actionVariable; 11482 11483 if (actionVariablesParent === null || typeof(actionVariablesParent) === "undefined" || actionVariablesParent.length === 0){ 11484 return map; 11485 } 11486 actionVariables = actionVariablesParent.ActionVariable; 11487 11488 if(actionVariables.length > 0){ 11489 for(i=0; i<actionVariables.length; i+=1){ 11490 actionVariable = actionVariables[i]; 11491 // escape nulls to empty string 11492 actionVariable.name = actionVariable.name || ""; 11493 actionVariable.type = actionVariable.type || ""; 11494 actionVariable.node = actionVariable.node || ""; 11495 actionVariable.testValue = actionVariable.testValue || ""; 11496 map[actionVariable.name] = actionVariable; 11497 } 11498 } else { 11499 map[actionVariables.name] = actionVariables; 11500 } 11501 11502 return map; 11503 }, 11504 11505 /** @private */ 11506 createPutSuccessHandler: function(action, contentBody, successHandler){ 11507 return function (rsp) { 11508 // Update internal structure based on response. Here we 11509 // inject the contentBody from the PUT request into the 11510 // rsp.object element to mimic a GET as a way to take 11511 // advantage of the existing _processResponse method. 11512 rsp.object = contentBody; 11513 action._processResponse(rsp); 11514 11515 //Remove the injected WorkflowAction object before cascading response 11516 rsp.object = {}; 11517 11518 //cascade response back to consumer's response handler 11519 successHandler(rsp); 11520 }; 11521 }, 11522 11523 /** @private */ 11524 createPostSuccessHandler: function (action, contentBody, successHandler) { 11525 return function (rsp) { 11526 rsp.object = contentBody; 11527 action._processResponse(rsp); 11528 11529 //Remove the injected WorkflowAction object before cascading response 11530 rsp.object = {}; 11531 11532 //cascade response back to consumer's response handler 11533 successHandler(rsp); 11534 }; 11535 }, 11536 11537 /** 11538 * @private 11539 * Build params array out of all the values coming into add or update methods 11540 * paramMap is a map of params.. we need to translate it into an array of Param objects 11541 * where path and windowName are params for the BROWSER_POP type 11542 */ 11543 buildParamsForRest: function(paramMap){ 11544 var params = {"Param": []}, 11545 i; 11546 for(i in paramMap){ 11547 if(paramMap.hasOwnProperty(i)){ 11548 params.Param.push({name: i, value: paramMap[i]}); 11549 } 11550 } 11551 return params; 11552 }, 11553 11554 /** 11555 * @private 11556 * Build actionVariables array out of all the values coming into add or update methods 11557 * actionVariableMap is a map of actionVariables.. we need to translate it into an array of ActionVariable objects 11558 * where path and windowName are params for the BROWSER_POP type 11559 */ 11560 buildActionVariablesForRest: function(actionVariableMap){ 11561 var actionVariables = {"ActionVariable": []}, 11562 i, 11563 actionVariable; 11564 for(i in actionVariableMap){ 11565 if(actionVariableMap.hasOwnProperty(i)){ 11566 // {name: "callVariable1", type: "SYSTEM", node: "", testValue: "<blink>"} 11567 actionVariable = { 11568 "name": actionVariableMap[i].name, 11569 "type": actionVariableMap[i].type, 11570 "node": actionVariableMap[i].node, 11571 "testValue": actionVariableMap[i].testValue 11572 }; 11573 actionVariables.ActionVariable.push(actionVariable); 11574 } 11575 } 11576 return actionVariables; 11577 }, 11578 11579 /** 11580 * Add 11581 */ 11582 add: function (newValues, handlers) { 11583 var contentBody = {}; 11584 11585 contentBody[this.getRestType()] = { 11586 "name": newValues.name, 11587 "type": newValues.type, 11588 "handledBy": newValues.handledBy, 11589 "params": this.buildParamsForRest(newValues.params), 11590 "actionVariables": this.buildActionVariablesForRest(newValues.actionVariables) 11591 }; 11592 11593 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 11594 handlers = handlers || {}; 11595 11596 this.restRequest(this.getRestUrl(), { 11597 method: 'POST', 11598 success: this.createPostSuccessHandler(this, contentBody, handlers.success), 11599 error: handlers.error, 11600 content: contentBody 11601 }); 11602 11603 return this; // Allow cascading 11604 }, 11605 11606 /** 11607 * @private 11608 * Update 11609 */ 11610 update: function (newValues, handlers) { 11611 this.isLoaded(); 11612 var contentBody = {}; 11613 11614 contentBody[this.getRestType()] = { 11615 "uri": this.getId(), 11616 "name": newValues.name, 11617 "type": newValues.type, 11618 "handledBy": newValues.handledBy, 11619 "params": this.buildParamsForRest(newValues.params), 11620 "actionVariables": this.buildActionVariablesForRest(newValues.actionVariables) 11621 }; 11622 11623 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 11624 handlers = handlers || {}; 11625 11626 this.restRequest(this.getRestUrl(), { 11627 method: 'PUT', 11628 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 11629 error: handlers.error, 11630 content: contentBody 11631 }); 11632 11633 return this; // Allow cascading 11634 }, 11635 11636 11637 /** 11638 * @private 11639 * Delete 11640 */ 11641 "delete": function ( handlers) { 11642 this.isLoaded(); 11643 11644 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 11645 handlers = handlers || {}; 11646 11647 this.restRequest(this.getRestUrl(), { 11648 method: 'DELETE', 11649 success: this.createPutSuccessHandler(this, {}, handlers.success), 11650 error: handlers.error, 11651 content: undefined 11652 }); 11653 11654 return this; // Allow cascading 11655 } 11656 11657 11658 11659 }); 11660 11661 window.finesse = window.finesse || {}; 11662 window.finesse.restservices = window.finesse.restservices || {}; 11663 window.finesse.restservices.WorkflowAction = WorkflowAction; 11664 11665 return WorkflowAction; 11666 }); 11667 11668 /** 11669 * JavaScript representation of the Finesse WorkflowActions collection 11670 * object which contains a list of WorkflowAction objects. 11671 * 11672 * @requires finesse.clientservices.ClientServices 11673 * @requires Class 11674 * @requires finesse.FinesseBase 11675 * @requires finesse.restservices.RestBase 11676 * @requires finesse.restservices.Dialog 11677 * @requires finesse.restservices.RestCollectionBase 11678 */ 11679 11680 /** @private */ 11681 define('restservices/WorkflowActions',[ 11682 'restservices/RestCollectionBase', 11683 'restservices/RestBase', 11684 'restservices/WorkflowAction' 11685 ], 11686 function (RestCollectionBase, RestBase, WorkflowAction) { 11687 11688 var WorkflowActions = RestCollectionBase.extend({ 11689 11690 /** 11691 * @class 11692 * JavaScript representation of a WorkflowActions collection object. 11693 * @augments finesse.restservices.RestCollectionBase 11694 * @constructs 11695 * @see finesse.restservices.WorkflowAction 11696 * @see finesse.restservices.Workflow 11697 * @see finesse.restservices.Workflows 11698 * @example 11699 * _workflowActions = _user.getWorkflowActions( { 11700 * onCollectionAdd : _handleWorkflowActionAdd, 11701 * onCollectionDelete : _handleWorkflowActionDelete, 11702 * onLoad : _handleWorkflowActionsLoaded 11703 * }); 11704 */ 11705 _fakeConstuctor: function () { 11706 /* This is here to hide the real init constructor from the public docs */ 11707 }, 11708 11709 /** 11710 * @private 11711 * JavaScript representation of a WorkflowActions collection object. Also exposes 11712 * methods to operate on the object against the server. 11713 * 11714 * @param {Object} options 11715 * An object with the following properties:<ul> 11716 * <li><b>id:</b> The id of the object being constructed</li> 11717 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 11718 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 11719 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 11720 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 11721 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 11722 * <li><b>status:</b> {Number} The HTTP status code returned</li> 11723 * <li><b>content:</b> {String} Raw string of response</li> 11724 * <li><b>object:</b> {Object} Parsed object of response</li> 11725 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 11726 * <li><b>errorType:</b> {String} Type of error that was caught</li> 11727 * <li><b>errorMessage:</b> {String} Message associated with error</li> 11728 * </ul></li> 11729 * </ul></li> 11730 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 11731 **/ 11732 init: function (options) { 11733 this._super(options); 11734 }, 11735 11736 /** 11737 * @private 11738 * Gets the REST class for the current object - this is the WorkflowActions class. 11739 */ 11740 getRestClass: function () { 11741 return WorkflowActions; 11742 }, 11743 11744 /** 11745 * @private 11746 * Gets the REST class for the objects that make up the collection. - this 11747 * is the WorkflowAction class. 11748 */ 11749 getRestItemClass: function () { 11750 return WorkflowAction; 11751 }, 11752 11753 /** 11754 * @private 11755 * Gets the REST type for the current object - this is a "WorkflowActions". 11756 */ 11757 getRestType: function () { 11758 return "WorkflowActions"; 11759 }, 11760 11761 /** 11762 * @private 11763 * Gets the REST type for the objects that make up the collection - this is "WorkflowActions". 11764 */ 11765 getRestItemType: function () { 11766 return "WorkflowAction"; 11767 }, 11768 11769 /** 11770 * @private 11771 * Override default to indicates that the collection supports making 11772 * requests. 11773 */ 11774 supportsRequests: true, 11775 11776 /** 11777 * @private 11778 * Override default to indicates that the collection subscribes to its objects. 11779 */ 11780 supportsRestItemSubscriptions: false, 11781 11782 /** 11783 * @private 11784 * Retrieve the WorkflowActions. 11785 * 11786 * @returns {finesse.restservices.WorkflowActions} 11787 * This WorkflowActions object to allow cascading. 11788 */ 11789 get: function () { 11790 // set loaded to false so it will rebuild the collection after the get 11791 this._loaded = false; 11792 // reset collection 11793 this._collection = {}; 11794 // perform get 11795 this._synchronize(); 11796 return this; 11797 } 11798 }); 11799 11800 window.finesse = window.finesse || {}; 11801 window.finesse.restservices = window.finesse.restservices || {}; 11802 window.finesse.restservices.WorkflowActions = WorkflowActions; 11803 11804 return WorkflowActions; 11805 }); 11806 11807 /** 11808 * JavaScript representation of the Finesse Workflow object. 11809 * 11810 * @requires finesse.clientservices.ClientServices 11811 * @requires Class 11812 * @requires finesse.FinesseBase 11813 * @requires finesse.restservices.RestBase 11814 */ 11815 11816 /*jslint browser: true, nomen: true, sloppy: true, forin: true */ 11817 /*global define,finesse */ 11818 11819 /** @private */ 11820 define('restservices/Workflow',[ 11821 'restservices/RestBase', 11822 'restservices/WorkflowActions' 11823 ], 11824 function (RestBase, WorkflowActions) { 11825 11826 var Workflow = RestBase.extend({ 11827 11828 /** 11829 * @class 11830 * JavaScript representation of a Workflow object. Also exposes 11831 * methods to operate on the object against the server. 11832 * 11833 * @param {Object} options 11834 * An object with the following properties:<ul> 11835 * <li><b>id:</b> The id of the object being constructed</li> 11836 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 11837 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 11838 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 11839 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 11840 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 11841 * <li><b>status:</b> {Number} The HTTP status code returned</li> 11842 * <li><b>content:</b> {String} Raw string of response</li> 11843 * <li><b>object:</b> {Object} Parsed object of response</li> 11844 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 11845 * <li><b>errorType:</b> {String} Type of error that was caught</li> 11846 * <li><b>errorMessage:</b> {String} Message associated with error</li> 11847 * </ul></li> 11848 * </ul></li> 11849 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 11850 * @constructs 11851 **/ 11852 init: function (options) { 11853 this._super(options); 11854 }, 11855 11856 /** 11857 * @private 11858 * Gets the REST class for the current object - this is the Workflow class. 11859 * @returns {Object} The Workflow class. 11860 */ 11861 getRestClass: function () { 11862 return Workflow; 11863 }, 11864 11865 /** 11866 * @private 11867 * Gets the REST type for the current object - this is a "Workflow". 11868 * @returns {String} The Workflow string. 11869 */ 11870 getRestType: function () { 11871 return "Workflow"; 11872 }, 11873 11874 /** 11875 * @private 11876 * Override default to indicate that this object doesn't support making 11877 * requests. 11878 */ 11879 supportsRequests: false, 11880 11881 /** 11882 * @private 11883 * Override default to indicate that this object doesn't support subscriptions. 11884 */ 11885 supportsSubscriptions: false, 11886 11887 /** 11888 * @private 11889 * Getter for the Uri value. 11890 * @returns {String} The Uri. 11891 */ 11892 getUri: function () { 11893 this.isLoaded(); 11894 return this.getData().uri; 11895 }, 11896 11897 /** 11898 * Getter for the name. 11899 * @returns {String} The name. 11900 */ 11901 getName: function () { 11902 this.isLoaded(); 11903 return this.getData().name; 11904 }, 11905 11906 /** 11907 * Getter for the description. 11908 * @returns {String} The description. 11909 */ 11910 getDescription: function () { 11911 this.isLoaded(); 11912 return this.getData().description; 11913 }, 11914 11915 /** 11916 * Getter for the media. 11917 * @returns {String} The media. 11918 */ 11919 getMedia: function () { 11920 this.isLoaded(); 11921 return this.getData().media; 11922 }, 11923 11924 /** 11925 * Getter for the trigger set. 11926 * @returns {String} The trigger set. 11927 */ 11928 getTriggerSet: function () { 11929 this.isLoaded(); 11930 return this.getData().TriggerSet; 11931 }, 11932 11933 /** 11934 * Getter for the condition set. 11935 * @returns {String} The condition set. 11936 */ 11937 getConditionSet: function () { 11938 this.isLoaded(); 11939 return this.getData().ConditionSet; 11940 }, 11941 11942 /** 11943 * Getter for the assigned workflowActions. 11944 * @returns {String} The workflowActions object. 11945 */ 11946 getWorkflowActions: function () { 11947 this.isLoaded(); 11948 var workflowActions = this.getData().workflowActions; 11949 if (workflowActions === null) { 11950 workflowActions = ""; 11951 } 11952 return workflowActions; 11953 }, 11954 11955 createPutSuccessHandler: function (workflow, contentBody, successHandler) { 11956 return function (rsp) { 11957 // Update internal structure based on response. Here we 11958 // inject the contentBody from the PUT request into the 11959 // rsp.object element to mimic a GET as a way to take 11960 // advantage of the existing _processResponse method. 11961 rsp.object = contentBody; 11962 workflow._processResponse(rsp); 11963 11964 //Remove the injected Workflow object before cascading response 11965 rsp.object = {}; 11966 11967 //cascade response back to consumer's response handler 11968 successHandler(rsp); 11969 }; 11970 }, 11971 11972 createPostSuccessHandler: function (workflow, contentBody, successHandler) { 11973 return function (rsp) { 11974 rsp.object = contentBody; 11975 workflow._processResponse(rsp); 11976 11977 //Remove the injected Workflow object before cascading response 11978 rsp.object = {}; 11979 11980 //cascade response back to consumer's response handler 11981 successHandler(rsp); 11982 }; 11983 }, 11984 11985 /** 11986 * @private 11987 * Add 11988 */ 11989 add: function (newValues, handlers) { 11990 // this.isLoaded(); 11991 var contentBody = {}; 11992 11993 contentBody[this.getRestType()] = { 11994 "media": newValues.media, 11995 "name": newValues.name, 11996 "description": newValues.description, 11997 "TriggerSet" : newValues.TriggerSet, 11998 "ConditionSet" : newValues.ConditionSet, 11999 "workflowActions" : newValues.workflowActions 12000 }; 12001 12002 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 12003 handlers = handlers || {}; 12004 12005 this.restRequest(this.getRestUrl(), { 12006 method: 'POST', 12007 success: this.createPostSuccessHandler(this, contentBody, handlers.success), 12008 error: handlers.error, 12009 content: contentBody 12010 }); 12011 12012 return this; // Allow cascading 12013 }, 12014 12015 /** 12016 * @private 12017 * Update 12018 */ 12019 update: function (newValues, handlers) { 12020 this.isLoaded(); 12021 var contentBody = {}; 12022 12023 contentBody[this.getRestType()] = { 12024 "uri": this.getId(), 12025 "media": this.getMedia(), 12026 "name": newValues.name, 12027 "description": newValues.description, 12028 "TriggerSet" : newValues.TriggerSet, 12029 "ConditionSet" : newValues.ConditionSet, 12030 "workflowActions" : newValues.workflowActions 12031 }; 12032 12033 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 12034 handlers = handlers || {}; 12035 12036 this.restRequest(this.getRestUrl(), { 12037 method: 'PUT', 12038 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 12039 error: handlers.error, 12040 content: contentBody 12041 }); 12042 12043 return this; // Allow cascading 12044 }, 12045 12046 12047 /** 12048 * @private 12049 * Delete 12050 */ 12051 "delete": function (handlers) { 12052 this.isLoaded(); 12053 12054 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 12055 handlers = handlers || {}; 12056 12057 this.restRequest(this.getRestUrl(), { 12058 method: 'DELETE', 12059 success: this.createPutSuccessHandler(this, {}, handlers.success), 12060 error: handlers.error, 12061 content: undefined 12062 }); 12063 12064 return this; // Allow cascading 12065 } 12066 12067 12068 12069 }); 12070 12071 window.finesse = window.finesse || {}; 12072 window.finesse.restservices = window.finesse.restservices || {}; 12073 window.finesse.restservices.Workflow = Workflow; 12074 12075 return Workflow; 12076 }); 12077 12078 /** 12079 * JavaScript representation of the Finesse workflows collection 12080 * object which contains a list of workflow objects. 12081 * 12082 * @requires finesse.clientservices.ClientServices 12083 * @requires Class 12084 * @requires finesse.FinesseBase 12085 * @requires finesse.restservices.RestBase 12086 * @requires finesse.restservices.Dialog 12087 * @requires finesse.restservices.RestCollectionBase 12088 */ 12089 12090 /** @private */ 12091 define('restservices/Workflows',[ 12092 'restservices/RestCollectionBase', 12093 'restservices/RestBase', 12094 'restservices/Workflow' 12095 ], 12096 function (RestCollectionBase, RestBase, Workflow) { 12097 12098 var Workflows = RestCollectionBase.extend({ 12099 12100 /** 12101 * @class 12102 * JavaScript representation of a workflows collection object. Also exposes 12103 * methods to operate on the object against the server. 12104 * 12105 * @param {Object} options 12106 * An object with the following properties:<ul> 12107 * <li><b>id:</b> The id of the object being constructed</li> 12108 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 12109 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 12110 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 12111 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 12112 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 12113 * <li><b>status:</b> {Number} The HTTP status code returned</li> 12114 * <li><b>content:</b> {String} Raw string of response</li> 12115 * <li><b>object:</b> {Object} Parsed object of response</li> 12116 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 12117 * <li><b>errorType:</b> {String} Type of error that was caught</li> 12118 * <li><b>errorMessage:</b> {String} Message associated with error</li> 12119 * </ul></li> 12120 * </ul></li> 12121 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 12122 * @constructs 12123 **/ 12124 init: function (options) { 12125 this._super(options); 12126 }, 12127 12128 /** 12129 * @private 12130 * Gets the REST class for the current object - this is the workflows class. 12131 */ 12132 getRestClass: function () { 12133 return Workflows; 12134 }, 12135 12136 /** 12137 * @private 12138 * Gets the REST class for the objects that make up the collection. - this 12139 * is the workflow class. 12140 */ 12141 getRestItemClass: function () { 12142 return Workflow; 12143 }, 12144 12145 /** 12146 * @private 12147 * Gets the REST type for the current object - this is a "workflows". 12148 */ 12149 getRestType: function () { 12150 return "Workflows"; 12151 }, 12152 12153 /** 12154 * @private 12155 * Gets the REST type for the objects that make up the collection - this is "workflows". 12156 */ 12157 getRestItemType: function () { 12158 return "Workflow"; 12159 }, 12160 12161 /** 12162 * @private 12163 * Override default to indicates that the collection supports making requests. 12164 */ 12165 supportsRequests: true, 12166 12167 /** 12168 * @private 12169 * Override default to indicates that the collection does not subscribe to its objects. 12170 */ 12171 supportsRestItemSubscriptions: false, 12172 12173 /** 12174 * @private 12175 * Retrieve the workflows. This call will re-query the server and refresh the collection. 12176 * 12177 * @returns {finesse.restservices.workflows} 12178 * This workflows object to allow cascading. 12179 */ 12180 get: function () { 12181 // set loaded to false so it will rebuild the collection after the get 12182 this._loaded = false; 12183 // reset collection 12184 this._collection = {}; 12185 // perform get 12186 this._synchronize(); 12187 return this; 12188 } 12189 }); 12190 12191 window.finesse = window.finesse || {}; 12192 window.finesse.restservices = window.finesse.restservices || {}; 12193 window.finesse.restservices.Workflows = Workflows; 12194 12195 return Workflows; 12196 }); 12197 12198 /** 12199 * JavaScript representation of the Finesse MediaPropertiesLayout object for the Admin webapp. 12200 * @requires finesse.clientservices.ClientServices 12201 * @requires Class 12202 * @requires finesse.FinesseBase 12203 * @requires finesse.restservices.RestBase 12204 */ 12205 12206 /** The following comment is to prevent jslint errors about 12207 * using variables before they are defined. 12208 */ 12209 /*global finesse*/ 12210 12211 /** 12212 * @class 12213 * JavaScript representation of a MediaPropertiesLayout object for the Admin webapp. Also exposes 12214 * methods to operate on the object against the server. 12215 * 12216 * @constructor 12217 * @param {String} id 12218 * Not required... 12219 * @param {Object} callbacks 12220 * An object containing callbacks for instantiation and runtime 12221 * @param {Function} callbacks.onLoad(this) 12222 * Callback to invoke upon successful instantiation, passes in MediaPropertiesLayout object 12223 * @param {Function} callbacks.onLoadError(rsp) 12224 * Callback to invoke on instantiation REST request error 12225 * as passed by finesse.clientservices.ClientServices.ajax() 12226 * { 12227 * status: {Number} The HTTP status code returned 12228 * content: {String} Raw string of response 12229 * object: {Object} Parsed object of response 12230 * error: {Object} Wrapped exception that was caught 12231 * error.errorType: {String} Type of error that was caught 12232 * error.errorMessage: {String} Message associated with error 12233 * } 12234 * @param {Function} callbacks.onChange(this) 12235 * Callback to invoke upon successful update, passes in MediaPropertiesLayout object 12236 * @param {Function} callbacks.onError(rsp) 12237 * Callback to invoke on update error (refresh or event) 12238 * as passed by finesse.clientservices.ClientServices.ajax() 12239 * { 12240 * status: {Number} The HTTP status code returned 12241 * content: {String} Raw string of response 12242 * object: {Object} Parsed object of response 12243 * error: {Object} Wrapped exception that was caught 12244 * error.errorType: {String} Type of error that was caught 12245 * error.errorMessage: {String} Message associated with error 12246 * } 12247 */ 12248 12249 /** @private */ 12250 define('restservices/MediaPropertiesLayout',['restservices/RestBase'], function (RestBase) { 12251 var MediaPropertiesLayout = RestBase.extend(/** @lends finesse.restservices.MediaPropertiesLayout.prototype */{ 12252 12253 /** 12254 * @class 12255 * The MediaPropertiesLayout handles which call variables are associated with Dialogs. 12256 * 12257 * @augments finesse.restservices.RestBase 12258 * @see finesse.restservices.Dialog#getMediaProperties 12259 * @see finesse.restservices.User#getMediaPropertiesLayout 12260 * @constructs 12261 */ 12262 _fakeConstuctor: function () { 12263 /* This is here to hide the real init constructor from the public docs */ 12264 }, 12265 12266 /** 12267 * @private 12268 * JavaScript representation of a MediaPropertiesLayout object. Also exposes 12269 * methods to operate on the object against the server. 12270 * 12271 * @param {Object} options 12272 * An object with the following properties:<ul> 12273 * <li><b>id:</b> The id of the object being constructed</li> 12274 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 12275 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 12276 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 12277 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 12278 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 12279 * <li><b>status:</b> {Number} The HTTP status code returned</li> 12280 * <li><b>content:</b> {String} Raw string of response</li> 12281 * <li><b>object:</b> {Object} Parsed object of response</li> 12282 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 12283 * <li><b>errorType:</b> {String} Type of error that was caught</li> 12284 * <li><b>errorMessage:</b> {String} Message associated with error</li> 12285 * </ul></li> 12286 * </ul></li> 12287 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 12288 **/ 12289 init: function (options) { 12290 this._super(options); 12291 }, 12292 12293 /** 12294 * @private 12295 * Gets the REST class for the current object - this is the MediaPropertiesLayout object. 12296 * @returns {Object} The MediaPropertiesLayout constructor. 12297 */ 12298 getRestClass: function () { 12299 return MediaPropertiesLayout; 12300 }, 12301 12302 /** 12303 * @private 12304 * Gets the REST type for the current object - this is a "MediaPropertiesLayout". 12305 * @returns {String} The MediaPropertiesLayout string 12306 */ 12307 getRestType: function () { 12308 return "MediaPropertiesLayout"; 12309 }, 12310 12311 /** 12312 * @private 12313 * Returns whether this object supports subscriptions 12314 */ 12315 supportsSubscriptions: false, 12316 12317 /** 12318 * Getter for the name. 12319 * @returns {String} The name. 12320 */ 12321 getName: function () { 12322 this.isLoaded(); 12323 return this._data.name; 12324 }, 12325 12326 /** 12327 * Getter for the description. 12328 * @returns {String} The description. 12329 */ 12330 getDescription: function () { 12331 this.isLoaded(); 12332 return this._data.description || ""; 12333 }, 12334 12335 /** 12336 * Getter for the layout type (should be DEFAULT or CUSTOM). 12337 * @returns {String} The layout type. 12338 */ 12339 getType: function () { 12340 this.isLoaded(); 12341 return this._data.type || ""; 12342 }, 12343 12344 /** 12345 * Retrieve the media properties layout. This call will re-query the server and refresh the layout object. 12346 * @returns {finesse.restservices.MediaPropertiesLayout} 12347 * This MediaPropertiesLayout object to allow cascading 12348 */ 12349 get: function () { 12350 this._synchronize(); 12351 12352 return this; //Allow cascading 12353 }, 12354 12355 /** 12356 * Gets the data for this object. 12357 * 12358 * Performs safe conversion from raw API data to ensure that the returned layout object 12359 * always has a header with correct entry fields, and exactly two columns with lists of entries. 12360 * 12361 * @returns {finesse.restservices.MediaPropertiesLayout.Object} Data in columns (unless only one defined). 12362 */ 12363 getData: function () { 12364 12365 var layout = this._data, result, _addColumnData; 12366 12367 result = this.getEmptyData(); 12368 result.name = layout.name; 12369 result.description = layout.description; 12370 result.type = layout.type; 12371 12372 /** 12373 * @private 12374 */ 12375 _addColumnData = function (entryData, colIndex) { 12376 12377 if (!entryData) { 12378 //If there's no entry data at all, rewrite entryData to be an empty collection of entries 12379 entryData = {}; 12380 } else if (entryData.mediaProperty) { 12381 //If entryData contains the keys for a single entry rather than being a collection of entries, 12382 //rewrite it to be a collection containing a single entry 12383 entryData = { "": entryData }; 12384 } 12385 12386 //Add each of the entries in the list to the column 12387 jQuery.each(entryData, function (i, entryData) { 12388 12389 //If the entry has no displayName specified, explicitly set it to the empty string 12390 if (!entryData.displayName) { 12391 entryData.displayName = ""; 12392 } 12393 12394 result.columns[colIndex].push(entryData); 12395 12396 }); 12397 12398 }; 12399 12400 //The header should only contain a single entry 12401 if (layout.header && layout.header.entry) { 12402 12403 //If the entry has no displayName specified, explicitly set it to the empty string 12404 if (!layout.header.entry.displayName) { 12405 layout.header.entry.displayName = ""; 12406 } 12407 12408 result.header = layout.header.entry; 12409 12410 } else { 12411 12412 throw "MediaPropertiesLayout.getData() - Header does not contain an entry"; 12413 12414 } 12415 12416 //If the column object contains an entry object that wasn't part of a list of entries, 12417 //it must be a single right-hand entry object (left-hand entry object would be part of a list.) 12418 //Force the entry object to be the 2nd element in an otherwise-empty list. 12419 if (layout.column && layout.column.entry) { 12420 layout.column = [ 12421 null, 12422 { "entry": layout.column.entry } 12423 ]; 12424 } 12425 12426 if (layout.column && layout.column.length > 0 && layout.column.length <= 2) { 12427 12428 //Render left column entries 12429 if (layout.column[0] && layout.column[0].entry) { 12430 _addColumnData(layout.column[0].entry, 0); 12431 } 12432 12433 //Render right column entries 12434 if (layout.column[1] && layout.column[1].entry) { 12435 _addColumnData(layout.column[1].entry, 1); 12436 } 12437 12438 } 12439 12440 return result; 12441 12442 }, 12443 12444 /** 12445 * @private 12446 * Empty/template version of getData(). 12447 * 12448 * Used by getData(), and by callers of getData() in error cases. 12449 * @returns Empty/template version of getData() 12450 */ 12451 getEmptyData: function () { 12452 12453 return { 12454 header : { 12455 displayName: null, 12456 mediaProperty: null 12457 }, 12458 columns : [[], []] 12459 }; 12460 12461 }, 12462 12463 /** 12464 * Update the layout of this MediaPropertiesLayout 12465 * @param {Object} layout 12466 * The object representation of the layout you are setting 12467 * @param {finesse.interfaces.RequestHandlers} handlers 12468 * An object containing the handlers for the request 12469 * @returns {finesse.restservices.MediaPropertiesLayout} 12470 * This MediaPropertiesLayout object to allow cascading 12471 * @private 12472 */ 12473 update: function (newLayoutObject, handlers) { 12474 var contentBody = {}; 12475 12476 // Make sure type is kept the same 12477 newLayoutObject.type = this.getType(); 12478 12479 contentBody[this.getRestType()] = newLayoutObject; 12480 12481 //Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 12482 handlers = handlers || {}; 12483 12484 this.restRequest(this.getRestUrl(), { 12485 method: 'PUT', 12486 success: handlers.success, 12487 error: handlers.error, 12488 content: contentBody 12489 }); 12490 12491 return this; // Allow cascading 12492 }, 12493 12494 /** 12495 * Create a new MediaPropertiesLayout object with the layout passed in 12496 * @param {Object} layout 12497 * The object representation of the layout you are creating 12498 * @param {finesse.interfaces.RequestHandlers} handlers 12499 * An object containing the handlers for the request 12500 * @returns {finesse.restservices.MediaPropertiesLayout} 12501 * This MediaPropertiesLayout object to allow cascading 12502 * @private 12503 */ 12504 add: function (layout, handlers) { 12505 var contentBody = {}; 12506 12507 contentBody[this.getRestType()] = layout; 12508 12509 handlers = handlers || {}; 12510 12511 this.restRequest(this.getRestUrl(), { 12512 method: 'POST', 12513 success: handlers.success, 12514 error: handlers.error, 12515 content: contentBody 12516 }); 12517 12518 return this; // Allow cascading 12519 }, 12520 12521 /** 12522 * Delete this MediaPropertiesLayout 12523 * @param {finesse.interfaces.RequestHandlers} handlers 12524 * An object containing the handlers for the request 12525 * @returns {finesse.restservices.MediaPropertiesLayout} 12526 * This MediaPropertiesLayout object to allow cascading 12527 * @private 12528 */ 12529 "delete": function (handlers) { 12530 handlers = handlers || {}; 12531 12532 this.restRequest(this.getRestUrl(), { 12533 method: 'DELETE', 12534 success: handlers.success, 12535 error: handlers.error, 12536 content: undefined 12537 }); 12538 12539 return this; // Allow cascading 12540 } 12541 12542 }); 12543 12544 MediaPropertiesLayout.Object = /** @lends finesse.restservices.MediaPropertiesLayout.Object.prototype */ { 12545 /** 12546 * @class Format of MediaPropertiesLayout Object.<br> 12547 * Object { <ul> 12548 * <li>header : { <ul> 12549 * <li>dispayName {String} 12550 * <li>mediaProperty {String}</ul>} 12551 * <li>columns : { <ul> 12552 * <li>[ [] , [] ] 12553 * </ul> 12554 * where column arrays consists of the same Object format as header.<br> 12555 * }</ul> 12556 * }<br> 12557 * @constructs 12558 */ 12559 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 12560 12561 }; 12562 12563 window.finesse = window.finesse || {}; 12564 window.finesse.restservices = window.finesse.restservices || {}; 12565 window.finesse.restservices.MediaPropertiesLayout = MediaPropertiesLayout; 12566 12567 return MediaPropertiesLayout; 12568 }); 12569 12570 /** 12571 * JavaScript representation of the Finesse MediaPropertiesLayout object for a User 12572 * 12573 * @requires MediaPropertiesLayout 12574 * @requires ClientServices 12575 * @requires finesse.FinesseBase 12576 * @requires finesse.restservices.RestBase 12577 */ 12578 12579 /** The following comment is to prevent jslint errors about 12580 * using variables before they are defined. 12581 */ 12582 /*global finesse*/ 12583 12584 /** @private */ 12585 define('restservices/UserMediaPropertiesLayout',['restservices/MediaPropertiesLayout'], function (MediaPropertiesLayout) { 12586 var UserMediaPropertiesLayout = MediaPropertiesLayout.extend(/** @lends finesse.restservices.UserMediaPropertiesLayout.prototype */{ 12587 12588 /** 12589 * @class 12590 * JavaScript representation of a UserMediaPropertiesLayout collection object. Also exposes 12591 * methods to operate on the object against the server. 12592 * 12593 * @param {Object} options 12594 * An object with the following properties:<ul> 12595 * <li><b>id:</b> The id of the object being constructed</li> 12596 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 12597 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 12598 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 12599 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 12600 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 12601 * <li><b>status:</b> {Number} The HTTP status code returned</li> 12602 * <li><b>content:</b> {String} Raw string of response</li> 12603 * <li><b>object:</b> {Object} Parsed object of response</li> 12604 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 12605 * <li><b>errorType:</b> {String} Type of error that was caught</li> 12606 * <li><b>errorMessage:</b> {String} Message associated with error</li> 12607 * </ul></li> 12608 * </ul></li> 12609 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 12610 * @constructs 12611 **/ 12612 init: function (options) { 12613 this._super(options); 12614 }, 12615 12616 /** 12617 * @private 12618 * Gets the REST class for the current object - this is the UserMediaPropertiesLayout class. 12619 */ 12620 getRestClass: function () { 12621 return UserMediaPropertiesLayout; 12622 }, 12623 12624 /** 12625 * Overrides the parent class. Returns the url for the UserMediaPropertiesLayout resource 12626 */ 12627 getRestUrl: function () { 12628 return ("/finesse/api/User/" + this.getId() + "/" + this.getRestType()); 12629 }, 12630 12631 /** 12632 * @private 12633 * Override to throw an error because we cannot do an update on the User's 12634 * MediaPropertiesLayout node 12635 */ 12636 update: function (layout, handlers) { 12637 throw new Error("update(): Cannot update layout for User's MediaPropertiesLayout"); 12638 }, 12639 12640 /** 12641 * @private 12642 * Override to throw an error because we cannot create a new layout on the User's 12643 * MediaPropertiesLayout node 12644 */ 12645 add: function (layout, handlers) { 12646 throw new Error("add(): Cannot create a new layout for User's MediaPropertiesLayout"); 12647 }, 12648 12649 /** 12650 * @private 12651 * Override to throw an error because we cannot delete the layout on the User's 12652 * MediaPropertiesLayout node 12653 */ 12654 "delete": function (layout, handlers) { 12655 throw new Error("delete(): Cannot delete the layout for User's MediaPropertiesLayout"); 12656 } 12657 12658 }); 12659 12660 window.finesse = window.finesse || {}; 12661 window.finesse.restservices = window.finesse.restservices || {}; 12662 window.finesse.restservices.UserMediaPropertiesLayout = UserMediaPropertiesLayout; 12663 12664 return UserMediaPropertiesLayout; 12665 }); 12666 12667 /** 12668 * JavaScript representation of the Finesse mediaPropertiesLayouts collection 12669 * object which contains a list of mediaPropertiesLayout objects. 12670 * 12671 * @requires finesse.clientservices.ClientServices 12672 * @requires Class 12673 * @requires finesse.FinesseBase 12674 * @requires finesse.restservices.RestBase 12675 * @requires finesse.restservices.Dialog 12676 * @requires finesse.restservices.RestCollectionBase 12677 */ 12678 12679 /** @private */ 12680 define('restservices/MediaPropertiesLayouts',[ 12681 'restservices/RestCollectionBase', 12682 'restservices/RestBase', 12683 'restservices/MediaPropertiesLayout' 12684 ], 12685 function (RestCollectionBase, RestBase, MediaPropertiesLayout) { 12686 12687 var MediaPropertiesLayouts = RestCollectionBase.extend({ 12688 12689 /** 12690 * @class 12691 * JavaScript representation of a mediaPropertiesLayouts collection object. Also exposes 12692 * methods to operate on the object against the server. 12693 * 12694 * @param {Object} options 12695 * An object with the following properties:<ul> 12696 * <li><b>id:</b> The id of the object being constructed</li> 12697 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 12698 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 12699 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 12700 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 12701 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 12702 * <li><b>status:</b> {Number} The HTTP status code returned</li> 12703 * <li><b>content:</b> {String} Raw string of response</li> 12704 * <li><b>object:</b> {Object} Parsed object of response</li> 12705 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 12706 * <li><b>errorType:</b> {String} Type of error that was caught</li> 12707 * <li><b>errorMessage:</b> {String} Message associated with error</li> 12708 * </ul></li> 12709 * </ul></li> 12710 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 12711 * @constructs 12712 **/ 12713 init: function (options) { 12714 this._super(options); 12715 }, 12716 12717 /** 12718 * @private 12719 * Gets the REST class for the current object - this is the mediaPropertiesLayouts class. 12720 */ 12721 getRestClass: function () { 12722 return MediaPropertiesLayouts; 12723 }, 12724 12725 /** 12726 * @private 12727 * Gets the REST class for the objects that make up the collection. - this 12728 * is the mediaPropertiesLayout class. 12729 */ 12730 getRestItemClass: function () { 12731 return MediaPropertiesLayout; 12732 }, 12733 12734 /** 12735 * @private 12736 * Gets the REST type for the current object - this is a "mediaPropertiesLayouts". 12737 */ 12738 getRestType: function () { 12739 return "MediaPropertiesLayouts"; 12740 }, 12741 12742 /** 12743 * @private 12744 * Gets the REST type for the objects that make up the collection - this is "mediaPropertiesLayouts". 12745 */ 12746 getRestItemType: function () { 12747 return "MediaPropertiesLayout"; 12748 }, 12749 12750 /** 12751 * @private 12752 * Override default to indicates that the collection supports making requests. 12753 */ 12754 supportsRequests: true, 12755 12756 /** 12757 * @private 12758 * Override default to indicates that the collection does not subscribe to its objects. 12759 */ 12760 supportsRestItemSubscriptions: false, 12761 12762 /** 12763 * @private 12764 * Retrieve the MediaPropertiesLayouts. This call will re-query the server and refresh the collection. 12765 * 12766 * @returns {finesse.restservices.MediaPropertiesLayouts} 12767 * This MediaPropertiesLayouts object to allow cascading. 12768 */ 12769 get: function () { 12770 // set loaded to false so it will rebuild the collection after the get 12771 this._loaded = false; 12772 // reset collection 12773 this._collection = {}; 12774 // perform get 12775 this._synchronize(); 12776 return this; 12777 } 12778 }); 12779 12780 window.finesse = window.finesse || {}; 12781 window.finesse.restservices = window.finesse.restservices || {}; 12782 window.finesse.restservices.MediaPropertiesLayouts = MediaPropertiesLayouts; 12783 12784 return MediaPropertiesLayouts; 12785 }); 12786 12787 /** 12788 * JavaScript representation of the Finesse MediaPropertiesLayout object for a User 12789 * 12790 * @requires MediaPropertiesLayout 12791 * @requires ClientServices 12792 * @requires finesse.FinesseBase 12793 * @requires finesse.restservices.RestBase 12794 */ 12795 12796 /** The following comment is to prevent jslint errors about 12797 * using variables before they are defined. 12798 */ 12799 /*global finesse*/ 12800 12801 /** @private */ 12802 define('restservices/UserMediaPropertiesLayouts',[ 12803 'restservices/MediaPropertiesLayouts', 12804 'restservices/UserMediaPropertiesLayout' 12805 ], 12806 function (MediaPropertiesLayouts, UserMediaPropertiesLayout) { 12807 var UserMediaPropertiesLayouts = MediaPropertiesLayouts.extend(/** @lends finesse.restservices.UserMediaPropertiesLayouts.prototype */{ 12808 12809 /** 12810 * @class 12811 * JavaScript representation of a UserMediaPropertiesLayouts collection object. Also exposes 12812 * methods to operate on the object against the server. 12813 * 12814 * @param {Object} options 12815 * An object with the following properties:<ul> 12816 * <li><b>id:</b> The id of the object being constructed</li> 12817 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 12818 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 12819 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 12820 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 12821 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 12822 * <li><b>status:</b> {Number} The HTTP status code returned</li> 12823 * <li><b>content:</b> {String} Raw string of response</li> 12824 * <li><b>object:</b> {Object} Parsed object of response</li> 12825 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 12826 * <li><b>errorType:</b> {String} Type of error that was caught</li> 12827 * <li><b>errorMessage:</b> {String} Message associated with error</li> 12828 * </ul></li> 12829 * </ul></li> 12830 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 12831 * @constructs 12832 **/ 12833 init: function (options) { 12834 this._super(options); 12835 }, 12836 12837 /** 12838 * @private 12839 * Gets the REST class for the current object - this is the UserMediaPropertiesLayouts class. 12840 */ 12841 getRestClass: function () { 12842 return UserMediaPropertiesLayouts; 12843 }, 12844 12845 /** 12846 * @private 12847 * Gets the REST class for the objects that make up the collection. - this 12848 * is the UserMediaPropertiesLayout class. 12849 */ 12850 getRestItemClass: function() { 12851 return UserMediaPropertiesLayout; 12852 } 12853 }); 12854 12855 window.finesse = window.finesse || {}; 12856 window.finesse.restservices = window.finesse.restservices || {}; 12857 window.finesse.restservices.UserMediaPropertiesLayouts = UserMediaPropertiesLayouts; 12858 12859 return UserMediaPropertiesLayouts; 12860 }); 12861 12862 /** 12863 * JavaScript representation of the Finesse Dialog object for non-voice media. 12864 * 12865 * @requires finesse.restservices.DialogBase 12866 */ 12867 12868 /** @private */ 12869 define('restservices/MediaDialog',[ 12870 'restservices/DialogBase' 12871 ], 12872 function (DialogBase) { 12873 var MediaDialog = DialogBase.extend(/** @lends finesse.restservices.MediaDialog.prototype */{ 12874 12875 /** 12876 * @private 12877 * 12878 * Support requests so that applications can refresh non-voice dialogs when the media channel that the 12879 * dialog belongs to is interrupted. An event is not sent to update a dialog's actions when the media is 12880 * interrupted so a refresh is required so that the application can get an updated set of actions. 12881 */ 12882 supportsRequests: true, 12883 12884 /** 12885 * @class 12886 * A MediaDialog is an attempted connection between or among multiple participants, 12887 * for example, a chat or email. 12888 * 12889 * @augments finesse.restservices.DialogBase 12890 * @constructs 12891 */ 12892 _fakeConstuctor: function () { 12893 /* This is here to hide the real init constructor from the public docs */ 12894 }, 12895 12896 /** 12897 * @private 12898 * 12899 * @param {Object} options 12900 * An object with the following properties:<ul> 12901 * <li><b>id:</b> The id of the object being constructed</li> 12902 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 12903 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 12904 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 12905 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 12906 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 12907 * <li><b>status:</b> {Number} The HTTP status code returned</li> 12908 * <li><b>content:</b> {String} Raw string of response</li> 12909 * <li><b>object:</b> {Object} Parsed object of response</li> 12910 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 12911 * <li><b>errorType:</b> {String} Type of error that was caught</li> 12912 * <li><b>errorMessage:</b> {String} Message associated with error</li> 12913 * </ul></li> 12914 * </ul></li> 12915 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 12916 **/ 12917 init: function (options) { 12918 this._super(options); 12919 }, 12920 12921 /** 12922 * @private 12923 * Gets the REST class for the current object - this is the MediaDialog class. 12924 * @returns {Object} The Dialog class. 12925 */ 12926 getRestClass: function () { 12927 return MediaDialog; 12928 }, 12929 12930 /** 12931 * Transfers a Media Dialog to the target specified 12932 * @param {String} target script selector 12933 * The script selector to transfer the dialog. 12934 * @param {finesse.interfaces.RequestHandlers} handlers 12935 * An object containing the handlers for the request 12936 */ 12937 transfer: function(target, handlers) { 12938 this.setTaskState(MediaDialog.TaskActions.TRANSFER, handlers, target); 12939 }, 12940 12941 /** 12942 * Set the state on a Media Dialog based on the action given. 12943 * @param {finesse.restservices.MediaDialog.TaskActions} action 12944 * The action string indicating the action to invoke on a Media dialog. 12945 * @param {finesse.interfaces.RequestHandlers} handlers 12946 * An object containing the handlers for the request 12947 * @param {String} target 12948 * The target to transfer the dialog. Pass null if not transfer 12949 * 12950 * @example 12951 * _mediaDialog.setTaskState(finesse.restservices.MediaDialog.TaskActions.ACCEPT, 12952 * { 12953 * success: _handleAcceptSuccess, 12954 * error: _handleAcceptError 12955 * }, 12956 * null); 12957 */ 12958 setTaskState: function (state,handlers,target) { 12959 this.isLoaded(); 12960 12961 var contentBody = {}; 12962 contentBody[this.getRestType()] = { 12963 "requestedAction": state, 12964 "target": target 12965 }; 12966 // (nonexistent) keys to be read as undefined 12967 handlers = handlers || {}; 12968 this.restRequest(this.getRestUrl(), { 12969 method: 'PUT', 12970 success: handlers.success, 12971 error: handlers.error, 12972 content: contentBody 12973 }); 12974 return this; // Allow cascading 12975 } 12976 12977 }); 12978 12979 MediaDialog.TaskActions = /** @lends finesse.restservices.MediaDialog.TaskActions.prototype */ { 12980 /** 12981 * Accept an incoming task. 12982 */ 12983 ACCEPT: "ACCEPT", 12984 /** 12985 * Start work on a task. 12986 */ 12987 START : "START", 12988 /** 12989 * Pause work on an active task. 12990 */ 12991 PAUSE: "PAUSE", 12992 /** 12993 * Resume work on a paused task. 12994 */ 12995 RESUME : "RESUME", 12996 /** 12997 * Wrap up work for a task. 12998 */ 12999 WRAP_UP : "WRAP_UP", 13000 /** 13001 * Transfer task to another target. 13002 */ 13003 TRANSFER : "TRANSFER", 13004 /** 13005 * End a task. 13006 */ 13007 CLOSE : "CLOSE", 13008 /** 13009 * @class Set of action constants for a Media Dialog. These should be used for 13010 * {@link finesse.restservices.MediaDialog#setTaskState}. 13011 * @constructs 13012 */ 13013 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 13014 }; 13015 13016 13017 13018 MediaDialog.States = /** @lends finesse.restservices.MediaDialog.States.prototype */ { 13019 /** 13020 * Indicates that the task has been offered to an agent. 13021 */ 13022 OFFERED: "OFFERED", 13023 /** 13024 * Indicates that the user has started work on the task. 13025 */ 13026 ACTIVE: "ACTIVE", 13027 /** 13028 * Indicates that the user has paused work on the task. 13029 */ 13030 PAUSED: "PAUSED", 13031 /** 13032 * Indicates that the user is wrapping up the task. 13033 */ 13034 WRAPPING_UP: "WRAPPING_UP", 13035 /** 13036 * Indicates that the task was interrupted. 13037 */ 13038 INTERRUPTED: "INTERRUPTED", 13039 /** 13040 * Indicates that the task has ended. 13041 */ 13042 CLOSED: "CLOSED", 13043 /** 13044 * Indicates that the user has accepted the task. 13045 */ 13046 ACCEPTED: "ACCEPTED", 13047 /** 13048 * Finesse has recovered a task after a failure. It does not have enough information to build a complete set 13049 * of actions for the task so it only allows the user to end the task. 13050 */ 13051 UNKNOWN: "UNKNOWN", 13052 /** 13053 * @class Possible Dialog State constants. 13054 * The State flow of a typical in-bound Dialog is as follows: OFFERED, ACCEPTED, ACTIVE, CLOSED. 13055 * @constructs 13056 */ 13057 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 13058 }; 13059 13060 MediaDialog.ParticipantStates = MediaDialog.States; 13061 13062 window.finesse = window.finesse || {}; 13063 window.finesse.restservices = window.finesse.restservices || {}; 13064 window.finesse.restservices.MediaDialog = MediaDialog; 13065 13066 13067 return MediaDialog; 13068 }); 13069 13070 /* Simple JavaScript Inheritance 13071 * By John Resig http://ejohn.org/ 13072 * MIT Licensed. 13073 */ 13074 // Inspired by base2 and Prototype 13075 define('restservices/../../thirdparty/Class',[], function () { 13076 var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/; 13077 // The base Class implementation (does nothing) 13078 /** @private */ 13079 Class = function(){}; 13080 13081 // Create a new Class that inherits from this class 13082 /** @private */ 13083 Class.extend = function(prop) { 13084 var _super = this.prototype; 13085 13086 // Instantiate a base class (but only create the instance, 13087 // don't run the init constructor) 13088 initializing = true; 13089 var prototype = new this(); 13090 initializing = false; 13091 13092 // Copy the properties over onto the new prototype 13093 for (var name in prop) { 13094 // Check if we're overwriting an existing function 13095 prototype[name] = typeof prop[name] == "function" && 13096 typeof _super[name] == "function" && fnTest.test(prop[name]) ? 13097 (function(name, fn){ 13098 return function() { 13099 var tmp = this._super; 13100 13101 // Add a new ._super() method that is the same method 13102 // but on the super-class 13103 this._super = _super[name]; 13104 13105 // The method only need to be bound temporarily, so we 13106 // remove it when we're done executing 13107 var ret = fn.apply(this, arguments); 13108 this._super = tmp; 13109 13110 return ret; 13111 }; 13112 })(name, prop[name]) : 13113 prop[name]; 13114 } 13115 13116 // The dummy class constructor 13117 /** @private */ 13118 function Class() { 13119 // All construction is actually done in the init method 13120 if ( !initializing && this.init ) 13121 this.init.apply(this, arguments); 13122 } 13123 13124 // Populate our constructed prototype object 13125 Class.prototype = prototype; 13126 13127 // Enforce the constructor to be what we expect 13128 Class.prototype.constructor = Class; 13129 13130 // And make this class extendable 13131 Class.extend = arguments.callee; 13132 13133 return Class; 13134 }; 13135 return Class; 13136 }); 13137 13138 /** 13139 * Class used to establish a Media/Dialogs subscription to be shared by MediaDialogs objects for non-voice 13140 * dialog events. 13141 * 13142 * @requires Class 13143 * @requires finesse.clientservices.ClientServices 13144 * @requires finesse.clientservices.Topics 13145 */ 13146 /** @private */ 13147 define('restservices/MediaDialogsSubscriptionManager',[ 13148 "../../thirdparty/Class", 13149 "clientservices/ClientServices", 13150 "clientservices/Topics" 13151 ], 13152 function (Class, ClientServices, Topics) { 13153 var MediaDialogsSubscriptionManager = Class.extend(/** @lends finesse.restservices.MediaDialogsSubscriptionManager.prototype */{ 13154 13155 /** 13156 * Map used to track the MediaDialogs objects managed by this object. 13157 * @private 13158 */ 13159 _mediaDialogsMap: {}, 13160 13161 /** 13162 * The regex used to match the source of BOSH/XMPP events. If an event matches this source, the event will 13163 * be processed by the subscription manager. 13164 * @private 13165 */ 13166 _sourceRegEx: null, 13167 13168 /** 13169 * The subscription ID/handle for Media/Dialogs events. 13170 * @private 13171 */ 13172 _subscriptionId: null, 13173 13174 _fakeConstuctor: function () 13175 { 13176 /* This is here to hide the real init constructor from the public docs */ 13177 }, 13178 13179 /** 13180 * Create the regex used to match the source of BOSH/XMPP events. If an event matches this source, the event 13181 * will be processed by the subscription manager. 13182 * 13183 * @param {Object} restObj 13184 * The restObj whose REST URL will be used as the base of the regex. 13185 * 13186 * @returns {RegExp} 13187 * The regex used to match the source of XMPP events. 13188 * @private 13189 */ 13190 _makeSourceRegEx: function(restObj) 13191 { 13192 return new RegExp("^" + restObj.getRestUrl() + "/Media/[0-9]+/Dialogs$"); 13193 }, 13194 13195 /** 13196 * Return the media ID associated with the update. 13197 * 13198 * @param {Object} update 13199 * The content of the update event. 13200 * 13201 * @returns {String} 13202 * The media ID associated with the update. 13203 * @private 13204 */ 13205 _getMediaIdFromEventUpdate: function(update) 13206 { 13207 var parts = update.object.Update.source.split("/"); 13208 return parts[MediaDialogsSubscriptionManager.MEDIA_ID_INDEX_IN_SOURCE]; 13209 }, 13210 13211 /** 13212 * Handler for update events. This handler forwards the update to the MediaDialogs object associated with 13213 * the media ID carried in the update event. 13214 * 13215 * @param {Object} update 13216 * The content of the update event. 13217 * 13218 * @private 13219 */ 13220 _updateEventHandler: function(update) 13221 { 13222 var mediaId = this._getMediaIdFromEventUpdate(update), 13223 mediaDialogs = this._mediaDialogsMap[mediaId]; 13224 13225 if ( mediaDialogs ) 13226 { 13227 mediaDialogs._updateEventHandler(mediaDialogs, update); 13228 } 13229 }, 13230 13231 /** 13232 * Return the media ID associated with the REST update. 13233 * 13234 * @param {Object} update 13235 * The content of the REST update. 13236 * 13237 * @returns {String} 13238 * The media ID associated with the update. 13239 * @private 13240 */ 13241 _getMediaIdFromRestUpdate: function(update) 13242 { 13243 return update.object.Update.data.dialog.mediaProperties.mediaId; 13244 }, 13245 13246 /** 13247 * Handler for REST updates. This handler forwards the update to the MediaDialogs object associated with 13248 * the media ID carried in the REST update. 13249 * 13250 * @param {Object} update 13251 * The content of the REST update. 13252 * 13253 * @private 13254 */ 13255 _processRestItemUpdate: function(update) 13256 { 13257 var mediaId = this._getMediaIdFromRestUpdate(update), 13258 mediaDialogs = this._mediaDialogsMap[mediaId]; 13259 13260 if ( mediaDialogs ) 13261 { 13262 mediaDialogs._processRestItemUpdate(update); 13263 } 13264 }, 13265 13266 /** 13267 * Utility method to create a callback to be given to OpenAjax to invoke when a message 13268 * is published on the topic of our REST URL (also XEP-0060 node). 13269 * This needs to be its own defined method so that subclasses can have their own implementation. 13270 * @returns {Function} callback(update) 13271 * The callback to be invoked when an update event is received. This callback will 13272 * process the update by notifying the MediaDialogs object associated with the media ID in the update. 13273 * 13274 * @private 13275 */ 13276 _createPubsubCallback: function () 13277 { 13278 var _this = this; 13279 return function (update) { 13280 //If the source of the update is our REST URL, this means the collection itself is modified 13281 if (update.object.Update.source.match(_this._sourceRegEx)) { 13282 _this._updateEventHandler(update); 13283 } else { 13284 //Otherwise, it is safe to assume that if we got an event on our topic, it must be a 13285 //rest item update of one of our children that was published on our node (OpenAjax topic) 13286 _this._processRestItemUpdate(update); 13287 } 13288 }; 13289 }, 13290 13291 /** 13292 * Track the MediaDialogs object so that events and REST updates signalled to this subscription manager 13293 * can be forwarded to the given MediaDialogs object. 13294 * @param {finesse.restservices.MediaDialogs} mediaDialogs MediaDialogs object to be tracked by the 13295 * subscription manager. 13296 * @private 13297 */ 13298 _manage: function(mediaDialogs) 13299 { 13300 this._mediaDialogsMap[mediaDialogs.getMedia().getMediaId()] = mediaDialogs; 13301 }, 13302 13303 /** 13304 * Stop tracking the MediaDialogs object. Events and REST updates signalled to this subscription manager 13305 * will no longer be forwarded to the given MediaDialogs object. 13306 * @param {finesse.restservices.MediaDialogs} mediaDialogs MediaDialogs object to no longer track. 13307 * @private 13308 */ 13309 _unManage: function(mediaDialogs) 13310 { 13311 var mediaId = mediaDialogs.getMedia().getMediaId(); 13312 if ( this._callbackMap[mediaId] ) 13313 { 13314 delete this._callbackMap[mediaId]; 13315 } 13316 }, 13317 13318 /** 13319 * @class 13320 * An internal class used to establish a Media/Dialogs subscription to be shared by MediaDialogs objects 13321 * for non-voice dialog events. 13322 * 13323 * @constructor 13324 * @param {RestBase} restObj 13325 * A RestBase object used to build the user portion of XMPP and REST paths. 13326 * @constructs 13327 * @private 13328 */ 13329 init: function (restObj) 13330 { 13331 var _this; 13332 13333 this._sourceRegEx = this._makeSourceRegEx(restObj); 13334 }, 13335 13336 /** 13337 * Create the BOSH/XMPP subscription used for non-voice dialog events. Additionally, store the given 13338 * MediaDialogs object so that events for the object can be forwarded to it. 13339 * 13340 * @param {finesse.restservices.MediaDialogs} mediaDialogs a MediaDialogs object to manage (forward events) 13341 * @param {Object} callbacks an object containing success and error callbacks used to signal the result of 13342 * the subscription. 13343 * @returns {MediaDialogsSubscriptionManager} 13344 * @private 13345 */ 13346 subscribe: function (mediaDialogs, callbacks) 13347 { 13348 var topic = Topics.getTopic(mediaDialogs.getXMPPNodePath()), 13349 _this = mediaDialogs, 13350 handlers, 13351 successful; 13352 13353 callbacks = callbacks || {}; 13354 13355 handlers = { 13356 /** @private */ 13357 success: function () { 13358 // Add item to the refresh list in ClientServices to refresh if 13359 // we recover due to our resilient connection. 13360 ClientServices.addToRefreshList(_this); 13361 if (typeof callbacks.success === "function") { 13362 callbacks.success(); 13363 } 13364 }, 13365 /** @private */ 13366 error: function (err) { 13367 if (successful) { 13368 _this._unManage(mediaDialogs); 13369 ClientServices.unsubscribe(topic); 13370 } 13371 13372 if (typeof callbacks.error === "function") { 13373 callbacks.error(err); 13374 } 13375 } 13376 }; 13377 13378 this._manage(mediaDialogs); 13379 if ( this._subscriptionId ) 13380 { 13381 successful = true; 13382 } 13383 else 13384 { 13385 successful = ClientServices.subscribe(topic, this._createPubsubCallback(), true); 13386 if ( successful ) 13387 { 13388 this._subscriptionId = "OpenAjaxOnly"; 13389 } 13390 } 13391 13392 if (successful) { 13393 handlers.success(); 13394 } else { 13395 handlers.error(); 13396 } 13397 13398 return this; 13399 } 13400 }); 13401 13402 MediaDialogsSubscriptionManager.MEDIA_ID_INDEX_IN_SOURCE = 6; 13403 13404 window.finesse = window.finesse || {}; 13405 window.finesse.restservices = window.finesse.restservices || {}; 13406 window.finesse.restservices.MediaDialogsSubscriptionManager = MediaDialogsSubscriptionManager; 13407 13408 return MediaDialogsSubscriptionManager; 13409 }); 13410 13411 /** 13412 * JavaScript representation of the Finesse MediaDialogs collection 13413 * object which contains a list of Dialog objects. 13414 * 13415 * @requires finesse.clientservices.ClientServices 13416 * @requires Class 13417 * @requires finesse.FinesseBase 13418 * @requires finesse.restservices.RestBase 13419 * @requires finesse.restservices.Dialogs 13420 * @requires finesse.restservices.MediaDialogsSubscriptionManager 13421 */ 13422 /** @private */ 13423 define('restservices/MediaDialogs',[ 13424 'restservices/RestCollectionBase', 13425 'restservices/RestBase', 13426 'restservices/Dialogs', 13427 'restservices/MediaDialog', 13428 'restservices/MediaDialogsSubscriptionManager' 13429 ], 13430 function (RestCollectionBase, RestBase, Dialogs, MediaDialog, MediaDialogsSubscriptionManager) { 13431 var MediaDialogs = Dialogs.extend(/** @lends finesse.restservices.MediaDialogs.prototype */{ 13432 13433 /** 13434 * @class 13435 * JavaScript representation of a collection of Dialogs for a specific non-voice Media. 13436 * @augments finesse.restservices.Dialogs 13437 * @constructs 13438 * @see finesse.restservices.Dialog 13439 * @example 13440 * _MediaDialogs = _media.getMediaDialogs( { 13441 * onCollectionAdd : _handleDialogAdd, 13442 * onCollectionDelete : _handleDialogDelete, 13443 * onLoad : _handleMediaDialogsLoaded 13444 * }); 13445 * 13446 * _dialogCollection = _MediaDialogs.getCollection(); 13447 * for (var dialogId in _dialogCollection) { 13448 * if (_dialogCollection.hasOwnProperty(dialogId)) { 13449 * _dialog = _dialogCollection[dialogId]; 13450 * etc... 13451 * } 13452 * } 13453 */ 13454 _fakeConstuctor: function () { 13455 /* This is here to hide the real init constructor from the public docs */ 13456 }, 13457 13458 /** 13459 * @private 13460 * @param {Object} options 13461 * An object with the following properties:<ul> 13462 * <li><b>id:</b> The id of the object being constructed</li> 13463 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 13464 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 13465 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 13466 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 13467 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 13468 * <li><b>status:</b> {Number} The HTTP status code returned</li> 13469 * <li><b>content:</b> {String} Raw string of response</li> 13470 * <li><b>object:</b> {Object} Parsed object of response</li> 13471 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 13472 * <li><b>errorType:</b> {String} Type of error that was caught</li> 13473 * <li><b>errorMessage:</b> {String} Message associated with error</li> 13474 * </ul></li> 13475 * </ul></li> 13476 * <li><b>parentObj:</b> The parent object</li></ul> 13477 * <li><b>mediaObj:</b> The media object</li></ul> 13478 **/ 13479 init: function (options) { 13480 this._mediaObj = options.mediaObj; 13481 this._super(options); 13482 }, 13483 13484 /** 13485 * Returns the associated Media object. 13486 * @returns {finesse.restservices.Media} the associated media object. 13487 */ 13488 getMedia: function() { 13489 return this._mediaObj; 13490 }, 13491 13492 /** 13493 * @private 13494 * Gets the REST class for the objects that make up the collection. - this 13495 * is the Dialog class. 13496 */ 13497 getRestItemClass: function () { 13498 return MediaDialog; 13499 }, 13500 13501 /** 13502 * @private 13503 * Gets the node path for the current object - this is the media node 13504 * @returns {String} The node path 13505 */ 13506 getXMPPNodePath: function () { 13507 var 13508 restObj = this._restObj, 13509 nodePath = ""; 13510 13511 //Prepend the base REST object if one was provided. 13512 if (restObj instanceof RestBase) { 13513 nodePath += restObj.getRestUrl(); 13514 } 13515 //Otherwise prepend with the default webapp name. 13516 else { 13517 nodePath += "/finesse/api"; 13518 } 13519 13520 //Append the REST type. 13521 nodePath += "/" + this.getRestType() + "/Media"; 13522 return nodePath; 13523 }, 13524 13525 /** 13526 * The REST URL in which this object can be referenced. 13527 * @return {String} 13528 * The REST URI for this object. 13529 * @private 13530 */ 13531 getRestUrl: function () { 13532 var 13533 restObj = this._mediaObj, 13534 restUrl = ""; 13535 13536 //Prepend the base REST object if one was provided. 13537 if (restObj instanceof RestBase) { 13538 restUrl += restObj.getRestUrl(); 13539 } 13540 //Otherwise prepend with the default webapp name. 13541 else { 13542 restUrl += "/finesse/api"; 13543 } 13544 13545 //Append the REST type. 13546 restUrl += "/" + this.getRestType(); 13547 13548 //Append ID if it is not undefined, null, or empty. 13549 if (this._id) { 13550 restUrl += "/" + this._id; 13551 } 13552 return restUrl; 13553 }, 13554 13555 /** 13556 * Overridden so that MediaDialogsSubscriptionManager can be used to share events across media dialogs. 13557 * 13558 * @param {Object} callbacks 13559 * An object containing success and error handlers for the subscription request. 13560 * @private 13561 */ 13562 subscribe: function (callbacks) 13563 { 13564 if ( !MediaDialogs.subscriptionManager ) 13565 { 13566 MediaDialogs.subscriptionManager = new MediaDialogsSubscriptionManager(this._restObj); 13567 } 13568 13569 MediaDialogs.subscriptionManager.subscribe(this, callbacks); 13570 13571 return this; 13572 } 13573 }); 13574 13575 MediaDialogs.subscriptionManager = /** @lends finesse.restservices.MediaDialogsSubscriptionManager.prototype */ null; 13576 13577 window.finesse = window.finesse || {}; 13578 window.finesse.restservices = window.finesse.restservices || {}; 13579 window.finesse.restservices.MediaDialogs = MediaDialogs; 13580 13581 return MediaDialogs; 13582 }); 13583 13584 /** 13585 * Utility class used to recover a media object after recovering from a connection or system failure. 13586 * 13587 * @requires Class 13588 * @requires finesse.restservices.Notifier 13589 * @requires finesse.clientservices.ClientServices 13590 * @requires finesse.restservices.Media 13591 */ 13592 13593 /** @private */ 13594 define('restservices/MediaOptionsHelper',['../../thirdparty/Class', 13595 '../utilities/Utilities', 13596 '../clientservices/ClientServices', 13597 '../cslogger/ClientLogger' 13598 ], 13599 function (Class, Utilities, ClientServices, ClientLogger) 13600 { 13601 /** 13602 * Utility class used to synchronize media login options after recovering from a connection or system failure. This 13603 * class will ensure that the Finesse server that the application fails over to has the same maxDialogLimit, 13604 * interruptAction, and dialogLogoutAction as the previous Finesse server. 13605 */ 13606 var MediaOptionsHelper = Class.extend(/** @lends finesse.restservices.MediaOptionsHelper.prototype */ 13607 { 13608 /** 13609 * @private 13610 * 13611 * The media that this helper is responsible for recovering in case of failover. 13612 */ 13613 _media: null, 13614 13615 /** 13616 * @private 13617 * 13618 * The media options that this helper will ensure are set properly across failures. 13619 */ 13620 _mediaOptions: null, 13621 13622 /** 13623 * @private 13624 * 13625 * The current state of the failover recovery. 13626 */ 13627 _state: null, 13628 13629 /** 13630 * @class 13631 * 13632 * Utility class used to synchronize media login options after recovering from a connection or system failure. This 13633 * class will ensure that the Finesse server that the application fails over to has the same maxDialogLimit, 13634 * interruptAction, and dialogLogoutAction as the previous Finesse server. 13635 * 13636 * @constructs 13637 */ 13638 _fakeConstuctor: function () 13639 { 13640 /* This is here to hide the real init constructor from the public docs */ 13641 }, 13642 13643 /** 13644 * Utility method to format a message logged by an instance of this class. 13645 * 13646 * @param {string} message the message to format 13647 * @returns {string} the given message prefixed with the name of this class and the ID of the Media object 13648 * associated with this class. 13649 * @private 13650 */ 13651 _formatLogMessage: function(message) 13652 { 13653 return "MediaOptionsHelper[" + this.media.getMediaId() + "]: " + message; 13654 }, 13655 13656 /** 13657 * Utility method to log an informational message. 13658 * 13659 * Note that this method piggy-backs on the logger setup by the gadget. If the gadget does not initialize 13660 * logger, this class will not log. 13661 * 13662 * @param {string} message the message to log 13663 * @private 13664 */ 13665 _log: function(message) 13666 { 13667 ClientLogger.log(this._formatLogMessage(message)); 13668 }, 13669 13670 /** 13671 * Utility method to log an error message. 13672 * 13673 * Note that this method piggy-backs on the logger setup by the gadget. If the gadget does not initialize 13674 * logger, this class will not log. 13675 * 13676 * @param {string} message the message to log 13677 * @private 13678 */ 13679 _error: function(message) 13680 { 13681 ClientLogger.error(this._formatLogMessage(message)); 13682 }, 13683 13684 /** 13685 * @private 13686 * 13687 * Set the running state of this failover helper. 13688 * 13689 * @param {String} newState the new state of the failover helper. 13690 */ 13691 _setState: function(newState) 13692 { 13693 this._state = newState; 13694 this._log("changed state to " + this._state); 13695 }, 13696 13697 /** 13698 * Check the given media object to see if the maxDialogLimit, interruptAction, and dialogLogoutAction options 13699 * need to be reset. These options need to be reset if the application specified login options and any of the 13700 * following conditions are true:<ul> 13701 * <li>the dialogLogoutAction in the given media object does not match the action set by the application</li> 13702 * <li>the interruptAction in the given media object does not match the action set by the application</li> 13703 * <li>the maxDialogLimit in the given media object does not match the limit set by the application</li></ul> 13704 * 13705 * @param {Object} media the media object to evaluate 13706 * @returns {*|{}|boolean} true if a login request should be sent to correct the media options 13707 * @private 13708 */ 13709 _shouldLoginToFixOptions: function(media) 13710 { 13711 return this._mediaOptions 13712 && media.isLoggedIn() 13713 && (media.getDialogLogoutAction() !== this._mediaOptions.dialogLogoutAction 13714 || media.getInterruptAction() !== this._mediaOptions.interruptAction 13715 || media.getMaxDialogLimit() !== this._mediaOptions.maxDialogLimit); 13716 }, 13717 13718 /** 13719 * @private 13720 * 13721 * Determine if the given response is an "agent already logged in" error. 13722 * 13723 * @param {Object} response the response to evaluate 13724 * 13725 * @returns {boolean} true if 13726 */ 13727 _agentIsAlreadyLoggedIn: function(response) 13728 { 13729 return response 13730 && response.object 13731 && response.object.ApiErrors 13732 && response.object.ApiErrors.ApiError 13733 && response.object.ApiErrors.ApiError.ErrorMessage === "E_ARM_STAT_AGENT_ALREADY_LOGGED_IN"; 13734 }, 13735 13736 /** 13737 * Determine if the given response to a media login request is successful. A response is successful under these 13738 * conditions:<ul> 13739 * <li>the response has a 202 status</li> 13740 * <li>the response has a 400 status and the error indicates the agent is already logged in</li> 13741 * </ul> 13742 * 13743 * @param {Object} loginResponse the response to evaluate 13744 * 13745 * @returns {*|boolean} true if the response status is 202 or if the response status is 400 and the error states 13746 * that the agent is already logged in. 13747 * @private 13748 */ 13749 _isSuccessfulLoginResponse: function(loginResponse) 13750 { 13751 return loginResponse && ((loginResponse.status === 202) || this._agentIsAlreadyLoggedIn(loginResponse)); 13752 }, 13753 13754 /** 13755 * Process a media load or change while in the connected state. This involves checking the media options to 13756 * ensure they are the same as those set by the application. 13757 * 13758 * @param {Object} media the media object that was loaded or changed. 13759 * @private 13760 */ 13761 _processConnectedState: function(media) 13762 { 13763 var _this = this, processResponse; 13764 13765 if ( this._shouldLoginToFixOptions(media) ) 13766 { 13767 processResponse = function(response) 13768 { 13769 _this._setState(MediaOptionsHelper.States.MONITORING_OPTIONS); 13770 13771 if ( !_this._isSuccessfulLoginResponse(response) ) 13772 { 13773 _this._error("failed to reset options: " + response.status + ": " + response.content); 13774 } 13775 }; 13776 13777 this._setState(MediaOptionsHelper.States.SETTING_OPTIONS); 13778 13779 this._log("logging in to fix options"); 13780 13781 this.media.login({ 13782 dialogLogoutAction: _this._mediaOptions.dialogLogoutAction, 13783 interruptAction: _this._mediaOptions.interruptAction, 13784 maxDialogLimit: _this._mediaOptions.maxDialogLimit, 13785 handlers: { 13786 success: processResponse, 13787 error: processResponse 13788 } 13789 }); 13790 } 13791 }, 13792 13793 /** 13794 * Process a media load or change while in the resetting options state. All that is done in this state is log a 13795 * message that a reset is already in progress. 13796 * 13797 * @param {Object} media the media object that was loaded or changed. 13798 * @private 13799 */ 13800 _processResettingOptionsState: function(media) 13801 { 13802 this._log("Resetting options is in progress"); 13803 }, 13804 13805 /** 13806 * Initialize a helper class used to recover media objects following connectivity or component failures related 13807 * to Finesse and/or CCE services. 13808 * 13809 * Initialize the failover helper to manage the recovery of the given media object. 13810 * 13811 * @param {Object} media the media object to recover 13812 * @param {Object} mediaOptions an object containing the media options used by the application:<ul> 13813 * <li><b>maxDialogLimit:</b> The id of the object being constructed</li> 13814 * <li><b>interruptAction:</b> Accept or ignore interrupts</li> 13815 * <li><b>dialogLogoutAction:</b> transfer or close the task at logout time</li></ul> 13816 */ 13817 init: function (media, mediaOptions) 13818 { 13819 var _this = this, processMediaStateChange = function(media) 13820 { 13821 switch ( _this._state ) 13822 { 13823 case MediaOptionsHelper.States.MONITORING_OPTIONS: 13824 _this._processConnectedState(media); 13825 break; 13826 case MediaOptionsHelper.States.SETTING_OPTIONS: 13827 _this._processResettingOptionsState(media); 13828 break; 13829 default: 13830 _this._error("unexpected state: " + _this.state); 13831 break; 13832 } 13833 }; 13834 13835 this.media = media; 13836 13837 this._mediaOptions = mediaOptions || {}; 13838 13839 // The maxDialogLimit is a string in media events. Ensure _mediaOptions.maxDialogLimit is a string to 13840 // make sure it can be compared to the maxDialogLimit field in media events. 13841 // 13842 if ( this._mediaOptions.maxDialogLimit ) 13843 { 13844 this._mediaOptions.maxDialogLimit = this._mediaOptions.maxDialogLimit.toString(); 13845 } 13846 13847 // Add the media object to the refresh list so that ClientServices calls refresh on the media object when 13848 // the connection is reestablished. This will trigger processMediaStateChange() which will trigger a login 13849 // to restore media options if media options are different. 13850 // 13851 ClientServices.addToRefreshList(this.media); 13852 13853 this._setState(MediaOptionsHelper.States.MONITORING_OPTIONS); 13854 13855 this.media.addHandler('load', processMediaStateChange); 13856 this.media.addHandler('change', processMediaStateChange); 13857 13858 this._log("initialized"); 13859 } 13860 }); 13861 13862 /** 13863 * @private 13864 * 13865 * The states that a running MediaOptionsHelper executes. 13866 * 13867 * @type {{CONNECTED: string, RECOVERING: string, RECOVERY_LOGIN: string, _fakeConstructor: _fakeConstructor}} 13868 */ 13869 MediaOptionsHelper.States = /** @lends finesse.restservices.MediaOptionsHelper.States.prototype */ { 13870 13871 /** 13872 * The media is synchronized with the Finesse server. Media options are being monitored for changes. 13873 */ 13874 MONITORING_OPTIONS: "MONITORING_OPTIONS", 13875 13876 /** 13877 * A media login request has been sent to Finesse to set the correct media options. 13878 */ 13879 SETTING_OPTIONS: "SETTING_OPTIONS", 13880 13881 /** 13882 * @class Possible MediaOptionsHelper state values. 13883 * @constructs 13884 */ 13885 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 13886 }; 13887 13888 window.finesse = window.finesse || {}; 13889 window.finesse.restservices = window.finesse.restservices || {}; 13890 window.finesse.restservices.MediaOptionsHelper = MediaOptionsHelper; 13891 13892 return MediaOptionsHelper; 13893 }); 13894 /** 13895 * JavaScript representation of the Finesse Media object 13896 * 13897 * @requires finesse.clientservices.ClientServices 13898 * @requires Class 13899 * @requires finesse.FinesseBase 13900 * @requires finesse.restservices.RestBase 13901 * @requires finesse.restservices.MediaDialogs 13902 * @requires finesse.restservices.MediaOptionsHelper 13903 */ 13904 13905 /** @private */ 13906 define('restservices/Media',[ 13907 'restservices/RestBase', 13908 'restservices/MediaDialogs', 13909 'restservices/MediaOptionsHelper' 13910 ], 13911 function (RestBase, MediaDialogs, MediaOptionsHelper) { 13912 var Media = RestBase.extend(/** @lends finesse.restservices.Media.prototype */{ 13913 13914 /** 13915 * @private 13916 * Media objects support GET REST requests. 13917 */ 13918 supportsRequests: true, 13919 13920 /** 13921 * @private 13922 * The list of dialogs associated with this media. 13923 */ 13924 _mdialogs : null, 13925 13926 /** 13927 * @private 13928 * The options used to log into this media. 13929 */ 13930 _mediaOptions: null, 13931 13932 /** 13933 * @private 13934 * Object used to keep the maxDialogLimit, interruptAction, and dialogLogoutAction in-synch across fail-overs. 13935 */ 13936 _optionsHelper: null, 13937 13938 /** 13939 * @class 13940 * A Media represents a non-voice channel, 13941 * for example, a chat or a email. 13942 * 13943 * @augments finesse.restservices.RestBase 13944 * @constructs 13945 */ 13946 _fakeConstuctor: function () { 13947 /* This is here to hide the real init constructor from the public docs */ 13948 }, 13949 13950 /** 13951 * @private 13952 * 13953 * @param {Object} options 13954 * An object with the following properties:<ul> 13955 * <li><b>id:</b> The id of the object being constructed</li> 13956 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 13957 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 13958 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 13959 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 13960 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 13961 * <li><b>status:</b> {Number} The HTTP status code returned</li> 13962 * <li><b>content:</b> {String} Raw string of response</li> 13963 * <li><b>object:</b> {Object} Parsed object of response</li> 13964 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 13965 * <li><b>errorType:</b> {String} Type of error that was caught</li> 13966 * <li><b>errorMessage:</b> {String} Message associated with error</li> 13967 * </ul></li> 13968 * </ul></li> 13969 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 13970 **/ 13971 init: function (options) { 13972 this._super(options); 13973 }, 13974 13975 /** 13976 * @private 13977 * Utility method used to retrieve an attribute from this media object's underlying data. 13978 * 13979 * @param {String} attributeName the name of the attribute to retrieve 13980 * @returns {String} the value of the attribute or undefined if the attribute is not found 13981 */ 13982 _getDataAttribute: function(attributeName) { 13983 this.isLoaded(); 13984 return this.getData()[attributeName]; 13985 }, 13986 13987 /** 13988 * @private 13989 * Gets the REST class for the current object - this is the Media class. 13990 * @returns {Object} The Media class. 13991 */ 13992 getRestClass: function () { 13993 return Media; 13994 }, 13995 13996 /** 13997 * @private 13998 * Gets the REST type for the current object - this is a "Media". 13999 * @returns {String} The Media string. 14000 */ 14001 getRestType: function () { 14002 return "Media"; 14003 }, 14004 14005 /** 14006 * @private 14007 * Getter for the uri. 14008 * @returns {String} The uri. 14009 */ 14010 getMediaUri: function () { 14011 return this._getDataAttribute('uri'); 14012 }, 14013 14014 /** 14015 * Returns the id. 14016 * @returns {String} The id. 14017 */ 14018 getId: function () { 14019 return this._getDataAttribute('id'); 14020 }, 14021 14022 /** 14023 * Returns the name. 14024 * @returns {String} The name. 14025 */ 14026 getName: function () { 14027 return this._getDataAttribute('name'); 14028 }, 14029 14030 /** 14031 * Returns the reason code id. 14032 * @returns {String} The reason code id. 14033 */ 14034 getReasonCodeId: function () { 14035 return this._getDataAttribute('reasonCodeId'); 14036 }, 14037 14038 /** 14039 * Returns the reason code label. 14040 * @returns {String} The reason code label. 14041 */ 14042 getReasonCodeLabel: function() { 14043 this.isLoaded(); 14044 if (this.getData().reasonCode) { 14045 return this.getData.reasonCode.label; 14046 } 14047 return ""; 14048 }, 14049 14050 /** 14051 * Returns the action to be taken in the event this media is interrupted. The action will be one of the 14052 * following:<ul> 14053 * <li><b>ACCEPT:</b> the interrupt will be accepted and the agent will not work on tasks in this media 14054 * until the media is no longer interrupted.</li> 14055 * <li><b>IGNORE:</b> the interrupt will be ignored and the agent is allowed to work on the task while the 14056 * media is interrupted.</li></ul> 14057 * @returns {*|Object} 14058 */ 14059 getInterruptAction: function() { 14060 return this._getDataAttribute('interruptAction'); 14061 }, 14062 14063 /** 14064 * Returns the action to be taken in the event the agent logs out with dialogs associated with this media. 14065 * The action will be one of the following:<ul> 14066 * <li><b>CLOSE:</b> the dialog will be closed.</li> 14067 * <li><b>TRANSFER:</b> the dialog will be transferred to another agent.</li></ul> 14068 * @returns {*|Object} 14069 */ 14070 getDialogLogoutAction: function() { 14071 return this._getDataAttribute('dialogLogoutAction'); 14072 }, 14073 14074 /** 14075 * Returns the state of the User on this Media. 14076 * @returns {String} 14077 * The current (or last fetched) state of the User on this Media 14078 * @see finesse.restservices.Media.States 14079 */ 14080 getState: function() { 14081 return this._getDataAttribute('state'); 14082 }, 14083 14084 /** 14085 * Returns the Media id 14086 * @returns {String} The Media id 14087 */ 14088 getMediaId: function() { 14089 return this._getDataAttribute('id'); 14090 }, 14091 14092 /** 14093 * Returns maximum number of dialogs allowed on this Media 14094 * @returns {String} The maximum number of Dialogs on this Media 14095 */ 14096 getMaxDialogLimit: function() { 14097 return this._getDataAttribute('maxDialogLimit'); 14098 }, 14099 14100 /** 14101 * Returns true if this media is interruptible 14102 * @returns {Boolean} true if interruptible; false otherwise 14103 */ 14104 getInterruptible: function() { 14105 var interruptible = this._getDataAttribute('interruptible'); 14106 return interruptible === 'true'; 14107 }, 14108 14109 /** 14110 * Returns true if the user interruptible on this Media. 14111 * @returns {Boolean} true if interruptible; false otherwise 14112 */ 14113 isInterruptible: function() { 14114 return this.getInterruptible(); 14115 }, 14116 14117 /** 14118 * Returns true if the user is routable on this Media 14119 * @returns {Boolean} true if routable, false otherwise 14120 */ 14121 getRoutable: function() { 14122 var routable = this._getDataAttribute('routable'); 14123 return routable === 'true'; 14124 }, 14125 14126 /** 14127 * Returns true if the user is routable on this Media. 14128 * @returns {Boolean} true if routable, false otherwise 14129 */ 14130 isRoutable: function() { 14131 return this.getRoutable(); 14132 }, 14133 14134 /** 14135 * Sets the routable status of this media 14136 * . 14137 * @param {Object} options 14138 * An object with the following properties:<ul> 14139 * <li><b>routable:</b> true if the agent is routable, false otherwise</li> 14140 * <li><b>{@link finesse.interfaces.RequestHandlers} handlers:</b> An object containing the handlers for the request</li></ul> 14141 * @returns {finesse.restservices.Media} 14142 * This Media object, to allow cascading 14143 */ 14144 setRoutable: function(params) { 14145 var handlers, contentBody = {}, 14146 restType = this.getRestType(), 14147 url = this.getRestUrl(); 14148 params = params || {}; 14149 14150 contentBody[restType] = { 14151 "routable": params.routable 14152 }; 14153 14154 handlers = params.handlers || {}; 14155 14156 this._makeRequest(contentBody, url, handlers); 14157 14158 return this; 14159 }, 14160 14161 /** 14162 * @private 14163 * Invoke a request to the server given a content body and handlers. 14164 * 14165 * @param {Object} contentBody 14166 * A JS object containing the body of the action request. 14167 * @param {finesse.interfaces.RequestHandlers} handlers 14168 * An object containing the handlers for the request 14169 */ 14170 _makeRequest: function (contentBody, url, handlers) { 14171 // Protect against null dereferencing of options allowing its 14172 // (nonexistent) keys to be read as undefined 14173 handlers = handlers || {}; 14174 14175 this.restRequest(url, { 14176 method: 'PUT', 14177 success: handlers.success, 14178 error: handlers.error, 14179 content: contentBody 14180 }); 14181 }, 14182 14183 /** 14184 * Return true if the params object contains one of the following:<ul> 14185 * <li>maxDialogLimit</li> 14186 * <li>interruptAction</li> 14187 * <li>dialogLogoutAction</li></ul> 14188 * 14189 * @param {Object} params the parameters to evaluate 14190 * @returns {*|Boolean} 14191 * @private 14192 */ 14193 _containsLoginOptions: function(params) { 14194 return params.maxDialogLimit || params.interruptAction || params.dialogLogoutAction; 14195 }, 14196 14197 /** 14198 * Create login parameters using the given algorithm:<ul> 14199 * <li>if loginOptions have not be set in the call to MediaList.getMedia(), use the params given by the application</li> 14200 * <li>if no params were set by the application, use the loginOptions set in the call to MediaList.getMedia()</li> 14201 * <li>if the parameters given by the application contains login options, use the parameters given by the application</li> 14202 * <li>if login options were given by the application but callbacks were given, create new login params with the 14203 * loginOptions set in the call to MediaList.getMedia() and the callbacks specified in the given login params</li></ul> 14204 * 14205 * @param params the parameters specified by the application in the call to Media.login() 14206 * 14207 * @see finesse.restservices.Media#login 14208 * @see finesse.restservices.MediaList#getMedia 14209 * 14210 * @returns {Object} login parameters built based on the algorithm listed above. 14211 * @private 14212 */ 14213 _makeLoginOptions: function(params) { 14214 if ( !this._mediaOptions ) { 14215 // If loginOptions have not be set, use the params given by the application. 14216 // 14217 return params; 14218 } 14219 14220 if ( !params ) { 14221 // If there were no params given by the application, use the loginOptions set in the call to 14222 // MediaList.getMedia(). 14223 // 14224 return this._mediaOptions; 14225 } 14226 14227 if ( this._containsLoginOptions(params) ) { 14228 // if the parameters given by the application contains login options, use the parameters given by the 14229 // application. 14230 // 14231 return params; 14232 } 14233 14234 // If login options were given by the application but callbacks were given, create new login params with the 14235 // loginOptions set in the call to MediaList.getMedia() and the callbacks specified in the given login 14236 // params. 14237 // 14238 return { 14239 maxDialogLimit: this._mediaOptions.maxDialogLimit, 14240 interruptAction: this._mediaOptions.interruptAction, 14241 dialogLogoutAction: this._mediaOptions.dialogLogoutAction, 14242 handlers: params.handlers 14243 }; 14244 }, 14245 14246 /** 14247 * Ensure that the maxDialogLimit, interruptAction, and dialogLogoutAction options are kept in synch across 14248 * fail-overs for this media object. 14249 * @private 14250 */ 14251 _ensureOptionsAreInSynch: function() { 14252 if ( !this._optionsHelper && this._mediaOptions ) { 14253 this._optionsHelper = new MediaOptionsHelper(this, this._mediaOptions); 14254 } 14255 }, 14256 14257 /** 14258 * Log the agent into this media. 14259 * 14260 * @param {Object} params 14261 * An object with the following properties:<ul> 14262 * <li><b>maxDialogLimit:</b>The maximum number of tasks that is allowed to handle concurrently</li> 14263 * <li><b>interruptAction:</b> Accept or ignore interrupts</li> 14264 * <li><b>dialogLogoutAction:</b> transfer or close the task at logout time</li> 14265 * <li><b>{@link finesse.interfaces.RequestHandlers} handlers:</b> An object containing the handlers for the request</li></ul> 14266 * 14267 * If maxDialogLimit, interruptAction, and dialogLogoutAction are not present, loginOptions specified in the 14268 * call to finesse.restservices.MediaList.getMedia() will be used. 14269 * 14270 * @see finesse.restservices.MediaList#getMedia 14271 * 14272 * @returns {finesse.restservices.Media} 14273 * This Media object, to allow cascading 14274 */ 14275 login: function(params) { 14276 this.setState(Media.States.LOGIN, null, this._makeLoginOptions(params)); 14277 return this; // Allow cascading 14278 }, 14279 14280 /** 14281 * Perform a logout for a user on this media. 14282 * 14283 * @param {String} reasonCode 14284 * The reason code for this user to logging out of this media. Pass null for no reason. 14285 * @param {Object} params 14286 * An object with the following properties:<ul> 14287 * <li><b>{@link finesse.interfaces.RequestHandlers} handlers:</b> An object containing the handlers for the request</li></ul> 14288 * 14289 * @returns {finesse.restservices.Media} 14290 * This Media object, to allow cascading 14291 */ 14292 logout: function(reasonCode, params) { 14293 var state = Media.States.LOGOUT; 14294 return this.setState(state, reasonCode, params); 14295 }, 14296 14297 /** 14298 * Set the state of the user on this Media. 14299 * 14300 * @param {String} newState 14301 * The new state to be set. 14302 * @param {ReasonCode} reasonCode 14303 * The reason code for the state change for this media. Pass null for no reason. 14304 * @param {Object} params 14305 * An object with the following properties:<ul> 14306 * <li><b>maxDialogLimit:</b>The maximum number of tasks that is allowed to handle concurrently</li> 14307 * <li><b>interruptAction:</b> Accept or ignore interrupts</li> 14308 * <li><b>dialogLogoutAction:</b> transfer or close the task at logout time</li> 14309 * <li><b>{@link finesse.interfaces.RequestHandlers} handlers:</b> An object containing the handlers for the request</li></ul> 14310 * 14311 * @see finesse.restservices.User.States 14312 * @returns {finesse.restservices.Media} 14313 * This Media object, to allow cascading 14314 * @example 14315 * _media.setState(finesse.restservices.Media.States.NOT_READY, 14316 * { 14317 * id: _reasonCodeId 14318 * }, 14319 * { 14320 * handlers: { 14321 * success: _handleStateChangeSuccess, 14322 * error : _handleStateChangeError 14323 * } 14324 * }); 14325 */ 14326 setState: function(state, reasonCode, params) { 14327 var handlers, reasonCodeId, contentBody = {}, 14328 restType = this.getRestType(), 14329 url = this.getRestUrl(); 14330 params = params || {}; 14331 14332 if(reasonCode) { 14333 reasonCodeId = reasonCode.id; 14334 } 14335 14336 contentBody[restType] = { 14337 "state": state, 14338 "maxDialogLimit": params.maxDialogLimit, 14339 "interruptAction": params.interruptAction, 14340 "dialogLogoutAction": params.dialogLogoutAction, 14341 "reasonCodeId": reasonCodeId 14342 }; 14343 14344 handlers = params.handlers || {}; 14345 14346 this._makeRequest(contentBody, url, handlers); 14347 14348 return this; 14349 }, 14350 14351 /** 14352 * Returns a MediaDialogs collection object that is associated with User on this Media. 14353 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 14354 * applicable when Object has not been previously created). 14355 * @returns {finesse.restservices.MediaDialogs} 14356 * A MediaDialogs collection object. 14357 * @example 14358 * First call: 14359 * _mediaDialogs = _media.getMediaDialogs({ 14360 * onLoad : _handleMediaDialogsLoad, 14361 * onChange : _handleTeamChange, 14362 * onAdd: _handleMediaDialogAdd, 14363 * onDelete: _handleMediaDialogDelete, 14364 * onError: _errorHandler 14365 * }); 14366 * Subsequent calls on the same object, after the media dialogs are loaded: 14367 * ... 14368 * _mediaDialogsNew = _media.getMediaDialogs(); 14369 * _dialogsCollection = _mediaDialogsNew.getCollection(); 14370 * ... 14371 */ 14372 getMediaDialogs: function (callbacks) { 14373 var options = callbacks || {}; 14374 options.parentObj = this._restObj; 14375 options.mediaObj = this; 14376 this.isLoaded(); 14377 14378 if (this._mdialogs === null) { 14379 this._mdialogs = new MediaDialogs(options); 14380 } 14381 14382 return this._mdialogs; 14383 }, 14384 14385 /** 14386 * Refresh the dialog collection associated with this media. 14387 * The refresh will happen only if the media dialogs have been initialized. 14388 */ 14389 refreshMediaDialogs: function() { 14390 if ( this._mdialogs ) { 14391 this._mdialogs.refresh(); 14392 } 14393 }, 14394 14395 /** 14396 * Set the maxDialogLimit, interruptAction, and dialogLogoutAction settings that the application will use for 14397 * this media. In the event of a failure, these options will be set on the new Finesse server. 14398 * 14399 * @param {Object} mediaOptions an object with the following properties:<ul> 14400 * <li><b>maxDialogLimit:</b>The maximum number of tasks that is allowed to handle concurrently</li> 14401 * <li><b>interruptAction:</b> Accept or ignore interrupts</li> 14402 * <li><b>dialogLogoutAction:</b> transfer or close the task at logout time</li></ul> 14403 */ 14404 setMediaOptions: function(mediaOptions) { 14405 if ( mediaOptions ) { 14406 this._mediaOptions = mediaOptions; 14407 if ( !this._optionsHelper ) { 14408 this._optionsHelper = new MediaOptionsHelper(this, this._mediaOptions); 14409 } 14410 } 14411 }, 14412 14413 /** 14414 * Refresh this media object and optionally refresh the list of media dialogs associated with this object. 14415 * 14416 * @param {Integer} retries the number of times to retry synchronizing this media object. 14417 */ 14418 refresh: function(retries) { 14419 retries = retries || 1; 14420 this._synchronize(retries); 14421 this.refreshMediaDialogs(); 14422 }, 14423 14424 /** 14425 * Returns true if the user in work state on this Media. 14426 * @returns {boolean} true if the media is in work state; false otherwise 14427 */ 14428 isInWorkState: function() { 14429 return this.getState() === Media.States.WORK; 14430 }, 14431 14432 /** 14433 * Returns true if the user in any state except LOGOUT on this Media. 14434 * @returns {boolean} returns true if the agent is in any state except LOGOUT in this media 14435 */ 14436 isLoggedIn: function() { 14437 var state = this.getState(); 14438 return state && state !== Media.States.LOGOUT; 14439 } 14440 }); 14441 14442 Media.States = /** @lends finesse.restservices.Media.States.prototype */ { 14443 /** 14444 * User Login on a non-voice Media. Note that while this is an action, is not technically a state, since a 14445 * logged-in User will always be in a specific state (READY, NOT_READY, etc.). 14446 */ 14447 LOGIN: "LOGIN", 14448 /** 14449 * User is logged out of this Media. 14450 */ 14451 LOGOUT: "LOGOUT", 14452 /** 14453 * User is not ready on this Media. 14454 */ 14455 NOT_READY: "NOT_READY", 14456 /** 14457 * User is ready for activity on this Media. 14458 */ 14459 READY: "READY", 14460 /** 14461 * User has a dialog coming in, but has not accepted it. 14462 */ 14463 RESERVED: "RESERVED", 14464 /** 14465 * The dialogs in this media have been interrupted by a dialog in a non-interruptible media. 14466 */ 14467 INTERRUPTED: "INTERRUPTED", 14468 /** 14469 * User enters this state when failing over from one Finesse to the other or when failing over from one 14470 * PG side to the other. 14471 */ 14472 WORK: "WORK", 14473 /** 14474 * @class Possible Media state values. Used in finesse.restservices.Media#setState. 14475 * @constructs 14476 */ 14477 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 14478 14479 }; 14480 14481 window.finesse = window.finesse || {}; 14482 window.finesse.restservices = window.finesse.restservices || {}; 14483 window.finesse.restservices.Media = Media; 14484 14485 return Media; 14486 }); 14487 /** 14488 * JavaScript representation of the Finesse Media collection 14489 * object which contains a list of Media objects. 14490 * 14491 * @requires finesse.clientservices.ClientServices 14492 * @requires Class 14493 * @requires finesse.FinesseBase 14494 * @requires finesse.restservices.RestBase 14495 * @requires finesse.restservices.Media 14496 */ 14497 /** @private */ 14498 define('restservices/MediaList',[ 14499 'restservices/RestCollectionBase', 14500 'restservices/Media', 14501 'restservices/RestBase', 14502 'utilities/Utilities' 14503 ], 14504 function (RestCollectionBase, Media, RestBase, Utilities) { 14505 var MediaList = RestCollectionBase.extend(/** @lends finesse.restservices.MediaList.prototype */{ 14506 14507 /** 14508 * @class 14509 * JavaScript representation of a MediaList collection object. 14510 * @augments finesse.restservices.RestCollectionBase 14511 * @constructs 14512 * @see finesse.restservices.Media 14513 * @example 14514 * mediaList = _user.getMediaList( { 14515 * onCollectionAdd : _handleMediaAdd, 14516 * onCollectionDelete : _handleMediaDelete, 14517 * onLoad : _handleMediaListLoaded 14518 * }); 14519 * 14520 * _mediaCollection = mediaList.getCollection(); 14521 * for (var mediaId in _mediaCollection) { 14522 * if (_mediaCollection.hasOwnProperty(mediaId)) { 14523 * media = _mediaCollection[mediaId]; 14524 * etc... 14525 * } 14526 * } 14527 */ 14528 _fakeConstuctor: function () { 14529 /* This is here to hide the real init constructor from the public docs */ 14530 }, 14531 14532 /** 14533 * @private 14534 * @param {Object} options 14535 * An object with the following properties:<ul> 14536 * <li><b>id:</b> The id of the object being constructed</li> 14537 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 14538 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 14539 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 14540 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 14541 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 14542 * <li><b>status:</b> {Number} The HTTP status code returned</li> 14543 * <li><b>content:</b> {String} Raw string of response</li> 14544 * <li><b>object:</b> {Object} Parsed object of response</li> 14545 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 14546 * <li><b>errorType:</b> {String} Type of error that was caught</li> 14547 * <li><b>errorMessage:</b> {String} Message associated with error</li> 14548 * </ul></li> 14549 * </ul></li> 14550 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 14551 **/ 14552 init: function (options) { 14553 this._super(options); 14554 }, 14555 14556 /** 14557 * @private 14558 * Gets the REST class for the current object - this is the MediaList class. 14559 */ 14560 getRestClass: function () { 14561 return MediaList; 14562 }, 14563 14564 /** 14565 * @private 14566 * Gets the REST class for the objects that make up the collection. - this 14567 * is the Media class. 14568 */ 14569 getRestItemClass: function () { 14570 return Media; 14571 }, 14572 14573 /** 14574 * @private 14575 * Gets the REST type for the current object - this is a "MediaList". 14576 */ 14577 getRestType: function () { 14578 return "MediaList"; 14579 }, 14580 14581 /** 14582 * @private 14583 * Gets the REST type for the objects that make up the collection - this is "Media". 14584 */ 14585 getRestItemType: function () { 14586 return "Media"; 14587 }, 14588 14589 /** 14590 * @private 14591 * Override default to indicates that the collection doesn't support making 14592 * requests. 14593 */ 14594 supportsRequests: true, 14595 14596 /** 14597 * @private 14598 * Override default to indicates that the collection subscribes to its objects. 14599 */ 14600 supportsRestItemSubscriptions: true, 14601 14602 /** 14603 * The REST URL in which this object can be referenced. 14604 * @return {String} 14605 * The REST URI for this object. 14606 * @private 14607 */ 14608 getRestUrl: function () { 14609 var 14610 restObj = this._restObj, 14611 restUrl = ""; 14612 14613 //Prepend the base REST object if one was provided. 14614 if (restObj instanceof RestBase) { 14615 restUrl += restObj.getRestUrl(); 14616 } 14617 //Otherwise prepend with the default webapp name. 14618 else { 14619 restUrl += "/finesse/api"; 14620 } 14621 14622 //Append the REST type. (Media not MediaList) 14623 restUrl += "/" + this.getRestItemType(); 14624 14625 //Append ID if it is not undefined, null, or empty. 14626 if (this._id) { 14627 restUrl += "/" + this._id; 14628 } 14629 14630 return restUrl; 14631 }, 14632 14633 /** 14634 * Returns a specific Media with the id passed, from the MediaList collection. 14635 * * @param {Object} options 14636 * An object with the following properties:<ul> 14637 * <li><b>id:</b> The id of the media to fetch</li> 14638 * <li><b>onLoad(this): (optional)</b> callback handler for when the object is successfully loaded from the server</li> 14639 * <li><b>onChange(this): (optional)</b> callback handler for when an update notification of the object is received</li> 14640 * <li><b>onAdd(this): (optional)</b> callback handler for when a notification that the object is created is received</li> 14641 * <li><b>onDelete(this): (optional)</b> callback handler for when a notification that the object is deleted is received</li> 14642 * <li><b>onError(rsp): (optional)</b> callback handler for if loading of the object fails, invoked with the error response object:<ul> 14643 * <li><b>status:</b> {Number} The HTTP status code returned</li> 14644 * <li><b>content:</b> {String} Raw string of response</li> 14645 * <li><b>object:</b> {Object} Parsed object of response</li> 14646 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 14647 * <li><b>errorType:</b> {String} Type of error that was caught</li> 14648 * <li><b>errorMessage:</b> {String} Message associated with error</li> 14649 * </ul></li> 14650 * </ul></li> 14651 * <li><b>mediaOptions:</b> {Object} An object with the following properties:<ul> 14652 * <li><b>maxDialogLimit:</b> The id of the object being constructed</li> 14653 * <li><b>interruptAction:</b> Accept or ignore interrupts</li> 14654 * <li><b>dialogLogoutAction:</b> transfer or close the task at logout time</li></ul> 14655 * </li></ul> 14656 * 14657 * @returns {finesse.restservices.Media} 14658 * A Media object. 14659 */ 14660 getMedia: function (options) { 14661 this.isLoaded(); 14662 options = options || {}; 14663 var objectId = options.id, 14664 media = this._collection[objectId]; 14665 14666 //throw error if media not found 14667 if(!media) { 14668 throw new Error("No media found with id: " + objectId); 14669 } 14670 14671 media.addHandler('load', options.onLoad); 14672 media.addHandler('change', options.onChange); 14673 media.addHandler('add', options.onAdd); 14674 media.addHandler('delete', options.onDelete); 14675 media.addHandler('error', options.onError); 14676 14677 media.setMediaOptions(options.mediaOptions); 14678 14679 return media; 14680 }, 14681 14682 /** 14683 * Utility method to build the internal collection data structure (object) based on provided data 14684 * @param {Object} data 14685 * The data to build the internal collection from 14686 * @private 14687 */ 14688 _buildCollection: function (data) { 14689 //overriding this from RestBaseCollection because we need to pass in parentObj 14690 //because restUrl is finesse/api/User/<useri>/Media/<mediaId> 14691 //other objects like dialog have finesse/api/Dialog/<dialogId> 14692 14693 var i, object, objectId, dataArray; 14694 if (data && this.getProperty(data, this.getRestItemType()) !== null) { 14695 dataArray = Utilities.getArray(this.getProperty(data, this.getRestItemType())); 14696 for (i = 0; i < dataArray.length; i += 1) { 14697 14698 object = {}; 14699 object[this.getRestItemType()] = dataArray[i]; 14700 14701 //get id from object.id instead of uri, since uri is not there for some reason 14702 objectId = object[this.getRestItemType()].id;//this._extractId(object); 14703 14704 //create the Media Object 14705 this._collection[objectId] = new (this.getRestItemClass())({ 14706 id: objectId, 14707 data: object, 14708 parentObj: this._restObj 14709 }); 14710 this.length += 1; 14711 } 14712 } 14713 } 14714 }); 14715 14716 window.finesse = window.finesse || {}; 14717 window.finesse.restservices = window.finesse.restservices || {}; 14718 window.finesse.restservices.MediaList = MediaList; 14719 14720 return MediaList; 14721 }); 14722 14723 /** 14724 * JavaScript representation of the Finesse ReasonCodes collection 14725 * object which contains a list of ReasonCodes objects. 14726 * 14727 * @requires Class 14728 */ 14729 14730 /** @private */ 14731 define('restservices/ReasonCodes',[], function () { 14732 14733 var ReasonCodes = Class.extend(/** @lends finesse.restservices.ReasonCodes.prototype */{ 14734 14735 /** 14736 * @class 14737 * JavaScript representation of a ReasonCodes collection object. 14738 * @augments Class 14739 * @constructs 14740 * @example 14741 * _user.getNotReadyReasonCodes({ 14742 * success: handleNotReadyReasonCodesSuccess, 14743 * error: handleNotReadyReasonCodesError 14744 * }); 14745 * 14746 * handleNotReadyReasonCodesSuccess = function(reasonCodes) { 14747 * for(var i = 0; i < reasonCodes.length; i++) { 14748 * var reasonCode = reasonCodes[i]; 14749 * var reasonCodeId = reasonCode.id; 14750 * var reasonCodeLabel = reasonCode.label; 14751 * } 14752 * } 14753 * } 14754 */ 14755 14756 _fakeConstuctor: function () { 14757 /* This is here to hide the real init constructor from the public docs */ 14758 } 14759 14760 }); 14761 14762 window.finesse = window.finesse || {}; 14763 window.finesse.restservices = window.finesse.restservices || {}; 14764 window.finesse.restservices.ReasonCodes = ReasonCodes; 14765 14766 return ReasonCodes; 14767 }); 14768 14769 /** 14770 * JavaScript representation of the Finesse User object 14771 * 14772 * @requires finesse.clientservices.ClientServices 14773 * @requires Class 14774 * @requires finesse.FinesseBase 14775 * @requires finesse.restservices.RestBase 14776 */ 14777 14778 /** @private */ 14779 define('restservices/User',[ 14780 'restservices/RestBase', 14781 'restservices/Dialogs', 14782 'restservices/ClientLog', 14783 'restservices/Queues', 14784 'restservices/WrapUpReasons', 14785 'restservices/PhoneBooks', 14786 'restservices/Workflows', 14787 'restservices/UserMediaPropertiesLayout', 14788 'restservices/UserMediaPropertiesLayouts', 14789 'restservices/Media', 14790 'restservices/MediaList', 14791 'restservices/ReasonCodes', 14792 'utilities/Utilities' 14793 ], 14794 function (RestBase, Dialogs, ClientLog, Queues, WrapUpReasons, PhoneBooks, Workflows, UserMediaPropertiesLayout, UserMediaPropertiesLayouts, Media, MediaList, ReasonCodes, Utilities) { 14795 14796 var User = RestBase.extend(/** @lends finesse.restservices.User.prototype */{ 14797 14798 _dialogs : null, 14799 _clientLogObj : null, 14800 _wrapUpReasons : null, 14801 _phoneBooks : null, 14802 _workflows : null, 14803 _mediaPropertiesLayout : null, 14804 _mediaPropertiesLayouts : null, 14805 _queues : null, 14806 media : null, 14807 mediaList : null, 14808 14809 /** 14810 * @class 14811 * The User represents a Finesse Agent or Supervisor. 14812 * 14813 * @param {Object} options 14814 * An object with the following properties:<ul> 14815 * <li><b>id:</b> The id of the object being constructed</li> 14816 * <li><b>onLoad(this): (optional)</b> callback handler for when the object is successfully loaded from the server</li> 14817 * <li><b>onChange(this): (optional)</b> callback handler for when an update notification of the object is received</li> 14818 * <li><b>onAdd(this): (optional)</b> callback handler for when a notification that the object is created is received</li> 14819 * <li><b>onDelete(this): (optional)</b> callback handler for when a notification that the object is deleted is received</li> 14820 * <li><b>onError(rsp): (optional)</b> callback handler for if loading of the object fails, invoked with the error response object:<ul> 14821 * <li><b>status:</b> {Number} The HTTP status code returned</li> 14822 * <li><b>content:</b> {String} Raw string of response</li> 14823 * <li><b>object:</b> {Object} Parsed object of response</li> 14824 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 14825 * <li><b>errorType:</b> {String} Type of error that was caught</li> 14826 * <li><b>errorMessage:</b> {String} Message associated with error</li> 14827 * </ul></li> 14828 * </ul></li> 14829 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 14830 * @augments finesse.restservices.RestBase 14831 * @constructs 14832 * @example 14833 * _user = new finesse.restservices.User({ 14834 * id: _id, 14835 * onLoad : _handleUserLoad, 14836 * onChange : _handleUserChange 14837 * }); 14838 **/ 14839 init: function (options) { 14840 this._super(options); 14841 }, 14842 14843 Callbacks: {}, 14844 14845 /** 14846 * @private 14847 * Gets the REST class for the current object - this is the User object. 14848 * @returns {Object} 14849 * The User constructor. 14850 */ 14851 getRestClass: function () { 14852 return User; 14853 }, 14854 14855 /** 14856 * @private 14857 * Gets the REST type for the current object - this is a "User". 14858 * @returns {String} 14859 * The User String 14860 */ 14861 getRestType: function () { 14862 return "User"; 14863 }, 14864 /** 14865 * @private 14866 * overloading this to return URI 14867 * @returns {String} 14868 * The REST URI for this object. 14869 */ 14870 getXMPPNodePath: function () { 14871 return this.getRestUrl(); 14872 }, 14873 /** 14874 * @private 14875 * Returns whether this object supports subscriptions 14876 * @returns {Boolean} True 14877 */ 14878 supportsSubscriptions: function () { 14879 return true; 14880 }, 14881 14882 /** 14883 * Getter for the firstName of this User. 14884 * @returns {String} 14885 * The firstName for this User 14886 */ 14887 getFirstName: function () { 14888 this.isLoaded(); 14889 return Utilities.convertNullToEmptyString(this.getData().firstName); 14890 }, 14891 14892 14893 /** 14894 * Getter for the reasonCode of this User. 14895 * 14896 * @returns {Object} The reasonCode for the state of the User<br> 14897 * The contents may include the following:<ul> 14898 * <li>uri: The URI for the reason code object. 14899 * <li>id: The unique ID for the reason code. 14900 * <li>category: The category. Can be either NOT_READY or LOGOUT. 14901 * <li>code: The numeric reason code value. 14902 * <li>label: The label for the reason code. 14903 * <li>forAll: Boolean flag that denotes the global status for the reason code. 14904 * <li>systemCode: Boolean flag which denotes whether the reason code is system generated or custom one. 14905 * </ul> 14906 * 14907 */ 14908 getReasonCode : function() { 14909 this.isLoaded(); 14910 return this.getData().reasonCode; 14911 }, 14912 14913 /** 14914 * Getter for the pending state reasonCode of this User. 14915 * 14916 * @returns {Object} The reasonCode for the pending state of the User.<br> 14917 * The contents may include the following:<ul> 14918 * <li>uri: The URI for the reason code object. 14919 * <li>id: The unique ID for the reason code. 14920 * <li>category: The category. Can be either NOT_READY or LOGOUT. 14921 * <li>code: The numeric reason code value. 14922 * <li>label: The label for the reason code. 14923 * <li>forAll: Boolean flag that denotes the global status for the reason code. 14924 * <li>systemCode: Boolean flag which denotes whether the reason code is system generated or custom one. 14925 * </ul> 14926 */ 14927 getPendingStateReasonCode : function() { 14928 this.isLoaded(); 14929 return this.getData().pendingStateReasonCode; 14930 }, 14931 14932 14933 /** 14934 * Getter for the reasonCode of this User. 14935 * 14936 * @returns {Boolean} True if the reason code for the state of the user 14937 * is a system generated reason code. 14938 */ 14939 isReasonCodeReserved : function() { 14940 var resonCode = this.getReasonCode(); 14941 if (resonCode) { 14942 return resonCode.systemCode === "true" ? true 14943 : false; 14944 } 14945 return false; 14946 }, 14947 14948 14949 /** 14950 * Getter for the lastName of this User. 14951 * @returns {String} 14952 * The lastName for this User 14953 */ 14954 getLastName: function () { 14955 this.isLoaded(); 14956 return Utilities.convertNullToEmptyString(this.getData().lastName); 14957 }, 14958 14959 /** 14960 * Getter for the full name of this User. 14961 * The full name is of the format "FirstName LastName" (for example: "John Doe") 14962 * 14963 * @returns {String} 14964 * The full name of this User. 14965 */ 14966 getFullName : function() { 14967 this.isLoaded(); 14968 14969 /* 14970 * Currently, the expected format is "FirstName LastName", but can differ later 14971 * to something "LastName, FirstName". 14972 * To accommodate such, we use formatString utility method so that, if required, 14973 * the same can be achieved just by flipping the format place holders as follows: 14974 * "{1}, {0}" 14975 * Also, the function could be enhanced to take the format parameter. 14976 */ 14977 var fmt = "{0} {1}"; 14978 return Utilities.formatString(fmt, 14979 Utilities.convertNullToEmptyString(this.getData().firstName), 14980 Utilities.convertNullToEmptyString(this.getData().lastName)); 14981 }, 14982 14983 /** 14984 * Getter for the extension of this User. 14985 * @returns {String} 14986 * The extension, if any, of this User 14987 */ 14988 getExtension: function () { 14989 this.isLoaded(); 14990 return Utilities.convertNullToEmptyString(this.getData().extension); 14991 }, 14992 14993 /** 14994 * Getter for the id of the Team of this User 14995 * @returns {String} 14996 * The current (or last fetched) id of the Team of this User 14997 */ 14998 getTeamId: function () { 14999 this.isLoaded(); 15000 return this.getData().teamId; 15001 }, 15002 15003 /** 15004 * Getter for the name of the Team of this User 15005 * @returns {String} 15006 * The current (or last fetched) name of the Team of this User 15007 */ 15008 getTeamName: function () { 15009 this.isLoaded(); 15010 return this.getData().teamName; 15011 }, 15012 15013 /** 15014 * Is user an agent? 15015 * @returns {Boolean} True if user has role of agent, else false. 15016 */ 15017 hasAgentRole: function () { 15018 this.isLoaded(); 15019 return this.hasRole("Agent"); 15020 }, 15021 15022 /** 15023 * Is user a supervisor? 15024 * @returns {Boolean} True if user has role of supervisor, else false. 15025 */ 15026 hasSupervisorRole: function () { 15027 this.isLoaded(); 15028 return this.hasRole("Supervisor"); 15029 }, 15030 15031 /** 15032 * @private 15033 * Checks to see if user has "theRole" 15034 * @returns {Boolean} True if "theRole" has the role of supervisor or agent, else false. 15035 */ 15036 hasRole: function (theRole) { 15037 this.isLoaded(); 15038 var result = false, i, roles, len; 15039 15040 roles = this.getData().roles.role; 15041 len = roles.length; 15042 if (typeof roles === 'string') { 15043 if (roles === theRole) { 15044 result = true; 15045 } 15046 } else { 15047 for (i = 0; i < len ; i = i + 1) { 15048 if (roles[i] === theRole) { 15049 result = true; 15050 break; 15051 } 15052 } 15053 } 15054 15055 return result; 15056 }, 15057 15058 /** 15059 * Getter for the pending state of this User. 15060 * @returns {String} 15061 * The pending state of this User 15062 * @see finesse.restservices.User.States 15063 */ 15064 getPendingState: function () { 15065 this.isLoaded(); 15066 return Utilities.convertNullToEmptyString(this.getData().pendingState); 15067 }, 15068 15069 15070 /** 15071 * Getter for the work mode timer for the user 15072 * @returns {String} 15073 * The WrapUpTimer for the user 15074 * @since 12.0.1 15075 */ 15076 getWrapUpTimer: function () { 15077 this.isLoaded(); 15078 return this.getData().wrapUpTimer; 15079 }, 15080 15081 /** 15082 * Getter for the state of this User. 15083 * @returns {String} 15084 * The current (or last fetched) state of this User 15085 * @see finesse.restservices.User.States 15086 */ 15087 getState: function () { 15088 this.isLoaded(); 15089 return this.getData().state; 15090 }, 15091 15092 /** 15093 * Getter for the media state of this User. 15094 * @returns {String} 15095 * The current (or last fetched) media state of this User 15096 * Will be applicable only in CCX deployments 15097 * When the agent is talking on a manual outbound call, it returns busy 15098 * The value will not be present in other cases. 15099 */ 15100 getMediaState: function () { 15101 this.isLoaded(); 15102 /* There is an assertion that the value should not be undefined while setting the value in datastore. Hence setting it to null*/ 15103 if(this.getData().mediaState === undefined) { 15104 this.getData().mediaState = null; 15105 } 15106 15107 return this.getData().mediaState; 15108 }, 15109 15110 /** 15111 * Getter for the state change time of this User. 15112 * @returns {String} 15113 * The state change time of this User 15114 */ 15115 getStateChangeTime: function () { 15116 this.isLoaded(); 15117 return this.getData().stateChangeTime; 15118 }, 15119 15120 /** 15121 * Getter for the wrap-up mode of this User. 15122 * @returns {String} The wrap-up mode of this user that is a value of {@link finesse.restservices.User.WrapUpMode} 15123 * @see finesse.restservices.User.WrapUpMode 15124 */ 15125 getWrapUpOnIncoming: function () { 15126 this.isLoaded(); 15127 return this.getData().settings.wrapUpOnIncoming; 15128 }, 15129 15130 /** 15131 * Is User required to go into wrap-up? 15132 * @return {Boolean} 15133 * True if this agent is required to go into wrap-up. 15134 * @see finesse.restservices.User.WrapUpMode 15135 */ 15136 isWrapUpRequired: function () { 15137 return (this.getWrapUpOnIncoming() === User.WrapUpMode.REQUIRED || 15138 this.getWrapUpOnIncoming() === User.WrapUpMode.REQUIRED_WITH_WRAP_UP_DATA); 15139 }, 15140 15141 /** 15142 * Checks to see if the user is considered a mobile agent by checking for 15143 * the existence of the mobileAgent node. 15144 * @returns {Boolean} 15145 * True if this agent is a mobile agent. 15146 */ 15147 isMobileAgent: function () { 15148 this.isLoaded(); 15149 var ma = this.getData().mobileAgent; 15150 return ma !== null && typeof ma === "object"; 15151 }, 15152 15153 /** 15154 * Getter for the mobile agent work mode. 15155 * @returns {finesse.restservices.User.WorkMode} 15156 * If available, return the mobile agent work mode, otherwise null. 15157 * @see finesse.restservices.User.WorkMode 15158 */ 15159 getMobileAgentMode: function () { 15160 this.isLoaded(); 15161 if (this.isMobileAgent()) { 15162 return this.getData().mobileAgent.mode; 15163 } 15164 return null; 15165 }, 15166 15167 /** 15168 * Getter for the mobile agent dial number. 15169 * @returns {String} 15170 * If available, return the mobile agent dial number, otherwise null. 15171 */ 15172 getMobileAgentDialNumber: function () { 15173 this.isLoaded(); 15174 if (this.isMobileAgent()) { 15175 return this.getData().mobileAgent.dialNumber; 15176 } 15177 return null; 15178 }, 15179 15180 /** 15181 * Getter for a Dialogs collection object that is associated with User. 15182 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 15183 * applicable when Object has not been previously created). 15184 * @returns {finesse.restservices.Dialogs} 15185 * A Dialogs collection object. 15186 * @see finesse.restservices.Dialogs 15187 */ 15188 getDialogs: function (callbacks) { 15189 var options = callbacks || {}; 15190 options.parentObj = this; 15191 this.isLoaded(); 15192 15193 if (this._dialogs === null) { 15194 this._dialogs = new Dialogs(options); 15195 } 15196 15197 return this._dialogs; 15198 }, 15199 15200 /** 15201 * Getter for a Dialogs collection object that is associated with User.This will always query from server 15202 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers 15203 * @returns {finesse.restservices.Dialogs} 15204 * A Dialogs collection object. 15205 * @see finesse.restservices.Dialogs 15206 */ 15207 getDialogsNoCache: function (callbacks) { 15208 var options = callbacks || {}; 15209 options.parentObj = this; 15210 this.isLoaded(); 15211 this._dialogs = new Dialogs(options); 15212 15213 return this._dialogs; 15214 }, 15215 15216 /** 15217 * Getter for Media collection object that is associated with User. 15218 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 15219 * applicable when Object has not been previously created). 15220 * @returns {finesse.restservices.MediaList} 15221 * A Media Dialogs collection object. 15222 * @see finesse.restservices.MediaList 15223 */ 15224 getMediaList: function (callbacks) { 15225 var options = callbacks || {}; 15226 options.parentObj = this; 15227 this.isLoaded(); 15228 15229 if (this.mediaList === null) { 15230 this.mediaList = new MediaList(options); 15231 } 15232 15233 return this.mediaList; 15234 }, 15235 15236 /** 15237 * @private 15238 * Getter for a ClientLog object that is associated with User. 15239 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 15240 * applicable when Object has not been previously created). 15241 * @returns {finesse.restservices.ClientLog} 15242 * A ClientLog collection object. 15243 * @see finesse.restservices.ClientLog 15244 */ 15245 getClientLog: function (callbacks) { 15246 var options = callbacks || {}; 15247 options.parentObj = this; 15248 this.isLoaded(); 15249 15250 if (this._clientLogObj === null) { 15251 this._clientLogObj = new ClientLog(options); 15252 } 15253 else { 15254 if(options.onLoad && typeof options.onLoad === "function") { 15255 options.onLoad(this._clientLogObj); 15256 } 15257 } 15258 return this._clientLogObj; 15259 }, 15260 15261 /** 15262 * Getter for a Queues collection object that is associated with User. 15263 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 15264 * applicable when Object has not been previously created). 15265 * @returns {finesse.restservices.Queues} 15266 * A Queues collection object. 15267 * @see finesse.restservices.Queues 15268 */ 15269 getQueues: function (callbacks) { 15270 var options = callbacks || {}; 15271 options.parentObj = this; 15272 this.isLoaded(); 15273 15274 if (this._queues === null) { 15275 this._queues = new Queues(options); 15276 } 15277 15278 return this._queues; 15279 }, 15280 15281 /** 15282 * Getter for a WrapUpReasons collection object that is associated with User. 15283 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 15284 * applicable when Object has not been previously created). 15285 * @returns {finesse.restservices.WrapUpReasons} 15286 * A WrapUpReasons collection object. 15287 * @see finesse.restservices.WrapUpReasons 15288 */ 15289 getWrapUpReasons: function (callbacks) { 15290 var options = callbacks || {}; 15291 options.parentObj = this; 15292 this.isLoaded(); 15293 15294 if (this._wrapUpReasons === null) { 15295 this._wrapUpReasons = new WrapUpReasons(options); 15296 } 15297 15298 return this._wrapUpReasons; 15299 }, 15300 15301 /** 15302 * Getter for a PhoneBooks collection object that is associated with User. 15303 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 15304 * applicable when Object has not been previously created). 15305 * @returns {finesse.restservices.PhoneBooks} 15306 * A PhoneBooks collection object. 15307 * @see finesse.restservices.PhoneBooks 15308 */ 15309 getPhoneBooks: function (callbacks) { 15310 var options = callbacks || {}; 15311 options.parentObj = this; 15312 this.isLoaded(); 15313 15314 if (this._phoneBooks === null) { 15315 this._phoneBooks = new PhoneBooks(options); 15316 } 15317 15318 return this._phoneBooks; 15319 }, 15320 15321 /** 15322 * @private 15323 * Loads the Workflows collection object that is associated with User and 15324 * 'returns' them to the caller via the handlers. 15325 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 15326 * applicable when Object has not been previously created). 15327 * @see finesse.restservices.Workflow 15328 * @see finesse.restservices.Workflows 15329 * @see finesse.restservices.RestCollectionBase 15330 */ 15331 loadWorkflows: function (callbacks) { 15332 var options = callbacks || {}; 15333 options.parentObj = this; 15334 this.isLoaded(); 15335 15336 if (this._workflows === null) { 15337 this._workflows = new Workflows(options); 15338 } else { 15339 this._workflows.refresh(); 15340 } 15341 15342 }, 15343 15344 /** 15345 * Getter for a UserMediaPropertiesLayout object that is associated with User. 15346 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 15347 * applicable when Object has not been previously created). 15348 * @returns {finesse.restservices.UserMediaPropertiesLayout} 15349 * The UserMediaPropertiesLayout object associated with this user 15350 * @see finesse.restservices.UserMediaPropertiesLayout 15351 */ 15352 getMediaPropertiesLayout: function (callbacks) { 15353 var options = callbacks || {}; 15354 options.parentObj = this; 15355 options.id = this._id; 15356 15357 this.isLoaded(); 15358 if (this._mediaPropertiesLayout === null) { 15359 this._mediaPropertiesLayout = new UserMediaPropertiesLayout(options); 15360 } 15361 return this._mediaPropertiesLayout; 15362 }, 15363 15364 /** 15365 * Getter for a UserMediaPropertiesLayouts object that is associated with User. 15366 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 15367 * applicable when Object has not been previously created). 15368 * @returns {finesse.restservices.UserMediaPropertiesLayout} 15369 * The UserMediaPropertiesLayout object associated with this user 15370 * @see finesse.restservices.UserMediaPropertiesLayout 15371 */ 15372 getMediaPropertiesLayouts: function (callbacks) { 15373 var options = callbacks || {}; 15374 options.parentObj = this; 15375 15376 this.isLoaded(); 15377 if (this._mediaPropertiesLayouts === null) { 15378 this._mediaPropertiesLayouts = new UserMediaPropertiesLayouts(options); 15379 } 15380 return this._mediaPropertiesLayouts; 15381 }, 15382 15383 /** 15384 * Getter for the Teams managed by this User(Supervisor), if any. 15385 * 15386 * @returns {Array} of objects containing id, name, uri of the Teams managed by this User(Supervisor).<br> 15387 * The object content includes the following:<ul> 15388 * <li>id: The unique ID for the team. 15389 * <li>name: The team name for the team. 15390 * <li>uri: The URI for the team. 15391 * </ul> 15392 * 15393 */ 15394 getSupervisedTeams: function () { 15395 this.isLoaded(); 15396 15397 try { 15398 return Utilities.getArray(this.getData().teams.Team); 15399 } catch (e) { 15400 return []; 15401 } 15402 15403 }, 15404 15405 /** 15406 * Perform an agent login for this user, associating him with the 15407 * specified extension. 15408 * @param {Object} params 15409 * An object containing properties for agent login. 15410 * @param {String} params.reasonCodeId 15411 * The reason code id associated with this login 15412 * @param {String} params.extension 15413 * The extension to associate with this user 15414 * @param {Object} [params.mobileAgent] 15415 * A mobile agent object containing the mode and dial number properties. 15416 * @param {finesse.interfaces.RequestHandlers} params.handlers 15417 * @see finesse.interfaces.RequestHandlers 15418 * @returns {finesse.restservices.User} 15419 * This User object, to allow cascading 15420 * @private 15421 */ 15422 _login: function (params) { 15423 var handlers, contentBody = {}, 15424 restType = this.getRestType(); 15425 15426 // Protect against null dereferencing. 15427 params = params || {}; 15428 15429 contentBody[restType] = { 15430 "state": User.States.LOGIN, 15431 "extension": params.extension 15432 }; 15433 15434 if(params.reasonCodeId){ 15435 contentBody[restType].reasonCodeId = params.reasonCodeId 15436 } 15437 15438 // Create mobile agent node if available. 15439 if (typeof params.mobileAgent === "object") { 15440 contentBody[restType].mobileAgent = { 15441 "mode": params.mobileAgent.mode, 15442 "dialNumber": params.mobileAgent.dialNumber 15443 }; 15444 } 15445 15446 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 15447 handlers = params.handlers || {}; 15448 15449 this.restRequest(this.getRestUrl(), { 15450 method: 'PUT', 15451 success: handlers.success, 15452 error: handlers.error, 15453 content: contentBody 15454 }); 15455 15456 return this; // Allow cascading 15457 }, 15458 15459 /** 15460 * Perform an agent login for this user, associating him with the 15461 * specified extension. 15462 * @param {String} extension 15463 * The extension to associate with this user 15464 * @param {finesse.interfaces.RequestHandlers} handlers 15465 * An object containing the handlers for the request 15466 * @returns {finesse.restservices.User} 15467 * This User object, to allow cascading 15468 */ 15469 login: function (extension, handlers) { 15470 this.isLoaded(); 15471 var params = { 15472 "extension": extension, 15473 "handlers": handlers 15474 }; 15475 return this._login(params); 15476 }, 15477 15478 15479 15480 15481 /** 15482 * Perform an agent login for this user, associating him with the 15483 * specified extension. 15484 * @param {String} extension 15485 * The extension to associate with this user 15486 * @param {String} mode 15487 * The mobile agent work mode as defined in finesse.restservices.User.WorkMode. 15488 * @param {String} extension 15489 * The external dial number desired to be used by the mobile agent. 15490 * @param {finesse.interfaces.RequestHandlers} handlers 15491 * An object containing the handlers for the request 15492 * @param {Object} reasonCode 15493 * An object containing the reasonCode for the login request 15494 * @returns {finesse.restservices.User} 15495 * This User object, to allow cascading 15496 */ 15497 loginMobileAgent: function (extension, mode, dialNumber, handlers, reasonCode) { 15498 this.isLoaded(); 15499 15500 var params = { 15501 "extension": extension, 15502 "mobileAgent": { 15503 "mode": mode, 15504 "dialNumber": dialNumber 15505 }, 15506 "handlers": handlers 15507 }; 15508 //US303866 - reasonCode added for restoring MobileAgent after CTI client disconnect 15509 if(reasonCode) { 15510 params.reasonCodeId = reasonCode.id; 15511 } 15512 return this._login(params); 15513 }, 15514 15515 15516 _updateMobileAgent: function (params) { 15517 var handlers, contentBody = {}, 15518 restType = this.getRestType(); 15519 15520 params = params || {}; 15521 15522 contentBody[restType] = { 15523 }; 15524 15525 if (typeof params.mobileAgent === "object") { 15526 contentBody[restType].mobileAgent = { 15527 "mode": params.mobileAgent.mode, 15528 "dialNumber": params.mobileAgent.dialNumber 15529 }; 15530 } 15531 15532 handlers = params.handlers || {}; 15533 15534 this.restRequest(this.getRestUrl(), { 15535 method: 'PUT', 15536 success: handlers.success, 15537 error: handlers.error, 15538 content: contentBody 15539 }); 15540 15541 return this; 15542 }, 15543 15544 /** 15545 * Update user object in Finesse with agent's mobile login information (Refer defect CSCvc35407) 15546 * @param {String} mode 15547 * The mobile agent work mode as defined in finesse.restservices.User.WorkMode. 15548 * @param {String} dialNumber 15549 * The external dial number desired to be used by the mobile agent. 15550 * @param {finesse.interfaces.RequestHandlers} handlers 15551 * An object containing the handlers for the request 15552 * @returns {finesse.restservices.User} 15553 * This User object, to allow cascading 15554 */ 15555 updateToMobileAgent: function (mode, dialNumber, handlers) { 15556 this.isLoaded(); 15557 var params = { 15558 "mobileAgent": { 15559 "mode": mode, 15560 "dialNumber": dialNumber 15561 }, 15562 "handlers": handlers 15563 }; 15564 return this._updateMobileAgent(params); 15565 }, 15566 15567 /** 15568 * Perform an agent logout for this user. 15569 * @param {String} reasonCode 15570 * The reason this user is logging out. Pass null for no reason. 15571 * @param {finesse.interfaces.RequestHandlers} handlers 15572 * An object containing the handlers for the request 15573 * @returns {finesse.restservices.User} 15574 * This User object, to allow cascading 15575 */ 15576 logout: function (reasonCode, handlers) { 15577 return this.setState("LOGOUT", reasonCode, handlers); 15578 }, 15579 15580 /** 15581 * Set the state of the user. 15582 * @param {String} newState 15583 * The state you are setting 15584 * @param {ReasonCode} reasonCode 15585 * The reason this user is logging out. Pass null for no reason. 15586 * @param {finesse.interfaces.RequestHandlers} handlers 15587 * An object containing the handlers for the request 15588 * @see finesse.restservices.User.States 15589 * @returns {finesse.restservices.User} 15590 * This User object, to allow cascading 15591 */ 15592 setState: function (newState, reasonCode, handlers) { 15593 this.isLoaded(); 15594 15595 var options, contentBody = {}; 15596 15597 if (!reasonCode) { 15598 contentBody[this.getRestType()] = { 15599 "state": newState 15600 }; 15601 15602 } else { 15603 contentBody[this.getRestType()] = { 15604 "state": newState, 15605 "reasonCodeId": reasonCode.id 15606 }; 15607 15608 } 15609 15610 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 15611 handlers = handlers || {}; 15612 15613 options = { 15614 method: 'PUT', 15615 success: handlers.success, 15616 error: handlers.error, 15617 content: contentBody 15618 }; 15619 15620 // After removing the selective 202 handling, we should be able to just use restRequest 15621 this.restRequest(this.getRestUrl(), options); 15622 15623 return this; // Allow cascading 15624 }, 15625 15626 /** 15627 * Make call to a particular phone number. 15628 * 15629 * @param {String} 15630 * The number to call 15631 * @param {finesse.interfaces.RequestHandlers} handlers 15632 * An object containing the handlers for the request 15633 * @returns {finesse.restservices.User} 15634 * This User object, to allow cascading 15635 */ 15636 makeCall: function (number, handlers) { 15637 this.isLoaded(); 15638 15639 this.getDialogs().createNewCallDialog(number, this.getExtension(), handlers); 15640 15641 return this; // Allow cascading 15642 }, 15643 15644 /** 15645 * Make a silent monitor call to a particular agent's phone number. 15646 * 15647 * @param {String} 15648 * The number to call 15649 * @param {finesse.interfaces.RequestHandlers} handlers 15650 * An object containing the handlers for the request 15651 * @returns {finesse.restservices.User} 15652 * This User object, to allow cascading 15653 */ 15654 makeSMCall: function (number, handlers) { 15655 this.isLoaded(); 15656 15657 var actionType = "SILENT_MONITOR"; 15658 15659 this.getDialogs().createNewSuperviseCallDialog(number, this.getExtension(), actionType, handlers); 15660 15661 return this; // Allow cascading 15662 }, 15663 15664 15665 /** 15666 * Make a silent monitor call to a particular agent's phone number. 15667 * 15668 * @param {String} 15669 * The number to call 15670 * @param {String} dialogUri 15671 * The associated dialog uri of SUPERVISOR_MONITOR call 15672 * @param {finesse.interfaces.RequestHandlers} handlers 15673 * An object containing the handlers for the request 15674 * @see finesse.restservices.dialog 15675 * @returns {finesse.restservices.User} 15676 * This User object, to allow cascading 15677 */ 15678 makeBargeCall:function (number, dialogURI, handlers) { 15679 this.isLoaded(); 15680 var actionType = "BARGE_CALL"; 15681 this.getDialogs().createNewBargeCall( this.getExtension(), number, actionType, dialogURI,handlers); 15682 15683 return this; // Allow cascading 15684 }, 15685 15686 /** 15687 * Returns true if the user's current state will result in a pending state change. A pending state 15688 * change is a request to change state that does not result in an immediate state change. For 15689 * example if an agent attempts to change to the NOT_READY state while in the TALKING state, the 15690 * agent will not change state until the call ends. 15691 * 15692 * The current set of states that result in pending state changes is as follows: 15693 * TALKING 15694 * HOLD 15695 * RESERVED_OUTBOUND_PREVIEW 15696 * @returns {Boolean} True if there is a pending state change. 15697 * @see finesse.restservices.User.States 15698 */ 15699 isPendingStateChange: function () { 15700 var state = this.getState(); 15701 return state && ((state === User.States.TALKING) || (state === User.States.HOLD) || (state === User.States.RESERVED_OUTBOUND_PREVIEW)); 15702 }, 15703 15704 /** 15705 * Returns true if the user's current state is WORK or WORK_READY. This is used so 15706 * that a pending state is not cleared when moving into wrap up (work) mode. 15707 * Note that we don't add this as a pending state, since changes while in wrap up 15708 * occur immediately (and we don't want any "pending state" to flash on screen. 15709 * 15710 * @see finesse.restservices.User.States 15711 * @returns {Boolean} True if user is in wrap-up mode. 15712 */ 15713 isWrapUp: function () { 15714 var state = this.getState(); 15715 return state && ((state === User.States.WORK) || (state === User.States.WORK_READY)); 15716 }, 15717 15718 /** 15719 * @private 15720 * Parses a uriString to retrieve the id portion 15721 * @param {String} uriString 15722 * @return {String} id 15723 */ 15724 _parseIdFromUriString : function (uriString) { 15725 return Utilities.getId(uriString); 15726 }, 15727 15728 /** 15729 * Gets the user's Reason Code label. Works for both Not Ready and 15730 * Logout reason codes 15731 * 15732 * @return {String} the reason code label, or empty string if none 15733 */ 15734 getReasonCodeLabel : function() { 15735 this.isLoaded(); 15736 15737 if (this.getData().reasonCode) { 15738 return this.getData().reasonCode.label; 15739 } else { 15740 return ""; 15741 } 15742 }, 15743 15744 /** 15745 * Gets the user's Not Ready reason code. 15746 * 15747 * @return {String} Reason Code Id, or undefined if not set or 15748 * indeterminate 15749 */ 15750 getNotReadyReasonCodeId : function () { 15751 this.isLoaded(); 15752 15753 var reasoncodeIdResult, finesseServerReasonCodeId; 15754 finesseServerReasonCodeId = this.getData().reasonCodeId; 15755 15756 //FinesseServer will give "-l" => we will set to undefined (for convenience) 15757 if (finesseServerReasonCodeId !== "-1") { 15758 reasoncodeIdResult = finesseServerReasonCodeId; 15759 } 15760 15761 return reasoncodeIdResult; 15762 }, 15763 15764 /** 15765 * Performs a GET against the Finesse server looking up the reasonCodeId specified. 15766 * Note that there is no return value; use the success handler to process a 15767 * valid return. 15768 * @param {finesse.interfaces.RequestHandlers} handlers 15769 * An object containing the handlers for the request 15770 * @param {String} reasonCodeId The id for the reason code to lookup 15771 * 15772 */ 15773 getReasonCodeById : function (handlers, reasonCodeId) 15774 { 15775 var self = this, contentBody, reasonCode, url; 15776 contentBody = {}; 15777 15778 url = this.getRestUrl() + "/ReasonCode/" + reasonCodeId; 15779 this.restRequest(url, { 15780 method: 'GET', 15781 success: function (rsp) { 15782 reasonCode = { 15783 uri: rsp.object.ReasonCode.uri, 15784 label: rsp.object.ReasonCode.label, 15785 id: self._parseIdFromUriString(rsp.object.ReasonCode.uri) 15786 }; 15787 handlers.success(reasonCode); 15788 }, 15789 error: function (rsp) { 15790 handlers.error(rsp); 15791 }, 15792 content: contentBody 15793 }); 15794 }, 15795 15796 /** 15797 * Performs a GET against Finesse server retrieving all the specified type of reason codes. 15798 * @param {String} type (LOGOUT or NOT_READY) 15799 * @param {finesse.interfaces.RequestHandlers} handlers 15800 * An object containing the handlers for the request 15801 */ 15802 _getReasonCodesByType : function (type, handlers) 15803 { 15804 var self = this, contentBody = {}, url, reasonCodes, i, reasonCodeArray; 15805 15806 url = this.getRestUrl() + "/ReasonCodes?category=" + type; 15807 this.restRequest(url, { 15808 method: 'GET', 15809 success: function (rsp) { 15810 reasonCodes = []; 15811 15812 reasonCodeArray = rsp.object.ReasonCodes.ReasonCode; 15813 if (reasonCodeArray === undefined) { 15814 reasonCodes = undefined; 15815 } else if (reasonCodeArray[0] !== undefined) { 15816 for (i = 0; i < reasonCodeArray.length; i = i + 1) { 15817 reasonCodes[i] = { 15818 label: rsp.object.ReasonCodes.ReasonCode[i].label, 15819 id: self._parseIdFromUriString(rsp.object.ReasonCodes.ReasonCode[i].uri) 15820 }; 15821 } 15822 } else { 15823 reasonCodes[0] = { 15824 label: rsp.object.ReasonCodes.ReasonCode.label, 15825 id: self._parseIdFromUriString(rsp.object.ReasonCodes.ReasonCode.uri) 15826 }; 15827 } 15828 handlers.success(reasonCodes); 15829 }, 15830 error: function (rsp) { 15831 handlers.error(rsp); 15832 }, 15833 content: contentBody 15834 }); 15835 }, 15836 15837 /** 15838 * Performs a GET against the Finesse server and retrieves all of the Not Ready 15839 * reason codes. Note that there is no return value; use the success handler to 15840 * process a valid return. 15841 * 15842 * <pre class="code"> 15843 * _user.getSignoutReasonCodes({ 15844 * success: handleSignoutReasonCodesSuccess, 15845 * error: handleSignoutReasonCodesError 15846 * }); 15847 * </pre> 15848 * 15849 * @see finesse.restservices.ReasonCodes 15850 * 15851 * @param {finesse.interfaces.RequestHandlers} handlers 15852 * An object containing the handlers for the request 15853 */ 15854 getSignoutReasonCodes : function (handlers) 15855 { 15856 this._getReasonCodesByType("LOGOUT", handlers); 15857 }, 15858 15859 /** 15860 * Performs a GET against the Finesse server and retrieves all of the Not Ready 15861 * reason codes. Note that there is no return value; use the success handler to 15862 * process a valid return. 15863 * 15864 * <pre class="code"> 15865 * _user.getNotReadyReasonCodes({ 15866 * success: handleNotReadyReasonCodesSuccess, 15867 * error: handleNotReadyReasonCodesError 15868 * }); 15869 * </pre> 15870 * 15871 * @see finesse.restservices.ReasonCodes 15872 * 15873 * @param {finesse.interfaces.RequestHandlers} handlers 15874 * An object containing the handlers for the request 15875 */ 15876 getNotReadyReasonCodes : function (handlers) 15877 { 15878 this._getReasonCodesByType("NOT_READY", handlers); 15879 } 15880 }); 15881 User.MediaStates = /** @lends finesse.restservices.User.MediaStates.prototype */ { 15882 /** 15883 * Will be applicable only in CCX deployments 15884 * When the agent is talking on a manual outbound call 15885 */ 15886 BUSY: "BUSY", 15887 /** 15888 * @class Possible Agent Media States. 15889 * @constructs 15890 */ 15891 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 15892 15893 }; 15894 User.States = /** @lends finesse.restservices.User.States.prototype */ { 15895 /** 15896 * User Login. Note that while this is an action, is not technically a state, since a 15897 * logged-in User will always be in a specific state (READY, NOT_READY, TALKING, etc.). 15898 */ 15899 LOGIN: "LOGIN", 15900 /** 15901 * User is logged out. 15902 */ 15903 LOGOUT: "LOGOUT", 15904 /** 15905 * User is not ready. Note that in UCCX implementations, the user is in this state while on a non-routed call. 15906 */ 15907 NOT_READY: "NOT_READY", 15908 /** 15909 * User is ready for calls. 15910 */ 15911 READY: "READY", 15912 /** 15913 * User has a call coming in, but has not answered it. 15914 */ 15915 RESERVED: "RESERVED", 15916 /** 15917 * User has an outbound call being made, but has not been connected to it. 15918 */ 15919 RESERVED_OUTBOUND: "RESERVED_OUTBOUND", 15920 /** 15921 * User has an outbound call's preview information being displayed, but has not acted on it. 15922 */ 15923 RESERVED_OUTBOUND_PREVIEW: "RESERVED_OUTBOUND_PREVIEW", 15924 /** 15925 * User is on a call. Note that in UCCX implementations, this is for routed calls only. 15926 */ 15927 TALKING: "TALKING", 15928 /** 15929 * User is on hold. Note that in UCCX implementations, the user remains in TALKING state while on hold. 15930 */ 15931 HOLD: "HOLD", 15932 /** 15933 * User is wrap-up/work mode. This mode is typically configured to time out, after which the user becomes NOT_READY. 15934 */ 15935 WORK: "WORK", 15936 /** 15937 * This is the same as WORK, except that after time out user becomes READY. 15938 */ 15939 WORK_READY: "WORK_READY", 15940 /** 15941 * @class Possible User state values. 15942 * @constructs 15943 */ 15944 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 15945 15946 }; 15947 15948 User.WorkMode = { /** @lends finesse.restservices.User.WorkMode.prototype */ 15949 /** 15950 * Mobile agent is connected (dialed) for each incoming call received. 15951 */ 15952 CALL_BY_CALL: "CALL_BY_CALL", 15953 /** 15954 * Mobile agent is connected (dialed) at login. 15955 */ 15956 NAILED_CONNECTION: "NAILED_CONNECTION", 15957 /** 15958 * @class Possible Mobile Agent Work Mode Types. 15959 * @constructs 15960 */ 15961 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 15962 15963 }; 15964 15965 User.WrapUpMode = { /** @lends finesse.restservices.User.WrapUpMode.prototype */ 15966 /** 15967 * Agent must go into wrap-up when call ends 15968 */ 15969 REQUIRED: "REQUIRED", 15970 /** 15971 * Agent must go into wrap-up when call ends and must enter wrap-up data 15972 */ 15973 REQUIRED_WITH_WRAP_UP_DATA: "REQUIRED_WITH_WRAP_UP_DATA", 15974 /** 15975 * Agent can choose to go into wrap-up on a call-by-call basis when the call ends 15976 */ 15977 OPTIONAL: "OPTIONAL", 15978 /** 15979 * Agent is not allowed to go into wrap-up when call ends. 15980 */ 15981 NOT_ALLOWED: "NOT_ALLOWED", 15982 /** 15983 * @class Possible Wrap-up Mode Types. 15984 * @constructs 15985 */ 15986 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 15987 15988 }; 15989 15990 window.finesse = window.finesse || {}; 15991 window.finesse.restservices = window.finesse.restservices || {}; 15992 window.finesse.restservices.User = User; 15993 15994 return User; 15995 }); 15996 15997 /** 15998 * JavaScript representation of the Finesse Users collection 15999 * object which contains a list of Users objects. 16000 * 16001 * @requires finesse.clientservices.ClientServices 16002 * @requires Class 16003 * @requires finesse.FinesseBase 16004 * @requires finesse.restservices.RestBase 16005 * @requires finesse.restservices.RestCollectionBase 16006 * @requires finesse.restservices.User 16007 */ 16008 16009 /** @private */ 16010 define('restservices/Users',[ 16011 'restservices/RestCollectionBase', 16012 'restservices/RestBase', 16013 'restservices/User' 16014 ], 16015 function (RestCollectionBase, RestBase, User) { 16016 16017 var Users = RestCollectionBase.extend(/** @lends finesse.restservices.Users.prototype */{ 16018 16019 /** 16020 * @class 16021 * JavaScript representation of a Users collection object. 16022 * While there is no method provided to retrieve all Users, this collection is 16023 * used to return the Users in a supervised Team. 16024 * @augments finesse.restservices.RestCollectionBase 16025 * @constructs 16026 * @see finesse.restservices.Team 16027 * @see finesse.restservices.User 16028 * @see finesse.restservices.User#getSupervisedTeams 16029 * @example 16030 * // Note: The following method gets an Array of Teams, not a Collection. 16031 * _teams = _user.getSupervisedTeams(); 16032 * if (_teams.length > 0) { 16033 * _team0Users = _teams[0].getUsers(); 16034 * } 16035 */ 16036 _fakeConstuctor: function () { 16037 /* This is here to hide the real init constructor from the public docs */ 16038 }, 16039 16040 /** 16041 * @private 16042 * JavaScript representation of the Finesse Users collection 16043 * object which contains a list of Users objects. 16044 * 16045 * @param {Object} options 16046 * An object with the following properties:<ul> 16047 * <li><b>id:</b> The id of the object being constructed</li> 16048 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 16049 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 16050 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 16051 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 16052 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 16053 * <li><b>status:</b> {Number} The HTTP status code returned</li> 16054 * <li><b>content:</b> {String} Raw string of response</li> 16055 * <li><b>object:</b> {Object} Parsed object of response</li> 16056 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 16057 * <li><b>errorType:</b> {String} Type of error that was caught</li> 16058 * <li><b>errorMessage:</b> {String} Message associated with error</li> 16059 * </ul></li> 16060 * </ul></li> 16061 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 16062 **/ 16063 init: function (options) { 16064 this._super(options); 16065 }, 16066 16067 /** 16068 * @private 16069 * Gets the REST class for the current object - this is the Users class. 16070 * @returns {Object} 16071 * The User constructor. 16072 */ 16073 getRestClass: function () { 16074 return Users; 16075 }, 16076 16077 /** 16078 * @private 16079 * Gets the REST class for the objects that make up the collection. - this 16080 * is the User class. 16081 * @returns {finesse.restservices.User} 16082 * This User object 16083 * @see finesse.restservices.User 16084 */ 16085 getRestItemClass: function () { 16086 return User; 16087 }, 16088 16089 /** 16090 * @private 16091 * Gets the REST type for the current object - this is a "Users". 16092 * @returns {String} The Users String 16093 */ 16094 getRestType: function () { 16095 return "Users"; 16096 }, 16097 16098 /** 16099 * @private 16100 * Gets the REST type for the objects that make up the collection - this is "User". 16101 * @returns {String} The User String 16102 */ 16103 getRestItemType: function () { 16104 return "User"; 16105 }, 16106 16107 /** 16108 * @private 16109 * Gets the node path for the current object - this is the team Users node 16110 * @returns {String} The node path 16111 */ 16112 getXMPPNodePath: function () { 16113 return this.getRestUrl(); 16114 }, 16115 16116 /** 16117 * @private 16118 * Overloading _doGET to reroute the GET to /Team/id from /Team/id/Users 16119 * This needs to be done because the GET /Team/id/Users API is missing 16120 * @returns {Users} This Users (collection) object to allow cascading 16121 */ 16122 _doGET: function (handlers) { 16123 var _this = this; 16124 handlers = handlers || {}; 16125 // Only do this for /Team/id/Users 16126 if (this._restObj && this._restObj.getRestType() === "Team") { 16127 this._restObj._doGET({ 16128 success: function (rspObj) { 16129 // Making sure the response was a valid Team 16130 if (_this._restObj._validate(rspObj.object)) { 16131 // Shimmying the response to look like a Users collection by extracting it from the Team response 16132 rspObj.object[_this.getRestType()] = rspObj.object[_this._restObj.getRestType()][_this.getRestType().toLowerCase()]; 16133 handlers.success(rspObj); 16134 } else { 16135 handlers.error(rspObj); 16136 } 16137 }, 16138 error: handlers.error 16139 }); 16140 return this; // Allow cascading 16141 } else { 16142 return this._super(handlers); 16143 } 16144 }, 16145 16146 /** 16147 * @private 16148 * Override default to indicates that the collection doesn't support making 16149 * requests. 16150 */ 16151 supportsRequests: false, 16152 16153 /** 16154 * @private 16155 * Indicates that this collection handles the subscription for its items 16156 */ 16157 handlesItemSubscription: true, 16158 16159 /** 16160 * @private 16161 * Override default to indicate that we need to subscribe explicitly 16162 */ 16163 explicitSubscription: true 16164 16165 }); 16166 16167 window.finesse = window.finesse || {}; 16168 window.finesse.restservices = window.finesse.restservices || {}; 16169 window.finesse.restservices.Users = Users; 16170 16171 return Users; 16172 }); 16173 16174 /** 16175 * JavaScript representation of the Finesse Team Not Ready Reason Code Assignment object. 16176 * 16177 * @requires finesse.clientservices.ClientServices 16178 * @requires Class 16179 * @requires finesse.FinesseBase 16180 * @requires finesse.restservices.RestBase 16181 */ 16182 16183 /** @private */ 16184 define('restservices/TeamNotReadyReasonCode',['restservices/RestBase'], function (RestBase) { 16185 16186 var TeamNotReadyReasonCode = RestBase.extend(/** @lends finesse.restservices.TeamNotReadyReasonCode.prototype */{ 16187 16188 /** 16189 * @class 16190 * JavaScript representation of a Team Not Ready ReasonCode object. Also exposes 16191 * methods to operate on the object against the server. 16192 * 16193 * @param {Object} options 16194 * An object with the following properties:<ul> 16195 * <li><b>id:</b> The id of the object being constructed</li> 16196 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 16197 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 16198 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 16199 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 16200 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 16201 * <li><b>status:</b> {Number} The HTTP status code returned</li> 16202 * <li><b>content:</b> {String} Raw string of response</li> 16203 * <li><b>object:</b> {Object} Parsed object of response</li> 16204 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 16205 * <li><b>errorType:</b> {String} Type of error that was caught</li> 16206 * <li><b>errorMessage:</b> {String} Message associated with error</li> 16207 * </ul></li> 16208 * </ul></li> 16209 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 16210 * @constructs 16211 **/ 16212 init: function (options) { 16213 this._super(options); 16214 }, 16215 16216 /** 16217 * @private 16218 * Gets the REST class for the current object - this is the TeamNotReadyReasonCode class. 16219 * @returns {Object} The TeamNotReadyReasonCode class. 16220 */ 16221 getRestClass: function () { 16222 return TeamNotReadyReasonCode; 16223 }, 16224 16225 /** 16226 * @private 16227 * Gets the REST type for the current object - this is a "ReasonCode". 16228 * @returns {String} The ReasonCode string. 16229 */ 16230 getRestType: function () { 16231 return "ReasonCode"; 16232 }, 16233 16234 /** 16235 * @private 16236 * Override default to indicate that this object doesn't support making 16237 * requests. 16238 */ 16239 supportsRequests: false, 16240 16241 /** 16242 * @private 16243 * Override default to indicate that this object doesn't support subscriptions. 16244 */ 16245 supportsSubscriptions: false, 16246 16247 /** 16248 * Getter for the category. 16249 * @returns {String} The category. 16250 */ 16251 getCategory: function () { 16252 this.isLoaded(); 16253 return this.getData().category; 16254 }, 16255 16256 /** 16257 * Getter for the code. 16258 * @returns {String} The code. 16259 */ 16260 getCode: function () { 16261 this.isLoaded(); 16262 return this.getData().code; 16263 }, 16264 16265 /** 16266 * Getter for the label. 16267 * @returns {String} The label. 16268 */ 16269 getLabel: function () { 16270 this.isLoaded(); 16271 return this.getData().label; 16272 }, 16273 16274 /** 16275 * Getter for the forAll value. 16276 * @returns {String} The forAll. 16277 */ 16278 getForAll: function () { 16279 this.isLoaded(); 16280 return this.getData().forAll; 16281 }, 16282 16283 /** 16284 * Getter for the Uri value. 16285 * @returns {String} The Uri. 16286 */ 16287 getUri: function () { 16288 this.isLoaded(); 16289 return this.getData().uri; 16290 }, 16291 /** 16292 * Getter for the systemCode value. 16293 * @returns {String} The value for systemCode. 16294 * @since 11.6(1)-ES1 onwards 16295 */ 16296 getSystemCode: function () { 16297 this.isLoaded(); 16298 return this.getData().systemCode; 16299 } 16300 16301 }); 16302 16303 window.finesse = window.finesse || {}; 16304 window.finesse.restservices = window.finesse.restservices || {}; 16305 window.finesse.restservices.TeamNotReadyReasonCode = TeamNotReadyReasonCode; 16306 16307 return TeamNotReadyReasonCode; 16308 }); 16309 16310 /** 16311 * JavaScript representation of the Finesse TeamNotReadyReasonCodes collection 16312 * object which contains a list of TeamNotReadyReasonCode objects. 16313 * 16314 * @requires finesse.clientservices.ClientServices 16315 * @requires Class 16316 * @requires finesse.FinesseBase 16317 * @requires finesse.restservices.RestBase 16318 * @requires finesse.restservices.Dialog 16319 * @requires finesse.restservices.RestCollectionBase 16320 */ 16321 16322 /** @private */ 16323 define('restservices/TeamNotReadyReasonCodes',[ 16324 'restservices/RestCollectionBase', 16325 'restservices/RestBase', 16326 'restservices/TeamNotReadyReasonCode' 16327 ], 16328 function (RestCollectionBase, RestBase, TeamNotReadyReasonCode) { 16329 16330 var TeamNotReadyReasonCodes = RestCollectionBase.extend(/** @lends finesse.restservices.TeamNotReadyReasonCodes.prototype */{ 16331 16332 /** 16333 * @class 16334 * JavaScript representation of a TeamNotReadyReasonCodes collection object. Also exposes 16335 * methods to operate on the object against the server. 16336 * 16337 * @param {Object} options 16338 * An object with the following properties:<ul> 16339 * <li><b>id:</b> The id of the object being constructed</li> 16340 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 16341 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 16342 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 16343 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 16344 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 16345 * <li><b>status:</b> {Number} The HTTP status code returned</li> 16346 * <li><b>content:</b> {String} Raw string of response</li> 16347 * <li><b>object:</b> {Object} Parsed object of response</li> 16348 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 16349 * <li><b>errorType:</b> {String} Type of error that was caught</li> 16350 * <li><b>errorMessage:</b> {String} Message associated with error</li> 16351 * </ul></li> 16352 * </ul></li> 16353 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 16354 * @augments finesse.restservices.RestCollectionBase 16355 * @constructs 16356 **/ 16357 init: function (options) { 16358 this._super(options); 16359 }, 16360 16361 /** 16362 * @private 16363 * Gets the REST class for the current object - this is the TeamNotReadyReasonCodes class. 16364 * @returns {Object} 16365 * The TeamNotReadyReasonCodes constructor. 16366 */ 16367 getRestClass: function () { 16368 return TeamNotReadyReasonCodes; 16369 }, 16370 16371 /** 16372 * @private 16373 * Gets the REST class for the objects that make up the collection. - this 16374 * is the TeamNotReadyReasonCode class. 16375 * @returns {finesse.restservices.TeamNotReadyReasonCode} 16376 * The TeamNotReadyReasonCode Object 16377 * @see finesse.restservices.TeamNotReadyReasonCode 16378 */ 16379 getRestItemClass: function () { 16380 return TeamNotReadyReasonCode; 16381 }, 16382 16383 /** 16384 * @private 16385 * Gets the REST type for the current object - this is a "ReasonCodes". 16386 * @returns {String} The ReasonCodes String 16387 */ 16388 getRestType: function () { 16389 return "ReasonCodes"; 16390 }, 16391 16392 /** 16393 * @private 16394 * Overrides the parent class. Returns the url for the NotReadyReasonCodes resource 16395 */ 16396 getRestUrl: function () { 16397 // return ("/finesse/api/" + this.getRestType() + "?category=NOT_READY"); 16398 var restObj = this._restObj, 16399 restUrl = ""; 16400 //Prepend the base REST object if one was provided. 16401 //Otherwise prepend with the default webapp name. 16402 if (restObj instanceof RestBase) { 16403 restUrl += restObj.getRestUrl(); 16404 } 16405 else { 16406 restUrl += "/finesse/api"; 16407 } 16408 //Append the REST type. 16409 restUrl += "/ReasonCodes?category=NOT_READY"; 16410 //Append ID if it is not undefined, null, or empty. 16411 if (this._id) { 16412 restUrl += "/" + this._id; 16413 } 16414 return restUrl; 16415 }, 16416 16417 /** 16418 * @private 16419 * Gets the REST type for the objects that make up the collection - this is "ReasonCode". 16420 */ 16421 getRestItemType: function () { 16422 return "ReasonCode"; 16423 }, 16424 16425 /** 16426 * @private 16427 * Override default to indicates that the collection supports making 16428 * requests. 16429 */ 16430 supportsRequests: true, 16431 16432 /** 16433 * @private 16434 * Override default to indicate that this object doesn't support subscriptions. 16435 */ 16436 supportsRestItemSubscriptions: false, 16437 16438 /** 16439 * @private 16440 * Retrieve the Not Ready Reason Codes. 16441 * 16442 * @returns {TeamNotReadyReasonCodes} 16443 * This TeamNotReadyReasonCodes object to allow cascading. 16444 */ 16445 get: function () { 16446 // set loaded to false so it will rebuild the collection after the get 16447 this._loaded = false; 16448 // reset collection 16449 this._collection = {}; 16450 // perform get 16451 this._synchronize(); 16452 return this; 16453 }, 16454 16455 /** 16456 * @private 16457 * Set up the PutSuccessHandler for TeamNotReadyReasonCodes 16458 * @param {Object} reasonCodes 16459 * @param {String} contentBody 16460 * @param successHandler 16461 * @return {function} 16462 */ 16463 createPutSuccessHandler: function (reasonCodes, contentBody, successHandler) { 16464 return function (rsp) { 16465 // Update internal structure based on response. Here we 16466 // inject the contentBody from the PUT request into the 16467 // rsp.object element to mimic a GET as a way to take 16468 // advantage of the existing _processResponse method. 16469 rsp.object = contentBody; 16470 reasonCodes._processResponse(rsp); 16471 16472 //Remove the injected contentBody object before cascading response 16473 rsp.object = {}; 16474 16475 //cascade response back to consumer's response handler 16476 successHandler(rsp); 16477 }; 16478 }, 16479 16480 /** 16481 * @private 16482 * Perform the REST API PUT call to update the reason code assignments for the team 16483 * @param {string[]} newValues 16484 * @param handlers 16485 */ 16486 update: function (newValues, handlers) { 16487 this.isLoaded(); 16488 var contentBody = {}, contentBodyInner = [], i, innerObject = {}; 16489 16490 contentBody[this.getRestType()] = { 16491 }; 16492 16493 for (i in newValues) { 16494 if (newValues.hasOwnProperty(i)) { 16495 innerObject = { 16496 "uri": newValues[i] 16497 }; 16498 contentBodyInner.push(innerObject); 16499 } 16500 } 16501 16502 contentBody[this.getRestType()] = { 16503 "ReasonCode" : contentBodyInner 16504 }; 16505 16506 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 16507 handlers = handlers || {}; 16508 16509 this.restRequest(this.getRestUrl(), { 16510 method: 'PUT', 16511 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 16512 error: handlers.error, 16513 content: contentBody 16514 }); 16515 16516 return this; // Allow cascading 16517 } 16518 }); 16519 16520 window.finesse = window.finesse || {}; 16521 window.finesse.restservices = window.finesse.restservices || {}; 16522 window.finesse.restservices.TeamNotReadyReasonCodes = TeamNotReadyReasonCodes; 16523 16524 return TeamNotReadyReasonCodes; 16525 }); 16526 16527 /** 16528 * JavaScript representation of the Finesse Team Wrap Up Reason object. 16529 * 16530 * @requires finesse.clientservices.ClientServices 16531 * @requires Class 16532 * @requires finesse.FinesseBase 16533 * @requires finesse.restservices.RestBase 16534 */ 16535 /** @private */ 16536 define('restservices/TeamWrapUpReason',['restservices/RestBase'], function (RestBase) { 16537 16538 var TeamWrapUpReason = RestBase.extend({ 16539 16540 /** 16541 * @class 16542 * JavaScript representation of a TeamWrapUpReason object. Also exposes 16543 * methods to operate on the object against the server. 16544 * 16545 * @param {Object} options 16546 * An object with the following properties:<ul> 16547 * <li><b>id:</b> The id of the object being constructed</li> 16548 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 16549 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 16550 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 16551 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 16552 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 16553 * <li><b>status:</b> {Number} The HTTP status code returned</li> 16554 * <li><b>content:</b> {String} Raw string of response</li> 16555 * <li><b>object:</b> {Object} Parsed object of response</li> 16556 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 16557 * <li><b>errorType:</b> {String} Type of error that was caught</li> 16558 * <li><b>errorMessage:</b> {String} Message associated with error</li> 16559 * </ul></li> 16560 * </ul></li> 16561 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 16562 * @constructs 16563 **/ 16564 init: function (options) { 16565 this._super(options); 16566 }, 16567 16568 /** 16569 * @private 16570 * Gets the REST class for the current object - this is the TeamWrapUpReason class. 16571 * @returns {Object} The TeamWrapUpReason class. 16572 */ 16573 getRestClass: function () { 16574 return TeamWrapUpReason; 16575 }, 16576 16577 /** 16578 * @private 16579 * Gets the REST type for the current object - this is a "WrapUpReason". 16580 * @returns {String} The WrapUpReason string. 16581 */ 16582 getRestType: function () { 16583 return "WrapUpReason"; 16584 }, 16585 16586 /** 16587 * @private 16588 * Override default to indicate that this object doesn't support making 16589 * requests. 16590 */ 16591 supportsRequests: false, 16592 16593 /** 16594 * @private 16595 * Override default to indicate that this object doesn't support subscriptions. 16596 */ 16597 supportsSubscriptions: false, 16598 16599 /** 16600 * Getter for the label. 16601 * @returns {String} The label. 16602 */ 16603 getLabel: function () { 16604 this.isLoaded(); 16605 return this.getData().label; 16606 }, 16607 16608 /** 16609 * @private 16610 * Getter for the forAll value. 16611 * @returns {Boolean} True if global 16612 */ 16613 getForAll: function () { 16614 this.isLoaded(); 16615 return this.getData().forAll; 16616 }, 16617 16618 /** 16619 * @private 16620 * Getter for the Uri value. 16621 * @returns {String} The Uri. 16622 */ 16623 getUri: function () { 16624 this.isLoaded(); 16625 return this.getData().uri; 16626 } 16627 }); 16628 16629 window.finesse = window.finesse || {}; 16630 window.finesse.restservices = window.finesse.restservices || {}; 16631 window.finesse.restservices.TeamWrapUpReason = TeamWrapUpReason; 16632 16633 return TeamWrapUpReason; 16634 }); 16635 16636 /** 16637 * JavaScript representation of the Finesse Team Wrap-Up Reasons collection 16638 * object which contains a list of Wrap-Up Reasons objects. 16639 * 16640 * @requires finesse.clientservices.ClientServices 16641 * @requires Class 16642 * @requires finesse.FinesseBase 16643 * @requires finesse.restservices.RestBase 16644 * @requires finesse.restservices.Dialog 16645 * @requires finesse.restservices.RestCollectionBase 16646 */ 16647 /** @private */ 16648 define('restservices/TeamWrapUpReasons',[ 16649 'restservices/RestCollectionBase', 16650 'restservices/RestBase', 16651 'restservices/TeamWrapUpReason' 16652 ], 16653 function (RestCollectionBase, RestBase, TeamWrapUpReason) { 16654 16655 var TeamWrapUpReasons = RestCollectionBase.extend({ 16656 16657 /** 16658 * @class 16659 * JavaScript representation of a TeamWrapUpReasons collection object. Also exposes 16660 * methods to operate on the object against the server. 16661 * 16662 * @param {Object} options 16663 * An object with the following properties:<ul> 16664 * <li><b>id:</b> The id of the object being constructed</li> 16665 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 16666 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 16667 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 16668 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 16669 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 16670 * <li><b>status:</b> {Number} The HTTP status code returned</li> 16671 * <li><b>content:</b> {String} Raw string of response</li> 16672 * <li><b>object:</b> {Object} Parsed object of response</li> 16673 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 16674 * <li><b>errorType:</b> {String} Type of error that was caught</li> 16675 * <li><b>errorMessage:</b> {String} Message associated with error</li> 16676 * </ul></li> 16677 * </ul></li> 16678 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 16679 * @constructs 16680 **/ 16681 init: function (options) { 16682 this._super(options); 16683 }, 16684 16685 /** 16686 * @private 16687 * Gets the REST class for the current object - this is the TeamWrapUpReasons class. 16688 */ 16689 getRestClass: function () { 16690 return TeamWrapUpReasons; 16691 }, 16692 16693 /** 16694 * @private 16695 * Gets the REST class for the objects that make up the collection. - this 16696 * is the TeamWrapUpReason class. 16697 */ 16698 getRestItemClass: function () { 16699 return TeamWrapUpReason; 16700 }, 16701 16702 /** 16703 * @private 16704 * Gets the REST type for the current object - this is a "WrapUpReasons". 16705 */ 16706 getRestType: function () { 16707 return "WrapUpReasons"; 16708 }, 16709 16710 /** 16711 * @private 16712 * Gets the REST type for the objects that make up the collection - this is "WrapUpReason". 16713 */ 16714 getRestItemType: function () { 16715 return "WrapUpReason"; 16716 }, 16717 16718 /** 16719 * @private 16720 * Override default to indicates that the collection supports making 16721 * requests. 16722 */ 16723 supportsRequests: true, 16724 16725 /** 16726 * @private 16727 * Override default to indicate that this object doesn't support subscriptions. 16728 */ 16729 supportsRestItemSubscriptions: false, 16730 16731 /** 16732 * Retrieve the Team Wrap Up Reasons. 16733 * 16734 * @returns {finesse.restservices.TeamWrapUpReasons} 16735 * This TeamWrapUpReasons object to allow cascading. 16736 */ 16737 get: function () { 16738 // set loaded to false so it will rebuild the collection after the get 16739 this._loaded = false; 16740 // reset collection 16741 this._collection = {}; 16742 // perform get 16743 this._synchronize(); 16744 return this; 16745 }, 16746 16747 /** 16748 * Set up the PutSuccessHandler for TeamWrapUpReasons 16749 * @param {Object} wrapUpReasons 16750 * @param {Object} contentBody 16751 * @param successHandler 16752 * @returns response 16753 */ 16754 createPutSuccessHandler: function (wrapUpReasons, contentBody, successHandler) { 16755 return function (rsp) { 16756 // Update internal structure based on response. Here we 16757 // inject the contentBody from the PUT request into the 16758 // rsp.object element to mimic a GET as a way to take 16759 // advantage of the existing _processResponse method. 16760 rsp.object = contentBody; 16761 16762 wrapUpReasons._processResponse(rsp); 16763 16764 //Remove the injected contentBody object before cascading response 16765 rsp.object = {}; 16766 16767 //cascade response back to consumer's response handler 16768 successHandler(rsp); 16769 }; 16770 }, 16771 16772 /** 16773 * Perform the REST API PUT call to update the reason code assignments for the team 16774 * @param {String Array} newValues 16775 * @param handlers 16776 * @returns {Object} this 16777 */ 16778 update: function (newValues, handlers) { 16779 this.isLoaded(); 16780 var contentBody = {}, contentBodyInner = [], i, innerObject = {}; 16781 16782 contentBody[this.getRestType()] = { 16783 }; 16784 16785 for (i in newValues) { 16786 if (newValues.hasOwnProperty(i)) { 16787 innerObject = { 16788 "uri": newValues[i] 16789 }; 16790 contentBodyInner.push(innerObject); 16791 } 16792 } 16793 16794 contentBody[this.getRestType()] = { 16795 "WrapUpReason" : contentBodyInner 16796 }; 16797 16798 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 16799 handlers = handlers || {}; 16800 16801 this.restRequest(this.getRestUrl(), { 16802 method: 'PUT', 16803 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 16804 error: handlers.error, 16805 content: contentBody 16806 }); 16807 16808 return this; // Allow cascading 16809 } 16810 }); 16811 16812 window.finesse = window.finesse || {}; 16813 window.finesse.restservices = window.finesse.restservices || {}; 16814 window.finesse.restservices.TeamWrapUpReasons = TeamWrapUpReasons; 16815 16816 return TeamWrapUpReasons; 16817 }); 16818 16819 /** 16820 * JavaScript representation of a TeamSignOutReasonCode. 16821 * 16822 * @requires finesse.clientservices.ClientServices 16823 * @requires Class 16824 * @requires finesse.FinesseBase 16825 * @requires finesse.restservices.RestBase 16826 */ 16827 16828 /** @private */ 16829 define('restservices/TeamSignOutReasonCode',['restservices/RestBase'], function (RestBase) { 16830 var TeamSignOutReasonCode = RestBase.extend(/** @lends finesse.restservices.TeamSignOutReasonCode.prototype */{ 16831 16832 /** 16833 * @class 16834 * JavaScript representation of a TeamSignOutReasonCode object. Also exposes 16835 * methods to operate on the object against the server. 16836 * 16837 * @param {Object} options 16838 * An object with the following properties:<ul> 16839 * <li><b>id:</b> The id of the object being constructed</li> 16840 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 16841 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 16842 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 16843 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 16844 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 16845 * <li><b>status:</b> {Number} The HTTP status code returned</li> 16846 * <li><b>content:</b> {String} Raw string of response</li> 16847 * <li><b>object:</b> {Object} Parsed object of response</li> 16848 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 16849 * <li><b>errorType:</b> {String} Type of error that was caught</li> 16850 * <li><b>errorMessage:</b> {String} Message associated with error</li> 16851 * </ul></li> 16852 * </ul></li> 16853 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 16854 * @constructs 16855 * @ignore 16856 **/ 16857 init: function (options) { 16858 this._super(options); 16859 }, 16860 16861 /** 16862 * @private 16863 * Gets the REST class for the current object - this is the TeamSignOutReasonCode class. 16864 * @returns {Object} The TeamSignOutReasonCode class. 16865 */ 16866 getRestClass: function () { 16867 return TeamSignOutReasonCode; 16868 }, 16869 16870 /** 16871 * @private 16872 * Gets the REST type for the current object - this is a "ReasonCode". 16873 * @returns {String} The ReasonCode string. 16874 */ 16875 getRestType: function () { 16876 return "ReasonCode"; 16877 }, 16878 16879 /** 16880 * @private 16881 * Override default to indicate that this object doesn't support making 16882 * requests. 16883 */ 16884 supportsRequests: false, 16885 16886 /** 16887 * @private 16888 * Override default to indicate that this object doesn't support subscriptions. 16889 */ 16890 supportsSubscriptions: false, 16891 16892 /** 16893 * Getter for the category. 16894 * @returns {String} The category. 16895 */ 16896 getCategory: function () { 16897 this.isLoaded(); 16898 return this.getData().category; 16899 }, 16900 16901 /** 16902 * Getter for the code. 16903 * @returns {String} The code. 16904 */ 16905 getCode: function () { 16906 this.isLoaded(); 16907 return this.getData().code; 16908 }, 16909 16910 /** 16911 * Getter for the label. 16912 * @returns {String} The label. 16913 */ 16914 getLabel: function () { 16915 this.isLoaded(); 16916 return this.getData().label; 16917 }, 16918 16919 /** 16920 * Getter for the forAll value. 16921 * @returns {String} The forAll. 16922 */ 16923 getForAll: function () { 16924 this.isLoaded(); 16925 return this.getData().forAll; 16926 }, 16927 16928 /** 16929 * Getter for the Uri value. 16930 * @returns {String} The Uri. 16931 */ 16932 getUri: function () { 16933 this.isLoaded(); 16934 return this.getData().uri; 16935 }, 16936 /** 16937 * Getter for the systemCode value. 16938 * @returns {String} The value for systemCode. 16939 * @since 11.6(1)-ES1 onwards 16940 */ 16941 getSystemCode: function () { 16942 this.isLoaded(); 16943 return this.getData().systemCode; 16944 } 16945 16946 }); 16947 16948 window.finesse = window.finesse || {}; 16949 window.finesse.restservices = window.finesse.restservices || {}; 16950 window.finesse.restservices.TeamSignOutReasonCode = TeamSignOutReasonCode; 16951 16952 return TeamSignOutReasonCode; 16953 }); 16954 16955 /** 16956 * JavaScript representation of the TeamSignOutReasonCodes collection 16957 * object which contains a list of TeamSignOutReasonCode objects. 16958 * 16959 * @requires finesse.clientservices.ClientServices 16960 * @requires Class 16961 * @requires finesse.FinesseBase 16962 * @requires finesse.restservices.RestBase 16963 * @requires finesse.restservices.Dialog 16964 * @requires finesse.restservices.RestCollectionBase 16965 */ 16966 16967 /** @private */ 16968 define('restservices/TeamSignOutReasonCodes',[ 16969 'restservices/RestCollectionBase', 16970 'restservices/RestBase', 16971 'restservices/TeamSignOutReasonCode' 16972 ], 16973 function (RestCollectionBase, RestBase, TeamSignOutReasonCode) { 16974 16975 var TeamSignOutReasonCodes = RestCollectionBase.extend(/** @lends finesse.restservices.TeamSignOutReasonCodes.prototype */{ 16976 /** 16977 * @class 16978 * JavaScript representation of a TeamSignOutReasonCodes collection object. Also exposes 16979 * methods to operate on the object against the server. 16980 * 16981 * @param {Object} options 16982 * An object with the following properties:<ul> 16983 * <li><b>id:</b> The id of the object being constructed</li> 16984 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 16985 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 16986 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 16987 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 16988 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 16989 * <li><b>status:</b> {Number} The HTTP status code returned</li> 16990 * <li><b>content:</b> {String} Raw string of response</li> 16991 * <li><b>object:</b> {Object} Parsed object of response</li> 16992 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 16993 * <li><b>errorType:</b> {String} Type of error that was caught</li> 16994 * <li><b>errorMessage:</b> {String} Message associated with error</li> 16995 * </ul></li> 16996 * </ul></li> 16997 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 16998 * @constructs 16999 **/ 17000 init: function (options) { 17001 this._super(options); 17002 }, 17003 17004 /** 17005 * @private 17006 * Gets the REST class for the current object - this is the TeamSignOutReasonCodes class. 17007 * @returns {Object} 17008 * The TeamSignOutReasonCodes constructor. 17009 */ 17010 getRestClass: function () { 17011 return TeamSignOutReasonCodes; 17012 }, 17013 17014 /** 17015 * @private 17016 * Gets the REST class for the objects that make up the collection. - this 17017 * is the TeamSignOutReasonCode class. 17018 * @returns {finesse.restservices.TeamSignOutReasonCode} 17019 * The TeamSignOutReasonCode Object 17020 * @see finesse.restservices.TeamSignOutReasonCode 17021 */ 17022 getRestItemClass: function () { 17023 return TeamSignOutReasonCode; 17024 }, 17025 17026 /** 17027 * @private 17028 * Gets the REST type for the current object - this is a "ReasonCodes". 17029 * @returns {String} The ReasonCodes String 17030 */ 17031 getRestType: function () { 17032 return "ReasonCodes"; 17033 }, 17034 17035 /** 17036 * Overrides the parent class. Returns the url for the SignOutReasonCodes resource 17037 */ 17038 getRestUrl: function () { 17039 var restObj = this._restObj, restUrl = ""; 17040 17041 //Prepend the base REST object if one was provided. 17042 //Otherwise prepend with the default webapp name. 17043 if (restObj instanceof RestBase) { 17044 restUrl += restObj.getRestUrl(); 17045 } else { 17046 restUrl += "/finesse/api"; 17047 } 17048 //Append the REST type. 17049 restUrl += "/ReasonCodes?category=LOGOUT"; 17050 //Append ID if it is not undefined, null, or empty. 17051 if (this._id) { 17052 restUrl += "/" + this._id; 17053 } 17054 return restUrl; 17055 }, 17056 17057 /** 17058 * @private 17059 * Gets the REST type for the objects that make up the collection - this is "ReasonCode". 17060 */ 17061 getRestItemType: function () { 17062 return "ReasonCode"; 17063 }, 17064 17065 /** 17066 * @private 17067 * Override default to indicates that the collection supports making requests. 17068 */ 17069 supportsRequests: true, 17070 17071 /** 17072 * @private 17073 * Override default to indicates that the collection does not subscribe to its objects. 17074 */ 17075 supportsRestItemSubscriptions: false, 17076 17077 /** 17078 * Retrieve the Sign Out Reason Codes. 17079 * 17080 * @returns {finesse.restservices.TeamSignOutReasonCodes} 17081 * This TeamSignOutReasonCodes object to allow cascading. 17082 */ 17083 get: function () { 17084 // set loaded to false so it will rebuild the collection after the get 17085 this._loaded = false; 17086 // reset collection 17087 this._collection = {}; 17088 // perform get 17089 this._synchronize(); 17090 return this; 17091 }, 17092 17093 /* We only use PUT and GET on Reason Code team assignments 17094 * @param {Object} contact 17095 * @param {Object} contentBody 17096 * @param {Function} successHandler 17097 */ 17098 createPutSuccessHandler: function (contact, contentBody, successHandler) { 17099 return function (rsp) { 17100 // Update internal structure based on response. Here we 17101 // inject the contentBody from the PUT request into the 17102 // rsp.object element to mimic a GET as a way to take 17103 // advantage of the existing _processResponse method. 17104 rsp.object = contentBody; 17105 contact._processResponse(rsp); 17106 17107 //Remove the injected contentBody object before cascading response 17108 rsp.object = {}; 17109 17110 //cascade response back to consumer's response handler 17111 successHandler(rsp); 17112 }; 17113 }, 17114 17115 /** 17116 * Update - This should be all that is needed. 17117 * @param {Object} newValues 17118 * @param {Object} handlers 17119 * @returns {finesse.restservices.TeamSignOutReasonCodes} 17120 * This TeamSignOutReasonCodes object to allow cascading. 17121 */ 17122 update: function (newValues, handlers) { 17123 this.isLoaded(); 17124 var contentBody = {}, contentBodyInner = [], i, innerObject = {}; 17125 17126 contentBody[this.getRestType()] = { 17127 }; 17128 17129 for (i in newValues) { 17130 if (newValues.hasOwnProperty(i)) { 17131 innerObject = { 17132 "uri": newValues[i] 17133 }; 17134 contentBodyInner.push(innerObject); 17135 } 17136 } 17137 17138 contentBody[this.getRestType()] = { 17139 "ReasonCode" : contentBodyInner 17140 }; 17141 17142 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 17143 handlers = handlers || {}; 17144 17145 this.restRequest(this.getRestUrl(), { 17146 method: 'PUT', 17147 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 17148 error: handlers.error, 17149 content: contentBody 17150 }); 17151 17152 return this; // Allow cascading 17153 } 17154 17155 }); 17156 17157 window.finesse = window.finesse || {}; 17158 window.finesse.restservices = window.finesse.restservices || {}; 17159 window.finesse.restservices.TeamSignOutReasonCodes = TeamSignOutReasonCodes; 17160 17161 return TeamSignOutReasonCodes; 17162 }); 17163 17164 /** 17165 * JavaScript representation of the Finesse PhoneBook Assignment object. 17166 * 17167 * @requires finesse.clientservices.ClientServices 17168 * @requires Class 17169 * @requires finesse.FinesseBase 17170 * @requires finesse.restservices.RestBase 17171 */ 17172 17173 /** 17174 * The following comment prevents JSLint errors concerning undefined global variables. 17175 * It tells JSLint that these identifiers are defined elsewhere. 17176 */ 17177 /*jslint bitwise:true, browser:true, nomen:true, regexp:true, sloppy:true, white:true */ 17178 17179 /** The following comment is to prevent jslint errors about 17180 * using variables before they are defined. 17181 */ 17182 /*global $, jQuery, Handlebars, dojox, dojo, finesse */ 17183 17184 /** @private */ 17185 define('restservices/TeamPhoneBook',['restservices/RestBase'], function (RestBase) { 17186 var TeamPhoneBook = RestBase.extend({ 17187 17188 /** 17189 * @class 17190 * JavaScript representation of a PhoneBook object. Also exposes 17191 * methods to operate on the object against the server. 17192 * 17193 * @param {Object} options 17194 * An object with the following properties:<ul> 17195 * <li><b>id:</b> The id of the object being constructed</li> 17196 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 17197 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 17198 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 17199 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 17200 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 17201 * <li><b>status:</b> {Number} The HTTP status code returned</li> 17202 * <li><b>content:</b> {String} Raw string of response</li> 17203 * <li><b>object:</b> {Object} Parsed object of response</li> 17204 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 17205 * <li><b>errorType:</b> {String} Type of error that was caught</li> 17206 * <li><b>errorMessage:</b> {String} Message associated with error</li> 17207 * </ul></li> 17208 * </ul></li> 17209 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 17210 * @constructs 17211 **/ 17212 init: function (options) { 17213 this._super(options); 17214 }, 17215 17216 /** 17217 * @private 17218 * Gets the REST class for the current object - this is the PhoneBooks class. 17219 * @returns {Object} The PhoneBooks class. 17220 */ 17221 getRestClass: function () { 17222 return TeamPhoneBook; 17223 }, 17224 17225 /** 17226 * @private 17227 * Gets the REST type for the current object - this is a "PhoneBook". 17228 * @returns {String} The PhoneBook string. 17229 */ 17230 getRestType: function () { 17231 return "PhoneBook"; 17232 }, 17233 17234 /** 17235 * @private 17236 * Override default to indicate that this object doesn't support making 17237 * requests. 17238 */ 17239 supportsRequests: false, 17240 17241 /** 17242 * @private 17243 * Override default to indicate that this object doesn't support subscriptions. 17244 */ 17245 supportsSubscriptions: false, 17246 17247 /** 17248 * Getter for the name. 17249 * @returns {String} The name. 17250 */ 17251 getName: function () { 17252 this.isLoaded(); 17253 return this.getData().name; 17254 }, 17255 17256 /** 17257 * Getter for the Uri value. 17258 * @returns {String} The Uri. 17259 */ 17260 getUri: function () { 17261 this.isLoaded(); 17262 return this.getData().uri; 17263 } 17264 17265 }); 17266 17267 window.finesse = window.finesse || {}; 17268 window.finesse.restservices = window.finesse.restservices || {}; 17269 window.finesse.restservices.TeamPhoneBook = TeamPhoneBook; 17270 17271 return TeamPhoneBook; 17272 }); 17273 17274 /** 17275 * JavaScript representation of the Finesse PhoneBook Assignments collection 17276 * object which contains a list of Not Ready Reason Codes objects. 17277 * 17278 * @requires finesse.clientservices.ClientServices 17279 * @requires Class 17280 * @requires finesse.FinesseBase 17281 * @requires finesse.restservices.RestBase 17282 * @requires finesse.restservices.Dialog 17283 * @requires finesse.restservices.RestCollectionBase 17284 */ 17285 17286 /** 17287 * The following comment prevents JSLint errors concerning undefined global variables. 17288 * It tells JSLint that these identifiers are defined elsewhere. 17289 */ 17290 /*jslint bitwise:true, browser:true, nomen:true, regexp:true, sloppy:true, white:true */ 17291 17292 /** The following comment is to prevent jslint errors about 17293 * using variables before they are defined. 17294 */ 17295 /*global $, jQuery, Handlebars, dojox, dojo, finesse */ 17296 17297 /** @private */ 17298 define('restservices/TeamPhoneBooks',[ 17299 'restservices/RestCollectionBase', 17300 'restservices/RestBase', 17301 'restservices/TeamPhoneBook' 17302 ], 17303 function (RestCollectionBase, RestBase, TeamPhoneBook) { 17304 var TeamPhoneBooks = RestCollectionBase.extend({ 17305 17306 /** 17307 * @class 17308 * JavaScript representation of a TeamPhoneBooks collection object. Also exposes 17309 * methods to operate on the object against the server. 17310 * 17311 * @param {Object} options 17312 * An object with the following properties:<ul> 17313 * <li><b>id:</b> The id of the object being constructed</li> 17314 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 17315 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 17316 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 17317 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 17318 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 17319 * <li><b>status:</b> {Number} The HTTP status code returned</li> 17320 * <li><b>content:</b> {String} Raw string of response</li> 17321 * <li><b>object:</b> {Object} Parsed object of response</li> 17322 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 17323 * <li><b>errorType:</b> {String} Type of error that was caught</li> 17324 * <li><b>errorMessage:</b> {String} Message associated with error</li> 17325 * </ul></li> 17326 * </ul></li> 17327 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 17328 * @constructs 17329 **/ 17330 init: function (options) { 17331 this._super(options); 17332 }, 17333 17334 /** 17335 * @private 17336 * Gets the REST class for the current object - this is the TeamPhoneBooks class. 17337 */ 17338 getRestClass: function () { 17339 return TeamPhoneBooks; 17340 }, 17341 17342 /** 17343 * @private 17344 * Gets the REST class for the objects that make up the collection. - this 17345 * is the TeamPhoneBooks class. 17346 */ 17347 getRestItemClass: function () { 17348 return TeamPhoneBook; 17349 }, 17350 17351 /** 17352 * @private 17353 * Gets the REST type for the current object - this is a "ReasonCodes". 17354 */ 17355 getRestType: function () { 17356 return "PhoneBooks"; 17357 }, 17358 17359 /** 17360 * Overrides the parent class. Returns the url for the PhoneBooks resource 17361 */ 17362 getRestUrl: function () { 17363 // return ("/finesse/api/" + this.getRestType() + "?category=NOT_READY"); 17364 var restObj = this._restObj, 17365 restUrl = ""; 17366 //Prepend the base REST object if one was provided. 17367 if (restObj instanceof RestBase) { 17368 restUrl += restObj.getRestUrl(); 17369 } 17370 //Otherwise prepend with the default webapp name. 17371 else { 17372 restUrl += "/finesse/api"; 17373 } 17374 //Append the REST type. 17375 restUrl += "/PhoneBooks"; 17376 //Append ID if it is not undefined, null, or empty. 17377 if (this._id) { 17378 restUrl += "/" + this._id; 17379 } 17380 return restUrl; 17381 }, 17382 17383 /** 17384 * @private 17385 * Gets the REST type for the objects that make up the collection - this is "ReasonCode". 17386 */ 17387 getRestItemType: function () { 17388 return "PhoneBook"; 17389 }, 17390 17391 /** 17392 * @private 17393 * Override default to indicates that the collection supports making 17394 * requests. 17395 */ 17396 supportsRequests: true, 17397 17398 /** 17399 * @private 17400 * Override default to indicates that the collection subscribes to its objects. 17401 */ 17402 supportsRestItemSubscriptions: false, 17403 17404 /** 17405 * Retrieve the Not Ready Reason Codes. 17406 * 17407 * @returns {finesse.restservices.TeamPhoneBooks} 17408 * This TeamPhoneBooks object to allow cascading. 17409 */ 17410 get: function () { 17411 // set loaded to false so it will rebuild the collection after the get 17412 /** @private */ 17413 this._loaded = false; 17414 // reset collection 17415 /** @private */ 17416 this._collection = {}; 17417 // perform get 17418 this._synchronize(); 17419 return this; 17420 }, 17421 17422 /* We only use PUT and GET on Reason Code team assignments 17423 */ 17424 createPutSuccessHandler: function(contact, contentBody, successHandler){ 17425 return function (rsp) { 17426 // Update internal structure based on response. Here we 17427 // inject the contentBody from the PUT request into the 17428 // rsp.object element to mimic a GET as a way to take 17429 // advantage of the existing _processResponse method. 17430 rsp.object = contentBody; 17431 contact._processResponse(rsp); 17432 17433 //Remove the injected Contact object before cascading response 17434 rsp.object = {}; 17435 17436 //cascade response back to consumer's response handler 17437 successHandler(rsp); 17438 }; 17439 }, 17440 17441 /** 17442 * Update - This should be all that is needed. 17443 */ 17444 update: function (newValues, handlers) { 17445 this.isLoaded(); 17446 var contentBody = {}, contentBodyInner = [], i, innerObject; 17447 17448 contentBody[this.getRestType()] = { 17449 }; 17450 17451 for (i in newValues) { 17452 if (newValues.hasOwnProperty(i)) { 17453 innerObject = {}; 17454 innerObject = { 17455 "uri": newValues[i] 17456 }; 17457 contentBodyInner.push(innerObject); 17458 } 17459 } 17460 17461 contentBody[this.getRestType()] = { 17462 "PhoneBook" : contentBodyInner 17463 }; 17464 17465 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 17466 handlers = handlers || {}; 17467 17468 this.restRequest(this.getRestUrl(), { 17469 method: 'PUT', 17470 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 17471 error: handlers.error, 17472 content: contentBody 17473 }); 17474 17475 return this; // Allow cascading 17476 } 17477 17478 }); 17479 17480 window.finesse = window.finesse || {}; 17481 window.finesse.restservices = window.finesse.restservices || {}; 17482 window.finesse.restservices.TeamPhoneBooks = TeamPhoneBooks; 17483 17484 return TeamPhoneBooks; 17485 }); 17486 17487 /** 17488 * JavaScript representation of the Finesse LayoutConfig object 17489 * @requires ClientServices 17490 * @requires finesse.FinesseBase 17491 * @requires finesse.restservices.RestBase 17492 */ 17493 17494 /** @private */ 17495 define('restservices/LayoutConfig',['restservices/RestBase'], function (RestBase) { 17496 /** @private */ 17497 var LayoutConfig = RestBase.extend({ 17498 17499 /** 17500 * @class 17501 * JavaScript representation of a LayoutConfig object. Also exposes methods to operate 17502 * on the object against the server. 17503 * 17504 * @param {String} id 17505 * Not required... 17506 * @param {Object} callbacks 17507 * An object containing callbacks for instantiation and runtime 17508 * @param {Function} callbacks.onLoad(this) 17509 * Callback to invoke upon successful instantiation 17510 * @param {Function} callbacks.onLoadError(rsp) 17511 * Callback to invoke on instantiation REST request error 17512 * as passed by finesse.clientservices.ClientServices.ajax() 17513 * { 17514 * status: {Number} The HTTP status code returned 17515 * content: {String} Raw string of response 17516 * object: {Object} Parsed object of response 17517 * error: {Object} Wrapped exception that was caught 17518 * error.errorType: {String} Type of error that was caught 17519 * error.errorMessage: {String} Message associated with error 17520 * } 17521 * @param {Function} callbacks.onChange(this) 17522 * Callback to invoke upon successful update 17523 * @param {Function} callbacks.onError(rsp) 17524 * Callback to invoke on update error (refresh or event) 17525 * as passed by finesse.clientservices.ClientServices.ajax() 17526 * { 17527 * status: {Number} The HTTP status code returned 17528 * content: {String} Raw string of response 17529 * object: {Object} Parsed object of response 17530 * error: {Object} Wrapped exception that was caught 17531 * error.errorType: {String} Type of error that was caught 17532 * error.errorMessage: {String} Message associated with error 17533 * } 17534 * 17535 * @constructs 17536 */ 17537 init: function (callbacks) { 17538 this._super("", callbacks); 17539 //when post is performed and id is empty 17540 /*if (id === "") { 17541 this._loaded = true; 17542 }*/ 17543 this._layoutxml = {}; 17544 }, 17545 17546 /** 17547 * Returns REST class of LayoutConfig object 17548 */ 17549 getRestClass: function () { 17550 return LayoutConfig; 17551 }, 17552 17553 /** 17554 * The type of this REST object is LayoutConfig 17555 */ 17556 getRestType: function () { 17557 return "LayoutConfig"; 17558 }, 17559 17560 /** 17561 * Gets the REST URL of this object. 17562 * 17563 * If the parent has an id, the id is appended. 17564 * On occasions of POST, it will not have an id. 17565 */ 17566 getRestUrl: function () { 17567 var layoutUri = "/finesse/api/" + this.getRestType() + "/default"; 17568 /*if (this._id) { 17569 layoutUri = layoutUri + "/" + this._id; 17570 }*/ 17571 return layoutUri; 17572 }, 17573 17574 /** 17575 * This API does not support subscription 17576 */ 17577 supportsSubscriptions: false, 17578 17579 keepRestResponse: true, 17580 17581 17582 /** 17583 * Gets finesselayout.xml retrieved from the API call 17584 */ 17585 getLayoutxml: function () { 17586 this.isLoaded(); 17587 var layoutxml = this.getData().layoutxml; 17588 17589 // We need to unescape everything that is unallowed in xml so consumers don't have to deal with it (used in tandem with update()) 17590 layoutxml = layoutxml.replace(/&/g,"&"); 17591 17592 return layoutxml; 17593 }, 17594 17595 /** 17596 * Gets the type of this LayoutConfig object 17597 */ 17598 /* 17599 getType: function () { 17600 this.isLoaded(); 17601 return this.getData().type; 17602 },*/ 17603 17604 /** 17605 * Retrieve the LayoutConfig settings. 17606 * If the id is not provided the API call will fail. 17607 * @returns {LayoutConfig} 17608 * This LayoutConfig object to allow cascading. 17609 */ 17610 get: function () { 17611 this._synchronize(); 17612 return this; 17613 }, 17614 17615 /** 17616 * Closure handle updating of the internal data for the LayoutConfig object 17617 * upon a successful update (PUT) request before calling the intended 17618 * success handler provided by the consumer 17619 * 17620 * @param {Object} 17621 * layoutconfig Reference to this LayoutConfig object 17622 * @param {Object} 17623 * LayoutConfig Object that contains the settings to be 17624 * submitted in the api request 17625 * @param {Function} 17626 * successHandler The success handler specified by the consumer 17627 * of this object 17628 * @returns {LayoutConfig} This LayoutConfig object to allow cascading 17629 */ 17630 17631 createPutSuccessHandler: function (layoutconfig, contentBody, successHandler) { 17632 return function (rsp) { 17633 // Update internal structure based on response. Here we 17634 // inject the contentBody from the PUT request into the 17635 // rsp.object element to mimic a GET as a way to take 17636 // advantage of the existing _processResponse method. 17637 rsp.content = contentBody; 17638 rsp.object.LayoutConfig = {}; 17639 rsp.object.LayoutConfig.finesseLayout = contentBody; 17640 layoutconfig._processResponse(rsp); 17641 17642 //Remove the injected layoutConfig object before cascading response 17643 rsp.object.LayoutConfig = {}; 17644 17645 //cascade response back to consumer's response handler 17646 successHandler(rsp); 17647 }; 17648 }, 17649 17650 /** 17651 * Update LayoutConfig 17652 * @param {Object} finesselayout 17653 * The XML for FinesseLayout being stored 17654 * 17655 * @param {Object} handlers 17656 * An object containing callback handlers for the request. Optional. 17657 * @param {Function} options.success(rsp) 17658 * A callback function to be invoked for a successful request. 17659 * { 17660 * status: {Number} The HTTP status code returned 17661 * content: {String} Raw string of response 17662 * object: {Object} Parsed object of response 17663 * } 17664 * @param {Function} options.error(rsp) 17665 * A callback function to be invoked for an unsuccessful request. 17666 * { 17667 * status: {Number} The HTTP status code returned 17668 * content: {String} Raw string of response 17669 * object: {Object} Parsed object of response (HTTP errors) 17670 * error: {Object} Wrapped exception that was caught 17671 * error.errorType: {String} Type of error that was caught 17672 * error.errorMessage: {String} Message associated with error 17673 * } 17674 * @returns {finesse.restservices.LayoutConfig} 17675 * This LayoutConfig object to allow cascading 17676 */ 17677 17678 update: function (layoutxml, handlers) { 17679 this.isLoaded(); 17680 17681 17682 var contentBody = {}, 17683 //Created a regex (re) to scoop out just the gadget URL (without before and after whitespace characters) 17684 re = /<gadget>\s*(\S+)\s*<\/gadget>/g; 17685 17686 // We need to escape everything that is unallowed in xml so consumers don't have to deal with it (used in tandem with getLayoutxml()) 17687 layoutxml = layoutxml.replace(/&(?!amp;)/g, "&"); 17688 17689 //used the regex (re) to the update and save the layoutxml with the improved gadget URL (without before/after whitespace) 17690 layoutxml = layoutxml.replace(re, "<gadget>$1</gadget>"); 17691 17692 contentBody[this.getRestType()] = { 17693 "layoutxml": finesse.utilities.Utilities.translateHTMLEntities(layoutxml, true) 17694 }; 17695 17696 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 17697 handlers = handlers || {}; 17698 17699 this.restRequest(this.getRestUrl(), { 17700 method: 'PUT', 17701 success: this.createPutSuccessHandler(this, layoutxml, handlers.success), 17702 error: handlers.error, 17703 content: contentBody 17704 }); 17705 17706 return this; // Allow cascading 17707 } 17708 17709 /** 17710 *TODO createPostSuccessHandler needs to be debugged to make it working 17711 * Closure handle creating new LayoutConfig object 17712 * upon a successful create (POST) request before calling the intended 17713 * success handler provided by the consumer 17714 * 17715 * @param {Object} 17716 * layoutconfig Reference to this LayoutConfig object 17717 * @param {Object} 17718 * LayoutConfig Object that contains the settings to be 17719 * submitted in the api request 17720 * @param {Function} 17721 * successHandler The success handler specified by the consumer 17722 * of this object 17723 * @returns {finesse.restservices.LayoutConfig} This LayoutConfig object to allow cascading 17724 */ 17725 /* 17726 createPostSuccessHandler: function (layoutconfig, contentBody, successHandler) { 17727 return function (rsp) { 17728 17729 rsp.object = contentBody; 17730 layoutconfig._processResponse(rsp); 17731 17732 //Remove the injected layoutConfig object before cascading response 17733 rsp.object = {}; 17734 17735 //cascade response back to consumer's response handler 17736 successHandler(rsp); 17737 }; 17738 }, */ 17739 17740 /** 17741 * TODO Method needs to be debugged to make POST working 17742 * Add LayoutConfig 17743 * @param {Object} finesselayout 17744 * The XML for FinesseLayout being stored 17745 * 17746 * @param {Object} handlers 17747 * An object containing callback handlers for the request. Optional. 17748 * @param {Function} options.success(rsp) 17749 * A callback function to be invoked for a successful request. 17750 * { 17751 * status: {Number} The HTTP status code returned 17752 * content: {String} Raw string of response 17753 * object: {Object} Parsed object of response 17754 * } 17755 * @param {Function} options.error(rsp) 17756 * A callback function to be invoked for an unsuccessful request. 17757 * { 17758 * status: {Number} The HTTP status code returned 17759 * content: {String} Raw string of response 17760 * object: {Object} Parsed object of response (HTTP errors) 17761 * error: {Object} Wrapped exception that was caught 17762 * error.errorType: {String} Type of error that was caught 17763 * error.errorMessage: {String} Message associated with error 17764 * } 17765 * @returns {finesse.restservices.LayoutConfig} 17766 * This LayoutConfig object to allow cascading 17767 */ 17768 /* 17769 add: function (layoutxml, handlers) { 17770 this.isLoaded(); 17771 var contentBody = {}; 17772 17773 17774 contentBody[this.getRestType()] = { 17775 "layoutxml": layoutxml, 17776 "type": "current" 17777 }; 17778 17779 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 17780 handlers = handlers || {}; 17781 17782 this.restRequest(this.getRestUrl(), { 17783 method: 'POST', 17784 success: this.createPostSuccessHandler(this, contentBody, handlers.success), 17785 error: handlers.error, 17786 content: contentBody 17787 }); 17788 17789 return this; // Allow cascading 17790 } */ 17791 }); 17792 17793 window.finesse = window.finesse || {}; 17794 window.finesse.restservices = window.finesse.restservices || {}; 17795 window.finesse.restservices.LayoutConfig = LayoutConfig; 17796 17797 return LayoutConfig; 17798 17799 }); 17800 17801 /** 17802 * JavaScript representation of the Finesse LayoutConfig object for a Team. 17803 * 17804 * @requires finesse.clientservices.ClientServices 17805 * @requires Class 17806 * @requires finesse.FinesseBase 17807 * @requires finesse.restservices.RestBase 17808 * @requires finesse.utilities.Utilities 17809 * @requires finesse.restservices.LayoutConfig 17810 */ 17811 17812 /** The following comment is to prevent jslint errors about 17813 * using variables before they are defined. 17814 */ 17815 /*global Exception */ 17816 17817 /** @private */ 17818 define('restservices/TeamLayoutConfig',[ 17819 'restservices/RestBase', 17820 'utilities/Utilities', 17821 'restservices/LayoutConfig' 17822 ], 17823 function (RestBase, Utilities, LayoutConfig) { 17824 17825 var TeamLayoutConfig = RestBase.extend({ 17826 // Keep the restresponse so we can parse the layoutxml out of it in getLayoutXML() 17827 keepRestResponse: true, 17828 17829 /** 17830 * @class 17831 * JavaScript representation of a LayoutConfig object for a Team. Also exposes 17832 * methods to operate on the object against the server. 17833 * 17834 * @param {Object} options 17835 * An object with the following properties:<ul> 17836 * <li><b>id:</b> The id of the object being constructed</li> 17837 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 17838 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 17839 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 17840 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 17841 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 17842 * <li><b>status:</b> {Number} The HTTP status code returned</li> 17843 * <li><b>content:</b> {String} Raw string of response</li> 17844 * <li><b>object:</b> {Object} Parsed object of response</li> 17845 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 17846 * <li><b>errorType:</b> {String} Type of error that was caught</li> 17847 * <li><b>errorMessage:</b> {String} Message associated with error</li> 17848 * </ul></li> 17849 * </ul></li> 17850 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 17851 * @constructs 17852 **/ 17853 init: function (options) { 17854 this._super(options); 17855 }, 17856 17857 /** 17858 * @private 17859 * Gets the REST class for the current object - this is the LayoutConfigs class. 17860 * @returns {Object} The LayoutConfigs class. 17861 */ 17862 getRestClass: function () { 17863 return TeamLayoutConfig; 17864 }, 17865 17866 /** 17867 * @private 17868 * Gets the REST type for the current object - this is a "LayoutConfig". 17869 * @returns {String} The LayoutConfig string. 17870 */ 17871 getRestType: function () { 17872 return "TeamLayoutConfig"; 17873 }, 17874 17875 /** 17876 * @private 17877 * Override default to indicate that this object doesn't support making 17878 * requests. 17879 */ 17880 supportsRequests: false, 17881 17882 /** 17883 * @private 17884 * Override default to indicate that this object doesn't support subscriptions. 17885 */ 17886 supportsSubscriptions: false, 17887 17888 /** 17889 * Getter for the category. 17890 * @returns {String} The category. 17891 */ 17892 getLayoutXML: function () { 17893 this.isLoaded(); 17894 var layoutxml = this.getData().layoutxml; 17895 17896 // We need to unescape everything that is unallowed in xml so consumers don't have to deal with it (used in tandem with put()) 17897 layoutxml = layoutxml.replace(/&/g,"&"); 17898 17899 return layoutxml; 17900 }, 17901 17902 /** 17903 * Getter for the code. 17904 * @returns {String} The code. 17905 */ 17906 getUseDefault: function () { 17907 this.isLoaded(); 17908 return this.getData().useDefault; 17909 }, 17910 17911 /** 17912 * Retrieve the TeamLayoutConfig. 17913 * 17914 * @returns {finesse.restservices.TeamLayoutConfig} 17915 */ 17916 get: function () { 17917 // this._id is needed, but is not used in this object.. we're overriding getRestUrl anyway 17918 this._id = "0"; 17919 // set loaded to false so it will rebuild the collection after the get 17920 this._loaded = false; 17921 // reset collection 17922 this._collection = {}; 17923 // perform get 17924 this._synchronize(); 17925 return this; 17926 }, 17927 17928 createPutSuccessHandler: function(contact, contentBody, successHandler){ 17929 return function (rsp) { 17930 // Update internal structure based on response. Here we 17931 // inject the contentBody from the PUT request into the 17932 // rsp.object element to mimic a GET as a way to take 17933 // advantage of the existing _processResponse method. 17934 rsp.object = contentBody; 17935 contact._processResponse(rsp); 17936 17937 //Remove the injected Contact object before cascading response 17938 rsp.object = {}; 17939 17940 //cascade response back to consumer's response handler 17941 successHandler(rsp); 17942 }; 17943 }, 17944 17945 put: function (newValues, handlers) { 17946 // this._id is needed, but is not used in this object.. we're overriding getRestUrl anyway 17947 this._id = "0"; 17948 this.isLoaded(); 17949 17950 // We need to escape everything that is unallowed in xml so consumers don't have to deal with it (used in tandem with getLayoutxml()) 17951 var layoutxml = newValues.layoutXML.replace(/&(?!amp;)/g, "&"), 17952 contentBody = {}, 17953 //Created a regex (re) to scoop out just the gadget URL (without before and after whitespace characters) 17954 re = /<gadget>\s*(\S+)\s*<\/gadget>/g; 17955 17956 //used the regex (re) to the update and save the layoutxml with the improved gadget URL (without before/after whitespace) 17957 layoutxml = layoutxml.replace(re, "<gadget>$1</gadget>"); 17958 17959 contentBody[this.getRestType()] = { 17960 "useDefault": newValues.useDefault, 17961 // The LayoutConfig restservice javascript class only translates ampersands, so we'll do that also 17962 "layoutxml": finesse.utilities.Utilities.translateHTMLEntities(layoutxml, true) 17963 }; 17964 17965 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 17966 handlers = handlers || {}; 17967 17968 this.restRequest(this.getRestUrl(), { 17969 method: 'PUT', 17970 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 17971 error: handlers.error, 17972 content: contentBody 17973 }); 17974 17975 return this; // Allow cascading 17976 }, 17977 17978 getRestUrl: function(){ 17979 // return team's url + /LayoutConfig 17980 // eg: /api/Team/1/LayoutConfig 17981 if(this._restObj === undefined){ 17982 throw new Exception("TeamLayoutConfig instances must have a parent team object."); 17983 } 17984 return this._restObj.getRestUrl() + '/LayoutConfig'; 17985 } 17986 17987 }); 17988 17989 window.finesse = window.finesse || {}; 17990 window.finesse.restservices = window.finesse.restservices || {}; 17991 window.finesse.restservices.TeamLayoutConfig = TeamLayoutConfig; 17992 17993 return TeamLayoutConfig; 17994 }); 17995 17996 /** 17997 * JavaScript representation of a TeamWorkflow. 17998 * 17999 * @requires finesse.clientservices.ClientServices 18000 * @requires Class 18001 * @requires finesse.FinesseBase 18002 * @requires finesse.restservices.RestBase 18003 */ 18004 /** @private */ 18005 define('restservices/TeamWorkflow',['restservices/RestBase'], function (RestBase) { 18006 18007 var TeamWorkflow = RestBase.extend({ 18008 18009 /** 18010 * @class 18011 * JavaScript representation of a TeamWorkflow object. Also exposes 18012 * methods to operate on the object against the server. 18013 * 18014 * @param {Object} options 18015 * An object with the following properties:<ul> 18016 * <li><b>id:</b> The id of the object being constructed</li> 18017 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 18018 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 18019 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 18020 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 18021 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 18022 * <li><b>status:</b> {Number} The HTTP status description returned</li> 18023 * <li><b>content:</b> {String} Raw string of response</li> 18024 * <li><b>object:</b> {Object} Parsed object of response</li> 18025 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 18026 * <li><b>errorType:</b> {String} Type of error that was caught</li> 18027 * <li><b>errorMessage:</b> {String} Message associated with error</li> 18028 * </ul></li> 18029 * </ul></li> 18030 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 18031 * @constructs 18032 **/ 18033 init: function (options) { 18034 this._super(options); 18035 }, 18036 18037 /** 18038 * @private 18039 * Gets the REST class for the current object - this is the TeamWorkflow class. 18040 * @returns {Object} The TeamWorkflow class. 18041 */ 18042 getRestClass: function () { 18043 return TeamWorkflow; 18044 }, 18045 18046 /** 18047 * @private 18048 * Gets the REST type for the current object - this is a "Workflow". 18049 * @returns {String} The Workflow string. 18050 */ 18051 getRestType: function () { 18052 return "Workflow"; 18053 }, 18054 18055 /** 18056 * @private 18057 * Override default to indicate that this object doesn't support making 18058 * requests. 18059 */ 18060 supportsRequests: false, 18061 18062 /** 18063 * @private 18064 * Override default to indicate that this object doesn't support subscriptions. 18065 */ 18066 supportsSubscriptions: false, 18067 18068 /** 18069 * Getter for the name. 18070 * @returns {String} The name. 18071 */ 18072 getName: function () { 18073 this.isLoaded(); 18074 return this.getData().name; 18075 }, 18076 18077 /** 18078 * Getter for the description. 18079 * @returns {String} The description. 18080 */ 18081 getDescription: function () { 18082 this.isLoaded(); 18083 return this.getData().description; 18084 }, 18085 18086 /** 18087 * Getter for the Uri value. 18088 * @returns {String} The Uri. 18089 */ 18090 getUri: function () { 18091 this.isLoaded(); 18092 return this.getData().uri; 18093 } 18094 18095 }); 18096 18097 window.finesse = window.finesse || {}; 18098 window.finesse.restservices = window.finesse.restservices || {}; 18099 window.finesse.restservices.TeamWorkflow = TeamWorkflow; 18100 18101 return TeamWorkflow; 18102 }); 18103 18104 /** 18105 * JavaScript representation of the TeamWorkflows collection 18106 * object which contains a list of TeamWorkflow objects. 18107 * 18108 * @requires finesse.clientservices.ClientServices 18109 * @requires Class 18110 * @requires finesse.FinesseBase 18111 * @requires finesse.restservices.RestBase 18112 * @requires finesse.restservices.Dialog 18113 * @requires finesse.restservices.RestCollectionBase 18114 */ 18115 /** @private */ 18116 define('restservices/TeamWorkflows',[ 18117 'restservices/RestCollectionBase', 18118 'restservices/TeamWorkflow', 18119 'restservices/RestBase' 18120 ], 18121 function (RestCollectionBase, TeamWorkflow, RestBase) { 18122 18123 var TeamWorkflows = RestCollectionBase.extend({ 18124 18125 /** 18126 * @class 18127 * JavaScript representation of a TeamWorkflows collection object. Also exposes 18128 * methods to operate on the object against the server. 18129 * 18130 * @param {Object} options 18131 * An object with the following properties:<ul> 18132 * <li><b>id:</b> The id of the object being constructed</li> 18133 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 18134 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 18135 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 18136 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 18137 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 18138 * <li><b>status:</b> {Number} The HTTP status code returned</li> 18139 * <li><b>content:</b> {String} Raw string of response</li> 18140 * <li><b>object:</b> {Object} Parsed object of response</li> 18141 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 18142 * <li><b>errorType:</b> {String} Type of error that was caught</li> 18143 * <li><b>errorMessage:</b> {String} Message associated with error</li> 18144 * </ul></li> 18145 * </ul></li> 18146 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 18147 * @constructs 18148 **/ 18149 init: function (options) { 18150 this._super(options); 18151 }, 18152 18153 /** 18154 * @private 18155 * Gets the REST class for the current object - this is the TeamWorkflows class. 18156 */ 18157 getRestClass: function () { 18158 return TeamWorkflows; 18159 }, 18160 18161 /** 18162 * @private 18163 * Gets the REST class for the objects that make up the collection. - this 18164 * is the TeamWorkflow class. 18165 */ 18166 getRestItemClass: function () { 18167 return TeamWorkflow; 18168 }, 18169 18170 /** 18171 * @private 18172 * Gets the REST type for the current object - this is a "Workflows". 18173 */ 18174 getRestType: function () { 18175 return "Workflows"; 18176 }, 18177 18178 /** 18179 * Overrides the parent class. Returns the url for the Workflows resource 18180 */ 18181 getRestUrl: function () { 18182 var restObj = this._restObj, restUrl = ""; 18183 18184 //Prepend the base REST object if one was provided. 18185 //Otherwise prepend with the default webapp name. 18186 if (restObj instanceof RestBase) { 18187 restUrl += restObj.getRestUrl(); 18188 } else { 18189 restUrl += "/finesse/api/Team"; 18190 } 18191 //Append ID if it is not undefined, null, or empty. 18192 if (this._id) { 18193 restUrl += "/" + this._id; 18194 } 18195 //Append the REST type. 18196 restUrl += "/Workflows"; 18197 18198 return restUrl; 18199 }, 18200 18201 /** 18202 * @private 18203 * Gets the REST type for the objects that make up the collection - this is "Workflow". 18204 */ 18205 getRestItemType: function () { 18206 return "Workflow"; 18207 }, 18208 18209 /** 18210 * @private 18211 * Override default to indicates that the collection supports making requests. 18212 */ 18213 supportsRequests: true, 18214 18215 /** 18216 * @private 18217 * Override default to indicates that the collection does not subscribe to its objects. 18218 */ 18219 supportsRestItemSubscriptions: false, 18220 18221 /** 18222 * Retrieve the Sign Out Reason Codes. 18223 * 18224 * @returns {finesse.restservices.TeamWorkflows} 18225 * This TeamWorkflows object to allow cascading. 18226 */ 18227 get: function () { 18228 // set loaded to false so it will rebuild the collection after the get 18229 this._loaded = false; 18230 // reset collection 18231 this._collection = {}; 18232 // perform get 18233 this._synchronize(); 18234 return this; 18235 }, 18236 18237 /* We only use PUT and GET on Reason Code team assignments 18238 * @param {Object} contact 18239 * @param {Object} contentBody 18240 * @param {Function} successHandler 18241 */ 18242 createPutSuccessHandler: function (contact, contentBody, successHandler) { 18243 return function (rsp) { 18244 // Update internal structure based on response. Here we 18245 // inject the contentBody from the PUT request into the 18246 // rsp.object element to mimic a GET as a way to take 18247 // advantage of the existing _processResponse method. 18248 rsp.object = contentBody; 18249 contact._processResponse(rsp); 18250 18251 //Remove the injected contentBody object before cascading response 18252 rsp.object = {}; 18253 18254 //cascade response back to consumer's response handler 18255 successHandler(rsp); 18256 }; 18257 }, 18258 18259 /** 18260 * Update - This should be all that is needed. 18261 * @param {Object} newValues 18262 * @param {Object} handlers 18263 * @returns {finesse.restservices.TeamWorkflows} 18264 * This TeamWorkflows object to allow cascading. 18265 */ 18266 update: function (newValues, handlers) { 18267 this.isLoaded(); 18268 var contentBody = {}, contentBodyInner = [], i, innerObject = {}; 18269 18270 contentBody[this.getRestType()] = { 18271 }; 18272 18273 for (i in newValues) { 18274 if (newValues.hasOwnProperty(i)) { 18275 innerObject = { 18276 "uri": newValues[i] 18277 }; 18278 contentBodyInner.push(innerObject); 18279 } 18280 } 18281 18282 contentBody[this.getRestType()] = { 18283 "Workflow" : contentBodyInner 18284 }; 18285 18286 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 18287 handlers = handlers || {}; 18288 18289 this.restRequest(this.getRestUrl(), { 18290 method: 'PUT', 18291 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 18292 error: handlers.error, 18293 content: contentBody 18294 }); 18295 18296 return this; // Allow cascading 18297 } 18298 18299 }); 18300 18301 window.finesse = window.finesse || {}; 18302 window.finesse.restservices = window.finesse.restservices || {}; 18303 window.finesse.restservices.TeamWorkflows = TeamWorkflows; 18304 18305 return TeamWorkflows; 18306 }); 18307 18308 /** 18309 * JavaScript representation of the Finesse TeamMessage Message object. 18310 * 18311 * @requires finesse.clientservices.ClientServices 18312 * @requires Class 18313 * @requires finesse.FinesseBase 18314 * @requires finesse.restservices.RestBase 18315 */ 18316 18317 /*jslint browser: true, nomen: true, sloppy: true, forin: true */ 18318 /*global define,finesse */ 18319 18320 /** @private */ 18321 define('restservices/TeamMessage',[ 18322 'restservices/RestBase' 18323 ], 18324 function (RestBase) { 18325 18326 var TeamMessage = RestBase.extend({ 18327 18328 /** 18329 * @class 18330 * JavaScript representation of a TeamMessage message object. Also exposes 18331 * methods to operate on the object against the server. 18332 * 18333 * @param {Object} options 18334 * An object with the following properties:<ul> 18335 * <li><b>id:</b> The id of the object being constructed</li> 18336 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 18337 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 18338 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 18339 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 18340 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 18341 * <li><b>status:</b> {Number} The HTTP status code returned</li> 18342 * <li><b>content:</b> {String} Raw string of response</li> 18343 * <li><b>object:</b> {Object} Parsed object of response</li> 18344 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 18345 * <li><b>errorType:</b> {String} Type of error that was caught</li> 18346 * <li><b>errorMessage:</b> {String} Message associated with error</li> 18347 * </ul></li> 18348 * </ul></li> 18349 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 18350 * @constructs 18351 **/ 18352 init: function (options) { 18353 this._super(options); 18354 }, 18355 18356 /** 18357 * @private 18358 * Gets the REST class for the current object - this is the TeamMessage class. 18359 * @returns {Object} The TeamMessage class. 18360 */ 18361 getRestClass: function () { 18362 throw new Error("getRestClass(): Not implemented in subtype."); 18363 }, 18364 18365 /** 18366 * @private 18367 * Gets the REST type for the current object - this is a "TeamMessage". 18368 * @returns {String} The Workflow string. 18369 */ 18370 getRestType: function () { 18371 return "TeamMessage"; 18372 }, 18373 18374 18375 /** 18376 * @private 18377 * Override default to indicate that this object doesn't support making 18378 * requests. 18379 */ 18380 supportsRequests: false, 18381 18382 /** 18383 * @private 18384 * Override default to indicate that this object doesn't support subscriptions. 18385 */ 18386 supportsSubscriptions: false, 18387 18388 /** 18389 * Getter for the TeamMessageuri value. 18390 * @returns {String} uri. 18391 */ 18392 getTeamMessageUri: function () { 18393 this.isLoaded(); 18394 return this.getData().uri; 18395 }, 18396 18397 /** 18398 * Getter for the teamMessage id value. 18399 * @returns {String} id. 18400 */ 18401 getId: function () { 18402 this.isLoaded(); 18403 return this.getData().id; 18404 }, 18405 18406 /** 18407 * Getter for the teamMessage createdBy value. 18408 * @returns {String} createdBy. 18409 */ 18410 getCreatedBy: function () { 18411 this.isLoaded(); 18412 return this.getData().createdBy; 18413 }, 18414 /** 18415 * Getter for the teamMessage createdAt value. 18416 * @returns {String} createdAt. 18417 */ 18418 getCreatedAt: function () { 18419 this.isLoaded(); 18420 return this.getData().createdAt; 18421 }, 18422 18423 /** 18424 * Getter for the teamMessage duration value. 18425 * @returns {String} duration. 18426 */ 18427 getDuration: function () { 18428 this.isLoaded(); 18429 return this.getData().duration; 18430 }, 18431 /** 18432 * Getter for the teamMessage content value. 18433 * @returns {String} content. 18434 */ 18435 getContent: function () { 18436 this.isLoaded(); 18437 return this.getData().content; 18438 18439 }, 18440 18441 add: function (newValues, handlers) { 18442 // this.isLoaded(); 18443 var contentBody = {}; 18444 18445 contentBody[this.getRestType()] = { 18446 "duration": newValues.expires, 18447 "content": newValues.message, 18448 "teams": newValues.teams 18449 18450 }; 18451 18452 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 18453 handlers = handlers || {}; 18454 18455 this.restRequest(this.getRestUrl(), { 18456 method: 'POST', 18457 success: handlers.success, 18458 error: handlers.error, 18459 content: contentBody 18460 }); 18461 18462 return this; // Allow cascading 18463 } 18464 18465 }); 18466 18467 window.finesse = window.finesse || {}; 18468 window.finesse.restservices = window.finesse.restservices || {}; 18469 window.finesse.restservices.TeamMessage = TeamMessage; 18470 18471 return TeamMessage; 18472 }); 18473 18474 /** 18475 * JavaScript representation of the Finesse TeamMessages object for a Team. 18476 * 18477 * @requires Class 18478 * @requires finesse.FinesseBase 18479 * @requires finesse.restservices.RestBase 18480 * @requires finesse.utilities.Utilities 18481 * @requires finesse.restservices.TeamMessage 18482 */ 18483 18484 /** The following comment is to prevent jslint errors about 18485 * using variables before they are defined. 18486 */ 18487 /*global Exception */ 18488 18489 /** @private */ 18490 define('restservices/TeamMessages',[ 18491 'restservices/RestCollectionBase', 18492 'restservices/RestBase', 18493 'restservices/TeamMessage', 18494 'utilities/Utilities' 18495 ], 18496 function (RestCollectionBase, RestBase, TeamMessage, Utilities) { 18497 18498 var TeamMessages = RestCollectionBase.extend({ 18499 18500 /** 18501 * @class 18502 * JavaScript representation of a TeamMessages object for a Team. Also exposes 18503 * methods to operate on the object against the server. 18504 * 18505 * @param {Object} options 18506 * An object with the following properties:<ul> 18507 * <li><b>id:</b> The id of the object being constructed</li> 18508 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 18509 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 18510 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 18511 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 18512 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 18513 * <li><b>status:</b> {Number} The HTTP status code returned</li> 18514 * <li><b>content:</b> {String} Raw string of response</li> 18515 * <li><b>object:</b> {Object} Parsed object of response</li> 18516 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 18517 * <li><b>errorType:</b> {String} Type of error that was caught</li> 18518 * <li><b>errorMessage:</b> {String} Message associated with error</li> 18519 * </ul></li> 18520 * </ul></li> 18521 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 18522 * @constructs 18523 **/ 18524 init: function (options) { 18525 this._super(options); 18526 }, 18527 18528 /** 18529 * Gets the REST class for the current object - this is the TeamMessages class. 18530 * @returns {Object} The TeamMessages class. 18531 */ 18532 getRestClass: function () { 18533 return TeamMessages; 18534 }, 18535 /** 18536 * Gets the REST class for the rest item - this is the TeamMessages class. 18537 * @returns {Object} The TeamMessages class. 18538 */ 18539 getRestItemClass: function () { 18540 return TeamMessage; 18541 }, 18542 18543 /** 18544 * Gets the REST type for the current object - that is a "teamMessages" class. 18545 * @returns {String} The teamMessages string. 18546 */ 18547 getRestType: function () { 18548 return "teamMessages"; 18549 }, 18550 18551 /** 18552 * Gets the REST type for the current object - this is a "TeamMessage" class. 18553 * @returns {String} The TeamMessage string. 18554 */ 18555 getRestItemType: function () { 18556 return "TeamMessage"; 18557 }, 18558 18559 18560 /** 18561 * @private 18562 * Override default to indicate that this object support making 18563 * requests. 18564 */ 18565 supportsRequests: true, 18566 18567 /** 18568 * @private 18569 * Override default to indicate that this object support subscriptions. 18570 */ 18571 supportsSubscriptions: true, 18572 /** 18573 * @private 18574 * Override default to indicates that the collection subscribes to its objects. 18575 */ 18576 supportsRestItemSubscriptions: true, 18577 18578 /** 18579 * Getter for the teamMessages. 18580 * @returns {Object} teamMessages. 18581 */ 18582 getBroadcastMessages: function () { 18583 this.isLoaded(); 18584 return this.getData; 18585 }, 18586 18587 18588 getRestUrl: function () { 18589 // return team's url + /TeamMessages 18590 // eg: /api/Team/1/TeamMessages 18591 if (this._restObj === undefined) { 18592 throw new Exception("Broadcast message instances must have a parent team object."); 18593 } 18594 return this._restObj.getRestUrl() + '/TeamMessages'; 18595 }, 18596 18597 _buildCollection: function (data) { 18598 var i, object, objectId, dataArray; 18599 if (data && this.getProperty(data, this.getRestItemType()) !== null) { 18600 dataArray = Utilities.getArray(this.getProperty(data, this.getRestItemType())); 18601 for (i = 0; i < dataArray.length; i += 1) { 18602 18603 object = {}; 18604 object[this.getRestItemType()] = dataArray[i]; 18605 18606 //get id from object.id instead of uri, since uri is not there for some reason 18607 objectId = object[this.getRestItemType()].id; //this._extractId(object); 18608 18609 //create the Media Object 18610 this._collection[objectId] = new(this.getRestItemClass())({ 18611 id: objectId, 18612 data: object, 18613 parentObj: this._restObj 18614 }); 18615 this.length += 1; 18616 } 18617 } 18618 } 18619 18620 }); 18621 18622 window.finesse = window.finesse || {}; 18623 window.finesse.restservices = window.finesse.restservices || {}; 18624 window.finesse.restservices.TeamMessages = TeamMessages; 18625 18626 return TeamMessages; 18627 }); 18628 18629 /** 18630 * JavaScript representation of the Finesse Team REST object. 18631 * 18632 * @requires finesse.clientservices.ClientServices 18633 * @requires Class 18634 * @requires finesse.FinesseBase 18635 * @requires finesse.restservices.RestBase 18636 * @requires finesse.restservices.RestCollectionBase 18637 * @requires finesse.restservices.User 18638 * @requires finesse.restservices.Users 18639 */ 18640 18641 /** 18642 * The following comment prevents JSLint errors concerning undefined global variables. 18643 * It tells JSLint that these identifiers are defined elsewhere. 18644 */ 18645 /*jslint bitwise:true, browser:true, nomen:true, regexp:true, sloppy:true, white:true */ 18646 18647 /** The following comment is to prevent jslint errors about 18648 * using variables before they are defined. 18649 */ 18650 /*global $, jQuery, Handlebars, dojox, dojo, finesse */ 18651 18652 /** @private */ 18653 define('restservices/Team',[ 18654 'restservices/RestBase', 18655 'utilities/Utilities', 18656 'restservices/Users', 18657 'restservices/TeamNotReadyReasonCodes', 18658 'restservices/TeamWrapUpReasons', 18659 'restservices/TeamSignOutReasonCodes', 18660 'restservices/TeamPhoneBooks', 18661 'restservices/TeamLayoutConfig', 18662 'restservices/TeamWorkflows', 18663 'restservices/TeamMessages' 18664 ], 18665 function (RestBase, Utilities, Users, TeamNotReadyReasonCodes, TeamWrapUpReasons, TeamSignOutReasonCodes, TeamPhoneBooks, TeamLayoutConfig, TeamWorkflows, TeamMessages) { 18666 var Team = RestBase.extend(/** @lends finesse.restservices.Team.prototype */{ 18667 18668 _teamLayoutConfig: null, 18669 18670 /** 18671 * 18672 * @class 18673 * JavaScript representation of a Team object. Also exposes methods to operate 18674 * on the object against the server. 18675 * 18676 * @param {Object} options 18677 * An object with the following properties:<ul> 18678 * <li><b>id:</b> The id of the object being constructed</li> 18679 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 18680 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 18681 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 18682 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 18683 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 18684 * <li><b>status:</b> {Number} The HTTP status code returned</li> 18685 * <li><b>content:</b> {String} Raw string of response</li> 18686 * <li><b>object:</b> {Object} Parsed object of response</li> 18687 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 18688 * <li><b>errorType:</b> {String} Type of error that was caught</li> 18689 * <li><b>errorMessage:</b> {String} Message associated with error</li> 18690 * </ul></li> 18691 * </ul></li> 18692 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 18693 * @augments finesse.restservices.RestBase 18694 * @constructs 18695 * @example 18696 * _team = new finesse.restservices.Team({ 18697 * id: _id, 18698 * onLoad : _handleTeamLoad(team), 18699 * onChange : _handleTeamChange(team) 18700 * }); 18701 **/ 18702 init: function (options) { 18703 this._super(options); 18704 }, 18705 18706 /** 18707 * @private 18708 * Gets the REST class for the current object - this is the Team class. 18709 * @returns {Object} The Team constructor. 18710 */ 18711 getRestClass: function () { 18712 return finesse.restesrvices.Team; 18713 }, 18714 18715 /** 18716 * @private 18717 * Gets the REST type for the current object - this is a "Team". 18718 * @returns {String} The Team string. 18719 */ 18720 getRestType: function () { 18721 return "Team"; 18722 }, 18723 18724 /** 18725 * @private 18726 * Override default to indicate that this object doesn't support making 18727 * requests. 18728 */ 18729 supportsSubscriptions: false, 18730 18731 /** 18732 * Getter for the team id. 18733 * @returns {String} The team id. 18734 */ 18735 getId: function () { 18736 this.isLoaded(); 18737 return this.getData().id; 18738 }, 18739 18740 /** 18741 * Getter for the team name. 18742 * @returns {String} The team name 18743 */ 18744 getName: function () { 18745 this.isLoaded(); 18746 return this.getData().name; 18747 }, 18748 18749 /** 18750 * @private 18751 * Getter for the team uri. 18752 * @returns {String} The team uri 18753 */ 18754 getUri: function () { 18755 this.isLoaded(); 18756 return this.getData().uri; 18757 }, 18758 18759 /** 18760 * Constructs and returns a collection of Users. 18761 * @param {Object} options that sets callback handlers. 18762 * An object with the following properties:<ul> 18763 * <li><b>onLoad(users): (optional)</b> when the object is successfully loaded from the server</li> 18764 * <li><b>onError(): (optional)</b> if loading of the object fails, invoked with the error response object</li> 18765 * @returns {finesse.restservices.Users} Users collection of User objects. 18766 */ 18767 getUsers: function (options) { 18768 this.isLoaded(); 18769 options = options || {}; 18770 18771 options.parentObj = this; 18772 // We are using getData() instead of getData.Users because the superclass (RestCollectionBase) 18773 // for Users needs the "Users" key to validate the provided payload matches the class type. 18774 options.data = this.getData(); 18775 18776 return new Users(options); 18777 }, 18778 18779 /** 18780 * @private 18781 * Getter for a teamNotReadyReasonCodes collection object that is associated with Team. 18782 * @param callbacks 18783 * @returns {teamNotReadyReasonCodes} 18784 * A teamNotReadyReasonCodes collection object. 18785 */ 18786 getTeamNotReadyReasonCodes: function (callbacks) { 18787 var options = callbacks || {}; 18788 options.parentObj = this; 18789 this.isLoaded(); 18790 18791 if (!this._teamNotReadyReasonCodes) { 18792 this._teamNotReadyReasonCodes = new TeamNotReadyReasonCodes(options); 18793 } 18794 18795 return this._teamNotReadyReasonCodes; 18796 }, 18797 18798 /** 18799 * @private 18800 * Getter for a teamWrapUpReasons collection object that is associated with Team. 18801 * @param callbacks 18802 * @returns {teamWrapUpReasons} 18803 * A teamWrapUpReasons collection object. 18804 */ 18805 getTeamWrapUpReasons: function (callbacks) { 18806 var options = callbacks || {}; 18807 options.parentObj = this; 18808 this.isLoaded(); 18809 18810 if (!this._teamWrapUpReasons) { 18811 this._teamWrapUpReasons = new TeamWrapUpReasons(options); 18812 } 18813 18814 return this._teamWrapUpReasons; 18815 }, 18816 18817 /** 18818 * @private 18819 * Getter for a teamSignOutReasonCodes collection object that is associated with Team. 18820 * @param callbacks 18821 * @returns {teamSignOutReasonCodes} 18822 * A teamSignOutReasonCodes collection object. 18823 */ 18824 18825 getTeamSignOutReasonCodes: function (callbacks) { 18826 var options = callbacks || {}; 18827 options.parentObj = this; 18828 this.isLoaded(); 18829 18830 if (!this._teamSignOutReasonCodes) { 18831 this._teamSignOutReasonCodes = new TeamSignOutReasonCodes(options); 18832 } 18833 18834 return this._teamSignOutReasonCodes; 18835 }, 18836 18837 /** 18838 * @private 18839 * Getter for a teamPhoneBooks collection object that is associated with Team. 18840 * @param callbacks 18841 * @returns {teamPhoneBooks} 18842 * A teamPhoneBooks collection object. 18843 */ 18844 getTeamPhoneBooks: function (callbacks) { 18845 var options = callbacks || {}; 18846 options.parentObj = this; 18847 this.isLoaded(); 18848 18849 if (!this._phonebooks) { 18850 this._phonebooks = new TeamPhoneBooks(options); 18851 } 18852 18853 return this._phonebooks; 18854 }, 18855 18856 /** 18857 * @private 18858 * Getter for a teamWorkflows collection object that is associated with Team. 18859 * @param callbacks 18860 * @returns {teamWorkflows} 18861 * A teamWorkflows collection object. 18862 */ 18863 getTeamWorkflows: function (callbacks) { 18864 var options = callbacks || {}; 18865 options.parentObj = this; 18866 this.isLoaded(); 18867 18868 if (!this._workflows) { 18869 this._workflows = new TeamWorkflows(options); 18870 } 18871 18872 return this._workflows; 18873 }, 18874 18875 /** 18876 * @private 18877 * Getter for a teamLayoutConfig object that is associated with Team. 18878 * @param callbacks 18879 * @returns {teamLayoutConfig} 18880 */ 18881 getTeamLayoutConfig: function (callbacks) { 18882 var options = callbacks || {}; 18883 options.parentObj = this; 18884 this.isLoaded(); 18885 18886 if (this._teamLayoutConfig === null) { 18887 this._teamLayoutConfig = new TeamLayoutConfig(options); 18888 } 18889 18890 return this._teamLayoutConfig; 18891 }, 18892 /** 18893 * @private 18894 * Getter for the teamMessages object that is associated with Team. 18895 * @param callbacks 18896 * @returns {teamMessages} 18897 */ 18898 getTeamMessages: function (callbacks) { 18899 var options = callbacks || {}; 18900 options.parentObj = this; 18901 this.isLoaded(); 18902 if(!this._teamMessages) { 18903 this._teamMessages = new TeamMessages(options); 18904 } 18905 return this._teamMessages; 18906 } 18907 18908 }); 18909 18910 window.finesse = window.finesse || {}; 18911 window.finesse.restservices = window.finesse.restservices || {}; 18912 window.finesse.restservices.Team = Team; 18913 18914 return Team; 18915 }); 18916 18917 /** 18918 * JavaScript representation of the Finesse Teams collection. 18919 * object which contains a list of Team objects 18920 * @requires finesse.clientservices.ClientServices 18921 * @requires Class 18922 * @requires finesse.FinesseBase 18923 * @requires finesse.restservices.RestBase 18924 * @requires finesse.restservices.RestCollectionBase 18925 */ 18926 18927 /** @private */ 18928 define('restservices/Teams',[ 18929 'restservices/RestCollectionBase', 18930 'restservices/Team' 18931 ], 18932 function (RestCollectionBase, Team) { 18933 /** @private */ 18934 var Teams = RestCollectionBase.extend({ 18935 18936 /** 18937 * @class 18938 * JavaScript representation of a Teams collection object. Also exposes methods to operate 18939 * on the object against the server. 18940 * 18941 * @param {Object} options 18942 * An object with the following properties:<ul> 18943 * <li><b>id:</b> The id of the object being constructed</li> 18944 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 18945 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 18946 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 18947 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 18948 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 18949 * <li><b>status:</b> {Number} The HTTP status code returned</li> 18950 * <li><b>content:</b> {String} Raw string of response</li> 18951 * <li><b>object:</b> {Object} Parsed object of response</li> 18952 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 18953 * <li><b>errorType:</b> {String} Type of error that was caught</li> 18954 * <li><b>errorMessage:</b> {String} Message associated with error</li> 18955 * </ul></li> 18956 * </ul></li> 18957 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 18958 * @constructs 18959 **/ 18960 init: function (options) { 18961 this._super(options); 18962 }, 18963 18964 /** 18965 * @private 18966 * Gets the REST class for the current object - this is the Teams class. 18967 * @returns {Object} The Teams constructor. 18968 */ 18969 getRestClass: function () { 18970 return Teams; 18971 }, 18972 18973 /** 18974 * @private 18975 * Gets the REST class for the objects that make up the collection. - this 18976 * is the Team class. 18977 */ 18978 getRestItemClass: function () { 18979 return Team; 18980 }, 18981 18982 /** 18983 * @private 18984 * Gets the REST type for the current object - this is a "Teams". 18985 * @returns {String} The Teams string. 18986 */ 18987 getRestType: function () { 18988 return "Teams"; 18989 }, 18990 18991 /** 18992 * @private 18993 * Gets the REST type for the objects that make up the collection - this is "Team". 18994 */ 18995 getRestItemType: function () { 18996 return "Team"; 18997 }, 18998 18999 /** 19000 * @private 19001 * Override default to indicates that the collection supports making 19002 * requests. 19003 */ 19004 supportsRequests: true, 19005 19006 /** 19007 * @private 19008 * Override default to indicate that this object doesn't support subscriptions. 19009 */ 19010 supportsRestItemSubscriptions: false, 19011 19012 /** 19013 * @private 19014 * Retrieve the Teams. This call will re-query the server and refresh the collection. 19015 * 19016 * @returns {finesse.restservices.Teams} 19017 * This Teams object to allow cascading. 19018 */ 19019 get: function () { 19020 // set loaded to false so it will rebuild the collection after the get 19021 this._loaded = false; 19022 // reset collection 19023 this._collection = {}; 19024 // perform get 19025 this._synchronize(); 19026 return this; 19027 } 19028 19029 }); 19030 19031 window.finesse = window.finesse || {}; 19032 window.finesse.restservices = window.finesse.restservices || {}; 19033 window.finesse.restservices.Teams = Teams; 19034 19035 return Teams; 19036 }); 19037 19038 /** 19039 * JavaScript representation of the Finesse SystemInfo object 19040 * 19041 * @requires finesse.clientservices.ClientServices 19042 * @requires Class 19043 * @requires finesse.FinesseBase 19044 * @requires finesse.restservices.RestBase 19045 */ 19046 19047 /** @private */ 19048 define('restservices/SystemInfo',['restservices/RestBase'], function (RestBase) { 19049 19050 var SystemInfo = RestBase.extend(/** @lends finesse.restservices.SystemInfo.prototype */{ 19051 /** 19052 * @private 19053 * Returns whether this object supports subscriptions 19054 */ 19055 supportsSubscriptions: false, 19056 19057 doNotRefresh: true, 19058 19059 /** 19060 * @class 19061 * JavaScript representation of a SystemInfo object. 19062 * 19063 * @augments finesse.restservices.RestBase 19064 * @see finesse.restservices.SystemInfo.Statuses 19065 * @constructs 19066 */ 19067 _fakeConstuctor: function () { 19068 /* This is here to hide the real init constructor from the public docs */ 19069 }, 19070 19071 /** 19072 * @private 19073 * JavaScript representation of a SystemInfo object. Also exposes methods to operate 19074 * on the object against the server. 19075 * 19076 * @param {Object} options 19077 * An object with the following properties:<ul> 19078 * <li><b>id:</b> The id of the object being constructed</li> 19079 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 19080 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 19081 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 19082 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 19083 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 19084 * <li><b>status:</b> {Number} The HTTP status code returned</li> 19085 * <li><b>content:</b> {String} Raw string of response</li> 19086 * <li><b>object:</b> {Object} Parsed object of response</li> 19087 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 19088 * <li><b>errorType:</b> {String} Type of error that was caught</li> 19089 * <li><b>errorMessage:</b> {String} Message associated with error</li> 19090 * </ul></li> 19091 * </ul></li> 19092 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 19093 **/ 19094 init: function (id, callbacks, restObj) 19095 { 19096 this._super(id, callbacks, restObj); 19097 }, 19098 19099 /** 19100 * @private 19101 * Gets the REST class for the current object - this is the SystemInfo object. 19102 * @returns {Object} The SystemInfo constructor. 19103 */ 19104 getRestClass: function () { 19105 return SystemInfo; 19106 }, 19107 19108 /** 19109 * @private 19110 * Gets the REST type for the current object - this is a "SystemInfo". 19111 * @returns {String} The SystemInfo string. 19112 */ 19113 getRestType: function () 19114 { 19115 return "SystemInfo"; 19116 }, 19117 19118 _validate: function (obj) 19119 { 19120 return true; 19121 }, 19122 19123 /** 19124 * Returns the status of the Finesse system. 19125 * IN_SERVICE if the Finesse API reports that it is in service, 19126 * OUT_OF_SERVICE otherwise. 19127 * @returns {finesse.restservices.SystemInfo.Statuses} System Status 19128 */ 19129 getStatus: function () { 19130 this.isLoaded(); 19131 return this.getData().status; 19132 }, 19133 19134 /** 19135 * Returns the lastCTIHeartbeatStatus 19136 * success - last CTI heartbeat status was successful. 19137 * failure - last CTI heartbeat status was unsuccessful. 19138 * @returns {finesse.restservices.SystemInfo.lastCTIHeartbeatStatus} Last Heartbeat to CTI was successful or not. 19139 */ 19140 getLastCTIHeartbeatStatus: function () { 19141 this.isLoaded(); 19142 return this.getData().lastCTIHeartbeatStatus; 19143 }, 19144 19145 /** 19146 * Returns the reason due to which Finesse is OUT OF SERVICE. 19147 * It returns empty string when Finesse status is IN_SERVICE. 19148 * @returns {String} statusReason if finesse is OUT OF SERVICE , or empty string otherwise. 19149 */ 19150 getStatusReason: function () { 19151 this.isLoaded(); 19152 return this.getData().statusReason; 19153 }, 19154 19155 /** 19156 * Returns the current timestamp from this SystemInfo object. 19157 * This is used to calculate time drift delta between server and client. 19158 * @returns {String} Time (GMT): yyyy-MM-dd'T'HH:mm:ss'Z' 19159 */ 19160 getCurrentTimestamp: function () { 19161 this.isLoaded(); 19162 return this.getData().currentTimestamp; 19163 }, 19164 19165 /** 19166 * Getter for the xmpp domain of the system. 19167 * @returns {String} The xmpp domain corresponding to this SystemInfo object. 19168 */ 19169 getXmppDomain: function () { 19170 this.isLoaded(); 19171 return this.getData().xmppDomain; 19172 }, 19173 19174 /** 19175 * Getter for the xmpp pubsub domain of the system. 19176 * @returns {String} The xmpp pubsub domain corresponding to this SystemInfo object. 19177 */ 19178 getXmppPubSubDomain: function () { 19179 this.isLoaded(); 19180 return this.getData().xmppPubSubDomain; 19181 }, 19182 19183 /** 19184 * Getter for the deployment type (UCCE or UCCX). 19185 * @returns {String} "UCCE" or "UCCX" 19186 */ 19187 getDeploymentType: function () { 19188 this.isLoaded(); 19189 return this.getData().deploymentType; 19190 }, 19191 19192 /** 19193 * Returns whether this is a single node deployment or not by checking for the existence of the secondary node in SystemInfo. 19194 * @returns {Boolean} True for single node deployments, false otherwise. 19195 */ 19196 isSingleNode: function () { 19197 var secondary = this.getData().secondaryNode; 19198 if (secondary && secondary.host) { 19199 return false; 19200 } 19201 return true; 19202 }, 19203 19204 /** 19205 * Checks all arguments against the primary and secondary hosts (FQDN) and returns the match. 19206 * This is useful for getting the FQDN of the current Finesse server. 19207 * @param {String} ...arguments[]... - any number of arguments to match against 19208 * @returns {String} FQDN (if properly configured) of the matched host of the primary or secondary node, or undefined if no match is found. 19209 */ 19210 getThisHost: function () { 19211 var i, 19212 primary = this.getData().primaryNode, 19213 secondary = this.getData().secondaryNode; 19214 19215 for (i = 0; (i < arguments.length); i = i + 1) { 19216 if (primary && arguments[i] === primary.host) { 19217 return primary.host; 19218 } else if (secondary && arguments[i] === secondary.host) { 19219 return secondary.host; 19220 } 19221 } 19222 }, 19223 19224 /** 19225 * Checks all arguments against the primary and secondary hosts (FQDN) and returns the other node. 19226 * This is useful for getting the FQDN of the other Finesse server, i.e. for failover purposes. 19227 * @param {String} arguments - any number of arguments to match against 19228 * @returns {String} FQDN (if properly configured) of the alternate node, defaults to primary if no match is found, undefined for single node deployments. 19229 */ 19230 getAlternateHost: function () { 19231 var i, 19232 isPrimary = false, 19233 primary = this.getData().primaryNode, 19234 secondary = this.getData().secondaryNode, 19235 xmppDomain = this.getData().xmppDomain, 19236 alternateHost; 19237 19238 if (primary && primary.host) { 19239 if (xmppDomain === primary.host) { 19240 isPrimary = true; 19241 } 19242 if (secondary && secondary.host) { 19243 if (isPrimary) { 19244 return secondary.host; 19245 } 19246 return primary.host; 19247 } 19248 } 19249 }, 19250 19251 /** 19252 * Gets the peripheral ID that Finesse is connected to. The peripheral 19253 * ID is the ID of the PG Routing Client (PIM). 19254 * 19255 * @returns {String} The peripheral Id if UCCE, or empty string otherwise. 19256 */ 19257 getPeripheralId : function () { 19258 this.isLoaded(); 19259 19260 var peripheralId = this.getData().peripheralId; 19261 if (peripheralId === null) { 19262 return ""; 19263 } else { 19264 return this.getData().peripheralId; 19265 } 19266 }, 19267 19268 /** 19269 * Gets the license. Only apply to UCCX. 19270 * 19271 * @returns {String} The license if UCCX, or empty string otherwise. 19272 */ 19273 getlicense : function () { 19274 this.isLoaded(); 19275 return this.getData().license || ""; 19276 }, 19277 19278 /** 19279 * Gets the systemAuthMode for the current deployment 19280 * 19281 * @returns {String} The System auth mode for current deployment 19282 */ 19283 getSystemAuthMode : function() { 19284 this.isLoaded(); 19285 return this.getData().systemAuthMode; 19286 } 19287 }); 19288 19289 SystemInfo.Statuses = /** @lends finesse.restservices.SystemInfo.Statuses.prototype */ { 19290 /** 19291 * Finesse is in service. 19292 */ 19293 IN_SERVICE: "IN_SERVICE", 19294 /** 19295 * Finesse is not in service. 19296 */ 19297 OUT_OF_SERVICE: "OUT_OF_SERVICE", 19298 /** 19299 * @class SystemInfo status values. 19300 * @constructs 19301 */ 19302 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 19303 19304 }; 19305 19306 window.finesse = window.finesse || {}; 19307 window.finesse.restservices = window.finesse.restservices || {}; 19308 window.finesse.restservices.SystemInfo = SystemInfo; 19309 19310 return SystemInfo; 19311 }); 19312 19313 define('restservices/DialogLogoutActions',[], function () 19314 { 19315 var DialogLogoutActions = /** @lends finesse.restservices.DialogLogoutActions.prototype */ { 19316 19317 /** 19318 * Set this action to close active dialogs when the agent logs out. 19319 */ 19320 CLOSE: "CLOSE", 19321 19322 /** 19323 * Set this action to transfer active dialogs when the agent logs out. 19324 */ 19325 TRANSFER: "TRANSFER", 19326 19327 /** 19328 * @class Actions used to handle tasks that are associated with a given media at logout time. 19329 * 19330 * @constructs 19331 */ 19332 _fakeConstructor: function () 19333 { 19334 }, // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 19335 19336 /** 19337 * Is the given action a valid dialog logout action. 19338 * 19339 * @param {String} action the action to evaluate 19340 * @returns {Boolean} true if the action is valid; false otherwise 19341 */ 19342 isValidAction: function(action) 19343 { 19344 if ( !action ) 19345 { 19346 return false; 19347 } 19348 19349 return DialogLogoutActions.hasOwnProperty(action.toUpperCase()); 19350 } 19351 }; 19352 19353 window.finesse = window.finesse || {}; 19354 window.finesse.restservices = window.finesse.restservices || {}; 19355 window.finesse.restservices.DialogLogoutActions = DialogLogoutActions; 19356 19357 return DialogLogoutActions; 19358 }); 19359 define('restservices/InterruptActions',[], function () 19360 { 19361 var InterruptActions = /** @lends finesse.restservices.InterruptActions.prototype */ 19362 { 19363 /** 19364 * The interrupt will be accepted and the agent will not work on dialogs in this media until the media is no longer interrupted. 19365 */ 19366 ACCEPT: "ACCEPT", 19367 19368 /** 19369 * the interrupt will be ignored and the agent is allowed to work on dialogs while the media is interrupted. 19370 */ 19371 IGNORE: "IGNORE", 19372 19373 /** 19374 * @class 19375 * 19376 * The action to be taken in the event this media is interrupted. The action will be one of the following:<ul> 19377 * <li><b>ACCEPT:</b> the interrupt will be accepted and the agent will not work on dialogs in this media 19378 * until the media is no longer interrupted.</li> 19379 * <li><b>IGNORE:</b> the interrupt will be ignored and the agent is allowed to work on dialogs while the 19380 * media is interrupted.</li></ul> 19381 * 19382 * @constructs 19383 */ 19384 _fakeConstructor: function () 19385 { 19386 }, // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 19387 19388 /** 19389 * Is the given action a valid dialog logout action. 19390 * 19391 * @param {String} action the action to evaluate 19392 * @returns {Boolean} true if the action is valid; false otherwise 19393 */ 19394 isValidAction: function(action) 19395 { 19396 if ( !action ) 19397 { 19398 return false; 19399 } 19400 19401 return InterruptActions.hasOwnProperty(action.toUpperCase()); 19402 } 19403 }; 19404 19405 window.finesse = window.finesse || {}; 19406 window.finesse.restservices = window.finesse.restservices || {}; 19407 window.finesse.restservices.InterruptActions = InterruptActions; 19408 19409 return InterruptActions; 19410 }); 19411 /** 19412 * Utility class for looking up a ReasonCode using the code and category. 19413 * 19414 */ 19415 19416 /** @private */ 19417 define('restservices/ReasonCodeLookup',['restservices/RestBase', 'utilities/Utilities'], function (RestBase, Utilities) { 19418 19419 var ReasonCodeLookup = RestBase.extend(/** @lends finesse.restservices.ReasonCodeLookup.prototype */{ 19420 /** 19421 * @private 19422 * Returns whether this object supports subscriptions 19423 */ 19424 supportsSubscriptions: false, 19425 19426 doNotRefresh: true, 19427 19428 autoSubscribe: false, 19429 19430 supportsRequests: false, 19431 19432 /** 19433 * @class 19434 * Utility class for looking up a ReasonCode using the code and category. 19435 * 19436 * @constructs 19437 */ 19438 _fakeConstuctor: function () { 19439 /* This is here to hide the real init constructor from the public docs */ 19440 }, 19441 19442 /** 19443 * @private 19444 * JavaScript representation of a ReasonCodeLookup object. Also exposes methods to operate 19445 * on the object against the server. 19446 * 19447 * @param {Object} options 19448 * An object with the following properties:<ul> 19449 * <li><b>id:</b> The id of the object being constructed</li> 19450 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 19451 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 19452 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 19453 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 19454 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 19455 * <li><b>status:</b> {Number} The HTTP status code returned</li> 19456 * <li><b>content:</b> {String} Raw string of response</li> 19457 * <li><b>object:</b> {Object} Parsed object of response</li> 19458 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 19459 * <li><b>errorType:</b> {String} Type of error that was caught</li> 19460 * <li><b>errorMessage:</b> {String} Message associated with error</li> 19461 * </ul></li> 19462 * </ul></li> 19463 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 19464 **/ 19465 init: function (options){ 19466 this._super(options); 19467 }, 19468 19469 /** 19470 * @private 19471 * Do get disabled. 19472 */ 19473 doGet: function(handlers) { 19474 return; 19475 }, 19476 19477 /** 19478 * @private 19479 * Gets the REST class for the current object - this is the ReasonCodeLookup object. 19480 */ 19481 getRestClass: function () { 19482 return ReasonCodeLookup; 19483 }, 19484 19485 /** 19486 * @private 19487 * Gets the REST type for the current object - this is a "ReasonCodeLookup". 19488 */ 19489 getRestType: function () { 19490 return "ReasonCode"; 19491 }, 19492 19493 19494 /** 19495 * @private 19496 * Parses a uriString to retrieve the id portion 19497 * @param {String} uriString 19498 * @return {String} id 19499 */ 19500 _parseIdFromUriString : function (uriString) { 19501 return Utilities.getId(uriString); 19502 }, 19503 19504 /** 19505 * Performs a GET against the Finesse server, for looking up the reason code 19506 * with its reason code value, and category. 19507 * Note that there is no return value; use the success handler to process a 19508 * valid return. 19509 * 19510 * @param {finesse.interfaces.RequestHandlers} handlers 19511 * An object containing the handlers for the request 19512 * @param {String} reasonCodeValue The code for the reason code to lookup 19513 * @param {String} reasonCodeCategory The category for the reason code to lookup. 19514 * The possible values are "NOT_READY" and "LOGOUT". 19515 * 19516 * @example 19517 * new finesse.restservices.ReasonCodeLookup().lookupReasonCode({ 19518 * success: _handleReasonCodeGet, 19519 * error: _handleReasonCodeGetError 19520 * }, '32762', 'NOT_READY'); 19521 * _handleReasonCodeGet(_reasonCode) { 19522 * var id = _reasonCode.id; 19523 * var uri = _reasonCode.uri; 19524 * var label = _reasonCode.label; 19525 * ... 19526 * } 19527 * 19528 */ 19529 lookupReasonCode : function (handlers, reasonCodeValue, reasonCodeCategory) { 19530 var self = this, contentBody, reasonCode, url; 19531 contentBody = {}; 19532 19533 url = this.getRestUrl(); 19534 url = url + "?category=" + reasonCodeCategory + "&code=" + reasonCodeValue; 19535 this.restRequest(url, { 19536 method: 'GET', 19537 success: function (rsp) { 19538 reasonCode = { 19539 uri: rsp.object.ReasonCode.uri, 19540 label: rsp.object.ReasonCode.label, 19541 id: self._parseIdFromUriString(rsp.object.ReasonCode.uri) 19542 }; 19543 handlers.success(reasonCode); 19544 }, 19545 error: function (rsp) { 19546 handlers.error(rsp); 19547 }, 19548 content: contentBody 19549 }); 19550 } 19551 19552 }); 19553 19554 19555 window.finesse = window.finesse || {}; 19556 window.finesse.restservices = window.finesse.restservices || {}; 19557 window.finesse.restservices.ReasonCodeLookup = ReasonCodeLookup; 19558 19559 return ReasonCodeLookup; 19560 }); 19561 19562 /** 19563 * JavaScript representation of the Finesse ChatConfig object 19564 * @requires ClientServices 19565 * @requires finesse.FinesseBase 19566 * @requires finesse.restservices.RestBase 19567 */ 19568 19569 /** @private */ 19570 define('restservices/ChatConfig',['restservices/RestBase'], function (RestBase) { 19571 19572 var ChatConfig = RestBase.extend(/** @lends finesse.restservices.ChatConfig.prototype */{ 19573 19574 /** 19575 * @class 19576 * JavaScript representation of a ChatConfig object. Also exposes methods to operate 19577 * on the object against the server. 19578 * 19579 * @constructor 19580 * @param {String} id 19581 * Not required... 19582 * @param {Object} callbacks 19583 * An object containing callbacks for instantiation and runtime 19584 * @param {Function} callbacks.onLoad(this) 19585 * Callback to invoke upon successful instantiation, passes in User object 19586 * @param {Function} callbacks.onLoadError(rsp) 19587 * Callback to invoke on instantiation REST request error 19588 * as passed by finesse.clientservices.ClientServices.ajax() 19589 * { 19590 * status: {Number} The HTTP status code returned 19591 * content: {String} Raw string of response 19592 * object: {Object} Parsed object of response 19593 * error: {Object} Wrapped exception that was caught 19594 * error.errorType: {String} Type of error that was caught 19595 * error.errorMessage: {String} Message associated with error 19596 * } 19597 * @param {Function} callbacks.onChange(this) 19598 * Callback to invoke upon successful update, passes in User object 19599 * @param {Function} callbacks.onError(rsp) 19600 * Callback to invoke on update error (refresh or event) 19601 * as passed by finesse.clientservices.ClientServices.ajax() 19602 * { 19603 * status: {Number} The HTTP status code returned 19604 * content: {String} Raw string of response 19605 * object: {Object} Parsed object of response 19606 * error: {Object} Wrapped exception that was caught 19607 * error.errorType: {String} Type of error that was caught 19608 * error.errorMessage: {String} Message associated with error 19609 * } 19610 * @constructs 19611 */ 19612 init: function (callbacks) { 19613 this._super("", callbacks); 19614 }, 19615 19616 /** 19617 * Gets the REST class for the current object - this is the ChatConfig object. 19618 */ 19619 getRestClass: function () { 19620 return ChatConfig; 19621 }, 19622 19623 /** 19624 * Gets the REST type for the current object - this is a "ChatConfig". 19625 */ 19626 getRestType: function () { 19627 return "ChatConfig"; 19628 }, 19629 19630 /** 19631 * Overrides the parent class. Returns the url for the ChatConfig resource 19632 */ 19633 getRestUrl: function () { 19634 return ("/finesse/api/" + this.getRestType()); 19635 }, 19636 19637 /** 19638 * Returns whether this object supports subscriptions 19639 */ 19640 supportsSubscriptions: false, 19641 19642 /** 19643 * Getter for the Chat primary node of the ChatConfig 19644 * @returns {String} 19645 */ 19646 getPrimaryNode: function () { 19647 this.isLoaded(); 19648 return this.getData().primaryNode; 19649 }, 19650 19651 /** 19652 * Getter for the Chat secondary node (if any) of the ChatConfig 19653 * @returns {String} 19654 */ 19655 getSecondaryNode: function () { 19656 this.isLoaded(); 19657 return this.getData().secondaryNode; 19658 }, 19659 19660 /** 19661 * Retrieve the chat config settings 19662 * @returns {finesse.restservices.ChatConfig} 19663 * This ChatConfig object to allow cascading 19664 */ 19665 get: function () { 19666 this._synchronize(); 19667 19668 return this; // Allow cascading 19669 }, 19670 19671 /** 19672 * Closure handle updating of the internal data for the ChatConfig object 19673 * upon a successful update (PUT) request before calling the intended 19674 * success handler provided by the consumer 19675 * 19676 * @param {Object} 19677 * chatconfig Reference to this ChatConfig object 19678 * @param {Object} 19679 * chatSettings Object that contains the chat server settings to be 19680 * submitted in the api request 19681 * @param {Function} 19682 * successHandler The success handler specified by the consumer 19683 * of this object 19684 * @returns {finesse.restservices.ChatConfig} This ChatConfig object to allow cascading 19685 */ 19686 createPutSuccessHandler: function (chatconfig, chatSettings, successHandler) { 19687 return function (rsp) { 19688 // Update internal structure based on response. Here we 19689 // inject the chatSettings object into the 19690 // rsp.object.ChatConfig element as a way to take 19691 // advantage of the existing _processResponse method. 19692 rsp.object.ChatConfig = chatSettings; 19693 chatconfig._processResponse(rsp); 19694 19695 //Remove the injected chatSettings object before cascading response 19696 rsp.object.ChatConfig = {}; 19697 19698 //cascade response back to consumer's response handler 19699 successHandler(rsp); 19700 }; 19701 }, 19702 19703 /** 19704 * Update the chat config settings 19705 * @param {Object} chatSettings 19706 * The Chat server settings you want to configure 19707 * @param {Object} handlers 19708 * An object containing callback handlers for the request. Optional. 19709 * @param {Function} options.success(rsp) 19710 * A callback function to be invoked for a successful request. 19711 * { 19712 * status: {Number} The HTTP status code returned 19713 * content: {String} Raw string of response 19714 * object: {Object} Parsed object of response 19715 * } 19716 * @param {Function} options.error(rsp) 19717 * A callback function to be invoked for an unsuccessful request. 19718 * { 19719 * status: {Number} The HTTP status code returned 19720 * content: {String} Raw string of response 19721 * object: {Object} Parsed object of response (HTTP errors) 19722 * error: {Object} Wrapped exception that was caught 19723 * error.errorType: {String} Type of error that was caught 19724 * error.errorMessage: {String} Message associated with error 19725 * } 19726 * @returns {finesse.restservices.ChatConfig} 19727 * This ChatConfig object to allow cascading 19728 */ 19729 update: function (chatSettings, handlers) { 19730 this.isLoaded(); 19731 var contentBody = {}; 19732 19733 contentBody[this.getRestType()] = { 19734 "primaryNode": chatSettings.primaryNode, 19735 "secondaryNode": chatSettings.secondaryNode 19736 }; 19737 19738 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 19739 handlers = handlers || {}; 19740 19741 this.restRequest(this.getRestUrl(), { 19742 method: 'PUT', 19743 success: this.createPutSuccessHandler(this, chatSettings, handlers.success), 19744 error: handlers.error, 19745 content: contentBody 19746 }); 19747 19748 return this; // Allow cascading 19749 } 19750 }); 19751 19752 window.finesse = window.finesse || {}; 19753 window.finesse.restservices = window.finesse.restservices || {}; 19754 window.finesse.restservices.ChatConfig = ChatConfig; 19755 19756 return ChatConfig; 19757 }); 19758 19759 /** 19760 * Provides standard way resolve message keys with substitution 19761 * 19762 * @requires finesse.container.I18n or gadgets.Prefs 19763 */ 19764 19765 // Add Utilities to the finesse.utilities namespace 19766 define('utilities/I18n',['utilities/Utilities'], function (Utilities) { 19767 var I18n = (function () { 19768 19769 /** 19770 * Shortcut to finesse.container.I18n.getMsg or gadgets.Prefs.getMsg 19771 * @private 19772 */ 19773 var _getMsg; 19774 19775 return { 19776 /** 19777 * Provides a message resolver for this utility singleton. 19778 * @param {Function} getMsg 19779 * A function that returns a string given a message key. 19780 * If the key is not found, this function must return 19781 * something that tests false (i.e. undefined or ""). 19782 */ 19783 setGetter : function (getMsg) { 19784 _getMsg = getMsg; 19785 }, 19786 19787 /** 19788 * Resolves the given message key, also performing substitution. 19789 * This generic utility will use a custom function to resolve the key 19790 * provided by finesse.utilities.I18n.setGetter. Otherwise, it will 19791 * discover either finesse.container.I18n.getMsg or gadgets.Prefs.getMsg 19792 * upon the first invocation and store that reference for efficiency. 19793 * 19794 * Since this will construct a new gadgets.Prefs object, it is recommended 19795 * for gadgets to explicitly provide the setter to prevent duplicate 19796 * gadgets.Prefs objects. This does not apply if your gadget does not need 19797 * access to gadgets.Prefs other than getMsg. 19798 * 19799 * @param {String} key 19800 * The key to lookup 19801 * @param {String} arguments 19802 * Arguments for substitution 19803 * @returns {String/Function} 19804 * The resolved string if successful, otherwise a function that returns 19805 * a '???' string that can also be casted into a string. 19806 */ 19807 getString : function (key) { 19808 var prefs, i, retStr, noMsg, getFailed = "", args; 19809 if (!_getMsg) { 19810 if (finesse.container && finesse.container.I18n) { 19811 _getMsg = finesse.container.I18n.getMsg; 19812 } else if (gadgets) { 19813 prefs = new gadgets.Prefs(); 19814 _getMsg = prefs.getMsg; 19815 } 19816 } 19817 19818 try { 19819 retStr = _getMsg(key); 19820 } catch (e) { 19821 getFailed = "finesse.utilities.I18n.getString(): invalid _getMsg"; 19822 } 19823 19824 if (retStr) { 19825 // Lookup was successful, perform substitution (if any) 19826 args = [ retStr ]; 19827 for (i = 1; i < arguments.length; i += 1) { 19828 args.push(arguments[i]); 19829 } 19830 return Utilities.formatString.apply(this, args); 19831 } 19832 // We want a function because jQuery.html() and jQuery.text() is smart enough to invoke it. 19833 /** @private */ 19834 noMsg = function () { 19835 return "???" + key + "???" + getFailed; 19836 }; 19837 // We overload the toString() of this "function" to allow JavaScript to cast it into a string 19838 // For example, var myMsg = "something " + finesse.utilities.I18n.getMsg("unresolvable.key"); 19839 /** @private */ 19840 noMsg.toString = function () { 19841 return "???" + key + "???" + getFailed; 19842 }; 19843 return noMsg; 19844 19845 } 19846 }; 19847 }()); 19848 19849 window.finesse = window.finesse || {}; 19850 window.finesse.utilities = window.finesse.utilities || {}; 19851 window.finesse.utilities.I18n = I18n; 19852 19853 return I18n; 19854 }); 19855 19856 /** 19857 * Logging.js: provides simple logging for clients to use and overrides synchronous native methods: alert(), confirm(), and prompt(). 19858 * 19859 * On Firefox, it will hook into console for logging. On IE, it will log to the status bar. 19860 */ 19861 // Add Utilities to the finesse.utilities namespace 19862 define('utilities/Logger',[], function () { 19863 var Logger = (function () { 19864 19865 var 19866 19867 /** @private **/ 19868 debugOn, 19869 19870 /** 19871 * Pads a single digit number for display purposes (e.g. '4' shows as '04') 19872 * @param num is the number to pad to 2 digits 19873 * @returns a two digit padded string 19874 * @private 19875 */ 19876 padTwoDigits = function (num) { 19877 return (num < 10) ? '0' + num : num; 19878 }, 19879 19880 /** 19881 * Checks to see if we have a console - this allows us to support Firefox or IE. 19882 * @returns {Boolean} True for Firefox, False for IE 19883 * @private 19884 */ 19885 hasConsole = function () { 19886 var retval = false; 19887 try 19888 { 19889 if (window.console !== undefined) 19890 { 19891 retval = true; 19892 } 19893 } 19894 catch (err) 19895 { 19896 retval = false; 19897 } 19898 19899 return retval; 19900 }, 19901 19902 /** 19903 * Gets a timestamp. 19904 * @returns {String} is a timestamp in the following format: HH:MM:SS 19905 * @private 19906 */ 19907 getTimeStamp = function () { 19908 var date = new Date(), timeStr; 19909 timeStr = padTwoDigits(date.getHours()) + ":" + padTwoDigits(date.getMinutes()) + ":" + padTwoDigits(date.getSeconds()); 19910 19911 return timeStr; 19912 }; 19913 19914 return { 19915 /** 19916 * Enable debug mode. Debug mode may impact performance on the UI. 19917 * 19918 * @param {Boolean} enable 19919 * True to enable debug logging. 19920 * @private 19921 */ 19922 setDebug : function (enable) { 19923 debugOn = enable; 19924 }, 19925 19926 /** 19927 * Logs a string as DEBUG. 19928 * 19929 * @param str is the string to log. 19930 * @private 19931 */ 19932 log : function (str) { 19933 var timeStr = getTimeStamp(); 19934 19935 if (debugOn) { 19936 if (hasConsole()) 19937 { 19938 window.console.log(timeStr + ": " + "DEBUG" + " - " + str); 19939 } 19940 } 19941 }, 19942 19943 /** 19944 * Logs a string as INFO. 19945 * 19946 * @param str is the string to log. 19947 * @private 19948 */ 19949 info : function (str) { 19950 var timeStr = getTimeStamp(); 19951 19952 if (hasConsole()) 19953 { 19954 window.console.info(timeStr + ": " + "INFO" + " - " + str); 19955 } 19956 }, 19957 19958 /** 19959 * Logs a string as WARN. 19960 * 19961 * @param str is the string to log. 19962 * @private 19963 */ 19964 warn : function (str) { 19965 var timeStr = getTimeStamp(); 19966 19967 if (hasConsole()) 19968 { 19969 window.console.warn(timeStr + ": " + "WARN" + " - " + str); 19970 } 19971 }, 19972 /** 19973 * Logs a string as ERROR. 19974 * 19975 * @param str is the string to log. 19976 * @private 19977 */ 19978 error : function (str) { 19979 var timeStr = getTimeStamp(); 19980 19981 if (hasConsole()) 19982 { 19983 window.console.error(timeStr + ": " + "ERROR" + " - " + str); 19984 } 19985 } 19986 }; 19987 }()); 19988 19989 return Logger; 19990 }); 19991 19992 /** 19993 * BackSpaceHandler.js: provides functionality to prevent the page from navigating back and hence losing the unsaved data when backspace is pressed. 19994 * 19995 */ 19996 define('utilities/BackSpaceHandler',[], function () { 19997 var eventCallback = function(event) { 19998 var doPrevent = false, d = event.srcElement || event.target; 19999 if (event.keyCode === 8) { 20000 if ((d.tagName.toUpperCase() === 'INPUT' && (d.type 20001 .toUpperCase() === 'TEXT' 20002 || d.type.toUpperCase() === 'PASSWORD' 20003 || d.type.toUpperCase() === 'FILE' 20004 || d.type.toUpperCase() === 'SEARCH' 20005 || d.type.toUpperCase() === 'EMAIL' 20006 || d.type.toUpperCase() === 'NUMBER' || d.type 20007 .toUpperCase() === 'DATE')) 20008 || d.tagName.toUpperCase() === 'TEXTAREA') { 20009 doPrevent = d.readOnly || d.disabled; 20010 } else { 20011 //if HTML content is editable doPrevent will be false and vice versa 20012 doPrevent = (!d.isContentEditable); 20013 } 20014 } 20015 20016 if (doPrevent) { 20017 event.preventDefault(); 20018 } 20019 }; 20020 20021 if (window.addEventListener) { 20022 window.addEventListener('keydown', eventCallback); 20023 } else if (window.attachEvent) { 20024 window.attachEvent('onkeydown', eventCallback); 20025 } else { 20026 window.console.error("Unable to attach backspace handler event "); 20027 } 20028 }); 20029 !function(a,b){"function"==typeof define&&define.amd?define('utilities/../../thirdparty/tv4/tv4.min.js',[],b):"undefined"!=typeof module&&module.exports?module.exports=b():a.tv4=b()}(this,function(){function a(a){return encodeURI(a).replace(/%25[0-9][0-9]/g,function(a){return"%"+a.substring(3)})}function b(b){var c="";m[b.charAt(0)]&&(c=b.charAt(0),b=b.substring(1));var d="",e="",f=!0,g=!1,h=!1;"+"===c?f=!1:"."===c?(e=".",d="."):"/"===c?(e="/",d="/"):"#"===c?(e="#",f=!1):";"===c?(e=";",d=";",g=!0,h=!0):"?"===c?(e="?",d="&",g=!0):"&"===c&&(e="&",d="&",g=!0);for(var i=[],j=b.split(","),k=[],l={},o=0;o<j.length;o++){var p=j[o],q=null;if(-1!==p.indexOf(":")){var r=p.split(":");p=r[0],q=parseInt(r[1],10)}for(var s={};n[p.charAt(p.length-1)];)s[p.charAt(p.length-1)]=!0,p=p.substring(0,p.length-1);var t={truncate:q,name:p,suffices:s};k.push(t),l[p]=t,i.push(p)}var u=function(b){for(var c="",i=0,j=0;j<k.length;j++){var l=k[j],m=b(l.name);if(null===m||void 0===m||Array.isArray(m)&&0===m.length||"object"==typeof m&&0===Object.keys(m).length)i++;else if(c+=j===i?e:d||",",Array.isArray(m)){g&&(c+=l.name+"=");for(var n=0;n<m.length;n++)n>0&&(c+=l.suffices["*"]?d||",":",",l.suffices["*"]&&g&&(c+=l.name+"=")),c+=f?encodeURIComponent(m[n]).replace(/!/g,"%21"):a(m[n])}else if("object"==typeof m){g&&!l.suffices["*"]&&(c+=l.name+"=");var o=!0;for(var p in m)o||(c+=l.suffices["*"]?d||",":","),o=!1,c+=f?encodeURIComponent(p).replace(/!/g,"%21"):a(p),c+=l.suffices["*"]?"=":",",c+=f?encodeURIComponent(m[p]).replace(/!/g,"%21"):a(m[p])}else g&&(c+=l.name,h&&""===m||(c+="=")),null!=l.truncate&&(m=m.substring(0,l.truncate)),c+=f?encodeURIComponent(m).replace(/!/g,"%21"):a(m)}return c};return u.varNames=i,{prefix:e,substitution:u}}function c(a){if(!(this instanceof c))return new c(a);for(var d=a.split("{"),e=[d.shift()],f=[],g=[],h=[];d.length>0;){var i=d.shift(),j=i.split("}")[0],k=i.substring(j.length+1),l=b(j);g.push(l.substitution),f.push(l.prefix),e.push(k),h=h.concat(l.substitution.varNames)}this.fill=function(a){for(var b=e[0],c=0;c<g.length;c++){var d=g[c];b+=d(a),b+=e[c+1]}return b},this.varNames=h,this.template=a}function d(a,b){if(a===b)return!0;if(a&&b&&"object"==typeof a&&"object"==typeof b){if(Array.isArray(a)!==Array.isArray(b))return!1;if(Array.isArray(a)){if(a.length!==b.length)return!1;for(var c=0;c<a.length;c++)if(!d(a[c],b[c]))return!1}else{var e;for(e in a)if(void 0===b[e]&&void 0!==a[e])return!1;for(e in b)if(void 0===a[e]&&void 0!==b[e])return!1;for(e in a)if(!d(a[e],b[e]))return!1}return!0}return!1}function e(a){var b=String(a).replace(/^\s+|\s+$/g,"").match(/^([^:\/?#]+:)?(\/\/(?:[^:@]*(?::[^:@]*)?@)?(([^:\/?#]*)(?::(\d*))?))?([^?#]*)(\?[^#]*)?(#[\s\S]*)?/);return b?{href:b[0]||"",protocol:b[1]||"",authority:b[2]||"",host:b[3]||"",hostname:b[4]||"",port:b[5]||"",pathname:b[6]||"",search:b[7]||"",hash:b[8]||""}:null}function f(a,b){function c(a){var b=[];return a.replace(/^(\.\.?(\/|$))+/,"").replace(/\/(\.(\/|$))+/g,"/").replace(/\/\.\.$/,"/../").replace(/\/?[^\/]*/g,function(a){"/.."===a?b.pop():b.push(a)}),b.join("").replace(/^\//,"/"===a.charAt(0)?"/":"")}return b=e(b||""),a=e(a||""),b&&a?(b.protocol||a.protocol)+(b.protocol||b.authority?b.authority:a.authority)+c(b.protocol||b.authority||"/"===b.pathname.charAt(0)?b.pathname:b.pathname?(a.authority&&!a.pathname?"/":"")+a.pathname.slice(0,a.pathname.lastIndexOf("/")+1)+b.pathname:a.pathname)+(b.protocol||b.authority||b.pathname?b.search:b.search||a.search)+b.hash:null}function g(a){return a.split("#")[0]}function h(a,b){if(a&&"object"==typeof a)if(void 0===b?b=a.id:"string"==typeof a.id&&(b=f(b,a.id),a.id=b),Array.isArray(a))for(var c=0;c<a.length;c++)h(a[c],b);else{"string"==typeof a.$ref&&(a.$ref=f(b,a.$ref));for(var d in a)"enum"!==d&&h(a[d],b)}}function i(a){a=a||"en";var b=v[a];return function(a){var c=b[a.code]||u[a.code];if("string"!=typeof c)return"Unknown error code "+a.code+": "+JSON.stringify(a.messageParams);var d=a.params;return c.replace(/\{([^{}]*)\}/g,function(a,b){var c=d[b];return"string"==typeof c||"number"==typeof c?c:a})}}function j(a,b,c,d,e){if(Error.call(this),void 0===a)throw new Error("No error code supplied: "+d);this.message="",this.params=b,this.code=a,this.dataPath=c||"",this.schemaPath=d||"",this.subErrors=e||null;var f=new Error(this.message);if(this.stack=f.stack||f.stacktrace,!this.stack)try{throw f}catch(f){this.stack=f.stack||f.stacktrace}}function k(a,b){if(b.substring(0,a.length)===a){var c=b.substring(a.length);if(b.length>0&&"/"===b.charAt(a.length-1)||"#"===c.charAt(0)||"?"===c.charAt(0))return!0}return!1}function l(a){var b,c,d=new o,e={setErrorReporter:function(a){return"string"==typeof a?this.language(a):(c=a,!0)},addFormat:function(){d.addFormat.apply(d,arguments)},language:function(a){return a?(v[a]||(a=a.split("-")[0]),v[a]?(b=a,a):!1):b},addLanguage:function(a,b){var c;for(c in r)b[c]&&!b[r[c]]&&(b[r[c]]=b[c]);var d=a.split("-")[0];if(v[d]){v[a]=Object.create(v[d]);for(c in b)"undefined"==typeof v[d][c]&&(v[d][c]=b[c]),v[a][c]=b[c]}else v[a]=b,v[d]=b;return this},freshApi:function(a){var b=l();return a&&b.language(a),b},validate:function(a,e,f,g){var h=i(b),j=c?function(a,b,d){return c(a,b,d)||h(a,b,d)}:h,k=new o(d,!1,j,f,g);"string"==typeof e&&(e={$ref:e}),k.addSchema("",e);var l=k.validateAll(a,e,null,null,"");return!l&&g&&(l=k.banUnknownProperties(a,e)),this.error=l,this.missing=k.missing,this.valid=null===l,this.valid},validateResult:function(){var a={};return this.validate.apply(a,arguments),a},validateMultiple:function(a,e,f,g){var h=i(b),j=c?function(a,b,d){return c(a,b,d)||h(a,b,d)}:h,k=new o(d,!0,j,f,g);"string"==typeof e&&(e={$ref:e}),k.addSchema("",e),k.validateAll(a,e,null,null,""),g&&k.banUnknownProperties(a,e);var l={};return l.errors=k.errors,l.missing=k.missing,l.valid=0===l.errors.length,l},addSchema:function(){return d.addSchema.apply(d,arguments)},getSchema:function(){return d.getSchema.apply(d,arguments)},getSchemaMap:function(){return d.getSchemaMap.apply(d,arguments)},getSchemaUris:function(){return d.getSchemaUris.apply(d,arguments)},getMissingUris:function(){return d.getMissingUris.apply(d,arguments)},dropSchemas:function(){d.dropSchemas.apply(d,arguments)},defineKeyword:function(){d.defineKeyword.apply(d,arguments)},defineError:function(a,b,c){if("string"!=typeof a||!/^[A-Z]+(_[A-Z]+)*$/.test(a))throw new Error("Code name must be a string in UPPER_CASE_WITH_UNDERSCORES");if("number"!=typeof b||b%1!==0||1e4>b)throw new Error("Code number must be an integer > 10000");if("undefined"!=typeof r[a])throw new Error("Error already defined: "+a+" as "+r[a]);if("undefined"!=typeof s[b])throw new Error("Error code already used: "+s[b]+" as "+b);r[a]=b,s[b]=a,u[a]=u[b]=c;for(var d in v){var e=v[d];e[a]&&(e[b]=e[b]||e[a])}},reset:function(){d.reset(),this.error=null,this.missing=[],this.valid=!0},missing:[],error:null,valid:!0,normSchema:h,resolveUrl:f,getDocumentUri:g,errorCodes:r};return e.language(a||"en"),e}Object.keys||(Object.keys=function(){var a=Object.prototype.hasOwnProperty,b=!{toString:null}.propertyIsEnumerable("toString"),c=["toString","toLocaleString","valueOf","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","constructor"],d=c.length;return function(e){if("object"!=typeof e&&"function"!=typeof e||null===e)throw new TypeError("Object.keys called on non-object");var f=[];for(var g in e)a.call(e,g)&&f.push(g);if(b)for(var h=0;d>h;h++)a.call(e,c[h])&&f.push(c[h]);return f}}()),Object.create||(Object.create=function(){function a(){}return function(b){if(1!==arguments.length)throw new Error("Object.create implementation only accepts one parameter.");return a.prototype=b,new a}}()),Array.isArray||(Array.isArray=function(a){return"[object Array]"===Object.prototype.toString.call(a)}),Array.prototype.indexOf||(Array.prototype.indexOf=function(a){if(null===this)throw new TypeError;var b=Object(this),c=b.length>>>0;if(0===c)return-1;var d=0;if(arguments.length>1&&(d=Number(arguments[1]),d!==d?d=0:0!==d&&d!==1/0&&d!==-(1/0)&&(d=(d>0||-1)*Math.floor(Math.abs(d)))),d>=c)return-1;for(var e=d>=0?d:Math.max(c-Math.abs(d),0);c>e;e++)if(e in b&&b[e]===a)return e;return-1}),Object.isFrozen||(Object.isFrozen=function(a){for(var b="tv4_test_frozen_key";a.hasOwnProperty(b);)b+=Math.random();try{return a[b]=!0,delete a[b],!1}catch(c){return!0}});var m={"+":!0,"#":!0,".":!0,"/":!0,";":!0,"?":!0,"&":!0},n={"*":!0};c.prototype={toString:function(){return this.template},fillFromObject:function(a){return this.fill(function(b){return a[b]})}};var o=function(a,b,c,d,e){if(this.missing=[],this.missingMap={},this.formatValidators=a?Object.create(a.formatValidators):{},this.schemas=a?Object.create(a.schemas):{},this.collectMultiple=b,this.errors=[],this.handleError=b?this.collectError:this.returnError,d&&(this.checkRecursive=!0,this.scanned=[],this.scannedFrozen=[],this.scannedFrozenSchemas=[],this.scannedFrozenValidationErrors=[],this.validatedSchemasKey="tv4_validation_id",this.validationErrorsKey="tv4_validation_errors_id"),e&&(this.trackUnknownProperties=!0,this.knownPropertyPaths={},this.unknownPropertyPaths={}),this.errorReporter=c||i("en"),"string"==typeof this.errorReporter)throw new Error("debug");if(this.definedKeywords={},a)for(var f in a.definedKeywords)this.definedKeywords[f]=a.definedKeywords[f].slice(0)};o.prototype.defineKeyword=function(a,b){this.definedKeywords[a]=this.definedKeywords[a]||[],this.definedKeywords[a].push(b)},o.prototype.createError=function(a,b,c,d,e,f,g){var h=new j(a,b,c,d,e);return h.message=this.errorReporter(h,f,g),h},o.prototype.returnError=function(a){return a},o.prototype.collectError=function(a){return a&&this.errors.push(a),null},o.prototype.prefixErrors=function(a,b,c){for(var d=a;d<this.errors.length;d++)this.errors[d]=this.errors[d].prefixWith(b,c);return this},o.prototype.banUnknownProperties=function(a,b){for(var c in this.unknownPropertyPaths){var d=this.createError(r.UNKNOWN_PROPERTY,{path:c},c,"",null,a,b),e=this.handleError(d);if(e)return e}return null},o.prototype.addFormat=function(a,b){if("object"==typeof a){for(var c in a)this.addFormat(c,a[c]);return this}this.formatValidators[a]=b},o.prototype.resolveRefs=function(a,b){if(void 0!==a.$ref){if(b=b||{},b[a.$ref])return this.createError(r.CIRCULAR_REFERENCE,{urls:Object.keys(b).join(", ")},"","",null,void 0,a);b[a.$ref]=!0,a=this.getSchema(a.$ref,b)}return a},o.prototype.getSchema=function(a,b){var c;if(void 0!==this.schemas[a])return c=this.schemas[a],this.resolveRefs(c,b);var d=a,e="";if(-1!==a.indexOf("#")&&(e=a.substring(a.indexOf("#")+1),d=a.substring(0,a.indexOf("#"))),"object"==typeof this.schemas[d]){c=this.schemas[d];var f=decodeURIComponent(e);if(""===f)return this.resolveRefs(c,b);if("/"!==f.charAt(0))return void 0;for(var g=f.split("/").slice(1),h=0;h<g.length;h++){var i=g[h].replace(/~1/g,"/").replace(/~0/g,"~");if(void 0===c[i]){c=void 0;break}c=c[i]}if(void 0!==c)return this.resolveRefs(c,b)}void 0===this.missing[d]&&(this.missing.push(d),this.missing[d]=d,this.missingMap[d]=d)},o.prototype.searchSchemas=function(a,b){if(Array.isArray(a))for(var c=0;c<a.length;c++)this.searchSchemas(a[c],b);else if(a&&"object"==typeof a){"string"==typeof a.id&&k(b,a.id)&&void 0===this.schemas[a.id]&&(this.schemas[a.id]=a);for(var d in a)if("enum"!==d)if("object"==typeof a[d])this.searchSchemas(a[d],b);else if("$ref"===d){var e=g(a[d]);e&&void 0===this.schemas[e]&&void 0===this.missingMap[e]&&(this.missingMap[e]=e)}}},o.prototype.addSchema=function(a,b){if("string"!=typeof a||"undefined"==typeof b){if("object"!=typeof a||"string"!=typeof a.id)return;b=a,a=b.id}a===g(a)+"#"&&(a=g(a)),this.schemas[a]=b,delete this.missingMap[a],h(b,a),this.searchSchemas(b,a)},o.prototype.getSchemaMap=function(){var a={};for(var b in this.schemas)a[b]=this.schemas[b];return a},o.prototype.getSchemaUris=function(a){var b=[];for(var c in this.schemas)(!a||a.test(c))&&b.push(c);return b},o.prototype.getMissingUris=function(a){var b=[];for(var c in this.missingMap)(!a||a.test(c))&&b.push(c);return b},o.prototype.dropSchemas=function(){this.schemas={},this.reset()},o.prototype.reset=function(){this.missing=[],this.missingMap={},this.errors=[]},o.prototype.validateAll=function(a,b,c,d,e){var f;if(b=this.resolveRefs(b),!b)return null;if(b instanceof j)return this.errors.push(b),b;var g,h=this.errors.length,i=null,k=null;if(this.checkRecursive&&a&&"object"==typeof a){if(f=!this.scanned.length,a[this.validatedSchemasKey]){var l=a[this.validatedSchemasKey].indexOf(b);if(-1!==l)return this.errors=this.errors.concat(a[this.validationErrorsKey][l]),null}if(Object.isFrozen(a)&&(g=this.scannedFrozen.indexOf(a),-1!==g)){var m=this.scannedFrozenSchemas[g].indexOf(b);if(-1!==m)return this.errors=this.errors.concat(this.scannedFrozenValidationErrors[g][m]),null}if(this.scanned.push(a),Object.isFrozen(a))-1===g&&(g=this.scannedFrozen.length,this.scannedFrozen.push(a),this.scannedFrozenSchemas.push([])),i=this.scannedFrozenSchemas[g].length,this.scannedFrozenSchemas[g][i]=b,this.scannedFrozenValidationErrors[g][i]=[];else{if(!a[this.validatedSchemasKey])try{Object.defineProperty(a,this.validatedSchemasKey,{value:[],configurable:!0}),Object.defineProperty(a,this.validationErrorsKey,{value:[],configurable:!0})}catch(n){a[this.validatedSchemasKey]=[],a[this.validationErrorsKey]=[]}k=a[this.validatedSchemasKey].length,a[this.validatedSchemasKey][k]=b,a[this.validationErrorsKey][k]=[]}}var o=this.errors.length,p=this.validateBasic(a,b,e)||this.validateNumeric(a,b,e)||this.validateString(a,b,e)||this.validateArray(a,b,e)||this.validateObject(a,b,e)||this.validateCombinations(a,b,e)||this.validateHypermedia(a,b,e)||this.validateFormat(a,b,e)||this.validateDefinedKeywords(a,b,e)||null;if(f){for(;this.scanned.length;){var q=this.scanned.pop();delete q[this.validatedSchemasKey]}this.scannedFrozen=[],this.scannedFrozenSchemas=[]}if(p||o!==this.errors.length)for(;c&&c.length||d&&d.length;){var r=c&&c.length?""+c.pop():null,s=d&&d.length?""+d.pop():null;p&&(p=p.prefixWith(r,s)),this.prefixErrors(o,r,s)}return null!==i?this.scannedFrozenValidationErrors[g][i]=this.errors.slice(h):null!==k&&(a[this.validationErrorsKey][k]=this.errors.slice(h)),this.handleError(p)},o.prototype.validateFormat=function(a,b){if("string"!=typeof b.format||!this.formatValidators[b.format])return null;var c=this.formatValidators[b.format].call(null,a,b);return"string"==typeof c||"number"==typeof c?this.createError(r.FORMAT_CUSTOM,{message:c},"","/format",null,a,b):c&&"object"==typeof c?this.createError(r.FORMAT_CUSTOM,{message:c.message||"?"},c.dataPath||"",c.schemaPath||"/format",null,a,b):null},o.prototype.validateDefinedKeywords=function(a,b,c){for(var d in this.definedKeywords)if("undefined"!=typeof b[d])for(var e=this.definedKeywords[d],f=0;f<e.length;f++){var g=e[f],h=g(a,b[d],b,c);if("string"==typeof h||"number"==typeof h)return this.createError(r.KEYWORD_CUSTOM,{key:d,message:h},"","",null,a,b).prefixWith(null,d);if(h&&"object"==typeof h){var i=h.code;if("string"==typeof i){if(!r[i])throw new Error("Undefined error code (use defineError): "+i);i=r[i]}else"number"!=typeof i&&(i=r.KEYWORD_CUSTOM);var j="object"==typeof h.message?h.message:{key:d,message:h.message||"?"},k=h.schemaPath||"/"+d.replace(/~/g,"~0").replace(/\//g,"~1");return this.createError(i,j,h.dataPath||null,k,null,a,b)}}return null},o.prototype.validateBasic=function(a,b,c){var d;return(d=this.validateType(a,b,c))?d.prefixWith(null,"type"):(d=this.validateEnum(a,b,c))?d.prefixWith(null,"type"):null},o.prototype.validateType=function(a,b){if(void 0===b.type)return null;var c=typeof a;null===a?c="null":Array.isArray(a)&&(c="array");var d=b.type;Array.isArray(d)||(d=[d]);for(var e=0;e<d.length;e++){var f=d[e];if(f===c||"integer"===f&&"number"===c&&a%1===0)return null}return this.createError(r.INVALID_TYPE,{type:c,expected:d.join("/")},"","",null,a,b)},o.prototype.validateEnum=function(a,b){if(void 0===b["enum"])return null;for(var c=0;c<b["enum"].length;c++){var e=b["enum"][c];if(d(a,e))return null}return this.createError(r.ENUM_MISMATCH,{value:"undefined"!=typeof JSON?JSON.stringify(a):a},"","",null,a,b)},o.prototype.validateNumeric=function(a,b,c){return this.validateMultipleOf(a,b,c)||this.validateMinMax(a,b,c)||this.validateNaN(a,b,c)||null};var p=Math.pow(2,-51),q=1-p;o.prototype.validateMultipleOf=function(a,b){var c=b.multipleOf||b.divisibleBy;if(void 0===c)return null;if("number"==typeof a){var d=a/c%1;if(d>=p&&q>d)return this.createError(r.NUMBER_MULTIPLE_OF,{value:a,multipleOf:c},"","",null,a,b)}return null},o.prototype.validateMinMax=function(a,b){if("number"!=typeof a)return null;if(void 0!==b.minimum){if(a<b.minimum)return this.createError(r.NUMBER_MINIMUM,{value:a,minimum:b.minimum},"","/minimum",null,a,b);if(b.exclusiveMinimum&&a===b.minimum)return this.createError(r.NUMBER_MINIMUM_EXCLUSIVE,{value:a,minimum:b.minimum},"","/exclusiveMinimum",null,a,b)}if(void 0!==b.maximum){if(a>b.maximum)return this.createError(r.NUMBER_MAXIMUM,{value:a,maximum:b.maximum},"","/maximum",null,a,b);if(b.exclusiveMaximum&&a===b.maximum)return this.createError(r.NUMBER_MAXIMUM_EXCLUSIVE,{value:a,maximum:b.maximum},"","/exclusiveMaximum",null,a,b)}return null},o.prototype.validateNaN=function(a,b){return"number"!=typeof a?null:isNaN(a)===!0||a===1/0||a===-(1/0)?this.createError(r.NUMBER_NOT_A_NUMBER,{value:a},"","/type",null,a,b):null},o.prototype.validateString=function(a,b,c){return this.validateStringLength(a,b,c)||this.validateStringPattern(a,b,c)||null},o.prototype.validateStringLength=function(a,b){return"string"!=typeof a?null:void 0!==b.minLength&&a.length<b.minLength?this.createError(r.STRING_LENGTH_SHORT,{length:a.length,minimum:b.minLength},"","/minLength",null,a,b):void 0!==b.maxLength&&a.length>b.maxLength?this.createError(r.STRING_LENGTH_LONG,{length:a.length,maximum:b.maxLength},"","/maxLength",null,a,b):null},o.prototype.validateStringPattern=function(a,b){if("string"!=typeof a||"string"!=typeof b.pattern&&!(b.pattern instanceof RegExp))return null;var c;if(b.pattern instanceof RegExp)c=b.pattern;else{var d,e="",f=b.pattern.match(/^\/(.+)\/([img]*)$/);f?(d=f[1],e=f[2]):d=b.pattern,c=new RegExp(d,e)}return c.test(a)?null:this.createError(r.STRING_PATTERN,{pattern:b.pattern},"","/pattern",null,a,b)},o.prototype.validateArray=function(a,b,c){return Array.isArray(a)?this.validateArrayLength(a,b,c)||this.validateArrayUniqueItems(a,b,c)||this.validateArrayItems(a,b,c)||null:null},o.prototype.validateArrayLength=function(a,b){var c;return void 0!==b.minItems&&a.length<b.minItems&&(c=this.createError(r.ARRAY_LENGTH_SHORT,{length:a.length,minimum:b.minItems},"","/minItems",null,a,b),this.handleError(c))?c:void 0!==b.maxItems&&a.length>b.maxItems&&(c=this.createError(r.ARRAY_LENGTH_LONG,{length:a.length,maximum:b.maxItems},"","/maxItems",null,a,b),this.handleError(c))?c:null},o.prototype.validateArrayUniqueItems=function(a,b){if(b.uniqueItems)for(var c=0;c<a.length;c++)for(var e=c+1;e<a.length;e++)if(d(a[c],a[e])){var f=this.createError(r.ARRAY_UNIQUE,{match1:c,match2:e},"","/uniqueItems",null,a,b);if(this.handleError(f))return f}return null},o.prototype.validateArrayItems=function(a,b,c){if(void 0===b.items)return null;var d,e;if(Array.isArray(b.items)){for(e=0;e<a.length;e++)if(e<b.items.length){if(d=this.validateAll(a[e],b.items[e],[e],["items",e],c+"/"+e))return d}else if(void 0!==b.additionalItems)if("boolean"==typeof b.additionalItems){if(!b.additionalItems&&(d=this.createError(r.ARRAY_ADDITIONAL_ITEMS,{},"/"+e,"/additionalItems",null,a,b),this.handleError(d)))return d}else if(d=this.validateAll(a[e],b.additionalItems,[e],["additionalItems"],c+"/"+e))return d}else for(e=0;e<a.length;e++)if(d=this.validateAll(a[e],b.items,[e],["items"],c+"/"+e))return d;return null},o.prototype.validateObject=function(a,b,c){return"object"!=typeof a||null===a||Array.isArray(a)?null:this.validateObjectMinMaxProperties(a,b,c)||this.validateObjectRequiredProperties(a,b,c)||this.validateObjectProperties(a,b,c)||this.validateObjectDependencies(a,b,c)||null},o.prototype.validateObjectMinMaxProperties=function(a,b){var c,d=Object.keys(a);return void 0!==b.minProperties&&d.length<b.minProperties&&(c=this.createError(r.OBJECT_PROPERTIES_MINIMUM,{propertyCount:d.length,minimum:b.minProperties},"","/minProperties",null,a,b),this.handleError(c))?c:void 0!==b.maxProperties&&d.length>b.maxProperties&&(c=this.createError(r.OBJECT_PROPERTIES_MAXIMUM,{propertyCount:d.length,maximum:b.maxProperties},"","/maxProperties",null,a,b),this.handleError(c))?c:null},o.prototype.validateObjectRequiredProperties=function(a,b){if(void 0!==b.required)for(var c=0;c<b.required.length;c++){var d=b.required[c];if(void 0===a[d]){var e=this.createError(r.OBJECT_REQUIRED,{key:d},"","/required/"+c,null,a,b);if(this.handleError(e))return e}}return null},o.prototype.validateObjectProperties=function(a,b,c){var d;for(var e in a){var f=c+"/"+e.replace(/~/g,"~0").replace(/\//g,"~1"),g=!1;if(void 0!==b.properties&&void 0!==b.properties[e]&&(g=!0,d=this.validateAll(a[e],b.properties[e],[e],["properties",e],f)))return d;if(void 0!==b.patternProperties)for(var h in b.patternProperties){var i=new RegExp(h);if(i.test(e)&&(g=!0,d=this.validateAll(a[e],b.patternProperties[h],[e],["patternProperties",h],f)))return d}if(g)this.trackUnknownProperties&&(this.knownPropertyPaths[f]=!0,delete this.unknownPropertyPaths[f]);else if(void 0!==b.additionalProperties){if(this.trackUnknownProperties&&(this.knownPropertyPaths[f]=!0,delete this.unknownPropertyPaths[f]),"boolean"==typeof b.additionalProperties){if(!b.additionalProperties&&(d=this.createError(r.OBJECT_ADDITIONAL_PROPERTIES,{key:e},"","/additionalProperties",null,a,b).prefixWith(e,null),this.handleError(d)))return d}else if(d=this.validateAll(a[e],b.additionalProperties,[e],["additionalProperties"],f))return d}else this.trackUnknownProperties&&!this.knownPropertyPaths[f]&&(this.unknownPropertyPaths[f]=!0)}return null},o.prototype.validateObjectDependencies=function(a,b,c){var d;if(void 0!==b.dependencies)for(var e in b.dependencies)if(void 0!==a[e]){var f=b.dependencies[e];if("string"==typeof f){if(void 0===a[f]&&(d=this.createError(r.OBJECT_DEPENDENCY_KEY,{key:e,missing:f},"","",null,a,b).prefixWith(null,e).prefixWith(null,"dependencies"),this.handleError(d)))return d}else if(Array.isArray(f))for(var g=0;g<f.length;g++){var h=f[g];if(void 0===a[h]&&(d=this.createError(r.OBJECT_DEPENDENCY_KEY,{key:e,missing:h},"","/"+g,null,a,b).prefixWith(null,e).prefixWith(null,"dependencies"),this.handleError(d)))return d}else if(d=this.validateAll(a,f,[],["dependencies",e],c))return d}return null},o.prototype.validateCombinations=function(a,b,c){return this.validateAllOf(a,b,c)||this.validateAnyOf(a,b,c)||this.validateOneOf(a,b,c)||this.validateNot(a,b,c)||null},o.prototype.validateAllOf=function(a,b,c){if(void 0===b.allOf)return null;for(var d,e=0;e<b.allOf.length;e++){var f=b.allOf[e];if(d=this.validateAll(a,f,[],["allOf",e],c))return d}return null},o.prototype.validateAnyOf=function(a,b,c){if(void 0===b.anyOf)return null;var d,e,f=[],g=this.errors.length;this.trackUnknownProperties&&(d=this.unknownPropertyPaths,e=this.knownPropertyPaths);for(var h=!0,i=0;i<b.anyOf.length;i++){this.trackUnknownProperties&&(this.unknownPropertyPaths={},this.knownPropertyPaths={});var j=b.anyOf[i],k=this.errors.length,l=this.validateAll(a,j,[],["anyOf",i],c);if(null===l&&k===this.errors.length){if(this.errors=this.errors.slice(0,g),this.trackUnknownProperties){for(var m in this.knownPropertyPaths)e[m]=!0,delete d[m];for(var n in this.unknownPropertyPaths)e[n]||(d[n]=!0);h=!1;continue}return null}l&&f.push(l.prefixWith(null,""+i).prefixWith(null,"anyOf"))}return this.trackUnknownProperties&&(this.unknownPropertyPaths=d,this.knownPropertyPaths=e),h?(f=f.concat(this.errors.slice(g)),this.errors=this.errors.slice(0,g),this.createError(r.ANY_OF_MISSING,{},"","/anyOf",f,a,b)):void 0},o.prototype.validateOneOf=function(a,b,c){if(void 0===b.oneOf)return null;var d,e,f=null,g=[],h=this.errors.length;this.trackUnknownProperties&&(d=this.unknownPropertyPaths,e=this.knownPropertyPaths);for(var i=0;i<b.oneOf.length;i++){this.trackUnknownProperties&&(this.unknownPropertyPaths={},this.knownPropertyPaths={});var j=b.oneOf[i],k=this.errors.length,l=this.validateAll(a,j,[],["oneOf",i],c);if(null===l&&k===this.errors.length){if(null!==f)return this.errors=this.errors.slice(0,h),this.createError(r.ONE_OF_MULTIPLE,{index1:f,index2:i},"","/oneOf",null,a,b);if(f=i,this.trackUnknownProperties){for(var m in this.knownPropertyPaths)e[m]=!0,delete d[m];for(var n in this.unknownPropertyPaths)e[n]||(d[n]=!0)}}else l&&g.push(l)}return this.trackUnknownProperties&&(this.unknownPropertyPaths=d,this.knownPropertyPaths=e),null===f?(g=g.concat(this.errors.slice(h)),this.errors=this.errors.slice(0,h),this.createError(r.ONE_OF_MISSING,{},"","/oneOf",g,a,b)):(this.errors=this.errors.slice(0,h),null)},o.prototype.validateNot=function(a,b,c){if(void 0===b.not)return null;var d,e,f=this.errors.length;this.trackUnknownProperties&&(d=this.unknownPropertyPaths,e=this.knownPropertyPaths,this.unknownPropertyPaths={},this.knownPropertyPaths={});var g=this.validateAll(a,b.not,null,null,c),h=this.errors.slice(f);return this.errors=this.errors.slice(0,f),this.trackUnknownProperties&&(this.unknownPropertyPaths=d,this.knownPropertyPaths=e),null===g&&0===h.length?this.createError(r.NOT_PASSED,{},"","/not",null,a,b):null},o.prototype.validateHypermedia=function(a,b,d){if(!b.links)return null;for(var e,f=0;f<b.links.length;f++){var g=b.links[f];if("describedby"===g.rel){for(var h=new c(g.href),i=!0,j=0;j<h.varNames.length;j++)if(!(h.varNames[j]in a)){i=!1;break}if(i){var k=h.fillFromObject(a),l={$ref:k};if(e=this.validateAll(a,l,[],["links",f],d))return e}}}};var r={INVALID_TYPE:0,ENUM_MISMATCH:1,ANY_OF_MISSING:10,ONE_OF_MISSING:11,ONE_OF_MULTIPLE:12,NOT_PASSED:13,NUMBER_MULTIPLE_OF:100,NUMBER_MINIMUM:101,NUMBER_MINIMUM_EXCLUSIVE:102,NUMBER_MAXIMUM:103,NUMBER_MAXIMUM_EXCLUSIVE:104,NUMBER_NOT_A_NUMBER:105,STRING_LENGTH_SHORT:200,STRING_LENGTH_LONG:201,STRING_PATTERN:202,OBJECT_PROPERTIES_MINIMUM:300,OBJECT_PROPERTIES_MAXIMUM:301,OBJECT_REQUIRED:302,OBJECT_ADDITIONAL_PROPERTIES:303,OBJECT_DEPENDENCY_KEY:304,ARRAY_LENGTH_SHORT:400,ARRAY_LENGTH_LONG:401,ARRAY_UNIQUE:402,ARRAY_ADDITIONAL_ITEMS:403,FORMAT_CUSTOM:500,KEYWORD_CUSTOM:501,CIRCULAR_REFERENCE:600,UNKNOWN_PROPERTY:1e3},s={};for(var t in r)s[r[t]]=t;var u={INVALID_TYPE:"Invalid type: {type} (expected {expected})",ENUM_MISMATCH:"No enum match for: {value}",ANY_OF_MISSING:'Data does not match any schemas from "anyOf"',ONE_OF_MISSING:'Data does not match any schemas from "oneOf"',ONE_OF_MULTIPLE:'Data is valid against more than one schema from "oneOf": indices {index1} and {index2}',NOT_PASSED:'Data matches schema from "not"',NUMBER_MULTIPLE_OF:"Value {value} is not a multiple of {multipleOf}",NUMBER_MINIMUM:"Value {value} is less than minimum {minimum}",NUMBER_MINIMUM_EXCLUSIVE:"Value {value} is equal to exclusive minimum {minimum}",NUMBER_MAXIMUM:"Value {value} is greater than maximum {maximum}",NUMBER_MAXIMUM_EXCLUSIVE:"Value {value} is equal to exclusive maximum {maximum}",NUMBER_NOT_A_NUMBER:"Value {value} is not a valid number",STRING_LENGTH_SHORT:"String is too short ({length} chars), minimum {minimum}",STRING_LENGTH_LONG:"String is too long ({length} chars), maximum {maximum}",STRING_PATTERN:"String does not match pattern: {pattern}",OBJECT_PROPERTIES_MINIMUM:"Too few properties defined ({propertyCount}), minimum {minimum}",OBJECT_PROPERTIES_MAXIMUM:"Too many properties defined ({propertyCount}), maximum {maximum}",OBJECT_REQUIRED:"Missing required property: {key}",OBJECT_ADDITIONAL_PROPERTIES:"Additional properties not allowed",OBJECT_DEPENDENCY_KEY:"Dependency failed - key must exist: {missing} (due to key: {key})",ARRAY_LENGTH_SHORT:"Array is too short ({length}), minimum {minimum}",ARRAY_LENGTH_LONG:"Array is too long ({length}), maximum {maximum}",ARRAY_UNIQUE:"Array items are not unique (indices {match1} and {match2})",ARRAY_ADDITIONAL_ITEMS:"Additional items not allowed",FORMAT_CUSTOM:"Format validation failed ({message})",KEYWORD_CUSTOM:"Keyword failed: {key} ({message})",CIRCULAR_REFERENCE:"Circular $refs: {urls}",UNKNOWN_PROPERTY:"Unknown property (not in schema)"};j.prototype=Object.create(Error.prototype),j.prototype.constructor=j,j.prototype.name="ValidationError",j.prototype.prefixWith=function(a,b){if(null!==a&&(a=a.replace(/~/g,"~0").replace(/\//g,"~1"),this.dataPath="/"+a+this.dataPath),null!==b&&(b=b.replace(/~/g,"~0").replace(/\//g,"~1"),this.schemaPath="/"+b+this.schemaPath),null!==this.subErrors)for(var c=0;c<this.subErrors.length;c++)this.subErrors[c].prefixWith(a,b);return this};var v={},w=l();return w.addLanguage("en-gb",u),w.tv4=w,w}); 20030 define('utilities/JsonValidator',[ 20031 "../../thirdparty/tv4/tv4.min.js" 20032 ], 20033 function (tv4) { 20034 20035 var jsonValdiator = /** @lends finesse.utilities.JsonValidator */ { 20036 20037 /** 20038 * @class 20039 * For JSON validation 20040 * 20041 * @constructs 20042 */ 20043 _fakeConstuctor: function () { 20044 /* This is here for jsdocs. */ 20045 }, 20046 20047 /** 20048 * Validates JSON data by applying a specific schema 20049 * 20050 * @param jsonData - JSON data 20051 * @param schema - JSON schema that would validate the parameter jsonData. 20052 * It needs to follow the <a href="http://json-schema.org">JSON schema definition standard</a> 20053 * @returns - JSON Result that is of the below format 20054 * <pre> 20055 * { 20056 * "valid": [true/false], 20057 * "error": [tv4 error object if schema is not valid] 20058 * } 20059 * </pre> 20060 * The error object will look something like this: 20061 * <pre> 20062 * { 20063 * "code": 0, 20064 * "message": "Invalid type: string", 20065 * "dataPath": "/intKey", 20066 * "schemaPath": "/properties/intKey/type" 20067 * } 20068 * </pre> 20069 */ 20070 validateJson: function (jsonData, schema) { 20071 //window.console.info("To validate schema"); 20072 var valid = tv4.validate(jsonData, schema); 20073 var result = {}; 20074 result.valid = valid; 20075 if (!valid) { 20076 result.error = tv4.error; 20077 } 20078 return result; 20079 } 20080 } 20081 20082 20083 20084 window.finesse = window.finesse || {}; 20085 window.finesse.utilities = window.finesse.utilities || {}; 20086 window.finesse.utilities.JsonValidator = jsonValdiator; 20087 20088 return jsonValdiator; 20089 }); 20090 /* using variables before they are defined. 20091 */ 20092 /*global navigator,unescape,sessionStorage,localStorage,_initSessionList,_initSessionListComplete */ 20093 20094 /** 20095 * Allows each gadget to communicate with the server to send logs. 20096 */ 20097 20098 /** 20099 * @class 20100 * @private 20101 * Allows each product to initialize its method of storage 20102 */ 20103 define('cslogger/FinesseLogger',["clientservices/ClientServices", "utilities/Utilities"], function (ClientServices, Utilities) { 20104 20105 var FinesseLogger = (function () { 20106 20107 var 20108 20109 /** 20110 * Array use to collect ongoing logs in memory 20111 * @private 20112 */ 20113 _logArray = [], 20114 20115 /** 20116 * The final data string sent to the server, =_logArray.join 20117 * @private 20118 */ 20119 _logStr = "", 20120 20121 /** 20122 * Keep track of size of log 20123 * @private 20124 */ 20125 _logSize = 0, 20126 20127 /** 20128 * Flag to keep track show/hide of send log link 20129 * @private 20130 */ 20131 _sendLogShown = false, 20132 20133 /** 20134 * Flag to keep track if local log initialized 20135 * @private 20136 */ 20137 _loggingInitialized = false, 20138 20139 20140 /** 20141 * local log size limit 20142 * @private 20143 */ 20144 _maxLocalStorageSize = 5000000, 20145 20146 /** 20147 * half local log size limit 20148 * @private 20149 */ 20150 _halfMaxLocalStorageSize = 0.5*_maxLocalStorageSize, 20151 20152 20153 /** 20154 * threshold for purge 20155 * @private 20156 */ 20157 _purgeStartPercent = 0.75, 20158 20159 /** 20160 * log item prefix 20161 * @private 20162 */ 20163 _linePrefix = null, 20164 20165 /** 20166 * locallog session 20167 * @private 20168 */ 20169 _session = null, 20170 20171 /** 20172 * Flag to keep track show/hide of send log link 20173 * @private 20174 */ 20175 _sessionKey = null, 20176 /** 20177 * Log session metadata 20178 * @private 20179 */ 20180 _logInfo = {}, 20181 20182 /** 20183 * Flag to find sessions 20184 * @private 20185 */ 20186 _findSessionsObj = null, 20187 20188 /** 20189 * Wrap up console.log esp. for IE9 20190 * @private 20191 */ 20192 _myConsoleLog = function (str) { 20193 if (window.console !== undefined) { 20194 window.console.log(str); 20195 } 20196 }, 20197 /** 20198 * Initialize the Local Logging 20199 * @private 20200 */ 20201 _initLogging = function () { 20202 if (_loggingInitialized) { 20203 return; 20204 } 20205 //Build a new store 20206 _session = sessionStorage.getItem("finSessKey"); 20207 //if the _session is null or empty, skip the init 20208 if (!_session) { 20209 return; 20210 } 20211 _sessionKey = "Fi"+_session; 20212 _linePrefix = _sessionKey + "_"; 20213 _logInfo = {}; 20214 _logInfo.name = _session; 20215 _logInfo.size = 0; 20216 _logInfo.head = 0; 20217 _logInfo.tail = 0; 20218 _logInfo.startTime = new Date().getTime(); 20219 _loggingInitialized = true; 20220 _initSessionList(); 20221 }, 20222 20223 /** 20224 * get total data size 20225 * 20226 * @return {Integer} which is the amount of data stored in local storage. 20227 * @private 20228 */ 20229 _getTotalData = function () 20230 { 20231 var sessName, sessLogInfoStr,sessLogInfoObj, sessionsInfoObj, totalData = 0, 20232 sessionsInfoStr = localStorage.getItem("FinesseSessionsInfo"); 20233 if (!sessionsInfoStr) { 20234 return 0; 20235 } 20236 sessionsInfoObj = JSON.parse(sessionsInfoStr); 20237 20238 for (sessName in sessionsInfoObj.sessions) 20239 { 20240 if (sessionsInfoObj.sessions.hasOwnProperty(sessName)) { 20241 sessLogInfoStr = localStorage.getItem("Fi" + sessName); 20242 if (!sessLogInfoStr) { 20243 _myConsoleLog("_getTotalData failed to get log info for "+sessName); 20244 } 20245 else { 20246 sessLogInfoObj = JSON.parse(sessLogInfoStr); 20247 totalData = totalData + sessLogInfoObj.size; 20248 } 20249 } 20250 } 20251 20252 return totalData; 20253 }, 20254 20255 /** 20256 * Remove lines from tail up until store size decreases to half of max size limit. 20257 * 20258 * @private 20259 */ 20260 _purgeCurrentSession = function() { 20261 var curStoreSize, purgedSize=0, line, tailKey, secLogInfoStr, logInfoStr, theLogInfo; 20262 curStoreSize = _getTotalData(); 20263 if (curStoreSize < _halfMaxLocalStorageSize) { 20264 return; 20265 } 20266 logInfoStr = localStorage.getItem(_sessionKey); 20267 if (!logInfoStr) { 20268 return; 20269 } 20270 theLogInfo = JSON.parse(logInfoStr); 20271 //_myConsoleLog("Starting _purgeCurrentSession() - currentStoreSize=" + curStoreSize); 20272 while(curStoreSize > _halfMaxLocalStorageSize) { 20273 try { 20274 tailKey = _sessionKey+"_"+theLogInfo.tail; 20275 line = localStorage.getItem(tailKey); 20276 if (line) { 20277 purgedSize = purgedSize +line.length; 20278 localStorage.removeItem(tailKey); 20279 curStoreSize = curStoreSize - line.length; 20280 theLogInfo.size = theLogInfo.size - line.length; 20281 } 20282 } 20283 catch (err) { 20284 _myConsoleLog("purgeCurrentSession encountered err="+err); 20285 } 20286 if (theLogInfo.tail < theLogInfo.head) { 20287 theLogInfo.tail = theLogInfo.tail + 1; 20288 } 20289 else { 20290 break; 20291 } 20292 } 20293 //purge stops here, we need to update session's meta data in storage 20294 secLogInfoStr = localStorage.getItem(_sessionKey); 20295 if (!secLogInfoStr) { 20296 //somebody cleared the localStorage 20297 return; 20298 } 20299 20300 //_myConsoleLog("In _purgeCurrentSession() - after purging current session, currentStoreSize=" + curStoreSize); 20301 //_myConsoleLog("In _purgeCurrentSession() - after purging purgedSize=" + purgedSize); 20302 //_myConsoleLog("In _purgeCurrentSession() - after purging logInfo.size=" + theLogInfo.size); 20303 //_myConsoleLog("In _purgeCurrentSession() - after purging logInfo.tail=" + theLogInfo.tail); 20304 localStorage.setItem(_sessionKey, JSON.stringify(theLogInfo)); 20305 _myConsoleLog("Done _purgeCurrentSession() - currentStoreSize=" + curStoreSize); 20306 }, 20307 20308 /** 20309 * Purge a session 20310 * 20311 * @param sessionName is the name of the session 20312 * @return {Integer} which is the current amount of data purged 20313 * @private 20314 */ 20315 _purgeSession = function (sessionName) { 20316 var theLogInfo, logInfoStr, sessionsInfoStr, sessionsInfoObj; 20317 //Get the session logInfo 20318 logInfoStr = localStorage.getItem("Fi" + sessionName); 20319 if (!logInfoStr) { 20320 _myConsoleLog("_purgeSession failed to get logInfo for "+sessionName); 20321 return 0; 20322 } 20323 theLogInfo = JSON.parse(logInfoStr); 20324 20325 //Note: This assumes that we don't crash in the middle of purging 20326 //=> if we do then it should get deleted next time 20327 //Purge tail->head 20328 while (theLogInfo.tail <= theLogInfo.head) 20329 { 20330 try { 20331 localStorage.removeItem("Fi" + sessionName + "_" + theLogInfo.tail); 20332 theLogInfo.tail = theLogInfo.tail + 1; 20333 } 20334 catch (err) { 20335 _myConsoleLog("In _purgeSession err="+err); 20336 break; 20337 } 20338 } 20339 20340 //Remove the entire session 20341 localStorage.removeItem("Fi" + sessionName); 20342 20343 //Update FinesseSessionsInfo 20344 sessionsInfoStr = localStorage.getItem("FinesseSessionsInfo"); 20345 if (!sessionsInfoStr) { 20346 _myConsoleLog("_purgeSession could not get sessions Info, it was cleared?"); 20347 return 0; 20348 } 20349 sessionsInfoObj = JSON.parse(sessionsInfoStr); 20350 if (sessionsInfoObj.sessions !== null) 20351 { 20352 delete sessionsInfoObj.sessions[sessionName]; 20353 20354 sessionsInfoObj.total = sessionsInfoObj.total - 1; 20355 sessionsInfoObj.lastWrittenBy = _session; 20356 localStorage.setItem("FinesseSessionsInfo", JSON.stringify(sessionsInfoObj)); 20357 } 20358 20359 return theLogInfo.size; 20360 }, 20361 20362 /** 20363 * purge old sessions 20364 * 20365 * @param storeSize 20366 * @return {Boolean} whether purging reaches its target 20367 * @private 20368 */ 20369 _purgeOldSessions = function (storeSize) { 20370 var sessionsInfoStr, purgedSize = 0, sessName, sessions, curStoreSize, activeSession, sessionsInfoObj; 20371 sessionsInfoStr = localStorage.getItem("FinesseSessionsInfo"); 20372 if (!sessionsInfoStr) { 20373 _myConsoleLog("Could not get FinesseSessionsInfo"); 20374 return true; 20375 } 20376 sessionsInfoObj = JSON.parse(sessionsInfoStr); 20377 curStoreSize = _getTotalData(); 20378 20379 activeSession = _session; 20380 sessions = sessionsInfoObj.sessions; 20381 for (sessName in sessions) { 20382 if (sessions.hasOwnProperty(sessName)) { 20383 if (sessName !== activeSession) { 20384 purgedSize = purgedSize + _purgeSession(sessName); 20385 if ((curStoreSize-purgedSize) < _halfMaxLocalStorageSize) { 20386 return true; 20387 } 20388 } 20389 } 20390 } 20391 //purge is not done, so return false 20392 return false; 20393 }, 20394 20395 /** 20396 * handle insert error 20397 * 20398 * @param error 20399 * @private 20400 */ 20401 _insertLineHandleError = function (error) { 20402 _myConsoleLog(error); 20403 }, 20404 20405 /** 20406 * check storage data size and if need purge 20407 * @private 20408 */ 20409 _checkSizeAndPurge = function () { 20410 var purgeIsDone=false, totalSize = _getTotalData(); 20411 if (totalSize > 0.75*_maxLocalStorageSize) { 20412 _myConsoleLog("in _checkSizeAndPurge, totalSize ("+totalSize+") exceeds limit"); 20413 purgeIsDone = _purgeOldSessions(totalSize); 20414 if (purgeIsDone) { 20415 _myConsoleLog("in _checkSizeAndPurge after purging old session, purge is done"); 20416 } 20417 else { 20418 //after all old sessions purged, still need purge 20419 totalSize = _getTotalData(); 20420 if (totalSize > 0.75*_maxLocalStorageSize) { 20421 _myConsoleLog("in _checkSizeAndPurge after purging old session,still needs purging, now storeSize ("+totalSize+")"); 20422 _purgeCurrentSession(); 20423 _myConsoleLog("in _checkSizeAndPurge done purging current session."); 20424 } 20425 } 20426 } 20427 }, 20428 20429 /** 20430 * check if the session is already in meta data 20431 * 20432 * @param metaData 20433 * @param sessionName 20434 * @return {Boolean} true if session has metaData (false otherwise) 20435 * @private 20436 */ 20437 _sessionsInfoContains = function (metaData, sessionName) { 20438 if (metaData && metaData.sessions && metaData.sessions.hasOwnProperty(sessionName)) { 20439 return true; 20440 } 20441 return false; 20442 }, 20443 20444 20445 /** 20446 * setup sessions in local storage 20447 * 20448 * @param logInfo 20449 * @private 20450 */ 20451 _getAndSetNumberOfSessions = function (logInfo) { 20452 var numOfSessionsPass1, numOfSessionsPass2, l; 20453 numOfSessionsPass1 = localStorage.getItem("FinesseSessionsInfo"); 20454 if (numOfSessionsPass1 === null) { 20455 //Init first time 20456 numOfSessionsPass1 = {}; 20457 numOfSessionsPass1.total = 1; 20458 numOfSessionsPass1.sessions = {}; 20459 numOfSessionsPass1.sessions[logInfo.name] = logInfo.startTime; 20460 numOfSessionsPass1.lastWrittenBy = logInfo.name; 20461 localStorage.setItem("FinesseSessionsInfo", JSON.stringify(numOfSessionsPass1)); 20462 } 20463 else { 20464 numOfSessionsPass1 = JSON.parse(numOfSessionsPass1); 20465 //check if the session is already in the FinesseSessionSInfo 20466 if (_sessionsInfoContains(numOfSessionsPass1, logInfo.name)) { 20467 return; 20468 } 20469 //Save numOfSessionsPass1 20470 numOfSessionsPass1.total = parseInt(numOfSessionsPass1.total, 10) + 1; 20471 numOfSessionsPass1.sessions[logInfo.name] = logInfo.startTime; 20472 numOfSessionsPass1.lastWrittenBy = logInfo.name; 20473 localStorage.setItem("FinesseSessionsInfo", JSON.stringify(numOfSessionsPass1)); 20474 numOfSessionsPass2 = localStorage.getItem("FinesseSessionsInfo"); 20475 if (!numOfSessionsPass2) { 20476 _myConsoleLog("Could not get FinesseSessionsInfo"); 20477 return; 20478 } 20479 numOfSessionsPass2 = JSON.parse(numOfSessionsPass2); 20480 //in future we need to confirm the numOfSessionsPass2 is the same as numOfSessionsPass1 20481 ////if (numOfSessionsPass1.lastWrittenBy !== numOfSessionsPass2.lastWrittenBy) { 20482 //// _myConsoleLog("Rebuild sessions"); 20483 //// _sessionTimerId = setTimeout(_initSessionList, 10000); 20484 ////} 20485 ////else { 20486 //// _sessionTimerId = null; 20487 ////callback(numOfSessionsPass2.sessions); 20488 ////} 20489 } 20490 if (!localStorage.getItem(_sessionKey)) { 20491 localStorage.setItem(_sessionKey, JSON.stringify(_logInfo)); 20492 } 20493 }, 20494 20495 20496 /** 20497 * init session list 20498 * @private 20499 */ 20500 _initSessionList = function () { 20501 _getAndSetNumberOfSessions(_logInfo); 20502 }, 20503 20504 /** 20505 * do the real store of log line 20506 * 20507 * @param line 20508 * @private 20509 */ 20510 _persistLine = function (line) { 20511 var key, logInfoStr; 20512 logInfoStr = localStorage.getItem(_sessionKey); 20513 if (logInfoStr === null) { 20514 return; 20515 } 20516 _logInfo = JSON.parse(logInfoStr); 20517 _logInfo.head = _logInfo.head + 1; 20518 key = _linePrefix + _logInfo.head; 20519 localStorage.setItem(key, line); 20520 //Save the size 20521 _logInfo.size = _logInfo.size + line.length; 20522 if (_logInfo.tail === 0) { 20523 _logInfo.tail = _logInfo.head; 20524 } 20525 20526 localStorage.setItem(_sessionKey, JSON.stringify(_logInfo)); 20527 _checkSizeAndPurge(); 20528 }, 20529 20530 /** 20531 * Insert a line into the localStorage. 20532 * 20533 * @param line line to be inserted 20534 * @private 20535 */ 20536 _insertLine = function (line) { 20537 //_myConsoleLog("_insertLine: [" + line + "]"); 20538 //Write the next line to localStorage 20539 try { 20540 //Persist the line 20541 _persistLine(line); 20542 } 20543 catch (err) { 20544 _myConsoleLog("error in _insertLine(), err="+err); 20545 //_insertLineHandleError(err); 20546 } 20547 }, 20548 20549 20550 /** 20551 * Clear the local storage 20552 * @private 20553 */ 20554 _clearLocalStorage = function() { 20555 localStorage.clear(); 20556 20557 }, 20558 20559 /** 20560 * Collect logs when onCollect called 20561 * 20562 * @param data 20563 * @private 20564 */ 20565 _collectMethod = function(data) { 20566 //Size of log should not exceed 1.5MB 20567 var info, maxLength = 1572864; 20568 20569 //add size buffer equal to the size of info to be added when publish 20570 info = Utilities.getSanitizedUserAgentString() + " "; 20571 info = escape(info); 20572 20573 //If log was empty previously, fade in buttons 20574 if (!_sendLogShown) { 20575 _sendLogShown = true; 20576 _logSize = info.length; 20577 } 20578 20579 //if local storage logging is enabled, then insert the log into local storage 20580 if (window.sessionStorage.getItem('enableLocalLog')==='true') { 20581 if (data) { 20582 if (data.length>0 && data.substring(0,1) === '\n') { 20583 _insertLine(data.substring(1)); 20584 } 20585 else { 20586 _insertLine(data); 20587 } 20588 } 20589 } 20590 20591 //escape all data to get accurate size (shindig will escape when it builds request) 20592 //escape 6 special chars for XML: &<>"'\n 20593 data = data.replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/>/g, ">").replace(/</g, "<").replace(/\n/g, " "); 20594 //Send Error Report crashes with Control characters. Replacing the control characters with empty string 20595 data = data.replace(/[\x00-\x1F\x7F-\x9F]/g, ""); 20596 data = escape(data+"\n"); 20597 20598 if (data.length < maxLength){ 20599 //make room for new data if log is exceeding max length 20600 while (_logSize + data.length > maxLength) { 20601 _logSize -= (_logArray.shift()).length; 20602 } 20603 } 20604 20605 //Else push the log into memory, increment the log size 20606 _logArray.push(data); 20607 20608 //inc the size accordingly 20609 _logSize+=data.length; 20610 20611 }; 20612 20613 return { 20614 20615 /** 20616 * @private 20617 * Initiate FinesseLogger. 20618 */ 20619 init: function () { 20620 ClientServices.subscribe("finesse.clientLogging.*", _collectMethod); 20621 _initLogging(); 20622 }, 20623 20624 /** 20625 * @private 20626 * Clear all items stored in localStorage. 20627 */ 20628 clear : function () { 20629 _clearLocalStorage(); 20630 }, 20631 20632 /** 20633 * @private 20634 * Initialize the local storage logging. 20635 */ 20636 initLocalLog: function () { 20637 _initLogging(); 20638 }, 20639 20640 /** 20641 * @private 20642 * Inserts a line into the localStorage. 20643 * @param line to insert 20644 */ 20645 localLog : function (line) { 20646 _insertLine(line); 20647 }, 20648 20649 /** 20650 * @ignore 20651 * Publish logs to server and clear the memory 20652 * 20653 * @param userObj 20654 * @param options 20655 * @param callBack 20656 */ 20657 publish: function(userObj, options, callBack) { 20658 // Avoid null references. 20659 options = options || {}; 20660 callBack = callBack || {}; 20661 20662 if (callBack.sending === "function") { 20663 callBack.sending(); 20664 } 20665 20666 //logs the basic version and machine info and escaped new line 20667 _logStr = Utilities.getSanitizedUserAgentString() + " "; 20668 20669 //join the logs to correct string format 20670 _logStr += unescape(_logArray.join("")); 20671 20672 //turning log string to JSON obj 20673 var logObj = { 20674 ClientLog: { 20675 logData : _logStr //_logStr 20676 } 20677 }, 20678 tmpOnAdd = (options.onAdd && typeof options.onAdd === "function")? options.onAdd : function(){}; 20679 /** @private */ 20680 options.onAdd = function(){ 20681 tmpOnAdd(); 20682 _logArray.length = 0; _logSize =0; 20683 _sendLogShown = false; 20684 }; 20685 //adding onLoad to the callbacks, this is the subscribe success case for the first time user subscribe to the client log node 20686 /** @private */ 20687 options.onLoad = function (clientLogObj) { 20688 clientLogObj.sendLogs(logObj,{ 20689 error: callBack.error 20690 }); 20691 }; 20692 20693 userObj.getClientLog(options); 20694 } 20695 }; 20696 }()); 20697 20698 window.finesse = window.finesse || {}; 20699 window.finesse.cslogger = window.finesse.cslogger || {}; 20700 /** @private */ 20701 window.finesse.cslogger.FinesseLogger = FinesseLogger; 20702 20703 return FinesseLogger; 20704 }); 20705 20706 /** 20707 * Contains a list of topics used for containerservices pubsub. 20708 * 20709 */ 20710 20711 /** 20712 * @class 20713 * Contains a list of topics with some utility functions. 20714 */ 20715 /** @private */ 20716 define('containerservices/Topics',[], function () { 20717 20718 var Topics = (function () { 20719 20720 /** 20721 * The namespace prepended to all Finesse topics. 20722 */ 20723 this.namespace = "finesse.containerservices"; 20724 20725 /** 20726 * @private 20727 * Gets the full topic name with the ContainerServices namespace prepended. 20728 * @param {String} topic 20729 * The topic category. 20730 * @returns {String} 20731 * The full topic name with prepended namespace. 20732 */ 20733 var _getNSTopic = function (topic) { 20734 return this.namespace + "." + topic; 20735 }; 20736 20737 20738 20739 /** @scope finesse.containerservices.Topics */ 20740 return { 20741 /** 20742 * @private 20743 * request channel. */ 20744 REQUESTS: _getNSTopic("requests"), 20745 20746 /** 20747 * @private 20748 * reload gadget channel. */ 20749 RELOAD_GADGET: _getNSTopic("reloadGadget"), 20750 20751 /** 20752 * @private 20753 * Convert a Finesse REST URI to a OpenAjax compatible topic name. 20754 */ 20755 getTopic: function (restUri) { 20756 //The topic should not start with '/' else it will get replaced with 20757 //'.' which is invalid. 20758 //Thus, remove '/' if it is at the beginning of the string 20759 if (restUri.indexOf('/') === 0) { 20760 restUri = restUri.substr(1); 20761 } 20762 20763 //Replace every instance of "/" with ".". This is done to follow the 20764 //OpenAjaxHub topic name convention. 20765 return restUri.replace(/\//g, "."); 20766 } 20767 }; 20768 }()); 20769 20770 window.finesse = window.finesse || {}; 20771 window.finesse.containerservices = window.finesse.containerservices || {}; 20772 window.finesse.containerservices.Topics = Topics; 20773 20774 /** @namespace JavaScript class objects and methods to handle gadget container services.*/ 20775 finesse.containerservices = finesse.containerservices || {}; 20776 20777 return Topics; 20778 }); 20779 20780 /** The following comment is to prevent jslint errors about 20781 * using variables before they are defined. 20782 */ 20783 /*global finesse*/ 20784 20785 /** 20786 * Per containerservices request, publish to the OpenAjax gadget pubsub infrastructure. 20787 * 20788 * @requires OpenAjax, finesse.containerservices.Topics 20789 */ 20790 20791 /** @private */ 20792 define('containerservices/MasterPublisher',[ 20793 "utilities/Utilities", 20794 "containerservices/Topics" 20795 ], 20796 function (Utilities, Topics) { 20797 20798 var MasterPublisher = function () { 20799 20800 var 20801 20802 /** 20803 * Reference to the gadget pubsub Hub instance. 20804 * @private 20805 */ 20806 _hub = gadgets.Hub, 20807 20808 /** 20809 * Reference to the Topics class. 20810 * @private 20811 */ 20812 _topics = Topics, 20813 20814 /** 20815 * Reference to conversion utilities class. 20816 * @private 20817 */ 20818 _utils = Utilities, 20819 20820 /** 20821 * References to ClientServices logger methods 20822 * @private 20823 */ 20824 _logger = { 20825 log: finesse.clientservices.ClientServices.log 20826 }, 20827 20828 /** 20829 * The types of possible request types supported when listening to the 20830 * requests channel. Each request type could result in different operations. 20831 * @private 20832 */ 20833 _REQTYPES = { 20834 ACTIVETAB: "ActiveTabReq", 20835 SET_ACTIVETAB: "SetActiveTabReq", 20836 RELOAD_GADGET: "ReloadGadgetReq" 20837 }, 20838 20839 /** 20840 * Handles client requests made to the request topic. The type of the 20841 * request is described in the "type" property within the data payload. Each 20842 * type can result in a different operation. 20843 * @param {String} topic 20844 * The topic which data was published to. 20845 * @param {Object} data 20846 * The data containing requests information published by clients. 20847 * @param {String} data.type 20848 * The type of the request. Supported: "ActiveTabReq", "SetActiveTabReq", "ReloadGadgetReq" 20849 * @param {Object} data.data 20850 * May contain data relevant for the particular requests. 20851 * @param {String} [data.invokeID] 20852 * The ID used to identify the request with the response. The invoke ID 20853 * will be included in the data in the publish to the topic. It is the 20854 * responsibility of the client to correlate the published data to the 20855 * request made by using the invoke ID. 20856 * @private 20857 */ 20858 _clientRequestHandler = function (topic, data) { 20859 20860 //Ensure a valid data object with "type" and "data" properties. 20861 if (typeof data === "object" && 20862 typeof data.type === "string" && 20863 typeof data.data === "object") { 20864 switch (data.type) { 20865 case _REQTYPES.ACTIVETAB: 20866 _hub.publish("finesse.containerservices.activeTab", finesse.container.Tabs.getActiveTab()); 20867 break; 20868 case _REQTYPES.SET_ACTIVETAB: 20869 if (typeof data.data.id === "string") { 20870 _logger.log("Handling request to activate tab: " + data.data.id); 20871 if (!finesse.container.Tabs.activateTab(data.data.id)) { 20872 _logger.log("No tab found with id: " + data.data.id); 20873 } 20874 } 20875 break; 20876 case _REQTYPES.RELOAD_GADGET: 20877 _hub.publish("finesse.containerservices.reloadGadget", data.data); 20878 break; 20879 default: 20880 break; 20881 } 20882 } 20883 }; 20884 20885 (function () { 20886 20887 //Listen to a request channel to respond to any requests made by other 20888 //clients because the Master may have access to useful information. 20889 _hub.subscribe(_topics.REQUESTS, _clientRequestHandler); 20890 }()); 20891 20892 //BEGIN TEST CODE// 20893 /** 20894 * Test code added to expose private functions that are used by unit test 20895 * framework. This section of code is removed during the build process 20896 * before packaging production code. The [begin|end]TestSection are used 20897 * by the build to identify the section to strip. 20898 * @ignore 20899 */ 20900 this.beginTestSection = 0; 20901 20902 /** 20903 * @ignore 20904 */ 20905 this.getTestObject = function () { 20906 //Load mock dependencies. 20907 var _mock = new MockControl(); 20908 _hub = _mock.createMock(gadgets.Hub); 20909 20910 return { 20911 //Expose mock dependencies 20912 mock: _mock, 20913 hub: _hub, 20914 20915 //Expose internal private functions 20916 reqtypes: _REQTYPES, 20917 20918 clientRequestHandler: _clientRequestHandler 20919 20920 }; 20921 }; 20922 20923 20924 /** 20925 * @ignore 20926 */ 20927 this.endTestSection = 0; 20928 //END TEST CODE// 20929 }; 20930 20931 window.finesse = window.finesse || {}; 20932 window.finesse.containerservices = window.finesse.containerservices || {}; 20933 window.finesse.containerservices.MasterPublisher = MasterPublisher; 20934 20935 return MasterPublisher; 20936 }); 20937 20938 /** 20939 * JavaScript representation of the Finesse WorkflowActionEvent object. 20940 * 20941 * @requires finesse.FinesseBase 20942 */ 20943 20944 /** The following comment is to prevent jslint errors about 20945 * using variables before they are defined. 20946 */ 20947 /*global FinesseBase: true, publisher:true, define:true, finesse:true, window:true */ 20948 /** @private */ 20949 define('containerservices/WorkflowActionEvent', ["FinesseBase"], function (FinesseBase) { 20950 var WorkflowActionEvent = FinesseBase.extend(/** @lends finesse.containerservices.WorkflowActionEvent.prototype */{ 20951 /** 20952 * Reference to the WorkflowActionEvent name 20953 * This will be set by setWorkflowActionEvent 20954 * @private 20955 */ 20956 _name: null, 20957 20958 /** 20959 * Reference to the WorkflowActionEvent type 20960 * This will be set by setWorkflowActionEvent 20961 * @private 20962 */ 20963 _type: null, 20964 20965 /** 20966 * Reference to the WorkflowActionEvent handledBy value 20967 * This will be set by setWorkflowActionEvent 20968 * @private 20969 */ 20970 _handledBy: null, 20971 20972 /** 20973 * Reference to the WorkflowActionEvent params array 20974 * This will be set by setWorkflowActionEvent 20975 * @private 20976 */ 20977 _params: [], 20978 20979 /** 20980 * Reference to the WorkflowActionEvent actionVariables array 20981 * This will be set by setWorkflowActionEvent 20982 * @private 20983 */ 20984 _actionVariables: [], 20985 20986 /** 20987 * @class 20988 * JavaScript representation of a WorkflowActionEvent object. 20989 * The WorkflowActionEvent object is delivered as the payload of 20990 * a WorkflowAction callback. This can be subscribed to by using 20991 * {@link finesse.containerservices.ContainerServices#addHandler} with a 20992 * topic of {@link finesse.containerservices.ContainerServices.Topics#WORKFLOW_ACTION_EVENT}. 20993 * Gadgets should key on events with a handleBy value of "OTHER". 20994 * 20995 * @constructs 20996 **/ 20997 init: function () { 20998 this._super(); 20999 }, 21000 21001 /** 21002 * Validate that the passed in object is a WorkflowActionEvent object 21003 * and sets the variables if it is 21004 * @param maybeWorkflowActionEvent A possible WorkflowActionEvent object to be evaluated and set if 21005 * it validates successfully. 21006 * @returns {Boolean} Whether it is valid or not. 21007 * @private 21008 */ 21009 setWorkflowActionEvent: function(maybeWorkflowActionEvent) { 21010 var returnValue; 21011 21012 if (maybeWorkflowActionEvent.hasOwnProperty("name") === true && 21013 maybeWorkflowActionEvent.hasOwnProperty("type") === true && 21014 maybeWorkflowActionEvent.hasOwnProperty("handledBy") === true && 21015 maybeWorkflowActionEvent.hasOwnProperty("params") === true && 21016 maybeWorkflowActionEvent.hasOwnProperty("actionVariables") === true) { 21017 this._name = maybeWorkflowActionEvent.name; 21018 this._type = maybeWorkflowActionEvent.type; 21019 this._handledBy = maybeWorkflowActionEvent.handledBy; 21020 this._params = maybeWorkflowActionEvent.params; 21021 this._actionVariables = maybeWorkflowActionEvent.actionVariables; 21022 returnValue = true; 21023 } else { 21024 returnValue = false; 21025 } 21026 21027 return returnValue; 21028 }, 21029 21030 /** 21031 * Getter for the WorkflowActionEvent name. 21032 * @returns {String} The name of the WorkflowAction. 21033 */ 21034 getName: function () { 21035 // escape nulls to empty string 21036 return this._name || ""; 21037 }, 21038 21039 /** 21040 * Getter for the WorkflowActionEvent type. 21041 * @returns {String} The type of the WorkflowAction (BROWSER_POP, HTTP_REQUEST). 21042 */ 21043 getType: function () { 21044 // escape nulls to empty string 21045 return this._type || ""; 21046 }, 21047 21048 /** 21049 * Getter for the WorkflowActionEvent handledBy value. Gadgets should look for 21050 * events with a handleBy of "OTHER". 21051 * @see finesse.containerservices.WorkflowActionEvent.HandledBy 21052 * @returns {String} The handledBy value of the WorkflowAction that is a value of {@link finesse.containerservices.WorkflowActionEvent.HandledBy}. 21053 */ 21054 getHandledBy: function () { 21055 // escape nulls to empty string 21056 return this._handledBy || ""; 21057 }, 21058 21059 21060 /** 21061 * Getter for the WorkflowActionEvent Params map. 21062 * @returns {Object} key = param name, value = Object{name, value, expandedValue} 21063 * BROWSER_POP<ul> 21064 * <li>windowName : Name of window to pop into, or blank to always open new window. 21065 * <li>path : URL to open.</ul> 21066 * HTTP_REQUEST<ul> 21067 * <li>method : "PUT" or "POST". 21068 * <li>location : "FINESSE" or "OTHER". 21069 * <li>contentType : MIME type of request body, if applicable, e.g. "text/plain". 21070 * <li>path : Request URL. 21071 * <li>body : Request content for POST requests.</ul> 21072 */ 21073 getParams: function () { 21074 var map = {}, 21075 params = this._params, 21076 i, 21077 param; 21078 21079 if (params === null || params.length === 0) { 21080 return map; 21081 } 21082 21083 for (i = 0; i < params.length; i += 1) { 21084 param = params[i]; 21085 // escape nulls to empty string 21086 param.name = param.name || ""; 21087 param.value = param.value || ""; 21088 param.expandedValue = param.expandedValue || ""; 21089 map[param.name] = param; 21090 } 21091 21092 return map; 21093 }, 21094 21095 /** 21096 * Getter for the WorkflowActionEvent ActionVariables map 21097 * @returns {Object} key = action variable name, value = Object{name, type, node, testValue, actualValue} 21098 */ 21099 getActionVariables: function() { 21100 var map = {}, 21101 actionVariables = this._actionVariables, 21102 i, 21103 actionVariable; 21104 21105 if (actionVariables === null || actionVariables.length === 0) { 21106 return map; 21107 } 21108 21109 for (i = 0; i < actionVariables.length; i += 1) { 21110 actionVariable = actionVariables[i]; 21111 // escape nulls to empty string 21112 actionVariable.name = actionVariable.name || ""; 21113 actionVariable.type = actionVariable.type || ""; 21114 actionVariable.node = actionVariable.node || ""; 21115 actionVariable.testValue = actionVariable.testValue || ""; 21116 actionVariable.actualValue = actionVariable.actualValue || ""; 21117 map[actionVariable.name] = actionVariable; 21118 } 21119 21120 return map; 21121 } 21122 }); 21123 21124 21125 WorkflowActionEvent.HandledBy = /** @lends finesse.containerservices.WorkflowActionEvent.HandledBy.prototype */ { 21126 /** 21127 * This specifies that Finesse will handle this WorkflowActionEvent. A 3rd Party can do additional processing 21128 * with the action, but first and foremost Finesse will handle this WorkflowAction. 21129 */ 21130 FINESSE: "FINESSE", 21131 21132 /** 21133 * This specifies that a 3rd Party will handle this WorkflowActionEvent. Finesse's Workflow Engine Executor will 21134 * ignore this action and expects Gadget Developers to take action. 21135 */ 21136 OTHER: "OTHER", 21137 21138 /** 21139 * @class This is the set of possible HandledBy values used for WorkflowActionEvent from ContainerServices. This 21140 * is provided from the {@link finesse.containerservices.WorkflowActionEvent#getHandledBy} method. 21141 * @constructs 21142 */ 21143 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 21144 }; 21145 21146 window.finesse = window.finesse || {}; 21147 window.finesse.containerservices = window.finesse.containerservices || {}; 21148 window.finesse.containerservices.WorkflowActionEvent = WorkflowActionEvent; 21149 21150 return WorkflowActionEvent; 21151 }); 21152 21153 /** 21154 * JavaScript representation of the Finesse TimerTickEvent 21155 * 21156 * @requires finesse.FinesseBase 21157 */ 21158 21159 /** The following comment is to prevent jslint errors about 21160 * using variables before they are defined. 21161 */ 21162 /*global FinesseBase: true, publisher:true, define:true, finesse:true, window:true */ 21163 /** @private */ 21164 define('containerservices/TimerTickEvent',[ 21165 "FinesseBase" 21166 ], 21167 function (FinesseBase) { 21168 var TimerTickEvent = FinesseBase.extend(/** @lends finesse.containerservices.TimerTickEvent.prototype */{ 21169 /** 21170 * date the TimerTickEvent was queued 21171 * @private 21172 */ 21173 _dateQueued: null, 21174 21175 /** 21176 * the frequency of the timer tick (in miiliseconds) 21177 * @private 21178 */ 21179 _tickFrequency: 1000, 21180 21181 /** 21182 * @class 21183 * JavaScript representation of a TimerTickEvent object. 21184 * The TimerTickEvent object is delivered as the payload of 21185 * a TimerTickEvent callback. This can be subscribed to by using 21186 * {@link finesse.containerservices.ContainerServices#addHandler} with a 21187 * topic of {@link finesse.containerservices.ContainerServices.Topics#TIMER_TICK_EVENT}. 21188 * 21189 * @constructs 21190 **/ 21191 init: function (tickFrequency, dateQueued) { 21192 this._super(); 21193 21194 this._tickFrequency = tickFrequency; 21195 this._dateQueued = dateQueued; 21196 }, 21197 21198 /** 21199 * Get the "tickFrequency" field 21200 * @param {int} which is the "TickFrequency" field 21201 * @private 21202 */ 21203 getTickFrequency: function () { 21204 return this._tickFrequency; 21205 }, 21206 21207 /** 21208 * Getter for the TimerTickEvent "DateQueued" field. 21209 * @returns {Date} which is a Date object when the TimerTickEvent was queued 21210 */ 21211 getDateQueued: function () { 21212 return this._dateQueued; 21213 } 21214 21215 }); 21216 21217 window.finesse = window.finesse || {}; 21218 window.finesse.containerservices = window.finesse.containerservices || {}; 21219 window.finesse.containerservices.TimerTickEvent = TimerTickEvent; 21220 21221 return TimerTickEvent; 21222 }); 21223 21224 /** 21225 * JavaScript representation of the Finesse GadgetViewChangedEvent object. 21226 * 21227 * @requires finesse.FinesseBase 21228 */ 21229 21230 /** The following comment is to prevent jslint errors about 21231 * using variables before they are defined. 21232 */ 21233 /*global FinesseBase: true, publisher:true, define:true, finesse:true, window:true */ 21234 /** @private */ 21235 define('containerservices/GadgetViewChangedEvent',[ 21236 "FinesseBase" 21237 ], 21238 function (FinesseBase) { 21239 var GadgetViewChangedEvent = FinesseBase.extend(/** @lends finesse.containerservices.GadgetViewChangedEvent.prototype */{ 21240 /** 21241 * Reference to the gadget id 21242 * @private 21243 */ 21244 _gadgetId: null, 21245 21246 /** 21247 * Reference to the tab id 21248 * @private 21249 */ 21250 _tabId: null, 21251 21252 /** 21253 * Reference to the maxAvailableHeight 21254 * @private 21255 */ 21256 _maxAvailableHeight: null, 21257 21258 /** 21259 * Reference to the view 21260 * E.g. 'default' or 'canvas' 21261 * @private 21262 */ 21263 _view: null, 21264 21265 /** 21266 * @class 21267 * JavaScript representation of a GadgetViewChangedEvent object. 21268 * The GadgetViewChangedEvent object is delivered as the payload of 21269 * a GadgetViewChangedEvent callback. This can be subscribed to by using 21270 * {@link finesse.containerservices.ContainerServices#addHandler} with a 21271 * topic of {@link finesse.containerservices.ContainerServices.Topics#GADGET_VIEW_CHANGED_EVENT}. 21272 * 21273 * @constructs 21274 **/ 21275 init: function (gadgetId, tabId, maxAvailableHeight, view) { 21276 this._super(); 21277 21278 this._gadgetId = gadgetId; 21279 this._tabId = tabId; 21280 this._maxAvailableHeight = maxAvailableHeight; 21281 this._view = view; 21282 }, 21283 21284 /** 21285 * Getter for the gadget id. 21286 * @returns {String} The identifier for the gadget changing view. 21287 */ 21288 getGadgetId: function () { 21289 // escape nulls to empty string 21290 return this._gadgetId || ""; 21291 }, 21292 21293 /** 21294 * Getter for the maximum available height. 21295 * @returns {String} The maximum available height for the gadget's view. 21296 */ 21297 getMaxAvailableHeight: function () { 21298 // escape nulls to empty string 21299 return this._maxAvailableHeight || ""; 21300 }, 21301 21302 /** 21303 * Getter for the tab id. 21304 * @returns {String} The identifier for the tab where the gadget changing view resides. 21305 */ 21306 getTabId: function () { 21307 // escape nulls to empty string 21308 return this._tabId || ""; 21309 }, 21310 21311 /** 21312 * Getter for the view. 21313 * @returns {String} The view type the gadget is changing to. 21314 */ 21315 getView: function () { 21316 // escape nulls to empty string 21317 return this._view || ""; 21318 } 21319 }); 21320 21321 window.finesse = window.finesse || {}; 21322 window.finesse.containerservices = window.finesse.containerservices || {}; 21323 window.finesse.containerservices.GadgetViewChangedEvent = GadgetViewChangedEvent; 21324 21325 return GadgetViewChangedEvent; 21326 }); 21327 21328 /** 21329 * JavaScript representation of the Finesse MaxAvailableHeightChangedEvent object. 21330 * 21331 * @requires finesse.FinesseBase 21332 */ 21333 21334 /** The following comment is to prevent jslint errors about 21335 * using variables before they are defined. 21336 */ 21337 /*global FinesseBase: true, publisher:true, define:true, finesse:true, window:true */ 21338 /** @private */ 21339 define('containerservices/MaxAvailableHeightChangedEvent',[ 21340 "FinesseBase" 21341 ], 21342 function (FinesseBase) { 21343 var MaxAvailableHeightChangedEvent = FinesseBase.extend(/** @lends finesse.containerservices.MaxAvailableHeightChangedEvent.prototype */{ 21344 21345 /** 21346 * Reference to the maxAvailableHeight 21347 * @private 21348 */ 21349 _maxAvailableHeight: null, 21350 21351 /** 21352 * @class 21353 * JavaScript representation of a MaxAvailableHeightChangedEvent object. 21354 * The MaxAvailableHeightChangedEvent object is delivered as the payload of 21355 * a MaxAvailableHeightChangedEvent callback. This can be subscribed to by using 21356 * {@link finesse.containerservices.ContainerServices#addHandler} with a 21357 * topic of {@link finesse.containerservices.ContainerServices.Topics#MAX_AVAILABLE_HEIGHT_CHANGED_EVENT}. 21358 * 21359 * @constructs 21360 **/ 21361 init: function (maxAvailableHeight) { 21362 this._super(); 21363 21364 this._maxAvailableHeight = maxAvailableHeight; 21365 }, 21366 21367 /** 21368 * Getter for the maximum available height. 21369 * @returns {String} The maximum available height for a gadget in canvas view 21370 */ 21371 getMaxAvailableHeight: function () { 21372 // escape nulls to empty string 21373 return this._maxAvailableHeight || ""; 21374 } 21375 }); 21376 21377 window.finesse = window.finesse || {}; 21378 window.finesse.containerservices = window.finesse.containerservices || {}; 21379 window.finesse.containerservices.MaxAvailableHeightChangedEvent = MaxAvailableHeightChangedEvent; 21380 21381 return MaxAvailableHeightChangedEvent; 21382 }); 21383 21384 /** 21385 * Exposes a set of API wrappers that will hide the dirty work of 21386 * constructing Finesse API requests and consuming Finesse events. 21387 * 21388 * @requires OpenAjax, jQuery 1.5, finesse.utilities.Utilities 21389 */ 21390 21391 /** The following comment is to prevent jslint errors about using variables before they are defined. */ 21392 /*global window:true, gadgets:true, publisher:true, define:true, finesse:true, _tabTracker:true, _workflowActionEventTracker:true, _masterReloader:true, _accessTokenRefreshed:true, frameElement:true, $:true, parent:true, MockControl:true, _getNotifierReference:true, _gadgetViewChanged:true, _maxAvailableHeightChanged:true */ 21393 /*jslint nomen: true, unparam: true, sloppy: true, white: true */ 21394 /** @private */ 21395 define('containerservices/ContainerServices',[ 21396 "utilities/Utilities", 21397 "restservices/Notifier", 21398 "containerservices/Topics", 21399 "containerservices/MasterPublisher", 21400 "containerservices/WorkflowActionEvent", 21401 "containerservices/TimerTickEvent", 21402 "containerservices/GadgetViewChangedEvent", 21403 "containerservices/MaxAvailableHeightChangedEvent" 21404 ], 21405 function (Utilities, Notifier, Topics, MasterPublisher, WorkflowActionEvent) { 21406 21407 var ContainerServices = ( function () { /** @lends finesse.containerservices.ContainerServices.prototype */ 21408 21409 var 21410 21411 /** 21412 * Shortcut reference to the Utilities singleton 21413 * This will be set by init() 21414 * @private 21415 */ 21416 _util, 21417 21418 /** 21419 * Shortcut reference to the gadget pubsub Hub instance. 21420 * This will be set by init() 21421 * @private 21422 */ 21423 _hub, 21424 21425 /** 21426 * Boolean whether this instance is master or not 21427 * @private 21428 */ 21429 _master = false, 21430 21431 /** 21432 * Whether the Client Services have been initiated yet. 21433 * @private 21434 */ 21435 _inited = false, 21436 21437 /** 21438 * References to ClientServices logger methods 21439 * @private 21440 */ 21441 _logger = { 21442 log: finesse.clientservices.ClientServices.log 21443 }, 21444 21445 /** 21446 * Stores the list of subscription IDs for all subscriptions so that it 21447 * could be retrieve for unsubscriptions. 21448 * @private 21449 */ 21450 _subscriptionID = {}, 21451 21452 /** 21453 * Reference to the gadget's parent container 21454 * @private 21455 */ 21456 _container, 21457 21458 /** 21459 * Reference to the MasterPublisher 21460 * @private 21461 */ 21462 _publisher, 21463 21464 /** 21465 * Object that will contain the Notifiers 21466 * @private 21467 */ 21468 _notifiers = {}, 21469 21470 /** 21471 * Reference to the tabId that is associated with the gadget 21472 * @private 21473 */ 21474 _myTab = null, 21475 21476 /** 21477 * Reference to the visibility of current gadget 21478 * @private 21479 */ 21480 _visible = false, 21481 21482 /** 21483 * Reference for auth modes constants. 21484 * @private 21485 */ 21486 _authModes, 21487 21488 /** 21489 * Shortcut reference to the Topics class. 21490 * This will be set by init() 21491 * @private 21492 */ 21493 _topics, 21494 21495 /** 21496 * Check whether the common desktop apis are available for finext. 21497 * In case it not available it will use the existing finesse Tab logic 21498 * @private 21499 */ 21500 _commonDesktop, 21501 21502 /** 21503 * Associates a topic name with the private handler function. 21504 * Adding a new topic requires that you add this association here 21505 * in to keep addHandler generic. 21506 * @param {String} topic : Specifies the callback to retrieve 21507 * @return {Function} The callback function associated with the topic param. 21508 * @private 21509 */ 21510 _topicCallback = function (topic) { 21511 var callback, notifier; 21512 switch (topic) 21513 { 21514 case finesse.containerservices.ContainerServices.Topics.ACTIVE_TAB: 21515 callback = _tabTracker; 21516 break; 21517 case finesse.containerservices.ContainerServices.Topics.WORKFLOW_ACTION_EVENT: 21518 callback = _workflowActionEventTracker; 21519 break; 21520 case finesse.containerservices.ContainerServices.Topics.RELOAD_GADGET_EVENT: 21521 callback = _masterReloader; 21522 break; 21523 case finesse.containerservices.ContainerServices.Topics.GADGET_VIEW_CHANGED_EVENT: 21524 callback = _gadgetViewChanged; 21525 break; 21526 case finesse.containerservices.ContainerServices.Topics.MAX_AVAILABLE_HEIGHT_CHANGED_EVENT: 21527 callback = _maxAvailableHeightChanged; 21528 break; 21529 case finesse.containerservices.ContainerServices.Topics.ACCESS_TOKEN_REFRESHED_EVENT: 21530 callback = _accessTokenRefreshed; 21531 break; 21532 default: 21533 callback = function (param) { 21534 var data = null; 21535 21536 notifier = _getNotifierReference(topic); 21537 21538 if (arguments.length === 1) { 21539 data = param; 21540 } else { 21541 data = arguments; 21542 } 21543 notifier.notifyListeners(data); 21544 }; 21545 } 21546 return callback; 21547 }, 21548 21549 /** 21550 * Ensure that ClientServices have been inited. 21551 * @private 21552 */ 21553 _isInited = function () { 21554 if (!_inited) { 21555 throw new Error("ContainerServices needs to be inited."); 21556 } 21557 return _inited; 21558 }, 21559 21560 /** 21561 * Retrieves a Notifier reference to a particular topic, and creates one if it doesn't exist. 21562 * @param {String} topic : Specifies the notifier to retrieve 21563 * @return {Notifier} The notifier object. 21564 * @private 21565 */ 21566 _getNotifierReference = function (topic) { 21567 if (!_notifiers.hasOwnProperty(topic)) 21568 { 21569 _notifiers[topic] = new Notifier(); 21570 } 21571 21572 return _notifiers[topic]; 21573 }, 21574 21575 /** 21576 * Utility function to make a subscription to a particular topic. Only one 21577 * callback function is registered to a particular topic at any time. 21578 * @param {String} topic 21579 * The full topic name. The topic name should follow the OpenAjax 21580 * convention using dot notation (ex: finesse.api.User.1000). 21581 * @param {Function} callback 21582 * The function that should be invoked with the data when an event 21583 * is delivered to the specific topic. 21584 * @returns {Boolean} 21585 * True if the subscription was made successfully and the callback was 21586 * been registered. False if the subscription already exist, the 21587 * callback was not overwritten. 21588 * @private 21589 */ 21590 _subscribe = function (topic, callback) { 21591 _isInited(); 21592 21593 //Ensure that the same subscription isn't made twice. 21594 if (!_subscriptionID[topic]) { 21595 //Store the subscription ID using the topic name as the key. 21596 _subscriptionID[topic] = _hub.subscribe(topic, 21597 //Invoke the callback just with the data object. 21598 function (topic, data) { 21599 callback(data); 21600 }); 21601 return true; 21602 } 21603 return false; 21604 }, 21605 21606 /** 21607 * Unsubscribe from a particular topic. 21608 * @param {String} topic : The full topic name. 21609 * @private 21610 */ 21611 _unsubscribe = function (topic) { 21612 _isInited(); 21613 21614 //Unsubscribe from the topic using the subscription ID recorded when 21615 //the subscription was made, then delete the ID from data structure. 21616 _hub.unsubscribe(_subscriptionID[topic]); 21617 delete _subscriptionID[topic]; 21618 }, 21619 21620 /** 21621 * Get my tab id. 21622 * @returns {String} tabid : The tabid of this container/gadget. 21623 * @private 21624 */ 21625 _getMyTab = function () { 21626 21627 // Adding startsWith to the string prototype for IE browser 21628 // See defect CSCvj93044 21629 21630 if (!String.prototype.startsWith) { 21631 String.prototype.startsWith = function(searchString, position) { 21632 position = position || 0; 21633 return this.indexOf(searchString, position) === position; 21634 }; 21635 } 21636 21637 if(_commonDesktop){ 21638 /** 21639 * This change is done for SPOG. SPOG container will set routNmae(i.e. current nav item) 21640 * as user preference 21641 */ 21642 var prefs,routeName; 21643 if (gadgets && gadgets.Prefs) { 21644 prefs = gadgets.Prefs(); 21645 routeName = prefs.getString('routeName'); 21646 } 21647 21648 if (routeName) { 21649 _myTab = routeName; 21650 } else { 21651 //This will return the nav name of the currently selected iframe.This selection is similar to the existing finesse desktop. 21652 //This is not tested with the page level gadget 21653 _myTab = _commonDesktop.route.getAllRoute()[$(frameElement).closest('div[data-group-id]').attr('data-group-id')-1]; 21654 if(_myTab){ 21655 _myTab = _myTab.startsWith('#/') ? _myTab.slice(2) : _myTab; 21656 } 21657 } 21658 }else{ 21659 if (_myTab === null){ 21660 try { 21661 _myTab = $(frameElement).closest("div.tab-panel").attr("id").replace("panel_", ""); 21662 }catch (err) { 21663 _logger.log("Error accessing current tab: " + err.message); 21664 _myTab = null; 21665 } 21666 } 21667 } 21668 return _myTab; 21669 }, 21670 21671 /** 21672 * Callback function that is called when an activeTab message is posted to the Hub. 21673 * Notifies listener functions if this tab is the one that was just made active. 21674 * @param {String} tabId : The tabId which was just made visible. 21675 * @private 21676 */ 21677 _tabTracker = function(tabId) { 21678 if (tabId === _getMyTab()) { 21679 if(!_visible) { 21680 _visible = true; 21681 _notifiers[finesse.containerservices.ContainerServices.Topics.ACTIVE_TAB].notifyListeners(this); 21682 } 21683 } else { 21684 _visible = false; 21685 } 21686 }, 21687 21688 /** 21689 * Make a request to set a particular tab active. This 21690 * method should be called after {@link finesse.containerservices.ContainerServices#addHandler} 21691 * to ensure the gadget gets properly initialized. 21692 * @param {String} tabId 21693 * The tabId (not the label text) of the tab to make active. If the id is invalid, no action will occur. 21694 * @private 21695 */ 21696 _activateTab = function ( tabId ) { 21697 _logger.log("Sending request to activate tab: " + tabId); 21698 if(_hub){ 21699 var data = { 21700 type: "SetActiveTabReq", 21701 data: { id: tabId }, 21702 invokeID: (new Date()).getTime() 21703 }; 21704 _hub.publish(_topics.REQUESTS, data); 21705 } else { 21706 throw new Error("Hub is not defined."); 21707 } 21708 21709 }, 21710 21711 /** 21712 * Callback function that is called when a gadget view changed message is posted to the Hub. 21713 * @private 21714 */ 21715 _gadgetViewChanged = function (data) { 21716 if (data) { 21717 var gadgetViewChangedEvent = new finesse.containerservices.GadgetViewChangedEvent( 21718 data.gadgetId, 21719 data.tabId, 21720 data.maxAvailableHeight, 21721 data.view); 21722 21723 _notifiers[finesse.containerservices.ContainerServices.Topics.GADGET_VIEW_CHANGED_EVENT].notifyListeners(gadgetViewChangedEvent); 21724 } 21725 }, 21726 21727 /** 21728 * Callback function that is called when a max available height changed message is posted to the Hub. 21729 * @private 21730 */ 21731 _maxAvailableHeightChanged = function (data) { 21732 if (data) { 21733 var maxAvailableHeightChangedEvent = new finesse.containerservices.MaxAvailableHeightChangedEvent( 21734 data.maxAvailableHeight); 21735 21736 _notifiers[finesse.containerservices.ContainerServices.Topics.MAX_AVAILABLE_HEIGHT_CHANGED_EVENT].notifyListeners(maxAvailableHeightChangedEvent); 21737 } 21738 }, 21739 21740 /** 21741 * Callback function that is called when a workflowActionEvent message is posted to the Hub. 21742 * Notifies listener functions if the posted object can be converted to a proper WorkflowActionEvent object. 21743 * @param {String} workflowActionEvent : The workflowActionEvent that was posted to the Hub 21744 * @private 21745 */ 21746 _workflowActionEventTracker = function(workflowActionEvent) { 21747 var vWorkflowActionEvent = new finesse.containerservices.WorkflowActionEvent(); 21748 21749 if (vWorkflowActionEvent.setWorkflowActionEvent(workflowActionEvent)) { 21750 _notifiers[finesse.containerservices.ContainerServices.Topics.WORKFLOW_ACTION_EVENT].notifyListeners(vWorkflowActionEvent); 21751 } 21752 // else 21753 // { 21754 //?console.log("Error in ContainerServices : _workflowActionEventTracker - could not map published HUB object to WorkflowActionEvent"); 21755 // } 21756 21757 }, 21758 21759 /** 21760 * Callback function that is called when a reloadGadget event message is posted to the Hub. 21761 * 21762 * Grabs the id of the gadget we want to reload from the data and reload it! 21763 * 21764 * @param {String} topic 21765 * which topic the event came on (unused) 21766 * @param {Object} data 21767 * the data published with the event 21768 * @private 21769 */ 21770 _masterReloader = function (topic, data) { 21771 var gadgetId = data.gadgetId; 21772 if (gadgetId) { 21773 _container.reloadGadget(gadgetId); 21774 } 21775 }, 21776 21777 /** 21778 * Pulls the gadget id from the url parameters 21779 * @return {String} id of the gadget 21780 * @private 21781 */ 21782 _findMyGadgetId = function () { 21783 if (gadgets && gadgets.util && gadgets.util.getUrlParameters()) { 21784 return gadgets.util.getUrlParameters().mid; 21785 } 21786 }; 21787 21788 return { 21789 /** 21790 * @class 21791 * This class provides container-level services for gadget developers, exposing container events by 21792 * calling a set of exposed functions. Gadgets can utilize the container dialogs and 21793 * event handling (add/remove). 21794 * @example 21795 * containerServices = finesse.containerservices.ContainerServices.init(); 21796 * containerServices.addHandler( 21797 * finesse.containerservices.ContainerServices.Topics.ACTIVE_TAB, 21798 * function() { 21799 * clientLogs.log("Gadget is now visible"); // log to Finesse logger 21800 * // automatically adjust the height of the gadget to show the html 21801 * gadgets.window.adjustHeight(); 21802 * }); 21803 * containerServices.makeActiveTabReq(); 21804 * 21805 * @constructs 21806 */ 21807 _fakeConstuctor: function () { 21808 /* This is here so we can document init() as a method rather than as a constructor. */ 21809 }, 21810 21811 /** 21812 * Initialize ContainerServices for use in gadget. 21813 * @param {Boolean} [master=false] Do not use this parameter from your gadget. 21814 * @returns ContainerServices instance. 21815 */ 21816 init: function (master) { 21817 if (!_inited) { 21818 _inited = true; 21819 // Set shortcuts 21820 _util = Utilities; 21821 _authModes = _util.getAuthModes(); 21822 try { 21823 _commonDesktop = window.top.cd; 21824 } catch(err) { 21825 _logger.log("Error accessing common desktop: " + err.message); 21826 } 21827 21828 //init the hub only when it's available 21829 if(gadgets.Hub) { 21830 _hub = gadgets.Hub; 21831 } 21832 21833 if(Topics) { 21834 _topics = Topics; 21835 } 21836 21837 if (master) { 21838 _master = true; 21839 _container = finesse.container.Container; 21840 _publisher = new MasterPublisher(); 21841 21842 // subscribe for reloading gadget events 21843 // we only want the master ContainerServices handling these events 21844 _hub.subscribe(_topics.RELOAD_GADGET, _topicCallback(_topics.RELOAD_GADGET)); 21845 } else { 21846 // For SPOG like containers where parent.finesse is undefined. 21847 if(parent.finesse){ 21848 _container = parent.finesse.container.Container; 21849 } 21850 } 21851 } 21852 21853 this.makeActiveTabReq(); 21854 21855 if(finesse.modules && finesse.modules.ToastPopover){ 21856 finesse.ToastPopoverInstance = new finesse.modules.ToastPopover(); 21857 } 21858 21859 /* initialize popOverService */ 21860 if(window.finesse.containerservices.PopoverService){ 21861 window.finesse.containerservices.PopoverService.init(this); 21862 } 21863 21864 //Return the CS object for object chaining. 21865 return this; 21866 }, 21867 21868 /** 21869 * Shows the jQuery UI Dialog with the specified parameters. The following are the 21870 * default parameters: <ul> 21871 * <li> Title of "Cisco Finesse".</li> 21872 * <li>Message of "A generic error has occured".</li> 21873 * <li>The only button, "Ok", closes the dialog.</li> 21874 * <li>Modal (blocks other dialogs).</li> 21875 * <li>Not draggable.</li> 21876 * <li>Fixed size.</li></ul> 21877 * @param {Object} options 21878 * An object containing additional options for the dialog. 21879 * @param {String/Boolean} options.title 21880 * Title to use. undefined defaults to "Cisco Finesse". false to hide 21881 * @param {Function} options.close 21882 * A function to invoke when the dialog is closed. 21883 * @param {String} options.message 21884 * The message to display in the dialog. 21885 * Defaults to "A generic error has occurred." 21886 * @param {Boolean} options.isBlocking 21887 * Flag indicating whether this dialog will block other dialogs from being shown (Modal). 21888 * @returns {jQuery} JQuery wrapped object of the dialog DOM element. 21889 * @see finesse.containerservices.ContainerServices#hideDialog 21890 */ 21891 showDialog: function(options) { 21892 if ((_container.showDialog !== undefined) && (_container.showDialog !== this.showDialog)) { 21893 return _container.showDialog(options); 21894 } 21895 }, 21896 21897 /** 21898 * Hides the jQuery UI Dialog. 21899 * @returns {jQuery} jQuery wrapped object of the dialog DOM element 21900 * @see finesse.containerservices.ContainerServices#showDialog 21901 */ 21902 hideDialog: function() { 21903 if ((_container.hideDialog !== undefined) && (_container.hideDialog !== this.hideDialog)) { 21904 return _container.hideDialog(); 21905 } 21906 }, 21907 /** 21908 * Shows the Certificate Banner with the specified parameters 21909 * @param {Function} callback (optional) 21910 * Callback to be called when user closes the banner 21911 * Id is fetched using window.finesse.containerservices.ContainerServices.getMyGadgetId() 21912 * @returns {String} id gadgetId or UniqueId if gadget is not present 21913 * 21914 */ 21915 showCertificateBanner: function(callback) { 21916 if ((_container.showCertificateBanner !== undefined) && (_container.showCertificateBanner !== this.showCertificateBanner)) { 21917 var options = {}; 21918 /** 21919 * If getMyGadgetId is undefined , i.e. for components, a unique id will be returned, 21920 * which should be sent while calling hideCertificateBanner 21921 */ 21922 options.id = window.finesse.containerservices.ContainerServices.getMyGadgetId(); 21923 options.callback = callback; 21924 return _container.showCertificateBanner(options); 21925 } 21926 }, 21927 21928 /** 21929 * Requests for hiding the Certificate Banner 21930 * Banner will only be hidden when all gadget's which called showCertificateBanner before 21931 * have called hideCertificateBanner 21932 * @param {String} id -> unique id returned while calling showCertificateBanner 21933 */ 21934 hideCertificateBanner: function(id) { 21935 if(id === undefined && window.finesse.containerservices.ContainerServices.getMyGadgetId() === undefined) { 21936 throw new Error('ID returned when showCertificateBanner was called need to be sent as params'); 21937 } 21938 if ((_container.hideCertificateBanner !== undefined) && (_container.hideCertificateBanner !== this.hideCertificateBanner)) { 21939 return _container.hideCertificateBanner(id || window.finesse.containerservices.ContainerServices.getMyGadgetId()); 21940 } 21941 }, 21942 21943 /** 21944 * Reloads the current gadget. 21945 * For use from within a gadget only. 21946 */ 21947 reloadMyGadget: function () { 21948 var topic, gadgetId, data; 21949 21950 if (!_master) { 21951 // first unsubscribe this gadget from all topics on the hub 21952 for (topic in _notifiers) { 21953 if (_notifiers.hasOwnProperty(topic)) { 21954 _unsubscribe(topic); 21955 delete _notifiers[topic]; 21956 } 21957 } 21958 21959 // send an asynch request to the hub to tell the master container 21960 // services that we want to refresh this gadget 21961 gadgetId = _findMyGadgetId(); 21962 data = { 21963 type: "ReloadGadgetReq", 21964 data: {gadgetId: gadgetId}, 21965 invokeID: (new Date()).getTime() 21966 }; 21967 _hub.publish(_topics.REQUESTS, data); 21968 } 21969 }, 21970 21971 /** 21972 * Updates the url for this gadget and then reload it. 21973 * 21974 * This allows the gadget to be reloaded from a different location 21975 * than what is uploaded to the current server. For example, this 21976 * would be useful for 3rd party gadgets to implement their own failover 21977 * mechanisms. 21978 * 21979 * For use from within a gadget only. 21980 * 21981 * @param {String} url 21982 * url from which to reload gadget 21983 */ 21984 reloadMyGadgetFromUrl: function (url) { 21985 if (!_master) { 21986 var gadgetId = _findMyGadgetId(); 21987 21988 // update the url in the container 21989 _container.modifyGadgetUrl(gadgetId, url); 21990 21991 // reload it 21992 this.reloadMyGadget(); 21993 } 21994 }, 21995 21996 /** 21997 * Adds a handler for one of the supported topics provided by ContainerServices. The callbacks provided 21998 * will be invoked when that topic is notified. 21999 * @param {String} topic 22000 * The Hub topic to which we are listening. 22001 * @param {Function} callback 22002 * The callback function to invoke. 22003 * @see finesse.containerservices.ContainerServices.Topics 22004 * @see finesse.containerservices.ContainerServices#removeHandler 22005 */ 22006 addHandler: function (topic, callback) { 22007 _isInited(); 22008 var notifier = null; 22009 22010 try { 22011 // For backwards compatibility... 22012 if (topic === "tabVisible") { 22013 if (window.console && typeof window.console.log === "function") { 22014 window.console.log("WARNING - Using tabVisible as topic. This is deprecated. Use finesse.containerservices.ContainerServices.Topics.ACTIVE_TAB now!"); 22015 } 22016 22017 topic = finesse.containerservices.ContainerServices.Topics.ACTIVE_TAB; 22018 } 22019 22020 // Add the callback to the notifier. 22021 _util.validateHandler(callback); 22022 22023 notifier = _getNotifierReference(topic); 22024 22025 notifier.addListener(callback); 22026 22027 // Subscribe to the topic. _subscribe ensures that a topic is only subscribed to once, 22028 // so attempt to subscribe each time a handler is added. This ensures that a topic is subscribed 22029 // to only when necessary. 22030 _subscribe(topic, _topicCallback(topic)); 22031 22032 } catch (err) { 22033 throw new Error("addHandler(): " + err); 22034 } 22035 }, 22036 22037 /** 22038 * Removes a previously-added handler for one of the supported topics. 22039 * @param {String} topic 22040 * The Hub topic from which we are removing the callback. 22041 * @param {Function} callback 22042 * The name of the callback function to remove. 22043 * @see finesse.containerservices.ContainerServices.Topics 22044 * @see finesse.containerservices.ContainerServices#addHandler 22045 */ 22046 removeHandler: function(topic, callback) { 22047 var notifier = null; 22048 22049 try { 22050 _util.validateHandler(callback); 22051 22052 notifier = _getNotifierReference(topic); 22053 22054 notifier.removeListener(callback); 22055 } catch (err) { 22056 throw new Error("removeHandler(): " + err); 22057 } 22058 }, 22059 22060 /** 22061 * Wrapper API for publishing data on the Openajax hub 22062 * @param {String} topic 22063 * The Hub topic to which we are publishing. 22064 * @param {Object} data 22065 * The data to be published on the hub. 22066 */ 22067 publish : function(topic , data){ 22068 if(_hub){ 22069 _hub.publish(topic, data); 22070 } else { 22071 throw new Error("Hub is not defined."); 22072 } 22073 }, 22074 22075 /** 22076 * Returns the visibility of current gadget. Note that this 22077 * will not be set until after the initialization of the gadget. 22078 * @return {Boolean} The visibility of current gadget. 22079 */ 22080 tabVisible: function(){ 22081 return _visible; 22082 }, 22083 22084 /** 22085 * Make a request to check the current tab. The 22086 * activeTab event will be invoked if on the active tab. This 22087 * method should be called after {@link finesse.containerservices.ContainerServices#addHandler} 22088 * to ensure the gadget gets properly initialized. 22089 */ 22090 makeActiveTabReq : function () { 22091 if(_hub){ 22092 var data = { 22093 type: "ActiveTabReq", 22094 data: {}, 22095 invokeID: (new Date()).getTime() 22096 }; 22097 _hub.publish(_topics.REQUESTS, data); 22098 } else { 22099 throw new Error("Hub is not defined."); 22100 } 22101 22102 }, 22103 22104 /** 22105 * Make a request to set a particular tab active. This 22106 * method should be called after {@link finesse.containerservices.ContainerServices#addHandler} 22107 * to ensure the gadget gets properly initialized. 22108 * @param {String} tabId 22109 * The tabId (not the label text) of the tab to make active. If the id is invalid, no action will occur. 22110 */ 22111 activateTab : function (tabId) { 22112 _activateTab(tabId); 22113 }, 22114 22115 /** 22116 * Make a request to set this container's tab active. This 22117 * method should be called after {@link finesse.containerservices.ContainerServices#addHandler} 22118 * to ensure the gadget gets properly initialized. 22119 */ 22120 activateMyTab : function () { 22121 _activateTab( _getMyTab() ); 22122 }, 22123 22124 /** 22125 * Get the tabId of my container/gadget. 22126 * @returns {String} tabid : The tabid of this container/gadget. 22127 */ 22128 getMyTabId : function () { 22129 return _getMyTab(); 22130 }, 22131 22132 /** 22133 * Gets the id of the gadget. 22134 * @returns {number} the id of the gadget 22135 */ 22136 getMyGadgetId : function () { 22137 return _findMyGadgetId(); 22138 }, 22139 22140 //BEGIN TEST CODE// 22141 /** 22142 * Test code added to expose private functions that are used by unit test 22143 * framework. This section of code is removed during the build process 22144 * before packaging production code. The [begin|end]TestSection are used 22145 * by the build to identify the section to strip. 22146 * @ignore 22147 */ 22148 beginTestSection : 0, 22149 22150 /** 22151 * @ignore 22152 */ 22153 getTestObject: function () { 22154 //Load mock dependencies. 22155 var _mock = new MockControl(); 22156 _util = _mock.createMock(Utilities); 22157 _hub = _mock.createMock(gadgets.Hub); 22158 _inited = true; 22159 return { 22160 //Expose mock dependencies 22161 mock: _mock, 22162 hub: _hub, 22163 util: _util, 22164 addHandler: this.addHandler, 22165 removeHandler: this.removeHandler 22166 }; 22167 }, 22168 22169 /** 22170 * @ignore 22171 */ 22172 endTestSection: 0 22173 //END TEST CODE// 22174 }; 22175 }()); 22176 22177 ContainerServices.Topics = /** @lends finesse.containerservices.ContainerServices.Topics.prototype */ { 22178 /** 22179 * Topic for subscribing to be notified when the active tab changes. 22180 * The provided callback will be invoked when the tab that the gadget 22181 * that subscribes with this becomes active. To ensure code is called 22182 * when the gadget is already on the active tab use the 22183 * {@link finesse.containerservices.ContainerServices#makeActiveTabReq} 22184 * method. 22185 */ 22186 ACTIVE_TAB: "finesse.containerservices.activeTab", 22187 22188 /** 22189 * Topic for WorkflowAction events traffic. 22190 * The provided callback will be invoked when a WorkflowAction needs 22191 * to be handled. The callback will be passed a {@link finesse.containerservices.WorkflowActionEvent} 22192 * that can be used to interrogate the WorkflowAction and determine to use or not. 22193 */ 22194 WORKFLOW_ACTION_EVENT: "finesse.containerservices.workflowActionEvent", 22195 22196 /** 22197 * Topic for Timer Tick event. 22198 * The provided callback will be invoked when this event is fired. 22199 * The callback will be passed a {@link finesse.containerservices.TimerTickEvent}. 22200 */ 22201 TIMER_TICK_EVENT : "finesse.containerservices.timerTickEvent", 22202 22203 /** 22204 * Topic for Non Voice Gadgets to communicate with Finext Container 22205 * Finext container will handle this event 22206 */ 22207 FINEXT_NON_VOICE_GADGET_EVENT : "finext.nv", 22208 22209 /** 22210 * Topic for listening to the active call event. Can a trigger a callback 22211 * when the agent voice state changes from READY/NOT_READY to any other 22212 * non-callable state and vice versa. 22213 */ 22214 ACTIVE_CALL_STATUS_EVENT : "finesse.containerservices.activeCallStatusEvent", 22215 22216 /** 22217 * Topic for Gadgets to communicate with Finext Popover Container. 22218 */ 22219 FINEXT_POPOVER_EVENT : "finext.popover", 22220 22221 /** 22222 * Topic for Reload Gadget events traffic. 22223 * Only the master ContainerServices instance will handle this event. 22224 */ 22225 RELOAD_GADGET_EVENT: "finesse.containerservices.reloadGadget", 22226 22227 /** 22228 * Topic for listening to gadget view changed events. 22229 * The provided callback will be invoked when a gadget changes view. 22230 * The callback will be passed a {@link finesse.containerservices.GadgetViewChangedEvent}. 22231 */ 22232 GADGET_VIEW_CHANGED_EVENT: "finesse.containerservices.gadgetViewChangedEvent", 22233 22234 /** 22235 * Topic for listening to max available height changed events. 22236 * The provided callback will be invoked when the maximum height available to a maximized gadget changes. 22237 * This event is only meant for maximized gadgets and will not be published unless a maximized gadget exists. 22238 * The callback will be passed a {@link finesse.containerservices.MaxAvailableHeightChangedEvent}. 22239 */ 22240 MAX_AVAILABLE_HEIGHT_CHANGED_EVENT: "finesse.containerservices.maxAvailableHeightChangedEvent", 22241 22242 /** 22243 * @class This is the set of Topics used for subscribing for events from ContainerServices. 22244 * Use {@link finesse.containerservices.ContainerServices#addHandler} to subscribe to the topic. 22245 * 22246 * @constructs 22247 */ 22248 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 22249 }; 22250 22251 window.finesse = window.finesse || {}; 22252 window.finesse.containerservices = window.finesse.containerservices || {}; 22253 window.finesse.containerservices.ContainerServices = ContainerServices; 22254 22255 return ContainerServices; 22256 }); 22257 22258 /** 22259 * FinesseToaster is a utility class to show toaster notification in Finesse. 22260 * FinesseToaster leverages HTML5 Notification API to display Toaster 22261 * Notification. 22262 * 22263 */ 22264 22265 define('containerservices/FinesseToaster',[],function() { 22266 22267 var FinesseToaster = (function() { 22268 /** @lends finesse.containerservices.FinesseToaster.prototype */ 22269 22270 var 22271 22272 /** How long the toaster will be displayed by default. Default timeout is 8 seconds */ 22273 AUTO_CLOSE_TIME = 8000, 22274 22275 /** PERMISSION_GRANTED constant for granted string */ 22276 PERMISSION_GRANTED = 'granted', 22277 22278 /** PERMISSION_DEFAULT constant for default string */ 22279 PERMISSION_DEFAULT = 'default', 22280 22281 /** PERMISSION_DENIED constant for denied string */ 22282 PERMISSION_DENIED = 'denied', 22283 22284 /** ICON_PATH constant for holding path icon images */ 22285 ICON_PATH = '/desktop/theme/finesse/images/modules/', 22286 22287 /** 22288 * Shortcut reference to finesse.cslogger.ClientLogger singleton This 22289 * will be set by init(), it should already be initialized by 22290 * PageServices 22291 * 22292 * @private 22293 */ 22294 _logger, 22295 22296 /** 22297 * Boolean variable to determine if finesse toaster is enabled 22298 * @private 22299 */ 22300 _isToasterDisabled = false, 22301 22302 /** 22303 * Boolean variable to determine if finesse toaster is initialized 22304 * @private 22305 */ 22306 _isToasterInitialized, 22307 22308 /** 22309 * this function check of provided parameter is a javascript function. 22310 * 22311 * @private 22312 */ 22313 isFunction = function(param) { 22314 if (typeof param === "function") { 22315 return true; 22316 } 22317 return false; 22318 }, 22319 22320 /** 22321 * _createNotification creates Notification instance 22322 * 22323 * @param {String} 22324 * title title string should be displayed in the Toaster 22325 * @param {Object} 22326 * options JSON object for notification options. 22327 */ 22328 _createNotification = function(title, options) { 22329 var notification = new window.Notification(title, options); 22330 return notification; 22331 }, 22332 22333 /** 22334 * _setAutoClose set the auto close time for toaster, it checks if 22335 * client code passed custom time out for the toaster, otherwise it set 22336 * the AUTO_CLOSE_TIME 22337 * 22338 * @param {Object} 22339 * notification window.Notification that creates Html5 22340 * Notification. 22341 * @param {String} 22342 * autoClose autoClose time of the Toaster 22343 * @return toasterTimeout Toaster Timeout 22344 */ 22345 _setAutoClose = function(notification, autoClose) { 22346 22347 // check if custom close time passed other wise set 22348 // DEFAULT_AUTO_CLOSE 22349 var autoCloseTime = (autoClose && !isNaN(autoClose)) ? autoClose 22350 : AUTO_CLOSE_TIME, 22351 // set the time out for notification toaster 22352 toasterTimer = setTimeout(function() { 22353 notification.close(); 22354 }, autoCloseTime); 22355 22356 return toasterTimer; 22357 22358 }, 22359 22360 /** This method will request permission to display Toaster. */ 22361 _requestPermission = function() { 22362 // If they are not denied (i.e. default) 22363 if (window.Notification 22364 && window.Notification.permission !== PERMISSION_DENIED) { 22365 // Request permission 22366 window.Notification 22367 .requestPermission(function(status) { 22368 22369 // Change based on user's decision 22370 if (window.Notification.permission !== status) { 22371 window.Notification.permission = status; 22372 } 22373 _logger 22374 .log("FinesseToaster.requestPermission(): request permission status " 22375 + status); 22376 22377 }); 22378 22379 } else { 22380 _logger 22381 .log("FinesseToaster.requestPermission(): Notification not supported or permission denied."); 22382 } 22383 22384 }, 22385 22386 /** 22387 * This method will add onclick and onerror listener to Notification. 22388 * on click of toaster the gadget which originally had focus may loose 22389 * the focus. To get the back the focus on any element inside the gadget 22390 * use the on click callback handler. 22391 * 22392 * @param {Object} 22393 * notification window.Notification that creates Html5 22394 * Notification. 22395 * @param {Object} 22396 * options JSON object for notification options. 22397 */ 22398 _addToasterListeners = function(notification, options, toasterTimer) { 22399 // this is onlcik handler of toaster. this handler will be invoked 22400 // on click of toaster 22401 notification.onclick = function() { 22402 // in case of manually closed toaster, stop the notification 22403 // auto close method to be invoked 22404 clearTimeout(toasterTimer); 22405 // This will maximize/activate chrome browser on click of 22406 // toaster. this handling required only in case of Chrome 22407 if (window.chrome) { 22408 parent.focus(); 22409 } 22410 22411 if (options && options.onclick) { 22412 if (isFunction(options.onclick)) { 22413 options.onclick(); 22414 } else { 22415 throw new Error("onclick callback must be a function"); 22416 } 22417 } 22418 22419 //close toaster upon click 22420 this.close(); 22421 22422 }; 22423 22424 // this is onerror handler of toaster, if there is any error while 22425 // loading toaster this hadnler will be invoked 22426 notification.onerror = function() { 22427 if (options && options.onerror) { 22428 if (isFunction(options.onerror)) { 22429 options.onerror(); 22430 } else { 22431 throw new Error("onerror callback must be a function"); 22432 } 22433 } 22434 }; 22435 }; 22436 22437 return { 22438 22439 /** 22440 * @class 22441 * FinesseToaster is a utility class to show toaster 22442 * notification in Finesse. FinesseToaster leverages <a 22443 * href="https://www.w3.org/TR/notifications/">HTML5 22444 * Notification</a> API to display Toaster Notification. 22445 * <p> <a 22446 * href="https://developer.mozilla.org/en/docs/Web/API/notification#Browser_compatibility">For 22447 * HTML5 Notification API and browser compatibility, please click 22448 * here.</a></p> 22449 * 22450 * @constructs 22451 */ 22452 _fakeConstuctor : function() { 22453 22454 }, 22455 /** 22456 * TOASTER_DEFAULT_ICONS constants has list of predefined icons (e.g INCOMING_CALL_ICON). 22457 * <p><b>Constant list</b></p> 22458 * <ul> 22459 * <li>TOASTER_DEFAULT_ICONS.INCOMING_CALL_ICON</li> 22460 * </ul> 22461 */ 22462 TOASTER_DEFAULT_ICONS : { 22463 INCOMING_CALL_ICON : ICON_PATH + "incoming_call.png", 22464 INCOMING_CHAT_ICON : ICON_PATH + "incoming_chat.png", 22465 INCOMING_TEAM_MESSAGE : ICON_PATH + "incoming_team_message.png" 22466 }, 22467 22468 /** 22469 * <b>showToaster </b>: shows Toaster Notification. 22470 * 22471 * @param {String} 22472 * <b>title</b> : title string should be displayed in the Toaster 22473 * @param {Object} 22474 * options is JSON object for notification options. 22475 * <ul> 22476 * <li><b>options</b> = { </li> 22477 * <li><b>body</b> : The body string of the notification as 22478 * specified in the options parameter of the constructor.</li> 22479 * <li><b>icon</b>: The URL of the image used as an icon of the 22480 * notification as specified in the options parameter of 22481 * the constructor.</li> 22482 * <li><b>autoClose</b> : custom auto close time of the toaster</li> 22483 * <li><b>showWhenVisible</b> : 'true' toaster shows up even when page is 22484 * visible,'false' toaster shows up only when page is invisible </li> 22485 * <li> }</li> 22486 * </ul> 22487 * 22488 */ 22489 showToaster : function(title, options) { 22490 22491 if(!_isToasterInitialized){ 22492 throw new Error("FinesseToaster.showToaster() : Finesse toaster is not initialized"); 22493 } 22494 22495 if(_isToasterDisabled){ 22496 _logger.log("FinesseToaster.showToaster() : FinesseToaster is disabled"); 22497 return; 22498 } 22499 22500 var notification, toasterTimer; 22501 22502 // If notifications are granted show the notification 22503 if (window.Notification 22504 && window.Notification.permission === PERMISSION_GRANTED) { 22505 22506 // document.hasFocus() used over document.hidden to keep the consistent behavior across mozilla/chrome 22507 if (document.hasFocus() === false 22508 || options.showWhenVisible) { 22509 if (_logger && AUTO_CLOSE_TIME > -1) { 22510 notification = _createNotification(title, options); 22511 22512 // set the auto close time out of the toaster 22513 toasterTimer = _setAutoClose(notification, 22514 options.autoClose); 22515 22516 // and Toaster Event listeners. eg. onclick , onerror. 22517 _addToasterListeners(notification, options, 22518 toasterTimer); 22519 } 22520 } else { 22521 _logger 22522 .log("FinesseToaster supressed : Page is visible and FineeseToaster.options.showWhenVisible is false"); 22523 } 22524 22525 } 22526 22527 return notification; 22528 }, 22529 22530 /** 22531 * initialize FininseToaster and inject dependencies. this method 22532 * will also request permission in browser from user to display 22533 * Toaster Notification. 22534 * 22535 *@param {Object} 22536 * Could be finesse.container.Config or finesse.gadget.Config based on where it is getting initialized from. 22537 * 22538 * @param {Object} 22539 * finesse.cslogger.ClientLogger 22540 * @return finesse.containerservices.FinesseToaster 22541 */ 22542 init : function(config , logger) { 22543 22544 _isToasterInitialized = true; 22545 22546 // This is for injecting mocked logger. 22547 if (logger) { 22548 _logger = logger; 22549 } else { 22550 _logger = finesse.cslogger.ClientLogger; 22551 } 22552 22553 //set default toaster notification timeout 22554 if (config && config.toasterNotificationTimeout !== undefined) { 22555 AUTO_CLOSE_TIME = Number(config.toasterNotificationTimeout) * 1000; 22556 22557 if(AUTO_CLOSE_TIME === 0){ 22558 //Finesse toaster has been disabled 22559 _isToasterDisabled = true; 22560 } 22561 } 22562 22563 // Request permission 22564 _requestPermission(); 22565 return finesse.containerservices.FinesseToaster; 22566 } 22567 }; 22568 22569 }()); 22570 22571 window.finesse = window.finesse || {}; 22572 window.finesse.containerservices = window.finesse.containerservices || {}; 22573 window.finesse.containerservices.FinesseToaster = FinesseToaster; 22574 22575 return FinesseToaster; 22576 }); 22577 22578 /** 22579 * This "interface" is just a way to easily jsdoc the Object callback handlers. 22580 * 22581 * @requires finesse.clientservices.ClientServices 22582 * @requires Class 22583 */ 22584 /** @private */ 22585 define('interfaces/RestObjectHandlers',[ 22586 "FinesseBase", 22587 "utilities/Utilities", 22588 "restservices/Notifier", 22589 "clientservices/ClientServices", 22590 "clientservices/Topics" 22591 ], 22592 function () { 22593 22594 var RestObjectHandlers = ( function () { /** @lends finesse.interfaces.RestObjectHandlers.prototype */ 22595 22596 return { 22597 22598 /** 22599 * @class 22600 * This "interface" defines REST Object callback handlers, passed as an argument to 22601 * Object getter methods in cases where the Object is going to be created. 22602 * 22603 * @param {Object} [handlers] 22604 * An object containing callback handlers for instantiation and runtime 22605 * Callback to invoke upon successful instantiation, passes in REST object. 22606 * @param {Function} [handlers.onLoad(this)] 22607 * Callback to invoke upon loading the data for the first time. 22608 * @param {Function} [handlers.onChange(this)] 22609 * Callback to invoke upon successful update object (PUT) 22610 * @param {Function} [handlers.onAdd(this)] 22611 * Callback to invoke upon successful update to add object (POST) 22612 * @param {Function} [handlers.onDelete(this)] 22613 * Callback to invoke upon successful update to delete object (DELETE) 22614 * @param {Function} [handlers.onError(rsp)] 22615 * Callback to invoke on update error (refresh or event) 22616 * as passed by finesse.restservices.RestBase.restRequest()<br> 22617 * {<br> 22618 * status: {Number} The HTTP status code returned<br> 22619 * content: {String} Raw string of response<br> 22620 * object: {Object} Parsed object of response<br> 22621 * error: {Object} Wrapped exception that was caught<br> 22622 * error.errorType: {String} Type of error that was caught<br> 22623 * error.errorMessage: {String} Message associated with error<br> 22624 * }<br> 22625 * <br> 22626 * Note that RestCollections have two additional callback handlers:<br> 22627 * <br> 22628 * @param {Function} [handlers.onCollectionAdd(this)]: when an object is added to this collection 22629 * @param {Function} [handlers.onCollectionDelete(this)]: when an object is removed from this collection 22630 22631 * @constructs 22632 */ 22633 _fakeConstuctor: function () { 22634 /* This is here to enable jsdoc to document this as a class. */ 22635 } 22636 }; 22637 }()); 22638 22639 window.finesse = window.finesse || {}; 22640 window.finesse.interfaces = window.finesse.interfaces || {}; 22641 window.finesse.interfaces.RestObjectHandlers = RestObjectHandlers; 22642 22643 return RestObjectHandlers; 22644 22645 }); 22646 22647 22648 /** 22649 * This "interface" is just a way to easily jsdoc the REST request handlers. 22650 * 22651 * @requires finesse.clientservices.ClientServices 22652 * @requires Class 22653 */ 22654 /** @private */ 22655 define('interfaces/RequestHandlers',[ 22656 "FinesseBase", 22657 "utilities/Utilities", 22658 "restservices/Notifier", 22659 "clientservices/ClientServices", 22660 "clientservices/Topics" 22661 ], 22662 function () { 22663 22664 var RequestHandlers = ( function () { /** @lends finesse.interfaces.RequestHandlers.prototype */ 22665 22666 return { 22667 22668 /** 22669 * @class 22670 * This "interface" defines REST Object callback handlers, passed as an argument to 22671 * Object getter methods in cases where the Object is going to be created. 22672 * 22673 * @param {Object} handlers 22674 * An object containing the following (optional) handlers for the request:<ul> 22675 * <li><b>success(rsp):</b> A callback function for a successful request to be invoked with the following 22676 * response object as its only parameter:<ul> 22677 * <li><b>status:</b> {Number} The HTTP status code returned</li> 22678 * <li><b>content:</b> {String} Raw string of response</li> 22679 * <li><b>object:</b> {Object} Parsed object of response</li></ul> 22680 * <li><b>error(rsp):</b> An error callback function for an unsuccessful request to be invoked with the 22681 * error response object as its only parameter:<ul> 22682 * <li><b>status:</b> {Number} The HTTP status code returned</li> 22683 * <li><b>content:</b> {String} Raw string of response</li> 22684 * <li><b>object:</b> {Object} Parsed object of response (HTTP errors)</li> 22685 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 22686 * <li><b>errorType:</b> {String} Type of error that was caught</li> 22687 * <li><b>errorMessage:</b> {String} Message associated with error</li> 22688 * </ul></li> 22689 * </ul> 22690 22691 * @constructs 22692 */ 22693 _fakeConstuctor: function () { 22694 /* This is here to enable jsdoc to document this as a class. */ 22695 } 22696 }; 22697 }()); 22698 22699 window.finesse = window.finesse || {}; 22700 window.finesse.interfaces = window.finesse.interfaces || {}; 22701 window.finesse.interfaces.RequestHandlers = RequestHandlers; 22702 22703 finesse = finesse || {}; 22704 /** @namespace These interfaces are just a convenience for documenting common parameter structures. */ 22705 finesse.interfaces = finesse.interfaces || {}; 22706 22707 return RequestHandlers; 22708 22709 }); 22710 22711 22712 22713 define('gadget/Config',[ 22714 "utilities/Utilities" 22715 ], function (Utilities) { 22716 var Config = (function () { /** @lends finesse.gadget.Config.prototype */ 22717 22718 if (gadgets && gadgets.Prefs) { 22719 22720 var _prefs = new gadgets.Prefs(); 22721 22722 return { 22723 /** 22724 * The base64 encoded "id:password" string used for authentication. 22725 * In case of SPOG container, the credentials will not be there in browser session storage so it will be taken from the gadget prefs. 22726 */ 22727 authorization: Utilities.getUserAuthString() || _prefs.getString("authorization"), 22728 22729 /** 22730 * The auth token string used for authentication in SSO deployments. 22731 */ 22732 authToken: Utilities.getToken(), 22733 22734 /** 22735 * The country code of the client (derived from locale). 22736 */ 22737 country: _prefs.getString("country"), 22738 22739 /** 22740 * The language code of the client (derived from locale). 22741 */ 22742 language: _prefs.getString("language"), 22743 22744 /** 22745 * The locale of the client. 22746 */ 22747 locale: _prefs.getString("locale"), 22748 22749 /** 22750 * The Finesse server IP/host as reachable from the browser. 22751 */ 22752 host: _prefs.getString("host"), 22753 22754 /** 22755 * The Finesse server host's port reachable from the browser. 22756 */ 22757 hostPort: _prefs.getString("hostPort"), 22758 22759 /** 22760 * The extension of the user. 22761 */ 22762 extension: _prefs.getString("extension"), 22763 22764 /** 22765 * One of the work modes found in {@link finesse.restservices.User.WorkMode}, or something false (undefined) for a normal login. 22766 */ 22767 mobileAgentMode: _prefs.getString("mobileAgentMode"), 22768 22769 /** 22770 * The dial number to use for mobile agent, or something false (undefined) for a normal login. 22771 */ 22772 mobileAgentDialNumber: _prefs.getString("mobileAgentDialNumber"), 22773 22774 /** 22775 * The domain of the XMPP server. 22776 */ 22777 xmppDomain: _prefs.getString("xmppDomain"), 22778 22779 /** 22780 * The pub sub domain where the pub sub service is running. 22781 */ 22782 pubsubDomain: _prefs.getString("pubsubDomain"), 22783 22784 /** 22785 * The Finesse API IP/host as reachable from the gadget container. 22786 */ 22787 restHost: _prefs.getString("restHost"), 22788 22789 /** 22790 * The type of HTTP protocol (http or https). 22791 */ 22792 scheme: _prefs.getString("scheme"), 22793 22794 /** 22795 * The localhost fully qualified domain name. 22796 */ 22797 localhostFQDN: _prefs.getString("localhostFQDN"), 22798 22799 /** 22800 * The localhost port. 22801 */ 22802 localhostPort: _prefs.getString("localhostPort"), 22803 22804 /** 22805 * The id of the team the user belongs to. 22806 */ 22807 teamId: _prefs.getString("teamId"), 22808 22809 /** 22810 * The name of the team the user belongs to. 22811 */ 22812 teamName: _prefs.getString("teamName"), 22813 22814 /** 22815 * The drift time between the client and the server in milliseconds. 22816 */ 22817 clientDriftInMillis: _prefs.getInt("clientDriftInMillis"), 22818 22819 /** 22820 * The client compatibility mode configuration (true if it is or false otherwise). 22821 */ 22822 compatibilityMode: _prefs.getString("compatibilityMode"), 22823 22824 /** 22825 * The peripheral Id that Finesse is connected to. 22826 */ 22827 peripheralId: _prefs.getString("peripheralId"), 22828 22829 /** 22830 * The auth mode of the finesse deployment. 22831 */ 22832 systemAuthMode: _prefs.getString("systemAuthMode"), 22833 22834 22835 /** 22836 * The time for which fineese toaster stay on the browser. 22837 */ 22838 toasterNotificationTimeout: _prefs.getString("toasterNotificationTimeout"), 22839 22840 /** 22841 * @class 22842 * The Config object for gadgets within the Finesse desktop container which 22843 * contains configuration data provided by the container page. 22844 * @constructs 22845 */ 22846 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 22847 22848 }; 22849 } else { 22850 return {}; 22851 } 22852 }()); 22853 22854 /** Assign to container and gadget namespace to have config available in both */ 22855 window.finesse = window.finesse || {}; 22856 window.finesse.container = window.finesse.container || {}; 22857 window.finesse.container.Config = window.finesse.container.Config || Config; 22858 22859 window.finesse.gadget = window.finesse.gadget || {}; 22860 window.finesse.gadget.Config = Config; 22861 22862 return Config; 22863 }); 22864 /** 22865 * Digital Channel uses a JSON payload for communication with DigitalChannelManager. 22866 * That payload has to conform to this schema. 22867 * This schema has been defined as per http://json-schema.org/ 22868 * 22869 * @see utilities.JsonValidator 22870 */ 22871 /** The following comment is to prevent jslint errors about using variables before they are defined. */ 22872 /*global window:true, define:true*/ 22873 /*jslint nomen: true, unparam: true, sloppy: true, white: true */ 22874 define('digital/ChannelSchema',['require','exports','module'],function (require, exports, module) { 22875 22876 var ChannelSchema = (function () { /** @lends finesse.digital.ChannelSchema.prototype */ 22877 22878 var _menuConfigSchema = { 22879 "$schema": "http://json-schema.org/draft-04/schema#", 22880 "type": "object", 22881 "properties": { 22882 "label": { 22883 "type": "string" 22884 }, 22885 "menuItems": { 22886 "type": "array", 22887 "uniqueItems": true, 22888 "items": [{ 22889 "type": "object", 22890 "properties": { 22891 "id": { 22892 "type": "string" 22893 }, 22894 "label": { 22895 "type": "string" 22896 }, 22897 "iconColor": { 22898 "type": "string", 22899 "enum": ["available", "unavailable", "busy"] 22900 } 22901 }, 22902 "required": ["id", "label", "iconColor"], 22903 "additionalProperties": false 22904 }] 22905 } 22906 }, 22907 "required": ["label", "menuItems"], 22908 "additionalProperties": false 22909 }, 22910 22911 _channelConfigSchema = { 22912 "$schema": "http://json-schema.org/draft-04/schema#", 22913 "type": "object", 22914 "properties": { 22915 "actionTimeoutInSec": { 22916 "type": "integer", 22917 "minimum": 1, 22918 "maximum": 30 22919 }, 22920 "icons": { 22921 "type": "array", 22922 "minItems": 1, 22923 "items": [ 22924 { 22925 "type": "object", 22926 "properties": { 22927 "type": { 22928 "type": "string", 22929 "enum": ["collab-icon", "url"] 22930 }, 22931 "value": { 22932 "type": "string" 22933 } 22934 }, 22935 "required": [ 22936 "type", 22937 "value" 22938 ], 22939 "additionalProperties": false 22940 } 22941 ] 22942 } 22943 }, 22944 "required": [ 22945 "actionTimeoutInSec", 22946 "icons" 22947 ], 22948 "additionalProperties": false 22949 }, 22950 22951 _channelStateSchema = { 22952 "$schema": "http://json-schema.org/draft-04/schema#", 22953 "type": "object", 22954 "properties": { 22955 "label": { 22956 "type": "string" 22957 }, 22958 "currentState": { 22959 "type": "string" 22960 }, 22961 "iconColor": { 22962 "type": "string" 22963 }, 22964 "enable": { 22965 "type": "boolean" 22966 }, 22967 "logoutDisabled": { 22968 "type": "boolean" 22969 }, 22970 "logoutDisabledText": { 22971 "type": "string" 22972 }, 22973 "iconBadge": { 22974 "type": "string", 22975 "enum": [ 22976 "error", 22977 "info", 22978 "warning", 22979 "none" 22980 ] 22981 }, 22982 "hoverText": { 22983 "type": "string" 22984 } 22985 }, 22986 "required": [ 22987 "label", 22988 "currentState", 22989 "iconColor", 22990 "enable", 22991 "logoutDisabled", 22992 "iconBadge" 22993 ], 22994 "additionalProperties": false 22995 }; 22996 22997 return { 22998 22999 /** 23000 * @class 23001 * <b>F</b>i<b>n</b>esse digital <b>c</b>hannel state control (referred to as FNC elsewhere in this document) 23002 * is a programmable desktop component that was introduced in Finesse 12.0. 23003 * This API provides the schema that is used in {@link finesse.digital.ChannelService} for various channel operations. 23004 * 23005 * This schema has been defined as per http://json-schema.org/ 23006 * <style> 23007 * .schemaTable tr:nth-child(even) { background-color:#EEEEEE; } 23008 * .schemaTable th { background-color: #999999; } 23009 * .schemaTable th, td { border: none; } 23010 * .pad30 {padding-left: 30px;} 23011 * .inset { 23012 * padding: 5px; 23013 * border-style: inset; 23014 * background-color: #DDDDDD; 23015 * width: auto; 23016 * text-shadow: 2px 2px 3px rgba(255,255,255,0.5); 23017 * font: 12px arial, sans-serif; 23018 * color:rebeccapurple; 23019 * } 23020 * 23021 * </style> 23022 * 23023 * @example 23024 * <h3 id='cdIcons'>Cisco Common Desktop Stock Icon names with image</h3> 23025 * 23026 * The channel configuration schema has options to take 23027 * Cisco Common Desktop icon (CD-icon) name as value. 23028 * 23029 * To get to know the list of CD-UI icon names and its visual design, 23030 * paste the below JavaScript code in javascript editor part of your 23031 * browser developer console after Finesse login. This script will clear off the 23032 * Finesse web-page and will display icon-name and its rendering in a HTML table. 23033 * To get back to the page, just refresh the browser. 23034 * Note: You can also set this up in a gadget for reference. 23035 * <pre class='inset'> 23036 var showIcons = function () { 23037 $('body').html(''); 23038 23039 $('body').append("<table border='1' background-color:#a0c0a0;'>" 23040 + "<thead style='display: none;'><th>Icon Name</th>" 23041 + "<th>Icon</th></thead><tbody " 23042 + "style='display: block; overflow-y: auto; height: 600px'>" 23043 + "</tbody></table>"); 23044 23045 var icons = window.top.cd.core.cdIcon; 23046 23047 var addIcon = function (name, iconJson) { 23048 23049 var width = (iconJson.width) ? iconJson.width : 1000; 23050 var height = (iconJson.height) ? iconJson.height : 1000; 23051 23052 var iconBuilt = "<tr><td>" + name 23053 + "</td><td><svg width='" + width 23054 + "' height='" + height 23055 + "' style='height: 30px; width: 30px;' viewBox='" 23056 + iconJson.viewBox + "'>" 23057 + iconJson.value + "</svg></td></tr>"; 23058 23059 23060 try { 23061 $('tbody').append(iconBuilt); 23062 } catch (e) { 23063 console.error("Error when adding " + name, e); 23064 } 23065 } 23066 23067 for (var icon in icons) { 23068 if (icons[icon].viewBox) addIcon(icon, icons[icon]) 23069 } 23070 } 23071 23072 showIcons(); 23073 * </pre> 23074 * 23075 * @constructs 23076 */ 23077 _fakeConstuctor: function () { 23078 /* This is here so we can document init() as a method rather than as a constructor. */ 23079 }, 23080 23081 /** 23082 * @example 23083 * <BR><BR><b>Example JSON for <i>MenuConfig</i> data:</b> 23084 * <pre class='inset'> 23085 { 23086 "label" : "Chat", 23087 "menuItems" : 23088 [ 23089 { 23090 "id": "ready-menu-item", 23091 "label": "Ready", 23092 "iconColor": "available" 23093 }, 23094 { 23095 "id": "not-ready-menu-item", 23096 "label": "Not Ready", 23097 "iconColor": "unavailable" 23098 } 23099 ] 23100 } 23101 * </pre> 23102 * @returns 23103 * Schema for validation of the below JSON definition: 23104 * <table class="schemaTable"> 23105 * <thead> 23106 * <tr><th>Key</th><th>Type</th><th>Example</th><th>Description</th></tr> 23107 * </thead> 23108 * <tbody> 23109 * <tr><td>label</td><td>String</td><td>Chat</td><td>This will be the top level menu name for the channel menu</td></tr> 23110 * <tr><td>menuItems</td><td colspan='2'>Array</td><td>These menus will be listed under the top level menu. A single item is defined below</td></tr> 23111 * <tr><td class='pad30'>id</td><td>String</td><td>ready-menu-item</td><td>This id needs to be unique for a channel. 23112 * When there is a user action on the channel menu, this id will be returned back via parameter {@link finesse.digital.ChannelService#selectedMenuItemId}</td></tr> 23113 * <tr><td class='pad30'>label</td><td>String</td><td>Ready</td><td>The text of menu item</td></tr> 23114 * <tr><td class='pad30'>iconColor</td><td>Enum</td><td>available</td><td>available - shows up as green; unavailable - shows up as red;busy - shows up as orange</td></tr> 23115 * </tbody> 23116 * </table> 23117 * 23118 * 23119 */ 23120 getMenuConfigSchema: function () { 23121 return _menuConfigSchema; 23122 }, 23123 23124 /** 23125 * @example 23126 * <BR><BR><b>Example JSON for <i>ChannelConfig</i> data:</b> 23127 * <pre class='inset'> 23128 { 23129 "actionTimeoutInSec": 5, 23130 "icons" : [ 23131 { 23132 "type": "collab-icon", 23133 "value": "Chat" 23134 }, 23135 { 23136 "type": "url", 23137 "value": "../../thirdparty/gadget3/channel-icon.png" 23138 } 23139 ] 23140 } 23141 * </pre> 23142 * @returns 23143 * Schema for validation of the below JSON definition: 23144 * <table class="schemaTable"> 23145 * <thead> 23146 * <tr><th>Key</th><th>Type</th><th>Example</th><th>Description</th></tr> 23147 * </thead> 23148 * <tbody> 23149 * <tr><td>actionTimeoutInSec</td><td>Integer</td><td>5</td><td>Value in seconds. Represents the max time the FNC will wait after sending the menu 23150 * selection request to the gadget. 23151 * Upper limit is 30 seconds. It is recommended that this limit be kept as 23152 * low as possible, since no other operation can be performed 23153 * on FNC during this period</td></tr> 23154 * <tr><td>icons</td><td colspan='2'>Array</td><td>JSON array of icons to be composed and displayed in the Header 23155 * to visually represent a channel. This should ideally never change. 23156 * A single item is defined below</td></tr> 23157 * <tr><td class='pad30'>type</td><td>Enum</td><td>collab-icon</td> 23158 * <td>Takes either <i>collab-icon</i> or <i>url</i> as value. <BR><i>collab-icon</i> would apply a stock icon. Refer to stock icon names over <a href="#cdIcons">here</a> 23159 * <BR><i>url</i> applies a custom icon supplied by gadget. The icon could be located as part of gadget files in Finesse.</td></tr> 23160 * <tr><td class='pad30'>value</td><td>String</td><td>Chat</td><td>The <a href="#cdIcons">stock icon name</a> 23161 * or the url of the custom icon</td></tr> 23162 * </tbody> 23163 * </table> 23164 */ 23165 getChannelConfigSchema: function () { 23166 return _channelConfigSchema; 23167 }, 23168 23169 23170 /** 23171 * @example 23172 * <BR><BR><b>Example JSON for <i>ChannelState</i> data:</b> 23173 * <pre class='inset'> 23174 { 23175 "label" : "Chat & Email", 23176 "currentState" : "ready", 23177 "iconColor" : "available", 23178 "enable" : true, 23179 "logoutDisabled" : true, 23180 "logoutDisabledText" : "Please go unavailable on chat before logout", 23181 "iconBadge" : "none" 23182 "hoverText" : "Tooltip text" 23183 } 23184 * </pre> 23185 * 23186 * @returns 23187 * Schema for validation of the below JSON definition: 23188 * 23189 * <table class="schemaTable"> 23190 * <thead> 23191 * <tr><th>Key</th><th>Type</th><th>Example</th><th>Description</th></tr> 23192 * </thead> 23193 * <tbody> 23194 * <tr><td>label</td><td>String</td><td>Chat & Email</td><td>The name used for the channel on hover</td></tr> 23195 * <tr><td>currentState</td><td>String</td><td>ready</td><td>Text for the current state of the channel. This text will be appended to the label & will be displayed on the channel icon as a hover message</td></tr> 23196 * <tr><td>iconColor</td><td>Enum</td><td>available</td> 23197 * <td>Takes one of these values; available - shows up as green; 23198 * unavailable - shows up as red; busy - shows up as orange. 23199 * The icon colors will be used by the FNC in composing the final icon for the channel, 23200 * that indicates the current state of the channel.</td></tr> 23201 * <tr><td>enable</td><td>boolean</td><td>true</td><td>true indicates the channel is active. false indicates the channel is not active and none of the menu should be shown.</td></tr> 23202 * <tr><td>logoutDisabled</td><td>boolean</td><td>true</td><td>Define as true if the logout menu in the user identity component has to be disabled. 23203 * For e.g. during Agent READY or BUSY state, it makes sense to disable the logout menu and enable it again when the state changes to NOT READY. 23204 * <BR>Note: The true setting in any of the Channel across all Channels will cause the Logout menu to be disabled.</td></tr> 23205 * <tr><td>logoutDisabledText</td><td>String</td><td>Please go unavailable on chat before logout</td> 23206 * <td>The text which will be shown to the user if the logout is disabled</td></tr> 23207 * <tr><td>iconBadge</td><td>Enum</td><td>none</td><td>info - Info badge will be displayed; error - Error badge will be displayed; warning - Warning badge will be displayed; none - No badge will be displayed </td> </tr> 23208 * <tr><td>hoverText</td><td>String</td><td>Tooltip text</td><td>If this is specified it will override the tooltip that shows up by default. Use this to indicate errors or disabled message etc. If the underlying issue is resolved then clear the string </td> /tr> 23209 * </tbody> 23210 * </table> 23211 */ 23212 getChannelStateSchema: function () { 23213 return _channelStateSchema; 23214 } 23215 }; 23216 }()); 23217 23218 window.finesse = window.finesse || {}; 23219 window.finesse.digital = window.finesse.digital || {}; 23220 window.finesse.digital.ChannelSchema = ChannelSchema; 23221 23222 // CommonJS 23223 if (typeof module === "object" && module.exports) { 23224 module.exports = ChannelSchema; 23225 } 23226 23227 return ChannelSchema; 23228 }); 23229 23230 /** 23231 * API for managing Finesse Digital Channels. 23232 */ 23233 /** @private */ 23234 define('digital/ChannelService',['require','exports','module','./ChannelSchema','../utilities/JsonValidator','../utilities/Utilities','../clientservices/ClientServices'],function (require, exports, module) { 23235 23236 var ChannelService = (function () { /** @lends finesse.digital.ChannelService.prototype */ 23237 23238 var SchemaMgr = require('./ChannelSchema'), 23239 SchemaValidator = require('../utilities/JsonValidator'), 23240 Utilities = require('../utilities/Utilities'), 23241 ClientService = require('../clientservices/ClientServices'), 23242 /** 23243 * References to ContainerService 23244 * @private 23245 */ 23246 _containerService, 23247 23248 /** 23249 * Whether the ChannelService have been initialized or not. 23250 * @private 23251 */ 23252 _inited = false, 23253 23254 /** 23255 * References to ClientServices logger 23256 * @private 23257 */ 23258 _logger = { 23259 log: ClientService.log 23260 }, 23261 23262 /** 23263 * Open Ajax topic for the API to publish requests for register, de-register and update on the 23264 * NonVoice Channels. The Finesse NonVoice Component (FNC) will listen to this topic and process 23265 * the request. 23266 * @private 23267 */ 23268 _fncTopic, 23269 23270 /** 23271 * Lists the various channel actions taken by gadget. 23272 * @private 23273 */ 23274 ACTIONS = Object.freeze({ 23275 ADD: 'ADD', 23276 REMOVE: 'REMOVE', 23277 UPDATE: 'UPDATE', 23278 UPDATE_MENU: 'UPDATE_MENU', 23279 UPDATE_CHANNEL_STATE: 'UPDATE_CHANNEL_STATE' 23280 }), 23281 23282 /** 23283 * Operation status enum 23284 * @private 23285 */ 23286 STATUS = Object.freeze({ 23287 SUCCESS: 'success', 23288 FAILURE: 'failure' 23289 }), 23290 23291 /** 23292 * State Status enum 23293 * @private 23294 */ 23295 STATE_STATUS = Object.freeze({ 23296 AVAILABLE: 'available', 23297 UNAVAILABLE: 'unavailable', 23298 BUSY: 'busy' 23299 }), 23300 23301 /** 23302 * Icon Color mapping for state status 23303 */ 23304 ICON_COLOR = Object.freeze({ 23305 available: '#2CB14C', 23306 unavailable: '#CF4237', 23307 busy: '#D79208' 23308 }), 23309 23310 /** 23311 * Channel Icon Type 23312 * @private 23313 */ 23314 ICON_TYPE = Object.freeze({ 23315 COLLAB_ICON: "collab-icon", 23316 URL: "url" 23317 }), 23318 23319 /** 23320 * Icon Badge Type 23321 * @private 23322 */ 23323 BADGE_TYPE = Object.freeze({ 23324 NONE: "none", 23325 INFO: "info", 23326 WARNING: "warning", 23327 ERROR: "error" 23328 }), 23329 23330 /* 23331 * Dynamic registry object, which keeps a map of success and error callbacks for requests whose 23332 * responses are expected asynchronously. Error callback will be invoked on operation timeout. 23333 * @private 23334 */ 23335 _requestCallBackRegistry = (function () { 23336 23337 var OPERATION_TIMEOUT = 60000, 23338 _map = {}, 23339 23340 _clear = function (_uid) { 23341 if (_map[_uid]) { 23342 if (_map[_uid].pendingTimer) { 23343 clearTimeout(_map[_uid].pendingTimer); 23344 } 23345 delete _map[_uid]; 23346 } 23347 }, 23348 23349 _requestTimedOutHandler = function (_uid) { 23350 var err = { 23351 status: 'failure', 23352 error: { 23353 errorCode: '408', 23354 errorDesc: 'Request Timed Out' 23355 } 23356 }; 23357 if (_map[_uid] && typeof _map[_uid].error === 'function') { 23358 _logger.log("ChannelService: Request Timeout. Request Id : " + _uid); 23359 _map[_uid].error(err); 23360 } 23361 _clear(_uid); 23362 }, 23363 23364 _initTimerForTimeout = function (_uid) { 23365 _map[_uid].pendingTimer = setTimeout(function () { 23366 _requestTimedOutHandler(_uid); 23367 }, OPERATION_TIMEOUT); 23368 }, 23369 23370 _add = function (_uid, _chId, _success, _error) { 23371 _map[_uid] = { 23372 channelId: _chId, 23373 success: _success, 23374 error: _error 23375 }; 23376 _initTimerForTimeout(_uid); 23377 }; 23378 23379 return { 23380 register: _add, 23381 clear: _clear, 23382 success: function (uid, data) { 23383 if (_map[uid] && typeof _map[uid].success === 'function') { 23384 data.channelId = _map[uid].channelId; 23385 var response = _map[uid].success(data); 23386 _clear(uid); 23387 return response; 23388 } 23389 }, 23390 error: function (uid, data) { 23391 if (_map[uid] && typeof _map[uid].error === 'function') { 23392 data.channelId = _map[uid].channelId; 23393 var response = _map[uid].error(data); 23394 _clear(uid); 23395 return response; 23396 } 23397 } 23398 }; 23399 }()), 23400 23401 /* 23402 * Dynamic registry object, which keeps a map of channel id and its menu handler function reference. 23403 * This handler will be invoked on user interaction with its channel menu. 23404 * @private 23405 */ 23406 _menuHandlerRegistry = (function () { 23407 var _map = {}, 23408 23409 _add = function (_id, _chId, _menuHandler) { 23410 _map[_id] = { 23411 channelId: _chId, 23412 menuHandler: _menuHandler 23413 }; 23414 }, 23415 23416 _clear = function (_id) { 23417 if (_map[_id]) { 23418 delete _map[_id]; 23419 } 23420 }; 23421 23422 return { 23423 register: _add, 23424 clear: _clear, 23425 menuSelection: function (_id, _menuItemId, _success, _error) { 23426 if (_map[_id] && _map[_id].menuHandler) { 23427 return _map[_id].menuHandler(_map[_id].channelId, _menuItemId, 23428 _success, _error); 23429 } 23430 } 23431 }; 23432 }()), 23433 23434 /** 23435 * Ensure that ChannelService have been inited. 23436 * @private 23437 */ 23438 _isInited = function () { 23439 if (!_inited) { 23440 throw new Error("ChannelService needs to be inited."); 23441 } 23442 }, 23443 23444 /** 23445 * Gets the id of the gadget. 23446 * @returns {number} the id of the gadget 23447 * @private 23448 */ 23449 _getMyGadgetId = function () { 23450 var gadgetId = _containerService.getMyGadgetId(); 23451 return gadgetId || ''; 23452 }, 23453 23454 /** 23455 * Validate the menuConfig structure in channelData payload. 23456 * 23457 * @param {Object} data 23458 * menuConfig structure. 23459 * @throws {Error} if the data is not in menuConfig defined format. 23460 * @private 23461 */ 23462 _validateMenuConfig = function (data) { 23463 var result = SchemaValidator.validateJson(data, SchemaMgr.getMenuConfigSchema()); 23464 /* if result.valid is false, then additional details about failure are contained in 23465 result.error which contains json like below: 23466 23467 { 23468 "code": 0, 23469 "message": "Invalid type: string", 23470 "dataPath": "/intKey", 23471 "schemaPath": "/properties/intKey/type" 23472 } 23473 */ 23474 if (!result.valid) { 23475 _logger.log("ChannelService: Finesse Nonvoice API Validation result : " + JSON.stringify(result)); 23476 throw new Error("menuConfig structure is not in expected format. Refer finesse client logs for more details."); 23477 } 23478 }, 23479 23480 /** 23481 * Validate the channelConfig structure in channelData payload. 23482 * 23483 * @param {Object} data 23484 * channelConfig structure. 23485 * @throws {Error} if the data is not in channelConfig defined format. 23486 * @private 23487 */ 23488 _validateChannelConfig = function (data) { 23489 var result = SchemaValidator.validateJson(data, SchemaMgr.getChannelConfigSchema()); 23490 if (!result.valid) { 23491 _logger.log("ChannelService: Finesse Nonvoice API Validation result : " + JSON.stringify(result)); 23492 throw new Error("channelConfig structure is not in expected format. Refer finesse client logs for more details."); 23493 } 23494 }, 23495 23496 /** 23497 * Validate the channelState structure in channelData payload. 23498 * 23499 * @param {Object} data 23500 * channelState structure. 23501 * @throws {Error} if the data is not in channelState defined format. 23502 * @private 23503 */ 23504 _validateChannelState = function (data) { 23505 var result = SchemaValidator.validateJson(data, SchemaMgr.getChannelStateSchema()); 23506 if (!result.valid) { 23507 _logger.log("ChannelService: Finesse Nonvoice API Validation result : " + JSON.stringify(result)); 23508 throw new Error("channelState structure is not in expected format. Refer finesse client logs for more details."); 23509 } 23510 }, 23511 23512 /** 23513 * Validate the entire channelData structure in payload. 23514 * 23515 * @param {Object} data 23516 * channelData structure. 23517 * @throws {Error} if the data is not in channelData defined format. 23518 * @private 23519 */ 23520 _validateAddChannelPayload = function (data) { 23521 var err = ""; 23522 if (!data.hasOwnProperty("menuConfig")) { 23523 err = "menuConfig property missing in Channel Data"; 23524 } else if (!data.hasOwnProperty("channelConfig")) { 23525 err = "channelConfig property missing in Channel Data"; 23526 } else if (!data.hasOwnProperty("channelState")) { 23527 err = "channelState property missing in Channel Data"; 23528 } 23529 23530 if (err) { 23531 throw new Error(err); 23532 } 23533 _validateMenuConfig(data.menuConfig); 23534 _validateChannelConfig(data.channelConfig); 23535 _validateChannelState(data.channelState); 23536 }, 23537 23538 /** 23539 * Validate the available structure in payload. 23540 * 23541 * @param {Object} data 23542 * channelData structure. 23543 * @throws {Error} if the data is not in channelData defined format. 23544 * @private 23545 */ 23546 _validateUpdateChannelPayload = function (data) { 23547 if (data.hasOwnProperty("menuConfig")) { 23548 _validateMenuConfig(data.menuConfig); 23549 } 23550 if (data.hasOwnProperty("channelConfig")) { 23551 _validateChannelConfig(data.channelConfig); 23552 } 23553 if (data.hasOwnProperty("channelState")) { 23554 _validateChannelState(data.channelState); 23555 } 23556 }, 23557 23558 /** 23559 * Validate the gadget passed JSON structure against the schema definition. 23560 * 23561 * @param {Object} data 23562 * channelData structure. 23563 * @throws {Error} if the data is not in channelData defined format. 23564 * @private 23565 */ 23566 _validateData = function (data, action) { 23567 switch (action) { 23568 case ACTIONS.ADD: 23569 _validateAddChannelPayload(data); 23570 break; 23571 case ACTIONS.UPDATE: 23572 _validateUpdateChannelPayload(data); 23573 break; 23574 case ACTIONS.UPDATE_CHANNEL_STATE: 23575 _validateChannelState(data); 23576 break; 23577 case ACTIONS.UPDATE_MENU: 23578 _validateMenuConfig(data); 23579 break; 23580 } 23581 }, 23582 23583 /** 23584 * Prepares the FNC required payload based on the action and the supplied JSON data. 23585 * 23586 * @param {Object} data 23587 * json structure used for channel request. 23588 * @returns {Object} data in FNC required payload format. 23589 * @private 23590 */ 23591 _prepareFNCChannelData = function (data, action) { 23592 switch (action) { 23593 case ACTIONS.ADD: 23594 if (data.hasOwnProperty("channelState")) { 23595 data.channelState.iconColor = ICON_COLOR[data.channelState.iconColor]; 23596 } 23597 data.menuConfig.menuItems.forEach(function (item) { 23598 item.iconColor = ICON_COLOR[item.iconColor]; 23599 }); 23600 break; 23601 case ACTIONS.UPDATE: 23602 if (data.hasOwnProperty("channelState")) { 23603 data.channelState.iconColor = ICON_COLOR[data.channelState.iconColor]; 23604 } 23605 if (data.hasOwnProperty("menuConfig")) { 23606 data.menuConfig.menuItems.forEach(function (item) { 23607 item.iconColor = ICON_COLOR[item.iconColor]; 23608 }); 23609 } 23610 break; 23611 case ACTIONS.UPDATE_CHANNEL_STATE: 23612 data.iconColor = ICON_COLOR[data.iconColor]; 23613 data = { 23614 channelState: data 23615 }; 23616 break; 23617 case ACTIONS.UPDATE_MENU: 23618 data.menuItems.forEach(function (item) { 23619 item.iconColor = ICON_COLOR[item.iconColor]; 23620 }); 23621 data = { 23622 menuConfig: data 23623 }; 23624 break; 23625 } 23626 if(data.channelState){ 23627 data.channelState.isAgentReady = data.channelState.iconColor === ICON_COLOR.available; 23628 } 23629 23630 return data; 23631 }, 23632 23633 /** 23634 * Utility function to make a subscription to a particular topic. Only one 23635 * callback function is registered to a particular topic at any time. 23636 * 23637 * @param {String} topic 23638 * The full topic name. The topic name should follow the OpenAjax 23639 * convention using dot notation (ex: finesse.api.User.1000). 23640 * @param {Function} handler 23641 * The function that should be invoked with the data when an event 23642 * is delivered to the specific topic. 23643 * @returns {Boolean} 23644 * True if the subscription was made successfully and the callback was 23645 * been registered. False if the subscription already exist, the 23646 * callback was not overwritten. 23647 * @private 23648 */ 23649 _subscribe = function (topic, handler) { 23650 try { 23651 return _containerService.addHandler(topic, handler); 23652 } catch (error) { 23653 _logger.log('ChannelService: Error while subscribing to open ajax topic : ' + topic + ', Exception:' + error.toString()); 23654 throw new Error('ChannelService: Unable to subscribe the channel topic with Open Ajax Hub : ' + error); 23655 } 23656 }, 23657 23658 /** 23659 * Function which processes the menu selection request from FNC. 23660 * 23661 * @param {Object} payload 23662 * The payload containing the menu selection request details. 23663 * @private 23664 */ 23665 _processMenuChangeRequest = function (payload) { 23666 _menuHandlerRegistry.menuSelection(payload.id, payload.selection, function (successPayload) { 23667 var response = { 23668 id: payload.id, 23669 requestId: payload.requestId, 23670 status: successPayload.hasOwnProperty('status') ? successPayload.status : STATUS.SUCCESS 23671 }; 23672 _containerService.publish(_fncTopic, response); 23673 }, function (failurePayload) { 23674 var response = { 23675 id: payload.id, 23676 requestId: payload.requestId, 23677 status: failurePayload.hasOwnProperty('status') ? failurePayload.status : STATUS.FAILURE 23678 }; 23679 if (failurePayload.hasOwnProperty('error')) { 23680 response.error = failurePayload.error; 23681 } 23682 _containerService.publish(_fncTopic, response); 23683 }); 23684 }, 23685 23686 _processChannelOperationResult = function (payload) { 23687 var response = { 23688 status: payload.status 23689 }; 23690 if (payload.status === STATUS.FAILURE) { 23691 // error response 23692 if (payload.hasOwnProperty('error')) { 23693 response.error = payload.error; 23694 } 23695 _logger.log('ChannelService: Failure response for requestId: ' + payload.requestId); 23696 _requestCallBackRegistry.error(payload.requestId, response); 23697 } else { 23698 // success response 23699 _requestCallBackRegistry.success(payload.requestId, response); 23700 } 23701 }, 23702 23703 /** 23704 * Function which processes the messages from Finesse NonVoice Component. 23705 * The messages received mainly for below cases, 23706 * <ul> 23707 * <li> Operation responses for ADD, REMOVE and UPDATE. 23708 * <li> User menu selection request. 23709 * <ul> 23710 * The registered callbacks are invoked based on the data. 23711 * 23712 * @param {String} payload 23713 * The actual message sent by FNC via open ajax hub. 23714 * @private 23715 */ 23716 _processFNCMessage = function (payload) { 23717 _logger.log('ChannelService: Received Message from FNC: ' + JSON.stringify(payload)); 23718 try { 23719 // if the received data from topic is in text format, then parse it to JSON format 23720 payload = typeof payload === 'string' ? JSON.parse(payload) : payload; 23721 } catch (error) { 23722 _logger.log('ChannelService: Error while parsing the FNC message' + payload); 23723 return; 23724 } 23725 23726 if (payload.hasOwnProperty('selection')) { 23727 // menu selection request from FNC 23728 _processMenuChangeRequest(payload); 23729 } else { 23730 // ADD or REMOVE or UPDATE operation status from FNC 23731 _processChannelOperationResult(payload); 23732 } 23733 }, 23734 23735 /* 23736 * Validate the data and process the channel API request based on the action. 23737 * 23738 * @param {String} channelId 23739 * Digtial channel id, should be unique within the gadget. 23740 * @param {Actions Enum} action 23741 * Any one of the channel action defined in ACTIONS enum. 23742 * @param {Function} success 23743 * Callback function invoked upon request successful. 23744 * @param {Function} error 23745 * Callback function invoked upon request errored. 23746 * @param {JSON} data 23747 * ADD or UPDATE operation data object as per the defined format. 23748 * @param {Function} menuHandler 23749 * Callback function invoked when the user interacts with the registered channel menu. 23750 * @throws Error 23751 * Throws error when the passed in data is not in defined format. 23752 * @private 23753 */ 23754 _processChannelRequest = function (channelId, action, success, error, data, menuHandler) { 23755 _logger.log('ChannelService: Received digital channel request. ChannelId ' + channelId + ', Action ' + action + ', Data ' + JSON.stringify(data)); 23756 23757 _isInited(); 23758 23759 var id = _getMyGadgetId() + '/' + channelId, 23760 topic = _fncTopic + '.' + id, 23761 requestUID, payload; 23762 23763 if (action === ACTIONS.REMOVE) { 23764 // clear the menuHandler for this channel from menu registry 23765 _menuHandlerRegistry.clear(id); 23766 23767 // remove the hub listener for the channel topic 23768 _containerService.removeHandler(topic, _processFNCMessage); 23769 } else { 23770 if (!data) { 23771 throw new Error("ChannelService: Channel Data cannot be empty."); 23772 } 23773 try { 23774 data = typeof data === 'string' ? JSON.parse(data) : data; 23775 } catch (err) { 23776 throw new Error("ChannelService: Data Parsing Failed. ADD or UPDTAE payload not in expected format. Error: " + err.toString); 23777 } 23778 23779 // validate the data 23780 _validateData(data, action); 23781 23782 // prepare FNC payload 23783 data = _prepareFNCChannelData(data, action); 23784 23785 if (action === ACTIONS.ADD) { 23786 // onMenuClick must be supplied and of type function 23787 if (!menuHandler || typeof menuHandler !== 'function') { 23788 throw new Error("ChannelService: onMenuClick parameter in addChannel() must be a function."); 23789 } 23790 23791 // register the menuHandler for this channel with menu registry for later use 23792 _menuHandlerRegistry.register(id, channelId, menuHandler); 23793 23794 // subscribe this channel topic for messages from Finesse NV Header Component 23795 _subscribe(topic, _processFNCMessage); 23796 } 23797 23798 // derive the FNC action 23799 action = action !== ACTIONS.ADD ? ACTIONS.UPDATE : action; 23800 } 23801 23802 23803 requestUID = Utilities.generateUUID(); 23804 payload = { 23805 id: id, 23806 topic: topic, 23807 requestId: requestUID, 23808 action: action, 23809 data: data 23810 }; 23811 23812 /* 23813 * register the request with request registry for asynchronously 23814 * invoking success and failure callbacks about the operation 23815 * status. 23816 */ 23817 _requestCallBackRegistry.register(requestUID, channelId, success, error); 23818 23819 _logger.log('ChannelService: Sending channel request to FNC: ' + JSON.stringify(payload)); 23820 23821 _containerService.publish(_fncTopic, payload); 23822 }; 23823 23824 return { 23825 23826 23827 23828 23829 /** 23830 * @class 23831 * <b>F</b>i<b>n</b>esse digital <b>c</b>hannel state control (referred to as FNC elsewhere in this document) 23832 * is a programmable desktop component that was introduced in Finesse 12.0. 23833 * The ChannelService API provides hooks to FNC that can be leveraged by gadget hosting channels. 23834 * 23835 * <h3> FNC API </h3> 23836 * 23837 * The FNC API provides methods that can be leveraged by gadgets serving channels to 23838 * register, update or modify channel specific display information and corresponding menu action behavior 23839 * in Agent State Control Menu (referred to as FNC Menu component). 23840 * 23841 * <BR> 23842 * These APIs are available natively to the gadget through the finesse.js import. Examples of how to write a sample gadget 23843 * can be found <a href="https://github.com/CiscoDevNet/finesse-sample-code/tree/master/LearningSampleGadget">here</a>. 23844 * <BR> 23845 * The FNC API is encapsulated in a module named ChannelService within finesse.js and it can be accessed 23846 * via the namespace <i><b>finesse.digital.ChannelService</b></i>. 23847 * 23848 * <style> 23849 * .channelTable tr:nth-child(even) { background-color:#EEEEEE; } 23850 * .channelTable th { background-color: #999999; } 23851 * .channelTable th, td { border: none; } 23852 * .pad30 {padding-left: 30px;} 23853 * .inset { 23854 * padding: 5px; 23855 * border-style: inset; 23856 * background-color: #DDDDDD; 23857 * width: auto; 23858 * text-shadow: 2px 2px 3px rgba(255,255,255,0.5); 23859 * font: 12px arial, sans-serif; 23860 * color:rebeccapurple; 23861 * } 23862 * 23863 * </style> 23864 * 23865 * @example 23866 * 23867 * <h3> Example demonstrating initialization </h3> 23868 * 23869 * <pre class='inset'> 23870 containerServices = finesse.containerservices.ContainerServices.init(); 23871 channelService = finesse.digital.ChannelService.init(containerServices); 23872 channelService.addChannel(channelId, channelData, onMenuClick, onSuccess, onError); 23873 * </pre> 23874 23875 * @constructs 23876 */ 23877 _fakeConstuctor: function () { 23878 /* This is here so we can document init() as a method rather than as a constructor. */ 23879 }, 23880 23881 /** 23882 * Initialize ChannelService for use in gadget. 23883 * 23884 * @param {finesse.containerservices.ContainerServices} ContainerServices instance. 23885 * @returns {finesse.digital.ChannelService} instance. 23886 */ 23887 init: function (containerServices) { 23888 if (!_inited) { 23889 if (!containerServices) { 23890 throw new Error("ChannelService: Invalid ContainerService reference."); 23891 } 23892 _inited = true; 23893 _containerService = containerServices; 23894 window.finesse.clientservices.ClientServices.setLogger(window.finesse.cslogger.ClientLogger); 23895 _fncTopic = containerServices.Topics.FINEXT_NON_VOICE_GADGET_EVENT; 23896 } 23897 return this; 23898 }, 23899 23900 /** 23901 * 23902 * This API starts the gadget interaction with FNC by adding the channel to the FNC Menu component. 23903 * The complete channel state is required in the form of JSON payload as part of this API. 23904 * It is advised developers pre-validate the JSON that is passed as parameter against its corresponding schema 23905 * by testing it through {@link finesse.utilities.JsonValidator.validateJson}. 23906 * The result of the add operation will be returned via the given success or error callback. 23907 * 23908 * @param {String} channelId 23909 * Digital channel id, should be unique within the gadget. 23910 * The API developer can supply any string that uniquely identifies the Digital Channel for registration with FNC. 23911 * This id would be returned in callbacks by FNC when there is a user action on menus that belongs to a channel. 23912 * @param {Object} channelData 23913 * It is a composition of the following KEY-VALUE pair as JSON. The value part would be the object that conforms to 'key' specific schema. 23914 * <table class="channelTable"> 23915 * <thead><tr><th>key</th><th>Value</th></tr><thead> 23916 * <tbody> 23917 * <tr><td>menuconfig</td><td>Refer {@link finesse.digital.ChannelSchema#getMenuConfigSchema} for description about this JSON payload</td></tr> 23918 * <tr><td>channelConfig</td><td>Refer {@link finesse.digital.ChannelSchema#getChannelConfigSchema} for description about this JSON payload</td></tr> 23919 * <tr><td>channelState</td><td>Refer {@link finesse.digital.ChannelSchema#getChannelStateSchema} for description about this JSON payload</td></tr> 23920 * </tbody> 23921 * </table> 23922 * 23923 * @param onMenuClick 23924 * Handler that is provided by the Gadget to the API during channel addition. 23925 * It is invoked whenever the user clicks a menu item on the FNC control. 23926 * <table class="channelTable"> 23927 * <thead><tr><th>Parameter</th><th>Description</th></tr></thead> 23928 * <tbody> 23929 * <tr><td><h4 id="selectedMenuItemId">selectedMenuItemId</h4></td><td>The selectedMenuItemId will contain the menuId as defined in 23930 * menuConfig {@link finesse.digital.ChannelSchema#getMenuConfigSchema} payload. 23931 * The gadget has to invoke onSuccess callback, if the state change is success or 23932 * onError callback if there is a failure performing state change operation 23933 * (refer actionTimeoutInSec in JSON payload for timeout) on the underlying channel service.</td></tr> 23934 * <tr><td><h5 id="success">onSuccess<h5></td><td>The success payload would be of the following format: 23935 * <pre> 23936 * { 23937 * "channelId" : "[ID of the Digital channel]", 23938 * "status" : "success" 23939 * } 23940 * </pre></td></tr> 23941 * <tr><td><h5 id="failure">onError<h5></td><td>The error payload would be of the following format: 23942 * <pre> 23943 * { 23944 * "channelId" : "[ID of the Digital channel]", 23945 * "status" : "failure", 23946 * "error" : { 23947 * "errorCode": "[Channel supplied error code that will be logged in Finesse client logs]", 23948 * "errorDesc": "An error occurred while processing request" 23949 * } 23950 * } 23951 * </pre></td></tr> 23952 * </tbody></table> 23953 * @param onSuccess 23954 * Callback function invoked upon add operation successful. Refer above for the returned JSON payload. 23955 * @param onError 23956 * Callback function invoked upon add operation is unsuccessful. Refer above for the returned JSON payload. 23957 * @throws 23958 * Throws error when the passed in channelData is not as per schema. 23959 */ 23960 addChannel: function (channelId, channelData, onMenuClick, onSuccess, onError) { 23961 _processChannelRequest(channelId, ACTIONS.ADD, onSuccess, onError, channelData, onMenuClick); 23962 }, 23963 23964 /** 23965 * Removes a channel representation from the FNC Menu component. 23966 * The result of the remove operation will be intimated via the given success and error callbacks. 23967 * 23968 * @param {String} channelId 23969 * Digtial channel id, should be unique within the gadget. 23970 * This id would be returned in the callbacks. 23971 * @param {Function} onSuccess 23972 * <a href="#success">onSuccess</a> function that is invoked for successful operation. 23973 * @param {Function} onError 23974 * <a href="#failure">onError</a> function that is invoked for failed operation. 23975 */ 23976 removeChannel: function (channelId, onSuccess, onError) { 23977 _processChannelRequest(channelId, ACTIONS.REMOVE, onSuccess, onError); 23978 }, 23979 23980 /** 23981 * Updates the channels representation in FNC Menu component. 23982 * None of the data passed within the data payload <i>channelData</i> is mandatory. 23983 * This API provides an easy way to update the complete channel configuration together in one go or partially if required. 23984 * The result of the update operation will be intimated via the given success and error callbacks. 23985 * 23986 * @param {String} channelId 23987 * Digtial channel id, should be unique within the gadget 23988 * This id would be returned in the callbacks. 23989 * @param {Object} channelData 23990 * Channel data JSON object as per the spec. Refer {@link #addChannel} for this object description. Partial data sections allowed. 23991 * @param {Function} onSuccess 23992 * <a href="#success">onSuccess</a> function that is invoked for successful operation. 23993 * @param {Function} onError 23994 * <a href="#failure">onError</a> function that is invoked for failed operation. 23995 */ 23996 updateChannel: function (channelId, channelData, onSuccess, onError) { 23997 _processChannelRequest(channelId, ACTIONS.UPDATE, onSuccess, onError, channelData); 23998 }, 23999 24000 /** 24001 * Updates the menu displayed against a channel. 24002 * The result of the update operation will be intimated via the given success and error callbacks. 24003 * 24004 * @param {String} channelId 24005 * Digtial channel id, should be unique within the gadget. 24006 * This id would be returned in the callbacks. 24007 * @param {menuItem[]} menuItems 24008 * Refer {@link finesse.digital.ChannelSchema#getMenuConfigSchema} for menuItem definition. 24009 * @param {Function} onSuccess 24010 * <a href="#success">onSuccess</a> function that is invoked for successful operation. 24011 * @param {Function} onError 24012 * <a href="#failure">onError</a> function that is invoked for failed operation. 24013 */ 24014 updateChannelMenu: function (channelId, menuConfig, onSuccess, onError) { 24015 _processChannelRequest(channelId, ACTIONS.UPDATE_MENU, onSuccess, onError, menuConfig); 24016 }, 24017 24018 /** 24019 * Updates the channels current state. The result of 24020 * the update operation will be intimated via the given success and error callbacks. 24021 * 24022 * @param {String} channelId 24023 * Digtial channel id, should be unique within the gadget. 24024 * This id would be returned in the callbacks. 24025 * @param {Object} channelState 24026 * Refer {@link finesse.digital.ChannelSchema#getChannelStateSchema} for channel state definition. 24027 * @param {Function} onSuccess 24028 * <a href="#success">onSuccess</a> function that is invoked for successful operation. 24029 * @param {Function} onError 24030 * <a href="#failure">onError</a> function that is invoked for failed operation. 24031 */ 24032 updateChannelState: function (channelId, channelState, onSuccess, onError) { 24033 _processChannelRequest(channelId, ACTIONS.UPDATE_CHANNEL_STATE, onSuccess, onError, channelState); 24034 }, 24035 24036 /** 24037 * ENUM: Operation status. 24038 * [SUCCESS, FAILURE] 24039 */ 24040 STATUS: STATUS, 24041 24042 /** 24043 * ENUM: State Status indicates the icon color in channel. 24044 * [AVAILABLE, UNAVAILABLE, BUSY]*/ 24045 STATE_STATUS: STATE_STATUS, 24046 24047 /** 24048 * ENUM: Channel Icon location type. 24049 * [COLLAB_ICON, URL] 24050 */ 24051 ICON_TYPE: ICON_TYPE, 24052 24053 /** 24054 * ENUM: Icon Badge Type. 24055 * [NONE, INFO, WARNING, ERROR] 24056 */ 24057 ICON_BADGE_TYPE: BADGE_TYPE 24058 }; 24059 }()); 24060 24061 24062 /** @namespace Namespace for JavaScript class objects and methods related to Digital Channel management.*/ 24063 finesse.digital = finesse.digital || {}; 24064 24065 window.finesse = window.finesse || {}; 24066 window.finesse.digital = window.finesse.digital || {}; 24067 window.finesse.digital.ChannelService = ChannelService; 24068 24069 // CommonJS 24070 if (typeof module === "object" && module.exports) { 24071 module.exports = ChannelService; 24072 } 24073 24074 24075 return ChannelService; 24076 }); 24077 24078 /** 24079 * Popover service uses a JSON payload for communication from gadget. 24080 * The aforesaid payload has to conform to this schema. 24081 * This schema has been defined as per <a href='http://json-schema.org/'> JSON schema </a> standard. 24082 * 24083 * @see utilities.JsonValidator 24084 */ 24085 /** The following comment is to prevent jslint errors about using variables before they are defined. */ 24086 /*global window:true, define:true*/ 24087 /*jslint nomen: true, unparam: true, sloppy: true, white: true */ 24088 define('containerservices/PopoverSchema',['require','exports','module'],function (require, exports, module) { 24089 24090 var PopoverSchema = (function () { /** @lends finesse.containerservices.PopoverSchema.prototype */ 24091 24092 var _bannerDataSchema = { 24093 "$schema": "http://json-schema.org/draft-04/schema#", 24094 "type": "object", 24095 "properties": { 24096 "icon": { 24097 "type": "object", 24098 "properties": { 24099 "type": { 24100 "type": "string", 24101 "enum": ["collab-icon", "url"] 24102 }, 24103 "value": { 24104 "type": "string" 24105 } 24106 }, 24107 "required": [ 24108 "type", 24109 "value" 24110 ] 24111 }, 24112 "content": { 24113 "type": "array", 24114 "items": [{ 24115 "type": "object", 24116 "properties": { 24117 "name": { 24118 "type": "string" 24119 }, 24120 "value": { 24121 "type": "string" 24122 } 24123 }, 24124 "required": [ 24125 "name", 24126 "value" 24127 ] 24128 }] 24129 }, 24130 "headerContent": { 24131 "type": "object", 24132 "properties": { 24133 "maxHeaderTitle": { 24134 "type": "string" 24135 }, 24136 "minHeaderTitle": { 24137 "type": "string" 24138 } 24139 } 24140 } 24141 }, 24142 "required": [ 24143 "icon" 24144 ], 24145 "additionalProperties": false 24146 }, 24147 24148 _timeDataSchema = { 24149 "$schema": "http://json-schema.org/draft-04/schema#", 24150 "type": "object", 24151 "properties": { 24152 "displayTimeoutInSecs": { 24153 "type": "integer", 24154 "oneOf": [ 24155 { 24156 "minimum": 3, 24157 "maximum": 3600 24158 }, 24159 { 24160 "enum": [-1] /* displayTimeoutInSecs can be in range 3 and 3600, or -1. -1 indicates there is no upper limit for timer */ 24161 } 24162 ] 24163 }, 24164 "display": { 24165 "type": "boolean" 24166 }, 24167 "counterType": { 24168 "type": "string", 24169 "enum": ["COUNT_UP", "COUNT_DOWN"] 24170 } 24171 }, 24172 "oneOf": [{ 24173 "properties": { 24174 "display": { 24175 "type": "boolean", 24176 "enum": [ 24177 false 24178 ] 24179 } 24180 }, 24181 "required": [ 24182 "displayTimeoutInSecs", 24183 "display" 24184 ], 24185 "not": { 24186 "required": ["counterType"] 24187 }, 24188 }, 24189 { 24190 "properties": { 24191 "display": { 24192 "type": "boolean", 24193 "enum": [ 24194 true 24195 ] 24196 } 24197 }, 24198 "required": [ 24199 "displayTimeoutInSecs", 24200 "display", 24201 "counterType" 24202 ] 24203 } 24204 ], 24205 "additionalProperties": false 24206 }, 24207 24208 _actionDataSchema = { 24209 "$schema": "http://json-schema.org/draft-04/schema#", 24210 "type": "object", 24211 "properties": { 24212 "keepMaximised": { 24213 "type": "boolean" 24214 }, 24215 "dismissible": { 24216 "type": "boolean" 24217 }, 24218 "clientIdentifier": { 24219 "type": "string" 24220 }, 24221 "requiredActionText": { 24222 "type": "string" 24223 }, 24224 "buttons": { 24225 "type": "array", 24226 "minItems": 1, 24227 "maxItems": 2, 24228 "uniqueItems": true, 24229 "items": [{ 24230 "type": "object", 24231 "properties": { 24232 "id": { 24233 "type": "string" 24234 }, 24235 "label": { 24236 "type": "string" 24237 }, 24238 "type": { 24239 "type": "string" 24240 }, 24241 "hoverText": { 24242 "type": "string" 24243 }, 24244 "confirmButtons": { 24245 "type": "array", 24246 "uniqueItems": true, 24247 "items": [{ 24248 "type": "object", 24249 "properties": { 24250 "id": { 24251 "type": "string" 24252 }, 24253 "label": { 24254 "type": "string" 24255 }, 24256 "hoverText": { 24257 "type": "string" 24258 } 24259 }, 24260 "required": [ 24261 "id", 24262 "label" 24263 ] 24264 }] 24265 } 24266 }, 24267 "required": [ 24268 "id", 24269 "label", 24270 "type" 24271 ] 24272 }] 24273 } 24274 }, 24275 "oneOf": [{ 24276 "additionalProperties": false, 24277 "properties": { 24278 "requiredActionText": { 24279 "type": "string" 24280 }, 24281 "clientIdentifier": { 24282 "type": "string" 24283 }, 24284 "dismissible": { 24285 "type": "boolean", 24286 "enum": [ 24287 false 24288 ] 24289 }, 24290 "keepMaximised": { 24291 "type": "boolean" 24292 }, 24293 "buttons": { 24294 "type": "array" 24295 } 24296 } 24297 }, 24298 { 24299 "additionalProperties": false, 24300 "properties": { 24301 "requiredActionText": { 24302 "type": "string" 24303 }, 24304 "clientIdentifier": { 24305 "type": "string" 24306 }, 24307 "dismissible": { 24308 "type": "boolean", 24309 "enum": [ 24310 true 24311 ] 24312 }, 24313 "keepMaximised": { 24314 "type": "boolean" 24315 } 24316 } 24317 } 24318 ], 24319 "required": [ 24320 "dismissible" 24321 ], 24322 "additionalProperties": false 24323 }, 24324 24325 _headerDataSchema = { 24326 "$schema": "http://json-schema.org/draft-04/schema#", 24327 "type": "object", 24328 "properties": { 24329 "maxHeaderTitle": { 24330 "type": "string" 24331 }, 24332 "minHeaderTitle": { 24333 "type": "string" 24334 } 24335 }, 24336 "additionalProperties": false 24337 }; 24338 24339 return { 24340 24341 /** 24342 * @class 24343 * Finesse Voice component and Gadget(s) hosting digital services require 24344 * the {@link finesse.containerservices.PopoverService} to display a popup 24345 * for incoming call and chat events. <BR> 24346 * This API provides the schema that is used in {@link finesse.containerservices.PopoverService} 24347 * for managing various operations in the popup. 24348 * 24349 * This schema has been defined as per http://json-schema.org/ 24350 * <style> 24351 * .schemaTable tr:nth-child(even) { background-color:#EEEEEE; } 24352 * .schemaTable th { background-color: #999999; } 24353 * .schemaTable th, td { border: none; } 24354 * .pad30 {padding-left: 30px;} 24355 * .pad60 {padding-left: 60px;} 24356 * .inset { 24357 * padding: 5px; 24358 * border-style: inset; 24359 * background-color: #DDDDDD; 24360 * width: auto; 24361 * text-shadow: 2px 2px 3px rgba(255,255,255,0.5); 24362 * font: 12px arial, sans-serif; 24363 * color:rebeccapurple; 24364 * } 24365 * </style> 24366 * 24367 * @constructs 24368 */ 24369 _fakeConstuctor: function () { 24370 /* This is here so we can document init() as a method rather than as a constructor. */ 24371 }, 24372 24373 /** 24374 * 24375 * @example 24376 * <BR><BR><b>Example JSON for <i>BannerData</i>:</b> 24377 * <pre class='inset'> 24378 { 24379 "icon": { // Mandatory 24380 "type": "collab-icon", 24381 "value": "chat" 24382 }, 24383 "content": [ // Optional. first 6 name/value pairs is shown in popover 24384 { 24385 "name": "Customer Name", 24386 "value": "Michael Littlefoot" 24387 }, 24388 { 24389 "name": "Phone Number", 24390 "value": "+1-408-567-789" 24391 }, 24392 { 24393 "name": "Account Number", 24394 "value": "23874567923" 24395 }, 24396 { 24397 "name": "Issue", // For the below one, tool tip is displayed 24398 "value": "a very long text. a very long text.a very long text.a very long text.a very long text." 24399 } 24400 ] 24401 "headerContent" : { 24402 "maxHeaderTitle" : "Popover maximised title", 24403 "minHeaderTitle" : "Popover minimized title" 24404 } 24405 } 24406 * </pre> 24407 * 24408 * @returns 24409 * Schema for the validation of the below JSON definition 24410 * <table class="schemaTable"> 24411 * <thead> 24412 * <tr><th>Key</th><th>Type</th><th>Example</th><th>Description</th></tr> 24413 * </thead> 24414 * <tbody> 24415 * <tr><td>icon</td><td colspan='2'>Object</td><td>A icon that should be displayed in the Popover. It should be defined as below:</td></tr> 24416 * <tr><td class='pad30'>type</td><td>Enum</td><td>collab-icon</td> 24417 * <td>Takes either <i>collab-icon</i> or <i>url</i> as a value. 24418 * <BR><i>collab-icon</i> applies a <a href="finesse.digital.ChannelSchema.html#cdIcons">stock icon</a> 24419 * <BR><i>url</i> applies a custom icon supplied by gadget. The icon could be located as part of gadget files in Finesse.</td></tr> 24420 * <tr><td class='pad30'>value</td><td>String</td><td>Chat</td><td>The <a href="finesse.digital.ChannelSchema.html#cdIcons">name</a> of the stock icon 24421 * or the url of the custom icon</td></tr> 24422 * <tr><td>content [Optional]</td><td colspan='2'>Array</td><td>First six name/value pairs is shown in popover. A single property should be defined as below:</td></tr> 24423 * <tr><td class='pad30'>name</td><td>String</td><td>Customer Name</td><td>The property name that is displayed on the left</td></tr> 24424 * <tr><td class='pad30'>value</td><td>String</td><td>Michael Littlefoot</td><td>The corresponding property value that is displayed on the right. 24425 * <BR>Note: For long property values, a tooltip is displayed.</td></tr> 24426 * <tr><td>headerContent</td><td colspan='2'>Object</td><td>The title of popover when it is shown or minimized. It should be defined as below</td></tr> 24427 * <tr><td class='pad30'>maxHeaderTitle</td><td>String</td><td>Chat from firstName lastName</td><td>Popover title when it is not minimized</td></tr> 24428 * <tr><td class='pad30'>minHeaderTitle</td><td>String</td><td>firstName</td><td>Popover title when it is minimized</td></tr> 24429 * 24430 * </tbody> 24431 * </table> 24432 * 24433 */ 24434 getBannerDataSchema: function () { 24435 return _bannerDataSchema; 24436 }, 24437 24438 /** 24439 * 24440 * @example 24441 * <BR><BR><b>Example JSON for <i>TimerData</i>:</b> 24442 * <pre class='inset'> 24443 { 24444 "displayTimeoutInSecs": 30, // mandatory. minimum is 3 and maximum is 3600. -1 indicates no upper limit 24445 "display": true, // false means no displayable UI for timer 24446 "counterType": COUNT_UP or COUNT_DOWN, 24447 } 24448 * </pre> 24449 * @returns 24450 * <table class="schemaTable"> 24451 * <thead> 24452 * <tr><th>Key</th><th>Type</th><th>Example</th><th>Description</th></tr> 24453 * </thead> 24454 * <tbody> 24455 * <tr><td>displayTimeoutInSecs [Mandatory]</td><td>Integer</td><td>30</td><td>Minimum is 3 and maximum is 3600. -1 indicates no upper limit</td></tr> 24456 * <tr><td>display</td><td>boolean</td><td>true</td><td>false indicates not to display any timer </td></tr> 24457 * <tr><td>counterType</td><td>enum</td><td>COUNT_UP</td><td>Takes value COUNT_UP or COUNT_DOWN. For scenarios like how long the chat has been active would require a COUNT_UP timer. 24458 * On the other hand before chat is autoclosed for a agent RONA, it would be apt to use a COUNT_DOWN timer. </td></tr> 24459 * 24460 * </tbody> 24461 * </table> 24462 */ 24463 getTimerDataSchema: function () { 24464 return _timeDataSchema; 24465 }, 24466 24467 24468 /** 24469 * 24470 * 24471 * @example 24472 * <BR><BR><b>Example JSON for <i>ActionData</i>:</b> 24473 * <pre class='inset'> 24474 { 24475 "dismissible": true, // or "false" 24476 "clientIdentifier" : 'popup1', // A string to uniquely identify a specific popover 24477 "requiredActionText": "Please answer the call from your phone", 24478 "buttons": // Optional. Max 2 24479 [ 24480 { 24481 "id": "No", 24482 "label": "Decline", 24483 "type": "Decline", 24484 "hoverText": "", 24485 "confirmButtons": [ // confirmButtons is an optional property in actionData 24486 { 24487 "id": "Yes", 24488 "label": "Reject - Return to campaign", 24489 "hoverText": "" 24490 }, 24491 { 24492 "id": "No", 24493 "label": "Close - Remove from campaign", 24494 "hoverText": "" 24495 } 24496 ] 24497 } 24498 ] 24499 } 24500 * </pre> 24501 * 24502 * @returns 24503 * Schema for the validation of the below JSON definition 24504 * 24505 * <table class="schemaTable"> 24506 * <thead> 24507 * <tr><th>Key</th><th>Type</th><th>Example</th><th>Description</th></tr> 24508 * </thead> 24509 * <tbody> 24510 * <tr><td>dismissible</td><td>boolean</td><td>true</td><td>True if button definition is optional. Flase if button definition is mandatory.</td></tr> 24511 * <tr><td>clientIdentifier</td><td>string</td><td>popover1</td><td>A unique identifier across all popovers. 24512 * This is used in the callback for popover events</td></tr> 24513 * <tr><td>requiredActionText [Optional]</td><td>String</td><td>Please answer the call from your phone</td><td>This text is at the bottom of the popover to inform what action the user has to take.</td></tr> 24514 * <tr><td>buttons [Optional]</td><td colspan='2'>Array</td><td>Buttons displayed in the popover. Maximum 2 buttons. It can be defined as below:</td></tr> 24515 * <tr><td class='pad30'>id</td><td>string</td><td>ok1</td><td> A unique ID that represents a button </td></tr> 24516 * <tr><td class='pad30'>label</td><td>string</td><td>Accept</td><td>The display text of the action button</td></tr> 24517 * <tr><td class='pad30'>type</td><td>enum</td><td>Affirm</td><td>Affirm for green button. Decline for red button</td></tr> 24518 * <tr><td class='pad30'>hoverText [Optional]</td><td>String</td><td>Click here to accept chat</td><td>The tooltip that is displayed on mouse hover</td></tr> 24519 * <tr><td class='pad30'>confirmButtons [Optional]</td><td colspan='2'>Object</td><td>An additional confirmation message with the buttons to be displayed when the user clicks on the above action button. It can be defined as below: </td></tr> 24520 * <tr><td class='pad60'>id</td><td>string</td><td>id</td><td>Id of the confirmation button</td></tr> 24521 * <tr><td class='pad60'>label</td><td>string</td><td>Reject - Return to campaign</td><td>Label to displayed on the button</td></tr> 24522 * <tr><td class='pad60'>hoverText</td><td>string</td><td>Click here to reject<td>Tooltip message on the button</td></tr> 24523 * </tbody> 24524 * </table> 24525 */ 24526 getActionDataSchema: function () { 24527 return _actionDataSchema; 24528 } 24529 }; 24530 }()); 24531 24532 window.finesse = window.finesse || {}; 24533 window.finesse.containerservices = window.finesse.containerservices || {}; 24534 window.finesse.containerservices.PopoverSchema = PopoverSchema; 24535 24536 // CommonJS 24537 if (typeof module === "object" && module.exports) { 24538 module.exports = PopoverSchema; 24539 } 24540 24541 return PopoverSchema; 24542 }); 24543 /** 24544 * PopoverService provides API consists of methods that would allow a gadget to request Finesse to show popovers. 24545 * 24546 * @example 24547 * containerServices = finesse.containerservices.ContainerServices.init(); 24548 * popoverService = finesse.containerservices.PopoverService.init(containerServices); 24549 * popoverService.showPopover(popoverId, popoverData, actionHandler); 24550 */ 24551 /** The following comment is to prevent jslint errors about using variables before they are defined. */ 24552 /*global window:true, define:true, finesse:true*/ 24553 /*jslint nomen: true, unparam: true, sloppy: true, white: true */ 24554 define('containerservices/PopoverService',['require','exports','module','../utilities/Utilities','../clientservices/ClientServices','./PopoverSchema','../utilities/JsonValidator'],function (require, exports, module) { 24555 24556 var PopoverService = (function () { /** @lends finesse.containerservices.PopoverService.prototype */ 24557 24558 const Utilities = require('../utilities/Utilities'); 24559 const ClientService = require('../clientservices/ClientServices'); 24560 const SchemaMgr = require('./PopoverSchema'); 24561 const SchemaValidator = require('../utilities/JsonValidator'); 24562 24563 var 24564 /** 24565 * Open Ajax topic for the API to publish popover requests to Finesse Popover Component. 24566 * @private 24567 */ 24568 _popoverTopic, 24569 24570 /** 24571 * References to gadget's ContainerService 24572 * @private 24573 */ 24574 _containerService, 24575 24576 /** 24577 * Whether the PopoverService have been initialized or not. 24578 * @private 24579 */ 24580 _inited = false, 24581 24582 /* 24583 * References to ClientServices logger 24584 * @private 24585 */ 24586 _logger = { 24587 log: ClientService.log 24588 }, 24589 24590 /** 24591 * Popover Action enum. 24592 * @private 24593 */ 24594 POPOVER_ACTION = Object.freeze({ 24595 SHOW: 'show', 24596 DISMISS: 'dismiss', 24597 UPDATE: 'update' 24598 }), 24599 24600 /** 24601 * Ensure that PopoverService have been inited. 24602 * @private 24603 */ 24604 _isInited = function () { 24605 if (!_inited) { 24606 throw new Error("PopoverService needs to be inited."); 24607 } 24608 return _inited; 24609 }, 24610 24611 /** 24612 * Dynamic registry object, which keeps a map of popover id and its action handler function reference. 24613 * This handler will be invoked on actions on the popover. 24614 * @private 24615 */ 24616 _popoverRegistry = (function () { 24617 var _map = {}, 24618 24619 _add = function (id, handler) { 24620 _map[id] = { 24621 handler: handler 24622 }; 24623 }, 24624 24625 _clear = function (id) { 24626 if (_map[id]) { 24627 delete _map[id]; 24628 } 24629 }; 24630 24631 return { 24632 add: _add, 24633 clear: _clear, 24634 isExist: function (id) { 24635 return _map[id] !== undefined; 24636 }, 24637 sendEvent: function (id, data) { 24638 if (_map[id]) { 24639 _map[id].handler(data); 24640 } 24641 } 24642 }; 24643 }()), 24644 24645 /** 24646 * Utility function to make a subscription to a particular topic. Only one 24647 * callback function is registered to a particular topic at any time. 24648 * 24649 * @param {String} topic 24650 * The full topic name. The topic name should follow the OpenAjax 24651 * convention using dot notation (ex: finesse.api.User.1000). 24652 * @param {Function} handler 24653 * The function that should be invoked with the data when an event 24654 * is delivered to the specific topic. 24655 * @returns {Boolean} 24656 * True if the subscription was made successfully and the callback was 24657 * been registered. False if the subscription already exist, the 24658 * callback was not overwritten. 24659 * @private 24660 */ 24661 _subscribe = function (topic, handler) { 24662 try { 24663 return _containerService.addHandler(topic, handler); 24664 } catch (error) { 24665 _logger.log('PopoverService: Error while subscribing to open ajax topic : ' + topic + ', Exception:' + error.toString()); 24666 throw new Error('PopoverService: Unable to subscribe the popover topic with Open Ajax Hub : ' + error); 24667 } 24668 }, 24669 24670 /** 24671 * Gets the id of the gadget. 24672 * @returns {number} the id of the gadget 24673 * @private 24674 */ 24675 _getMyGadgetId = function () { 24676 var gadgetId = _containerService.getMyGadgetId(); 24677 return gadgetId ? gadgetId : ''; 24678 }, 24679 24680 /** 24681 * Function which processes the messages from Finesse Popover Component. 24682 * The messages received mainly for below cases, 24683 * <ul> 24684 * <li> User Interaction with popover. 24685 * <li> Timeout. 24686 * <ul> 24687 * The registered callbacks are invoked based on the data. 24688 * 24689 * @param {String} response 24690 * The actual message sent by Finesse Popover Component via open ajax hub. 24691 * @private 24692 */ 24693 _processResponseFromPopover = function (response) { 24694 _logger.log('PopoverService: Received Message from PopoverComp: ' + JSON.stringify(response)); 24695 if (response) { 24696 try { 24697 // if the received data from topic is in text format, then parse it to JSON format 24698 response = typeof response === 'string' ? JSON.parse(response) : response; 24699 } catch (error) { 24700 _logger.log('PopoverService: Error while parsing the popover message: ' + error.toString()); 24701 return; 24702 } 24703 24704 // construct the response for gadget 24705 var data = {}; 24706 data.popoverId = response.id; 24707 data.source = response.source; 24708 24709 _logger.log('PopoverService: Calling gadget action handler'); 24710 24711 // invoke the gadget's action handler to process the action taken in popover 24712 _popoverRegistry.sendEvent(data.popoverId, data); 24713 24714 // clear the popover cached data, no more needed 24715 _popoverRegistry.clear(data.popoverId); 24716 } 24717 }, 24718 24719 /** 24720 * Validate the gadget passed JSON structure against the schema definition. 24721 * 24722 * @param {Object} bannerData 24723 * Banner data JSON object as per the spec. 24724 * @param {Object} timerData 24725 * Timer data JSON object as per the spec. 24726 * @param {Object} actionData 24727 * Action data JSON object as per the spec. 24728 * @throws {Error} if the data is not as per defined format. 24729 * @private 24730 */ 24731 _validateData = function (bannerData, timerData, actionData) { 24732 var bannerDataResult = SchemaValidator.validateJson(bannerData, SchemaMgr.getBannerDataSchema()); 24733 /* if result.valid is false, then additional details about failure are contained in 24734 result.error which contains json like below: 24735 24736 { 24737 "code": 0, 24738 "message": "Invalid type: string", 24739 "dataPath": "/intKey", 24740 "schemaPath": "/properties/intKey/type" 24741 } 24742 */ 24743 if (!bannerDataResult.valid) { 24744 _logger.log("PopoverService: Banner data validation bannerDataResult : " + JSON.stringify(bannerDataResult)); 24745 throw new Error("PopoverService: Banner data structure is not in expected format. Refer finesse client logs for more details."); 24746 } 24747 var timerDataResult = SchemaValidator.validateJson(timerData, SchemaMgr.getTimerDataSchema()); 24748 if (!timerDataResult.valid) { 24749 _logger.log("PopoverService: Timer data validation timerDataResult : " + JSON.stringify(timerDataResult)); 24750 throw new Error("PopoverService: Timer data structure is not in expected format. Refer finesse client logs for more details."); 24751 } 24752 var actionDataResult = SchemaValidator.validateJson(actionData, SchemaMgr.getActionDataSchema()); 24753 if (!actionDataResult.valid) { 24754 _logger.log("PopoverService: Action data validation actionDataResult : " + JSON.stringify(actionDataResult)); 24755 throw new Error("PopoverService: Action data structure is not in expected format. Refer finesse client logs for more details."); 24756 } 24757 }, 24758 24759 /** 24760 * Parse the data for JSON object. 24761 * 24762 * @param {String} data 24763 * popover data structure. 24764 * @throws {Error} if the JSON parsing fails. 24765 * @private 24766 */ 24767 _getJSONObject = function (data) { 24768 if (data) { 24769 try { 24770 // parse the data as user may pass in string or object format 24771 data = typeof data === 'string' ? JSON.parse(data) : data; 24772 } catch (err) { 24773 throw new Error("PopoverService: Data Parsing Failed. Popover data not in expected format. Error: " + err.toString()); 24774 } 24775 return data; 24776 } 24777 }, 24778 24779 /** 24780 * Requests Finesse to show notification popover with the given payload. The user interaction or timeout of the popover 24781 * will be notified to gadget through the registered action handler. 24782 * 24783 * @param {Boolean} isExistingPopover 24784 * If Update or new Show operation . 24785 * @param {String} popoverId 24786 * Popover Id 24787 * @param {Object} payload 24788 * Action data JSON object as per the spec. 24789 * @param {Function} actionHandler 24790 * Callback function invoked when the user interacts with the popover or popover times out. 24791 * @throws Error 24792 * Throws error when the passed in popoverData is not as per defined format. 24793 */ 24794 _display = function (isExistingPopover, popoverId, payload, actionHandler) { 24795 // subscribe this popover topic with open ajax hub for processing the response from popover component 24796 _subscribe(payload.topic, _processResponseFromPopover); 24797 24798 // cache the gadget action handler, so that it will be used when processing the popover response 24799 if (!isExistingPopover) _popoverRegistry.add(popoverId, actionHandler); 24800 24801 _logger.log('PopoverService: Sending popover show request to Finesse Popover Component: ' + JSON.stringify(payload)); 24802 24803 // publish the popover show request to popover UI component 24804 _containerService.publish(_popoverTopic, payload); 24805 }, 24806 24807 /** 24808 * Requests Finesse to dismiss the notification popover. 24809 * 24810 * @param {String} popoverId 24811 * Popover id which was returned from showPopover call 24812 * @throws Error 24813 * Throws error if the service is not yet initialized. 24814 */ 24815 _dismiss = function (popoverId) { 24816 // check whether the service is inited 24817 _isInited(); 24818 24819 _logger.log('PopoverService: Received popover dismiss request for popover id ' + popoverId); 24820 24821 if (popoverId && _popoverRegistry.isExist(popoverId)) { 24822 // construct the payload required for the popover component 24823 var payload = { 24824 id: popoverId, 24825 action: POPOVER_ACTION.DISMISS 24826 }; 24827 24828 // publish the popover dismiss request to popover UI component 24829 _containerService.publish(_popoverTopic, payload); 24830 24831 // clear the popover cached data, no more needed 24832 _popoverRegistry.clear(popoverId); 24833 } 24834 }; 24835 24836 return { 24837 /** 24838 * @class 24839 * Finesse Voice component and Gadget(s) hosting digital services require 24840 * the {@link finesse.containerservices.PopoverService} to display a popup 24841 * for incoming call and chat events. <BR> 24842 * This API provides makes use of the schema as defined in {@link finesse.containerservices.PopoverSchema} 24843 * and provides various operations for managing the popup. 24844 * 24845 * <style> 24846 * .popoverTable tr:nth-child(even) { background-color:#EEEEEE; } 24847 * .popoverTable th { background-color: #999999; } 24848 * .popoverTable th, td { border: none; } 24849 * .pad30 {padding-left: 30px;} 24850 * .inset { 24851 * padding: 5px; 24852 * border-style: inset; 24853 * background-color: #DDDDDD; 24854 * width: auto; 24855 * text-shadow: 2px 2px 3px rgba(255,255,255,0.5); 24856 * font: 12px arial, sans-serif; 24857 * color:rebeccapurple; 24858 * } 24859 * 24860 * 24861 * </style> 24862 * 24863 * @example 24864 * <BR><BR><b>Example demonstrating initialization</b> 24865 * 24866 <pre class='inset'> 24867 containerServices = finesse.containerservices.ContainerServices.init(); 24868 popoverService = finesse.containerservices.PopoverService.init(containerServices); 24869 popoverService.showPopover(popoverId, popoverData, actionHandler); 24870 </pre> 24871 * Details of the parameters are described in this API spec below. 24872 * 24873 * @constructs 24874 */ 24875 _fakeConstuctor: function () { 24876 /* This is here so we can document init() as a method rather than as a constructor. */ 24877 }, 24878 24879 /** 24880 * Initialize the PopoverService for use in gadget. 24881 * See example in <i> Class Detail </i> for initialization. 24882 * 24883 * @param {finesse.containerservices.ContainerServices} ContainerService instance. 24884 * @returns {finesse.containerservices.PopoverService} instance. 24885 */ 24886 init: function (containerService) { 24887 if (!_inited) { 24888 if (!containerService) { 24889 throw new Error("PopoverService: Invalid ContainerService reference."); 24890 } 24891 _containerService = containerService; 24892 _popoverTopic = containerService.Topics.FINEXT_POPOVER_EVENT; 24893 window.finesse.clientservices.ClientServices.setLogger(window.finesse.cslogger.ClientLogger); 24894 _inited = true; 24895 } 24896 return this; 24897 }, 24898 24899 /** 24900 * Combines multiple parameters and generates a single payload for use by the popover service. 24901 * 24902 * @param {Boolean} isExistingPopover 24903 * True if this popover already exists. False if not. 24904 * @param {String} popoverId 24905 * The unique identifier for the popover. This id was returned from showPopover call. 24906 * @param {Object} bannerData 24907 * Refer {@link finesse.containerservices.PopoverSchema#getBannerDataSchema} for description about this JSON payload 24908 * @param {Object} timerData 24909 * Refer {@link finesse.containerservices.PopoverSchema#getTimerDataSchema} for description about this JSON payload 24910 * @param {Object} actionData 24911 * Refer {@link finesse.containerservices.PopoverSchema#getActionDataSchema} for description about this JSON payload 24912 * @throws 24913 * Throws error when the passed in popoverData is not as per defined format. 24914 */ 24915 generatePayload: function (isExistingPopover, popoverId, bannerData, timerData, actionData) { 24916 // parse the data 24917 bannerData = _getJSONObject(bannerData); 24918 timerData = _getJSONObject(timerData); 24919 actionData = _getJSONObject(actionData); 24920 var topic = _popoverTopic + '.' + popoverId; 24921 24922 // validate the data 24923 _validateData(bannerData, timerData, actionData); 24924 24925 var action = isExistingPopover ? POPOVER_ACTION.UPDATE : POPOVER_ACTION.SHOW; 24926 // construct the payload required for the popover component 24927 var payload = { 24928 id: popoverId, 24929 topic: topic, 24930 action: action, 24931 data: { 24932 bannerData: bannerData, 24933 timerData: timerData 24934 } 24935 }; 24936 24937 if (actionData) { 24938 payload.data.actionData = actionData; 24939 } 24940 24941 return payload; 24942 }, 24943 24944 /** 24945 * 24946 * Display a popover with the given data. The user interaction or timeout of the popover 24947 * will be notified to gadget through the registered action handler. 24948 * 24949 * @param {Object} bannerData 24950 * Refer {@link finesse.containerservices.PopoverSchema#getBannerDataSchema} for description about this JSON payload 24951 * @param {Object} timerData 24952 * Refer {@link finesse.containerservices.PopoverSchema#getTimerDataSchema} for description about this JSON payload 24953 * @param {Object} actionData 24954 * Refer {@link finesse.containerservices.PopoverSchema#getActionDataSchema} for description about this JSON payload 24955 * @param {Function} actionHandler 24956 * This is a Handler that gets called for events due to user interaction. Following are the params of this function. 24957 * <table class="popoverTable"> 24958 * <thead><tr><th>Parameter</th><th>Description</th></tr></thead> 24959 * <tbody> 24960 * <tr><td>popoverId</td><td>A unique popover id that was assigned to the new popover.</td></tr> 24961 * <tr><td>source</td><td>The id of the source which generated the event. Examples are 'btn_[id]_click' or 'dismissed' or 'timeout'</td></tr> 24962 * </table> 24963 * 24964 * @returns {String} 24965 * Generated Popover-id, can be used for subsequent interaction with the service. 24966 * @throws 24967 * Throws error when the passed in popoverData is not as per defined format. 24968 */ 24969 showPopover: function (bannerData, timerData, actionData, actionHandler) { 24970 // check whether the service is inited 24971 _isInited(); 24972 24973 var isExistingPopover = false; 24974 24975 // construct a unique id for the popover 24976 var popoverId = Utilities.generateUUID(), 24977 gadgetId = _getMyGadgetId(); 24978 24979 _logger.log('PopoverService: Received new popover show request from gadgetId ' + gadgetId); 24980 24981 var payload = this.generatePayload(isExistingPopover, popoverId, bannerData, timerData, actionData); 24982 24983 // check for action handler 24984 if (typeof actionHandler !== 'function') { 24985 throw new Error('PopoverService: Action handler should be a function'); 24986 } 24987 24988 _display(isExistingPopover, popoverId, payload, actionHandler); 24989 24990 return popoverId; 24991 }, 24992 24993 /** 24994 * 24995 * Modify an active popover's displayed content. 24996 * 24997 * @param {String} popoverId 24998 * A unique popover id that was returned in {@link #showPopover}. 24999 * @param {Object} bannerData 25000 * Refer {@link finesse.containerservices.PopoverSchema#getBannerDataSchema} for description about this JSON payload 25001 * @param {Object} timerData 25002 * Refer {@link finesse.containerservices.PopoverSchema#getTimerDataSchema} for description about this JSON payload 25003 * @param {Object} actionData 25004 * Refer {@link finesse.containerservices.PopoverSchema#getActionDataSchema} for description about this JSON payload 25005 * @param {Function} actionHandler 25006 * Refer The callback function requirements as described in {@link #showPopover} 25007 * @throws 25008 * Throws error when the passed in popoverData is not as per defined format. 25009 */ 25010 updatePopover: function (popoverId, bannerData, timerData, actionData) { 25011 // check whether the service is inited 25012 _isInited(); 25013 25014 var isExistingPopover = true; 25015 25016 var gadgetId = _getMyGadgetId(); 25017 25018 _logger.log('PopoverService: Received an update popover request from gadgetId ' + gadgetId); 25019 25020 var payload = this.generatePayload(isExistingPopover, popoverId, bannerData, timerData, actionData); 25021 25022 _display(isExistingPopover, popoverId, payload, null); 25023 }, 25024 25025 /** 25026 * Dismisses the notification popover. 25027 * 25028 * @param {String} popoverId 25029 * The unique identifier for the popover. This id was returned from showPopover call. 25030 * @throws 25031 * Throws error if the service is not yet initialized. 25032 */ 25033 dismissPopover: function (popoverId) { 25034 _dismiss(popoverId); 25035 }, 25036 25037 /** 25038 * ENUM: Determines how the time in popover should update. 25039 * [COUNT_UP, COUNT_DOWN] 25040 */ 25041 COUNTER_TYPE: Object.freeze({ 25042 COUNT_UP: 'COUNT_UP', 25043 COUNT_DOWN: 'COUNT_DOWN' 25044 }) 25045 }; 25046 }()); 25047 25048 window.finesse = window.finesse || {}; 25049 window.finesse.containerservices = window.finesse.containerservices || {}; 25050 window.finesse.containerservices.PopoverService = PopoverService; 25051 25052 // CommonJS 25053 if (typeof module === "object" && module.exports) { 25054 module.exports = PopoverService; 25055 } 25056 25057 return PopoverService; 25058 }); 25059 /** 25060 * UCCX Email/Chat uses a JSON payload for to trigger workflow. 25061 * That payload has to conform to this schema. 25062 * This schema has been defined as per http://json-schema.org/ 25063 * 25064 * @see utilities.JsonValidator 25065 */ 25066 /** The following comment is to prevent jslint errors about using variables before they are defined. */ 25067 /*global window:true, define:true*/ 25068 /*jslint nomen: true, unparam: true, sloppy: true, white: true */ 25069 define('workflow/WorkflowSchema',['require','exports','module'],function (require, exports, module) { 25070 25071 var WorkflowSchema = (function () { 25072 var _workflowParamSchema = { 25073 "$schema": "http://json-schema.org/draft-06/schema#", 25074 "additionalProperties": false, 25075 "properties": { 25076 "mediaType": { 25077 "type": "string", 25078 "title": "media type , eg. email, chat etc." 25079 }, 25080 "dialogId": { 25081 "type": "string", 25082 "title": "The Workflow requestId. this is will be used for logging purpose " 25083 }, 25084 "state": { 25085 "type": "string", 25086 "title": "The When to performa ction Schema e.g. EMAIL_PRESENTED, EMAIL_READ etc" 25087 }, 25088 "taskVariables": { 25089 "type": "object" 25090 } 25091 }, 25092 "required": [ 25093 "dialogId", 25094 "state", 25095 "mediaType" 25096 ] 25097 }; 25098 25099 return { 25100 _fakeConstuctor: function () { 25101 /* This is here so we can document init() as a method rather than as a constructor. */ 25102 }, 25103 25104 /** 25105 * Returns schema that is applicable for workflow param JSON Structure. 25106 */ 25107 getWorkflowParamSchema: function () { 25108 return _workflowParamSchema; 25109 } 25110 }; 25111 }()); 25112 25113 window.finesse = window.finesse || {}; 25114 window.finesse.workflow = window.finesse.workflow || {}; 25115 window.finesse.workflow.WorkflowSchema = WorkflowSchema; 25116 25117 // CommonJS 25118 if (typeof module === "object" && module.exports) { 25119 module.exports = WorkflowSchema; 25120 } 25121 25122 return WorkflowSchema; 25123 }); 25124 25125 /** 25126 * WorkflowService provides API consists of methods that would allow a gadgets to submit workflow task. 25127 * 25128 * @example 25129 * var containerServices = finesse.containerservices.ContainerServices.init(); 25130 * workflowService = finesse.workflow.WorkflowService.init(containerServices); 25131 * var payload = { 25132 "dialogId": "email1", 25133 "mediaType": "email", 25134 "state": "EMAIL_READ", 25135 "taskVariables": { 25136 "from": "mme@cisco.com", 25137 "cc": "yyy@cisco.com" 25138 } 25139 } 25140 * workflowService.submitTask(payload); 25141 */ 25142 /** @private */ 25143 define('workflow/WorkflowService',['require','exports','module','./WorkflowSchema','../utilities/JsonValidator','../clientservices/ClientServices'],function (require, exports, module) { 25144 /** @lends finesse.workflow.WorkflowService.prototype */ 25145 25146 var WorkflowService = (function () { 25147 var WorkflowSchema = require('./WorkflowSchema'), 25148 SchemaValidator = require('../utilities/JsonValidator'), 25149 ClientService = require('../clientservices/ClientServices'), 25150 /** 25151 * References to ContainerService 25152 * @private 25153 */ 25154 _containerService, 25155 25156 /** 25157 * Whether the WorkflowService have been initialized or not. 25158 * @private 25159 */ 25160 _inited = false, 25161 25162 /** 25163 * References to ClientServices logger 25164 * @private 25165 */ 25166 _logger = { 25167 log: ClientService.log 25168 }, 25169 25170 /** 25171 * Ensure that WorkflowService have been initiated. 25172 * @private 25173 */ 25174 _isInitiated = function () { 25175 if (!_inited) { 25176 throw new Error("WorkflowService needs to be initiated."); 25177 } 25178 }, 25179 25180 /** 25181 * Gets the id of the gadget. 25182 * @returns {number} the id of the gadget 25183 * @private 25184 */ 25185 _getMyGadgetId = function () { 25186 var gadgetId = _containerService.getMyGadgetId(); 25187 return gadgetId || ''; 25188 }, 25189 25190 /** 25191 * Validate the workflow parameter payload structure.. 25192 * 25193 * @param {Object} payload 25194 * workflow parameter. 25195 * @throws {Error} if the payload is not in defined format. 25196 * @private 25197 */ 25198 _validateWorkflowParam = function (payload) { 25199 return SchemaValidator.validateJson(payload, WorkflowSchema.getWorkflowParamSchema()); 25200 }, 25201 25202 /** 25203 * Function which processes the the trigger workflow for digital channels. 25204 * 25205 * @param {Object} payload 25206 * The payload containing workflow submit parameter. 25207 * @private 25208 */ 25209 _processWorkflowRequest = function (payload) { 25210 var result = _validateWorkflowParam(payload), data; 25211 25212 if (!result.valid) { 25213 _logger.log("WorkflowService: Finesse workflow trigger API parameter Validation result : " + JSON.stringify(result)); 25214 throw new Error("workflow parameter structure is not in expected format. Refer finesse client logs for more details."); 25215 } 25216 25217 _logger.log("[WorkflowService] workflow task submitted for Gadget : " + _getMyGadgetId() + ' : Workflow dialogId: ' + payload.dialogId); 25218 data = {gadgetId: _getMyGadgetId(), payload: payload}; 25219 25220 _containerService.publish("finesse.workflow.digitalChannels", data); 25221 }; 25222 25223 return { 25224 25225 /** 25226 * Method to trigger workflow for digital channels. 25227 * 25228 * @example 25229 * var containerServices = finesse.containerservices.ContainerServices.init(); 25230 * workflowService = finesse.workflow.WorkflowService.init(containerServices); 25231 * var payload = { 25232 "dialogId": "email1", 25233 "mediaType": "email", 25234 "state": "EMAIL_READ", 25235 "taskVariables": { 25236 "from": "mme@cisco.com", 25237 "cc": "yyy@cisco.com" 25238 } 25239 } 25240 * workflowService.submitTask(payload); 25241 * 25242 * @param {Object} payload 25243 * 25244 */ 25245 submitTask: function (payload) { 25246 _isInitiated(); 25247 _processWorkflowRequest(payload); 25248 }, 25249 /** 25250 * @class 25251 * WorkflowService provides API consists of methods that would allow a gadgets to submit workflow task. 25252 * 25253 * @example 25254 * var containerServices = finesse.containerservices.ContainerServices.init(); 25255 * workflowService = finesse.workflow.WorkflowService.init(containerServices); 25256 * workflowService.submitTask(payload); 25257 * 25258 * @constructs 25259 */ 25260 _fakeConstuctor: function () { 25261 /* This is here so we can document init() as a method rather than as a constructor. */ 25262 }, 25263 25264 /** 25265 * 25266 * Initialize WorkflowService. 25267 * 25268 * @example 25269 * var containerServices = finesse.containerservices.ContainerServices.init(); 25270 * workflowService = finesse.workflow.WorkflowService.init(containerServices); 25271 * 25272 * @returns {finesse.workflow.WorkflowService} instance. 25273 * 25274 */ 25275 init: function (containerServices) { 25276 _logger.log("WorkflowService is getting initiated....."); 25277 if (!_inited) { 25278 if (!containerServices) { 25279 throw new Error("WorkflowService: Invalid ContainerService reference."); 25280 } 25281 _inited = true; 25282 _containerService = containerServices; 25283 window.finesse.clientservices.ClientServices.setLogger(window.finesse.cslogger.ClientLogger); 25284 } 25285 return this; 25286 } 25287 }; 25288 }()); 25289 25290 window.finesse = window.finesse || {}; 25291 window.finesse.workflow = window.finesse.workflow || {}; 25292 window.finesse.workflow.WorkflowService = WorkflowService; 25293 25294 // CommonJS 25295 if (typeof module === "object" && module.exports) { 25296 module.exports = WorkflowService; 25297 } 25298 25299 return WorkflowService; 25300 }); 25301 25302 25303 /** 25304 * JavaScript representation of the Finesse UserTeamMessages object for a Supervisor. 25305 * 25306 * @requires Class 25307 * @requires finesse.FinesseBase 25308 * @requires finesse.restservices.RestBase 25309 * @requires finesse.utilities.Utilities 25310 * @requires finesse.restservices.TeamMessage 25311 */ 25312 25313 /** The following comment is to prevent jslint errors about 25314 * using variables before they are defined. 25315 */ 25316 /*global Exception */ 25317 25318 /** @private */ 25319 define('restservices/UserTeamMessages',[ 25320 'restservices/RestCollectionBase', 25321 'restservices/RestBase', 25322 'utilities/Utilities', 25323 'restservices/TeamMessage', 25324 'restservices/TeamMessages' 25325 ], 25326 function (RestCollectionBase, RestBase,Utilities, TeamMessage, TeamMessages) { 25327 25328 var UserTeamMessages = TeamMessages.extend({ 25329 25330 /** 25331 * @class 25332 * JavaScript representation of a LayoutConfig object for a Team. Also exposes 25333 * methods to operate on the object against the server. 25334 * 25335 * @param {Object} options 25336 * An object with the following properties:<ul> 25337 * <li><b>id:</b> The id of the object being constructed</li> 25338 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 25339 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 25340 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 25341 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 25342 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 25343 * <li><b>status:</b> {Number} The HTTP status code returned</li> 25344 * <li><b>content:</b> {String} Raw string of response</li> 25345 * <li><b>object:</b> {Object} Parsed object of response</li> 25346 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 25347 * <li><b>errorType:</b> {String} Type of error that was caught</li> 25348 * <li><b>errorMessage:</b> {String} Message associated with error</li> 25349 * </ul></li> 25350 * </ul></li> 25351 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 25352 * @constructs 25353 **/ 25354 init: function (options) { 25355 var options = options || {}; 25356 options.parentObj = this; 25357 this._super(options); 25358 }, 25359 25360 /** 25361 * @private 25362 * Gets the REST class for the current object - this is the TeamMessages class. 25363 * @returns {Object} The LayoutConfigs class. 25364 */ 25365 getRestClass: function () { 25366 return TeamMessages; 25367 }, 25368 25369 getRestItemClass: function () { 25370 return TeamMessage; 25371 }, 25372 25373 getRestType: function () { 25374 return "TeamMessages"; 25375 }, 25376 25377 /** 25378 * @private 25379 * Override default to indicate that this object doesn't support making 25380 * requests. 25381 */ 25382 supportsRequests: false, 25383 25384 /** 25385 * @private 25386 * Override default to indicate that this object doesn't support subscriptions. 25387 */ 25388 supportsSubscriptions: false, 25389 25390 supportsRestItemSubscriptions: false, 25391 25392 getTeamMessages: function (createdBy, handlers) { 25393 25394 var self = this, contentBody, reasonCode, url; 25395 contentBody = {}; 25396 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 25397 handlers = handlers || {}; 25398 this.restRequest('/finesse/api/'+this.getRestType()+'?createdBy='+createdBy, { 25399 method: 'GET', 25400 success: function(rsp) { 25401 var teamMessage = rsp.object.teamMessages? rsp.object.teamMessages.TeamMessage: null 25402 handlers.success(teamMessage); 25403 }, 25404 error: function (rsp) { 25405 handlers.error(rsp); 25406 }, 25407 content: contentBody 25408 }); 25409 25410 return this; // Allow cascading 25411 }, 25412 25413 /** 25414 * 25415 */ 25416 deleteTeamMessage: function (messageId, handlers) { 25417 handlers = handlers || {}; 25418 this.restRequest(this.getRestItemBaseUrl() +'/'+messageId, { 25419 method: 'DELETE', 25420 success: handlers.success, 25421 error: handlers.error, 25422 content: undefined 25423 }); 25424 return this; // Allow cascading 25425 } 25426 25427 }); 25428 25429 window.finesse = window.finesse || {}; 25430 window.finesse.restservices = window.finesse.restservices || {}; 25431 window.finesse.restservices.UserTeamMessages = UserTeamMessages; 25432 25433 return UserTeamMessages; 25434 }); 25435 define('finesse',[ 25436 'restservices/Users', 25437 'restservices/Teams', 25438 'restservices/SystemInfo', 25439 'restservices/Media', 25440 'restservices/MediaDialogs', 25441 'restservices/DialogLogoutActions', 25442 'restservices/InterruptActions', 25443 'restservices/ReasonCodeLookup', 25444 'restservices/ChatConfig', 25445 'utilities/I18n', 25446 'utilities/Logger', 25447 'utilities/SaxParser', 25448 'utilities/BackSpaceHandler', 25449 'utilities/JsonValidator', 25450 'cslogger/ClientLogger', 25451 'cslogger/FinesseLogger', 25452 'containerservices/ContainerServices', 25453 'containerservices/FinesseToaster', 25454 'interfaces/RestObjectHandlers', 25455 'interfaces/RequestHandlers', 25456 'gadget/Config', 25457 'digital/ChannelSchema', 25458 'digital/ChannelService', 25459 'containerservices/PopoverService', 25460 'containerservices/PopoverSchema', 25461 'workflow/WorkflowSchema', 25462 'workflow/WorkflowService', 25463 'restservices/UserTeamMessages' 25464 ], 25465 function () { 25466 return window.finesse; 25467 }); 25468 25469 require(["finesse"]); 25470 return require('finesse'); })); 25471 25472 // Prevent other JS files from wiping out window.finesse from the namespace 25473 var finesse = window.finesse;