1 /** 2 * Cisco Finesse - JavaScript Library 3 * Version 11.6.1) 4 * Cisco Systems, Inc. 5 * http://www.cisco.com/ 6 * 7 * Portions created or assigned to Cisco Systems, Inc. are 8 * Copyright (c) 2017 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 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('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('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 "utilities/SaxParser", 1242 "iso8601", 1243 "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 } 1427 } // Otherwise, don't return anything (undefined) 1428 }, 1429 1430 /** 1431 * @private 1432 * Tries to get extract the AWS error data from a 1433 * finesse.clientservices.ClientServices parsed error response object. 1434 * @param {Object} rsp 1435 * The handler to validate 1436 * @returns {String} 1437 * The error data, HTTP status code, or undefined 1438 */ 1439 getErrData: function (rsp) { 1440 try { // Best effort to get the error data 1441 return rsp.object.ApiErrors.ApiError.ErrorData; 1442 } catch (e) { // Second best effort to get the HTTP Status code 1443 if (rsp && rsp.status) { 1444 return "HTTP " + rsp.status; 1445 } 1446 } // Otherwise, don't return anything (undefined) 1447 }, 1448 1449 /** 1450 * @private 1451 * Tries to get extract the AWS overrideable boolean from a 1452 * finesse.clientservices.ClientServices parsed error response object. 1453 * @param {Object} rsp 1454 * The handler to validate 1455 * @returns {String} 1456 * The overrideable boolean, HTTP status code, or undefined 1457 */ 1458 getErrOverrideable: function (rsp) { 1459 try { // Best effort to get the override boolean 1460 return rsp.object.ApiErrors.ApiError.Overrideable; 1461 } catch (e) { // Second best effort to get the HTTP Status code 1462 if (rsp && rsp.status) { 1463 return "HTTP " + rsp.status; 1464 } 1465 } // Otherwise, don't return anything (undefined) 1466 }, 1467 1468 /** 1469 * Trims leading and trailing whitespace from a string. 1470 * @param {String} str 1471 * The string to trim 1472 * @returns {String} 1473 * The trimmed string 1474 */ 1475 trim: function (str) { 1476 return str.replace(/^\s*/, "").replace(/\s*$/, ""); 1477 }, 1478 1479 /** 1480 * Utility method for getting the current time in milliseconds. 1481 * @returns {String} 1482 * The current time in milliseconds 1483 */ 1484 currentTimeMillis : function () { 1485 return (new Date()).getTime(); 1486 }, 1487 1488 /** 1489 * Gets the current drift (between client and server) 1490 * 1491 * @returns {integer} which is the current drift (last calculated; 0 if we have not calculated yet) 1492 */ 1493 getCurrentDrift : function () { 1494 var drift; 1495 1496 //Get the current client drift from localStorage 1497 drift = window.sessionStorage.getItem("clientTimestampDrift"); 1498 if (drift) { 1499 drift = parseInt(drift, 10); 1500 if (isNaN(drift)) { 1501 drift = 0; 1502 } 1503 } 1504 return drift; 1505 }, 1506 1507 /** 1508 * Converts the specified clientTime to server time by adjusting by the current drift. 1509 * 1510 * @param clientTime is the time in milliseconds 1511 * @returns {int} serverTime in milliseconds 1512 */ 1513 convertToServerTimeMillis : function(clientTime) { 1514 var drift = this.getCurrentDrift(); 1515 return (clientTime + drift); 1516 }, 1517 1518 /** 1519 * Utility method for getting the current time, 1520 * adjusted by the calculated "drift" to closely 1521 * approximate the server time. This is used 1522 * when calculating durations based on a server 1523 * timestamp, which otherwise can produce unexpected 1524 * results if the times on client and server are 1525 * off. 1526 * 1527 * @returns {String} 1528 * The current server time in milliseconds 1529 */ 1530 currentServerTimeMillis : function () { 1531 var drift = this.getCurrentDrift(); 1532 return (new Date()).getTime() + drift; 1533 }, 1534 1535 /** 1536 * Given a specified timeInMs, this method will builds a string which displays minutes and seconds. 1537 * 1538 * @param timeInMs is the time in milliseconds 1539 * @returns {String} which corresponds to minutes and seconds (e.g. 11:23) 1540 */ 1541 buildTimeString : function (timeInMs) { 1542 var min, sec, timeStr = "00:00"; 1543 1544 if (timeInMs && timeInMs !== "-1") { 1545 // calculate minutes, and seconds 1546 min = this.pad(Math.floor(timeInMs / 60000)); 1547 sec = this.pad(Math.floor((timeInMs % 60000) / 1000)); 1548 1549 // construct MM:SS time string 1550 timeStr = min + ":" + sec; 1551 } 1552 return timeStr; 1553 }, 1554 1555 /** 1556 * Given a specified timeInMs, this method will builds a string which displays minutes and seconds (and optionally hours) 1557 * 1558 * @param timeInMs is the time in milliseconds 1559 * @returns {String} which corresponds to hours, minutes and seconds (e.g. 01:11:23 or 11:23) 1560 */ 1561 buildTimeStringWithOptionalHours: function (timeInMs) { 1562 var hour, min, sec, timeStr = "00:00", optionalHour = "", timeInSecs; 1563 1564 if (timeInMs && timeInMs !== "-1") { 1565 timeInSecs = timeInMs / 1000; 1566 1567 // calculate {hours}, minutes, and seconds 1568 hour = this.pad(Math.floor(timeInSecs / 3600)); 1569 min = this.pad(Math.floor((timeInSecs % 3600) / 60)); 1570 sec = this.pad(Math.floor((timeInSecs % 3600) % 60)); 1571 1572 //Optionally add the hour if we have hours 1573 if (hour > 0) { 1574 optionalHour = hour + ":"; 1575 } 1576 1577 // construct MM:SS time string (or optionally HH:MM:SS) 1578 timeStr = optionalHour + min + ":" + sec; 1579 } 1580 return timeStr; 1581 }, 1582 1583 1584 /** 1585 * Builds a string which specifies the amount of time user has been in this state (e.g. 11:23). 1586 * 1587 * @param adjustedServerTimeInMs is integer argument which specifies the expected server time (accounting for clientdrift) 1588 * @param stateStartTimeInMs is integer argument which specifies time call entered current state 1589 * @returns {String} which is the elapsed time (MINUTES:SECONDS) 1590 * 1591 */ 1592 buildElapsedTimeString : function (adjustedServerTimeInMs, stateStartTimeInMs) { 1593 var result, delta; 1594 1595 result = "--:--"; 1596 if (stateStartTimeInMs !== 0) { 1597 delta = adjustedServerTimeInMs - stateStartTimeInMs; 1598 1599 if (delta > 0) { 1600 result = this.buildTimeString(delta); 1601 } 1602 } 1603 return result; 1604 }, 1605 1606 /** 1607 * 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). 1608 * 1609 * @param adjustedServerTimeInMs is integer argument which specifies the expected server time (accounting for clientdrift) 1610 * @param startTimeInMs is integer argument which specifies the start time 1611 * @returns {String} which is the elapsed time (MINUTES:SECONDS) or (HOURS:MINUTES:SECONDS) 1612 * 1613 */ 1614 buildElapsedTimeStringWithOptionalHours : function (adjustedServerTimeInMs, stateStartTimeInMs) { 1615 var result, delta; 1616 1617 result = "--:--"; 1618 if (stateStartTimeInMs !== 0) { 1619 delta = adjustedServerTimeInMs - stateStartTimeInMs; 1620 1621 if (delta > 0) { 1622 result = this.buildTimeStringWithOptionalHours(delta); 1623 } 1624 } 1625 return result; 1626 }, 1627 1628 1629 /** 1630 * Builds a string which displays the total call time in minutes and seconds. 1631 * 1632 * @param adjustedServerTimeInMs is integer argument which specifies the expected server time (accounting for clientdrift) 1633 * @param callStartTimeInMs is integer argument which specifies time the call started 1634 * @returns {String} which is the elapsed time [MINUTES:SECONDS] 1635 */ 1636 buildTotalTimeString : function (adjustedServerTimeInMs, callStartTimeInMs) { 1637 return this.buildElapsedTimeString(adjustedServerTimeInMs, callStartTimeInMs); 1638 }, 1639 1640 /** 1641 * Builds a string which displays the hold time in minutes and seconds. 1642 * 1643 * @param adjustedServerTimeInMs is integer argument which specifies the expected server time (accounting for clientdrift) 1644 * @param holdStartTimeInMs is integer argument which specifies time the hold started 1645 * @returns {String} which is the elapsed time [MINUTES:SECONDS] 1646 */ 1647 buildHoldTimeString : function (adjustedServerTimeInMs, holdStartTimeInMs) { 1648 return this.buildElapsedTimeString(adjustedServerTimeInMs, holdStartTimeInMs); 1649 }, 1650 1651 /** 1652 * Builds a string which displays the elapsed time the call has been in wrap up. 1653 * 1654 * @param adjustedServerTimeInMs is integer argument which specifies the expected server time (accounting for clientdrift) 1655 * @param wrapupStartTimeInMs is integer argument which specifies time call entered wrapup state 1656 * @returns {String} which is the elapsed wrapup time 1657 * 1658 */ 1659 buildWrapupTimeString : function (adjustedServerTimeInMs, wrapupStartTimeInMs) { 1660 return this.buildElapsedTimeString(adjustedServerTimeInMs, wrapupStartTimeInMs); 1661 }, 1662 1663 /** 1664 * Extracts a time from the timeStr. Note: The timeStr could be empty. In this case, the time returned will be 0. 1665 * @param timeStr is a time string in ISO8601 format (note: could be empty) 1666 * @returns {long} is the time 1667 */ 1668 extractTime : function (timeStr) { 1669 var result = 0, theDate; 1670 if (timeStr === "") { 1671 result = 0; 1672 } else if (timeStr === null) { 1673 result = 0; 1674 } else { 1675 theDate = this.parseDateStringISO8601(timeStr); 1676 result = theDate.getTime(); 1677 } 1678 return result; 1679 }, 1680 1681 /** 1682 * @private 1683 * Generates an RFC1422v4-compliant UUID using pesudorandom numbers. 1684 * @returns {String} 1685 * An RFC1422v4-compliant UUID using pesudorandom numbers. 1686 **/ 1687 generateUUID: function () { 1688 return Math.uuidCompact(); 1689 }, 1690 1691 /** @private */ 1692 xml2json: finesse.Converter.xml2json, 1693 1694 1695 /** 1696 * @private 1697 * Utility method to get the JSON parser either from gadgets.json 1698 * or from window.JSON (which will be initialized by CUIC if 1699 * browser doesn't support 1700 */ 1701 getJSONParser: function() { 1702 var _container = window.gadgets || {}, 1703 parser = _container.json || window.JSON; 1704 return parser; 1705 }, 1706 1707 /** 1708 * @private 1709 * Utility method to convert a javascript object to XML. 1710 * @param {Object} object 1711 * The object to convert to XML. 1712 * @param {Boolean} escapeFlag 1713 * If escapeFlag evaluates to true: 1714 * - XML escaping is done on the element values. 1715 * - Attributes, #cdata, and #text is not supported. 1716 * - The XML is unformatted (no whitespace between elements). 1717 * If escapeFlag evaluates to false: 1718 * - Element values are written 'as is' (no escaping). 1719 * - Attributes, #cdata, and #text is supported. 1720 * - The XML is formatted. 1721 * @returns The XML string. 1722 */ 1723 json2xml: function (object, escapeFlag) { 1724 var xml; 1725 if (escapeFlag) { 1726 xml = this._json2xmlWithEscape(object); 1727 } 1728 else { 1729 xml = finesse.Converter.json2xml(object, '\t'); 1730 } 1731 return xml; 1732 }, 1733 1734 /** 1735 * @private 1736 * Utility method to convert XML string into javascript object. 1737 */ 1738 xml2JsObj : function (event) { 1739 var parser = this.getJSONParser(); 1740 return parser.parse(finesse.Converter.xml2json(jQuery.parseXML(event), "")); 1741 }, 1742 1743 /** 1744 * @private 1745 * Utility method to convert an XML string to a javascript object. 1746 * @desc This function calls to the SAX parser and responds to callbacks 1747 * received from the parser. Entity translation is not handled here. 1748 * @param {String} xml 1749 * The XML to parse. 1750 * @returns The javascript object. 1751 */ 1752 xml2js: function (xml) { 1753 var STATES = { 1754 INVALID: 0, 1755 NEW_NODE: 1, 1756 ATTRIBUTE_NODE: 2, 1757 TEXT_NODE: 3, 1758 END_NODE: 4 1759 }, 1760 state = STATES.INVALID, 1761 rootObj = {}, 1762 newObj, 1763 objStack = [rootObj], 1764 nodeName = "", 1765 1766 /** 1767 * @private 1768 * Adds a property to the current top JSO. 1769 * @desc This is also where we make considerations for arrays. 1770 * @param {String} name 1771 * The name of the property to add. 1772 * @param (String) value 1773 * The value of the property to add. 1774 */ 1775 addProperty = function (name, value) { 1776 var current = objStack[objStack.length - 1]; 1777 if(current.hasOwnProperty(name) && current[name] instanceof Array){ 1778 current[name].push(value); 1779 }else if(current.hasOwnProperty(name)){ 1780 current[name] = [current[name], value]; 1781 }else{ 1782 current[name] = value; 1783 } 1784 }, 1785 1786 /** 1787 * @private 1788 * The callback passed to the SAX parser which processes events from 1789 * the SAX parser in order to construct the resulting JSO. 1790 * @param (String) type 1791 * The type of event received. 1792 * @param (String) data 1793 * The data received from the SAX parser. The contents of this 1794 * parameter vary based on the type of event. 1795 */ 1796 xmlFound = function (type, data) { 1797 switch (type) { 1798 case "StartElement": 1799 // Because different node types have different expectations 1800 // of parenting, we don't push another JSO until we know 1801 // what content we're getting 1802 1803 // If we're already in the new node state, we're running 1804 // into a child node. There won't be any text here, so 1805 // create another JSO 1806 if(state === STATES.NEW_NODE){ 1807 newObj = {}; 1808 addProperty(nodeName, newObj); 1809 objStack.push(newObj); 1810 } 1811 state = STATES.NEW_NODE; 1812 nodeName = data; 1813 break; 1814 case "EndElement": 1815 // If we're in the new node state, we've found no content 1816 // set the tag property to null 1817 if(state === STATES.NEW_NODE){ 1818 addProperty(nodeName, null); 1819 }else if(state === STATES.END_NODE){ 1820 objStack.pop(); 1821 } 1822 state = STATES.END_NODE; 1823 break; 1824 case "Attribute": 1825 // If were in the new node state, no JSO has yet been created 1826 // for this node, create one 1827 if(state === STATES.NEW_NODE){ 1828 newObj = {}; 1829 addProperty(nodeName, newObj); 1830 objStack.push(newObj); 1831 } 1832 // Attributes are differentiated from child elements by a 1833 // preceding "@" in the property name 1834 addProperty("@" + data.name, data.value); 1835 state = STATES.ATTRIBUTE_NODE; 1836 break; 1837 case "Text": 1838 // In order to maintain backwards compatibility, when no 1839 // attributes are assigned to a tag, its text contents are 1840 // assigned directly to the tag property instead of a JSO. 1841 1842 // If we're in the attribute node state, then the JSO for 1843 // this tag was already created when the attribute was 1844 // assigned, differentiate this property from a child 1845 // element by naming it "#text" 1846 if(state === STATES.ATTRIBUTE_NODE){ 1847 addProperty("#text", data); 1848 }else{ 1849 addProperty(nodeName, data); 1850 } 1851 state = STATES.TEXT_NODE; 1852 break; 1853 } 1854 }; 1855 SaxParser.parse(xml, xmlFound); 1856 return rootObj; 1857 }, 1858 1859 /** 1860 * @private 1861 * Traverses a plain-old-javascript-object recursively and outputs its XML representation. 1862 * @param {Object} obj 1863 * The javascript object to be converted into XML. 1864 * @returns {String} The XML representation of the object. 1865 */ 1866 js2xml: function (obj) { 1867 var xml = "", i, elem; 1868 1869 if (obj !== null) { 1870 if (obj.constructor === Object) { 1871 for (elem in obj) { 1872 if (obj[elem] === null || typeof(obj[elem]) === 'undefined') { 1873 xml += '<' + elem + '/>'; 1874 } else if (obj[elem].constructor === Array) { 1875 for (i = 0; i < obj[elem].length; i++) { 1876 xml += '<' + elem + '>' + this.js2xml(obj[elem][i]) + '</' + elem + '>'; 1877 } 1878 } else if (elem[0] !== '@') { 1879 if (this.js2xmlObjIsEmpty(obj[elem])) { 1880 xml += '<' + elem + this.js2xmlAtt(obj[elem]) + '/>'; 1881 } else if (elem === "#text") { 1882 xml += obj[elem]; 1883 } else { 1884 xml += '<' + elem + this.js2xmlAtt(obj[elem]) + '>' + this.js2xml(obj[elem]) + '</' + elem + '>'; 1885 } 1886 } 1887 } 1888 } else { 1889 xml = obj; 1890 } 1891 } 1892 1893 return xml; 1894 }, 1895 1896 /** 1897 * @private 1898 * Utility method called exclusively by js2xml() to find xml attributes. 1899 * @desc Traverses children one layer deep of a javascript object to "look ahead" 1900 * for properties flagged as such (with '@'). 1901 * @param {Object} obj 1902 * The obj to traverse. 1903 * @returns {String} Any attributes formatted for xml, if any. 1904 */ 1905 js2xmlAtt: function (obj) { 1906 var elem; 1907 1908 if (obj !== null) { 1909 if (obj.constructor === Object) { 1910 for (elem in obj) { 1911 if (obj[elem] !== null && typeof(obj[elem]) !== "undefined" && obj[elem].constructor !== Array) { 1912 if (elem[0] === '@'){ 1913 return ' ' + elem.substring(1) + '="' + obj[elem] + '"'; 1914 } 1915 } 1916 } 1917 } 1918 } 1919 1920 return ''; 1921 }, 1922 1923 /** 1924 * @private 1925 * Utility method called exclusively by js2xml() to determine if 1926 * a node has any children, with special logic for ignoring attributes. 1927 * @desc Attempts to traverse the elements in the object while ignoring attributes. 1928 * @param {Object} obj 1929 * The obj to traverse. 1930 * @returns {Boolean} whether or not the JS object is "empty" 1931 */ 1932 js2xmlObjIsEmpty: function (obj) { 1933 var elem; 1934 1935 if (obj !== null) { 1936 if (obj.constructor === Object) { 1937 for (elem in obj) { 1938 if (obj[elem] !== null) { 1939 if (obj[elem].constructor === Array){ 1940 return false; 1941 } 1942 1943 if (elem[0] !== '@'){ 1944 return false; 1945 } 1946 } else { 1947 return false; 1948 } 1949 } 1950 } else { 1951 return false; 1952 } 1953 } 1954 1955 return true; 1956 }, 1957 1958 /** 1959 * Encodes the given string into base64. 1960 *<br> 1961 * <b>NOTE:</b> {input} is assumed to be UTF-8; only the first 1962 * 8 bits of each input element are significant. 1963 * 1964 * @param {String} input 1965 * The string to convert to base64. 1966 * @returns {String} 1967 * The converted string. 1968 */ 1969 b64Encode: function (input) { 1970 var output = "", idx, data, 1971 table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; 1972 1973 for (idx = 0; idx < input.length; idx += 3) { 1974 data = input.charCodeAt(idx) << 16 | 1975 input.charCodeAt(idx + 1) << 8 | 1976 input.charCodeAt(idx + 2); 1977 1978 //assume the first 12 bits are valid 1979 output += table.charAt((data >>> 18) & 0x003f) + 1980 table.charAt((data >>> 12) & 0x003f); 1981 output += ((idx + 1) < input.length) ? 1982 table.charAt((data >>> 6) & 0x003f) : 1983 "="; 1984 output += ((idx + 2) < input.length) ? 1985 table.charAt(data & 0x003f) : 1986 "="; 1987 } 1988 1989 return output; 1990 }, 1991 1992 /** 1993 * Decodes the given base64 string. 1994 * <br> 1995 * <b>NOTE:</b> output is assumed to be UTF-8; only the first 1996 * 8 bits of each output element are significant. 1997 * 1998 * @param {String} input 1999 * The base64 encoded string 2000 * @returns {String} 2001 * Decoded string 2002 */ 2003 b64Decode: function (input) { 2004 var output = "", idx, h, data, 2005 table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; 2006 2007 for (idx = 0; idx < input.length; idx += 4) { 2008 h = [ 2009 table.indexOf(input.charAt(idx)), 2010 table.indexOf(input.charAt(idx + 1)), 2011 table.indexOf(input.charAt(idx + 2)), 2012 table.indexOf(input.charAt(idx + 3)) 2013 ]; 2014 2015 data = (h[0] << 18) | (h[1] << 12) | (h[2] << 6) | h[3]; 2016 if (input.charAt(idx + 2) === '=') { 2017 data = String.fromCharCode( 2018 (data >>> 16) & 0x00ff 2019 ); 2020 } else if (input.charAt(idx + 3) === '=') { 2021 data = String.fromCharCode( 2022 (data >>> 16) & 0x00ff, 2023 (data >>> 8) & 0x00ff 2024 ); 2025 } else { 2026 data = String.fromCharCode( 2027 (data >>> 16) & 0x00ff, 2028 (data >>> 8) & 0x00ff, 2029 data & 0x00ff 2030 ); 2031 } 2032 output += data; 2033 } 2034 2035 return output; 2036 }, 2037 2038 /** 2039 * @private 2040 * Extracts the username and the password from the Base64 encoded string. 2041 * @params {String} 2042 * A base64 encoded string containing credentials that (when decoded) 2043 * are colon delimited. 2044 * @returns {Object} 2045 * An object with the following structure: 2046 * {id:string, password:string} 2047 */ 2048 getCredentials: function (authorization) { 2049 var credObj = {}, 2050 credStr = this.b64Decode(authorization), 2051 colonIndx = credStr.indexOf(":"); 2052 2053 //Check to ensure that string is colon delimited. 2054 if (colonIndx === -1) { 2055 throw new Error("String is not colon delimited."); 2056 } 2057 2058 //Extract ID and password. 2059 credObj.id = credStr.substring(0, colonIndx); 2060 credObj.password = credStr.substring(colonIndx + 1); 2061 return credObj; 2062 }, 2063 2064 /** 2065 * Takes a string and removes any spaces within the string. 2066 * @param {String} string 2067 * The string to remove spaces from 2068 * @returns {String} 2069 * The string without spaces 2070 */ 2071 removeSpaces: function (string) { 2072 return string.split(' ').join(''); 2073 }, 2074 2075 /** 2076 * Escapes spaces as encoded " " characters so they can 2077 * be safely rendered by jQuery.text(string) in all browsers. 2078 * 2079 * (Although IE behaves as expected, Firefox collapses spaces if this function is not used.) 2080 * 2081 * @param text 2082 * The string whose spaces should be escaped 2083 * 2084 * @returns 2085 * The string with spaces escaped 2086 */ 2087 escapeSpaces: function (string) { 2088 return string.replace(/\s/g, '\u00a0'); 2089 }, 2090 2091 /** 2092 * Adds a span styled to line break at word edges around the string passed in. 2093 * @param str String to be wrapped in word-breaking style. 2094 * @private 2095 */ 2096 addWordWrapping : function (str) { 2097 return '<span style="word-wrap: break-word;">' + str + '</span>'; 2098 }, 2099 2100 /** 2101 * Takes an Object and determines whether it is an Array or not. 2102 * @param {Object} obj 2103 * The Object in question 2104 * @returns {Boolean} 2105 * true if the object is an Array, else false. 2106 */ 2107 isArray: function (obj) { 2108 return obj.constructor.toString().indexOf("Array") !== -1; 2109 }, 2110 2111 /** 2112 * @private 2113 * Takes a data object and returns an array extracted 2114 * @param {Object} data 2115 * JSON payload 2116 * 2117 * @returns {array} 2118 * extracted array 2119 */ 2120 getArray: function (data) { 2121 if (this.isArray(data)) { 2122 //Return if already an array. 2123 return data; 2124 } else { 2125 //Create an array, iterate through object, and push to array. This 2126 //should only occur with one object, and therefore one obj in array. 2127 var arr = []; 2128 arr.push(data); 2129 return arr; 2130 } 2131 }, 2132 2133 /** 2134 * @private 2135 * Extracts the ID for an entity given the Finesse REST URI. The ID is 2136 * assumed to be the last element in the URI (after the last "/"). 2137 * @param {String} uri 2138 * The Finesse REST URI to extract the ID from. 2139 * @returns {String} 2140 * The ID extracted from the REST URI. 2141 */ 2142 getId: function (uri) { 2143 if (!uri) { 2144 return ""; 2145 } 2146 var strLoc = uri.lastIndexOf("/"); 2147 return uri.slice(strLoc + 1); 2148 }, 2149 2150 /** 2151 * Compares two objects for equality. 2152 * @param {Object} obj1 2153 * First of two objects to compare. 2154 * @param {Object} obj2 2155 * Second of two objects to compare. 2156 */ 2157 getEquals: function (objA, objB) { 2158 var key; 2159 2160 for (key in objA) { 2161 if (objA.hasOwnProperty(key)) { 2162 if (!objA[key]) { 2163 objA[key] = ""; 2164 } 2165 2166 if (typeof objB[key] === 'undefined') { 2167 return false; 2168 } 2169 if (typeof objB[key] === 'object') { 2170 if (!objB[key].equals(objA[key])) { 2171 return false; 2172 } 2173 } 2174 if (objB[key] !== objA[key]) { 2175 return false; 2176 } 2177 } 2178 } 2179 return true; 2180 }, 2181 2182 /** 2183 * Regular expressions used in translating HTML and XML entities 2184 */ 2185 ampRegEx : new RegExp('&', 'gi'), 2186 ampEntityRefRegEx : new RegExp('&', 'gi'), 2187 ltRegEx : new RegExp('<', 'gi'), 2188 ltEntityRefRegEx : new RegExp('<', 'gi'), 2189 gtRegEx : new RegExp('>', 'gi'), 2190 gtEntityRefRegEx : new RegExp('>', 'gi'), 2191 xmlSpecialCharRegEx: new RegExp('[&<>"\']', 'g'), 2192 entityRefRegEx: new RegExp('&[^;]+(?:;|$)', 'g'), 2193 2194 /** 2195 * Translates between special characters and HTML entities 2196 * 2197 * @param text 2198 * The text to translate 2199 * 2200 * @param makeEntityRefs 2201 * If true, encode special characters as HTML entities; if 2202 * false, decode HTML entities back to special characters 2203 * 2204 * @private 2205 */ 2206 translateHTMLEntities: function (text, makeEntityRefs) { 2207 if (typeof(text) !== "undefined" && text !== null && text !== "") { 2208 if (makeEntityRefs) { 2209 text = text.replace(this.ampRegEx, '&'); 2210 text = text.replace(this.ltRegEx, '<'); 2211 text = text.replace(this.gtRegEx, '>'); 2212 } else { 2213 text = text.replace(this.gtEntityRefRegEx, '>'); 2214 text = text.replace(this.ltEntityRefRegEx, '<'); 2215 text = text.replace(this.ampEntityRefRegEx, '&'); 2216 } 2217 } 2218 2219 return text; 2220 }, 2221 2222 /** 2223 * Translates between special characters and XML entities 2224 * 2225 * @param text 2226 * The text to translate 2227 * 2228 * @param makeEntityRefs 2229 * If true, encode special characters as XML entities; if 2230 * false, decode XML entities back to special characters 2231 * 2232 * @private 2233 */ 2234 translateXMLEntities: function (text, makeEntityRefs) { 2235 /** @private */ 2236 var escape = function (character) { 2237 switch (character) { 2238 case "&": 2239 return "&"; 2240 case "<": 2241 return "<"; 2242 case ">": 2243 return ">"; 2244 case "'": 2245 return "'"; 2246 case "\"": 2247 return """; 2248 default: 2249 return character; 2250 } 2251 }, 2252 /** @private */ 2253 unescape = function (entity) { 2254 switch (entity) { 2255 case "&": 2256 return "&"; 2257 case "<": 2258 return "<"; 2259 case ">": 2260 return ">"; 2261 case "'": 2262 return "'"; 2263 case """: 2264 return "\""; 2265 default: 2266 if (entity.charAt(1) === "#" && entity.charAt(entity.length - 1) === ";") { 2267 if (entity.charAt(2) === "x") { 2268 return String.fromCharCode(parseInt(entity.slice(3, -1), 16)); 2269 } else { 2270 return String.fromCharCode(parseInt(entity.slice(2, -1), 10)); 2271 } 2272 } else { 2273 throw new Error("Invalid XML entity: " + entity); 2274 } 2275 } 2276 }; 2277 2278 if (typeof(text) !== "undefined" && text !== null && text !== "") { 2279 if (makeEntityRefs) { 2280 text = text.replace(this.xmlSpecialCharRegEx, escape); 2281 } else { 2282 text = text.replace(this.entityRefRegEx, unescape); 2283 } 2284 } 2285 2286 return text; 2287 }, 2288 2289 /** 2290 * @private 2291 * Utility method to pad the number with a leading 0 for single digits 2292 * @param (Number) num 2293 * the number to pad 2294 */ 2295 pad : function (num) { 2296 if (num < 10) { 2297 return "0" + num; 2298 } 2299 2300 return String(num); 2301 }, 2302 2303 /** 2304 * Pad with zeros based on a padWidth. 2305 * 2306 * @param num 2307 * @param padWidth 2308 * @returns {String} with padded zeros (based on padWidth) 2309 */ 2310 padWithWidth : function (num, padWidth) { 2311 var value, index, result; 2312 2313 result = ""; 2314 for(index=padWidth;index>1;index--) 2315 { 2316 value = Math.pow(10, index-1); 2317 2318 if (num < value) { 2319 result = result + "0"; 2320 } 2321 } 2322 result = result + num; 2323 2324 return String(result); 2325 }, 2326 2327 /** 2328 * Converts a date to an ISO date string. 2329 * 2330 * @param aDate 2331 * @returns {String} in ISO date format 2332 * 2333 * Note: Some browsers don't support this method (e.g. IE8). 2334 */ 2335 convertDateToISODateString : function (aDate) { 2336 var result; 2337 2338 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"; 2339 return result; 2340 }, 2341 2342 /** 2343 * Get the date in ISO date format. 2344 * 2345 * @param aDate is the date 2346 * @returns {String} date in ISO format 2347 * 2348 * Note: see convertDateToISODateString() above. 2349 */ 2350 dateToISOString : function (aDate) { 2351 var result; 2352 2353 try { 2354 result = aDate.toISOString(); 2355 } catch (e) { 2356 result = this.convertDateToISODateString(aDate); 2357 } 2358 return result; 2359 }, 2360 2361 /** 2362 * Parse string (which is formated as ISO8601 date) into Javascript Date object. 2363 * 2364 * @param s ISO8601 string 2365 * @return {Date} 2366 * Note: Some browsers don't support Date constructor which take ISO8601 date (e.g. IE 8). 2367 */ 2368 parseDateStringISO8601 : function (s) { 2369 var i, re = /(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:.(\d+))?(Z|[+\-]\d{2})(?::(\d{2}))?/, 2370 d = s.match(re); 2371 if( !d ) { 2372 return null; 2373 } 2374 for( i in d ) { 2375 d[i] = ~~d[i]; 2376 } 2377 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); 2378 }, 2379 2380 /** 2381 * Utility method to render a timestamp value (in seconds) into HH:MM:SS format. 2382 * @param {Number} time 2383 * The timestamp in ms to render 2384 * @returns {String} 2385 * Time string in HH:MM:SS format. 2386 */ 2387 getDisplayTime : function (time) { 2388 var hour, min, sec, timeStr = "00:00:00"; 2389 2390 if (time && time !== "-1") { 2391 // calculate hours, minutes, and seconds 2392 hour = this.pad(Math.floor(time / 3600)); 2393 min = this.pad(Math.floor((time % 3600) / 60)); 2394 sec = this.pad(Math.floor((time % 3600) % 60)); 2395 // construct HH:MM:SS time string 2396 timeStr = hour + ":" + min + ":" + sec; 2397 } 2398 2399 return timeStr; 2400 }, 2401 2402 /** 2403 * Checks if the string is null. If it is, return empty string; else return 2404 * the string itself. 2405 * 2406 * @param {String} str 2407 * The string to check 2408 * @return {String} 2409 * Empty string or string itself 2410 */ 2411 convertNullToEmptyString : function (str) { 2412 return str || ""; 2413 }, 2414 2415 /** 2416 * Utility method to render a timestamp string (of format 2417 * YYYY-MM-DDTHH:MM:SSZ) into a duration of HH:MM:SS format. 2418 * 2419 * @param {String} timestamp 2420 * The timestamp to render 2421 * @param {Date} [now] 2422 * Optional argument to provide the time from which to 2423 * calculate the duration instead of using the current time 2424 * @returns {String} 2425 * Duration string in HH:MM:SS format. 2426 */ 2427 convertTsToDuration : function (timestamp, now) { 2428 return this.convertTsToDurationWithFormat(timestamp, false, now); 2429 }, 2430 2431 /** 2432 * Utility method to render a timestamp string (of format 2433 * YYYY-MM-DDTHH:MM:SSZ) into a duration of HH:MM:SS format, 2434 * with optional -1 for null or negative times. 2435 * 2436 * @param {String} timestamp 2437 * The timestamp to render 2438 * @param {Boolean} forFormat 2439 * If True, if duration is null or negative, return -1 so that the duration can be formated 2440 * as needed in the Gadget. 2441 * @param {Date} [now] 2442 * Optional argument to provide the time from which to 2443 * calculate the duration instead of using the current time 2444 * @returns {String} 2445 * Duration string in HH:MM:SS format. 2446 */ 2447 convertTsToDurationWithFormat : function (timestamp, forFormat, now) { 2448 var startTimeInMs, nowInMs, durationInSec = "-1"; 2449 2450 // Calculate duration 2451 if (timestamp && typeof timestamp === "string") { 2452 // first check it '--' for a msg in grid 2453 if (timestamp === '--' || timestamp ==="" || timestamp === "-1") { 2454 return "-1"; 2455 } 2456 // else try convert string into a time 2457 startTimeInMs = Date.parse(timestamp); 2458 if (!isNaN(startTimeInMs)) { 2459 if (!now || !(now instanceof Date)) { 2460 nowInMs = this.currentServerTimeMillis(); 2461 } else { 2462 nowInMs = this.convertToServerTimeMillis(now.getTime()); 2463 } 2464 durationInSec = Math.floor((nowInMs - startTimeInMs) / 1000); 2465 // Since currentServerTime is not exact (lag between sending and receiving 2466 // messages will differ slightly), treat a slightly negative (less than 1 sec) 2467 // value as 0, to avoid "--" showing up when a state first changes. 2468 if (durationInSec === -1) { 2469 durationInSec = 0; 2470 } 2471 2472 if (durationInSec < 0) { 2473 if (forFormat) { 2474 return "-1"; 2475 } else { 2476 return this.getDisplayTime("-1"); 2477 } 2478 } 2479 } 2480 }else { 2481 if(forFormat){ 2482 return "-1"; 2483 } 2484 } 2485 return this.getDisplayTime(durationInSec); 2486 }, 2487 2488 /** 2489 * @private 2490 * Takes the time in seconds and duration in % and return the duration in milliseconds. 2491 * 2492 * @param time in seconds 2493 * @param duration in % 2494 */ 2495 2496 getRefreshTime :function(expiryTime , duration){ 2497 var durationInMs = Math.floor((expiryTime * duration * 1000) / 100); 2498 return durationInMs; 2499 }, 2500 2501 /** 2502 * Takes a string (typically from window.location) and finds the value which corresponds to a name. For 2503 * example: http://www.company.com/?param1=value1¶m2=value2 2504 * 2505 * @param str is the string to search 2506 * @param name is the name to search for 2507 */ 2508 getParameterByName : function(str, name) { 2509 var regex, results; 2510 name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); 2511 regex = new RegExp("[\\?&]" + name + "=([^]*)"); 2512 results = regex.exec(str); 2513 return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " ")); 2514 }, 2515 2516 /** 2517 * 2518 * Returns the base64 encoded user authorization String. 2519 * @returns {String} the Authorization String 2520 * 2521 */ 2522 getUserAuthString: function () { 2523 var authString = window.sessionStorage.getItem('userFinesseAuth'); 2524 return authString; 2525 }, 2526 2527 /** 2528 * Return the user access token as JSON Object. 2529 * @returns {Object} the access token JSON object 2530 * 2531 */ 2532 getAuthTokenObj: function(){ 2533 var authTokenString = window.sessionStorage.getItem('ssoTokenObject'); 2534 return this.getJSONParser().parse(authTokenString); 2535 }, 2536 2537 /** 2538 * Returns the user access token as String. 2539 * @returns {String} the access token 2540 * 2541 */ 2542 2543 getToken: function () { 2544 var tokenString = window.sessionStorage.getItem('ssoTokenObject'), tokenObj; 2545 if (tokenString && typeof tokenString === "string") { 2546 tokenObj = this.getJSONParser().parse(tokenString); 2547 if (tokenObj.token) { 2548 return tokenObj.token; 2549 } else { 2550 throw new Error( 2551 "Unable to retrieve token : Invalid token Object in browser session"); 2552 } 2553 } 2554 }, 2555 2556 /** 2557 * The authorization header based on SSO or non SSO deployment. 2558 * Can be "Bearer " or "Basic " 2559 * @returns {String} The authorization header string. 2560 */ 2561 getAuthHeaderString : function(configObj) { 2562 var authHeader; 2563 if (configObj.systemAuthMode === this.getAuthModes().SSO) { 2564 authHeader = "Bearer " + configObj.authToken; 2565 } else if (configObj.systemAuthMode === this.getAuthModes().NONSSO) { 2566 authHeader = "Basic " + configObj.authorization; 2567 } else { 2568 throw new Error("Unknown auth mode "+configObj.systemAuthMode); 2569 } 2570 return authHeader; 2571 }, 2572 2573 /** 2574 * Can be used as a constant for auth modes 2575 * Can be "SSO" , "NON_SSO" or "HYBRID" 2576 * @returns {String} The authorization header string. 2577 */ 2578 getAuthModes : function(){ 2579 return { 2580 SSO: "SSO", 2581 NONSSO: "NON_SSO", 2582 HYBRID: "HYBRID" 2583 }; 2584 }, 2585 2586 /** 2587 * Encodes the node name 2588 */ 2589 encodeNodeName : function(node){ 2590 if (node === null){ 2591 return null; 2592 } 2593 var originalChars, encodedChars,encodedNode, i; 2594 originalChars = ["?", "@", "&","'"]; 2595 encodedChars = ["?3F", "?40", "?26","?27"]; 2596 encodedNode = node; 2597 2598 if(encodedNode.indexOf(originalChars[0]) !== -1){ 2599 encodedNode = encodedNode.replace(/\?/g, encodedChars[0]); 2600 } 2601 for (i = 1; i < originalChars.length; i++){ 2602 if(encodedNode.indexOf(originalChars[i]) !== -1){ 2603 encodedNode = encodedNode.replace(new RegExp(originalChars[i], "g"), encodedChars[i]); 2604 } 2605 } 2606 return encodedNode; 2607 }, 2608 2609 /** 2610 * @private Utility method to convert milliseconds into minutes. 2611 * @param {String} Time in milliseconds 2612 * @returns {String} Time in minutes 2613 */ 2614 convertMilliSecondsToMinutes : function(millisec){ 2615 if(!millisec || isNaN(millisec)){ 2616 throw new Error("passed argument is not a number"); 2617 }else{ 2618 var minutes = Math.floor(millisec / (1000 * 60)); 2619 return minutes; 2620 } 2621 }, 2622 2623 2624 /** 2625 * @private Adds a new cookie to the page with a default domain. 2626 * @param {String} 2627 * key the key to assign a value to 2628 * @param {String} 2629 * value the value to assign to the key 2630 * @param {Number} 2631 * days number of days (from current) until the cookie should 2632 * expire 2633 */ 2634 addCookie : function (key, value, days) { 2635 var date, expires = "", 2636 cookie = key + "=" + escape(value); 2637 if (typeof days === "number") { 2638 date = new Date(); 2639 date.setTime(date.getTime() + (days * 24 * 3600 * 1000)); 2640 cookie += "; expires=" + date.toGMTString(); 2641 } 2642 document.cookie = cookie + "; path=/"; 2643 }, 2644 2645 /** 2646 * @private 2647 * Get the value of a cookie given a key. 2648 * @param {String} key 2649 * a key to lookup 2650 * @returns {String} 2651 * the value mapped to a key, null if key doesn't exist 2652 */ 2653 getCookie : function (key) { 2654 var i, pairs, pair; 2655 if (document.cookie) { 2656 pairs = document.cookie.split(";"); 2657 for (i = 0; i < pairs.length; i += 1) { 2658 pair = this.trim(pairs[i]).split("="); 2659 if (pair[0] === key) { 2660 return unescape(pair[1]); 2661 } 2662 } 2663 } 2664 return null; 2665 }, 2666 2667 /** 2668 * @private 2669 * Deletes the cookie mapped to specified key. 2670 * @param {String} key 2671 * the key to delete 2672 */ 2673 deleteCookie : function (key) { 2674 this.addCookie(key, "", -1); 2675 }, 2676 2677 /** 2678 * @private 2679 * Case insensitive sort for use with arrays or Dojox stores 2680 * @param {String} a 2681 * first value 2682 * @param {String} b 2683 * second value 2684 */ 2685 caseInsensitiveSort: function (a, b) { 2686 var ret = 0, emptyString = ""; 2687 a = a + emptyString; 2688 b = b + emptyString; 2689 a = a.toLowerCase(); 2690 b = b.toLowerCase(); 2691 if (a > b) { 2692 ret = 1; 2693 } 2694 if (a < b) { 2695 ret = -1; 2696 } 2697 return ret; 2698 }, 2699 2700 /** 2701 * @private 2702 * Calls the specified function to render the dojo wijit for a gadget when the gadget first becomes visible. 2703 * 2704 * The displayWjitFunc function will be called once and only once when the div for our wijit 2705 * becomes visible for the first time. This is necessary because some dojo wijits such as the grid 2706 * throw exceptions and do not render properly if they are created in a display:none div. 2707 * If our gadget is visisble the function will be called immediately. 2708 * If our gadget is not yet visisble, then it sets a timer and waits for it to become visible. 2709 * NOTE: The timer may seem inefficent, originally I tried connecting to the tab onclick handler, but 2710 * there is a problem with dojo.connnect to an iframe's parent node in Internet Explorer. 2711 * In Firefox the click handler works OK, but it happens before the node is actually visisble, so you 2712 * end up waiting for the node to become visisble anyway. 2713 * @displayWjitFunc: A function to be called once our gadget has become visisble for th first time. 2714 */ 2715 onGadgetFirstVisible: function (displayWjitFunc) { 2716 var i, q, frameId, gadgetNbr, gadgetTitleId, panelId, panelNode, link, iterval, once = false, active = false, tabId = "#finesse-tab-selector"; 2717 try { 2718 frameId = dojo.attr(window.frameElement, "id"); // Figure out what gadget number we are by looking at our frameset 2719 gadgetNbr = frameId.match(/\d+$/)[0]; // Strip the number off the end of the frame Id, that's our gadget number 2720 gadgetTitleId = "#finesse_gadget_" + gadgetNbr + "_title"; // Create a a gadget title id from the number 2721 2722 // Loop through all of the tab panels to find one that has our gadget id 2723 dojo.query('.tab-panel', window.parent.document).some(function (node, index, arr) { 2724 q = dojo.query(gadgetTitleId, node); // Look in this panel for our gadget id 2725 if (q.length > 0) { // You found it 2726 panelNode = node; 2727 panelId = dojo.attr(panelNode, "id"); // Get panel id e.g. panel_Workgroups 2728 active = dojo.hasClass(panelNode, "active"); 2729 tabId = "#tab_" + panelId.slice(6); // Turn it into a tab id e.g.tab_Workgroups 2730 return; 2731 } 2732 }); 2733 // If panel is already active - execute the function - we're done 2734 if (active) { 2735 //?console.log(frameId + " is visible display it"); 2736 setTimeout(displayWjitFunc); 2737 } 2738 // If its not visible - wait for the active class to show up. 2739 else { 2740 //?console.log(frameId + " (" + tabId + ") is NOT active wait for it"); 2741 iterval = setInterval(dojo.hitch(this, function () { 2742 if (dojo.hasClass(panelNode, "active")) { 2743 //?console.log(frameId + " (" + tabId + ") is visible display it"); 2744 clearInterval(iterval); 2745 setTimeout(displayWjitFunc); 2746 } 2747 }), 250); 2748 } 2749 } catch (err) { 2750 //?console.log("Could not figure out what tab " + frameId + " is in: " + err); 2751 } 2752 }, 2753 2754 /** 2755 * @private 2756 * Downloads the specified url using a hidden iframe. In order to cause the browser to download rather than render 2757 * in the hidden iframe, the server code must append the header "Content-Disposition" with a value of 2758 * "attachment; filename=\"<WhateverFileNameYouWant>\"". 2759 */ 2760 downloadFile : function (url) { 2761 var iframe = document.getElementById("download_iframe"); 2762 2763 if (!iframe) 2764 { 2765 iframe = document.createElement("iframe"); 2766 $(document.body).append(iframe); 2767 $(iframe).css("display", "none"); 2768 } 2769 2770 iframe.src = url; 2771 }, 2772 2773 /** 2774 * @private 2775 * bitMask has functions for testing whether bit flags specified by integers are set in the supplied value 2776 */ 2777 bitMask: { 2778 /** @private */ 2779 isSet: function (value, mask) { 2780 return (value & mask) === mask; 2781 }, 2782 /** 2783 * Returns true if all flags in the intArray are set on the specified value 2784 * @private 2785 */ 2786 all: function (value, intArray) { 2787 var i = intArray.length; 2788 if (typeof(i) === "undefined") 2789 { 2790 intArray = [intArray]; 2791 i = 1; 2792 } 2793 while ((i = i - 1) !== -1) 2794 { 2795 if (!this.isSet(value, intArray[i])) 2796 { 2797 return false; 2798 } 2799 } 2800 return true; 2801 }, 2802 /** 2803 * @private 2804 * Returns true if any flags in the intArray are set on the specified value 2805 */ 2806 any: function (value, intArray) { 2807 var i = intArray.length; 2808 if (typeof(i) === "undefined") 2809 { 2810 intArray = [intArray]; 2811 i = 1; 2812 } 2813 while ((i = i - 1) !== -1) 2814 { 2815 if (this.isSet(value, intArray[i])) 2816 { 2817 return true; 2818 } 2819 } 2820 return false; 2821 } 2822 }, 2823 2824 /** @private */ 2825 renderDojoGridOffScreen: function (grid) { 2826 var offscreenDiv = $("<div style='position: absolute; left: -5001px; width: 5000px;'></div>")[0]; 2827 $(document.body).append(offscreenDiv); 2828 grid.placeAt(offscreenDiv); 2829 grid.startup(); 2830 document.body.removeChild(offscreenDiv); 2831 return grid; 2832 }, 2833 2834 /** @private */ 2835 initializeSearchInput: function(searchInput, changeCallback, callbackDelay, callbackScope, placeholderText) { 2836 var timerId = null, 2837 theControl = typeof(searchInput) === "string" ? $("#" + searchInput) : $(searchInput), 2838 theInputControl = theControl.find("input"), 2839 theClearButton = theControl.find("a"), 2840 inputControlWidthWithClear = 204, 2841 inputControlWidthNoClear = 230, 2842 sPreviousInput = theInputControl.val(), 2843 /** @private **/ 2844 toggleClearButton = function(){ 2845 if (theInputControl.val() === "") { 2846 theClearButton.hide(); 2847 theControl.removeClass("input-append"); 2848 theInputControl.width(inputControlWidthNoClear); 2849 } else { 2850 theInputControl.width(inputControlWidthWithClear); 2851 theClearButton.show(); 2852 theControl.addClass("input-append"); 2853 } 2854 }; 2855 2856 // set placeholder text 2857 theInputControl.attr('placeholder', placeholderText); 2858 2859 theInputControl.unbind('keyup').bind('keyup', function() { 2860 if (sPreviousInput !== theInputControl.val()) { 2861 window.clearTimeout(timerId); 2862 sPreviousInput = theInputControl.val(); 2863 timerId = window.setTimeout(function() { 2864 changeCallback.call((callbackScope || window), theInputControl.val()); 2865 theInputControl[0].focus(); 2866 }, callbackDelay); 2867 } 2868 2869 toggleClearButton(); 2870 }); 2871 2872 theClearButton.bind('click', function() { 2873 theInputControl.val(''); 2874 changeCallback.call((callbackScope || window), ''); 2875 2876 toggleClearButton(); 2877 theInputControl[0].focus(); // jquery and dojo on the same page break jquery's focus() method 2878 }); 2879 2880 theInputControl.val(""); 2881 toggleClearButton(); 2882 }, 2883 2884 DataTables: { 2885 /** @private */ 2886 createDataTable: function (options, dataTableOptions) { 2887 var grid, 2888 table = $('<table cellpadding="0" cellspacing="0" border="0" class="finesse"><thead><tr></tr></thead></table>'), 2889 headerRow = table.find("tr"), 2890 defaultOptions = { 2891 "aaData": [], 2892 "bPaginate": false, 2893 "bLengthChange": false, 2894 "bFilter": false, 2895 "bInfo": false, 2896 "sScrollY": "176", 2897 "oLanguage": { 2898 "sEmptyTable": "", 2899 "sZeroRecords": "" 2900 } 2901 }, 2902 gridOptions = $.extend({}, defaultOptions, dataTableOptions), 2903 columnDefs = [], 2904 columnFormatter; 2905 2906 // Create a header cell for each column, and set up the datatable definition for the column 2907 $(options.columns).each(function (index, column) { 2908 headerRow.append($("<th></th>")); 2909 columnDefs[index] = { 2910 "mData": column.propertyName, 2911 "sTitle": column.columnHeader, 2912 "sWidth": column.width, 2913 "aTargets": [index], 2914 "bSortable": column.sortable, 2915 "bVisible": column.visible, 2916 "mRender": column.render 2917 }; 2918 if (typeof(column.renderFunction) === "function") 2919 { 2920 /** @ignore **/ 2921 columnDefs[index].mRender = /** @ignore **/ function (value, type, dataObject) { 2922 var returnValue; 2923 2924 //Apply column render logic to value before applying extra render function 2925 if (typeof(column.render) === "function") 2926 { 2927 value = column.render.call(value, value, value); 2928 } 2929 2930 if (typeof(type) === "string") 2931 { 2932 switch (type) 2933 { 2934 case "undefined": 2935 case "sort": 2936 returnValue = value; 2937 break; 2938 case "set": 2939 throw new Error("Unsupported set data in Finesse Grid"); 2940 case "filter": 2941 case "display": 2942 case "type": 2943 returnValue = column.renderFunction.call(dataObject, value, dataObject); 2944 break; 2945 default: 2946 break; 2947 } 2948 } 2949 else 2950 { 2951 throw new Error("type param not specified in Finesse DataTable mData"); 2952 } 2953 2954 return returnValue; 2955 }; 2956 } 2957 }); 2958 gridOptions.aoColumnDefs = columnDefs; 2959 2960 // Set the height 2961 if (typeof(options.bodyHeightPixels) !== "undefined" && options.bodyHeightPixels !== null) 2962 { 2963 gridOptions.sScrollY = options.bodyHeightPixels + "px"; 2964 } 2965 2966 // Place it into the DOM 2967 if (typeof(options.container) !== "undefined" && options.container !== null) 2968 { 2969 $(options.container).append(table); 2970 } 2971 2972 // Create the DataTable 2973 table.dataTable(gridOptions); 2974 2975 return table; 2976 } 2977 }, 2978 2979 /** 2980 * @private 2981 * Sets a dojo button to the specified disable state, removing it from 2982 * the tab order if disabling, and restoring it to the tab order if enabling. 2983 * @param {Object} dojoButton Reference to the dijit.form.Button object. This is not the DOM element. 2984 * @param {bool} disabled 2985 */ 2986 setDojoButtonDisabledAttribute: function (dojoButton, disabled) { 2987 var labelNode, 2988 tabIndex; 2989 2990 dojoButton.set("disabled", disabled); 2991 2992 // Remove the tabindex attribute on disabled buttons, store it, 2993 // and replace it when it becomes enabled again 2994 labelNode = $("#" + dojoButton.id + "_label"); 2995 if (disabled) 2996 { 2997 labelNode.data("finesse:dojoButton:tabIndex", labelNode.attr("tabindex")); 2998 labelNode.removeAttr("tabindex"); 2999 } 3000 else 3001 { 3002 tabIndex = labelNode.data("finesse:dojoButton:tabIndex"); 3003 if (typeof(tabIndex) === "string") 3004 { 3005 labelNode.attr("tabindex", Number(tabIndex)); 3006 } 3007 } 3008 }, 3009 3010 /** 3011 * @private 3012 * Use this utility to disable the tab stop for a Dojo Firebug iframe within a gadget. 3013 * 3014 * Dojo sometimes adds a hidden iframe for enabling a firebug lite console in older 3015 * browsers. Unfortunately, this adds an additional tab stop that impacts accessibility. 3016 */ 3017 disableTabStopForDojoFirebugIframe: function () { 3018 var iframe = $("iframe[src*='loadFirebugConsole']"); 3019 3020 if ((iframe.length) && (iframe.attr("tabIndex") !== "-1")) { 3021 iframe.attr('tabIndex', '-1'); 3022 } 3023 }, 3024 3025 /** 3026 * @private 3027 * Measures the given text using the supplied fontFamily and fontSize 3028 * @param {string} text text to measure 3029 * @param {string} fontFamily 3030 * @param {string} fontSize 3031 * @return {number} pixel width 3032 */ 3033 measureText: function (text, fontFamily, fontSize) { 3034 var width, 3035 element = $("<div></div>").text(text).css({ 3036 "fontSize": fontSize, 3037 "fontFamily": fontFamily 3038 }).addClass("offscreen").appendTo(document.body); 3039 3040 width = element.width(); 3041 element.remove(); 3042 3043 return width; 3044 }, 3045 3046 /** 3047 * Adjusts the gadget height. Shindig's gadgets.window.adjustHeight fails when 3048 * needing to resize down in IE. This gets around that by calculating the height 3049 * manually and passing it in. 3050 * @return {undefined} 3051 */ 3052 "adjustGadgetHeight": function () { 3053 var bScrollHeight = $("body").height() + 20; 3054 gadgets.window.adjustHeight(bScrollHeight); 3055 }, 3056 3057 /** 3058 * Private helper method for converting a javascript object to xml, where the values of the elements are 3059 * appropriately escaped for XML. 3060 * This is a simple implementation that does not implement cdata or attributes. It is also 'unformatted' in that 3061 * there is no whitespace between elements. 3062 * @param object The javascript object to convert to XML. 3063 * @returns The XML string. 3064 * @private 3065 */ 3066 _json2xmlWithEscape: function(object) { 3067 var that = this, 3068 xml = "", 3069 m, 3070 /** @private **/ 3071 toXmlHelper = function(value, name) { 3072 var xml = "", 3073 i, 3074 m; 3075 if (value instanceof Array) { 3076 for (i = 0; i < value.length; ++i) { 3077 xml += toXmlHelper(value[i], name); 3078 } 3079 } 3080 else if (typeof value === "object") { 3081 xml += "<" + name + ">"; 3082 for (m in value) { 3083 if (value.hasOwnProperty(m)) { 3084 xml += toXmlHelper(value[m], m); 3085 } 3086 } 3087 xml += "</" + name + ">"; 3088 } 3089 else { 3090 // is a leaf node 3091 xml += "<" + name + ">" + that.translateHTMLEntities(value.toString(), true) + 3092 "</" + name + ">"; 3093 } 3094 return xml; 3095 }; 3096 for (m in object) { 3097 if (object.hasOwnProperty(m)) { 3098 xml += toXmlHelper(object[m], m); 3099 } 3100 } 3101 return xml; 3102 }, 3103 3104 /** 3105 * Private method for returning a sanitized version of the user agent string. 3106 * @returns the user agent string, but sanitized! 3107 * @private 3108 */ 3109 getSanitizedUserAgentString: function () { 3110 return this.translateXMLEntities(navigator.userAgent, true); 3111 }, 3112 3113 /** 3114 * Use JQuery's implementation of Promises (Deferred) to execute code when 3115 * multiple async processes have finished. An example use: 3116 * 3117 * var asyncProcess1 = $.Deferred(), 3118 * asyncProcess2 = $.Deferred(); 3119 * 3120 * finesse.utilities.Utilities.whenAllDone(asyncProcess1, asyncProcess2) // WHEN both asyncProcess1 and asyncProcess2 are resolved or rejected ... 3121 * .then( 3122 * // First function passed to then() is called when all async processes are complete, regardless of errors 3123 * function () { 3124 * console.log("all processes completed"); 3125 * }, 3126 * // Second function passed to then() is called if any async processed threw an exception 3127 * function (failures) { // Array of failure messages 3128 * console.log("Number of failed async processes: " + failures.length); 3129 * }); 3130 * 3131 * Found at: 3132 * http://stackoverflow.com/a/15094263/1244030 3133 * 3134 * Pass in any number of $.Deferred instances. 3135 * @returns {Object} 3136 */ 3137 whenAllDone: function () { 3138 var deferreds = [], 3139 result = $.Deferred(); 3140 3141 $.each(arguments, function(i, current) { 3142 var currentDeferred = $.Deferred(); 3143 current.then(function() { 3144 currentDeferred.resolve(false, arguments); 3145 }, function() { 3146 currentDeferred.resolve(true, arguments); 3147 }); 3148 deferreds.push(currentDeferred); 3149 }); 3150 3151 $.when.apply($, deferreds).then(function() { 3152 var failures = [], 3153 successes = []; 3154 3155 $.each(arguments, function(i, args) { 3156 // If we resolved with `true` as the first parameter 3157 // we have a failure, a success otherwise 3158 var target = args[0] ? failures : successes, 3159 data = args[1]; 3160 // Push either all arguments or the only one 3161 target.push(data.length === 1 ? data[0] : args); 3162 }); 3163 3164 if(failures.length) { 3165 return result.reject.apply(result, failures); 3166 } 3167 3168 return result.resolve.apply(result, successes); 3169 }); 3170 3171 return result; 3172 }, 3173 3174 /** 3175 * Private method to format a given string by replacing the place holders (like {0}) with the 3176 * corresponding supplied arguments. For example, calling this method as follows: 3177 * formatString("Hello {0}, {1} rocks!", "there", "Finesse"); 3178 * results in the following output string: 3179 * "Hello there, Finesse rocks!" 3180 * 3181 * @param {String} format - a string that holds the place holder to be replaced 3182 * 3183 * @returns {String} - string where the place holders are replaced with respective values 3184 * @private 3185 */ 3186 formatString : function(format) { 3187 if (!format || arguments.length <= 1) { 3188 return format; 3189 } 3190 3191 var i, retStr = format; 3192 for (i = 1; i < arguments.length; i += 1) { 3193 retStr = retStr.replace(new RegExp("\\{" + (i - 1) + "\\}", "g"), arguments[i]); 3194 } 3195 3196 // in order to fix French text with single quotes in it, we need to replace \' with ' 3197 return retStr.replace(/\\'/g, "'"); 3198 } 3199 }; 3200 3201 window.finesse = window.finesse || {}; 3202 window.finesse.utilities = window.finesse.utilities || {}; 3203 window.finesse.utilities.Utilities = Utilities; 3204 3205 return Utilities; 3206 }); 3207 3208 /** The following comment is to prevent jslint errors about 3209 * using variables before they are defined. 3210 */ 3211 /*global finesse*/ 3212 3213 /** 3214 * Initiated by the Master to create a shared BOSH connection. 3215 * 3216 * @requires Utilities 3217 */ 3218 3219 /** 3220 * @class 3221 * Establishes a shared event connection by creating a communication tunnel 3222 * with the notification server and consume events which could be published. 3223 * Public functions are exposed to register to the connection status information 3224 * and events. 3225 * @constructor 3226 * @param {String} host 3227 * The host name/ip of the Finesse server. 3228 * @throws {Error} If required constructor parameter is missing. 3229 */ 3230 /** @private */ 3231 define('clientservices/MasterTunnel',["utilities/Utilities"], function (Utilities) { 3232 var MasterTunnel = function (host, scheme) { 3233 if (typeof host !== "string" || host.length === 0) { 3234 throw new Error("Required host parameter missing."); 3235 } 3236 3237 var 3238 3239 /** 3240 * Flag to indicate whether the tunnel frame is loaded. 3241 * @private 3242 */ 3243 _isTunnelLoaded = false, 3244 3245 /** 3246 * Short reference to the Finesse utility. 3247 * @private 3248 */ 3249 _util = Utilities, 3250 3251 /** 3252 * The URL with host and port to the Finesse server. 3253 * @private 3254 */ 3255 _tunnelOrigin, 3256 3257 /** 3258 * Location of the tunnel HTML URL. 3259 * @private 3260 */ 3261 _tunnelURL, 3262 3263 /** 3264 * The port on which to connect to the Finesse server to load the eventing resources. 3265 * @private 3266 */ 3267 _tunnelOriginPort, 3268 3269 /** 3270 * Flag to indicate whether we have processed the tunnel config yet. 3271 * @private 3272 */ 3273 _isTunnelConfigInit = false, 3274 3275 /** 3276 * The tunnel frame window object. 3277 * @private 3278 */ 3279 _tunnelFrame, 3280 3281 /** 3282 * The handler registered with the object to be invoked when an event is 3283 * delivered by the notification server. 3284 * @private 3285 */ 3286 _eventHandler, 3287 3288 /** 3289 * The handler registered with the object to be invoked when presence is 3290 * delivered by the notification server. 3291 * @private 3292 */ 3293 _presenceHandler, 3294 3295 /** 3296 * The handler registered with the object to be invoked when the BOSH 3297 * connection has changed states. The object will contain the "status" 3298 * property and a "resourceID" property only if "status" is "connected". 3299 * @private 3300 */ 3301 _connInfoHandler, 3302 3303 /** 3304 * The last connection status published by the JabberWerx library. 3305 * @private 3306 */ 3307 _statusCache, 3308 3309 /** 3310 * The last event sent by notification server. 3311 * @private 3312 */ 3313 _eventCache, 3314 3315 /** 3316 * The ID of the user logged into notification server. 3317 * @private 3318 */ 3319 _id, 3320 3321 /** 3322 * The domain of the XMPP server, representing the portion of the JID 3323 * following '@': userid@domain.com 3324 * @private 3325 */ 3326 _xmppDomain, 3327 3328 /** 3329 * The password of the user logged into notification server. 3330 * @private 3331 */ 3332 _password, 3333 3334 /** 3335 * The jid of the pubsub service on the XMPP server 3336 * @private 3337 */ 3338 _pubsubDomain, 3339 3340 /** 3341 * The resource to use for the BOSH connection. 3342 * @private 3343 */ 3344 _resource, 3345 3346 /** 3347 * The resource ID identifying the client device (that we receive from the server). 3348 * @private 3349 */ 3350 _resourceID, 3351 3352 /** 3353 * The different types of messages that could be sent to the parent frame. 3354 * The types here should be understood by the parent frame and used to 3355 * identify how the message is formatted. 3356 * @private 3357 */ 3358 _TYPES = { 3359 EVENT: 0, 3360 ID: 1, 3361 PASSWORD: 2, 3362 RESOURCEID: 3, 3363 STATUS: 4, 3364 XMPPDOMAIN: 5, 3365 PUBSUBDOMAIN: 6, 3366 SUBSCRIBE: 7, 3367 UNSUBSCRIBE: 8, 3368 PRESENCE: 9, 3369 CONNECT_REQ: 10 3370 }, 3371 3372 _handlers = { 3373 subscribe: {}, 3374 unsubscribe: {} 3375 }, 3376 3377 3378 /** 3379 * Create a connection info object. 3380 * @returns {Object} 3381 * A connection info object containing a "status" and "resourceID". 3382 * @private 3383 */ 3384 _createConnInfoObj = function () { 3385 return { 3386 status: _statusCache, 3387 resourceID: _resourceID 3388 }; 3389 }, 3390 3391 /** 3392 * Utility function which sends a message to the dynamic tunnel frame 3393 * event frame formatted as follows: "type|message". 3394 * @param {Number} type 3395 * The category type of the message. 3396 * @param {String} message 3397 * The message to be sent to the tunnel frame. 3398 * @private 3399 */ 3400 _sendMessage = function (type, message) { 3401 message = type + "|" + message; 3402 _util.sendMessage(message, _tunnelFrame, _tunnelOrigin); 3403 }, 3404 3405 /** 3406 * Utility to process the response of a subscribe request from 3407 * the tunnel frame, then invoking the stored callback handler 3408 * with the respective data (error, when applicable) 3409 * @param {String} data 3410 * The response in the format of "node[|error]" 3411 * @private 3412 */ 3413 _processSubscribeResponse = function (data) { 3414 var dataArray = data.split("|"), 3415 node = dataArray[0], 3416 err; 3417 3418 //Error is optionally the second item in the array 3419 if (dataArray.length) { 3420 err = dataArray[1]; 3421 } 3422 3423 // These response handlers are short lived and should be removed and cleaned up immediately after invocation. 3424 if (_handlers.subscribe[node]) { 3425 _handlers.subscribe[node](err); 3426 delete _handlers.subscribe[node]; 3427 } 3428 }, 3429 3430 /** 3431 * Utility to process the response of an unsubscribe request from 3432 * the tunnel frame, then invoking the stored callback handler 3433 * with the respective data (error, when applicable) 3434 * @param {String} data 3435 * The response in the format of "node[|error]" 3436 * @private 3437 */ 3438 _processUnsubscribeResponse = function (data) { 3439 var dataArray = data.split("|"), 3440 node = dataArray[0], 3441 err; 3442 3443 //Error is optionally the second item in the array 3444 if (dataArray.length) { 3445 err = dataArray[1]; 3446 } 3447 3448 // These response handlers are short lived and should be removed and cleaned up immediately after invocation. 3449 if (_handlers.unsubscribe[node]) { 3450 _handlers.unsubscribe[node](err); 3451 delete _handlers.unsubscribe[node]; 3452 } 3453 }, 3454 3455 /** 3456 * Handler for messages delivered by window.postMessage. Listens for events 3457 * published by the notification server, connection status published by 3458 * the JabberWerx library, and the resource ID created when the BOSH 3459 * connection has been established. 3460 * @param {Object} e 3461 * The message object as provided by the window.postMessage feature. 3462 * @private 3463 */ 3464 _messageHandler = function (e) { 3465 var 3466 3467 //Extract the message type and message data. The expected format is 3468 //"type|data" where type is a number represented by the TYPES object. 3469 delimPos = e.data.indexOf("|"), 3470 type = Number(e.data.substr(0, delimPos)), 3471 data = e.data.substr(delimPos + 1); 3472 3473 //Accepts messages and invoke the correct registered handlers. 3474 switch (type) { 3475 case _TYPES.EVENT: 3476 _eventCache = data; 3477 if (typeof _eventHandler === "function") { 3478 _eventHandler(data); 3479 } 3480 break; 3481 case _TYPES.STATUS: 3482 _statusCache = data; 3483 3484 //A "loaded" status means that the frame is ready to accept 3485 //credentials for establishing a BOSH connection. 3486 if (data === "loaded") { 3487 _isTunnelLoaded = true; 3488 if(_resource) { 3489 _sendMessage(_TYPES.RESOURCEID, _resource); 3490 } 3491 _sendMessage(_TYPES.ID, _id); 3492 _sendMessage(_TYPES.XMPPDOMAIN, _xmppDomain); 3493 _sendMessage(_TYPES.PASSWORD, _password); 3494 _sendMessage(_TYPES.PUBSUBDOMAIN, _pubsubDomain); 3495 } else if (typeof _connInfoHandler === "function") { 3496 _connInfoHandler(_createConnInfoObj()); 3497 } 3498 break; 3499 case _TYPES.RESOURCEID: 3500 _resourceID = data; 3501 break; 3502 case _TYPES.SUBSCRIBE: 3503 _processSubscribeResponse(data); 3504 break; 3505 case _TYPES.UNSUBSCRIBE: 3506 _processUnsubscribeResponse(data); 3507 break; 3508 case _TYPES.PRESENCE: 3509 if (typeof _presenceHandler === "function") { 3510 _presenceHandler(data); 3511 } 3512 break; 3513 default: 3514 break; 3515 } 3516 }, 3517 3518 /** 3519 * Initialize the tunnel config so that the url can be http or https with the appropriate port 3520 * @private 3521 */ 3522 _initTunnelConfig = function () { 3523 if (_isTunnelConfigInit === true) { 3524 return; 3525 } 3526 3527 //Initialize tunnel origin 3528 //Determine tunnel origin based on host and scheme 3529 _tunnelOriginPort = (scheme && scheme.indexOf("https") !== -1) ? "7443" : "7071"; 3530 if (scheme) { 3531 _tunnelOrigin = scheme + "://" + host + ":" + _tunnelOriginPort; 3532 } else { 3533 _tunnelOrigin = "http://" + host + ":" + _tunnelOriginPort; 3534 } 3535 _tunnelURL = _tunnelOrigin + "/tunnel/"; 3536 3537 _isTunnelConfigInit = true; 3538 }, 3539 3540 /** 3541 * Create the tunnel iframe which establishes the shared BOSH connection. 3542 * Messages are sent across frames using window.postMessage. 3543 * @private 3544 */ 3545 _createTunnel = function () { 3546 var tunnelID = ((self === parent) ? "tunnel-frame" : "autopilot-tunnel-frame"), 3547 iframe = document.createElement("iframe"); 3548 iframe.style.display = "none"; 3549 iframe.setAttribute("id", tunnelID); 3550 iframe.setAttribute("name", tunnelID); 3551 iframe.setAttribute("src", _tunnelURL); 3552 document.body.appendChild(iframe); 3553 _tunnelFrame = window.frames[tunnelID]; 3554 }; 3555 3556 /** 3557 * Sends a message via postmessage to the EventTunnel to attempt to connect to the XMPP server 3558 * @private 3559 */ 3560 this.makeConnectReq = function () { 3561 _sendMessage(_TYPES.PASSWORD, _password); 3562 }; 3563 3564 /** 3565 * @private 3566 * Returns the host of the Finesse server. 3567 * @returns {String} 3568 * The host specified during the creation of the object. 3569 */ 3570 this.getHost = function () { 3571 return host; 3572 }; 3573 3574 /** 3575 * @private 3576 * The resource ID of the user who is logged into the notification server. 3577 * @returns {String} 3578 * The resource ID generated by the notification server. 3579 */ 3580 this.getResourceID = function () { 3581 return _resourceID; 3582 }; 3583 3584 /** 3585 * @private 3586 * Indicates whether the tunnel frame is loaded. 3587 * @returns {Boolean} 3588 * True if the tunnel frame is loaded, false otherwise. 3589 */ 3590 this.isTunnelLoaded = function () { 3591 return _isTunnelLoaded; 3592 }; 3593 3594 /** 3595 * @private 3596 * The location of the tunnel HTML URL. 3597 * @returns {String} 3598 * The location of the tunnel HTML URL. 3599 */ 3600 this.getTunnelURL = function () { 3601 return _tunnelURL; 3602 }; 3603 3604 /** 3605 * @private 3606 * Tunnels a subscribe request to the eventing iframe. 3607 * @param {String} node 3608 * The node to subscribe to 3609 * @param {Function} handler 3610 * Handler to invoke upon success or failure 3611 */ 3612 this.subscribe = function (node, handler) { 3613 if (handler && typeof handler !== "function") { 3614 throw new Error("Parameter is not a function."); 3615 } 3616 _handlers.subscribe[node] = handler; 3617 _sendMessage(_TYPES.SUBSCRIBE, node); 3618 }; 3619 3620 /** 3621 * @private 3622 * Tunnels an unsubscribe request to the eventing iframe. 3623 * @param {String} node 3624 * The node to unsubscribe from 3625 * @param {Function} handler 3626 * Handler to invoke upon success or failure 3627 */ 3628 this.unsubscribe = function (node, handler) { 3629 if (handler && typeof handler !== "function") { 3630 throw new Error("Parameter is not a function."); 3631 } 3632 _handlers.unsubscribe[node] = handler; 3633 _sendMessage(_TYPES.UNSUBSCRIBE, node); 3634 }; 3635 3636 /** 3637 * @private 3638 * Registers a handler to be invoked when an event is delivered. Only one 3639 * is registered at a time. If there has already been an event that was 3640 * delivered, the handler will be invoked immediately. 3641 * @param {Function} handler 3642 * Invoked when an event is delivered through the event connection. 3643 */ 3644 this.registerEventHandler = function (handler) { 3645 if (typeof handler !== "function") { 3646 throw new Error("Parameter is not a function."); 3647 } 3648 _eventHandler = handler; 3649 if (_eventCache) { 3650 handler(_eventCache); 3651 } 3652 }; 3653 3654 /** 3655 * @private 3656 * Unregisters the event handler completely. 3657 */ 3658 this.unregisterEventHandler = function () { 3659 _eventHandler = undefined; 3660 }; 3661 3662 /** 3663 * @private 3664 * Registers a handler to be invoked when a presence event is delivered. Only one 3665 * is registered at a time. 3666 * @param {Function} handler 3667 * Invoked when a presence event is delivered through the event connection. 3668 */ 3669 this.registerPresenceHandler = function (handler) { 3670 if (typeof handler !== "function") { 3671 throw new Error("Parameter is not a function."); 3672 } 3673 _presenceHandler = handler; 3674 }; 3675 3676 /** 3677 * @private 3678 * Unregisters the presence event handler completely. 3679 */ 3680 this.unregisterPresenceHandler = function () { 3681 _presenceHandler = undefined; 3682 }; 3683 3684 /** 3685 * @private 3686 * Registers a handler to be invoked when a connection status changes. The 3687 * object passed will contain a "status" property, and a "resourceID" 3688 * property, which will contain the most current resource ID assigned to 3689 * the client. If there has already been an event that was delivered, the 3690 * handler will be invoked immediately. 3691 * @param {Function} handler 3692 * Invoked when a connection status changes. 3693 */ 3694 this.registerConnectionInfoHandler = function (handler) { 3695 if (typeof handler !== "function") { 3696 throw new Error("Parameter is not a function."); 3697 } 3698 _connInfoHandler = handler; 3699 if (_statusCache) { 3700 handler(_createConnInfoObj()); 3701 } 3702 }; 3703 3704 /** 3705 * @private 3706 * Unregisters the connection information handler. 3707 */ 3708 this.unregisterConnectionInfoHandler = function () { 3709 _connInfoHandler = undefined; 3710 }; 3711 3712 /** 3713 * @private 3714 * Start listening for events and create a event tunnel for the shared BOSH 3715 * connection. 3716 * @param {String} id 3717 * The ID of the user for the notification server. 3718 * @param {String} password 3719 * The password of the user for the notification server. 3720 * @param {String} xmppDomain 3721 * The XMPP domain of the notification server 3722 * @param {String} pubsubDomain 3723 * The location (JID) of the XEP-0060 PubSub service 3724 * @param {String} resource 3725 * The resource to connect to the notification servier with. 3726 */ 3727 this.init = function (id, password, xmppDomain, pubsubDomain, resource) { 3728 3729 if (typeof id !== "string" || typeof password !== "string" || typeof xmppDomain !== "string" || typeof pubsubDomain !== "string") { 3730 throw new Error("Invalid or missing required parameters."); 3731 } 3732 3733 _initTunnelConfig(); 3734 3735 _id = id; 3736 _password = password; 3737 _xmppDomain = xmppDomain; 3738 _pubsubDomain = pubsubDomain; 3739 _resource = resource; 3740 3741 //Attach a listener for messages sent from tunnel frame. 3742 _util.receiveMessage(_messageHandler, _tunnelOrigin); 3743 3744 //Create the tunnel iframe which will establish the shared connection. 3745 _createTunnel(); 3746 }; 3747 3748 //BEGIN TEST CODE// 3749 // /** 3750 // * Test code added to expose private functions that are used by unit test 3751 // * framework. This section of code is removed during the build process 3752 // * before packaging production code. The [begin|end]TestSection are used 3753 // * by the build to identify the section to strip. 3754 // * @ignore 3755 // */ 3756 // this.beginTestSection = 0; 3757 // 3758 // /** 3759 // * @ignore 3760 // */ 3761 // this.getTestObject = function () { 3762 // //Load mock dependencies. 3763 // var _mock = new MockControl(); 3764 // _util = _mock.createMock(finesse.utilities.Utilities); 3765 // 3766 // return { 3767 // //Expose mock dependencies 3768 // mock: _mock, 3769 // util: _util, 3770 // 3771 // //Expose internal private functions 3772 // types: _TYPES, 3773 // createConnInfoObj: _createConnInfoObj, 3774 // sendMessage: _sendMessage, 3775 // messageHandler: _messageHandler, 3776 // createTunnel: _createTunnel, 3777 // handlers: _handlers, 3778 // initTunnelConfig : _initTunnelConfig 3779 // }; 3780 // }; 3781 // 3782 // /** 3783 // * @ignore 3784 // */ 3785 // this.endTestSection = 0; 3786 // //END TEST CODE// 3787 }; 3788 3789 /** @namespace JavaScript class objects and methods to handle the subscription to Finesse events.*/ 3790 finesse.clientservices = finesse.clientservices || {}; 3791 3792 window.finesse = window.finesse || {}; 3793 window.finesse.clientservices = window.finesse.clientservices || {}; 3794 window.finesse.clientservices.MasterTunnel = MasterTunnel; 3795 3796 return MasterTunnel; 3797 3798 }); 3799 3800 /** 3801 * Contains a list of topics used for client side pubsub. 3802 * 3803 */ 3804 3805 /** @private */ 3806 define('clientservices/Topics',[], function () { 3807 3808 var Topics = (function () { 3809 3810 /** 3811 * @private 3812 * The namespace prepended to all Finesse topics. 3813 */ 3814 this.namespace = "finesse.info"; 3815 3816 /** 3817 * @private 3818 * Gets the full topic name with the Finesse namespace prepended. 3819 * @param {String} topic 3820 * The topic category. 3821 * @returns {String} 3822 * The full topic name with prepended namespace. 3823 */ 3824 var _getNSTopic = function (topic) { 3825 return this.namespace + "." + topic; 3826 }; 3827 3828 /** @scope finesse.clientservices.Topics */ 3829 return { 3830 /** 3831 * @private 3832 * Client side request channel. 3833 */ 3834 REQUESTS: _getNSTopic("requests"), 3835 3836 /** 3837 * @private 3838 * Client side response channel. 3839 */ 3840 RESPONSES: _getNSTopic("responses"), 3841 3842 /** 3843 * @private 3844 * Connection status. 3845 */ 3846 EVENTS_CONNECTION_INFO: _getNSTopic("connection"), 3847 3848 /** 3849 * @private 3850 * Presence channel 3851 */ 3852 PRESENCE: _getNSTopic("presence"), 3853 3854 /** 3855 * Topic for listening to token refresh events. 3856 * The provided callback will be invoked when the access token is refreshed. 3857 * This event is only meant for updating the access token in gadget Config object 3858 */ 3859 ACCESS_TOKEN_REFRESHED_EVENT: _getNSTopic("accessTokenRefresh"), 3860 3861 /** 3862 * @private 3863 * Convert a Finesse REST URI to a OpenAjax compatible topic name. 3864 */ 3865 getTopic: function (restUri) { 3866 //The topic should not start with '/' else it will get replaced with 3867 //'.' which is invalid. 3868 //Thus, remove '/' if it is at the beginning of the string 3869 if (restUri.indexOf('/') === 0) { 3870 restUri = restUri.substr(1); 3871 } 3872 3873 //Replace every instance of "/" with ".". This is done to follow the 3874 //OpenAjaxHub topic name convention. 3875 return restUri.replace(/\//g, "."); 3876 } 3877 }; 3878 }()); 3879 window.finesse = window.finesse || {}; 3880 window.finesse.clientservices = window.finesse.clientservices || {}; 3881 /** @private */ 3882 window.finesse.clientservices.Topics = Topics; 3883 3884 return Topics; 3885 }); 3886 /** The following comment is to prevent jslint errors about 3887 * using variables before they are defined. 3888 */ 3889 /*global finesse*/ 3890 3891 /** 3892 * Registers with the MasterTunnel to receive events, which it 3893 * could publish to the OpenAjax gadget pubsub infrastructure. 3894 * 3895 * @requires OpenAjax, finesse.clientservices.MasterTunnel, finesse.clientservices.Topics 3896 */ 3897 3898 /** @private */ 3899 define('clientservices/MasterPublisher',[ 3900 "clientservices/MasterTunnel", 3901 "clientservices/Topics", 3902 "utilities/Utilities" 3903 ], 3904 function (MasterTunnel, Topics, Utilities) { 3905 3906 var MasterPublisher = function (tunnel, hub) { 3907 if (!(tunnel instanceof MasterTunnel)) { 3908 throw new Error("Required tunnel object missing or invalid."); 3909 } 3910 3911 var 3912 3913 ClientServices = finesse.clientservices.ClientServices, 3914 3915 /** 3916 * Reference to the gadget pubsub Hub instance. 3917 * @private 3918 */ 3919 _hub = hub, 3920 3921 /** 3922 * Reference to the Topics class. 3923 * @private 3924 */ 3925 _topics = Topics, 3926 3927 /** 3928 * Reference to conversion utilities class. 3929 * @private 3930 */ 3931 _utils = Utilities, 3932 3933 /** 3934 * References to ClientServices logger methods 3935 * @private 3936 */ 3937 _logger = { 3938 log: ClientServices.log 3939 }, 3940 3941 /** 3942 * Store the passed in tunnel. 3943 * @private 3944 */ 3945 _tunnel = tunnel, 3946 3947 /** 3948 * Caches the connection info event so that it could be published if there 3949 * is a request for it. 3950 * @private 3951 */ 3952 _connInfoCache = {}, 3953 3954 /** 3955 * The types of possible request types supported when listening to the 3956 * requests channel. Each request type could result in different operations. 3957 * @private 3958 */ 3959 _REQTYPES = { 3960 CONNECTIONINFO: "ConnectionInfoReq", 3961 SUBSCRIBE: "SubscribeNodeReq", 3962 UNSUBSCRIBE: "UnsubscribeNodeReq", 3963 CONNECT: "ConnectionReq" 3964 }, 3965 3966 /** 3967 * Will store list of nodes that have OF subscriptions created 3968 * _nodesList[node][subscribing].reqIds[subid] 3969 * _nodesList[node][active].reqIds[subid] 3970 * _nodesList[node][unsubscribing].reqIds[subid] 3971 * _nodesList[node][holding].reqIds[subid] 3972 * @private 3973 */ 3974 _nodesList = {}, 3975 3976 /** 3977 * The states that a subscription can be in 3978 * @private 3979 */ 3980 _CHANNELSTATES = { 3981 UNINITIALIZED: "Uninitialized", 3982 PENDING: "Pending", 3983 OPERATIONAL: "Operational" 3984 }, 3985 3986 /** 3987 * Checks if the payload is JSON 3988 * @returns {Boolean} 3989 * @private 3990 */ 3991 _isJsonPayload = function(event) { 3992 var delimStart, delimEnd, retval = false; 3993 3994 try { 3995 delimStart = event.indexOf('{'); 3996 delimEnd = event.lastIndexOf('}'); 3997 3998 if ((delimStart !== -1 ) && (delimEnd === (event.length - 1))) { 3999 retval = true; //event contains JSON payload 4000 } 4001 } catch (err) { 4002 _logger.log("MasterPublisher._isJsonPayload() - Caught error: " + err); 4003 } 4004 return retval; 4005 }, 4006 4007 /** 4008 * Parses a JSON event and then publishes. 4009 * 4010 * @param {String} event 4011 * The full event payload. 4012 * @throws {Error} If the payload object is malformed. 4013 * @private 4014 */ 4015 _parseAndPublishJSONEvent = function(event) { 4016 var topic, eventObj, publishEvent, 4017 delimPos = event.indexOf("{"), 4018 node, parser, 4019 eventJson = event, 4020 returnObj = {node: null, data: null}; 4021 4022 try { 4023 //Extract and strip the node path from the message 4024 if (delimPos > 0) 4025 { 4026 //We need to decode the URI encoded node path 4027 //TODO: make sure this is kosher with OpenAjax topic naming 4028 node = decodeURI(event.substr(0, delimPos)); 4029 eventJson = event.substr(delimPos); 4030 4031 //Converting the node path to openAjaxhub topic 4032 topic = _topics.getTopic(node); 4033 4034 returnObj.node = node; 4035 returnObj.payload = eventJson; 4036 } 4037 else 4038 { 4039 _logger.log("MasterPublisher._parseAndPublishJSONEvent() - [ERROR] node is not given in postMessage: " + eventJson); 4040 throw new Error("node is not given in postMessage: " + eventJson); 4041 } 4042 4043 parser = _utils.getJSONParser(); 4044 4045 eventObj = parser.parse(eventJson); 4046 returnObj.data = eventObj; 4047 4048 } catch (err) { 4049 _logger.log("MasterPublisher._parseAndPublishJSONEvent() - [ERROR] Malformed event payload: " + err); 4050 throw new Error("Malformed event payload : " + err); 4051 } 4052 4053 _logger.log("MasterPublisher._parseAndPublishJSONEvent() - Received JSON event on node '" + node + "': " + eventJson); 4054 4055 publishEvent = {content : event, object : eventObj }; 4056 4057 //Publish event to proper event topic. 4058 if (topic && eventObj) { 4059 _hub.publish(topic, publishEvent); 4060 } 4061 }, 4062 4063 /** 4064 * Parses an XML event and then publishes. 4065 * 4066 * @param {String} event 4067 * The full event payload. 4068 * @throws {Error} If the payload object is malformed. 4069 * @private 4070 */ 4071 _parseAndPublishXMLEvent = function(event) { 4072 var topic, eventObj, publishEvent, restTopic, 4073 delimPos = event.indexOf("<"), 4074 node, 4075 eventXml = event; 4076 4077 try { 4078 //Extract and strip the node path from the message 4079 if (delimPos > 0) { 4080 //We need to decode the URI encoded node path 4081 //TODO: make sure this is kosher with OpenAjax topic naming 4082 node = decodeURI(event.substr(0, delimPos)); 4083 eventXml = event.substr(delimPos); 4084 //Converting the node path to openAjaxhub topic 4085 topic = _topics.getTopic(node); 4086 } else { 4087 _logger.log("MasterPublisher._parseAndPublishXMLEvent() - [ERROR] node is not given in postMessage: " + eventXml); 4088 throw new Error("node is not given in postMessage: " + eventXml); 4089 } 4090 4091 eventObj = _utils.xml2JsObj(eventXml); 4092 4093 } catch (err) { 4094 _logger.log("MasterPublisher._parseAndPublishXMLEvent() - [ERROR] Malformed event payload: " + err); 4095 throw new Error("Malformed event payload : " + err); 4096 } 4097 4098 _logger.log("MasterPublisher._parseAndPublishXMLEvent() - Received XML event on node '" + node + "': " + eventXml); 4099 4100 publishEvent = {content : event, object : eventObj }; 4101 4102 //Publish event to proper event topic. 4103 if (topic && eventObj) { 4104 _hub.publish(topic, publishEvent); 4105 } 4106 }, 4107 4108 /** 4109 * Publishes events to the appropriate topic. The topic name is determined 4110 * by fetching the source value from the event. 4111 * @param {String} event 4112 * The full event payload. 4113 * @throws {Error} If the payload object is malformed. 4114 * @private 4115 */ 4116 _eventHandler = function (event) { 4117 4118 //Handle JSON or XML events 4119 if (!_isJsonPayload(event)) 4120 { 4121 //XML 4122 _parseAndPublishXMLEvent(event); 4123 } 4124 else 4125 { 4126 //JSON 4127 _parseAndPublishJSONEvent(event); 4128 } 4129 }, 4130 4131 4132 /** 4133 * Handler for when presence events are sent through the MasterTunnel. 4134 * @returns {Object} 4135 * A presence xml event. 4136 * @private 4137 */ 4138 _presenceHandler = function (event) { 4139 var eventObj = _utils.xml2JsObj(event), publishEvent; 4140 4141 publishEvent = {content : event, object : eventObj}; 4142 4143 if (eventObj) { 4144 _hub.publish(_topics.PRESENCE, publishEvent); 4145 } 4146 }, 4147 4148 /** 4149 * Clone the connection info object from cache. 4150 * @returns {Object} 4151 * A connection info object containing a "status" and "resourceID". 4152 * @private 4153 */ 4154 _cloneConnInfoObj = function () { 4155 if (_connInfoCache) { 4156 return { 4157 status: _connInfoCache.status, 4158 resourceID: _connInfoCache.resourceID 4159 }; 4160 } else { 4161 return null; 4162 } 4163 }, 4164 4165 /** 4166 * Cleans up any outstanding subscribe/unsubscribe requests and notifies them of errors. 4167 * This is done if we get disconnected because we cleanup explicit subscriptions on disconnect. 4168 * @private 4169 */ 4170 _cleanupPendingRequests = function () { 4171 var node, curSubid, errObj = { 4172 error: { 4173 errorType: "Disconnected", 4174 errorMessage: "Outstanding request will never complete." 4175 } 4176 }; 4177 4178 // Iterate through all outstanding subscribe requests to notify them that it will never return 4179 for (node in _nodesList) { 4180 if (_nodesList.hasOwnProperty(node)) { 4181 for (curSubid in _nodesList[node].subscribing.reqIds) { 4182 if (_nodesList[node].subscribing.reqIds.hasOwnProperty(curSubid)) { 4183 // Notify this outstanding subscribe request to give up and error out 4184 _hub.publish(_topics.RESPONSES + "." + curSubid, errObj); 4185 } 4186 } 4187 for (curSubid in _nodesList[node].unsubscribing.reqIds) { 4188 if (_nodesList[node].unsubscribing.reqIds.hasOwnProperty(curSubid)) { 4189 // Notify this outstanding unsubscribe request to give up and error out 4190 _hub.publish(_topics.RESPONSES + "." + curSubid, errObj); 4191 } 4192 } 4193 } 4194 } 4195 }, 4196 4197 /** 4198 * Publishes the connection info to the connection info topic. 4199 * @param {Object} connInfo 4200 * The connection info object containing the status and resource ID. 4201 * @private 4202 */ 4203 _connInfoHandler = function (connInfo) { 4204 var before = _connInfoCache.status; 4205 _connInfoCache = connInfo; 4206 _logger.log("MasterPublisher._connInfoHandler() - Connection status: " + connInfo.status); 4207 _hub.publish(_topics.EVENTS_CONNECTION_INFO, _cloneConnInfoObj()); 4208 if (before === "connected" && connInfo.status !== "connected") { 4209 // Fail all pending requests when we transition to disconnected 4210 _cleanupPendingRequests(); 4211 } 4212 }, 4213 4214 4215 /** 4216 * Utility method to bookkeep node subscription requests and determine 4217 * whehter it is necessary to tunnel the request to JabberWerx. 4218 * @param {String} node 4219 * The node of interest 4220 * @param {String} reqId 4221 * A unique string identifying the request/subscription 4222 * @private 4223 */ 4224 _subscribeNode = function (node, subid) { 4225 if (_connInfoCache.status !== "connected") { 4226 _hub.publish(_topics.RESPONSES + "." + subid, { 4227 error: { 4228 errorType: "Not connected", 4229 errorMessage: "Cannot subscribe without connection." 4230 } 4231 }); 4232 return; 4233 } 4234 // NODE DOES NOT YET EXIST 4235 if (!_nodesList[node]) { 4236 _nodesList[node] = { 4237 "subscribing": { 4238 "reqIds": {}, 4239 "length": 0 4240 }, 4241 "active": { 4242 "reqIds": {}, 4243 "length": 0 4244 }, 4245 "unsubscribing": { 4246 "reqIds": {}, 4247 "length": 0 4248 }, 4249 "holding": { 4250 "reqIds": {}, 4251 "length": 0 4252 } 4253 }; 4254 } 4255 if (_nodesList[node].active.length === 0) { 4256 if (_nodesList[node].unsubscribing.length === 0) { 4257 if (_nodesList[node].subscribing.length === 0) { 4258 _nodesList[node].subscribing.reqIds[subid] = true; 4259 _nodesList[node].subscribing.length += 1; 4260 4261 _logger.log("MasterPublisher._subscribeNode() - Attempting to subscribe to node '" + node + "'"); 4262 _tunnel.subscribe(node, function (err) { 4263 var errObj, curSubid; 4264 if (err) { 4265 errObj = { 4266 subscribe: { 4267 content: err 4268 } 4269 }; 4270 4271 try { 4272 errObj.subscribe.object = gadgets.json.parse((_utils.xml2json(jQuery.parseXML(err), ""))); 4273 } catch (e) { 4274 errObj.error = { 4275 errorType: "parseError", 4276 errorMessage: "Could not serialize XML: " + e 4277 }; 4278 } 4279 _logger.log("MasterPublisher._subscribeNode() - Error subscribing to node '" + node + "': " + err); 4280 } else { 4281 _logger.log("MasterPublisher._subscribeNode() - Subscribed to node '" + node + "'"); 4282 } 4283 4284 for (curSubid in _nodesList[node].subscribing.reqIds) { 4285 if (_nodesList[node].subscribing.reqIds.hasOwnProperty(curSubid)) { 4286 _hub.publish(_topics.RESPONSES + "." + curSubid, errObj); 4287 if (!err) { 4288 _nodesList[node].active.reqIds[curSubid] = true; 4289 _nodesList[node].active.length += 1; 4290 } 4291 delete _nodesList[node].subscribing.reqIds[curSubid]; 4292 _nodesList[node].subscribing.length -= 1; 4293 } 4294 } 4295 }); 4296 4297 } else { //other ids are subscribing 4298 _nodesList[node].subscribing.reqIds[subid] = true; 4299 _nodesList[node].subscribing.length += 1; 4300 } 4301 } else { //An unsubscribe request is pending, hold onto these subscribes until it is done 4302 _nodesList[node].holding.reqIds[subid] = true; 4303 _nodesList[node].holding.length += 1; 4304 } 4305 } else { // The node has active subscriptions; add this subid and return successful response 4306 _nodesList[node].active.reqIds[subid] = true; 4307 _nodesList[node].active.length += 1; 4308 _hub.publish(_topics.RESPONSES + "." + subid, undefined); 4309 } 4310 }, 4311 4312 /** 4313 * Utility method to bookkeep node unsubscribe requests and determine 4314 * whehter it is necessary to tunnel the request to JabberWerx. 4315 * @param {String} node 4316 * The node to unsubscribe from 4317 * @param {String} reqId 4318 * A unique string identifying the subscription to remove 4319 * @private 4320 */ 4321 _unsubscribeNode = function (node, subid) { 4322 if (!_nodesList[node]) { //node DNE, publish success response 4323 _hub.publish(_topics.RESPONSES + "." + subid, undefined); 4324 } else { 4325 if (_connInfoCache.status !== "connected") { 4326 _hub.publish(_topics.RESPONSES + "." + subid, { 4327 error: { 4328 errorType: "Not connected", 4329 errorMessage: "Cannot unsubscribe without connection." 4330 } 4331 }); 4332 return; 4333 } 4334 if (_nodesList[node].active.length > 1) { 4335 delete _nodesList[node].active.reqIds[subid]; 4336 _hub.publish(_topics.RESPONSES + "." + subid, undefined); 4337 _nodesList[node].active.length -= 1; 4338 } else if (_nodesList[node].active.length === 1) { // transition subid from active category to unsubscribing category 4339 _nodesList[node].unsubscribing.reqIds[subid] = true; 4340 _nodesList[node].unsubscribing.length += 1; 4341 delete _nodesList[node].active.reqIds[subid]; 4342 _nodesList[node].active.length -= 1; 4343 4344 _logger.log("MasterPublisher._unsubscribeNode() - Attempting to unsubscribe from node '" + node + "'"); 4345 _tunnel.unsubscribe(node, function (err) { 4346 var errObj, curSubid; 4347 if (err) { 4348 errObj = { 4349 subscribe: { 4350 content: err 4351 } 4352 }; 4353 4354 try { 4355 errObj.subscribe.object = gadgets.json.parse((_utils.xml2json(jQuery.parseXML(err), ""))); 4356 } catch (e) { 4357 errObj.error = { 4358 errorType: "parseError", 4359 errorMessage: "Could not serialize XML: " + e 4360 }; 4361 } 4362 _logger.log("MasterPublisher._unsubscribeNode() - Error unsubscribing from node '" + node + "': " + err); 4363 } else { 4364 _logger.log("MasterPublisher._unsubscribeNode() - Unsubscribed from node '" + node + "'"); 4365 } 4366 4367 for (curSubid in _nodesList[node].unsubscribing.reqIds) { 4368 if (_nodesList[node].unsubscribing.reqIds.hasOwnProperty(curSubid)) { 4369 // publish to all subids whether unsubscribe failed or succeeded 4370 _hub.publish(_topics.RESPONSES + "." + curSubid, errObj); 4371 if (!err) { 4372 delete _nodesList[node].unsubscribing.reqIds[curSubid]; 4373 _nodesList[node].unsubscribing.length -= 1; 4374 } else { // Just remove the subid from unsubscribing; the next subscribe request will operate with node already created 4375 delete _nodesList[node].unsubscribing.reqIds[curSubid]; 4376 _nodesList[node].unsubscribing.length -= 1; 4377 } 4378 } 4379 } 4380 4381 if (!err && _nodesList[node].holding.length > 0) { // if any subscribe requests came in while unsubscribing from OF, now transition from holding to subscribing 4382 for (curSubid in _nodesList[node].holding.reqIds) { 4383 if (_nodesList[node].holding.reqIds.hasOwnProperty(curSubid)) { 4384 delete _nodesList[node].holding.reqIds[curSubid]; 4385 _nodesList[node].holding.length -= 1; 4386 _subscribeNode(node, curSubid); 4387 } 4388 } 4389 } 4390 }); 4391 } else { // length <= 0? 4392 _hub.publish(_topics.RESPONSES + "." + subid, undefined); 4393 } 4394 } 4395 }, 4396 4397 /** 4398 * Handles client requests to establish a BOSH connection. 4399 * @param {String} id 4400 * id of the xmpp user 4401 * @param {String} password 4402 * password of the xmpp user 4403 * @param {String} xmppDomain 4404 * xmppDomain of the xmpp user account 4405 * @private 4406 */ 4407 _connect = function (id, password, xmppDomain) { 4408 _tunnel.makeConnectReq(id, password, xmppDomain); 4409 }, 4410 4411 /** 4412 * Handles client requests made to the request topic. The type of the 4413 * request is described in the "type" property within the data payload. Each 4414 * type can result in a different operation. 4415 * @param {String} topic 4416 * The topic which data was published to. 4417 * @param {Object} data 4418 * The data containing requests information published by clients. 4419 * @param {String} data.type 4420 * The type of the request. Supported: "ConnectionInfoReq" 4421 * @param {Object} data.data 4422 * May contain data relevant for the particular requests. 4423 * @param {String} [data.invokeID] 4424 * The ID used to identify the request with the response. The invoke ID 4425 * will be included in the data in the publish to the topic. It is the 4426 * responsibility of the client to correlate the published data to the 4427 * request made by using the invoke ID. 4428 * @private 4429 */ 4430 _clientRequestHandler = function (topic, data) { 4431 var dataCopy; 4432 4433 //Ensure a valid data object with "type" and "data" properties. 4434 if (typeof data === "object" && 4435 typeof data.type === "string" && 4436 typeof data.data === "object") { 4437 switch (data.type) { 4438 case _REQTYPES.CONNECTIONINFO: 4439 //It is possible that Slave clients come up before the Master 4440 //client. If that is the case, the Slaves will need to make a 4441 //request for the Master to send the latest connection info to the 4442 //connectionInfo topic. 4443 dataCopy = _cloneConnInfoObj(); 4444 if (dataCopy) { 4445 if (data.invokeID !== undefined) { 4446 dataCopy.invokeID = data.invokeID; 4447 } 4448 _hub.publish(_topics.EVENTS_CONNECTION_INFO, dataCopy); 4449 } 4450 break; 4451 case _REQTYPES.SUBSCRIBE: 4452 if (typeof data.data.node === "string") { 4453 _subscribeNode(data.data.node, data.invokeID); 4454 } 4455 break; 4456 case _REQTYPES.UNSUBSCRIBE: 4457 if (typeof data.data.node === "string") { 4458 _unsubscribeNode(data.data.node, data.invokeID); 4459 } 4460 break; 4461 case _REQTYPES.CONNECT: 4462 // Deprecated: Disallow others (non-masters) from administering BOSH connections that are not theirs 4463 _logger.log("MasterPublisher._clientRequestHandler(): F403: Access denied. Non-master ClientService instances do not have the clearance level to make this request: makeConnectionReq"); 4464 break; 4465 default: 4466 break; 4467 } 4468 } 4469 }; 4470 4471 (function () { 4472 //Register to receive events and connection status from tunnel. 4473 _tunnel.registerEventHandler(_eventHandler); 4474 _tunnel.registerPresenceHandler(_presenceHandler); 4475 _tunnel.registerConnectionInfoHandler(_connInfoHandler); 4476 4477 //Listen to a request channel to respond to any requests made by other 4478 //clients because the Master may have access to useful information. 4479 _hub.subscribe(_topics.REQUESTS, _clientRequestHandler); 4480 }()); 4481 4482 /** 4483 * @private 4484 * Handles client requests to establish a BOSH connection. 4485 * @param {String} id 4486 * id of the xmpp user 4487 * @param {String} password 4488 * password of the xmpp user 4489 * @param {String} xmppDomain 4490 * xmppDomain of the xmpp user account 4491 */ 4492 this.connect = function (id, password, xmppDomain) { 4493 _connect(id, password, xmppDomain); 4494 }; 4495 4496 /** 4497 * @private 4498 * Resets the list of explicit subscriptions 4499 */ 4500 this.wipeout = function () { 4501 _cleanupPendingRequests(); 4502 _nodesList = {}; 4503 }; 4504 4505 //BEGIN TEST CODE// 4506 /** 4507 * Test code added to expose private functions that are used by unit test 4508 * framework. This section of code is removed during the build process 4509 * before packaging production code. The [begin|end]TestSection are used 4510 * by the build to identify the section to strip. 4511 * @ignore 4512 */ 4513 this.beginTestSection = 0; 4514 4515 /** 4516 * @ignore 4517 */ 4518 this.getTestObject = function () { 4519 //Load mock dependencies. 4520 var _mock = new MockControl(); 4521 _hub = _mock.createMock(gadgets.Hub); 4522 _tunnel = _mock.createMock(); 4523 4524 return { 4525 //Expose mock dependencies 4526 mock: _mock, 4527 hub: _hub, 4528 tunnel: _tunnel, 4529 setTunnel: function (tunnel) { 4530 _tunnel = tunnel; 4531 }, 4532 getTunnel: function () { 4533 return _tunnel; 4534 }, 4535 4536 //Expose internal private functions 4537 reqtypes: _REQTYPES, 4538 eventHandler: _eventHandler, 4539 presenceHandler: _presenceHandler, 4540 4541 subscribeNode: _subscribeNode, 4542 unsubscribeNode: _unsubscribeNode, 4543 4544 getNodeList: function () { 4545 return _nodesList; 4546 }, 4547 setNodeList: function (nodelist) { 4548 _nodesList = nodelist; 4549 }, 4550 4551 cloneConnInfoObj: _cloneConnInfoObj, 4552 connInfoHandler: _connInfoHandler, 4553 clientRequestHandler: _clientRequestHandler 4554 4555 }; 4556 }; 4557 4558 4559 /** 4560 * @ignore 4561 */ 4562 this.endTestSection = 0; 4563 //END TEST CODE// 4564 4565 }; 4566 4567 window.finesse = window.finesse || {}; 4568 window.finesse.clientservices = window.finesse.clientservices || {}; 4569 window.finesse.clientservices.MasterPublisher = MasterPublisher; 4570 4571 return MasterPublisher; 4572 }); 4573 4574 /** The following comment is to prevent jslint errors about 4575 * using variables before they are defined. 4576 */ 4577 /*global publisher:true */ 4578 4579 /** 4580 * Exposes a set of API wrappers that will hide the dirty work of 4581 * constructing Finesse API requests and consuming Finesse events. 4582 * 4583 * @requires OpenAjax, jQuery 1.5, finesse.utilities.Utilities 4584 */ 4585 4586 4587 /** 4588 * Allow clients to make Finesse API requests and consume Finesse events by 4589 * calling a set of exposed functions. The Services layer will do the dirty 4590 * work of establishing a shared BOSH connection (for designated Master 4591 * modules), consuming events for client subscriptions, and constructing API 4592 * requests. 4593 */ 4594 /** @private */ 4595 define('clientservices/ClientServices',[ 4596 "clientservices/MasterTunnel", 4597 "clientservices/MasterPublisher", 4598 "clientservices/Topics", 4599 "utilities/Utilities" 4600 ], 4601 function (MasterTunnel, MasterPublisher, Topics, Utilities) { 4602 4603 var ClientServices = (function () {/** @lends finesse.clientservices.ClientServices.prototype */ 4604 var 4605 4606 /** 4607 * Shortcut reference to the master tunnel 4608 * @private 4609 */ 4610 _tunnel, 4611 4612 _publisher, 4613 4614 /** 4615 * Shortcut reference to the finesse.utilities.Utilities singleton 4616 * This will be set by init() 4617 * @private 4618 */ 4619 _util, 4620 4621 /** 4622 * Shortcut reference to the gadgets.io object. 4623 * This will be set by init() 4624 * @private 4625 */ 4626 _io, 4627 4628 /** 4629 * Shortcut reference to the gadget pubsub Hub instance. 4630 * This will be set by init() 4631 * @private 4632 */ 4633 _hub, 4634 4635 /** 4636 * Logger object set externally by setLogger, defaults to nothing. 4637 * @private 4638 */ 4639 _logger = {}, 4640 4641 /** 4642 * Shortcut reference to the Topics class. 4643 * This will be set by init() 4644 * @private 4645 */ 4646 _topics, 4647 4648 /** 4649 * Config object needed to initialize this library 4650 * This must be set by init() 4651 * @private 4652 */ 4653 _config, 4654 4655 /** 4656 * @private 4657 * Whether or not this ClientService instance is a Master. 4658 */ 4659 _isMaster = false, 4660 4661 /** 4662 * @private 4663 * Whether the Client Services have been initiated yet. 4664 */ 4665 _inited = false, 4666 4667 /** 4668 * Stores the list of subscription IDs for all subscriptions so that it 4669 * could be retrieve for unsubscriptions. 4670 * @private 4671 */ 4672 _subscriptionID = {}, 4673 4674 /** 4675 * The possible states of the JabberWerx BOSH connection. 4676 * @private 4677 */ 4678 _STATUS = { 4679 CONNECTING: "connecting", 4680 CONNECTED: "connected", 4681 DISCONNECTED: "disconnected", 4682 DISCONNECTED_CONFLICT: "conflict", 4683 DISCONNECTED_UNAUTHORIZED: "unauthorized", 4684 DISCONNECTING: "disconnecting", 4685 RECONNECTING: "reconnecting", 4686 UNLOADING: "unloading", 4687 FAILING: "failing", 4688 RECOVERED: "recovered" 4689 }, 4690 4691 /** 4692 * Local reference for authMode enum object. 4693 * @private 4694 */ 4695 _authModes, 4696 4697 _failoverMode = false, 4698 4699 /** 4700 * Handler function to be invoked when BOSH connection is connecting. 4701 * @private 4702 */ 4703 _onConnectingHandler, 4704 4705 /** 4706 * Handler function to be invoked when BOSH connection is connected 4707 * @private 4708 */ 4709 _onConnectHandler, 4710 4711 /** 4712 * Handler function to be invoked when BOSH connection is disconnecting. 4713 * @private 4714 */ 4715 _onDisconnectingHandler, 4716 4717 /** 4718 * Handler function to be invoked when the BOSH is disconnected. 4719 * @private 4720 */ 4721 _onDisconnectHandler, 4722 4723 /** 4724 * Handler function to be invoked when the BOSH is reconnecting. 4725 * @private 4726 */ 4727 _onReconnectingHandler, 4728 4729 /** 4730 * Handler function to be invoked when the BOSH is unloading. 4731 * @private 4732 */ 4733 _onUnloadingHandler, 4734 4735 /** 4736 * Contains a cache of the latest connection info containing the current 4737 * state of the BOSH connection and the resource ID. 4738 * @private 4739 */ 4740 _connInfo, 4741 4742 /** 4743 * Keeps track of all the objects that need to be refreshed when we recover 4744 * due to our resilient connection. Only objects that we subscribe to will 4745 * be added to this list. 4746 * @private 4747 */ 4748 _refreshList = [], 4749 4750 /** 4751 * Needs to be passed as authorization header inside makeRequest wrapper function 4752 */ 4753 _authHeaderString, 4754 4755 /** 4756 * @private 4757 * Centralized logger.log method for external logger 4758 * @param {String} msg 4759 */ 4760 _log = function (msg) { 4761 // If the external logger throws up, it stops here. 4762 try { 4763 if (_logger.log) { 4764 _logger.log("[ClientServices] " + msg); 4765 } 4766 } catch (e) { } 4767 }, 4768 4769 /** 4770 * Go through each object in the _refreshList and call its refresh() function 4771 * @private 4772 */ 4773 _refreshObjects = function () { 4774 var i; 4775 4776 // wipe out the explicit subscription list before we refresh objects 4777 if (_publisher) { 4778 _publisher.wipeout(); 4779 } 4780 4781 // refresh each item in the refresh list 4782 for (i = _refreshList.length - 1; i >= 0; i -= 1) { 4783 _log("Refreshing " + _refreshList[i].getRestUrl()); 4784 _refreshList[i].refresh(10); 4785 } 4786 }, 4787 4788 /** 4789 * Handler to process connection info publishes. 4790 * @param {Object} data 4791 * The connection info data object. 4792 * @param {String} data.status 4793 * The BOSH connection status. 4794 * @param {String} data.resourceID 4795 * The resource ID for the connection. 4796 * @private 4797 */ 4798 _connInfoHandler = function (data) { 4799 4800 //Invoke registered handler depending on status received. Due to the 4801 //request topic where clients can make request for the Master to publish 4802 //the connection info, there is a chance that duplicate connection info 4803 //events may be sent, so ensure that there has been a state change 4804 //before invoking the handlers. 4805 if (_connInfo === undefined || _connInfo.status !== data.status) { 4806 _connInfo = data; 4807 switch (data.status) { 4808 case _STATUS.CONNECTING: 4809 if (_isMaster && _onConnectingHandler) { 4810 _onConnectingHandler(); 4811 } 4812 break; 4813 case _STATUS.CONNECTED: 4814 if ((_isMaster || !_failoverMode) && _onConnectHandler) { 4815 _onConnectHandler(); 4816 } 4817 break; 4818 case _STATUS.DISCONNECTED: 4819 if (_isMaster && _onDisconnectHandler) { 4820 _onDisconnectHandler(); 4821 } 4822 break; 4823 case _STATUS.DISCONNECTED_CONFLICT: 4824 if (_isMaster && _onDisconnectHandler) { 4825 _onDisconnectHandler("conflict"); 4826 } 4827 break; 4828 case _STATUS.DISCONNECTED_UNAUTHORIZED: 4829 if (_isMaster && _onDisconnectHandler) { 4830 _onDisconnectHandler("unauthorized"); 4831 } 4832 break; 4833 case _STATUS.DISCONNECTING: 4834 if (_isMaster && _onDisconnectingHandler) { 4835 _onDisconnectingHandler(); 4836 } 4837 break; 4838 case _STATUS.RECONNECTING: 4839 if (_isMaster && _onReconnectingHandler) { 4840 _onReconnectingHandler(); 4841 } 4842 break; 4843 case _STATUS.UNLOADING: 4844 if (_isMaster && _onUnloadingHandler) { 4845 _onUnloadingHandler(); 4846 } 4847 break; 4848 case _STATUS.FAILING: 4849 if (!_isMaster) { 4850 // Stop 4851 _failoverMode = true; 4852 if (_onDisconnectHandler) { 4853 _onDisconnectHandler(); 4854 } 4855 } 4856 break; 4857 case _STATUS.RECOVERED: 4858 if (!_isMaster) { 4859 _failoverMode = false; 4860 if (_onConnectHandler) { 4861 _onConnectHandler(); 4862 } 4863 } 4864 // Whenever we are recovered, we need to refresh any objects 4865 // that are stored. 4866 _refreshObjects(); 4867 break; 4868 } 4869 } 4870 }, 4871 4872 /** 4873 * Ensure that ClientServices have been inited. 4874 * @private 4875 */ 4876 _isInited = function () { 4877 if (!_inited) { 4878 throw new Error("ClientServices needs to be inited."); 4879 } 4880 }, 4881 4882 /** 4883 * Have the client become the Master by initiating a tunnel to a shared 4884 * event BOSH connection. The Master is responsible for publishing all 4885 * events to the pubsub infrastructure. 4886 * 4887 * TODO: Currently we only check the global auth mode. This code has to 4888 * handle mixed mode - in this case the user specfic SSO mode has to be 4889 * exposed via an API. 4890 * 4891 * @private 4892 */ 4893 _becomeMaster = function () { 4894 var creds , id; 4895 _tunnel = new MasterTunnel(_config.host, _config.scheme); 4896 _publisher = new MasterPublisher(_tunnel, _hub); 4897 if(_authModes.SSO === _config.systemAuthMode ) { 4898 creds = _config.authToken; 4899 } else { 4900 creds = _config.password; 4901 } 4902 _util = Utilities; 4903 id = _util.encodeNodeName(_config.id); 4904 _tunnel.init(id, creds, _config.xmppDomain, _config.pubsubDomain, _config.resource); 4905 _isMaster = true; 4906 }, 4907 4908 /** 4909 * Make a request to the request channel to have the Master publish the 4910 * connection info object. 4911 * @private 4912 */ 4913 _makeConnectionInfoReq = function () { 4914 var data = { 4915 type: "ConnectionInfoReq", 4916 data: {}, 4917 invokeID: (new Date()).getTime() 4918 }; 4919 _hub.publish(_topics.REQUESTS, data); 4920 }, 4921 4922 /** 4923 * Utility method to register a handler which is associated with a 4924 * particular connection status. 4925 * @param {String} status 4926 * The connection status string. 4927 * @param {Function} handler 4928 * The handler to associate with a particular connection status. 4929 * @throws {Error} 4930 * If the handler provided is not a function. 4931 * @private 4932 */ 4933 _registerHandler = function (status, handler) { 4934 if (typeof handler === "function") { 4935 if (_connInfo && _connInfo.status === status) { 4936 handler(); 4937 } 4938 switch (status) { 4939 case _STATUS.CONNECTING: 4940 _onConnectingHandler = handler; 4941 break; 4942 case _STATUS.CONNECTED: 4943 _onConnectHandler = handler; 4944 break; 4945 case _STATUS.DISCONNECTED: 4946 _onDisconnectHandler = handler; 4947 break; 4948 case _STATUS.DISCONNECTING: 4949 _onDisconnectingHandler = handler; 4950 break; 4951 case _STATUS.RECONNECTING: 4952 _onReconnectingHandler = handler; 4953 break; 4954 case _STATUS.UNLOADING: 4955 _onUnloadingHandler = handler; 4956 break; 4957 } 4958 4959 } else { 4960 throw new Error("Callback is not a function"); 4961 } 4962 }, 4963 4964 /** 4965 * Callback function that is called when a refresh access token event message is posted to the Hub. 4966 * 4967 * Get access token from the data and update the finesse.gadget.Config object! 4968 * 4969 * @param {String} topic 4970 * which topic the event came on (unused) 4971 * @param {Object} data 4972 * the data published with the event 4973 * @private 4974 */ 4975 _accessTokenRefreshHandler = function(topic , data){ 4976 _log("Access token refreshed - topic :" + topic + ", authToken :" + data.authToken); 4977 4978 if(data.authToken){ 4979 _config.authToken = data.authToken; 4980 if(finesse.gadget && finesse.gadget.Config){ 4981 finesse.gadget.Config.authToken = data.authToken; 4982 } 4983 } 4984 }, 4985 4986 /** 4987 * @private 4988 * Retrieves systemAuthMode from parent Finesse Container. If parent is not available, mode will be retrieved from the systemInfo rest object 4989 * @throws {Error} 4990 * If unable to retrieve systemAuthMode 4991 */ 4992 _getSystemAuthMode = function(){ 4993 var parentFinesse , sysInfo; 4994 // For gadgets hosted outside of finesse container , finesse parent object will not be available 4995 try{ 4996 parentFinesse = window.parent.finesse; 4997 } catch (e){ 4998 parentFinesse = undefined; 4999 } 5000 5001 if( parentFinesse ){ 5002 _config.systemAuthMode = parentFinesse.container.Config.systemAuthMode; 5003 } else { 5004 sysInfo = new finesse.restservices.SystemInfo({ 5005 id: _config.id, 5006 onLoad: function (systemInfo) { 5007 _config.systemAuthMode = systemInfo.getSystemAuthMode(); 5008 }, 5009 onError: function (errRsp) { 5010 throw new Error("Unable to retrieve systemAuthMode from config. Initialization failed......"); 5011 } 5012 }); 5013 5014 } 5015 }; 5016 5017 return { 5018 5019 /** 5020 * @private 5021 * Adds an item to the list to be refreshed upon reconnect 5022 * @param {RestBase} object - rest object to be refreshed 5023 */ 5024 addToRefreshList: function (object) { 5025 _refreshList.push(object); 5026 }, 5027 5028 /** 5029 * @private 5030 * Removes the given item from the refresh list 5031 * @param {RestBase} object - rest object to be removed 5032 */ 5033 removeFromRefreshList: function (object) { 5034 var i; 5035 for (i = _refreshList.length - 1; i >= 0; i -= 1) { 5036 if (_refreshList[i] === object) { 5037 _refreshList.splice(i, 1); 5038 break; 5039 } 5040 } 5041 }, 5042 5043 /** 5044 * @private 5045 * The location of the tunnel HTML URL. 5046 * @returns {String} 5047 * The location of the tunnel HTML URL. 5048 */ 5049 getTunnelURL: function () { 5050 return _tunnel.getTunnelURL(); 5051 }, 5052 5053 /** 5054 * @private 5055 * Indicates whether the tunnel frame is loaded. 5056 * @returns {Boolean} 5057 * True if the tunnel frame is loaded, false otherwise. 5058 */ 5059 isTunnelLoaded: function () { 5060 return _tunnel.isTunnelLoaded(); 5061 }, 5062 5063 /** 5064 * @private 5065 * Indicates whether the ClientServices instance is a Master. 5066 * @returns {Boolean} 5067 * True if this instance of ClientServices is a Master, false otherwise. 5068 */ 5069 isMaster: function () { 5070 return _isMaster; 5071 }, 5072 5073 /** 5074 * @private 5075 * Get the resource ID. An ID is only available if the BOSH connection has 5076 * been able to connect successfully. 5077 * @returns {String} 5078 * The resource ID string. Null if the BOSH connection was never 5079 * successfully created and/or the resource ID has not been associated. 5080 */ 5081 getResourceID: function () { 5082 if (_connInfo !== undefined) { 5083 return _connInfo.resourceID; 5084 } 5085 return null; 5086 }, 5087 5088 /* 5089 getHub: function () { 5090 return _hub; 5091 }, 5092 */ 5093 /** 5094 * @private 5095 * Add a callback to be invoked when the BOSH connection is attempting 5096 * to connect. If the connection is already trying to connect, the 5097 * callback will be invoked immediately. 5098 * @param {Function} handler 5099 * An empty param function to be invoked on connecting. Only one 5100 * handler can be registered at a time. Handlers already registered 5101 * will be overwritten. 5102 */ 5103 registerOnConnectingHandler: function (handler) { 5104 _registerHandler(_STATUS.CONNECTING, handler); 5105 }, 5106 5107 /** 5108 * @private 5109 * Removes the on connecting callback that was registered. 5110 */ 5111 unregisterOnConnectingHandler: function () { 5112 _onConnectingHandler = undefined; 5113 }, 5114 5115 /** 5116 * Add a callback to be invoked when all of the following conditions are met: 5117 * <ul> 5118 * <li>When Finesse goes IN_SERVICE</li> 5119 * <li>The BOSH connection is established</li> 5120 * <li>The Finesse user presence becomes available</li> 5121 * </ul> 5122 * If all these conditions are met at the time this function is called, then 5123 * the handler will be invoked immediately. 5124 * @param {Function} handler 5125 * An empty param function to be invoked on connect. Only one handler 5126 * can be registered at a time. Handlers already registered will be 5127 * overwritten. 5128 * @example 5129 * finesse.clientservices.ClientServices.registerOnConnectHandler(gadget.myCallback); 5130 */ 5131 registerOnConnectHandler: function (handler) { 5132 _registerHandler(_STATUS.CONNECTED, handler); 5133 }, 5134 5135 /** 5136 * @private 5137 * Removes the on connect callback that was registered. 5138 */ 5139 unregisterOnConnectHandler: function () { 5140 _onConnectHandler = undefined; 5141 }, 5142 5143 /** 5144 * Add a callback to be invoked when any of the following occurs: 5145 * <ul> 5146 * <li>Finesse is no longer IN_SERVICE</li> 5147 * <li>The BOSH connection is lost</li> 5148 * <li>The presence of the Finesse user is no longer available</li> 5149 * </ul> 5150 * If any of these conditions are met at the time this function is 5151 * called, the callback will be invoked immediately. 5152 * @param {Function} handler 5153 * An empty param function to be invoked on disconnected. Only one 5154 * handler can be registered at a time. Handlers already registered 5155 * will be overwritten. 5156 * @example 5157 * finesse.clientservices.ClientServices.registerOnDisconnectHandler(gadget.myCallback); 5158 */ 5159 registerOnDisconnectHandler: function (handler) { 5160 _registerHandler(_STATUS.DISCONNECTED, handler); 5161 }, 5162 5163 /** 5164 * @private 5165 * Removes the on disconnect callback that was registered. 5166 */ 5167 unregisterOnDisconnectHandler: function () { 5168 _onDisconnectHandler = undefined; 5169 }, 5170 5171 /** 5172 * @private 5173 * Add a callback to be invoked when the BOSH is currently disconnecting. If 5174 * the connection is already disconnecting, invoke the callback immediately. 5175 * @param {Function} handler 5176 * An empty param function to be invoked on disconnected. Only one 5177 * handler can be registered at a time. Handlers already registered 5178 * will be overwritten. 5179 */ 5180 registerOnDisconnectingHandler: function (handler) { 5181 _registerHandler(_STATUS.DISCONNECTING, handler); 5182 }, 5183 5184 /** 5185 * @private 5186 * Removes the on disconnecting callback that was registered. 5187 */ 5188 unregisterOnDisconnectingHandler: function () { 5189 _onDisconnectingHandler = undefined; 5190 }, 5191 5192 /** 5193 * @private 5194 * Add a callback to be invoked when the BOSH connection is attempting 5195 * to connect. If the connection is already trying to connect, the 5196 * callback will be invoked immediately. 5197 * @param {Function} handler 5198 * An empty param function to be invoked on connecting. Only one 5199 * handler can be registered at a time. Handlers already registered 5200 * will be overwritten. 5201 */ 5202 registerOnReconnectingHandler: function (handler) { 5203 _registerHandler(_STATUS.RECONNECTING, handler); 5204 }, 5205 5206 /** 5207 * @private 5208 * Removes the on reconnecting callback that was registered. 5209 */ 5210 unregisterOnReconnectingHandler: function () { 5211 _onReconnectingHandler = undefined; 5212 }, 5213 5214 /** 5215 * @private 5216 * Add a callback to be invoked when the BOSH connection is unloading 5217 * 5218 * @param {Function} handler 5219 * An empty param function to be invoked on connecting. Only one 5220 * handler can be registered at a time. Handlers already registered 5221 * will be overwritten. 5222 */ 5223 registerOnUnloadingHandler: function (handler) { 5224 _registerHandler(_STATUS.UNLOADING, handler); 5225 }, 5226 5227 /** 5228 * @private 5229 * Removes the on unloading callback that was registered. 5230 */ 5231 unregisterOnUnloadingHandler: function () { 5232 _onUnloadingHandler = undefined; 5233 }, 5234 5235 /** 5236 * @private 5237 * Proxy method for gadgets.io.makeRequest. The will be identical to gadgets.io.makeRequest 5238 * ClientServices will mixin the BASIC Auth string, locale, and host, since the 5239 * configuration is encapsulated in here anyways. 5240 * This removes the dependency 5241 * @param {String} url 5242 * The relative url to make the request to (the host from the passed in config will be 5243 * appended). It is expected that any encoding to the URL is already done. 5244 * @param {Function} handler 5245 * Callback handler for makeRequest to invoke when the response returns. 5246 * Completely passed through to gadgets.io.makeRequest 5247 * @param {Object} params 5248 * The params object that gadgets.io.makeRequest expects. Authorization and locale 5249 * headers are mixed in. 5250 */ 5251 makeRequest: function (url, handler, params) { 5252 var requestedScheme, scheme = "http"; 5253 5254 // ClientServices needs to be initialized with a config for restHost, auth, and locale 5255 _isInited(); 5256 5257 // Allow mixin of auth and locale headers 5258 params = params || {}; 5259 5260 // Override refresh interval to 0 instead of default 3600 as a way to workaround makeRequest 5261 // using GET http method because then the params are added to the url as query params, which 5262 // exposes the authorization string in the url. This is a placeholder until oauth comes in 5263 params[gadgets.io.RequestParameters.REFRESH_INTERVAL] = params[gadgets.io.RequestParameters.REFRESH_INTERVAL] || 0; 5264 5265 params[gadgets.io.RequestParameters.HEADERS] = params[gadgets.io.RequestParameters.HEADERS] || {}; 5266 5267 // Add Basic auth to request header 5268 params[gadgets.io.RequestParameters.HEADERS].Authorization = _util.getAuthHeaderString(_config); 5269 5270 //Locale 5271 params[gadgets.io.RequestParameters.HEADERS].locale = _config.locale; 5272 5273 //Allow clients to override the scheme: 5274 // - If not specified => we use HTTP 5275 // - If null specified => we use _config.scheme 5276 // - Otherwise => we use whatever they provide 5277 requestedScheme = params.SCHEME; 5278 if (!(requestedScheme === undefined || requestedScheme === "undefined")) { 5279 if (requestedScheme === null) { 5280 scheme = _config.scheme; 5281 } else { 5282 scheme = requestedScheme; 5283 } 5284 } 5285 scheme = _config.restScheme || scheme; 5286 5287 _log("RequestedScheme: " + requestedScheme + "; Scheme: " + scheme); 5288 gadgets.io.makeRequest(encodeURI(scheme + "://" + _config.restHost + ":" + _config.localhostPort) + url, handler, params); 5289 }, 5290 5291 /** 5292 * @private 5293 * Utility function to make a subscription to a particular topic. Only one 5294 * callback function is registered to a particular topic at any time. 5295 * @param {String} topic 5296 * The full topic name. The topic name should follow the OpenAjax 5297 * convention using dot notation (ex: finesse.api.User.1000). 5298 * @param {Function} callback 5299 * The function that should be invoked with the data when an event 5300 * is delivered to the specific topic. 5301 * @returns {Boolean} 5302 * True if the subscription was made successfully and the callback was 5303 * been registered. False if the subscription already exist, the 5304 * callback was not overwritten. 5305 */ 5306 subscribe: function (topic, callback, disableDuringFailover) { 5307 _isInited(); 5308 5309 //Ensure that the same subscription isn't made twice. 5310 if (!_subscriptionID[topic]) { 5311 //Store the subscription ID using the topic name as the key. 5312 _subscriptionID[topic] = _hub.subscribe(topic, 5313 //Invoke the callback just with the data object. 5314 function (topic, data) { 5315 if (!disableDuringFailover || _isMaster || !_failoverMode) { 5316 // Halt all intermediate event processing while the master instance attempts to rebuild the connection. This only occurs: 5317 // - For RestBase objects (which pass the disableDuringFailover flag), so that intermediary events while recovering are hidden away until everything is all good 5318 // - We shouldn't be halting anything else because we have infrastructure requests that use the hub pub sub 5319 // - Master instance will get all events regardless, because it is responsible for managing failover 5320 // - If we are not in a failover mode, everything goes 5321 // _refreshObjects will reconcile anything that was missed once we are back in action 5322 callback(data); 5323 } 5324 }); 5325 return true; 5326 } 5327 return false; 5328 }, 5329 5330 /** 5331 * @private 5332 * Unsubscribe from a particular topic. 5333 * @param {String} topic 5334 * The full topic name. 5335 */ 5336 unsubscribe: function (topic) { 5337 _isInited(); 5338 5339 //Unsubscribe from the topic using the subscription ID recorded when 5340 //the subscription was made, then delete the ID from data structure. 5341 if (_subscriptionID[topic]) { 5342 _hub.unsubscribe(_subscriptionID[topic]); 5343 delete _subscriptionID[topic]; 5344 } 5345 }, 5346 5347 /** 5348 * @private 5349 * Make a request to the request channel to have the Master subscribe 5350 * to a node. 5351 * @param {String} node 5352 * The node to subscribe to. 5353 */ 5354 subscribeNode: function (node, handler) { 5355 if (handler && typeof handler !== "function") { 5356 throw new Error("ClientServices.subscribeNode: handler is not a function"); 5357 } 5358 5359 // Construct the request to send to MasterPublisher through the OpenAjax Hub 5360 var data = { 5361 type: "SubscribeNodeReq", 5362 data: {node: node}, 5363 invokeID: _util.generateUUID() 5364 }, 5365 responseTopic = _topics.RESPONSES + "." + data.invokeID, 5366 _this = this; 5367 5368 // We need to first subscribe to the response channel 5369 this.subscribe(responseTopic, function (rsp) { 5370 // Since this channel is only used for this singular request, 5371 // we are not interested anymore. 5372 // This is also critical to not leaking memory by having OpenAjax 5373 // store a bunch of orphaned callback handlers that enclose on 5374 // our entire ClientServices singleton 5375 _this.unsubscribe(responseTopic); 5376 if (handler) { 5377 handler(data.invokeID, rsp); 5378 } 5379 }); 5380 // Then publish the request on the request channel 5381 _hub.publish(_topics.REQUESTS, data); 5382 }, 5383 5384 /** 5385 * @private 5386 * Make a request to the request channel to have the Master unsubscribe 5387 * from a node. 5388 * @param {String} node 5389 * The node to unsubscribe from. 5390 */ 5391 unsubscribeNode: function (node, subid, handler) { 5392 if (handler && typeof handler !== "function") { 5393 throw new Error("ClientServices.unsubscribeNode: handler is not a function"); 5394 } 5395 5396 // Construct the request to send to MasterPublisher through the OpenAjax Hub 5397 var data = { 5398 type: "UnsubscribeNodeReq", 5399 data: { 5400 node: node, 5401 subid: subid 5402 }, 5403 invokeID: _util.generateUUID() 5404 }, 5405 responseTopic = _topics.RESPONSES + "." + data.invokeID, 5406 _this = this; 5407 5408 // We need to first subscribe to the response channel 5409 this.subscribe(responseTopic, function (rsp) { 5410 // Since this channel is only used for this singular request, 5411 // we are not interested anymore. 5412 // This is also critical to not leaking memory by having OpenAjax 5413 // store a bunch of orphaned callback handlers that enclose on 5414 // our entire ClientServices singleton 5415 _this.unsubscribe(responseTopic); 5416 if (handler) { 5417 handler(rsp); 5418 } 5419 }); 5420 // Then publish the request on the request channel 5421 _hub.publish(_topics.REQUESTS, data); 5422 }, 5423 5424 /** 5425 * @private 5426 * Make a request to the request channel to have the Master connect to the XMPP server via BOSH 5427 */ 5428 makeConnectionReq : function () { 5429 // Disallow others (non-masters) from administering BOSH connections that are not theirs 5430 if (_isMaster && _publisher) { 5431 _publisher.connect(_config.id, _config.password, _config.xmppDomain); 5432 } else { 5433 _log("F403: Access denied. Non-master ClientService instances do not have the clearance level to make this request: makeConnectionReq"); 5434 } 5435 }, 5436 5437 /** 5438 * @private 5439 * Set's the global logger for this Client Services instance. 5440 * @param {Object} logger 5441 * Logger object with the following attributes defined:<ul> 5442 * <li><b>log:</b> function (msg) to simply log a message 5443 * </ul> 5444 */ 5445 setLogger: function (logger) { 5446 // We want to check the logger coming in so we don't have to check every time it is called. 5447 if (logger && typeof logger === "object" && typeof logger.log === "function") { 5448 _logger = logger; 5449 } else { 5450 // We are resetting it to an empty object so that _logger.log in .log is falsy. 5451 _logger = {}; 5452 } 5453 }, 5454 5455 /** 5456 * @private 5457 * Centralized logger.log method for external logger 5458 * @param {String} msg 5459 * Message to log 5460 */ 5461 log: _log, 5462 5463 /** 5464 * @class 5465 * Allow clients to make Finesse API requests and consume Finesse events by 5466 * calling a set of exposed functions. The Services layer will do the dirty 5467 * work of establishing a shared BOSH connection (for designated Master 5468 * modules), consuming events for client subscriptions, and constructing API 5469 * requests. 5470 * 5471 * @constructs 5472 */ 5473 _fakeConstuctor: function () { 5474 /* This is here so we can document init() as a method rather than as a constructor. */ 5475 }, 5476 5477 /** 5478 * Initiates the Client Services with the specified config parameters. 5479 * Enabling the Client Services as Master will trigger the establishment 5480 * of a BOSH event connection. 5481 * @param {finesse.gadget.Config} config 5482 * Configuration object containing properties used for making REST requests:<ul> 5483 * <li><b>host:</b> The Finesse server IP/host as reachable from the browser 5484 * <li><b>restHost:</b> The Finesse API IP/host as reachable from the gadget container 5485 * <li><b>id:</b> The ID of the user. This is an optional param as long as the 5486 * appropriate authorization string is provided, otherwise it is 5487 * required.</li> 5488 * <li><b>password:</b> The password belonging to the user. This is an optional param as 5489 * long as the appropriate authorization string is provided, 5490 * otherwise it is required.</li> 5491 * <li><b>authorization:</b> The base64 encoded "id:password" authentication string. This 5492 * param is provided to allow the ability to hide the password 5493 * param. If provided, the id and the password extracted from this 5494 * string will be used over the config.id and config.password.</li> 5495 * </ul> 5496 * @throws {Error} If required constructor parameter is missing. 5497 * @example 5498 * finesse.clientservices.ClientServices.init(finesse.gadget.Config); 5499 */ 5500 init: function (config) { 5501 if (!_inited) { 5502 //Validate the properties within the config object if one is provided. 5503 if (!(typeof config === "object" && 5504 typeof config.host === "string" && config.host.length > 0 && config.restHost && 5505 (typeof config.authorization === "string" || 5506 (typeof config.id === "string")))) { 5507 throw new Error("Config object contains invalid properties."); 5508 } 5509 5510 // Initialize configuration 5511 _config = config; 5512 5513 // Set shortcuts 5514 _util = Utilities; 5515 _authModes = _util.getAuthModes(); 5516 _topics = Topics; 5517 5518 //TODO: document when this is properly supported 5519 // Allows hub and io dependencies to be passed in. Currently only used for unit tests. 5520 _hub = config.hub || gadgets.Hub; 5521 _io = config.io || gadgets.io; 5522 5523 //If the authorization string is provided, then use that to 5524 //extract the ID and the password. Otherwise use the ID and 5525 //password from the respective ID and password params. 5526 if (_config.authorization) { 5527 var creds = _util.getCredentials(_config.authorization); 5528 _config.id = creds.id; 5529 _config.password = creds.password; 5530 } 5531 else { 5532 _config.authorization = _util.b64Encode( 5533 _config.id + ":" + _config.password); 5534 } 5535 5536 //In case if gadgets create their own config instance , add systemAuthMode property inside config object 5537 if(!_config.systemAuthMode || _config.systemAuthMode === ""){ 5538 _getSystemAuthMode(); 5539 } 5540 5541 if(_config.systemAuthMode === _authModes.SSO){ 5542 _accessTokenRefreshHandler(undefined , {authToken : _util.getToken()}); 5543 if(!_config.authToken){ 5544 throw new Error("ClientServices.init() - Access token is unavailable inside Config object."); 5545 } 5546 5547 if (_hub){ 5548 _hub.subscribe(_topics.ACCESS_TOKEN_REFRESHED_EVENT, _accessTokenRefreshHandler); 5549 } 5550 } 5551 5552 _inited = true; 5553 5554 if (_hub) { 5555 //Subscribe to receive connection information. Since it is possible that 5556 //the client comes up after the Master comes up, the client will need 5557 //to make a request to have the Master send the latest connection info. 5558 //It would be possible that all clients get connection info again. 5559 this.subscribe(_topics.EVENTS_CONNECTION_INFO, _connInfoHandler); 5560 _makeConnectionInfoReq(); 5561 } 5562 } 5563 5564 //Return the CS object for object chaining. 5565 return this; 5566 }, 5567 5568 /** 5569 * @private 5570 * Initializes the BOSH component of this ClientServices instance. This establishes 5571 * the BOSH connection and will trigger the registered handlers as the connection 5572 * status changes respectively:<ul> 5573 * <li>registerOnConnectingHandler</li> 5574 * <li>registerOnConnectHandler</li> 5575 * <li>registerOnDisconnectHandler</li> 5576 * <li>registerOnDisconnectingHandler</li> 5577 * <li>registerOnReconnectingHandler</li> 5578 * <li>registerOnUnloadingHandler</li> 5579 * <ul> 5580 * 5581 * @param {Object} config 5582 * An object containing the following (optional) handlers for the request:<ul> 5583 * <li><b>xmppDomain:</b> {String} The domain of the XMPP server. Available from the SystemInfo object. 5584 * This is used to construct the JID: user@domain.com</li> 5585 * <li><b>pubsubDomain:</b> {String} The pub sub domain where the pub sub service is running. 5586 * Available from the SystemInfo object. 5587 * This is used for creating or removing subscriptions.</li> 5588 * <li><b>resource:</b> {String} The resource to connect to the notification server with.</li> 5589 * </ul> 5590 */ 5591 initBosh: function (config) { 5592 //Validate the properties within the config object if one is provided. 5593 if (!(typeof config === "object" && typeof config.xmppDomain === "string" && typeof config.pubsubDomain === "string")) { 5594 throw new Error("Config object contains invalid properties."); 5595 } 5596 5597 // Mixin the required information for establishing the BOSH connection 5598 _config.xmppDomain = config.xmppDomain; 5599 _config.pubsubDomain = config.pubsubDomain; 5600 _config.resource = config.resource; 5601 5602 //Initiate Master launch sequence 5603 _becomeMaster(); 5604 }, 5605 5606 /** 5607 * @private 5608 * Sets the failover mode to either FAILING or RECOVERED. This will only occur in the master instance and is meant to be 5609 * used by a stereotypical failover monitor type module to notify non-master instances (i.e. in gadgets) 5610 * @param {Object} failoverMode 5611 * true if failing, false or something falsy when recovered 5612 */ 5613 setFailoverMode: function (failoverMode) { 5614 if (_isMaster) { 5615 _hub.publish(_topics.EVENTS_CONNECTION_INFO, { 5616 status: (failoverMode ? _STATUS.FAILING : _STATUS.RECOVERED) 5617 }); 5618 } 5619 }, 5620 5621 /** 5622 * @private 5623 * Private accessor used to inject mocked private dependencies for unit testing 5624 */ 5625 _getTestObj: function () { 5626 return { 5627 setPublisher: function (publisher) { 5628 _publisher = publisher; 5629 } 5630 }; 5631 } 5632 }; 5633 }()); 5634 5635 window.finesse = window.finesse || {}; 5636 window.finesse.clientservices = window.finesse.clientservices || {}; 5637 window.finesse.clientservices.ClientServices = ClientServices; 5638 5639 return ClientServices; 5640 5641 }); 5642 /** 5643 * The following comment prevents JSLint errors concerning undefined global variables. 5644 * It tells JSLint that these identifiers are defined elsewhere. 5645 */ 5646 /*jslint bitwise:true, browser:true, nomen:true, regexp:true, sloppy:true, white:true */ 5647 5648 /** The following comment is to prevent jslint errors about 5649 * using variables before they are defined. 5650 */ 5651 /*global Handlebars */ 5652 5653 /** 5654 * JavaScript class to implement common notification 5655 * functionality. 5656 * 5657 * @requires Class 5658 * @requires finesse.FinesseBase 5659 */ 5660 /** @private */ 5661 define('restservices/Notifier',[ 5662 'FinesseBase', 5663 'clientservices/ClientServices' 5664 ], 5665 function (FinesseBase, ClientServices) { 5666 var Notifier = FinesseBase.extend({ 5667 /** 5668 * Initializes the notifier object. 5669 */ 5670 init : function () { 5671 this._super(); 5672 this._listenerCallback = []; 5673 }, 5674 5675 /** 5676 * Add a listener. 5677 * 5678 * @param callback_function 5679 * @param scope 5680 * is the callback function to add 5681 */ 5682 addListener : function (callback_function, scope) { 5683 var len = this._listenerCallback.length, i, cb, add = true; 5684 for (i = 0; i < len; i += 1) { 5685 cb = this._listenerCallback[i].callback; 5686 if (cb === callback_function) { 5687 // this callback already exists 5688 add = false; 5689 break; 5690 } 5691 } 5692 if (add) { 5693 this._listenerCallback.push({ "callback": this._isAFunction(callback_function), "scope": (scope || window) }); 5694 } 5695 }, 5696 5697 /** 5698 * Remove a listener. 5699 * 5700 * @param callback_function 5701 * is the callback function to remove 5702 * @return {Boolean} true if removed 5703 */ 5704 removeListener : function (callback_function) { 5705 5706 var result = false, len = this._listenerCallback.length, i, cb; 5707 for (i = len - 1; i >= 0; i -=1) { 5708 cb = this._listenerCallback[i].callback; 5709 if (cb === callback_function) { 5710 this._listenerCallback[i] = undefined; 5711 this._listenerCallback.splice(i, 1); 5712 result = true; 5713 break; 5714 } 5715 } 5716 5717 return result; 5718 }, 5719 5720 /** 5721 * Removes all listeners 5722 * @return {undefined} 5723 */ 5724 reset: function () { 5725 this._listenerCallback = []; 5726 }, 5727 5728 /** 5729 * Notify all listeners. 5730 * 5731 * @param obj 5732 * is the object that has changed 5733 */ 5734 notifyListeners : function (obj) { 5735 var len = this._listenerCallback.length, i, callbackFunction, scope; 5736 5737 for (i = 0; i < len; i += 1) { 5738 // Be sure that one bad callback does not prevent other listeners 5739 // from receiving. 5740 try { 5741 callbackFunction = this._listenerCallback[i].callback; 5742 scope = this._listenerCallback[i].scope; 5743 if (typeof callbackFunction === 'function') { 5744 callbackFunction.call(scope, obj); 5745 } 5746 } catch (err) { 5747 ClientServices.log("Exception caught: " + err); 5748 } 5749 } 5750 }, 5751 5752 /** 5753 * Gets a copy of the listeners. 5754 * @return changeListenerCopy (array of callbacks) 5755 */ 5756 getListeners : function () { 5757 var changeListenerCopy = [], len = this._listenerCallback.length, i; 5758 5759 for (i = 0; i < len; i += 1) { 5760 changeListenerCopy.push(this._listenerCallback[i].callback); 5761 } 5762 5763 return changeListenerCopy; 5764 }, 5765 5766 /** 5767 * Verifies that the handler is function. 5768 * @param handler to verify 5769 * @return the handler 5770 * @throws Error if not a function 5771 */ 5772 _isAFunction : function (handler) { 5773 if (handler === undefined || typeof handler === "function") { 5774 return handler; 5775 } else { 5776 throw new Error("handler must be a function"); 5777 } 5778 } 5779 }); 5780 5781 window.finesse = window.finesse || {}; 5782 window.finesse.restservices = window.finesse.restservices || {}; 5783 window.finesse.restservices.Notifier = Notifier; 5784 5785 /** @namespace JavaScript classes and methods that represent REST objects and collections. */ 5786 finesse.restservices = finesse.restservices || {}; 5787 5788 return Notifier; 5789 }); 5790 5791 /** 5792 * JavaScript base object that all REST objects should inherit 5793 * from because it encapsulates and provides the common functionality that 5794 * all REST objects need. 5795 * 5796 * @requires finesse.clientservices.ClientServices 5797 * @requires Class 5798 */ 5799 5800 /** @private */ 5801 define('restservices/RestBase',[ 5802 "FinesseBase", 5803 "utilities/Utilities", 5804 "restservices/Notifier", 5805 "clientservices/ClientServices", 5806 "clientservices/Topics" 5807 ], 5808 function (FinesseBase, Utilities, Notifier, ClientServices, Topics) { 5809 5810 var RestBase = FinesseBase.extend(/** @lends finesse.restservices.RestBase.prototype */{ 5811 5812 doNotLog: false, 5813 5814 /** 5815 * Used by _processUpdate() and restRequest(). 5816 * Maps requestIds to object-wrapped callbacks passed to restRequest(), 5817 * so that one of the callbacks can be fired when a corresponding event is 5818 * received inside _processUpdate(). 5819 * @private 5820 */ 5821 _pendingCallbacks: {}, 5822 5823 /** 5824 * Gets the REST class for the current object. This object throws an 5825 * exception because subtype must implement. 5826 * @throws {Error} because subtype must implement 5827 * @private 5828 */ 5829 getRestClass: function () { 5830 throw new Error("getRestClass(): Not implemented in subtype."); 5831 }, 5832 5833 /** 5834 * Gets the REST type for the current object. This object throws an 5835 * exception because subtype must implement. 5836 * @throws {Error} because subtype must implement. 5837 * @private 5838 */ 5839 getRestType: function () { 5840 throw new Error("getRestType(): Not implemented in subtype."); 5841 }, 5842 5843 /** 5844 * Gets the node path for the current object. 5845 * @private 5846 */ 5847 getXMPPNodePath: function () { 5848 return this.getRestUrl(); 5849 }, 5850 5851 /** 5852 * Boolean function that specifies whether the REST object supports 5853 * requests. True by default. Subclasses should override if false. 5854 * @private 5855 */ 5856 supportsRequests: true, 5857 5858 /** 5859 * Boolean function that specifies whether the REST object supports 5860 * subscriptions. True by default. Subclasses should override if false. 5861 * @private 5862 */ 5863 supportsSubscriptions: true, 5864 5865 /** 5866 * Boolean function that specifies whether the REST object should retain 5867 * a copy of the REST response. False by default. Subclasses should override if true. 5868 * @private 5869 */ 5870 keepRestResponse: false, 5871 5872 /** 5873 * Number that represents the REST Response status that is returned. Only 5874 * set if keepRestResponse is set to true. 5875 * @public 5876 */ 5877 restResponseStatus: null, 5878 5879 /** 5880 * Object to store additional headers to be sent with the REST Request, null by default. 5881 * @private 5882 */ 5883 extraHeaders: null, 5884 5885 /** 5886 * Boolean function that specifies whether the REST object explicitly 5887 * subscribes. False by default. Subclasses should override if true. 5888 * @private 5889 */ 5890 explicitSubscription: false, 5891 5892 /** 5893 * Boolean function that specifies whether subscribing should be 5894 * automatically done at construction. Defaults to true. 5895 * This be overridden at object construction, not by implementing subclasses 5896 * @private 5897 */ 5898 autoSubscribe: true, 5899 5900 /** 5901 * Private reference to default logger 5902 * @private 5903 */ 5904 _logger: { 5905 log: ClientServices.log, 5906 error: ClientServices.log 5907 }, 5908 5909 /** 5910 * @class 5911 * JavaScript representation of a REST object. Also exposes methods to operate 5912 * on the object against the server. This object is typically extended into individual 5913 * REST Objects (like Dialog, User, etc...), and shouldn't be used directly. 5914 * 5915 * @constructor 5916 * @param {String} id 5917 * The ID that uniquely identifies the REST object. 5918 * @param {Object} callbacks 5919 * An object containing callbacks for instantiation and runtime 5920 * Callback to invoke upon successful instantiation, passes in REST object. 5921 * @param {Function} callbacks.onLoad(this) 5922 * Callback to invoke upon loading the data for the first time. 5923 * @param {Function} callbacks.onChange(this) 5924 * Callback to invoke upon successful update object (PUT) 5925 * @param {Function} callbacks.onAdd(this) 5926 * Callback to invoke upon successful update to add object (POST) 5927 * @param {Function} callbacks.onDelete(this) 5928 * Callback to invoke upon successful update to delete object (DELETE) 5929 * @param {Function} callbacks.onError(rsp) 5930 * Callback to invoke on update error (refresh or event) 5931 * as passed by finesse.restservices.RestBase.restRequest() 5932 * { 5933 * status: {Number} The HTTP status code returned 5934 * content: {String} Raw string of response 5935 * object: {Object} Parsed object of response 5936 * error: {Object} Wrapped exception that was caught 5937 * error.errorType: {String} Type of error that was caught 5938 * error.errorMessage: {String} Message associated with error 5939 * } 5940 * @param {RestBase} [restObj] 5941 * A RestBase parent object which this object has an association with. 5942 * @constructs 5943 */ 5944 init: function (options, callbacks, restObj) { 5945 /** 5946 * Initialize the base class 5947 */ 5948 var _this = this; 5949 5950 this._super(); 5951 5952 if (typeof options === "object") { 5953 this._id = options.id; 5954 this._restObj = options.parentObj; 5955 this.autoSubscribe = (options.autoSubscribe === false) ? false : true; 5956 this.doNotSubscribe = options.doNotSubscribe; 5957 this.doNotRefresh = this.doNotRefresh || options.doNotRefresh; 5958 callbacks = { 5959 onLoad: options.onLoad, 5960 onChange: options.onChange, 5961 onAdd: options.onAdd, 5962 onDelete: options.onDelete, 5963 onError: options.onError 5964 }; 5965 } else { 5966 this._id = options; 5967 this._restObj = restObj; 5968 } 5969 5970 // Common stuff 5971 5972 this._data = {}; 5973 5974 //Contains the full rest response to be processed by upper layers if needed 5975 this._restResponse = undefined; 5976 5977 this._lastUpdate = {}; 5978 5979 this._util = Utilities; 5980 5981 //Should be correctly initialized in either a window OR gadget context 5982 this._config = finesse.container.Config; 5983 5984 // Setup all the notifiers - change, load and error. 5985 this._changeNotifier = new Notifier(); 5986 this._loadNotifier = new Notifier(); 5987 this._addNotifier = new Notifier(); 5988 this._deleteNotifier = new Notifier(); 5989 this._errorNotifier = new Notifier(); 5990 5991 this._loaded = false; 5992 5993 // Protect against null dereferencing of options allowing its 5994 // (nonexistent) keys to be read as undefined 5995 callbacks = callbacks || {}; 5996 5997 this.addHandler('load', callbacks.onLoad); 5998 this.addHandler('change', callbacks.onChange); 5999 this.addHandler('add', callbacks.onAdd); 6000 this.addHandler('delete', callbacks.onDelete); 6001 this.addHandler('error', callbacks.onError); 6002 6003 // Attempt to get the RestType then synchronize 6004 try { 6005 this.getRestType(); 6006 6007 // Only subscribe if this REST object supports subscriptions 6008 // and autoSubscribe was not requested to be disabled as a construction option 6009 if (this.supportsSubscriptions && this.autoSubscribe && !this.doNotSubscribe) { 6010 this.subscribe({ 6011 success: function () { 6012 //TODO: figure out how to use Function.call() or Function.apply() here... 6013 //this is exactly the same as the below else case other than the scope of "this" 6014 if (typeof options === "object" && options.data) { 6015 if (!_this._processObject(_this._normalize(options.data))) { 6016 // notify of error if we fail to construct 6017 _this._errorNotifier.notifyListeners(_this); 6018 } 6019 } else { 6020 // Only subscribe if this REST object supports requests 6021 if (_this.supportsRequests) { 6022 _this._synchronize(); 6023 } 6024 } 6025 }, 6026 error: function (err) { 6027 _this._errorNotifier.notifyListeners(err); 6028 } 6029 }); 6030 } else { 6031 if (typeof options === "object" && options.data) { 6032 if (!this._processObject(this._normalize(options.data))) { 6033 // notify of error if we fail to construct 6034 this._errorNotifier.notifyListeners(this); 6035 } 6036 } else { 6037 // Only subscribe if this REST object supports requests 6038 if (this.supportsRequests) { 6039 this._synchronize(); 6040 } 6041 } 6042 } 6043 6044 } catch (err) { 6045 this._logger.error('id=' + this._id + ': ' + err); 6046 } 6047 }, 6048 6049 /** 6050 * Determines if the object has a particular property. 6051 * @param obj is the object to examine 6052 * @param property is the property to check for 6053 * @returns {Boolean} 6054 */ 6055 hasProperty: function (obj, prop) { 6056 return (obj !== null) && (obj.hasOwnProperty(prop)); 6057 }, 6058 6059 /** 6060 * Gets a property from the object. 6061 * @param obj is the object to examine 6062 * @param property is the property to get 6063 * @returns {Property Value} or {Null} if not found 6064 */ 6065 getProperty: function (obj, property) { 6066 var result = null; 6067 6068 if (this.hasProperty(obj, property) === false) { 6069 result = null; 6070 } else { 6071 result = obj[property]; 6072 } 6073 return result; 6074 }, 6075 6076 /** 6077 * Utility to extracts the ID from the specified REST URI. This is with the 6078 * assumption that the ID is always the last element in the URI after the 6079 * "/" delimiter. 6080 * @param {String} restUri 6081 * The REST uri (i.e. /finesse/api/User/1000). 6082 * @private 6083 */ 6084 _extractId: function (restObj) { 6085 var obj, restUri = "", strLoc; 6086 for (obj in restObj) { 6087 if (restObj.hasOwnProperty(obj)) { 6088 restUri = restObj[obj].uri; 6089 break; 6090 } 6091 } 6092 return Utilities.getId(restUri); 6093 }, 6094 6095 /** 6096 * Gets the data for this object. 6097 * @returns {Object} which is contained in data 6098 */ 6099 getData: function () { 6100 return this._data; 6101 }, 6102 6103 /** 6104 * Gets the complete REST response to the request made 6105 * @returns {Object} which is contained in data 6106 * @private 6107 */ 6108 getRestResponse: function () { 6109 return this._restResponse; 6110 }, 6111 6112 /** 6113 * The REST URL in which this object can be referenced. 6114 * @return {String} 6115 * The REST URI for this object. 6116 * @private 6117 */ 6118 getRestUrl: function () { 6119 var 6120 restObj = this._restObj, 6121 restUrl = ""; 6122 6123 //Prepend the base REST object if one was provided. 6124 if (restObj instanceof RestBase) { 6125 restUrl += restObj.getRestUrl(); 6126 } 6127 //Otherwise prepend with the default webapp name. 6128 else { 6129 restUrl += "/finesse/api"; 6130 } 6131 6132 //Append the REST type. 6133 restUrl += "/" + this.getRestType(); 6134 6135 //Append ID if it is not undefined, null, or empty. 6136 if (this._id) { 6137 restUrl += "/" + this._id; 6138 } 6139 return restUrl; 6140 }, 6141 6142 /** 6143 * Getter for the id of this RestBase 6144 * @returns {String} 6145 * The id of this RestBase 6146 */ 6147 getId: function () { 6148 return this._id; 6149 }, 6150 6151 /** 6152 * Synchronize this object with the server using REST GET request. 6153 * @returns {Object} 6154 * { 6155 * abort: {function} Function that signifies the callback handler to NOT process the response of the rest request 6156 * } 6157 * @private 6158 */ 6159 _synchronize: function (retries) { 6160 // Fetch this REST object 6161 if (typeof this._id === "string") { 6162 var _this = this, isLoaded = this._loaded, _RETRY_INTERVAL = 10 * 1000; 6163 6164 return this._doGET( 6165 { 6166 success: function (rsp) { 6167 if (!_this._processResponse(rsp)) { 6168 if (retries > 0) { 6169 setTimeout(function () { 6170 _this._synchronize(retries - 1); 6171 }, _RETRY_INTERVAL); 6172 } else { 6173 _this._errorNotifier.notifyListeners(_this); 6174 } 6175 } else { 6176 // If this object was already "loaded" prior to 6177 // the _doGET request, then call the 6178 // changeNotifier 6179 if (isLoaded) { 6180 _this._changeNotifier.notifyListeners(_this); 6181 } 6182 } 6183 }, 6184 error: function (rsp) { 6185 if (retries > 0) { 6186 setTimeout(function () { 6187 _this._synchronize(retries - 1); 6188 }, _RETRY_INTERVAL); 6189 6190 } else { 6191 _this._errorNotifier.notifyListeners(rsp); 6192 } 6193 } 6194 } 6195 ); 6196 6197 } else { 6198 throw new Error("Can't construct a <" + this.getRestType() + "> due to invalid id type."); 6199 } 6200 }, 6201 6202 /** 6203 * Adds an handler to this object. 6204 * If notifierType is 'load' and the object has already loaded, the callback is invoked immediately 6205 * @param {String} notifierType 6206 * The type of notifier to add to ('load', 'change', 'add', 'delete', 'error') 6207 * @param {Function} callback 6208 * The function callback to invoke. 6209 * @example 6210 * // Handler for additions to the Dialogs collection object. 6211 * // When Dialog (a RestBase object) is created, add a change handler. 6212 * _handleDialogAdd = function(dialog) { 6213 * dialog.addHandler('change', _handleDialogChange); 6214 * } 6215 */ 6216 addHandler: function (notifierType, callback, scope) { 6217 var notifier = null; 6218 try { 6219 Utilities.validateHandler(callback); 6220 6221 notifier = this._getNotifierReference(notifierType); 6222 6223 notifier.addListener(callback, scope); 6224 6225 // If load handler is added and object has 6226 // already been loaded, invoke callback 6227 // immediately 6228 if (notifierType === 'load' && this._loaded) { 6229 callback.call((scope || window), this); 6230 } 6231 } catch (err) { 6232 this._logger.error('id=' + this._id + ': ' + err); 6233 } 6234 }, 6235 6236 /** 6237 * Removes a handler from this object. 6238 * @param {String} notifierType 6239 * The type of notifier to remove ('load', 'change', 'add', 'delete', 'error') 6240 * @param {Function} callback 6241 * The function to remove. 6242 */ 6243 removeHandler: function (notifierType, callback) { 6244 var notifier = null; 6245 try { 6246 Utilities.validateHandler(callback); 6247 6248 notifier = this._getNotifierReference(notifierType); 6249 6250 if (typeof(callback) === "undefined") 6251 { 6252 // Remove all listeners for the type 6253 notifier.reset(); 6254 } 6255 else 6256 { 6257 // Remove the specified listener 6258 finesse.utilities.Utilities.validateHandler(callback); 6259 notifier.removeListener(callback); 6260 } 6261 } catch (err) { 6262 this._logger.error('id=' + this._id + ': ' + err); 6263 } 6264 }, 6265 6266 /** 6267 * Utility method gating any operations that require complete instantiation 6268 * @throws Error 6269 * If this object was not fully instantiated yet 6270 * @returns {finesse.restservices.RestBase} 6271 * This RestBase object to allow cascading 6272 */ 6273 isLoaded: function () { 6274 if (!this._loaded) { 6275 throw new Error("Cannot operate on object that is not fully instantiated, use onLoad/onLoadError handlers"); 6276 } 6277 return this; // Allow cascading 6278 }, 6279 6280 6281 6282 /** 6283 * Force an update on this object. Since an asynchronous GET is performed, 6284 * it is necessary to have an onChange handler registered in order to be 6285 * notified when the response of this returns. 6286 * @param {Integer} retries 6287 * The number or retry attempts to make. 6288 * @returns {Object} 6289 * { 6290 * abort: {function} Function that signifies the callback handler to NOT process the response of the asynchronous request 6291 * } 6292 */ 6293 refresh: function (retries) { 6294 var _this = this; 6295 6296 if (this.explicitSubscription) { 6297 this._subscribeNode({ 6298 success: function () { 6299 //Disallow GETs if object doesn't support it. 6300 if (!_this.supportsRequests) { 6301 throw new Error("Object doesn't support request operations."); 6302 } 6303 6304 _this._synchronize(retries); 6305 6306 return this; // Allow cascading 6307 }, 6308 error: function (err) { 6309 _this._errorNotifier.notifyListeners(err); 6310 } 6311 }); 6312 } else { 6313 //Disallow GETs if object doesn't support it. 6314 if (!this.supportsRequests) { 6315 throw new Error("Object doesn't support request operations."); 6316 } 6317 6318 return this._synchronize(retries); 6319 } 6320 }, 6321 6322 /** 6323 * Utility method to validate against the known schema of this RestBase 6324 * @param {Object} obj 6325 * The object to validate 6326 * @returns {Boolean} 6327 * True if the object fits the schema of this object. This usually 6328 * means all required keys or nested objects are present. 6329 * False otherwise. 6330 * @private 6331 */ 6332 _validate: function (obj) { 6333 var valid = (typeof obj === "object" && this.hasProperty(obj, this.getRestType())); 6334 if (!valid) 6335 { 6336 this._logger.error(this.getRestType() + " failed validation! Does your JS REST class return the correct string from getRestType()?"); 6337 } 6338 return valid; 6339 }, 6340 6341 /** 6342 * Utility method to fetch this RestBase from the server 6343 * @param {finesse.interfaces.RequestHandlers} handlers 6344 * An object containing the handlers for the request 6345 * @returns {Object} 6346 * { 6347 * abort: {function} Function that signifies the callback handler to NOT process the response of the rest request 6348 * } 6349 * @private 6350 */ 6351 _doGET: function (handlers) { 6352 return this.restRequest(this.getRestUrl(), handlers); 6353 }, 6354 6355 /** 6356 * Common update event handler used by the pubsub callback closure. 6357 * Processes the update event then notifies listeners. 6358 * @param {Object} scope 6359 * An object containing callbacks to handle the asynchronous get 6360 * @param {Object} update 6361 * An object containing callbacks to handle the asynchronous get 6362 * @private 6363 */ 6364 _updateEventHandler: function (scope, update) { 6365 if (scope._processUpdate(update)) { 6366 switch (update.object.Update.event) { 6367 case "POST": 6368 scope._addNotifier.notifyListeners(scope); 6369 break; 6370 case "PUT": 6371 scope._changeNotifier.notifyListeners(scope); 6372 break; 6373 case "DELETE": 6374 scope._deleteNotifier.notifyListeners(scope); 6375 break; 6376 } 6377 } 6378 }, 6379 6380 /** 6381 * Utility method to create a callback to be given to OpenAjax to invoke when a message 6382 * is published on the topic of our REST URL (also XEP-0060 node). 6383 * This needs to be its own defined method so that subclasses can have their own implementation. 6384 * @returns {Function} callback(update) 6385 * The callback to be invoked when an update event is received. This callback will 6386 * process the update and notify listeners. 6387 * @private 6388 */ 6389 _createPubsubCallback: function () { 6390 var _this = this; 6391 return function (update) { 6392 _this._updateEventHandler(_this, update); 6393 }; 6394 }, 6395 6396 /** 6397 * Subscribe to pubsub infra using the REST URL as the topic name. 6398 * @param {finesse.interfaces.RequestHandlers} handlers 6399 * An object containing the handlers for the request 6400 * @private 6401 */ 6402 subscribe: function (callbacks) { 6403 // Only need to do a subscription to client pubsub. No need to trigger 6404 // a subscription on the Finesse server due to implicit subscribe (at 6405 // least for now). 6406 var _this = this, 6407 topic = Topics.getTopic(this.getXMPPNodePath()), 6408 handlers, 6409 successful = ClientServices.subscribe(topic, this._createPubsubCallback(), true); 6410 6411 callbacks = callbacks || {}; 6412 6413 handlers = { 6414 /** @private */ 6415 success: function () { 6416 // Add item to the refresh list in ClientServices to refresh if 6417 // we recover due to our resilient connection. However, do 6418 // not add if doNotRefresh flag is set. 6419 if (!_this.doNotRefresh) { 6420 ClientServices.addToRefreshList(_this); 6421 } 6422 6423 if (typeof callbacks.success === "function") { 6424 callbacks.success(); 6425 } 6426 }, 6427 /** @private */ 6428 error: function (err) { 6429 if (successful) { 6430 ClientServices.unsubscribe(topic); 6431 } 6432 6433 if (typeof callbacks.error === "function") { 6434 callbacks.error(err); 6435 } 6436 } 6437 }; 6438 6439 // Request a node subscription only if this object requires explicit subscriptions 6440 if (this.explicitSubscription === true) { 6441 this._subscribeNode(handlers); 6442 } else { 6443 if (successful) { 6444 this._subid = "OpenAjaxOnly"; 6445 handlers.success(); 6446 } else { 6447 handlers.error(); 6448 } 6449 } 6450 6451 return this; 6452 }, 6453 6454 /** 6455 * Unsubscribe to pubsub infra using the REST URL as the topic name. 6456 * @param {finesse.interfaces.RequestHandlers} handlers 6457 * An object containing the handlers for the request 6458 * @private 6459 */ 6460 unsubscribe: function (callbacks) { 6461 // Only need to do a subscription to client pubsub. No need to trigger 6462 // a subscription on the Finesse server due to implicit subscribe (at 6463 // least for now). 6464 var _this = this, 6465 topic = Topics.getTopic(this.getRestUrl()), 6466 handlers; 6467 6468 // no longer keep track of object to refresh on reconnect 6469 ClientServices.removeFromRefreshList(_this); 6470 6471 callbacks = callbacks || {}; 6472 6473 handlers = { 6474 /** @private */ 6475 success: function () { 6476 if (typeof callbacks.success === "function") { 6477 callbacks.success(); 6478 } 6479 }, 6480 /** @private */ 6481 error: function (err) { 6482 if (typeof callbacks.error === "function") { 6483 callbacks.error(err); 6484 } 6485 } 6486 }; 6487 6488 if (this._subid) { 6489 ClientServices.unsubscribe(topic); 6490 // Request a node unsubscribe only if this object requires explicit subscriptions 6491 if (this.explicitSubscription === true) { 6492 this._unsubscribeNode(handlers); 6493 } else { 6494 this._subid = undefined; 6495 handlers.success(); 6496 } 6497 } else { 6498 handlers.success(); 6499 } 6500 6501 return this; 6502 }, 6503 6504 /** 6505 * Private utility to perform node subscribe requests for explicit subscriptions 6506 * @param {finesse.interfaces.RequestHandlers} handlers 6507 * An object containing the handlers for the request 6508 * @private 6509 */ 6510 _subscribeNode: function (callbacks) { 6511 var _this = this; 6512 6513 // Protect against null dereferencing of callbacks allowing its (nonexistent) keys to be read as undefined 6514 callbacks = callbacks || {}; 6515 6516 ClientServices.subscribeNode(this.getXMPPNodePath(), function (subid, err) { 6517 if (err) { 6518 if (typeof callbacks.error === "function") { 6519 callbacks.error(err); 6520 } 6521 } else { 6522 // Store the subid on a successful subscribe 6523 _this._subid = subid; 6524 if (typeof callbacks.success === "function") { 6525 callbacks.success(); 6526 } 6527 } 6528 }); 6529 }, 6530 6531 /** 6532 * Private utility to perform node unsubscribe requests for explicit subscriptions 6533 * @param {finesse.interfaces.RequestHandlers} handlers 6534 * An object containing the handlers for the request 6535 * @private 6536 */ 6537 _unsubscribeNode: function (callbacks) { 6538 var _this = this; 6539 6540 // Protect against null dereferencing of callbacks allowing its (nonexistent) keys to be read as undefined 6541 callbacks = callbacks || {}; 6542 6543 ClientServices.unsubscribeNode(this.getXMPPNodePath(), this._subid, function (err) { 6544 _this._subid = undefined; 6545 if (err) { 6546 if (typeof callbacks.error === "function") { 6547 callbacks.error(err); 6548 } 6549 } else { 6550 if (typeof callbacks.success === "function") { 6551 callbacks.success(); 6552 } 6553 } 6554 }); 6555 }, 6556 6557 /** 6558 * Validate and store the object into the internal data store. 6559 * @param {Object} object 6560 * The JavaScript object that should match of schema of this REST object. 6561 * @returns {Boolean} 6562 * True if the object was validated and stored successfully. 6563 * @private 6564 */ 6565 _processObject: function (object) { 6566 if (this._validate(object)) { 6567 this._data = this.getProperty(object, this.getRestType()); // Should clone the object here? 6568 6569 // If loaded for the first time, call the load notifiers. 6570 if (!this._loaded) { 6571 this._loaded = true; 6572 this._loadNotifier.notifyListeners(this); 6573 } 6574 6575 return true; 6576 } 6577 return false; 6578 }, 6579 6580 /** 6581 * Normalize the object to mitigate the differences between the backend 6582 * and what this REST object should hold. For example, the backend sends 6583 * send an event with the root property name being lower case. In order to 6584 * match the GET, the property should be normalized to an upper case. 6585 * @param {Object} object 6586 * The object which should be normalized. 6587 * @returns {Object} 6588 * Return the normalized object. 6589 * @private 6590 */ 6591 _normalize: function (object) { 6592 var 6593 restType = this.getRestType(), 6594 // Get the REST object name with first character being lower case. 6595 objRestType = restType.charAt(0).toLowerCase() + restType.slice(1); 6596 6597 // Normalize payload to match REST object. The payload for an update 6598 // use a lower case object name as oppose to upper case. Only normalize 6599 // if necessary. 6600 if (!this.hasProperty(object, restType) && this.hasProperty(object, objRestType)) { 6601 //Since the object is going to be modified, clone the object so that 6602 //it doesn't affect others (due to OpenAjax publishing to other 6603 //subscriber. 6604 object = jQuery.extend(true, {}, object); 6605 6606 object[restType] = object[objRestType]; 6607 delete(object[objRestType]); 6608 } 6609 return object; 6610 }, 6611 6612 /** 6613 * Utility method to process the response of a successful get 6614 * @param {Object} rsp 6615 * The response of a successful get 6616 * @returns {Boolean} 6617 * True if the update was successfully processed (the response object 6618 * passed the schema validation) and updated the internal data cache, 6619 * false otherwise. 6620 * @private 6621 */ 6622 _processResponse: function (rsp) { 6623 try { 6624 if (this.keepRestResponse) { 6625 this._restResponse = rsp.content; 6626 this.restResponseStatus = rsp.status; 6627 } 6628 return this._processObject(rsp.object); 6629 } 6630 catch (err) { 6631 this._logger.error(this.getRestType() + ': ' + err); 6632 } 6633 return false; 6634 }, 6635 6636 /** 6637 * Method that is called at the end of _processUpdate() which by default 6638 * will just delete the requestId-to-callbacks mapping but can be overridden. 6639 * @param {String} requestId The requestId of the event 6640 */ 6641 _postProcessUpdateStrategy: function (requestId) { 6642 //Clean up _pendingCallbacks now that we fired a callback corresponding to the received requestId. 6643 delete this._pendingCallbacks[requestId]; 6644 }, 6645 6646 /** 6647 * Utility method to process the update notification. 6648 * @param {Object} update 6649 * The payload of an update notification. 6650 * @returns {Boolean} 6651 * True if the update was successfully processed (the update object 6652 * passed the schema validation) and updated the internal data cache, 6653 * false otherwise. 6654 * @private 6655 */ 6656 _processUpdate: function (update) { 6657 try { 6658 var updateObj, requestId, fakeResponse, receivedError; 6659 6660 // The backend will send the data object with a lower case. To be 6661 // consistent with what should be represented in this object, the 6662 // object name should be upper case. This will normalize the object. 6663 updateObj = this._normalize(update.object.Update.data); 6664 6665 // Store the last event. 6666 this._lastUpdate = update.object; 6667 6668 requestId = this._lastUpdate.Update ? this._lastUpdate.Update.requestId : undefined; 6669 6670 if (requestId && this._pendingCallbacks[requestId]) { 6671 6672 /* 6673 * The passed success/error callbacks are expecting to be passed an AJAX response, so construct 6674 * a simulated/"fake" AJAX response object from the information in the received event. 6675 * The constructed object should conform to the contract for response objects specified 6676 * in _createAjaxHandler(). 6677 */ 6678 fakeResponse = {}; 6679 6680 //The contract says that rsp.content should contain the raw text of the response so we simulate that here. 6681 //For some reason json2xml has trouble with the native update object, so we serialize a clone of it by 6682 //doing a parse(stringify(update)). 6683 fakeResponse.content = this._util.json2xml(gadgets.json.parse(gadgets.json.stringify(update))); 6684 6685 fakeResponse.object = {}; 6686 6687 if (updateObj.apiErrors && updateObj.apiErrors.apiError) { //Error case 6688 6689 //TODO: The lowercase -> uppercase ApiErrors translation method below is undesirable, can it be improved? 6690 receivedError = updateObj.apiErrors.apiError; 6691 fakeResponse.object.ApiErrors = {}; 6692 fakeResponse.object.ApiErrors.ApiError = {}; 6693 fakeResponse.object.ApiErrors.ApiError.ErrorData = receivedError.errorData || undefined; 6694 fakeResponse.object.ApiErrors.ApiError.ErrorMessage = receivedError.errorMessage || undefined; 6695 fakeResponse.object.ApiErrors.ApiError.ErrorType = receivedError.errorType || undefined; 6696 6697 /* 6698 * Since this is the error case, supply the error callback with a '400 BAD REQUEST' status code. We don't know what the real 6699 * status code should be since the event we're constructing fakeResponse from doesn't include a status code. 6700 * This is just to conform to the contract for the error callback in _createAjaxHandler(). 6701 **/ 6702 fakeResponse.status = 400; 6703 6704 } else { //Success case 6705 6706 fakeResponse.object = this._lastUpdate; 6707 6708 /* 6709 * Since this is the success case, supply the success callback with a '200 OK' status code. We don't know what the real 6710 * status code should be since the event we're constructing fakeResponse from doesn't include a status code. 6711 * This is just to conform to the contract for the success callback in _createAjaxHandler(). 6712 **/ 6713 fakeResponse.status = 200; 6714 } 6715 6716 try { 6717 6718 if (fakeResponse.object.ApiErrors && this._pendingCallbacks[requestId].error) { 6719 this._pendingCallbacks[requestId].error(fakeResponse); 6720 } 6721 // HTTP 202 is handled as a success, besides, we cannot infer that a non-error is a success. 6722 /*else if (this._pendingCallbacks[requestId].success) { 6723 this._pendingCallbacks[requestId].success(fakeResponse); 6724 }*/ 6725 6726 } catch (callbackErr) { 6727 6728 this._logger.error(this.getRestType() + ": Caught error while firing callback: " + callbackErr); 6729 6730 } 6731 6732 this._postProcessUpdateStrategy(requestId); 6733 6734 } 6735 6736 return this._processObject(updateObj); 6737 } 6738 catch (err) { 6739 this._logger.error(this.getRestType() + ': ' + err); 6740 } 6741 return false; 6742 }, 6743 6744 /** 6745 * Utility method to create ajax response handler closures around the 6746 * provided callbacks. Callbacks should be passed through from .ajax(). 6747 * makeRequest is responsible for garbage collecting these closures. 6748 * @param {finesse.interfaces.RequestHandlers} handlers 6749 * An object containing the handlers for the request 6750 * @returns {Object} 6751 * { 6752 * abort: {function} Function that signifies the callback handler to NOT process the response when the response returns 6753 * callback: {function} Callback handler to be invoked when the response returns 6754 * } 6755 * @private 6756 */ 6757 _createAjaxHandler: function (options) { 6758 //We should not need to check this again since it has already been done in .restRequest() 6759 //options = options || {}; 6760 6761 //Flag to indicate whether or not to process the response 6762 var abort = false, 6763 6764 //Get a reference to the parent User object 6765 _this = this; 6766 6767 return { 6768 6769 abort: function () { 6770 abort = true; 6771 }, 6772 6773 callback: function (rsp) { 6774 6775 if (abort) { 6776 // Do not process the response 6777 return; 6778 } 6779 6780 var requestId, error = false, rspObj; 6781 6782 if (options.success || options.error) { 6783 rspObj = { 6784 status: rsp.rc, 6785 content: rsp.text, 6786 isUnsent: rsp.isUnsent 6787 }; 6788 6789 if (!_this.doNotLog) { 6790 _this._logger.log(_this.getRestType() + ": requestId='" + options.uuid + "', Returned with status=" + rspObj.status + ", content='" + rspObj.content + "', isUnsent = " + rspObj.isUnsent); 6791 } 6792 6793 //Some responses may not have a body. 6794 if (rsp.text && rsp.text.length > 0) { 6795 try { 6796 rspObj.object = _this._util.xml2js(rsp.text); 6797 } catch (e) { 6798 error = true; 6799 rspObj.error = { 6800 errorType: "parseError", 6801 errorMessage: "Could not serialize XML: " + e 6802 }; 6803 } 6804 } else { 6805 rspObj.object = {}; 6806 } 6807 6808 if (!error && rspObj.status >= 200 && rspObj.status < 300) { 6809 if (options.success) { 6810 options.success(rspObj); 6811 } 6812 } else { 6813 if (options.error) { 6814 options.error(rspObj); 6815 } 6816 } 6817 6818 /* 6819 * If a synchronous error happened after a non-GET request (usually a validation error), we 6820 * need to clean up the request's entry in _pendingCallbacks since no corresponding event 6821 * will arrive later. The corresponding requestId should be present in the response headers. 6822 * 6823 * It appears Shindig changes the header keys to lower case, hence 'requestid' instead of 6824 * 'requestId' below. 6825 **/ 6826 if (rspObj.status !== 202 && rsp.headers && rsp.headers.requestid) { 6827 requestId = rsp.headers.requestid[0]; 6828 if (_this._pendingCallbacks[requestId]) { 6829 delete _this._pendingCallbacks[requestId]; 6830 } 6831 } 6832 } 6833 } 6834 }; 6835 }, 6836 6837 /** 6838 * Utility method to make an asynchronous request 6839 * @param {String} url 6840 * The unencoded URL to which the request is sent (will be encoded) 6841 * @param {Object} options 6842 * An object containing additional options for the request. 6843 * @param {Object} options.content 6844 * An object to send in the content body of the request. Will be 6845 * serialized into XML before sending. 6846 * @param {String} options.method 6847 * The type of request. Defaults to "GET" when none is specified. 6848 * @param {Function} options.success(rsp) 6849 * A callback function to be invoked for a successful request. 6850 * { 6851 * status: {Number} The HTTP status code returned 6852 * content: {String} Raw string of response 6853 * object: {Object} Parsed object of response 6854 * } 6855 * @param {Function} options.error(rsp) 6856 * A callback function to be invoked for an unsuccessful request. 6857 * { 6858 * status: {Number} The HTTP status code returned 6859 * content: {String} Raw string of response 6860 * object: {Object} Parsed object of response 6861 * error: {Object} Wrapped exception that was caught 6862 * error.errorType: {String} Type of error that was caught 6863 * error.errorMessage: {String} Message associated with error 6864 * } 6865 * @returns {Object} 6866 * { 6867 * abort: {function} Function that signifies the callback handler to NOT process the response of this asynchronous request 6868 * } 6869 * @private 6870 */ 6871 restRequest : function(url, options) { 6872 var encodedUrl, ajaxHandler, scheme = window.location.protocol, host = window.location.hostname, port = window.location.port, dataTypeAX, contentTypeAX, mtype, postdata = "", auth, rspObj, locale = this._config.locale; 6873 // Protect against null dereferencing of options 6874 // allowing its (nonexistent) keys to be read as 6875 // undefined 6876 options = options || {}; 6877 options.success = this._util 6878 .validateHandler(options.success); 6879 options.error = this._util 6880 .validateHandler(options.error); 6881 6882 // true if this should be a GET request, false 6883 // otherwise 6884 if (!options.method || options.method === "GET") { 6885 // Disable caching for GETs 6886 if (url.indexOf("?") > -1) { 6887 url += "&"; 6888 } else { 6889 url += "?"; 6890 } 6891 url += "nocache=" 6892 + this._util.currentTimeMillis(); 6893 } else { 6894 /** 6895 * If not GET, generate a requestID and add it 6896 * to the headers, then wrap callbacks into an 6897 * object and store it in _pendingCallbacks. If 6898 * we receive a synchronous error response 6899 * instead of a 202 as expected, the AJAX 6900 * handler will clean up _pendingCallbacks. 6901 */ 6902 /* 6903 * TODO: Clean up _pendingCallbacks if an entry 6904 * persists after a certain amount of time has 6905 * passed. In the block below, can store the 6906 * current time (new Date().getTime()) alongside 6907 * the callbacks in the new _pendingCallbacks 6908 * entry. Then iterate through a copty of 6909 * _pendingCallbacks, deleting all entries 6910 * inside _pendingCallbacks that are older than 6911 * a certain threshold (2 minutes for example.) 6912 * This solves a potential memory leak issue if 6913 * we never receive an event for a given stored 6914 * requestId; we don't want to store unfired 6915 * callbacks forever. 6916 */ 6917 /** @private */ 6918 options.uuid = this._util.generateUUID(); 6919 // params[gadgets.io.RequestParameters.HEADERS].requestId 6920 // = options.uuid; 6921 // By default, Shindig strips nearly all of the 6922 // response headers, but this parameter tells 6923 // Shindig 6924 // to send the headers through unmodified; we 6925 // need to be able to read the 'requestId' 6926 // header if we 6927 // get a synchronous error as a result of a 6928 // non-GET request. (See the bottom of 6929 // _createAjaxHandler().) 6930 // params[gadgets.io.RequestParameters.GET_FULL_HEADERS] 6931 // = "true"; 6932 this._pendingCallbacks[options.uuid] = {}; 6933 this._pendingCallbacks[options.uuid].success = options.success; 6934 this._pendingCallbacks[options.uuid].error = options.error; 6935 } 6936 encodedUrl = encodeURI(url) 6937 + (window.errorOnRestRequest ? "ERROR" : ""); 6938 ajaxHandler = this._createAjaxHandler(options); 6939 // ClientServices.makeRequest(encodedUrl, 6940 // ajaxHandler.callback, params); 6941 mtype = options.method || 'GET'; 6942 encodedUrl = scheme + "//" + host + ":" + port 6943 + encodedUrl; 6944 6945 if (typeof options.content === "object" 6946 && typeof options.content !== "undefined") { 6947 // Except get, all the other operations accepts 6948 // application/xml only. 6949 // @Consumes in all the webservices except GET 6950 // are having this. 6951 postdata = this._util.js2xml(options.content); 6952 if (postdata !== null && postdata !== "") { 6953 contentTypeAX = "application/xml"; 6954 } else { 6955 // in desktop, reason code GET request was 6956 // called with empty object content 6957 // if not able to parse any content to post 6958 // data, then it is GET only 6959 contentTypeAX = ""; 6960 dataTypeAX = "text"; 6961 } 6962 } else { 6963 // No content type for GET operation, by default 6964 // it will take text/plain || application/xml. 6965 // for dataType - GET will result plain text, 6966 // which we are taking as xml. 6967 // Queried list of @Produces from code base, all 6968 // the GET operations accepts text/plain OR 6969 // application/xml and produces application/xml 6970 contentTypeAX = ""; 6971 dataTypeAX = "text"; 6972 } 6973 auth = this._util.getAuthHeaderString(this._config); 6974 6975 if (!this.doNotLog) { 6976 this._logger.log(this.getRestType() 6977 + ": requestId='" + options.uuid 6978 + "', Making REST request: method=" 6979 + (options.method || "GET") + ", url='" 6980 + encodedUrl + "'"); 6981 } 6982 $ 6983 .ajax({ 6984 url : encodedUrl, 6985 headers : this.extraHeaders || {}, 6986 beforeSend : function(xhr) { 6987 xhr.setRequestHeader( 6988 "Authorization", auth); 6989 xhr.setRequestHeader("locale", 6990 locale); 6991 if (options.uuid) { 6992 xhr.setRequestHeader( 6993 "requestId", 6994 options.uuid); 6995 } 6996 }, 6997 dataType : dataTypeAX, // xml or json? 6998 contentType : contentTypeAX, 6999 type : mtype, 7000 data : postdata, 7001 timeout : 5000, 7002 success : function(response, status, 7003 xhr) { 7004 var rspObj = { 7005 rc : xhr.status, 7006 text : response, 7007 isUnsent : xhr.readyState==0, 7008 headers : { 7009 requestid : xhr 7010 .getResponseHeader("requestId") 7011 } 7012 }; 7013 ajaxHandler.callback(rspObj); 7014 }, 7015 error : function(jqXHR, request, error) { 7016 var resObj = { 7017 rc : jqXHR.status, 7018 text : jqXHR.responseText, 7019 isUnsent : jqXHR.readyState==0, 7020 headers : { 7021 requestid : jqXHR 7022 .getResponseHeader("requestId") 7023 } 7024 }; 7025 ajaxHandler.callback(resObj); 7026 } 7027 }); 7028 return { 7029 abort : ajaxHandler.abort 7030 }; 7031 }, 7032 7033 7034 /** 7035 * Retrieves a reference to a particular notifierType. 7036 * 7037 * @param notifierType 7038 * is a string which indicates the notifier to retrieve 7039 * ('load', 'change', 'add', 'delete', 'error') 7040 * @return {Notifier} 7041 * @private 7042 */ 7043 _getNotifierReference: function (notifierType) { 7044 var notifierReference = null; 7045 if (notifierType === 'load') { 7046 notifierReference = this._loadNotifier; 7047 } else if (notifierType === 'change') { 7048 notifierReference = this._changeNotifier; 7049 } else if (notifierType === 'add') { 7050 notifierReference = this._addNotifier; 7051 } else if (notifierType === 'delete') { 7052 notifierReference = this._deleteNotifier; 7053 } else if (notifierType === 'error') { 7054 notifierReference = this._errorNotifier; 7055 } else { 7056 throw new Error("_getNotifierReference(): Trying to get unknown notifier(notifierType=" + notifierType + ")"); 7057 } 7058 7059 return notifierReference; 7060 } 7061 }); 7062 7063 window.finesse = window.finesse || {}; 7064 window.finesse.restservices = window.finesse.restservices || {}; 7065 window.finesse.restservices.RestBase = RestBase; 7066 7067 return RestBase; 7068 }); 7069 7070 /** The following comment is to prevent jslint errors about 7071 * using variables before they are defined. 7072 */ 7073 /*global finesse*/ 7074 7075 /** 7076 * JavaScript base object that all REST collection objects should 7077 * inherit from because it encapsulates and provides the common functionality 7078 * that all REST objects need. 7079 * 7080 * @requires finesse.clientservices.ClientServices 7081 * @requires Class 7082 * @requires finesse.FinesseBase 7083 * @requires finesse.restservices.RestBase 7084 */ 7085 7086 /** 7087 * @class 7088 * JavaScript representation of a REST collection object. 7089 * 7090 * @constructor 7091 * @param {Function} callbacks.onCollectionAdd(this) 7092 * Callback to invoke upon successful item addition to the collection. 7093 * @param {Function} callbacks.onCollectionDelete(this) 7094 * Callback to invoke upon successful item deletion from the collection. 7095 * @borrows finesse.restservices.RestBase as finesse.restservices.RestCollectionBase 7096 */ 7097 /** @private */ 7098 define('restservices/RestCollectionBase',[ 7099 'restservices/RestBase', 7100 'utilities/Utilities', 7101 'restservices/Notifier' 7102 ], 7103 function (RestBase, Utilities, Notifier) { 7104 var RestCollectionBase = RestBase.extend(/** @lends finesse.restservices.RestCollectionBase.prototype */{ 7105 7106 /** 7107 * Boolean function that specifies whether the collection handles subscribing 7108 * and propagation of events for the individual REST object items the 7109 * collection holds. False by default. Subclasses should override if true. 7110 * @private 7111 */ 7112 supportsRestItemSubscriptions: false, 7113 7114 /** 7115 * Gets the constructor the individual items that make of the collection. 7116 * For example, a Dialogs collection object will hold a list of Dialog items. 7117 * @throws Error because subtype must implement. 7118 * @private 7119 */ 7120 getRestItemClass: function () { 7121 throw new Error("getRestItemClass(): Not implemented in subtype."); 7122 }, 7123 7124 /** 7125 * Gets the REST type of the individual items that make of the collection. 7126 * For example, a Dialogs collection object will hold a list of Dialog items. 7127 * @throws Error because subtype must implement. 7128 * @private 7129 */ 7130 getRestItemType: function () { 7131 throw new Error("getRestItemType(): Not implemented in subtype."); 7132 }, 7133 7134 /** 7135 * The base REST URL in which items this object contains can be referenced. 7136 * @return {String} 7137 * The REST URI for items this object contains. 7138 * @private 7139 */ 7140 getRestItemBaseUrl: function () { 7141 var 7142 restUrl = "/finesse/api"; 7143 7144 //Append the REST type. 7145 restUrl += "/" + this.getRestItemType(); 7146 7147 return restUrl; 7148 }, 7149 7150 /* 7151 * Creates a new object from the given data 7152 * @param data - data object 7153 * @private 7154 */ 7155 _objectCreator: function (data) { 7156 var objectId = this._extractId(data), 7157 newRestObj = this._collection[objectId], 7158 _this = this; 7159 7160 //Prevent duplicate entries into collection. 7161 if (!newRestObj) { 7162 //Create a new REST object using the subtype defined by the 7163 //overridden method. 7164 newRestObj = new (this.getRestItemClass())({ 7165 doNotSubscribe: this.handlesItemSubscription, 7166 doNotRefresh: this.handlesItemRefresh, 7167 id: objectId, 7168 data: data, 7169 onLoad: function (newObj) { 7170 //Normalize and add REST object to collection datastore. 7171 _this._collection[objectId] = newObj; 7172 _this._collectionAddNotifier.notifyListeners(newObj); 7173 _this.length += 1; 7174 } 7175 }); 7176 } 7177 else { 7178 //If entry already exist in collection, process the new event, 7179 //and notify all change listeners since an existing object has 7180 //change. This could happen in the case when the Finesse server 7181 //cycles, and sends a snapshot of the user's calls. 7182 newRestObj._processObject(data); 7183 newRestObj._changeNotifier.notifyListeners(newRestObj); 7184 } 7185 }, 7186 7187 /* 7188 * Deletes and object and notifies its handlers 7189 * @param data - data object 7190 * @private 7191 */ 7192 _objectDeleter: function (data) { 7193 var objectId = this._extractId(data), 7194 object = this._collection[objectId]; 7195 if (object) { 7196 //Even though this is a delete, let's make sure the object we are passing has got good data 7197 object._processObject(data); 7198 //Notify listeners and delete from internal datastore. 7199 this._collectionDeleteNotifier.notifyListeners(object); 7200 delete this._collection[objectId]; 7201 this.length -= 1; 7202 } 7203 }, 7204 7205 /** 7206 * Creates an anonymous function for notifiying error listeners of a particular object 7207 * data. 7208 * @param obj - the objects whose error listeners to notify 7209 * @returns {Function} 7210 * Callback for notifying of errors 7211 * @private 7212 */ 7213 _createErrorNotifier: function (obj) { 7214 return function (err) { 7215 obj._errorNotifier.notifyListeners(err); 7216 }; 7217 }, 7218 7219 /** 7220 * Replaces the collection with a refreshed list using the passed in 7221 * data. 7222 * @param data - data object (usually this._data) 7223 * @private 7224 */ 7225 _buildRefreshedCollection: function (data) { 7226 var i, dataObject, object, objectId, dataArray, newIds = [], foundFlag; 7227 if (data && this.getProperty(data, this.getRestItemType()) !== null) { 7228 dataArray = Utilities.getArray(this.getProperty(data, this.getRestItemType())); 7229 } else { 7230 dataArray = []; 7231 } 7232 7233 // iterate through each item in the new data and add to or update collection 7234 for (i = 0; i < dataArray.length; i += 1) { 7235 dataObject = {}; 7236 dataObject[this.getRestItemType()] = dataArray[i]; 7237 objectId = this._extractId(dataObject); 7238 7239 this._objectCreator(dataObject); 7240 newIds.push(objectId); 7241 7242 // resubscribe if the object requires an explicit subscription 7243 object = this._collection[objectId]; 7244 if (this.handlesItemRefresh && object.explicitSubscription) { 7245 object._subscribeNode({ 7246 error: this._createErrorNotifier(object) 7247 }); 7248 } 7249 } 7250 7251 // now clean up items (if any) that were removed 7252 for (objectId in this._collection) { 7253 if (this._collection.hasOwnProperty(objectId)) { 7254 foundFlag = false; 7255 for (i = newIds.length - 1; i >= 0; i -= 1) { 7256 if (newIds[i] === objectId) { 7257 foundFlag = true; 7258 break; 7259 } 7260 } 7261 // did not find in updated list, so delete it 7262 if (!foundFlag) { 7263 this._objectDeleter({'data': this._collection[objectId]._data}); 7264 } 7265 } 7266 } 7267 }, 7268 7269 /** 7270 * The actual refresh operation, refactored out so we don't have to repeat code 7271 * @private 7272 */ 7273 _RESTRefresh: function () { 7274 var _this = this; 7275 this._doGET({ 7276 success: function(rsp) { 7277 if (_this._processResponse(rsp)) { 7278 _this._buildRefreshedCollection(_this._data); 7279 } else { 7280 _this._errorNotifier.notifyListeners(_this); 7281 } 7282 }, 7283 error: function(rsp) { 7284 _this._errorNotifier.notifyListeners(rsp); 7285 } 7286 }); 7287 }, 7288 7289 /** 7290 * Force an update on this object. Since an asynchronous GET is performed, 7291 * it is necessary to have an onChange handler registered in order to be 7292 * notified when the response of this returns. 7293 * @returns {finesse.restservices.RestBaseCollection} 7294 * This RestBaseCollection object to allow cascading 7295 */ 7296 refresh: function() { 7297 var _this = this, isLoaded = this._loaded; 7298 7299 // resubscribe if the collection requires an explicit subscription 7300 if (this.explicitSubscription) { 7301 this._subscribeNode({ 7302 success: function () { 7303 _this._RESTRefresh(); 7304 }, 7305 error: function (err) { 7306 _this._errorNotifier.notifyListeners(err); 7307 } 7308 }); 7309 } else { 7310 this._RESTRefresh(); 7311 } 7312 7313 return this; // Allow cascading 7314 }, 7315 7316 /** 7317 * @private 7318 * The _addHandlerCb and _deleteHandlerCb require that data be passed in the 7319 * format of an array of {(Object Type): object} objects. For example, a 7320 * queues object would return [{Queue: queue1}, {Queue: queue2}, ...]. 7321 * @param skipOuterObject If {true} is passed in for this param, then the "data" 7322 * property is returned instead of an object with the 7323 * data appended. 7324 * @return {Array} 7325 */ 7326 extractCollectionData: function (skipOuterObject) { 7327 var restObjs, 7328 obj, 7329 result = [], 7330 _this = this; 7331 7332 if (this._data) 7333 { 7334 restObjs = this._data[this.getRestItemType()]; 7335 7336 if (restObjs) 7337 { 7338 // check if there are multiple objects to pass 7339 if (!$.isArray(restObjs)) 7340 { 7341 restObjs = [restObjs]; 7342 } 7343 7344 // if so, create an object for each and add to result array 7345 $.each(restObjs, function (id, object) { 7346 if (skipOuterObject === true) 7347 { 7348 obj = object; 7349 } 7350 else 7351 { 7352 obj = {}; 7353 obj[_this.getRestItemType()] = object; 7354 } 7355 result.push(obj); 7356 }); 7357 } 7358 7359 } 7360 7361 return result; 7362 }, 7363 7364 /** 7365 * For Finesse, collections are handled uniquely on a POST and 7366 * doesn't necessary follow REST conventions. A POST on a collection 7367 * doesn't mean that the collection has been created, it means that an 7368 * item has been added to the collection. This function will generate 7369 * a closure which will handle this logic appropriately. 7370 * @param {Object} scope 7371 * The scope of where the callback should be invoked. 7372 * @private 7373 */ 7374 _addHandlerCb: function (scope) { 7375 return function (restItem) { 7376 var data = restItem.extractCollectionData(); 7377 7378 $.each(data, function (id, object) { 7379 scope._objectCreator(object); 7380 }); 7381 }; 7382 }, 7383 7384 /** 7385 * For Finesse, collections are handled uniquely on a DELETE and 7386 * doesn't necessary follow REST conventions. A DELETE on a collection 7387 * doesn't mean that the collection has been deleted, it means that an 7388 * item has been deleted from the collection. This function will generate 7389 * a closure which will handle this logic appropriately. 7390 * @param {Object} scope 7391 * The scope of where the callback should be invoked. 7392 * @private 7393 */ 7394 _deleteHandlerCb: function (scope) { 7395 return function (restItem) { 7396 var data = restItem.extractCollectionData(); 7397 7398 $.each(data, function (id, obj) { 7399 scope._objectDeleter(obj); 7400 }); 7401 }; 7402 }, 7403 7404 /** 7405 * Utility method to process the update notification for Rest Items 7406 * that are children of the collection whose events are published to 7407 * the collection's node. 7408 * @param {Object} update 7409 * The payload of an update notification. 7410 * @returns {Boolean} 7411 * True if the update was successfully processed (the update object 7412 * passed the schema validation) and updated the internal data cache, 7413 * false otherwise. 7414 * @private 7415 */ 7416 _processRestItemUpdate: function (update) { 7417 var object, objectId, updateObj = update.object.Update; 7418 7419 //Extract the ID from the source if the Update was an error. 7420 if (updateObj.data.apiErrors) { 7421 objectId = Utilities.getId(updateObj.source); 7422 } 7423 //Otherwise extract from the data object itself. 7424 else { 7425 objectId = this._extractId(updateObj.data); 7426 } 7427 7428 object = this._collection[objectId]; 7429 if (object) { 7430 if (object._processUpdate(update)) { 7431 switch (updateObj.event) { 7432 case "POST": 7433 object._addNotifier.notifyListeners(object); 7434 break; 7435 case "PUT": 7436 object._changeNotifier.notifyListeners(object); 7437 break; 7438 case "DELETE": 7439 object._deleteNotifier.notifyListeners(object); 7440 break; 7441 } 7442 } 7443 } 7444 }, 7445 7446 /** 7447 * SUBCLASS IMPLEMENTATION (override): 7448 * For collections, this callback has the additional responsibility of passing events 7449 * of collection item updates to the item objects themselves. The collection needs to 7450 * do this because item updates are published to the collection's node. 7451 * @returns {Function} 7452 * The callback to be invoked when an update event is received 7453 * @private 7454 */ 7455 _createPubsubCallback: function () { 7456 var _this = this; 7457 return function (update) { 7458 //If the source of the update is our REST URL, this means the collection itself is modified 7459 if (update.object.Update.source === _this.getRestUrl()) { 7460 _this._updateEventHandler(_this, update); 7461 } else { 7462 //Otherwise, it is safe to assume that if we got an event on our topic, it must be a 7463 //rest item update of one of our children that was published on our node (OpenAjax topic) 7464 _this._processRestItemUpdate(update); 7465 } 7466 }; 7467 }, 7468 7469 /** 7470 * @class 7471 * This is the base collection object. 7472 * 7473 * @constructs 7474 * @augments finesse.restservices.RestBase 7475 * @see finesse.restservices.Contacts 7476 * @see finesse.restservices.Dialogs 7477 * @see finesse.restservices.PhoneBooks 7478 * @see finesse.restservices.Queues 7479 * @see finesse.restservices.WorkflowActions 7480 * @see finesse.restservices.Workflows 7481 * @see finesse.restservices.WrapUpReasons 7482 */ 7483 _fakeConstuctor: function () { 7484 /* This is here to hide the real init constructor from the public docs */ 7485 }, 7486 7487 /** 7488 * @private 7489 * @param {Object} options 7490 * An object with the following properties:<ul> 7491 * <li><b>id:</b> The id of the object being constructed</li> 7492 * <li><b>onCollectionAdd(this): (optional)</b> when an object is added to this collection</li> 7493 * <li><b>onCollectionDelete(this): (optional)</b> when an object is removed from this collection</li> 7494 * <li><b>onLoad(this): (optional)</b> when the collection is successfully loaded from the server</li> 7495 * <li><b>onChange(this): (optional)</b> when an update notification of the collection is received. 7496 * This does not include adding and deleting members of the collection</li> 7497 * <li><b>onAdd(this): (optional)</b> when a notification that the collection is created is received</li> 7498 * <li><b>onDelete(this): (optional)</b> when a notification that the collection is deleted is received</li> 7499 * <li><b>onError(rsp): (optional)</b> if loading of the collection fails, invoked with the error response object:<ul> 7500 * <li><b>status:</b> {Number} The HTTP status code returned</li> 7501 * <li><b>content:</b> {String} Raw string of response</li> 7502 * <li><b>object:</b> {Object} Parsed object of response</li> 7503 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 7504 * <li><b>errorType:</b> {String} Type of error that was caught</li> 7505 * <li><b>errorMessage:</b> {String} Message associated with error</li> 7506 * </ul></li> 7507 * </ul></li> 7508 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 7509 **/ 7510 init: function (options) { 7511 7512 options = options || {}; 7513 options.id = ""; 7514 7515 //Make internal datastore collection to hold a list of objects. 7516 this._collection = {}; 7517 this.length = 0; 7518 7519 //Collections will have additional callbacks that will be invoked when 7520 //an item has been added/deleted. 7521 this._collectionAddNotifier = new Notifier(); 7522 this._collectionDeleteNotifier = new Notifier(); 7523 7524 //Initialize the base class. 7525 this._super(options); 7526 7527 this.addHandler('collectionAdd', options.onCollectionAdd); 7528 this.addHandler('collectionDelete', options.onCollectionDelete); 7529 7530 //For Finesse, collections are handled uniquely on a POST/DELETE and 7531 //doesn't necessary follow REST conventions. A POST on a collection 7532 //doesn't mean that the collection has been created, it means that an 7533 //item has been added to the collection. A DELETE means that an item has 7534 //been removed from the collection. Due to this, we are attaching 7535 //special callbacks to the add/delete that will handle this logic. 7536 this.addHandler("add", this._addHandlerCb(this)); 7537 this.addHandler("delete", this._deleteHandlerCb(this)); 7538 }, 7539 7540 /** 7541 * Returns the collection. 7542 * @returns {Object} 7543 * The collection as an object 7544 */ 7545 getCollection: function () { 7546 //TODO: is this safe? or should we instead return protected functions such as .each(function)? 7547 return this._collection; 7548 }, 7549 7550 /** 7551 * Utility method to build the internal collection data structure (object) based on provided data 7552 * @param {Object} data 7553 * The data to build the internal collection from 7554 * @private 7555 */ 7556 _buildCollection: function (data) { 7557 var i, object, objectId, dataArray; 7558 if (data && this.getProperty(data, this.getRestItemType()) !== null) { 7559 dataArray = Utilities.getArray(this.getProperty(data, this.getRestItemType())); 7560 for (i = 0; i < dataArray.length; i += 1) { 7561 7562 object = {}; 7563 object[this.getRestItemType()] = dataArray[i]; 7564 objectId = this._extractId(object); 7565 this._collection[objectId] = new (this.getRestItemClass())({ 7566 doNotSubscribe: this.handlesItemSubscription, 7567 doNotRefresh: this.handlesItemRefresh, 7568 id: objectId, 7569 data: object 7570 }); 7571 this.length += 1; 7572 } 7573 } 7574 }, 7575 7576 /** 7577 * Called to know whether to include an item in the _collection and _data. Return false to keep it, true to filter out (discard) it. 7578 * Override this in subclasses if you need only object with certain attribute values. 7579 * @param {Object} item Item to test. 7580 * @return {Boolean} False to keep, true to filter out (discard); 7581 */ 7582 _filterOutItem: function (item) { 7583 return false; 7584 }, 7585 7586 /** 7587 * Validate and store the object into the internal data store. 7588 * SUBCLASS IMPLEMENTATION (override): 7589 * Performs collection specific logic to _buildCollection internally based on provided data 7590 * @param {Object} object 7591 * The JavaScript object that should match of schema of this REST object. 7592 * @returns {Boolean} 7593 * True if the object was validated and stored successfully. 7594 * @private 7595 */ 7596 _processObject: function (object) { 7597 var i, 7598 restItemType = this.getRestItemType(), 7599 items; 7600 if (this._validate(object)) { 7601 this._data = this.getProperty(object, this.getRestType()); // Should clone the object here? 7602 7603 // If a subclass has overriden _filterOutItem then we'll need to run through the items and remove them 7604 if (this._data) 7605 { 7606 items = this._data[restItemType]; 7607 7608 if (typeof(items) !== "undefined") 7609 { 7610 if (typeof(items.length) === "undefined") 7611 { 7612 // Single object 7613 if (this._filterOutItem(items)) 7614 { 7615 this._data[restItemType] = items = []; 7616 } 7617 7618 } 7619 else 7620 { 7621 // filter out objects 7622 for (i = items.length - 1; i !== -1; i = i - 1) 7623 { 7624 if (this._filterOutItem(items[i])) 7625 { 7626 items.splice(i, 1); 7627 } 7628 } 7629 } 7630 } 7631 } 7632 7633 // If loaded for the first time, call the load notifiers. 7634 if (!this._loaded) { 7635 this._buildCollection(this._data); 7636 this._loaded = true; 7637 this._loadNotifier.notifyListeners(this); 7638 } 7639 7640 return true; 7641 7642 } 7643 return false; 7644 }, 7645 7646 /** 7647 * Retrieves a reference to a particular notifierType. 7648 * @param {String} notifierType 7649 * Specifies the notifier to retrieve (load, change, error, add, delete) 7650 * @return {Notifier} The notifier object. 7651 */ 7652 _getNotifierReference: function (notifierType) { 7653 var notifierReference; 7654 7655 try { 7656 //Use the base method to get references for load/change/error. 7657 notifierReference = this._super(notifierType); 7658 } catch (err) { 7659 //Check for add/delete 7660 if (notifierType === "collectionAdd") { 7661 notifierReference = this._collectionAddNotifier; 7662 } else if (notifierType === "collectionDelete") { 7663 notifierReference = this._collectionDeleteNotifier; 7664 } else { 7665 //Rethrow exception from base class. 7666 throw err; 7667 } 7668 } 7669 return notifierReference; 7670 } 7671 }); 7672 7673 window.finesse = window.finesse || {}; 7674 window.finesse.restservices = window.finesse.restservices || {}; 7675 window.finesse.restservices.RestCollectionBase = RestCollectionBase; 7676 7677 return RestCollectionBase; 7678 }); 7679 7680 /** 7681 * JavaScript representation of the Finesse Dialog object. 7682 * 7683 * @requires finesse.clientservices.ClientServices 7684 * @requires Class 7685 * @requires finesse.FinesseBase 7686 * @requires finesse.restservices.RestBase 7687 */ 7688 7689 /** @private */ 7690 define('restservices/DialogBase',[ 7691 'restservices/RestBase', 7692 'utilities/Utilities' 7693 ], 7694 function (RestBase, Utilities) { 7695 var DialogBase = RestBase.extend(/** @lends finesse.restservices.DialogBase.prototype */{ 7696 7697 /** 7698 * @class 7699 * A DialogBase is an attempted connection between or among multiple participants, 7700 * for example, a regular phone call, a chat, or an email. 7701 * 7702 * This object is typically extended into individual 7703 * REST Objects (like Dialog, MediaDialog, etc...), and shouldn't be used directly. 7704 * 7705 * @augments finesse.restservices.RestBase 7706 * @constructs 7707 */ 7708 _fakeConstuctor: function () { 7709 /* This is here to hide the real init constructor from the public docs */ 7710 }, 7711 7712 /** 7713 * @private 7714 * 7715 * @param {Object} options 7716 * An object with the following properties:<ul> 7717 * <li><b>id:</b> The id of the object being constructed</li> 7718 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 7719 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 7720 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 7721 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 7722 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 7723 * <li><b>status:</b> {Number} The HTTP status code returned</li> 7724 * <li><b>content:</b> {String} Raw string of response</li> 7725 * <li><b>object:</b> {Object} Parsed object of response</li> 7726 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 7727 * <li><b>errorType:</b> {String} Type of error that was caught</li> 7728 * <li><b>errorMessage:</b> {String} Message associated with error</li> 7729 * </ul></li> 7730 * </ul></li> 7731 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 7732 **/ 7733 init: function (options) { 7734 this._super(options); 7735 }, 7736 7737 /** 7738 * @private 7739 * Gets the REST class for the current object - this is the Dialog class. 7740 * @returns {Object} The Dialog class. 7741 */ 7742 getRestClass: function () { 7743 throw new Error("getRestClass(): Not implemented in subtype."); 7744 }, 7745 7746 /** 7747 * @private 7748 * The constant for agent device. 7749 */ 7750 _agentDeviceType: "AGENT_DEVICE", 7751 7752 /** 7753 * @private 7754 * Gets the REST type for the current object - this is a "Dialog". 7755 * @returns {String} The Dialog string. 7756 */ 7757 getRestType: function () { 7758 return "Dialog"; 7759 }, 7760 7761 /** 7762 * @private 7763 * Override default to indicate that this object doesn't support making 7764 * requests. 7765 */ 7766 supportsRequests: false, 7767 7768 /** 7769 * @private 7770 * Override default to indicate that this object doesn't support subscriptions. 7771 */ 7772 supportsSubscriptions: false, 7773 7774 7775 /** 7776 * Getter for the media type. 7777 * @returns {String} The media type. 7778 */ 7779 getMediaType: function () { 7780 this.isLoaded(); 7781 return this.getData().mediaType; 7782 }, 7783 7784 /** 7785 * @private 7786 * Getter for the uri. 7787 * @returns {String} The uri. 7788 */ 7789 getDialogUri: function () { 7790 this.isLoaded(); 7791 return this.getData().uri; 7792 }, 7793 7794 /** 7795 * Getter for the callType. 7796 * @deprecated Use getMediaProperties().callType instead. 7797 * @returns {String} The callType. 7798 */ 7799 getCallType: function () { 7800 this.isLoaded(); 7801 return this.getData().mediaProperties.callType; 7802 }, 7803 7804 7805 /** 7806 * Getter for the Dialog state. 7807 * @returns {String} The Dialog state. 7808 */ 7809 getState: function () { 7810 this.isLoaded(); 7811 return this.getData().state; 7812 }, 7813 7814 /** 7815 * Retrieves a list of participants within the Dialog object. 7816 * @returns {Object} Array list of participants. 7817 * Participant entity properties are as follows:<ul> 7818 * <li>state - The state of the Participant. 7819 * <li>stateCause - The state cause of the Participant. 7820 * <li>mediaAddress - The media address of the Participant. 7821 * <li>startTime - The start Time of the Participant. 7822 * <li>stateChangeTime - The time when participant state has changed. 7823 * <li>actions - These are the actions that a Participant can perform</ul> 7824 */ 7825 getParticipants: function () { 7826 this.isLoaded(); 7827 var participants = this.getData().participants.Participant; 7828 //Due to the nature of the XML->JSO converter library, a single 7829 //element in the XML array will be considered to an object instead of 7830 //a real array. This will handle those cases to ensure that an array is 7831 //always returned. 7832 7833 return Utilities.getArray(participants); 7834 }, 7835 7836 /** 7837 * This method retrieves the participant timer counters 7838 * 7839 * @param {String} participantExt Extension of participant. 7840 * @returns {Object} Array of Participants which contains properties :<ul> 7841 * <li>state - The state of the Participant. 7842 * <li>startTime - The start Time of the Participant. 7843 * <li>stateChangeTime - The time when participant state has changed.</ul> 7844 * 7845 */ 7846 getParticipantTimerCounters : function (participantExt) { 7847 var part, participantTimerCounters = {}, idx, participants; 7848 7849 participants = this.getParticipants(); 7850 7851 7852 //Loop through all the participants and find the right participant (based on participantExt) 7853 for(idx=0;idx<participants.length;idx=idx+1) 7854 { 7855 part = participants[idx]; 7856 7857 if (part.mediaAddress === participantExt) 7858 { 7859 participantTimerCounters.startTime= part.startTime; 7860 participantTimerCounters.stateChangeTime= part.stateChangeTime; 7861 participantTimerCounters.state= part.state; 7862 break; 7863 } 7864 } 7865 7866 return participantTimerCounters; 7867 }, 7868 7869 7870 /** 7871 * Retrieves a list of media properties from the dialog object. 7872 * @returns {Object} Map of call variables; names mapped to values. 7873 * Variables may include the following:<ul> 7874 * <li>dialedNumber: The number dialed. 7875 * <li>callType: The type of call. Call types include:<ul> 7876 * <li>ACD_IN 7877 * <li>PREROUTE_ACD_IN 7878 * <li>PREROUTE_DIRECT_AGENT 7879 * <li>TRANSFER 7880 * <li>OTHER_IN 7881 * <li>OUT 7882 * <li>AGENT_INSIDE 7883 * <li>CONSULT 7884 * <li>CONFERENCE 7885 * <li>SUPERVISOR_MONITOR 7886 * <li>OUTBOUND 7887 * <li>OUTBOUND_PREVIEW</ul> 7888 * <li>DNIS: The DNIS provided. For routed calls, this is the route point. 7889 * <li>wrapUpReason: A description of the call. 7890 * <li>queueNumber: Number of the agent Skill Group the call is attributed to. 7891 * <li>queueName: Name of the agent Skill Group the call is attributed to. 7892 * <li>callKeyCallId: unique number of the call routed on a particular day. 7893 * <li>callKeyPrefix: represents the day when the call is routed. 7894 * <li>Call Variables, by name. The name indicates whether it is a call variable or ECC variable. 7895 * Call variable names start with callVariable#, where # is 1-10. ECC variable names (both scalar and array) are prepended with "user". 7896 * ECC variable arrays include an index enclosed within square brackets located at the end of the ECC array name. 7897 * <li>The following call variables provide additional details about an Outbound Option call:<ul> 7898 * <li>BACampaign 7899 * <li>BAAccountNumber 7900 * <li>BAResponse 7901 * <li>BAStatus<ul> 7902 * <li>PREDICTIVE_OUTBOUND: A predictive outbound call. 7903 * <li>PROGRESSIVE_OUTBOUND: A progressive outbound call. 7904 * <li>PREVIEW_OUTBOUND_RESERVATION: Agent is reserved for a preview outbound call. 7905 * <li>PREVIEW_OUTBOUND: Agent is on a preview outbound call.</ul> 7906 * <li>BADialedListID 7907 * <li>BATimeZone 7908 * <li>BABuddyName</ul></ul> 7909 * 7910 */ 7911 getMediaProperties: function () { 7912 var mpData, currentMediaPropertiesMap, thisMediaPropertiesJQuery; 7913 7914 this.isLoaded(); 7915 7916 // We have to convert to jQuery object to do a proper compare 7917 thisMediaPropertiesJQuery = jQuery(this.getData().mediaProperties); 7918 7919 if ((this._lastMediaPropertiesJQuery !== undefined) 7920 && (this._lastMediaPropertiesMap !== undefined) 7921 && (this._lastMediaPropertiesJQuery.is(thisMediaPropertiesJQuery))) { 7922 7923 return this._lastMediaPropertiesMap; 7924 } 7925 7926 currentMediaPropertiesMap = {}; 7927 7928 mpData = this.getData().mediaProperties; 7929 7930 if (mpData) { 7931 if (mpData.callvariables && mpData.callvariables.CallVariable) { 7932 if (mpData.callvariables.CallVariable.length === undefined) { 7933 mpData.callvariables.CallVariable = [mpData.callvariables.CallVariable]; 7934 } 7935 jQuery.each(mpData.callvariables.CallVariable, function (i, callVariable) { 7936 currentMediaPropertiesMap[callVariable.name] = callVariable.value; 7937 }); 7938 } 7939 7940 jQuery.each(mpData, function (key, value) { 7941 if (key !== 'callvariables') { 7942 currentMediaPropertiesMap[key] = value; 7943 } 7944 }); 7945 } 7946 7947 this._lastMediaPropertiesMap = currentMediaPropertiesMap; 7948 this._lastMediaPropertiesJQuery = thisMediaPropertiesJQuery; 7949 7950 return this._lastMediaPropertiesMap; 7951 }, 7952 7953 7954 7955 /** 7956 * @private 7957 * Invoke a request to the server given a content body and handlers. 7958 * 7959 * @param {Object} contentBody 7960 * A JS object containing the body of the action request. 7961 * @param {finesse.interfaces.RequestHandlers} handlers 7962 * An object containing the handlers for the request 7963 */ 7964 _makeRequest: function (contentBody, handlers) { 7965 // Protect against null dereferencing of options allowing its 7966 // (nonexistent) keys to be read as undefined 7967 handlers = handlers || {}; 7968 7969 this.restRequest(this.getRestUrl(), { 7970 method: 'PUT', 7971 success: handlers.success, 7972 error: handlers.error, 7973 content: contentBody 7974 }); 7975 } 7976 7977 }); 7978 7979 window.finesse = window.finesse || {}; 7980 window.finesse.restservices = window.finesse.restservices || {}; 7981 window.finesse.restservices.DialogBase = DialogBase; 7982 7983 7984 return DialogBase; 7985 }); 7986 7987 /** 7988 * JavaScript representation of the Finesse Dialog object. 7989 * 7990 * @requires finesse.clientservices.ClientServices 7991 * @requires Class 7992 * @requires finesse.FinesseBase 7993 * @requires finesse.restservices.RestBase 7994 */ 7995 7996 /** @private */ 7997 define('restservices/Dialog',[ 7998 'restservices/DialogBase', 7999 'utilities/Utilities' 8000 ], 8001 function (DialogBase, Utilities) { 8002 var Dialog = DialogBase.extend(/** @lends finesse.restservices.Dialog.prototype */{ 8003 8004 /** 8005 * @class 8006 * A Dialog is an attempted connection between or among multiple participants, 8007 * for example, a regular phone call, a conference, or a silent monitor session. 8008 * 8009 * @augments finesse.restservices.DialogBase 8010 * @constructs 8011 */ 8012 _fakeConstuctor: function () { 8013 /* This is here to hide the real init constructor from the public docs */ 8014 }, 8015 8016 /** 8017 * @private 8018 * 8019 * @param {Object} options 8020 * An object with the following properties:<ul> 8021 * <li><b>id:</b> The id of the object being constructed</li> 8022 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 8023 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 8024 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 8025 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 8026 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 8027 * <li><b>status:</b> {Number} The HTTP status code returned</li> 8028 * <li><b>content:</b> {String} Raw string of response</li> 8029 * <li><b>object:</b> {Object} Parsed object of response</li> 8030 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 8031 * <li><b>errorType:</b> {String} Type of error that was caught</li> 8032 * <li><b>errorMessage:</b> {String} Message associated with error</li> 8033 * </ul></li> 8034 * </ul></li> 8035 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 8036 **/ 8037 init: function (options) { 8038 this._super(options); 8039 }, 8040 8041 /** 8042 * @private 8043 * Gets the REST class for the current object - this is the Dialog class. 8044 * @returns {Object} The Dialog class. 8045 */ 8046 getRestClass: function () { 8047 return Dialog; 8048 }, 8049 8050 /** 8051 * The requestId reaper timeout in ms 8052 */ 8053 REQUESTID_REAPER_TIMEOUT: 5000, 8054 8055 /** 8056 * Getter for the from address. 8057 * @returns {String} The from address. 8058 */ 8059 getFromAddress: function () { 8060 this.isLoaded(); 8061 return this.getData().fromAddress; 8062 }, 8063 8064 /** 8065 * Getter for the to address. 8066 * @returns {String} The to address. 8067 */ 8068 getToAddress: function () { 8069 this.isLoaded(); 8070 return this.getData().toAddress; 8071 }, 8072 8073 /** 8074 * Getter for the secondaryId of a dialog. 8075 * A CONSULT call has two call legs (primary leg and a consult leg). 8076 * As the CONSULT call is completed (either with TRANSFER or CONFERENCE), call legs would be merged. 8077 * The surviving call's Dialog will contain the dropped call's Dialog Id in secondaryId field. 8078 * For CCE deployments, DIRECT_TRANSFER also have the secondaryId populated as mentioned above. 8079 * @returns {String} The id of the secondary dialog. 8080 * @since 11.6(1)-ES1 onwards 8081 */ 8082 getSecondaryId: function () { 8083 this.isLoaded(); 8084 return this.getData().secondaryId; 8085 }, 8086 8087 /** 8088 * gets the participant timer counters 8089 * 8090 * @param {String} participantExt Extension of participant. 8091 * @returns {Object} Array of Participants which contains properties :<ul> 8092 * <li>state - The state of the Participant. 8093 * <li>startTime - The start Time of the Participant. 8094 * <li>stateChangeTime - The time when participant state has changed.</ul> 8095 */ 8096 getParticipantTimerCounters : function (participantExt) { 8097 var part, participantTimerCounters = {}, idx, participants; 8098 8099 participants = this.getParticipants(); 8100 8101 8102 //Loop through all the participants and find the right participant (based on participantExt) 8103 for(idx=0;idx<participants.length;idx=idx+1) 8104 { 8105 part = participants[idx]; 8106 8107 if (part.mediaAddress === participantExt) 8108 { 8109 participantTimerCounters.startTime= part.startTime; 8110 participantTimerCounters.stateChangeTime= part.stateChangeTime; 8111 participantTimerCounters.state= part.state; 8112 break; 8113 } 8114 } 8115 8116 return participantTimerCounters; 8117 }, 8118 8119 /** 8120 * Determines the droppable participants. A droppable participant is a participant that is an agent extension. 8121 * (It is not a CTI Route Point, IVR Port, or the caller) 8122 * 8123 * @param {String} filterExtension used to remove a single extension from the list 8124 * @returns {Object} Array of Participants that can be dropped. 8125 * Participant entity properties are as follows:<ul> 8126 * <li>state - The state of the Participant. 8127 * <li>stateCause - The state cause of the Participant. 8128 * <li>mediaAddress - The media address of the Participant. 8129 * <li>startTime - The start Time of the Participant. 8130 * <li>stateChangeTime - The time when participant state has changed. 8131 * <li>actions - These are the actions that a Participant can perform</ul> 8132 */ 8133 getDroppableParticipants: function (filterExtension) { 8134 this.isLoaded(); 8135 var droppableParticipants = [], participants, index, idx, filterExtensionToRemove = "", callStateOk, part; 8136 8137 participants = this.getParticipants(); 8138 8139 if (filterExtension) 8140 { 8141 filterExtensionToRemove = filterExtension; 8142 } 8143 8144 //Loop through all the participants to remove non-agents & remove filterExtension 8145 //We could have removed filterExtension using splice, but we have to iterate through 8146 //the list anyway. 8147 for(idx=0;idx<participants.length;idx=idx+1) 8148 { 8149 part = participants[idx]; 8150 8151 //Skip the filterExtension 8152 if (part.mediaAddress !== filterExtensionToRemove) 8153 { 8154 callStateOk = this._isParticipantStateDroppable(part); 8155 8156 //Remove non-agents & make sure callstate 8157 if (callStateOk === true && part.mediaAddressType === this._agentDeviceType) 8158 { 8159 droppableParticipants.push(part); 8160 } 8161 } 8162 } 8163 8164 return Utilities.getArray(droppableParticipants); 8165 }, 8166 8167 _isParticipantStateDroppable : function (part) 8168 { 8169 var isParticipantStateDroppable = false; 8170 if (part.state === Dialog.ParticipantStates.ACTIVE || part.state === Dialog.ParticipantStates.ACCEPTED || part.state === Dialog.ParticipantStates.HELD) 8171 { 8172 isParticipantStateDroppable = true; 8173 } 8174 8175 return isParticipantStateDroppable; 8176 }, 8177 8178 /** 8179 * Is the participant droppable 8180 * 8181 * @param {String} participantExt Extension of participant. 8182 * @returns {Boolean} True is droppable. 8183 */ 8184 isParticipantDroppable : function (participantExt) { 8185 var droppableParticipants = null, isDroppable = false, idx, part, callStateOk; 8186 8187 droppableParticipants = this.getDroppableParticipants(); 8188 8189 if (droppableParticipants) 8190 { 8191 for(idx=0;idx<droppableParticipants.length;idx=idx+1) 8192 { 8193 part = droppableParticipants[idx]; 8194 8195 if (part.mediaAddress === participantExt) 8196 { 8197 callStateOk = this._isParticipantStateDroppable(part); 8198 8199 //Remove non-agents & make sure callstate 8200 if (callStateOk === true && part.mediaAddressType === this._agentDeviceType) 8201 { 8202 isDroppable = true; 8203 break; 8204 } 8205 } 8206 } 8207 } 8208 8209 return isDroppable; 8210 }, 8211 8212 /** 8213 * Retrieves information about the currently scheduled callback, if any. 8214 * @returns {Object} If no callback has been set, will return undefined. If 8215 * a callback has been set, it will return a map with one or more of the 8216 * following entries, depending on what values have been set. 8217 * callbackTime - the callback time, if it has been set. 8218 * callbackNumber - the callback number, if it has been set. 8219 */ 8220 getCallbackInfo: function() { 8221 this.isLoaded(); 8222 return this.getData().scheduledCallbackInfo; 8223 }, 8224 8225 /** 8226 * Invoke a consult call out to a destination. 8227 * 8228 * @param {String} mediaAddress 8229 * The media address of the user performing the consult call. 8230 * @param {String} toAddress 8231 * The destination address of the consult call. 8232 * @param {finesse.interfaces.RequestHandlers} handlers 8233 * An object containing the handlers for the request 8234 */ 8235 makeConsultCall: function (mediaAddress, toAddress, handlers) { 8236 this.isLoaded(); 8237 var contentBody = {}; 8238 contentBody[this.getRestType()] = { 8239 "targetMediaAddress": mediaAddress, 8240 "toAddress": toAddress, 8241 "requestedAction": Dialog.Actions.CONSULT_CALL 8242 }; 8243 this._makeRequest(contentBody, handlers); 8244 return this; // Allow cascading 8245 }, 8246 8247 /** 8248 * Invoke a single step transfer request. 8249 * 8250 * @param {String} mediaAddress 8251 * The media address of the user performing the single step transfer. 8252 * @param {String} toAddress 8253 * The destination address of the single step transfer. 8254 * @param {finesse.interfaces.RequestHandlers} handlers 8255 * An object containing the handlers for the request 8256 */ 8257 initiateDirectTransfer: function (mediaAddress, toAddress, handlers) { 8258 this.isLoaded(); 8259 var contentBody = {}; 8260 contentBody[this.getRestType()] = { 8261 "targetMediaAddress": mediaAddress, 8262 "toAddress": toAddress, 8263 "requestedAction": Dialog.Actions.TRANSFER_SST 8264 }; 8265 this._makeRequest(contentBody, handlers); 8266 return this; // Allow cascading 8267 }, 8268 8269 /** 8270 * Update this dialog's wrap-up reason. 8271 * 8272 * @param {String} wrapUpReason 8273 * The new wrap-up reason for this dialog 8274 * @param {finesse.interfaces.RequestHandlers} handlers 8275 * An object containing the handlers for the request 8276 */ 8277 updateWrapUpReason: function (wrapUpReason, options) 8278 { 8279 this.isLoaded(); 8280 var mediaProperties = 8281 { 8282 "wrapUpReason": wrapUpReason 8283 }; 8284 8285 options = options || {}; 8286 options.content = {}; 8287 options.content[this.getRestType()] = 8288 { 8289 "mediaProperties": mediaProperties, 8290 "requestedAction": Dialog.Actions.UPDATE_CALL_DATA 8291 }; 8292 options.method = "PUT"; 8293 this.restRequest(this.getRestUrl(), options); 8294 8295 return this; 8296 }, 8297 8298 /** 8299 * Invoke a request to server based on the action given. 8300 * @param {String} mediaAddress 8301 * The media address of the user performing the action. 8302 * @param {finesse.restservices.Dialog.Actions} action 8303 * The action string indicating the action to invoke on dialog. 8304 * @param {finesse.interfaces.RequestHandlers} handlers 8305 * An object containing the handlers for the request 8306 */ 8307 requestAction: function (mediaAddress, action, handlers) { 8308 this.isLoaded(); 8309 var contentBody = {}; 8310 contentBody[this.getRestType()] = { 8311 "targetMediaAddress": mediaAddress, 8312 "requestedAction": action 8313 }; 8314 this._makeRequest(contentBody, handlers); 8315 return this; // Allow cascading 8316 }, 8317 8318 /** 8319 * Wrapper around "requestAction" to request PARTICIPANT_DROP action. 8320 * 8321 * @param targetMediaAddress is the address to drop 8322 * @param {finesse.interfaces.RequestHandlers} handlers 8323 * An object containing the handlers for the request 8324 */ 8325 dropParticipant: function (targetMediaAddress, handlers) { 8326 this.requestAction(targetMediaAddress, Dialog.Actions.PARTICIPANT_DROP, handlers); 8327 }, 8328 8329 /** 8330 * Invoke a request to server to send DTMF digit tones. 8331 * @param {String} mediaAddress 8332 * @param {finesse.interfaces.RequestHandlers} handlers 8333 * An object containing the handlers for the request 8334 * @param {String} digit 8335 * The digit which causes invocation of an action on the dialog. 8336 */ 8337 sendDTMFRequest: function (mediaAddress, handlers, digit) { 8338 this.isLoaded(); 8339 var contentBody = {}; 8340 contentBody[this.getRestType()] = { 8341 "targetMediaAddress": mediaAddress, 8342 "requestedAction": "SEND_DTMF", 8343 "actionParams": { 8344 "ActionParam": { 8345 "name": "dtmfString", 8346 "value": digit 8347 } 8348 } 8349 }; 8350 this._makeRequest(contentBody, handlers); 8351 return this; // Allow cascading 8352 }, 8353 8354 /** 8355 * Invoke a request to server to set the time for a callback. 8356 * @param {String} mediaAddress 8357 * @param {String} callbackTime 8358 * The requested time for the callback, in YYYY-MM-DDTHH:MM format 8359 * (ex: 2013-12-24T23:59) 8360 * @param {finesse.interfaces.RequestHandlers} handlers 8361 * An object containing the handlers for the request 8362 */ 8363 updateCallbackTime: function (mediaAddress, callbackTime, handlers) { 8364 this.isLoaded(); 8365 var contentBody = {}; 8366 contentBody[this.getRestType()] = { 8367 "targetMediaAddress": mediaAddress, 8368 "requestedAction": Dialog.Actions.UPDATE_SCHEDULED_CALLBACK, 8369 "actionParams": { 8370 "ActionParam": { 8371 "name": "callbackTime", 8372 "value": callbackTime 8373 } 8374 } 8375 }; 8376 this._makeRequest(contentBody, handlers); 8377 return this; // Allow cascading 8378 }, 8379 8380 /** 8381 * Invoke a request to server to set the number for a callback. 8382 * @param {String} mediaAddress 8383 * @param {String} callbackNumber 8384 * The requested number to call for the callback 8385 * @param {finesse.interfaces.RequestHandlers} handlers 8386 * An object containing the handlers for the request 8387 */ 8388 updateCallbackNumber: function (mediaAddress, callbackNumber, handlers) { 8389 this.isLoaded(); 8390 var contentBody = {}; 8391 contentBody[this.getRestType()] = { 8392 "targetMediaAddress": mediaAddress, 8393 "requestedAction": Dialog.Actions.UPDATE_SCHEDULED_CALLBACK, 8394 "actionParams": { 8395 "ActionParam": { 8396 "name": "callbackNumber", 8397 "value": callbackNumber 8398 } 8399 } 8400 }; 8401 this._makeRequest(contentBody, handlers); 8402 return this; // Allow cascading 8403 }, 8404 8405 /** 8406 * Invoke a request to server to cancel a callback. 8407 * @param {String} mediaAddress 8408 * @param {finesse.interfaces.RequestHandlers} handlers 8409 * An object containing the handlers for the request 8410 */ 8411 cancelCallback: function (mediaAddress, handlers) { 8412 this.isLoaded(); 8413 var contentBody = {}; 8414 contentBody[this.getRestType()] = { 8415 "targetMediaAddress": mediaAddress, 8416 "requestedAction": Dialog.Actions.CANCEL_SCHEDULED_CALLBACK 8417 }; 8418 this._makeRequest(contentBody, handlers); 8419 return this; // Allow cascading 8420 }, 8421 8422 /** 8423 * Invoke a request to server to reclassify the call type. 8424 * @param {String} mediaAddress 8425 * The media address of the user performing the consult call. 8426 * @param {String} classification 8427 * The classification to assign to the call. Valid values are "VOICE", "FAX", 8428 * "ANS_MACHINE", "INVALID", "BUSY" (CCX only), and "DO_NOT_CALL". 8429 * @param {finesse.interfaces.RequestHandlers} handlers 8430 * An object containing the handlers for the request 8431 */ 8432 reclassifyCall: function (mediaAddress, classification, handlers) { 8433 this.isLoaded(); 8434 var contentBody = {}; 8435 contentBody[this.getRestType()] = { 8436 "targetMediaAddress": mediaAddress, 8437 "requestedAction": Dialog.Actions.RECLASSIFY, 8438 "actionParams": { 8439 "ActionParam": { 8440 "name": "outboundClassification", 8441 "value": classification 8442 } 8443 } 8444 }; 8445 this._makeRequest(contentBody, handlers); 8446 return this; // Allow cascading 8447 }, 8448 8449 /** 8450 * Utility method to create a closure containing the requestId and the Dialogs object so 8451 * that the _pendingCallbacks map can be manipulated when the timer task is executed. 8452 * @param {String} requestId The requestId of the event 8453 * @return {Function} The function to be executed by setTimeout 8454 */ 8455 _createRequestIdReaper: function (requestId) { 8456 var that = this; 8457 return function () { 8458 that._logger.log("Dialog: clearing the requestId-to-callbacks mapping for requestId=" + requestId); 8459 delete that._pendingCallbacks[requestId]; 8460 }; 8461 }, 8462 8463 /** 8464 * Overriding implementation of the one in RestBase.js 8465 * This determines the strategy that Dialogs will take after processing an event that contains a requestId. 8466 * @param {String} requestId The requestId of the event 8467 */ 8468 _postProcessUpdateStrategy: function (requestId) { 8469 this._logger.log("Dialog: determining whether to set timeout for clearing requestId-to-callbacks mapping requestId=" + requestId); 8470 var callbacksObj = this._pendingCallbacks[requestId]; 8471 if (callbacksObj && !callbacksObj.used) { 8472 this._logger.log("Dialog: setting timeout for clearing requestId-to-callbacks mapping requestId=" + requestId); 8473 setTimeout(this._createRequestIdReaper(requestId), this.REQUESTID_REAPER_TIMEOUT); 8474 callbacksObj.used = true; 8475 } 8476 } 8477 8478 }); 8479 8480 Dialog.Actions = /** @lends finesse.restservices.Dialog.Actions.prototype */ { 8481 /** 8482 * Drops the Participant from the Dialog. 8483 */ 8484 DROP: "DROP", 8485 /** 8486 * Answers a Dialog. 8487 */ 8488 ANSWER: "ANSWER", 8489 /** 8490 * Holds the Dialog. 8491 */ 8492 HOLD: "HOLD", 8493 /** 8494 * Barges into a Call Dialog. 8495 */ 8496 BARGE_CALL: "BARGE_CALL", 8497 /** 8498 * Allow as Supervisor to Drop a Participant from the Dialog. 8499 */ 8500 PARTICIPANT_DROP: "PARTICIPANT_DROP", 8501 /** 8502 * Makes a new Call Dialog. 8503 */ 8504 MAKE_CALL: "MAKE_CALL", 8505 /** 8506 * Retrieves a Dialog that is on Hold. 8507 */ 8508 RETRIEVE: "RETRIEVE", 8509 /** 8510 * Sets the time or number for a callback. Can be 8511 * either a new callback, or updating an existing one. 8512 */ 8513 UPDATE_SCHEDULED_CALLBACK: "UPDATE_SCHEDULED_CALLBACK", 8514 /** 8515 * Cancels a callback. 8516 */ 8517 CANCEL_SCHEDULED_CALLBACK: "CANCEL_SCHEDULED_CALLBACK", 8518 /** 8519 * Initiates a Consult Call. 8520 */ 8521 CONSULT_CALL: "CONSULT_CALL", 8522 /** 8523 * Initiates a Transfer of a Dialog. 8524 */ 8525 TRANSFER: "TRANSFER", 8526 /** 8527 * Initiates a Single-Step Transfer of a Dialog. 8528 */ 8529 TRANSFER_SST: "TRANSFER_SST", 8530 /** 8531 * Initiates a Conference of a Dialog. 8532 */ 8533 CONFERENCE: "CONFERENCE", 8534 /** 8535 * Changes classification for a call 8536 */ 8537 RECLASSIFY: "RECLASSIFY", 8538 /** 8539 * Updates data on a Call Dialog. 8540 */ 8541 UPDATE_CALL_DATA: "UPDATE_CALL_DATA", 8542 /** 8543 * Initiates a Recording on a Call Dialog. 8544 */ 8545 START_RECORDING : "START_RECORDING", 8546 /** 8547 * Sends DTMF (dialed digits) to a Call Dialog. 8548 */ 8549 DTMF : "SEND_DTMF", 8550 /** 8551 * Accepts a Dialog that is being Previewed. 8552 */ 8553 ACCEPT: "ACCEPT", 8554 /** 8555 * Rejects a Dialog. 8556 */ 8557 REJECT: "REJECT", 8558 /** 8559 * Closes a Dialog. 8560 */ 8561 CLOSE : "CLOSE", 8562 /** 8563 * @class Set of action constants for a Dialog. These should be used for 8564 * {@link finesse.restservices.Dialog#requestAction}. 8565 * @constructs 8566 */ 8567 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 8568 }; 8569 8570 Dialog.States = /** @lends finesse.restservices.Dialog.States.prototype */ { 8571 /** 8572 * Indicates that the call is ringing at a device. 8573 */ 8574 ALERTING: "ALERTING", 8575 /** 8576 * Indicates that the phone is off the hook at a device. 8577 */ 8578 INITIATING: "INITIATING", 8579 /** 8580 * Indicates that the dialog has a least one active participant. 8581 */ 8582 ACTIVE: "ACTIVE", 8583 /** 8584 * Indicates that the dialog has no active participants. 8585 */ 8586 DROPPED: "DROPPED", 8587 /** 8588 * Indicates that the phone is dialing at the device. 8589 */ 8590 INITIATED: "INITIATED", 8591 /** 8592 * Indicates that the dialog has failed. 8593 * @see Dialog.ReasonStates 8594 */ 8595 FAILED: "FAILED", 8596 /** 8597 * Indicates that the user has accepted an OUTBOUND_PREVIEW dialog. 8598 */ 8599 ACCEPTED: "ACCEPTED", 8600 /** 8601 * @class Possible Dialog State constants. 8602 * The State flow of a typical in-bound Dialog is as follows: INITIATING, INITIATED, ALERTING, ACTIVE, DROPPED. 8603 * @constructs 8604 */ 8605 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 8606 }; 8607 8608 Dialog.ParticipantStates = /** @lends finesse.restservices.Dialog.ParticipantStates.prototype */ { 8609 /** 8610 * Indicates that an incoming call is ringing on the device. 8611 */ 8612 ALERTING: "ALERTING", 8613 /** 8614 * Indicates that an outgoing call, not yet active, exists on the device. 8615 */ 8616 INITIATING: "INITIATING", 8617 /** 8618 * Indicates that the participant is active on the call. 8619 */ 8620 ACTIVE: "ACTIVE", 8621 /** 8622 * Indicates that the participant has dropped from the call. 8623 */ 8624 DROPPED: "DROPPED", 8625 /** 8626 * Indicates that the participant has held their connection to the call. 8627 */ 8628 HELD: "HELD", 8629 /** 8630 * Indicates that the phone is dialing at a device. 8631 */ 8632 INITIATED: "INITIATED", 8633 /** 8634 * Indicates that the call failed. 8635 * @see Dialog.ReasonStates 8636 */ 8637 FAILED: "FAILED", 8638 /** 8639 * Indicates that the participant is not in active state on the call, but is wrapping up after the participant has dropped from the call. 8640 */ 8641 WRAP_UP: "WRAP_UP", 8642 /** 8643 * Indicates that the participant has accepted the dialog. This state is applicable to OUTBOUND_PREVIEW dialogs. 8644 */ 8645 ACCEPTED: "ACCEPTED", 8646 /** 8647 * @class Possible Dialog Participant State constants. 8648 * @constructs 8649 */ 8650 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 8651 }; 8652 8653 Dialog.ReasonStates = /** @lends finesse.restservices.Dialog.ReasonStates.prototype */ { 8654 /** 8655 * Dialog was Busy. This will typically be for a Failed Dialog. 8656 */ 8657 BUSY: "BUSY", 8658 /** 8659 * Dialog reached a Bad Destination. This will typically be for a Failed Dialog. 8660 */ 8661 BAD_DESTINATION: "BAD_DESTINATION", 8662 /** 8663 * All Other Reasons. This will typically be for a Failed Dialog. 8664 */ 8665 OTHER: "OTHER", 8666 /** 8667 * The Device Resource for the Dialog was not available. 8668 */ 8669 DEVICE_RESOURCE_NOT_AVAILABLE : "DEVICE_RESOURCE_NOT_AVAILABLE", 8670 /** 8671 * @class Possible dialog state reasons code constants. 8672 * @constructs 8673 */ 8674 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 8675 }; 8676 8677 window.finesse = window.finesse || {}; 8678 window.finesse.restservices = window.finesse.restservices || {}; 8679 window.finesse.restservices.Dialog = Dialog; 8680 8681 8682 return Dialog; 8683 }); 8684 8685 /** 8686 * JavaScript representation of the Finesse Dialogs collection 8687 * object which contains a list of Dialog objects. 8688 * 8689 * @requires finesse.clientservices.ClientServices 8690 * @requires Class 8691 * @requires finesse.FinesseBase 8692 * @requires finesse.restservices.RestBase 8693 * @requires finesse.restservices.Dialog 8694 */ 8695 /** @private */ 8696 define('restservices/Dialogs',[ 8697 'restservices/RestCollectionBase', 8698 'restservices/Dialog' 8699 ], 8700 function (RestCollectionBase, Dialog) { 8701 var Dialogs = RestCollectionBase.extend(/** @lends finesse.restservices.Dialogs.prototype */{ 8702 8703 /** 8704 * @class 8705 * JavaScript representation of a Dialogs collection object. Also exposes 8706 * methods to operate on the object against the server. 8707 * @augments finesse.restservices.RestCollectionBase 8708 * @constructs 8709 * @see finesse.restservices.Dialog 8710 * @example 8711 * _dialogs = _user.getDialogs( { 8712 * onCollectionAdd : _handleDialogAdd, 8713 * onCollectionDelete : _handleDialogDelete, 8714 * onLoad : _handleDialogsLoaded 8715 * }); 8716 * 8717 * _dialogCollection = _dialogs.getCollection(); 8718 * for (var dialogId in _dialogCollection) { 8719 * if (_dialogCollection.hasOwnProperty(dialogId)) { 8720 * _dialog = _dialogCollection[dialogId]; 8721 * etc... 8722 * } 8723 * } 8724 */ 8725 _fakeConstuctor: function () { 8726 /* This is here to hide the real init constructor from the public docs */ 8727 }, 8728 8729 /** 8730 * @private 8731 * @param {Object} options 8732 * An object with the following properties:<ul> 8733 * <li><b>id:</b> The id of the object being constructed</li> 8734 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 8735 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 8736 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 8737 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 8738 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 8739 * <li><b>status:</b> {Number} The HTTP status code returned</li> 8740 * <li><b>content:</b> {String} Raw string of response</li> 8741 * <li><b>object:</b> {Object} Parsed object of response</li> 8742 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 8743 * <li><b>errorType:</b> {String} Type of error that was caught</li> 8744 * <li><b>errorMessage:</b> {String} Message associated with error</li> 8745 * </ul></li> 8746 * </ul></li> 8747 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 8748 **/ 8749 init: function (options) { 8750 this._super(options); 8751 }, 8752 8753 /** 8754 * @private 8755 * Gets the REST class for the current object - this is the Dialogs class. 8756 */ 8757 getRestClass: function () { 8758 return Dialogs; 8759 }, 8760 8761 /** 8762 * @private 8763 * Gets the REST class for the objects that make up the collection. - this 8764 * is the Dialog class. 8765 */ 8766 getRestItemClass: function () { 8767 return Dialog; 8768 }, 8769 8770 /** 8771 * @private 8772 * Gets the REST type for the current object - this is a "Dialogs". 8773 */ 8774 getRestType: function () { 8775 return "Dialogs"; 8776 }, 8777 8778 /** 8779 * @private 8780 * Gets the REST type for the objects that make up the collection - this is "Dialogs". 8781 */ 8782 getRestItemType: function () { 8783 return "Dialog"; 8784 }, 8785 8786 /** 8787 * @private 8788 * Override default to indicates that the collection doesn't support making 8789 * requests. 8790 */ 8791 supportsRequests: true, 8792 8793 /** 8794 * @private 8795 * Override default to indicates that the collection subscribes to its objects. 8796 */ 8797 supportsRestItemSubscriptions: true, 8798 8799 /** 8800 * The requestId reaper timeout in ms 8801 */ 8802 REQUESTID_REAPER_TIMEOUT: 5000, 8803 8804 /** 8805 * @private 8806 * Create a new Dialog in this collection 8807 * 8808 * @param {String} toAddress 8809 * The to address of the new Dialog 8810 * @param {String} fromAddress 8811 * The from address of the new Dialog 8812 * @param {finesse.interfaces.RequestHandlers} handlers 8813 * An object containing the (optional) handlers for the request. 8814 * @return {finesse.restservices.Dialogs} 8815 * This Dialogs object, to allow cascading. 8816 */ 8817 createNewCallDialog: function (toAddress, fromAddress, handlers) 8818 { 8819 var contentBody = {}; 8820 contentBody[this.getRestItemType()] = { 8821 "requestedAction": "MAKE_CALL", 8822 "toAddress": toAddress, 8823 "fromAddress": fromAddress 8824 }; 8825 8826 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 8827 handlers = handlers || {}; 8828 8829 this.restRequest(this.getRestUrl(), { 8830 method: 'POST', 8831 success: handlers.success, 8832 error: handlers.error, 8833 content: contentBody 8834 }); 8835 return this; // Allow cascading 8836 }, 8837 8838 /** 8839 * @private 8840 * Create a new Dialog in this collection as a result of a requested action 8841 * 8842 * @param {String} toAddress 8843 * The to address of the new Dialog 8844 * @param {String} fromAddress 8845 * The from address of the new Dialog 8846 * @param {finesse.restservices.Dialog.Actions} actionType 8847 * The associated action to request for creating this new dialog 8848 * @param {finesse.interfaces.RequestHandlers} handlers 8849 * An object containing the (optional) handlers for the request. 8850 * @return {finesse.restservices.Dialogs} 8851 * This Dialogs object, to allow cascading. 8852 */ 8853 createNewSuperviseCallDialog: function (toAddress, fromAddress, actionType, handlers) 8854 { 8855 var contentBody = {}; 8856 this._isLoaded = true; 8857 8858 contentBody[this.getRestItemType()] = { 8859 "requestedAction": actionType, 8860 "toAddress": toAddress, 8861 "fromAddress": fromAddress 8862 }; 8863 8864 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 8865 handlers = handlers || {}; 8866 8867 this.restRequest(this.getRestUrl(), { 8868 method: 'POST', 8869 success: handlers.success, 8870 error: handlers.error, 8871 content: contentBody 8872 }); 8873 return this; // Allow cascading 8874 }, 8875 8876 /** 8877 * @private 8878 * Create a new Dialog in this collection as a result of a requested action 8879 * @param {String} fromAddress 8880 * The from address of the new Dialog 8881 * @param {String} toAddress 8882 * The to address of the new Dialog 8883 * @param {finesse.restservices.Dialog.Actions} actionType 8884 * The associated action to request for creating this new dialog 8885 * @param {String} dialogUri 8886 * The associated uri of SUPERVISOR_MONITOR call 8887 * @param {finesse.interfaces.RequestHandlers} handlers 8888 * An object containing the (optional) handlers for the request. 8889 * @return {finesse.restservices.Dialogs} 8890 * This Dialogs object, to allow cascading. 8891 */ 8892 createNewBargeCall: function (fromAddress, toAddress, actionType, dialogURI, handlers) { 8893 this.isLoaded(); 8894 8895 var contentBody = {}; 8896 contentBody[this.getRestItemType()] = { 8897 "fromAddress": fromAddress, 8898 "toAddress": toAddress, 8899 "requestedAction": actionType, 8900 "associatedDialogUri": dialogURI 8901 8902 }; 8903 // (nonexistent) keys to be read as undefined 8904 handlers = handlers || {}; 8905 this.restRequest(this.getRestUrl(), { 8906 method: 'POST', 8907 success: handlers.success, 8908 error: handlers.error, 8909 content: contentBody 8910 }); 8911 return this; // Allow cascading 8912 }, 8913 8914 /** 8915 * Utility method to get the number of dialogs in this collection. 8916 * 'excludeSilentMonitor' flag is provided as an option to exclude calls with type 8917 * 'SUPERVISOR_MONITOR' from the count. 8918 * @param {Boolean} excludeSilentMonitor If true, calls with type of 'SUPERVISOR_MONITOR' will be excluded from the count. 8919 * @return {Number} The number of dialogs in this collection. 8920 */ 8921 getDialogCount: function (excludeSilentMonitor) { 8922 this.isLoaded(); 8923 8924 var dialogId, count = 0; 8925 if (excludeSilentMonitor) { 8926 for (dialogId in this._collection) { 8927 if (this._collection.hasOwnProperty(dialogId)) { 8928 if (this._collection[dialogId].getCallType() !== 'SUPERVISOR_MONITOR') { 8929 count += 1; 8930 } 8931 } 8932 } 8933 8934 return count; 8935 } else { 8936 return this.length; 8937 } 8938 }, 8939 8940 /** 8941 * Utility method to create a closure containing the requestId and the Dialogs object so 8942 * that the _pendingCallbacks map can be manipulated when the timer task is executed. 8943 * @param {String} requestId The requestId of the event 8944 * @return {Function} The function to be executed by setTimeout 8945 */ 8946 _createRequestIdReaper: function (requestId) { 8947 var that = this; 8948 return function () { 8949 that._logger.log("Dialogs: clearing the requestId-to-callbacks mapping for requestId=" + requestId); 8950 delete that._pendingCallbacks[requestId]; 8951 }; 8952 }, 8953 8954 /** 8955 * Overriding implementation of the one in RestBase.js 8956 * This determines the strategy that Dialogs will take after processing an event that contains a requestId. 8957 * @param {String} requestId The requestId of the event 8958 */ 8959 _postProcessUpdateStrategy: function (requestId) { 8960 this._logger.log("Dialogs: determining whether to set timeout for clearing requestId-to-callbacks mapping requestId=" + requestId); 8961 var callbacksObj = this._pendingCallbacks[requestId]; 8962 if (callbacksObj && !callbacksObj.used) { 8963 this._logger.log("Dialogs: setting timeout for clearing requestId-to-callbacks mapping requestId=" + requestId); 8964 setTimeout(this._createRequestIdReaper(requestId), this.REQUESTID_REAPER_TIMEOUT); 8965 callbacksObj.used = true; 8966 } 8967 } 8968 8969 }); 8970 8971 window.finesse = window.finesse || {}; 8972 window.finesse.restservices = window.finesse.restservices || {}; 8973 window.finesse.restservices.Dialogs = Dialogs; 8974 8975 return Dialogs; 8976 }); 8977 8978 /** 8979 * JavaScript representation of the Finesse ClientLog object 8980 * 8981 * @requires finesse.clientservices.ClientServices 8982 * @requires Class 8983 * @requires finesse.FinesseBase 8984 * @requires finesse.restservices.RestBase 8985 */ 8986 8987 /** The following comment is to prevent jslint errors about 8988 * using variables before they are defined. 8989 */ 8990 /** @private */ 8991 /*global finesse*/ 8992 8993 define('restservices/ClientLog',["restservices/RestBase"], function (RestBase) { 8994 8995 var ClientLog = RestBase.extend(/** @lends finesse.restservices.ClientLog.prototype */{ 8996 /** 8997 * @private 8998 * Returns whether this object supports transport logs 8999 */ 9000 doNotLog : true, 9001 9002 explicitSubscription : true, 9003 9004 /** 9005 * @class 9006 * @private 9007 * JavaScript representation of a ClientLog object. Also exposes methods to operate 9008 * on the object against the server. 9009 * 9010 * @param {Object} options 9011 * An object with the following properties:<ul> 9012 * <li><b>id:</b> The id of the object being constructed</li> 9013 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 9014 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 9015 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 9016 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 9017 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 9018 * <li><b>status:</b> {Number} The HTTP status code returned</li> 9019 * <li><b>content:</b> {String} Raw string of response</li> 9020 * <li><b>object:</b> {Object} Parsed object of response</li> 9021 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 9022 * <li><b>errorType:</b> {String} Type of error that was caught</li> 9023 * <li><b>errorMessage:</b> {String} Message associated with error</li> 9024 * </ul></li> 9025 * </ul></li> 9026 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 9027 * @constructs 9028 * @augments finesse.restservices.RestBase 9029 **/ 9030 init: function (options) { 9031 this._super({ 9032 id: "", 9033 data: {clientLog : null}, 9034 onAdd: options.onAdd, 9035 onChange: options.onChange, 9036 onLoad: options.onLoad, 9037 onError: options.onError, 9038 parentObj: options.parentObj 9039 }); 9040 }, 9041 9042 /** 9043 * @private 9044 * Gets the REST class for the current object - this is the ClientLog object. 9045 */ 9046 getRestClass: function () { 9047 return ClientLog; 9048 }, 9049 9050 /** 9051 * @private 9052 * Gets the REST type for the current object - this is a "ClientLog". 9053 */ 9054 getRestType: function () 9055 { 9056 return "ClientLog"; 9057 }, 9058 9059 /** 9060 * @private 9061 * Gets the node path for the current object 9062 * @returns {String} The node path 9063 */ 9064 getXMPPNodePath: function () { 9065 return this.getRestUrl(); 9066 }, 9067 9068 /** 9069 * @private 9070 * Utility method to fetch this object from the server, however we 9071 * override it for ClientLog to not do anything because GET is not supported 9072 * for ClientLog object. 9073 */ 9074 _doGET: function (handlers) { 9075 return; 9076 }, 9077 9078 /** 9079 * @private 9080 * Invoke a request to the server given a content body and handlers. 9081 * 9082 * @param {Object} contentBody 9083 * A JS object containing the body of the action request. 9084 * @param {Object} handlers 9085 * An object containing the following (optional) handlers for the request:<ul> 9086 * <li><b>success(rsp):</b> A callback function for a successful request to be invoked with the following 9087 * response object as its only parameter:<ul> 9088 * <li><b>status:</b> {Number} The HTTP status code returned</li> 9089 * <li><b>content:</b> {String} Raw string of response</li> 9090 * <li><b>object:</b> {Object} Parsed object of response</li></ul> 9091 * <li>A error callback function for an unsuccessful request to be invoked with the 9092 * error response object as its only parameter:<ul> 9093 * <li><b>status:</b> {Number} The HTTP status code returned</li> 9094 * <li><b>content:</b> {String} Raw string of response</li> 9095 * <li><b>object:</b> {Object} Parsed object of response (HTTP errors)</li> 9096 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 9097 * <li><b>errorType:</b> {String} Type of error that was caught</li> 9098 * <li><b>errorMessage:</b> {String} Message associated with error</li> 9099 * </ul></li> 9100 * </ul> 9101 */ 9102 sendLogs: function (contentBody, handlers) { 9103 // Protect against null dereferencing of options allowing its 9104 // (nonexistent) keys to be read as undefined 9105 handlers = handlers || {}; 9106 9107 this.restRequest(this.getRestUrl(), { 9108 method: 'POST', 9109 //success: handlers.success, 9110 error: handlers.error, 9111 content: contentBody 9112 }); 9113 } 9114 }); 9115 9116 window.finesse = window.finesse || {}; 9117 window.finesse.restservices = window.finesse.restservices || {}; 9118 window.finesse.restservices.ClientLog = ClientLog; 9119 9120 return ClientLog; 9121 }); 9122 9123 /** 9124 * JavaScript representation of the Finesse Queue object 9125 * @requires finesse.clientservices.ClientServices 9126 * @requires Class 9127 * @requires finesse.FinesseBase 9128 * @requires finesse.restservices.RestBase 9129 */ 9130 9131 /** @private */ 9132 define('restservices/Queue',[ 9133 'restservices/RestBase', 9134 'utilities/Utilities' 9135 ], 9136 function (RestBase, Utilities) { 9137 var Queue = RestBase.extend(/** @lends finesse.restservices.Queue.prototype */{ 9138 9139 /** 9140 * @class 9141 * A Queue is a list of Contacts available to a User for quick dial. 9142 * 9143 * @augments finesse.restservices.RestBase 9144 * @constructs 9145 */ 9146 _fakeConstuctor: function () { 9147 /* This is here to hide the real init constructor from the public docs */ 9148 }, 9149 9150 /** 9151 * @private 9152 * JavaScript representation of a Queue object. Also exposes methods to operate 9153 * on the object against the server. 9154 * 9155 * @constructor 9156 * @param {String} id 9157 * Not required... 9158 * @param {Object} callbacks 9159 * An object containing callbacks for instantiation and runtime 9160 * @param {Function} callbacks.onLoad(this) 9161 * Callback to invoke upon successful instantiation 9162 * @param {Function} callbacks.onLoadError(rsp) 9163 * Callback to invoke on instantiation REST request error 9164 * as passed by finesse.clientservices.ClientServices.ajax() 9165 * { 9166 * status: {Number} The HTTP status code returned 9167 * content: {String} Raw string of response 9168 * object: {Object} Parsed object of response 9169 * error: {Object} Wrapped exception that was caught 9170 * error.errorType: {String} Type of error that was caught 9171 * error.errorMessage: {String} Message associated with error 9172 * } 9173 * @param {Function} callbacks.onChange(this) 9174 * Callback to invoke upon successful update 9175 * @param {Function} callbacks.onError(rsp) 9176 * Callback to invoke on update error (refresh or event) 9177 * as passed by finesse.clientservices.ClientServices.ajax() 9178 * { 9179 * status: {Number} The HTTP status code returned 9180 * content: {String} Raw string of response 9181 * object: {Object} Parsed object of response 9182 * error: {Object} Wrapped exception that was caught 9183 * error.errorType: {String} Type of error that was caught 9184 * error.errorMessage: {String} Message associated with error 9185 * } 9186 * 9187 */ 9188 init: function (id, callbacks, restObj) { 9189 this._super(id, callbacks, restObj); 9190 }, 9191 9192 /** 9193 * @private 9194 * Gets the REST class for the current object - this is the Queue object. 9195 */ 9196 getRestClass: function () { 9197 return Queue; 9198 }, 9199 9200 /** 9201 * @private 9202 * Gets the REST type for the current object - this is a "Queue". 9203 */ 9204 getRestType: function () { 9205 return "Queue"; 9206 }, 9207 9208 /** 9209 * @private 9210 * Returns whether this object supports subscriptions 9211 */ 9212 supportsSubscriptions: function () { 9213 return true; 9214 }, 9215 9216 /** 9217 * @private 9218 * Specifies whether this object's subscriptions need to be explicitly requested 9219 */ 9220 explicitSubscription: true, 9221 9222 /** 9223 * @private 9224 * Gets the node path for the current object - this is the team Users node 9225 * @returns {String} The node path 9226 */ 9227 getXMPPNodePath: function () { 9228 return this.getRestUrl(); 9229 }, 9230 9231 /** 9232 * Getter for the queue id 9233 * @returns {String} 9234 * The id of the Queue 9235 */ 9236 getId: function () { 9237 this.isLoaded(); 9238 return this._id; 9239 }, 9240 9241 /** 9242 * Getter for the queue name 9243 * @returns {String} 9244 * The name of the Queue 9245 */ 9246 getName: function () { 9247 this.isLoaded(); 9248 return this.getData().name; 9249 }, 9250 9251 /** 9252 * Getter for the queue statistics. 9253 * Supported statistics include:<br> 9254 * - callsInQueue<br> 9255 * - startTimeOfLongestCallInQueue<br> 9256 * <br> 9257 * These statistics can be accessed via dot notation:<br> 9258 * i.e.: getStatistics().callsInQueue 9259 * @returns {Object} 9260 * The Object with different statistics as properties. 9261 */ 9262 getStatistics: function () { 9263 this.isLoaded(); 9264 return this.getData().statistics; 9265 }, 9266 9267 /** 9268 * Parses a uriString to retrieve the id portion 9269 * @param {String} uriString 9270 * @return {String} id 9271 */ 9272 _parseIdFromUriString : function (uriString) { 9273 return Utilities.getId(uriString); 9274 } 9275 9276 }); 9277 9278 window.finesse = window.finesse || {}; 9279 window.finesse.restservices = window.finesse.restservices || {}; 9280 window.finesse.restservices.Queue = Queue; 9281 9282 return Queue; 9283 }); 9284 9285 /** 9286 * JavaScript representation of the Finesse Queues collection 9287 * object which contains a list of Queue objects. 9288 * @requires finesse.clientservices.ClientServices 9289 * @requires Class 9290 * @requires finesse.FinesseBase 9291 * @requires finesse.restservices.RestBase 9292 * @requires finesse.restservices.RestCollectionBase 9293 */ 9294 9295 /** 9296 * @class 9297 * JavaScript representation of a Queues collection object. 9298 * 9299 * @constructor 9300 * @borrows finesse.restservices.RestCollectionBase as finesse.restservices.Queues 9301 */ 9302 9303 /** @private */ 9304 define('restservices/Queues',[ 9305 'restservices/RestCollectionBase', 9306 'restservices/Queue' 9307 ], 9308 function (RestCollectionBase, Queue) { 9309 var Queues = RestCollectionBase.extend(/** @lends finesse.restservices.Queues.prototype */{ 9310 9311 /** 9312 * @class 9313 * JavaScript representation of a Queues collection object. 9314 * @augments finesse.restservices.RestCollectionBase 9315 * @constructs 9316 * @see finesse.restservices.Queue 9317 * @example 9318 * _queues = _user.getQueues( { 9319 * onCollectionAdd : _handleQueueAdd, 9320 * onCollectionDelete : _handleQueueDelete, 9321 * onLoad : _handleQueuesLoaded 9322 * }); 9323 * 9324 * _queueCollection = _queues.getCollection(); 9325 * for (var queueId in _queueCollection) { 9326 * if (_queueCollection.hasOwnProperty(queueId)) { 9327 * _queue = _queueCollection[queueId]; 9328 * etc... 9329 * } 9330 * } 9331 */ 9332 _fakeConstuctor: function () { 9333 /* This is here to hide the real init constructor from the public docs */ 9334 }, 9335 9336 /** 9337 * @private 9338 * JavaScript representation of a Queues object. Also exposes 9339 * methods to operate on the object against the server. 9340 * 9341 * @param {Object} options 9342 * An object with the following properties:<ul> 9343 * <li><b>id:</b> The id of the object being constructed</li> 9344 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 9345 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 9346 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 9347 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 9348 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 9349 * <li><b>status:</b> {Number} The HTTP status code returned</li> 9350 * <li><b>content:</b> {String} Raw string of response</li> 9351 * <li><b>object:</b> {Object} Parsed object of response</li> 9352 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 9353 * <li><b>errorType:</b> {String} Type of error that was caught</li> 9354 * <li><b>errorMessage:</b> {String} Message associated with error</li> 9355 * </ul></li> 9356 * </ul></li> 9357 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 9358 **/ 9359 init: function (options) { 9360 this._super(options); 9361 }, 9362 9363 /** 9364 * @private 9365 * Gets xmpp node path. 9366 */ 9367 getXMPPNodePath: function () { 9368 return this.getRestUrl(); 9369 }, 9370 9371 /** 9372 * @private 9373 * Gets the REST class for the current object - this is the Queues class. 9374 */ 9375 getRestClass: function () { 9376 return Queues; 9377 }, 9378 9379 /** 9380 * @private 9381 * Gets the REST class for the objects that make up the collection. - this 9382 * is the Queue class. 9383 */ 9384 getRestItemClass: function () { 9385 return Queue; 9386 }, 9387 9388 /** 9389 * @private 9390 * Gets the REST type for the current object - this is a "Queues". 9391 */ 9392 getRestType: function () { 9393 return "Queues"; 9394 }, 9395 9396 /** 9397 * @private 9398 * Gets the REST type for the objects that make up the collection - this is "Queue". 9399 */ 9400 getRestItemType: function () { 9401 return "Queue"; 9402 }, 9403 9404 explicitSubscription: true, 9405 9406 handlesItemRefresh: true 9407 }); 9408 9409 window.finesse = window.finesse || {}; 9410 window.finesse.restservices = window.finesse.restservices || {}; 9411 window.finesse.restservices.Queues = Queues; 9412 9413 return Queues; 9414 }); 9415 9416 /** 9417 * JavaScript representation of the Finesse WrapUpReason object. 9418 * 9419 * @requires finesse.clientservices.ClientServices 9420 * @requires Class 9421 * @requires finesse.FinesseBase 9422 * @requires finesse.restservices.RestBase 9423 */ 9424 9425 /** @private */ 9426 define('restservices/WrapUpReason',['restservices/RestBase'], function (RestBase) { 9427 9428 var WrapUpReason = RestBase.extend(/** @lends finesse.restservices.WrapUpReason.prototype */{ 9429 9430 /** 9431 * @class 9432 * A WrapUpReason is a code and description identifying a particular reason that a 9433 * User is in WORK (WrapUp) mode. 9434 * 9435 * @augments finesse.restservices.RestBase 9436 * @see finesse.restservices.User 9437 * @see finesse.restservices.User.States#WORK 9438 * @constructs 9439 */ 9440 _fakeConstuctor: function () { 9441 /* This is here to hide the real init constructor from the public docs */ 9442 }, 9443 9444 /** 9445 * @private 9446 * JavaScript representation of a WrapUpReason object. Also exposes 9447 * methods to operate on the object against the server. 9448 * 9449 * @param {Object} options 9450 * An object with the following properties:<ul> 9451 * <li><b>id:</b> The id of the object being constructed</li> 9452 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 9453 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 9454 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 9455 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 9456 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 9457 * <li><b>status:</b> {Number} The HTTP status code returned</li> 9458 * <li><b>content:</b> {String} Raw string of response</li> 9459 * <li><b>object:</b> {Object} Parsed object of response</li> 9460 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 9461 * <li><b>errorType:</b> {String} Type of error that was caught</li> 9462 * <li><b>errorMessage:</b> {String} Message associated with error</li> 9463 * </ul></li> 9464 * </ul></li> 9465 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 9466 **/ 9467 init: function (options) { 9468 this._super(options); 9469 }, 9470 9471 /** 9472 * @private 9473 * Gets the REST class for the current object - this is the WrapUpReason class. 9474 * @returns {Object} The WrapUpReason class. 9475 */ 9476 getRestClass: function () { 9477 return WrapUpReason; 9478 }, 9479 9480 /** 9481 * @private 9482 * Gets the REST type for the current object - this is a "WrapUpReason". 9483 * @returns {String} The WrapUpReason string. 9484 */ 9485 getRestType: function () { 9486 return "WrapUpReason"; 9487 }, 9488 9489 /** 9490 * @private 9491 * Gets the REST type for the current object - this is a "WrapUpReasons". 9492 * @returns {String} The WrapUpReasons string. 9493 */ 9494 getParentRestType: function () { 9495 return "WrapUpReasons"; 9496 }, 9497 9498 /** 9499 * @private 9500 * Override default to indicate that this object doesn't support making 9501 * requests. 9502 */ 9503 supportsRequests: false, 9504 9505 /** 9506 * @private 9507 * Override default to indicate that this object doesn't support subscriptions. 9508 */ 9509 supportsSubscriptions: false, 9510 9511 /** 9512 * Getter for the label. 9513 * @returns {String} The label. 9514 */ 9515 getLabel: function () { 9516 this.isLoaded(); 9517 return this.getData().label; 9518 }, 9519 9520 /** 9521 * @private 9522 * Getter for the forAll flag. 9523 * @returns {Boolean} True if global. 9524 */ 9525 getForAll: function () { 9526 this.isLoaded(); 9527 return this.getData().forAll; 9528 }, 9529 9530 /** 9531 * @private 9532 * Getter for the Uri value. 9533 * @returns {String} The Uri. 9534 */ 9535 getUri: function () { 9536 this.isLoaded(); 9537 return this.getData().uri; 9538 } 9539 }); 9540 9541 window.finesse = window.finesse || {}; 9542 window.finesse.restservices = window.finesse.restservices || {}; 9543 window.finesse.restservices.WrapUpReason = WrapUpReason; 9544 9545 return WrapUpReason; 9546 }); 9547 9548 /** 9549 * JavaScript representation of the Finesse WrapUpReasons collection 9550 * object which contains a list of WrapUpReason objects. 9551 * 9552 * @requires finesse.clientservices.ClientServices 9553 * @requires Class 9554 * @requires finesse.FinesseBase 9555 * @requires finesse.restservices.RestBase 9556 * @requires finesse.restservices.Dialog 9557 * @requires finesse.restservices.RestCollectionBase 9558 */ 9559 9560 /** @private */ 9561 define('restservices/WrapUpReasons',[ 9562 'restservices/RestCollectionBase', 9563 'restservices/WrapUpReason' 9564 ], 9565 function (RestCollectionBase, WrapUpReason) { 9566 9567 var WrapUpReasons = RestCollectionBase.extend(/** @lends finesse.restservices.WrapUpReasons.prototype */{ 9568 9569 /** 9570 * @class 9571 * JavaScript representation of a WrapUpReasons collection object. 9572 * @augments finesse.restservices.RestCollectionBase 9573 * @constructs 9574 * @see finesse.restservices.WrapUpReason 9575 * @example 9576 * _wrapUpReasons = _user.getWrapUpReasons ( { 9577 * onCollectionAdd : _handleWrapUpReasonAdd, 9578 * onCollectionDelete : _handleWrapUpReasonDelete, 9579 * onLoad : _handleWrapUpReasonsLoaded 9580 * }); 9581 * 9582 * _wrapUpReasonCollection = _wrapUpReasons.getCollection(); 9583 * for (var wrapUpReasonId in _wrapUpReasonCollection) { 9584 * if (_wrapUpReasonCollection.hasOwnProperty(wrapUpReasonId)) { 9585 * _wrapUpReason = _wrapUpReasonCollection[wrapUpReasonId]; 9586 * etc... 9587 * } 9588 * } 9589 */ 9590 _fakeConstuctor: function () { 9591 /* This is here to hide the real init constructor from the public docs */ 9592 }, 9593 9594 /** 9595 * @private 9596 * JavaScript representation of a WrapUpReasons collection object. Also exposes 9597 * methods to operate on the object against the server. 9598 * 9599 * @param {Object} options 9600 * An object with the following properties:<ul> 9601 * <li><b>id:</b> The id of the object being constructed</li> 9602 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 9603 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 9604 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 9605 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 9606 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 9607 * <li><b>status:</b> {Number} The HTTP status code returned</li> 9608 * <li><b>content:</b> {String} Raw string of response</li> 9609 * <li><b>object:</b> {Object} Parsed object of response</li> 9610 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 9611 * <li><b>errorType:</b> {String} Type of error that was caught</li> 9612 * <li><b>errorMessage:</b> {String} Message associated with error</li> 9613 * </ul></li> 9614 * </ul></li> 9615 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 9616 **/ 9617 init: function (options) { 9618 this._super(options); 9619 }, 9620 9621 /** 9622 * @private 9623 * Gets the REST class for the current object - this is the WrapUpReasons class. 9624 */ 9625 getRestClass: function () { 9626 return WrapUpReasons; 9627 }, 9628 9629 /** 9630 * @private 9631 * Gets the REST class for the objects that make up the collection. - this 9632 * is the WrapUpReason class. 9633 */ 9634 getRestItemClass: function () { 9635 return WrapUpReason; 9636 }, 9637 9638 /** 9639 * @private 9640 * Gets the REST type for the current object - this is a "WrapUpReasons". 9641 */ 9642 getRestType: function () { 9643 return "WrapUpReasons"; 9644 }, 9645 9646 /** 9647 * @private 9648 * Gets the REST type for the objects that make up the collection - this is "WrapUpReason". 9649 */ 9650 getRestItemType: function () { 9651 return "WrapUpReason"; 9652 }, 9653 9654 /** 9655 * @private 9656 * Override default to indicates that the collection supports making 9657 * requests. 9658 */ 9659 supportsRequests: true, 9660 9661 /** 9662 * @private 9663 * Override default to indicate that this object doesn't support subscriptions. 9664 */ 9665 supportsRestItemSubscriptions: false, 9666 9667 /** 9668 * @private 9669 * Retrieve the Wrap-Up Reason Codes. This call will re-query the server and refresh the collection. 9670 * 9671 * @returns {finesse.restservices.WrapUpReasons} 9672 * This ReadyReasonCodes object to allow cascading. 9673 */ 9674 get: function () { 9675 // set loaded to false so it will rebuild the collection after the get 9676 this._loaded = false; 9677 // reset collection 9678 this._collection = {}; 9679 // perform get 9680 this._synchronize(); 9681 return this; 9682 } 9683 9684 }); 9685 9686 window.finesse = window.finesse || {}; 9687 window.finesse.restservices = window.finesse.restservices || {}; 9688 window.finesse.restservices.WrapUpReasons = WrapUpReasons; 9689 9690 return WrapUpReasons; 9691 }); 9692 9693 /** 9694 * JavaScript representation of the Finesse Contact object. 9695 * @requires finesse.clientservices.ClientServices 9696 * @requires Class 9697 * @requires finesse.FinesseBase 9698 * @requires finesse.restservices.RestBase 9699 */ 9700 /** @private */ 9701 define('restservices/Contact',['restservices/RestBase'], function (RestBase) { 9702 9703 var Contact = RestBase.extend(/** @lends finesse.restservices.Contact.prototype */{ 9704 9705 /** 9706 * @class 9707 * A Contact is a single entry in a PhoneBook, consisting of a First and Last Name, 9708 * a Phone Number, and a Description. 9709 * 9710 * @augments finesse.restservices.RestBase 9711 * @see finesse.restservices.PhoneBook 9712 * @constructs 9713 */ 9714 _fakeConstuctor: function () { 9715 /* This is here to hide the real init constructor from the public docs */ 9716 }, 9717 9718 /** 9719 * @private 9720 * @param {Object} options 9721 * An object with the following properties:<ul> 9722 * <li><b>id:</b> The id of the object being constructed</li> 9723 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 9724 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 9725 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 9726 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 9727 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 9728 * <li><b>status:</b> {Number} The HTTP status code returned</li> 9729 * <li><b>content:</b> {String} Raw string of response</li> 9730 * <li><b>object:</b> {Object} Parsed object of response</li> 9731 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 9732 * <li><b>errorType:</b> {String} Type of error that was caught</li> 9733 * <li><b>errorMessage:</b> {String} Message associated with error</li> 9734 * </ul></li> 9735 * </ul></li> 9736 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 9737 **/ 9738 init: function (options) { 9739 this._super(options); 9740 }, 9741 9742 /** 9743 * @private 9744 * Gets the REST class for the current object - this is the Contact class. 9745 * @returns {Object} The Contact class. 9746 */ 9747 getRestClass: function () { 9748 return Contact; 9749 }, 9750 9751 /** 9752 * @private 9753 * Gets the REST type for the current object - this is a "Contact". 9754 * @returns {String} The Contact string. 9755 */ 9756 getRestType: function () { 9757 return "Contact"; 9758 }, 9759 9760 /** 9761 * @private 9762 * Override default to indicate that this object doesn't support making 9763 * requests. 9764 */ 9765 supportsRequests: false, 9766 9767 /** 9768 * @private 9769 * Override default to indicate that this object doesn't support subscriptions. 9770 */ 9771 supportsSubscriptions: false, 9772 9773 /** 9774 * Getter for the firstName. 9775 * @returns {String} The firstName. 9776 */ 9777 getFirstName: function () { 9778 this.isLoaded(); 9779 return this.getData().firstName; 9780 }, 9781 9782 /** 9783 * Getter for the lastName. 9784 * @returns {String} The lastName. 9785 */ 9786 getLastName: function () { 9787 this.isLoaded(); 9788 return this.getData().lastName; 9789 }, 9790 9791 /** 9792 * Getter for the phoneNumber. 9793 * @returns {String} The phoneNumber. 9794 */ 9795 getPhoneNumber: function () { 9796 this.isLoaded(); 9797 return this.getData().phoneNumber; 9798 }, 9799 9800 /** 9801 * Getter for the description. 9802 * @returns {String} The description. 9803 */ 9804 getDescription: function () { 9805 this.isLoaded(); 9806 return this.getData().description; 9807 }, 9808 9809 /** @private */ 9810 createPutSuccessHandler: function(contact, contentBody, successHandler){ 9811 return function (rsp) { 9812 // Update internal structure based on response. Here we 9813 // inject the contentBody from the PUT request into the 9814 // rsp.object element to mimic a GET as a way to take 9815 // advantage of the existing _processResponse method. 9816 rsp.object = contentBody; 9817 contact._processResponse(rsp); 9818 9819 //Remove the injected Contact object before cascading response 9820 rsp.object = {}; 9821 9822 //cascade response back to consumer's response handler 9823 successHandler(rsp); 9824 }; 9825 }, 9826 9827 /** @private */ 9828 createPostSuccessHandler: function (contact, contentBody, successHandler) { 9829 return function (rsp) { 9830 rsp.object = contentBody; 9831 contact._processResponse(rsp); 9832 9833 //Remove the injected Contact object before cascading response 9834 rsp.object = {}; 9835 9836 //cascade response back to consumer's response handler 9837 successHandler(rsp); 9838 }; 9839 }, 9840 9841 /** 9842 * Add 9843 * @private 9844 */ 9845 add: function (newValues, handlers) { 9846 // this.isLoaded(); 9847 var contentBody = {}; 9848 9849 contentBody[this.getRestType()] = { 9850 "firstName": newValues.firstName, 9851 "lastName": newValues.lastName, 9852 "phoneNumber": newValues.phoneNumber, 9853 "description": newValues.description 9854 }; 9855 9856 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 9857 handlers = handlers || {}; 9858 9859 this.restRequest(this.getRestUrl(), { 9860 method: 'POST', 9861 success: this.createPostSuccessHandler(this, contentBody, handlers.success), 9862 error: handlers.error, 9863 content: contentBody 9864 }); 9865 9866 return this; // Allow cascading 9867 }, 9868 9869 /** 9870 * Update 9871 * @private 9872 */ 9873 update: function (newValues, handlers) { 9874 this.isLoaded(); 9875 var contentBody = {}; 9876 9877 contentBody[this.getRestType()] = { 9878 "uri": this.getId(), 9879 "firstName": newValues.firstName, 9880 "lastName": newValues.lastName, 9881 "phoneNumber": newValues.phoneNumber, 9882 "description": newValues.description 9883 }; 9884 9885 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 9886 handlers = handlers || {}; 9887 9888 this.restRequest(this.getRestUrl(), { 9889 method: 'PUT', 9890 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 9891 error: handlers.error, 9892 content: contentBody 9893 }); 9894 9895 return this; // Allow cascading 9896 }, 9897 9898 9899 /** 9900 * Delete 9901 * @private 9902 */ 9903 "delete": function ( handlers) { 9904 this.isLoaded(); 9905 9906 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 9907 handlers = handlers || {}; 9908 9909 this.restRequest(this.getRestUrl(), { 9910 method: 'DELETE', 9911 success: this.createPutSuccessHandler(this, {}, handlers.success), 9912 error: handlers.error, 9913 content: undefined 9914 }); 9915 9916 return this; // Allow cascading 9917 } 9918 }); 9919 9920 window.finesse = window.finesse || {}; 9921 window.finesse.restservices = window.finesse.restservices || {}; 9922 window.finesse.restservices.Contact = Contact; 9923 9924 return Contact; 9925 }); 9926 9927 /** 9928 * JavaScript representation of the Finesse Contacts collection 9929 * object which contains a list of Contact objects. 9930 * 9931 * @requires finesse.clientservices.ClientServices 9932 * @requires Class 9933 * @requires finesse.FinesseBase 9934 * @requires finesse.restservices.RestBase 9935 * @requires finesse.restservices.Dialog 9936 * @requires finesse.restservices.RestCollectionBase 9937 */ 9938 /** @private */ 9939 define('restservices/Contacts',[ 9940 'restservices/RestCollectionBase', 9941 'restservices/Contact' 9942 ], 9943 function (RestCollectionBase, Contact) { 9944 var Contacts = RestCollectionBase.extend(/** @lends finesse.restservices.Contacts.prototype */{ 9945 9946 /** 9947 * @class 9948 * JavaScript representation of a Contacts collection object. Also exposes 9949 * methods to operate on the object against the server. 9950 * @augments finesse.restservices.RestCollectionBase 9951 * @constructs 9952 * @see finesse.restservices.Contact 9953 * @see finesse.restservices.PhoneBook 9954 * @example 9955 * _contacts = _phonebook.getContacts( { 9956 * onCollectionAdd : _handleContactAdd, 9957 * onCollectionDelete : _handleContactDelete, 9958 * onLoad : _handleContactsLoaded 9959 * }); 9960 * 9961 * _contactCollection = _contacts.getCollection(); 9962 * for (var contactId in _contactCollection) { 9963 * if (_contactCollection.hasOwnProperty(contactId)) { 9964 * _contact = _contactCollection[contactId]; 9965 * etc... 9966 * } 9967 * } 9968 */ 9969 _fakeConstuctor: function () { 9970 /* This is here to hide the real init constructor from the public docs */ 9971 }, 9972 9973 /** 9974 * @private 9975 * JavaScript representation of a Contacts collection object. Also exposes 9976 * methods to operate on the object against the server. 9977 * 9978 * @param {Object} options 9979 * An object with the following properties:<ul> 9980 * <li><b>id:</b> The id of the object being constructed</li> 9981 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 9982 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 9983 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 9984 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 9985 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 9986 * <li><b>status:</b> {Number} The HTTP status code returned</li> 9987 * <li><b>content:</b> {String} Raw string of response</li> 9988 * <li><b>object:</b> {Object} Parsed object of response</li> 9989 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 9990 * <li><b>errorType:</b> {String} Type of error that was caught</li> 9991 * <li><b>errorMessage:</b> {String} Message associated with error</li> 9992 * </ul></li> 9993 * </ul></li> 9994 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 9995 **/ 9996 init: function (options) { 9997 this._super(options); 9998 }, 9999 10000 /** 10001 * @private 10002 * Gets the REST class for the current object - this is the Contacts class. 10003 */ 10004 getRestClass: function () { 10005 return Contacts; 10006 }, 10007 10008 /** 10009 * @private 10010 * Gets the REST class for the objects that make up the collection. - this 10011 * is the Contact class. 10012 */ 10013 getRestItemClass: function () { 10014 return Contact; 10015 }, 10016 10017 /** 10018 * @private 10019 * Gets the REST type for the current object - this is a "Contacts". 10020 */ 10021 getRestType: function () { 10022 return "Contacts"; 10023 }, 10024 10025 /** 10026 * @private 10027 * Gets the REST type for the objects that make up the collection - this is "Contacts". 10028 */ 10029 getRestItemType: function () { 10030 return "Contact"; 10031 }, 10032 10033 /** 10034 * @private 10035 * Override default to indicates that the collection supports making 10036 * requests. 10037 */ 10038 supportsRequests: true, 10039 10040 /** 10041 * @private 10042 * Override default to indicates that the collection subscribes to its objects. 10043 */ 10044 supportsRestItemSubscriptions: false, 10045 10046 /** 10047 * @private 10048 * Retrieve the Contacts. This call will re-query the server and refresh the collection. 10049 * 10050 * @returns {finesse.restservices.Contacts} 10051 * This Contacts object, to allow cascading. 10052 */ 10053 get: function () { 10054 // set loaded to false so it will rebuild the collection after the get 10055 this._loaded = false; 10056 // reset collection 10057 this._collection = {}; 10058 // perform get 10059 this._synchronize(); 10060 return this; 10061 } 10062 10063 }); 10064 10065 window.finesse = window.finesse || {}; 10066 window.finesse.restservices = window.finesse.restservices || {}; 10067 window.finesse.restservices.Contacts = Contacts; 10068 10069 10070 return Contacts; 10071 }); 10072 10073 /** 10074 * JavaScript representation of the Finesse PhoneBook object. 10075 * 10076 * @requires finesse.clientservices.ClientServices 10077 * @requires Class 10078 * @requires finesse.FinesseBase 10079 * @requires finesse.restservices.RestBase 10080 */ 10081 10082 /** @private */ 10083 define('restservices/PhoneBook',[ 10084 'restservices/RestBase', 10085 'restservices/Contacts' 10086 ], 10087 function (RestBase, Contacts) { 10088 var PhoneBook = RestBase.extend(/** @lends finesse.restservices.PhoneBook.prototype */{ 10089 10090 _contacts: null, 10091 10092 /** 10093 * @class 10094 * A PhoneBook is a list of Contacts available to a User for quick dial. 10095 * 10096 * @augments finesse.restservices.RestBase 10097 * @see finesse.restservices.Contacts 10098 * @constructs 10099 */ 10100 _fakeConstuctor: function () { 10101 /* This is here to hide the real init constructor from the public docs */ 10102 }, 10103 10104 /** 10105 * @private 10106 * JavaScript representation of a PhoneBook object. Also exposes 10107 * methods to operate on the object against the server. 10108 * 10109 * @param {Object} options 10110 * An object with the following properties:<ul> 10111 * <li><b>id:</b> The id of the object being constructed</li> 10112 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 10113 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 10114 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 10115 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 10116 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 10117 * <li><b>status:</b> {Number} The HTTP status code returned</li> 10118 * <li><b>content:</b> {String} Raw string of response</li> 10119 * <li><b>object:</b> {Object} Parsed object of response</li> 10120 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 10121 * <li><b>errorType:</b> {String} Type of error that was caught</li> 10122 * <li><b>errorMessage:</b> {String} Message associated with error</li> 10123 * </ul></li> 10124 * </ul></li> 10125 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 10126 **/ 10127 init: function (options) { 10128 this._super(options); 10129 }, 10130 10131 /** 10132 * @private 10133 * Gets the REST class for the current object - this is the PhoneBook class. 10134 * @returns {Object} The PhoneBook class. 10135 */ 10136 getRestClass: function () { 10137 return PhoneBook; 10138 }, 10139 10140 /** 10141 * @private 10142 * Gets the REST type for the current object - this is a "PhoneBook". 10143 * @returns {String} The PhoneBook string. 10144 */ 10145 getRestType: function () { 10146 return "PhoneBook"; 10147 }, 10148 10149 /** 10150 * @private 10151 * Override default to indicate that this object doesn't support making 10152 * requests. 10153 */ 10154 supportsRequests: false, 10155 10156 /** 10157 * @private 10158 * Override default to indicate that this object doesn't support subscriptions. 10159 */ 10160 supportsSubscriptions: false, 10161 10162 /** 10163 * Getter for the name of the Phone Book. 10164 * @returns {String} The name. 10165 */ 10166 getName: function () { 10167 this.isLoaded(); 10168 return this.getData().name; 10169 }, 10170 10171 /** 10172 * Getter for the type flag. 10173 * @returns {String} The type. 10174 */ 10175 getType: function () { 10176 this.isLoaded(); 10177 return this.getData().type; 10178 }, 10179 10180 /** 10181 * @private 10182 * Getter for the Uri value. 10183 * @returns {String} The Uri. 10184 */ 10185 getUri: function () { 10186 this.isLoaded(); 10187 return this.getData().uri; 10188 }, 10189 10190 /** 10191 * Getter for a Contacts collection object that is associated with PhoneBook. 10192 * @param {finesse.interfaces.RequestHandlers} handlers 10193 * An object containing the handlers for the request 10194 * @returns {finesse.restservices.Contacts} 10195 * A Contacts collection object. 10196 */ 10197 getContacts: function (callbacks) { 10198 var options = callbacks || {}; 10199 options.parentObj = this; 10200 this.isLoaded(); 10201 10202 if (this._contacts === null) { 10203 this._contacts = new Contacts(options); 10204 } 10205 10206 return this._contacts; 10207 }, 10208 10209 /** 10210 * Getter for <contacts> node within PhoneBook - sometimes it's just a URI, sometimes it is a Contacts collection 10211 * @returns {String} uri to contacts 10212 * or {finesse.restservices.Contacts} collection 10213 */ 10214 getEmbeddedContacts: function(){ 10215 this.isLoaded(); 10216 return this.getData().contacts; 10217 }, 10218 10219 /** @private */ 10220 createPutSuccessHandler: function(phonebook, contentBody, successHandler){ 10221 return function (rsp) { 10222 // Update internal structure based on response. Here we 10223 // inject the contentBody from the PUT request into the 10224 // rsp.object element to mimic a GET as a way to take 10225 // advantage of the existing _processResponse method. 10226 rsp.object = contentBody; 10227 phonebook._processResponse(rsp); 10228 10229 //Remove the injected PhoneBook object before cascading response 10230 rsp.object = {}; 10231 10232 //cascade response back to consumer's response handler 10233 successHandler(rsp); 10234 }; 10235 }, 10236 10237 /** @private */ 10238 createPostSuccessHandler: function (phonebook, contentBody, successHandler) { 10239 return function (rsp) { 10240 rsp.object = contentBody; 10241 phonebook._processResponse(rsp); 10242 10243 //Remove the injected PhoneBook object before cascading response 10244 rsp.object = {}; 10245 10246 //cascade response back to consumer's response handler 10247 successHandler(rsp); 10248 }; 10249 }, 10250 10251 /** 10252 * @private 10253 * Add a PhoneBook. 10254 * @param {Object} newValues 10255 * @param {String} newValues.name Name of PhoneBook 10256 * @param {String} newValues.type Type of PhoneBook 10257 * @param {finesse.interfaces.RequestHandlers} handlers 10258 * An object containing the handlers for the request 10259 * @returns {finesse.restservices.PhoneBook} 10260 * This PhoneBook object, to allow cascading 10261 */ 10262 add: function (newValues, handlers) { 10263 // this.isLoaded(); 10264 var contentBody = {}; 10265 10266 contentBody[this.getRestType()] = { 10267 "name": newValues.name, 10268 "type": newValues.type 10269 }; 10270 10271 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 10272 handlers = handlers || {}; 10273 10274 this.restRequest(this.getRestUrl(), { 10275 method: 'POST', 10276 success: this.createPostSuccessHandler(this, contentBody, handlers.success), 10277 error: handlers.error, 10278 content: contentBody 10279 }); 10280 10281 return this; // Allow cascading 10282 }, 10283 10284 /** 10285 * @private 10286 * Update a PhoneBook. 10287 * @param {Object} newValues 10288 * @param {String} newValues.name Name of PhoneBook 10289 * @param {String} newValues.type Type of PhoneBook 10290 * @param {finesse.interfaces.RequestHandlers} handlers 10291 * An object containing the handlers for the request 10292 * @returns {finesse.restservices.PhoneBook} 10293 * This PhoneBook object, to allow cascading 10294 */ 10295 update: function (newValues, handlers) { 10296 this.isLoaded(); 10297 var contentBody = {}; 10298 10299 contentBody[this.getRestType()] = { 10300 "uri": this.getId(), 10301 "name": newValues.name, 10302 "type": newValues.type 10303 }; 10304 10305 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 10306 handlers = handlers || {}; 10307 10308 this.restRequest(this.getRestUrl(), { 10309 method: 'PUT', 10310 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 10311 error: handlers.error, 10312 content: contentBody 10313 }); 10314 10315 return this; // Allow cascading 10316 }, 10317 10318 10319 /** 10320 * Delete a PhoneBook. 10321 * @param {finesse.interfaces.RequestHandlers} handlers 10322 * An object containing the handlers for the request 10323 * @returns {finesse.restservices.PhoneBook} 10324 * This PhoneBook object, to allow cascading 10325 */ 10326 "delete": function ( handlers) { 10327 this.isLoaded(); 10328 10329 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 10330 handlers = handlers || {}; 10331 10332 this.restRequest(this.getRestUrl(), { 10333 method: 'DELETE', 10334 success: this.createPutSuccessHandler(this, {}, handlers.success), 10335 error: handlers.error, 10336 content: undefined 10337 }); 10338 10339 return this; // Allow cascading 10340 } 10341 10342 10343 10344 }); 10345 10346 window.finesse = window.finesse || {}; 10347 window.finesse.restservices = window.finesse.restservices || {}; 10348 window.finesse.restservices.PhoneBook = PhoneBook; 10349 10350 return PhoneBook; 10351 }); 10352 10353 /** 10354 * JavaScript representation of the Finesse PhoneBooks collection 10355 * object which contains a list of PhoneBook objects. 10356 * 10357 * @requires finesse.clientservices.ClientServices 10358 * @requires Class 10359 * @requires finesse.FinesseBase 10360 * @requires finesse.restservices.RestBase 10361 * @requires finesse.restservices.Dialog 10362 * @requires finesse.restservices.RestCollectionBase 10363 */ 10364 /** @private */ 10365 define('restservices/PhoneBooks',[ 10366 'restservices/RestCollectionBase', 10367 'restservices/PhoneBook' 10368 ], 10369 function (RestCollectionBase, PhoneBook) { 10370 var PhoneBooks = RestCollectionBase.extend(/** @lends finesse.restservices.PhoneBooks.prototype */{ 10371 10372 /** 10373 * @class 10374 * JavaScript representation of a PhoneBooks collection object. 10375 * @augments finesse.restservices.RestCollectionBase 10376 * @constructs 10377 * @see finesse.restservices.PhoneBook 10378 * @see finesse.restservices.Contacts 10379 * @see finesse.restservices.Contact 10380 * @example 10381 * _phoneBooks = _user.getPhoneBooks( { 10382 * onCollectionAdd : _handlePhoneBookAdd, 10383 * onCollectionDelete : _handlePhoneBookDelete, 10384 * onLoad : _handlePhoneBooksLoaded 10385 * }); 10386 * 10387 * _phoneBookCollection = _phoneBooks.getCollection(); 10388 * for (var phoneBookId in _phoneBookCollection) { 10389 * if (_phoneBookCollection.hasOwnProperty(phoneBookId)) { 10390 * _phoneBook = _phoneBookCollection[phoneBookId]; 10391 * etc... 10392 * } 10393 * } 10394 */ 10395 _fakeConstuctor: function () { 10396 /* This is here to hide the real init constructor from the public docs */ 10397 }, 10398 10399 /** 10400 * @private 10401 * 10402 * @param {Object} options 10403 * An object with the following properties:<ul> 10404 * <li><b>id:</b> The id of the object being constructed</li> 10405 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 10406 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 10407 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 10408 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 10409 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 10410 * <li><b>status:</b> {Number} The HTTP status code returned</li> 10411 * <li><b>content:</b> {String} Raw string of response</li> 10412 * <li><b>object:</b> {Object} Parsed object of response</li> 10413 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 10414 * <li><b>errorType:</b> {String} Type of error that was caught</li> 10415 * <li><b>errorMessage:</b> {String} Message associated with error</li> 10416 * </ul></li> 10417 * </ul></li> 10418 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 10419 **/ 10420 init: function (options) { 10421 // Keep the REST response for PhoneBooks to check for 206 Partial Content. 10422 this.keepRestResponse = true; 10423 // Add in the Range header which is required for PhoneBooks API. 10424 this.extraHeaders = { "Range": "objects=1-1500" }; 10425 this._super(options); 10426 }, 10427 10428 /** 10429 * @private 10430 * Gets the REST class for the current object - this is the PhoneBooks class. 10431 */ 10432 getRestClass: function () { 10433 return PhoneBooks; 10434 }, 10435 10436 /** 10437 * @private 10438 * Gets the REST class for the objects that make up the collection. - this 10439 * is the PhoneBook class. 10440 */ 10441 getRestItemClass: function () { 10442 return PhoneBook; 10443 }, 10444 10445 /** 10446 * @private 10447 * Gets the REST type for the current object - this is a "PhoneBooks". 10448 */ 10449 getRestType: function () { 10450 return "PhoneBooks"; 10451 }, 10452 10453 /** 10454 * @private 10455 * Gets the REST type for the objects that make up the collection - this is "PhoneBooks". 10456 */ 10457 getRestItemType: function () { 10458 return "PhoneBook"; 10459 }, 10460 10461 /** 10462 * @private 10463 * Override default to indicates that the collection supports making 10464 * requests. 10465 */ 10466 supportsRequests: true, 10467 10468 /** 10469 * @private 10470 * Override default to indicates that the collection subscribes to its objects. 10471 */ 10472 supportsRestItemSubscriptions: false, 10473 10474 /** 10475 * @private 10476 * Retrieve the PhoneBooks. This call will re-query the server and refresh the collection. 10477 * 10478 * @returns {finesse.restservices.PhoneBooks} 10479 * This PhoneBooks object, to allow cascading. 10480 */ 10481 get: function () { 10482 // set loaded to false so it will rebuild the collection after the get 10483 this._loaded = false; 10484 // reset collection 10485 this._collection = {}; 10486 // perform get 10487 this._synchronize(); 10488 return this; 10489 } 10490 10491 }); 10492 10493 window.finesse = window.finesse || {}; 10494 window.finesse.restservices = window.finesse.restservices || {}; 10495 window.finesse.restservices.PhoneBooks = PhoneBooks; 10496 10497 return PhoneBooks; 10498 }); 10499 10500 /** 10501 * JavaScript representation of the Finesse WorkflowAction object. 10502 * 10503 * @requires finesse.clientservices.ClientServices 10504 * @requires Class 10505 * @requires finesse.FinesseBase 10506 * @requires finesse.restservices.RestBase 10507 */ 10508 10509 /*jslint browser: true, nomen: true, sloppy: true, forin: true */ 10510 /*global define,finesse */ 10511 10512 /** @private */ 10513 define('restservices/WorkflowAction',['restservices/RestBase'], function (RestBase) { 10514 10515 var WorkflowAction = RestBase.extend({ 10516 10517 _contacts: null, 10518 10519 actionTypes: [ 10520 { 10521 name: 'BROWSER_POP', 10522 params: [ 10523 { 10524 name: 'windowName', 10525 type: 'text' 10526 }, 10527 { 10528 name: 'path', 10529 type: 'systemVariableSingleLineEditor' 10530 } 10531 ] 10532 }, 10533 { 10534 name: 'HTTP_REQUEST', 10535 params: [ 10536 { 10537 name: 'method', 10538 type: 'dropdown', 10539 values: ['POST', 'PUT'] 10540 }, 10541 { 10542 name: 'location', 10543 type: 'dropdown', 10544 values: ['FINESSE', 'OTHER'] 10545 }, 10546 { 10547 name: 'contentType', 10548 type: 'text' 10549 }, 10550 { 10551 name: 'path', 10552 type: 'systemVariableSingleLineEditor' 10553 }, 10554 { 10555 name: 'body', 10556 type: 'systemVariableMultiLineEditor' 10557 } 10558 ] 10559 } 10560 // more action type definitions here 10561 ], 10562 10563 /** 10564 * @class 10565 * A WorkflowAction is an action (e.g. Browser Pop, Rest Request) defined in a 10566 * Workflow and triggered by a system event (Call Received, Call Ended, etc.). 10567 * 10568 * @augments finesse.restservices.RestBase 10569 * @see finesse.restservices.Workflow 10570 * @constructs 10571 */ 10572 _fakeConstuctor: function () { 10573 /* This is here to hide the real init constructor from the public docs */ 10574 }, 10575 10576 /** 10577 * @private 10578 * JavaScript representation of a WorkflowAction object. Also exposes 10579 * methods to operate on the object against the server. 10580 * 10581 * @param {Object} options 10582 * An object with the following properties:<ul> 10583 * <li><b>id:</b> The id of the object being constructed</li> 10584 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 10585 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 10586 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 10587 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 10588 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 10589 * <li><b>status:</b> {Number} The HTTP status code returned</li> 10590 * <li><b>content:</b> {String} Raw string of response</li> 10591 * <li><b>object:</b> {Object} Parsed object of response</li> 10592 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 10593 * <li><b>errorType:</b> {String} Type of error that was caught</li> 10594 * <li><b>errorMessage:</b> {String} Message associated with error</li> 10595 * </ul></li> 10596 * </ul></li> 10597 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 10598 **/ 10599 init: function (options) { 10600 this._super(options); 10601 }, 10602 10603 /** 10604 * @private 10605 * Gets the REST class for the current object - this is the WorkflowAction class. 10606 * @returns {Object} The WorkflowAction class. 10607 */ 10608 getRestClass: function () { 10609 return finesse.restservices.WorkflowAction; 10610 }, 10611 10612 /** 10613 * @private 10614 * Gets the REST type for the current object - this is a "WorkflowAction". 10615 * @returns {String} The WorkflowAction string. 10616 */ 10617 getRestType: function () { 10618 return "WorkflowAction"; 10619 }, 10620 10621 /** 10622 * @private 10623 * Override default to indicate that this object doesn't support making 10624 * requests. 10625 */ 10626 supportsRequests: false, 10627 10628 /** 10629 * @private 10630 * Override default to indicate that this object doesn't support subscriptions. 10631 */ 10632 supportsSubscriptions: false, 10633 10634 /** 10635 * Getter for the name. 10636 * @returns {String} The name. 10637 */ 10638 getName: function () { 10639 this.isLoaded(); 10640 return this.getData().name; 10641 }, 10642 10643 /** 10644 * Getter for the type flag. 10645 * @returns {String} The type. 10646 */ 10647 getType: function () { 10648 this.isLoaded(); 10649 return this.getData().type; 10650 }, 10651 10652 /** 10653 * @private 10654 * Getter for the Uri value. 10655 * @returns {String} The Uri. 10656 */ 10657 getUri: function () { 10658 this.isLoaded(); 10659 return this.getData().uri; 10660 }, 10661 10662 /** 10663 * @private 10664 * Getter for the handledBy value. 10665 * @returns {String} handledBy. 10666 */ 10667 getHandledBy: function () { 10668 this.isLoaded(); 10669 return this.getData().handledBy; 10670 }, 10671 10672 /** 10673 * Getter for the parameters. 10674 * @returns {Object} key = param name, value = param value 10675 */ 10676 getParams: function () { 10677 var map = {}, 10678 params = this.getData().params.Param, 10679 i, 10680 param; 10681 10682 for(i=0; i<params.length; i+=1){ 10683 param = params[i]; 10684 map[param.name] = param.value || ""; 10685 } 10686 10687 return map; 10688 }, 10689 10690 /** 10691 * Getter for the ActionVariables 10692 * @returns {Object} key = action variable name, value = Object{name, type, node, testValue} 10693 */ 10694 getActionVariables: function() { 10695 var map = {}, 10696 actionVariablesParent = this.getData().actionVariables, 10697 actionVariables, 10698 i, 10699 actionVariable; 10700 10701 if (actionVariablesParent === null || typeof(actionVariablesParent) === "undefined" || actionVariablesParent.length === 0){ 10702 return map; 10703 } 10704 actionVariables = actionVariablesParent.ActionVariable; 10705 10706 if(actionVariables.length > 0){ 10707 for(i=0; i<actionVariables.length; i+=1){ 10708 actionVariable = actionVariables[i]; 10709 // escape nulls to empty string 10710 actionVariable.name = actionVariable.name || ""; 10711 actionVariable.type = actionVariable.type || ""; 10712 actionVariable.node = actionVariable.node || ""; 10713 actionVariable.testValue = actionVariable.testValue || ""; 10714 map[actionVariable.name] = actionVariable; 10715 } 10716 } else { 10717 map[actionVariables.name] = actionVariables; 10718 } 10719 10720 return map; 10721 }, 10722 10723 /** @private */ 10724 createPutSuccessHandler: function(action, contentBody, successHandler){ 10725 return function (rsp) { 10726 // Update internal structure based on response. Here we 10727 // inject the contentBody from the PUT request into the 10728 // rsp.object element to mimic a GET as a way to take 10729 // advantage of the existing _processResponse method. 10730 rsp.object = contentBody; 10731 action._processResponse(rsp); 10732 10733 //Remove the injected WorkflowAction object before cascading response 10734 rsp.object = {}; 10735 10736 //cascade response back to consumer's response handler 10737 successHandler(rsp); 10738 }; 10739 }, 10740 10741 /** @private */ 10742 createPostSuccessHandler: function (action, contentBody, successHandler) { 10743 return function (rsp) { 10744 rsp.object = contentBody; 10745 action._processResponse(rsp); 10746 10747 //Remove the injected WorkflowAction object before cascading response 10748 rsp.object = {}; 10749 10750 //cascade response back to consumer's response handler 10751 successHandler(rsp); 10752 }; 10753 }, 10754 10755 /** 10756 * @private 10757 * Build params array out of all the values coming into add or update methods 10758 * paramMap is a map of params.. we need to translate it into an array of Param objects 10759 * where path and windowName are params for the BROWSER_POP type 10760 */ 10761 buildParamsForRest: function(paramMap){ 10762 var params = {"Param": []}, 10763 i; 10764 for(i in paramMap){ 10765 if(paramMap.hasOwnProperty(i)){ 10766 params.Param.push({name: i, value: paramMap[i]}); 10767 } 10768 } 10769 return params; 10770 }, 10771 10772 /** 10773 * @private 10774 * Build actionVariables array out of all the values coming into add or update methods 10775 * actionVariableMap is a map of actionVariables.. we need to translate it into an array of ActionVariable objects 10776 * where path and windowName are params for the BROWSER_POP type 10777 */ 10778 buildActionVariablesForRest: function(actionVariableMap){ 10779 var actionVariables = {"ActionVariable": []}, 10780 i, 10781 actionVariable; 10782 for(i in actionVariableMap){ 10783 if(actionVariableMap.hasOwnProperty(i)){ 10784 // {name: "callVariable1", type: "SYSTEM", node: "", testValue: "<blink>"} 10785 actionVariable = { 10786 "name": actionVariableMap[i].name, 10787 "type": actionVariableMap[i].type, 10788 "node": actionVariableMap[i].node, 10789 "testValue": actionVariableMap[i].testValue 10790 }; 10791 actionVariables.ActionVariable.push(actionVariable); 10792 } 10793 } 10794 return actionVariables; 10795 }, 10796 10797 /** 10798 * Add 10799 */ 10800 add: function (newValues, handlers) { 10801 var contentBody = {}; 10802 10803 contentBody[this.getRestType()] = { 10804 "name": newValues.name, 10805 "type": newValues.type, 10806 "handledBy": newValues.handledBy, 10807 "params": this.buildParamsForRest(newValues.params), 10808 "actionVariables": this.buildActionVariablesForRest(newValues.actionVariables) 10809 }; 10810 10811 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 10812 handlers = handlers || {}; 10813 10814 this.restRequest(this.getRestUrl(), { 10815 method: 'POST', 10816 success: this.createPostSuccessHandler(this, contentBody, handlers.success), 10817 error: handlers.error, 10818 content: contentBody 10819 }); 10820 10821 return this; // Allow cascading 10822 }, 10823 10824 /** 10825 * @private 10826 * Update 10827 */ 10828 update: function (newValues, handlers) { 10829 this.isLoaded(); 10830 var contentBody = {}; 10831 10832 contentBody[this.getRestType()] = { 10833 "uri": this.getId(), 10834 "name": newValues.name, 10835 "type": newValues.type, 10836 "handledBy": newValues.handledBy, 10837 "params": this.buildParamsForRest(newValues.params), 10838 "actionVariables": this.buildActionVariablesForRest(newValues.actionVariables) 10839 }; 10840 10841 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 10842 handlers = handlers || {}; 10843 10844 this.restRequest(this.getRestUrl(), { 10845 method: 'PUT', 10846 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 10847 error: handlers.error, 10848 content: contentBody 10849 }); 10850 10851 return this; // Allow cascading 10852 }, 10853 10854 10855 /** 10856 * @private 10857 * Delete 10858 */ 10859 "delete": function ( handlers) { 10860 this.isLoaded(); 10861 10862 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 10863 handlers = handlers || {}; 10864 10865 this.restRequest(this.getRestUrl(), { 10866 method: 'DELETE', 10867 success: this.createPutSuccessHandler(this, {}, handlers.success), 10868 error: handlers.error, 10869 content: undefined 10870 }); 10871 10872 return this; // Allow cascading 10873 } 10874 10875 10876 10877 }); 10878 10879 window.finesse = window.finesse || {}; 10880 window.finesse.restservices = window.finesse.restservices || {}; 10881 window.finesse.restservices.WorkflowAction = WorkflowAction; 10882 10883 return WorkflowAction; 10884 }); 10885 10886 /** 10887 * JavaScript representation of the Finesse WorkflowActions collection 10888 * object which contains a list of WorkflowAction objects. 10889 * 10890 * @requires finesse.clientservices.ClientServices 10891 * @requires Class 10892 * @requires finesse.FinesseBase 10893 * @requires finesse.restservices.RestBase 10894 * @requires finesse.restservices.Dialog 10895 * @requires finesse.restservices.RestCollectionBase 10896 */ 10897 10898 /** @private */ 10899 define('restservices/WorkflowActions',[ 10900 'restservices/RestCollectionBase', 10901 'restservices/RestBase', 10902 'restservices/WorkflowAction' 10903 ], 10904 function (RestCollectionBase, RestBase, WorkflowAction) { 10905 10906 var WorkflowActions = RestCollectionBase.extend({ 10907 10908 /** 10909 * @class 10910 * JavaScript representation of a WorkflowActions collection object. 10911 * @augments finesse.restservices.RestCollectionBase 10912 * @constructs 10913 * @see finesse.restservices.WorkflowAction 10914 * @see finesse.restservices.Workflow 10915 * @see finesse.restservices.Workflows 10916 * @example 10917 * _workflowActions = _user.getWorkflowActions( { 10918 * onCollectionAdd : _handleWorkflowActionAdd, 10919 * onCollectionDelete : _handleWorkflowActionDelete, 10920 * onLoad : _handleWorkflowActionsLoaded 10921 * }); 10922 */ 10923 _fakeConstuctor: function () { 10924 /* This is here to hide the real init constructor from the public docs */ 10925 }, 10926 10927 /** 10928 * @private 10929 * JavaScript representation of a WorkflowActions collection object. Also exposes 10930 * methods to operate on the object against the server. 10931 * 10932 * @param {Object} options 10933 * An object with the following properties:<ul> 10934 * <li><b>id:</b> The id of the object being constructed</li> 10935 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 10936 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 10937 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 10938 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 10939 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 10940 * <li><b>status:</b> {Number} The HTTP status code returned</li> 10941 * <li><b>content:</b> {String} Raw string of response</li> 10942 * <li><b>object:</b> {Object} Parsed object of response</li> 10943 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 10944 * <li><b>errorType:</b> {String} Type of error that was caught</li> 10945 * <li><b>errorMessage:</b> {String} Message associated with error</li> 10946 * </ul></li> 10947 * </ul></li> 10948 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 10949 **/ 10950 init: function (options) { 10951 this._super(options); 10952 }, 10953 10954 /** 10955 * @private 10956 * Gets the REST class for the current object - this is the WorkflowActions class. 10957 */ 10958 getRestClass: function () { 10959 return WorkflowActions; 10960 }, 10961 10962 /** 10963 * @private 10964 * Gets the REST class for the objects that make up the collection. - this 10965 * is the WorkflowAction class. 10966 */ 10967 getRestItemClass: function () { 10968 return WorkflowAction; 10969 }, 10970 10971 /** 10972 * @private 10973 * Gets the REST type for the current object - this is a "WorkflowActions". 10974 */ 10975 getRestType: function () { 10976 return "WorkflowActions"; 10977 }, 10978 10979 /** 10980 * @private 10981 * Gets the REST type for the objects that make up the collection - this is "WorkflowActions". 10982 */ 10983 getRestItemType: function () { 10984 return "WorkflowAction"; 10985 }, 10986 10987 /** 10988 * @private 10989 * Override default to indicates that the collection supports making 10990 * requests. 10991 */ 10992 supportsRequests: true, 10993 10994 /** 10995 * @private 10996 * Override default to indicates that the collection subscribes to its objects. 10997 */ 10998 supportsRestItemSubscriptions: false, 10999 11000 /** 11001 * @private 11002 * Retrieve the WorkflowActions. 11003 * 11004 * @returns {finesse.restservices.WorkflowActions} 11005 * This WorkflowActions object to allow cascading. 11006 */ 11007 get: function () { 11008 // set loaded to false so it will rebuild the collection after the get 11009 this._loaded = false; 11010 // reset collection 11011 this._collection = {}; 11012 // perform get 11013 this._synchronize(); 11014 return this; 11015 } 11016 }); 11017 11018 window.finesse = window.finesse || {}; 11019 window.finesse.restservices = window.finesse.restservices || {}; 11020 window.finesse.restservices.WorkflowActions = WorkflowActions; 11021 11022 return WorkflowActions; 11023 }); 11024 11025 /** 11026 * JavaScript representation of the Finesse Workflow object. 11027 * 11028 * @requires finesse.clientservices.ClientServices 11029 * @requires Class 11030 * @requires finesse.FinesseBase 11031 * @requires finesse.restservices.RestBase 11032 */ 11033 11034 /*jslint browser: true, nomen: true, sloppy: true, forin: true */ 11035 /*global define,finesse */ 11036 11037 /** @private */ 11038 define('restservices/Workflow',[ 11039 'restservices/RestBase', 11040 'restservices/WorkflowActions' 11041 ], 11042 function (RestBase, WorkflowActions) { 11043 11044 var Workflow = RestBase.extend({ 11045 11046 /** 11047 * @class 11048 * JavaScript representation of a Workflow object. Also exposes 11049 * methods to operate on the object against the server. 11050 * 11051 * @param {Object} options 11052 * An object with the following properties:<ul> 11053 * <li><b>id:</b> The id of the object being constructed</li> 11054 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 11055 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 11056 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 11057 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 11058 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 11059 * <li><b>status:</b> {Number} The HTTP status code returned</li> 11060 * <li><b>content:</b> {String} Raw string of response</li> 11061 * <li><b>object:</b> {Object} Parsed object of response</li> 11062 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 11063 * <li><b>errorType:</b> {String} Type of error that was caught</li> 11064 * <li><b>errorMessage:</b> {String} Message associated with error</li> 11065 * </ul></li> 11066 * </ul></li> 11067 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 11068 * @constructs 11069 **/ 11070 init: function (options) { 11071 this._super(options); 11072 }, 11073 11074 /** 11075 * @private 11076 * Gets the REST class for the current object - this is the Workflow class. 11077 * @returns {Object} The Workflow class. 11078 */ 11079 getRestClass: function () { 11080 return Workflow; 11081 }, 11082 11083 /** 11084 * @private 11085 * Gets the REST type for the current object - this is a "Workflow". 11086 * @returns {String} The Workflow string. 11087 */ 11088 getRestType: function () { 11089 return "Workflow"; 11090 }, 11091 11092 /** 11093 * @private 11094 * Override default to indicate that this object doesn't support making 11095 * requests. 11096 */ 11097 supportsRequests: false, 11098 11099 /** 11100 * @private 11101 * Override default to indicate that this object doesn't support subscriptions. 11102 */ 11103 supportsSubscriptions: false, 11104 11105 /** 11106 * @private 11107 * Getter for the Uri value. 11108 * @returns {String} The Uri. 11109 */ 11110 getUri: function () { 11111 this.isLoaded(); 11112 return this.getData().uri; 11113 }, 11114 11115 /** 11116 * Getter for the name. 11117 * @returns {String} The name. 11118 */ 11119 getName: function () { 11120 this.isLoaded(); 11121 return this.getData().name; 11122 }, 11123 11124 /** 11125 * Getter for the description. 11126 * @returns {String} The description. 11127 */ 11128 getDescription: function () { 11129 this.isLoaded(); 11130 return this.getData().description; 11131 }, 11132 11133 /** 11134 * Getter for the trigger set. 11135 * @returns {String} The trigger set. 11136 */ 11137 getTriggerSet: function () { 11138 this.isLoaded(); 11139 return this.getData().TriggerSet; 11140 }, 11141 11142 /** 11143 * Getter for the condition set. 11144 * @returns {String} The condition set. 11145 */ 11146 getConditionSet: function () { 11147 this.isLoaded(); 11148 return this.getData().ConditionSet; 11149 }, 11150 11151 /** 11152 * Getter for the assigned workflowActions. 11153 * @returns {String} The workflowActions object. 11154 */ 11155 getWorkflowActions: function () { 11156 this.isLoaded(); 11157 var workflowActions = this.getData().workflowActions; 11158 if (workflowActions === null) { 11159 workflowActions = ""; 11160 } 11161 return workflowActions; 11162 }, 11163 11164 createPutSuccessHandler: function (workflow, contentBody, successHandler) { 11165 return function (rsp) { 11166 // Update internal structure based on response. Here we 11167 // inject the contentBody from the PUT request into the 11168 // rsp.object element to mimic a GET as a way to take 11169 // advantage of the existing _processResponse method. 11170 rsp.object = contentBody; 11171 workflow._processResponse(rsp); 11172 11173 //Remove the injected Workflow object before cascading response 11174 rsp.object = {}; 11175 11176 //cascade response back to consumer's response handler 11177 successHandler(rsp); 11178 }; 11179 }, 11180 11181 createPostSuccessHandler: function (workflow, contentBody, successHandler) { 11182 return function (rsp) { 11183 rsp.object = contentBody; 11184 workflow._processResponse(rsp); 11185 11186 //Remove the injected Workflow object before cascading response 11187 rsp.object = {}; 11188 11189 //cascade response back to consumer's response handler 11190 successHandler(rsp); 11191 }; 11192 }, 11193 11194 /** 11195 * @private 11196 * Add 11197 */ 11198 add: function (newValues, handlers) { 11199 // this.isLoaded(); 11200 var contentBody = {}; 11201 11202 contentBody[this.getRestType()] = { 11203 "name": newValues.name, 11204 "description": newValues.description, 11205 "TriggerSet" : newValues.TriggerSet, 11206 "ConditionSet" : newValues.ConditionSet, 11207 "workflowActions" : newValues.workflowActions 11208 }; 11209 11210 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 11211 handlers = handlers || {}; 11212 11213 this.restRequest(this.getRestUrl(), { 11214 method: 'POST', 11215 success: this.createPostSuccessHandler(this, contentBody, handlers.success), 11216 error: handlers.error, 11217 content: contentBody 11218 }); 11219 11220 return this; // Allow cascading 11221 }, 11222 11223 /** 11224 * @private 11225 * Update 11226 */ 11227 update: function (newValues, handlers) { 11228 this.isLoaded(); 11229 var contentBody = {}; 11230 11231 contentBody[this.getRestType()] = { 11232 "uri": this.getId(), 11233 "name": newValues.name, 11234 "description": newValues.description, 11235 "TriggerSet" : newValues.TriggerSet, 11236 "ConditionSet" : newValues.ConditionSet, 11237 "workflowActions" : newValues.workflowActions 11238 }; 11239 11240 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 11241 handlers = handlers || {}; 11242 11243 this.restRequest(this.getRestUrl(), { 11244 method: 'PUT', 11245 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 11246 error: handlers.error, 11247 content: contentBody 11248 }); 11249 11250 return this; // Allow cascading 11251 }, 11252 11253 11254 /** 11255 * @private 11256 * Delete 11257 */ 11258 "delete": function (handlers) { 11259 this.isLoaded(); 11260 11261 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 11262 handlers = handlers || {}; 11263 11264 this.restRequest(this.getRestUrl(), { 11265 method: 'DELETE', 11266 success: this.createPutSuccessHandler(this, {}, handlers.success), 11267 error: handlers.error, 11268 content: undefined 11269 }); 11270 11271 return this; // Allow cascading 11272 } 11273 11274 11275 11276 }); 11277 11278 window.finesse = window.finesse || {}; 11279 window.finesse.restservices = window.finesse.restservices || {}; 11280 window.finesse.restservices.Workflow = Workflow; 11281 11282 return Workflow; 11283 }); 11284 11285 /** 11286 * JavaScript representation of the Finesse workflows collection 11287 * object which contains a list of workflow objects. 11288 * 11289 * @requires finesse.clientservices.ClientServices 11290 * @requires Class 11291 * @requires finesse.FinesseBase 11292 * @requires finesse.restservices.RestBase 11293 * @requires finesse.restservices.Dialog 11294 * @requires finesse.restservices.RestCollectionBase 11295 */ 11296 11297 /** @private */ 11298 define('restservices/Workflows',[ 11299 'restservices/RestCollectionBase', 11300 'restservices/RestBase', 11301 'restservices/Workflow' 11302 ], 11303 function (RestCollectionBase, RestBase, Workflow) { 11304 11305 var Workflows = RestCollectionBase.extend({ 11306 11307 /** 11308 * @class 11309 * JavaScript representation of a workflows collection object. Also exposes 11310 * methods to operate on the object against the server. 11311 * 11312 * @param {Object} options 11313 * An object with the following properties:<ul> 11314 * <li><b>id:</b> The id of the object being constructed</li> 11315 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 11316 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 11317 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 11318 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 11319 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 11320 * <li><b>status:</b> {Number} The HTTP status code returned</li> 11321 * <li><b>content:</b> {String} Raw string of response</li> 11322 * <li><b>object:</b> {Object} Parsed object of response</li> 11323 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 11324 * <li><b>errorType:</b> {String} Type of error that was caught</li> 11325 * <li><b>errorMessage:</b> {String} Message associated with error</li> 11326 * </ul></li> 11327 * </ul></li> 11328 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 11329 * @constructs 11330 **/ 11331 init: function (options) { 11332 this._super(options); 11333 }, 11334 11335 /** 11336 * @private 11337 * Gets the REST class for the current object - this is the workflows class. 11338 */ 11339 getRestClass: function () { 11340 return Workflows; 11341 }, 11342 11343 /** 11344 * @private 11345 * Gets the REST class for the objects that make up the collection. - this 11346 * is the workflow class. 11347 */ 11348 getRestItemClass: function () { 11349 return Workflow; 11350 }, 11351 11352 /** 11353 * @private 11354 * Gets the REST type for the current object - this is a "workflows". 11355 */ 11356 getRestType: function () { 11357 return "Workflows"; 11358 }, 11359 11360 /** 11361 * @private 11362 * Gets the REST type for the objects that make up the collection - this is "workflows". 11363 */ 11364 getRestItemType: function () { 11365 return "Workflow"; 11366 }, 11367 11368 /** 11369 * @private 11370 * Override default to indicates that the collection supports making requests. 11371 */ 11372 supportsRequests: true, 11373 11374 /** 11375 * @private 11376 * Override default to indicates that the collection does not subscribe to its objects. 11377 */ 11378 supportsRestItemSubscriptions: false, 11379 11380 /** 11381 * @private 11382 * Retrieve the workflows. This call will re-query the server and refresh the collection. 11383 * 11384 * @returns {finesse.restservices.workflows} 11385 * This workflows object to allow cascading. 11386 */ 11387 get: function () { 11388 // set loaded to false so it will rebuild the collection after the get 11389 this._loaded = false; 11390 // reset collection 11391 this._collection = {}; 11392 // perform get 11393 this._synchronize(); 11394 return this; 11395 } 11396 }); 11397 11398 window.finesse = window.finesse || {}; 11399 window.finesse.restservices = window.finesse.restservices || {}; 11400 window.finesse.restservices.Workflows = Workflows; 11401 11402 return Workflows; 11403 }); 11404 11405 /** 11406 * JavaScript representation of the Finesse MediaPropertiesLayout object for the Admin webapp. 11407 * @requires finesse.clientservices.ClientServices 11408 * @requires Class 11409 * @requires finesse.FinesseBase 11410 * @requires finesse.restservices.RestBase 11411 */ 11412 11413 /** The following comment is to prevent jslint errors about 11414 * using variables before they are defined. 11415 */ 11416 /*global finesse*/ 11417 11418 /** 11419 * @class 11420 * JavaScript representation of a MediaPropertiesLayout object for the Admin webapp. Also exposes 11421 * methods to operate on the object against the server. 11422 * 11423 * @constructor 11424 * @param {String} id 11425 * Not required... 11426 * @param {Object} callbacks 11427 * An object containing callbacks for instantiation and runtime 11428 * @param {Function} callbacks.onLoad(this) 11429 * Callback to invoke upon successful instantiation, passes in MediaPropertiesLayout object 11430 * @param {Function} callbacks.onLoadError(rsp) 11431 * Callback to invoke on instantiation REST request error 11432 * as passed by finesse.clientservices.ClientServices.ajax() 11433 * { 11434 * status: {Number} The HTTP status code returned 11435 * content: {String} Raw string of response 11436 * object: {Object} Parsed object of response 11437 * error: {Object} Wrapped exception that was caught 11438 * error.errorType: {String} Type of error that was caught 11439 * error.errorMessage: {String} Message associated with error 11440 * } 11441 * @param {Function} callbacks.onChange(this) 11442 * Callback to invoke upon successful update, passes in MediaPropertiesLayout object 11443 * @param {Function} callbacks.onError(rsp) 11444 * Callback to invoke on update error (refresh or event) 11445 * as passed by finesse.clientservices.ClientServices.ajax() 11446 * { 11447 * status: {Number} The HTTP status code returned 11448 * content: {String} Raw string of response 11449 * object: {Object} Parsed object of response 11450 * error: {Object} Wrapped exception that was caught 11451 * error.errorType: {String} Type of error that was caught 11452 * error.errorMessage: {String} Message associated with error 11453 * } 11454 */ 11455 11456 /** @private */ 11457 define('restservices/MediaPropertiesLayout',['restservices/RestBase'], function (RestBase) { 11458 var MediaPropertiesLayout = RestBase.extend(/** @lends finesse.restservices.MediaPropertiesLayout.prototype */{ 11459 11460 /** 11461 * @class 11462 * The MediaPropertiesLayout handles which call variables are associated with Dialogs. 11463 * 11464 * @augments finesse.restservices.RestBase 11465 * @see finesse.restservices.Dialog#getMediaProperties 11466 * @see finesse.restservices.User#getMediaPropertiesLayout 11467 * @constructs 11468 */ 11469 _fakeConstuctor: function () { 11470 /* This is here to hide the real init constructor from the public docs */ 11471 }, 11472 11473 /** 11474 * @private 11475 * JavaScript representation of a MediaPropertiesLayout object. Also exposes 11476 * methods to operate on the object against the server. 11477 * 11478 * @param {Object} options 11479 * An object with the following properties:<ul> 11480 * <li><b>id:</b> The id of the object being constructed</li> 11481 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 11482 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 11483 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 11484 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 11485 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 11486 * <li><b>status:</b> {Number} The HTTP status code returned</li> 11487 * <li><b>content:</b> {String} Raw string of response</li> 11488 * <li><b>object:</b> {Object} Parsed object of response</li> 11489 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 11490 * <li><b>errorType:</b> {String} Type of error that was caught</li> 11491 * <li><b>errorMessage:</b> {String} Message associated with error</li> 11492 * </ul></li> 11493 * </ul></li> 11494 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 11495 **/ 11496 init: function (options) { 11497 this._super(options); 11498 }, 11499 11500 /** 11501 * @private 11502 * Gets the REST class for the current object - this is the MediaPropertiesLayout object. 11503 */ 11504 getRestClass: function () { 11505 return MediaPropertiesLayout; 11506 }, 11507 11508 /** 11509 * @private 11510 * Gets the REST type for the current object - this is a "MediaPropertiesLayout". 11511 */ 11512 getRestType: function () { 11513 return "MediaPropertiesLayout"; 11514 }, 11515 11516 /** 11517 * @private 11518 * Returns whether this object supports subscriptions 11519 */ 11520 supportsSubscriptions: false, 11521 11522 /** 11523 * Getter for the name. 11524 * @returns {String} The name. 11525 */ 11526 getName: function () { 11527 this.isLoaded(); 11528 return this._data.name; 11529 }, 11530 11531 /** 11532 * Getter for the description. 11533 * @returns {String} The description. 11534 */ 11535 getDescription: function () { 11536 this.isLoaded(); 11537 return this._data.description || ""; 11538 }, 11539 11540 /** 11541 * Getter for the layout type (should be DEFAULT or CUSTOM). 11542 * @returns {String} The layout type. 11543 */ 11544 getType: function () { 11545 this.isLoaded(); 11546 return this._data.type || ""; 11547 }, 11548 11549 /** 11550 * Retrieve the media properties layout. This call will re-query the server and refresh the layout object. 11551 * @returns {finesse.restservices.MediaPropertiesLayout} 11552 * This MediaPropertiesLayout object to allow cascading 11553 */ 11554 get: function () { 11555 this._synchronize(); 11556 11557 return this; //Allow cascading 11558 }, 11559 11560 /** 11561 * Gets the data for this object. 11562 * 11563 * Performs safe conversion from raw API data to ensure that the returned layout object 11564 * always has a header with correct entry fields, and exactly two columns with lists of entries. 11565 * 11566 * @returns {finesse.restservices.MediaPropertiesLayout.Object} Data in columns (unless only one defined). 11567 */ 11568 getData: function () { 11569 11570 var layout = this._data, result, _addColumnData; 11571 11572 result = this.getEmptyData(); 11573 result.name = layout.name; 11574 result.description = layout.description; 11575 result.type = layout.type; 11576 11577 /** 11578 * @private 11579 */ 11580 _addColumnData = function (entryData, colIndex) { 11581 11582 if (!entryData) { 11583 //If there's no entry data at all, rewrite entryData to be an empty collection of entries 11584 entryData = {}; 11585 } else if (entryData.mediaProperty) { 11586 //If entryData contains the keys for a single entry rather than being a collection of entries, 11587 //rewrite it to be a collection containing a single entry 11588 entryData = { "": entryData }; 11589 } 11590 11591 //Add each of the entries in the list to the column 11592 jQuery.each(entryData, function (i, entryData) { 11593 11594 //If the entry has no displayName specified, explicitly set it to the empty string 11595 if (!entryData.displayName) { 11596 entryData.displayName = ""; 11597 } 11598 11599 result.columns[colIndex].push(entryData); 11600 11601 }); 11602 11603 }; 11604 11605 //The header should only contain a single entry 11606 if (layout.header && layout.header.entry) { 11607 11608 //If the entry has no displayName specified, explicitly set it to the empty string 11609 if (!layout.header.entry.displayName) { 11610 layout.header.entry.displayName = ""; 11611 } 11612 11613 result.header = layout.header.entry; 11614 11615 } else { 11616 11617 throw "MediaPropertiesLayout.getData() - Header does not contain an entry"; 11618 11619 } 11620 11621 //If the column object contains an entry object that wasn't part of a list of entries, 11622 //it must be a single right-hand entry object (left-hand entry object would be part of a list.) 11623 //Force the entry object to be the 2nd element in an otherwise-empty list. 11624 if (layout.column && layout.column.entry) { 11625 layout.column = [ 11626 null, 11627 { "entry": layout.column.entry } 11628 ]; 11629 } 11630 11631 if (layout.column && layout.column.length > 0 && layout.column.length <= 2) { 11632 11633 //Render left column entries 11634 if (layout.column[0] && layout.column[0].entry) { 11635 _addColumnData(layout.column[0].entry, 0); 11636 } 11637 11638 //Render right column entries 11639 if (layout.column[1] && layout.column[1].entry) { 11640 _addColumnData(layout.column[1].entry, 1); 11641 } 11642 11643 } 11644 11645 return result; 11646 11647 }, 11648 11649 /** 11650 * @private 11651 * Empty/template version of getData(). 11652 * 11653 * Used by getData(), and by callers of getData() in error cases. 11654 */ 11655 getEmptyData: function () { 11656 11657 return { 11658 header : { 11659 displayName: null, 11660 mediaProperty: null 11661 }, 11662 columns : [[], []] 11663 }; 11664 11665 }, 11666 11667 /** 11668 * Update the layout of this MediaPropertiesLayout 11669 * @param {Object} layout 11670 * The object representation of the layout you are setting 11671 * @param {finesse.interfaces.RequestHandlers} handlers 11672 * An object containing the handlers for the request 11673 * @returns {finesse.restservices.MediaPropertiesLayout} 11674 * This MediaPropertiesLayout object to allow cascading 11675 * @private 11676 */ 11677 update: function (newLayoutObject, handlers) { 11678 var contentBody = {}; 11679 11680 // Make sure type is kept the same 11681 newLayoutObject.type = this.getType(); 11682 11683 contentBody[this.getRestType()] = newLayoutObject; 11684 11685 //Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 11686 handlers = handlers || {}; 11687 11688 this.restRequest(this.getRestUrl(), { 11689 method: 'PUT', 11690 success: handlers.success, 11691 error: handlers.error, 11692 content: contentBody 11693 }); 11694 11695 return this; // Allow cascading 11696 }, 11697 11698 /** 11699 * Create a new MediaPropertiesLayout object with the layout passed in 11700 * @param {Object} layout 11701 * The object representation of the layout you are creating 11702 * @param {finesse.interfaces.RequestHandlers} handlers 11703 * An object containing the handlers for the request 11704 * @returns {finesse.restservices.MediaPropertiesLayout} 11705 * This MediaPropertiesLayout object to allow cascading 11706 * @private 11707 */ 11708 add: function (layout, handlers) { 11709 var contentBody = {}; 11710 11711 contentBody[this.getRestType()] = layout; 11712 11713 handlers = handlers || {}; 11714 11715 this.restRequest(this.getRestUrl(), { 11716 method: 'POST', 11717 success: handlers.success, 11718 error: handlers.error, 11719 content: contentBody 11720 }); 11721 11722 return this; // Allow cascading 11723 }, 11724 11725 /** 11726 * Delete this MediaPropertiesLayout 11727 * @param {finesse.interfaces.RequestHandlers} handlers 11728 * An object containing the handlers for the request 11729 * @returns {finesse.restservices.MediaPropertiesLayout} 11730 * This MediaPropertiesLayout object to allow cascading 11731 * @private 11732 */ 11733 "delete": function (handlers) { 11734 handlers = handlers || {}; 11735 11736 this.restRequest(this.getRestUrl(), { 11737 method: 'DELETE', 11738 success: handlers.success, 11739 error: handlers.error, 11740 content: undefined 11741 }); 11742 11743 return this; // Allow cascading 11744 } 11745 11746 }); 11747 11748 MediaPropertiesLayout.Object = /** @lends finesse.restservices.MediaPropertiesLayout.Object.prototype */ { 11749 /** 11750 * @class Format of MediaPropertiesLayout Object.<br> 11751 * Object { <ul> 11752 * <li>header : { <ul> 11753 * <li>dispayName {String} 11754 * <li>mediaProperty {String}</ul>} 11755 * <li>columns : { <ul> 11756 * <li>[ [] , [] ] 11757 * </ul> 11758 * where column arrays consists of the same Object format as header.<br> 11759 * }</ul> 11760 * }<br> 11761 * @constructs 11762 */ 11763 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 11764 11765 }; 11766 11767 window.finesse = window.finesse || {}; 11768 window.finesse.restservices = window.finesse.restservices || {}; 11769 window.finesse.restservices.MediaPropertiesLayout = MediaPropertiesLayout; 11770 11771 return MediaPropertiesLayout; 11772 }); 11773 11774 /** 11775 * JavaScript representation of the Finesse MediaPropertiesLayout object for a User 11776 * 11777 * @requires MediaPropertiesLayout 11778 * @requires ClientServices 11779 * @requires finesse.FinesseBase 11780 * @requires finesse.restservices.RestBase 11781 */ 11782 11783 /** The following comment is to prevent jslint errors about 11784 * using variables before they are defined. 11785 */ 11786 /*global finesse*/ 11787 11788 /** @private */ 11789 define('restservices/UserMediaPropertiesLayout',['restservices/MediaPropertiesLayout'], function (MediaPropertiesLayout) { 11790 var UserMediaPropertiesLayout = MediaPropertiesLayout.extend(/** @lends finesse.restservices.UserMediaPropertiesLayout.prototype */{ 11791 11792 /** 11793 * @class 11794 * JavaScript representation of a UserMediaPropertiesLayout collection object. Also exposes 11795 * methods to operate on the object against the server. 11796 * 11797 * @param {Object} options 11798 * An object with the following properties:<ul> 11799 * <li><b>id:</b> The id of the object being constructed</li> 11800 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 11801 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 11802 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 11803 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 11804 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 11805 * <li><b>status:</b> {Number} The HTTP status code returned</li> 11806 * <li><b>content:</b> {String} Raw string of response</li> 11807 * <li><b>object:</b> {Object} Parsed object of response</li> 11808 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 11809 * <li><b>errorType:</b> {String} Type of error that was caught</li> 11810 * <li><b>errorMessage:</b> {String} Message associated with error</li> 11811 * </ul></li> 11812 * </ul></li> 11813 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 11814 * @constructs 11815 **/ 11816 init: function (options) { 11817 this._super(options); 11818 }, 11819 11820 /** 11821 * @private 11822 * Gets the REST class for the current object - this is the UserMediaPropertiesLayout class. 11823 */ 11824 getRestClass: function () { 11825 return UserMediaPropertiesLayout; 11826 }, 11827 11828 /** 11829 * Overrides the parent class. Returns the url for the UserMediaPropertiesLayout resource 11830 */ 11831 getRestUrl: function () { 11832 return ("/finesse/api/User/" + this.getId() + "/" + this.getRestType()); 11833 }, 11834 11835 /** 11836 * @private 11837 * Override to throw an error because we cannot do an update on the User's 11838 * MediaPropertiesLayout node 11839 */ 11840 update: function (layout, handlers) { 11841 throw new Error("update(): Cannot update layout for User's MediaPropertiesLayout"); 11842 }, 11843 11844 /** 11845 * @private 11846 * Override to throw an error because we cannot create a new layout on the User's 11847 * MediaPropertiesLayout node 11848 */ 11849 add: function (layout, handlers) { 11850 throw new Error("add(): Cannot create a new layout for User's MediaPropertiesLayout"); 11851 }, 11852 11853 /** 11854 * @private 11855 * Override to throw an error because we cannot delete the layout on the User's 11856 * MediaPropertiesLayout node 11857 */ 11858 "delete": function (layout, handlers) { 11859 throw new Error("delete(): Cannot delete the layout for User's MediaPropertiesLayout"); 11860 } 11861 11862 }); 11863 11864 window.finesse = window.finesse || {}; 11865 window.finesse.restservices = window.finesse.restservices || {}; 11866 window.finesse.restservices.UserMediaPropertiesLayout = UserMediaPropertiesLayout; 11867 11868 return UserMediaPropertiesLayout; 11869 }); 11870 11871 /** 11872 * JavaScript representation of the Finesse mediaPropertiesLayouts collection 11873 * object which contains a list of mediaPropertiesLayout objects. 11874 * 11875 * @requires finesse.clientservices.ClientServices 11876 * @requires Class 11877 * @requires finesse.FinesseBase 11878 * @requires finesse.restservices.RestBase 11879 * @requires finesse.restservices.Dialog 11880 * @requires finesse.restservices.RestCollectionBase 11881 */ 11882 11883 /** @private */ 11884 define('restservices/MediaPropertiesLayouts',[ 11885 'restservices/RestCollectionBase', 11886 'restservices/RestBase', 11887 'restservices/MediaPropertiesLayout' 11888 ], 11889 function (RestCollectionBase, RestBase, MediaPropertiesLayout) { 11890 11891 var MediaPropertiesLayouts = RestCollectionBase.extend({ 11892 11893 /** 11894 * @class 11895 * JavaScript representation of a mediaPropertiesLayouts collection object. Also exposes 11896 * methods to operate on the object against the server. 11897 * 11898 * @param {Object} options 11899 * An object with the following properties:<ul> 11900 * <li><b>id:</b> The id of the object being constructed</li> 11901 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 11902 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 11903 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 11904 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 11905 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 11906 * <li><b>status:</b> {Number} The HTTP status code returned</li> 11907 * <li><b>content:</b> {String} Raw string of response</li> 11908 * <li><b>object:</b> {Object} Parsed object of response</li> 11909 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 11910 * <li><b>errorType:</b> {String} Type of error that was caught</li> 11911 * <li><b>errorMessage:</b> {String} Message associated with error</li> 11912 * </ul></li> 11913 * </ul></li> 11914 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 11915 * @constructs 11916 **/ 11917 init: function (options) { 11918 this._super(options); 11919 }, 11920 11921 /** 11922 * @private 11923 * Gets the REST class for the current object - this is the mediaPropertiesLayouts class. 11924 */ 11925 getRestClass: function () { 11926 return MediaPropertiesLayouts; 11927 }, 11928 11929 /** 11930 * @private 11931 * Gets the REST class for the objects that make up the collection. - this 11932 * is the mediaPropertiesLayout class. 11933 */ 11934 getRestItemClass: function () { 11935 return MediaPropertiesLayout; 11936 }, 11937 11938 /** 11939 * @private 11940 * Gets the REST type for the current object - this is a "mediaPropertiesLayouts". 11941 */ 11942 getRestType: function () { 11943 return "MediaPropertiesLayouts"; 11944 }, 11945 11946 /** 11947 * @private 11948 * Gets the REST type for the objects that make up the collection - this is "mediaPropertiesLayouts". 11949 */ 11950 getRestItemType: function () { 11951 return "MediaPropertiesLayout"; 11952 }, 11953 11954 /** 11955 * @private 11956 * Override default to indicates that the collection supports making requests. 11957 */ 11958 supportsRequests: true, 11959 11960 /** 11961 * @private 11962 * Override default to indicates that the collection does not subscribe to its objects. 11963 */ 11964 supportsRestItemSubscriptions: false, 11965 11966 /** 11967 * @private 11968 * Retrieve the MediaPropertiesLayouts. This call will re-query the server and refresh the collection. 11969 * 11970 * @returns {finesse.restservices.MediaPropertiesLayouts} 11971 * This MediaPropertiesLayouts object to allow cascading. 11972 */ 11973 get: function () { 11974 // set loaded to false so it will rebuild the collection after the get 11975 this._loaded = false; 11976 // reset collection 11977 this._collection = {}; 11978 // perform get 11979 this._synchronize(); 11980 return this; 11981 } 11982 }); 11983 11984 window.finesse = window.finesse || {}; 11985 window.finesse.restservices = window.finesse.restservices || {}; 11986 window.finesse.restservices.MediaPropertiesLayouts = MediaPropertiesLayouts; 11987 11988 return MediaPropertiesLayouts; 11989 }); 11990 11991 /** 11992 * JavaScript representation of the Finesse MediaPropertiesLayout object for a User 11993 * 11994 * @requires MediaPropertiesLayout 11995 * @requires ClientServices 11996 * @requires finesse.FinesseBase 11997 * @requires finesse.restservices.RestBase 11998 */ 11999 12000 /** The following comment is to prevent jslint errors about 12001 * using variables before they are defined. 12002 */ 12003 /*global finesse*/ 12004 12005 /** @private */ 12006 define('restservices/UserMediaPropertiesLayouts',[ 12007 'restservices/MediaPropertiesLayouts', 12008 'restservices/UserMediaPropertiesLayout' 12009 ], 12010 function (MediaPropertiesLayouts, UserMediaPropertiesLayout) { 12011 var UserMediaPropertiesLayouts = MediaPropertiesLayouts.extend(/** @lends finesse.restservices.UserMediaPropertiesLayouts.prototype */{ 12012 12013 /** 12014 * @class 12015 * JavaScript representation of a UserMediaPropertiesLayouts collection object. Also exposes 12016 * methods to operate on the object against the server. 12017 * 12018 * @param {Object} options 12019 * An object with the following properties:<ul> 12020 * <li><b>id:</b> The id of the object being constructed</li> 12021 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 12022 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 12023 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 12024 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 12025 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 12026 * <li><b>status:</b> {Number} The HTTP status code returned</li> 12027 * <li><b>content:</b> {String} Raw string of response</li> 12028 * <li><b>object:</b> {Object} Parsed object of response</li> 12029 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 12030 * <li><b>errorType:</b> {String} Type of error that was caught</li> 12031 * <li><b>errorMessage:</b> {String} Message associated with error</li> 12032 * </ul></li> 12033 * </ul></li> 12034 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 12035 * @constructs 12036 **/ 12037 init: function (options) { 12038 this._super(options); 12039 }, 12040 12041 /** 12042 * @private 12043 * Gets the REST class for the current object - this is the UserMediaPropertiesLayouts class. 12044 */ 12045 getRestClass: function () { 12046 return UserMediaPropertiesLayouts; 12047 }, 12048 12049 /** 12050 * @private 12051 * Gets the REST class for the objects that make up the collection. - this 12052 * is the UserMediaPropertiesLayout class. 12053 */ 12054 getRestItemClass: function() { 12055 return UserMediaPropertiesLayout; 12056 } 12057 }); 12058 12059 window.finesse = window.finesse || {}; 12060 window.finesse.restservices = window.finesse.restservices || {}; 12061 window.finesse.restservices.UserMediaPropertiesLayouts = UserMediaPropertiesLayouts; 12062 12063 return UserMediaPropertiesLayouts; 12064 }); 12065 12066 /** 12067 * JavaScript representation of the Finesse Dialog object for non-voice media. 12068 * 12069 * @requires finesse.restservices.DialogBase 12070 */ 12071 12072 /** @private */ 12073 define('restservices/MediaDialog',[ 12074 'restservices/DialogBase' 12075 ], 12076 function (DialogBase) { 12077 var MediaDialog = DialogBase.extend(/** @lends finesse.restservices.MediaDialog.prototype */{ 12078 12079 /** 12080 * @private 12081 * 12082 * Support requests so that applications can refresh non-voice dialogs when the media channel that the 12083 * dialog belongs to is interrupted. An event is not sent to update a dialog's actions when the media is 12084 * interrupted so a refresh is required so that the application can get an updated set of actions. 12085 */ 12086 supportsRequests: true, 12087 12088 /** 12089 * @class 12090 * A MediaDialog is an attempted connection between or among multiple participants, 12091 * for example, a chat or email. 12092 * 12093 * @augments finesse.restservices.DialogBase 12094 * @constructs 12095 */ 12096 _fakeConstuctor: function () { 12097 /* This is here to hide the real init constructor from the public docs */ 12098 }, 12099 12100 /** 12101 * @private 12102 * 12103 * @param {Object} options 12104 * An object with the following properties:<ul> 12105 * <li><b>id:</b> The id of the object being constructed</li> 12106 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 12107 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 12108 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 12109 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 12110 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 12111 * <li><b>status:</b> {Number} The HTTP status code returned</li> 12112 * <li><b>content:</b> {String} Raw string of response</li> 12113 * <li><b>object:</b> {Object} Parsed object of response</li> 12114 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 12115 * <li><b>errorType:</b> {String} Type of error that was caught</li> 12116 * <li><b>errorMessage:</b> {String} Message associated with error</li> 12117 * </ul></li> 12118 * </ul></li> 12119 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 12120 **/ 12121 init: function (options) { 12122 this._super(options); 12123 }, 12124 12125 /** 12126 * @private 12127 * Gets the REST class for the current object - this is the MediaDialog class. 12128 * @returns {Object} The Dialog class. 12129 */ 12130 getRestClass: function () { 12131 return MediaDialog; 12132 }, 12133 12134 /** 12135 * Transfers a Media Dialog to the target specified 12136 * @param {String} target script selector 12137 * The script selector to transfer the dialog. 12138 * @param {finesse.interfaces.RequestHandlers} handlers 12139 * An object containing the handlers for the request 12140 */ 12141 transfer: function(target, handlers) { 12142 this.setTaskState(MediaDialog.TaskActions.TRANSFER, handlers, target); 12143 }, 12144 12145 /** 12146 * Set the state on a Media Dialog based on the action given. 12147 * @param {finesse.restservices.MediaDialog.TaskActions} action 12148 * The action string indicating the action to invoke on a Media dialog. 12149 * @param {finesse.interfaces.RequestHandlers} handlers 12150 * An object containing the handlers for the request 12151 * @param {String} target 12152 * The target to transfer the dialog. Pass null if not transfer 12153 */ 12154 setTaskState: function (state,handlers,target) { 12155 this.isLoaded(); 12156 12157 var contentBody = {}; 12158 contentBody[this.getRestType()] = { 12159 "requestedAction": state, 12160 "target": target 12161 }; 12162 // (nonexistent) keys to be read as undefined 12163 handlers = handlers || {}; 12164 this.restRequest(this.getRestUrl(), { 12165 method: 'PUT', 12166 success: handlers.success, 12167 error: handlers.error, 12168 content: contentBody 12169 }); 12170 return this; // Allow cascading 12171 } 12172 12173 }); 12174 12175 MediaDialog.TaskActions = /** @lends finesse.restservices.MediaDialog.TaskActions.prototype */ { 12176 /** 12177 * Accept an incoming task. 12178 */ 12179 ACCEPT: "ACCEPT", 12180 /** 12181 * Start work on a task. 12182 */ 12183 START : "START", 12184 /** 12185 * Pause work on an active task. 12186 */ 12187 PAUSE: "PAUSE", 12188 /** 12189 * Resume work on a paused task. 12190 */ 12191 RESUME : "RESUME", 12192 /** 12193 * Wrap up work for a task. 12194 */ 12195 WRAP_UP : "WRAP_UP", 12196 /** 12197 * Transfer task to another target. 12198 */ 12199 TRANSFER : "TRANSFER", 12200 /** 12201 * End a task. 12202 */ 12203 CLOSE : "CLOSE", 12204 /** 12205 * @class Set of action constants for a Media Dialog. These should be used for 12206 * {@link finesse.restservices.MediaDialog#setTaskState}. 12207 * @constructs 12208 */ 12209 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 12210 }; 12211 12212 12213 12214 MediaDialog.States = /** @lends finesse.restservices.MediaDialog.States.prototype */ { 12215 /** 12216 * Indicates that the task has been offered to an agent. 12217 */ 12218 OFFERED: "OFFERED", 12219 /** 12220 * Indicates that the user has started work on the task. 12221 */ 12222 ACTIVE: "ACTIVE", 12223 /** 12224 * Indicates that the user has paused work on the task. 12225 */ 12226 PAUSED: "PAUSED", 12227 /** 12228 * Indicates that the user is wrapping up the task. 12229 */ 12230 WRAPPING_UP: "WRAPPING_UP", 12231 /** 12232 * Indicates that the task was interrupted. 12233 */ 12234 INTERRUPTED: "INTERRUPTED", 12235 /** 12236 * Indicates that the task has ended. 12237 */ 12238 CLOSED: "CLOSED", 12239 /** 12240 * Indicates that the user has accepted the task. 12241 */ 12242 ACCEPTED: "ACCEPTED", 12243 /** 12244 * Finesse has recovered a task after a failure. It does not have enough information to build a complete set 12245 * of actions for the task so it only allows the user to end the task. 12246 */ 12247 UNKNOWN: "UNKNOWN", 12248 /** 12249 * @class Possible Dialog State constants. 12250 * The State flow of a typical in-bound Dialog is as follows: OFFERED, ACCEPTED, ACTIVE, CLOSED. 12251 * @constructs 12252 */ 12253 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 12254 }; 12255 12256 MediaDialog.ParticipantStates = MediaDialog.States; 12257 12258 window.finesse = window.finesse || {}; 12259 window.finesse.restservices = window.finesse.restservices || {}; 12260 window.finesse.restservices.MediaDialog = MediaDialog; 12261 12262 12263 return MediaDialog; 12264 }); 12265 12266 /* Simple JavaScript Inheritance 12267 * By John Resig http://ejohn.org/ 12268 * MIT Licensed. 12269 */ 12270 // Inspired by base2 and Prototype 12271 define('restservices/../../thirdparty/Class',[], function () { 12272 var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/; 12273 // The base Class implementation (does nothing) 12274 /** @private */ 12275 Class = function(){}; 12276 12277 // Create a new Class that inherits from this class 12278 /** @private */ 12279 Class.extend = function(prop) { 12280 var _super = this.prototype; 12281 12282 // Instantiate a base class (but only create the instance, 12283 // don't run the init constructor) 12284 initializing = true; 12285 var prototype = new this(); 12286 initializing = false; 12287 12288 // Copy the properties over onto the new prototype 12289 for (var name in prop) { 12290 // Check if we're overwriting an existing function 12291 prototype[name] = typeof prop[name] == "function" && 12292 typeof _super[name] == "function" && fnTest.test(prop[name]) ? 12293 (function(name, fn){ 12294 return function() { 12295 var tmp = this._super; 12296 12297 // Add a new ._super() method that is the same method 12298 // but on the super-class 12299 this._super = _super[name]; 12300 12301 // The method only need to be bound temporarily, so we 12302 // remove it when we're done executing 12303 var ret = fn.apply(this, arguments); 12304 this._super = tmp; 12305 12306 return ret; 12307 }; 12308 })(name, prop[name]) : 12309 prop[name]; 12310 } 12311 12312 // The dummy class constructor 12313 /** @private */ 12314 function Class() { 12315 // All construction is actually done in the init method 12316 if ( !initializing && this.init ) 12317 this.init.apply(this, arguments); 12318 } 12319 12320 // Populate our constructed prototype object 12321 Class.prototype = prototype; 12322 12323 // Enforce the constructor to be what we expect 12324 Class.prototype.constructor = Class; 12325 12326 // And make this class extendable 12327 Class.extend = arguments.callee; 12328 12329 return Class; 12330 }; 12331 return Class; 12332 }); 12333 12334 /** 12335 * Class used to establish a Media/Dialogs subscription to be shared by MediaDialogs objects for non-voice 12336 * dialog events. 12337 * 12338 * @requires Class 12339 * @requires finesse.clientservices.ClientServices 12340 * @requires finesse.clientservices.Topics 12341 */ 12342 /** @private */ 12343 define('restservices/MediaDialogsSubscriptionManager',[ 12344 "../../thirdparty/Class", 12345 "clientservices/ClientServices", 12346 "clientservices/Topics" 12347 ], 12348 function (Class, ClientServices, Topics) { 12349 var MediaDialogsSubscriptionManager = Class.extend(/** @lends finesse.restservices.MediaDialogsSubscriptionManager.prototype */{ 12350 12351 /** 12352 * Map used to track the MediaDialogs objects managed by this object. 12353 * @private 12354 */ 12355 _mediaDialogsMap: {}, 12356 12357 /** 12358 * The regex used to match the source of BOSH/XMPP events. If an event matches this source, the event will 12359 * be processed by the subscription manager. 12360 * @private 12361 */ 12362 _sourceRegEx: null, 12363 12364 /** 12365 * The subscription ID/handle for Media/Dialogs events. 12366 * @private 12367 */ 12368 _subscriptionId: null, 12369 12370 _fakeConstuctor: function () 12371 { 12372 /* This is here to hide the real init constructor from the public docs */ 12373 }, 12374 12375 /** 12376 * Create the regex used to match the source of BOSH/XMPP events. If an event matches this source, the event 12377 * will be processed by the subscription manager. 12378 * 12379 * @param {Object} restObj 12380 * The restObj whose REST URL will be used as the base of the regex. 12381 * 12382 * @returns {RegExp} 12383 * The regex used to match the source of XMPP events. 12384 * @private 12385 */ 12386 _makeSourceRegEx: function(restObj) 12387 { 12388 return new RegExp("^" + restObj.getRestUrl() + "/Media/[0-9]+/Dialogs$"); 12389 }, 12390 12391 /** 12392 * Return the media ID associated with the update. 12393 * 12394 * @param {Object} update 12395 * The content of the update event. 12396 * 12397 * @returns {String} 12398 * The media ID associated with the update. 12399 * @private 12400 */ 12401 _getMediaIdFromEventUpdate: function(update) 12402 { 12403 var parts = update.object.Update.source.split("/"); 12404 return parts[MediaDialogsSubscriptionManager.MEDIA_ID_INDEX_IN_SOURCE]; 12405 }, 12406 12407 /** 12408 * Handler for update events. This handler forwards the update to the MediaDialogs object associated with 12409 * the media ID carried in the update event. 12410 * 12411 * @param {Object} update 12412 * The content of the update event. 12413 * 12414 * @private 12415 */ 12416 _updateEventHandler: function(update) 12417 { 12418 var mediaId = this._getMediaIdFromEventUpdate(update), 12419 mediaDialogs = this._mediaDialogsMap[mediaId]; 12420 12421 if ( mediaDialogs ) 12422 { 12423 mediaDialogs._updateEventHandler(mediaDialogs, update); 12424 } 12425 }, 12426 12427 /** 12428 * Return the media ID associated with the REST update. 12429 * 12430 * @param {Object} update 12431 * The content of the REST update. 12432 * 12433 * @returns {String} 12434 * The media ID associated with the update. 12435 * @private 12436 */ 12437 _getMediaIdFromRestUpdate: function(update) 12438 { 12439 return update.object.Update.data.dialog.mediaProperties.mediaId; 12440 }, 12441 12442 /** 12443 * Handler for REST updates. This handler forwards the update to the MediaDialogs object associated with 12444 * the media ID carried in the REST update. 12445 * 12446 * @param {Object} update 12447 * The content of the REST update. 12448 * 12449 * @private 12450 */ 12451 _processRestItemUpdate: function(update) 12452 { 12453 var mediaId = this._getMediaIdFromRestUpdate(update), 12454 mediaDialogs = this._mediaDialogsMap[mediaId]; 12455 12456 if ( mediaDialogs ) 12457 { 12458 mediaDialogs._processRestItemUpdate(update); 12459 } 12460 }, 12461 12462 /** 12463 * Utility method to create a callback to be given to OpenAjax to invoke when a message 12464 * is published on the topic of our REST URL (also XEP-0060 node). 12465 * This needs to be its own defined method so that subclasses can have their own implementation. 12466 * @returns {Function} callback(update) 12467 * The callback to be invoked when an update event is received. This callback will 12468 * process the update by notifying the MediaDialogs object associated with the media ID in the update. 12469 * 12470 * @private 12471 */ 12472 _createPubsubCallback: function () 12473 { 12474 var _this = this; 12475 return function (update) { 12476 //If the source of the update is our REST URL, this means the collection itself is modified 12477 if (update.object.Update.source.match(_this._sourceRegEx)) { 12478 _this._updateEventHandler(update); 12479 } else { 12480 //Otherwise, it is safe to assume that if we got an event on our topic, it must be a 12481 //rest item update of one of our children that was published on our node (OpenAjax topic) 12482 _this._processRestItemUpdate(update); 12483 } 12484 }; 12485 }, 12486 12487 /** 12488 * Track the MediaDialogs object so that events and REST updates signalled to this subscription manager 12489 * can be forwarded to the given MediaDialogs object. 12490 * @param {finesse.restservices.MediaDialogs} mediaDialogs MediaDialogs object to be tracked by the 12491 * subscription manager. 12492 * @private 12493 */ 12494 _manage: function(mediaDialogs) 12495 { 12496 this._mediaDialogsMap[mediaDialogs.getMedia().getMediaId()] = mediaDialogs; 12497 }, 12498 12499 /** 12500 * Stop tracking the MediaDialogs object. Events and REST updates signalled to this subscription manager 12501 * will no longer be forwarded to the given MediaDialogs object. 12502 * @param {finesse.restservices.MediaDialogs} mediaDialogs MediaDialogs object to no longer track. 12503 * @private 12504 */ 12505 _unManage: function(mediaDialogs) 12506 { 12507 var mediaId = mediaDialogs.getMedia().getMediaId(); 12508 if ( this._callbackMap[mediaId] ) 12509 { 12510 delete this._callbackMap[mediaId]; 12511 } 12512 }, 12513 12514 /** 12515 * @class 12516 * An internal class used to establish a Media/Dialogs subscription to be shared by MediaDialogs objects 12517 * for non-voice dialog events. 12518 * 12519 * @constructor 12520 * @param {RestBase} restObj 12521 * A RestBase object used to build the user portion of XMPP and REST paths. 12522 * @constructs 12523 */ 12524 init: function (restObj) 12525 { 12526 var _this; 12527 12528 this._sourceRegEx = this._makeSourceRegEx(restObj); 12529 }, 12530 12531 /** 12532 * Create the BOSH/XMPP subscription used for non-voice dialog events. Additionally, store the given 12533 * MediaDialogs object so that events for the object can be forwarded to it. 12534 * 12535 * @param {finesse.restservices.MediaDialogs} mediaDialogs a MediaDialogs object to manage (forward events) 12536 * @param {Object} callbacks an object containing success and error callbacks used to signal the result of 12537 * the subscription. 12538 * @returns {MediaDialogsSubscriptionManager} 12539 * @private 12540 */ 12541 subscribe: function (mediaDialogs, callbacks) 12542 { 12543 var topic = Topics.getTopic(mediaDialogs.getXMPPNodePath()), 12544 _this = this, 12545 handlers, 12546 successful; 12547 12548 callbacks = callbacks || {}; 12549 12550 handlers = { 12551 /** @private */ 12552 success: function () { 12553 // Add item to the refresh list in ClientServices to refresh if 12554 // we recover due to our resilient connection. 12555 ClientServices.addToRefreshList(_this); 12556 if (typeof callbacks.success === "function") { 12557 callbacks.success(); 12558 } 12559 }, 12560 /** @private */ 12561 error: function (err) { 12562 if (successful) { 12563 _this._unManage(mediaDialogs); 12564 ClientServices.unsubscribe(topic); 12565 } 12566 12567 if (typeof callbacks.error === "function") { 12568 callbacks.error(err); 12569 } 12570 } 12571 }; 12572 12573 this._manage(mediaDialogs); 12574 if ( this._subscriptionId ) 12575 { 12576 successful = true; 12577 } 12578 else 12579 { 12580 successful = ClientServices.subscribe(topic, this._createPubsubCallback(), true); 12581 if ( successful ) 12582 { 12583 this._subscriptionId = "OpenAjaxOnly"; 12584 } 12585 } 12586 12587 if (successful) { 12588 handlers.success(); 12589 } else { 12590 handlers.error(); 12591 } 12592 12593 return this; 12594 } 12595 }); 12596 12597 MediaDialogsSubscriptionManager.MEDIA_ID_INDEX_IN_SOURCE = 6; 12598 12599 window.finesse = window.finesse || {}; 12600 window.finesse.restservices = window.finesse.restservices || {}; 12601 window.finesse.restservices.MediaDialogsSubscriptionManager = MediaDialogsSubscriptionManager; 12602 12603 return MediaDialogsSubscriptionManager; 12604 }); 12605 12606 /** 12607 * JavaScript representation of the Finesse MediaDialogs collection 12608 * object which contains a list of Dialog objects. 12609 * 12610 * @requires finesse.clientservices.ClientServices 12611 * @requires Class 12612 * @requires finesse.FinesseBase 12613 * @requires finesse.restservices.RestBase 12614 * @requires finesse.restservices.Dialogs 12615 * @requires finesse.restservices.MediaDialogsSubscriptionManager 12616 */ 12617 /** @private */ 12618 define('restservices/MediaDialogs',[ 12619 'restservices/RestCollectionBase', 12620 'restservices/RestBase', 12621 'restservices/Dialogs', 12622 'restservices/MediaDialog', 12623 'restservices/MediaDialogsSubscriptionManager' 12624 ], 12625 function (RestCollectionBase, RestBase, Dialogs, MediaDialog, MediaDialogsSubscriptionManager) { 12626 var MediaDialogs = Dialogs.extend(/** @lends finesse.restservices.MediaDialogs.prototype */{ 12627 12628 /** 12629 * @class 12630 * JavaScript representation of a collection of Dialogs for a specific non-voice Media. 12631 * @augments finesse.restservices.Dialogs 12632 * @constructs 12633 * @see finesse.restservices.Dialog 12634 * @example 12635 * _MediaDialogs = _media.getMediaDialogs( { 12636 * onCollectionAdd : _handleDialogAdd, 12637 * onCollectionDelete : _handleDialogDelete, 12638 * onLoad : _handleMediaDialogsLoaded 12639 * }); 12640 * 12641 * _dialogCollection = _MediaDialogs.getCollection(); 12642 * for (var dialogId in _dialogCollection) { 12643 * if (_dialogCollection.hasOwnProperty(dialogId)) { 12644 * _dialog = _dialogCollection[dialogId]; 12645 * etc... 12646 * } 12647 * } 12648 */ 12649 _fakeConstuctor: function () { 12650 /* This is here to hide the real init constructor from the public docs */ 12651 }, 12652 12653 /** 12654 * @private 12655 * @param {Object} options 12656 * An object with the following properties:<ul> 12657 * <li><b>id:</b> The id of the object being constructed</li> 12658 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 12659 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 12660 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 12661 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 12662 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 12663 * <li><b>status:</b> {Number} The HTTP status code returned</li> 12664 * <li><b>content:</b> {String} Raw string of response</li> 12665 * <li><b>object:</b> {Object} Parsed object of response</li> 12666 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 12667 * <li><b>errorType:</b> {String} Type of error that was caught</li> 12668 * <li><b>errorMessage:</b> {String} Message associated with error</li> 12669 * </ul></li> 12670 * </ul></li> 12671 * <li><b>parentObj:</b> The parent object</li></ul> 12672 * <li><b>mediaObj:</b> The media object</li></ul> 12673 **/ 12674 init: function (options) { 12675 this._mediaObj = options.mediaObj; 12676 this._super(options); 12677 }, 12678 12679 getMedia: function() { 12680 return this._mediaObj; 12681 }, 12682 12683 /** 12684 * @private 12685 * Gets the REST class for the objects that make up the collection. - this 12686 * is the Dialog class. 12687 */ 12688 getRestItemClass: function () { 12689 return MediaDialog; 12690 }, 12691 12692 /** 12693 * @private 12694 * Gets the node path for the current object - this is the media node 12695 * @returns {String} The node path 12696 */ 12697 getXMPPNodePath: function () { 12698 var 12699 restObj = this._restObj, 12700 nodePath = ""; 12701 12702 //Prepend the base REST object if one was provided. 12703 if (restObj instanceof RestBase) { 12704 nodePath += restObj.getRestUrl(); 12705 } 12706 //Otherwise prepend with the default webapp name. 12707 else { 12708 nodePath += "/finesse/api"; 12709 } 12710 12711 //Append the REST type. 12712 nodePath += "/" + this.getRestType() + "/Media"; 12713 return nodePath; 12714 }, 12715 12716 /** 12717 * The REST URL in which this object can be referenced. 12718 * @return {String} 12719 * The REST URI for this object. 12720 * @private 12721 */ 12722 getRestUrl: function () { 12723 var 12724 restObj = this._mediaObj, 12725 restUrl = ""; 12726 12727 //Prepend the base REST object if one was provided. 12728 if (restObj instanceof RestBase) { 12729 restUrl += restObj.getRestUrl(); 12730 } 12731 //Otherwise prepend with the default webapp name. 12732 else { 12733 restUrl += "/finesse/api"; 12734 } 12735 12736 //Append the REST type. 12737 restUrl += "/" + this.getRestType(); 12738 12739 //Append ID if it is not undefined, null, or empty. 12740 if (this._id) { 12741 restUrl += "/" + this._id; 12742 } 12743 return restUrl; 12744 }, 12745 12746 /** 12747 * Overridden so that MediaDialogsSubscriptionManager can be used to share events across media dialogs. 12748 * 12749 * @param {Object} callbacks 12750 * An object containing success and error handlers for the subscription request. 12751 * @private 12752 */ 12753 subscribe: function (callbacks) 12754 { 12755 if ( !MediaDialogs.subscriptionManager ) 12756 { 12757 MediaDialogs.subscriptionManager = new MediaDialogsSubscriptionManager(this._restObj); 12758 } 12759 12760 MediaDialogs.subscriptionManager.subscribe(this, callbacks); 12761 12762 return this; 12763 } 12764 }); 12765 12766 MediaDialogs.subscriptionManager = /** @lends finesse.restservices.MediaDialogsSubscriptionManager.prototype */ null; 12767 12768 window.finesse = window.finesse || {}; 12769 window.finesse.restservices = window.finesse.restservices || {}; 12770 window.finesse.restservices.MediaDialogs = MediaDialogs; 12771 12772 return MediaDialogs; 12773 }); 12774 12775 /** 12776 * Allows gadgets to call the log function to publish client logging messages over the hub. 12777 * 12778 * @requires OpenAjax 12779 */ 12780 /** @private */ 12781 define('cslogger/ClientLogger',[], function () { 12782 12783 var ClientLogger = ( function () { /** @lends finesse.cslogger.ClientLogger.prototype */ 12784 var _hub, _logTopic, _originId, _sessId, _host, 12785 MONTH = { 0 : "Jan", 1 : "Feb", 2 : "Mar", 3 : "Apr", 4 : "May", 5 : "Jun", 12786 6 : "Jul", 7 : "Aug", 8 : "Sep", 9 : "Oct", 10 : "Nov", 11 : "Dec"}, 12787 12788 /** 12789 * Gets timestamp drift stored in sessionStorage 12790 * @returns drift in seconds if it is set in sessionStorage otherwise returns undefined. 12791 * @private 12792 */ 12793 getTsDrift = function() { 12794 if (window.sessionStorage.getItem('clientTimestampDrift') !== null) { 12795 return parseInt(window.sessionStorage.getItem('clientTimestampDrift'), 10); 12796 } 12797 else { 12798 return undefined; 12799 } 12800 }, 12801 12802 /** 12803 * Sets timestamp drift in sessionStorage 12804 * @param delta is the timestamp drift between server.and client. 12805 * @private 12806 */ 12807 setTsDrift = function(delta) { 12808 window.sessionStorage.setItem('clientTimestampDrift', delta.toString()); 12809 }, 12810 12811 /** 12812 * Gets Finesse server timezone offset from GMT in seconds 12813 * @returns offset in seconds if it is set in sessionStorage otherwise returns undefined. 12814 * @private 12815 */ 12816 getServerOffset = function() { 12817 if (window.sessionStorage.getItem('serverTimezoneOffset') !== null) { 12818 return parseInt(window.sessionStorage.getItem('serverTimezoneOffset'), 10); 12819 } 12820 else { 12821 return undefined; 12822 } 12823 }, 12824 12825 /** 12826 * Sets server timezone offset 12827 * @param sec is the server timezone GMT offset in seconds. 12828 * @private 12829 */ 12830 setServerOffset = function(sec) { 12831 window.sessionStorage.setItem('serverTimezoneOffset', sec.toString()); 12832 }, 12833 12834 /** 12835 * Checks to see if we have a console. 12836 * @returns Whether the console object exists. 12837 * @private 12838 */ 12839 hasConsole = function () { 12840 try { 12841 if (window.console !== undefined) { 12842 return true; 12843 } 12844 } 12845 catch (err) { 12846 // ignore and return false 12847 } 12848 12849 return false; 12850 }, 12851 12852 /** 12853 * Gets a short form (6 character) session ID from sessionStorage 12854 * @private 12855 */ 12856 getSessId = function() { 12857 if (!_sessId) { 12858 //when _sessId not defined yet, initiate it 12859 if (window.sessionStorage.getItem('enableLocalLog') === 'true') { 12860 _sessId= " "+window.sessionStorage.getItem('finSessKey'); 12861 } 12862 else { 12863 _sessId=" "; 12864 } 12865 } 12866 return _sessId; 12867 }, 12868 12869 /** 12870 * Pads a single digit number for display purposes (e.g. '4' shows as '04') 12871 * @param num is the number to pad to 2 digits 12872 * @returns a two digit padded string 12873 * @private 12874 */ 12875 padTwoDigits = function (num) 12876 { 12877 return (num < 10) ? '0' + num : num; 12878 }, 12879 12880 /** 12881 * Pads a single digit number for display purposes (e.g. '4' shows as '004') 12882 * @param num is the number to pad to 3 digits 12883 * @returns a three digit padded string 12884 * @private 12885 */ 12886 padThreeDigits = function (num) 12887 { 12888 if (num < 10) 12889 { 12890 return '00'+num; 12891 } 12892 else if (num < 100) 12893 { 12894 return '0'+num; 12895 } 12896 else 12897 { 12898 return num; 12899 } 12900 }, 12901 12902 /** 12903 * Compute the "hour" 12904 * 12905 * @param s is time in seconds 12906 * @returns {String} which is the hour 12907 * @private 12908 */ 12909 ho = function (s) { 12910 return ((s/60).toString()).split(".")[0]; 12911 }, 12912 12913 /** 12914 * Gets local timezone offset string. 12915 * 12916 * @param t is the time in seconds 12917 * @param s is the separator character between hours and minutes, e.g. ':' 12918 * @returns {String} is local timezone GMT offset in the following format: [+|-]hh[|:]MM 12919 * @private 12920 */ 12921 getGmtOffString = function (min,s) { 12922 var t, sign; 12923 if (min<0) { 12924 t = -min; 12925 sign = "-"; 12926 } 12927 else { 12928 t = min; 12929 sign = "+"; 12930 } 12931 12932 if (s===':') { 12933 return sign+padTwoDigits(ho(t))+s+padTwoDigits(t%60); 12934 } 12935 else { 12936 return sign+padTwoDigits(ho(t))+padTwoDigits(t%60); 12937 } 12938 }, 12939 12940 /** 12941 * Gets short form of a month name in English 12942 * 12943 * @param monthNum is zero-based month number 12944 * @returns {String} is short form of month name in English 12945 * @private 12946 */ 12947 getMonthShortStr = function (monthNum) { 12948 var result; 12949 try { 12950 result = MONTH[monthNum]; 12951 } 12952 catch (err) { 12953 if (hasConsole()) { 12954 window.console.log("Month must be between 0 and 11"); 12955 } 12956 } 12957 return result; 12958 }, 12959 12960 /** 12961 * Gets a timestamp. 12962 * @param aDate is a javascript Date object 12963 * @returns {String} is a timestamp in the following format: yyyy-mm-ddTHH:MM:ss.SSS [+|-]HH:MM 12964 * @private 12965 */ 12966 getDateTimeStamp = function (aDate) 12967 { 12968 var date, off, timeStr; 12969 if (aDate === null) { 12970 date = new Date(); 12971 } 12972 else { 12973 date = aDate; 12974 } 12975 off = -1*date.getTimezoneOffset(); 12976 timeStr = date.getFullYear().toString() + "-" + 12977 padTwoDigits(date.getMonth()+1) + "-" + 12978 padTwoDigits (date.getDate()) + "T"+ 12979 padTwoDigits(date.getHours()) + ":" + 12980 padTwoDigits(date.getMinutes()) + ":" + 12981 padTwoDigits(date.getSeconds())+"." + 12982 padThreeDigits(date.getMilliseconds()) + " "+ 12983 getGmtOffString(off, ':'); 12984 12985 return timeStr; 12986 }, 12987 12988 /** 12989 * Gets drift-adjusted timestamp. 12990 * @param aTimestamp is a timestamp in milliseconds 12991 * @param drift is a timestamp drift in milliseconds 12992 * @param serverOffset is a timezone GMT offset in minutes 12993 * @returns {String} is a timestamp in the Finesse server log format, e.g. Jan 07 2104 HH:MM:ss.SSS -0500 12994 * @private 12995 */ 12996 getDriftedDateTimeStamp = function (aTimestamp, drift, serverOffset) 12997 { 12998 var date, timeStr, localOffset; 12999 if (aTimestamp === null) { 13000 return "--- -- ---- --:--:--.--- -----"; 13001 } 13002 else if (drift === undefined || serverOffset === undefined) { 13003 if (hasConsole()) { 13004 window.console.log("drift or serverOffset must be a number"); 13005 } 13006 return "--- -- ---- --:--:--.--- -----"; 13007 } 13008 else { 13009 //need to get a zone diff in minutes 13010 localOffset = (new Date()).getTimezoneOffset(); 13011 date = new Date(aTimestamp+drift+(localOffset+serverOffset)*60000); 13012 timeStr = getMonthShortStr(date.getMonth()) + " "+ 13013 padTwoDigits (date.getDate())+ " "+ 13014 date.getFullYear().toString() + " "+ 13015 padTwoDigits(date.getHours()) + ":" + 13016 padTwoDigits(date.getMinutes()) + ":" + 13017 padTwoDigits(date.getSeconds())+"." + 13018 padThreeDigits(date.getMilliseconds())+" "+ 13019 getGmtOffString(serverOffset, ''); 13020 return timeStr; 13021 } 13022 }, 13023 13024 /** 13025 * Logs a message to a hidden textarea element on the page 13026 * 13027 * @param msg is the string to log. 13028 * @private 13029 */ 13030 writeToLogOutput = function (msg) { 13031 var logOutput = document.getElementById("finesseLogOutput"); 13032 13033 if (logOutput === null) 13034 { 13035 logOutput = document.createElement("textarea"); 13036 logOutput.id = "finesseLogOutput"; 13037 logOutput.style.display = "none"; 13038 document.body.appendChild(logOutput); 13039 } 13040 13041 if (logOutput.value === "") 13042 { 13043 logOutput.value = msg; 13044 } 13045 else 13046 { 13047 logOutput.value = logOutput.value + "\n" + msg; 13048 } 13049 }, 13050 13051 /* 13052 * Logs a message to console 13053 * @param str is the string to log. * @private 13054 */ 13055 logToConsole = function (str) 13056 { 13057 var now, msg, timeStr, driftedTimeStr, sessKey=getSessId(); 13058 now = new Date(); 13059 timeStr = getDateTimeStamp(now); 13060 if (getTsDrift() !== undefined) { 13061 driftedTimeStr = getDriftedDateTimeStamp(now.getTime(), getTsDrift(), getServerOffset()); 13062 } 13063 else { 13064 driftedTimeStr = getDriftedDateTimeStamp(null, 0, 0); 13065 } 13066 msg = timeStr + ":"+sessKey+": "+ _host + ": "+driftedTimeStr+ ": " + str; 13067 // Log to console 13068 if (hasConsole()) { 13069 window.console.log(msg); 13070 } 13071 13072 //Uncomment to print logs to hidden textarea. 13073 //writeToLogOutput(msg); 13074 13075 return msg; 13076 }; 13077 return { 13078 13079 /** 13080 * Publishes a Log Message over the hub. 13081 * 13082 * @param {String} message 13083 * The string to log. 13084 * @example 13085 * _clientLogger.log("This is some important message for MyGadget"); 13086 * 13087 */ 13088 log : function (message) { 13089 if(_hub) { 13090 _hub.publish(_logTopic, logToConsole(_originId + message)); 13091 } 13092 }, 13093 13094 /** 13095 * @class 13096 * Allows gadgets to call the log function to publish client logging messages over the hub. 13097 * 13098 * @constructs 13099 */ 13100 _fakeConstuctor: function () { 13101 /* This is here so we can document init() as a method rather than as a constructor. */ 13102 }, 13103 13104 /** 13105 * Initiates the client logger with a hub a gadgetId and gadget's config object. 13106 * @param {Object} hub 13107 * The hub to communicate with. 13108 * @param {String} gadgetId 13109 * A unique string to identify which gadget is doing the logging. 13110 * @param {finesse.gadget.Config} config 13111 * The config object used to get host name for that thirdparty gadget 13112 * @example 13113 * var _clientLogger = finesse.cslogger.ClientLogger; 13114 * _clientLogger.init(gadgets.Hub, "MyGadgetId", config); 13115 * 13116 */ 13117 init: function (hub, gadgetId, config) { 13118 _hub = hub; 13119 _logTopic = "finesse.clientLogging." + gadgetId; 13120 _originId = gadgetId + " : "; 13121 if ((config === undefined) || (config === "undefined")) 13122 { 13123 _host = ((finesse.container && finesse.container.Config && finesse.container.Config.host)?finesse.container.Config.host : "?.?.?.?"); 13124 } 13125 else 13126 { 13127 _host = ((config && config.host)?config.host : "?.?.?.?"); 13128 } 13129 } 13130 }; 13131 }()); 13132 13133 window.finesse = window.finesse || {}; 13134 window.finesse.cslogger = window.finesse.cslogger || {}; 13135 window.finesse.cslogger.ClientLogger = ClientLogger; 13136 13137 finesse = finesse || {}; 13138 /** @namespace Supports writing messages to a central log. */ 13139 finesse.cslogger = finesse.cslogger || {}; 13140 13141 return ClientLogger; 13142 }); 13143 13144 /** 13145 * Utility class used to recover a media object after recovering from a connection or system failure. 13146 * 13147 * @requires Class 13148 * @requires finesse.restservices.Notifier 13149 * @requires finesse.clientservices.ClientServices 13150 * @requires finesse.restservices.Media 13151 */ 13152 13153 /** @private */ 13154 define('restservices/MediaOptionsHelper',['../../thirdparty/Class', 13155 '../utilities/Utilities', 13156 '../clientservices/ClientServices', 13157 '../cslogger/ClientLogger' 13158 ], 13159 function (Class, Utilities, ClientServices, ClientLogger) 13160 { 13161 /** 13162 * Utility class used to synchronize media login options after recovering from a connection or system failure. This 13163 * class will ensure that the Finesse server that the application fails over to has the same maxDialogLimit, 13164 * interruptAction, and dialogLogoutAction as the previous Finesse server. 13165 */ 13166 var MediaOptionsHelper = Class.extend(/** @lends finesse.restservices.MediaOptionsHelper.prototype */ 13167 { 13168 /** 13169 * @private 13170 * 13171 * The media that this helper is responsible for recovering in case of failover. 13172 */ 13173 _media: null, 13174 13175 /** 13176 * @private 13177 * 13178 * The media options that this helper will ensure are set properly across failures. 13179 */ 13180 _mediaOptions: null, 13181 13182 /** 13183 * @private 13184 * 13185 * The current state of the failover recovery. 13186 */ 13187 _state: null, 13188 13189 /** 13190 * @class 13191 * 13192 * Utility class used to synchronize media login options after recovering from a connection or system failure. This 13193 * class will ensure that the Finesse server that the application fails over to has the same maxDialogLimit, 13194 * interruptAction, and dialogLogoutAction as the previous Finesse server. 13195 * 13196 * @constructs 13197 */ 13198 _fakeConstuctor: function () 13199 { 13200 /* This is here to hide the real init constructor from the public docs */ 13201 }, 13202 13203 /** 13204 * Utility method to format a message logged by an instance of this class. 13205 * 13206 * @param {string} message the message to format 13207 * @returns {string} the given message prefixed with the name of this class and the ID of the Media object 13208 * associated with this class. 13209 * @private 13210 */ 13211 _formatLogMessage: function(message) 13212 { 13213 return "MediaOptionsHelper[" + this.media.getMediaId() + "]: " + message; 13214 }, 13215 13216 /** 13217 * Utility method to log an informational message. 13218 * 13219 * Note that this method piggy-backs on the logger setup by the gadget. If the gadget does not initialize 13220 * logger, this class will not log. 13221 * 13222 * @param {string} message the message to log 13223 * @private 13224 */ 13225 _log: function(message) 13226 { 13227 ClientLogger.log(this._formatLogMessage(message)); 13228 }, 13229 13230 /** 13231 * Utility method to log an error message. 13232 * 13233 * Note that this method piggy-backs on the logger setup by the gadget. If the gadget does not initialize 13234 * logger, this class will not log. 13235 * 13236 * @param {string} message the message to log 13237 * @private 13238 */ 13239 _error: function(message) 13240 { 13241 ClientLogger.error(this._formatLogMessage(message)); 13242 }, 13243 13244 /** 13245 * @private 13246 * 13247 * Set the running state of this failover helper. 13248 * 13249 * @param {String} newState the new state of the failover helper. 13250 */ 13251 _setState: function(newState) 13252 { 13253 this._state = newState; 13254 this._log("changed state to " + this._state); 13255 }, 13256 13257 /** 13258 * Check the given media object to see if the maxDialogLimit, interruptAction, and dialogLogoutAction options 13259 * need to be reset. These options need to be reset if the application specified login options and any of the 13260 * following conditions are true:<ul> 13261 * <li>the dialogLogoutAction in the given media object does not match the action set by the application</li> 13262 * <li>the interruptAction in the given media object does not match the action set by the application</li> 13263 * <li>the maxDialogLimit in the given media object does not match the limit set by the application</li></ul> 13264 * 13265 * @param {Object} media the media object to evaluate 13266 * @returns {*|{}|boolean} true if a login request should be sent to correct the media options 13267 * @private 13268 */ 13269 _shouldLoginToFixOptions: function(media) 13270 { 13271 return this._mediaOptions 13272 && media.isLoggedIn() 13273 && (media.getDialogLogoutAction() !== this._mediaOptions.dialogLogoutAction 13274 || media.getInterruptAction() !== this._mediaOptions.interruptAction 13275 || media.getMaxDialogLimit() !== this._mediaOptions.maxDialogLimit); 13276 }, 13277 13278 /** 13279 * @private 13280 * 13281 * Determine if the given response is an "agent already logged in" error. 13282 * 13283 * @param {Object} response the response to evaluate 13284 * 13285 * @returns {boolean} true if 13286 */ 13287 _agentIsAlreadyLoggedIn: function(response) 13288 { 13289 return response 13290 && response.object 13291 && response.object.ApiErrors 13292 && response.object.ApiErrors.ApiError 13293 && response.object.ApiErrors.ApiError.ErrorMessage === "E_ARM_STAT_AGENT_ALREADY_LOGGED_IN"; 13294 }, 13295 13296 /** 13297 * Determine if the given response to a media login request is successful. A response is successful under these 13298 * conditions:<ul> 13299 * <li>the response has a 202 status</li> 13300 * <li>the response has a 400 status and the error indicates the agent is already logged in</li> 13301 * </ul> 13302 * 13303 * @param {Object} loginResponse the response to evaluate 13304 * 13305 * @returns {*|boolean} true if the response status is 202 or if the response status is 400 and the error states 13306 * that the agent is already logged in. 13307 * @private 13308 */ 13309 _isSuccessfulLoginResponse: function(loginResponse) 13310 { 13311 return loginResponse && ((loginResponse.status === 202) || this._agentIsAlreadyLoggedIn(loginResponse)); 13312 }, 13313 13314 /** 13315 * Process a media load or change while in the connected state. This involves checking the media options to 13316 * ensure they are the same as those set by the application. 13317 * 13318 * @param {Object} media the media object that was loaded or changed. 13319 * @private 13320 */ 13321 _processConnectedState: function(media) 13322 { 13323 var _this = this, processResponse; 13324 13325 if ( this._shouldLoginToFixOptions(media) ) 13326 { 13327 processResponse = function(response) 13328 { 13329 _this._setState(MediaOptionsHelper.States.MONITORING_OPTIONS); 13330 13331 if ( !_this._isSuccessfulLoginResponse(response) ) 13332 { 13333 _this._error("failed to reset options: " + response.status + ": " + response.content); 13334 } 13335 }; 13336 13337 this._setState(MediaOptionsHelper.States.SETTING_OPTIONS); 13338 13339 this._log("logging in to fix options"); 13340 13341 this.media.login({ 13342 dialogLogoutAction: _this._mediaOptions.dialogLogoutAction, 13343 interruptAction: _this._mediaOptions.interruptAction, 13344 maxDialogLimit: _this._mediaOptions.maxDialogLimit, 13345 handlers: { 13346 success: processResponse, 13347 error: processResponse 13348 } 13349 }); 13350 } 13351 }, 13352 13353 /** 13354 * Process a media load or change while in the resetting options state. All that is done in this state is log a 13355 * message that a reset is already in progress. 13356 * 13357 * @param {Object} media the media object that was loaded or changed. 13358 * @private 13359 */ 13360 _processResettingOptionsState: function(media) 13361 { 13362 this._log("Resetting options is in progress"); 13363 }, 13364 13365 /** 13366 * Initialize a helper class used to recover media objects following connectivity or component failures related 13367 * to Finesse and/or CCE services. 13368 * 13369 * Initialize the failover helper to manage the recovery of the given media object. 13370 * 13371 * @param {Object} media the media object to recover 13372 * @param {Object} mediaOptions an object containing the media options used by the application:<ul> 13373 * <li><b>maxDialogLimit:</b> The id of the object being constructed</li> 13374 * <li><b>interruptAction:</b> Accept or ignore interrupts</li> 13375 * <li><b>dialogLogoutAction:</b> transfer or close the task at logout time</li></ul> 13376 */ 13377 init: function (media, mediaOptions) 13378 { 13379 var _this = this, processMediaStateChange = function(media) 13380 { 13381 switch ( _this._state ) 13382 { 13383 case MediaOptionsHelper.States.MONITORING_OPTIONS: 13384 _this._processConnectedState(media); 13385 break; 13386 case MediaOptionsHelper.States.SETTING_OPTIONS: 13387 _this._processResettingOptionsState(media); 13388 break; 13389 default: 13390 _this._error("unexpected state: " + _this.state); 13391 break; 13392 } 13393 }; 13394 13395 this.media = media; 13396 13397 this._mediaOptions = mediaOptions || {}; 13398 13399 // The maxDialogLimit is a string in media events. Ensure _mediaOptions.maxDialogLimit is a string to 13400 // make sure it can be compared to the maxDialogLimit field in media events. 13401 // 13402 if ( this._mediaOptions.maxDialogLimit ) 13403 { 13404 this._mediaOptions.maxDialogLimit = this._mediaOptions.maxDialogLimit.toString(); 13405 } 13406 13407 // Add the media object to the refresh list so that ClientServices calls refresh on the media object when 13408 // the connection is reestablished. This will trigger processMediaStateChange() which will trigger a login 13409 // to restore media options if media options are different. 13410 // 13411 ClientServices.addToRefreshList(this.media); 13412 13413 this._setState(MediaOptionsHelper.States.MONITORING_OPTIONS); 13414 13415 this.media.addHandler('load', processMediaStateChange); 13416 this.media.addHandler('change', processMediaStateChange); 13417 13418 this._log("initialized"); 13419 } 13420 }); 13421 13422 /** 13423 * @private 13424 * 13425 * The states that a running MediaOptionsHelper executes. 13426 * 13427 * @type {{CONNECTED: string, RECOVERING: string, RECOVERY_LOGIN: string, _fakeConstructor: _fakeConstructor}} 13428 */ 13429 MediaOptionsHelper.States = /** @lends finesse.restservices.MediaOptionsHelper.States.prototype */ { 13430 13431 /** 13432 * The media is synchronized with the Finesse server. Media options are being monitored for changes. 13433 */ 13434 MONITORING_OPTIONS: "MONITORING_OPTIONS", 13435 13436 /** 13437 * A media login request has been sent to Finesse to set the correct media options. 13438 */ 13439 SETTING_OPTIONS: "SETTING_OPTIONS", 13440 13441 /** 13442 * @class Possible MediaOptionsHelper state values. 13443 * @constructs 13444 */ 13445 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 13446 }; 13447 13448 window.finesse = window.finesse || {}; 13449 window.finesse.restservices = window.finesse.restservices || {}; 13450 window.finesse.restservices.MediaOptionsHelper = MediaOptionsHelper; 13451 13452 return MediaOptionsHelper; 13453 }); 13454 /** 13455 * JavaScript representation of the Finesse Media object 13456 * 13457 * @requires finesse.clientservices.ClientServices 13458 * @requires Class 13459 * @requires finesse.FinesseBase 13460 * @requires finesse.restservices.RestBase 13461 * @requires finesse.restservices.MediaDialogs 13462 * @requires finesse.restservices.MediaOptionsHelper 13463 */ 13464 13465 /** @private */ 13466 define('restservices/Media',[ 13467 'restservices/RestBase', 13468 'restservices/MediaDialogs', 13469 'restservices/MediaOptionsHelper' 13470 ], 13471 function (RestBase, MediaDialogs, MediaOptionsHelper) { 13472 var Media = RestBase.extend(/** @lends finesse.restservices.Media.prototype */{ 13473 13474 /** 13475 * Media objects support GET REST requests. 13476 */ 13477 supportsRequests: true, 13478 13479 /** 13480 * @private 13481 * The list of dialogs associated with this media. 13482 */ 13483 _mdialogs : null, 13484 13485 /** 13486 * @private 13487 * The options used to log into this media. 13488 */ 13489 _mediaOptions: null, 13490 13491 /** 13492 * @private 13493 * Object used to keep the maxDialogLimit, interruptAction, and dialogLogoutAction in-synch across fail-overs. 13494 */ 13495 _optionsHelper: null, 13496 13497 /** 13498 * @class 13499 * A Media represents a non-voice channel, 13500 * for example, a chat or a email. 13501 * 13502 * @augments finesse.restservices.RestBase 13503 * @constructs 13504 */ 13505 _fakeConstuctor: function () { 13506 /* This is here to hide the real init constructor from the public docs */ 13507 }, 13508 13509 /** 13510 * @private 13511 * 13512 * @param {Object} options 13513 * An object with the following properties:<ul> 13514 * <li><b>id:</b> The id of the object being constructed</li> 13515 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 13516 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 13517 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 13518 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 13519 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 13520 * <li><b>status:</b> {Number} The HTTP status code returned</li> 13521 * <li><b>content:</b> {String} Raw string of response</li> 13522 * <li><b>object:</b> {Object} Parsed object of response</li> 13523 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 13524 * <li><b>errorType:</b> {String} Type of error that was caught</li> 13525 * <li><b>errorMessage:</b> {String} Message associated with error</li> 13526 * </ul></li> 13527 * </ul></li> 13528 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 13529 **/ 13530 init: function (options) { 13531 this._super(options); 13532 }, 13533 13534 /** 13535 * @private 13536 * Utility method used to retrieve an attribute from this media object's underlying data. 13537 * 13538 * @param {String} attributeName the name of the attribute to retrieve 13539 * @returns {String} the value of the attribute or undefined if the attribute is not found 13540 */ 13541 _getDataAttribute: function(attributeName) { 13542 this.isLoaded(); 13543 return this.getData()[attributeName]; 13544 }, 13545 13546 /** 13547 * @private 13548 * Gets the REST class for the current object - this is the Media class. 13549 * @returns {Object} The Media class. 13550 */ 13551 getRestClass: function () { 13552 return Media; 13553 }, 13554 13555 /** 13556 * @private 13557 * Gets the REST type for the current object - this is a "Media". 13558 * @returns {String} The Media string. 13559 */ 13560 getRestType: function () { 13561 return "Media"; 13562 }, 13563 13564 /** 13565 * @private 13566 * Getter for the uri. 13567 * @returns {String} The uri. 13568 */ 13569 getMediaUri: function () { 13570 return this._getDataAttribute('uri'); 13571 }, 13572 13573 /** 13574 * Getter for the id. 13575 * @returns {String} The id. 13576 */ 13577 getId: function () { 13578 return this._getDataAttribute('id'); 13579 }, 13580 13581 /** 13582 * Getter for the name. 13583 * @returns {String} The name. 13584 */ 13585 getName: function () { 13586 return this._getDataAttribute('name'); 13587 }, 13588 13589 /** 13590 * Getter for the reason code id. 13591 * @returns {String} The reason code id. 13592 */ 13593 getReasonCodeId: function () { 13594 return this._getDataAttribute('reasonCodeId'); 13595 }, 13596 13597 /** 13598 * Getter for the reason code label. 13599 * @returns {String} The reason code label. 13600 */ 13601 getReasonCodeLabel: function() { 13602 this.isLoaded(); 13603 if (this.getData().reasonCode) { 13604 return this.getData.reasonCode.label; 13605 } 13606 return ""; 13607 }, 13608 13609 /** 13610 * Getter for the action to be taken in the event this media is interrupted. The action will be one of the 13611 * following:<ul> 13612 * <li><b>ACCEPT:</b> the interrupt will be accepted and the agent will not work on tasks in this media 13613 * until the media is no longer interrupted.</li> 13614 * <li><b>IGNORE:</b> the interrupt will be ignored and the agent is allowed to work on the task while the 13615 * media is interrupted.</li></ul> 13616 * @returns {*|Object} 13617 */ 13618 getInterruptAction: function() { 13619 return this._getDataAttribute('interruptAction'); 13620 }, 13621 13622 /** 13623 * Getter for the action to be taken in the event the agent logs out with dialogs associated with this media. 13624 * The action will be one of the following:<ul> 13625 * <li><b>CLOSE:</b> the dialog will be closed.</li> 13626 * <li><b>TRANSFER:</b> the dialog will be transferred to another agent.</li></ul> 13627 * @returns {*|Object} 13628 */ 13629 getDialogLogoutAction: function() { 13630 return this._getDataAttribute('dialogLogoutAction'); 13631 }, 13632 13633 /** 13634 * Getter for the state of the User on this Media. 13635 * @returns {String} 13636 * The current (or last fetched) state of the User on this Media 13637 * @see finesse.restservices.Media.States 13638 */ 13639 getState: function() { 13640 return this._getDataAttribute('state'); 13641 }, 13642 13643 /** 13644 * Getter for the Media id 13645 * @returns {String} The Media id 13646 */ 13647 getMediaId: function() { 13648 return this._getDataAttribute('id'); 13649 }, 13650 13651 /** 13652 * Getter for maximum number of dialogs allowed on this Media 13653 * @returns {String} The max number of Dialogs on this Media 13654 */ 13655 getMaxDialogLimit: function() { 13656 return this._getDataAttribute('maxDialogLimit'); 13657 }, 13658 13659 /** 13660 * Getter for whether or not this media is interruptible 13661 * @returns {Boolean} true if interruptible; false otherwise 13662 */ 13663 getInterruptible: function() { 13664 var interruptible = this._getDataAttribute('interruptible'); 13665 return interruptible === 'true'; 13666 }, 13667 13668 /** 13669 * Is the user interruptible on this Media. 13670 * @returns {Boolean} true if interruptible; false otherwise 13671 */ 13672 isInterruptible: function() { 13673 return this.getInterruptible(); 13674 }, 13675 13676 /** 13677 * Getter for routable field on this Media 13678 * @returns {Boolean} true if routable, false otherwise 13679 */ 13680 getRoutable: function() { 13681 var routable = this._getDataAttribute('routable'); 13682 return routable === 'true'; 13683 }, 13684 13685 /** 13686 * Is the user routable on this Media. 13687 * @returns {Boolean} true if routable, false otherwise 13688 */ 13689 isRoutable: function() { 13690 return this.getRoutable(); 13691 }, 13692 13693 /** 13694 * @param {Object} options 13695 * An object with the following properties:<ul> 13696 * <li><b>routable:</b> true if the agent is routable, false otherwise</li> 13697 * <li><b>{finesse.interfaces.RequestHandlers} handlers:</b> An object containing the handlers for the request</li></ul> 13698 * @returns {finesse.restservices.Media} 13699 * This Media object, to allow cascading 13700 */ 13701 setRoutable: function(params) { 13702 var handlers, contentBody = {}, 13703 restType = this.getRestType(), 13704 url = this.getRestUrl(); 13705 params = params || {}; 13706 13707 contentBody[restType] = { 13708 "routable": params.routable 13709 }; 13710 13711 handlers = params.handlers || {}; 13712 13713 this._makeRequest(contentBody, url, handlers); 13714 13715 return this; 13716 }, 13717 13718 /** 13719 * @private 13720 * Invoke a request to the server given a content body and handlers. 13721 * 13722 * @param {Object} contentBody 13723 * A JS object containing the body of the action request. 13724 * @param {finesse.interfaces.RequestHandlers} handlers 13725 * An object containing the handlers for the request 13726 */ 13727 _makeRequest: function (contentBody, url, handlers) { 13728 // Protect against null dereferencing of options allowing its 13729 // (nonexistent) keys to be read as undefined 13730 handlers = handlers || {}; 13731 13732 this.restRequest(url, { 13733 method: 'PUT', 13734 success: handlers.success, 13735 error: handlers.error, 13736 content: contentBody 13737 }); 13738 }, 13739 13740 /** 13741 * Return true if the params object contains one of the following:<ul> 13742 * <li>maxDialogLimit</li> 13743 * <li>interruptAction</li> 13744 * <li>dialogLogoutAction</li></ul> 13745 * 13746 * @param {Object} params the parameters to evaluate 13747 * @returns {*|Boolean} 13748 * @private 13749 */ 13750 _containsLoginOptions: function(params) { 13751 return params.maxDialogLimit || params.interruptAction || params.dialogLogoutAction; 13752 }, 13753 13754 /** 13755 * Create login parameters using the given algorithm:<ul> 13756 * <li>if loginOptions have not be set in the call to MediaList.getMedia(), use the params given by the application</li> 13757 * <li>if no params were set by the application, use the loginOptions set in the call to MediaList.getMedia()</li> 13758 * <li>if the parameters given by the application contains login options, use the parameters given by the application</li> 13759 * <li>if login options were given by the application but callbacks were given, create new login params with the 13760 * loginOptions set in the call to MediaList.getMedia() and the callbacks specified in the given login params</li></ul> 13761 * 13762 * @param params the parameters specified by the application in the call to Media.login() 13763 * 13764 * @see finesse.restservices.Media#login 13765 * @see finesse.restservices.MediaList#getMedia 13766 * 13767 * @returns {Object} login parameters built based on the algorithm listed above. 13768 * @private 13769 */ 13770 _makeLoginOptions: function(params) { 13771 if ( !this._mediaOptions ) { 13772 // If loginOptions have not be set, use the params given by the application. 13773 // 13774 return params; 13775 } 13776 13777 if ( !params ) { 13778 // If there were no params given by the application, use the loginOptions set in the call to 13779 // MediaList.getMedia(). 13780 // 13781 return this._mediaOptions; 13782 } 13783 13784 if ( this._containsLoginOptions(params) ) { 13785 // if the parameters given by the application contains login options, use the parameters given by the 13786 // application. 13787 // 13788 return params; 13789 } 13790 13791 // If login options were given by the application but callbacks were given, create new login params with the 13792 // loginOptions set in the call to MediaList.getMedia() and the callbacks specified in the given login 13793 // params. 13794 // 13795 return { 13796 maxDialogLimit: this._mediaOptions.maxDialogLimit, 13797 interruptAction: this._mediaOptions.interruptAction, 13798 dialogLogoutAction: this._mediaOptions.dialogLogoutAction, 13799 handlers: params.handlers 13800 }; 13801 }, 13802 13803 /** 13804 * Ensure that the maxDialogLimit, interruptAction, and dialogLogoutAction options are kept in synch across 13805 * fail-overs for this media object. 13806 * @private 13807 */ 13808 _ensureOptionsAreInSynch: function() { 13809 if ( !this._optionsHelper && this._mediaOptions ) { 13810 this._optionsHelper = new MediaOptionsHelper(this, this._mediaOptions); 13811 } 13812 }, 13813 13814 /** 13815 * Log the agent into this media. 13816 * 13817 * @param {Object} options 13818 * An object with the following properties:<ul> 13819 * <li><b>maxDialogLimit:</b> The id of the object being constructed</li> 13820 * <li><b>interruptAction:</b> Accept or ignore interrupts</li> 13821 * <li><b>dialogLogoutAction:</b> transfer or close the task at logout time</li> 13822 * <li><b>{finesse.interfaces.RequestHandlers} handlers:</b> An object containing the handlers for the request</li></ul> 13823 * 13824 * If maxDialogLimit, interruptAction, and dialogLogoutAction are not present, loginOptions specified in the 13825 * call to finesse.restservices.MediaList.getMedia() will be used. 13826 * 13827 * @see finesse.restservices.MediaList#getMedia 13828 * 13829 * @returns {finesse.restservices.Media} 13830 * This Media object, to allow cascading 13831 */ 13832 login: function(params) { 13833 this.setState(Media.States.LOGIN, null, this._makeLoginOptions(params)); 13834 return this; // Allow cascading 13835 }, 13836 13837 /** 13838 * Perform a logout for a user on this media. 13839 * @param {String} reasonCode 13840 * The reason this user is logging out of this media. Pass null for no reason. 13841 * @param {finesse.interfaces.RequestHandlers} handlers 13842 * An object containing the handlers for the request 13843 * @returns {finesse.restservices.Media} 13844 * This Media object, to allow cascading 13845 */ 13846 logout: function(reasonCode, params) { 13847 var state = Media.States.LOGOUT; 13848 return this.setState(state, reasonCode, params); 13849 }, 13850 13851 /** 13852 * Set the state of the user on this Media. 13853 * @param {String} newState 13854 * The state you are setting 13855 * @param {ReasonCode} reasonCode 13856 * The reason this user is changing state for this media. Pass null for no reason. 13857 * @param {finesse.interfaces.RequestHandlers} handlers 13858 * An object containing the handlers for the request 13859 * @see finesse.restservices.User.States 13860 * @returns {finesse.restservices.Media} 13861 * This Media object, to allow cascading 13862 */ 13863 setState: function(state, reasonCode, params) { 13864 var handlers, reasonCodeId, contentBody = {}, 13865 restType = this.getRestType(), 13866 url = this.getRestUrl(); 13867 params = params || {}; 13868 13869 if(reasonCode) { 13870 reasonCodeId = reasonCode.id; 13871 } 13872 13873 contentBody[restType] = { 13874 "state": state, 13875 "maxDialogLimit": params.maxDialogLimit, 13876 "interruptAction": params.interruptAction, 13877 "dialogLogoutAction": params.dialogLogoutAction, 13878 "reasonCodeId": reasonCodeId 13879 }; 13880 13881 handlers = params.handlers || {}; 13882 13883 this._makeRequest(contentBody, url, handlers); 13884 13885 return this; 13886 }, 13887 13888 /** 13889 * Getter for a MediaDialogs collection object that is associated with User on this Media. 13890 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 13891 * applicable when Object has not been previously created). 13892 * @returns {finesse.restservices.MediaDialogs} 13893 * A MediaDialogs collection object. 13894 */ 13895 getMediaDialogs: function (callbacks) { 13896 var options = callbacks || {}; 13897 options.parentObj = this._restObj; 13898 options.mediaObj = this; 13899 this.isLoaded(); 13900 13901 if (this._mdialogs === null) { 13902 this._mdialogs = new MediaDialogs(options); 13903 } 13904 13905 return this._mdialogs; 13906 }, 13907 13908 /** 13909 * Refresh the dialog collection associated with this media. 13910 */ 13911 refreshMediaDialogs: function() { 13912 if ( this._mdialogs ) { 13913 this._mdialogs.refresh(); 13914 } 13915 }, 13916 13917 /** 13918 * Set the maxDialogLimit, interruptAction, and dialogLogoutAction settings that the application will use for 13919 * this media. In the event of a failure, these options will be set on the new Finesse server. 13920 * 13921 * @param {Object} mediaOptions an object with the following properties:<ul> 13922 * <li><b>maxDialogLimit:</b> The id of the object being constructed</li> 13923 * <li><b>interruptAction:</b> Accept or ignore interrupts</li> 13924 * <li><b>dialogLogoutAction:</b> transfer or close the task at logout time</li></ul> 13925 */ 13926 setMediaOptions: function(mediaOptions) { 13927 if ( mediaOptions ) { 13928 this._mediaOptions = mediaOptions; 13929 if ( !this._optionsHelper ) { 13930 this._optionsHelper = new MediaOptionsHelper(this, this._mediaOptions); 13931 } 13932 } 13933 }, 13934 13935 /** 13936 * Refresh this media object and optionally refresh the list of media dialogs associated with this object. 13937 * 13938 * @param {Integer} retries the number of times to retry synchronizing this media object. 13939 */ 13940 refresh: function(retries) { 13941 retries = retries || 1; 13942 this._synchronize(retries); 13943 this.refreshMediaDialogs(); 13944 }, 13945 13946 /** 13947 * Is the user in work state on this Media. 13948 * @returns {boolean} returns true if the media is in work state; false otherwise 13949 */ 13950 isInWorkState: function() { 13951 return this.getState() === Media.States.WORK; 13952 }, 13953 13954 /** 13955 * Is the user in any state except LOGOUT on this Media. 13956 * @returns {boolean} returns true if the agent is in any state except LOGOUT in this media 13957 */ 13958 isLoggedIn: function() { 13959 var state = this.getState(); 13960 return state && state !== Media.States.LOGOUT; 13961 } 13962 }); 13963 13964 Media.States = /** @lends finesse.restservices.Media.States.prototype */ { 13965 /** 13966 * User Login on a non-voice Media. Note that while this is an action, is not technically a state, since a 13967 * logged-in User will always be in a specific state (READY, NOT_READY, etc.). 13968 */ 13969 LOGIN: "LOGIN", 13970 /** 13971 * User is logged out of this Media. 13972 */ 13973 LOGOUT: "LOGOUT", 13974 /** 13975 * User is not ready on this Media. 13976 */ 13977 NOT_READY: "NOT_READY", 13978 /** 13979 * User is ready for activity on this Media. 13980 */ 13981 READY: "READY", 13982 /** 13983 * User has a dialog coming in, but has not accepted it. 13984 */ 13985 RESERVED: "RESERVED", 13986 /** 13987 * The dialogs in this media have been interrupted by a dialog in a non-interruptible media. 13988 */ 13989 INTERRUPTED: "INTERRUPTED", 13990 /** 13991 * User enters this state when failing over from one Finesse to the other or when failing over from one 13992 * PG side to the other. 13993 */ 13994 WORK: "WORK", 13995 /** 13996 * @class Possible Media state values. 13997 * @constructs 13998 */ 13999 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 14000 14001 }; 14002 14003 window.finesse = window.finesse || {}; 14004 window.finesse.restservices = window.finesse.restservices || {}; 14005 window.finesse.restservices.Media = Media; 14006 14007 return Media; 14008 }); 14009 /** 14010 * JavaScript representation of the Finesse Media collection 14011 * object which contains a list of Media objects. 14012 * 14013 * @requires finesse.clientservices.ClientServices 14014 * @requires Class 14015 * @requires finesse.FinesseBase 14016 * @requires finesse.restservices.RestBase 14017 * @requires finesse.restservices.Media 14018 */ 14019 /** @private */ 14020 define('restservices/MediaList',[ 14021 'restservices/RestCollectionBase', 14022 'restservices/Media', 14023 'restservices/RestBase', 14024 'utilities/Utilities' 14025 ], 14026 function (RestCollectionBase, Media, RestBase, Utilities) { 14027 var MediaList = RestCollectionBase.extend(/** @lends finesse.restservices.MediaList.prototype */{ 14028 14029 /** 14030 * @class 14031 * JavaScript representation of a MediaList collection object. 14032 * @augments finesse.restservices.RestCollectionBase 14033 * @constructs 14034 * @see finesse.restservices.Media 14035 * @example 14036 * mediaList = _user.getMediaList( { 14037 * onCollectionAdd : _handleMediaAdd, 14038 * onCollectionDelete : _handleMediaDelete, 14039 * onLoad : _handleMediaListLoaded 14040 * }); 14041 * 14042 * _mediaCollection = mediaList.getCollection(); 14043 * for (var mediaId in _mediaCollection) { 14044 * if (_mediaCollection.hasOwnProperty(mediaId)) { 14045 * media = _mediaCollection[mediaId]; 14046 * etc... 14047 * } 14048 * } 14049 */ 14050 _fakeConstuctor: function () { 14051 /* This is here to hide the real init constructor from the public docs */ 14052 }, 14053 14054 /** 14055 * @private 14056 * @param {Object} options 14057 * An object with the following properties:<ul> 14058 * <li><b>id:</b> The id of the object being constructed</li> 14059 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 14060 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 14061 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 14062 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 14063 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 14064 * <li><b>status:</b> {Number} The HTTP status code returned</li> 14065 * <li><b>content:</b> {String} Raw string of response</li> 14066 * <li><b>object:</b> {Object} Parsed object of response</li> 14067 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 14068 * <li><b>errorType:</b> {String} Type of error that was caught</li> 14069 * <li><b>errorMessage:</b> {String} Message associated with error</li> 14070 * </ul></li> 14071 * </ul></li> 14072 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 14073 **/ 14074 init: function (options) { 14075 this._super(options); 14076 }, 14077 14078 /** 14079 * @private 14080 * Gets the REST class for the current object - this is the MediaList class. 14081 */ 14082 getRestClass: function () { 14083 return MediaList; 14084 }, 14085 14086 /** 14087 * @private 14088 * Gets the REST class for the objects that make up the collection. - this 14089 * is the Media class. 14090 */ 14091 getRestItemClass: function () { 14092 return Media; 14093 }, 14094 14095 /** 14096 * @private 14097 * Gets the REST type for the current object - this is a "MediaList". 14098 */ 14099 getRestType: function () { 14100 return "MediaList"; 14101 }, 14102 14103 /** 14104 * @private 14105 * Gets the REST type for the objects that make up the collection - this is "Media". 14106 */ 14107 getRestItemType: function () { 14108 return "Media"; 14109 }, 14110 14111 /** 14112 * @private 14113 * Override default to indicates that the collection doesn't support making 14114 * requests. 14115 */ 14116 supportsRequests: true, 14117 14118 /** 14119 * @private 14120 * Override default to indicates that the collection subscribes to its objects. 14121 */ 14122 supportsRestItemSubscriptions: true, 14123 14124 /** 14125 * The REST URL in which this object can be referenced. 14126 * @return {String} 14127 * The REST URI for this object. 14128 * @private 14129 */ 14130 getRestUrl: function () { 14131 var 14132 restObj = this._restObj, 14133 restUrl = ""; 14134 14135 //Prepend the base REST object if one was provided. 14136 if (restObj instanceof RestBase) { 14137 restUrl += restObj.getRestUrl(); 14138 } 14139 //Otherwise prepend with the default webapp name. 14140 else { 14141 restUrl += "/finesse/api"; 14142 } 14143 14144 //Append the REST type. (Media not MediaList) 14145 restUrl += "/" + this.getRestItemType(); 14146 14147 //Append ID if it is not undefined, null, or empty. 14148 if (this._id) { 14149 restUrl += "/" + this._id; 14150 } 14151 14152 return restUrl; 14153 }, 14154 14155 /** 14156 * Getter for a Media from the MediaList collection. 14157 * * @param {Object} options 14158 * An object with the following properties:<ul> 14159 * <li><b>id:</b> The id of the media to fetch</li> 14160 * <li><b>onLoad(this): (optional)</b> callback handler for when the object is successfully loaded from the server</li> 14161 * <li><b>onChange(this): (optional)</b> callback handler for when an update notification of the object is received</li> 14162 * <li><b>onAdd(this): (optional)</b> callback handler for when a notification that the object is created is received</li> 14163 * <li><b>onDelete(this): (optional)</b> callback handler for when a notification that the object is deleted is received</li> 14164 * <li><b>onError(rsp): (optional)</b> callback handler for if loading of the object fails, invoked with the error response object:<ul> 14165 * <li><b>status:</b> {Number} The HTTP status code returned</li> 14166 * <li><b>content:</b> {String} Raw string of response</li> 14167 * <li><b>object:</b> {Object} Parsed object of response</li> 14168 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 14169 * <li><b>errorType:</b> {String} Type of error that was caught</li> 14170 * <li><b>errorMessage:</b> {String} Message associated with error</li> 14171 * </ul></li> 14172 * </ul></li> 14173 * <li><b>mediaOptions:</b> {Object} An object with the following properties:<ul> 14174 * <li><b>maxDialogLimit:</b> The id of the object being constructed</li> 14175 * <li><b>interruptAction:</b> Accept or ignore interrupts</li> 14176 * <li><b>dialogLogoutAction:</b> transfer or close the task at logout time</li></ul> 14177 * </li></ul> 14178 * 14179 * @returns {finesse.restservices.Media} 14180 * A Media object. 14181 */ 14182 getMedia: function (options) { 14183 this.isLoaded(); 14184 options = options || {}; 14185 var objectId = options.id, 14186 media = this._collection[objectId]; 14187 14188 //throw error if media not found 14189 if(!media) { 14190 throw new Error("No media found with id: " + objectId); 14191 } 14192 14193 media.addHandler('load', options.onLoad); 14194 media.addHandler('change', options.onChange); 14195 media.addHandler('add', options.onAdd); 14196 media.addHandler('delete', options.onDelete); 14197 media.addHandler('error', options.onError); 14198 14199 media.setMediaOptions(options.mediaOptions); 14200 14201 return media; 14202 }, 14203 14204 /** 14205 * Utility method to build the internal collection data structure (object) based on provided data 14206 * @param {Object} data 14207 * The data to build the internal collection from 14208 * @private 14209 */ 14210 _buildCollection: function (data) { 14211 //overriding this from RestBaseCollection because we need to pass in parentObj 14212 //because restUrl is finesse/api/User/<useri>/Media/<mediaId> 14213 //other objects like dialog have finesse/api/Dialog/<dialogId> 14214 14215 var i, object, objectId, dataArray; 14216 if (data && this.getProperty(data, this.getRestItemType()) !== null) { 14217 dataArray = Utilities.getArray(this.getProperty(data, this.getRestItemType())); 14218 for (i = 0; i < dataArray.length; i += 1) { 14219 14220 object = {}; 14221 object[this.getRestItemType()] = dataArray[i]; 14222 14223 //get id from object.id instead of uri, since uri is not there for some reason 14224 objectId = object[this.getRestItemType()].id;//this._extractId(object); 14225 14226 //create the Media Object 14227 this._collection[objectId] = new (this.getRestItemClass())({ 14228 id: objectId, 14229 data: object, 14230 parentObj: this._restObj 14231 }); 14232 this.length += 1; 14233 } 14234 } 14235 } 14236 }); 14237 14238 window.finesse = window.finesse || {}; 14239 window.finesse.restservices = window.finesse.restservices || {}; 14240 window.finesse.restservices.MediaList = MediaList; 14241 14242 return MediaList; 14243 }); 14244 14245 /** 14246 * JavaScript representation of the Finesse ReasonCodes collection 14247 * object which contains a list of ReasonCodes objects. 14248 * 14249 * @requires Class 14250 */ 14251 14252 /** @private */ 14253 define('restservices/ReasonCodes',[], function () { 14254 14255 var ReasonCodes = Class.extend(/** @lends finesse.restservices.ReasonCodes.prototype */{ 14256 14257 /** 14258 * @class 14259 * JavaScript representation of a ReasonCodes collection object. 14260 * @augments Class 14261 * @constructs 14262 * @example 14263 * _user.getNotReadyReasonCodes({ 14264 * success: handleNotReadyReasonCodesSuccess, 14265 * error: handleNotReadyReasonCodesError 14266 * }); 14267 * 14268 * handleNotReadyReasonCodesSuccess = function(reasonCodes) { 14269 * for(var i = 0; i < reasonCodes.length; i++) { 14270 * var reasonCode = reasonCodes[i]; 14271 * var reasonCodeId = reasonCode.id; 14272 * var reasonCodeLabel = reasonCode.label; 14273 * } 14274 * } 14275 * } 14276 */ 14277 14278 _fakeConstuctor: function () { 14279 /* This is here to hide the real init constructor from the public docs */ 14280 } 14281 14282 }); 14283 14284 window.finesse = window.finesse || {}; 14285 window.finesse.restservices = window.finesse.restservices || {}; 14286 window.finesse.restservices.ReasonCodes = ReasonCodes; 14287 14288 return ReasonCodes; 14289 }); 14290 14291 /** 14292 * JavaScript representation of the Finesse User object 14293 * 14294 * @requires finesse.clientservices.ClientServices 14295 * @requires Class 14296 * @requires finesse.FinesseBase 14297 * @requires finesse.restservices.RestBase 14298 */ 14299 14300 /** @private */ 14301 define('restservices/User',[ 14302 'restservices/RestBase', 14303 'restservices/Dialogs', 14304 'restservices/ClientLog', 14305 'restservices/Queues', 14306 'restservices/WrapUpReasons', 14307 'restservices/PhoneBooks', 14308 'restservices/Workflows', 14309 'restservices/UserMediaPropertiesLayout', 14310 'restservices/UserMediaPropertiesLayouts', 14311 'restservices/Media', 14312 'restservices/MediaList', 14313 'restservices/ReasonCodes', 14314 'utilities/Utilities' 14315 ], 14316 function (RestBase, Dialogs, ClientLog, Queues, WrapUpReasons, PhoneBooks, Workflows, UserMediaPropertiesLayout, UserMediaPropertiesLayouts, Media, MediaList, ReasonCodes, Utilities) { 14317 14318 var User = RestBase.extend(/** @lends finesse.restservices.User.prototype */{ 14319 14320 _dialogs : null, 14321 _clientLogObj : null, 14322 _wrapUpReasons : null, 14323 _phoneBooks : null, 14324 _workflows : null, 14325 _mediaPropertiesLayout : null, 14326 _mediaPropertiesLayouts : null, 14327 _queues : null, 14328 media : null, 14329 mediaList : null, 14330 14331 /** 14332 * @class 14333 * The User represents a Finesse Agent or Supervisor. 14334 * 14335 * @param {Object} options 14336 * An object with the following properties:<ul> 14337 * <li><b>id:</b> The id of the object being constructed</li> 14338 * <li><b>onLoad(this): (optional)</b> callback handler for when the object is successfully loaded from the server</li> 14339 * <li><b>onChange(this): (optional)</b> callback handler for when an update notification of the object is received</li> 14340 * <li><b>onAdd(this): (optional)</b> callback handler for when a notification that the object is created is received</li> 14341 * <li><b>onDelete(this): (optional)</b> callback handler for when a notification that the object is deleted is received</li> 14342 * <li><b>onError(rsp): (optional)</b> callback handler for if loading of the object fails, invoked with the error response object:<ul> 14343 * <li><b>status:</b> {Number} The HTTP status code returned</li> 14344 * <li><b>content:</b> {String} Raw string of response</li> 14345 * <li><b>object:</b> {Object} Parsed object of response</li> 14346 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 14347 * <li><b>errorType:</b> {String} Type of error that was caught</li> 14348 * <li><b>errorMessage:</b> {String} Message associated with error</li> 14349 * </ul></li> 14350 * </ul></li> 14351 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 14352 * @augments finesse.restservices.RestBase 14353 * @constructs 14354 * @example 14355 * _user = new finesse.restservices.User({ 14356 * id: _id, 14357 * onLoad : _handleUserLoad, 14358 * onChange : _handleUserChange 14359 * }); 14360 **/ 14361 init: function (options) { 14362 this._super(options); 14363 }, 14364 14365 Callbacks: {}, 14366 14367 /** 14368 * @private 14369 * Gets the REST class for the current object - this is the User object. 14370 */ 14371 getRestClass: function () { 14372 return User; 14373 }, 14374 14375 /** 14376 * @private 14377 * Gets the REST type for the current object - this is a "User". 14378 */ 14379 getRestType: function () { 14380 return "User"; 14381 }, 14382 /** 14383 * @private 14384 * overloading this to return URI 14385 */ 14386 getXMPPNodePath: function () { 14387 return this.getRestUrl(); 14388 }, 14389 /** 14390 * @private 14391 * Returns whether this object supports subscriptions 14392 */ 14393 supportsSubscriptions: function () { 14394 return true; 14395 }, 14396 14397 /** 14398 * Getter for the firstName of this User. 14399 * @returns {String} 14400 * The firstName for this User 14401 */ 14402 getFirstName: function () { 14403 this.isLoaded(); 14404 return Utilities.convertNullToEmptyString(this.getData().firstName); 14405 }, 14406 14407 14408 /** 14409 * Getter for the reasonCode of this User. 14410 * @returns {Object} The reasonCode for the state of the User<br> 14411 * The contents may include the following:<ul> 14412 * <li>uri: The URI for the reason code object. 14413 * <li>id: The unique ID for the reason code. 14414 * <li>category: The category. Can be either NOT_READY or LOGOUT. 14415 * <li>code: The numeric reason code value. 14416 * <li>label: The label for the reason code. 14417 * <li>forAll: Boolean flag that denotes the global status for the reason code. 14418 * <li>systemCode: Boolean flag which denotes whether the reason code is system generated or custom one. 14419 * </ul> 14420 * 14421 */ 14422 getReasonCode : function() { 14423 this.isLoaded(); 14424 return this.getData().reasonCode; 14425 }, 14426 14427 /** 14428 * Getter for the pending state reasonCode of this User. 14429 * 14430 * @returns {Object} The reasonCode for the pending state of the User.<br> 14431 * The contents may include the following:<ul> 14432 * <li>uri: The URI for the reason code object. 14433 * <li>id: The unique ID for the reason code. 14434 * <li>category: The category. Can be either NOT_READY or LOGOUT. 14435 * <li>code: The numeric reason code value. 14436 * <li>label: The label for the reason code. 14437 * <li>forAll: Boolean flag that denotes the global status for the reason code. 14438 * <li>systemCode: Boolean flag which denotes whether the reason code is system generated or custom one. 14439 * </ul> 14440 */ 14441 getPendingStateReasonCode : function() { 14442 this.isLoaded(); 14443 return this.getData().pendingStateReasonCode; 14444 }, 14445 14446 14447 /** 14448 * Getter for the reasonCode of this User. 14449 * 14450 * @returns {Boolean} True if the reason code for the state of the user 14451 * is a system generated reason code. 14452 */ 14453 isReasonCodeReserved : function() { 14454 var resonCode = this.getReasonCode(); 14455 if (resonCode) { 14456 return resonCode.systemCode === "true" ? true 14457 : false; 14458 } 14459 return false; 14460 }, 14461 14462 14463 /** 14464 * Getter for the lastName of this User. 14465 * @returns {String} 14466 * The lastName for this User 14467 */ 14468 getLastName: function () { 14469 this.isLoaded(); 14470 return Utilities.convertNullToEmptyString(this.getData().lastName); 14471 }, 14472 14473 /** 14474 * Getter for the full name of this User. 14475 * The full name is of the format "FirstName LastName" (for example: "John Doe") 14476 * 14477 * @returns {String} 14478 * The full name of this User. 14479 */ 14480 getFullName : function() { 14481 this.isLoaded(); 14482 14483 /* 14484 * Currently, the expected format is "FirstName LastName", but can differ later 14485 * to something "LastName, FirstName". 14486 * To accommodate such, we use formatString utility method so that, if required, 14487 * the same can be achieved just by flipping the format place holders as follows: 14488 * "{1}, {0}" 14489 * Also, the function could be enhanced to take the format parameter. 14490 */ 14491 var fmt = "{0} {1}"; 14492 return Utilities.formatString(fmt, 14493 Utilities.convertNullToEmptyString(this.getData().firstName), 14494 Utilities.convertNullToEmptyString(this.getData().lastName)); 14495 }, 14496 14497 /** 14498 * Getter for the extension of this User. 14499 * @returns {String} 14500 * The extension, if any, of this User 14501 */ 14502 getExtension: function () { 14503 this.isLoaded(); 14504 return Utilities.convertNullToEmptyString(this.getData().extension); 14505 }, 14506 14507 /** 14508 * Getter for the id of the Team of this User 14509 * @returns {String} 14510 * The current (or last fetched) id of the Team of this User 14511 */ 14512 getTeamId: function () { 14513 this.isLoaded(); 14514 return this.getData().teamId; 14515 }, 14516 14517 /** 14518 * Getter for the name of the Team of this User 14519 * @returns {String} 14520 * The current (or last fetched) name of the Team of this User 14521 */ 14522 getTeamName: function () { 14523 this.isLoaded(); 14524 return this.getData().teamName; 14525 }, 14526 14527 /** 14528 * Is user an agent? 14529 * @returns {Boolean} True if user has role of agent, else false. 14530 */ 14531 hasAgentRole: function () { 14532 this.isLoaded(); 14533 return this.hasRole("Agent"); 14534 }, 14535 14536 /** 14537 * Is user a supervisor? 14538 * @returns {Boolean} True if user has role of supervisor, else false. 14539 */ 14540 hasSupervisorRole: function () { 14541 this.isLoaded(); 14542 return this.hasRole("Supervisor"); 14543 }, 14544 14545 /** 14546 * @private 14547 * Checks to see if user has "theRole" 14548 * @returns {Boolean} 14549 */ 14550 hasRole: function (theRole) { 14551 this.isLoaded(); 14552 var result = false, i, roles, len; 14553 14554 roles = this.getData().roles.role; 14555 len = roles.length; 14556 if (typeof roles === 'string') { 14557 if (roles === theRole) { 14558 result = true; 14559 } 14560 } else { 14561 for (i = 0; i < len ; i = i + 1) { 14562 if (roles[i] === theRole) { 14563 result = true; 14564 break; 14565 } 14566 } 14567 } 14568 14569 return result; 14570 }, 14571 14572 /** 14573 * Getter for the pending state of this User. 14574 * @returns {String} 14575 * The pending state of this User 14576 * @see finesse.restservices.User.States 14577 */ 14578 getPendingState: function () { 14579 this.isLoaded(); 14580 return Utilities.convertNullToEmptyString(this.getData().pendingState); 14581 }, 14582 14583 /** 14584 * Getter for the state of this User. 14585 * @returns {String} 14586 * The current (or last fetched) state of this User 14587 * @see finesse.restservices.User.States 14588 */ 14589 getState: function () { 14590 this.isLoaded(); 14591 return this.getData().state; 14592 }, 14593 14594 /** 14595 * Getter for the media state of this User. 14596 * @returns {String} 14597 * The current (or last fetched) media state of this User 14598 * Will be applicable only in CCX deployments 14599 * When the agent is talking on a manual outbound call, it returns busy 14600 * The value will not be present in other cases. 14601 */ 14602 getMediaState: function () { 14603 this.isLoaded(); 14604 /* There is an assertion that the value should not be undefined while setting the value in datastore. Hence setting it to null*/ 14605 if(this.getData().mediaState === undefined) { 14606 this.getData().mediaState = null; 14607 } 14608 14609 return this.getData().mediaState; 14610 }, 14611 14612 /** 14613 * Getter for the state change time of this User. 14614 * @returns {String} 14615 * The state change time of this User 14616 */ 14617 getStateChangeTime: function () { 14618 this.isLoaded(); 14619 return this.getData().stateChangeTime; 14620 }, 14621 14622 /** 14623 * Getter for the wrap-up mode of this User. 14624 * @see finesse.restservices.User.WrapUpMode 14625 * @returns {String} The wrap-up mode of this user that is a value of {@link finesse.restservices.User.WrapUpMode} 14626 */ 14627 getWrapUpOnIncoming: function () { 14628 this.isLoaded(); 14629 return this.getData().settings.wrapUpOnIncoming; 14630 }, 14631 14632 /** 14633 * Is User required to go into wrap-up? 14634 * @see finesse.restservices.User.WrapUpMode 14635 * @return {Boolean} 14636 * True if this agent is required to go into wrap-up. 14637 */ 14638 isWrapUpRequired: function () { 14639 return (this.getWrapUpOnIncoming() === User.WrapUpMode.REQUIRED || 14640 this.getWrapUpOnIncoming() === User.WrapUpMode.REQUIRED_WITH_WRAP_UP_DATA); 14641 }, 14642 14643 /** 14644 * Checks to see if the user is considered a mobile agent by checking for 14645 * the existence of the mobileAgent node. 14646 * @returns {Boolean} 14647 * True if this agent is a mobile agent. 14648 */ 14649 isMobileAgent: function () { 14650 this.isLoaded(); 14651 var ma = this.getData().mobileAgent; 14652 return ma !== null && typeof ma === "object"; 14653 }, 14654 14655 /** 14656 * Getter for the mobile agent work mode. 14657 * @returns {finesse.restservices.User.WorkMode} 14658 * If available, return the mobile agent work mode, otherwise null. 14659 */ 14660 getMobileAgentMode: function () { 14661 this.isLoaded(); 14662 if (this.isMobileAgent()) { 14663 return this.getData().mobileAgent.mode; 14664 } 14665 return null; 14666 }, 14667 14668 /** 14669 * Getter for the mobile agent dial number. 14670 * @returns {String} 14671 * If available, return the mobile agent dial number, otherwise null. 14672 */ 14673 getMobileAgentDialNumber: function () { 14674 this.isLoaded(); 14675 if (this.isMobileAgent()) { 14676 return this.getData().mobileAgent.dialNumber; 14677 } 14678 return null; 14679 }, 14680 14681 /** 14682 * Getter for a Dialogs collection object that is associated with User. 14683 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 14684 * applicable when Object has not been previously created). 14685 * @returns {finesse.restservices.Dialogs} 14686 * A Dialogs collection object. 14687 */ 14688 getDialogs: function (callbacks) { 14689 var options = callbacks || {}; 14690 options.parentObj = this; 14691 this.isLoaded(); 14692 14693 if (this._dialogs === null) { 14694 this._dialogs = new Dialogs(options); 14695 } 14696 14697 return this._dialogs; 14698 }, 14699 14700 /** 14701 * Getter for Media collection object that is associated with User. 14702 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 14703 * applicable when Object has not been previously created). 14704 * @returns {finesse.restservices.MediaList} 14705 * A Media Dialogs collection object. 14706 */ 14707 getMediaList: function (callbacks) { 14708 var options = callbacks || {}; 14709 options.parentObj = this; 14710 this.isLoaded(); 14711 14712 if (this.mediaList === null) { 14713 this.mediaList = new MediaList(options); 14714 } 14715 14716 return this.mediaList; 14717 }, 14718 14719 /** 14720 * @private 14721 * Getter for a ClientLog object that is associated with User. 14722 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 14723 * applicable when Object has not been previously created). 14724 * @returns {finesse.restservices.ClientLog} 14725 * A ClientLog collection object. 14726 */ 14727 getClientLog: function (callbacks) { 14728 var options = callbacks || {}; 14729 options.parentObj = this; 14730 this.isLoaded(); 14731 14732 if (this._clientLogObj === null) { 14733 this._clientLogObj = new ClientLog(options); 14734 } 14735 else { 14736 if(options.onLoad && typeof options.onLoad === "function") { 14737 options.onLoad(this._clientLogObj); 14738 } 14739 } 14740 return this._clientLogObj; 14741 }, 14742 14743 /** 14744 * Getter for a Queues collection object that is associated with User. 14745 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 14746 * applicable when Object has not been previously created). 14747 * @returns {finesse.restservices.Queues} 14748 * A Queues collection object. 14749 */ 14750 getQueues: function (callbacks) { 14751 var options = callbacks || {}; 14752 options.parentObj = this; 14753 this.isLoaded(); 14754 14755 if (this._queues === null) { 14756 this._queues = new Queues(options); 14757 } 14758 14759 return this._queues; 14760 }, 14761 14762 /** 14763 * Getter for a WrapUpReasons collection object that is associated with User. 14764 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 14765 * applicable when Object has not been previously created). 14766 * @returns {finesse.restservices.WrapUpReasons} 14767 * A WrapUpReasons collection object. 14768 */ 14769 getWrapUpReasons: function (callbacks) { 14770 var options = callbacks || {}; 14771 options.parentObj = this; 14772 this.isLoaded(); 14773 14774 if (this._wrapUpReasons === null) { 14775 this._wrapUpReasons = new WrapUpReasons(options); 14776 } 14777 14778 return this._wrapUpReasons; 14779 }, 14780 14781 /** 14782 * Getter for a PhoneBooks collection object that is associated with User. 14783 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 14784 * applicable when Object has not been previously created). 14785 * @returns {finesse.restservices.PhoneBooks} 14786 * A PhoneBooks collection object. 14787 */ 14788 getPhoneBooks: function (callbacks) { 14789 var options = callbacks || {}; 14790 options.parentObj = this; 14791 this.isLoaded(); 14792 14793 if (this._phoneBooks === null) { 14794 this._phoneBooks = new PhoneBooks(options); 14795 } 14796 14797 return this._phoneBooks; 14798 }, 14799 14800 /** 14801 * @private 14802 * Loads the Workflows collection object that is associated with User and 14803 * 'returns' them to the caller via the handlers. 14804 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 14805 * applicable when Object has not been previously created). 14806 * @see finesse.restservices.Workflow 14807 * @see finesse.restservices.Workflows 14808 * @see finesse.restservices.RestCollectionBase 14809 */ 14810 loadWorkflows: function (callbacks) { 14811 var options = callbacks || {}; 14812 options.parentObj = this; 14813 this.isLoaded(); 14814 14815 if (this._workflows === null) { 14816 this._workflows = new Workflows(options); 14817 } else { 14818 this._workflows.refresh(); 14819 } 14820 14821 }, 14822 14823 /** 14824 * Getter for a UserMediaPropertiesLayout object that is associated with User. 14825 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 14826 * applicable when Object has not been previously created). 14827 * @returns {finesse.restservices.UserMediaPropertiesLayout} 14828 * The UserMediaPropertiesLayout object associated with this user 14829 */ 14830 getMediaPropertiesLayout: function (callbacks) { 14831 var options = callbacks || {}; 14832 options.parentObj = this; 14833 options.id = this._id; 14834 14835 this.isLoaded(); 14836 if (this._mediaPropertiesLayout === null) { 14837 this._mediaPropertiesLayout = new UserMediaPropertiesLayout(options); 14838 } 14839 return this._mediaPropertiesLayout; 14840 }, 14841 14842 /** 14843 * Getter for a UserMediaPropertiesLayouts object that is associated with User. 14844 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 14845 * applicable when Object has not been previously created). 14846 * @returns {finesse.restservices.UserMediaPropertiesLayout} 14847 * The UserMediaPropertiesLayout object associated with this user 14848 */ 14849 getMediaPropertiesLayouts: function (callbacks) { 14850 var options = callbacks || {}; 14851 options.parentObj = this; 14852 14853 this.isLoaded(); 14854 if (this._mediaPropertiesLayouts === null) { 14855 this._mediaPropertiesLayouts = new UserMediaPropertiesLayouts(options); 14856 } 14857 return this._mediaPropertiesLayouts; 14858 }, 14859 14860 /** 14861 * Getter for the supervised Teams this User (Supervisor) supervises, if any. 14862 * @see finesse.restservices.Team 14863 * @returns {Array} 14864 * An array of Teams supervised by this User (Supervisor) 14865 */ 14866 getSupervisedTeams: function () { 14867 this.isLoaded(); 14868 14869 try { 14870 return Utilities.getArray(this.getData().teams.Team); 14871 } catch (e) { 14872 return []; 14873 } 14874 14875 }, 14876 14877 /** 14878 * Perform an agent login for this user, associating him with the 14879 * specified extension. 14880 * @param {Object} params 14881 * An object containing properties for agent login. 14882 * @param {String} params.extension 14883 * The extension to associate with this user 14884 * @param {Object} [params.mobileAgent] 14885 * A mobile agent object containing the mode and dial number properties. 14886 * @param {finesse.interfaces.RequestHandlers} params.handlers 14887 * @see finesse.interfaces.RequestHandlers 14888 * @returns {finesse.restservices.User} 14889 * This User object, to allow cascading 14890 * @private 14891 */ 14892 _login: function (params) { 14893 var handlers, contentBody = {}, 14894 restType = this.getRestType(); 14895 14896 // Protect against null dereferencing. 14897 params = params || {}; 14898 14899 contentBody[restType] = { 14900 "state": User.States.LOGIN, 14901 "extension": params.extension 14902 }; 14903 14904 // Create mobile agent node if available. 14905 if (typeof params.mobileAgent === "object") { 14906 contentBody[restType].mobileAgent = { 14907 "mode": params.mobileAgent.mode, 14908 "dialNumber": params.mobileAgent.dialNumber 14909 }; 14910 } 14911 14912 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 14913 handlers = params.handlers || {}; 14914 14915 this.restRequest(this.getRestUrl(), { 14916 method: 'PUT', 14917 success: handlers.success, 14918 error: handlers.error, 14919 content: contentBody 14920 }); 14921 14922 return this; // Allow cascading 14923 }, 14924 14925 /** 14926 * Perform an agent login for this user, associating him with the 14927 * specified extension. 14928 * @param {String} extension 14929 * The extension to associate with this user 14930 * @param {finesse.interfaces.RequestHandlers} handlers 14931 * An object containing the handlers for the request 14932 * @returns {finesse.restservices.User} 14933 * This User object, to allow cascading 14934 */ 14935 login: function (extension, handlers) { 14936 this.isLoaded(); 14937 var params = { 14938 "extension": extension, 14939 "handlers": handlers 14940 }; 14941 return this._login(params); 14942 }, 14943 14944 14945 14946 14947 /** 14948 * Perform an agent login for this user, associating him with the 14949 * specified extension. 14950 * @param {String} extension 14951 * The extension to associate with this user 14952 * @param {String} mode 14953 * The mobile agent work mode as defined in finesse.restservices.User.WorkMode. 14954 * @param {String} extension 14955 * The external dial number desired to be used by the mobile agent. 14956 * @param {finesse.interfaces.RequestHandlers} handlers 14957 * An object containing the handlers for the request 14958 * @returns {finesse.restservices.User} 14959 * This User object, to allow cascading 14960 */ 14961 loginMobileAgent: function (extension, mode, dialNumber, handlers) { 14962 this.isLoaded(); 14963 var params = { 14964 "extension": extension, 14965 "mobileAgent": { 14966 "mode": mode, 14967 "dialNumber": dialNumber 14968 }, 14969 "handlers": handlers 14970 }; 14971 return this._login(params); 14972 }, 14973 14974 14975 _updateMobileAgent: function (params) { 14976 var handlers, contentBody = {}, 14977 restType = this.getRestType(); 14978 14979 params = params || {}; 14980 14981 contentBody[restType] = { 14982 }; 14983 14984 if (typeof params.mobileAgent === "object") { 14985 contentBody[restType].mobileAgent = { 14986 "mode": params.mobileAgent.mode, 14987 "dialNumber": params.mobileAgent.dialNumber 14988 }; 14989 } 14990 14991 handlers = params.handlers || {}; 14992 14993 this.restRequest(this.getRestUrl(), { 14994 method: 'PUT', 14995 success: handlers.success, 14996 error: handlers.error, 14997 content: contentBody 14998 }); 14999 15000 return this; 15001 }, 15002 15003 /** 15004 * Update user object in Finesse with agent's mobile login information (Refer defect CSCvc35407) 15005 * @param {String} mode 15006 * The mobile agent work mode as defined in finesse.restservices.User.WorkMode. 15007 * @param {String} dialNumber 15008 * The external dial number desired to be used by the mobile agent. 15009 * @param {finesse.interfaces.RequestHandlers} handlers 15010 * An object containing the handlers for the request 15011 * @returns {finesse.restservices.User} 15012 * This User object, to allow cascading 15013 */ 15014 updateToMobileAgent: function (mode, dialNumber, handlers) { 15015 this.isLoaded(); 15016 var params = { 15017 "mobileAgent": { 15018 "mode": mode, 15019 "dialNumber": dialNumber 15020 }, 15021 "handlers": handlers 15022 }; 15023 return this._updateMobileAgent(params); 15024 }, 15025 15026 /** 15027 * Perform an agent logout for this user. 15028 * @param {String} reasonCode 15029 * The reason this user is logging out. Pass null for no reason. 15030 * @param {finesse.interfaces.RequestHandlers} handlers 15031 * An object containing the handlers for the request 15032 * @returns {finesse.restservices.User} 15033 * This User object, to allow cascading 15034 */ 15035 logout: function (reasonCode, handlers) { 15036 return this.setState("LOGOUT", reasonCode, handlers); 15037 }, 15038 15039 /** 15040 * Set the state of the user. 15041 * @param {String} newState 15042 * The state you are setting 15043 * @param {ReasonCode} reasonCode 15044 * The reason this user is logging out. Pass null for no reason. 15045 * @param {finesse.interfaces.RequestHandlers} handlers 15046 * An object containing the handlers for the request 15047 * @see finesse.restservices.User.States 15048 * @returns {finesse.restservices.User} 15049 * This User object, to allow cascading 15050 */ 15051 setState: function (newState, reasonCode, handlers) { 15052 this.isLoaded(); 15053 15054 var options, contentBody = {}; 15055 15056 if (!reasonCode) { 15057 if(newState === "LOGOUT"){ 15058 contentBody[this.getRestType()] = { 15059 "state": newState, 15060 "logoutAllMedia": "true" 15061 }; 15062 } 15063 else{ 15064 contentBody[this.getRestType()] = { 15065 "state": newState 15066 }; 15067 } 15068 15069 } else { 15070 if(newState === "LOGOUT"){ 15071 contentBody[this.getRestType()] = { 15072 "state": newState, 15073 "reasonCodeId": reasonCode.id, 15074 "logoutAllMedia": "true" 15075 }; 15076 } 15077 else{ 15078 contentBody[this.getRestType()] = { 15079 "state": newState, 15080 "reasonCodeId": reasonCode.id 15081 }; 15082 } 15083 15084 } 15085 15086 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 15087 handlers = handlers || {}; 15088 15089 options = { 15090 method: 'PUT', 15091 success: handlers.success, 15092 error: handlers.error, 15093 content: contentBody 15094 }; 15095 15096 // After removing the selective 202 handling, we should be able to just use restRequest 15097 this.restRequest(this.getRestUrl(), options); 15098 15099 return this; // Allow cascading 15100 }, 15101 15102 /** 15103 * Make call to a particular phone number. 15104 * 15105 * @param {String} 15106 * The number to call 15107 * @param {finesse.interfaces.RequestHandlers} handlers 15108 * An object containing the handlers for the request 15109 * @returns {finesse.restservices.User} 15110 * This User object, to allow cascading 15111 */ 15112 makeCall: function (number, handlers) { 15113 this.isLoaded(); 15114 15115 this.getDialogs().createNewCallDialog(number, this.getExtension(), handlers); 15116 15117 return this; // Allow cascading 15118 }, 15119 15120 /** 15121 * Make a silent monitor call to a particular agent's phone number. 15122 * 15123 * @param {String} 15124 * The number to call 15125 * @param {finesse.interfaces.RequestHandlers} handlers 15126 * An object containing the handlers for the request 15127 * @returns {finesse.restservices.User} 15128 * This User object, to allow cascading 15129 */ 15130 makeSMCall: function (number, handlers) { 15131 this.isLoaded(); 15132 15133 var actionType = "SILENT_MONITOR"; 15134 15135 this.getDialogs().createNewSuperviseCallDialog(number, this.getExtension(), actionType, handlers); 15136 15137 return this; // Allow cascading 15138 }, 15139 15140 15141 /** 15142 * Make a silent monitor call to a particular agent's phone number. 15143 * 15144 * @param {String} 15145 * The number to call 15146 * @param {String} dialogUri 15147 * The associated dialog uri of SUPERVISOR_MONITOR call 15148 * @param {finesse.interfaces.RequestHandlers} handlers 15149 * An object containing the handlers for the request 15150 * @see finesse.restservices.dialog 15151 * @returns {finesse.restservices.User} 15152 * This User object, to allow cascading 15153 */ 15154 makeBargeCall:function (number, dialogURI, handlers) { 15155 this.isLoaded(); 15156 var actionType = "BARGE_CALL"; 15157 this.getDialogs().createNewBargeCall( this.getExtension(), number, actionType, dialogURI,handlers); 15158 15159 return this; // Allow cascading 15160 }, 15161 15162 /** 15163 * Returns true if the user's current state will result in a pending state change. A pending state 15164 * change is a request to change state that does not result in an immediate state change. For 15165 * example if an agent attempts to change to the NOT_READY state while in the TALKING state, the 15166 * agent will not change state until the call ends. 15167 * 15168 * The current set of states that result in pending state changes is as follows: 15169 * TALKING 15170 * HOLD 15171 * RESERVED_OUTBOUND_PREVIEW 15172 * @returns {Boolean} True if there is a pending state change. 15173 * @see finesse.restservices.User.States 15174 */ 15175 isPendingStateChange: function () { 15176 var state = this.getState(); 15177 return state && ((state === User.States.TALKING) || (state === User.States.HOLD) || (state === User.States.RESERVED_OUTBOUND_PREVIEW)); 15178 }, 15179 15180 /** 15181 * Returns true if the user's current state is WORK or WORK_READY. This is used so 15182 * that a pending state is not cleared when moving into wrap up (work) mode. 15183 * Note that we don't add this as a pending state, since changes while in wrap up 15184 * occur immediately (and we don't want any "pending state" to flash on screen. 15185 * 15186 * @see finesse.restservices.User.States 15187 * @returns {Boolean} True if user is in wrap-up mode. 15188 */ 15189 isWrapUp: function () { 15190 var state = this.getState(); 15191 return state && ((state === User.States.WORK) || (state === User.States.WORK_READY)); 15192 }, 15193 15194 /** 15195 * @private 15196 * Parses a uriString to retrieve the id portion 15197 * @param {String} uriString 15198 * @return {String} id 15199 */ 15200 _parseIdFromUriString : function (uriString) { 15201 return Utilities.getId(uriString); 15202 }, 15203 15204 /** 15205 * Gets the user's Reason Code label. Works for both Not Ready and 15206 * Logout reason codes 15207 * 15208 * @return {String} the reason code label, or empty string if none 15209 */ 15210 getReasonCodeLabel : function() { 15211 this.isLoaded(); 15212 15213 if (this.getData().reasonCode) { 15214 return this.getData().reasonCode.label; 15215 } else { 15216 return ""; 15217 } 15218 }, 15219 15220 /** 15221 * Gets the user's Not Ready reason code. 15222 * 15223 * @return {String} Reason Code Id, or undefined if not set or 15224 * indeterminate 15225 */ 15226 getNotReadyReasonCodeId : function () { 15227 this.isLoaded(); 15228 15229 var reasoncodeIdResult, finesseServerReasonCodeId; 15230 finesseServerReasonCodeId = this.getData().reasonCodeId; 15231 15232 //FinesseServer will give "-l" => we will set to undefined (for convenience) 15233 if (finesseServerReasonCodeId !== "-1") { 15234 reasoncodeIdResult = finesseServerReasonCodeId; 15235 } 15236 15237 return reasoncodeIdResult; 15238 }, 15239 15240 /** 15241 * Performs a GET against the Finesse server looking up the reasonCodeId specified. 15242 * Note that there is no return value; use the success handler to process a 15243 * valid return. 15244 * @param {finesse.interfaces.RequestHandlers} handlers 15245 * An object containing the handlers for the request 15246 * @param {String} reasonCodeId The id for the reason code to lookup 15247 * 15248 */ 15249 getReasonCodeById : function (handlers, reasonCodeId) 15250 { 15251 var self = this, contentBody, reasonCode, url; 15252 contentBody = {}; 15253 15254 url = this.getRestUrl() + "/ReasonCode/" + reasonCodeId; 15255 this.restRequest(url, { 15256 method: 'GET', 15257 success: function (rsp) { 15258 reasonCode = { 15259 uri: rsp.object.ReasonCode.uri, 15260 label: rsp.object.ReasonCode.label, 15261 id: self._parseIdFromUriString(rsp.object.ReasonCode.uri) 15262 }; 15263 handlers.success(reasonCode); 15264 }, 15265 error: function (rsp) { 15266 handlers.error(rsp); 15267 }, 15268 content: contentBody 15269 }); 15270 }, 15271 15272 /** 15273 * Performs a GET against Finesse server retrieving all the specified type of reason codes. 15274 * @param {String} type (LOGOUT or NOT_READY) 15275 * @param {finesse.interfaces.RequestHandlers} handlers 15276 * An object containing the handlers for the request 15277 */ 15278 _getReasonCodesByType : function (type, handlers) 15279 { 15280 var self = this, contentBody = {}, url, reasonCodes, i, reasonCodeArray; 15281 15282 url = this.getRestUrl() + "/ReasonCodes?category=" + type; 15283 this.restRequest(url, { 15284 method: 'GET', 15285 success: function (rsp) { 15286 reasonCodes = []; 15287 15288 reasonCodeArray = rsp.object.ReasonCodes.ReasonCode; 15289 if (reasonCodeArray === undefined) { 15290 reasonCodes = undefined; 15291 } else if (reasonCodeArray[0] !== undefined) { 15292 for (i = 0; i < reasonCodeArray.length; i = i + 1) { 15293 reasonCodes[i] = { 15294 label: rsp.object.ReasonCodes.ReasonCode[i].label, 15295 id: self._parseIdFromUriString(rsp.object.ReasonCodes.ReasonCode[i].uri) 15296 }; 15297 } 15298 } else { 15299 reasonCodes[0] = { 15300 label: rsp.object.ReasonCodes.ReasonCode.label, 15301 id: self._parseIdFromUriString(rsp.object.ReasonCodes.ReasonCode.uri) 15302 }; 15303 } 15304 handlers.success(reasonCodes); 15305 }, 15306 error: function (rsp) { 15307 handlers.error(rsp); 15308 }, 15309 content: contentBody 15310 }); 15311 }, 15312 15313 /** 15314 * Performs a GET against the Finesse server and retrieves all of the Not Ready 15315 * reason codes. Note that there is no return value; use the success handler to 15316 * process a valid return. 15317 * 15318 * <pre class="code"> 15319 * _user.getSignoutReasonCodes({ 15320 * success: handleSignoutReasonCodesSuccess, 15321 * error: handleSignoutReasonCodesError 15322 * }); 15323 * </pre> 15324 * 15325 * @see finesse.restservices.ReasonCodes 15326 * 15327 * @param {finesse.interfaces.RequestHandlers} handlers 15328 * An object containing the handlers for the request 15329 */ 15330 getSignoutReasonCodes : function (handlers) 15331 { 15332 this._getReasonCodesByType("LOGOUT", handlers); 15333 }, 15334 15335 /** 15336 * Performs a GET against the Finesse server and retrieves all of the Not Ready 15337 * reason codes. Note that there is no return value; use the success handler to 15338 * process a valid return. 15339 * 15340 * <pre class="code"> 15341 * _user.getNotReadyReasonCodes({ 15342 * success: handleNotReadyReasonCodesSuccess, 15343 * error: handleNotReadyReasonCodesError 15344 * }); 15345 * </pre> 15346 * 15347 * @see finesse.restservices.ReasonCodes 15348 * 15349 * @param {finesse.interfaces.RequestHandlers} handlers 15350 * An object containing the handlers for the request 15351 */ 15352 getNotReadyReasonCodes : function (handlers) 15353 { 15354 this._getReasonCodesByType("NOT_READY", handlers); 15355 } 15356 }); 15357 User.MediaStates = /** @lends finesse.restservices.User.MediaStates.prototype */ { 15358 /** 15359 * Will be applicable only in CCX deployments 15360 * When the agent is talking on a manual outbound call 15361 */ 15362 BUSY: "BUSY", 15363 /** 15364 * @class Possible Agent Media States. 15365 * @constructs 15366 */ 15367 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 15368 15369 }; 15370 User.States = /** @lends finesse.restservices.User.States.prototype */ { 15371 /** 15372 * User Login. Note that while this is an action, is not technically a state, since a 15373 * logged-in User will always be in a specific state (READY, NOT_READY, TALKING, etc.). 15374 */ 15375 LOGIN: "LOGIN", 15376 /** 15377 * User is logged out. 15378 */ 15379 LOGOUT: "LOGOUT", 15380 /** 15381 * User is not ready. Note that in UCCX implementations, the user is in this state while on a non-routed call. 15382 */ 15383 NOT_READY: "NOT_READY", 15384 /** 15385 * User is ready for calls. 15386 */ 15387 READY: "READY", 15388 /** 15389 * User has a call coming in, but has not answered it. 15390 */ 15391 RESERVED: "RESERVED", 15392 /** 15393 * User has an outbound call being made, but has not been connected to it. 15394 */ 15395 RESERVED_OUTBOUND: "RESERVED_OUTBOUND", 15396 /** 15397 * User has an outbound call's preview information being displayed, but has not acted on it. 15398 */ 15399 RESERVED_OUTBOUND_PREVIEW: "RESERVED_OUTBOUND_PREVIEW", 15400 /** 15401 * User is on a call. Note that in UCCX implementations, this is for routed calls only. 15402 */ 15403 TALKING: "TALKING", 15404 /** 15405 * User is on hold. Note that in UCCX implementations, the user remains in TALKING state while on hold. 15406 */ 15407 HOLD: "HOLD", 15408 /** 15409 * User is wrap-up/work mode. This mode is typically configured to time out, after which the user becomes NOT_READY. 15410 */ 15411 WORK: "WORK", 15412 /** 15413 * This is the same as WORK, except that after time out user becomes READY. 15414 */ 15415 WORK_READY: "WORK_READY", 15416 /** 15417 * @class Possible User state values. 15418 * @constructs 15419 */ 15420 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 15421 15422 }; 15423 15424 User.WorkMode = { /** @lends finesse.restservices.User.WorkMode.prototype */ 15425 /** 15426 * Mobile agent is connected (dialed) for each incoming call received. 15427 */ 15428 CALL_BY_CALL: "CALL_BY_CALL", 15429 /** 15430 * Mobile agent is connected (dialed) at login. 15431 */ 15432 NAILED_CONNECTION: "NAILED_CONNECTION", 15433 /** 15434 * @class Possible Mobile Agent Work Mode Types. 15435 * @constructs 15436 */ 15437 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 15438 15439 }; 15440 15441 User.WrapUpMode = { /** @lends finesse.restservices.User.WrapUpMode.prototype */ 15442 /** 15443 * Agent must go into wrap-up when call ends 15444 */ 15445 REQUIRED: "REQUIRED", 15446 /** 15447 * Agent must go into wrap-up when call ends and must enter wrap-up data 15448 */ 15449 REQUIRED_WITH_WRAP_UP_DATA: "REQUIRED_WITH_WRAP_UP_DATA", 15450 /** 15451 * Agent can choose to go into wrap-up on a call-by-call basis when the call ends 15452 */ 15453 OPTIONAL: "OPTIONAL", 15454 /** 15455 * Agent is not allowed to go into wrap-up when call ends. 15456 */ 15457 NOT_ALLOWED: "NOT_ALLOWED", 15458 /** 15459 * @class Possible Wrap-up Mode Types. 15460 * @constructs 15461 */ 15462 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 15463 15464 }; 15465 15466 window.finesse = window.finesse || {}; 15467 window.finesse.restservices = window.finesse.restservices || {}; 15468 window.finesse.restservices.User = User; 15469 15470 return User; 15471 }); 15472 15473 /** 15474 * JavaScript representation of the Finesse Users collection 15475 * object which contains a list of Users objects. 15476 * 15477 * @requires finesse.clientservices.ClientServices 15478 * @requires Class 15479 * @requires finesse.FinesseBase 15480 * @requires finesse.restservices.RestBase 15481 * @requires finesse.restservices.RestCollectionBase 15482 * @requires finesse.restservices.User 15483 */ 15484 15485 /** @private */ 15486 define('restservices/Users',[ 15487 'restservices/RestCollectionBase', 15488 'restservices/RestBase', 15489 'restservices/User' 15490 ], 15491 function (RestCollectionBase, RestBase, User) { 15492 15493 var Users = RestCollectionBase.extend(/** @lends finesse.restservices.Users.prototype */{ 15494 15495 /** 15496 * @class 15497 * JavaScript representation of a Users collection object. 15498 * While there is no method provided to retrieve all Users, this collection is 15499 * used to return the Users in a supervised Team. 15500 * @augments finesse.restservices.RestCollectionBase 15501 * @constructs 15502 * @see finesse.restservices.Team 15503 * @see finesse.restservices.User 15504 * @see finesse.restservices.User#getSupervisedTeams 15505 * @example 15506 * // Note: The following method gets an Array of Teams, not a Collection. 15507 * _teams = _user.getSupervisedTeams(); 15508 * if (_teams.length > 0) { 15509 * _team0Users = _teams[0].getUsers(); 15510 * } 15511 */ 15512 _fakeConstuctor: function () { 15513 /* This is here to hide the real init constructor from the public docs */ 15514 }, 15515 15516 /** 15517 * @private 15518 * JavaScript representation of the Finesse Users collection 15519 * object which contains a list of Users objects. 15520 * 15521 * @param {Object} options 15522 * An object with the following properties:<ul> 15523 * <li><b>id:</b> The id of the object being constructed</li> 15524 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 15525 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 15526 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 15527 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 15528 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 15529 * <li><b>status:</b> {Number} The HTTP status code returned</li> 15530 * <li><b>content:</b> {String} Raw string of response</li> 15531 * <li><b>object:</b> {Object} Parsed object of response</li> 15532 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 15533 * <li><b>errorType:</b> {String} Type of error that was caught</li> 15534 * <li><b>errorMessage:</b> {String} Message associated with error</li> 15535 * </ul></li> 15536 * </ul></li> 15537 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 15538 **/ 15539 init: function (options) { 15540 this._super(options); 15541 }, 15542 15543 /** 15544 * @private 15545 * Gets the REST class for the current object - this is the Users class. 15546 */ 15547 getRestClass: function () { 15548 return Users; 15549 }, 15550 15551 /** 15552 * @private 15553 * Gets the REST class for the objects that make up the collection. - this 15554 * is the User class. 15555 */ 15556 getRestItemClass: function () { 15557 return User; 15558 }, 15559 15560 /** 15561 * @private 15562 * Gets the REST type for the current object - this is a "Users". 15563 */ 15564 getRestType: function () { 15565 return "Users"; 15566 }, 15567 15568 /** 15569 * @private 15570 * Gets the REST type for the objects that make up the collection - this is "User". 15571 */ 15572 getRestItemType: function () { 15573 return "User"; 15574 }, 15575 15576 /** 15577 * @private 15578 * Gets the node path for the current object - this is the team Users node 15579 * @returns {String} The node path 15580 */ 15581 getXMPPNodePath: function () { 15582 return this.getRestUrl(); 15583 }, 15584 15585 /** 15586 * @private 15587 * Overloading _doGET to reroute the GET to /Team/id from /Team/id/Users 15588 * This needs to be done because the GET /Team/id/Users API is missing 15589 * @returns {Users} This Users (collection) object to allow cascading 15590 */ 15591 _doGET: function (handlers) { 15592 var _this = this; 15593 handlers = handlers || {}; 15594 // Only do this for /Team/id/Users 15595 if (this._restObj && this._restObj.getRestType() === "Team") { 15596 this._restObj._doGET({ 15597 success: function (rspObj) { 15598 // Making sure the response was a valid Team 15599 if (_this._restObj._validate(rspObj.object)) { 15600 // Shimmying the response to look like a Users collection by extracting it from the Team response 15601 rspObj.object[_this.getRestType()] = rspObj.object[_this._restObj.getRestType()][_this.getRestType().toLowerCase()]; 15602 handlers.success(rspObj); 15603 } else { 15604 handlers.error(rspObj); 15605 } 15606 }, 15607 error: handlers.error 15608 }); 15609 return this; // Allow cascading 15610 } else { 15611 return this._super(handlers); 15612 } 15613 }, 15614 15615 /** 15616 * @private 15617 * Override default to indicates that the collection doesn't support making 15618 * requests. 15619 */ 15620 supportsRequests: false, 15621 15622 /** 15623 * @private 15624 * Indicates that this collection handles the subscription for its items 15625 */ 15626 handlesItemSubscription: true, 15627 15628 /** 15629 * @private 15630 * Override default to indicate that we need to subscribe explicitly 15631 */ 15632 explicitSubscription: true 15633 15634 }); 15635 15636 window.finesse = window.finesse || {}; 15637 window.finesse.restservices = window.finesse.restservices || {}; 15638 window.finesse.restservices.Users = Users; 15639 15640 return Users; 15641 }); 15642 15643 /** 15644 * JavaScript representation of the Finesse Team Not Ready Reason Code Assignment object. 15645 * 15646 * @requires finesse.clientservices.ClientServices 15647 * @requires Class 15648 * @requires finesse.FinesseBase 15649 * @requires finesse.restservices.RestBase 15650 */ 15651 15652 /** @private */ 15653 define('restservices/TeamNotReadyReasonCode',['restservices/RestBase'], function (RestBase) { 15654 15655 var TeamNotReadyReasonCode = RestBase.extend(/** @lends finesse.restservices.TeamNotReadyReasonCode.prototype */ { 15656 15657 /** 15658 * @class 15659 * JavaScript representation of a Team Not Ready ReasonCode object. Also exposes 15660 * methods to operate on the object against the server. 15661 * 15662 * @param {Object} options 15663 * An object with the following properties:<ul> 15664 * <li><b>id:</b> The id of the object being constructed</li> 15665 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 15666 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 15667 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 15668 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 15669 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 15670 * <li><b>status:</b> {Number} The HTTP status code returned</li> 15671 * <li><b>content:</b> {String} Raw string of response</li> 15672 * <li><b>object:</b> {Object} Parsed object of response</li> 15673 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 15674 * <li><b>errorType:</b> {String} Type of error that was caught</li> 15675 * <li><b>errorMessage:</b> {String} Message associated with error</li> 15676 * </ul></li> 15677 * </ul></li> 15678 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 15679 * @augments finesse.restservices.RestBase 15680 * @constructs 15681 **/ 15682 init: function (options) { 15683 this._super(options); 15684 }, 15685 15686 /** 15687 * @private 15688 * Gets the REST class for the current object - this is the TeamNotReadyReasonCode class. 15689 * @returns {Object} The TeamNotReadyReasonCode class. 15690 */ 15691 getRestClass: function () { 15692 return TeamNotReadyReasonCode; 15693 }, 15694 15695 /** 15696 * @private 15697 * Gets the REST type for the current object - this is a "ReasonCode". 15698 * @returns {String} The ReasonCode string. 15699 */ 15700 getRestType: function () { 15701 return "ReasonCode"; 15702 }, 15703 15704 /** 15705 * @private 15706 * Override default to indicate that this object doesn't support making 15707 * requests. 15708 */ 15709 supportsRequests: false, 15710 15711 /** 15712 * @private 15713 * Override default to indicate that this object doesn't support subscriptions. 15714 */ 15715 supportsSubscriptions: false, 15716 15717 /** 15718 * Getter for the category. 15719 * @returns {String} The category. 15720 */ 15721 getCategory: function () { 15722 this.isLoaded(); 15723 return this.getData().category; 15724 }, 15725 15726 /** 15727 * Getter for the code. 15728 * @returns {String} The code. 15729 */ 15730 getCode: function () { 15731 this.isLoaded(); 15732 return this.getData().code; 15733 }, 15734 15735 /** 15736 * Getter for the label. 15737 * @returns {String} The label. 15738 */ 15739 getLabel: function () { 15740 this.isLoaded(); 15741 return this.getData().label; 15742 }, 15743 15744 /** 15745 * Getter for the forAll value. 15746 * @returns {String} The forAll. 15747 */ 15748 getForAll: function () { 15749 this.isLoaded(); 15750 return this.getData().forAll; 15751 }, 15752 15753 /** 15754 * Getter for the Uri value. 15755 * @returns {String} The Uri. 15756 */ 15757 getUri: function () { 15758 this.isLoaded(); 15759 return this.getData().uri; 15760 }, 15761 15762 /** 15763 * Getter for the systemCode value. 15764 * @returns {String} The value for systemCode. 15765 * @since 11.6(1)-ES1 onwards 15766 */ 15767 getSystemCode: function () { 15768 this.isLoaded(); 15769 return this.getData().systemCode; 15770 } 15771 15772 }); 15773 15774 window.finesse = window.finesse || {}; 15775 window.finesse.restservices = window.finesse.restservices || {}; 15776 window.finesse.restservices.TeamNotReadyReasonCode = TeamNotReadyReasonCode; 15777 15778 return TeamNotReadyReasonCode; 15779 }); 15780 15781 /** 15782 * JavaScript representation of the Finesse TeamNotReadyReasonCodes collection 15783 * object which contains a list of TeamNotReadyReasonCode objects. 15784 * 15785 * @requires finesse.clientservices.ClientServices 15786 * @requires Class 15787 * @requires finesse.FinesseBase 15788 * @requires finesse.restservices.RestBase 15789 * @requires finesse.restservices.Dialog 15790 * @requires finesse.restservices.RestCollectionBase 15791 */ 15792 15793 /** @private */ 15794 define('restservices/TeamNotReadyReasonCodes',[ 15795 'restservices/RestCollectionBase', 15796 'restservices/RestBase', 15797 'restservices/TeamNotReadyReasonCode' 15798 ], 15799 function (RestCollectionBase, RestBase, TeamNotReadyReasonCode) { 15800 15801 var TeamNotReadyReasonCodes = RestCollectionBase.extend( { 15802 15803 /** 15804 * @class 15805 * JavaScript representation of a TeamNotReadyReasonCodes collection object. Also exposes 15806 * methods to operate on the object against the server. 15807 * 15808 * @param {Object} options 15809 * An object with the following properties:<ul> 15810 * <li><b>id:</b> The id of the object being constructed</li> 15811 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 15812 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 15813 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 15814 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 15815 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 15816 * <li><b>status:</b> {Number} The HTTP status code returned</li> 15817 * <li><b>content:</b> {String} Raw string of response</li> 15818 * <li><b>object:</b> {Object} Parsed object of response</li> 15819 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 15820 * <li><b>errorType:</b> {String} Type of error that was caught</li> 15821 * <li><b>errorMessage:</b> {String} Message associated with error</li> 15822 * </ul></li> 15823 * </ul></li> 15824 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 15825 * @augments finesse.restservices.RestCollectionBase 15826 * @constructs 15827 **/ 15828 init: function (options) { 15829 this._super(options); 15830 }, 15831 15832 /** 15833 * @private 15834 * Gets the REST class for the current object - this is the TeamNotReadyReasonCodes class. 15835 */ 15836 getRestClass: function () { 15837 return TeamNotReadyReasonCodes; 15838 }, 15839 15840 /** 15841 * @private 15842 * Gets the REST class for the objects that make up the collection. - this 15843 * is the TeamNotReadyReasonCode class. 15844 */ 15845 getRestItemClass: function () { 15846 return TeamNotReadyReasonCode; 15847 }, 15848 15849 /** 15850 * @private 15851 * Gets the REST type for the current object - this is a "ReasonCodes". 15852 */ 15853 getRestType: function () { 15854 return "ReasonCodes"; 15855 }, 15856 15857 /** 15858 * @private 15859 * Overrides the parent class. Returns the url for the NotReadyReasonCodes resource 15860 */ 15861 getRestUrl: function () { 15862 // return ("/finesse/api/" + this.getRestType() + "?category=NOT_READY"); 15863 var restObj = this._restObj, 15864 restUrl = ""; 15865 //Prepend the base REST object if one was provided. 15866 //Otherwise prepend with the default webapp name. 15867 if (restObj instanceof RestBase) { 15868 restUrl += restObj.getRestUrl(); 15869 } 15870 else { 15871 restUrl += "/finesse/api"; 15872 } 15873 //Append the REST type. 15874 restUrl += "/ReasonCodes?category=NOT_READY"; 15875 //Append ID if it is not undefined, null, or empty. 15876 if (this._id) { 15877 restUrl += "/" + this._id; 15878 } 15879 return restUrl; 15880 }, 15881 15882 /** 15883 * @private 15884 * Gets the REST type for the objects that make up the collection - this is "ReasonCode". 15885 */ 15886 getRestItemType: function () { 15887 return "ReasonCode"; 15888 }, 15889 15890 /** 15891 * @private 15892 * Override default to indicates that the collection supports making 15893 * requests. 15894 */ 15895 supportsRequests: true, 15896 15897 /** 15898 * @private 15899 * Override default to indicate that this object doesn't support subscriptions. 15900 */ 15901 supportsRestItemSubscriptions: false, 15902 15903 /** 15904 * @private 15905 * Retrieve the Not Ready Reason Codes. 15906 * 15907 * @returns {TeamNotReadyReasonCodes} 15908 * This TeamNotReadyReasonCodes object to allow cascading. 15909 */ 15910 get: function () { 15911 // set loaded to false so it will rebuild the collection after the get 15912 this._loaded = false; 15913 // reset collection 15914 this._collection = {}; 15915 // perform get 15916 this._synchronize(); 15917 return this; 15918 }, 15919 15920 /** 15921 * @private 15922 * Set up the PutSuccessHandler for TeamNotReadyReasonCodes 15923 * @param {Object} reasonCodes 15924 * @param {String} contentBody 15925 * @param successHandler 15926 * @return {function} 15927 */ 15928 createPutSuccessHandler: function (reasonCodes, contentBody, successHandler) { 15929 return function (rsp) { 15930 // Update internal structure based on response. Here we 15931 // inject the contentBody from the PUT request into the 15932 // rsp.object element to mimic a GET as a way to take 15933 // advantage of the existing _processResponse method. 15934 rsp.object = contentBody; 15935 reasonCodes._processResponse(rsp); 15936 15937 //Remove the injected contentBody object before cascading response 15938 rsp.object = {}; 15939 15940 //cascade response back to consumer's response handler 15941 successHandler(rsp); 15942 }; 15943 }, 15944 15945 /** 15946 * @private 15947 * Perform the REST API PUT call to update the reason code assignments for the team 15948 * @param {string[]} newValues 15949 * @param handlers 15950 */ 15951 update: function (newValues, handlers) { 15952 this.isLoaded(); 15953 var contentBody = {}, contentBodyInner = [], i, innerObject = {}; 15954 15955 contentBody[this.getRestType()] = { 15956 }; 15957 15958 for (i in newValues) { 15959 if (newValues.hasOwnProperty(i)) { 15960 innerObject = { 15961 "uri": newValues[i] 15962 }; 15963 contentBodyInner.push(innerObject); 15964 } 15965 } 15966 15967 contentBody[this.getRestType()] = { 15968 "ReasonCode" : contentBodyInner 15969 }; 15970 15971 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 15972 handlers = handlers || {}; 15973 15974 this.restRequest(this.getRestUrl(), { 15975 method: 'PUT', 15976 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 15977 error: handlers.error, 15978 content: contentBody 15979 }); 15980 15981 return this; // Allow cascading 15982 } 15983 }); 15984 15985 window.finesse = window.finesse || {}; 15986 window.finesse.restservices = window.finesse.restservices || {}; 15987 window.finesse.restservices.TeamNotReadyReasonCodes = TeamNotReadyReasonCodes; 15988 15989 return TeamNotReadyReasonCodes; 15990 }); 15991 15992 /** 15993 * JavaScript representation of the Finesse Team Wrap Up Reason object. 15994 * 15995 * @requires finesse.clientservices.ClientServices 15996 * @requires Class 15997 * @requires finesse.FinesseBase 15998 * @requires finesse.restservices.RestBase 15999 */ 16000 /** @private */ 16001 define('restservices/TeamWrapUpReason',['restservices/RestBase'], function (RestBase) { 16002 16003 var TeamWrapUpReason = RestBase.extend({ 16004 16005 /** 16006 * @class 16007 * JavaScript representation of a TeamWrapUpReason object. Also exposes 16008 * methods to operate on the object against the server. 16009 * 16010 * @param {Object} options 16011 * An object with the following properties:<ul> 16012 * <li><b>id:</b> The id of the object being constructed</li> 16013 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 16014 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 16015 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 16016 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 16017 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 16018 * <li><b>status:</b> {Number} The HTTP status code returned</li> 16019 * <li><b>content:</b> {String} Raw string of response</li> 16020 * <li><b>object:</b> {Object} Parsed object of response</li> 16021 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 16022 * <li><b>errorType:</b> {String} Type of error that was caught</li> 16023 * <li><b>errorMessage:</b> {String} Message associated with error</li> 16024 * </ul></li> 16025 * </ul></li> 16026 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 16027 * @constructs 16028 **/ 16029 init: function (options) { 16030 this._super(options); 16031 }, 16032 16033 /** 16034 * @private 16035 * Gets the REST class for the current object - this is the TeamWrapUpReason class. 16036 * @returns {Object} The TeamWrapUpReason class. 16037 */ 16038 getRestClass: function () { 16039 return TeamWrapUpReason; 16040 }, 16041 16042 /** 16043 * @private 16044 * Gets the REST type for the current object - this is a "WrapUpReason". 16045 * @returns {String} The WrapUpReason string. 16046 */ 16047 getRestType: function () { 16048 return "WrapUpReason"; 16049 }, 16050 16051 /** 16052 * @private 16053 * Override default to indicate that this object doesn't support making 16054 * requests. 16055 */ 16056 supportsRequests: false, 16057 16058 /** 16059 * @private 16060 * Override default to indicate that this object doesn't support subscriptions. 16061 */ 16062 supportsSubscriptions: false, 16063 16064 /** 16065 * Getter for the label. 16066 * @returns {String} The label. 16067 */ 16068 getLabel: function () { 16069 this.isLoaded(); 16070 return this.getData().label; 16071 }, 16072 16073 /** 16074 * @private 16075 * Getter for the forAll value. 16076 * @returns {Boolean} True if global 16077 */ 16078 getForAll: function () { 16079 this.isLoaded(); 16080 return this.getData().forAll; 16081 }, 16082 16083 /** 16084 * @private 16085 * Getter for the Uri value. 16086 * @returns {String} The Uri. 16087 */ 16088 getUri: function () { 16089 this.isLoaded(); 16090 return this.getData().uri; 16091 } 16092 }); 16093 16094 window.finesse = window.finesse || {}; 16095 window.finesse.restservices = window.finesse.restservices || {}; 16096 window.finesse.restservices.TeamWrapUpReason = TeamWrapUpReason; 16097 16098 return TeamWrapUpReason; 16099 }); 16100 16101 /** 16102 * JavaScript representation of the Finesse Team Wrap-Up Reasons collection 16103 * object which contains a list of Wrap-Up Reasons objects. 16104 * 16105 * @requires finesse.clientservices.ClientServices 16106 * @requires Class 16107 * @requires finesse.FinesseBase 16108 * @requires finesse.restservices.RestBase 16109 * @requires finesse.restservices.Dialog 16110 * @requires finesse.restservices.RestCollectionBase 16111 */ 16112 /** @private */ 16113 define('restservices/TeamWrapUpReasons',[ 16114 'restservices/RestCollectionBase', 16115 'restservices/RestBase', 16116 'restservices/TeamWrapUpReason' 16117 ], 16118 function (RestCollectionBase, RestBase, TeamWrapUpReason) { 16119 16120 var TeamWrapUpReasons = RestCollectionBase.extend({ 16121 16122 /** 16123 * @class 16124 * JavaScript representation of a TeamWrapUpReasons collection object. Also exposes 16125 * methods to operate on the object against the server. 16126 * 16127 * @param {Object} options 16128 * An object with the following properties:<ul> 16129 * <li><b>id:</b> The id of the object being constructed</li> 16130 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 16131 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 16132 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 16133 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 16134 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 16135 * <li><b>status:</b> {Number} The HTTP status code returned</li> 16136 * <li><b>content:</b> {String} Raw string of response</li> 16137 * <li><b>object:</b> {Object} Parsed object of response</li> 16138 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 16139 * <li><b>errorType:</b> {String} Type of error that was caught</li> 16140 * <li><b>errorMessage:</b> {String} Message associated with error</li> 16141 * </ul></li> 16142 * </ul></li> 16143 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 16144 * @constructs 16145 **/ 16146 init: function (options) { 16147 this._super(options); 16148 }, 16149 16150 /** 16151 * @private 16152 * Gets the REST class for the current object - this is the TeamWrapUpReasons class. 16153 */ 16154 getRestClass: function () { 16155 return TeamWrapUpReasons; 16156 }, 16157 16158 /** 16159 * @private 16160 * Gets the REST class for the objects that make up the collection. - this 16161 * is the TeamWrapUpReason class. 16162 */ 16163 getRestItemClass: function () { 16164 return TeamWrapUpReason; 16165 }, 16166 16167 /** 16168 * @private 16169 * Gets the REST type for the current object - this is a "WrapUpReasons". 16170 */ 16171 getRestType: function () { 16172 return "WrapUpReasons"; 16173 }, 16174 16175 /** 16176 * @private 16177 * Gets the REST type for the objects that make up the collection - this is "WrapUpReason". 16178 */ 16179 getRestItemType: function () { 16180 return "WrapUpReason"; 16181 }, 16182 16183 /** 16184 * @private 16185 * Override default to indicates that the collection supports making 16186 * requests. 16187 */ 16188 supportsRequests: true, 16189 16190 /** 16191 * @private 16192 * Override default to indicate that this object doesn't support subscriptions. 16193 */ 16194 supportsRestItemSubscriptions: false, 16195 16196 /** 16197 * Retrieve the Team Wrap Up Reasons. 16198 * 16199 * @returns {finesse.restservices.TeamWrapUpReasons} 16200 * This TeamWrapUpReasons object to allow cascading. 16201 */ 16202 get: function () { 16203 // set loaded to false so it will rebuild the collection after the get 16204 this._loaded = false; 16205 // reset collection 16206 this._collection = {}; 16207 // perform get 16208 this._synchronize(); 16209 return this; 16210 }, 16211 16212 /** 16213 * Set up the PutSuccessHandler for TeamWrapUpReasons 16214 * @param {Object} wrapUpReasons 16215 * @param {Object} contentBody 16216 * @param successHandler 16217 * @returns response 16218 */ 16219 createPutSuccessHandler: function (wrapUpReasons, contentBody, successHandler) { 16220 return function (rsp) { 16221 // Update internal structure based on response. Here we 16222 // inject the contentBody from the PUT request into the 16223 // rsp.object element to mimic a GET as a way to take 16224 // advantage of the existing _processResponse method. 16225 rsp.object = contentBody; 16226 16227 wrapUpReasons._processResponse(rsp); 16228 16229 //Remove the injected contentBody object before cascading response 16230 rsp.object = {}; 16231 16232 //cascade response back to consumer's response handler 16233 successHandler(rsp); 16234 }; 16235 }, 16236 16237 /** 16238 * Perform the REST API PUT call to update the reason code assignments for the team 16239 * @param {String Array} newValues 16240 * @param handlers 16241 * @returns {Object} this 16242 */ 16243 update: function (newValues, handlers) { 16244 this.isLoaded(); 16245 var contentBody = {}, contentBodyInner = [], i, innerObject = {}; 16246 16247 contentBody[this.getRestType()] = { 16248 }; 16249 16250 for (i in newValues) { 16251 if (newValues.hasOwnProperty(i)) { 16252 innerObject = { 16253 "uri": newValues[i] 16254 }; 16255 contentBodyInner.push(innerObject); 16256 } 16257 } 16258 16259 contentBody[this.getRestType()] = { 16260 "WrapUpReason" : contentBodyInner 16261 }; 16262 16263 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 16264 handlers = handlers || {}; 16265 16266 this.restRequest(this.getRestUrl(), { 16267 method: 'PUT', 16268 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 16269 error: handlers.error, 16270 content: contentBody 16271 }); 16272 16273 return this; // Allow cascading 16274 } 16275 }); 16276 16277 window.finesse = window.finesse || {}; 16278 window.finesse.restservices = window.finesse.restservices || {}; 16279 window.finesse.restservices.TeamWrapUpReasons = TeamWrapUpReasons; 16280 16281 return TeamWrapUpReasons; 16282 }); 16283 16284 /** 16285 * JavaScript representation of a TeamSignOutReasonCode. 16286 * 16287 * @requires finesse.clientservices.ClientServices 16288 * @requires Class 16289 * @requires finesse.FinesseBase 16290 * @requires finesse.restservices.RestBase 16291 */ 16292 16293 /** @private */ 16294 define('restservices/TeamSignOutReasonCode',['restservices/RestBase'], function (RestBase) { 16295 var TeamSignOutReasonCode = RestBase.extend(/** @lends finesse.restservices.TeamSignOutReasonCode.prototype */ { 16296 16297 /** 16298 * @class 16299 * JavaScript representation of a TeamSignOutReasonCode object. Also exposes 16300 * methods to operate on the object against the server. 16301 * 16302 * @param {Object} options 16303 * An object with the following properties:<ul> 16304 * <li><b>id:</b> The id of the object being constructed</li> 16305 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 16306 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 16307 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 16308 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 16309 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 16310 * <li><b>status:</b> {Number} The HTTP status code returned</li> 16311 * <li><b>content:</b> {String} Raw string of response</li> 16312 * <li><b>object:</b> {Object} Parsed object of response</li> 16313 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 16314 * <li><b>errorType:</b> {String} Type of error that was caught</li> 16315 * <li><b>errorMessage:</b> {String} Message associated with error</li> 16316 * </ul></li> 16317 * </ul></li> 16318 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 16319 * @augments finesse.restservices.RestBase 16320 * @constructs 16321 **/ 16322 init: function (options) { 16323 this._super(options); 16324 }, 16325 16326 /** 16327 * @private 16328 * Gets the REST class for the current object - this is the TeamSignOutReasonCode class. 16329 * @returns {Object} The TeamSignOutReasonCode class. 16330 */ 16331 getRestClass: function () { 16332 return TeamSignOutReasonCode; 16333 }, 16334 16335 /** 16336 * @private 16337 * Gets the REST type for the current object - this is a "ReasonCode". 16338 * @returns {String} The ReasonCode string. 16339 */ 16340 getRestType: function () { 16341 return "ReasonCode"; 16342 }, 16343 16344 /** 16345 * @private 16346 * Override default to indicate that this object doesn't support making 16347 * requests. 16348 */ 16349 supportsRequests: false, 16350 16351 /** 16352 * @private 16353 * Override default to indicate that this object doesn't support subscriptions. 16354 */ 16355 supportsSubscriptions: false, 16356 16357 /** 16358 * Getter for the category. 16359 * @returns {String} The category. 16360 */ 16361 getCategory: function () { 16362 this.isLoaded(); 16363 return this.getData().category; 16364 }, 16365 16366 /** 16367 * Getter for the code. 16368 * @returns {String} The code. 16369 */ 16370 getCode: function () { 16371 this.isLoaded(); 16372 return this.getData().code; 16373 }, 16374 16375 /** 16376 * Getter for the label. 16377 * @returns {String} The label. 16378 */ 16379 getLabel: function () { 16380 this.isLoaded(); 16381 return this.getData().label; 16382 }, 16383 16384 /** 16385 * Getter for the forAll value. 16386 * @returns {String} The forAll. 16387 */ 16388 getForAll: function () { 16389 this.isLoaded(); 16390 return this.getData().forAll; 16391 }, 16392 16393 /** 16394 * Getter for the Uri value. 16395 * @returns {String} The Uri. 16396 */ 16397 getUri: function () { 16398 this.isLoaded(); 16399 return this.getData().uri; 16400 }, 16401 16402 /** 16403 * Getter for the systemCode value. 16404 * @returns {String} The value for systemCode. 16405 * @since 11.6(1)-ES1 onwards 16406 */ 16407 getSystemCode: function () { 16408 this.isLoaded(); 16409 return this.getData().systemCode; 16410 } 16411 16412 }); 16413 16414 window.finesse = window.finesse || {}; 16415 window.finesse.restservices = window.finesse.restservices || {}; 16416 window.finesse.restservices.TeamSignOutReasonCode = TeamSignOutReasonCode; 16417 16418 return TeamSignOutReasonCode; 16419 }); 16420 16421 /** 16422 * JavaScript representation of the TeamSignOutReasonCodes collection 16423 * object which contains a list of TeamSignOutReasonCode objects. 16424 * 16425 * @requires finesse.clientservices.ClientServices 16426 * @requires Class 16427 * @requires finesse.FinesseBase 16428 * @requires finesse.restservices.RestBase 16429 * @requires finesse.restservices.Dialog 16430 * @requires finesse.restservices.RestCollectionBase 16431 */ 16432 16433 /** @private */ 16434 define('restservices/TeamSignOutReasonCodes',[ 16435 'restservices/RestCollectionBase', 16436 'restservices/RestBase', 16437 'restservices/TeamSignOutReasonCode' 16438 ], 16439 function (RestCollectionBase, RestBase, TeamSignOutReasonCode) { 16440 16441 var TeamSignOutReasonCodes = RestCollectionBase.extend({ 16442 /** 16443 * @class 16444 * JavaScript representation of a TeamSignOutReasonCodes collection object. Also exposes 16445 * methods to operate on the object against the server. 16446 * 16447 * @param {Object} options 16448 * An object with the following properties:<ul> 16449 * <li><b>id:</b> The id of the object being constructed</li> 16450 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 16451 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 16452 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 16453 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 16454 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 16455 * <li><b>status:</b> {Number} The HTTP status code returned</li> 16456 * <li><b>content:</b> {String} Raw string of response</li> 16457 * <li><b>object:</b> {Object} Parsed object of response</li> 16458 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 16459 * <li><b>errorType:</b> {String} Type of error that was caught</li> 16460 * <li><b>errorMessage:</b> {String} Message associated with error</li> 16461 * </ul></li> 16462 * </ul></li> 16463 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 16464 * @constructs 16465 **/ 16466 init: function (options) { 16467 this._super(options); 16468 }, 16469 16470 /** 16471 * @private 16472 * Gets the REST class for the current object - this is the TeamSignOutReasonCodes class. 16473 */ 16474 getRestClass: function () { 16475 return TeamSignOutReasonCodes; 16476 }, 16477 16478 /** 16479 * @private 16480 * Gets the REST class for the objects that make up the collection. - this 16481 * is the TeamSignOutReasonCode class. 16482 */ 16483 getRestItemClass: function () { 16484 return TeamSignOutReasonCode; 16485 }, 16486 16487 /** 16488 * @private 16489 * Gets the REST type for the current object - this is a "ReasonCodes". 16490 */ 16491 getRestType: function () { 16492 return "ReasonCodes"; 16493 }, 16494 16495 /** 16496 * Overrides the parent class. Returns the url for the SignOutReasonCodes resource 16497 */ 16498 getRestUrl: function () { 16499 var restObj = this._restObj, restUrl = ""; 16500 16501 //Prepend the base REST object if one was provided. 16502 //Otherwise prepend with the default webapp name. 16503 if (restObj instanceof RestBase) { 16504 restUrl += restObj.getRestUrl(); 16505 } else { 16506 restUrl += "/finesse/api"; 16507 } 16508 //Append the REST type. 16509 restUrl += "/ReasonCodes?category=LOGOUT"; 16510 //Append ID if it is not undefined, null, or empty. 16511 if (this._id) { 16512 restUrl += "/" + this._id; 16513 } 16514 return restUrl; 16515 }, 16516 16517 /** 16518 * @private 16519 * Gets the REST type for the objects that make up the collection - this is "ReasonCode". 16520 */ 16521 getRestItemType: function () { 16522 return "ReasonCode"; 16523 }, 16524 16525 /** 16526 * @private 16527 * Override default to indicates that the collection supports making requests. 16528 */ 16529 supportsRequests: true, 16530 16531 /** 16532 * @private 16533 * Override default to indicates that the collection does not subscribe to its objects. 16534 */ 16535 supportsRestItemSubscriptions: false, 16536 16537 /** 16538 * Retrieve the Sign Out Reason Codes. 16539 * 16540 * @returns {finesse.restservices.TeamSignOutReasonCodes} 16541 * This TeamSignOutReasonCodes object to allow cascading. 16542 */ 16543 get: function () { 16544 // set loaded to false so it will rebuild the collection after the get 16545 this._loaded = false; 16546 // reset collection 16547 this._collection = {}; 16548 // perform get 16549 this._synchronize(); 16550 return this; 16551 }, 16552 16553 /* We only use PUT and GET on Reason Code team assignments 16554 * @param {Object} contact 16555 * @param {Object} contentBody 16556 * @param {Function} successHandler 16557 */ 16558 createPutSuccessHandler: function (contact, contentBody, successHandler) { 16559 return function (rsp) { 16560 // Update internal structure based on response. Here we 16561 // inject the contentBody from the PUT request into the 16562 // rsp.object element to mimic a GET as a way to take 16563 // advantage of the existing _processResponse method. 16564 rsp.object = contentBody; 16565 contact._processResponse(rsp); 16566 16567 //Remove the injected contentBody object before cascading response 16568 rsp.object = {}; 16569 16570 //cascade response back to consumer's response handler 16571 successHandler(rsp); 16572 }; 16573 }, 16574 16575 /** 16576 * Update - This should be all that is needed. 16577 * @param {Object} newValues 16578 * @param {Object} handlers 16579 * @returns {finesse.restservices.TeamSignOutReasonCodes} 16580 * This TeamSignOutReasonCodes object to allow cascading. 16581 */ 16582 update: function (newValues, handlers) { 16583 this.isLoaded(); 16584 var contentBody = {}, contentBodyInner = [], i, innerObject = {}; 16585 16586 contentBody[this.getRestType()] = { 16587 }; 16588 16589 for (i in newValues) { 16590 if (newValues.hasOwnProperty(i)) { 16591 innerObject = { 16592 "uri": newValues[i] 16593 }; 16594 contentBodyInner.push(innerObject); 16595 } 16596 } 16597 16598 contentBody[this.getRestType()] = { 16599 "ReasonCode" : contentBodyInner 16600 }; 16601 16602 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 16603 handlers = handlers || {}; 16604 16605 this.restRequest(this.getRestUrl(), { 16606 method: 'PUT', 16607 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 16608 error: handlers.error, 16609 content: contentBody 16610 }); 16611 16612 return this; // Allow cascading 16613 } 16614 16615 }); 16616 16617 window.finesse = window.finesse || {}; 16618 window.finesse.restservices = window.finesse.restservices || {}; 16619 window.finesse.restservices.TeamSignOutReasonCodes = TeamSignOutReasonCodes; 16620 16621 return TeamSignOutReasonCodes; 16622 }); 16623 16624 /** 16625 * JavaScript representation of the Finesse PhoneBook Assignment object. 16626 * 16627 * @requires finesse.clientservices.ClientServices 16628 * @requires Class 16629 * @requires finesse.FinesseBase 16630 * @requires finesse.restservices.RestBase 16631 */ 16632 16633 /** 16634 * The following comment prevents JSLint errors concerning undefined global variables. 16635 * It tells JSLint that these identifiers are defined elsewhere. 16636 */ 16637 /*jslint bitwise:true, browser:true, nomen:true, regexp:true, sloppy:true, white:true */ 16638 16639 /** The following comment is to prevent jslint errors about 16640 * using variables before they are defined. 16641 */ 16642 /*global $, jQuery, Handlebars, dojox, dojo, finesse */ 16643 16644 /** @private */ 16645 define('restservices/TeamPhoneBook',['restservices/RestBase'], function (RestBase) { 16646 var TeamPhoneBook = RestBase.extend({ 16647 16648 /** 16649 * @class 16650 * JavaScript representation of a PhoneBook object. Also exposes 16651 * methods to operate on the object against the server. 16652 * 16653 * @param {Object} options 16654 * An object with the following properties:<ul> 16655 * <li><b>id:</b> The id of the object being constructed</li> 16656 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 16657 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 16658 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 16659 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 16660 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 16661 * <li><b>status:</b> {Number} The HTTP status code returned</li> 16662 * <li><b>content:</b> {String} Raw string of response</li> 16663 * <li><b>object:</b> {Object} Parsed object of response</li> 16664 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 16665 * <li><b>errorType:</b> {String} Type of error that was caught</li> 16666 * <li><b>errorMessage:</b> {String} Message associated with error</li> 16667 * </ul></li> 16668 * </ul></li> 16669 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 16670 * @constructs 16671 **/ 16672 init: function (options) { 16673 this._super(options); 16674 }, 16675 16676 /** 16677 * @private 16678 * Gets the REST class for the current object - this is the PhoneBooks class. 16679 * @returns {Object} The PhoneBooks class. 16680 */ 16681 getRestClass: function () { 16682 return TeamPhoneBook; 16683 }, 16684 16685 /** 16686 * @private 16687 * Gets the REST type for the current object - this is a "PhoneBook". 16688 * @returns {String} The PhoneBook string. 16689 */ 16690 getRestType: function () { 16691 return "PhoneBook"; 16692 }, 16693 16694 /** 16695 * @private 16696 * Override default to indicate that this object doesn't support making 16697 * requests. 16698 */ 16699 supportsRequests: false, 16700 16701 /** 16702 * @private 16703 * Override default to indicate that this object doesn't support subscriptions. 16704 */ 16705 supportsSubscriptions: false, 16706 16707 /** 16708 * Getter for the name. 16709 * @returns {String} The name. 16710 */ 16711 getName: function () { 16712 this.isLoaded(); 16713 return this.getData().name; 16714 }, 16715 16716 /** 16717 * Getter for the Uri value. 16718 * @returns {String} The Uri. 16719 */ 16720 getUri: function () { 16721 this.isLoaded(); 16722 return this.getData().uri; 16723 } 16724 16725 }); 16726 16727 window.finesse = window.finesse || {}; 16728 window.finesse.restservices = window.finesse.restservices || {}; 16729 window.finesse.restservices.TeamPhoneBook = TeamPhoneBook; 16730 16731 return TeamPhoneBook; 16732 }); 16733 16734 /** 16735 * JavaScript representation of the Finesse PhoneBook Assignments collection 16736 * object which contains a list of Not Ready Reason Codes objects. 16737 * 16738 * @requires finesse.clientservices.ClientServices 16739 * @requires Class 16740 * @requires finesse.FinesseBase 16741 * @requires finesse.restservices.RestBase 16742 * @requires finesse.restservices.Dialog 16743 * @requires finesse.restservices.RestCollectionBase 16744 */ 16745 16746 /** 16747 * The following comment prevents JSLint errors concerning undefined global variables. 16748 * It tells JSLint that these identifiers are defined elsewhere. 16749 */ 16750 /*jslint bitwise:true, browser:true, nomen:true, regexp:true, sloppy:true, white:true */ 16751 16752 /** The following comment is to prevent jslint errors about 16753 * using variables before they are defined. 16754 */ 16755 /*global $, jQuery, Handlebars, dojox, dojo, finesse */ 16756 16757 /** @private */ 16758 define('restservices/TeamPhoneBooks',[ 16759 'restservices/RestCollectionBase', 16760 'restservices/RestBase', 16761 'restservices/TeamPhoneBook' 16762 ], 16763 function (RestCollectionBase, RestBase, TeamPhoneBook) { 16764 var TeamPhoneBooks = RestCollectionBase.extend({ 16765 16766 /** 16767 * @class 16768 * JavaScript representation of a TeamPhoneBooks collection object. Also exposes 16769 * methods to operate on the object against the server. 16770 * 16771 * @param {Object} options 16772 * An object with the following properties:<ul> 16773 * <li><b>id:</b> The id of the object being constructed</li> 16774 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 16775 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 16776 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 16777 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 16778 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 16779 * <li><b>status:</b> {Number} The HTTP status code returned</li> 16780 * <li><b>content:</b> {String} Raw string of response</li> 16781 * <li><b>object:</b> {Object} Parsed object of response</li> 16782 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 16783 * <li><b>errorType:</b> {String} Type of error that was caught</li> 16784 * <li><b>errorMessage:</b> {String} Message associated with error</li> 16785 * </ul></li> 16786 * </ul></li> 16787 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 16788 * @constructs 16789 **/ 16790 init: function (options) { 16791 this._super(options); 16792 }, 16793 16794 /** 16795 * @private 16796 * Gets the REST class for the current object - this is the TeamPhoneBooks class. 16797 */ 16798 getRestClass: function () { 16799 return TeamPhoneBooks; 16800 }, 16801 16802 /** 16803 * @private 16804 * Gets the REST class for the objects that make up the collection. - this 16805 * is the TeamPhoneBooks class. 16806 */ 16807 getRestItemClass: function () { 16808 return TeamPhoneBook; 16809 }, 16810 16811 /** 16812 * @private 16813 * Gets the REST type for the current object - this is a "ReasonCodes". 16814 */ 16815 getRestType: function () { 16816 return "PhoneBooks"; 16817 }, 16818 16819 /** 16820 * Overrides the parent class. Returns the url for the PhoneBooks resource 16821 */ 16822 getRestUrl: function () { 16823 // return ("/finesse/api/" + this.getRestType() + "?category=NOT_READY"); 16824 var restObj = this._restObj, 16825 restUrl = ""; 16826 //Prepend the base REST object if one was provided. 16827 if (restObj instanceof RestBase) { 16828 restUrl += restObj.getRestUrl(); 16829 } 16830 //Otherwise prepend with the default webapp name. 16831 else { 16832 restUrl += "/finesse/api"; 16833 } 16834 //Append the REST type. 16835 restUrl += "/PhoneBooks"; 16836 //Append ID if it is not undefined, null, or empty. 16837 if (this._id) { 16838 restUrl += "/" + this._id; 16839 } 16840 return restUrl; 16841 }, 16842 16843 /** 16844 * @private 16845 * Gets the REST type for the objects that make up the collection - this is "ReasonCode". 16846 */ 16847 getRestItemType: function () { 16848 return "PhoneBook"; 16849 }, 16850 16851 /** 16852 * @private 16853 * Override default to indicates that the collection supports making 16854 * requests. 16855 */ 16856 supportsRequests: true, 16857 16858 /** 16859 * @private 16860 * Override default to indicates that the collection subscribes to its objects. 16861 */ 16862 supportsRestItemSubscriptions: false, 16863 16864 /** 16865 * Retrieve the Not Ready Reason Codes. 16866 * 16867 * @returns {finesse.restservices.TeamPhoneBooks} 16868 * This TeamPhoneBooks object to allow cascading. 16869 */ 16870 get: function () { 16871 // set loaded to false so it will rebuild the collection after the get 16872 /** @private */ 16873 this._loaded = false; 16874 // reset collection 16875 /** @private */ 16876 this._collection = {}; 16877 // perform get 16878 this._synchronize(); 16879 return this; 16880 }, 16881 16882 /* We only use PUT and GET on Reason Code team assignments 16883 */ 16884 createPutSuccessHandler: function(contact, contentBody, successHandler){ 16885 return function (rsp) { 16886 // Update internal structure based on response. Here we 16887 // inject the contentBody from the PUT request into the 16888 // rsp.object element to mimic a GET as a way to take 16889 // advantage of the existing _processResponse method. 16890 rsp.object = contentBody; 16891 contact._processResponse(rsp); 16892 16893 //Remove the injected Contact object before cascading response 16894 rsp.object = {}; 16895 16896 //cascade response back to consumer's response handler 16897 successHandler(rsp); 16898 }; 16899 }, 16900 16901 /** 16902 * Update - This should be all that is needed. 16903 */ 16904 update: function (newValues, handlers) { 16905 this.isLoaded(); 16906 var contentBody = {}, contentBodyInner = [], i, innerObject; 16907 16908 contentBody[this.getRestType()] = { 16909 }; 16910 16911 for (i in newValues) { 16912 if (newValues.hasOwnProperty(i)) { 16913 innerObject = {}; 16914 innerObject = { 16915 "uri": newValues[i] 16916 }; 16917 contentBodyInner.push(innerObject); 16918 } 16919 } 16920 16921 contentBody[this.getRestType()] = { 16922 "PhoneBook" : contentBodyInner 16923 }; 16924 16925 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 16926 handlers = handlers || {}; 16927 16928 this.restRequest(this.getRestUrl(), { 16929 method: 'PUT', 16930 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 16931 error: handlers.error, 16932 content: contentBody 16933 }); 16934 16935 return this; // Allow cascading 16936 } 16937 16938 }); 16939 16940 window.finesse = window.finesse || {}; 16941 window.finesse.restservices = window.finesse.restservices || {}; 16942 window.finesse.restservices.TeamPhoneBooks = TeamPhoneBooks; 16943 16944 return TeamPhoneBooks; 16945 }); 16946 16947 /** 16948 * JavaScript representation of the Finesse LayoutConfig object 16949 * @requires ClientServices 16950 * @requires finesse.FinesseBase 16951 * @requires finesse.restservices.RestBase 16952 */ 16953 16954 /** @private */ 16955 define('restservices/LayoutConfig',['restservices/RestBase'], function (RestBase) { 16956 /** @private */ 16957 var LayoutConfig = RestBase.extend({ 16958 16959 /** 16960 * @class 16961 * JavaScript representation of a LayoutConfig object. Also exposes methods to operate 16962 * on the object against the server. 16963 * 16964 * @param {String} id 16965 * Not required... 16966 * @param {Object} callbacks 16967 * An object containing callbacks for instantiation and runtime 16968 * @param {Function} callbacks.onLoad(this) 16969 * Callback to invoke upon successful instantiation 16970 * @param {Function} callbacks.onLoadError(rsp) 16971 * Callback to invoke on instantiation REST request error 16972 * as passed by finesse.clientservices.ClientServices.ajax() 16973 * { 16974 * status: {Number} The HTTP status code returned 16975 * content: {String} Raw string of response 16976 * object: {Object} Parsed object of response 16977 * error: {Object} Wrapped exception that was caught 16978 * error.errorType: {String} Type of error that was caught 16979 * error.errorMessage: {String} Message associated with error 16980 * } 16981 * @param {Function} callbacks.onChange(this) 16982 * Callback to invoke upon successful update 16983 * @param {Function} callbacks.onError(rsp) 16984 * Callback to invoke on update error (refresh or event) 16985 * as passed by finesse.clientservices.ClientServices.ajax() 16986 * { 16987 * status: {Number} The HTTP status code returned 16988 * content: {String} Raw string of response 16989 * object: {Object} Parsed object of response 16990 * error: {Object} Wrapped exception that was caught 16991 * error.errorType: {String} Type of error that was caught 16992 * error.errorMessage: {String} Message associated with error 16993 * } 16994 * 16995 * @constructs 16996 */ 16997 init: function (callbacks) { 16998 this._super("", callbacks); 16999 //when post is performed and id is empty 17000 /*if (id === "") { 17001 this._loaded = true; 17002 }*/ 17003 this._layoutxml = {}; 17004 }, 17005 17006 /** 17007 * Returns REST class of LayoutConfig object 17008 */ 17009 getRestClass: function () { 17010 return LayoutConfig; 17011 }, 17012 17013 /** 17014 * The type of this REST object is LayoutConfig 17015 */ 17016 getRestType: function () { 17017 return "LayoutConfig"; 17018 }, 17019 17020 /** 17021 * Gets the REST URL of this object. 17022 * 17023 * If the parent has an id, the id is appended. 17024 * On occasions of POST, it will not have an id. 17025 */ 17026 getRestUrl: function () { 17027 var layoutUri = "/finesse/api/" + this.getRestType() + "/default"; 17028 /*if (this._id) { 17029 layoutUri = layoutUri + "/" + this._id; 17030 }*/ 17031 return layoutUri; 17032 }, 17033 17034 /** 17035 * This API does not support subscription 17036 */ 17037 supportsSubscriptions: false, 17038 17039 keepRestResponse: true, 17040 17041 17042 /** 17043 * Gets finesselayout.xml retrieved from the API call 17044 */ 17045 getLayoutxml: function () { 17046 this.isLoaded(); 17047 var layoutxml = this.getData().layoutxml; 17048 17049 // We need to unescape everything that is unallowed in xml so consumers don't have to deal with it (used in tandem with update()) 17050 layoutxml = layoutxml.replace(/&/g,"&"); 17051 17052 return layoutxml; 17053 }, 17054 17055 /** 17056 * Gets the type of this LayoutConfig object 17057 */ 17058 /* 17059 getType: function () { 17060 this.isLoaded(); 17061 return this.getData().type; 17062 },*/ 17063 17064 /** 17065 * Retrieve the LayoutConfig settings. 17066 * If the id is not provided the API call will fail. 17067 * @returns {LayoutConfig} 17068 * This LayoutConfig object to allow cascading. 17069 */ 17070 get: function () { 17071 this._synchronize(); 17072 return this; 17073 }, 17074 17075 /** 17076 * Closure handle updating of the internal data for the LayoutConfig object 17077 * upon a successful update (PUT) request before calling the intended 17078 * success handler provided by the consumer 17079 * 17080 * @param {Object} 17081 * layoutconfig Reference to this LayoutConfig object 17082 * @param {Object} 17083 * LayoutConfig Object that contains the settings to be 17084 * submitted in the api request 17085 * @param {Function} 17086 * successHandler The success handler specified by the consumer 17087 * of this object 17088 * @returns {LayoutConfig} This LayoutConfig object to allow cascading 17089 */ 17090 17091 createPutSuccessHandler: function (layoutconfig, contentBody, successHandler) { 17092 return function (rsp) { 17093 // Update internal structure based on response. Here we 17094 // inject the contentBody from the PUT request into the 17095 // rsp.object element to mimic a GET as a way to take 17096 // advantage of the existing _processResponse method. 17097 rsp.content = contentBody; 17098 rsp.object.LayoutConfig = {}; 17099 rsp.object.LayoutConfig.finesseLayout = contentBody; 17100 layoutconfig._processResponse(rsp); 17101 17102 //Remove the injected layoutConfig object before cascading response 17103 rsp.object.LayoutConfig = {}; 17104 17105 //cascade response back to consumer's response handler 17106 successHandler(rsp); 17107 }; 17108 }, 17109 17110 /** 17111 * Update LayoutConfig 17112 * @param {Object} finesselayout 17113 * The XML for FinesseLayout being stored 17114 * 17115 * @param {Object} handlers 17116 * An object containing callback handlers for the request. Optional. 17117 * @param {Function} options.success(rsp) 17118 * A callback function to be invoked for a successful request. 17119 * { 17120 * status: {Number} The HTTP status code returned 17121 * content: {String} Raw string of response 17122 * object: {Object} Parsed object of response 17123 * } 17124 * @param {Function} options.error(rsp) 17125 * A callback function to be invoked for an unsuccessful request. 17126 * { 17127 * status: {Number} The HTTP status code returned 17128 * content: {String} Raw string of response 17129 * object: {Object} Parsed object of response (HTTP errors) 17130 * error: {Object} Wrapped exception that was caught 17131 * error.errorType: {String} Type of error that was caught 17132 * error.errorMessage: {String} Message associated with error 17133 * } 17134 * @returns {finesse.restservices.LayoutConfig} 17135 * This LayoutConfig object to allow cascading 17136 */ 17137 17138 update: function (layoutxml, handlers) { 17139 this.isLoaded(); 17140 17141 17142 var contentBody = {}, 17143 //Created a regex (re) to scoop out just the gadget URL (without before and after whitespace characters) 17144 re = /<gadget>\s*(\S+)\s*<\/gadget>/g; 17145 17146 // We need to escape everything that is unallowed in xml so consumers don't have to deal with it (used in tandem with getLayoutxml()) 17147 layoutxml = layoutxml.replace(/&(?!amp;)/g, "&"); 17148 17149 //used the regex (re) to the update and save the layoutxml with the improved gadget URL (without before/after whitespace) 17150 layoutxml = layoutxml.replace(re, "<gadget>$1</gadget>"); 17151 17152 contentBody[this.getRestType()] = { 17153 "layoutxml": finesse.utilities.Utilities.translateHTMLEntities(layoutxml, true) 17154 }; 17155 17156 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 17157 handlers = handlers || {}; 17158 17159 this.restRequest(this.getRestUrl(), { 17160 method: 'PUT', 17161 success: this.createPutSuccessHandler(this, layoutxml, handlers.success), 17162 error: handlers.error, 17163 content: contentBody 17164 }); 17165 17166 return this; // Allow cascading 17167 } 17168 17169 /** 17170 *TODO createPostSuccessHandler needs to be debugged to make it working 17171 * Closure handle creating new LayoutConfig object 17172 * upon a successful create (POST) request before calling the intended 17173 * success handler provided by the consumer 17174 * 17175 * @param {Object} 17176 * layoutconfig Reference to this LayoutConfig object 17177 * @param {Object} 17178 * LayoutConfig Object that contains the settings to be 17179 * submitted in the api request 17180 * @param {Function} 17181 * successHandler The success handler specified by the consumer 17182 * of this object 17183 * @returns {finesse.restservices.LayoutConfig} This LayoutConfig object to allow cascading 17184 */ 17185 /* 17186 createPostSuccessHandler: function (layoutconfig, contentBody, successHandler) { 17187 return function (rsp) { 17188 17189 rsp.object = contentBody; 17190 layoutconfig._processResponse(rsp); 17191 17192 //Remove the injected layoutConfig object before cascading response 17193 rsp.object = {}; 17194 17195 //cascade response back to consumer's response handler 17196 successHandler(rsp); 17197 }; 17198 }, */ 17199 17200 /** 17201 * TODO Method needs to be debugged to make POST working 17202 * Add LayoutConfig 17203 * @param {Object} finesselayout 17204 * The XML for FinesseLayout being stored 17205 * 17206 * @param {Object} handlers 17207 * An object containing callback handlers for the request. Optional. 17208 * @param {Function} options.success(rsp) 17209 * A callback function to be invoked for a successful request. 17210 * { 17211 * status: {Number} The HTTP status code returned 17212 * content: {String} Raw string of response 17213 * object: {Object} Parsed object of response 17214 * } 17215 * @param {Function} options.error(rsp) 17216 * A callback function to be invoked for an unsuccessful request. 17217 * { 17218 * status: {Number} The HTTP status code returned 17219 * content: {String} Raw string of response 17220 * object: {Object} Parsed object of response (HTTP errors) 17221 * error: {Object} Wrapped exception that was caught 17222 * error.errorType: {String} Type of error that was caught 17223 * error.errorMessage: {String} Message associated with error 17224 * } 17225 * @returns {finesse.restservices.LayoutConfig} 17226 * This LayoutConfig object to allow cascading 17227 */ 17228 /* 17229 add: function (layoutxml, handlers) { 17230 this.isLoaded(); 17231 var contentBody = {}; 17232 17233 17234 contentBody[this.getRestType()] = { 17235 "layoutxml": layoutxml, 17236 "type": "current" 17237 }; 17238 17239 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 17240 handlers = handlers || {}; 17241 17242 this.restRequest(this.getRestUrl(), { 17243 method: 'POST', 17244 success: this.createPostSuccessHandler(this, contentBody, handlers.success), 17245 error: handlers.error, 17246 content: contentBody 17247 }); 17248 17249 return this; // Allow cascading 17250 } */ 17251 }); 17252 17253 window.finesse = window.finesse || {}; 17254 window.finesse.restservices = window.finesse.restservices || {}; 17255 window.finesse.restservices.LayoutConfig = LayoutConfig; 17256 17257 return LayoutConfig; 17258 17259 }); 17260 17261 /** 17262 * JavaScript representation of the Finesse LayoutConfig object for a Team. 17263 * 17264 * @requires finesse.clientservices.ClientServices 17265 * @requires Class 17266 * @requires finesse.FinesseBase 17267 * @requires finesse.restservices.RestBase 17268 * @requires finesse.utilities.Utilities 17269 * @requires finesse.restservices.LayoutConfig 17270 */ 17271 17272 /** The following comment is to prevent jslint errors about 17273 * using variables before they are defined. 17274 */ 17275 /*global Exception */ 17276 17277 /** @private */ 17278 define('restservices/TeamLayoutConfig',[ 17279 'restservices/RestBase', 17280 'utilities/Utilities', 17281 'restservices/LayoutConfig' 17282 ], 17283 function (RestBase, Utilities, LayoutConfig) { 17284 17285 var TeamLayoutConfig = RestBase.extend({ 17286 // Keep the restresponse so we can parse the layoutxml out of it in getLayoutXML() 17287 keepRestResponse: true, 17288 17289 /** 17290 * @class 17291 * JavaScript representation of a LayoutConfig object for a Team. Also exposes 17292 * methods to operate on the object against the server. 17293 * 17294 * @param {Object} options 17295 * An object with the following properties:<ul> 17296 * <li><b>id:</b> The id of the object being constructed</li> 17297 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 17298 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 17299 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 17300 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 17301 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 17302 * <li><b>status:</b> {Number} The HTTP status code returned</li> 17303 * <li><b>content:</b> {String} Raw string of response</li> 17304 * <li><b>object:</b> {Object} Parsed object of response</li> 17305 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 17306 * <li><b>errorType:</b> {String} Type of error that was caught</li> 17307 * <li><b>errorMessage:</b> {String} Message associated with error</li> 17308 * </ul></li> 17309 * </ul></li> 17310 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 17311 * @constructs 17312 **/ 17313 init: function (options) { 17314 this._super(options); 17315 }, 17316 17317 /** 17318 * @private 17319 * Gets the REST class for the current object - this is the LayoutConfigs class. 17320 * @returns {Object} The LayoutConfigs class. 17321 */ 17322 getRestClass: function () { 17323 return TeamLayoutConfig; 17324 }, 17325 17326 /** 17327 * @private 17328 * Gets the REST type for the current object - this is a "LayoutConfig". 17329 * @returns {String} The LayoutConfig string. 17330 */ 17331 getRestType: function () { 17332 return "TeamLayoutConfig"; 17333 }, 17334 17335 /** 17336 * @private 17337 * Override default to indicate that this object doesn't support making 17338 * requests. 17339 */ 17340 supportsRequests: false, 17341 17342 /** 17343 * @private 17344 * Override default to indicate that this object doesn't support subscriptions. 17345 */ 17346 supportsSubscriptions: false, 17347 17348 /** 17349 * Getter for the category. 17350 * @returns {String} The category. 17351 */ 17352 getLayoutXML: function () { 17353 this.isLoaded(); 17354 var layoutxml = this.getData().layoutxml; 17355 17356 // We need to unescape everything that is unallowed in xml so consumers don't have to deal with it (used in tandem with put()) 17357 layoutxml = layoutxml.replace(/&/g,"&"); 17358 17359 return layoutxml; 17360 }, 17361 17362 /** 17363 * Getter for the code. 17364 * @returns {String} The code. 17365 */ 17366 getUseDefault: function () { 17367 this.isLoaded(); 17368 return this.getData().useDefault; 17369 }, 17370 17371 /** 17372 * Retrieve the TeamLayoutConfig. 17373 * 17374 * @returns {finesse.restservices.TeamLayoutConfig} 17375 */ 17376 get: function () { 17377 // this._id is needed, but is not used in this object.. we're overriding getRestUrl anyway 17378 this._id = "0"; 17379 // set loaded to false so it will rebuild the collection after the get 17380 this._loaded = false; 17381 // reset collection 17382 this._collection = {}; 17383 // perform get 17384 this._synchronize(); 17385 return this; 17386 }, 17387 17388 createPutSuccessHandler: function(contact, contentBody, successHandler){ 17389 return function (rsp) { 17390 // Update internal structure based on response. Here we 17391 // inject the contentBody from the PUT request into the 17392 // rsp.object element to mimic a GET as a way to take 17393 // advantage of the existing _processResponse method. 17394 rsp.object = contentBody; 17395 contact._processResponse(rsp); 17396 17397 //Remove the injected Contact object before cascading response 17398 rsp.object = {}; 17399 17400 //cascade response back to consumer's response handler 17401 successHandler(rsp); 17402 }; 17403 }, 17404 17405 put: function (newValues, handlers) { 17406 // this._id is needed, but is not used in this object.. we're overriding getRestUrl anyway 17407 this._id = "0"; 17408 this.isLoaded(); 17409 17410 // We need to escape everything that is unallowed in xml so consumers don't have to deal with it (used in tandem with getLayoutxml()) 17411 var layoutxml = newValues.layoutXML.replace(/&(?!amp;)/g, "&"), 17412 contentBody = {}, 17413 //Created a regex (re) to scoop out just the gadget URL (without before and after whitespace characters) 17414 re = /<gadget>\s*(\S+)\s*<\/gadget>/g; 17415 17416 //used the regex (re) to the update and save the layoutxml with the improved gadget URL (without before/after whitespace) 17417 layoutxml = layoutxml.replace(re, "<gadget>$1</gadget>"); 17418 17419 contentBody[this.getRestType()] = { 17420 "useDefault": newValues.useDefault, 17421 // The LayoutConfig restservice javascript class only translates ampersands, so we'll do that also 17422 "layoutxml": finesse.utilities.Utilities.translateHTMLEntities(layoutxml, true) 17423 }; 17424 17425 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 17426 handlers = handlers || {}; 17427 17428 this.restRequest(this.getRestUrl(), { 17429 method: 'PUT', 17430 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 17431 error: handlers.error, 17432 content: contentBody 17433 }); 17434 17435 return this; // Allow cascading 17436 }, 17437 17438 getRestUrl: function(){ 17439 // return team's url + /LayoutConfig 17440 // eg: /api/Team/1/LayoutConfig 17441 if(this._restObj === undefined){ 17442 throw new Exception("TeamLayoutConfig instances must have a parent team object."); 17443 } 17444 return this._restObj.getRestUrl() + '/LayoutConfig'; 17445 } 17446 17447 }); 17448 17449 window.finesse = window.finesse || {}; 17450 window.finesse.restservices = window.finesse.restservices || {}; 17451 window.finesse.restservices.TeamLayoutConfig = TeamLayoutConfig; 17452 17453 return TeamLayoutConfig; 17454 }); 17455 17456 /** 17457 * JavaScript representation of a TeamWorkflow. 17458 * 17459 * @requires finesse.clientservices.ClientServices 17460 * @requires Class 17461 * @requires finesse.FinesseBase 17462 * @requires finesse.restservices.RestBase 17463 */ 17464 /** @private */ 17465 define('restservices/TeamWorkflow',['restservices/RestBase'], function (RestBase) { 17466 17467 var TeamWorkflow = RestBase.extend({ 17468 17469 /** 17470 * @class 17471 * JavaScript representation of a TeamWorkflow object. Also exposes 17472 * methods to operate on the object against the server. 17473 * 17474 * @param {Object} options 17475 * An object with the following properties:<ul> 17476 * <li><b>id:</b> The id of the object being constructed</li> 17477 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 17478 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 17479 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 17480 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 17481 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 17482 * <li><b>status:</b> {Number} The HTTP status description returned</li> 17483 * <li><b>content:</b> {String} Raw string of response</li> 17484 * <li><b>object:</b> {Object} Parsed object of response</li> 17485 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 17486 * <li><b>errorType:</b> {String} Type of error that was caught</li> 17487 * <li><b>errorMessage:</b> {String} Message associated with error</li> 17488 * </ul></li> 17489 * </ul></li> 17490 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 17491 * @constructs 17492 **/ 17493 init: function (options) { 17494 this._super(options); 17495 }, 17496 17497 /** 17498 * @private 17499 * Gets the REST class for the current object - this is the TeamWorkflow class. 17500 * @returns {Object} The TeamWorkflow class. 17501 */ 17502 getRestClass: function () { 17503 return TeamWorkflow; 17504 }, 17505 17506 /** 17507 * @private 17508 * Gets the REST type for the current object - this is a "Workflow". 17509 * @returns {String} The Workflow string. 17510 */ 17511 getRestType: function () { 17512 return "Workflow"; 17513 }, 17514 17515 /** 17516 * @private 17517 * Override default to indicate that this object doesn't support making 17518 * requests. 17519 */ 17520 supportsRequests: false, 17521 17522 /** 17523 * @private 17524 * Override default to indicate that this object doesn't support subscriptions. 17525 */ 17526 supportsSubscriptions: false, 17527 17528 /** 17529 * Getter for the name. 17530 * @returns {String} The name. 17531 */ 17532 getName: function () { 17533 this.isLoaded(); 17534 return this.getData().name; 17535 }, 17536 17537 /** 17538 * Getter for the description. 17539 * @returns {String} The description. 17540 */ 17541 getDescription: function () { 17542 this.isLoaded(); 17543 return this.getData().description; 17544 }, 17545 17546 /** 17547 * Getter for the Uri value. 17548 * @returns {String} The Uri. 17549 */ 17550 getUri: function () { 17551 this.isLoaded(); 17552 return this.getData().uri; 17553 } 17554 17555 }); 17556 17557 window.finesse = window.finesse || {}; 17558 window.finesse.restservices = window.finesse.restservices || {}; 17559 window.finesse.restservices.TeamWorkflow = TeamWorkflow; 17560 17561 return TeamWorkflow; 17562 }); 17563 17564 /** 17565 * JavaScript representation of the TeamWorkflows collection 17566 * object which contains a list of TeamWorkflow objects. 17567 * 17568 * @requires finesse.clientservices.ClientServices 17569 * @requires Class 17570 * @requires finesse.FinesseBase 17571 * @requires finesse.restservices.RestBase 17572 * @requires finesse.restservices.Dialog 17573 * @requires finesse.restservices.RestCollectionBase 17574 */ 17575 /** @private */ 17576 define('restservices/TeamWorkflows',[ 17577 'restservices/RestCollectionBase', 17578 'restservices/TeamWorkflow', 17579 'restservices/RestBase' 17580 ], 17581 function (RestCollectionBase, TeamWorkflow, RestBase) { 17582 17583 var TeamWorkflows = RestCollectionBase.extend({ 17584 17585 /** 17586 * @class 17587 * JavaScript representation of a TeamWorkflows collection object. Also exposes 17588 * methods to operate on the object against the server. 17589 * 17590 * @param {Object} options 17591 * An object with the following properties:<ul> 17592 * <li><b>id:</b> The id of the object being constructed</li> 17593 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 17594 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 17595 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 17596 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 17597 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 17598 * <li><b>status:</b> {Number} The HTTP status code returned</li> 17599 * <li><b>content:</b> {String} Raw string of response</li> 17600 * <li><b>object:</b> {Object} Parsed object of response</li> 17601 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 17602 * <li><b>errorType:</b> {String} Type of error that was caught</li> 17603 * <li><b>errorMessage:</b> {String} Message associated with error</li> 17604 * </ul></li> 17605 * </ul></li> 17606 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 17607 * @constructs 17608 **/ 17609 init: function (options) { 17610 this._super(options); 17611 }, 17612 17613 /** 17614 * @private 17615 * Gets the REST class for the current object - this is the TeamWorkflows class. 17616 */ 17617 getRestClass: function () { 17618 return TeamWorkflows; 17619 }, 17620 17621 /** 17622 * @private 17623 * Gets the REST class for the objects that make up the collection. - this 17624 * is the TeamWorkflow class. 17625 */ 17626 getRestItemClass: function () { 17627 return TeamWorkflow; 17628 }, 17629 17630 /** 17631 * @private 17632 * Gets the REST type for the current object - this is a "Workflows". 17633 */ 17634 getRestType: function () { 17635 return "Workflows"; 17636 }, 17637 17638 /** 17639 * Overrides the parent class. Returns the url for the Workflows resource 17640 */ 17641 getRestUrl: function () { 17642 var restObj = this._restObj, restUrl = ""; 17643 17644 //Prepend the base REST object if one was provided. 17645 //Otherwise prepend with the default webapp name. 17646 if (restObj instanceof RestBase) { 17647 restUrl += restObj.getRestUrl(); 17648 } else { 17649 restUrl += "/finesse/api/Team"; 17650 } 17651 //Append ID if it is not undefined, null, or empty. 17652 if (this._id) { 17653 restUrl += "/" + this._id; 17654 } 17655 //Append the REST type. 17656 restUrl += "/Workflows"; 17657 17658 return restUrl; 17659 }, 17660 17661 /** 17662 * @private 17663 * Gets the REST type for the objects that make up the collection - this is "Workflow". 17664 */ 17665 getRestItemType: function () { 17666 return "Workflow"; 17667 }, 17668 17669 /** 17670 * @private 17671 * Override default to indicates that the collection supports making requests. 17672 */ 17673 supportsRequests: true, 17674 17675 /** 17676 * @private 17677 * Override default to indicates that the collection does not subscribe to its objects. 17678 */ 17679 supportsRestItemSubscriptions: false, 17680 17681 /** 17682 * Retrieve the Sign Out Reason Codes. 17683 * 17684 * @returns {finesse.restservices.TeamWorkflows} 17685 * This TeamWorkflows object to allow cascading. 17686 */ 17687 get: function () { 17688 // set loaded to false so it will rebuild the collection after the get 17689 this._loaded = false; 17690 // reset collection 17691 this._collection = {}; 17692 // perform get 17693 this._synchronize(); 17694 return this; 17695 }, 17696 17697 /* We only use PUT and GET on Reason Code team assignments 17698 * @param {Object} contact 17699 * @param {Object} contentBody 17700 * @param {Function} successHandler 17701 */ 17702 createPutSuccessHandler: function (contact, contentBody, successHandler) { 17703 return function (rsp) { 17704 // Update internal structure based on response. Here we 17705 // inject the contentBody from the PUT request into the 17706 // rsp.object element to mimic a GET as a way to take 17707 // advantage of the existing _processResponse method. 17708 rsp.object = contentBody; 17709 contact._processResponse(rsp); 17710 17711 //Remove the injected contentBody object before cascading response 17712 rsp.object = {}; 17713 17714 //cascade response back to consumer's response handler 17715 successHandler(rsp); 17716 }; 17717 }, 17718 17719 /** 17720 * Update - This should be all that is needed. 17721 * @param {Object} newValues 17722 * @param {Object} handlers 17723 * @returns {finesse.restservices.TeamWorkflows} 17724 * This TeamWorkflows object to allow cascading. 17725 */ 17726 update: function (newValues, handlers) { 17727 this.isLoaded(); 17728 var contentBody = {}, contentBodyInner = [], i, innerObject = {}; 17729 17730 contentBody[this.getRestType()] = { 17731 }; 17732 17733 for (i in newValues) { 17734 if (newValues.hasOwnProperty(i)) { 17735 innerObject = { 17736 "uri": newValues[i] 17737 }; 17738 contentBodyInner.push(innerObject); 17739 } 17740 } 17741 17742 contentBody[this.getRestType()] = { 17743 "Workflow" : contentBodyInner 17744 }; 17745 17746 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 17747 handlers = handlers || {}; 17748 17749 this.restRequest(this.getRestUrl(), { 17750 method: 'PUT', 17751 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 17752 error: handlers.error, 17753 content: contentBody 17754 }); 17755 17756 return this; // Allow cascading 17757 } 17758 17759 }); 17760 17761 window.finesse = window.finesse || {}; 17762 window.finesse.restservices = window.finesse.restservices || {}; 17763 window.finesse.restservices.TeamWorkflows = TeamWorkflows; 17764 17765 return TeamWorkflows; 17766 }); 17767 17768 /** 17769 * JavaScript representation of the Finesse Team REST object. 17770 * 17771 * @requires finesse.clientservices.ClientServices 17772 * @requires Class 17773 * @requires finesse.FinesseBase 17774 * @requires finesse.restservices.RestBase 17775 * @requires finesse.restservices.RestCollectionBase 17776 * @requires finesse.restservices.User 17777 * @requires finesse.restservices.Users 17778 */ 17779 17780 /** 17781 * The following comment prevents JSLint errors concerning undefined global variables. 17782 * It tells JSLint that these identifiers are defined elsewhere. 17783 */ 17784 /*jslint bitwise:true, browser:true, nomen:true, regexp:true, sloppy:true, white:true */ 17785 17786 /** The following comment is to prevent jslint errors about 17787 * using variables before they are defined. 17788 */ 17789 /*global $, jQuery, Handlebars, dojox, dojo, finesse */ 17790 17791 /** @private */ 17792 define('restservices/Team',[ 17793 'restservices/RestBase', 17794 'utilities/Utilities', 17795 'restservices/Users', 17796 'restservices/TeamNotReadyReasonCodes', 17797 'restservices/TeamWrapUpReasons', 17798 'restservices/TeamSignOutReasonCodes', 17799 'restservices/TeamPhoneBooks', 17800 'restservices/TeamLayoutConfig', 17801 'restservices/TeamWorkflows' 17802 ], 17803 function (RestBase, Utilities, Users, TeamNotReadyReasonCodes, TeamWrapUpReasons, TeamSignOutReasonCodes, TeamPhoneBooks, TeamLayoutConfig, TeamWorkflows) { 17804 var Team = RestBase.extend(/** @lends finesse.restservices.Team.prototype */{ 17805 17806 _teamLayoutConfig: null, 17807 17808 /** 17809 * @class 17810 * A Team is a set of Agent Users, typically supervised by one or more Supervisor Users. 17811 * 17812 * @augments finesse.restservices.RestBase 17813 * @see finesse.restservices.User#getSupervisedTeams 17814 * @see finesse.restservices.Users 17815 * @constructs 17816 */ 17817 _fakeConstuctor: function () { 17818 /* This is here to hide the real init constructor from the public docs */ 17819 }, 17820 17821 /** 17822 * @private 17823 * @class 17824 * JavaScript representation of a Team object. Also exposes methods to operate 17825 * on the object against the server. 17826 * 17827 * @param {Object} options 17828 * An object with the following properties:<ul> 17829 * <li><b>id:</b> The id of the object being constructed</li> 17830 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 17831 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 17832 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 17833 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 17834 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 17835 * <li><b>status:</b> {Number} The HTTP status code returned</li> 17836 * <li><b>content:</b> {String} Raw string of response</li> 17837 * <li><b>object:</b> {Object} Parsed object of response</li> 17838 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 17839 * <li><b>errorType:</b> {String} Type of error that was caught</li> 17840 * <li><b>errorMessage:</b> {String} Message associated with error</li> 17841 * </ul></li> 17842 * </ul></li> 17843 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 17844 **/ 17845 init: function (options) { 17846 this._super(options); 17847 }, 17848 17849 /** 17850 * @private 17851 * Gets the REST class for the current object - this is the Team class. 17852 * @returns {Object} The Team constructor. 17853 */ 17854 getRestClass: function () { 17855 return finesse.restesrvices.Team; 17856 }, 17857 17858 /** 17859 * @private 17860 * Gets the REST type for the current object - this is a "Team". 17861 * @returns {String} The Team string. 17862 */ 17863 getRestType: function () { 17864 return "Team"; 17865 }, 17866 17867 /** 17868 * @private 17869 * Override default to indicate that this object doesn't support making 17870 * requests. 17871 */ 17872 supportsSubscriptions: false, 17873 17874 /** 17875 * Getter for the team id. 17876 * @returns {String} The team id. 17877 */ 17878 getId: function () { 17879 this.isLoaded(); 17880 return this.getData().id; 17881 }, 17882 17883 /** 17884 * Getter for the team name. 17885 * @returns {String} The team name 17886 */ 17887 getName: function () { 17888 this.isLoaded(); 17889 return this.getData().name; 17890 }, 17891 17892 /** 17893 * @private 17894 * Getter for the team uri. 17895 * @returns {String} The team uri 17896 */ 17897 getUri: function () { 17898 this.isLoaded(); 17899 return this.getData().uri; 17900 }, 17901 17902 /** 17903 * Constructs and returns a collection of Users. 17904 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers. 17905 * @returns {finesse.restservices.Users} Users collection of User objects. 17906 */ 17907 getUsers: function (options) { 17908 this.isLoaded(); 17909 options = options || {}; 17910 17911 options.parentObj = this; 17912 // We are using getData() instead of getData.Users because the superclass (RestCollectionBase) 17913 // for Users needs the "Users" key to validate the provided payload matches the class type. 17914 options.data = this.getData(); 17915 17916 return new Users(options); 17917 }, 17918 17919 /** 17920 * @private 17921 * Getter for a teamNotReadyReasonCodes collection object that is associated with Team. 17922 * @param callbacks 17923 * @returns {teamNotReadyReasonCodes} 17924 * A teamNotReadyReasonCodes collection object. 17925 */ 17926 getTeamNotReadyReasonCodes: function (callbacks) { 17927 var options = callbacks || {}; 17928 options.parentObj = this; 17929 this.isLoaded(); 17930 17931 if (!this._teamNotReadyReasonCodes) { 17932 this._teamNotReadyReasonCodes = new TeamNotReadyReasonCodes(options); 17933 } 17934 17935 return this._teamNotReadyReasonCodes; 17936 }, 17937 17938 /** 17939 * @private 17940 * Getter for a teamWrapUpReasons collection object that is associated with Team. 17941 * @param callbacks 17942 * @returns {teamWrapUpReasons} 17943 * A teamWrapUpReasons collection object. 17944 */ 17945 getTeamWrapUpReasons: function (callbacks) { 17946 var options = callbacks || {}; 17947 options.parentObj = this; 17948 this.isLoaded(); 17949 17950 if (!this._teamWrapUpReasons) { 17951 this._teamWrapUpReasons = new TeamWrapUpReasons(options); 17952 } 17953 17954 return this._teamWrapUpReasons; 17955 }, 17956 17957 /** 17958 * @private 17959 * Getter for a teamSignOutReasonCodes collection object that is associated with Team. 17960 * @param callbacks 17961 * @returns {teamSignOutReasonCodes} 17962 * A teamSignOutReasonCodes collection object. 17963 */ 17964 17965 getTeamSignOutReasonCodes: function (callbacks) { 17966 var options = callbacks || {}; 17967 options.parentObj = this; 17968 this.isLoaded(); 17969 17970 if (!this._teamSignOutReasonCodes) { 17971 this._teamSignOutReasonCodes = new TeamSignOutReasonCodes(options); 17972 } 17973 17974 return this._teamSignOutReasonCodes; 17975 }, 17976 17977 /** 17978 * @private 17979 * Getter for a teamPhoneBooks collection object that is associated with Team. 17980 * @param callbacks 17981 * @returns {teamPhoneBooks} 17982 * A teamPhoneBooks collection object. 17983 */ 17984 getTeamPhoneBooks: function (callbacks) { 17985 var options = callbacks || {}; 17986 options.parentObj = this; 17987 this.isLoaded(); 17988 17989 if (!this._phonebooks) { 17990 this._phonebooks = new TeamPhoneBooks(options); 17991 } 17992 17993 return this._phonebooks; 17994 }, 17995 17996 /** 17997 * @private 17998 * Getter for a teamWorkflows collection object that is associated with Team. 17999 * @param callbacks 18000 * @returns {teamWorkflows} 18001 * A teamWorkflows collection object. 18002 */ 18003 getTeamWorkflows: function (callbacks) { 18004 var options = callbacks || {}; 18005 options.parentObj = this; 18006 this.isLoaded(); 18007 18008 if (!this._workflows) { 18009 this._workflows = new TeamWorkflows(options); 18010 } 18011 18012 return this._workflows; 18013 }, 18014 18015 /** 18016 * @private 18017 * Getter for a teamLayoutConfig object that is associated with Team. 18018 * @param callbacks 18019 * @returns {teamLayoutConfig} 18020 */ 18021 getTeamLayoutConfig: function (callbacks) { 18022 var options = callbacks || {}; 18023 options.parentObj = this; 18024 this.isLoaded(); 18025 18026 if (this._teamLayoutConfig === null) { 18027 this._teamLayoutConfig = new TeamLayoutConfig(options); 18028 } 18029 18030 return this._teamLayoutConfig; 18031 } 18032 18033 }); 18034 18035 window.finesse = window.finesse || {}; 18036 window.finesse.restservices = window.finesse.restservices || {}; 18037 window.finesse.restservices.Team = Team; 18038 18039 return Team; 18040 }); 18041 18042 /** 18043 * JavaScript representation of the Finesse Teams collection. 18044 * object which contains a list of Team objects 18045 * @requires finesse.clientservices.ClientServices 18046 * @requires Class 18047 * @requires finesse.FinesseBase 18048 * @requires finesse.restservices.RestBase 18049 * @requires finesse.restservices.RestCollectionBase 18050 */ 18051 18052 /** @private */ 18053 define('restservices/Teams',[ 18054 'restservices/RestCollectionBase', 18055 'restservices/Team' 18056 ], 18057 function (RestCollectionBase, Team) { 18058 /** @private */ 18059 var Teams = RestCollectionBase.extend({ 18060 18061 /** 18062 * @class 18063 * JavaScript representation of a Teams collection object. Also exposes methods to operate 18064 * on the object against the server. 18065 * 18066 * @param {Object} options 18067 * An object with the following properties:<ul> 18068 * <li><b>id:</b> The id of the object being constructed</li> 18069 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 18070 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 18071 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 18072 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 18073 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 18074 * <li><b>status:</b> {Number} The HTTP status code returned</li> 18075 * <li><b>content:</b> {String} Raw string of response</li> 18076 * <li><b>object:</b> {Object} Parsed object of response</li> 18077 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 18078 * <li><b>errorType:</b> {String} Type of error that was caught</li> 18079 * <li><b>errorMessage:</b> {String} Message associated with error</li> 18080 * </ul></li> 18081 * </ul></li> 18082 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 18083 * @constructs 18084 **/ 18085 init: function (options) { 18086 this._super(options); 18087 }, 18088 18089 /** 18090 * @private 18091 * Gets the REST class for the current object - this is the Teams class. 18092 * @returns {Object} The Teams constructor. 18093 */ 18094 getRestClass: function () { 18095 return Teams; 18096 }, 18097 18098 /** 18099 * @private 18100 * Gets the REST class for the objects that make up the collection. - this 18101 * is the Team class. 18102 */ 18103 getRestItemClass: function () { 18104 return Team; 18105 }, 18106 18107 /** 18108 * @private 18109 * Gets the REST type for the current object - this is a "Teams". 18110 * @returns {String} The Teams string. 18111 */ 18112 getRestType: function () { 18113 return "Teams"; 18114 }, 18115 18116 /** 18117 * @private 18118 * Gets the REST type for the objects that make up the collection - this is "Team". 18119 */ 18120 getRestItemType: function () { 18121 return "Team"; 18122 }, 18123 18124 /** 18125 * @private 18126 * Override default to indicates that the collection supports making 18127 * requests. 18128 */ 18129 supportsRequests: true, 18130 18131 /** 18132 * @private 18133 * Override default to indicate that this object doesn't support subscriptions. 18134 */ 18135 supportsRestItemSubscriptions: false, 18136 18137 /** 18138 * @private 18139 * Retrieve the Teams. This call will re-query the server and refresh the collection. 18140 * 18141 * @returns {finesse.restservices.Teams} 18142 * This Teams object to allow cascading. 18143 */ 18144 get: function () { 18145 // set loaded to false so it will rebuild the collection after the get 18146 this._loaded = false; 18147 // reset collection 18148 this._collection = {}; 18149 // perform get 18150 this._synchronize(); 18151 return this; 18152 } 18153 18154 }); 18155 18156 window.finesse = window.finesse || {}; 18157 window.finesse.restservices = window.finesse.restservices || {}; 18158 window.finesse.restservices.Teams = Teams; 18159 18160 return Teams; 18161 }); 18162 18163 /** 18164 * JavaScript representation of the Finesse SystemInfo object 18165 * 18166 * @requires finesse.clientservices.ClientServices 18167 * @requires Class 18168 * @requires finesse.FinesseBase 18169 * @requires finesse.restservices.RestBase 18170 */ 18171 18172 /** @private */ 18173 define('restservices/SystemInfo',['restservices/RestBase'], function (RestBase) { 18174 18175 var SystemInfo = RestBase.extend(/** @lends finesse.restservices.SystemInfo.prototype */{ 18176 /** 18177 * @private 18178 * Returns whether this object supports subscriptions 18179 */ 18180 supportsSubscriptions: false, 18181 18182 doNotRefresh: true, 18183 18184 /** 18185 * @class 18186 * JavaScript representation of a SystemInfo object. 18187 * 18188 * @augments finesse.restservices.RestBase 18189 * @see finesse.restservices.SystemInfo.Statuses 18190 * @constructs 18191 */ 18192 _fakeConstuctor: function () { 18193 /* This is here to hide the real init constructor from the public docs */ 18194 }, 18195 18196 /** 18197 * @private 18198 * JavaScript representation of a SystemInfo object. Also exposes methods to operate 18199 * on the object against the server. 18200 * 18201 * @param {Object} options 18202 * An object with the following properties:<ul> 18203 * <li><b>id:</b> The id of the object being constructed</li> 18204 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 18205 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 18206 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 18207 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 18208 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 18209 * <li><b>status:</b> {Number} The HTTP status code returned</li> 18210 * <li><b>content:</b> {String} Raw string of response</li> 18211 * <li><b>object:</b> {Object} Parsed object of response</li> 18212 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 18213 * <li><b>errorType:</b> {String} Type of error that was caught</li> 18214 * <li><b>errorMessage:</b> {String} Message associated with error</li> 18215 * </ul></li> 18216 * </ul></li> 18217 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 18218 **/ 18219 init: function (id, callbacks, restObj) 18220 { 18221 this._super(id, callbacks, restObj); 18222 }, 18223 18224 /** 18225 * @private 18226 * Gets the REST class for the current object - this is the SystemInfo object. 18227 */ 18228 getRestClass: function () { 18229 return SystemInfo; 18230 }, 18231 18232 /** 18233 * @private 18234 * Gets the REST type for the current object - this is a "SystemInfo". 18235 */ 18236 getRestType: function () 18237 { 18238 return "SystemInfo"; 18239 }, 18240 18241 _validate: function (obj) 18242 { 18243 return true; 18244 }, 18245 18246 /** 18247 * Returns the status of the Finesse system. 18248 * IN_SERVICE if the Finesse API reports that it is in service, 18249 * OUT_OF_SERVICE otherwise. 18250 * @returns {finesse.restservices.SystemInfo.Statuses} System Status 18251 */ 18252 getStatus: function () { 18253 this.isLoaded(); 18254 return this.getData().status; 18255 }, 18256 18257 /** 18258 * Returns the reason due to which Finesse is OUT OF SERVICE. 18259 * It returns empty string when Finesse status is IN_SERVICE. 18260 * @returns {String} statusReason if finesse is OUT OF SERVICE , or empty string otherwise. 18261 */ 18262 getStatusReason: function () { 18263 this.isLoaded(); 18264 return this.getData().statusReason; 18265 }, 18266 18267 /** 18268 * Returns the current timestamp from this SystemInfo object. 18269 * This is used to calculate time drift delta between server and client. 18270 * @returns {String} Time (GMT): yyyy-MM-dd'T'HH:mm:ss'Z' 18271 */ 18272 getCurrentTimestamp: function () { 18273 this.isLoaded(); 18274 return this.getData().currentTimestamp; 18275 }, 18276 18277 /** 18278 * Getter for the xmpp domain of the system. 18279 * @returns {String} The xmpp domain corresponding to this SystemInfo object. 18280 */ 18281 getXmppDomain: function () { 18282 this.isLoaded(); 18283 return this.getData().xmppDomain; 18284 }, 18285 18286 /** 18287 * Getter for the xmpp pubsub domain of the system. 18288 * @returns {String} The xmpp pubsub domain corresponding to this SystemInfo object. 18289 */ 18290 getXmppPubSubDomain: function () { 18291 this.isLoaded(); 18292 return this.getData().xmppPubSubDomain; 18293 }, 18294 18295 /** 18296 * Getter for the deployment type (UCCE or UCCX). 18297 * @returns {String} "UCCE" or "UCCX" 18298 */ 18299 getDeploymentType: function () { 18300 this.isLoaded(); 18301 return this.getData().deploymentType; 18302 }, 18303 18304 /** 18305 * Returns whether this is a single node deployment or not by checking for the existence of the secondary node in SystemInfo. 18306 * @returns {Boolean} True for single node deployments, false otherwise. 18307 */ 18308 isSingleNode: function () { 18309 var secondary = this.getData().secondaryNode; 18310 if (secondary && secondary.host) { 18311 return false; 18312 } 18313 return true; 18314 }, 18315 18316 /** 18317 * Checks all arguments against the primary and secondary hosts (FQDN) and returns the match. 18318 * This is useful for getting the FQDN of the current Finesse server. 18319 * @param {String} ...arguments[]... - any number of arguments to match against 18320 * @returns {String} FQDN (if properly configured) of the matched host of the primary or secondary node, or undefined if no match is found. 18321 */ 18322 getThisHost: function () { 18323 var i, 18324 primary = this.getData().primaryNode, 18325 secondary = this.getData().secondaryNode; 18326 18327 for (i = 0; (i < arguments.length); i = i + 1) { 18328 if (primary && arguments[i] === primary.host) { 18329 return primary.host; 18330 } else if (secondary && arguments[i] === secondary.host) { 18331 return secondary.host; 18332 } 18333 } 18334 }, 18335 18336 /** 18337 * Checks all arguments against the primary and secondary hosts (FQDN) and returns the other node. 18338 * This is useful for getting the FQDN of the other Finesse server, i.e. for failover purposes. 18339 * @param {String} arguments - any number of arguments to match against 18340 * @returns {String} FQDN (if properly configured) of the alternate node, defaults to primary if no match is found, undefined for single node deployments. 18341 */ 18342 getAlternateHost: function () { 18343 var i, 18344 isPrimary = false, 18345 primary = this.getData().primaryNode, 18346 secondary = this.getData().secondaryNode, 18347 xmppDomain = this.getData().xmppDomain, 18348 alternateHost; 18349 18350 if (primary && primary.host) { 18351 if (xmppDomain === primary.host) { 18352 isPrimary = true; 18353 } 18354 if (secondary && secondary.host) { 18355 if (isPrimary) { 18356 return secondary.host; 18357 } 18358 return primary.host; 18359 } 18360 } 18361 }, 18362 18363 /** 18364 * Gets the peripheral ID that Finesse is connected to. The peripheral 18365 * ID is the ID of the PG Routing Client (PIM). 18366 * 18367 * @returns {String} The peripheral Id if UCCE, or empty string otherwise. 18368 */ 18369 getPeripheralId : function () { 18370 this.isLoaded(); 18371 18372 var peripheralId = this.getData().peripheralId; 18373 if (peripheralId === null) { 18374 return ""; 18375 } else { 18376 return this.getData().peripheralId; 18377 } 18378 }, 18379 18380 /** 18381 * Gets the license. Only apply to UCCX. 18382 * 18383 * @returns {String} The license if UCCX, or empty string otherwise. 18384 */ 18385 getlicense : function () { 18386 this.isLoaded(); 18387 return this.getData().license || ""; 18388 }, 18389 18390 /** 18391 * Gets the systemAuthMode for the current deployment 18392 * 18393 * @returns {String} The System auth mode for current deployment 18394 */ 18395 getSystemAuthMode : function() { 18396 this.isLoaded(); 18397 return this.getData().systemAuthMode; 18398 } 18399 }); 18400 18401 SystemInfo.Statuses = /** @lends finesse.restservices.SystemInfo.Statuses.prototype */ { 18402 /** 18403 * Finesse is in service. 18404 */ 18405 IN_SERVICE: "IN_SERVICE", 18406 /** 18407 * Finesse is not in service. 18408 */ 18409 OUT_OF_SERVICE: "OUT_OF_SERVICE", 18410 /** 18411 * @class SystemInfo status values. 18412 * @constructs 18413 */ 18414 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 18415 18416 }; 18417 18418 window.finesse = window.finesse || {}; 18419 window.finesse.restservices = window.finesse.restservices || {}; 18420 window.finesse.restservices.SystemInfo = SystemInfo; 18421 18422 return SystemInfo; 18423 }); 18424 18425 define('restservices/DialogLogoutActions',[], function () 18426 { 18427 var DialogLogoutActions = /** @lends finesse.restservices.DialogLogoutActions.prototype */ { 18428 18429 /** 18430 * Set this action to close active dialogs when the agent logs out. 18431 */ 18432 CLOSE: "CLOSE", 18433 18434 /** 18435 * Set this action to transfer active dialogs when the agent logs out. 18436 */ 18437 TRANSFER: "TRANSFER", 18438 18439 /** 18440 * @class Actions used to handle tasks that are associated with a given media at logout time. 18441 * 18442 * @constructs 18443 */ 18444 _fakeConstructor: function () 18445 { 18446 }, // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 18447 18448 /** 18449 * Is the given action a valid dialog logout action. 18450 * 18451 * @param {String} action the action to evaluate 18452 * @returns {Boolean} true if the action is valid; false otherwise 18453 */ 18454 isValidAction: function(action) 18455 { 18456 if ( !action ) 18457 { 18458 return false; 18459 } 18460 18461 return DialogLogoutActions.hasOwnProperty(action.toUpperCase()); 18462 } 18463 }; 18464 18465 window.finesse = window.finesse || {}; 18466 window.finesse.restservices = window.finesse.restservices || {}; 18467 window.finesse.restservices.DialogLogoutActions = DialogLogoutActions; 18468 18469 return DialogLogoutActions; 18470 }); 18471 define('restservices/InterruptActions',[], function () 18472 { 18473 var InterruptActions = /** @lends finesse.restservices.InterruptActions.prototype */ 18474 { 18475 /** 18476 * The interrupt will be accepted and the agent will not work on dialogs in this media until the media is no longer interrupted. 18477 */ 18478 ACCEPT: "ACCEPT", 18479 18480 /** 18481 * the interrupt will be ignored and the agent is allowed to work on dialogs while the media is interrupted. 18482 */ 18483 IGNORE: "IGNORE", 18484 18485 /** 18486 * @class 18487 * 18488 * The action to be taken in the event this media is interrupted. The action will be one of the following:<ul> 18489 * <li><b>ACCEPT:</b> the interrupt will be accepted and the agent will not work on dialogs in this media 18490 * until the media is no longer interrupted.</li> 18491 * <li><b>IGNORE:</b> the interrupt will be ignored and the agent is allowed to work on dialogs while the 18492 * media is interrupted.</li></ul> 18493 * 18494 * @constructs 18495 */ 18496 _fakeConstructor: function () 18497 { 18498 }, // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 18499 18500 /** 18501 * Is the given action a valid dialog logout action. 18502 * 18503 * @param {String} action the action to evaluate 18504 * @returns {Boolean} true if the action is valid; false otherwise 18505 */ 18506 isValidAction: function(action) 18507 { 18508 if ( !action ) 18509 { 18510 return false; 18511 } 18512 18513 return InterruptActions.hasOwnProperty(action.toUpperCase()); 18514 } 18515 }; 18516 18517 window.finesse = window.finesse || {}; 18518 window.finesse.restservices = window.finesse.restservices || {}; 18519 window.finesse.restservices.InterruptActions = InterruptActions; 18520 18521 return InterruptActions; 18522 }); 18523 /** 18524 * JavaScript representation of the ReasonCode lookup object. 18525 * This has got reason code related APIs like looking up a reason code with 18526 * its code value and category. 18527 * 18528 */ 18529 18530 /** @private */ 18531 define('restservices/ReasonCodeLookup',['restservices/RestBase', 'utilities/Utilities'], function (RestBase, Utilities) { 18532 18533 var ReasonCodeLookup = RestBase.extend(/** @lends finesse.restservices.ReasonCodeLookup.prototype */{ 18534 /** 18535 * @private 18536 * Returns whether this object supports subscriptions 18537 */ 18538 supportsSubscriptions: false, 18539 18540 doNotRefresh: true, 18541 18542 autoSubscribe: false, 18543 18544 supportsRequests: false, 18545 18546 /** 18547 * @class 18548 * JavaScript representation of a ReasonCodeLookup object. 18549 * 18550 * @constructs 18551 */ 18552 _fakeConstuctor: function () { 18553 /* This is here to hide the real init constructor from the public docs */ 18554 }, 18555 18556 /** 18557 * @private 18558 * JavaScript representation of a ReasonCodeLookup object. Also exposes methods to operate 18559 * on the object against the server. 18560 * 18561 * @param {Object} options 18562 * An object with the following properties:<ul> 18563 * <li><b>id:</b> The id of the object being constructed</li> 18564 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 18565 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 18566 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 18567 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 18568 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 18569 * <li><b>status:</b> {Number} The HTTP status code returned</li> 18570 * <li><b>content:</b> {String} Raw string of response</li> 18571 * <li><b>object:</b> {Object} Parsed object of response</li> 18572 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 18573 * <li><b>errorType:</b> {String} Type of error that was caught</li> 18574 * <li><b>errorMessage:</b> {String} Message associated with error</li> 18575 * </ul></li> 18576 * </ul></li> 18577 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 18578 **/ 18579 init: function (options){ 18580 this._super(options); 18581 }, 18582 18583 /** 18584 * Do get disabled. 18585 */ 18586 doGet: function(handlers) { 18587 return; 18588 }, 18589 18590 /** 18591 * @private 18592 * Gets the REST class for the current object - this is the ReasonCodeLookup object. 18593 */ 18594 getRestClass: function () { 18595 return ReasonCodeLookup; 18596 }, 18597 18598 /** 18599 * @private 18600 * Gets the REST type for the current object - this is a "ReasonCodeLookup". 18601 */ 18602 getRestType: function () { 18603 return "ReasonCode"; 18604 }, 18605 18606 18607 /** 18608 * @private 18609 * Parses a uriString to retrieve the id portion 18610 * @param {String} uriString 18611 * @return {String} id 18612 */ 18613 _parseIdFromUriString : function (uriString) { 18614 return Utilities.getId(uriString); 18615 }, 18616 18617 /** 18618 * Performs a GET against the Finesse server looking up the reason code 18619 * with its reason code value, and category specified. 18620 * Note that there is no return value; use the success handler to process a 18621 * valid return. 18622 * 18623 * @param {finesse.interfaces.RequestHandlers} handlers 18624 * An object containing the handlers for the request 18625 * @param {String} reasonCodeValue The code for the reason code to lookup 18626 * @param {String} reasonCodeCategory The category for the reason code to lookup 18627 * 18628 */ 18629 lookupReasonCode : function (handlers, reasonCodeValue, reasonCodeCategory) { 18630 var self = this, contentBody, reasonCode, url; 18631 contentBody = {}; 18632 18633 url = this.getRestUrl(); 18634 url = url + "?category=" + reasonCodeCategory + "&code=" + reasonCodeValue; 18635 this.restRequest(url, { 18636 method: 'GET', 18637 success: function (rsp) { 18638 reasonCode = { 18639 uri: rsp.object.ReasonCode.uri, 18640 label: rsp.object.ReasonCode.label, 18641 id: self._parseIdFromUriString(rsp.object.ReasonCode.uri) 18642 }; 18643 handlers.success(reasonCode); 18644 }, 18645 error: function (rsp) { 18646 handlers.error(rsp); 18647 }, 18648 content: contentBody 18649 }); 18650 } 18651 18652 }); 18653 18654 18655 window.finesse = window.finesse || {}; 18656 window.finesse.restservices = window.finesse.restservices || {}; 18657 window.finesse.restservices.ReasonCodeLookup = ReasonCodeLookup; 18658 18659 return ReasonCodeLookup; 18660 }); 18661 18662 /** 18663 * Provides standard way resolve message keys with substitution 18664 * 18665 * @requires finesse.container.I18n or gadgets.Prefs 18666 */ 18667 18668 // Add Utilities to the finesse.utilities namespace 18669 define('utilities/I18n',['utilities/Utilities'], function (Utilities) { 18670 var I18n = (function () { 18671 18672 /** 18673 * Shortcut to finesse.container.I18n.getMsg or gadgets.Prefs.getMsg 18674 * @private 18675 */ 18676 var _getMsg; 18677 18678 return { 18679 /** 18680 * Provides a message resolver for this utility singleton. 18681 * @param {Function} getMsg 18682 * A function that returns a string given a message key. 18683 * If the key is not found, this function must return 18684 * something that tests false (i.e. undefined or ""). 18685 */ 18686 setGetter : function (getMsg) { 18687 _getMsg = getMsg; 18688 }, 18689 18690 /** 18691 * Resolves the given message key, also performing substitution. 18692 * This generic utility will use a custom function to resolve the key 18693 * provided by finesse.utilities.I18n.setGetter. Otherwise, it will 18694 * discover either finesse.container.I18n.getMsg or gadgets.Prefs.getMsg 18695 * upon the first invocation and store that reference for efficiency. 18696 * 18697 * Since this will construct a new gadgets.Prefs object, it is recommended 18698 * for gadgets to explicitly provide the setter to prevent duplicate 18699 * gadgets.Prefs objects. This does not apply if your gadget does not need 18700 * access to gadgets.Prefs other than getMsg. 18701 * 18702 * @param {String} key 18703 * The key to lookup 18704 * @param {String} arguments 18705 * Arguments for substitution 18706 * @returns {String/Function} 18707 * The resolved string if successful, otherwise a function that returns 18708 * a '???' string that can also be casted into a string. 18709 */ 18710 getString : function (key) { 18711 var prefs, i, retStr, noMsg, getFailed = "", args; 18712 if (!_getMsg) { 18713 if (finesse.container && finesse.container.I18n) { 18714 _getMsg = finesse.container.I18n.getMsg; 18715 } else if (gadgets) { 18716 prefs = new gadgets.Prefs(); 18717 _getMsg = prefs.getMsg; 18718 } 18719 } 18720 18721 try { 18722 retStr = _getMsg(key); 18723 } catch (e) { 18724 getFailed = "finesse.utilities.I18n.getString(): invalid _getMsg"; 18725 } 18726 18727 if (retStr) { 18728 // Lookup was successful, perform substitution (if any) 18729 args = [ retStr ]; 18730 for (i = 1; i < arguments.length; i += 1) { 18731 args.push(arguments[i]); 18732 } 18733 return Utilities.formatString.apply(this, args); 18734 } 18735 // We want a function because jQuery.html() and jQuery.text() is smart enough to invoke it. 18736 /** @private */ 18737 noMsg = function () { 18738 return "???" + key + "???" + getFailed; 18739 }; 18740 // We overload the toString() of this "function" to allow JavaScript to cast it into a string 18741 // For example, var myMsg = "something " + finesse.utilities.I18n.getMsg("unresolvable.key"); 18742 /** @private */ 18743 noMsg.toString = function () { 18744 return "???" + key + "???" + getFailed; 18745 }; 18746 return noMsg; 18747 18748 } 18749 }; 18750 }()); 18751 18752 window.finesse = window.finesse || {}; 18753 window.finesse.utilities = window.finesse.utilities || {}; 18754 window.finesse.utilities.I18n = I18n; 18755 18756 return I18n; 18757 }); 18758 18759 /** 18760 * Logging.js: provides simple logging for clients to use and overrides synchronous native methods: alert(), confirm(), and prompt(). 18761 * 18762 * On Firefox, it will hook into console for logging. On IE, it will log to the status bar. 18763 */ 18764 // Add Utilities to the finesse.utilities namespace 18765 define('utilities/Logger',[], function () { 18766 var Logger = (function () { 18767 18768 var 18769 18770 /** @private **/ 18771 debugOn, 18772 18773 /** 18774 * Pads a single digit number for display purposes (e.g. '4' shows as '04') 18775 * @param num is the number to pad to 2 digits 18776 * @returns a two digit padded string 18777 * @private 18778 */ 18779 padTwoDigits = function (num) { 18780 return (num < 10) ? '0' + num : num; 18781 }, 18782 18783 /** 18784 * Checks to see if we have a console - this allows us to support Firefox or IE. 18785 * @returns {Boolean} True for Firefox, False for IE 18786 * @private 18787 */ 18788 hasConsole = function () { 18789 var retval = false; 18790 try 18791 { 18792 if (window.console !== undefined) 18793 { 18794 retval = true; 18795 } 18796 } 18797 catch (err) 18798 { 18799 retval = false; 18800 } 18801 18802 return retval; 18803 }, 18804 18805 /** 18806 * Gets a timestamp. 18807 * @returns {String} is a timestamp in the following format: HH:MM:SS 18808 * @private 18809 */ 18810 getTimeStamp = function () { 18811 var date = new Date(), timeStr; 18812 timeStr = padTwoDigits(date.getHours()) + ":" + padTwoDigits(date.getMinutes()) + ":" + padTwoDigits(date.getSeconds()); 18813 18814 return timeStr; 18815 }; 18816 18817 return { 18818 /** 18819 * Enable debug mode. Debug mode may impact performance on the UI. 18820 * 18821 * @param {Boolean} enable 18822 * True to enable debug logging. 18823 * @private 18824 */ 18825 setDebug : function (enable) { 18826 debugOn = enable; 18827 }, 18828 18829 /** 18830 * Logs a string as DEBUG. 18831 * 18832 * @param str is the string to log. 18833 * @private 18834 */ 18835 log : function (str) { 18836 var timeStr = getTimeStamp(); 18837 18838 if (debugOn) { 18839 if (hasConsole()) 18840 { 18841 window.console.log(timeStr + ": " + "DEBUG" + " - " + str); 18842 } 18843 } 18844 }, 18845 18846 /** 18847 * Logs a string as INFO. 18848 * 18849 * @param str is the string to log. 18850 * @private 18851 */ 18852 info : function (str) { 18853 var timeStr = getTimeStamp(); 18854 18855 if (hasConsole()) 18856 { 18857 window.console.info(timeStr + ": " + "INFO" + " - " + str); 18858 } 18859 }, 18860 18861 /** 18862 * Logs a string as WARN. 18863 * 18864 * @param str is the string to log. 18865 * @private 18866 */ 18867 warn : function (str) { 18868 var timeStr = getTimeStamp(); 18869 18870 if (hasConsole()) 18871 { 18872 window.console.warn(timeStr + ": " + "WARN" + " - " + str); 18873 } 18874 }, 18875 /** 18876 * Logs a string as ERROR. 18877 * 18878 * @param str is the string to log. 18879 * @private 18880 */ 18881 error : function (str) { 18882 var timeStr = getTimeStamp(); 18883 18884 if (hasConsole()) 18885 { 18886 window.console.error(timeStr + ": " + "ERROR" + " - " + str); 18887 } 18888 } 18889 }; 18890 }()); 18891 18892 return Logger; 18893 }); 18894 18895 /** 18896 * BackSpaceHandler.js: provides functionality to prevent the page from navigating back and hence losing the unsaved data when backspace is pressed. 18897 * 18898 */ 18899 define('utilities/BackSpaceHandler',[], function () { 18900 var eventCallback = function(event) { 18901 var doPrevent = false, d = event.srcElement || event.target; 18902 if (event.keyCode === 8) { 18903 if ((d.tagName.toUpperCase() === 'INPUT' && (d.type 18904 .toUpperCase() === 'TEXT' 18905 || d.type.toUpperCase() === 'PASSWORD' 18906 || d.type.toUpperCase() === 'FILE' 18907 || d.type.toUpperCase() === 'SEARCH' 18908 || d.type.toUpperCase() === 'EMAIL' 18909 || d.type.toUpperCase() === 'NUMBER' || d.type 18910 .toUpperCase() === 'DATE')) 18911 || d.tagName.toUpperCase() === 'TEXTAREA') { 18912 doPrevent = d.readOnly || d.disabled; 18913 } else { 18914 //if HTML content is editable doPrevent will be false and vice versa 18915 doPrevent = (!d.isContentEditable); 18916 } 18917 } 18918 18919 if (doPrevent) { 18920 event.preventDefault(); 18921 } 18922 }; 18923 18924 if (window.addEventListener) { 18925 window.addEventListener('keydown', eventCallback); 18926 } else if (window.attachEvent) { 18927 window.attachEvent('onkeydown', eventCallback); 18928 } else { 18929 window.console.error("Unable to attach backspace handler event "); 18930 } 18931 }); 18932 /* using variables before they are defined. 18933 */ 18934 /*global navigator,unescape,sessionStorage,localStorage,_initSessionList,_initSessionListComplete */ 18935 18936 /** 18937 * Allows each gadget to communicate with the server to send logs. 18938 */ 18939 18940 /** 18941 * @class 18942 * @private 18943 * Allows each product to initialize its method of storage 18944 */ 18945 define('cslogger/FinesseLogger',["clientservices/ClientServices", "utilities/Utilities"], function (ClientServices, Utilities) { 18946 18947 var FinesseLogger = (function () { 18948 18949 var 18950 18951 /** 18952 * Array use to collect ongoing logs in memory 18953 * @private 18954 */ 18955 _logArray = [], 18956 18957 /** 18958 * The final data string sent to the server, =_logArray.join 18959 * @private 18960 */ 18961 _logStr = "", 18962 18963 /** 18964 * Keep track of size of log 18965 * @private 18966 */ 18967 _logSize = 0, 18968 18969 /** 18970 * Flag to keep track show/hide of send log link 18971 * @private 18972 */ 18973 _sendLogShown = false, 18974 18975 /** 18976 * Flag to keep track if local log initialized 18977 * @private 18978 */ 18979 _loggingInitialized = false, 18980 18981 18982 /** 18983 * local log size limit 18984 * @private 18985 */ 18986 _maxLocalStorageSize = 5000000, 18987 18988 /** 18989 * half local log size limit 18990 * @private 18991 */ 18992 _halfMaxLocalStorageSize = 0.5*_maxLocalStorageSize, 18993 18994 18995 /** 18996 * threshold for purge 18997 * @private 18998 */ 18999 _purgeStartPercent = 0.75, 19000 19001 /** 19002 * log item prefix 19003 * @private 19004 */ 19005 _linePrefix = null, 19006 19007 /** 19008 * locallog session 19009 * @private 19010 */ 19011 _session = null, 19012 19013 /** 19014 * Flag to keep track show/hide of send log link 19015 * @private 19016 */ 19017 _sessionKey = null, 19018 /** 19019 * Log session metadata 19020 * @private 19021 */ 19022 _logInfo = {}, 19023 19024 /** 19025 * Flag to find sessions 19026 * @private 19027 */ 19028 _findSessionsObj = null, 19029 19030 /** 19031 * Wrap up console.log esp. for IE9 19032 * @private 19033 */ 19034 _myConsoleLog = function (str) { 19035 if (window.console !== undefined) { 19036 window.console.log(str); 19037 } 19038 }, 19039 /** 19040 * Initialize the Local Logging 19041 * @private 19042 */ 19043 _initLogging = function () { 19044 if (_loggingInitialized) { 19045 return; 19046 } 19047 //Build a new store 19048 _session = sessionStorage.getItem("finSessKey"); 19049 //if the _session is null or empty, skip the init 19050 if (!_session) { 19051 return; 19052 } 19053 _sessionKey = "Fi"+_session; 19054 _linePrefix = _sessionKey + "_"; 19055 _logInfo = {}; 19056 _logInfo.name = _session; 19057 _logInfo.size = 0; 19058 _logInfo.head = 0; 19059 _logInfo.tail = 0; 19060 _logInfo.startTime = new Date().getTime(); 19061 _loggingInitialized = true; 19062 _initSessionList(); 19063 }, 19064 19065 /** 19066 * get total data size 19067 * 19068 * @return {Integer} which is the amount of data stored in local storage. 19069 * @private 19070 */ 19071 _getTotalData = function () 19072 { 19073 var sessName, sessLogInfoStr,sessLogInfoObj, sessionsInfoObj, totalData = 0, 19074 sessionsInfoStr = localStorage.getItem("FinesseSessionsInfo"); 19075 if (!sessionsInfoStr) { 19076 return 0; 19077 } 19078 sessionsInfoObj = JSON.parse(sessionsInfoStr); 19079 19080 for (sessName in sessionsInfoObj.sessions) 19081 { 19082 if (sessionsInfoObj.sessions.hasOwnProperty(sessName)) { 19083 sessLogInfoStr = localStorage.getItem("Fi" + sessName); 19084 if (!sessLogInfoStr) { 19085 _myConsoleLog("_getTotalData failed to get log info for "+sessName); 19086 } 19087 else { 19088 sessLogInfoObj = JSON.parse(sessLogInfoStr); 19089 totalData = totalData + sessLogInfoObj.size; 19090 } 19091 } 19092 } 19093 19094 return totalData; 19095 }, 19096 19097 /** 19098 * Remove lines from tail up until store size decreases to half of max size limit. 19099 * 19100 * @private 19101 */ 19102 _purgeCurrentSession = function() { 19103 var curStoreSize, purgedSize=0, line, tailKey, secLogInfoStr, logInfoStr, theLogInfo; 19104 curStoreSize = _getTotalData(); 19105 if (curStoreSize < _halfMaxLocalStorageSize) { 19106 return; 19107 } 19108 logInfoStr = localStorage.getItem(_sessionKey); 19109 if (!logInfoStr) { 19110 return; 19111 } 19112 theLogInfo = JSON.parse(logInfoStr); 19113 //_myConsoleLog("Starting _purgeCurrentSession() - currentStoreSize=" + curStoreSize); 19114 while(curStoreSize > _halfMaxLocalStorageSize) { 19115 try { 19116 tailKey = _sessionKey+"_"+theLogInfo.tail; 19117 line = localStorage.getItem(tailKey); 19118 if (line) { 19119 purgedSize = purgedSize +line.length; 19120 localStorage.removeItem(tailKey); 19121 curStoreSize = curStoreSize - line.length; 19122 theLogInfo.size = theLogInfo.size - line.length; 19123 } 19124 } 19125 catch (err) { 19126 _myConsoleLog("purgeCurrentSession encountered err="+err); 19127 } 19128 if (theLogInfo.tail < theLogInfo.head) { 19129 theLogInfo.tail = theLogInfo.tail + 1; 19130 } 19131 else { 19132 break; 19133 } 19134 } 19135 //purge stops here, we need to update session's meta data in storage 19136 secLogInfoStr = localStorage.getItem(_sessionKey); 19137 if (!secLogInfoStr) { 19138 //somebody cleared the localStorage 19139 return; 19140 } 19141 19142 //_myConsoleLog("In _purgeCurrentSession() - after purging current session, currentStoreSize=" + curStoreSize); 19143 //_myConsoleLog("In _purgeCurrentSession() - after purging purgedSize=" + purgedSize); 19144 //_myConsoleLog("In _purgeCurrentSession() - after purging logInfo.size=" + theLogInfo.size); 19145 //_myConsoleLog("In _purgeCurrentSession() - after purging logInfo.tail=" + theLogInfo.tail); 19146 localStorage.setItem(_sessionKey, JSON.stringify(theLogInfo)); 19147 _myConsoleLog("Done _purgeCurrentSession() - currentStoreSize=" + curStoreSize); 19148 }, 19149 19150 /** 19151 * Purge a session 19152 * 19153 * @param sessionName is the name of the session 19154 * @return {Integer} which is the current amount of data purged 19155 * @private 19156 */ 19157 _purgeSession = function (sessionName) { 19158 var theLogInfo, logInfoStr, sessionsInfoStr, sessionsInfoObj; 19159 //Get the session logInfo 19160 logInfoStr = localStorage.getItem("Fi" + sessionName); 19161 if (!logInfoStr) { 19162 _myConsoleLog("_purgeSession failed to get logInfo for "+sessionName); 19163 return 0; 19164 } 19165 theLogInfo = JSON.parse(logInfoStr); 19166 19167 //Note: This assumes that we don't crash in the middle of purging 19168 //=> if we do then it should get deleted next time 19169 //Purge tail->head 19170 while (theLogInfo.tail <= theLogInfo.head) 19171 { 19172 try { 19173 localStorage.removeItem("Fi" + sessionName + "_" + theLogInfo.tail); 19174 theLogInfo.tail = theLogInfo.tail + 1; 19175 } 19176 catch (err) { 19177 _myConsoleLog("In _purgeSession err="+err); 19178 break; 19179 } 19180 } 19181 19182 //Remove the entire session 19183 localStorage.removeItem("Fi" + sessionName); 19184 19185 //Update FinesseSessionsInfo 19186 sessionsInfoStr = localStorage.getItem("FinesseSessionsInfo"); 19187 if (!sessionsInfoStr) { 19188 _myConsoleLog("_purgeSession could not get sessions Info, it was cleared?"); 19189 return 0; 19190 } 19191 sessionsInfoObj = JSON.parse(sessionsInfoStr); 19192 if (sessionsInfoObj.sessions !== null) 19193 { 19194 delete sessionsInfoObj.sessions[sessionName]; 19195 19196 sessionsInfoObj.total = sessionsInfoObj.total - 1; 19197 sessionsInfoObj.lastWrittenBy = _session; 19198 localStorage.setItem("FinesseSessionsInfo", JSON.stringify(sessionsInfoObj)); 19199 } 19200 19201 return theLogInfo.size; 19202 }, 19203 19204 /** 19205 * purge old sessions 19206 * 19207 * @param storeSize 19208 * @return {Boolean} whether purging reaches its target 19209 * @private 19210 */ 19211 _purgeOldSessions = function (storeSize) { 19212 var sessionsInfoStr, purgedSize = 0, sessName, sessions, curStoreSize, activeSession, sessionsInfoObj; 19213 sessionsInfoStr = localStorage.getItem("FinesseSessionsInfo"); 19214 if (!sessionsInfoStr) { 19215 _myConsoleLog("Could not get FinesseSessionsInfo"); 19216 return true; 19217 } 19218 sessionsInfoObj = JSON.parse(sessionsInfoStr); 19219 curStoreSize = _getTotalData(); 19220 19221 activeSession = _session; 19222 sessions = sessionsInfoObj.sessions; 19223 for (sessName in sessions) { 19224 if (sessions.hasOwnProperty(sessName)) { 19225 if (sessName !== activeSession) { 19226 purgedSize = purgedSize + _purgeSession(sessName); 19227 if ((curStoreSize-purgedSize) < _halfMaxLocalStorageSize) { 19228 return true; 19229 } 19230 } 19231 } 19232 } 19233 //purge is not done, so return false 19234 return false; 19235 }, 19236 19237 /** 19238 * handle insert error 19239 * 19240 * @param error 19241 * @private 19242 */ 19243 _insertLineHandleError = function (error) { 19244 _myConsoleLog(error); 19245 }, 19246 19247 /** 19248 * check storage data size and if need purge 19249 * @private 19250 */ 19251 _checkSizeAndPurge = function () { 19252 var purgeIsDone=false, totalSize = _getTotalData(); 19253 if (totalSize > 0.75*_maxLocalStorageSize) { 19254 _myConsoleLog("in _checkSizeAndPurge, totalSize ("+totalSize+") exceeds limit"); 19255 purgeIsDone = _purgeOldSessions(totalSize); 19256 if (purgeIsDone) { 19257 _myConsoleLog("in _checkSizeAndPurge after purging old session, purge is done"); 19258 } 19259 else { 19260 //after all old sessions purged, still need purge 19261 totalSize = _getTotalData(); 19262 if (totalSize > 0.75*_maxLocalStorageSize) { 19263 _myConsoleLog("in _checkSizeAndPurge after purging old session,still needs purging, now storeSize ("+totalSize+")"); 19264 _purgeCurrentSession(); 19265 _myConsoleLog("in _checkSizeAndPurge done purging current session."); 19266 } 19267 } 19268 } 19269 }, 19270 19271 /** 19272 * check if the session is already in meta data 19273 * 19274 * @param metaData 19275 * @param sessionName 19276 * @return {Boolean} true if session has metaData (false otherwise) 19277 * @private 19278 */ 19279 _sessionsInfoContains = function (metaData, sessionName) { 19280 if (metaData && metaData.sessions && metaData.sessions.hasOwnProperty(sessionName)) { 19281 return true; 19282 } 19283 return false; 19284 }, 19285 19286 19287 /** 19288 * setup sessions in local storage 19289 * 19290 * @param logInfo 19291 * @private 19292 */ 19293 _getAndSetNumberOfSessions = function (logInfo) { 19294 var numOfSessionsPass1, numOfSessionsPass2, l; 19295 numOfSessionsPass1 = localStorage.getItem("FinesseSessionsInfo"); 19296 if (numOfSessionsPass1 === null) { 19297 //Init first time 19298 numOfSessionsPass1 = {}; 19299 numOfSessionsPass1.total = 1; 19300 numOfSessionsPass1.sessions = {}; 19301 numOfSessionsPass1.sessions[logInfo.name] = logInfo.startTime; 19302 numOfSessionsPass1.lastWrittenBy = logInfo.name; 19303 localStorage.setItem("FinesseSessionsInfo", JSON.stringify(numOfSessionsPass1)); 19304 } 19305 else { 19306 numOfSessionsPass1 = JSON.parse(numOfSessionsPass1); 19307 //check if the session is already in the FinesseSessionSInfo 19308 if (_sessionsInfoContains(numOfSessionsPass1, logInfo.name)) { 19309 return; 19310 } 19311 //Save numOfSessionsPass1 19312 numOfSessionsPass1.total = parseInt(numOfSessionsPass1.total, 10) + 1; 19313 numOfSessionsPass1.sessions[logInfo.name] = logInfo.startTime; 19314 numOfSessionsPass1.lastWrittenBy = logInfo.name; 19315 localStorage.setItem("FinesseSessionsInfo", JSON.stringify(numOfSessionsPass1)); 19316 numOfSessionsPass2 = localStorage.getItem("FinesseSessionsInfo"); 19317 if (!numOfSessionsPass2) { 19318 _myConsoleLog("Could not get FinesseSessionsInfo"); 19319 return; 19320 } 19321 numOfSessionsPass2 = JSON.parse(numOfSessionsPass2); 19322 //in future we need to confirm the numOfSessionsPass2 is the same as numOfSessionsPass1 19323 ////if (numOfSessionsPass1.lastWrittenBy !== numOfSessionsPass2.lastWrittenBy) { 19324 //// _myConsoleLog("Rebuild sessions"); 19325 //// _sessionTimerId = setTimeout(_initSessionList, 10000); 19326 ////} 19327 ////else { 19328 //// _sessionTimerId = null; 19329 ////callback(numOfSessionsPass2.sessions); 19330 ////} 19331 } 19332 if (!localStorage.getItem(_sessionKey)) { 19333 localStorage.setItem(_sessionKey, JSON.stringify(_logInfo)); 19334 } 19335 }, 19336 19337 19338 /** 19339 * init session list 19340 * @private 19341 */ 19342 _initSessionList = function () { 19343 _getAndSetNumberOfSessions(_logInfo); 19344 }, 19345 19346 /** 19347 * do the real store of log line 19348 * 19349 * @param line 19350 * @private 19351 */ 19352 _persistLine = function (line) { 19353 var key, logInfoStr; 19354 logInfoStr = localStorage.getItem(_sessionKey); 19355 if (logInfoStr === null) { 19356 return; 19357 } 19358 _logInfo = JSON.parse(logInfoStr); 19359 _logInfo.head = _logInfo.head + 1; 19360 key = _linePrefix + _logInfo.head; 19361 localStorage.setItem(key, line); 19362 //Save the size 19363 _logInfo.size = _logInfo.size + line.length; 19364 if (_logInfo.tail === 0) { 19365 _logInfo.tail = _logInfo.head; 19366 } 19367 19368 localStorage.setItem(_sessionKey, JSON.stringify(_logInfo)); 19369 _checkSizeAndPurge(); 19370 }, 19371 19372 /** 19373 * Insert a line into the localStorage. 19374 * 19375 * @param line line to be inserted 19376 * @private 19377 */ 19378 _insertLine = function (line) { 19379 //_myConsoleLog("_insertLine: [" + line + "]"); 19380 //Write the next line to localStorage 19381 try { 19382 //Persist the line 19383 _persistLine(line); 19384 } 19385 catch (err) { 19386 _myConsoleLog("error in _insertLine(), err="+err); 19387 //_insertLineHandleError(err); 19388 } 19389 }, 19390 19391 19392 /** 19393 * Clear the local storage 19394 * @private 19395 */ 19396 _clearLocalStorage = function() { 19397 localStorage.clear(); 19398 19399 }, 19400 19401 /** 19402 * Collect logs when onCollect called 19403 * 19404 * @param data 19405 * @private 19406 */ 19407 _collectMethod = function(data) { 19408 //Size of log should not exceed 1.5MB 19409 var info, maxLength = 1572864; 19410 19411 //add size buffer equal to the size of info to be added when publish 19412 info = Utilities.getSanitizedUserAgentString() + " "; 19413 info = escape(info); 19414 19415 //If log was empty previously, fade in buttons 19416 if (!_sendLogShown) { 19417 //call the fadeInSendLog() in Footer 19418 finesse.modules.Footer.sendLogAppear(); 19419 _sendLogShown = true; 19420 _logSize = info.length; 19421 } 19422 19423 //if local storage logging is enabled, then insert the log into local storage 19424 if (window.sessionStorage.getItem('enableLocalLog')==='true') { 19425 if (data) { 19426 if (data.length>0 && data.substring(0,1) === '\n') { 19427 _insertLine(data.substring(1)); 19428 } 19429 else { 19430 _insertLine(data); 19431 } 19432 } 19433 } 19434 19435 //escape all data to get accurate size (shindig will escape when it builds request) 19436 //escape 6 special chars for XML: &<>"'\n 19437 data = data.replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/>/g, ">").replace(/</g, "<").replace(/\n/g, " "); 19438 data = escape(data+"\n"); 19439 19440 if (data.length < maxLength){ 19441 //make room for new data if log is exceeding max length 19442 while (_logSize + data.length > maxLength) { 19443 _logSize -= (_logArray.shift()).length; 19444 } 19445 } 19446 19447 //Else push the log into memory, increment the log size 19448 _logArray.push(data); 19449 19450 //inc the size accordingly 19451 _logSize+=data.length; 19452 19453 }; 19454 19455 return { 19456 19457 /** 19458 * @private 19459 * Initiate FinesseLogger. 19460 */ 19461 init: function () { 19462 ClientServices.subscribe("finesse.clientLogging.*", _collectMethod); 19463 _initLogging(); 19464 }, 19465 19466 /** 19467 * @private 19468 * Clear all items stored in localStorage. 19469 */ 19470 clear : function () { 19471 _clearLocalStorage(); 19472 }, 19473 19474 /** 19475 * @private 19476 * Initialize the local storage logging. 19477 */ 19478 initLocalLog: function () { 19479 _initLogging(); 19480 }, 19481 19482 /** 19483 * @private 19484 * Inserts a line into the localStorage. 19485 * @param line to insert 19486 */ 19487 localLog : function (line) { 19488 _insertLine(line); 19489 }, 19490 19491 /** 19492 * @ignore 19493 * Publish logs to server and clear the memory 19494 * 19495 * @param userObj 19496 * @param options 19497 * @param callBack 19498 */ 19499 publish: function(userObj, options, callBack) { 19500 // Avoid null references. 19501 options = options || {}; 19502 callBack = callBack || {}; 19503 19504 if (callBack.sending === "function") { 19505 callBack.sending(); 19506 } 19507 19508 //logs the basic version and machine info and escaped new line 19509 _logStr = Utilities.getSanitizedUserAgentString() + " "; 19510 19511 //join the logs to correct string format 19512 _logStr += unescape(_logArray.join("")); 19513 19514 //turning log string to JSON obj 19515 var logObj = { 19516 ClientLog: { 19517 logData : _logStr //_logStr 19518 } 19519 }, 19520 tmpOnAdd = (options.onAdd && typeof options.onAdd === "function")? options.onAdd : function(){}; 19521 /** @private */ 19522 options.onAdd = function(){ 19523 tmpOnAdd(); 19524 _logArray.length = 0; _logSize =0; 19525 _sendLogShown = false; 19526 }; 19527 //adding onLoad to the callbacks, this is the subscribe success case for the first time user subscribe to the client log node 19528 /** @private */ 19529 options.onLoad = function (clientLogObj) { 19530 clientLogObj.sendLogs(logObj,{ 19531 error: callBack.error 19532 }); 19533 }; 19534 19535 userObj.getClientLog(options); 19536 } 19537 }; 19538 }()); 19539 19540 window.finesse = window.finesse || {}; 19541 window.finesse.cslogger = window.finesse.cslogger || {}; 19542 /** @private */ 19543 window.finesse.cslogger.FinesseLogger = FinesseLogger; 19544 19545 return FinesseLogger; 19546 }); 19547 19548 /** 19549 * Contains a list of topics used for containerservices pubsub. 19550 * 19551 */ 19552 19553 /** 19554 * @class 19555 * Contains a list of topics with some utility functions. 19556 */ 19557 /** @private */ 19558 define('containerservices/Topics',[], function () { 19559 19560 var Topics = (function () { 19561 19562 /** 19563 * The namespace prepended to all Finesse topics. 19564 */ 19565 this.namespace = "finesse.containerservices"; 19566 19567 /** 19568 * @private 19569 * Gets the full topic name with the ContainerServices namespace prepended. 19570 * @param {String} topic 19571 * The topic category. 19572 * @returns {String} 19573 * The full topic name with prepended namespace. 19574 */ 19575 var _getNSTopic = function (topic) { 19576 return this.namespace + "." + topic; 19577 }; 19578 19579 19580 19581 /** @scope finesse.containerservices.Topics */ 19582 return { 19583 /** 19584 * @private 19585 * request channel. */ 19586 REQUESTS: _getNSTopic("requests"), 19587 19588 /** 19589 * @private 19590 * reload gadget channel. */ 19591 RELOAD_GADGET: _getNSTopic("reloadGadget"), 19592 19593 /** 19594 * @private 19595 * Convert a Finesse REST URI to a OpenAjax compatible topic name. 19596 */ 19597 getTopic: function (restUri) { 19598 //The topic should not start with '/' else it will get replaced with 19599 //'.' which is invalid. 19600 //Thus, remove '/' if it is at the beginning of the string 19601 if (restUri.indexOf('/') === 0) { 19602 restUri = restUri.substr(1); 19603 } 19604 19605 //Replace every instance of "/" with ".". This is done to follow the 19606 //OpenAjaxHub topic name convention. 19607 return restUri.replace(/\//g, "."); 19608 } 19609 }; 19610 }()); 19611 19612 window.finesse = window.finesse || {}; 19613 window.finesse.containerservices = window.finesse.containerservices || {}; 19614 window.finesse.containerservices.Topics = Topics; 19615 19616 /** @namespace JavaScript class objects and methods to handle gadget container services.*/ 19617 finesse.containerservices = finesse.containerservices || {}; 19618 19619 return Topics; 19620 }); 19621 19622 /** The following comment is to prevent jslint errors about 19623 * using variables before they are defined. 19624 */ 19625 /*global finesse*/ 19626 19627 /** 19628 * Per containerservices request, publish to the OpenAjax gadget pubsub infrastructure. 19629 * 19630 * @requires OpenAjax, finesse.containerservices.Topics 19631 */ 19632 19633 /** @private */ 19634 define('containerservices/MasterPublisher',[ 19635 "utilities/Utilities", 19636 "containerservices/Topics" 19637 ], 19638 function (Utilities, Topics) { 19639 19640 var MasterPublisher = function () { 19641 19642 var 19643 19644 /** 19645 * Reference to the gadget pubsub Hub instance. 19646 * @private 19647 */ 19648 _hub = gadgets.Hub, 19649 19650 /** 19651 * Reference to the Topics class. 19652 * @private 19653 */ 19654 _topics = Topics, 19655 19656 /** 19657 * Reference to conversion utilities class. 19658 * @private 19659 */ 19660 _utils = Utilities, 19661 19662 /** 19663 * References to ClientServices logger methods 19664 * @private 19665 */ 19666 _logger = { 19667 log: finesse.clientservices.ClientServices.log 19668 }, 19669 19670 /** 19671 * The types of possible request types supported when listening to the 19672 * requests channel. Each request type could result in different operations. 19673 * @private 19674 */ 19675 _REQTYPES = { 19676 ACTIVETAB: "ActiveTabReq", 19677 SET_ACTIVETAB: "SetActiveTabReq", 19678 RELOAD_GADGET: "ReloadGadgetReq" 19679 }, 19680 19681 /** 19682 * Handles client requests made to the request topic. The type of the 19683 * request is described in the "type" property within the data payload. Each 19684 * type can result in a different operation. 19685 * @param {String} topic 19686 * The topic which data was published to. 19687 * @param {Object} data 19688 * The data containing requests information published by clients. 19689 * @param {String} data.type 19690 * The type of the request. Supported: "ActiveTabReq", "SetActiveTabReq", "ReloadGadgetReq" 19691 * @param {Object} data.data 19692 * May contain data relevant for the particular requests. 19693 * @param {String} [data.invokeID] 19694 * The ID used to identify the request with the response. The invoke ID 19695 * will be included in the data in the publish to the topic. It is the 19696 * responsibility of the client to correlate the published data to the 19697 * request made by using the invoke ID. 19698 * @private 19699 */ 19700 _clientRequestHandler = function (topic, data) { 19701 19702 //Ensure a valid data object with "type" and "data" properties. 19703 if (typeof data === "object" && 19704 typeof data.type === "string" && 19705 typeof data.data === "object") { 19706 switch (data.type) { 19707 case _REQTYPES.ACTIVETAB: 19708 _hub.publish("finesse.containerservices.activeTab", finesse.container.Tabs.getActiveTab()); 19709 break; 19710 case _REQTYPES.SET_ACTIVETAB: 19711 if (typeof data.data.id === "string") { 19712 _logger.log("Handling request to activate tab: " + data.data.id); 19713 if (!finesse.container.Tabs.activateTab(data.data.id)) { 19714 _logger.log("No tab found with id: " + data.data.id); 19715 } 19716 } 19717 break; 19718 case _REQTYPES.RELOAD_GADGET: 19719 _hub.publish("finesse.containerservices.reloadGadget", data.data); 19720 break; 19721 default: 19722 break; 19723 } 19724 } 19725 }; 19726 19727 (function () { 19728 19729 //Listen to a request channel to respond to any requests made by other 19730 //clients because the Master may have access to useful information. 19731 _hub.subscribe(_topics.REQUESTS, _clientRequestHandler); 19732 }()); 19733 19734 //BEGIN TEST CODE// 19735 /** 19736 * Test code added to expose private functions that are used by unit test 19737 * framework. This section of code is removed during the build process 19738 * before packaging production code. The [begin|end]TestSection are used 19739 * by the build to identify the section to strip. 19740 * @ignore 19741 */ 19742 this.beginTestSection = 0; 19743 19744 /** 19745 * @ignore 19746 */ 19747 this.getTestObject = function () { 19748 //Load mock dependencies. 19749 var _mock = new MockControl(); 19750 _hub = _mock.createMock(gadgets.Hub); 19751 19752 return { 19753 //Expose mock dependencies 19754 mock: _mock, 19755 hub: _hub, 19756 19757 //Expose internal private functions 19758 reqtypes: _REQTYPES, 19759 19760 clientRequestHandler: _clientRequestHandler 19761 19762 }; 19763 }; 19764 19765 19766 /** 19767 * @ignore 19768 */ 19769 this.endTestSection = 0; 19770 //END TEST CODE// 19771 }; 19772 19773 window.finesse = window.finesse || {}; 19774 window.finesse.containerservices = window.finesse.containerservices || {}; 19775 window.finesse.containerservices.MasterPublisher = MasterPublisher; 19776 19777 return MasterPublisher; 19778 }); 19779 19780 /** 19781 * JavaScript representation of the Finesse WorkflowActionEvent object. 19782 * 19783 * @requires finesse.FinesseBase 19784 */ 19785 19786 /** The following comment is to prevent jslint errors about 19787 * using variables before they are defined. 19788 */ 19789 /*global FinesseBase: true, publisher:true, define:true, finesse:true, window:true */ 19790 /** @private */ 19791 define('containerservices/WorkflowActionEvent', ["FinesseBase"], function (FinesseBase) { 19792 var WorkflowActionEvent = FinesseBase.extend(/** @lends finesse.containerservices.WorkflowActionEvent.prototype */{ 19793 /** 19794 * Reference to the WorkflowActionEvent name 19795 * This will be set by setWorkflowActionEvent 19796 * @private 19797 */ 19798 _name: null, 19799 19800 /** 19801 * Reference to the WorkflowActionEvent type 19802 * This will be set by setWorkflowActionEvent 19803 * @private 19804 */ 19805 _type: null, 19806 19807 /** 19808 * Reference to the WorkflowActionEvent handledBy value 19809 * This will be set by setWorkflowActionEvent 19810 * @private 19811 */ 19812 _handledBy: null, 19813 19814 /** 19815 * Reference to the WorkflowActionEvent params array 19816 * This will be set by setWorkflowActionEvent 19817 * @private 19818 */ 19819 _params: [], 19820 19821 /** 19822 * Reference to the WorkflowActionEvent actionVariables array 19823 * This will be set by setWorkflowActionEvent 19824 * @private 19825 */ 19826 _actionVariables: [], 19827 19828 /** 19829 * @class 19830 * JavaScript representation of a WorkflowActionEvent object. 19831 * The WorkflowActionEvent object is delivered as the payload of 19832 * a WorkflowAction callback. This can be subscribed to by using 19833 * {@link finesse.containerservices.ContainerServices#addHandler} with a 19834 * topic of {@link finesse.containerservices.ContainerServices.Topics#WORKFLOW_ACTION_EVENT}. 19835 * Gadgets should key on events with a handleBy value of "OTHER". 19836 * 19837 * @constructs 19838 **/ 19839 init: function () { 19840 this._super(); 19841 }, 19842 19843 /** 19844 * Validate that the passed in object is a WorkflowActionEvent object 19845 * and sets the variables if it is 19846 * @param maybeWorkflowActionEvent A possible WorkflowActionEvent object to be evaluated and set if 19847 * it validates successfully. 19848 * @returns {Boolean} Whether it is valid or not. 19849 * @private 19850 */ 19851 setWorkflowActionEvent: function(maybeWorkflowActionEvent) { 19852 var returnValue; 19853 19854 if (maybeWorkflowActionEvent.hasOwnProperty("name") === true && 19855 maybeWorkflowActionEvent.hasOwnProperty("type") === true && 19856 maybeWorkflowActionEvent.hasOwnProperty("handledBy") === true && 19857 maybeWorkflowActionEvent.hasOwnProperty("params") === true && 19858 maybeWorkflowActionEvent.hasOwnProperty("actionVariables") === true) { 19859 this._name = maybeWorkflowActionEvent.name; 19860 this._type = maybeWorkflowActionEvent.type; 19861 this._handledBy = maybeWorkflowActionEvent.handledBy; 19862 this._params = maybeWorkflowActionEvent.params; 19863 this._actionVariables = maybeWorkflowActionEvent.actionVariables; 19864 returnValue = true; 19865 } else { 19866 returnValue = false; 19867 } 19868 19869 return returnValue; 19870 }, 19871 19872 /** 19873 * Getter for the WorkflowActionEvent name. 19874 * @returns {String} The name of the WorkflowAction. 19875 */ 19876 getName: function () { 19877 // escape nulls to empty string 19878 return this._name || ""; 19879 }, 19880 19881 /** 19882 * Getter for the WorkflowActionEvent type. 19883 * @returns {String} The type of the WorkflowAction (BROWSER_POP, HTTP_REQUEST). 19884 */ 19885 getType: function () { 19886 // escape nulls to empty string 19887 return this._type || ""; 19888 }, 19889 19890 /** 19891 * Getter for the WorkflowActionEvent handledBy value. Gadgets should look for 19892 * events with a handleBy of "OTHER". 19893 * @see finesse.containerservices.WorkflowActionEvent.HandledBy 19894 * @returns {String} The handledBy value of the WorkflowAction that is a value of {@link finesse.containerservices.WorkflowActionEvent.HandledBy}. 19895 */ 19896 getHandledBy: function () { 19897 // escape nulls to empty string 19898 return this._handledBy || ""; 19899 }, 19900 19901 19902 /** 19903 * Getter for the WorkflowActionEvent Params map. 19904 * @returns {Object} key = param name, value = Object{name, value, expandedValue} 19905 * BROWSER_POP<ul> 19906 * <li>windowName : Name of window to pop into, or blank to always open new window. 19907 * <li>path : URL to open.</ul> 19908 * HTTP_REQUEST<ul> 19909 * <li>method : "PUT" or "POST". 19910 * <li>location : "FINESSE" or "OTHER". 19911 * <li>contentType : MIME type of request body, if applicable, e.g. "text/plain". 19912 * <li>path : Request URL. 19913 * <li>body : Request content for POST requests.</ul> 19914 */ 19915 getParams: function () { 19916 var map = {}, 19917 params = this._params, 19918 i, 19919 param; 19920 19921 if (params === null || params.length === 0) { 19922 return map; 19923 } 19924 19925 for (i = 0; i < params.length; i += 1) { 19926 param = params[i]; 19927 // escape nulls to empty string 19928 param.name = param.name || ""; 19929 param.value = param.value || ""; 19930 param.expandedValue = param.expandedValue || ""; 19931 map[param.name] = param; 19932 } 19933 19934 return map; 19935 }, 19936 19937 /** 19938 * Getter for the WorkflowActionEvent ActionVariables map 19939 * @returns {Object} key = action variable name, value = Object{name, type, node, testValue, actualValue} 19940 */ 19941 getActionVariables: function() { 19942 var map = {}, 19943 actionVariables = this._actionVariables, 19944 i, 19945 actionVariable; 19946 19947 if (actionVariables === null || actionVariables.length === 0) { 19948 return map; 19949 } 19950 19951 for (i = 0; i < actionVariables.length; i += 1) { 19952 actionVariable = actionVariables[i]; 19953 // escape nulls to empty string 19954 actionVariable.name = actionVariable.name || ""; 19955 actionVariable.type = actionVariable.type || ""; 19956 actionVariable.node = actionVariable.node || ""; 19957 actionVariable.testValue = actionVariable.testValue || ""; 19958 actionVariable.actualValue = actionVariable.actualValue || ""; 19959 map[actionVariable.name] = actionVariable; 19960 } 19961 19962 return map; 19963 } 19964 }); 19965 19966 19967 WorkflowActionEvent.HandledBy = /** @lends finesse.containerservices.WorkflowActionEvent.HandledBy.prototype */ { 19968 /** 19969 * This specifies that Finesse will handle this WorkflowActionEvent. A 3rd Party can do additional processing 19970 * with the action, but first and foremost Finesse will handle this WorkflowAction. 19971 */ 19972 FINESSE: "FINESSE", 19973 19974 /** 19975 * This specifies that a 3rd Party will handle this WorkflowActionEvent. Finesse's Workflow Engine Executor will 19976 * ignore this action and expects Gadget Developers to take action. 19977 */ 19978 OTHER: "OTHER", 19979 19980 /** 19981 * @class This is the set of possible HandledBy values used for WorkflowActionEvent from ContainerServices. This 19982 * is provided from the {@link finesse.containerservices.WorkflowActionEvent#getHandledBy} method. 19983 * @constructs 19984 */ 19985 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 19986 }; 19987 19988 window.finesse = window.finesse || {}; 19989 window.finesse.containerservices = window.finesse.containerservices || {}; 19990 window.finesse.containerservices.WorkflowActionEvent = WorkflowActionEvent; 19991 19992 return WorkflowActionEvent; 19993 }); 19994 19995 /** 19996 * JavaScript representation of the Finesse TimerTickEvent 19997 * 19998 * @requires finesse.FinesseBase 19999 */ 20000 20001 /** The following comment is to prevent jslint errors about 20002 * using variables before they are defined. 20003 */ 20004 /*global FinesseBase: true, publisher:true, define:true, finesse:true, window:true */ 20005 /** @private */ 20006 define('containerservices/TimerTickEvent',[ 20007 "FinesseBase" 20008 ], 20009 function (FinesseBase) { 20010 var TimerTickEvent = FinesseBase.extend(/** @lends finesse.containerservices.TimerTickEvent.prototype */{ 20011 /** 20012 * date the TimerTickEvent was queued 20013 * @private 20014 */ 20015 _dateQueued: null, 20016 20017 /** 20018 * the frequency of the timer tick (in miiliseconds) 20019 * @private 20020 */ 20021 _tickFrequency: 1000, 20022 20023 /** 20024 * @class 20025 * JavaScript representation of a TimerTickEvent object. 20026 * The TimerTickEvent object is delivered as the payload of 20027 * a TimerTickEvent callback. This can be subscribed to by using 20028 * {@link finesse.containerservices.ContainerServices#addHandler} with a 20029 * topic of {@link finesse.containerservices.ContainerServices.Topics#TIMER_TICK_EVENT}. 20030 * 20031 * @constructs 20032 **/ 20033 init: function (tickFrequency, dateQueued) { 20034 this._super(); 20035 20036 this._tickFrequency = tickFrequency; 20037 this._dateQueued = dateQueued; 20038 }, 20039 20040 /** 20041 * Get the "tickFrequency" field 20042 * @param {int} which is the "TickFrequency" field 20043 * @private 20044 */ 20045 getTickFrequency: function () { 20046 return this._tickFrequency; 20047 }, 20048 20049 /** 20050 * Getter for the TimerTickEvent "DateQueued" field. 20051 * @returns {Date} which is a Date object when the TimerTickEvent was queued 20052 */ 20053 getDateQueued: function () { 20054 return this._dateQueued; 20055 } 20056 20057 }); 20058 20059 window.finesse = window.finesse || {}; 20060 window.finesse.containerservices = window.finesse.containerservices || {}; 20061 window.finesse.containerservices.TimerTickEvent = TimerTickEvent; 20062 20063 return TimerTickEvent; 20064 }); 20065 20066 /** 20067 * JavaScript representation of the Finesse GadgetViewChangedEvent object. 20068 * 20069 * @requires finesse.FinesseBase 20070 */ 20071 20072 /** The following comment is to prevent jslint errors about 20073 * using variables before they are defined. 20074 */ 20075 /*global FinesseBase: true, publisher:true, define:true, finesse:true, window:true */ 20076 /** @private */ 20077 define('containerservices/GadgetViewChangedEvent',[ 20078 "FinesseBase" 20079 ], 20080 function (FinesseBase) { 20081 var GadgetViewChangedEvent = FinesseBase.extend(/** @lends finesse.containerservices.GadgetViewChangedEvent.prototype */{ 20082 /** 20083 * Reference to the gadget id 20084 * @private 20085 */ 20086 _gadgetId: null, 20087 20088 /** 20089 * Reference to the tab id 20090 * @private 20091 */ 20092 _tabId: null, 20093 20094 /** 20095 * Reference to the maxAvailableHeight 20096 * @private 20097 */ 20098 _maxAvailableHeight: null, 20099 20100 /** 20101 * Reference to the view 20102 * E.g. 'default' or 'canvas' 20103 * @private 20104 */ 20105 _view: null, 20106 20107 /** 20108 * @class 20109 * JavaScript representation of a GadgetViewChangedEvent object. 20110 * The GadgetViewChangedEvent object is delivered as the payload of 20111 * a GadgetViewChangedEvent callback. This can be subscribed to by using 20112 * {@link finesse.containerservices.ContainerServices#addHandler} with a 20113 * topic of {@link finesse.containerservices.ContainerServices.Topics#GADGET_VIEW_CHANGED_EVENT}. 20114 * 20115 * @constructs 20116 **/ 20117 init: function (gadgetId, tabId, maxAvailableHeight, view) { 20118 this._super(); 20119 20120 this._gadgetId = gadgetId; 20121 this._tabId = tabId; 20122 this._maxAvailableHeight = maxAvailableHeight; 20123 this._view = view; 20124 }, 20125 20126 /** 20127 * Getter for the gadget id. 20128 * @returns {String} The identifier for the gadget changing view. 20129 */ 20130 getGadgetId: function () { 20131 // escape nulls to empty string 20132 return this._gadgetId || ""; 20133 }, 20134 20135 /** 20136 * Getter for the maximum available height. 20137 * @returns {String} The maximum available height for the gadget's view. 20138 */ 20139 getMaxAvailableHeight: function () { 20140 // escape nulls to empty string 20141 return this._maxAvailableHeight || ""; 20142 }, 20143 20144 /** 20145 * Getter for the tab id. 20146 * @returns {String} The identifier for the tab where the gadget changing view resides. 20147 */ 20148 getTabId: function () { 20149 // escape nulls to empty string 20150 return this._tabId || ""; 20151 }, 20152 20153 /** 20154 * Getter for the view. 20155 * @returns {String} The view type the gadget is changing to. 20156 */ 20157 getView: function () { 20158 // escape nulls to empty string 20159 return this._view || ""; 20160 } 20161 }); 20162 20163 window.finesse = window.finesse || {}; 20164 window.finesse.containerservices = window.finesse.containerservices || {}; 20165 window.finesse.containerservices.GadgetViewChangedEvent = GadgetViewChangedEvent; 20166 20167 return GadgetViewChangedEvent; 20168 }); 20169 20170 /** 20171 * JavaScript representation of the Finesse MaxAvailableHeightChangedEvent object. 20172 * 20173 * @requires finesse.FinesseBase 20174 */ 20175 20176 /** The following comment is to prevent jslint errors about 20177 * using variables before they are defined. 20178 */ 20179 /*global FinesseBase: true, publisher:true, define:true, finesse:true, window:true */ 20180 /** @private */ 20181 define('containerservices/MaxAvailableHeightChangedEvent',[ 20182 "FinesseBase" 20183 ], 20184 function (FinesseBase) { 20185 var MaxAvailableHeightChangedEvent = FinesseBase.extend(/** @lends finesse.containerservices.MaxAvailableHeightChangedEvent.prototype */{ 20186 20187 /** 20188 * Reference to the maxAvailableHeight 20189 * @private 20190 */ 20191 _maxAvailableHeight: null, 20192 20193 /** 20194 * @class 20195 * JavaScript representation of a MaxAvailableHeightChangedEvent object. 20196 * The MaxAvailableHeightChangedEvent object is delivered as the payload of 20197 * a MaxAvailableHeightChangedEvent callback. This can be subscribed to by using 20198 * {@link finesse.containerservices.ContainerServices#addHandler} with a 20199 * topic of {@link finesse.containerservices.ContainerServices.Topics#MAX_AVAILABLE_HEIGHT_CHANGED_EVENT}. 20200 * 20201 * @constructs 20202 **/ 20203 init: function (maxAvailableHeight) { 20204 this._super(); 20205 20206 this._maxAvailableHeight = maxAvailableHeight; 20207 }, 20208 20209 /** 20210 * Getter for the maximum available height. 20211 * @returns {String} The maximum available height for a gadget in canvas view 20212 */ 20213 getMaxAvailableHeight: function () { 20214 // escape nulls to empty string 20215 return this._maxAvailableHeight || ""; 20216 } 20217 }); 20218 20219 window.finesse = window.finesse || {}; 20220 window.finesse.containerservices = window.finesse.containerservices || {}; 20221 window.finesse.containerservices.MaxAvailableHeightChangedEvent = MaxAvailableHeightChangedEvent; 20222 20223 return MaxAvailableHeightChangedEvent; 20224 }); 20225 20226 /** 20227 * Exposes a set of API wrappers that will hide the dirty work of 20228 * constructing Finesse API requests and consuming Finesse events. 20229 * 20230 * @requires OpenAjax, jQuery 1.5, finesse.utilities.Utilities 20231 */ 20232 20233 /** The following comment is to prevent jslint errors about using variables before they are defined. */ 20234 /*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 */ 20235 /*jslint nomen: true, unparam: true, sloppy: true, white: true */ 20236 /** @private */ 20237 define('containerservices/ContainerServices',[ 20238 "utilities/Utilities", 20239 "restservices/Notifier", 20240 "containerservices/Topics", 20241 "containerservices/MasterPublisher", 20242 "containerservices/WorkflowActionEvent", 20243 "containerservices/TimerTickEvent", 20244 "containerservices/GadgetViewChangedEvent", 20245 "containerservices/MaxAvailableHeightChangedEvent" 20246 ], 20247 function (Utilities, Notifier, Topics, MasterPublisher, WorkflowActionEvent) { 20248 20249 var ContainerServices = ( function () { /** @lends finesse.containerservices.ContainerServices.prototype */ 20250 20251 var 20252 20253 /** 20254 * Shortcut reference to the Utilities singleton 20255 * This will be set by init() 20256 * @private 20257 */ 20258 _util, 20259 20260 /** 20261 * Shortcut reference to the gadget pubsub Hub instance. 20262 * This will be set by init() 20263 * @private 20264 */ 20265 _hub, 20266 20267 /** 20268 * Boolean whether this instance is master or not 20269 * @private 20270 */ 20271 _master = false, 20272 20273 /** 20274 * Whether the Client Services have been initiated yet. 20275 * @private 20276 */ 20277 _inited = false, 20278 20279 /** 20280 * References to ClientServices logger methods 20281 * @private 20282 */ 20283 _logger = { 20284 log: finesse.clientservices.ClientServices.log 20285 }, 20286 20287 /** 20288 * Stores the list of subscription IDs for all subscriptions so that it 20289 * could be retrieve for unsubscriptions. 20290 * @private 20291 */ 20292 _subscriptionID = {}, 20293 20294 /** 20295 * Reference to the gadget's parent container 20296 * @private 20297 */ 20298 _container, 20299 20300 /** 20301 * Reference to the MasterPublisher 20302 * @private 20303 */ 20304 _publisher, 20305 20306 /** 20307 * Object that will contain the Notifiers 20308 * @private 20309 */ 20310 _notifiers = {}, 20311 20312 /** 20313 * Reference to the tabId that is associated with the gadget 20314 * @private 20315 */ 20316 _myTab = null, 20317 20318 /** 20319 * Reference to the visibility of current gadget 20320 * @private 20321 */ 20322 _visible = false, 20323 20324 /** 20325 * Reference for auth modes constants. 20326 * @private 20327 */ 20328 _authModes, 20329 20330 /** 20331 * Shortcut reference to the Topics class. 20332 * This will be set by init() 20333 * @private 20334 */ 20335 _topics, 20336 20337 /** 20338 * Associates a topic name with the private handler function. 20339 * Adding a new topic requires that you add this association here 20340 * in to keep addHandler generic. 20341 * @param {String} topic : Specifies the callback to retrieve 20342 * @return {Function} The callback function associated with the topic param. 20343 * @private 20344 */ 20345 _topicCallback = function (topic) { 20346 var callback, notifier; 20347 switch (topic) 20348 { 20349 case finesse.containerservices.ContainerServices.Topics.ACTIVE_TAB: 20350 callback = _tabTracker; 20351 break; 20352 case finesse.containerservices.ContainerServices.Topics.WORKFLOW_ACTION_EVENT: 20353 callback = _workflowActionEventTracker; 20354 break; 20355 case finesse.containerservices.ContainerServices.Topics.RELOAD_GADGET_EVENT: 20356 callback = _masterReloader; 20357 break; 20358 case finesse.containerservices.ContainerServices.Topics.GADGET_VIEW_CHANGED_EVENT: 20359 callback = _gadgetViewChanged; 20360 break; 20361 case finesse.containerservices.ContainerServices.Topics.MAX_AVAILABLE_HEIGHT_CHANGED_EVENT: 20362 callback = _maxAvailableHeightChanged; 20363 break; 20364 case finesse.containerservices.ContainerServices.Topics.ACCESS_TOKEN_REFRESHED_EVENT: 20365 callback = _accessTokenRefreshed; 20366 break; 20367 default: 20368 callback = function (param) { 20369 var data = null; 20370 20371 notifier = _getNotifierReference(topic); 20372 20373 if (arguments.length === 1) { 20374 data = param; 20375 } else { 20376 data = arguments; 20377 } 20378 notifier.notifyListeners(data); 20379 }; 20380 } 20381 return callback; 20382 }, 20383 20384 /** 20385 * Ensure that ClientServices have been inited. 20386 * @private 20387 */ 20388 _isInited = function () { 20389 if (!_inited) { 20390 throw new Error("ContainerServices needs to be inited."); 20391 } 20392 }, 20393 20394 /** 20395 * Retrieves a Notifier reference to a particular topic, and creates one if it doesn't exist. 20396 * @param {String} topic : Specifies the notifier to retrieve 20397 * @return {Notifier} The notifier object. 20398 * @private 20399 */ 20400 _getNotifierReference = function (topic) { 20401 if (!_notifiers.hasOwnProperty(topic)) 20402 { 20403 _notifiers[topic] = new Notifier(); 20404 } 20405 20406 return _notifiers[topic]; 20407 }, 20408 20409 /** 20410 * Utility function to make a subscription to a particular topic. Only one 20411 * callback function is registered to a particular topic at any time. 20412 * @param {String} topic 20413 * The full topic name. The topic name should follow the OpenAjax 20414 * convention using dot notation (ex: finesse.api.User.1000). 20415 * @param {Function} callback 20416 * The function that should be invoked with the data when an event 20417 * is delivered to the specific topic. 20418 * @returns {Boolean} 20419 * True if the subscription was made successfully and the callback was 20420 * been registered. False if the subscription already exist, the 20421 * callback was not overwritten. 20422 * @private 20423 */ 20424 _subscribe = function (topic, callback) { 20425 _isInited(); 20426 20427 //Ensure that the same subscription isn't made twice. 20428 if (!_subscriptionID[topic]) { 20429 //Store the subscription ID using the topic name as the key. 20430 _subscriptionID[topic] = _hub.subscribe(topic, 20431 //Invoke the callback just with the data object. 20432 function (topic, data) { 20433 callback(data); 20434 }); 20435 return true; 20436 } 20437 return false; 20438 }, 20439 20440 /** 20441 * Unsubscribe from a particular topic. 20442 * @param {String} topic : The full topic name. 20443 * @private 20444 */ 20445 _unsubscribe = function (topic) { 20446 _isInited(); 20447 20448 //Unsubscribe from the topic using the subscription ID recorded when 20449 //the subscription was made, then delete the ID from data structure. 20450 _hub.unsubscribe(_subscriptionID[topic]); 20451 delete _subscriptionID[topic]; 20452 }, 20453 20454 /** 20455 * Get my tab id. 20456 * @returns {String} tabid : The tabid of this container/gadget. 20457 * @private 20458 */ 20459 _getMyTab = function () { 20460 if (_myTab === null) 20461 { 20462 try { 20463 _myTab = $(frameElement).closest("div.tab-panel").attr("id").replace("panel_", ""); 20464 } catch (err) { 20465 _logger.log("Error accessing current tab: " + err.message); 20466 _myTab = null; 20467 } 20468 } 20469 return _myTab; 20470 }, 20471 20472 /** 20473 * Callback function that is called when an activeTab message is posted to the Hub. 20474 * Notifies listener functions if this tab is the one that was just made active. 20475 * @param {String} tabId : The tabId which was just made visible. 20476 * @private 20477 */ 20478 _tabTracker = function(tabId) { 20479 if (tabId === _getMyTab()) { 20480 if(!_visible) { 20481 _visible = true; 20482 _notifiers[finesse.containerservices.ContainerServices.Topics.ACTIVE_TAB].notifyListeners(this); 20483 } 20484 } else { 20485 _visible = false; 20486 } 20487 }, 20488 20489 /** 20490 * Make a request to set a particular tab active. This 20491 * method should be called after {@link finesse.containerservices.ContainerServices#addHandler} 20492 * to ensure the gadget gets properly initialized. 20493 * @param {String} tabId 20494 * The tabId (not the label text) of the tab to make active. If the id is invalid, no action will occur. 20495 * @private 20496 */ 20497 _activateTab = function ( tabId ) { 20498 _logger.log("Sending request to activate tab: " + tabId); 20499 if(_hub){ 20500 var data = { 20501 type: "SetActiveTabReq", 20502 data: { id: tabId }, 20503 invokeID: (new Date()).getTime() 20504 }; 20505 _hub.publish(_topics.REQUESTS, data); 20506 } else { 20507 throw new Error("Hub is not defined."); 20508 } 20509 20510 }, 20511 20512 /** 20513 * Callback function that is called when a gadget view changed message is posted to the Hub. 20514 * @private 20515 */ 20516 _gadgetViewChanged = function (data) { 20517 if (data) { 20518 var gadgetViewChangedEvent = new finesse.containerservices.GadgetViewChangedEvent( 20519 data.gadgetId, 20520 data.tabId, 20521 data.maxAvailableHeight, 20522 data.view); 20523 20524 _notifiers[finesse.containerservices.ContainerServices.Topics.GADGET_VIEW_CHANGED_EVENT].notifyListeners(gadgetViewChangedEvent); 20525 } 20526 }, 20527 20528 /** 20529 * Callback function that is called when a max available height changed message is posted to the Hub. 20530 * @private 20531 */ 20532 _maxAvailableHeightChanged = function (data) { 20533 if (data) { 20534 var maxAvailableHeightChangedEvent = new finesse.containerservices.MaxAvailableHeightChangedEvent( 20535 data.maxAvailableHeight); 20536 20537 _notifiers[finesse.containerservices.ContainerServices.Topics.MAX_AVAILABLE_HEIGHT_CHANGED_EVENT].notifyListeners(maxAvailableHeightChangedEvent); 20538 } 20539 }, 20540 20541 /** 20542 * Callback function that is called when a workflowActionEvent message is posted to the Hub. 20543 * Notifies listener functions if the posted object can be converted to a proper WorkflowActionEvent object. 20544 * @param {String} workflowActionEvent : The workflowActionEvent that was posted to the Hub 20545 * @private 20546 */ 20547 _workflowActionEventTracker = function(workflowActionEvent) { 20548 var vWorkflowActionEvent = new finesse.containerservices.WorkflowActionEvent(); 20549 20550 if (vWorkflowActionEvent.setWorkflowActionEvent(workflowActionEvent)) { 20551 _notifiers[finesse.containerservices.ContainerServices.Topics.WORKFLOW_ACTION_EVENT].notifyListeners(vWorkflowActionEvent); 20552 } 20553 // else 20554 // { 20555 //?console.log("Error in ContainerServices : _workflowActionEventTracker - could not map published HUB object to WorkflowActionEvent"); 20556 // } 20557 20558 }, 20559 20560 /** 20561 * Callback function that is called when a reloadGadget event message is posted to the Hub. 20562 * 20563 * Grabs the id of the gadget we want to reload from the data and reload it! 20564 * 20565 * @param {String} topic 20566 * which topic the event came on (unused) 20567 * @param {Object} data 20568 * the data published with the event 20569 * @private 20570 */ 20571 _masterReloader = function (topic, data) { 20572 var gadgetId = data.gadgetId; 20573 if (gadgetId) { 20574 _container.reloadGadget(gadgetId); 20575 } 20576 }, 20577 20578 /** 20579 * Pulls the gadget id from the url parameters 20580 * @return {String} id of the gadget 20581 * @private 20582 */ 20583 _findMyGadgetId = function () { 20584 if (gadgets && gadgets.util && gadgets.util.getUrlParameters()) { 20585 return gadgets.util.getUrlParameters().mid; 20586 } 20587 }; 20588 20589 return { 20590 /** 20591 * @class 20592 * This class provides container-level services for gadget developers, exposing container events by 20593 * calling a set of exposed functions. Gadgets can utilize the container dialogs and 20594 * event handling (add/remove). 20595 * @example 20596 * containerServices = finesse.containerservices.ContainerServices.init(); 20597 * containerServices.addHandler( 20598 * finesse.containerservices.ContainerServices.Topics.ACTIVE_TAB, 20599 * function() { 20600 * clientLogs.log("Gadget is now visible"); // log to Finesse logger 20601 * // automatically adjust the height of the gadget to show the html 20602 * gadgets.window.adjustHeight(); 20603 * }); 20604 * containerServices.makeActiveTabReq(); 20605 * 20606 * @constructs 20607 */ 20608 _fakeConstuctor: function () { 20609 /* This is here so we can document init() as a method rather than as a constructor. */ 20610 }, 20611 20612 /** 20613 * Initialize ContainerServices for use in gadget. 20614 * @param {Boolean} [master=false] Do not use this parameter from your gadget. 20615 * @returns ContainerServices instance. 20616 */ 20617 init: function (master) { 20618 if (!_inited) { 20619 _inited = true; 20620 // Set shortcuts 20621 _util = Utilities; 20622 _authModes = _util.getAuthModes(); 20623 20624 //init the hub only when it's available 20625 if(gadgets.Hub) { 20626 _hub = gadgets.Hub; 20627 } 20628 20629 if(Topics) { 20630 _topics = Topics; 20631 } 20632 20633 if (master) { 20634 _master = true; 20635 _container = finesse.container.Container; 20636 _publisher = new MasterPublisher(); 20637 20638 // subscribe for reloading gadget events 20639 // we only want the master ContainerServices handling these events 20640 _hub.subscribe(_topics.RELOAD_GADGET, _topicCallback(_topics.RELOAD_GADGET)); 20641 } else { 20642 _container = parent.finesse.container.Container; 20643 } 20644 } 20645 20646 this.makeActiveTabReq(); 20647 20648 //Return the CS object for object chaining. 20649 return this; 20650 }, 20651 20652 /** 20653 * Shows the jQuery UI Dialog with the specified parameters. The following are the 20654 * default parameters: <ul> 20655 * <li> Title of "Cisco Finesse".</li> 20656 * <li>Message of "A generic error has occured".</li> 20657 * <li>The only button, "Ok", closes the dialog.</li> 20658 * <li>Modal (blocks other dialogs).</li> 20659 * <li>Not draggable.</li> 20660 * <li>Fixed size.</li></ul> 20661 * @param {Object} options 20662 * An object containing additional options for the dialog. 20663 * @param {String/Boolean} options.title 20664 * Title to use. undefined defaults to "Cisco Finesse". false to hide 20665 * @param {Function} options.close 20666 * A function to invoke when the dialog is closed. 20667 * @param {String} options.message 20668 * The message to display in the dialog. 20669 * Defaults to "A generic error has occurred." 20670 * @param {Boolean} options.isBlocking 20671 * Flag indicating whether this dialog will block other dialogs from being shown (Modal). 20672 * @returns {jQuery} JQuery wrapped object of the dialog DOM element. 20673 * @see finesse.containerservices.ContainerServices#hideDialog 20674 */ 20675 showDialog: function(options) { 20676 if ((_container.showDialog !== undefined) && (_container.showDialog !== this.showDialog)) { 20677 return _container.showDialog(options); 20678 } 20679 }, 20680 20681 /** 20682 * Hides the jQuery UI Dialog. 20683 * @returns {jQuery} jQuery wrapped object of the dialog DOM element 20684 * @see finesse.containerservices.ContainerServices#showDialog 20685 */ 20686 hideDialog: function() { 20687 if ((_container.hideDialog !== undefined) && (_container.hideDialog !== this.hideDialog)) { 20688 return _container.hideDialog(); 20689 } 20690 }, 20691 20692 /** 20693 * Reloads the current gadget. 20694 * For use from within a gadget only. 20695 */ 20696 reloadMyGadget: function () { 20697 var topic, gadgetId, data; 20698 20699 if (!_master) { 20700 // first unsubscribe this gadget from all topics on the hub 20701 for (topic in _notifiers) { 20702 if (_notifiers.hasOwnProperty(topic)) { 20703 _unsubscribe(topic); 20704 delete _notifiers[topic]; 20705 } 20706 } 20707 20708 // send an asynch request to the hub to tell the master container 20709 // services that we want to refresh this gadget 20710 gadgetId = _findMyGadgetId(); 20711 data = { 20712 type: "ReloadGadgetReq", 20713 data: {gadgetId: gadgetId}, 20714 invokeID: (new Date()).getTime() 20715 }; 20716 _hub.publish(_topics.REQUESTS, data); 20717 } 20718 }, 20719 20720 /** 20721 * Updates the url for this gadget and then reload it. 20722 * 20723 * This allows the gadget to be reloaded from a different location 20724 * than what is uploaded to the current server. For example, this 20725 * would be useful for 3rd party gadgets to implement their own failover 20726 * mechanisms. 20727 * 20728 * For use from within a gadget only. 20729 * 20730 * @param {String} url 20731 * url from which to reload gadget 20732 */ 20733 reloadMyGadgetFromUrl: function (url) { 20734 if (!_master) { 20735 var gadgetId = _findMyGadgetId(); 20736 20737 // update the url in the container 20738 _container.modifyGadgetUrl(gadgetId, url); 20739 20740 // reload it 20741 this.reloadMyGadget(); 20742 } 20743 }, 20744 20745 /** 20746 * Adds a handler for one of the supported topics provided by ContainerServices. The callbacks provided 20747 * will be invoked when that topic is notified. 20748 * @param {String} topic 20749 * The Hub topic to which we are listening. 20750 * @param {Function} callback 20751 * The callback function to invoke. 20752 * @see finesse.containerservices.ContainerServices.Topics 20753 * @see finesse.containerservices.ContainerServices#removeHandler 20754 */ 20755 addHandler: function (topic, callback) { 20756 _isInited(); 20757 var notifier = null; 20758 20759 try { 20760 // For backwards compatibility... 20761 if (topic === "tabVisible") { 20762 if (window.console && typeof window.console.log === "function") { 20763 window.console.log("WARNING - Using tabVisible as topic. This is deprecated. Use finesse.containerservices.ContainerServices.Topics.ACTIVE_TAB now!"); 20764 } 20765 20766 topic = finesse.containerservices.ContainerServices.Topics.ACTIVE_TAB; 20767 } 20768 20769 // Add the callback to the notifier. 20770 _util.validateHandler(callback); 20771 20772 notifier = _getNotifierReference(topic); 20773 20774 notifier.addListener(callback); 20775 20776 // Subscribe to the topic. _subscribe ensures that a topic is only subscribed to once, 20777 // so attempt to subscribe each time a handler is added. This ensures that a topic is subscribed 20778 // to only when necessary. 20779 _subscribe(topic, _topicCallback(topic)); 20780 20781 } catch (err) { 20782 throw new Error("addHandler(): " + err); 20783 } 20784 }, 20785 20786 /** 20787 * Removes a previously-added handler for one of the supported topics. 20788 * @param {String} topic 20789 * The Hub topic from which we are removing the callback. 20790 * @param {Function} callback 20791 * The name of the callback function to remove. 20792 * @see finesse.containerservices.ContainerServices.Topics 20793 * @see finesse.containerservices.ContainerServices#addHandler 20794 */ 20795 removeHandler: function(topic, callback) { 20796 var notifier = null; 20797 20798 try { 20799 _util.validateHandler(callback); 20800 20801 notifier = _getNotifierReference(topic); 20802 20803 notifier.removeListener(callback); 20804 } catch (err) { 20805 throw new Error("removeHandler(): " + err); 20806 } 20807 }, 20808 20809 /** 20810 * Wrapper API for publishing data on the Openajax hub 20811 * @param {String} topic 20812 * The Hub topic to which we are publishing. 20813 * @param {Object} data 20814 * The data to be published on the hub. 20815 */ 20816 publish : function(topic , data){ 20817 if(_hub){ 20818 _hub.publish(topic, data); 20819 } else { 20820 throw new Error("Hub is not defined."); 20821 } 20822 }, 20823 20824 /** 20825 * Returns the visibility of current gadget. Note that this 20826 * will not be set until after the initialization of the gadget. 20827 * @return {Boolean} The visibility of current gadget. 20828 */ 20829 tabVisible: function(){ 20830 return _visible; 20831 }, 20832 20833 /** 20834 * Make a request to check the current tab. The 20835 * activeTab event will be invoked if on the active tab. This 20836 * method should be called after {@link finesse.containerservices.ContainerServices#addHandler} 20837 * to ensure the gadget gets properly initialized. 20838 */ 20839 makeActiveTabReq : function () { 20840 if(_hub){ 20841 var data = { 20842 type: "ActiveTabReq", 20843 data: {}, 20844 invokeID: (new Date()).getTime() 20845 }; 20846 _hub.publish(_topics.REQUESTS, data); 20847 } else { 20848 throw new Error("Hub is not defined."); 20849 } 20850 20851 }, 20852 20853 /** 20854 * Make a request to set a particular tab active. This 20855 * method should be called after {@link finesse.containerservices.ContainerServices#addHandler} 20856 * to ensure the gadget gets properly initialized. 20857 * @param {String} tabId 20858 * The tabId (not the label text) of the tab to make active. If the id is invalid, no action will occur. 20859 */ 20860 activateTab : function (tabId) { 20861 _activateTab(tabId); 20862 }, 20863 20864 /** 20865 * Make a request to set this container's tab active. This 20866 * method should be called after {@link finesse.containerservices.ContainerServices#addHandler} 20867 * to ensure the gadget gets properly initialized. 20868 */ 20869 activateMyTab : function () { 20870 _activateTab( _getMyTab() ); 20871 }, 20872 20873 /** 20874 * Get the tabId of my container/gadget. 20875 * @returns {String} tabid : The tabid of this container/gadget. 20876 */ 20877 getMyTabId : function () { 20878 return _getMyTab(); 20879 }, 20880 20881 /** 20882 * Gets the id of the gadget. 20883 * @returns {number} the id of the gadget 20884 */ 20885 getMyGadgetId : function () { 20886 return _findMyGadgetId(); 20887 }, 20888 20889 //BEGIN TEST CODE// 20890 /** 20891 * Test code added to expose private functions that are used by unit test 20892 * framework. This section of code is removed during the build process 20893 * before packaging production code. The [begin|end]TestSection are used 20894 * by the build to identify the section to strip. 20895 * @ignore 20896 */ 20897 beginTestSection : 0, 20898 20899 /** 20900 * @ignore 20901 */ 20902 getTestObject: function () { 20903 //Load mock dependencies. 20904 var _mock = new MockControl(); 20905 _util = _mock.createMock(Utilities); 20906 _hub = _mock.createMock(gadgets.Hub); 20907 _inited = true; 20908 return { 20909 //Expose mock dependencies 20910 mock: _mock, 20911 hub: _hub, 20912 util: _util, 20913 addHandler: this.addHandler, 20914 removeHandler: this.removeHandler 20915 }; 20916 }, 20917 20918 /** 20919 * @ignore 20920 */ 20921 endTestSection: 0 20922 //END TEST CODE// 20923 }; 20924 }()); 20925 20926 ContainerServices.Topics = /** @lends finesse.containerservices.ContainerServices.Topics.prototype */ { 20927 /** 20928 * Topic for subscribing to be notified when the active tab changes. 20929 * The provided callback will be invoked when the tab that the gadget 20930 * that subscribes with this becomes active. To ensure code is called 20931 * when the gadget is already on the active tab use the 20932 * {@link finesse.containerservices.ContainerServices#makeActiveTabReq} 20933 * method. 20934 */ 20935 ACTIVE_TAB: "finesse.containerservices.activeTab", 20936 20937 /** 20938 * Topic for WorkflowAction events traffic. 20939 * The provided callback will be invoked when a WorkflowAction needs 20940 * to be handled. The callback will be passed a {@link finesse.containerservices.WorkflowActionEvent} 20941 * that can be used to interrogate the WorkflowAction and determine to use or not. 20942 */ 20943 WORKFLOW_ACTION_EVENT: "finesse.containerservices.workflowActionEvent", 20944 20945 /** 20946 * Topic for Timer Tick event. 20947 * The provided callback will be invoked when this event is fired. 20948 * The callback will be passed a {@link finesse.containerservices.TimerTickEvent}. 20949 */ 20950 TIMER_TICK_EVENT : "finesse.containerservices.timerTickEvent", 20951 20952 /** 20953 * Topic for Reload Gadget events traffic. 20954 * Only the master ContainerServices instance will handle this event. 20955 */ 20956 RELOAD_GADGET_EVENT: "finesse.containerservices.reloadGadget", 20957 20958 /** 20959 * Topic for listening to gadget view changed events. 20960 * The provided callback will be invoked when a gadget changes view. 20961 * The callback will be passed a {@link finesse.containerservices.GadgetViewChangedEvent}. 20962 */ 20963 GADGET_VIEW_CHANGED_EVENT: "finesse.containerservices.gadgetViewChangedEvent", 20964 20965 /** 20966 * Topic for listening to max available height changed events. 20967 * The provided callback will be invoked when the maximum height available to a maximized gadget changes. 20968 * This event is only meant for maximized gadgets and will not be published unless a maximized gadget exists. 20969 * The callback will be passed a {@link finesse.containerservices.MaxAvailableHeightChangedEvent}. 20970 */ 20971 MAX_AVAILABLE_HEIGHT_CHANGED_EVENT: "finesse.containerservices.maxAvailableHeightChangedEvent", 20972 20973 /** 20974 * @class This is the set of Topics used for subscribing for events from ContainerServices. 20975 * Use {@link finesse.containerservices.ContainerServices#addHandler} to subscribe to the topic. 20976 * 20977 * @constructs 20978 */ 20979 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 20980 }; 20981 20982 window.finesse = window.finesse || {}; 20983 window.finesse.containerservices = window.finesse.containerservices || {}; 20984 window.finesse.containerservices.ContainerServices = ContainerServices; 20985 20986 return ContainerServices; 20987 }); 20988 /** 20989 * FinesseToaster is a utility class to show toaster notification in Finesse. 20990 * FinesseToaster leverages HTML5 Notification API to display Toaster 20991 * Notification. 20992 * 20993 */ 20994 20995 define('containerservices/FinesseToaster',[],function() { 20996 20997 var FinesseToaster = (function() { 20998 /** @lends finesse.containerservices.FinesseToaster.prototype */ 20999 21000 var 21001 21002 /** How long the toaster will be displayed by default. Default timeout is 8 seconds */ 21003 AUTO_CLOSE_TIME = 8000, 21004 21005 /** PERMISSION_GRANTED constant for granted string */ 21006 PERMISSION_GRANTED = 'granted', 21007 21008 /** PERMISSION_DEFAULT constant for default string */ 21009 PERMISSION_DEFAULT = 'default', 21010 21011 /** PERMISSION_DENIED constant for denied string */ 21012 PERMISSION_DENIED = 'denied', 21013 21014 /** ICON_PATH constant for holding path icon images */ 21015 ICON_PATH = '/desktop/theme/finesse/images/modules/', 21016 21017 /** 21018 * Shortcut reference to finesse.cslogger.ClientLogger singleton This 21019 * will be set by init(), it should already be initialized by 21020 * PageServices 21021 * 21022 * @private 21023 */ 21024 _logger, 21025 21026 /** 21027 * Boolean variable to determine if finesse toaster is enabled 21028 * @private 21029 */ 21030 _isToasterDisabled = false, 21031 21032 /** 21033 * Boolean variable to determine if finesse toaster is initialized 21034 * @private 21035 */ 21036 _isToasterInitialized, 21037 21038 /** 21039 * this function check of provided parameter is a javascript function. 21040 * 21041 * @private 21042 */ 21043 isFunction = function(param) { 21044 if (typeof param === "function") { 21045 return true; 21046 } 21047 return false; 21048 }, 21049 21050 /** 21051 * _createNotification creates Notification instance 21052 * 21053 * @param {String} 21054 * title title string should be displayed in the Toaster 21055 * @param {Object} 21056 * options JSON object for notification options. 21057 */ 21058 _createNotification = function(title, options) { 21059 var notification = new window.Notification(title, options); 21060 return notification; 21061 }, 21062 21063 /** 21064 * _setAutoClose set the auto close time for toaster, it checks if 21065 * client code passed custom time out for the toaster, otherwise it set 21066 * the AUTO_CLOSE_TIME 21067 * 21068 * @param {Object} 21069 * notification window.Notification that creates Html5 21070 * Notification. 21071 * @param {String} 21072 * autoClose autoClose time of the Toaster 21073 * @return toasterTimeout Toaster Timeout 21074 */ 21075 _setAutoClose = function(notification, autoClose) { 21076 21077 // check if custom close time passed other wise set 21078 // DEFAULT_AUTO_CLOSE 21079 var autoCloseTime = (autoClose && !isNaN(autoClose)) ? autoClose 21080 : AUTO_CLOSE_TIME, 21081 // set the time out for notification toaster 21082 toasterTimer = setTimeout(function() { 21083 notification.close(); 21084 }, autoCloseTime); 21085 21086 return toasterTimer; 21087 21088 }, 21089 21090 /** This method will request permission to display Toaster. */ 21091 _requestPermission = function() { 21092 // If they are not denied (i.e. default) 21093 if (window.Notification 21094 && window.Notification.permission !== PERMISSION_DENIED) { 21095 // Request permission 21096 window.Notification 21097 .requestPermission(function(status) { 21098 21099 // Change based on user's decision 21100 if (window.Notification.permission !== status) { 21101 window.Notification.permission = status; 21102 } 21103 _logger 21104 .log("FinesseToaster.requestPermission(): request permission status " 21105 + status); 21106 21107 }); 21108 21109 } else { 21110 _logger 21111 .log("FinesseToaster.requestPermission(): Notification not supported or permission denied."); 21112 } 21113 21114 }, 21115 21116 /** 21117 * This method will add onclick and onerror listener to Notification. 21118 * on click of toaster the gadget which originally had focus may loose 21119 * the focus. To get the back the focus on any element inside the gadget 21120 * use the on click callback handler. 21121 * 21122 * @param {Object} 21123 * notification window.Notification that creates Html5 21124 * Notification. 21125 * @param {Object} 21126 * options JSON object for notification options. 21127 */ 21128 _addToasterListeners = function(notification, options, toasterTimer) { 21129 // this is onlcik handler of toaster. this handler will be invoked 21130 // on click of toaster 21131 notification.onclick = function() { 21132 // in case of manually closed toaster, stop the notification 21133 // auto close method to be invoked 21134 clearTimeout(toasterTimer); 21135 // This will maximize/activate chrome browser on click of 21136 // toaster. this handling required only in case of Chrome 21137 if (window.chrome) { 21138 parent.focus(); 21139 } 21140 21141 if (options && options.onclick) { 21142 if (isFunction(options.onclick)) { 21143 options.onclick(); 21144 } else { 21145 throw new Error("onclick callback must be a function"); 21146 } 21147 } 21148 21149 //close toaster upon click 21150 this.close(); 21151 21152 }; 21153 21154 // this is onerror handler of toaster, if there is any error while 21155 // loading toaster this hadnler will be invoked 21156 notification.onerror = function() { 21157 if (options && options.onerror) { 21158 if (isFunction(options.onerror)) { 21159 options.onerror(); 21160 } else { 21161 throw new Error("onerror callback must be a function"); 21162 } 21163 } 21164 }; 21165 }; 21166 21167 return { 21168 21169 /** 21170 * @class 21171 * FinesseToaster is a utility class to show toaster 21172 * notification in Finesse. FinesseToaster leverages <a 21173 * href="https://www.w3.org/TR/notifications/">HTML5 21174 * Notification</a> API to display Toaster Notification. 21175 * <p> <a 21176 * href="https://developer.mozilla.org/en/docs/Web/API/notification#Browser_compatibility">For 21177 * HTML5 Notification API and browser compatibility, please click 21178 * here.</a></p> 21179 * 21180 * @constructs 21181 */ 21182 _fakeConstuctor : function() { 21183 21184 }, 21185 /** 21186 * TOASTER_DEFAULT_ICONS constants has list of predefined icons (e.g INCOMING_CALL_ICON). 21187 * <p><b>Constant list</b></p> 21188 * <ul> 21189 * <li>TOASTER_DEFAULT_ICONS.INCOMING_CALL_ICON</li> 21190 * </ul> 21191 */ 21192 TOASTER_DEFAULT_ICONS : { 21193 INCOMING_CALL_ICON : ICON_PATH + "incoming_call.png" 21194 }, 21195 21196 /** 21197 * <b>showToaster </b>: shows Toaster Notification. 21198 * 21199 * @param {String} 21200 * <b>title</b> : title string should be displayed in the Toaster 21201 * @param {Object} 21202 * options is JSON object for notification options. 21203 * <ul> 21204 * <li><b>options</b> = { </li> 21205 * <li><b>body</b> : The body string of the notification as 21206 * specified in the options parameter of the constructor.</li> 21207 * <li><b>icon</b>: The URL of the image used as an icon of the 21208 * notification as specified in the options parameter of 21209 * the constructor.</li> 21210 * <li><b>autoClose</b> : custom auto close time of the toaster</li> 21211 * <li><b>showWhenVisible</b> : 'true' toaster shows up even when page is 21212 * visible,'false' toaster shows up only when page is invisible </li> 21213 * <li> }</li> 21214 * </ul> 21215 * 21216 */ 21217 showToaster : function(title, options) { 21218 21219 if(!_isToasterInitialized){ 21220 throw new Error("FinesseToaster.showToaster() : Finesse toaster is not initialized"); 21221 } 21222 21223 if(_isToasterDisabled){ 21224 _logger.log("FinesseToaster.showToaster() : FinesseToaster is disabled"); 21225 return; 21226 } 21227 21228 var notification, toasterTimer; 21229 21230 // If notifications are granted show the notification 21231 if (window.Notification 21232 && window.Notification.permission === PERMISSION_GRANTED) { 21233 21234 // document.hasFocus() used over document.hidden to keep the consistent behavior across mozilla/chrome 21235 if (document.hasFocus() === false 21236 || options.showWhenVisible) { 21237 if (_logger && AUTO_CLOSE_TIME > -1) { 21238 notification = _createNotification(title, options); 21239 21240 // set the auto close time out of the toaster 21241 toasterTimer = _setAutoClose(notification, 21242 options.autoClose); 21243 21244 // and Toaster Event listeners. eg. onclick , onerror. 21245 _addToasterListeners(notification, options, 21246 toasterTimer); 21247 } 21248 } else { 21249 _logger 21250 .log("FinesseToaster supressed : Page is visible and FineeseToaster.options.showWhenVisible is false"); 21251 } 21252 21253 } 21254 21255 return notification; 21256 }, 21257 21258 /** 21259 * initialize FininseToaster and inject dependencies. this method 21260 * will also request permission in browser from user to display 21261 * Toaster Notification. 21262 * 21263 *@param {Object} 21264 * Could be finesse.container.Config or finesse.gadget.Config based on where it is getting initialized from. 21265 * 21266 * @param {Object} 21267 * finesse.cslogger.ClientLogger 21268 * @return finesse.containerservices.FinesseToaster 21269 */ 21270 init : function(config , logger) { 21271 21272 _isToasterInitialized = true; 21273 21274 // This is for injecting mocked logger. 21275 if (logger) { 21276 _logger = logger; 21277 } else { 21278 _logger = finesse.cslogger.ClientLogger; 21279 } 21280 21281 //set default toaster notification timeout 21282 if (config && config.toasterNotificationTimeout) { 21283 AUTO_CLOSE_TIME = Number(config.toasterNotificationTimeout) * 1000; 21284 21285 if(AUTO_CLOSE_TIME === 0){ 21286 //Finesse toaster has been disabled 21287 _isToasterDisabled = true; 21288 } 21289 } 21290 21291 // Request permission 21292 _requestPermission(); 21293 return finesse.containerservices.FinesseToaster; 21294 } 21295 }; 21296 21297 }()); 21298 21299 window.finesse = window.finesse || {}; 21300 window.finesse.containerservices = window.finesse.containerservices || {}; 21301 window.finesse.containerservices.FinesseToaster = FinesseToaster; 21302 21303 return FinesseToaster; 21304 }); 21305 21306 /** 21307 * This "interface" is just a way to easily jsdoc the Object callback handlers. 21308 * 21309 * @requires finesse.clientservices.ClientServices 21310 * @requires Class 21311 */ 21312 /** @private */ 21313 define('interfaces/RestObjectHandlers',[ 21314 "FinesseBase", 21315 "utilities/Utilities", 21316 "restservices/Notifier", 21317 "clientservices/ClientServices", 21318 "clientservices/Topics" 21319 ], 21320 function () { 21321 21322 var RestObjectHandlers = ( function () { /** @lends finesse.interfaces.RestObjectHandlers.prototype */ 21323 21324 return { 21325 21326 /** 21327 * @class 21328 * This "interface" defines REST Object callback handlers, passed as an argument to 21329 * Object getter methods in cases where the Object is going to be created. 21330 * 21331 * @param {Object} [handlers] 21332 * An object containing callback handlers for instantiation and runtime 21333 * Callback to invoke upon successful instantiation, passes in REST object. 21334 * @param {Function} [handlers.onLoad(this)] 21335 * Callback to invoke upon loading the data for the first time. 21336 * @param {Function} [handlers.onChange(this)] 21337 * Callback to invoke upon successful update object (PUT) 21338 * @param {Function} [handlers.onAdd(this)] 21339 * Callback to invoke upon successful update to add object (POST) 21340 * @param {Function} [handlers.onDelete(this)] 21341 * Callback to invoke upon successful update to delete object (DELETE) 21342 * @param {Function} [handlers.onError(rsp)] 21343 * Callback to invoke on update error (refresh or event) 21344 * as passed by finesse.restservices.RestBase.restRequest()<br> 21345 * {<br> 21346 * status: {Number} The HTTP status code returned<br> 21347 * content: {String} Raw string of response<br> 21348 * object: {Object} Parsed object of response<br> 21349 * error: {Object} Wrapped exception that was caught<br> 21350 * error.errorType: {String} Type of error that was caught<br> 21351 * error.errorMessage: {String} Message associated with error<br> 21352 * }<br> 21353 * <br> 21354 * Note that RestCollections have two additional callback handlers:<br> 21355 * <br> 21356 * @param {Function} [handlers.onCollectionAdd(this)]: when an object is added to this collection 21357 * @param {Function} [handlers.onCollectionDelete(this)]: when an object is removed from this collection 21358 21359 * @constructs 21360 */ 21361 _fakeConstuctor: function () { 21362 /* This is here to enable jsdoc to document this as a class. */ 21363 } 21364 }; 21365 }()); 21366 21367 window.finesse = window.finesse || {}; 21368 window.finesse.interfaces = window.finesse.interfaces || {}; 21369 window.finesse.interfaces.RestObjectHandlers = RestObjectHandlers; 21370 21371 return RestObjectHandlers; 21372 21373 }); 21374 21375 21376 /** 21377 * This "interface" is just a way to easily jsdoc the REST request handlers. 21378 * 21379 * @requires finesse.clientservices.ClientServices 21380 * @requires Class 21381 */ 21382 /** @private */ 21383 define('interfaces/RequestHandlers',[ 21384 "FinesseBase", 21385 "utilities/Utilities", 21386 "restservices/Notifier", 21387 "clientservices/ClientServices", 21388 "clientservices/Topics" 21389 ], 21390 function () { 21391 21392 var RequestHandlers = ( function () { /** @lends finesse.interfaces.RequestHandlers.prototype */ 21393 21394 return { 21395 21396 /** 21397 * @class 21398 * This "interface" defines REST Object callback handlers, passed as an argument to 21399 * Object getter methods in cases where the Object is going to be created. 21400 * 21401 * @param {Object} handlers 21402 * An object containing the following (optional) handlers for the request:<ul> 21403 * <li><b>success(rsp):</b> A callback function for a successful request to be invoked with the following 21404 * response object as its only parameter:<ul> 21405 * <li><b>status:</b> {Number} The HTTP status code returned</li> 21406 * <li><b>content:</b> {String} Raw string of response</li> 21407 * <li><b>object:</b> {Object} Parsed object of response</li></ul> 21408 * <li><b>error(rsp):</b> An error callback function for an unsuccessful request to be invoked with the 21409 * error response object as its only parameter:<ul> 21410 * <li><b>status:</b> {Number} The HTTP status code returned</li> 21411 * <li><b>content:</b> {String} Raw string of response</li> 21412 * <li><b>object:</b> {Object} Parsed object of response (HTTP errors)</li> 21413 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 21414 * <li><b>errorType:</b> {String} Type of error that was caught</li> 21415 * <li><b>errorMessage:</b> {String} Message associated with error</li> 21416 * </ul></li> 21417 * </ul> 21418 21419 * @constructs 21420 */ 21421 _fakeConstuctor: function () { 21422 /* This is here to enable jsdoc to document this as a class. */ 21423 } 21424 }; 21425 }()); 21426 21427 window.finesse = window.finesse || {}; 21428 window.finesse.interfaces = window.finesse.interfaces || {}; 21429 window.finesse.interfaces.RequestHandlers = RequestHandlers; 21430 21431 finesse = finesse || {}; 21432 /** @namespace These interfaces are just a convenience for documenting common parameter structures. */ 21433 finesse.interfaces = finesse.interfaces || {}; 21434 21435 return RequestHandlers; 21436 21437 }); 21438 21439 21440 21441 define('gadget/Config',[ 21442 "utilities/Utilities" 21443 ], function (Utilities) { 21444 var Config = (function () { /** @lends finesse.gadget.Config.prototype */ 21445 21446 if (gadgets && gadgets.Prefs) { 21447 21448 var _prefs = new gadgets.Prefs(); 21449 21450 return { 21451 /** 21452 * The base64 encoded "id:password" string used for authentication. 21453 */ 21454 authorization: Utilities.getUserAuthString(), 21455 21456 /** 21457 * The auth token string used for authentication in SSO deployments. 21458 */ 21459 authToken: Utilities.getToken(), 21460 21461 /** 21462 * The country code of the client (derived from locale). 21463 */ 21464 country: _prefs.getString("country"), 21465 21466 /** 21467 * The language code of the client (derived from locale). 21468 */ 21469 language: _prefs.getString("language"), 21470 21471 /** 21472 * The locale of the client. 21473 */ 21474 locale: _prefs.getString("locale"), 21475 21476 /** 21477 * The Finesse server IP/host as reachable from the browser. 21478 */ 21479 host: _prefs.getString("host"), 21480 21481 /** 21482 * The Finesse server host's port reachable from the browser. 21483 */ 21484 hostPort: _prefs.getString("hostPort"), 21485 21486 /** 21487 * The extension of the user. 21488 */ 21489 extension: _prefs.getString("extension"), 21490 21491 /** 21492 * One of the work modes found in {@link finesse.restservices.User.WorkMode}, or something false (undefined) for a normal login. 21493 */ 21494 mobileAgentMode: _prefs.getString("mobileAgentMode"), 21495 21496 /** 21497 * The dial number to use for mobile agent, or something false (undefined) for a normal login. 21498 */ 21499 mobileAgentDialNumber: _prefs.getString("mobileAgentDialNumber"), 21500 21501 /** 21502 * The domain of the XMPP server. 21503 */ 21504 xmppDomain: _prefs.getString("xmppDomain"), 21505 21506 /** 21507 * The pub sub domain where the pub sub service is running. 21508 */ 21509 pubsubDomain: _prefs.getString("pubsubDomain"), 21510 21511 /** 21512 * The Finesse API IP/host as reachable from the gadget container. 21513 */ 21514 restHost: _prefs.getString("restHost"), 21515 21516 /** 21517 * The type of HTTP protocol (http or https). 21518 */ 21519 scheme: _prefs.getString("scheme"), 21520 21521 /** 21522 * The localhost fully qualified domain name. 21523 */ 21524 localhostFQDN: _prefs.getString("localhostFQDN"), 21525 21526 /** 21527 * The localhost port. 21528 */ 21529 localhostPort: _prefs.getString("localhostPort"), 21530 21531 /** 21532 * The id of the team the user belongs to. 21533 */ 21534 teamId: _prefs.getString("teamId"), 21535 21536 /** 21537 * The name of the team the user belongs to. 21538 */ 21539 teamName: _prefs.getString("teamName"), 21540 21541 /** 21542 * The drift time between the client and the server in milliseconds. 21543 */ 21544 clientDriftInMillis: _prefs.getInt("clientDriftInMillis"), 21545 21546 /** 21547 * The client compatibility mode configuration (true if it is or false otherwise). 21548 */ 21549 compatibilityMode: _prefs.getString("compatibilityMode"), 21550 21551 /** 21552 * The peripheral Id that Finesse is connected to. 21553 */ 21554 peripheralId: _prefs.getString("peripheralId"), 21555 21556 /** 21557 * The auth mode of the finesse deployment. 21558 */ 21559 systemAuthMode: _prefs.getString("systemAuthMode"), 21560 21561 21562 /** 21563 * The time for which fineese toaster stay on the browser. 21564 */ 21565 toasterNotificationTimeout: _prefs.getString("toasterNotificationTimeout"), 21566 21567 /** 21568 * @class 21569 * The Config object for gadgets within the Finesse desktop container which 21570 * contains configuration data provided by the container page. 21571 * @constructs 21572 */ 21573 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 21574 21575 }; 21576 } else { 21577 return {}; 21578 } 21579 }()); 21580 21581 /** Assign to container and gadget namespace to have config available in both */ 21582 window.finesse = window.finesse || {}; 21583 window.finesse.container = window.finesse.container || {}; 21584 window.finesse.container.Config = window.finesse.container.Config || Config; 21585 21586 window.finesse.gadget = window.finesse.gadget || {}; 21587 window.finesse.gadget.Config = Config; 21588 21589 return Config; 21590 }); 21591 define('finesse',[ 21592 'restservices/Users', 21593 'restservices/Teams', 21594 'restservices/SystemInfo', 21595 'restservices/Media', 21596 'restservices/MediaDialogs', 21597 'restservices/DialogLogoutActions', 21598 'restservices/InterruptActions', 21599 'restservices/ReasonCodeLookup', 21600 'utilities/I18n', 21601 'utilities/Logger', 21602 'utilities/SaxParser', 21603 'utilities/BackSpaceHandler', 21604 'cslogger/ClientLogger', 21605 'cslogger/FinesseLogger', 21606 'containerservices/ContainerServices', 21607 'containerservices/FinesseToaster', 21608 'interfaces/RestObjectHandlers', 21609 'interfaces/RequestHandlers', 21610 'gadget/Config' 21611 ], 21612 function () { 21613 return window.finesse; 21614 }); 21615 21616 require(["finesse"]); 21617 return require('finesse'); })); 21618 21619 // Prevent other JS files from wiping out window.finesse from the namespace 21620 var finesse = window.finesse;