1 /** 2 * Cisco Finesse - JavaScript Library 3 * Version 11.5(1) 4 * Cisco Systems, Inc. 5 * http://www.cisco.com/ 6 * 7 * Portions created or assigned to Cisco Systems, Inc. are 8 * Copyright (c) 2016 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 window.finesse = window.finesse || {}; 3176 window.finesse.utilities = window.finesse.utilities || {}; 3177 window.finesse.utilities.Utilities = Utilities; 3178 3179 return Utilities; 3180 }); 3181 3182 /** The following comment is to prevent jslint errors about 3183 * using variables before they are defined. 3184 */ 3185 /*global finesse*/ 3186 3187 /** 3188 * Initiated by the Master to create a shared BOSH connection. 3189 * 3190 * @requires Utilities 3191 */ 3192 3193 /** 3194 * @class 3195 * Establishes a shared event connection by creating a communication tunnel 3196 * with the notification server and consume events which could be published. 3197 * Public functions are exposed to register to the connection status information 3198 * and events. 3199 * @constructor 3200 * @param {String} host 3201 * The host name/ip of the Finesse server. 3202 * @throws {Error} If required constructor parameter is missing. 3203 */ 3204 /** @private */ 3205 define('clientservices/MasterTunnel',["utilities/Utilities"], function (Utilities) { 3206 var MasterTunnel = function (host, scheme) { 3207 if (typeof host !== "string" || host.length === 0) { 3208 throw new Error("Required host parameter missing."); 3209 } 3210 3211 var 3212 3213 /** 3214 * Flag to indicate whether the tunnel frame is loaded. 3215 * @private 3216 */ 3217 _isTunnelLoaded = false, 3218 3219 /** 3220 * Short reference to the Finesse utility. 3221 * @private 3222 */ 3223 _util = Utilities, 3224 3225 /** 3226 * The URL with host and port to the Finesse server. 3227 * @private 3228 */ 3229 _tunnelOrigin, 3230 3231 /** 3232 * Location of the tunnel HTML URL. 3233 * @private 3234 */ 3235 _tunnelURL, 3236 3237 /** 3238 * The port on which to connect to the Finesse server to load the eventing resources. 3239 * @private 3240 */ 3241 _tunnelOriginPort, 3242 3243 /** 3244 * Flag to indicate whether we have processed the tunnel config yet. 3245 * @private 3246 */ 3247 _isTunnelConfigInit = false, 3248 3249 /** 3250 * The tunnel frame window object. 3251 * @private 3252 */ 3253 _tunnelFrame, 3254 3255 /** 3256 * The handler registered with the object to be invoked when an event is 3257 * delivered by the notification server. 3258 * @private 3259 */ 3260 _eventHandler, 3261 3262 /** 3263 * The handler registered with the object to be invoked when presence is 3264 * delivered by the notification server. 3265 * @private 3266 */ 3267 _presenceHandler, 3268 3269 /** 3270 * The handler registered with the object to be invoked when the BOSH 3271 * connection has changed states. The object will contain the "status" 3272 * property and a "resourceID" property only if "status" is "connected". 3273 * @private 3274 */ 3275 _connInfoHandler, 3276 3277 /** 3278 * The last connection status published by the JabberWerx library. 3279 * @private 3280 */ 3281 _statusCache, 3282 3283 /** 3284 * The last event sent by notification server. 3285 * @private 3286 */ 3287 _eventCache, 3288 3289 /** 3290 * The ID of the user logged into notification server. 3291 * @private 3292 */ 3293 _id, 3294 3295 /** 3296 * The domain of the XMPP server, representing the portion of the JID 3297 * following '@': userid@domain.com 3298 * @private 3299 */ 3300 _xmppDomain, 3301 3302 /** 3303 * The password of the user logged into notification server. 3304 * @private 3305 */ 3306 _password, 3307 3308 /** 3309 * The jid of the pubsub service on the XMPP server 3310 * @private 3311 */ 3312 _pubsubDomain, 3313 3314 /** 3315 * The resource to use for the BOSH connection. 3316 * @private 3317 */ 3318 _resource, 3319 3320 /** 3321 * The resource ID identifying the client device (that we receive from the server). 3322 * @private 3323 */ 3324 _resourceID, 3325 3326 /** 3327 * The different types of messages that could be sent to the parent frame. 3328 * The types here should be understood by the parent frame and used to 3329 * identify how the message is formatted. 3330 * @private 3331 */ 3332 _TYPES = { 3333 EVENT: 0, 3334 ID: 1, 3335 PASSWORD: 2, 3336 RESOURCEID: 3, 3337 STATUS: 4, 3338 XMPPDOMAIN: 5, 3339 PUBSUBDOMAIN: 6, 3340 SUBSCRIBE: 7, 3341 UNSUBSCRIBE: 8, 3342 PRESENCE: 9, 3343 CONNECT_REQ: 10 3344 }, 3345 3346 _handlers = { 3347 subscribe: {}, 3348 unsubscribe: {} 3349 }, 3350 3351 3352 /** 3353 * Create a connection info object. 3354 * @returns {Object} 3355 * A connection info object containing a "status" and "resourceID". 3356 * @private 3357 */ 3358 _createConnInfoObj = function () { 3359 return { 3360 status: _statusCache, 3361 resourceID: _resourceID 3362 }; 3363 }, 3364 3365 /** 3366 * Utility function which sends a message to the dynamic tunnel frame 3367 * event frame formatted as follows: "type|message". 3368 * @param {Number} type 3369 * The category type of the message. 3370 * @param {String} message 3371 * The message to be sent to the tunnel frame. 3372 * @private 3373 */ 3374 _sendMessage = function (type, message) { 3375 message = type + "|" + message; 3376 _util.sendMessage(message, _tunnelFrame, _tunnelOrigin); 3377 }, 3378 3379 /** 3380 * Utility to process the response of a subscribe request from 3381 * the tunnel frame, then invoking the stored callback handler 3382 * with the respective data (error, when applicable) 3383 * @param {String} data 3384 * The response in the format of "node[|error]" 3385 * @private 3386 */ 3387 _processSubscribeResponse = function (data) { 3388 var dataArray = data.split("|"), 3389 node = dataArray[0], 3390 err; 3391 3392 //Error is optionally the second item in the array 3393 if (dataArray.length) { 3394 err = dataArray[1]; 3395 } 3396 3397 // These response handlers are short lived and should be removed and cleaned up immediately after invocation. 3398 if (_handlers.subscribe[node]) { 3399 _handlers.subscribe[node](err); 3400 delete _handlers.subscribe[node]; 3401 } 3402 }, 3403 3404 /** 3405 * Utility to process the response of an unsubscribe request from 3406 * the tunnel frame, then invoking the stored callback handler 3407 * with the respective data (error, when applicable) 3408 * @param {String} data 3409 * The response in the format of "node[|error]" 3410 * @private 3411 */ 3412 _processUnsubscribeResponse = function (data) { 3413 var dataArray = data.split("|"), 3414 node = dataArray[0], 3415 err; 3416 3417 //Error is optionally the second item in the array 3418 if (dataArray.length) { 3419 err = dataArray[1]; 3420 } 3421 3422 // These response handlers are short lived and should be removed and cleaned up immediately after invocation. 3423 if (_handlers.unsubscribe[node]) { 3424 _handlers.unsubscribe[node](err); 3425 delete _handlers.unsubscribe[node]; 3426 } 3427 }, 3428 3429 /** 3430 * Handler for messages delivered by window.postMessage. Listens for events 3431 * published by the notification server, connection status published by 3432 * the JabberWerx library, and the resource ID created when the BOSH 3433 * connection has been established. 3434 * @param {Object} e 3435 * The message object as provided by the window.postMessage feature. 3436 * @private 3437 */ 3438 _messageHandler = function (e) { 3439 var 3440 3441 //Extract the message type and message data. The expected format is 3442 //"type|data" where type is a number represented by the TYPES object. 3443 delimPos = e.data.indexOf("|"), 3444 type = Number(e.data.substr(0, delimPos)), 3445 data = e.data.substr(delimPos + 1); 3446 3447 //Accepts messages and invoke the correct registered handlers. 3448 switch (type) { 3449 case _TYPES.EVENT: 3450 _eventCache = data; 3451 if (typeof _eventHandler === "function") { 3452 _eventHandler(data); 3453 } 3454 break; 3455 case _TYPES.STATUS: 3456 _statusCache = data; 3457 3458 //A "loaded" status means that the frame is ready to accept 3459 //credentials for establishing a BOSH connection. 3460 if (data === "loaded") { 3461 _isTunnelLoaded = true; 3462 if(_resource) { 3463 _sendMessage(_TYPES.RESOURCEID, _resource); 3464 } 3465 _sendMessage(_TYPES.ID, _id); 3466 _sendMessage(_TYPES.XMPPDOMAIN, _xmppDomain); 3467 _sendMessage(_TYPES.PASSWORD, _password); 3468 _sendMessage(_TYPES.PUBSUBDOMAIN, _pubsubDomain); 3469 } else if (typeof _connInfoHandler === "function") { 3470 _connInfoHandler(_createConnInfoObj()); 3471 } 3472 break; 3473 case _TYPES.RESOURCEID: 3474 _resourceID = data; 3475 break; 3476 case _TYPES.SUBSCRIBE: 3477 _processSubscribeResponse(data); 3478 break; 3479 case _TYPES.UNSUBSCRIBE: 3480 _processUnsubscribeResponse(data); 3481 break; 3482 case _TYPES.PRESENCE: 3483 if (typeof _presenceHandler === "function") { 3484 _presenceHandler(data); 3485 } 3486 break; 3487 default: 3488 break; 3489 } 3490 }, 3491 3492 /** 3493 * Initialize the tunnel config so that the url can be http or https with the appropriate port 3494 * @private 3495 */ 3496 _initTunnelConfig = function () { 3497 if (_isTunnelConfigInit === true) { 3498 return; 3499 } 3500 3501 //Initialize tunnel origin 3502 //Determine tunnel origin based on host and scheme 3503 _tunnelOriginPort = (scheme && scheme.indexOf("https") !== -1) ? "7443" : "7071"; 3504 if (scheme) { 3505 _tunnelOrigin = scheme + "://" + host + ":" + _tunnelOriginPort; 3506 } else { 3507 _tunnelOrigin = "http://" + host + ":" + _tunnelOriginPort; 3508 } 3509 _tunnelURL = _tunnelOrigin + "/tunnel/"; 3510 3511 _isTunnelConfigInit = true; 3512 }, 3513 3514 /** 3515 * Create the tunnel iframe which establishes the shared BOSH connection. 3516 * Messages are sent across frames using window.postMessage. 3517 * @private 3518 */ 3519 _createTunnel = function () { 3520 var tunnelID = ((self === parent) ? "tunnel-frame" : "autopilot-tunnel-frame"), 3521 iframe = document.createElement("iframe"); 3522 iframe.style.display = "none"; 3523 iframe.setAttribute("id", tunnelID); 3524 iframe.setAttribute("name", tunnelID); 3525 iframe.setAttribute("src", _tunnelURL); 3526 document.body.appendChild(iframe); 3527 _tunnelFrame = window.frames[tunnelID]; 3528 }; 3529 3530 /** 3531 * Sends a message via postmessage to the EventTunnel to attempt to connect to the XMPP server 3532 * @private 3533 */ 3534 this.makeConnectReq = function () { 3535 _sendMessage(_TYPES.PASSWORD, _password); 3536 }; 3537 3538 /** 3539 * @private 3540 * Returns the host of the Finesse server. 3541 * @returns {String} 3542 * The host specified during the creation of the object. 3543 */ 3544 this.getHost = function () { 3545 return host; 3546 }; 3547 3548 /** 3549 * @private 3550 * The resource ID of the user who is logged into the notification server. 3551 * @returns {String} 3552 * The resource ID generated by the notification server. 3553 */ 3554 this.getResourceID = function () { 3555 return _resourceID; 3556 }; 3557 3558 /** 3559 * @private 3560 * Indicates whether the tunnel frame is loaded. 3561 * @returns {Boolean} 3562 * True if the tunnel frame is loaded, false otherwise. 3563 */ 3564 this.isTunnelLoaded = function () { 3565 return _isTunnelLoaded; 3566 }; 3567 3568 /** 3569 * @private 3570 * The location of the tunnel HTML URL. 3571 * @returns {String} 3572 * The location of the tunnel HTML URL. 3573 */ 3574 this.getTunnelURL = function () { 3575 return _tunnelURL; 3576 }; 3577 3578 /** 3579 * @private 3580 * Tunnels a subscribe request to the eventing iframe. 3581 * @param {String} node 3582 * The node to subscribe to 3583 * @param {Function} handler 3584 * Handler to invoke upon success or failure 3585 */ 3586 this.subscribe = function (node, handler) { 3587 if (handler && typeof handler !== "function") { 3588 throw new Error("Parameter is not a function."); 3589 } 3590 _handlers.subscribe[node] = handler; 3591 _sendMessage(_TYPES.SUBSCRIBE, node); 3592 }; 3593 3594 /** 3595 * @private 3596 * Tunnels an unsubscribe request to the eventing iframe. 3597 * @param {String} node 3598 * The node to unsubscribe from 3599 * @param {Function} handler 3600 * Handler to invoke upon success or failure 3601 */ 3602 this.unsubscribe = function (node, handler) { 3603 if (handler && typeof handler !== "function") { 3604 throw new Error("Parameter is not a function."); 3605 } 3606 _handlers.unsubscribe[node] = handler; 3607 _sendMessage(_TYPES.UNSUBSCRIBE, node); 3608 }; 3609 3610 /** 3611 * @private 3612 * Registers a handler to be invoked when an event is delivered. Only one 3613 * is registered at a time. If there has already been an event that was 3614 * delivered, the handler will be invoked immediately. 3615 * @param {Function} handler 3616 * Invoked when an event is delivered through the event connection. 3617 */ 3618 this.registerEventHandler = function (handler) { 3619 if (typeof handler !== "function") { 3620 throw new Error("Parameter is not a function."); 3621 } 3622 _eventHandler = handler; 3623 if (_eventCache) { 3624 handler(_eventCache); 3625 } 3626 }; 3627 3628 /** 3629 * @private 3630 * Unregisters the event handler completely. 3631 */ 3632 this.unregisterEventHandler = function () { 3633 _eventHandler = undefined; 3634 }; 3635 3636 /** 3637 * @private 3638 * Registers a handler to be invoked when a presence event is delivered. Only one 3639 * is registered at a time. 3640 * @param {Function} handler 3641 * Invoked when a presence event is delivered through the event connection. 3642 */ 3643 this.registerPresenceHandler = function (handler) { 3644 if (typeof handler !== "function") { 3645 throw new Error("Parameter is not a function."); 3646 } 3647 _presenceHandler = handler; 3648 }; 3649 3650 /** 3651 * @private 3652 * Unregisters the presence event handler completely. 3653 */ 3654 this.unregisterPresenceHandler = function () { 3655 _presenceHandler = undefined; 3656 }; 3657 3658 /** 3659 * @private 3660 * Registers a handler to be invoked when a connection status changes. The 3661 * object passed will contain a "status" property, and a "resourceID" 3662 * property, which will contain the most current resource ID assigned to 3663 * the client. If there has already been an event that was delivered, the 3664 * handler will be invoked immediately. 3665 * @param {Function} handler 3666 * Invoked when a connection status changes. 3667 */ 3668 this.registerConnectionInfoHandler = function (handler) { 3669 if (typeof handler !== "function") { 3670 throw new Error("Parameter is not a function."); 3671 } 3672 _connInfoHandler = handler; 3673 if (_statusCache) { 3674 handler(_createConnInfoObj()); 3675 } 3676 }; 3677 3678 /** 3679 * @private 3680 * Unregisters the connection information handler. 3681 */ 3682 this.unregisterConnectionInfoHandler = function () { 3683 _connInfoHandler = undefined; 3684 }; 3685 3686 /** 3687 * @private 3688 * Start listening for events and create a event tunnel for the shared BOSH 3689 * connection. 3690 * @param {String} id 3691 * The ID of the user for the notification server. 3692 * @param {String} password 3693 * The password of the user for the notification server. 3694 * @param {String} xmppDomain 3695 * The XMPP domain of the notification server 3696 * @param {String} pubsubDomain 3697 * The location (JID) of the XEP-0060 PubSub service 3698 * @param {String} resource 3699 * The resource to connect to the notification servier with. 3700 */ 3701 this.init = function (id, password, xmppDomain, pubsubDomain, resource) { 3702 3703 if (typeof id !== "string" || typeof password !== "string" || typeof xmppDomain !== "string" || typeof pubsubDomain !== "string") { 3704 throw new Error("Invalid or missing required parameters."); 3705 } 3706 3707 _initTunnelConfig(); 3708 3709 _id = id; 3710 _password = password; 3711 _xmppDomain = xmppDomain; 3712 _pubsubDomain = pubsubDomain; 3713 _resource = resource; 3714 3715 //Attach a listener for messages sent from tunnel frame. 3716 _util.receiveMessage(_messageHandler, _tunnelOrigin); 3717 3718 //Create the tunnel iframe which will establish the shared connection. 3719 _createTunnel(); 3720 }; 3721 3722 //BEGIN TEST CODE// 3723 // /** 3724 // * Test code added to expose private functions that are used by unit test 3725 // * framework. This section of code is removed during the build process 3726 // * before packaging production code. The [begin|end]TestSection are used 3727 // * by the build to identify the section to strip. 3728 // * @ignore 3729 // */ 3730 // this.beginTestSection = 0; 3731 // 3732 // /** 3733 // * @ignore 3734 // */ 3735 // this.getTestObject = function () { 3736 // //Load mock dependencies. 3737 // var _mock = new MockControl(); 3738 // _util = _mock.createMock(finesse.utilities.Utilities); 3739 // 3740 // return { 3741 // //Expose mock dependencies 3742 // mock: _mock, 3743 // util: _util, 3744 // 3745 // //Expose internal private functions 3746 // types: _TYPES, 3747 // createConnInfoObj: _createConnInfoObj, 3748 // sendMessage: _sendMessage, 3749 // messageHandler: _messageHandler, 3750 // createTunnel: _createTunnel, 3751 // handlers: _handlers, 3752 // initTunnelConfig : _initTunnelConfig 3753 // }; 3754 // }; 3755 // 3756 // /** 3757 // * @ignore 3758 // */ 3759 // this.endTestSection = 0; 3760 // //END TEST CODE// 3761 }; 3762 3763 /** @namespace JavaScript class objects and methods to handle the subscription to Finesse events.*/ 3764 finesse.clientservices = finesse.clientservices || {}; 3765 3766 window.finesse = window.finesse || {}; 3767 window.finesse.clientservices = window.finesse.clientservices || {}; 3768 window.finesse.clientservices.MasterTunnel = MasterTunnel; 3769 3770 return MasterTunnel; 3771 3772 }); 3773 3774 /** 3775 * Contains a list of topics used for client side pubsub. 3776 * 3777 */ 3778 3779 /** @private */ 3780 define('clientservices/Topics',[], function () { 3781 3782 var Topics = (function () { 3783 3784 /** 3785 * @private 3786 * The namespace prepended to all Finesse topics. 3787 */ 3788 this.namespace = "finesse.info"; 3789 3790 /** 3791 * @private 3792 * Gets the full topic name with the Finesse namespace prepended. 3793 * @param {String} topic 3794 * The topic category. 3795 * @returns {String} 3796 * The full topic name with prepended namespace. 3797 */ 3798 var _getNSTopic = function (topic) { 3799 return this.namespace + "." + topic; 3800 }; 3801 3802 /** @scope finesse.clientservices.Topics */ 3803 return { 3804 /** 3805 * @private 3806 * Client side request channel. 3807 */ 3808 REQUESTS: _getNSTopic("requests"), 3809 3810 /** 3811 * @private 3812 * Client side response channel. 3813 */ 3814 RESPONSES: _getNSTopic("responses"), 3815 3816 /** 3817 * @private 3818 * Connection status. 3819 */ 3820 EVENTS_CONNECTION_INFO: _getNSTopic("connection"), 3821 3822 /** 3823 * @private 3824 * Presence channel 3825 */ 3826 PRESENCE: _getNSTopic("presence"), 3827 3828 /** 3829 * @private 3830 * Convert a Finesse REST URI to a OpenAjax compatible topic name. 3831 */ 3832 getTopic: function (restUri) { 3833 //The topic should not start with '/' else it will get replaced with 3834 //'.' which is invalid. 3835 //Thus, remove '/' if it is at the beginning of the string 3836 if (restUri.indexOf('/') === 0) { 3837 restUri = restUri.substr(1); 3838 } 3839 3840 //Replace every instance of "/" with ".". This is done to follow the 3841 //OpenAjaxHub topic name convention. 3842 return restUri.replace(/\//g, "."); 3843 } 3844 }; 3845 }()); 3846 window.finesse = window.finesse || {}; 3847 window.finesse.clientservices = window.finesse.clientservices || {}; 3848 /** @private */ 3849 window.finesse.clientservices.Topics = Topics; 3850 3851 return Topics; 3852 }); 3853 3854 /** The following comment is to prevent jslint errors about 3855 * using variables before they are defined. 3856 */ 3857 /*global finesse*/ 3858 3859 /** 3860 * Registers with the MasterTunnel to receive events, which it 3861 * could publish to the OpenAjax gadget pubsub infrastructure. 3862 * 3863 * @requires OpenAjax, finesse.clientservices.MasterTunnel, finesse.clientservices.Topics 3864 */ 3865 3866 /** @private */ 3867 define('clientservices/MasterPublisher',[ 3868 "clientservices/MasterTunnel", 3869 "clientservices/Topics", 3870 "utilities/Utilities" 3871 ], 3872 function (MasterTunnel, Topics, Utilities) { 3873 3874 var MasterPublisher = function (tunnel, hub) { 3875 if (!(tunnel instanceof MasterTunnel)) { 3876 throw new Error("Required tunnel object missing or invalid."); 3877 } 3878 3879 var 3880 3881 ClientServices = finesse.clientservices.ClientServices, 3882 3883 /** 3884 * Reference to the gadget pubsub Hub instance. 3885 * @private 3886 */ 3887 _hub = hub, 3888 3889 /** 3890 * Reference to the Topics class. 3891 * @private 3892 */ 3893 _topics = Topics, 3894 3895 /** 3896 * Reference to conversion utilities class. 3897 * @private 3898 */ 3899 _utils = Utilities, 3900 3901 /** 3902 * References to ClientServices logger methods 3903 * @private 3904 */ 3905 _logger = { 3906 log: ClientServices.log 3907 }, 3908 3909 /** 3910 * Store the passed in tunnel. 3911 * @private 3912 */ 3913 _tunnel = tunnel, 3914 3915 /** 3916 * Caches the connection info event so that it could be published if there 3917 * is a request for it. 3918 * @private 3919 */ 3920 _connInfoCache = {}, 3921 3922 /** 3923 * The types of possible request types supported when listening to the 3924 * requests channel. Each request type could result in different operations. 3925 * @private 3926 */ 3927 _REQTYPES = { 3928 CONNECTIONINFO: "ConnectionInfoReq", 3929 SUBSCRIBE: "SubscribeNodeReq", 3930 UNSUBSCRIBE: "UnsubscribeNodeReq", 3931 CONNECT: "ConnectionReq" 3932 }, 3933 3934 /** 3935 * Will store list of nodes that have OF subscriptions created 3936 * _nodesList[node][subscribing].reqIds[subid] 3937 * _nodesList[node][active].reqIds[subid] 3938 * _nodesList[node][unsubscribing].reqIds[subid] 3939 * _nodesList[node][holding].reqIds[subid] 3940 * @private 3941 */ 3942 _nodesList = {}, 3943 3944 /** 3945 * The states that a subscription can be in 3946 * @private 3947 */ 3948 _CHANNELSTATES = { 3949 UNINITIALIZED: "Uninitialized", 3950 PENDING: "Pending", 3951 OPERATIONAL: "Operational" 3952 }, 3953 3954 /** 3955 * Checks if the payload is JSON 3956 * @returns {Boolean} 3957 * @private 3958 */ 3959 _isJsonPayload = function(event) { 3960 var delimStart, delimEnd, retval = false; 3961 3962 try { 3963 delimStart = event.indexOf('{'); 3964 delimEnd = event.lastIndexOf('}'); 3965 3966 if ((delimStart !== -1 ) && (delimEnd === (event.length - 1))) { 3967 retval = true; //event contains JSON payload 3968 } 3969 } catch (err) { 3970 _logger.log("MasterPublisher._isJsonPayload() - Caught error: " + err); 3971 } 3972 return retval; 3973 }, 3974 3975 /** 3976 * Parses a JSON event and then publishes. 3977 * 3978 * @param {String} event 3979 * The full event payload. 3980 * @throws {Error} If the payload object is malformed. 3981 * @private 3982 */ 3983 _parseAndPublishJSONEvent = function(event) { 3984 var topic, eventObj, publishEvent, 3985 delimPos = event.indexOf("{"), 3986 node, parser, 3987 eventJson = event, 3988 returnObj = {node: null, data: null}; 3989 3990 try { 3991 //Extract and strip the node path from the message 3992 if (delimPos > 0) 3993 { 3994 //We need to decode the URI encoded node path 3995 //TODO: make sure this is kosher with OpenAjax topic naming 3996 node = decodeURI(event.substr(0, delimPos)); 3997 eventJson = event.substr(delimPos); 3998 3999 //Converting the node path to openAjaxhub topic 4000 topic = _topics.getTopic(node); 4001 4002 returnObj.node = node; 4003 returnObj.payload = eventJson; 4004 } 4005 else 4006 { 4007 _logger.log("MasterPublisher._parseAndPublishJSONEvent() - [ERROR] node is not given in postMessage: " + eventJson); 4008 throw new Error("node is not given in postMessage: " + eventJson); 4009 } 4010 4011 parser = _utils.getJSONParser(); 4012 4013 eventObj = parser.parse(eventJson); 4014 returnObj.data = eventObj; 4015 4016 } catch (err) { 4017 _logger.log("MasterPublisher._parseAndPublishJSONEvent() - [ERROR] Malformed event payload: " + err); 4018 throw new Error("Malformed event payload : " + err); 4019 } 4020 4021 _logger.log("MasterPublisher._parseAndPublishJSONEvent() - Received JSON event on node '" + node + "': " + eventJson); 4022 4023 publishEvent = {content : event, object : eventObj }; 4024 4025 //Publish event to proper event topic. 4026 if (topic && eventObj) { 4027 _hub.publish(topic, publishEvent); 4028 } 4029 }, 4030 4031 /** 4032 * Parses an XML event and then publishes. 4033 * 4034 * @param {String} event 4035 * The full event payload. 4036 * @throws {Error} If the payload object is malformed. 4037 * @private 4038 */ 4039 _parseAndPublishXMLEvent = function(event) { 4040 var topic, eventObj, publishEvent, restTopic, 4041 delimPos = event.indexOf("<"), 4042 node, 4043 eventXml = event; 4044 4045 try { 4046 //Extract and strip the node path from the message 4047 if (delimPos > 0) { 4048 //We need to decode the URI encoded node path 4049 //TODO: make sure this is kosher with OpenAjax topic naming 4050 node = decodeURI(event.substr(0, delimPos)); 4051 eventXml = event.substr(delimPos); 4052 //Converting the node path to openAjaxhub topic 4053 topic = _topics.getTopic(node); 4054 } else { 4055 _logger.log("MasterPublisher._parseAndPublishXMLEvent() - [ERROR] node is not given in postMessage: " + eventXml); 4056 throw new Error("node is not given in postMessage: " + eventXml); 4057 } 4058 4059 eventObj = _utils.xml2JsObj(eventXml); 4060 4061 } catch (err) { 4062 _logger.log("MasterPublisher._parseAndPublishXMLEvent() - [ERROR] Malformed event payload: " + err); 4063 throw new Error("Malformed event payload : " + err); 4064 } 4065 4066 _logger.log("MasterPublisher._parseAndPublishXMLEvent() - Received XML event on node '" + node + "': " + eventXml); 4067 4068 publishEvent = {content : event, object : eventObj }; 4069 4070 //Publish event to proper event topic. 4071 if (topic && eventObj) { 4072 _hub.publish(topic, publishEvent); 4073 } 4074 }, 4075 4076 /** 4077 * Publishes events to the appropriate topic. The topic name is determined 4078 * by fetching the source value from the event. 4079 * @param {String} event 4080 * The full event payload. 4081 * @throws {Error} If the payload object is malformed. 4082 * @private 4083 */ 4084 _eventHandler = function (event) { 4085 4086 //Handle JSON or XML events 4087 if (!_isJsonPayload(event)) 4088 { 4089 //XML 4090 _parseAndPublishXMLEvent(event); 4091 } 4092 else 4093 { 4094 //JSON 4095 _parseAndPublishJSONEvent(event); 4096 } 4097 }, 4098 4099 4100 /** 4101 * Handler for when presence events are sent through the MasterTunnel. 4102 * @returns {Object} 4103 * A presence xml event. 4104 * @private 4105 */ 4106 _presenceHandler = function (event) { 4107 var eventObj = _utils.xml2JsObj(event), publishEvent; 4108 4109 publishEvent = {content : event, object : eventObj}; 4110 4111 if (eventObj) { 4112 _hub.publish(_topics.PRESENCE, publishEvent); 4113 } 4114 }, 4115 4116 /** 4117 * Clone the connection info object from cache. 4118 * @returns {Object} 4119 * A connection info object containing a "status" and "resourceID". 4120 * @private 4121 */ 4122 _cloneConnInfoObj = function () { 4123 if (_connInfoCache) { 4124 return { 4125 status: _connInfoCache.status, 4126 resourceID: _connInfoCache.resourceID 4127 }; 4128 } else { 4129 return null; 4130 } 4131 }, 4132 4133 /** 4134 * Cleans up any outstanding subscribe/unsubscribe requests and notifies them of errors. 4135 * This is done if we get disconnected because we cleanup explicit subscriptions on disconnect. 4136 * @private 4137 */ 4138 _cleanupPendingRequests = function () { 4139 var node, curSubid, errObj = { 4140 error: { 4141 errorType: "Disconnected", 4142 errorMessage: "Outstanding request will never complete." 4143 } 4144 }; 4145 4146 // Iterate through all outstanding subscribe requests to notify them that it will never return 4147 for (node in _nodesList) { 4148 if (_nodesList.hasOwnProperty(node)) { 4149 for (curSubid in _nodesList[node].subscribing.reqIds) { 4150 if (_nodesList[node].subscribing.reqIds.hasOwnProperty(curSubid)) { 4151 // Notify this outstanding subscribe request to give up and error out 4152 _hub.publish(_topics.RESPONSES + "." + curSubid, errObj); 4153 } 4154 } 4155 for (curSubid in _nodesList[node].unsubscribing.reqIds) { 4156 if (_nodesList[node].unsubscribing.reqIds.hasOwnProperty(curSubid)) { 4157 // Notify this outstanding unsubscribe request to give up and error out 4158 _hub.publish(_topics.RESPONSES + "." + curSubid, errObj); 4159 } 4160 } 4161 } 4162 } 4163 }, 4164 4165 /** 4166 * Publishes the connection info to the connection info topic. 4167 * @param {Object} connInfo 4168 * The connection info object containing the status and resource ID. 4169 * @private 4170 */ 4171 _connInfoHandler = function (connInfo) { 4172 var before = _connInfoCache.status; 4173 _connInfoCache = connInfo; 4174 _logger.log("MasterPublisher._connInfoHandler() - Connection status: " + connInfo.status); 4175 _hub.publish(_topics.EVENTS_CONNECTION_INFO, _cloneConnInfoObj()); 4176 if (before === "connected" && connInfo.status !== "connected") { 4177 // Fail all pending requests when we transition to disconnected 4178 _cleanupPendingRequests(); 4179 } 4180 }, 4181 4182 4183 /** 4184 * Utility method to bookkeep node subscription requests and determine 4185 * whehter it is necessary to tunnel the request to JabberWerx. 4186 * @param {String} node 4187 * The node of interest 4188 * @param {String} reqId 4189 * A unique string identifying the request/subscription 4190 * @private 4191 */ 4192 _subscribeNode = function (node, subid) { 4193 if (_connInfoCache.status !== "connected") { 4194 _hub.publish(_topics.RESPONSES + "." + subid, { 4195 error: { 4196 errorType: "Not connected", 4197 errorMessage: "Cannot subscribe without connection." 4198 } 4199 }); 4200 return; 4201 } 4202 // NODE DOES NOT YET EXIST 4203 if (!_nodesList[node]) { 4204 _nodesList[node] = { 4205 "subscribing": { 4206 "reqIds": {}, 4207 "length": 0 4208 }, 4209 "active": { 4210 "reqIds": {}, 4211 "length": 0 4212 }, 4213 "unsubscribing": { 4214 "reqIds": {}, 4215 "length": 0 4216 }, 4217 "holding": { 4218 "reqIds": {}, 4219 "length": 0 4220 } 4221 }; 4222 } 4223 if (_nodesList[node].active.length === 0) { 4224 if (_nodesList[node].unsubscribing.length === 0) { 4225 if (_nodesList[node].subscribing.length === 0) { 4226 _nodesList[node].subscribing.reqIds[subid] = true; 4227 _nodesList[node].subscribing.length += 1; 4228 4229 _logger.log("MasterPublisher._subscribeNode() - Attempting to subscribe to node '" + node + "'"); 4230 _tunnel.subscribe(node, function (err) { 4231 var errObj, curSubid; 4232 if (err) { 4233 errObj = { 4234 subscribe: { 4235 content: err 4236 } 4237 }; 4238 4239 try { 4240 errObj.subscribe.object = gadgets.json.parse((_utils.xml2json(jQuery.parseXML(err), ""))); 4241 } catch (e) { 4242 errObj.error = { 4243 errorType: "parseError", 4244 errorMessage: "Could not serialize XML: " + e 4245 }; 4246 } 4247 _logger.log("MasterPublisher._subscribeNode() - Error subscribing to node '" + node + "': " + err); 4248 } else { 4249 _logger.log("MasterPublisher._subscribeNode() - Subscribed to node '" + node + "'"); 4250 } 4251 4252 for (curSubid in _nodesList[node].subscribing.reqIds) { 4253 if (_nodesList[node].subscribing.reqIds.hasOwnProperty(curSubid)) { 4254 _hub.publish(_topics.RESPONSES + "." + curSubid, errObj); 4255 if (!err) { 4256 _nodesList[node].active.reqIds[curSubid] = true; 4257 _nodesList[node].active.length += 1; 4258 } 4259 delete _nodesList[node].subscribing.reqIds[curSubid]; 4260 _nodesList[node].subscribing.length -= 1; 4261 } 4262 } 4263 }); 4264 4265 } else { //other ids are subscribing 4266 _nodesList[node].subscribing.reqIds[subid] = true; 4267 _nodesList[node].subscribing.length += 1; 4268 } 4269 } else { //An unsubscribe request is pending, hold onto these subscribes until it is done 4270 _nodesList[node].holding.reqIds[subid] = true; 4271 _nodesList[node].holding.length += 1; 4272 } 4273 } else { // The node has active subscriptions; add this subid and return successful response 4274 _nodesList[node].active.reqIds[subid] = true; 4275 _nodesList[node].active.length += 1; 4276 _hub.publish(_topics.RESPONSES + "." + subid, undefined); 4277 } 4278 }, 4279 4280 /** 4281 * Utility method to bookkeep node unsubscribe requests and determine 4282 * whehter it is necessary to tunnel the request to JabberWerx. 4283 * @param {String} node 4284 * The node to unsubscribe from 4285 * @param {String} reqId 4286 * A unique string identifying the subscription to remove 4287 * @private 4288 */ 4289 _unsubscribeNode = function (node, subid) { 4290 if (!_nodesList[node]) { //node DNE, publish success response 4291 _hub.publish(_topics.RESPONSES + "." + subid, undefined); 4292 } else { 4293 if (_connInfoCache.status !== "connected") { 4294 _hub.publish(_topics.RESPONSES + "." + subid, { 4295 error: { 4296 errorType: "Not connected", 4297 errorMessage: "Cannot unsubscribe without connection." 4298 } 4299 }); 4300 return; 4301 } 4302 if (_nodesList[node].active.length > 1) { 4303 delete _nodesList[node].active.reqIds[subid]; 4304 _hub.publish(_topics.RESPONSES + "." + subid, undefined); 4305 _nodesList[node].active.length -= 1; 4306 } else if (_nodesList[node].active.length === 1) { // transition subid from active category to unsubscribing category 4307 _nodesList[node].unsubscribing.reqIds[subid] = true; 4308 _nodesList[node].unsubscribing.length += 1; 4309 delete _nodesList[node].active.reqIds[subid]; 4310 _nodesList[node].active.length -= 1; 4311 4312 _logger.log("MasterPublisher._unsubscribeNode() - Attempting to unsubscribe from node '" + node + "'"); 4313 _tunnel.unsubscribe(node, function (err) { 4314 var errObj, curSubid; 4315 if (err) { 4316 errObj = { 4317 subscribe: { 4318 content: err 4319 } 4320 }; 4321 4322 try { 4323 errObj.subscribe.object = gadgets.json.parse((_utils.xml2json(jQuery.parseXML(err), ""))); 4324 } catch (e) { 4325 errObj.error = { 4326 errorType: "parseError", 4327 errorMessage: "Could not serialize XML: " + e 4328 }; 4329 } 4330 _logger.log("MasterPublisher._unsubscribeNode() - Error unsubscribing from node '" + node + "': " + err); 4331 } else { 4332 _logger.log("MasterPublisher._unsubscribeNode() - Unsubscribed from node '" + node + "'"); 4333 } 4334 4335 for (curSubid in _nodesList[node].unsubscribing.reqIds) { 4336 if (_nodesList[node].unsubscribing.reqIds.hasOwnProperty(curSubid)) { 4337 // publish to all subids whether unsubscribe failed or succeeded 4338 _hub.publish(_topics.RESPONSES + "." + curSubid, errObj); 4339 if (!err) { 4340 delete _nodesList[node].unsubscribing.reqIds[curSubid]; 4341 _nodesList[node].unsubscribing.length -= 1; 4342 } else { // Just remove the subid from unsubscribing; the next subscribe request will operate with node already created 4343 delete _nodesList[node].unsubscribing.reqIds[curSubid]; 4344 _nodesList[node].unsubscribing.length -= 1; 4345 } 4346 } 4347 } 4348 4349 if (!err && _nodesList[node].holding.length > 0) { // if any subscribe requests came in while unsubscribing from OF, now transition from holding to subscribing 4350 for (curSubid in _nodesList[node].holding.reqIds) { 4351 if (_nodesList[node].holding.reqIds.hasOwnProperty(curSubid)) { 4352 delete _nodesList[node].holding.reqIds[curSubid]; 4353 _nodesList[node].holding.length -= 1; 4354 _subscribeNode(node, curSubid); 4355 } 4356 } 4357 } 4358 }); 4359 } else { // length <= 0? 4360 _hub.publish(_topics.RESPONSES + "." + subid, undefined); 4361 } 4362 } 4363 }, 4364 4365 /** 4366 * Handles client requests to establish a BOSH connection. 4367 * @param {String} id 4368 * id of the xmpp user 4369 * @param {String} password 4370 * password of the xmpp user 4371 * @param {String} xmppDomain 4372 * xmppDomain of the xmpp user account 4373 * @private 4374 */ 4375 _connect = function (id, password, xmppDomain) { 4376 _tunnel.makeConnectReq(id, password, xmppDomain); 4377 }, 4378 4379 /** 4380 * Handles client requests made to the request topic. The type of the 4381 * request is described in the "type" property within the data payload. Each 4382 * type can result in a different operation. 4383 * @param {String} topic 4384 * The topic which data was published to. 4385 * @param {Object} data 4386 * The data containing requests information published by clients. 4387 * @param {String} data.type 4388 * The type of the request. Supported: "ConnectionInfoReq" 4389 * @param {Object} data.data 4390 * May contain data relevant for the particular requests. 4391 * @param {String} [data.invokeID] 4392 * The ID used to identify the request with the response. The invoke ID 4393 * will be included in the data in the publish to the topic. It is the 4394 * responsibility of the client to correlate the published data to the 4395 * request made by using the invoke ID. 4396 * @private 4397 */ 4398 _clientRequestHandler = function (topic, data) { 4399 var dataCopy; 4400 4401 //Ensure a valid data object with "type" and "data" properties. 4402 if (typeof data === "object" && 4403 typeof data.type === "string" && 4404 typeof data.data === "object") { 4405 switch (data.type) { 4406 case _REQTYPES.CONNECTIONINFO: 4407 //It is possible that Slave clients come up before the Master 4408 //client. If that is the case, the Slaves will need to make a 4409 //request for the Master to send the latest connection info to the 4410 //connectionInfo topic. 4411 dataCopy = _cloneConnInfoObj(); 4412 if (dataCopy) { 4413 if (data.invokeID !== undefined) { 4414 dataCopy.invokeID = data.invokeID; 4415 } 4416 _hub.publish(_topics.EVENTS_CONNECTION_INFO, dataCopy); 4417 } 4418 break; 4419 case _REQTYPES.SUBSCRIBE: 4420 if (typeof data.data.node === "string") { 4421 _subscribeNode(data.data.node, data.invokeID); 4422 } 4423 break; 4424 case _REQTYPES.UNSUBSCRIBE: 4425 if (typeof data.data.node === "string") { 4426 _unsubscribeNode(data.data.node, data.invokeID); 4427 } 4428 break; 4429 case _REQTYPES.CONNECT: 4430 // Deprecated: Disallow others (non-masters) from administering BOSH connections that are not theirs 4431 _logger.log("MasterPublisher._clientRequestHandler(): F403: Access denied. Non-master ClientService instances do not have the clearance level to make this request: makeConnectionReq"); 4432 break; 4433 default: 4434 break; 4435 } 4436 } 4437 }; 4438 4439 (function () { 4440 //Register to receive events and connection status from tunnel. 4441 _tunnel.registerEventHandler(_eventHandler); 4442 _tunnel.registerPresenceHandler(_presenceHandler); 4443 _tunnel.registerConnectionInfoHandler(_connInfoHandler); 4444 4445 //Listen to a request channel to respond to any requests made by other 4446 //clients because the Master may have access to useful information. 4447 _hub.subscribe(_topics.REQUESTS, _clientRequestHandler); 4448 }()); 4449 4450 /** 4451 * @private 4452 * Handles client requests to establish a BOSH connection. 4453 * @param {String} id 4454 * id of the xmpp user 4455 * @param {String} password 4456 * password of the xmpp user 4457 * @param {String} xmppDomain 4458 * xmppDomain of the xmpp user account 4459 */ 4460 this.connect = function (id, password, xmppDomain) { 4461 _connect(id, password, xmppDomain); 4462 }; 4463 4464 /** 4465 * @private 4466 * Resets the list of explicit subscriptions 4467 */ 4468 this.wipeout = function () { 4469 _cleanupPendingRequests(); 4470 _nodesList = {}; 4471 }; 4472 4473 //BEGIN TEST CODE// 4474 /** 4475 * Test code added to expose private functions that are used by unit test 4476 * framework. This section of code is removed during the build process 4477 * before packaging production code. The [begin|end]TestSection are used 4478 * by the build to identify the section to strip. 4479 * @ignore 4480 */ 4481 this.beginTestSection = 0; 4482 4483 /** 4484 * @ignore 4485 */ 4486 this.getTestObject = function () { 4487 //Load mock dependencies. 4488 var _mock = new MockControl(); 4489 _hub = _mock.createMock(gadgets.Hub); 4490 _tunnel = _mock.createMock(); 4491 4492 return { 4493 //Expose mock dependencies 4494 mock: _mock, 4495 hub: _hub, 4496 tunnel: _tunnel, 4497 setTunnel: function (tunnel) { 4498 _tunnel = tunnel; 4499 }, 4500 getTunnel: function () { 4501 return _tunnel; 4502 }, 4503 4504 //Expose internal private functions 4505 reqtypes: _REQTYPES, 4506 eventHandler: _eventHandler, 4507 presenceHandler: _presenceHandler, 4508 4509 subscribeNode: _subscribeNode, 4510 unsubscribeNode: _unsubscribeNode, 4511 4512 getNodeList: function () { 4513 return _nodesList; 4514 }, 4515 setNodeList: function (nodelist) { 4516 _nodesList = nodelist; 4517 }, 4518 4519 cloneConnInfoObj: _cloneConnInfoObj, 4520 connInfoHandler: _connInfoHandler, 4521 clientRequestHandler: _clientRequestHandler 4522 4523 }; 4524 }; 4525 4526 4527 /** 4528 * @ignore 4529 */ 4530 this.endTestSection = 0; 4531 //END TEST CODE// 4532 4533 }; 4534 4535 window.finesse = window.finesse || {}; 4536 window.finesse.clientservices = window.finesse.clientservices || {}; 4537 window.finesse.clientservices.MasterPublisher = MasterPublisher; 4538 4539 return MasterPublisher; 4540 }); 4541 4542 /** The following comment is to prevent jslint errors about 4543 * using variables before they are defined. 4544 */ 4545 /*global publisher:true */ 4546 4547 /** 4548 * Exposes a set of API wrappers that will hide the dirty work of 4549 * constructing Finesse API requests and consuming Finesse events. 4550 * 4551 * @requires OpenAjax, jQuery 1.5, finesse.utilities.Utilities 4552 */ 4553 4554 4555 /** 4556 * Allow clients to make Finesse API requests and consume Finesse events by 4557 * calling a set of exposed functions. The Services layer will do the dirty 4558 * work of establishing a shared BOSH connection (for designated Master 4559 * modules), consuming events for client subscriptions, and constructing API 4560 * requests. 4561 */ 4562 /** @private */ 4563 define('clientservices/ClientServices',[ 4564 "clientservices/MasterTunnel", 4565 "clientservices/MasterPublisher", 4566 "clientservices/Topics", 4567 "utilities/Utilities" 4568 ], 4569 function (MasterTunnel, MasterPublisher, Topics, Utilities) { 4570 4571 var ClientServices = (function () {/** @lends finesse.clientservices.ClientServices.prototype */ 4572 var 4573 4574 /** 4575 * Shortcut reference to the master tunnel 4576 * @private 4577 */ 4578 _tunnel, 4579 4580 _publisher, 4581 4582 /** 4583 * Shortcut reference to the finesse.utilities.Utilities singleton 4584 * This will be set by init() 4585 * @private 4586 */ 4587 _util, 4588 4589 /** 4590 * Shortcut reference to the gadgets.io object. 4591 * This will be set by init() 4592 * @private 4593 */ 4594 _io, 4595 4596 /** 4597 * Shortcut reference to the gadget pubsub Hub instance. 4598 * This will be set by init() 4599 * @private 4600 */ 4601 _hub, 4602 4603 /** 4604 * Logger object set externally by setLogger, defaults to nothing. 4605 * @private 4606 */ 4607 _logger = {}, 4608 4609 /** 4610 * Shortcut reference to the Topics class. 4611 * This will be set by init() 4612 * @private 4613 */ 4614 _topics, 4615 4616 /** 4617 * Config object needed to initialize this library 4618 * This must be set by init() 4619 * @private 4620 */ 4621 _config, 4622 4623 /** 4624 * @private 4625 * Whether or not this ClientService instance is a Master. 4626 */ 4627 _isMaster = false, 4628 4629 /** 4630 * @private 4631 * Whether the Client Services have been initiated yet. 4632 */ 4633 _inited = false, 4634 4635 /** 4636 * Stores the list of subscription IDs for all subscriptions so that it 4637 * could be retrieve for unsubscriptions. 4638 * @private 4639 */ 4640 _subscriptionID = {}, 4641 4642 /** 4643 * The possible states of the JabberWerx BOSH connection. 4644 * @private 4645 */ 4646 _STATUS = { 4647 CONNECTING: "connecting", 4648 CONNECTED: "connected", 4649 DISCONNECTED: "disconnected", 4650 DISCONNECTED_CONFLICT: "conflict", 4651 DISCONNECTED_UNAUTHORIZED: "unauthorized", 4652 DISCONNECTING: "disconnecting", 4653 RECONNECTING: "reconnecting", 4654 UNLOADING: "unloading", 4655 FAILING: "failing", 4656 RECOVERED: "recovered" 4657 }, 4658 4659 /** 4660 * Local reference for authMode enum object. 4661 * @private 4662 */ 4663 _authModes, 4664 4665 _failoverMode = false, 4666 4667 /** 4668 * Handler function to be invoked when BOSH connection is connecting. 4669 * @private 4670 */ 4671 _onConnectingHandler, 4672 4673 /** 4674 * Handler function to be invoked when BOSH connection is connected 4675 * @private 4676 */ 4677 _onConnectHandler, 4678 4679 /** 4680 * Handler function to be invoked when BOSH connection is disconnecting. 4681 * @private 4682 */ 4683 _onDisconnectingHandler, 4684 4685 /** 4686 * Handler function to be invoked when the BOSH is disconnected. 4687 * @private 4688 */ 4689 _onDisconnectHandler, 4690 4691 /** 4692 * Handler function to be invoked when the BOSH is reconnecting. 4693 * @private 4694 */ 4695 _onReconnectingHandler, 4696 4697 /** 4698 * Handler function to be invoked when the BOSH is unloading. 4699 * @private 4700 */ 4701 _onUnloadingHandler, 4702 4703 /** 4704 * Contains a cache of the latest connection info containing the current 4705 * state of the BOSH connection and the resource ID. 4706 * @private 4707 */ 4708 _connInfo, 4709 4710 /** 4711 * Keeps track of all the objects that need to be refreshed when we recover 4712 * due to our resilient connection. Only objects that we subscribe to will 4713 * be added to this list. 4714 * @private 4715 */ 4716 _refreshList = [], 4717 4718 /** 4719 * Needs to be passed as authorization header inside makeRequest wrapper function 4720 */ 4721 _authHeaderString, 4722 4723 /** 4724 * @private 4725 * Centralized logger.log method for external logger 4726 * @param {String} msg 4727 */ 4728 _log = function (msg) { 4729 // If the external logger throws up, it stops here. 4730 try { 4731 if (_logger.log) { 4732 _logger.log("[ClientServices] " + msg); 4733 } 4734 } catch (e) { } 4735 }, 4736 4737 /** 4738 * Go through each object in the _refreshList and call its refresh() function 4739 * @private 4740 */ 4741 _refreshObjects = function () { 4742 var i; 4743 4744 // wipe out the explicit subscription list before we refresh objects 4745 if (_publisher) { 4746 _publisher.wipeout(); 4747 } 4748 4749 // refresh each item in the refresh list 4750 for (i = _refreshList.length - 1; i >= 0; i -= 1) { 4751 _log("Refreshing " + _refreshList[i].getRestUrl()); 4752 _refreshList[i].refresh(10); 4753 } 4754 }, 4755 4756 /** 4757 * Handler to process connection info publishes. 4758 * @param {Object} data 4759 * The connection info data object. 4760 * @param {String} data.status 4761 * The BOSH connection status. 4762 * @param {String} data.resourceID 4763 * The resource ID for the connection. 4764 * @private 4765 */ 4766 _connInfoHandler = function (data) { 4767 4768 //Invoke registered handler depending on status received. Due to the 4769 //request topic where clients can make request for the Master to publish 4770 //the connection info, there is a chance that duplicate connection info 4771 //events may be sent, so ensure that there has been a state change 4772 //before invoking the handlers. 4773 if (_connInfo === undefined || _connInfo.status !== data.status) { 4774 _connInfo = data; 4775 switch (data.status) { 4776 case _STATUS.CONNECTING: 4777 if (_isMaster && _onConnectingHandler) { 4778 _onConnectingHandler(); 4779 } 4780 break; 4781 case _STATUS.CONNECTED: 4782 if ((_isMaster || !_failoverMode) && _onConnectHandler) { 4783 _onConnectHandler(); 4784 } 4785 break; 4786 case _STATUS.DISCONNECTED: 4787 if (_isMaster && _onDisconnectHandler) { 4788 _onDisconnectHandler(); 4789 } 4790 break; 4791 case _STATUS.DISCONNECTED_CONFLICT: 4792 if (_isMaster && _onDisconnectHandler) { 4793 _onDisconnectHandler("conflict"); 4794 } 4795 break; 4796 case _STATUS.DISCONNECTED_UNAUTHORIZED: 4797 if (_isMaster && _onDisconnectHandler) { 4798 _onDisconnectHandler("unauthorized"); 4799 } 4800 break; 4801 case _STATUS.DISCONNECTING: 4802 if (_isMaster && _onDisconnectingHandler) { 4803 _onDisconnectingHandler(); 4804 } 4805 break; 4806 case _STATUS.RECONNECTING: 4807 if (_isMaster && _onReconnectingHandler) { 4808 _onReconnectingHandler(); 4809 } 4810 break; 4811 case _STATUS.UNLOADING: 4812 if (_isMaster && _onUnloadingHandler) { 4813 _onUnloadingHandler(); 4814 } 4815 break; 4816 case _STATUS.FAILING: 4817 if (!_isMaster) { 4818 // Stop 4819 _failoverMode = true; 4820 if (_onDisconnectHandler) { 4821 _onDisconnectHandler(); 4822 } 4823 } 4824 break; 4825 case _STATUS.RECOVERED: 4826 if (!_isMaster) { 4827 _failoverMode = false; 4828 if (_onConnectHandler) { 4829 _onConnectHandler(); 4830 } 4831 } 4832 // Whenever we are recovered, we need to refresh any objects 4833 // that are stored. 4834 _refreshObjects(); 4835 break; 4836 } 4837 } 4838 }, 4839 4840 /** 4841 * Ensure that ClientServices have been inited. 4842 * @private 4843 */ 4844 _isInited = function () { 4845 if (!_inited) { 4846 throw new Error("ClientServices needs to be inited."); 4847 } 4848 }, 4849 4850 /** 4851 * Have the client become the Master by initiating a tunnel to a shared 4852 * event BOSH connection. The Master is responsible for publishing all 4853 * events to the pubsub infrastructure. 4854 * 4855 * TODO: Currently we only check the global auth mode. This code has to 4856 * handle mixed mode - in this case the user specfic SSO mode has to be 4857 * exposed via an API. 4858 * 4859 * @private 4860 */ 4861 _becomeMaster = function () { 4862 var creds , id; 4863 _tunnel = new MasterTunnel(_config.host, _config.scheme); 4864 _publisher = new MasterPublisher(_tunnel, _hub); 4865 if(_authModes.SSO === _config.systemAuthMode ) { 4866 creds = _config.authToken; 4867 } else { 4868 creds = _config.password; 4869 } 4870 _util = Utilities; 4871 id = _util.encodeNodeName(_config.id); 4872 _tunnel.init(id, creds, _config.xmppDomain, _config.pubsubDomain, _config.resource); 4873 _isMaster = true; 4874 }, 4875 4876 /** 4877 * Make a request to the request channel to have the Master publish the 4878 * connection info object. 4879 * @private 4880 */ 4881 _makeConnectionInfoReq = function () { 4882 var data = { 4883 type: "ConnectionInfoReq", 4884 data: {}, 4885 invokeID: (new Date()).getTime() 4886 }; 4887 _hub.publish(_topics.REQUESTS, data); 4888 }, 4889 4890 /** 4891 * Utility method to register a handler which is associated with a 4892 * particular connection status. 4893 * @param {String} status 4894 * The connection status string. 4895 * @param {Function} handler 4896 * The handler to associate with a particular connection status. 4897 * @throws {Error} 4898 * If the handler provided is not a function. 4899 * @private 4900 */ 4901 _registerHandler = function (status, handler) { 4902 if (typeof handler === "function") { 4903 if (_connInfo && _connInfo.status === status) { 4904 handler(); 4905 } 4906 switch (status) { 4907 case _STATUS.CONNECTING: 4908 _onConnectingHandler = handler; 4909 break; 4910 case _STATUS.CONNECTED: 4911 _onConnectHandler = handler; 4912 break; 4913 case _STATUS.DISCONNECTED: 4914 _onDisconnectHandler = handler; 4915 break; 4916 case _STATUS.DISCONNECTING: 4917 _onDisconnectingHandler = handler; 4918 break; 4919 case _STATUS.RECONNECTING: 4920 _onReconnectingHandler = handler; 4921 break; 4922 case _STATUS.UNLOADING: 4923 _onUnloadingHandler = handler; 4924 break; 4925 } 4926 4927 } else { 4928 throw new Error("Callback is not a function"); 4929 } 4930 }, 4931 4932 /** 4933 * @private 4934 * Retrieves systemAuthMode from parent Finesse Container. If parent is not available, mode will be retrieved from the systemInfo rest object 4935 * @throws {Error} 4936 * If unable to retrieve systemAuthMode 4937 */ 4938 _getSystemAuthMode = function(){ 4939 var parentFinesse , sysInfo; 4940 // For gadgets hosted outside of finesse container , finesse parent object will not be available 4941 try{ 4942 parentFinesse = window.parent.finesse; 4943 } catch (e){ 4944 parentFinesse = undefined; 4945 } 4946 4947 if( parentFinesse ){ 4948 _config.systemAuthMode = parentFinesse.container.Config.systemAuthMode; 4949 } else { 4950 sysInfo = new finesse.restservices.SystemInfo({ 4951 id: _config.id, 4952 onLoad: function (systemInfo) { 4953 _config.systemAuthMode = systemInfo.getSystemAuthMode(); 4954 }, 4955 onError: function (errRsp) { 4956 throw new Error("Unable to retrieve systemAuthMode from config. Initialization failed......"); 4957 } 4958 }); 4959 4960 } 4961 }; 4962 4963 return { 4964 4965 /** 4966 * @private 4967 * Adds an item to the list to be refreshed upon reconnect 4968 * @param {RestBase} object - rest object to be refreshed 4969 */ 4970 addToRefreshList: function (object) { 4971 _refreshList.push(object); 4972 }, 4973 4974 /** 4975 * @private 4976 * Removes the given item from the refresh list 4977 * @param {RestBase} object - rest object to be removed 4978 */ 4979 removeFromRefreshList: function (object) { 4980 var i; 4981 for (i = _refreshList.length - 1; i >= 0; i -= 1) { 4982 if (_refreshList[i] === object) { 4983 _refreshList.splice(i, 1); 4984 break; 4985 } 4986 } 4987 }, 4988 4989 /** 4990 * @private 4991 * The location of the tunnel HTML URL. 4992 * @returns {String} 4993 * The location of the tunnel HTML URL. 4994 */ 4995 getTunnelURL: function () { 4996 return _tunnel.getTunnelURL(); 4997 }, 4998 4999 /** 5000 * @private 5001 * Indicates whether the tunnel frame is loaded. 5002 * @returns {Boolean} 5003 * True if the tunnel frame is loaded, false otherwise. 5004 */ 5005 isTunnelLoaded: function () { 5006 return _tunnel.isTunnelLoaded(); 5007 }, 5008 5009 /** 5010 * @private 5011 * Indicates whether the ClientServices instance is a Master. 5012 * @returns {Boolean} 5013 * True if this instance of ClientServices is a Master, false otherwise. 5014 */ 5015 isMaster: function () { 5016 return _isMaster; 5017 }, 5018 5019 /** 5020 * @private 5021 * Get the resource ID. An ID is only available if the BOSH connection has 5022 * been able to connect successfully. 5023 * @returns {String} 5024 * The resource ID string. Null if the BOSH connection was never 5025 * successfully created and/or the resource ID has not been associated. 5026 */ 5027 getResourceID: function () { 5028 if (_connInfo !== undefined) { 5029 return _connInfo.resourceID; 5030 } 5031 return null; 5032 }, 5033 5034 /* 5035 getHub: function () { 5036 return _hub; 5037 }, 5038 */ 5039 /** 5040 * @private 5041 * Add a callback to be invoked when the BOSH connection is attempting 5042 * to connect. If the connection is already trying to connect, the 5043 * callback will be invoked immediately. 5044 * @param {Function} handler 5045 * An empty param function to be invoked on connecting. Only one 5046 * handler can be registered at a time. Handlers already registered 5047 * will be overwritten. 5048 */ 5049 registerOnConnectingHandler: function (handler) { 5050 _registerHandler(_STATUS.CONNECTING, handler); 5051 }, 5052 5053 /** 5054 * @private 5055 * Removes the on connecting callback that was registered. 5056 */ 5057 unregisterOnConnectingHandler: function () { 5058 _onConnectingHandler = undefined; 5059 }, 5060 5061 /** 5062 * Add a callback to be invoked when all of the following conditions are met: 5063 * <ul> 5064 * <li>When Finesse goes IN_SERVICE</li> 5065 * <li>The BOSH connection is established</li> 5066 * <li>The Finesse user presence becomes available</li> 5067 * </ul> 5068 * If all these conditions are met at the time this function is called, then 5069 * the handler will be invoked immediately. 5070 * @param {Function} handler 5071 * An empty param function to be invoked on connect. Only one handler 5072 * can be registered at a time. Handlers already registered will be 5073 * overwritten. 5074 * @example 5075 * finesse.clientservices.ClientServices.registerOnConnectHandler(gadget.myCallback); 5076 */ 5077 registerOnConnectHandler: function (handler) { 5078 _registerHandler(_STATUS.CONNECTED, handler); 5079 }, 5080 5081 /** 5082 * @private 5083 * Removes the on connect callback that was registered. 5084 */ 5085 unregisterOnConnectHandler: function () { 5086 _onConnectHandler = undefined; 5087 }, 5088 5089 /** 5090 * Add a callback to be invoked when any of the following occurs: 5091 * <ul> 5092 * <li>Finesse is no longer IN_SERVICE</li> 5093 * <li>The BOSH connection is lost</li> 5094 * <li>The presence of the Finesse user is no longer available</li> 5095 * </ul> 5096 * If any of these conditions are met at the time this function is 5097 * called, the callback will be invoked immediately. 5098 * @param {Function} handler 5099 * An empty param function to be invoked on disconnected. Only one 5100 * handler can be registered at a time. Handlers already registered 5101 * will be overwritten. 5102 * @example 5103 * finesse.clientservices.ClientServices.registerOnDisconnectHandler(gadget.myCallback); 5104 */ 5105 registerOnDisconnectHandler: function (handler) { 5106 _registerHandler(_STATUS.DISCONNECTED, handler); 5107 }, 5108 5109 /** 5110 * @private 5111 * Removes the on disconnect callback that was registered. 5112 */ 5113 unregisterOnDisconnectHandler: function () { 5114 _onDisconnectHandler = undefined; 5115 }, 5116 5117 /** 5118 * @private 5119 * Add a callback to be invoked when the BOSH is currently disconnecting. If 5120 * the connection is already disconnecting, invoke the callback immediately. 5121 * @param {Function} handler 5122 * An empty param function to be invoked on disconnected. Only one 5123 * handler can be registered at a time. Handlers already registered 5124 * will be overwritten. 5125 */ 5126 registerOnDisconnectingHandler: function (handler) { 5127 _registerHandler(_STATUS.DISCONNECTING, handler); 5128 }, 5129 5130 /** 5131 * @private 5132 * Removes the on disconnecting callback that was registered. 5133 */ 5134 unregisterOnDisconnectingHandler: function () { 5135 _onDisconnectingHandler = undefined; 5136 }, 5137 5138 /** 5139 * @private 5140 * Add a callback to be invoked when the BOSH connection is attempting 5141 * to connect. If the connection is already trying to connect, the 5142 * callback will be invoked immediately. 5143 * @param {Function} handler 5144 * An empty param function to be invoked on connecting. Only one 5145 * handler can be registered at a time. Handlers already registered 5146 * will be overwritten. 5147 */ 5148 registerOnReconnectingHandler: function (handler) { 5149 _registerHandler(_STATUS.RECONNECTING, handler); 5150 }, 5151 5152 /** 5153 * @private 5154 * Removes the on reconnecting callback that was registered. 5155 */ 5156 unregisterOnReconnectingHandler: function () { 5157 _onReconnectingHandler = undefined; 5158 }, 5159 5160 /** 5161 * @private 5162 * Add a callback to be invoked when the BOSH connection is unloading 5163 * 5164 * @param {Function} handler 5165 * An empty param function to be invoked on connecting. Only one 5166 * handler can be registered at a time. Handlers already registered 5167 * will be overwritten. 5168 */ 5169 registerOnUnloadingHandler: function (handler) { 5170 _registerHandler(_STATUS.UNLOADING, handler); 5171 }, 5172 5173 /** 5174 * @private 5175 * Removes the on unloading callback that was registered. 5176 */ 5177 unregisterOnUnloadingHandler: function () { 5178 _onUnloadingHandler = undefined; 5179 }, 5180 5181 /** 5182 * @private 5183 * Proxy method for gadgets.io.makeRequest. The will be identical to gadgets.io.makeRequest 5184 * ClientServices will mixin the BASIC Auth string, locale, and host, since the 5185 * configuration is encapsulated in here anyways. 5186 * This removes the dependency 5187 * @param {String} url 5188 * The relative url to make the request to (the host from the passed in config will be 5189 * appended). It is expected that any encoding to the URL is already done. 5190 * @param {Function} handler 5191 * Callback handler for makeRequest to invoke when the response returns. 5192 * Completely passed through to gadgets.io.makeRequest 5193 * @param {Object} params 5194 * The params object that gadgets.io.makeRequest expects. Authorization and locale 5195 * headers are mixed in. 5196 */ 5197 makeRequest: function (url, handler, params) { 5198 var requestedScheme, scheme = "http"; 5199 5200 // ClientServices needs to be initialized with a config for restHost, auth, and locale 5201 _isInited(); 5202 5203 // Allow mixin of auth and locale headers 5204 params = params || {}; 5205 5206 // Override refresh interval to 0 instead of default 3600 as a way to workaround makeRequest 5207 // using GET http method because then the params are added to the url as query params, which 5208 // exposes the authorization string in the url. This is a placeholder until oauth comes in 5209 params[gadgets.io.RequestParameters.REFRESH_INTERVAL] = params[gadgets.io.RequestParameters.REFRESH_INTERVAL] || 0; 5210 5211 params[gadgets.io.RequestParameters.HEADERS] = params[gadgets.io.RequestParameters.HEADERS] || {}; 5212 5213 // Add Basic auth to request header 5214 params[gadgets.io.RequestParameters.HEADERS].Authorization = _util.getAuthHeaderString(_config); 5215 5216 //Locale 5217 params[gadgets.io.RequestParameters.HEADERS].locale = _config.locale; 5218 5219 //Allow clients to override the scheme: 5220 // - If not specified => we use HTTP 5221 // - If null specified => we use _config.scheme 5222 // - Otherwise => we use whatever they provide 5223 requestedScheme = params.SCHEME; 5224 if (!(requestedScheme === undefined || requestedScheme === "undefined")) { 5225 if (requestedScheme === null) { 5226 scheme = _config.scheme; 5227 } else { 5228 scheme = requestedScheme; 5229 } 5230 } 5231 scheme = _config.restScheme || scheme; 5232 5233 _log("RequestedScheme: " + requestedScheme + "; Scheme: " + scheme); 5234 gadgets.io.makeRequest(encodeURI(scheme + "://" + _config.restHost + ":" + _config.localhostPort) + url, handler, params); 5235 }, 5236 5237 /** 5238 * @private 5239 * Utility function to make a subscription to a particular topic. Only one 5240 * callback function is registered to a particular topic at any time. 5241 * @param {String} topic 5242 * The full topic name. The topic name should follow the OpenAjax 5243 * convention using dot notation (ex: finesse.api.User.1000). 5244 * @param {Function} callback 5245 * The function that should be invoked with the data when an event 5246 * is delivered to the specific topic. 5247 * @returns {Boolean} 5248 * True if the subscription was made successfully and the callback was 5249 * been registered. False if the subscription already exist, the 5250 * callback was not overwritten. 5251 */ 5252 subscribe: function (topic, callback, disableDuringFailover) { 5253 _isInited(); 5254 5255 //Ensure that the same subscription isn't made twice. 5256 if (!_subscriptionID[topic]) { 5257 //Store the subscription ID using the topic name as the key. 5258 _subscriptionID[topic] = _hub.subscribe(topic, 5259 //Invoke the callback just with the data object. 5260 function (topic, data) { 5261 if (!disableDuringFailover || _isMaster || !_failoverMode) { 5262 // Halt all intermediate event processing while the master instance attempts to rebuild the connection. This only occurs: 5263 // - For RestBase objects (which pass the disableDuringFailover flag), so that intermediary events while recovering are hidden away until everything is all good 5264 // - We shouldn't be halting anything else because we have infrastructure requests that use the hub pub sub 5265 // - Master instance will get all events regardless, because it is responsible for managing failover 5266 // - If we are not in a failover mode, everything goes 5267 // _refreshObjects will reconcile anything that was missed once we are back in action 5268 callback(data); 5269 } 5270 }); 5271 return true; 5272 } 5273 return false; 5274 }, 5275 5276 /** 5277 * @private 5278 * Unsubscribe from a particular topic. 5279 * @param {String} topic 5280 * The full topic name. 5281 */ 5282 unsubscribe: function (topic) { 5283 _isInited(); 5284 5285 //Unsubscribe from the topic using the subscription ID recorded when 5286 //the subscription was made, then delete the ID from data structure. 5287 if (_subscriptionID[topic]) { 5288 _hub.unsubscribe(_subscriptionID[topic]); 5289 delete _subscriptionID[topic]; 5290 } 5291 }, 5292 5293 /** 5294 * @private 5295 * Make a request to the request channel to have the Master subscribe 5296 * to a node. 5297 * @param {String} node 5298 * The node to subscribe to. 5299 */ 5300 subscribeNode: function (node, handler) { 5301 if (handler && typeof handler !== "function") { 5302 throw new Error("ClientServices.subscribeNode: handler is not a function"); 5303 } 5304 5305 // Construct the request to send to MasterPublisher through the OpenAjax Hub 5306 var data = { 5307 type: "SubscribeNodeReq", 5308 data: {node: node}, 5309 invokeID: _util.generateUUID() 5310 }, 5311 responseTopic = _topics.RESPONSES + "." + data.invokeID, 5312 _this = this; 5313 5314 // We need to first subscribe to the response channel 5315 this.subscribe(responseTopic, function (rsp) { 5316 // Since this channel is only used for this singular request, 5317 // we are not interested anymore. 5318 // This is also critical to not leaking memory by having OpenAjax 5319 // store a bunch of orphaned callback handlers that enclose on 5320 // our entire ClientServices singleton 5321 _this.unsubscribe(responseTopic); 5322 if (handler) { 5323 handler(data.invokeID, rsp); 5324 } 5325 }); 5326 // Then publish the request on the request channel 5327 _hub.publish(_topics.REQUESTS, data); 5328 }, 5329 5330 /** 5331 * @private 5332 * Make a request to the request channel to have the Master unsubscribe 5333 * from a node. 5334 * @param {String} node 5335 * The node to unsubscribe from. 5336 */ 5337 unsubscribeNode: function (node, subid, handler) { 5338 if (handler && typeof handler !== "function") { 5339 throw new Error("ClientServices.unsubscribeNode: handler is not a function"); 5340 } 5341 5342 // Construct the request to send to MasterPublisher through the OpenAjax Hub 5343 var data = { 5344 type: "UnsubscribeNodeReq", 5345 data: { 5346 node: node, 5347 subid: subid 5348 }, 5349 invokeID: _util.generateUUID() 5350 }, 5351 responseTopic = _topics.RESPONSES + "." + data.invokeID, 5352 _this = this; 5353 5354 // We need to first subscribe to the response channel 5355 this.subscribe(responseTopic, function (rsp) { 5356 // Since this channel is only used for this singular request, 5357 // we are not interested anymore. 5358 // This is also critical to not leaking memory by having OpenAjax 5359 // store a bunch of orphaned callback handlers that enclose on 5360 // our entire ClientServices singleton 5361 _this.unsubscribe(responseTopic); 5362 if (handler) { 5363 handler(rsp); 5364 } 5365 }); 5366 // Then publish the request on the request channel 5367 _hub.publish(_topics.REQUESTS, data); 5368 }, 5369 5370 /** 5371 * @private 5372 * Make a request to the request channel to have the Master connect to the XMPP server via BOSH 5373 */ 5374 makeConnectionReq : function () { 5375 // Disallow others (non-masters) from administering BOSH connections that are not theirs 5376 if (_isMaster && _publisher) { 5377 _publisher.connect(_config.id, _config.password, _config.xmppDomain); 5378 } else { 5379 _log("F403: Access denied. Non-master ClientService instances do not have the clearance level to make this request: makeConnectionReq"); 5380 } 5381 }, 5382 5383 /** 5384 * @private 5385 * Set's the global logger for this Client Services instance. 5386 * @param {Object} logger 5387 * Logger object with the following attributes defined:<ul> 5388 * <li><b>log:</b> function (msg) to simply log a message 5389 * </ul> 5390 */ 5391 setLogger: function (logger) { 5392 // We want to check the logger coming in so we don't have to check every time it is called. 5393 if (logger && typeof logger === "object" && typeof logger.log === "function") { 5394 _logger = logger; 5395 } else { 5396 // We are resetting it to an empty object so that _logger.log in .log is falsy. 5397 _logger = {}; 5398 } 5399 }, 5400 5401 /** 5402 * @private 5403 * Centralized logger.log method for external logger 5404 * @param {String} msg 5405 * Message to log 5406 */ 5407 log: _log, 5408 5409 /** 5410 * @class 5411 * Allow clients to make Finesse API requests and consume Finesse events by 5412 * calling a set of exposed functions. The Services layer will do the dirty 5413 * work of establishing a shared BOSH connection (for designated Master 5414 * modules), consuming events for client subscriptions, and constructing API 5415 * requests. 5416 * 5417 * @constructs 5418 */ 5419 _fakeConstuctor: function () { 5420 /* This is here so we can document init() as a method rather than as a constructor. */ 5421 }, 5422 5423 /** 5424 * Initiates the Client Services with the specified config parameters. 5425 * Enabling the Client Services as Master will trigger the establishment 5426 * of a BOSH event connection. 5427 * @param {finesse.gadget.Config} config 5428 * Configuration object containing properties used for making REST requests:<ul> 5429 * <li><b>host:</b> The Finesse server IP/host as reachable from the browser 5430 * <li><b>restHost:</b> The Finesse API IP/host as reachable from the gadget container 5431 * <li><b>id:</b> The ID of the user. This is an optional param as long as the 5432 * appropriate authorization string is provided, otherwise it is 5433 * required.</li> 5434 * <li><b>password:</b> The password belonging to the user. This is an optional param as 5435 * long as the appropriate authorization string is provided, 5436 * otherwise it is required.</li> 5437 * <li><b>authorization:</b> The base64 encoded "id:password" authentication string. This 5438 * param is provided to allow the ability to hide the password 5439 * param. If provided, the id and the password extracted from this 5440 * string will be used over the config.id and config.password.</li> 5441 * </ul> 5442 * @throws {Error} If required constructor parameter is missing. 5443 * @example 5444 * finesse.clientservices.ClientServices.init(finesse.gadget.Config); 5445 */ 5446 init: function (config) { 5447 if (!_inited) { 5448 //Validate the properties within the config object if one is provided. 5449 if (!(typeof config === "object" && 5450 typeof config.host === "string" && config.host.length > 0 && config.restHost && 5451 (typeof config.authorization === "string" || 5452 (typeof config.id === "string")))) { 5453 throw new Error("Config object contains invalid properties."); 5454 } 5455 5456 // Initialize configuration 5457 _config = config; 5458 5459 // Set shortcuts 5460 _util = Utilities; 5461 _authModes = _util.getAuthModes(); 5462 _topics = Topics; 5463 5464 //TODO: document when this is properly supported 5465 // Allows hub and io dependencies to be passed in. Currently only used for unit tests. 5466 _hub = config.hub || gadgets.Hub; 5467 _io = config.io || gadgets.io; 5468 5469 //If the authorization string is provided, then use that to 5470 //extract the ID and the password. Otherwise use the ID and 5471 //password from the respective ID and password params. 5472 if (_config.authorization) { 5473 var creds = _util.getCredentials(_config.authorization); 5474 _config.id = creds.id; 5475 _config.password = creds.password; 5476 } 5477 else { 5478 _config.authorization = _util.b64Encode( 5479 _config.id + ":" + _config.password); 5480 } 5481 5482 //In case if gadgets create their own config instance , add systemAuthMode property inside config object 5483 if(!_config.systemAuthMode || _config.systemAuthMode === ""){ 5484 _getSystemAuthMode(); 5485 } 5486 5487 if(!_config.authToken && _config.systemAuthMode === _authModes.SSO){ 5488 _config.authToken = _util.getToken(); 5489 if(!_config.authToken){ 5490 throw new Error("Access token is unavailable inside Config object."); 5491 } 5492 } 5493 5494 _inited = true; 5495 5496 if (_hub) { 5497 //Subscribe to receive connection information. Since it is possible that 5498 //the client comes up after the Master comes up, the client will need 5499 //to make a request to have the Master send the latest connection info. 5500 //It would be possible that all clients get connection info again. 5501 this.subscribe(_topics.EVENTS_CONNECTION_INFO, _connInfoHandler); 5502 _makeConnectionInfoReq(); 5503 } 5504 } 5505 5506 //Return the CS object for object chaining. 5507 return this; 5508 }, 5509 5510 /** 5511 * @private 5512 * Initializes the BOSH component of this ClientServices instance. This establishes 5513 * the BOSH connection and will trigger the registered handlers as the connection 5514 * status changes respectively:<ul> 5515 * <li>registerOnConnectingHandler</li> 5516 * <li>registerOnConnectHandler</li> 5517 * <li>registerOnDisconnectHandler</li> 5518 * <li>registerOnDisconnectingHandler</li> 5519 * <li>registerOnReconnectingHandler</li> 5520 * <li>registerOnUnloadingHandler</li> 5521 * <ul> 5522 * 5523 * @param {Object} config 5524 * An object containing the following (optional) handlers for the request:<ul> 5525 * <li><b>xmppDomain:</b> {String} The domain of the XMPP server. Available from the SystemInfo object. 5526 * This is used to construct the JID: user@domain.com</li> 5527 * <li><b>pubsubDomain:</b> {String} The pub sub domain where the pub sub service is running. 5528 * Available from the SystemInfo object. 5529 * This is used for creating or removing subscriptions.</li> 5530 * <li><b>resource:</b> {String} The resource to connect to the notification server with.</li> 5531 * </ul> 5532 */ 5533 initBosh: function (config) { 5534 //Validate the properties within the config object if one is provided. 5535 if (!(typeof config === "object" && typeof config.xmppDomain === "string" && typeof config.pubsubDomain === "string")) { 5536 throw new Error("Config object contains invalid properties."); 5537 } 5538 5539 // Mixin the required information for establishing the BOSH connection 5540 _config.xmppDomain = config.xmppDomain; 5541 _config.pubsubDomain = config.pubsubDomain; 5542 _config.resource = config.resource; 5543 5544 //Initiate Master launch sequence 5545 _becomeMaster(); 5546 }, 5547 5548 /** 5549 * @private 5550 * Sets the failover mode to either FAILING or RECOVERED. This will only occur in the master instance and is meant to be 5551 * used by a stereotypical failover monitor type module to notify non-master instances (i.e. in gadgets) 5552 * @param {Object} failoverMode 5553 * true if failing, false or something falsy when recovered 5554 */ 5555 setFailoverMode: function (failoverMode) { 5556 if (_isMaster) { 5557 _hub.publish(_topics.EVENTS_CONNECTION_INFO, { 5558 status: (failoverMode ? _STATUS.FAILING : _STATUS.RECOVERED) 5559 }); 5560 } 5561 }, 5562 5563 /** 5564 * @private 5565 * Private accessor used to inject mocked private dependencies for unit testing 5566 */ 5567 _getTestObj: function () { 5568 return { 5569 setPublisher: function (publisher) { 5570 _publisher = publisher; 5571 } 5572 }; 5573 } 5574 }; 5575 }()); 5576 5577 window.finesse = window.finesse || {}; 5578 window.finesse.clientservices = window.finesse.clientservices || {}; 5579 window.finesse.clientservices.ClientServices = ClientServices; 5580 5581 return ClientServices; 5582 5583 }); 5584 5585 /** 5586 * The following comment prevents JSLint errors concerning undefined global variables. 5587 * It tells JSLint that these identifiers are defined elsewhere. 5588 */ 5589 /*jslint bitwise:true, browser:true, nomen:true, regexp:true, sloppy:true, white:true */ 5590 5591 /** The following comment is to prevent jslint errors about 5592 * using variables before they are defined. 5593 */ 5594 /*global Handlebars */ 5595 5596 /** 5597 * JavaScript class to implement common notification 5598 * functionality. 5599 * 5600 * @requires Class 5601 * @requires finesse.FinesseBase 5602 */ 5603 /** @private */ 5604 define('restservices/Notifier',[ 5605 'FinesseBase', 5606 'clientservices/ClientServices' 5607 ], 5608 function (FinesseBase, ClientServices) { 5609 var Notifier = FinesseBase.extend({ 5610 /** 5611 * Initializes the notifier object. 5612 */ 5613 init : function () { 5614 this._super(); 5615 this._listenerCallback = []; 5616 }, 5617 5618 /** 5619 * Add a listener. 5620 * 5621 * @param callback_function 5622 * @param scope 5623 * is the callback function to add 5624 */ 5625 addListener : function (callback_function, scope) { 5626 var len = this._listenerCallback.length, i, cb, add = true; 5627 for (i = 0; i < len; i += 1) { 5628 cb = this._listenerCallback[i].callback; 5629 if (cb === callback_function) { 5630 // this callback already exists 5631 add = false; 5632 break; 5633 } 5634 } 5635 if (add) { 5636 this._listenerCallback.push({ "callback": this._isAFunction(callback_function), "scope": (scope || window) }); 5637 } 5638 }, 5639 5640 /** 5641 * Remove a listener. 5642 * 5643 * @param callback_function 5644 * is the callback function to remove 5645 * @return {Boolean} true if removed 5646 */ 5647 removeListener : function (callback_function) { 5648 5649 var result = false, len = this._listenerCallback.length, i, cb; 5650 for (i = len - 1; i >= 0; i -=1) { 5651 cb = this._listenerCallback[i].callback; 5652 if (cb === callback_function) { 5653 this._listenerCallback[i] = undefined; 5654 this._listenerCallback.splice(i, 1); 5655 result = true; 5656 break; 5657 } 5658 } 5659 5660 return result; 5661 }, 5662 5663 /** 5664 * Removes all listeners 5665 * @return {undefined} 5666 */ 5667 reset: function () { 5668 this._listenerCallback = []; 5669 }, 5670 5671 /** 5672 * Notify all listeners. 5673 * 5674 * @param obj 5675 * is the object that has changed 5676 */ 5677 notifyListeners : function (obj) { 5678 var len = this._listenerCallback.length, i, callbackFunction, scope; 5679 5680 for (i = 0; i < len; i += 1) { 5681 // Be sure that one bad callback does not prevent other listeners 5682 // from receiving. 5683 try { 5684 callbackFunction = this._listenerCallback[i].callback; 5685 scope = this._listenerCallback[i].scope; 5686 if (typeof callbackFunction === 'function') { 5687 callbackFunction.call(scope, obj); 5688 } 5689 } catch (err) { 5690 ClientServices.log("Exception caught: " + err); 5691 } 5692 } 5693 }, 5694 5695 /** 5696 * Gets a copy of the listeners. 5697 * @return changeListenerCopy (array of callbacks) 5698 */ 5699 getListeners : function () { 5700 var changeListenerCopy = [], len = this._listenerCallback.length, i; 5701 5702 for (i = 0; i < len; i += 1) { 5703 changeListenerCopy.push(this._listenerCallback[i].callback); 5704 } 5705 5706 return changeListenerCopy; 5707 }, 5708 5709 /** 5710 * Verifies that the handler is function. 5711 * @param handler to verify 5712 * @return the handler 5713 * @throws Error if not a function 5714 */ 5715 _isAFunction : function (handler) { 5716 if (handler === undefined || typeof handler === "function") { 5717 return handler; 5718 } else { 5719 throw new Error("handler must be a function"); 5720 } 5721 } 5722 }); 5723 5724 window.finesse = window.finesse || {}; 5725 window.finesse.restservices = window.finesse.restservices || {}; 5726 window.finesse.restservices.Notifier = Notifier; 5727 5728 /** @namespace JavaScript classes and methods that represent REST objects and collections. */ 5729 finesse.restservices = finesse.restservices || {}; 5730 5731 return Notifier; 5732 }); 5733 5734 /** 5735 * JavaScript base object that all REST objects should inherit 5736 * from because it encapsulates and provides the common functionality that 5737 * all REST objects need. 5738 * 5739 * @requires finesse.clientservices.ClientServices 5740 * @requires Class 5741 */ 5742 5743 /** @private */ 5744 define('restservices/RestBase',[ 5745 "FinesseBase", 5746 "utilities/Utilities", 5747 "restservices/Notifier", 5748 "clientservices/ClientServices", 5749 "clientservices/Topics" 5750 ], 5751 function (FinesseBase, Utilities, Notifier, ClientServices, Topics) { 5752 5753 var RestBase = FinesseBase.extend(/** @lends finesse.restservices.RestBase.prototype */{ 5754 5755 doNotLog: false, 5756 5757 /** 5758 * Used by _processUpdate() and restRequest(). 5759 * Maps requestIds to object-wrapped callbacks passed to restRequest(), 5760 * so that one of the callbacks can be fired when a corresponding event is 5761 * received inside _processUpdate(). 5762 * @private 5763 */ 5764 _pendingCallbacks: {}, 5765 5766 /** 5767 * Gets the REST class for the current object. This object throws an 5768 * exception because subtype must implement. 5769 * @throws {Error} because subtype must implement 5770 * @private 5771 */ 5772 getRestClass: function () { 5773 throw new Error("getRestClass(): Not implemented in subtype."); 5774 }, 5775 5776 /** 5777 * Gets the REST type for the current object. This object throws an 5778 * exception because subtype must implement. 5779 * @throws {Error} because subtype must implement. 5780 * @private 5781 */ 5782 getRestType: function () { 5783 throw new Error("getRestType(): Not implemented in subtype."); 5784 }, 5785 5786 /** 5787 * Gets the node path for the current object. 5788 * @private 5789 */ 5790 getXMPPNodePath: function () { 5791 return this.getRestUrl(); 5792 }, 5793 5794 /** 5795 * Boolean function that specifies whether the REST object supports 5796 * requests. True by default. Subclasses should override if false. 5797 * @private 5798 */ 5799 supportsRequests: true, 5800 5801 /** 5802 * Boolean function that specifies whether the REST object supports 5803 * subscriptions. True by default. Subclasses should override if false. 5804 * @private 5805 */ 5806 supportsSubscriptions: true, 5807 5808 /** 5809 * Boolean function that specifies whether the REST object should retain 5810 * a copy of the REST response. False by default. Subclasses should override if true. 5811 * @private 5812 */ 5813 keepRestResponse: false, 5814 5815 /** 5816 * Number that represents the REST Response status that is returned. Only 5817 * set if keepRestResponse is set to true. 5818 * @public 5819 */ 5820 restResponseStatus: null, 5821 5822 /** 5823 * Object to store additional headers to be sent with the REST Request, null by default. 5824 * @private 5825 */ 5826 extraHeaders: null, 5827 5828 /** 5829 * Boolean function that specifies whether the REST object explicitly 5830 * subscribes. False by default. Subclasses should override if true. 5831 * @private 5832 */ 5833 explicitSubscription: false, 5834 5835 /** 5836 * Boolean function that specifies whether subscribing should be 5837 * automatically done at construction. Defaults to true. 5838 * This be overridden at object construction, not by implementing subclasses 5839 * @private 5840 */ 5841 autoSubscribe: true, 5842 5843 /** 5844 * Private reference to default logger 5845 * @private 5846 */ 5847 _logger: { 5848 log: ClientServices.log, 5849 error: ClientServices.log 5850 }, 5851 5852 /** 5853 * @class 5854 * JavaScript representation of a REST object. Also exposes methods to operate 5855 * on the object against the server. This object is typically extended into individual 5856 * REST Objects (like Dialog, User, etc...), and shouldn't be used directly. 5857 * 5858 * @constructor 5859 * @param {String} id 5860 * The ID that uniquely identifies the REST object. 5861 * @param {Object} callbacks 5862 * An object containing callbacks for instantiation and runtime 5863 * Callback to invoke upon successful instantiation, passes in REST object. 5864 * @param {Function} callbacks.onLoad(this) 5865 * Callback to invoke upon loading the data for the first time. 5866 * @param {Function} callbacks.onChange(this) 5867 * Callback to invoke upon successful update object (PUT) 5868 * @param {Function} callbacks.onAdd(this) 5869 * Callback to invoke upon successful update to add object (POST) 5870 * @param {Function} callbacks.onDelete(this) 5871 * Callback to invoke upon successful update to delete object (DELETE) 5872 * @param {Function} callbacks.onError(rsp) 5873 * Callback to invoke on update error (refresh or event) 5874 * as passed by finesse.restservices.RestBase.restRequest() 5875 * { 5876 * status: {Number} The HTTP status code returned 5877 * content: {String} Raw string of response 5878 * object: {Object} Parsed object of response 5879 * error: {Object} Wrapped exception that was caught 5880 * error.errorType: {String} Type of error that was caught 5881 * error.errorMessage: {String} Message associated with error 5882 * } 5883 * @param {RestBase} [restObj] 5884 * A RestBase parent object which this object has an association with. 5885 * @constructs 5886 */ 5887 init: function (options, callbacks, restObj) { 5888 /** 5889 * Initialize the base class 5890 */ 5891 var _this = this; 5892 5893 this._super(); 5894 5895 if (typeof options === "object") { 5896 this._id = options.id; 5897 this._restObj = options.parentObj; 5898 this.autoSubscribe = (options.autoSubscribe === false) ? false : true; 5899 this.doNotSubscribe = options.doNotSubscribe; 5900 this.doNotRefresh = this.doNotRefresh || options.doNotRefresh; 5901 callbacks = { 5902 onLoad: options.onLoad, 5903 onChange: options.onChange, 5904 onAdd: options.onAdd, 5905 onDelete: options.onDelete, 5906 onError: options.onError 5907 }; 5908 } else { 5909 this._id = options; 5910 this._restObj = restObj; 5911 } 5912 5913 // Common stuff 5914 5915 this._data = {}; 5916 5917 //Contains the full rest response to be processed by upper layers if needed 5918 this._restResponse = undefined; 5919 5920 this._lastUpdate = {}; 5921 5922 this._util = Utilities; 5923 5924 //Should be correctly initialized in either a window OR gadget context 5925 this._config = finesse.container.Config; 5926 5927 // Setup all the notifiers - change, load and error. 5928 this._changeNotifier = new Notifier(); 5929 this._loadNotifier = new Notifier(); 5930 this._addNotifier = new Notifier(); 5931 this._deleteNotifier = new Notifier(); 5932 this._errorNotifier = new Notifier(); 5933 5934 this._loaded = false; 5935 5936 // Protect against null dereferencing of options allowing its 5937 // (nonexistent) keys to be read as undefined 5938 callbacks = callbacks || {}; 5939 5940 this.addHandler('load', callbacks.onLoad); 5941 this.addHandler('change', callbacks.onChange); 5942 this.addHandler('add', callbacks.onAdd); 5943 this.addHandler('delete', callbacks.onDelete); 5944 this.addHandler('error', callbacks.onError); 5945 5946 // Attempt to get the RestType then synchronize 5947 try { 5948 this.getRestType(); 5949 5950 // Only subscribe if this REST object supports subscriptions 5951 // and autoSubscribe was not requested to be disabled as a construction option 5952 if (this.supportsSubscriptions && this.autoSubscribe && !this.doNotSubscribe) { 5953 this.subscribe({ 5954 success: function () { 5955 //TODO: figure out how to use Function.call() or Function.apply() here... 5956 //this is exactly the same as the below else case other than the scope of "this" 5957 if (typeof options === "object" && options.data) { 5958 if (!_this._processObject(_this._normalize(options.data))) { 5959 // notify of error if we fail to construct 5960 _this._errorNotifier.notifyListeners(_this); 5961 } 5962 } else { 5963 // Only subscribe if this REST object supports requests 5964 if (_this.supportsRequests) { 5965 _this._synchronize(); 5966 } 5967 } 5968 }, 5969 error: function (err) { 5970 _this._errorNotifier.notifyListeners(err); 5971 } 5972 }); 5973 } else { 5974 if (typeof options === "object" && options.data) { 5975 if (!this._processObject(this._normalize(options.data))) { 5976 // notify of error if we fail to construct 5977 this._errorNotifier.notifyListeners(this); 5978 } 5979 } else { 5980 // Only subscribe if this REST object supports requests 5981 if (this.supportsRequests) { 5982 this._synchronize(); 5983 } 5984 } 5985 } 5986 5987 } catch (err) { 5988 this._logger.error('id=' + this._id + ': ' + err); 5989 } 5990 }, 5991 5992 /** 5993 * Determines if the object has a particular property. 5994 * @param obj is the object to examine 5995 * @param property is the property to check for 5996 * @returns {Boolean} 5997 */ 5998 hasProperty: function (obj, prop) { 5999 return (obj !== null) && (obj.hasOwnProperty(prop)); 6000 }, 6001 6002 /** 6003 * Gets a property from the object. 6004 * @param obj is the object to examine 6005 * @param property is the property to get 6006 * @returns {Property Value} or {Null} if not found 6007 */ 6008 getProperty: function (obj, property) { 6009 var result = null; 6010 6011 if (this.hasProperty(obj, property) === false) { 6012 result = null; 6013 } else { 6014 result = obj[property]; 6015 } 6016 return result; 6017 }, 6018 6019 /** 6020 * Utility to extracts the ID from the specified REST URI. This is with the 6021 * assumption that the ID is always the last element in the URI after the 6022 * "/" delimiter. 6023 * @param {String} restUri 6024 * The REST uri (i.e. /finesse/api/User/1000). 6025 * @private 6026 */ 6027 _extractId: function (restObj) { 6028 var obj, restUri = "", strLoc; 6029 for (obj in restObj) { 6030 if (restObj.hasOwnProperty(obj)) { 6031 restUri = restObj[obj].uri; 6032 break; 6033 } 6034 } 6035 return Utilities.getId(restUri); 6036 }, 6037 6038 /** 6039 * Gets the data for this object. 6040 * @returns {Object} which is contained in data 6041 */ 6042 getData: function () { 6043 return this._data; 6044 }, 6045 6046 /** 6047 * Gets the complete REST response to the request made 6048 * @returns {Object} which is contained in data 6049 * @private 6050 */ 6051 getRestResponse: function () { 6052 return this._restResponse; 6053 }, 6054 6055 /** 6056 * The REST URL in which this object can be referenced. 6057 * @return {String} 6058 * The REST URI for this object. 6059 * @private 6060 */ 6061 getRestUrl: function () { 6062 var 6063 restObj = this._restObj, 6064 restUrl = ""; 6065 6066 //Prepend the base REST object if one was provided. 6067 if (restObj instanceof RestBase) { 6068 restUrl += restObj.getRestUrl(); 6069 } 6070 //Otherwise prepend with the default webapp name. 6071 else { 6072 restUrl += "/finesse/api"; 6073 } 6074 6075 //Append the REST type. 6076 restUrl += "/" + this.getRestType(); 6077 6078 //Append ID if it is not undefined, null, or empty. 6079 if (this._id) { 6080 restUrl += "/" + this._id; 6081 } 6082 return restUrl; 6083 }, 6084 6085 /** 6086 * Getter for the id of this RestBase 6087 * @returns {String} 6088 * The id of this RestBase 6089 */ 6090 getId: function () { 6091 return this._id; 6092 }, 6093 6094 /** 6095 * Synchronize this object with the server using REST GET request. 6096 * @returns {Object} 6097 * { 6098 * abort: {function} Function that signifies the callback handler to NOT process the response of the rest request 6099 * } 6100 * @private 6101 */ 6102 _synchronize: function (retries) { 6103 // Fetch this REST object 6104 if (typeof this._id === "string") { 6105 var _this = this, isLoaded = this._loaded, _RETRY_INTERVAL = 10 * 1000; 6106 6107 return this._doGET( 6108 { 6109 success: function (rsp) { 6110 if (!_this._processResponse(rsp)) { 6111 if (retries > 0) { 6112 setTimeout(function () { 6113 _this._synchronize(retries - 1); 6114 }, _RETRY_INTERVAL); 6115 } else { 6116 _this._errorNotifier.notifyListeners(_this); 6117 } 6118 } else { 6119 // If this object was already "loaded" prior to 6120 // the _doGET request, then call the 6121 // changeNotifier 6122 if (isLoaded) { 6123 _this._changeNotifier.notifyListeners(_this); 6124 } 6125 } 6126 }, 6127 error: function (rsp) { 6128 if (retries > 0) { 6129 setTimeout(function () { 6130 _this._synchronize(retries - 1); 6131 }, _RETRY_INTERVAL); 6132 6133 } else { 6134 _this._errorNotifier.notifyListeners(rsp); 6135 } 6136 } 6137 } 6138 ); 6139 6140 } else { 6141 throw new Error("Can't construct a <" + this.getRestType() + "> due to invalid id type."); 6142 } 6143 }, 6144 6145 /** 6146 * Adds an handler to this object. 6147 * If notifierType is 'load' and the object has already loaded, the callback is invoked immediately 6148 * @param {String} notifierType 6149 * The type of notifier to add to ('load', 'change', 'add', 'delete', 'error') 6150 * @param {Function} callback 6151 * The function callback to invoke. 6152 * @example 6153 * // Handler for additions to the Dialogs collection object. 6154 * // When Dialog (a RestBase object) is created, add a change handler. 6155 * _handleDialogAdd = function(dialog) { 6156 * dialog.addHandler('change', _handleDialogChange); 6157 * } 6158 */ 6159 addHandler: function (notifierType, callback, scope) { 6160 var notifier = null; 6161 try { 6162 Utilities.validateHandler(callback); 6163 6164 notifier = this._getNotifierReference(notifierType); 6165 6166 notifier.addListener(callback, scope); 6167 6168 // If load handler is added and object has 6169 // already been loaded, invoke callback 6170 // immediately 6171 if (notifierType === 'load' && this._loaded) { 6172 callback.call((scope || window), this); 6173 } 6174 } catch (err) { 6175 this._logger.error('id=' + this._id + ': ' + err); 6176 } 6177 }, 6178 6179 /** 6180 * Removes a handler from this object. 6181 * @param {String} notifierType 6182 * The type of notifier to remove ('load', 'change', 'add', 'delete', 'error') 6183 * @param {Function} callback 6184 * The function to remove. 6185 */ 6186 removeHandler: function (notifierType, callback) { 6187 var notifier = null; 6188 try { 6189 Utilities.validateHandler(callback); 6190 6191 notifier = this._getNotifierReference(notifierType); 6192 6193 if (typeof(callback) === "undefined") 6194 { 6195 // Remove all listeners for the type 6196 notifier.reset(); 6197 } 6198 else 6199 { 6200 // Remove the specified listener 6201 finesse.utilities.Utilities.validateHandler(callback); 6202 notifier.removeListener(callback); 6203 } 6204 } catch (err) { 6205 this._logger.error('id=' + this._id + ': ' + err); 6206 } 6207 }, 6208 6209 /** 6210 * Utility method gating any operations that require complete instantiation 6211 * @throws Error 6212 * If this object was not fully instantiated yet 6213 * @returns {finesse.restservices.RestBase} 6214 * This RestBase object to allow cascading 6215 */ 6216 isLoaded: function () { 6217 if (!this._loaded) { 6218 throw new Error("Cannot operate on object that is not fully instantiated, use onLoad/onLoadError handlers"); 6219 } 6220 return this; // Allow cascading 6221 }, 6222 6223 6224 6225 /** 6226 * Force an update on this object. Since an asynchronous GET is performed, 6227 * it is necessary to have an onChange handler registered in order to be 6228 * notified when the response of this returns. 6229 * @param {Integer} retries 6230 * The number or retry attempts to make. 6231 * @returns {Object} 6232 * { 6233 * abort: {function} Function that signifies the callback handler to NOT process the response of the asynchronous request 6234 * } 6235 */ 6236 refresh: function (retries) { 6237 var _this = this; 6238 6239 if (this.explicitSubscription) { 6240 this._subscribeNode({ 6241 success: function () { 6242 //Disallow GETs if object doesn't support it. 6243 if (!_this.supportsRequests) { 6244 throw new Error("Object doesn't support request operations."); 6245 } 6246 6247 _this._synchronize(retries); 6248 6249 return this; // Allow cascading 6250 }, 6251 error: function (err) { 6252 _this._errorNotifier.notifyListeners(err); 6253 } 6254 }); 6255 } else { 6256 //Disallow GETs if object doesn't support it. 6257 if (!this.supportsRequests) { 6258 throw new Error("Object doesn't support request operations."); 6259 } 6260 6261 return this._synchronize(retries); 6262 } 6263 }, 6264 6265 /** 6266 * Utility method to validate against the known schema of this RestBase 6267 * @param {Object} obj 6268 * The object to validate 6269 * @returns {Boolean} 6270 * True if the object fits the schema of this object. This usually 6271 * means all required keys or nested objects are present. 6272 * False otherwise. 6273 * @private 6274 */ 6275 _validate: function (obj) { 6276 var valid = (typeof obj === "object" && this.hasProperty(obj, this.getRestType())); 6277 if (!valid) 6278 { 6279 this._logger.error(this.getRestType() + " failed validation! Does your JS REST class return the correct string from getRestType()?"); 6280 } 6281 return valid; 6282 }, 6283 6284 /** 6285 * Utility method to fetch this RestBase from the server 6286 * @param {finesse.interfaces.RequestHandlers} handlers 6287 * An object containing the handlers for the request 6288 * @returns {Object} 6289 * { 6290 * abort: {function} Function that signifies the callback handler to NOT process the response of the rest request 6291 * } 6292 * @private 6293 */ 6294 _doGET: function (handlers) { 6295 return this.restRequest(this.getRestUrl(), handlers); 6296 }, 6297 6298 /** 6299 * Common update event handler used by the pubsub callback closure. 6300 * Processes the update event then notifies listeners. 6301 * @param {Object} scope 6302 * An object containing callbacks to handle the asynchronous get 6303 * @param {Object} update 6304 * An object containing callbacks to handle the asynchronous get 6305 * @private 6306 */ 6307 _updateEventHandler: function (scope, update) { 6308 if (scope._processUpdate(update)) { 6309 switch (update.object.Update.event) { 6310 case "POST": 6311 scope._addNotifier.notifyListeners(scope); 6312 break; 6313 case "PUT": 6314 scope._changeNotifier.notifyListeners(scope); 6315 break; 6316 case "DELETE": 6317 scope._deleteNotifier.notifyListeners(scope); 6318 break; 6319 } 6320 } 6321 }, 6322 6323 /** 6324 * Utility method to create a callback to be given to OpenAjax to invoke when a message 6325 * is published on the topic of our REST URL (also XEP-0060 node). 6326 * This needs to be its own defined method so that subclasses can have their own implementation. 6327 * @returns {Function} callback(update) 6328 * The callback to be invoked when an update event is received. This callback will 6329 * process the update and notify listeners. 6330 * @private 6331 */ 6332 _createPubsubCallback: function () { 6333 var _this = this; 6334 return function (update) { 6335 _this._updateEventHandler(_this, update); 6336 }; 6337 }, 6338 6339 /** 6340 * Subscribe to pubsub infra using the REST URL as the topic name. 6341 * @param {finesse.interfaces.RequestHandlers} handlers 6342 * An object containing the handlers for the request 6343 * @private 6344 */ 6345 subscribe: function (callbacks) { 6346 // Only need to do a subscription to client pubsub. No need to trigger 6347 // a subscription on the Finesse server due to implicit subscribe (at 6348 // least for now). 6349 var _this = this, 6350 topic = Topics.getTopic(this.getXMPPNodePath()), 6351 handlers, 6352 successful = ClientServices.subscribe(topic, this._createPubsubCallback(), true); 6353 6354 callbacks = callbacks || {}; 6355 6356 handlers = { 6357 /** @private */ 6358 success: function () { 6359 // Add item to the refresh list in ClientServices to refresh if 6360 // we recover due to our resilient connection. However, do 6361 // not add if doNotRefresh flag is set. 6362 if (!_this.doNotRefresh) { 6363 ClientServices.addToRefreshList(_this); 6364 } 6365 6366 if (typeof callbacks.success === "function") { 6367 callbacks.success(); 6368 } 6369 }, 6370 /** @private */ 6371 error: function (err) { 6372 if (successful) { 6373 ClientServices.unsubscribe(topic); 6374 } 6375 6376 if (typeof callbacks.error === "function") { 6377 callbacks.error(err); 6378 } 6379 } 6380 }; 6381 6382 // Request a node subscription only if this object requires explicit subscriptions 6383 if (this.explicitSubscription === true) { 6384 this._subscribeNode(handlers); 6385 } else { 6386 if (successful) { 6387 this._subid = "OpenAjaxOnly"; 6388 handlers.success(); 6389 } else { 6390 handlers.error(); 6391 } 6392 } 6393 6394 return this; 6395 }, 6396 6397 /** 6398 * Unsubscribe to pubsub infra using the REST URL as the topic name. 6399 * @param {finesse.interfaces.RequestHandlers} handlers 6400 * An object containing the handlers for the request 6401 * @private 6402 */ 6403 unsubscribe: function (callbacks) { 6404 // Only need to do a subscription to client pubsub. No need to trigger 6405 // a subscription on the Finesse server due to implicit subscribe (at 6406 // least for now). 6407 var _this = this, 6408 topic = Topics.getTopic(this.getRestUrl()), 6409 handlers; 6410 6411 // no longer keep track of object to refresh on reconnect 6412 ClientServices.removeFromRefreshList(_this); 6413 6414 callbacks = callbacks || {}; 6415 6416 handlers = { 6417 /** @private */ 6418 success: function () { 6419 if (typeof callbacks.success === "function") { 6420 callbacks.success(); 6421 } 6422 }, 6423 /** @private */ 6424 error: function (err) { 6425 if (typeof callbacks.error === "function") { 6426 callbacks.error(err); 6427 } 6428 } 6429 }; 6430 6431 if (this._subid) { 6432 ClientServices.unsubscribe(topic); 6433 // Request a node unsubscribe only if this object requires explicit subscriptions 6434 if (this.explicitSubscription === true) { 6435 this._unsubscribeNode(handlers); 6436 } else { 6437 this._subid = undefined; 6438 handlers.success(); 6439 } 6440 } else { 6441 handlers.success(); 6442 } 6443 6444 return this; 6445 }, 6446 6447 /** 6448 * Private utility to perform node subscribe requests for explicit subscriptions 6449 * @param {finesse.interfaces.RequestHandlers} handlers 6450 * An object containing the handlers for the request 6451 * @private 6452 */ 6453 _subscribeNode: function (callbacks) { 6454 var _this = this; 6455 6456 // Protect against null dereferencing of callbacks allowing its (nonexistent) keys to be read as undefined 6457 callbacks = callbacks || {}; 6458 6459 ClientServices.subscribeNode(this.getXMPPNodePath(), function (subid, err) { 6460 if (err) { 6461 if (typeof callbacks.error === "function") { 6462 callbacks.error(err); 6463 } 6464 } else { 6465 // Store the subid on a successful subscribe 6466 _this._subid = subid; 6467 if (typeof callbacks.success === "function") { 6468 callbacks.success(); 6469 } 6470 } 6471 }); 6472 }, 6473 6474 /** 6475 * Private utility to perform node unsubscribe requests for explicit subscriptions 6476 * @param {finesse.interfaces.RequestHandlers} handlers 6477 * An object containing the handlers for the request 6478 * @private 6479 */ 6480 _unsubscribeNode: function (callbacks) { 6481 var _this = this; 6482 6483 // Protect against null dereferencing of callbacks allowing its (nonexistent) keys to be read as undefined 6484 callbacks = callbacks || {}; 6485 6486 ClientServices.unsubscribeNode(this.getXMPPNodePath(), this._subid, function (err) { 6487 _this._subid = undefined; 6488 if (err) { 6489 if (typeof callbacks.error === "function") { 6490 callbacks.error(err); 6491 } 6492 } else { 6493 if (typeof callbacks.success === "function") { 6494 callbacks.success(); 6495 } 6496 } 6497 }); 6498 }, 6499 6500 /** 6501 * Validate and store the object into the internal data store. 6502 * @param {Object} object 6503 * The JavaScript object that should match of schema of this REST object. 6504 * @returns {Boolean} 6505 * True if the object was validated and stored successfully. 6506 * @private 6507 */ 6508 _processObject: function (object) { 6509 if (this._validate(object)) { 6510 this._data = this.getProperty(object, this.getRestType()); // Should clone the object here? 6511 6512 // If loaded for the first time, call the load notifiers. 6513 if (!this._loaded) { 6514 this._loaded = true; 6515 this._loadNotifier.notifyListeners(this); 6516 } 6517 6518 return true; 6519 } 6520 return false; 6521 }, 6522 6523 /** 6524 * Normalize the object to mitigate the differences between the backend 6525 * and what this REST object should hold. For example, the backend sends 6526 * send an event with the root property name being lower case. In order to 6527 * match the GET, the property should be normalized to an upper case. 6528 * @param {Object} object 6529 * The object which should be normalized. 6530 * @returns {Object} 6531 * Return the normalized object. 6532 * @private 6533 */ 6534 _normalize: function (object) { 6535 var 6536 restType = this.getRestType(), 6537 // Get the REST object name with first character being lower case. 6538 objRestType = restType.charAt(0).toLowerCase() + restType.slice(1); 6539 6540 // Normalize payload to match REST object. The payload for an update 6541 // use a lower case object name as oppose to upper case. Only normalize 6542 // if necessary. 6543 if (!this.hasProperty(object, restType) && this.hasProperty(object, objRestType)) { 6544 //Since the object is going to be modified, clone the object so that 6545 //it doesn't affect others (due to OpenAjax publishing to other 6546 //subscriber. 6547 object = jQuery.extend(true, {}, object); 6548 6549 object[restType] = object[objRestType]; 6550 delete(object[objRestType]); 6551 } 6552 return object; 6553 }, 6554 6555 /** 6556 * Utility method to process the response of a successful get 6557 * @param {Object} rsp 6558 * The response of a successful get 6559 * @returns {Boolean} 6560 * True if the update was successfully processed (the response object 6561 * passed the schema validation) and updated the internal data cache, 6562 * false otherwise. 6563 * @private 6564 */ 6565 _processResponse: function (rsp) { 6566 try { 6567 if (this.keepRestResponse) { 6568 this._restResponse = rsp.content; 6569 this.restResponseStatus = rsp.status; 6570 } 6571 return this._processObject(rsp.object); 6572 } 6573 catch (err) { 6574 this._logger.error(this.getRestType() + ': ' + err); 6575 } 6576 return false; 6577 }, 6578 6579 /** 6580 * Method that is called at the end of _processUpdate() which by default 6581 * will just delete the requestId-to-callbacks mapping but can be overridden. 6582 * @param {String} requestId The requestId of the event 6583 */ 6584 _postProcessUpdateStrategy: function (requestId) { 6585 //Clean up _pendingCallbacks now that we fired a callback corresponding to the received requestId. 6586 delete this._pendingCallbacks[requestId]; 6587 }, 6588 6589 /** 6590 * Utility method to process the update notification. 6591 * @param {Object} update 6592 * The payload of an update notification. 6593 * @returns {Boolean} 6594 * True if the update was successfully processed (the update object 6595 * passed the schema validation) and updated the internal data cache, 6596 * false otherwise. 6597 * @private 6598 */ 6599 _processUpdate: function (update) { 6600 try { 6601 var updateObj, requestId, fakeResponse, receivedError; 6602 6603 // The backend will send the data object with a lower case. To be 6604 // consistent with what should be represented in this object, the 6605 // object name should be upper case. This will normalize the object. 6606 updateObj = this._normalize(update.object.Update.data); 6607 6608 // Store the last event. 6609 this._lastUpdate = update.object; 6610 6611 requestId = this._lastUpdate.Update ? this._lastUpdate.Update.requestId : undefined; 6612 6613 if (requestId && this._pendingCallbacks[requestId]) { 6614 6615 /* 6616 * The passed success/error callbacks are expecting to be passed an AJAX response, so construct 6617 * a simulated/"fake" AJAX response object from the information in the received event. 6618 * The constructed object should conform to the contract for response objects specified 6619 * in _createAjaxHandler(). 6620 */ 6621 fakeResponse = {}; 6622 6623 //The contract says that rsp.content should contain the raw text of the response so we simulate that here. 6624 //For some reason json2xml has trouble with the native update object, so we serialize a clone of it by 6625 //doing a parse(stringify(update)). 6626 fakeResponse.content = this._util.json2xml(gadgets.json.parse(gadgets.json.stringify(update))); 6627 6628 fakeResponse.object = {}; 6629 6630 if (updateObj.apiErrors && updateObj.apiErrors.apiError) { //Error case 6631 6632 //TODO: The lowercase -> uppercase ApiErrors translation method below is undesirable, can it be improved? 6633 receivedError = updateObj.apiErrors.apiError; 6634 fakeResponse.object.ApiErrors = {}; 6635 fakeResponse.object.ApiErrors.ApiError = {}; 6636 fakeResponse.object.ApiErrors.ApiError.ErrorData = receivedError.errorData || undefined; 6637 fakeResponse.object.ApiErrors.ApiError.ErrorMessage = receivedError.errorMessage || undefined; 6638 fakeResponse.object.ApiErrors.ApiError.ErrorType = receivedError.errorType || undefined; 6639 6640 /* 6641 * Since this is the error case, supply the error callback with a '400 BAD REQUEST' status code. We don't know what the real 6642 * status code should be since the event we're constructing fakeResponse from doesn't include a status code. 6643 * This is just to conform to the contract for the error callback in _createAjaxHandler(). 6644 **/ 6645 fakeResponse.status = 400; 6646 6647 } else { //Success case 6648 6649 fakeResponse.object = this._lastUpdate; 6650 6651 /* 6652 * Since this is the success case, supply the success callback with a '200 OK' status code. We don't know what the real 6653 * status code should be since the event we're constructing fakeResponse from doesn't include a status code. 6654 * This is just to conform to the contract for the success callback in _createAjaxHandler(). 6655 **/ 6656 fakeResponse.status = 200; 6657 } 6658 6659 try { 6660 6661 if (fakeResponse.object.ApiErrors && this._pendingCallbacks[requestId].error) { 6662 this._pendingCallbacks[requestId].error(fakeResponse); 6663 } 6664 // HTTP 202 is handled as a success, besides, we cannot infer that a non-error is a success. 6665 /*else if (this._pendingCallbacks[requestId].success) { 6666 this._pendingCallbacks[requestId].success(fakeResponse); 6667 }*/ 6668 6669 } catch (callbackErr) { 6670 6671 this._logger.error(this.getRestType() + ": Caught error while firing callback: " + callbackErr); 6672 6673 } 6674 6675 this._postProcessUpdateStrategy(requestId); 6676 6677 } 6678 6679 return this._processObject(updateObj); 6680 } 6681 catch (err) { 6682 this._logger.error(this.getRestType() + ': ' + err); 6683 } 6684 return false; 6685 }, 6686 6687 /** 6688 * Utility method to create ajax response handler closures around the 6689 * provided callbacks. Callbacks should be passed through from .ajax(). 6690 * makeRequest is responsible for garbage collecting these closures. 6691 * @param {finesse.interfaces.RequestHandlers} handlers 6692 * An object containing the handlers for the request 6693 * @returns {Object} 6694 * { 6695 * abort: {function} Function that signifies the callback handler to NOT process the response when the response returns 6696 * callback: {function} Callback handler to be invoked when the response returns 6697 * } 6698 * @private 6699 */ 6700 _createAjaxHandler: function (options) { 6701 //We should not need to check this again since it has already been done in .restRequest() 6702 //options = options || {}; 6703 6704 //Flag to indicate whether or not to process the response 6705 var abort = false, 6706 6707 //Get a reference to the parent User object 6708 _this = this; 6709 6710 return { 6711 6712 abort: function () { 6713 abort = true; 6714 }, 6715 6716 callback: function (rsp) { 6717 6718 if (abort) { 6719 // Do not process the response 6720 return; 6721 } 6722 6723 var requestId, error = false, rspObj; 6724 6725 if (options.success || options.error) { 6726 rspObj = { 6727 status: rsp.rc, 6728 content: rsp.text 6729 }; 6730 6731 if (!_this.doNotLog) { 6732 _this._logger.log(_this.getRestType() + ": requestId='" + options.uuid + "', Returned with status=" + rspObj.status + ", content='" + rspObj.content + "'"); 6733 } 6734 6735 //Some responses may not have a body. 6736 if (rsp.text && rsp.text.length > 0) { 6737 try { 6738 rspObj.object = _this._util.xml2js(rsp.text); 6739 } catch (e) { 6740 error = true; 6741 rspObj.error = { 6742 errorType: "parseError", 6743 errorMessage: "Could not serialize XML: " + e 6744 }; 6745 } 6746 } else { 6747 rspObj.object = {}; 6748 } 6749 6750 if (!error && rspObj.status >= 200 && rspObj.status < 300) { 6751 if (options.success) { 6752 options.success(rspObj); 6753 } 6754 } else { 6755 if (options.error) { 6756 options.error(rspObj); 6757 } 6758 } 6759 6760 /* 6761 * If a synchronous error happened after a non-GET request (usually a validation error), we 6762 * need to clean up the request's entry in _pendingCallbacks since no corresponding event 6763 * will arrive later. The corresponding requestId should be present in the response headers. 6764 * 6765 * It appears Shindig changes the header keys to lower case, hence 'requestid' instead of 6766 * 'requestId' below. 6767 **/ 6768 if (rspObj.status !== 202 && rsp.headers && rsp.headers.requestid) { 6769 requestId = rsp.headers.requestid[0]; 6770 if (_this._pendingCallbacks[requestId]) { 6771 delete _this._pendingCallbacks[requestId]; 6772 } 6773 } 6774 } 6775 } 6776 }; 6777 }, 6778 6779 /** 6780 * Utility method to make an asynchronous request 6781 * @param {String} url 6782 * The unencoded URL to which the request is sent (will be encoded) 6783 * @param {Object} options 6784 * An object containing additional options for the request. 6785 * @param {Object} options.content 6786 * An object to send in the content body of the request. Will be 6787 * serialized into XML before sending. 6788 * @param {String} options.method 6789 * The type of request. Defaults to "GET" when none is specified. 6790 * @param {Function} options.success(rsp) 6791 * A callback function to be invoked for a successful request. 6792 * { 6793 * status: {Number} The HTTP status code returned 6794 * content: {String} Raw string of response 6795 * object: {Object} Parsed object of response 6796 * } 6797 * @param {Function} options.error(rsp) 6798 * A callback function to be invoked for an unsuccessful request. 6799 * { 6800 * status: {Number} The HTTP status code returned 6801 * content: {String} Raw string of response 6802 * object: {Object} Parsed object of response 6803 * error: {Object} Wrapped exception that was caught 6804 * error.errorType: {String} Type of error that was caught 6805 * error.errorMessage: {String} Message associated with error 6806 * } 6807 * @returns {Object} 6808 * { 6809 * abort: {function} Function that signifies the callback handler to NOT process the response of this asynchronous request 6810 * } 6811 * @private 6812 */ 6813 restRequest : function(url, options) { 6814 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; 6815 // Protect against null dereferencing of options 6816 // allowing its (nonexistent) keys to be read as 6817 // undefined 6818 options = options || {}; 6819 options.success = this._util 6820 .validateHandler(options.success); 6821 options.error = this._util 6822 .validateHandler(options.error); 6823 6824 // true if this should be a GET request, false 6825 // otherwise 6826 if (!options.method || options.method === "GET") { 6827 // Disable caching for GETs 6828 if (url.indexOf("?") > -1) { 6829 url += "&"; 6830 } else { 6831 url += "?"; 6832 } 6833 url += "nocache=" 6834 + this._util.currentTimeMillis(); 6835 } else { 6836 /** 6837 * If not GET, generate a requestID and add it 6838 * to the headers, then wrap callbacks into an 6839 * object and store it in _pendingCallbacks. If 6840 * we receive a synchronous error response 6841 * instead of a 202 as expected, the AJAX 6842 * handler will clean up _pendingCallbacks. 6843 */ 6844 /* 6845 * TODO: Clean up _pendingCallbacks if an entry 6846 * persists after a certain amount of time has 6847 * passed. In the block below, can store the 6848 * current time (new Date().getTime()) alongside 6849 * the callbacks in the new _pendingCallbacks 6850 * entry. Then iterate through a copty of 6851 * _pendingCallbacks, deleting all entries 6852 * inside _pendingCallbacks that are older than 6853 * a certain threshold (2 minutes for example.) 6854 * This solves a potential memory leak issue if 6855 * we never receive an event for a given stored 6856 * requestId; we don't want to store unfired 6857 * callbacks forever. 6858 */ 6859 /** @private */ 6860 options.uuid = this._util.generateUUID(); 6861 // params[gadgets.io.RequestParameters.HEADERS].requestId 6862 // = options.uuid; 6863 // By default, Shindig strips nearly all of the 6864 // response headers, but this parameter tells 6865 // Shindig 6866 // to send the headers through unmodified; we 6867 // need to be able to read the 'requestId' 6868 // header if we 6869 // get a synchronous error as a result of a 6870 // non-GET request. (See the bottom of 6871 // _createAjaxHandler().) 6872 // params[gadgets.io.RequestParameters.GET_FULL_HEADERS] 6873 // = "true"; 6874 this._pendingCallbacks[options.uuid] = {}; 6875 this._pendingCallbacks[options.uuid].success = options.success; 6876 this._pendingCallbacks[options.uuid].error = options.error; 6877 } 6878 encodedUrl = encodeURI(url) 6879 + (window.errorOnRestRequest ? "ERROR" : ""); 6880 ajaxHandler = this._createAjaxHandler(options); 6881 // ClientServices.makeRequest(encodedUrl, 6882 // ajaxHandler.callback, params); 6883 mtype = options.method || 'GET'; 6884 encodedUrl = scheme + "//" + host + ":" + port 6885 + encodedUrl; 6886 6887 if (typeof options.content === "object" 6888 && typeof options.content !== "undefined") { 6889 // Except get, all the other operations accepts 6890 // application/xml only. 6891 // @Consumes in all the webservices except GET 6892 // are having this. 6893 postdata = this._util.js2xml(options.content); 6894 if (postdata !== null && postdata !== "") { 6895 contentTypeAX = "application/xml"; 6896 } else { 6897 // in desktop, reason code GET request was 6898 // called with empty object content 6899 // if not able to parse any content to post 6900 // data, then it is GET only 6901 contentTypeAX = ""; 6902 dataTypeAX = "text"; 6903 } 6904 } else { 6905 // No content type for GET operation, by default 6906 // it will take text/plain || application/xml. 6907 // for dataType - GET will result plain text, 6908 // which we are taking as xml. 6909 // Queried list of @Produces from code base, all 6910 // the GET operations accepts text/plain OR 6911 // application/xml and produces application/xml 6912 contentTypeAX = ""; 6913 dataTypeAX = "text"; 6914 } 6915 auth = this._util.getAuthHeaderString(this._config); 6916 6917 if (!this.doNotLog) { 6918 this._logger.log(this.getRestType() 6919 + ": requestId='" + options.uuid 6920 + "', Making REST request: method=" 6921 + (options.method || "GET") + ", url='" 6922 + encodedUrl + "'"); 6923 } 6924 $ 6925 .ajax({ 6926 url : encodedUrl, 6927 headers : this.extraHeaders || {}, 6928 beforeSend : function(xhr) { 6929 xhr.setRequestHeader( 6930 "Authorization", auth); 6931 xhr.setRequestHeader("locale", 6932 locale); 6933 if (options.uuid) { 6934 xhr.setRequestHeader( 6935 "requestId", 6936 options.uuid); 6937 } 6938 }, 6939 dataType : dataTypeAX, // xml or json? 6940 contentType : contentTypeAX, 6941 type : mtype, 6942 data : postdata, 6943 timeout : 5000, 6944 success : function(response, status, 6945 xhr) { 6946 var rspObj = { 6947 rc : xhr.status, 6948 text : response, 6949 headers : { 6950 requestid : xhr 6951 .getResponseHeader("requestId") 6952 } 6953 }; 6954 ajaxHandler.callback(rspObj); 6955 }, 6956 error : function(jqXHR, request, error) { 6957 var resObj = { 6958 rc : jqXHR.status, 6959 text : jqXHR.responseText, 6960 headers : { 6961 requestid : jqXHR 6962 .getResponseHeader("requestId") 6963 } 6964 }; 6965 ajaxHandler.callback(resObj); 6966 } 6967 }); 6968 return { 6969 abort : ajaxHandler.abort 6970 }; 6971 }, 6972 6973 6974 /** 6975 * Retrieves a reference to a particular notifierType. 6976 * 6977 * @param notifierType 6978 * is a string which indicates the notifier to retrieve 6979 * ('load', 'change', 'add', 'delete', 'error') 6980 * @return {Notifier} 6981 * @private 6982 */ 6983 _getNotifierReference: function (notifierType) { 6984 var notifierReference = null; 6985 if (notifierType === 'load') { 6986 notifierReference = this._loadNotifier; 6987 } else if (notifierType === 'change') { 6988 notifierReference = this._changeNotifier; 6989 } else if (notifierType === 'add') { 6990 notifierReference = this._addNotifier; 6991 } else if (notifierType === 'delete') { 6992 notifierReference = this._deleteNotifier; 6993 } else if (notifierType === 'error') { 6994 notifierReference = this._errorNotifier; 6995 } else { 6996 throw new Error("_getNotifierReference(): Trying to get unknown notifier(notifierType=" + notifierType + ")"); 6997 } 6998 6999 return notifierReference; 7000 } 7001 }); 7002 7003 window.finesse = window.finesse || {}; 7004 window.finesse.restservices = window.finesse.restservices || {}; 7005 window.finesse.restservices.RestBase = RestBase; 7006 7007 return RestBase; 7008 }); 7009 7010 /** The following comment is to prevent jslint errors about 7011 * using variables before they are defined. 7012 */ 7013 /*global finesse*/ 7014 7015 /** 7016 * JavaScript base object that all REST collection objects should 7017 * inherit from because it encapsulates and provides the common functionality 7018 * that all REST objects need. 7019 * 7020 * @requires finesse.clientservices.ClientServices 7021 * @requires Class 7022 * @requires finesse.FinesseBase 7023 * @requires finesse.restservices.RestBase 7024 */ 7025 7026 /** 7027 * @class 7028 * JavaScript representation of a REST collection object. 7029 * 7030 * @constructor 7031 * @param {Function} callbacks.onCollectionAdd(this) 7032 * Callback to invoke upon successful item addition to the collection. 7033 * @param {Function} callbacks.onCollectionDelete(this) 7034 * Callback to invoke upon successful item deletion from the collection. 7035 * @borrows finesse.restservices.RestBase as finesse.restservices.RestCollectionBase 7036 */ 7037 /** @private */ 7038 define('restservices/RestCollectionBase',[ 7039 'restservices/RestBase', 7040 'utilities/Utilities', 7041 'restservices/Notifier' 7042 ], 7043 function (RestBase, Utilities, Notifier) { 7044 var RestCollectionBase = RestBase.extend(/** @lends finesse.restservices.RestCollectionBase.prototype */{ 7045 7046 /** 7047 * Boolean function that specifies whether the collection handles subscribing 7048 * and propagation of events for the individual REST object items the 7049 * collection holds. False by default. Subclasses should override if true. 7050 * @private 7051 */ 7052 supportsRestItemSubscriptions: false, 7053 7054 /** 7055 * Gets the constructor the individual items that make of the collection. 7056 * For example, a Dialogs collection object will hold a list of Dialog items. 7057 * @throws Error because subtype must implement. 7058 * @private 7059 */ 7060 getRestItemClass: function () { 7061 throw new Error("getRestItemClass(): Not implemented in subtype."); 7062 }, 7063 7064 /** 7065 * Gets the REST type of the individual items that make of the collection. 7066 * For example, a Dialogs collection object will hold a list of Dialog items. 7067 * @throws Error because subtype must implement. 7068 * @private 7069 */ 7070 getRestItemType: function () { 7071 throw new Error("getRestItemType(): Not implemented in subtype."); 7072 }, 7073 7074 /** 7075 * The base REST URL in which items this object contains can be referenced. 7076 * @return {String} 7077 * The REST URI for items this object contains. 7078 * @private 7079 */ 7080 getRestItemBaseUrl: function () { 7081 var 7082 restUrl = "/finesse/api"; 7083 7084 //Append the REST type. 7085 restUrl += "/" + this.getRestItemType(); 7086 7087 return restUrl; 7088 }, 7089 7090 /* 7091 * Creates a new object from the given data 7092 * @param data - data object 7093 * @private 7094 */ 7095 _objectCreator: function (data) { 7096 var objectId = this._extractId(data), 7097 newRestObj = this._collection[objectId], 7098 _this = this; 7099 7100 //Prevent duplicate entries into collection. 7101 if (!newRestObj) { 7102 //Create a new REST object using the subtype defined by the 7103 //overridden method. 7104 newRestObj = new (this.getRestItemClass())({ 7105 doNotSubscribe: this.handlesItemSubscription, 7106 doNotRefresh: this.handlesItemRefresh, 7107 id: objectId, 7108 data: data, 7109 onLoad: function (newObj) { 7110 //Normalize and add REST object to collection datastore. 7111 _this._collection[objectId] = newObj; 7112 _this._collectionAddNotifier.notifyListeners(newObj); 7113 _this.length += 1; 7114 } 7115 }); 7116 } 7117 else { 7118 //If entry already exist in collection, process the new event, 7119 //and notify all change listeners since an existing object has 7120 //change. This could happen in the case when the Finesse server 7121 //cycles, and sends a snapshot of the user's calls. 7122 newRestObj._processObject(data); 7123 newRestObj._changeNotifier.notifyListeners(newRestObj); 7124 } 7125 }, 7126 7127 /* 7128 * Deletes and object and notifies its handlers 7129 * @param data - data object 7130 * @private 7131 */ 7132 _objectDeleter: function (data) { 7133 var objectId = this._extractId(data), 7134 object = this._collection[objectId]; 7135 if (object) { 7136 //Even though this is a delete, let's make sure the object we are passing has got good data 7137 object._processObject(data); 7138 //Notify listeners and delete from internal datastore. 7139 this._collectionDeleteNotifier.notifyListeners(object); 7140 delete this._collection[objectId]; 7141 this.length -= 1; 7142 } 7143 }, 7144 7145 /** 7146 * Creates an anonymous function for notifiying error listeners of a particular object 7147 * data. 7148 * @param obj - the objects whose error listeners to notify 7149 * @returns {Function} 7150 * Callback for notifying of errors 7151 * @private 7152 */ 7153 _createErrorNotifier: function (obj) { 7154 return function (err) { 7155 obj._errorNotifier.notifyListeners(err); 7156 }; 7157 }, 7158 7159 /** 7160 * Replaces the collection with a refreshed list using the passed in 7161 * data. 7162 * @param data - data object (usually this._data) 7163 * @private 7164 */ 7165 _buildRefreshedCollection: function (data) { 7166 var i, dataObject, object, objectId, dataArray, newIds = [], foundFlag; 7167 if (data && this.getProperty(data, this.getRestItemType()) !== null) { 7168 dataArray = Utilities.getArray(this.getProperty(data, this.getRestItemType())); 7169 } else { 7170 dataArray = []; 7171 } 7172 7173 // iterate through each item in the new data and add to or update collection 7174 for (i = 0; i < dataArray.length; i += 1) { 7175 dataObject = {}; 7176 dataObject[this.getRestItemType()] = dataArray[i]; 7177 objectId = this._extractId(dataObject); 7178 7179 this._objectCreator(dataObject); 7180 newIds.push(objectId); 7181 7182 // resubscribe if the object requires an explicit subscription 7183 object = this._collection[objectId]; 7184 if (this.handlesItemRefresh && object.explicitSubscription) { 7185 object._subscribeNode({ 7186 error: this._createErrorNotifier(object) 7187 }); 7188 } 7189 } 7190 7191 // now clean up items (if any) that were removed 7192 for (objectId in this._collection) { 7193 if (this._collection.hasOwnProperty(objectId)) { 7194 foundFlag = false; 7195 for (i = newIds.length - 1; i >= 0; i -= 1) { 7196 if (newIds[i] === objectId) { 7197 foundFlag = true; 7198 break; 7199 } 7200 } 7201 // did not find in updated list, so delete it 7202 if (!foundFlag) { 7203 this._objectDeleter({'data': this._collection[objectId]._data}); 7204 } 7205 } 7206 } 7207 }, 7208 7209 /** 7210 * The actual refresh operation, refactored out so we don't have to repeat code 7211 * @private 7212 */ 7213 _RESTRefresh: function () { 7214 var _this = this; 7215 this._doGET({ 7216 success: function(rsp) { 7217 if (_this._processResponse(rsp)) { 7218 _this._buildRefreshedCollection(_this._data); 7219 } else { 7220 _this._errorNotifier.notifyListeners(_this); 7221 } 7222 }, 7223 error: function(rsp) { 7224 _this._errorNotifier.notifyListeners(rsp); 7225 } 7226 }); 7227 }, 7228 7229 /** 7230 * Force an update on this object. Since an asynchronous GET is performed, 7231 * it is necessary to have an onChange handler registered in order to be 7232 * notified when the response of this returns. 7233 * @returns {finesse.restservices.RestBaseCollection} 7234 * This RestBaseCollection object to allow cascading 7235 */ 7236 refresh: function() { 7237 var _this = this, isLoaded = this._loaded; 7238 7239 // resubscribe if the collection requires an explicit subscription 7240 if (this.explicitSubscription) { 7241 this._subscribeNode({ 7242 success: function () { 7243 _this._RESTRefresh(); 7244 }, 7245 error: function (err) { 7246 _this._errorNotifier.notifyListeners(err); 7247 } 7248 }); 7249 } else { 7250 this._RESTRefresh(); 7251 } 7252 7253 return this; // Allow cascading 7254 }, 7255 7256 /** 7257 * @private 7258 * The _addHandlerCb and _deleteHandlerCb require that data be passed in the 7259 * format of an array of {(Object Type): object} objects. For example, a 7260 * queues object would return [{Queue: queue1}, {Queue: queue2}, ...]. 7261 * @param skipOuterObject If {true} is passed in for this param, then the "data" 7262 * property is returned instead of an object with the 7263 * data appended. 7264 * @return {Array} 7265 */ 7266 extractCollectionData: function (skipOuterObject) { 7267 var restObjs, 7268 obj, 7269 result = [], 7270 _this = this; 7271 7272 if (this._data) 7273 { 7274 restObjs = this._data[this.getRestItemType()]; 7275 7276 if (restObjs) 7277 { 7278 // check if there are multiple objects to pass 7279 if (!$.isArray(restObjs)) 7280 { 7281 restObjs = [restObjs]; 7282 } 7283 7284 // if so, create an object for each and add to result array 7285 $.each(restObjs, function (id, object) { 7286 if (skipOuterObject === true) 7287 { 7288 obj = object; 7289 } 7290 else 7291 { 7292 obj = {}; 7293 obj[_this.getRestItemType()] = object; 7294 } 7295 result.push(obj); 7296 }); 7297 } 7298 7299 } 7300 7301 return result; 7302 }, 7303 7304 /** 7305 * For Finesse, collections are handled uniquely on a POST and 7306 * doesn't necessary follow REST conventions. A POST on a collection 7307 * doesn't mean that the collection has been created, it means that an 7308 * item has been added to the collection. This function will generate 7309 * a closure which will handle this logic appropriately. 7310 * @param {Object} scope 7311 * The scope of where the callback should be invoked. 7312 * @private 7313 */ 7314 _addHandlerCb: function (scope) { 7315 return function (restItem) { 7316 var data = restItem.extractCollectionData(); 7317 7318 $.each(data, function (id, object) { 7319 scope._objectCreator(object); 7320 }); 7321 }; 7322 }, 7323 7324 /** 7325 * For Finesse, collections are handled uniquely on a DELETE and 7326 * doesn't necessary follow REST conventions. A DELETE on a collection 7327 * doesn't mean that the collection has been deleted, it means that an 7328 * item has been deleted from the collection. This function will generate 7329 * a closure which will handle this logic appropriately. 7330 * @param {Object} scope 7331 * The scope of where the callback should be invoked. 7332 * @private 7333 */ 7334 _deleteHandlerCb: function (scope) { 7335 return function (restItem) { 7336 var data = restItem.extractCollectionData(); 7337 7338 $.each(data, function (id, obj) { 7339 scope._objectDeleter(obj); 7340 }); 7341 }; 7342 }, 7343 7344 /** 7345 * Utility method to process the update notification for Rest Items 7346 * that are children of the collection whose events are published to 7347 * the collection's node. 7348 * @param {Object} update 7349 * The payload of an update notification. 7350 * @returns {Boolean} 7351 * True if the update was successfully processed (the update object 7352 * passed the schema validation) and updated the internal data cache, 7353 * false otherwise. 7354 * @private 7355 */ 7356 _processRestItemUpdate: function (update) { 7357 var object, objectId, updateObj = update.object.Update; 7358 7359 //Extract the ID from the source if the Update was an error. 7360 if (updateObj.data.apiErrors) { 7361 objectId = Utilities.getId(updateObj.source); 7362 } 7363 //Otherwise extract from the data object itself. 7364 else { 7365 objectId = this._extractId(updateObj.data); 7366 } 7367 7368 object = this._collection[objectId]; 7369 if (object) { 7370 if (object._processUpdate(update)) { 7371 switch (updateObj.event) { 7372 case "POST": 7373 object._addNotifier.notifyListeners(object); 7374 break; 7375 case "PUT": 7376 object._changeNotifier.notifyListeners(object); 7377 break; 7378 case "DELETE": 7379 object._deleteNotifier.notifyListeners(object); 7380 break; 7381 } 7382 } 7383 } 7384 }, 7385 7386 /** 7387 * SUBCLASS IMPLEMENTATION (override): 7388 * For collections, this callback has the additional responsibility of passing events 7389 * of collection item updates to the item objects themselves. The collection needs to 7390 * do this because item updates are published to the collection's node. 7391 * @returns {Function} 7392 * The callback to be invoked when an update event is received 7393 * @private 7394 */ 7395 _createPubsubCallback: function () { 7396 var _this = this; 7397 return function (update) { 7398 //If the source of the update is our REST URL, this means the collection itself is modified 7399 if (update.object.Update.source === _this.getRestUrl()) { 7400 _this._updateEventHandler(_this, update); 7401 } else { 7402 //Otherwise, it is safe to assume that if we got an event on our topic, it must be a 7403 //rest item update of one of our children that was published on our node (OpenAjax topic) 7404 _this._processRestItemUpdate(update); 7405 } 7406 }; 7407 }, 7408 7409 /** 7410 * @class 7411 * This is the base collection object. 7412 * 7413 * @constructs 7414 * @augments finesse.restservices.RestBase 7415 * @see finesse.restservices.Contacts 7416 * @see finesse.restservices.Dialogs 7417 * @see finesse.restservices.PhoneBooks 7418 * @see finesse.restservices.Queues 7419 * @see finesse.restservices.WorkflowActions 7420 * @see finesse.restservices.Workflows 7421 * @see finesse.restservices.WrapUpReasons 7422 */ 7423 _fakeConstuctor: function () { 7424 /* This is here to hide the real init constructor from the public docs */ 7425 }, 7426 7427 /** 7428 * @private 7429 * @param {Object} options 7430 * An object with the following properties:<ul> 7431 * <li><b>id:</b> The id of the object being constructed</li> 7432 * <li><b>onCollectionAdd(this): (optional)</b> when an object is added to this collection</li> 7433 * <li><b>onCollectionDelete(this): (optional)</b> when an object is removed from this collection</li> 7434 * <li><b>onLoad(this): (optional)</b> when the collection is successfully loaded from the server</li> 7435 * <li><b>onChange(this): (optional)</b> when an update notification of the collection is received. 7436 * This does not include adding and deleting members of the collection</li> 7437 * <li><b>onAdd(this): (optional)</b> when a notification that the collection is created is received</li> 7438 * <li><b>onDelete(this): (optional)</b> when a notification that the collection is deleted is received</li> 7439 * <li><b>onError(rsp): (optional)</b> if loading of the collection fails, invoked with the error response object:<ul> 7440 * <li><b>status:</b> {Number} The HTTP status code returned</li> 7441 * <li><b>content:</b> {String} Raw string of response</li> 7442 * <li><b>object:</b> {Object} Parsed object of response</li> 7443 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 7444 * <li><b>errorType:</b> {String} Type of error that was caught</li> 7445 * <li><b>errorMessage:</b> {String} Message associated with error</li> 7446 * </ul></li> 7447 * </ul></li> 7448 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 7449 **/ 7450 init: function (options) { 7451 7452 options = options || {}; 7453 options.id = ""; 7454 7455 //Make internal datastore collection to hold a list of objects. 7456 this._collection = {}; 7457 this.length = 0; 7458 7459 //Collections will have additional callbacks that will be invoked when 7460 //an item has been added/deleted. 7461 this._collectionAddNotifier = new Notifier(); 7462 this._collectionDeleteNotifier = new Notifier(); 7463 7464 //Initialize the base class. 7465 this._super(options); 7466 7467 this.addHandler('collectionAdd', options.onCollectionAdd); 7468 this.addHandler('collectionDelete', options.onCollectionDelete); 7469 7470 //For Finesse, collections are handled uniquely on a POST/DELETE and 7471 //doesn't necessary follow REST conventions. A POST on a collection 7472 //doesn't mean that the collection has been created, it means that an 7473 //item has been added to the collection. A DELETE means that an item has 7474 //been removed from the collection. Due to this, we are attaching 7475 //special callbacks to the add/delete that will handle this logic. 7476 this.addHandler("add", this._addHandlerCb(this)); 7477 this.addHandler("delete", this._deleteHandlerCb(this)); 7478 }, 7479 7480 /** 7481 * Returns the collection. 7482 * @returns {Object} 7483 * The collection as an object 7484 */ 7485 getCollection: function () { 7486 //TODO: is this safe? or should we instead return protected functions such as .each(function)? 7487 return this._collection; 7488 }, 7489 7490 /** 7491 * Utility method to build the internal collection data structure (object) based on provided data 7492 * @param {Object} data 7493 * The data to build the internal collection from 7494 * @private 7495 */ 7496 _buildCollection: function (data) { 7497 var i, object, objectId, dataArray; 7498 if (data && this.getProperty(data, this.getRestItemType()) !== null) { 7499 dataArray = Utilities.getArray(this.getProperty(data, this.getRestItemType())); 7500 for (i = 0; i < dataArray.length; i += 1) { 7501 7502 object = {}; 7503 object[this.getRestItemType()] = dataArray[i]; 7504 objectId = this._extractId(object); 7505 this._collection[objectId] = new (this.getRestItemClass())({ 7506 doNotSubscribe: this.handlesItemSubscription, 7507 doNotRefresh: this.handlesItemRefresh, 7508 id: objectId, 7509 data: object 7510 }); 7511 this.length += 1; 7512 } 7513 } 7514 }, 7515 7516 /** 7517 * Called to know whether to include an item in the _collection and _data. Return false to keep it, true to filter out (discard) it. 7518 * Override this in subclasses if you need only object with certain attribute values. 7519 * @param {Object} item Item to test. 7520 * @return {Boolean} False to keep, true to filter out (discard); 7521 */ 7522 _filterOutItem: function (item) { 7523 return false; 7524 }, 7525 7526 /** 7527 * Validate and store the object into the internal data store. 7528 * SUBCLASS IMPLEMENTATION (override): 7529 * Performs collection specific logic to _buildCollection internally based on provided data 7530 * @param {Object} object 7531 * The JavaScript object that should match of schema of this REST object. 7532 * @returns {Boolean} 7533 * True if the object was validated and stored successfully. 7534 * @private 7535 */ 7536 _processObject: function (object) { 7537 var i, 7538 restItemType = this.getRestItemType(), 7539 items; 7540 if (this._validate(object)) { 7541 this._data = this.getProperty(object, this.getRestType()); // Should clone the object here? 7542 7543 // If a subclass has overriden _filterOutItem then we'll need to run through the items and remove them 7544 if (this._data) 7545 { 7546 items = this._data[restItemType]; 7547 7548 if (typeof(items) !== "undefined") 7549 { 7550 if (typeof(items.length) === "undefined") 7551 { 7552 // Single object 7553 if (this._filterOutItem(items)) 7554 { 7555 this._data[restItemType] = items = []; 7556 } 7557 7558 } 7559 else 7560 { 7561 // filter out objects 7562 for (i = items.length - 1; i !== -1; i = i - 1) 7563 { 7564 if (this._filterOutItem(items[i])) 7565 { 7566 items.splice(i, 1); 7567 } 7568 } 7569 } 7570 } 7571 } 7572 7573 // If loaded for the first time, call the load notifiers. 7574 if (!this._loaded) { 7575 this._buildCollection(this._data); 7576 this._loaded = true; 7577 this._loadNotifier.notifyListeners(this); 7578 } 7579 7580 return true; 7581 7582 } 7583 return false; 7584 }, 7585 7586 /** 7587 * Retrieves a reference to a particular notifierType. 7588 * @param {String} notifierType 7589 * Specifies the notifier to retrieve (load, change, error, add, delete) 7590 * @return {Notifier} The notifier object. 7591 */ 7592 _getNotifierReference: function (notifierType) { 7593 var notifierReference; 7594 7595 try { 7596 //Use the base method to get references for load/change/error. 7597 notifierReference = this._super(notifierType); 7598 } catch (err) { 7599 //Check for add/delete 7600 if (notifierType === "collectionAdd") { 7601 notifierReference = this._collectionAddNotifier; 7602 } else if (notifierType === "collectionDelete") { 7603 notifierReference = this._collectionDeleteNotifier; 7604 } else { 7605 //Rethrow exception from base class. 7606 throw err; 7607 } 7608 } 7609 return notifierReference; 7610 } 7611 }); 7612 7613 window.finesse = window.finesse || {}; 7614 window.finesse.restservices = window.finesse.restservices || {}; 7615 window.finesse.restservices.RestCollectionBase = RestCollectionBase; 7616 7617 return RestCollectionBase; 7618 }); 7619 7620 /** 7621 * JavaScript representation of the Finesse Dialog object. 7622 * 7623 * @requires finesse.clientservices.ClientServices 7624 * @requires Class 7625 * @requires finesse.FinesseBase 7626 * @requires finesse.restservices.RestBase 7627 */ 7628 7629 /** @private */ 7630 define('restservices/DialogBase',[ 7631 'restservices/RestBase', 7632 'utilities/Utilities' 7633 ], 7634 function (RestBase, Utilities) { 7635 var DialogBase = RestBase.extend(/** @lends finesse.restservices.DialogBase.prototype */{ 7636 7637 /** 7638 * @class 7639 * A DialogBase is an attempted connection between or among multiple participants, 7640 * for example, a regular phone call, a chat, or an email. 7641 * 7642 * This object is typically extended into individual 7643 * REST Objects (like Dialog, MediaDialog, etc...), and shouldn't be used directly. 7644 * 7645 * @augments finesse.restservices.RestBase 7646 * @constructs 7647 */ 7648 _fakeConstuctor: function () { 7649 /* This is here to hide the real init constructor from the public docs */ 7650 }, 7651 7652 /** 7653 * @private 7654 * 7655 * @param {Object} options 7656 * An object with the following properties:<ul> 7657 * <li><b>id:</b> The id of the object being constructed</li> 7658 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 7659 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 7660 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 7661 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 7662 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 7663 * <li><b>status:</b> {Number} The HTTP status code returned</li> 7664 * <li><b>content:</b> {String} Raw string of response</li> 7665 * <li><b>object:</b> {Object} Parsed object of response</li> 7666 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 7667 * <li><b>errorType:</b> {String} Type of error that was caught</li> 7668 * <li><b>errorMessage:</b> {String} Message associated with error</li> 7669 * </ul></li> 7670 * </ul></li> 7671 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 7672 **/ 7673 init: function (options) { 7674 this._super(options); 7675 }, 7676 7677 /** 7678 * @private 7679 * Gets the REST class for the current object - this is the Dialog class. 7680 * @returns {Object} The Dialog class. 7681 */ 7682 getRestClass: function () { 7683 throw new Error("getRestClass(): Not implemented in subtype."); 7684 }, 7685 7686 /** 7687 * @private 7688 * The constant for agent device. 7689 */ 7690 _agentDeviceType: "AGENT_DEVICE", 7691 7692 /** 7693 * @private 7694 * Gets the REST type for the current object - this is a "Dialog". 7695 * @returns {String} The Dialog string. 7696 */ 7697 getRestType: function () { 7698 return "Dialog"; 7699 }, 7700 7701 /** 7702 * @private 7703 * Override default to indicate that this object doesn't support making 7704 * requests. 7705 */ 7706 supportsRequests: false, 7707 7708 /** 7709 * @private 7710 * Override default to indicate that this object doesn't support subscriptions. 7711 */ 7712 supportsSubscriptions: false, 7713 7714 7715 /** 7716 * Getter for the media type. 7717 * @returns {String} The media type. 7718 */ 7719 getMediaType: function () { 7720 this.isLoaded(); 7721 return this.getData().mediaType; 7722 }, 7723 7724 /** 7725 * @private 7726 * Getter for the uri. 7727 * @returns {String} The uri. 7728 */ 7729 getDialogUri: function () { 7730 this.isLoaded(); 7731 return this.getData().uri; 7732 }, 7733 7734 /** 7735 * Getter for the callType. 7736 * @deprecated Use getMediaProperties().callType instead. 7737 * @returns {String} The callType. 7738 */ 7739 getCallType: function () { 7740 this.isLoaded(); 7741 return this.getData().mediaProperties.callType; 7742 }, 7743 7744 7745 /** 7746 * Getter for the Dialog state. 7747 * @returns {String} The Dialog state. 7748 */ 7749 getState: function () { 7750 this.isLoaded(); 7751 return this.getData().state; 7752 }, 7753 7754 /** 7755 * Retrieves a list of participants within the Dialog object. 7756 * @returns {Object} Array list of participants. 7757 * Participant entity properties are as follows:<ul> 7758 * <li>state - The state of the Participant. 7759 * <li>stateCause - The state cause of the Participant. 7760 * <li>mediaAddress - The media address of the Participant. 7761 * <li>startTime - The start Time of the Participant. 7762 * <li>stateChangeTime - The time when participant state has changed. 7763 * <li>actions - These are the actions that a Participant can perform</ul> 7764 */ 7765 getParticipants: function () { 7766 this.isLoaded(); 7767 var participants = this.getData().participants.Participant; 7768 //Due to the nature of the XML->JSO converter library, a single 7769 //element in the XML array will be considered to an object instead of 7770 //a real array. This will handle those cases to ensure that an array is 7771 //always returned. 7772 7773 return Utilities.getArray(participants); 7774 }, 7775 7776 /** 7777 * This method retrieves the participant timer counters 7778 * 7779 * @param {String} participantExt Extension of participant. 7780 * @returns {Object} Array of Participants which contains properties :<ul> 7781 * <li>state - The state of the Participant. 7782 * <li>startTime - The start Time of the Participant. 7783 * <li>stateChangeTime - The time when participant state has changed.</ul> 7784 * 7785 */ 7786 getParticipantTimerCounters : function (participantExt) { 7787 var part, participantTimerCounters = {}, idx, participants; 7788 7789 participants = this.getParticipants(); 7790 7791 7792 //Loop through all the participants and find the right participant (based on participantExt) 7793 for(idx=0;idx<participants.length;idx=idx+1) 7794 { 7795 part = participants[idx]; 7796 7797 if (part.mediaAddress === participantExt) 7798 { 7799 participantTimerCounters.startTime= part.startTime; 7800 participantTimerCounters.stateChangeTime= part.stateChangeTime; 7801 participantTimerCounters.state= part.state; 7802 break; 7803 } 7804 } 7805 7806 return participantTimerCounters; 7807 }, 7808 7809 7810 /** 7811 * Retrieves a list of media properties from the dialog object. 7812 * @returns {Object} Map of call variables; names mapped to values. 7813 * Variables may include the following:<ul> 7814 * <li>dialedNumber: The number dialed. 7815 * <li>callType: The type of call. Call types include:<ul> 7816 * <li>ACD_IN 7817 * <li>PREROUTE_ACD_IN 7818 * <li>PREROUTE_DIRECT_AGENT 7819 * <li>TRANSFER 7820 * <li>OTHER_IN 7821 * <li>OUT 7822 * <li>AGENT_INSIDE 7823 * <li>CONSULT 7824 * <li>CONFERENCE 7825 * <li>SUPERVISOR_MONITOR 7826 * <li>OUTBOUND 7827 * <li>OUTBOUND_PREVIEW</ul> 7828 * <li>DNIS: The DNIS provided. For routed calls, this is the route point. 7829 * <li>wrapUpReason: A description of the call. 7830 * <li>Call Variables, by name. The name indicates whether it is a call variable or ECC variable. 7831 * Call variable names start with callVariable#, where # is 1-10. ECC variable names (both scalar and array) are prepended with "user". 7832 * ECC variable arrays include an index enclosed within square brackets located at the end of the ECC array name. 7833 * <li>The following call variables provide additional details about an Outbound Option call:<ul> 7834 * <li>BACampaign 7835 * <li>BAAccountNumber 7836 * <li>BAResponse 7837 * <li>BAStatus<ul> 7838 * <li>PREDICTIVE_OUTBOUND: A predictive outbound call. 7839 * <li>PROGRESSIVE_OUTBOUND: A progressive outbound call. 7840 * <li>PREVIEW_OUTBOUND_RESERVATION: Agent is reserved for a preview outbound call. 7841 * <li>PREVIEW_OUTBOUND: Agent is on a preview outbound call.</ul> 7842 * <li>BADialedListID 7843 * <li>BATimeZone 7844 * <li>BABuddyName</ul></ul> 7845 * 7846 */ 7847 getMediaProperties: function () { 7848 var mpData, currentMediaPropertiesMap, thisMediaPropertiesJQuery; 7849 7850 this.isLoaded(); 7851 7852 // We have to convert to jQuery object to do a proper compare 7853 thisMediaPropertiesJQuery = jQuery(this.getData().mediaProperties); 7854 7855 if ((this._lastMediaPropertiesJQuery !== undefined) 7856 && (this._lastMediaPropertiesMap !== undefined) 7857 && (this._lastMediaPropertiesJQuery.is(thisMediaPropertiesJQuery))) { 7858 7859 return this._lastMediaPropertiesMap; 7860 } 7861 7862 currentMediaPropertiesMap = {}; 7863 7864 mpData = this.getData().mediaProperties; 7865 7866 if (mpData) { 7867 if (mpData.callvariables && mpData.callvariables.CallVariable) { 7868 if (mpData.callvariables.CallVariable.length === undefined) { 7869 mpData.callvariables.CallVariable = [mpData.callvariables.CallVariable]; 7870 } 7871 jQuery.each(mpData.callvariables.CallVariable, function (i, callVariable) { 7872 currentMediaPropertiesMap[callVariable.name] = callVariable.value; 7873 }); 7874 } 7875 7876 jQuery.each(mpData, function (key, value) { 7877 if (key !== 'callvariables') { 7878 currentMediaPropertiesMap[key] = value; 7879 } 7880 }); 7881 } 7882 7883 this._lastMediaPropertiesMap = currentMediaPropertiesMap; 7884 this._lastMediaPropertiesJQuery = thisMediaPropertiesJQuery; 7885 7886 return this._lastMediaPropertiesMap; 7887 }, 7888 7889 7890 7891 /** 7892 * @private 7893 * Invoke a request to the server given a content body and handlers. 7894 * 7895 * @param {Object} contentBody 7896 * A JS object containing the body of the action request. 7897 * @param {finesse.interfaces.RequestHandlers} handlers 7898 * An object containing the handlers for the request 7899 */ 7900 _makeRequest: function (contentBody, handlers) { 7901 // Protect against null dereferencing of options allowing its 7902 // (nonexistent) keys to be read as undefined 7903 handlers = handlers || {}; 7904 7905 this.restRequest(this.getRestUrl(), { 7906 method: 'PUT', 7907 success: handlers.success, 7908 error: handlers.error, 7909 content: contentBody 7910 }); 7911 } 7912 7913 }); 7914 7915 window.finesse = window.finesse || {}; 7916 window.finesse.restservices = window.finesse.restservices || {}; 7917 window.finesse.restservices.DialogBase = DialogBase; 7918 7919 7920 return DialogBase; 7921 }); 7922 7923 /** 7924 * JavaScript representation of the Finesse Dialog object. 7925 * 7926 * @requires finesse.clientservices.ClientServices 7927 * @requires Class 7928 * @requires finesse.FinesseBase 7929 * @requires finesse.restservices.RestBase 7930 */ 7931 7932 /** @private */ 7933 define('restservices/Dialog',[ 7934 'restservices/DialogBase', 7935 'utilities/Utilities' 7936 ], 7937 function (DialogBase, Utilities) { 7938 var Dialog = DialogBase.extend(/** @lends finesse.restservices.Dialog.prototype */{ 7939 7940 /** 7941 * @class 7942 * A Dialog is an attempted connection between or among multiple participants, 7943 * for example, a regular phone call, a conference, or a silent monitor session. 7944 * 7945 * @augments finesse.restservices.DialogBase 7946 * @constructs 7947 */ 7948 _fakeConstuctor: function () { 7949 /* This is here to hide the real init constructor from the public docs */ 7950 }, 7951 7952 /** 7953 * @private 7954 * 7955 * @param {Object} options 7956 * An object with the following properties:<ul> 7957 * <li><b>id:</b> The id of the object being constructed</li> 7958 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 7959 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 7960 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 7961 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 7962 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 7963 * <li><b>status:</b> {Number} The HTTP status code returned</li> 7964 * <li><b>content:</b> {String} Raw string of response</li> 7965 * <li><b>object:</b> {Object} Parsed object of response</li> 7966 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 7967 * <li><b>errorType:</b> {String} Type of error that was caught</li> 7968 * <li><b>errorMessage:</b> {String} Message associated with error</li> 7969 * </ul></li> 7970 * </ul></li> 7971 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 7972 **/ 7973 init: function (options) { 7974 this._super(options); 7975 }, 7976 7977 /** 7978 * @private 7979 * Gets the REST class for the current object - this is the Dialog class. 7980 * @returns {Object} The Dialog class. 7981 */ 7982 getRestClass: function () { 7983 return Dialog; 7984 }, 7985 7986 /** 7987 * The requestId reaper timeout in ms 7988 */ 7989 REQUESTID_REAPER_TIMEOUT: 5000, 7990 7991 /** 7992 * Getter for the from address. 7993 * @returns {String} The from address. 7994 */ 7995 getFromAddress: function () { 7996 this.isLoaded(); 7997 return this.getData().fromAddress; 7998 }, 7999 8000 /** 8001 * Getter for the to address. 8002 * @returns {String} The to address. 8003 */ 8004 getToAddress: function () { 8005 this.isLoaded(); 8006 return this.getData().toAddress; 8007 }, 8008 8009 8010 /** 8011 * gets the participant timer counters 8012 * 8013 * @param {String} participantExt Extension of participant. 8014 * @returns {Object} Array of Participants which contains properties :<ul> 8015 * <li>state - The state of the Participant. 8016 * <li>startTime - The start Time of the Participant. 8017 * <li>stateChangeTime - The time when participant state has changed.</ul> 8018 */ 8019 getParticipantTimerCounters : function (participantExt) { 8020 var part, participantTimerCounters = {}, idx, participants; 8021 8022 participants = this.getParticipants(); 8023 8024 8025 //Loop through all the participants and find the right participant (based on participantExt) 8026 for(idx=0;idx<participants.length;idx=idx+1) 8027 { 8028 part = participants[idx]; 8029 8030 if (part.mediaAddress === participantExt) 8031 { 8032 participantTimerCounters.startTime= part.startTime; 8033 participantTimerCounters.stateChangeTime= part.stateChangeTime; 8034 participantTimerCounters.state= part.state; 8035 break; 8036 } 8037 } 8038 8039 return participantTimerCounters; 8040 }, 8041 8042 /** 8043 * Determines the droppable participants. A droppable participant is a participant that is an agent extension. 8044 * (It is not a CTI Route Point, IVR Port, or the caller) 8045 * 8046 * @param {String} filterExtension used to remove a single extension from the list 8047 * @returns {Object} Array of Participants that can be dropped. 8048 * Participant entity properties are as follows:<ul> 8049 * <li>state - The state of the Participant. 8050 * <li>stateCause - The state cause of the Participant. 8051 * <li>mediaAddress - The media address of the Participant. 8052 * <li>startTime - The start Time of the Participant. 8053 * <li>stateChangeTime - The time when participant state has changed. 8054 * <li>actions - These are the actions that a Participant can perform</ul> 8055 */ 8056 getDroppableParticipants: function (filterExtension) { 8057 this.isLoaded(); 8058 var droppableParticipants = [], participants, index, idx, filterExtensionToRemove = "", callStateOk, part; 8059 8060 participants = this.getParticipants(); 8061 8062 if (filterExtension) 8063 { 8064 filterExtensionToRemove = filterExtension; 8065 } 8066 8067 //Loop through all the participants to remove non-agents & remove filterExtension 8068 //We could have removed filterExtension using splice, but we have to iterate through 8069 //the list anyway. 8070 for(idx=0;idx<participants.length;idx=idx+1) 8071 { 8072 part = participants[idx]; 8073 8074 //Skip the filterExtension 8075 if (part.mediaAddress !== filterExtensionToRemove) 8076 { 8077 callStateOk = this._isParticipantStateDroppable(part); 8078 8079 //Remove non-agents & make sure callstate 8080 if (callStateOk === true && part.mediaAddressType === this._agentDeviceType) 8081 { 8082 droppableParticipants.push(part); 8083 } 8084 } 8085 } 8086 8087 return Utilities.getArray(droppableParticipants); 8088 }, 8089 8090 _isParticipantStateDroppable : function (part) 8091 { 8092 var isParticipantStateDroppable = false; 8093 if (part.state === Dialog.ParticipantStates.ACTIVE || part.state === Dialog.ParticipantStates.ACCEPTED || part.state === Dialog.ParticipantStates.HELD) 8094 { 8095 isParticipantStateDroppable = true; 8096 } 8097 8098 return isParticipantStateDroppable; 8099 }, 8100 8101 /** 8102 * Is the participant droppable 8103 * 8104 * @param {String} participantExt Extension of participant. 8105 * @returns {Boolean} True is droppable. 8106 */ 8107 isParticipantDroppable : function (participantExt) { 8108 var droppableParticipants = null, isDroppable = false, idx, part, callStateOk; 8109 8110 droppableParticipants = this.getDroppableParticipants(); 8111 8112 if (droppableParticipants) 8113 { 8114 for(idx=0;idx<droppableParticipants.length;idx=idx+1) 8115 { 8116 part = droppableParticipants[idx]; 8117 8118 if (part.mediaAddress === participantExt) 8119 { 8120 callStateOk = this._isParticipantStateDroppable(part); 8121 8122 //Remove non-agents & make sure callstate 8123 if (callStateOk === true && part.mediaAddressType === this._agentDeviceType) 8124 { 8125 isDroppable = true; 8126 break; 8127 } 8128 } 8129 } 8130 } 8131 8132 return isDroppable; 8133 }, 8134 8135 /** 8136 * Retrieves information about the currently scheduled callback, if any. 8137 * @returns {Object} If no callback has been set, will return undefined. If 8138 * a callback has been set, it will return a map with one or more of the 8139 * following entries, depending on what values have been set. 8140 * callbackTime - the callback time, if it has been set. 8141 * callbackNumber - the callback number, if it has been set. 8142 */ 8143 getCallbackInfo: function() { 8144 this.isLoaded(); 8145 return this.getData().scheduledCallbackInfo; 8146 }, 8147 8148 /** 8149 * Invoke a consult call out to a destination. 8150 * 8151 * @param {String} mediaAddress 8152 * The media address of the user performing the consult call. 8153 * @param {String} toAddress 8154 * The destination address of the consult call. 8155 * @param {finesse.interfaces.RequestHandlers} handlers 8156 * An object containing the handlers for the request 8157 */ 8158 makeConsultCall: function (mediaAddress, toAddress, handlers) { 8159 this.isLoaded(); 8160 var contentBody = {}; 8161 contentBody[this.getRestType()] = { 8162 "targetMediaAddress": mediaAddress, 8163 "toAddress": toAddress, 8164 "requestedAction": Dialog.Actions.CONSULT_CALL 8165 }; 8166 this._makeRequest(contentBody, handlers); 8167 return this; // Allow cascading 8168 }, 8169 8170 /** 8171 * Invoke a single step transfer request. 8172 * 8173 * @param {String} mediaAddress 8174 * The media address of the user performing the single step transfer. 8175 * @param {String} toAddress 8176 * The destination address of the single step transfer. 8177 * @param {finesse.interfaces.RequestHandlers} handlers 8178 * An object containing the handlers for the request 8179 */ 8180 initiateDirectTransfer: function (mediaAddress, toAddress, handlers) { 8181 this.isLoaded(); 8182 var contentBody = {}; 8183 contentBody[this.getRestType()] = { 8184 "targetMediaAddress": mediaAddress, 8185 "toAddress": toAddress, 8186 "requestedAction": Dialog.Actions.TRANSFER_SST 8187 }; 8188 this._makeRequest(contentBody, handlers); 8189 return this; // Allow cascading 8190 }, 8191 8192 /** 8193 * Update this dialog's wrap-up reason. 8194 * 8195 * @param {String} wrapUpReason 8196 * The new wrap-up reason for this dialog 8197 * @param {finesse.interfaces.RequestHandlers} handlers 8198 * An object containing the handlers for the request 8199 */ 8200 updateWrapUpReason: function (wrapUpReason, options) 8201 { 8202 this.isLoaded(); 8203 var mediaProperties = 8204 { 8205 "wrapUpReason": wrapUpReason 8206 }; 8207 8208 options = options || {}; 8209 options.content = {}; 8210 options.content[this.getRestType()] = 8211 { 8212 "mediaProperties": mediaProperties, 8213 "requestedAction": Dialog.Actions.UPDATE_CALL_DATA 8214 }; 8215 options.method = "PUT"; 8216 this.restRequest(this.getRestUrl(), options); 8217 8218 return this; 8219 }, 8220 8221 /** 8222 * Invoke a request to server based on the action given. 8223 * @param {String} mediaAddress 8224 * The media address of the user performing the action. 8225 * @param {finesse.restservices.Dialog.Actions} action 8226 * The action string indicating the action to invoke on dialog. 8227 * @param {finesse.interfaces.RequestHandlers} handlers 8228 * An object containing the handlers for the request 8229 */ 8230 requestAction: function (mediaAddress, action, handlers) { 8231 this.isLoaded(); 8232 var contentBody = {}; 8233 contentBody[this.getRestType()] = { 8234 "targetMediaAddress": mediaAddress, 8235 "requestedAction": action 8236 }; 8237 this._makeRequest(contentBody, handlers); 8238 return this; // Allow cascading 8239 }, 8240 8241 /** 8242 * Wrapper around "requestAction" to request PARTICIPANT_DROP action. 8243 * 8244 * @param targetMediaAddress is the address to drop 8245 * @param {finesse.interfaces.RequestHandlers} handlers 8246 * An object containing the handlers for the request 8247 */ 8248 dropParticipant: function (targetMediaAddress, handlers) { 8249 this.requestAction(targetMediaAddress, Dialog.Actions.PARTICIPANT_DROP, handlers); 8250 }, 8251 8252 /** 8253 * Invoke a request to server to send DTMF digit tones. 8254 * @param {String} mediaAddress 8255 * @param {String} action 8256 * The action string indicating the action to invoke on dialog. 8257 * @param {finesse.interfaces.RequestHandlers} handlers 8258 * An object containing the handlers for the request 8259 */ 8260 sendDTMFRequest: function (mediaAddress, handlers, digit) { 8261 this.isLoaded(); 8262 var contentBody = {}; 8263 contentBody[this.getRestType()] = { 8264 "targetMediaAddress": mediaAddress, 8265 "requestedAction": "SEND_DTMF", 8266 "actionParams": { 8267 "ActionParam": { 8268 "name": "dtmfString", 8269 "value": digit 8270 } 8271 } 8272 }; 8273 this._makeRequest(contentBody, handlers); 8274 return this; // Allow cascading 8275 }, 8276 8277 /** 8278 * Invoke a request to server to set the time for a callback. 8279 * @param {String} mediaAddress 8280 * @param {String} callbackTime 8281 * The requested time for the callback, in YYYY-MM-DDTHH:MM format 8282 * (ex: 2013-12-24T23:59) 8283 * @param {finesse.interfaces.RequestHandlers} handlers 8284 * An object containing the handlers for the request 8285 */ 8286 updateCallbackTime: function (mediaAddress, callbackTime, handlers) { 8287 this.isLoaded(); 8288 var contentBody = {}; 8289 contentBody[this.getRestType()] = { 8290 "targetMediaAddress": mediaAddress, 8291 "requestedAction": Dialog.Actions.UPDATE_SCHEDULED_CALLBACK, 8292 "actionParams": { 8293 "ActionParam": { 8294 "name": "callbackTime", 8295 "value": callbackTime 8296 } 8297 } 8298 }; 8299 this._makeRequest(contentBody, handlers); 8300 return this; // Allow cascading 8301 }, 8302 8303 /** 8304 * Invoke a request to server to set the number for a callback. 8305 * @param {String} mediaAddress 8306 * @param {String} callbackNumber 8307 * The requested number to call for the callback 8308 * @param {finesse.interfaces.RequestHandlers} handlers 8309 * An object containing the handlers for the request 8310 */ 8311 updateCallbackNumber: function (mediaAddress, callbackNumber, handlers) { 8312 this.isLoaded(); 8313 var contentBody = {}; 8314 contentBody[this.getRestType()] = { 8315 "targetMediaAddress": mediaAddress, 8316 "requestedAction": Dialog.Actions.UPDATE_SCHEDULED_CALLBACK, 8317 "actionParams": { 8318 "ActionParam": { 8319 "name": "callbackNumber", 8320 "value": callbackNumber 8321 } 8322 } 8323 }; 8324 this._makeRequest(contentBody, handlers); 8325 return this; // Allow cascading 8326 }, 8327 8328 /** 8329 * Invoke a request to server to cancel a callback. 8330 * @param {String} mediaAddress 8331 * @param {finesse.interfaces.RequestHandlers} handlers 8332 * An object containing the handlers for the request 8333 */ 8334 cancelCallback: function (mediaAddress, handlers) { 8335 this.isLoaded(); 8336 var contentBody = {}; 8337 contentBody[this.getRestType()] = { 8338 "targetMediaAddress": mediaAddress, 8339 "requestedAction": Dialog.Actions.CANCEL_SCHEDULED_CALLBACK 8340 }; 8341 this._makeRequest(contentBody, handlers); 8342 return this; // Allow cascading 8343 }, 8344 8345 /** 8346 * Invoke a request to server to reclassify the call type. 8347 * @param {String} mediaAddress 8348 * The media address of the user performing the consult call. 8349 * @param {String} classification 8350 * The classification to assign to the call. Valid values are "VOICE", "FAX", 8351 * "ANS_MACHINE", "INVALID", "BUSY" (CCX only), and "DO_NOT_CALL". 8352 * @param {finesse.interfaces.RequestHandlers} handlers 8353 * An object containing the handlers for the request 8354 */ 8355 reclassifyCall: function (mediaAddress, classification, handlers) { 8356 this.isLoaded(); 8357 var contentBody = {}; 8358 contentBody[this.getRestType()] = { 8359 "targetMediaAddress": mediaAddress, 8360 "requestedAction": Dialog.Actions.RECLASSIFY, 8361 "actionParams": { 8362 "ActionParam": { 8363 "name": "outboundClassification", 8364 "value": classification 8365 } 8366 } 8367 }; 8368 this._makeRequest(contentBody, handlers); 8369 return this; // Allow cascading 8370 }, 8371 8372 /** 8373 * Utility method to create a closure containing the requestId and the Dialogs object so 8374 * that the _pendingCallbacks map can be manipulated when the timer task is executed. 8375 * @param {String} requestId The requestId of the event 8376 * @return {Function} The function to be executed by setTimeout 8377 */ 8378 _createRequestIdReaper: function (requestId) { 8379 var that = this; 8380 return function () { 8381 that._logger.log("Dialog: clearing the requestId-to-callbacks mapping for requestId=" + requestId); 8382 delete that._pendingCallbacks[requestId]; 8383 }; 8384 }, 8385 8386 /** 8387 * Overriding implementation of the one in RestBase.js 8388 * This determines the strategy that Dialogs will take after processing an event that contains a requestId. 8389 * @param {String} requestId The requestId of the event 8390 */ 8391 _postProcessUpdateStrategy: function (requestId) { 8392 this._logger.log("Dialog: determining whether to set timeout for clearing requestId-to-callbacks mapping requestId=" + requestId); 8393 var callbacksObj = this._pendingCallbacks[requestId]; 8394 if (callbacksObj && !callbacksObj.used) { 8395 this._logger.log("Dialog: setting timeout for clearing requestId-to-callbacks mapping requestId=" + requestId); 8396 setTimeout(this._createRequestIdReaper(requestId), this.REQUESTID_REAPER_TIMEOUT); 8397 callbacksObj.used = true; 8398 } 8399 } 8400 8401 }); 8402 8403 Dialog.Actions = /** @lends finesse.restservices.Dialog.Actions.prototype */ { 8404 /** 8405 * Drops the Participant from the Dialog. 8406 */ 8407 DROP: "DROP", 8408 /** 8409 * Answers a Dialog. 8410 */ 8411 ANSWER: "ANSWER", 8412 /** 8413 * Holds the Dialog. 8414 */ 8415 HOLD: "HOLD", 8416 /** 8417 * Barges into a Call Dialog. 8418 */ 8419 BARGE_CALL: "BARGE_CALL", 8420 /** 8421 * Allow as Supervisor to Drop a Participant from the Dialog. 8422 */ 8423 PARTICIPANT_DROP: "PARTICIPANT_DROP", 8424 /** 8425 * Makes a new Call Dialog. 8426 */ 8427 MAKE_CALL: "MAKE_CALL", 8428 /** 8429 * Retrieves a Dialog that is on Hold. 8430 */ 8431 RETRIEVE: "RETRIEVE", 8432 /** 8433 * Sets the time or number for a callback. Can be 8434 * either a new callback, or updating an existing one. 8435 */ 8436 UPDATE_SCHEDULED_CALLBACK: "UPDATE_SCHEDULED_CALLBACK", 8437 /** 8438 * Cancels a callback. 8439 */ 8440 CANCEL_SCHEDULED_CALLBACK: "CANCEL_SCHEDULED_CALLBACK", 8441 /** 8442 * Initiates a Consult Call. 8443 */ 8444 CONSULT_CALL: "CONSULT_CALL", 8445 /** 8446 * Initiates a Transfer of a Dialog. 8447 */ 8448 TRANSFER: "TRANSFER", 8449 /** 8450 * Initiates a Single-Step Transfer of a Dialog. 8451 */ 8452 TRANSFER_SST: "TRANSFER_SST", 8453 /** 8454 * Initiates a Conference of a Dialog. 8455 */ 8456 CONFERENCE: "CONFERENCE", 8457 /** 8458 * Changes classification for a call 8459 */ 8460 RECLASSIFY: "RECLASSIFY", 8461 /** 8462 * Updates data on a Call Dialog. 8463 */ 8464 UPDATE_CALL_DATA: "UPDATE_CALL_DATA", 8465 /** 8466 * Initiates a Recording on a Call Dialog. 8467 */ 8468 START_RECORDING : "START_RECORDING", 8469 /** 8470 * Sends DTMF (dialed digits) to a Call Dialog. 8471 */ 8472 DTMF : "SEND_DTMF", 8473 /** 8474 * Accepts a Dialog that is being Previewed. 8475 */ 8476 ACCEPT: "ACCEPT", 8477 /** 8478 * Rejects a Dialog. 8479 */ 8480 REJECT: "REJECT", 8481 /** 8482 * Closes a Dialog. 8483 */ 8484 CLOSE : "CLOSE", 8485 /** 8486 * @class Set of action constants for a Dialog. These should be used for 8487 * {@link finesse.restservices.Dialog#requestAction}. 8488 * @constructs 8489 */ 8490 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 8491 }; 8492 8493 Dialog.States = /** @lends finesse.restservices.Dialog.States.prototype */ { 8494 /** 8495 * Indicates that the call is ringing at a device. 8496 */ 8497 ALERTING: "ALERTING", 8498 /** 8499 * Indicates that the phone is off the hook at a device. 8500 */ 8501 INITIATING: "INITIATING", 8502 /** 8503 * Indicates that the dialog has a least one active participant. 8504 */ 8505 ACTIVE: "ACTIVE", 8506 /** 8507 * Indicates that the dialog has no active participants. 8508 */ 8509 DROPPED: "DROPPED", 8510 /** 8511 * Indicates that the phone is dialing at the device. 8512 */ 8513 INITIATED: "INITIATED", 8514 /** 8515 * Indicates that the dialog has failed. 8516 * @see Dialog.ReasonStates 8517 */ 8518 FAILED: "FAILED", 8519 /** 8520 * Indicates that the user has accepted an OUTBOUND_PREVIEW dialog. 8521 */ 8522 ACCEPTED: "ACCEPTED", 8523 /** 8524 * @class Possible Dialog State constants. 8525 * The State flow of a typical in-bound Dialog is as follows: INITIATING, INITIATED, ALERTING, ACTIVE, DROPPED. 8526 * @constructs 8527 */ 8528 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 8529 }; 8530 8531 Dialog.ParticipantStates = /** @lends finesse.restservices.Dialog.ParticipantStates.prototype */ { 8532 /** 8533 * Indicates that an incoming call is ringing on the device. 8534 */ 8535 ALERTING: "ALERTING", 8536 /** 8537 * Indicates that an outgoing call, not yet active, exists on the device. 8538 */ 8539 INITIATING: "INITIATING", 8540 /** 8541 * Indicates that the participant is active on the call. 8542 */ 8543 ACTIVE: "ACTIVE", 8544 /** 8545 * Indicates that the participant has dropped from the call. 8546 */ 8547 DROPPED: "DROPPED", 8548 /** 8549 * Indicates that the participant has held their connection to the call. 8550 */ 8551 HELD: "HELD", 8552 /** 8553 * Indicates that the phone is dialing at a device. 8554 */ 8555 INITIATED: "INITIATED", 8556 /** 8557 * Indicates that the call failed. 8558 * @see Dialog.ReasonStates 8559 */ 8560 FAILED: "FAILED", 8561 /** 8562 * Indicates that the participant is not in active state on the call, but is wrapping up after the participant has dropped from the call. 8563 */ 8564 WRAP_UP: "WRAP_UP", 8565 /** 8566 * Indicates that the participant has accepted the dialog. This state is applicable to OUTBOUND_PREVIEW dialogs. 8567 */ 8568 ACCEPTED: "ACCEPTED", 8569 /** 8570 * @class Possible Dialog Participant State constants. 8571 * @constructs 8572 */ 8573 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 8574 }; 8575 8576 Dialog.ReasonStates = /** @lends finesse.restservices.Dialog.ReasonStates.prototype */ { 8577 /** 8578 * Dialog was Busy. This will typically be for a Failed Dialog. 8579 */ 8580 BUSY: "BUSY", 8581 /** 8582 * Dialog reached a Bad Destination. This will typically be for a Failed Dialog. 8583 */ 8584 BAD_DESTINATION: "BAD_DESTINATION", 8585 /** 8586 * All Other Reasons. This will typically be for a Failed Dialog. 8587 */ 8588 OTHER: "OTHER", 8589 /** 8590 * The Device Resource for the Dialog was not available. 8591 */ 8592 DEVICE_RESOURCE_NOT_AVAILABLE : "DEVICE_RESOURCE_NOT_AVAILABLE", 8593 /** 8594 * @class Possible dialog state reasons code constants. 8595 * @constructs 8596 */ 8597 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 8598 }; 8599 8600 window.finesse = window.finesse || {}; 8601 window.finesse.restservices = window.finesse.restservices || {}; 8602 window.finesse.restservices.Dialog = Dialog; 8603 8604 8605 return Dialog; 8606 }); 8607 8608 /** 8609 * JavaScript representation of the Finesse Dialogs collection 8610 * object which contains a list of Dialog objects. 8611 * 8612 * @requires finesse.clientservices.ClientServices 8613 * @requires Class 8614 * @requires finesse.FinesseBase 8615 * @requires finesse.restservices.RestBase 8616 * @requires finesse.restservices.Dialog 8617 */ 8618 /** @private */ 8619 define('restservices/Dialogs',[ 8620 'restservices/RestCollectionBase', 8621 'restservices/Dialog' 8622 ], 8623 function (RestCollectionBase, Dialog) { 8624 var Dialogs = RestCollectionBase.extend(/** @lends finesse.restservices.Dialogs.prototype */{ 8625 8626 /** 8627 * @class 8628 * JavaScript representation of a Dialogs collection object. Also exposes 8629 * methods to operate on the object against the server. 8630 * @augments finesse.restservices.RestCollectionBase 8631 * @constructs 8632 * @see finesse.restservices.Dialog 8633 * @example 8634 * _dialogs = _user.getDialogs( { 8635 * onCollectionAdd : _handleDialogAdd, 8636 * onCollectionDelete : _handleDialogDelete, 8637 * onLoad : _handleDialogsLoaded 8638 * }); 8639 * 8640 * _dialogCollection = _dialogs.getCollection(); 8641 * for (var dialogId in _dialogCollection) { 8642 * if (_dialogCollection.hasOwnProperty(dialogId)) { 8643 * _dialog = _dialogCollection[dialogId]; 8644 * etc... 8645 * } 8646 * } 8647 */ 8648 _fakeConstuctor: function () { 8649 /* This is here to hide the real init constructor from the public docs */ 8650 }, 8651 8652 /** 8653 * @private 8654 * @param {Object} options 8655 * An object with the following properties:<ul> 8656 * <li><b>id:</b> The id of the object being constructed</li> 8657 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 8658 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 8659 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 8660 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 8661 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 8662 * <li><b>status:</b> {Number} The HTTP status code returned</li> 8663 * <li><b>content:</b> {String} Raw string of response</li> 8664 * <li><b>object:</b> {Object} Parsed object of response</li> 8665 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 8666 * <li><b>errorType:</b> {String} Type of error that was caught</li> 8667 * <li><b>errorMessage:</b> {String} Message associated with error</li> 8668 * </ul></li> 8669 * </ul></li> 8670 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 8671 **/ 8672 init: function (options) { 8673 this._super(options); 8674 }, 8675 8676 /** 8677 * @private 8678 * Gets the REST class for the current object - this is the Dialogs class. 8679 */ 8680 getRestClass: function () { 8681 return Dialogs; 8682 }, 8683 8684 /** 8685 * @private 8686 * Gets the REST class for the objects that make up the collection. - this 8687 * is the Dialog class. 8688 */ 8689 getRestItemClass: function () { 8690 return Dialog; 8691 }, 8692 8693 /** 8694 * @private 8695 * Gets the REST type for the current object - this is a "Dialogs". 8696 */ 8697 getRestType: function () { 8698 return "Dialogs"; 8699 }, 8700 8701 /** 8702 * @private 8703 * Gets the REST type for the objects that make up the collection - this is "Dialogs". 8704 */ 8705 getRestItemType: function () { 8706 return "Dialog"; 8707 }, 8708 8709 /** 8710 * @private 8711 * Override default to indicates that the collection doesn't support making 8712 * requests. 8713 */ 8714 supportsRequests: true, 8715 8716 /** 8717 * @private 8718 * Override default to indicates that the collection subscribes to its objects. 8719 */ 8720 supportsRestItemSubscriptions: true, 8721 8722 /** 8723 * The requestId reaper timeout in ms 8724 */ 8725 REQUESTID_REAPER_TIMEOUT: 5000, 8726 8727 /** 8728 * @private 8729 * Create a new Dialog in this collection 8730 * 8731 * @param {String} toAddress 8732 * The to address of the new Dialog 8733 * @param {String} fromAddress 8734 * The from address of the new Dialog 8735 * @param {finesse.interfaces.RequestHandlers} handlers 8736 * An object containing the (optional) handlers for the request. 8737 * @return {finesse.restservices.Dialogs} 8738 * This Dialogs object, to allow cascading. 8739 */ 8740 createNewCallDialog: function (toAddress, fromAddress, handlers) 8741 { 8742 var contentBody = {}; 8743 contentBody[this.getRestItemType()] = { 8744 "requestedAction": "MAKE_CALL", 8745 "toAddress": toAddress, 8746 "fromAddress": fromAddress 8747 }; 8748 8749 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 8750 handlers = handlers || {}; 8751 8752 this.restRequest(this.getRestUrl(), { 8753 method: 'POST', 8754 success: handlers.success, 8755 error: handlers.error, 8756 content: contentBody 8757 }); 8758 return this; // Allow cascading 8759 }, 8760 8761 /** 8762 * @private 8763 * Create a new Dialog in this collection as a result of a requested action 8764 * 8765 * @param {String} toAddress 8766 * The to address of the new Dialog 8767 * @param {String} fromAddress 8768 * The from address of the new Dialog 8769 * @param {finesse.restservices.Dialog.Actions} actionType 8770 * The associated action to request for creating this new dialog 8771 * @param {finesse.interfaces.RequestHandlers} handlers 8772 * An object containing the (optional) handlers for the request. 8773 * @return {finesse.restservices.Dialogs} 8774 * This Dialogs object, to allow cascading. 8775 */ 8776 createNewSuperviseCallDialog: function (toAddress, fromAddress, actionType, handlers) 8777 { 8778 var contentBody = {}; 8779 this._isLoaded = true; 8780 8781 contentBody[this.getRestItemType()] = { 8782 "requestedAction": actionType, 8783 "toAddress": toAddress, 8784 "fromAddress": fromAddress 8785 }; 8786 8787 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 8788 handlers = handlers || {}; 8789 8790 this.restRequest(this.getRestUrl(), { 8791 method: 'POST', 8792 success: handlers.success, 8793 error: handlers.error, 8794 content: contentBody 8795 }); 8796 return this; // Allow cascading 8797 }, 8798 8799 /** 8800 * @private 8801 * Create a new Dialog in this collection as a result of a requested action 8802 * @param {String} fromAddress 8803 * The from address of the new Dialog 8804 * @param {String} toAddress 8805 * The to address of the new Dialog 8806 * @param {finesse.restservices.Dialog.Actions} actionType 8807 * The associated action to request for creating this new dialog 8808 * @param {String} dialogUri 8809 * The associated uri of SUPERVISOR_MONITOR call 8810 * @param {finesse.interfaces.RequestHandlers} handlers 8811 * An object containing the (optional) handlers for the request. 8812 * @return {finesse.restservices.Dialogs} 8813 * This Dialogs object, to allow cascading. 8814 */ 8815 createNewBargeCall: function (fromAddress, toAddress, actionType, dialogURI, handlers) { 8816 this.isLoaded(); 8817 8818 var contentBody = {}; 8819 contentBody[this.getRestItemType()] = { 8820 "fromAddress": fromAddress, 8821 "toAddress": toAddress, 8822 "requestedAction": actionType, 8823 "associatedDialogUri": dialogURI 8824 8825 }; 8826 // (nonexistent) keys to be read as undefined 8827 handlers = handlers || {}; 8828 this.restRequest(this.getRestUrl(), { 8829 method: 'POST', 8830 success: handlers.success, 8831 error: handlers.error, 8832 content: contentBody 8833 }); 8834 return this; // Allow cascading 8835 }, 8836 8837 /** 8838 * Utility method to get the number of dialogs in this collection. 8839 * 'excludeSilentMonitor' flag is provided as an option to exclude calls with type 8840 * 'SUPERVISOR_MONITOR' from the count. 8841 * @param {Boolean} excludeSilentMonitor If true, calls with type of 'SUPERVISOR_MONITOR' will be excluded from the count. 8842 * @return {Number} The number of dialogs in this collection. 8843 */ 8844 getDialogCount: function (excludeSilentMonitor) { 8845 this.isLoaded(); 8846 8847 var dialogId, count = 0; 8848 if (excludeSilentMonitor) { 8849 for (dialogId in this._collection) { 8850 if (this._collection.hasOwnProperty(dialogId)) { 8851 if (this._collection[dialogId].getCallType() !== 'SUPERVISOR_MONITOR') { 8852 count += 1; 8853 } 8854 } 8855 } 8856 8857 return count; 8858 } else { 8859 return this.length; 8860 } 8861 }, 8862 8863 /** 8864 * Utility method to create a closure containing the requestId and the Dialogs object so 8865 * that the _pendingCallbacks map can be manipulated when the timer task is executed. 8866 * @param {String} requestId The requestId of the event 8867 * @return {Function} The function to be executed by setTimeout 8868 */ 8869 _createRequestIdReaper: function (requestId) { 8870 var that = this; 8871 return function () { 8872 that._logger.log("Dialogs: clearing the requestId-to-callbacks mapping for requestId=" + requestId); 8873 delete that._pendingCallbacks[requestId]; 8874 }; 8875 }, 8876 8877 /** 8878 * Overriding implementation of the one in RestBase.js 8879 * This determines the strategy that Dialogs will take after processing an event that contains a requestId. 8880 * @param {String} requestId The requestId of the event 8881 */ 8882 _postProcessUpdateStrategy: function (requestId) { 8883 this._logger.log("Dialogs: determining whether to set timeout for clearing requestId-to-callbacks mapping requestId=" + requestId); 8884 var callbacksObj = this._pendingCallbacks[requestId]; 8885 if (callbacksObj && !callbacksObj.used) { 8886 this._logger.log("Dialogs: setting timeout for clearing requestId-to-callbacks mapping requestId=" + requestId); 8887 setTimeout(this._createRequestIdReaper(requestId), this.REQUESTID_REAPER_TIMEOUT); 8888 callbacksObj.used = true; 8889 } 8890 } 8891 8892 }); 8893 8894 window.finesse = window.finesse || {}; 8895 window.finesse.restservices = window.finesse.restservices || {}; 8896 window.finesse.restservices.Dialogs = Dialogs; 8897 8898 return Dialogs; 8899 }); 8900 8901 /** 8902 * JavaScript representation of the Finesse ClientLog object 8903 * 8904 * @requires finesse.clientservices.ClientServices 8905 * @requires Class 8906 * @requires finesse.FinesseBase 8907 * @requires finesse.restservices.RestBase 8908 */ 8909 8910 /** The following comment is to prevent jslint errors about 8911 * using variables before they are defined. 8912 */ 8913 /** @private */ 8914 /*global finesse*/ 8915 8916 define('restservices/ClientLog',["restservices/RestBase"], function (RestBase) { 8917 8918 var ClientLog = RestBase.extend(/** @lends finesse.restservices.ClientLog.prototype */{ 8919 /** 8920 * @private 8921 * Returns whether this object supports transport logs 8922 */ 8923 doNotLog : true, 8924 8925 explicitSubscription : true, 8926 8927 /** 8928 * @class 8929 * @private 8930 * JavaScript representation of a ClientLog object. Also exposes methods to operate 8931 * on the object against the server. 8932 * 8933 * @param {Object} options 8934 * An object with the following properties:<ul> 8935 * <li><b>id:</b> The id of the object being constructed</li> 8936 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 8937 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 8938 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 8939 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 8940 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 8941 * <li><b>status:</b> {Number} The HTTP status code returned</li> 8942 * <li><b>content:</b> {String} Raw string of response</li> 8943 * <li><b>object:</b> {Object} Parsed object of response</li> 8944 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 8945 * <li><b>errorType:</b> {String} Type of error that was caught</li> 8946 * <li><b>errorMessage:</b> {String} Message associated with error</li> 8947 * </ul></li> 8948 * </ul></li> 8949 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 8950 * @constructs 8951 * @augments finesse.restservices.RestBase 8952 **/ 8953 init: function (options) { 8954 this._super({ 8955 id: "", 8956 data: {clientLog : null}, 8957 onAdd: options.onAdd, 8958 onChange: options.onChange, 8959 onLoad: options.onLoad, 8960 onError: options.onError, 8961 parentObj: options.parentObj 8962 }); 8963 }, 8964 8965 /** 8966 * @private 8967 * Gets the REST class for the current object - this is the ClientLog object. 8968 */ 8969 getRestClass: function () { 8970 return ClientLog; 8971 }, 8972 8973 /** 8974 * @private 8975 * Gets the REST type for the current object - this is a "ClientLog". 8976 */ 8977 getRestType: function () 8978 { 8979 return "ClientLog"; 8980 }, 8981 8982 /** 8983 * @private 8984 * Gets the node path for the current object 8985 * @returns {String} The node path 8986 */ 8987 getXMPPNodePath: function () { 8988 return this.getRestUrl(); 8989 }, 8990 8991 /** 8992 * @private 8993 * Utility method to fetch this object from the server, however we 8994 * override it for ClientLog to not do anything because GET is not supported 8995 * for ClientLog object. 8996 */ 8997 _doGET: function (handlers) { 8998 return; 8999 }, 9000 9001 /** 9002 * @private 9003 * Invoke a request to the server given a content body and handlers. 9004 * 9005 * @param {Object} contentBody 9006 * A JS object containing the body of the action request. 9007 * @param {Object} handlers 9008 * An object containing the following (optional) handlers for the request:<ul> 9009 * <li><b>success(rsp):</b> A callback function for a successful request to be invoked with the following 9010 * response object as its only parameter:<ul> 9011 * <li><b>status:</b> {Number} The HTTP status code returned</li> 9012 * <li><b>content:</b> {String} Raw string of response</li> 9013 * <li><b>object:</b> {Object} Parsed object of response</li></ul> 9014 * <li>A error callback function for an unsuccessful request to be invoked with the 9015 * error response object as its only parameter:<ul> 9016 * <li><b>status:</b> {Number} The HTTP status code returned</li> 9017 * <li><b>content:</b> {String} Raw string of response</li> 9018 * <li><b>object:</b> {Object} Parsed object of response (HTTP errors)</li> 9019 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 9020 * <li><b>errorType:</b> {String} Type of error that was caught</li> 9021 * <li><b>errorMessage:</b> {String} Message associated with error</li> 9022 * </ul></li> 9023 * </ul> 9024 */ 9025 sendLogs: function (contentBody, handlers) { 9026 // Protect against null dereferencing of options allowing its 9027 // (nonexistent) keys to be read as undefined 9028 handlers = handlers || {}; 9029 9030 this.restRequest(this.getRestUrl(), { 9031 method: 'POST', 9032 //success: handlers.success, 9033 error: handlers.error, 9034 content: contentBody 9035 }); 9036 } 9037 }); 9038 9039 window.finesse = window.finesse || {}; 9040 window.finesse.restservices = window.finesse.restservices || {}; 9041 window.finesse.restservices.ClientLog = ClientLog; 9042 9043 return ClientLog; 9044 }); 9045 9046 /** 9047 * JavaScript representation of the Finesse Queue object 9048 * @requires finesse.clientservices.ClientServices 9049 * @requires Class 9050 * @requires finesse.FinesseBase 9051 * @requires finesse.restservices.RestBase 9052 */ 9053 9054 /** @private */ 9055 define('restservices/Queue',[ 9056 'restservices/RestBase', 9057 'utilities/Utilities' 9058 ], 9059 function (RestBase, Utilities) { 9060 var Queue = RestBase.extend(/** @lends finesse.restservices.Queue.prototype */{ 9061 9062 /** 9063 * @class 9064 * A Queue is a list of Contacts available to a User for quick dial. 9065 * 9066 * @augments finesse.restservices.RestBase 9067 * @constructs 9068 */ 9069 _fakeConstuctor: function () { 9070 /* This is here to hide the real init constructor from the public docs */ 9071 }, 9072 9073 /** 9074 * @private 9075 * JavaScript representation of a Queue object. Also exposes methods to operate 9076 * on the object against the server. 9077 * 9078 * @constructor 9079 * @param {String} id 9080 * Not required... 9081 * @param {Object} callbacks 9082 * An object containing callbacks for instantiation and runtime 9083 * @param {Function} callbacks.onLoad(this) 9084 * Callback to invoke upon successful instantiation 9085 * @param {Function} callbacks.onLoadError(rsp) 9086 * Callback to invoke on instantiation REST request error 9087 * as passed by finesse.clientservices.ClientServices.ajax() 9088 * { 9089 * status: {Number} The HTTP status code returned 9090 * content: {String} Raw string of response 9091 * object: {Object} Parsed object of response 9092 * error: {Object} Wrapped exception that was caught 9093 * error.errorType: {String} Type of error that was caught 9094 * error.errorMessage: {String} Message associated with error 9095 * } 9096 * @param {Function} callbacks.onChange(this) 9097 * Callback to invoke upon successful update 9098 * @param {Function} callbacks.onError(rsp) 9099 * Callback to invoke on update error (refresh or event) 9100 * as passed by finesse.clientservices.ClientServices.ajax() 9101 * { 9102 * status: {Number} The HTTP status code returned 9103 * content: {String} Raw string of response 9104 * object: {Object} Parsed object of response 9105 * error: {Object} Wrapped exception that was caught 9106 * error.errorType: {String} Type of error that was caught 9107 * error.errorMessage: {String} Message associated with error 9108 * } 9109 * 9110 */ 9111 init: function (id, callbacks, restObj) { 9112 this._super(id, callbacks, restObj); 9113 }, 9114 9115 /** 9116 * @private 9117 * Gets the REST class for the current object - this is the Queue object. 9118 */ 9119 getRestClass: function () { 9120 return Queue; 9121 }, 9122 9123 /** 9124 * @private 9125 * Gets the REST type for the current object - this is a "Queue". 9126 */ 9127 getRestType: function () { 9128 return "Queue"; 9129 }, 9130 9131 /** 9132 * @private 9133 * Returns whether this object supports subscriptions 9134 */ 9135 supportsSubscriptions: function () { 9136 return true; 9137 }, 9138 9139 /** 9140 * @private 9141 * Specifies whether this object's subscriptions need to be explicitly requested 9142 */ 9143 explicitSubscription: true, 9144 9145 /** 9146 * @private 9147 * Gets the node path for the current object - this is the team Users node 9148 * @returns {String} The node path 9149 */ 9150 getXMPPNodePath: function () { 9151 return this.getRestUrl(); 9152 }, 9153 9154 /** 9155 * Getter for the queue id 9156 * @returns {String} 9157 * The id of the Queue 9158 */ 9159 getId: function () { 9160 this.isLoaded(); 9161 return this._id; 9162 }, 9163 9164 /** 9165 * Getter for the queue name 9166 * @returns {String} 9167 * The name of the Queue 9168 */ 9169 getName: function () { 9170 this.isLoaded(); 9171 return this.getData().name; 9172 }, 9173 9174 /** 9175 * Getter for the queue statistics. 9176 * Supported statistics include:<br> 9177 * - callsInQueue<br> 9178 * - startTimeOfLongestCallInQueue<br> 9179 * <br> 9180 * These statistics can be accessed via dot notation:<br> 9181 * i.e.: getStatistics().callsInQueue 9182 * @returns {Object} 9183 * The Object with different statistics as properties. 9184 */ 9185 getStatistics: function () { 9186 this.isLoaded(); 9187 return this.getData().statistics; 9188 }, 9189 9190 /** 9191 * Parses a uriString to retrieve the id portion 9192 * @param {String} uriString 9193 * @return {String} id 9194 */ 9195 _parseIdFromUriString : function (uriString) { 9196 return Utilities.getId(uriString); 9197 } 9198 9199 }); 9200 9201 window.finesse = window.finesse || {}; 9202 window.finesse.restservices = window.finesse.restservices || {}; 9203 window.finesse.restservices.Queue = Queue; 9204 9205 return Queue; 9206 }); 9207 9208 /** 9209 * JavaScript representation of the Finesse Queues collection 9210 * object which contains a list of Queue objects. 9211 * @requires finesse.clientservices.ClientServices 9212 * @requires Class 9213 * @requires finesse.FinesseBase 9214 * @requires finesse.restservices.RestBase 9215 * @requires finesse.restservices.RestCollectionBase 9216 */ 9217 9218 /** 9219 * @class 9220 * JavaScript representation of a Queues collection object. 9221 * 9222 * @constructor 9223 * @borrows finesse.restservices.RestCollectionBase as finesse.restservices.Queues 9224 */ 9225 9226 /** @private */ 9227 define('restservices/Queues',[ 9228 'restservices/RestCollectionBase', 9229 'restservices/Queue' 9230 ], 9231 function (RestCollectionBase, Queue) { 9232 var Queues = RestCollectionBase.extend(/** @lends finesse.restservices.Queues.prototype */{ 9233 9234 /** 9235 * @class 9236 * JavaScript representation of a Queues collection object. 9237 * @augments finesse.restservices.RestCollectionBase 9238 * @constructs 9239 * @see finesse.restservices.Queue 9240 * @example 9241 * _queues = _user.getQueues( { 9242 * onCollectionAdd : _handleQueueAdd, 9243 * onCollectionDelete : _handleQueueDelete, 9244 * onLoad : _handleQueuesLoaded 9245 * }); 9246 * 9247 * _queueCollection = _queues.getCollection(); 9248 * for (var queueId in _queueCollection) { 9249 * if (_queueCollection.hasOwnProperty(queueId)) { 9250 * _queue = _queueCollection[queueId]; 9251 * etc... 9252 * } 9253 * } 9254 */ 9255 _fakeConstuctor: function () { 9256 /* This is here to hide the real init constructor from the public docs */ 9257 }, 9258 9259 /** 9260 * @private 9261 * JavaScript representation of a Queues object. Also exposes 9262 * methods to operate on the object against the server. 9263 * 9264 * @param {Object} options 9265 * An object with the following properties:<ul> 9266 * <li><b>id:</b> The id of the object being constructed</li> 9267 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 9268 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 9269 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 9270 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 9271 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 9272 * <li><b>status:</b> {Number} The HTTP status code returned</li> 9273 * <li><b>content:</b> {String} Raw string of response</li> 9274 * <li><b>object:</b> {Object} Parsed object of response</li> 9275 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 9276 * <li><b>errorType:</b> {String} Type of error that was caught</li> 9277 * <li><b>errorMessage:</b> {String} Message associated with error</li> 9278 * </ul></li> 9279 * </ul></li> 9280 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 9281 **/ 9282 init: function (options) { 9283 this._super(options); 9284 }, 9285 9286 /** 9287 * @private 9288 * Gets xmpp node path. 9289 */ 9290 getXMPPNodePath: function () { 9291 return this.getRestUrl(); 9292 }, 9293 9294 /** 9295 * @private 9296 * Gets the REST class for the current object - this is the Queues class. 9297 */ 9298 getRestClass: function () { 9299 return Queues; 9300 }, 9301 9302 /** 9303 * @private 9304 * Gets the REST class for the objects that make up the collection. - this 9305 * is the Queue class. 9306 */ 9307 getRestItemClass: function () { 9308 return Queue; 9309 }, 9310 9311 /** 9312 * @private 9313 * Gets the REST type for the current object - this is a "Queues". 9314 */ 9315 getRestType: function () { 9316 return "Queues"; 9317 }, 9318 9319 /** 9320 * @private 9321 * Gets the REST type for the objects that make up the collection - this is "Queue". 9322 */ 9323 getRestItemType: function () { 9324 return "Queue"; 9325 }, 9326 9327 explicitSubscription: true, 9328 9329 handlesItemRefresh: true 9330 }); 9331 9332 window.finesse = window.finesse || {}; 9333 window.finesse.restservices = window.finesse.restservices || {}; 9334 window.finesse.restservices.Queues = Queues; 9335 9336 return Queues; 9337 }); 9338 9339 /** 9340 * JavaScript representation of the Finesse WrapUpReason object. 9341 * 9342 * @requires finesse.clientservices.ClientServices 9343 * @requires Class 9344 * @requires finesse.FinesseBase 9345 * @requires finesse.restservices.RestBase 9346 */ 9347 9348 /** @private */ 9349 define('restservices/WrapUpReason',['restservices/RestBase'], function (RestBase) { 9350 9351 var WrapUpReason = RestBase.extend(/** @lends finesse.restservices.WrapUpReason.prototype */{ 9352 9353 /** 9354 * @class 9355 * A WrapUpReason is a code and description identifying a particular reason that a 9356 * User is in WORK (WrapUp) mode. 9357 * 9358 * @augments finesse.restservices.RestBase 9359 * @see finesse.restservices.User 9360 * @see finesse.restservices.User.States#WORK 9361 * @constructs 9362 */ 9363 _fakeConstuctor: function () { 9364 /* This is here to hide the real init constructor from the public docs */ 9365 }, 9366 9367 /** 9368 * @private 9369 * JavaScript representation of a WrapUpReason object. Also exposes 9370 * methods to operate on the object against the server. 9371 * 9372 * @param {Object} options 9373 * An object with the following properties:<ul> 9374 * <li><b>id:</b> The id of the object being constructed</li> 9375 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 9376 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 9377 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 9378 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 9379 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 9380 * <li><b>status:</b> {Number} The HTTP status code returned</li> 9381 * <li><b>content:</b> {String} Raw string of response</li> 9382 * <li><b>object:</b> {Object} Parsed object of response</li> 9383 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 9384 * <li><b>errorType:</b> {String} Type of error that was caught</li> 9385 * <li><b>errorMessage:</b> {String} Message associated with error</li> 9386 * </ul></li> 9387 * </ul></li> 9388 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 9389 **/ 9390 init: function (options) { 9391 this._super(options); 9392 }, 9393 9394 /** 9395 * @private 9396 * Gets the REST class for the current object - this is the WrapUpReason class. 9397 * @returns {Object} The WrapUpReason class. 9398 */ 9399 getRestClass: function () { 9400 return WrapUpReason; 9401 }, 9402 9403 /** 9404 * @private 9405 * Gets the REST type for the current object - this is a "WrapUpReason". 9406 * @returns {String} The WrapUpReason string. 9407 */ 9408 getRestType: function () { 9409 return "WrapUpReason"; 9410 }, 9411 9412 /** 9413 * @private 9414 * Gets the REST type for the current object - this is a "WrapUpReasons". 9415 * @returns {String} The WrapUpReasons string. 9416 */ 9417 getParentRestType: function () { 9418 return "WrapUpReasons"; 9419 }, 9420 9421 /** 9422 * @private 9423 * Override default to indicate that this object doesn't support making 9424 * requests. 9425 */ 9426 supportsRequests: false, 9427 9428 /** 9429 * @private 9430 * Override default to indicate that this object doesn't support subscriptions. 9431 */ 9432 supportsSubscriptions: false, 9433 9434 /** 9435 * Getter for the label. 9436 * @returns {String} The label. 9437 */ 9438 getLabel: function () { 9439 this.isLoaded(); 9440 return this.getData().label; 9441 }, 9442 9443 /** 9444 * @private 9445 * Getter for the forAll flag. 9446 * @returns {Boolean} True if global. 9447 */ 9448 getForAll: function () { 9449 this.isLoaded(); 9450 return this.getData().forAll; 9451 }, 9452 9453 /** 9454 * @private 9455 * Getter for the Uri value. 9456 * @returns {String} The Uri. 9457 */ 9458 getUri: function () { 9459 this.isLoaded(); 9460 return this.getData().uri; 9461 } 9462 }); 9463 9464 window.finesse = window.finesse || {}; 9465 window.finesse.restservices = window.finesse.restservices || {}; 9466 window.finesse.restservices.WrapUpReason = WrapUpReason; 9467 9468 return WrapUpReason; 9469 }); 9470 9471 /** 9472 * JavaScript representation of the Finesse WrapUpReasons collection 9473 * object which contains a list of WrapUpReason objects. 9474 * 9475 * @requires finesse.clientservices.ClientServices 9476 * @requires Class 9477 * @requires finesse.FinesseBase 9478 * @requires finesse.restservices.RestBase 9479 * @requires finesse.restservices.Dialog 9480 * @requires finesse.restservices.RestCollectionBase 9481 */ 9482 9483 /** @private */ 9484 define('restservices/WrapUpReasons',[ 9485 'restservices/RestCollectionBase', 9486 'restservices/WrapUpReason' 9487 ], 9488 function (RestCollectionBase, WrapUpReason) { 9489 9490 var WrapUpReasons = RestCollectionBase.extend(/** @lends finesse.restservices.WrapUpReasons.prototype */{ 9491 9492 /** 9493 * @class 9494 * JavaScript representation of a WrapUpReasons collection object. 9495 * @augments finesse.restservices.RestCollectionBase 9496 * @constructs 9497 * @see finesse.restservices.WrapUpReason 9498 * @example 9499 * _wrapUpReasons = _user.getWrapUpReasons ( { 9500 * onCollectionAdd : _handleWrapUpReasonAdd, 9501 * onCollectionDelete : _handleWrapUpReasonDelete, 9502 * onLoad : _handleWrapUpReasonsLoaded 9503 * }); 9504 * 9505 * _wrapUpReasonCollection = _wrapUpReasons.getCollection(); 9506 * for (var wrapUpReasonId in _wrapUpReasonCollection) { 9507 * if (_wrapUpReasonCollection.hasOwnProperty(wrapUpReasonId)) { 9508 * _wrapUpReason = _wrapUpReasonCollection[wrapUpReasonId]; 9509 * etc... 9510 * } 9511 * } 9512 */ 9513 _fakeConstuctor: function () { 9514 /* This is here to hide the real init constructor from the public docs */ 9515 }, 9516 9517 /** 9518 * @private 9519 * JavaScript representation of a WrapUpReasons collection object. Also exposes 9520 * methods to operate on the object against the server. 9521 * 9522 * @param {Object} options 9523 * An object with the following properties:<ul> 9524 * <li><b>id:</b> The id of the object being constructed</li> 9525 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 9526 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 9527 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 9528 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 9529 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 9530 * <li><b>status:</b> {Number} The HTTP status code returned</li> 9531 * <li><b>content:</b> {String} Raw string of response</li> 9532 * <li><b>object:</b> {Object} Parsed object of response</li> 9533 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 9534 * <li><b>errorType:</b> {String} Type of error that was caught</li> 9535 * <li><b>errorMessage:</b> {String} Message associated with error</li> 9536 * </ul></li> 9537 * </ul></li> 9538 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 9539 **/ 9540 init: function (options) { 9541 this._super(options); 9542 }, 9543 9544 /** 9545 * @private 9546 * Gets the REST class for the current object - this is the WrapUpReasons class. 9547 */ 9548 getRestClass: function () { 9549 return WrapUpReasons; 9550 }, 9551 9552 /** 9553 * @private 9554 * Gets the REST class for the objects that make up the collection. - this 9555 * is the WrapUpReason class. 9556 */ 9557 getRestItemClass: function () { 9558 return WrapUpReason; 9559 }, 9560 9561 /** 9562 * @private 9563 * Gets the REST type for the current object - this is a "WrapUpReasons". 9564 */ 9565 getRestType: function () { 9566 return "WrapUpReasons"; 9567 }, 9568 9569 /** 9570 * @private 9571 * Gets the REST type for the objects that make up the collection - this is "WrapUpReason". 9572 */ 9573 getRestItemType: function () { 9574 return "WrapUpReason"; 9575 }, 9576 9577 /** 9578 * @private 9579 * Override default to indicates that the collection supports making 9580 * requests. 9581 */ 9582 supportsRequests: true, 9583 9584 /** 9585 * @private 9586 * Override default to indicate that this object doesn't support subscriptions. 9587 */ 9588 supportsRestItemSubscriptions: false, 9589 9590 /** 9591 * @private 9592 * Retrieve the Wrap-Up Reason Codes. This call will re-query the server and refresh the collection. 9593 * 9594 * @returns {finesse.restservices.WrapUpReasons} 9595 * This ReadyReasonCodes object to allow cascading. 9596 */ 9597 get: function () { 9598 // set loaded to false so it will rebuild the collection after the get 9599 this._loaded = false; 9600 // reset collection 9601 this._collection = {}; 9602 // perform get 9603 this._synchronize(); 9604 return this; 9605 } 9606 9607 }); 9608 9609 window.finesse = window.finesse || {}; 9610 window.finesse.restservices = window.finesse.restservices || {}; 9611 window.finesse.restservices.WrapUpReasons = WrapUpReasons; 9612 9613 return WrapUpReasons; 9614 }); 9615 9616 /** 9617 * JavaScript representation of the Finesse Contact object. 9618 * @requires finesse.clientservices.ClientServices 9619 * @requires Class 9620 * @requires finesse.FinesseBase 9621 * @requires finesse.restservices.RestBase 9622 */ 9623 /** @private */ 9624 define('restservices/Contact',['restservices/RestBase'], function (RestBase) { 9625 9626 var Contact = RestBase.extend(/** @lends finesse.restservices.Contact.prototype */{ 9627 9628 /** 9629 * @class 9630 * A Contact is a single entry in a PhoneBook, consisting of a First and Last Name, 9631 * a Phone Number, and a Description. 9632 * 9633 * @augments finesse.restservices.RestBase 9634 * @see finesse.restservices.PhoneBook 9635 * @constructs 9636 */ 9637 _fakeConstuctor: function () { 9638 /* This is here to hide the real init constructor from the public docs */ 9639 }, 9640 9641 /** 9642 * @private 9643 * @param {Object} options 9644 * An object with the following properties:<ul> 9645 * <li><b>id:</b> The id of the object being constructed</li> 9646 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 9647 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 9648 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 9649 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 9650 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 9651 * <li><b>status:</b> {Number} The HTTP status code returned</li> 9652 * <li><b>content:</b> {String} Raw string of response</li> 9653 * <li><b>object:</b> {Object} Parsed object of response</li> 9654 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 9655 * <li><b>errorType:</b> {String} Type of error that was caught</li> 9656 * <li><b>errorMessage:</b> {String} Message associated with error</li> 9657 * </ul></li> 9658 * </ul></li> 9659 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 9660 **/ 9661 init: function (options) { 9662 this._super(options); 9663 }, 9664 9665 /** 9666 * @private 9667 * Gets the REST class for the current object - this is the Contact class. 9668 * @returns {Object} The Contact class. 9669 */ 9670 getRestClass: function () { 9671 return Contact; 9672 }, 9673 9674 /** 9675 * @private 9676 * Gets the REST type for the current object - this is a "Contact". 9677 * @returns {String} The Contact string. 9678 */ 9679 getRestType: function () { 9680 return "Contact"; 9681 }, 9682 9683 /** 9684 * @private 9685 * Override default to indicate that this object doesn't support making 9686 * requests. 9687 */ 9688 supportsRequests: false, 9689 9690 /** 9691 * @private 9692 * Override default to indicate that this object doesn't support subscriptions. 9693 */ 9694 supportsSubscriptions: false, 9695 9696 /** 9697 * Getter for the firstName. 9698 * @returns {String} The firstName. 9699 */ 9700 getFirstName: function () { 9701 this.isLoaded(); 9702 return this.getData().firstName; 9703 }, 9704 9705 /** 9706 * Getter for the lastName. 9707 * @returns {String} The lastName. 9708 */ 9709 getLastName: function () { 9710 this.isLoaded(); 9711 return this.getData().lastName; 9712 }, 9713 9714 /** 9715 * Getter for the phoneNumber. 9716 * @returns {String} The phoneNumber. 9717 */ 9718 getPhoneNumber: function () { 9719 this.isLoaded(); 9720 return this.getData().phoneNumber; 9721 }, 9722 9723 /** 9724 * Getter for the description. 9725 * @returns {String} The description. 9726 */ 9727 getDescription: function () { 9728 this.isLoaded(); 9729 return this.getData().description; 9730 }, 9731 9732 /** @private */ 9733 createPutSuccessHandler: function(contact, contentBody, successHandler){ 9734 return function (rsp) { 9735 // Update internal structure based on response. Here we 9736 // inject the contentBody from the PUT request into the 9737 // rsp.object element to mimic a GET as a way to take 9738 // advantage of the existing _processResponse method. 9739 rsp.object = contentBody; 9740 contact._processResponse(rsp); 9741 9742 //Remove the injected Contact object before cascading response 9743 rsp.object = {}; 9744 9745 //cascade response back to consumer's response handler 9746 successHandler(rsp); 9747 }; 9748 }, 9749 9750 /** @private */ 9751 createPostSuccessHandler: function (contact, contentBody, successHandler) { 9752 return function (rsp) { 9753 rsp.object = contentBody; 9754 contact._processResponse(rsp); 9755 9756 //Remove the injected Contact object before cascading response 9757 rsp.object = {}; 9758 9759 //cascade response back to consumer's response handler 9760 successHandler(rsp); 9761 }; 9762 }, 9763 9764 /** 9765 * Add 9766 * @private 9767 */ 9768 add: function (newValues, handlers) { 9769 // this.isLoaded(); 9770 var contentBody = {}; 9771 9772 contentBody[this.getRestType()] = { 9773 "firstName": newValues.firstName, 9774 "lastName": newValues.lastName, 9775 "phoneNumber": newValues.phoneNumber, 9776 "description": newValues.description 9777 }; 9778 9779 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 9780 handlers = handlers || {}; 9781 9782 this.restRequest(this.getRestUrl(), { 9783 method: 'POST', 9784 success: this.createPostSuccessHandler(this, contentBody, handlers.success), 9785 error: handlers.error, 9786 content: contentBody 9787 }); 9788 9789 return this; // Allow cascading 9790 }, 9791 9792 /** 9793 * Update 9794 * @private 9795 */ 9796 update: function (newValues, handlers) { 9797 this.isLoaded(); 9798 var contentBody = {}; 9799 9800 contentBody[this.getRestType()] = { 9801 "uri": this.getId(), 9802 "firstName": newValues.firstName, 9803 "lastName": newValues.lastName, 9804 "phoneNumber": newValues.phoneNumber, 9805 "description": newValues.description 9806 }; 9807 9808 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 9809 handlers = handlers || {}; 9810 9811 this.restRequest(this.getRestUrl(), { 9812 method: 'PUT', 9813 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 9814 error: handlers.error, 9815 content: contentBody 9816 }); 9817 9818 return this; // Allow cascading 9819 }, 9820 9821 9822 /** 9823 * Delete 9824 * @private 9825 */ 9826 "delete": function ( handlers) { 9827 this.isLoaded(); 9828 9829 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 9830 handlers = handlers || {}; 9831 9832 this.restRequest(this.getRestUrl(), { 9833 method: 'DELETE', 9834 success: this.createPutSuccessHandler(this, {}, handlers.success), 9835 error: handlers.error, 9836 content: undefined 9837 }); 9838 9839 return this; // Allow cascading 9840 } 9841 }); 9842 9843 window.finesse = window.finesse || {}; 9844 window.finesse.restservices = window.finesse.restservices || {}; 9845 window.finesse.restservices.Contact = Contact; 9846 9847 return Contact; 9848 }); 9849 9850 /** 9851 * JavaScript representation of the Finesse Contacts collection 9852 * object which contains a list of Contact objects. 9853 * 9854 * @requires finesse.clientservices.ClientServices 9855 * @requires Class 9856 * @requires finesse.FinesseBase 9857 * @requires finesse.restservices.RestBase 9858 * @requires finesse.restservices.Dialog 9859 * @requires finesse.restservices.RestCollectionBase 9860 */ 9861 /** @private */ 9862 define('restservices/Contacts',[ 9863 'restservices/RestCollectionBase', 9864 'restservices/Contact' 9865 ], 9866 function (RestCollectionBase, Contact) { 9867 var Contacts = RestCollectionBase.extend(/** @lends finesse.restservices.Contacts.prototype */{ 9868 9869 /** 9870 * @class 9871 * JavaScript representation of a Contacts collection object. Also exposes 9872 * methods to operate on the object against the server. 9873 * @augments finesse.restservices.RestCollectionBase 9874 * @constructs 9875 * @see finesse.restservices.Contact 9876 * @see finesse.restservices.PhoneBook 9877 * @example 9878 * _contacts = _phonebook.getContacts( { 9879 * onCollectionAdd : _handleContactAdd, 9880 * onCollectionDelete : _handleContactDelete, 9881 * onLoad : _handleContactsLoaded 9882 * }); 9883 * 9884 * _contactCollection = _contacts.getCollection(); 9885 * for (var contactId in _contactCollection) { 9886 * if (_contactCollection.hasOwnProperty(contactId)) { 9887 * _contact = _contactCollection[contactId]; 9888 * etc... 9889 * } 9890 * } 9891 */ 9892 _fakeConstuctor: function () { 9893 /* This is here to hide the real init constructor from the public docs */ 9894 }, 9895 9896 /** 9897 * @private 9898 * JavaScript representation of a Contacts collection object. Also exposes 9899 * methods to operate on the object against the server. 9900 * 9901 * @param {Object} options 9902 * An object with the following properties:<ul> 9903 * <li><b>id:</b> The id of the object being constructed</li> 9904 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 9905 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 9906 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 9907 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 9908 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 9909 * <li><b>status:</b> {Number} The HTTP status code returned</li> 9910 * <li><b>content:</b> {String} Raw string of response</li> 9911 * <li><b>object:</b> {Object} Parsed object of response</li> 9912 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 9913 * <li><b>errorType:</b> {String} Type of error that was caught</li> 9914 * <li><b>errorMessage:</b> {String} Message associated with error</li> 9915 * </ul></li> 9916 * </ul></li> 9917 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 9918 **/ 9919 init: function (options) { 9920 this._super(options); 9921 }, 9922 9923 /** 9924 * @private 9925 * Gets the REST class for the current object - this is the Contacts class. 9926 */ 9927 getRestClass: function () { 9928 return Contacts; 9929 }, 9930 9931 /** 9932 * @private 9933 * Gets the REST class for the objects that make up the collection. - this 9934 * is the Contact class. 9935 */ 9936 getRestItemClass: function () { 9937 return Contact; 9938 }, 9939 9940 /** 9941 * @private 9942 * Gets the REST type for the current object - this is a "Contacts". 9943 */ 9944 getRestType: function () { 9945 return "Contacts"; 9946 }, 9947 9948 /** 9949 * @private 9950 * Gets the REST type for the objects that make up the collection - this is "Contacts". 9951 */ 9952 getRestItemType: function () { 9953 return "Contact"; 9954 }, 9955 9956 /** 9957 * @private 9958 * Override default to indicates that the collection supports making 9959 * requests. 9960 */ 9961 supportsRequests: true, 9962 9963 /** 9964 * @private 9965 * Override default to indicates that the collection subscribes to its objects. 9966 */ 9967 supportsRestItemSubscriptions: false, 9968 9969 /** 9970 * @private 9971 * Retrieve the Contacts. This call will re-query the server and refresh the collection. 9972 * 9973 * @returns {finesse.restservices.Contacts} 9974 * This Contacts object, to allow cascading. 9975 */ 9976 get: function () { 9977 // set loaded to false so it will rebuild the collection after the get 9978 this._loaded = false; 9979 // reset collection 9980 this._collection = {}; 9981 // perform get 9982 this._synchronize(); 9983 return this; 9984 } 9985 9986 }); 9987 9988 window.finesse = window.finesse || {}; 9989 window.finesse.restservices = window.finesse.restservices || {}; 9990 window.finesse.restservices.Contacts = Contacts; 9991 9992 9993 return Contacts; 9994 }); 9995 9996 /** 9997 * JavaScript representation of the Finesse PhoneBook object. 9998 * 9999 * @requires finesse.clientservices.ClientServices 10000 * @requires Class 10001 * @requires finesse.FinesseBase 10002 * @requires finesse.restservices.RestBase 10003 */ 10004 10005 /** @private */ 10006 define('restservices/PhoneBook',[ 10007 'restservices/RestBase', 10008 'restservices/Contacts' 10009 ], 10010 function (RestBase, Contacts) { 10011 var PhoneBook = RestBase.extend(/** @lends finesse.restservices.PhoneBook.prototype */{ 10012 10013 _contacts: null, 10014 10015 /** 10016 * @class 10017 * A PhoneBook is a list of Contacts available to a User for quick dial. 10018 * 10019 * @augments finesse.restservices.RestBase 10020 * @see finesse.restservices.Contacts 10021 * @constructs 10022 */ 10023 _fakeConstuctor: function () { 10024 /* This is here to hide the real init constructor from the public docs */ 10025 }, 10026 10027 /** 10028 * @private 10029 * JavaScript representation of a PhoneBook object. Also exposes 10030 * methods to operate on the object against the server. 10031 * 10032 * @param {Object} options 10033 * An object with the following properties:<ul> 10034 * <li><b>id:</b> The id of the object being constructed</li> 10035 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 10036 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 10037 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 10038 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 10039 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 10040 * <li><b>status:</b> {Number} The HTTP status code returned</li> 10041 * <li><b>content:</b> {String} Raw string of response</li> 10042 * <li><b>object:</b> {Object} Parsed object of response</li> 10043 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 10044 * <li><b>errorType:</b> {String} Type of error that was caught</li> 10045 * <li><b>errorMessage:</b> {String} Message associated with error</li> 10046 * </ul></li> 10047 * </ul></li> 10048 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 10049 **/ 10050 init: function (options) { 10051 this._super(options); 10052 }, 10053 10054 /** 10055 * @private 10056 * Gets the REST class for the current object - this is the PhoneBook class. 10057 * @returns {Object} The PhoneBook class. 10058 */ 10059 getRestClass: function () { 10060 return PhoneBook; 10061 }, 10062 10063 /** 10064 * @private 10065 * Gets the REST type for the current object - this is a "PhoneBook". 10066 * @returns {String} The PhoneBook string. 10067 */ 10068 getRestType: function () { 10069 return "PhoneBook"; 10070 }, 10071 10072 /** 10073 * @private 10074 * Override default to indicate that this object doesn't support making 10075 * requests. 10076 */ 10077 supportsRequests: false, 10078 10079 /** 10080 * @private 10081 * Override default to indicate that this object doesn't support subscriptions. 10082 */ 10083 supportsSubscriptions: false, 10084 10085 /** 10086 * Getter for the name of the Phone Book. 10087 * @returns {String} The name. 10088 */ 10089 getName: function () { 10090 this.isLoaded(); 10091 return this.getData().name; 10092 }, 10093 10094 /** 10095 * Getter for the type flag. 10096 * @returns {String} The type. 10097 */ 10098 getType: function () { 10099 this.isLoaded(); 10100 return this.getData().type; 10101 }, 10102 10103 /** 10104 * @private 10105 * Getter for the Uri value. 10106 * @returns {String} The Uri. 10107 */ 10108 getUri: function () { 10109 this.isLoaded(); 10110 return this.getData().uri; 10111 }, 10112 10113 /** 10114 * Getter for a Contacts collection object that is associated with PhoneBook. 10115 * @param {finesse.interfaces.RequestHandlers} handlers 10116 * An object containing the handlers for the request 10117 * @returns {finesse.restservices.Contacts} 10118 * A Contacts collection object. 10119 */ 10120 getContacts: function (callbacks) { 10121 var options = callbacks || {}; 10122 options.parentObj = this; 10123 this.isLoaded(); 10124 10125 if (this._contacts === null) { 10126 this._contacts = new Contacts(options); 10127 } 10128 10129 return this._contacts; 10130 }, 10131 10132 /** 10133 * Getter for <contacts> node within PhoneBook - sometimes it's just a URI, sometimes it is a Contacts collection 10134 * @returns {String} uri to contacts 10135 * or {finesse.restservices.Contacts} collection 10136 */ 10137 getEmbeddedContacts: function(){ 10138 this.isLoaded(); 10139 return this.getData().contacts; 10140 }, 10141 10142 /** @private */ 10143 createPutSuccessHandler: function(phonebook, contentBody, successHandler){ 10144 return function (rsp) { 10145 // Update internal structure based on response. Here we 10146 // inject the contentBody from the PUT request into the 10147 // rsp.object element to mimic a GET as a way to take 10148 // advantage of the existing _processResponse method. 10149 rsp.object = contentBody; 10150 phonebook._processResponse(rsp); 10151 10152 //Remove the injected PhoneBook object before cascading response 10153 rsp.object = {}; 10154 10155 //cascade response back to consumer's response handler 10156 successHandler(rsp); 10157 }; 10158 }, 10159 10160 /** @private */ 10161 createPostSuccessHandler: function (phonebook, contentBody, successHandler) { 10162 return function (rsp) { 10163 rsp.object = contentBody; 10164 phonebook._processResponse(rsp); 10165 10166 //Remove the injected PhoneBook object before cascading response 10167 rsp.object = {}; 10168 10169 //cascade response back to consumer's response handler 10170 successHandler(rsp); 10171 }; 10172 }, 10173 10174 /** 10175 * @private 10176 * Add a PhoneBook. 10177 * @param {Object} newValues 10178 * @param {String} newValues.name Name of PhoneBook 10179 * @param {String} newValues.type Type of PhoneBook 10180 * @param {finesse.interfaces.RequestHandlers} handlers 10181 * An object containing the handlers for the request 10182 * @returns {finesse.restservices.PhoneBook} 10183 * This PhoneBook object, to allow cascading 10184 */ 10185 add: function (newValues, handlers) { 10186 // this.isLoaded(); 10187 var contentBody = {}; 10188 10189 contentBody[this.getRestType()] = { 10190 "name": newValues.name, 10191 "type": newValues.type 10192 }; 10193 10194 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 10195 handlers = handlers || {}; 10196 10197 this.restRequest(this.getRestUrl(), { 10198 method: 'POST', 10199 success: this.createPostSuccessHandler(this, contentBody, handlers.success), 10200 error: handlers.error, 10201 content: contentBody 10202 }); 10203 10204 return this; // Allow cascading 10205 }, 10206 10207 /** 10208 * @private 10209 * Update a PhoneBook. 10210 * @param {Object} newValues 10211 * @param {String} newValues.name Name of PhoneBook 10212 * @param {String} newValues.type Type of PhoneBook 10213 * @param {finesse.interfaces.RequestHandlers} handlers 10214 * An object containing the handlers for the request 10215 * @returns {finesse.restservices.PhoneBook} 10216 * This PhoneBook object, to allow cascading 10217 */ 10218 update: function (newValues, handlers) { 10219 this.isLoaded(); 10220 var contentBody = {}; 10221 10222 contentBody[this.getRestType()] = { 10223 "uri": this.getId(), 10224 "name": newValues.name, 10225 "type": newValues.type 10226 }; 10227 10228 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 10229 handlers = handlers || {}; 10230 10231 this.restRequest(this.getRestUrl(), { 10232 method: 'PUT', 10233 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 10234 error: handlers.error, 10235 content: contentBody 10236 }); 10237 10238 return this; // Allow cascading 10239 }, 10240 10241 10242 /** 10243 * Delete a PhoneBook. 10244 * @param {finesse.interfaces.RequestHandlers} handlers 10245 * An object containing the handlers for the request 10246 * @returns {finesse.restservices.PhoneBook} 10247 * This PhoneBook object, to allow cascading 10248 */ 10249 "delete": function ( handlers) { 10250 this.isLoaded(); 10251 10252 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 10253 handlers = handlers || {}; 10254 10255 this.restRequest(this.getRestUrl(), { 10256 method: 'DELETE', 10257 success: this.createPutSuccessHandler(this, {}, handlers.success), 10258 error: handlers.error, 10259 content: undefined 10260 }); 10261 10262 return this; // Allow cascading 10263 } 10264 10265 10266 10267 }); 10268 10269 window.finesse = window.finesse || {}; 10270 window.finesse.restservices = window.finesse.restservices || {}; 10271 window.finesse.restservices.PhoneBook = PhoneBook; 10272 10273 return PhoneBook; 10274 }); 10275 10276 /** 10277 * JavaScript representation of the Finesse PhoneBooks collection 10278 * object which contains a list of PhoneBook objects. 10279 * 10280 * @requires finesse.clientservices.ClientServices 10281 * @requires Class 10282 * @requires finesse.FinesseBase 10283 * @requires finesse.restservices.RestBase 10284 * @requires finesse.restservices.Dialog 10285 * @requires finesse.restservices.RestCollectionBase 10286 */ 10287 /** @private */ 10288 define('restservices/PhoneBooks',[ 10289 'restservices/RestCollectionBase', 10290 'restservices/PhoneBook' 10291 ], 10292 function (RestCollectionBase, PhoneBook) { 10293 var PhoneBooks = RestCollectionBase.extend(/** @lends finesse.restservices.PhoneBooks.prototype */{ 10294 10295 /** 10296 * @class 10297 * JavaScript representation of a PhoneBooks collection object. 10298 * @augments finesse.restservices.RestCollectionBase 10299 * @constructs 10300 * @see finesse.restservices.PhoneBook 10301 * @see finesse.restservices.Contacts 10302 * @see finesse.restservices.Contact 10303 * @example 10304 * _phoneBooks = _user.getPhoneBooks( { 10305 * onCollectionAdd : _handlePhoneBookAdd, 10306 * onCollectionDelete : _handlePhoneBookDelete, 10307 * onLoad : _handlePhoneBooksLoaded 10308 * }); 10309 * 10310 * _phoneBookCollection = _phoneBooks.getCollection(); 10311 * for (var phoneBookId in _phoneBookCollection) { 10312 * if (_phoneBookCollection.hasOwnProperty(phoneBookId)) { 10313 * _phoneBook = _phoneBookCollection[phoneBookId]; 10314 * etc... 10315 * } 10316 * } 10317 */ 10318 _fakeConstuctor: function () { 10319 /* This is here to hide the real init constructor from the public docs */ 10320 }, 10321 10322 /** 10323 * @private 10324 * 10325 * @param {Object} options 10326 * An object with the following properties:<ul> 10327 * <li><b>id:</b> The id of the object being constructed</li> 10328 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 10329 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 10330 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 10331 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 10332 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 10333 * <li><b>status:</b> {Number} The HTTP status code returned</li> 10334 * <li><b>content:</b> {String} Raw string of response</li> 10335 * <li><b>object:</b> {Object} Parsed object of response</li> 10336 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 10337 * <li><b>errorType:</b> {String} Type of error that was caught</li> 10338 * <li><b>errorMessage:</b> {String} Message associated with error</li> 10339 * </ul></li> 10340 * </ul></li> 10341 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 10342 **/ 10343 init: function (options) { 10344 // Keep the REST response for PhoneBooks to check for 206 Partial Content. 10345 this.keepRestResponse = true; 10346 // Add in the Range header which is required for PhoneBooks API. 10347 this.extraHeaders = { "Range": "objects=1-1500" }; 10348 this._super(options); 10349 }, 10350 10351 /** 10352 * @private 10353 * Gets the REST class for the current object - this is the PhoneBooks class. 10354 */ 10355 getRestClass: function () { 10356 return PhoneBooks; 10357 }, 10358 10359 /** 10360 * @private 10361 * Gets the REST class for the objects that make up the collection. - this 10362 * is the PhoneBook class. 10363 */ 10364 getRestItemClass: function () { 10365 return PhoneBook; 10366 }, 10367 10368 /** 10369 * @private 10370 * Gets the REST type for the current object - this is a "PhoneBooks". 10371 */ 10372 getRestType: function () { 10373 return "PhoneBooks"; 10374 }, 10375 10376 /** 10377 * @private 10378 * Gets the REST type for the objects that make up the collection - this is "PhoneBooks". 10379 */ 10380 getRestItemType: function () { 10381 return "PhoneBook"; 10382 }, 10383 10384 /** 10385 * @private 10386 * Override default to indicates that the collection supports making 10387 * requests. 10388 */ 10389 supportsRequests: true, 10390 10391 /** 10392 * @private 10393 * Override default to indicates that the collection subscribes to its objects. 10394 */ 10395 supportsRestItemSubscriptions: false, 10396 10397 /** 10398 * @private 10399 * Retrieve the PhoneBooks. This call will re-query the server and refresh the collection. 10400 * 10401 * @returns {finesse.restservices.PhoneBooks} 10402 * This PhoneBooks object, to allow cascading. 10403 */ 10404 get: function () { 10405 // set loaded to false so it will rebuild the collection after the get 10406 this._loaded = false; 10407 // reset collection 10408 this._collection = {}; 10409 // perform get 10410 this._synchronize(); 10411 return this; 10412 } 10413 10414 }); 10415 10416 window.finesse = window.finesse || {}; 10417 window.finesse.restservices = window.finesse.restservices || {}; 10418 window.finesse.restservices.PhoneBooks = PhoneBooks; 10419 10420 return PhoneBooks; 10421 }); 10422 10423 /** 10424 * JavaScript representation of the Finesse WorkflowAction object. 10425 * 10426 * @requires finesse.clientservices.ClientServices 10427 * @requires Class 10428 * @requires finesse.FinesseBase 10429 * @requires finesse.restservices.RestBase 10430 */ 10431 10432 /*jslint browser: true, nomen: true, sloppy: true, forin: true */ 10433 /*global define,finesse */ 10434 10435 /** @private */ 10436 define('restservices/WorkflowAction',['restservices/RestBase'], function (RestBase) { 10437 10438 var WorkflowAction = RestBase.extend({ 10439 10440 _contacts: null, 10441 10442 actionTypes: [ 10443 { 10444 name: 'BROWSER_POP', 10445 params: [ 10446 { 10447 name: 'windowName', 10448 type: 'text' 10449 }, 10450 { 10451 name: 'path', 10452 type: 'systemVariableSingleLineEditor' 10453 } 10454 ] 10455 }, 10456 { 10457 name: 'HTTP_REQUEST', 10458 params: [ 10459 { 10460 name: 'method', 10461 type: 'dropdown', 10462 values: ['POST', 'PUT'] 10463 }, 10464 { 10465 name: 'location', 10466 type: 'dropdown', 10467 values: ['FINESSE', 'OTHER'] 10468 }, 10469 { 10470 name: 'contentType', 10471 type: 'text' 10472 }, 10473 { 10474 name: 'path', 10475 type: 'systemVariableSingleLineEditor' 10476 }, 10477 { 10478 name: 'body', 10479 type: 'systemVariableMultiLineEditor' 10480 } 10481 ] 10482 } 10483 // more action type definitions here 10484 ], 10485 10486 /** 10487 * @class 10488 * A WorkflowAction is an action (e.g. Browser Pop, Rest Request) defined in a 10489 * Workflow and triggered by a system event (Call Received, Call Ended, etc.). 10490 * 10491 * @augments finesse.restservices.RestBase 10492 * @see finesse.restservices.Workflow 10493 * @constructs 10494 */ 10495 _fakeConstuctor: function () { 10496 /* This is here to hide the real init constructor from the public docs */ 10497 }, 10498 10499 /** 10500 * @private 10501 * JavaScript representation of a WorkflowAction object. Also exposes 10502 * methods to operate on the object against the server. 10503 * 10504 * @param {Object} options 10505 * An object with the following properties:<ul> 10506 * <li><b>id:</b> The id of the object being constructed</li> 10507 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 10508 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 10509 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 10510 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 10511 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 10512 * <li><b>status:</b> {Number} The HTTP status code returned</li> 10513 * <li><b>content:</b> {String} Raw string of response</li> 10514 * <li><b>object:</b> {Object} Parsed object of response</li> 10515 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 10516 * <li><b>errorType:</b> {String} Type of error that was caught</li> 10517 * <li><b>errorMessage:</b> {String} Message associated with error</li> 10518 * </ul></li> 10519 * </ul></li> 10520 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 10521 **/ 10522 init: function (options) { 10523 this._super(options); 10524 }, 10525 10526 /** 10527 * @private 10528 * Gets the REST class for the current object - this is the WorkflowAction class. 10529 * @returns {Object} The WorkflowAction class. 10530 */ 10531 getRestClass: function () { 10532 return finesse.restservices.WorkflowAction; 10533 }, 10534 10535 /** 10536 * @private 10537 * Gets the REST type for the current object - this is a "WorkflowAction". 10538 * @returns {String} The WorkflowAction string. 10539 */ 10540 getRestType: function () { 10541 return "WorkflowAction"; 10542 }, 10543 10544 /** 10545 * @private 10546 * Override default to indicate that this object doesn't support making 10547 * requests. 10548 */ 10549 supportsRequests: false, 10550 10551 /** 10552 * @private 10553 * Override default to indicate that this object doesn't support subscriptions. 10554 */ 10555 supportsSubscriptions: false, 10556 10557 /** 10558 * Getter for the name. 10559 * @returns {String} The name. 10560 */ 10561 getName: function () { 10562 this.isLoaded(); 10563 return this.getData().name; 10564 }, 10565 10566 /** 10567 * Getter for the type flag. 10568 * @returns {String} The type. 10569 */ 10570 getType: function () { 10571 this.isLoaded(); 10572 return this.getData().type; 10573 }, 10574 10575 /** 10576 * @private 10577 * Getter for the Uri value. 10578 * @returns {String} The Uri. 10579 */ 10580 getUri: function () { 10581 this.isLoaded(); 10582 return this.getData().uri; 10583 }, 10584 10585 /** 10586 * @private 10587 * Getter for the handledBy value. 10588 * @returns {String} handledBy. 10589 */ 10590 getHandledBy: function () { 10591 this.isLoaded(); 10592 return this.getData().handledBy; 10593 }, 10594 10595 /** 10596 * Getter for the parameters. 10597 * @returns {Object} key = param name, value = param value 10598 */ 10599 getParams: function () { 10600 var map = {}, 10601 params = this.getData().params.Param, 10602 i, 10603 param; 10604 10605 for(i=0; i<params.length; i+=1){ 10606 param = params[i]; 10607 map[param.name] = param.value || ""; 10608 } 10609 10610 return map; 10611 }, 10612 10613 /** 10614 * Getter for the ActionVariables 10615 * @returns {Object} key = action variable name, value = Object{name, type, node, testValue} 10616 */ 10617 getActionVariables: function() { 10618 var map = {}, 10619 actionVariablesParent = this.getData().actionVariables, 10620 actionVariables, 10621 i, 10622 actionVariable; 10623 10624 if (actionVariablesParent === null || typeof(actionVariablesParent) === "undefined" || actionVariablesParent.length === 0){ 10625 return map; 10626 } 10627 actionVariables = actionVariablesParent.ActionVariable; 10628 10629 if(actionVariables.length > 0){ 10630 for(i=0; i<actionVariables.length; i+=1){ 10631 actionVariable = actionVariables[i]; 10632 // escape nulls to empty string 10633 actionVariable.name = actionVariable.name || ""; 10634 actionVariable.type = actionVariable.type || ""; 10635 actionVariable.node = actionVariable.node || ""; 10636 actionVariable.testValue = actionVariable.testValue || ""; 10637 map[actionVariable.name] = actionVariable; 10638 } 10639 } else { 10640 map[actionVariables.name] = actionVariables; 10641 } 10642 10643 return map; 10644 }, 10645 10646 /** @private */ 10647 createPutSuccessHandler: function(action, contentBody, successHandler){ 10648 return function (rsp) { 10649 // Update internal structure based on response. Here we 10650 // inject the contentBody from the PUT request into the 10651 // rsp.object element to mimic a GET as a way to take 10652 // advantage of the existing _processResponse method. 10653 rsp.object = contentBody; 10654 action._processResponse(rsp); 10655 10656 //Remove the injected WorkflowAction object before cascading response 10657 rsp.object = {}; 10658 10659 //cascade response back to consumer's response handler 10660 successHandler(rsp); 10661 }; 10662 }, 10663 10664 /** @private */ 10665 createPostSuccessHandler: function (action, contentBody, successHandler) { 10666 return function (rsp) { 10667 rsp.object = contentBody; 10668 action._processResponse(rsp); 10669 10670 //Remove the injected WorkflowAction object before cascading response 10671 rsp.object = {}; 10672 10673 //cascade response back to consumer's response handler 10674 successHandler(rsp); 10675 }; 10676 }, 10677 10678 /** 10679 * @private 10680 * Build params array out of all the values coming into add or update methods 10681 * paramMap is a map of params.. we need to translate it into an array of Param objects 10682 * where path and windowName are params for the BROWSER_POP type 10683 */ 10684 buildParamsForRest: function(paramMap){ 10685 var params = {"Param": []}, 10686 i; 10687 for(i in paramMap){ 10688 if(paramMap.hasOwnProperty(i)){ 10689 params.Param.push({name: i, value: paramMap[i]}); 10690 } 10691 } 10692 return params; 10693 }, 10694 10695 /** 10696 * @private 10697 * Build actionVariables array out of all the values coming into add or update methods 10698 * actionVariableMap is a map of actionVariables.. we need to translate it into an array of ActionVariable objects 10699 * where path and windowName are params for the BROWSER_POP type 10700 */ 10701 buildActionVariablesForRest: function(actionVariableMap){ 10702 var actionVariables = {"ActionVariable": []}, 10703 i, 10704 actionVariable; 10705 for(i in actionVariableMap){ 10706 if(actionVariableMap.hasOwnProperty(i)){ 10707 // {name: "callVariable1", type: "SYSTEM", node: "", testValue: "<blink>"} 10708 actionVariable = { 10709 "name": actionVariableMap[i].name, 10710 "type": actionVariableMap[i].type, 10711 "node": actionVariableMap[i].node, 10712 "testValue": actionVariableMap[i].testValue 10713 }; 10714 actionVariables.ActionVariable.push(actionVariable); 10715 } 10716 } 10717 return actionVariables; 10718 }, 10719 10720 /** 10721 * Add 10722 */ 10723 add: function (newValues, handlers) { 10724 var contentBody = {}; 10725 10726 contentBody[this.getRestType()] = { 10727 "name": newValues.name, 10728 "type": newValues.type, 10729 "handledBy": newValues.handledBy, 10730 "params": this.buildParamsForRest(newValues.params), 10731 "actionVariables": this.buildActionVariablesForRest(newValues.actionVariables) 10732 }; 10733 10734 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 10735 handlers = handlers || {}; 10736 10737 this.restRequest(this.getRestUrl(), { 10738 method: 'POST', 10739 success: this.createPostSuccessHandler(this, contentBody, handlers.success), 10740 error: handlers.error, 10741 content: contentBody 10742 }); 10743 10744 return this; // Allow cascading 10745 }, 10746 10747 /** 10748 * @private 10749 * Update 10750 */ 10751 update: function (newValues, handlers) { 10752 this.isLoaded(); 10753 var contentBody = {}; 10754 10755 contentBody[this.getRestType()] = { 10756 "uri": this.getId(), 10757 "name": newValues.name, 10758 "type": newValues.type, 10759 "handledBy": newValues.handledBy, 10760 "params": this.buildParamsForRest(newValues.params), 10761 "actionVariables": this.buildActionVariablesForRest(newValues.actionVariables) 10762 }; 10763 10764 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 10765 handlers = handlers || {}; 10766 10767 this.restRequest(this.getRestUrl(), { 10768 method: 'PUT', 10769 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 10770 error: handlers.error, 10771 content: contentBody 10772 }); 10773 10774 return this; // Allow cascading 10775 }, 10776 10777 10778 /** 10779 * @private 10780 * Delete 10781 */ 10782 "delete": function ( handlers) { 10783 this.isLoaded(); 10784 10785 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 10786 handlers = handlers || {}; 10787 10788 this.restRequest(this.getRestUrl(), { 10789 method: 'DELETE', 10790 success: this.createPutSuccessHandler(this, {}, handlers.success), 10791 error: handlers.error, 10792 content: undefined 10793 }); 10794 10795 return this; // Allow cascading 10796 } 10797 10798 10799 10800 }); 10801 10802 window.finesse = window.finesse || {}; 10803 window.finesse.restservices = window.finesse.restservices || {}; 10804 window.finesse.restservices.WorkflowAction = WorkflowAction; 10805 10806 return WorkflowAction; 10807 }); 10808 10809 /** 10810 * JavaScript representation of the Finesse WorkflowActions collection 10811 * object which contains a list of WorkflowAction objects. 10812 * 10813 * @requires finesse.clientservices.ClientServices 10814 * @requires Class 10815 * @requires finesse.FinesseBase 10816 * @requires finesse.restservices.RestBase 10817 * @requires finesse.restservices.Dialog 10818 * @requires finesse.restservices.RestCollectionBase 10819 */ 10820 10821 /** @private */ 10822 define('restservices/WorkflowActions',[ 10823 'restservices/RestCollectionBase', 10824 'restservices/RestBase', 10825 'restservices/WorkflowAction' 10826 ], 10827 function (RestCollectionBase, RestBase, WorkflowAction) { 10828 10829 var WorkflowActions = RestCollectionBase.extend({ 10830 10831 /** 10832 * @class 10833 * JavaScript representation of a WorkflowActions collection object. 10834 * @augments finesse.restservices.RestCollectionBase 10835 * @constructs 10836 * @see finesse.restservices.WorkflowAction 10837 * @see finesse.restservices.Workflow 10838 * @see finesse.restservices.Workflows 10839 * @example 10840 * _workflowActions = _user.getWorkflowActions( { 10841 * onCollectionAdd : _handleWorkflowActionAdd, 10842 * onCollectionDelete : _handleWorkflowActionDelete, 10843 * onLoad : _handleWorkflowActionsLoaded 10844 * }); 10845 */ 10846 _fakeConstuctor: function () { 10847 /* This is here to hide the real init constructor from the public docs */ 10848 }, 10849 10850 /** 10851 * @private 10852 * JavaScript representation of a WorkflowActions collection object. Also exposes 10853 * methods to operate on the object against the server. 10854 * 10855 * @param {Object} options 10856 * An object with the following properties:<ul> 10857 * <li><b>id:</b> The id of the object being constructed</li> 10858 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 10859 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 10860 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 10861 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 10862 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 10863 * <li><b>status:</b> {Number} The HTTP status code returned</li> 10864 * <li><b>content:</b> {String} Raw string of response</li> 10865 * <li><b>object:</b> {Object} Parsed object of response</li> 10866 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 10867 * <li><b>errorType:</b> {String} Type of error that was caught</li> 10868 * <li><b>errorMessage:</b> {String} Message associated with error</li> 10869 * </ul></li> 10870 * </ul></li> 10871 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 10872 **/ 10873 init: function (options) { 10874 this._super(options); 10875 }, 10876 10877 /** 10878 * @private 10879 * Gets the REST class for the current object - this is the WorkflowActions class. 10880 */ 10881 getRestClass: function () { 10882 return WorkflowActions; 10883 }, 10884 10885 /** 10886 * @private 10887 * Gets the REST class for the objects that make up the collection. - this 10888 * is the WorkflowAction class. 10889 */ 10890 getRestItemClass: function () { 10891 return WorkflowAction; 10892 }, 10893 10894 /** 10895 * @private 10896 * Gets the REST type for the current object - this is a "WorkflowActions". 10897 */ 10898 getRestType: function () { 10899 return "WorkflowActions"; 10900 }, 10901 10902 /** 10903 * @private 10904 * Gets the REST type for the objects that make up the collection - this is "WorkflowActions". 10905 */ 10906 getRestItemType: function () { 10907 return "WorkflowAction"; 10908 }, 10909 10910 /** 10911 * @private 10912 * Override default to indicates that the collection supports making 10913 * requests. 10914 */ 10915 supportsRequests: true, 10916 10917 /** 10918 * @private 10919 * Override default to indicates that the collection subscribes to its objects. 10920 */ 10921 supportsRestItemSubscriptions: false, 10922 10923 /** 10924 * @private 10925 * Retrieve the WorkflowActions. 10926 * 10927 * @returns {finesse.restservices.WorkflowActions} 10928 * This WorkflowActions object to allow cascading. 10929 */ 10930 get: function () { 10931 // set loaded to false so it will rebuild the collection after the get 10932 this._loaded = false; 10933 // reset collection 10934 this._collection = {}; 10935 // perform get 10936 this._synchronize(); 10937 return this; 10938 } 10939 }); 10940 10941 window.finesse = window.finesse || {}; 10942 window.finesse.restservices = window.finesse.restservices || {}; 10943 window.finesse.restservices.WorkflowActions = WorkflowActions; 10944 10945 return WorkflowActions; 10946 }); 10947 10948 /** 10949 * JavaScript representation of the Finesse Workflow object. 10950 * 10951 * @requires finesse.clientservices.ClientServices 10952 * @requires Class 10953 * @requires finesse.FinesseBase 10954 * @requires finesse.restservices.RestBase 10955 */ 10956 10957 /*jslint browser: true, nomen: true, sloppy: true, forin: true */ 10958 /*global define,finesse */ 10959 10960 /** @private */ 10961 define('restservices/Workflow',[ 10962 'restservices/RestBase', 10963 'restservices/WorkflowActions' 10964 ], 10965 function (RestBase, WorkflowActions) { 10966 10967 var Workflow = RestBase.extend({ 10968 10969 /** 10970 * @class 10971 * JavaScript representation of a Workflow object. Also exposes 10972 * methods to operate on the object against the server. 10973 * 10974 * @param {Object} options 10975 * An object with the following properties:<ul> 10976 * <li><b>id:</b> The id of the object being constructed</li> 10977 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 10978 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 10979 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 10980 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 10981 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 10982 * <li><b>status:</b> {Number} The HTTP status code returned</li> 10983 * <li><b>content:</b> {String} Raw string of response</li> 10984 * <li><b>object:</b> {Object} Parsed object of response</li> 10985 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 10986 * <li><b>errorType:</b> {String} Type of error that was caught</li> 10987 * <li><b>errorMessage:</b> {String} Message associated with error</li> 10988 * </ul></li> 10989 * </ul></li> 10990 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 10991 * @constructs 10992 **/ 10993 init: function (options) { 10994 this._super(options); 10995 }, 10996 10997 /** 10998 * @private 10999 * Gets the REST class for the current object - this is the Workflow class. 11000 * @returns {Object} The Workflow class. 11001 */ 11002 getRestClass: function () { 11003 return Workflow; 11004 }, 11005 11006 /** 11007 * @private 11008 * Gets the REST type for the current object - this is a "Workflow". 11009 * @returns {String} The Workflow string. 11010 */ 11011 getRestType: function () { 11012 return "Workflow"; 11013 }, 11014 11015 /** 11016 * @private 11017 * Override default to indicate that this object doesn't support making 11018 * requests. 11019 */ 11020 supportsRequests: false, 11021 11022 /** 11023 * @private 11024 * Override default to indicate that this object doesn't support subscriptions. 11025 */ 11026 supportsSubscriptions: false, 11027 11028 /** 11029 * @private 11030 * Getter for the Uri value. 11031 * @returns {String} The Uri. 11032 */ 11033 getUri: function () { 11034 this.isLoaded(); 11035 return this.getData().uri; 11036 }, 11037 11038 /** 11039 * Getter for the name. 11040 * @returns {String} The name. 11041 */ 11042 getName: function () { 11043 this.isLoaded(); 11044 return this.getData().name; 11045 }, 11046 11047 /** 11048 * Getter for the description. 11049 * @returns {String} The description. 11050 */ 11051 getDescription: function () { 11052 this.isLoaded(); 11053 return this.getData().description; 11054 }, 11055 11056 /** 11057 * Getter for the trigger set. 11058 * @returns {String} The trigger set. 11059 */ 11060 getTriggerSet: function () { 11061 this.isLoaded(); 11062 return this.getData().TriggerSet; 11063 }, 11064 11065 /** 11066 * Getter for the condition set. 11067 * @returns {String} The condition set. 11068 */ 11069 getConditionSet: function () { 11070 this.isLoaded(); 11071 return this.getData().ConditionSet; 11072 }, 11073 11074 /** 11075 * Getter for the assigned workflowActions. 11076 * @returns {String} The workflowActions object. 11077 */ 11078 getWorkflowActions: function () { 11079 this.isLoaded(); 11080 var workflowActions = this.getData().workflowActions; 11081 if (workflowActions === null) { 11082 workflowActions = ""; 11083 } 11084 return workflowActions; 11085 }, 11086 11087 createPutSuccessHandler: function (workflow, contentBody, successHandler) { 11088 return function (rsp) { 11089 // Update internal structure based on response. Here we 11090 // inject the contentBody from the PUT request into the 11091 // rsp.object element to mimic a GET as a way to take 11092 // advantage of the existing _processResponse method. 11093 rsp.object = contentBody; 11094 workflow._processResponse(rsp); 11095 11096 //Remove the injected Workflow object before cascading response 11097 rsp.object = {}; 11098 11099 //cascade response back to consumer's response handler 11100 successHandler(rsp); 11101 }; 11102 }, 11103 11104 createPostSuccessHandler: function (workflow, contentBody, successHandler) { 11105 return function (rsp) { 11106 rsp.object = contentBody; 11107 workflow._processResponse(rsp); 11108 11109 //Remove the injected Workflow object before cascading response 11110 rsp.object = {}; 11111 11112 //cascade response back to consumer's response handler 11113 successHandler(rsp); 11114 }; 11115 }, 11116 11117 /** 11118 * @private 11119 * Add 11120 */ 11121 add: function (newValues, handlers) { 11122 // this.isLoaded(); 11123 var contentBody = {}; 11124 11125 contentBody[this.getRestType()] = { 11126 "name": newValues.name, 11127 "description": newValues.description, 11128 "TriggerSet" : newValues.TriggerSet, 11129 "ConditionSet" : newValues.ConditionSet, 11130 "workflowActions" : newValues.workflowActions 11131 }; 11132 11133 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 11134 handlers = handlers || {}; 11135 11136 this.restRequest(this.getRestUrl(), { 11137 method: 'POST', 11138 success: this.createPostSuccessHandler(this, contentBody, handlers.success), 11139 error: handlers.error, 11140 content: contentBody 11141 }); 11142 11143 return this; // Allow cascading 11144 }, 11145 11146 /** 11147 * @private 11148 * Update 11149 */ 11150 update: function (newValues, handlers) { 11151 this.isLoaded(); 11152 var contentBody = {}; 11153 11154 contentBody[this.getRestType()] = { 11155 "uri": this.getId(), 11156 "name": newValues.name, 11157 "description": newValues.description, 11158 "TriggerSet" : newValues.TriggerSet, 11159 "ConditionSet" : newValues.ConditionSet, 11160 "workflowActions" : newValues.workflowActions 11161 }; 11162 11163 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 11164 handlers = handlers || {}; 11165 11166 this.restRequest(this.getRestUrl(), { 11167 method: 'PUT', 11168 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 11169 error: handlers.error, 11170 content: contentBody 11171 }); 11172 11173 return this; // Allow cascading 11174 }, 11175 11176 11177 /** 11178 * @private 11179 * Delete 11180 */ 11181 "delete": function (handlers) { 11182 this.isLoaded(); 11183 11184 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 11185 handlers = handlers || {}; 11186 11187 this.restRequest(this.getRestUrl(), { 11188 method: 'DELETE', 11189 success: this.createPutSuccessHandler(this, {}, handlers.success), 11190 error: handlers.error, 11191 content: undefined 11192 }); 11193 11194 return this; // Allow cascading 11195 } 11196 11197 11198 11199 }); 11200 11201 window.finesse = window.finesse || {}; 11202 window.finesse.restservices = window.finesse.restservices || {}; 11203 window.finesse.restservices.Workflow = Workflow; 11204 11205 return Workflow; 11206 }); 11207 11208 /** 11209 * JavaScript representation of the Finesse workflows collection 11210 * object which contains a list of workflow objects. 11211 * 11212 * @requires finesse.clientservices.ClientServices 11213 * @requires Class 11214 * @requires finesse.FinesseBase 11215 * @requires finesse.restservices.RestBase 11216 * @requires finesse.restservices.Dialog 11217 * @requires finesse.restservices.RestCollectionBase 11218 */ 11219 11220 /** @private */ 11221 define('restservices/Workflows',[ 11222 'restservices/RestCollectionBase', 11223 'restservices/RestBase', 11224 'restservices/Workflow' 11225 ], 11226 function (RestCollectionBase, RestBase, Workflow) { 11227 11228 var Workflows = RestCollectionBase.extend({ 11229 11230 /** 11231 * @class 11232 * JavaScript representation of a workflows collection object. Also exposes 11233 * methods to operate on the object against the server. 11234 * 11235 * @param {Object} options 11236 * An object with the following properties:<ul> 11237 * <li><b>id:</b> The id of the object being constructed</li> 11238 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 11239 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 11240 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 11241 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 11242 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 11243 * <li><b>status:</b> {Number} The HTTP status code returned</li> 11244 * <li><b>content:</b> {String} Raw string of response</li> 11245 * <li><b>object:</b> {Object} Parsed object of response</li> 11246 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 11247 * <li><b>errorType:</b> {String} Type of error that was caught</li> 11248 * <li><b>errorMessage:</b> {String} Message associated with error</li> 11249 * </ul></li> 11250 * </ul></li> 11251 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 11252 * @constructs 11253 **/ 11254 init: function (options) { 11255 this._super(options); 11256 }, 11257 11258 /** 11259 * @private 11260 * Gets the REST class for the current object - this is the workflows class. 11261 */ 11262 getRestClass: function () { 11263 return Workflows; 11264 }, 11265 11266 /** 11267 * @private 11268 * Gets the REST class for the objects that make up the collection. - this 11269 * is the workflow class. 11270 */ 11271 getRestItemClass: function () { 11272 return Workflow; 11273 }, 11274 11275 /** 11276 * @private 11277 * Gets the REST type for the current object - this is a "workflows". 11278 */ 11279 getRestType: function () { 11280 return "Workflows"; 11281 }, 11282 11283 /** 11284 * @private 11285 * Gets the REST type for the objects that make up the collection - this is "workflows". 11286 */ 11287 getRestItemType: function () { 11288 return "Workflow"; 11289 }, 11290 11291 /** 11292 * @private 11293 * Override default to indicates that the collection supports making requests. 11294 */ 11295 supportsRequests: true, 11296 11297 /** 11298 * @private 11299 * Override default to indicates that the collection does not subscribe to its objects. 11300 */ 11301 supportsRestItemSubscriptions: false, 11302 11303 /** 11304 * @private 11305 * Retrieve the workflows. This call will re-query the server and refresh the collection. 11306 * 11307 * @returns {finesse.restservices.workflows} 11308 * This workflows object to allow cascading. 11309 */ 11310 get: function () { 11311 // set loaded to false so it will rebuild the collection after the get 11312 this._loaded = false; 11313 // reset collection 11314 this._collection = {}; 11315 // perform get 11316 this._synchronize(); 11317 return this; 11318 } 11319 }); 11320 11321 window.finesse = window.finesse || {}; 11322 window.finesse.restservices = window.finesse.restservices || {}; 11323 window.finesse.restservices.Workflows = Workflows; 11324 11325 return Workflows; 11326 }); 11327 11328 /** 11329 * JavaScript representation of the Finesse MediaPropertiesLayout object for the Admin webapp. 11330 * @requires finesse.clientservices.ClientServices 11331 * @requires Class 11332 * @requires finesse.FinesseBase 11333 * @requires finesse.restservices.RestBase 11334 */ 11335 11336 /** The following comment is to prevent jslint errors about 11337 * using variables before they are defined. 11338 */ 11339 /*global finesse*/ 11340 11341 /** 11342 * @class 11343 * JavaScript representation of a MediaPropertiesLayout object for the Admin webapp. Also exposes 11344 * methods to operate on the object against the server. 11345 * 11346 * @constructor 11347 * @param {String} id 11348 * Not required... 11349 * @param {Object} callbacks 11350 * An object containing callbacks for instantiation and runtime 11351 * @param {Function} callbacks.onLoad(this) 11352 * Callback to invoke upon successful instantiation, passes in MediaPropertiesLayout object 11353 * @param {Function} callbacks.onLoadError(rsp) 11354 * Callback to invoke on instantiation REST request error 11355 * as passed by finesse.clientservices.ClientServices.ajax() 11356 * { 11357 * status: {Number} The HTTP status code returned 11358 * content: {String} Raw string of response 11359 * object: {Object} Parsed object of response 11360 * error: {Object} Wrapped exception that was caught 11361 * error.errorType: {String} Type of error that was caught 11362 * error.errorMessage: {String} Message associated with error 11363 * } 11364 * @param {Function} callbacks.onChange(this) 11365 * Callback to invoke upon successful update, passes in MediaPropertiesLayout object 11366 * @param {Function} callbacks.onError(rsp) 11367 * Callback to invoke on update error (refresh or event) 11368 * as passed by finesse.clientservices.ClientServices.ajax() 11369 * { 11370 * status: {Number} The HTTP status code returned 11371 * content: {String} Raw string of response 11372 * object: {Object} Parsed object of response 11373 * error: {Object} Wrapped exception that was caught 11374 * error.errorType: {String} Type of error that was caught 11375 * error.errorMessage: {String} Message associated with error 11376 * } 11377 */ 11378 11379 /** @private */ 11380 define('restservices/MediaPropertiesLayout',['restservices/RestBase'], function (RestBase) { 11381 var MediaPropertiesLayout = RestBase.extend(/** @lends finesse.restservices.MediaPropertiesLayout.prototype */{ 11382 11383 /** 11384 * @class 11385 * The MediaPropertiesLayout handles which call variables are associated with Dialogs. 11386 * 11387 * @augments finesse.restservices.RestBase 11388 * @see finesse.restservices.Dialog#getMediaProperties 11389 * @see finesse.restservices.User#getMediaPropertiesLayout 11390 * @constructs 11391 */ 11392 _fakeConstuctor: function () { 11393 /* This is here to hide the real init constructor from the public docs */ 11394 }, 11395 11396 /** 11397 * @private 11398 * JavaScript representation of a MediaPropertiesLayout object. Also exposes 11399 * methods to operate on the object against the server. 11400 * 11401 * @param {Object} options 11402 * An object with the following properties:<ul> 11403 * <li><b>id:</b> The id of the object being constructed</li> 11404 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 11405 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 11406 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 11407 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 11408 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 11409 * <li><b>status:</b> {Number} The HTTP status code returned</li> 11410 * <li><b>content:</b> {String} Raw string of response</li> 11411 * <li><b>object:</b> {Object} Parsed object of response</li> 11412 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 11413 * <li><b>errorType:</b> {String} Type of error that was caught</li> 11414 * <li><b>errorMessage:</b> {String} Message associated with error</li> 11415 * </ul></li> 11416 * </ul></li> 11417 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 11418 **/ 11419 init: function (options) { 11420 this._super(options); 11421 }, 11422 11423 /** 11424 * @private 11425 * Gets the REST class for the current object - this is the MediaPropertiesLayout object. 11426 */ 11427 getRestClass: function () { 11428 return MediaPropertiesLayout; 11429 }, 11430 11431 /** 11432 * @private 11433 * Gets the REST type for the current object - this is a "MediaPropertiesLayout". 11434 */ 11435 getRestType: function () { 11436 return "MediaPropertiesLayout"; 11437 }, 11438 11439 /** 11440 * @private 11441 * Returns whether this object supports subscriptions 11442 */ 11443 supportsSubscriptions: false, 11444 11445 /** 11446 * Getter for the name. 11447 * @returns {String} The name. 11448 */ 11449 getName: function () { 11450 this.isLoaded(); 11451 return this._data.name; 11452 }, 11453 11454 /** 11455 * Getter for the description. 11456 * @returns {String} The description. 11457 */ 11458 getDescription: function () { 11459 this.isLoaded(); 11460 return this._data.description || ""; 11461 }, 11462 11463 /** 11464 * Getter for the layout type (should be DEFAULT or CUSTOM). 11465 * @returns {String} The layout type. 11466 */ 11467 getType: function () { 11468 this.isLoaded(); 11469 return this._data.type || ""; 11470 }, 11471 11472 /** 11473 * Retrieve the media properties layout. This call will re-query the server and refresh the layout object. 11474 * @returns {finesse.restservices.MediaPropertiesLayout} 11475 * This MediaPropertiesLayout object to allow cascading 11476 */ 11477 get: function () { 11478 this._synchronize(); 11479 11480 return this; //Allow cascading 11481 }, 11482 11483 /** 11484 * Gets the data for this object. 11485 * 11486 * Performs safe conversion from raw API data to ensure that the returned layout object 11487 * always has a header with correct entry fields, and exactly two columns with lists of entries. 11488 * 11489 * @returns {finesse.restservices.MediaPropertiesLayout.Object} Data in columns (unless only one defined). 11490 */ 11491 getData: function () { 11492 11493 var layout = this._data, result, _addColumnData; 11494 11495 result = this.getEmptyData(); 11496 result.name = layout.name; 11497 result.description = layout.description; 11498 result.type = layout.type; 11499 11500 /** 11501 * @private 11502 */ 11503 _addColumnData = function (entryData, colIndex) { 11504 11505 if (!entryData) { 11506 //If there's no entry data at all, rewrite entryData to be an empty collection of entries 11507 entryData = {}; 11508 } else if (entryData.mediaProperty) { 11509 //If entryData contains the keys for a single entry rather than being a collection of entries, 11510 //rewrite it to be a collection containing a single entry 11511 entryData = { "": entryData }; 11512 } 11513 11514 //Add each of the entries in the list to the column 11515 jQuery.each(entryData, function (i, entryData) { 11516 11517 //If the entry has no displayName specified, explicitly set it to the empty string 11518 if (!entryData.displayName) { 11519 entryData.displayName = ""; 11520 } 11521 11522 result.columns[colIndex].push(entryData); 11523 11524 }); 11525 11526 }; 11527 11528 //The header should only contain a single entry 11529 if (layout.header && layout.header.entry) { 11530 11531 //If the entry has no displayName specified, explicitly set it to the empty string 11532 if (!layout.header.entry.displayName) { 11533 layout.header.entry.displayName = ""; 11534 } 11535 11536 result.header = layout.header.entry; 11537 11538 } else { 11539 11540 throw "MediaPropertiesLayout.getData() - Header does not contain an entry"; 11541 11542 } 11543 11544 //If the column object contains an entry object that wasn't part of a list of entries, 11545 //it must be a single right-hand entry object (left-hand entry object would be part of a list.) 11546 //Force the entry object to be the 2nd element in an otherwise-empty list. 11547 if (layout.column && layout.column.entry) { 11548 layout.column = [ 11549 null, 11550 { "entry": layout.column.entry } 11551 ]; 11552 } 11553 11554 if (layout.column && layout.column.length > 0 && layout.column.length <= 2) { 11555 11556 //Render left column entries 11557 if (layout.column[0] && layout.column[0].entry) { 11558 _addColumnData(layout.column[0].entry, 0); 11559 } 11560 11561 //Render right column entries 11562 if (layout.column[1] && layout.column[1].entry) { 11563 _addColumnData(layout.column[1].entry, 1); 11564 } 11565 11566 } 11567 11568 return result; 11569 11570 }, 11571 11572 /** 11573 * @private 11574 * Empty/template version of getData(). 11575 * 11576 * Used by getData(), and by callers of getData() in error cases. 11577 */ 11578 getEmptyData: function () { 11579 11580 return { 11581 header : { 11582 displayName: null, 11583 mediaProperty: null 11584 }, 11585 columns : [[], []] 11586 }; 11587 11588 }, 11589 11590 /** 11591 * Update the layout of this MediaPropertiesLayout 11592 * @param {Object} layout 11593 * The object representation of the layout you are setting 11594 * @param {finesse.interfaces.RequestHandlers} handlers 11595 * An object containing the handlers for the request 11596 * @returns {finesse.restservices.MediaPropertiesLayout} 11597 * This MediaPropertiesLayout object to allow cascading 11598 * @private 11599 */ 11600 update: function (newLayoutObject, handlers) { 11601 var contentBody = {}; 11602 11603 // Make sure type is kept the same 11604 newLayoutObject.type = this.getType(); 11605 11606 contentBody[this.getRestType()] = newLayoutObject; 11607 11608 //Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 11609 handlers = handlers || {}; 11610 11611 this.restRequest(this.getRestUrl(), { 11612 method: 'PUT', 11613 success: handlers.success, 11614 error: handlers.error, 11615 content: contentBody 11616 }); 11617 11618 return this; // Allow cascading 11619 }, 11620 11621 /** 11622 * Create a new MediaPropertiesLayout object with the layout passed in 11623 * @param {Object} layout 11624 * The object representation of the layout you are creating 11625 * @param {finesse.interfaces.RequestHandlers} handlers 11626 * An object containing the handlers for the request 11627 * @returns {finesse.restservices.MediaPropertiesLayout} 11628 * This MediaPropertiesLayout object to allow cascading 11629 * @private 11630 */ 11631 add: function (layout, handlers) { 11632 var contentBody = {}; 11633 11634 contentBody[this.getRestType()] = layout; 11635 11636 handlers = handlers || {}; 11637 11638 this.restRequest(this.getRestUrl(), { 11639 method: 'POST', 11640 success: handlers.success, 11641 error: handlers.error, 11642 content: contentBody 11643 }); 11644 11645 return this; // Allow cascading 11646 }, 11647 11648 /** 11649 * Delete this MediaPropertiesLayout 11650 * @param {finesse.interfaces.RequestHandlers} handlers 11651 * An object containing the handlers for the request 11652 * @returns {finesse.restservices.MediaPropertiesLayout} 11653 * This MediaPropertiesLayout object to allow cascading 11654 * @private 11655 */ 11656 "delete": function (handlers) { 11657 handlers = handlers || {}; 11658 11659 this.restRequest(this.getRestUrl(), { 11660 method: 'DELETE', 11661 success: handlers.success, 11662 error: handlers.error, 11663 content: undefined 11664 }); 11665 11666 return this; // Allow cascading 11667 } 11668 11669 }); 11670 11671 MediaPropertiesLayout.Object = /** @lends finesse.restservices.MediaPropertiesLayout.Object.prototype */ { 11672 /** 11673 * @class Format of MediaPropertiesLayout Object.<br> 11674 * Object { <ul> 11675 * <li>header : { <ul> 11676 * <li>dispayName {String} 11677 * <li>mediaProperty {String}</ul>} 11678 * <li>columns : { <ul> 11679 * <li>[ [] , [] ] 11680 * </ul> 11681 * where column arrays consists of the same Object format as header.<br> 11682 * }</ul> 11683 * }<br> 11684 * @constructs 11685 */ 11686 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 11687 11688 }; 11689 11690 window.finesse = window.finesse || {}; 11691 window.finesse.restservices = window.finesse.restservices || {}; 11692 window.finesse.restservices.MediaPropertiesLayout = MediaPropertiesLayout; 11693 11694 return MediaPropertiesLayout; 11695 }); 11696 11697 /** 11698 * JavaScript representation of the Finesse MediaPropertiesLayout object for a User 11699 * 11700 * @requires MediaPropertiesLayout 11701 * @requires ClientServices 11702 * @requires finesse.FinesseBase 11703 * @requires finesse.restservices.RestBase 11704 */ 11705 11706 /** The following comment is to prevent jslint errors about 11707 * using variables before they are defined. 11708 */ 11709 /*global finesse*/ 11710 11711 /** @private */ 11712 define('restservices/UserMediaPropertiesLayout',['restservices/MediaPropertiesLayout'], function (MediaPropertiesLayout) { 11713 var UserMediaPropertiesLayout = MediaPropertiesLayout.extend(/** @lends finesse.restservices.UserMediaPropertiesLayout.prototype */{ 11714 11715 /** 11716 * @class 11717 * JavaScript representation of a UserMediaPropertiesLayout collection object. Also exposes 11718 * methods to operate on the object against the server. 11719 * 11720 * @param {Object} options 11721 * An object with the following properties:<ul> 11722 * <li><b>id:</b> The id of the object being constructed</li> 11723 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 11724 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 11725 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 11726 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 11727 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 11728 * <li><b>status:</b> {Number} The HTTP status code returned</li> 11729 * <li><b>content:</b> {String} Raw string of response</li> 11730 * <li><b>object:</b> {Object} Parsed object of response</li> 11731 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 11732 * <li><b>errorType:</b> {String} Type of error that was caught</li> 11733 * <li><b>errorMessage:</b> {String} Message associated with error</li> 11734 * </ul></li> 11735 * </ul></li> 11736 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 11737 * @constructs 11738 **/ 11739 init: function (options) { 11740 this._super(options); 11741 }, 11742 11743 /** 11744 * @private 11745 * Gets the REST class for the current object - this is the UserMediaPropertiesLayout class. 11746 */ 11747 getRestClass: function () { 11748 return UserMediaPropertiesLayout; 11749 }, 11750 11751 /** 11752 * Overrides the parent class. Returns the url for the UserMediaPropertiesLayout resource 11753 */ 11754 getRestUrl: function () { 11755 return ("/finesse/api/User/" + this.getId() + "/" + this.getRestType()); 11756 }, 11757 11758 /** 11759 * @private 11760 * Override to throw an error because we cannot do an update on the User's 11761 * MediaPropertiesLayout node 11762 */ 11763 update: function (layout, handlers) { 11764 throw new Error("update(): Cannot update layout for User's MediaPropertiesLayout"); 11765 }, 11766 11767 /** 11768 * @private 11769 * Override to throw an error because we cannot create a new layout on the User's 11770 * MediaPropertiesLayout node 11771 */ 11772 add: function (layout, handlers) { 11773 throw new Error("add(): Cannot create a new layout for User's MediaPropertiesLayout"); 11774 }, 11775 11776 /** 11777 * @private 11778 * Override to throw an error because we cannot delete the layout on the User's 11779 * MediaPropertiesLayout node 11780 */ 11781 "delete": function (layout, handlers) { 11782 throw new Error("delete(): Cannot delete the layout for User's MediaPropertiesLayout"); 11783 } 11784 11785 }); 11786 11787 window.finesse = window.finesse || {}; 11788 window.finesse.restservices = window.finesse.restservices || {}; 11789 window.finesse.restservices.UserMediaPropertiesLayout = UserMediaPropertiesLayout; 11790 11791 return UserMediaPropertiesLayout; 11792 }); 11793 11794 /** 11795 * JavaScript representation of the Finesse mediaPropertiesLayouts collection 11796 * object which contains a list of mediaPropertiesLayout objects. 11797 * 11798 * @requires finesse.clientservices.ClientServices 11799 * @requires Class 11800 * @requires finesse.FinesseBase 11801 * @requires finesse.restservices.RestBase 11802 * @requires finesse.restservices.Dialog 11803 * @requires finesse.restservices.RestCollectionBase 11804 */ 11805 11806 /** @private */ 11807 define('restservices/MediaPropertiesLayouts',[ 11808 'restservices/RestCollectionBase', 11809 'restservices/RestBase', 11810 'restservices/MediaPropertiesLayout' 11811 ], 11812 function (RestCollectionBase, RestBase, MediaPropertiesLayout) { 11813 11814 var MediaPropertiesLayouts = RestCollectionBase.extend({ 11815 11816 /** 11817 * @class 11818 * JavaScript representation of a mediaPropertiesLayouts collection object. Also exposes 11819 * methods to operate on the object against the server. 11820 * 11821 * @param {Object} options 11822 * An object with the following properties:<ul> 11823 * <li><b>id:</b> The id of the object being constructed</li> 11824 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 11825 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 11826 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 11827 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 11828 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 11829 * <li><b>status:</b> {Number} The HTTP status code returned</li> 11830 * <li><b>content:</b> {String} Raw string of response</li> 11831 * <li><b>object:</b> {Object} Parsed object of response</li> 11832 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 11833 * <li><b>errorType:</b> {String} Type of error that was caught</li> 11834 * <li><b>errorMessage:</b> {String} Message associated with error</li> 11835 * </ul></li> 11836 * </ul></li> 11837 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 11838 * @constructs 11839 **/ 11840 init: function (options) { 11841 this._super(options); 11842 }, 11843 11844 /** 11845 * @private 11846 * Gets the REST class for the current object - this is the mediaPropertiesLayouts class. 11847 */ 11848 getRestClass: function () { 11849 return MediaPropertiesLayouts; 11850 }, 11851 11852 /** 11853 * @private 11854 * Gets the REST class for the objects that make up the collection. - this 11855 * is the mediaPropertiesLayout class. 11856 */ 11857 getRestItemClass: function () { 11858 return MediaPropertiesLayout; 11859 }, 11860 11861 /** 11862 * @private 11863 * Gets the REST type for the current object - this is a "mediaPropertiesLayouts". 11864 */ 11865 getRestType: function () { 11866 return "MediaPropertiesLayouts"; 11867 }, 11868 11869 /** 11870 * @private 11871 * Gets the REST type for the objects that make up the collection - this is "mediaPropertiesLayouts". 11872 */ 11873 getRestItemType: function () { 11874 return "MediaPropertiesLayout"; 11875 }, 11876 11877 /** 11878 * @private 11879 * Override default to indicates that the collection supports making requests. 11880 */ 11881 supportsRequests: true, 11882 11883 /** 11884 * @private 11885 * Override default to indicates that the collection does not subscribe to its objects. 11886 */ 11887 supportsRestItemSubscriptions: false, 11888 11889 /** 11890 * @private 11891 * Retrieve the MediaPropertiesLayouts. This call will re-query the server and refresh the collection. 11892 * 11893 * @returns {finesse.restservices.MediaPropertiesLayouts} 11894 * This MediaPropertiesLayouts object to allow cascading. 11895 */ 11896 get: function () { 11897 // set loaded to false so it will rebuild the collection after the get 11898 this._loaded = false; 11899 // reset collection 11900 this._collection = {}; 11901 // perform get 11902 this._synchronize(); 11903 return this; 11904 } 11905 }); 11906 11907 window.finesse = window.finesse || {}; 11908 window.finesse.restservices = window.finesse.restservices || {}; 11909 window.finesse.restservices.MediaPropertiesLayouts = MediaPropertiesLayouts; 11910 11911 return MediaPropertiesLayouts; 11912 }); 11913 11914 /** 11915 * JavaScript representation of the Finesse MediaPropertiesLayout object for a User 11916 * 11917 * @requires MediaPropertiesLayout 11918 * @requires ClientServices 11919 * @requires finesse.FinesseBase 11920 * @requires finesse.restservices.RestBase 11921 */ 11922 11923 /** The following comment is to prevent jslint errors about 11924 * using variables before they are defined. 11925 */ 11926 /*global finesse*/ 11927 11928 /** @private */ 11929 define('restservices/UserMediaPropertiesLayouts',[ 11930 'restservices/MediaPropertiesLayouts', 11931 'restservices/UserMediaPropertiesLayout' 11932 ], 11933 function (MediaPropertiesLayouts, UserMediaPropertiesLayout) { 11934 var UserMediaPropertiesLayouts = MediaPropertiesLayouts.extend(/** @lends finesse.restservices.UserMediaPropertiesLayouts.prototype */{ 11935 11936 /** 11937 * @class 11938 * JavaScript representation of a UserMediaPropertiesLayouts collection object. Also exposes 11939 * methods to operate on the object against the server. 11940 * 11941 * @param {Object} options 11942 * An object with the following properties:<ul> 11943 * <li><b>id:</b> The id of the object being constructed</li> 11944 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 11945 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 11946 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 11947 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 11948 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 11949 * <li><b>status:</b> {Number} The HTTP status code returned</li> 11950 * <li><b>content:</b> {String} Raw string of response</li> 11951 * <li><b>object:</b> {Object} Parsed object of response</li> 11952 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 11953 * <li><b>errorType:</b> {String} Type of error that was caught</li> 11954 * <li><b>errorMessage:</b> {String} Message associated with error</li> 11955 * </ul></li> 11956 * </ul></li> 11957 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 11958 * @constructs 11959 **/ 11960 init: function (options) { 11961 this._super(options); 11962 }, 11963 11964 /** 11965 * @private 11966 * Gets the REST class for the current object - this is the UserMediaPropertiesLayouts class. 11967 */ 11968 getRestClass: function () { 11969 return UserMediaPropertiesLayouts; 11970 }, 11971 11972 /** 11973 * @private 11974 * Gets the REST class for the objects that make up the collection. - this 11975 * is the UserMediaPropertiesLayout class. 11976 */ 11977 getRestItemClass: function() { 11978 return UserMediaPropertiesLayout; 11979 } 11980 }); 11981 11982 window.finesse = window.finesse || {}; 11983 window.finesse.restservices = window.finesse.restservices || {}; 11984 window.finesse.restservices.UserMediaPropertiesLayouts = UserMediaPropertiesLayouts; 11985 11986 return UserMediaPropertiesLayouts; 11987 }); 11988 11989 /** 11990 * JavaScript representation of the Finesse Dialog object for non-voice media. 11991 * 11992 * @requires finesse.restservices.DialogBase 11993 */ 11994 11995 /** @private */ 11996 define('restservices/MediaDialog',[ 11997 'restservices/DialogBase' 11998 ], 11999 function (DialogBase) { 12000 var MediaDialog = DialogBase.extend(/** @lends finesse.restservices.MediaDialog.prototype */{ 12001 12002 /** 12003 * @private 12004 * 12005 * Support requests so that applications can refresh non-voice dialogs when the media channel that the 12006 * dialog belongs to is interrupted. An event is not sent to update a dialog's actions when the media is 12007 * interrupted so a refresh is required so that the application can get an updated set of actions. 12008 */ 12009 supportsRequests: true, 12010 12011 /** 12012 * @class 12013 * A MediaDialog is an attempted connection between or among multiple participants, 12014 * for example, a chat or email. 12015 * 12016 * @augments finesse.restservices.DialogBase 12017 * @constructs 12018 */ 12019 _fakeConstuctor: function () { 12020 /* This is here to hide the real init constructor from the public docs */ 12021 }, 12022 12023 /** 12024 * @private 12025 * 12026 * @param {Object} options 12027 * An object with the following properties:<ul> 12028 * <li><b>id:</b> The id of the object being constructed</li> 12029 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 12030 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 12031 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 12032 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 12033 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 12034 * <li><b>status:</b> {Number} The HTTP status code returned</li> 12035 * <li><b>content:</b> {String} Raw string of response</li> 12036 * <li><b>object:</b> {Object} Parsed object of response</li> 12037 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 12038 * <li><b>errorType:</b> {String} Type of error that was caught</li> 12039 * <li><b>errorMessage:</b> {String} Message associated with error</li> 12040 * </ul></li> 12041 * </ul></li> 12042 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 12043 **/ 12044 init: function (options) { 12045 this._super(options); 12046 }, 12047 12048 /** 12049 * @private 12050 * Gets the REST class for the current object - this is the MediaDialog class. 12051 * @returns {Object} The Dialog class. 12052 */ 12053 getRestClass: function () { 12054 return MediaDialog; 12055 }, 12056 12057 /** 12058 * Transfers a Media Dialog to the target specified 12059 * @param {String} target script selector 12060 * The script selector to transfer the dialog. 12061 * @param {finesse.interfaces.RequestHandlers} handlers 12062 * An object containing the handlers for the request 12063 */ 12064 transfer: function(target, handlers) { 12065 this.setTaskState(MediaDialog.TaskActions.TRANSFER, handlers, target); 12066 }, 12067 12068 /** 12069 * Set the state on a Media Dialog based on the action given. 12070 * @param {finesse.restservices.MediaDialog.TaskActions} action 12071 * The action string indicating the action to invoke on a Media dialog. 12072 * @param {finesse.interfaces.RequestHandlers} handlers 12073 * An object containing the handlers for the request 12074 * @param {String} target 12075 * The target to transfer the dialog. Pass null if not transfer 12076 */ 12077 setTaskState: function (state,handlers,target) { 12078 this.isLoaded(); 12079 12080 var contentBody = {}; 12081 contentBody[this.getRestType()] = { 12082 "requestedAction": state, 12083 "target": target 12084 }; 12085 // (nonexistent) keys to be read as undefined 12086 handlers = handlers || {}; 12087 this.restRequest(this.getRestUrl(), { 12088 method: 'PUT', 12089 success: handlers.success, 12090 error: handlers.error, 12091 content: contentBody 12092 }); 12093 return this; // Allow cascading 12094 } 12095 12096 }); 12097 12098 MediaDialog.TaskActions = /** @lends finesse.restservices.MediaDialog.TaskActions.prototype */ { 12099 /** 12100 * Accept an incoming task. 12101 */ 12102 ACCEPT: "ACCEPT", 12103 /** 12104 * Start work on a task. 12105 */ 12106 START : "START", 12107 /** 12108 * Pause work on an active task. 12109 */ 12110 PAUSE: "PAUSE", 12111 /** 12112 * Resume work on a paused task. 12113 */ 12114 RESUME : "RESUME", 12115 /** 12116 * Wrap up work for a task. 12117 */ 12118 WRAP_UP : "WRAP_UP", 12119 /** 12120 * Transfer task to another target. 12121 */ 12122 TRANSFER : "TRANSFER", 12123 /** 12124 * End a task. 12125 */ 12126 CLOSE : "CLOSE", 12127 /** 12128 * @class Set of action constants for a Media Dialog. These should be used for 12129 * {@link finesse.restservices.MediaDialog#setTaskState}. 12130 * @constructs 12131 */ 12132 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 12133 }; 12134 12135 12136 12137 MediaDialog.States = /** @lends finesse.restservices.MediaDialog.States.prototype */ { 12138 /** 12139 * Indicates that the task has been offered to an agent. 12140 */ 12141 OFFERED: "OFFERED", 12142 /** 12143 * Indicates that the user has started work on the task. 12144 */ 12145 ACTIVE: "ACTIVE", 12146 /** 12147 * Indicates that the user has paused work on the task. 12148 */ 12149 PAUSED: "PAUSED", 12150 /** 12151 * Indicates that the user is wrapping up the task. 12152 */ 12153 WRAPPING_UP: "WRAPPING_UP", 12154 /** 12155 * Indicates that the task was interrupted. 12156 */ 12157 INTERRUPTED: "INTERRUPTED", 12158 /** 12159 * Indicates that the task has ended. 12160 */ 12161 CLOSED: "CLOSED", 12162 /** 12163 * Indicates that the user has accepted the task. 12164 */ 12165 ACCEPTED: "ACCEPTED", 12166 /** 12167 * Finesse has recovered a task after a failure. It does not have enough information to build a complete set 12168 * of actions for the task so it only allows the user to end the task. 12169 */ 12170 UNKNOWN: "UNKNOWN", 12171 /** 12172 * @class Possible Dialog State constants. 12173 * The State flow of a typical in-bound Dialog is as follows: OFFERED, ACCEPTED, ACTIVE, CLOSED. 12174 * @constructs 12175 */ 12176 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 12177 }; 12178 12179 MediaDialog.ParticipantStates = MediaDialog.States; 12180 12181 window.finesse = window.finesse || {}; 12182 window.finesse.restservices = window.finesse.restservices || {}; 12183 window.finesse.restservices.MediaDialog = MediaDialog; 12184 12185 12186 return MediaDialog; 12187 }); 12188 12189 /* Simple JavaScript Inheritance 12190 * By John Resig http://ejohn.org/ 12191 * MIT Licensed. 12192 */ 12193 // Inspired by base2 and Prototype 12194 define('restservices/../../thirdparty/Class',[], function () { 12195 var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/; 12196 // The base Class implementation (does nothing) 12197 /** @private */ 12198 Class = function(){}; 12199 12200 // Create a new Class that inherits from this class 12201 /** @private */ 12202 Class.extend = function(prop) { 12203 var _super = this.prototype; 12204 12205 // Instantiate a base class (but only create the instance, 12206 // don't run the init constructor) 12207 initializing = true; 12208 var prototype = new this(); 12209 initializing = false; 12210 12211 // Copy the properties over onto the new prototype 12212 for (var name in prop) { 12213 // Check if we're overwriting an existing function 12214 prototype[name] = typeof prop[name] == "function" && 12215 typeof _super[name] == "function" && fnTest.test(prop[name]) ? 12216 (function(name, fn){ 12217 return function() { 12218 var tmp = this._super; 12219 12220 // Add a new ._super() method that is the same method 12221 // but on the super-class 12222 this._super = _super[name]; 12223 12224 // The method only need to be bound temporarily, so we 12225 // remove it when we're done executing 12226 var ret = fn.apply(this, arguments); 12227 this._super = tmp; 12228 12229 return ret; 12230 }; 12231 })(name, prop[name]) : 12232 prop[name]; 12233 } 12234 12235 // The dummy class constructor 12236 /** @private */ 12237 function Class() { 12238 // All construction is actually done in the init method 12239 if ( !initializing && this.init ) 12240 this.init.apply(this, arguments); 12241 } 12242 12243 // Populate our constructed prototype object 12244 Class.prototype = prototype; 12245 12246 // Enforce the constructor to be what we expect 12247 Class.prototype.constructor = Class; 12248 12249 // And make this class extendable 12250 Class.extend = arguments.callee; 12251 12252 return Class; 12253 }; 12254 return Class; 12255 }); 12256 12257 /** 12258 * Class used to establish a Media/Dialogs subscription to be shared by MediaDialogs objects for non-voice 12259 * dialog events. 12260 * 12261 * @requires Class 12262 * @requires finesse.clientservices.ClientServices 12263 * @requires finesse.clientservices.Topics 12264 */ 12265 /** @private */ 12266 define('restservices/MediaDialogsSubscriptionManager',[ 12267 "../../thirdparty/Class", 12268 "clientservices/ClientServices", 12269 "clientservices/Topics" 12270 ], 12271 function (Class, ClientServices, Topics) { 12272 var MediaDialogsSubscriptionManager = Class.extend(/** @lends finesse.restservices.MediaDialogsSubscriptionManager.prototype */{ 12273 12274 /** 12275 * Map used to track the MediaDialogs objects managed by this object. 12276 * @private 12277 */ 12278 _mediaDialogsMap: {}, 12279 12280 /** 12281 * The regex used to match the source of BOSH/XMPP events. If an event matches this source, the event will 12282 * be processed by the subscription manager. 12283 * @private 12284 */ 12285 _sourceRegEx: null, 12286 12287 /** 12288 * The subscription ID/handle for Media/Dialogs events. 12289 * @private 12290 */ 12291 _subscriptionId: null, 12292 12293 _fakeConstuctor: function () 12294 { 12295 /* This is here to hide the real init constructor from the public docs */ 12296 }, 12297 12298 /** 12299 * Create the regex used to match the source of BOSH/XMPP events. If an event matches this source, the event 12300 * will be processed by the subscription manager. 12301 * 12302 * @param {Object} restObj 12303 * The restObj whose REST URL will be used as the base of the regex. 12304 * 12305 * @returns {RegExp} 12306 * The regex used to match the source of XMPP events. 12307 * @private 12308 */ 12309 _makeSourceRegEx: function(restObj) 12310 { 12311 return new RegExp("^" + restObj.getRestUrl() + "/Media/[0-9]+/Dialogs$"); 12312 }, 12313 12314 /** 12315 * Return the media ID associated with the update. 12316 * 12317 * @param {Object} update 12318 * The content of the update event. 12319 * 12320 * @returns {String} 12321 * The media ID associated with the update. 12322 * @private 12323 */ 12324 _getMediaIdFromEventUpdate: function(update) 12325 { 12326 var parts = update.object.Update.source.split("/"); 12327 return parts[MediaDialogsSubscriptionManager.MEDIA_ID_INDEX_IN_SOURCE]; 12328 }, 12329 12330 /** 12331 * Handler for update events. This handler forwards the update to the MediaDialogs object associated with 12332 * the media ID carried in the update event. 12333 * 12334 * @param {Object} update 12335 * The content of the update event. 12336 * 12337 * @private 12338 */ 12339 _updateEventHandler: function(update) 12340 { 12341 var mediaId = this._getMediaIdFromEventUpdate(update), 12342 mediaDialogs = this._mediaDialogsMap[mediaId]; 12343 12344 if ( mediaDialogs ) 12345 { 12346 mediaDialogs._updateEventHandler(mediaDialogs, update); 12347 } 12348 }, 12349 12350 /** 12351 * Return the media ID associated with the REST update. 12352 * 12353 * @param {Object} update 12354 * The content of the REST update. 12355 * 12356 * @returns {String} 12357 * The media ID associated with the update. 12358 * @private 12359 */ 12360 _getMediaIdFromRestUpdate: function(update) 12361 { 12362 return update.object.Update.data.dialog.mediaProperties.mediaId; 12363 }, 12364 12365 /** 12366 * Handler for REST updates. This handler forwards the update to the MediaDialogs object associated with 12367 * the media ID carried in the REST update. 12368 * 12369 * @param {Object} update 12370 * The content of the REST update. 12371 * 12372 * @private 12373 */ 12374 _processRestItemUpdate: function(update) 12375 { 12376 var mediaId = this._getMediaIdFromRestUpdate(update), 12377 mediaDialogs = this._mediaDialogsMap[mediaId]; 12378 12379 if ( mediaDialogs ) 12380 { 12381 mediaDialogs._processRestItemUpdate(update); 12382 } 12383 }, 12384 12385 /** 12386 * Utility method to create a callback to be given to OpenAjax to invoke when a message 12387 * is published on the topic of our REST URL (also XEP-0060 node). 12388 * This needs to be its own defined method so that subclasses can have their own implementation. 12389 * @returns {Function} callback(update) 12390 * The callback to be invoked when an update event is received. This callback will 12391 * process the update by notifying the MediaDialogs object associated with the media ID in the update. 12392 * 12393 * @private 12394 */ 12395 _createPubsubCallback: function () 12396 { 12397 var _this = this; 12398 return function (update) { 12399 //If the source of the update is our REST URL, this means the collection itself is modified 12400 if (update.object.Update.source.match(_this._sourceRegEx)) { 12401 _this._updateEventHandler(update); 12402 } else { 12403 //Otherwise, it is safe to assume that if we got an event on our topic, it must be a 12404 //rest item update of one of our children that was published on our node (OpenAjax topic) 12405 _this._processRestItemUpdate(update); 12406 } 12407 }; 12408 }, 12409 12410 /** 12411 * Track the MediaDialogs object so that events and REST updates signalled to this subscription manager 12412 * can be forwarded to the given MediaDialogs object. 12413 * @param {finesse.restservices.MediaDialogs} mediaDialogs MediaDialogs object to be tracked by the 12414 * subscription manager. 12415 * @private 12416 */ 12417 _manage: function(mediaDialogs) 12418 { 12419 this._mediaDialogsMap[mediaDialogs.getMedia().getMediaId()] = mediaDialogs; 12420 }, 12421 12422 /** 12423 * Stop tracking the MediaDialogs object. Events and REST updates signalled to this subscription manager 12424 * will no longer be forwarded to the given MediaDialogs object. 12425 * @param {finesse.restservices.MediaDialogs} mediaDialogs MediaDialogs object to no longer track. 12426 * @private 12427 */ 12428 _unManage: function(mediaDialogs) 12429 { 12430 var mediaId = mediaDialogs.getMedia().getMediaId(); 12431 if ( this._callbackMap[mediaId] ) 12432 { 12433 delete this._callbackMap[mediaId]; 12434 } 12435 }, 12436 12437 /** 12438 * @class 12439 * An internal class used to establish a Media/Dialogs subscription to be shared by MediaDialogs objects 12440 * for non-voice dialog events. 12441 * 12442 * @constructor 12443 * @param {RestBase} restObj 12444 * A RestBase object used to build the user portion of XMPP and REST paths. 12445 * @constructs 12446 */ 12447 init: function (restObj) 12448 { 12449 var _this; 12450 12451 this._sourceRegEx = this._makeSourceRegEx(restObj); 12452 }, 12453 12454 /** 12455 * Create the BOSH/XMPP subscription used for non-voice dialog events. Additionally, store the given 12456 * MediaDialogs object so that events for the object can be forwarded to it. 12457 * 12458 * @param {finesse.restservices.MediaDialogs} mediaDialogs a MediaDialogs object to manage (forward events) 12459 * @param {Object} callbacks an object containing success and error callbacks used to signal the result of 12460 * the subscription. 12461 * @returns {MediaDialogsSubscriptionManager} 12462 * @private 12463 */ 12464 subscribe: function (mediaDialogs, callbacks) 12465 { 12466 var topic = Topics.getTopic(mediaDialogs.getXMPPNodePath()), 12467 _this = this, 12468 handlers, 12469 successful; 12470 12471 callbacks = callbacks || {}; 12472 12473 handlers = { 12474 /** @private */ 12475 success: function () { 12476 // Add item to the refresh list in ClientServices to refresh if 12477 // we recover due to our resilient connection. 12478 ClientServices.addToRefreshList(_this); 12479 if (typeof callbacks.success === "function") { 12480 callbacks.success(); 12481 } 12482 }, 12483 /** @private */ 12484 error: function (err) { 12485 if (successful) { 12486 _this._unManage(mediaDialogs); 12487 ClientServices.unsubscribe(topic); 12488 } 12489 12490 if (typeof callbacks.error === "function") { 12491 callbacks.error(err); 12492 } 12493 } 12494 }; 12495 12496 this._manage(mediaDialogs); 12497 if ( this._subscriptionId ) 12498 { 12499 successful = true; 12500 } 12501 else 12502 { 12503 successful = ClientServices.subscribe(topic, this._createPubsubCallback(), true); 12504 if ( successful ) 12505 { 12506 this._subscriptionId = "OpenAjaxOnly"; 12507 } 12508 } 12509 12510 if (successful) { 12511 handlers.success(); 12512 } else { 12513 handlers.error(); 12514 } 12515 12516 return this; 12517 } 12518 }); 12519 12520 MediaDialogsSubscriptionManager.MEDIA_ID_INDEX_IN_SOURCE = 6; 12521 12522 window.finesse = window.finesse || {}; 12523 window.finesse.restservices = window.finesse.restservices || {}; 12524 window.finesse.restservices.MediaDialogsSubscriptionManager = MediaDialogsSubscriptionManager; 12525 12526 return MediaDialogsSubscriptionManager; 12527 }); 12528 12529 /** 12530 * JavaScript representation of the Finesse MediaDialogs collection 12531 * object which contains a list of Dialog objects. 12532 * 12533 * @requires finesse.clientservices.ClientServices 12534 * @requires Class 12535 * @requires finesse.FinesseBase 12536 * @requires finesse.restservices.RestBase 12537 * @requires finesse.restservices.Dialogs 12538 * @requires finesse.restservices.MediaDialogsSubscriptionManager 12539 */ 12540 /** @private */ 12541 define('restservices/MediaDialogs',[ 12542 'restservices/RestCollectionBase', 12543 'restservices/RestBase', 12544 'restservices/Dialogs', 12545 'restservices/MediaDialog', 12546 'restservices/MediaDialogsSubscriptionManager' 12547 ], 12548 function (RestCollectionBase, RestBase, Dialogs, MediaDialog, MediaDialogsSubscriptionManager) { 12549 var MediaDialogs = Dialogs.extend(/** @lends finesse.restservices.MediaDialogs.prototype */{ 12550 12551 /** 12552 * @class 12553 * JavaScript representation of a collection of Dialogs for a specific non-voice Media. 12554 * @augments finesse.restservices.Dialogs 12555 * @constructs 12556 * @see finesse.restservices.Dialog 12557 * @example 12558 * _MediaDialogs = _media.getMediaDialogs( { 12559 * onCollectionAdd : _handleDialogAdd, 12560 * onCollectionDelete : _handleDialogDelete, 12561 * onLoad : _handleMediaDialogsLoaded 12562 * }); 12563 * 12564 * _dialogCollection = _MediaDialogs.getCollection(); 12565 * for (var dialogId in _dialogCollection) { 12566 * if (_dialogCollection.hasOwnProperty(dialogId)) { 12567 * _dialog = _dialogCollection[dialogId]; 12568 * etc... 12569 * } 12570 * } 12571 */ 12572 _fakeConstuctor: function () { 12573 /* This is here to hide the real init constructor from the public docs */ 12574 }, 12575 12576 /** 12577 * @private 12578 * @param {Object} options 12579 * An object with the following properties:<ul> 12580 * <li><b>id:</b> The id of the object being constructed</li> 12581 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 12582 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 12583 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 12584 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 12585 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 12586 * <li><b>status:</b> {Number} The HTTP status code returned</li> 12587 * <li><b>content:</b> {String} Raw string of response</li> 12588 * <li><b>object:</b> {Object} Parsed object of response</li> 12589 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 12590 * <li><b>errorType:</b> {String} Type of error that was caught</li> 12591 * <li><b>errorMessage:</b> {String} Message associated with error</li> 12592 * </ul></li> 12593 * </ul></li> 12594 * <li><b>parentObj:</b> The parent object</li></ul> 12595 * <li><b>mediaObj:</b> The media object</li></ul> 12596 **/ 12597 init: function (options) { 12598 this._mediaObj = options.mediaObj; 12599 this._super(options); 12600 }, 12601 12602 getMedia: function() { 12603 return this._mediaObj; 12604 }, 12605 12606 /** 12607 * @private 12608 * Gets the REST class for the objects that make up the collection. - this 12609 * is the Dialog class. 12610 */ 12611 getRestItemClass: function () { 12612 return MediaDialog; 12613 }, 12614 12615 /** 12616 * @private 12617 * Gets the node path for the current object - this is the media node 12618 * @returns {String} The node path 12619 */ 12620 getXMPPNodePath: function () { 12621 var 12622 restObj = this._restObj, 12623 nodePath = ""; 12624 12625 //Prepend the base REST object if one was provided. 12626 if (restObj instanceof RestBase) { 12627 nodePath += restObj.getRestUrl(); 12628 } 12629 //Otherwise prepend with the default webapp name. 12630 else { 12631 nodePath += "/finesse/api"; 12632 } 12633 12634 //Append the REST type. 12635 nodePath += "/" + this.getRestType() + "/Media"; 12636 return nodePath; 12637 }, 12638 12639 /** 12640 * The REST URL in which this object can be referenced. 12641 * @return {String} 12642 * The REST URI for this object. 12643 * @private 12644 */ 12645 getRestUrl: function () { 12646 var 12647 restObj = this._mediaObj, 12648 restUrl = ""; 12649 12650 //Prepend the base REST object if one was provided. 12651 if (restObj instanceof RestBase) { 12652 restUrl += restObj.getRestUrl(); 12653 } 12654 //Otherwise prepend with the default webapp name. 12655 else { 12656 restUrl += "/finesse/api"; 12657 } 12658 12659 //Append the REST type. 12660 restUrl += "/" + this.getRestType(); 12661 12662 //Append ID if it is not undefined, null, or empty. 12663 if (this._id) { 12664 restUrl += "/" + this._id; 12665 } 12666 return restUrl; 12667 }, 12668 12669 /** 12670 * Overridden so that MediaDialogsSubscriptionManager can be used to share events across media dialogs. 12671 * 12672 * @param {Object} callbacks 12673 * An object containing success and error handlers for the subscription request. 12674 * @private 12675 */ 12676 subscribe: function (callbacks) 12677 { 12678 if ( !MediaDialogs.subscriptionManager ) 12679 { 12680 MediaDialogs.subscriptionManager = new MediaDialogsSubscriptionManager(this._restObj); 12681 } 12682 12683 MediaDialogs.subscriptionManager.subscribe(this, callbacks); 12684 12685 return this; 12686 } 12687 }); 12688 12689 MediaDialogs.subscriptionManager = /** @lends finesse.restservices.MediaDialogsSubscriptionManager.prototype */ null; 12690 12691 window.finesse = window.finesse || {}; 12692 window.finesse.restservices = window.finesse.restservices || {}; 12693 window.finesse.restservices.MediaDialogs = MediaDialogs; 12694 12695 return MediaDialogs; 12696 }); 12697 12698 /** 12699 * Allows gadgets to call the log function to publish client logging messages over the hub. 12700 * 12701 * @requires OpenAjax 12702 */ 12703 /** @private */ 12704 define('cslogger/ClientLogger',[], function () { 12705 12706 var ClientLogger = ( function () { /** @lends finesse.cslogger.ClientLogger.prototype */ 12707 var _hub, _logTopic, _originId, _sessId, _host, 12708 MONTH = { 0 : "Jan", 1 : "Feb", 2 : "Mar", 3 : "Apr", 4 : "May", 5 : "Jun", 12709 6 : "Jul", 7 : "Aug", 8 : "Sep", 9 : "Oct", 10 : "Nov", 11 : "Dec"}, 12710 12711 /** 12712 * Gets timestamp drift stored in sessionStorage 12713 * @returns drift in seconds if it is set in sessionStorage otherwise returns undefined. 12714 * @private 12715 */ 12716 getTsDrift = function() { 12717 if (window.sessionStorage.getItem('clientTimestampDrift') !== null) { 12718 return parseInt(window.sessionStorage.getItem('clientTimestampDrift'), 10); 12719 } 12720 else { 12721 return undefined; 12722 } 12723 }, 12724 12725 /** 12726 * Sets timestamp drift in sessionStorage 12727 * @param delta is the timestamp drift between server.and client. 12728 * @private 12729 */ 12730 setTsDrift = function(delta) { 12731 window.sessionStorage.setItem('clientTimestampDrift', delta.toString()); 12732 }, 12733 12734 /** 12735 * Gets Finesse server timezone offset from GMT in seconds 12736 * @returns offset in seconds if it is set in sessionStorage otherwise returns undefined. 12737 * @private 12738 */ 12739 getServerOffset = function() { 12740 if (window.sessionStorage.getItem('serverTimezoneOffset') !== null) { 12741 return parseInt(window.sessionStorage.getItem('serverTimezoneOffset'), 10); 12742 } 12743 else { 12744 return undefined; 12745 } 12746 }, 12747 12748 /** 12749 * Sets server timezone offset 12750 * @param sec is the server timezone GMT offset in seconds. 12751 * @private 12752 */ 12753 setServerOffset = function(sec) { 12754 window.sessionStorage.setItem('serverTimezoneOffset', sec.toString()); 12755 }, 12756 12757 /** 12758 * Checks to see if we have a console. 12759 * @returns Whether the console object exists. 12760 * @private 12761 */ 12762 hasConsole = function () { 12763 try { 12764 if (window.console !== undefined) { 12765 return true; 12766 } 12767 } 12768 catch (err) { 12769 // ignore and return false 12770 } 12771 12772 return false; 12773 }, 12774 12775 /** 12776 * Gets a short form (6 character) session ID from sessionStorage 12777 * @private 12778 */ 12779 getSessId = function() { 12780 if (!_sessId) { 12781 //when _sessId not defined yet, initiate it 12782 if (window.sessionStorage.getItem('enableLocalLog') === 'true') { 12783 _sessId= " "+window.sessionStorage.getItem('finSessKey'); 12784 } 12785 else { 12786 _sessId=" "; 12787 } 12788 } 12789 return _sessId; 12790 }, 12791 12792 /** 12793 * Pads a single digit number for display purposes (e.g. '4' shows as '04') 12794 * @param num is the number to pad to 2 digits 12795 * @returns a two digit padded string 12796 * @private 12797 */ 12798 padTwoDigits = function (num) 12799 { 12800 return (num < 10) ? '0' + num : num; 12801 }, 12802 12803 /** 12804 * Pads a single digit number for display purposes (e.g. '4' shows as '004') 12805 * @param num is the number to pad to 3 digits 12806 * @returns a three digit padded string 12807 * @private 12808 */ 12809 padThreeDigits = function (num) 12810 { 12811 if (num < 10) 12812 { 12813 return '00'+num; 12814 } 12815 else if (num < 100) 12816 { 12817 return '0'+num; 12818 } 12819 else 12820 { 12821 return num; 12822 } 12823 }, 12824 12825 /** 12826 * Compute the "hour" 12827 * 12828 * @param s is time in seconds 12829 * @returns {String} which is the hour 12830 * @private 12831 */ 12832 ho = function (s) { 12833 return ((s/60).toString()).split(".")[0]; 12834 }, 12835 12836 /** 12837 * Gets local timezone offset string. 12838 * 12839 * @param t is the time in seconds 12840 * @param s is the separator character between hours and minutes, e.g. ':' 12841 * @returns {String} is local timezone GMT offset in the following format: [+|-]hh[|:]MM 12842 * @private 12843 */ 12844 getGmtOffString = function (min,s) { 12845 var t, sign; 12846 if (min<0) { 12847 t = -min; 12848 sign = "-"; 12849 } 12850 else { 12851 t = min; 12852 sign = "+"; 12853 } 12854 12855 if (s===':') { 12856 return sign+padTwoDigits(ho(t))+s+padTwoDigits(t%60); 12857 } 12858 else { 12859 return sign+padTwoDigits(ho(t))+padTwoDigits(t%60); 12860 } 12861 }, 12862 12863 /** 12864 * Gets short form of a month name in English 12865 * 12866 * @param monthNum is zero-based month number 12867 * @returns {String} is short form of month name in English 12868 * @private 12869 */ 12870 getMonthShortStr = function (monthNum) { 12871 var result; 12872 try { 12873 result = MONTH[monthNum]; 12874 } 12875 catch (err) { 12876 if (hasConsole()) { 12877 window.console.log("Month must be between 0 and 11"); 12878 } 12879 } 12880 return result; 12881 }, 12882 12883 /** 12884 * Gets a timestamp. 12885 * @param aDate is a javascript Date object 12886 * @returns {String} is a timestamp in the following format: yyyy-mm-ddTHH:MM:ss.SSS [+|-]HH:MM 12887 * @private 12888 */ 12889 getDateTimeStamp = function (aDate) 12890 { 12891 var date, off, timeStr; 12892 if (aDate === null) { 12893 date = new Date(); 12894 } 12895 else { 12896 date = aDate; 12897 } 12898 off = -1*date.getTimezoneOffset(); 12899 timeStr = date.getFullYear().toString() + "-" + 12900 padTwoDigits(date.getMonth()+1) + "-" + 12901 padTwoDigits (date.getDate()) + "T"+ 12902 padTwoDigits(date.getHours()) + ":" + 12903 padTwoDigits(date.getMinutes()) + ":" + 12904 padTwoDigits(date.getSeconds())+"." + 12905 padThreeDigits(date.getMilliseconds()) + " "+ 12906 getGmtOffString(off, ':'); 12907 12908 return timeStr; 12909 }, 12910 12911 /** 12912 * Gets drift-adjusted timestamp. 12913 * @param aTimestamp is a timestamp in milliseconds 12914 * @param drift is a timestamp drift in milliseconds 12915 * @param serverOffset is a timezone GMT offset in minutes 12916 * @returns {String} is a timestamp in the Finesse server log format, e.g. Jan 07 2104 HH:MM:ss.SSS -0500 12917 * @private 12918 */ 12919 getDriftedDateTimeStamp = function (aTimestamp, drift, serverOffset) 12920 { 12921 var date, timeStr, localOffset; 12922 if (aTimestamp === null) { 12923 return "--- -- ---- --:--:--.--- -----"; 12924 } 12925 else if (drift === undefined || serverOffset === undefined) { 12926 if (hasConsole()) { 12927 window.console.log("drift or serverOffset must be a number"); 12928 } 12929 return "--- -- ---- --:--:--.--- -----"; 12930 } 12931 else { 12932 //need to get a zone diff in minutes 12933 localOffset = (new Date()).getTimezoneOffset(); 12934 date = new Date(aTimestamp+drift+(localOffset+serverOffset)*60000); 12935 timeStr = getMonthShortStr(date.getMonth()) + " "+ 12936 padTwoDigits (date.getDate())+ " "+ 12937 date.getFullYear().toString() + " "+ 12938 padTwoDigits(date.getHours()) + ":" + 12939 padTwoDigits(date.getMinutes()) + ":" + 12940 padTwoDigits(date.getSeconds())+"." + 12941 padThreeDigits(date.getMilliseconds())+" "+ 12942 getGmtOffString(serverOffset, ''); 12943 return timeStr; 12944 } 12945 }, 12946 12947 /** 12948 * Logs a message to a hidden textarea element on the page 12949 * 12950 * @param msg is the string to log. 12951 * @private 12952 */ 12953 writeToLogOutput = function (msg) { 12954 var logOutput = document.getElementById("finesseLogOutput"); 12955 12956 if (logOutput === null) 12957 { 12958 logOutput = document.createElement("textarea"); 12959 logOutput.id = "finesseLogOutput"; 12960 logOutput.style.display = "none"; 12961 document.body.appendChild(logOutput); 12962 } 12963 12964 if (logOutput.value === "") 12965 { 12966 logOutput.value = msg; 12967 } 12968 else 12969 { 12970 logOutput.value = logOutput.value + "\n" + msg; 12971 } 12972 }, 12973 12974 /* 12975 * Logs a message to console 12976 * @param str is the string to log. * @private 12977 */ 12978 logToConsole = function (str) 12979 { 12980 var now, msg, timeStr, driftedTimeStr, sessKey=getSessId(); 12981 now = new Date(); 12982 timeStr = getDateTimeStamp(now); 12983 if (getTsDrift() !== undefined) { 12984 driftedTimeStr = getDriftedDateTimeStamp(now.getTime(), getTsDrift(), getServerOffset()); 12985 } 12986 else { 12987 driftedTimeStr = getDriftedDateTimeStamp(null, 0, 0); 12988 } 12989 msg = timeStr + ":"+sessKey+": "+ _host + ": "+driftedTimeStr+ ": " + str; 12990 // Log to console 12991 if (hasConsole()) { 12992 window.console.log(msg); 12993 } 12994 12995 //Uncomment to print logs to hidden textarea. 12996 //writeToLogOutput(msg); 12997 12998 return msg; 12999 }; 13000 return { 13001 13002 /** 13003 * Publishes a Log Message over the hub. 13004 * 13005 * @param {String} message 13006 * The string to log. 13007 * @example 13008 * _clientLogger.log("This is some important message for MyGadget"); 13009 * 13010 */ 13011 log : function (message) { 13012 if(_hub) { 13013 _hub.publish(_logTopic, logToConsole(_originId + message)); 13014 } 13015 }, 13016 13017 /** 13018 * @class 13019 * Allows gadgets to call the log function to publish client logging messages over the hub. 13020 * 13021 * @constructs 13022 */ 13023 _fakeConstuctor: function () { 13024 /* This is here so we can document init() as a method rather than as a constructor. */ 13025 }, 13026 13027 /** 13028 * Initiates the client logger with a hub a gadgetId and gadget's config object. 13029 * @param {Object} hub 13030 * The hub to communicate with. 13031 * @param {String} gadgetId 13032 * A unique string to identify which gadget is doing the logging. 13033 * @param {finesse.gadget.Config} config 13034 * The config object used to get host name for that thirdparty gadget 13035 * @example 13036 * var _clientLogger = finesse.cslogger.ClientLogger; 13037 * _clientLogger.init(gadgets.Hub, "MyGadgetId", config); 13038 * 13039 */ 13040 init: function (hub, gadgetId, config) { 13041 _hub = hub; 13042 _logTopic = "finesse.clientLogging." + gadgetId; 13043 _originId = gadgetId + " : "; 13044 if ((config === undefined) || (config === "undefined")) 13045 { 13046 _host = ((finesse.container && finesse.container.Config && finesse.container.Config.host)?finesse.container.Config.host : "?.?.?.?"); 13047 } 13048 else 13049 { 13050 _host = ((config && config.host)?config.host : "?.?.?.?"); 13051 } 13052 } 13053 }; 13054 }()); 13055 13056 window.finesse = window.finesse || {}; 13057 window.finesse.cslogger = window.finesse.cslogger || {}; 13058 window.finesse.cslogger.ClientLogger = ClientLogger; 13059 13060 finesse = finesse || {}; 13061 /** @namespace Supports writing messages to a central log. */ 13062 finesse.cslogger = finesse.cslogger || {}; 13063 13064 return ClientLogger; 13065 }); 13066 13067 /** 13068 * Utility class used to recover a media object after recovering from a connection or system failure. 13069 * 13070 * @requires Class 13071 * @requires finesse.restservices.Notifier 13072 * @requires finesse.clientservices.ClientServices 13073 * @requires finesse.restservices.Media 13074 */ 13075 13076 /** @private */ 13077 define('restservices/MediaOptionsHelper',['../../thirdparty/Class', 13078 '../utilities/Utilities', 13079 '../clientservices/ClientServices', 13080 '../cslogger/ClientLogger' 13081 ], 13082 function (Class, Utilities, ClientServices, ClientLogger) 13083 { 13084 /** 13085 * Utility class used to synchronize media login options after recovering from a connection or system failure. This 13086 * class will ensure that the Finesse server that the application fails over to has the same maxDialogLimit, 13087 * interruptAction, and dialogLogoutAction as the previous Finesse server. 13088 */ 13089 var MediaOptionsHelper = Class.extend(/** @lends finesse.restservices.MediaOptionsHelper.prototype */ 13090 { 13091 /** 13092 * @private 13093 * 13094 * The media that this helper is responsible for recovering in case of failover. 13095 */ 13096 _media: null, 13097 13098 /** 13099 * @private 13100 * 13101 * The media options that this helper will ensure are set properly across failures. 13102 */ 13103 _mediaOptions: null, 13104 13105 /** 13106 * @private 13107 * 13108 * The current state of the failover recovery. 13109 */ 13110 _state: null, 13111 13112 /** 13113 * @class 13114 * 13115 * Utility class used to synchronize media login options after recovering from a connection or system failure. This 13116 * class will ensure that the Finesse server that the application fails over to has the same maxDialogLimit, 13117 * interruptAction, and dialogLogoutAction as the previous Finesse server. 13118 * 13119 * @constructs 13120 */ 13121 _fakeConstuctor: function () 13122 { 13123 /* This is here to hide the real init constructor from the public docs */ 13124 }, 13125 13126 /** 13127 * Utility method to format a message logged by an instance of this class. 13128 * 13129 * @param {string} message the message to format 13130 * @returns {string} the given message prefixed with the name of this class and the ID of the Media object 13131 * associated with this class. 13132 * @private 13133 */ 13134 _formatLogMessage: function(message) 13135 { 13136 return "MediaOptionsHelper[" + this.media.getMediaId() + "]: " + message; 13137 }, 13138 13139 /** 13140 * Utility method to log an informational message. 13141 * 13142 * Note that this method piggy-backs on the logger setup by the gadget. If the gadget does not initialize 13143 * logger, this class will not log. 13144 * 13145 * @param {string} message the message to log 13146 * @private 13147 */ 13148 _log: function(message) 13149 { 13150 ClientLogger.log(this._formatLogMessage(message)); 13151 }, 13152 13153 /** 13154 * Utility method to log an error message. 13155 * 13156 * Note that this method piggy-backs on the logger setup by the gadget. If the gadget does not initialize 13157 * logger, this class will not log. 13158 * 13159 * @param {string} message the message to log 13160 * @private 13161 */ 13162 _error: function(message) 13163 { 13164 ClientLogger.error(this._formatLogMessage(message)); 13165 }, 13166 13167 /** 13168 * @private 13169 * 13170 * Set the running state of this failover helper. 13171 * 13172 * @param {String} newState the new state of the failover helper. 13173 */ 13174 _setState: function(newState) 13175 { 13176 this._state = newState; 13177 this._log("changed state to " + this._state); 13178 }, 13179 13180 /** 13181 * Check the given media object to see if the maxDialogLimit, interruptAction, and dialogLogoutAction options 13182 * need to be reset. These options need to be reset if the application specified login options and any of the 13183 * following conditions are true:<ul> 13184 * <li>the dialogLogoutAction in the given media object does not match the action set by the application</li> 13185 * <li>the interruptAction in the given media object does not match the action set by the application</li> 13186 * <li>the maxDialogLimit in the given media object does not match the limit set by the application</li></ul> 13187 * 13188 * @param {Object} media the media object to evaluate 13189 * @returns {*|{}|boolean} true if a login request should be sent to correct the media options 13190 * @private 13191 */ 13192 _shouldLoginToFixOptions: function(media) 13193 { 13194 return this._mediaOptions 13195 && media.isLoggedIn() 13196 && (media.getDialogLogoutAction() !== this._mediaOptions.dialogLogoutAction 13197 || media.getInterruptAction() !== this._mediaOptions.interruptAction 13198 || media.getMaxDialogLimit() !== this._mediaOptions.maxDialogLimit); 13199 }, 13200 13201 /** 13202 * @private 13203 * 13204 * Determine if the given response is an "agent already logged in" error. 13205 * 13206 * @param {Object} response the response to evaluate 13207 * 13208 * @returns {boolean} true if 13209 */ 13210 _agentIsAlreadyLoggedIn: function(response) 13211 { 13212 return response 13213 && response.object 13214 && response.object.ApiErrors 13215 && response.object.ApiErrors.ApiError 13216 && response.object.ApiErrors.ApiError.ErrorMessage === "E_ARM_STAT_AGENT_ALREADY_LOGGED_IN"; 13217 }, 13218 13219 /** 13220 * Determine if the given response to a media login request is successful. A response is successful under these 13221 * conditions:<ul> 13222 * <li>the response has a 202 status</li> 13223 * <li>the response has a 400 status and the error indicates the agent is already logged in</li> 13224 * </ul> 13225 * 13226 * @param {Object} loginResponse the response to evaluate 13227 * 13228 * @returns {*|boolean} true if the response status is 202 or if the response status is 400 and the error states 13229 * that the agent is already logged in. 13230 * @private 13231 */ 13232 _isSuccessfulLoginResponse: function(loginResponse) 13233 { 13234 return loginResponse && ((loginResponse.status === 202) || this._agentIsAlreadyLoggedIn(loginResponse)); 13235 }, 13236 13237 /** 13238 * Process a media load or change while in the connected state. This involves checking the media options to 13239 * ensure they are the same as those set by the application. 13240 * 13241 * @param {Object} media the media object that was loaded or changed. 13242 * @private 13243 */ 13244 _processConnectedState: function(media) 13245 { 13246 var _this = this, processResponse; 13247 13248 if ( this._shouldLoginToFixOptions(media) ) 13249 { 13250 processResponse = function(response) 13251 { 13252 _this._setState(MediaOptionsHelper.States.MONITORING_OPTIONS); 13253 13254 if ( !_this._isSuccessfulLoginResponse(response) ) 13255 { 13256 _this._error("failed to reset options: " + response.status + ": " + response.content); 13257 } 13258 }; 13259 13260 this._setState(MediaOptionsHelper.States.SETTING_OPTIONS); 13261 13262 this._log("logging in to fix options"); 13263 13264 this.media.login({ 13265 dialogLogoutAction: _this._mediaOptions.dialogLogoutAction, 13266 interruptAction: _this._mediaOptions.interruptAction, 13267 maxDialogLimit: _this._mediaOptions.maxDialogLimit, 13268 handlers: { 13269 success: processResponse, 13270 error: processResponse 13271 } 13272 }); 13273 } 13274 }, 13275 13276 /** 13277 * Process a media load or change while in the resetting options state. All that is done in this state is log a 13278 * message that a reset is already in progress. 13279 * 13280 * @param {Object} media the media object that was loaded or changed. 13281 * @private 13282 */ 13283 _processResettingOptionsState: function(media) 13284 { 13285 this._log("Resetting options is in progress"); 13286 }, 13287 13288 /** 13289 * Initialize a helper class used to recover media objects following connectivity or component failures related 13290 * to Finesse and/or CCE services. 13291 * 13292 * Initialize the failover helper to manage the recovery of the given media object. 13293 * 13294 * @param {Object} media the media object to recover 13295 * @param {Object} mediaOptions an object containing the media options used by the application:<ul> 13296 * <li><b>maxDialogLimit:</b> The id of the object being constructed</li> 13297 * <li><b>interruptAction:</b> Accept or ignore interrupts</li> 13298 * <li><b>dialogLogoutAction:</b> transfer or close the task at logout time</li></ul> 13299 */ 13300 init: function (media, mediaOptions) 13301 { 13302 var _this = this, processMediaStateChange = function(media) 13303 { 13304 switch ( _this._state ) 13305 { 13306 case MediaOptionsHelper.States.MONITORING_OPTIONS: 13307 _this._processConnectedState(media); 13308 break; 13309 case MediaOptionsHelper.States.SETTING_OPTIONS: 13310 _this._processResettingOptionsState(media); 13311 break; 13312 default: 13313 _this._error("unexpected state: " + _this.state); 13314 break; 13315 } 13316 }; 13317 13318 this.media = media; 13319 13320 this._mediaOptions = mediaOptions || {}; 13321 13322 // The maxDialogLimit is a string in media events. Ensure _mediaOptions.maxDialogLimit is a string to 13323 // make sure it can be compared to the maxDialogLimit field in media events. 13324 // 13325 if ( this._mediaOptions.maxDialogLimit ) 13326 { 13327 this._mediaOptions.maxDialogLimit = this._mediaOptions.maxDialogLimit.toString(); 13328 } 13329 13330 // Add the media object to the refresh list so that ClientServices calls refresh on the media object when 13331 // the connection is reestablished. This will trigger processMediaStateChange() which will trigger a login 13332 // to restore media options if media options are different. 13333 // 13334 ClientServices.addToRefreshList(this.media); 13335 13336 this._setState(MediaOptionsHelper.States.MONITORING_OPTIONS); 13337 13338 this.media.addHandler('load', processMediaStateChange); 13339 this.media.addHandler('change', processMediaStateChange); 13340 13341 this._log("initialized"); 13342 } 13343 }); 13344 13345 /** 13346 * @private 13347 * 13348 * The states that a running MediaOptionsHelper executes. 13349 * 13350 * @type {{CONNECTED: string, RECOVERING: string, RECOVERY_LOGIN: string, _fakeConstructor: _fakeConstructor}} 13351 */ 13352 MediaOptionsHelper.States = /** @lends finesse.restservices.MediaOptionsHelper.States.prototype */ { 13353 13354 /** 13355 * The media is synchronized with the Finesse server. Media options are being monitored for changes. 13356 */ 13357 MONITORING_OPTIONS: "MONITORING_OPTIONS", 13358 13359 /** 13360 * A media login request has been sent to Finesse to set the correct media options. 13361 */ 13362 SETTING_OPTIONS: "SETTING_OPTIONS", 13363 13364 /** 13365 * @class Possible MediaOptionsHelper state values. 13366 * @constructs 13367 */ 13368 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 13369 }; 13370 13371 window.finesse = window.finesse || {}; 13372 window.finesse.restservices = window.finesse.restservices || {}; 13373 window.finesse.restservices.MediaOptionsHelper = MediaOptionsHelper; 13374 13375 return MediaOptionsHelper; 13376 }); 13377 /** 13378 * JavaScript representation of the Finesse Media object 13379 * 13380 * @requires finesse.clientservices.ClientServices 13381 * @requires Class 13382 * @requires finesse.FinesseBase 13383 * @requires finesse.restservices.RestBase 13384 * @requires finesse.restservices.MediaDialogs 13385 * @requires finesse.restservices.MediaOptionsHelper 13386 */ 13387 13388 /** @private */ 13389 define('restservices/Media',[ 13390 'restservices/RestBase', 13391 'restservices/MediaDialogs', 13392 'restservices/MediaOptionsHelper' 13393 ], 13394 function (RestBase, MediaDialogs, MediaOptionsHelper) { 13395 var Media = RestBase.extend(/** @lends finesse.restservices.Media.prototype */{ 13396 13397 /** 13398 * Media objects support GET REST requests. 13399 */ 13400 supportsRequests: true, 13401 13402 /** 13403 * @private 13404 * The list of dialogs associated with this media. 13405 */ 13406 _mdialogs : null, 13407 13408 /** 13409 * @private 13410 * The options used to log into this media. 13411 */ 13412 _mediaOptions: null, 13413 13414 /** 13415 * @private 13416 * Object used to keep the maxDialogLimit, interruptAction, and dialogLogoutAction in-synch across fail-overs. 13417 */ 13418 _optionsHelper: null, 13419 13420 /** 13421 * @class 13422 * A Media represents a non-voice channel, 13423 * for example, a chat or a email. 13424 * 13425 * @augments finesse.restservices.RestBase 13426 * @constructs 13427 */ 13428 _fakeConstuctor: function () { 13429 /* This is here to hide the real init constructor from the public docs */ 13430 }, 13431 13432 /** 13433 * @private 13434 * 13435 * @param {Object} options 13436 * An object with the following properties:<ul> 13437 * <li><b>id:</b> The id of the object being constructed</li> 13438 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 13439 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 13440 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 13441 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 13442 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 13443 * <li><b>status:</b> {Number} The HTTP status code returned</li> 13444 * <li><b>content:</b> {String} Raw string of response</li> 13445 * <li><b>object:</b> {Object} Parsed object of response</li> 13446 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 13447 * <li><b>errorType:</b> {String} Type of error that was caught</li> 13448 * <li><b>errorMessage:</b> {String} Message associated with error</li> 13449 * </ul></li> 13450 * </ul></li> 13451 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 13452 **/ 13453 init: function (options) { 13454 this._super(options); 13455 }, 13456 13457 /** 13458 * @private 13459 * Utility method used to retrieve an attribute from this media object's underlying data. 13460 * 13461 * @param {String} attributeName the name of the attribute to retrieve 13462 * @returns {String} the value of the attribute or undefined if the attribute is not found 13463 */ 13464 _getDataAttribute: function(attributeName) { 13465 this.isLoaded(); 13466 return this.getData()[attributeName]; 13467 }, 13468 13469 /** 13470 * @private 13471 * Gets the REST class for the current object - this is the Media class. 13472 * @returns {Object} The Media class. 13473 */ 13474 getRestClass: function () { 13475 return Media; 13476 }, 13477 13478 /** 13479 * @private 13480 * Gets the REST type for the current object - this is a "Media". 13481 * @returns {String} The Media string. 13482 */ 13483 getRestType: function () { 13484 return "Media"; 13485 }, 13486 13487 /** 13488 * @private 13489 * Getter for the uri. 13490 * @returns {String} The uri. 13491 */ 13492 getMediaUri: function () { 13493 return this._getDataAttribute('uri'); 13494 }, 13495 13496 /** 13497 * Getter for the id. 13498 * @returns {String} The id. 13499 */ 13500 getId: function () { 13501 return this._getDataAttribute('id'); 13502 }, 13503 13504 /** 13505 * Getter for the name. 13506 * @returns {String} The name. 13507 */ 13508 getName: function () { 13509 return this._getDataAttribute('name'); 13510 }, 13511 13512 /** 13513 * Getter for the reason code id. 13514 * @returns {String} The reason code id. 13515 */ 13516 getReasonCodeId: function () { 13517 return this._getDataAttribute('reasonCodeId'); 13518 }, 13519 13520 /** 13521 * Getter for the reason code label. 13522 * @returns {String} The reason code label. 13523 */ 13524 getReasonCodeLabel: function() { 13525 this.isLoaded(); 13526 if (this.getData().reasonCode) { 13527 return this.getData.reasonCode.label; 13528 } 13529 return ""; 13530 }, 13531 13532 /** 13533 * Getter for the action to be taken in the event this media is interrupted. The action will be one of the 13534 * following:<ul> 13535 * <li><b>ACCEPT:</b> the interrupt will be accepted and the agent will not work on tasks in this media 13536 * until the media is no longer interrupted.</li> 13537 * <li><b>IGNORE:</b> the interrupt will be ignored and the agent is allowed to work on the task while the 13538 * media is interrupted.</li></ul> 13539 * @returns {*|Object} 13540 */ 13541 getInterruptAction: function() { 13542 return this._getDataAttribute('interruptAction'); 13543 }, 13544 13545 /** 13546 * Getter for the action to be taken in the event the agent logs out with dialogs associated with this media. 13547 * The action will be one of the following:<ul> 13548 * <li><b>CLOSE:</b> the dialog will be closed.</li> 13549 * <li><b>TRANSFER:</b> the dialog will be transferred to another agent.</li></ul> 13550 * @returns {*|Object} 13551 */ 13552 getDialogLogoutAction: function() { 13553 return this._getDataAttribute('dialogLogoutAction'); 13554 }, 13555 13556 /** 13557 * Getter for the state of the User on this Media. 13558 * @returns {String} 13559 * The current (or last fetched) state of the User on this Media 13560 * @see finesse.restservices.Media.States 13561 */ 13562 getState: function() { 13563 return this._getDataAttribute('state'); 13564 }, 13565 13566 /** 13567 * Getter for the Media id 13568 * @returns {String} The Media id 13569 */ 13570 getMediaId: function() { 13571 return this._getDataAttribute('id'); 13572 }, 13573 13574 /** 13575 * Getter for maximum number of dialogs allowed on this Media 13576 * @returns {String} The max number of Dialogs on this Media 13577 */ 13578 getMaxDialogLimit: function() { 13579 return this._getDataAttribute('maxDialogLimit'); 13580 }, 13581 13582 /** 13583 * Getter for whether or not this media is interruptible 13584 * @returns {Boolean} true if interruptible; false otherwise 13585 */ 13586 getInterruptible: function() { 13587 var interruptible = this._getDataAttribute('interruptible'); 13588 return interruptible === 'true'; 13589 }, 13590 13591 /** 13592 * Is the user interruptible on this Media. 13593 * @returns {Boolean} true if interruptible; false otherwise 13594 */ 13595 isInterruptible: function() { 13596 return this.getInterruptible(); 13597 }, 13598 13599 /** 13600 * Getter for routable field on this Media 13601 * @returns {Boolean} true if routable, false otherwise 13602 */ 13603 getRoutable: function() { 13604 var routable = this._getDataAttribute('routable'); 13605 return routable === 'true'; 13606 }, 13607 13608 /** 13609 * Is the user routable on this Media. 13610 * @returns {Boolean} true if routable, false otherwise 13611 */ 13612 isRoutable: function() { 13613 return this.getRoutable(); 13614 }, 13615 13616 /** 13617 * @param {Object} options 13618 * An object with the following properties:<ul> 13619 * <li><b>routable:</b> true if the agent is routable, false otherwise</li> 13620 * <li><b>{finesse.interfaces.RequestHandlers} handlers:</b> An object containing the handlers for the request</li></ul> 13621 * @returns {finesse.restservices.Media} 13622 * This Media object, to allow cascading 13623 */ 13624 setRoutable: function(params) { 13625 var handlers, contentBody = {}, 13626 restType = this.getRestType(), 13627 url = this.getRestUrl(); 13628 params = params || {}; 13629 13630 contentBody[restType] = { 13631 "routable": params.routable 13632 }; 13633 13634 handlers = params.handlers || {}; 13635 13636 this._makeRequest(contentBody, url, handlers); 13637 13638 return this; 13639 }, 13640 13641 /** 13642 * @private 13643 * Invoke a request to the server given a content body and handlers. 13644 * 13645 * @param {Object} contentBody 13646 * A JS object containing the body of the action request. 13647 * @param {finesse.interfaces.RequestHandlers} handlers 13648 * An object containing the handlers for the request 13649 */ 13650 _makeRequest: function (contentBody, url, handlers) { 13651 // Protect against null dereferencing of options allowing its 13652 // (nonexistent) keys to be read as undefined 13653 handlers = handlers || {}; 13654 13655 this.restRequest(url, { 13656 method: 'PUT', 13657 success: handlers.success, 13658 error: handlers.error, 13659 content: contentBody 13660 }); 13661 }, 13662 13663 /** 13664 * Return true if the params object contains one of the following:<ul> 13665 * <li>maxDialogLimit</li> 13666 * <li>interruptAction</li> 13667 * <li>dialogLogoutAction</li></ul> 13668 * 13669 * @param {Object} params the parameters to evaluate 13670 * @returns {*|Boolean} 13671 * @private 13672 */ 13673 _containsLoginOptions: function(params) { 13674 return params.maxDialogLimit || params.interruptAction || params.dialogLogoutAction; 13675 }, 13676 13677 /** 13678 * Create login parameters using the given algorithm:<ul> 13679 * <li>if loginOptions have not be set in the call to MediaList.getMedia(), use the params given by the application</li> 13680 * <li>if no params were set by the application, use the loginOptions set in the call to MediaList.getMedia()</li> 13681 * <li>if the parameters given by the application contains login options, use the parameters given by the application</li> 13682 * <li>if login options were given by the application but callbacks were given, create new login params with the 13683 * loginOptions set in the call to MediaList.getMedia() and the callbacks specified in the given login params</li></ul> 13684 * 13685 * @param params the parameters specified by the application in the call to Media.login() 13686 * 13687 * @see finesse.restservices.Media#login 13688 * @see finesse.restservices.MediaList#getMedia 13689 * 13690 * @returns {Object} login parameters built based on the algorithm listed above. 13691 * @private 13692 */ 13693 _makeLoginOptions: function(params) { 13694 if ( !this._mediaOptions ) { 13695 // If loginOptions have not be set, use the params given by the application. 13696 // 13697 return params; 13698 } 13699 13700 if ( !params ) { 13701 // If there were no params given by the application, use the loginOptions set in the call to 13702 // MediaList.getMedia(). 13703 // 13704 return this._mediaOptions; 13705 } 13706 13707 if ( this._containsLoginOptions(params) ) { 13708 // if the parameters given by the application contains login options, use the parameters given by the 13709 // application. 13710 // 13711 return params; 13712 } 13713 13714 // If login options were given by the application but callbacks were given, create new login params with the 13715 // loginOptions set in the call to MediaList.getMedia() and the callbacks specified in the given login 13716 // params. 13717 // 13718 return { 13719 maxDialogLimit: this._mediaOptions.maxDialogLimit, 13720 interruptAction: this._mediaOptions.interruptAction, 13721 dialogLogoutAction: this._mediaOptions.dialogLogoutAction, 13722 handlers: params.handlers 13723 }; 13724 }, 13725 13726 /** 13727 * Ensure that the maxDialogLimit, interruptAction, and dialogLogoutAction options are kept in synch across 13728 * fail-overs for this media object. 13729 * @private 13730 */ 13731 _ensureOptionsAreInSynch: function() { 13732 if ( !this._optionsHelper && this._mediaOptions ) { 13733 this._optionsHelper = new MediaOptionsHelper(this, this._mediaOptions); 13734 } 13735 }, 13736 13737 /** 13738 * Log the agent into this media. 13739 * 13740 * @param {Object} options 13741 * An object with the following properties:<ul> 13742 * <li><b>maxDialogLimit:</b> The id of the object being constructed</li> 13743 * <li><b>interruptAction:</b> Accept or ignore interrupts</li> 13744 * <li><b>dialogLogoutAction:</b> transfer or close the task at logout time</li> 13745 * <li><b>{finesse.interfaces.RequestHandlers} handlers:</b> An object containing the handlers for the request</li></ul> 13746 * 13747 * If maxDialogLimit, interruptAction, and dialogLogoutAction are not present, loginOptions specified in the 13748 * call to finesse.restservices.MediaList.getMedia() will be used. 13749 * 13750 * @see finesse.restservices.MediaList#getMedia 13751 * 13752 * @returns {finesse.restservices.Media} 13753 * This Media object, to allow cascading 13754 */ 13755 login: function(params) { 13756 this.setState(Media.States.LOGIN, null, this._makeLoginOptions(params)); 13757 return this; // Allow cascading 13758 }, 13759 13760 /** 13761 * Perform a logout for a user on this media. 13762 * @param {String} reasonCode 13763 * The reason this user is logging out of this media. Pass null for no reason. 13764 * @param {finesse.interfaces.RequestHandlers} handlers 13765 * An object containing the handlers for the request 13766 * @returns {finesse.restservices.Media} 13767 * This Media object, to allow cascading 13768 */ 13769 logout: function(reasonCode, params) { 13770 var state = Media.States.LOGOUT; 13771 return this.setState(state, reasonCode, params); 13772 }, 13773 13774 /** 13775 * Set the state of the user on this Media. 13776 * @param {String} newState 13777 * The state you are setting 13778 * @param {ReasonCode} reasonCode 13779 * The reason this user is changing state for this media. Pass null for no reason. 13780 * @param {finesse.interfaces.RequestHandlers} handlers 13781 * An object containing the handlers for the request 13782 * @see finesse.restservices.User.States 13783 * @returns {finesse.restservices.Media} 13784 * This Media object, to allow cascading 13785 */ 13786 setState: function(state, reasonCode, params) { 13787 var handlers, reasonCodeId, contentBody = {}, 13788 restType = this.getRestType(), 13789 url = this.getRestUrl(); 13790 params = params || {}; 13791 13792 if(reasonCode) { 13793 reasonCodeId = reasonCode.id; 13794 } 13795 13796 contentBody[restType] = { 13797 "state": state, 13798 "maxDialogLimit": params.maxDialogLimit, 13799 "interruptAction": params.interruptAction, 13800 "dialogLogoutAction": params.dialogLogoutAction, 13801 "reasonCodeId": reasonCodeId 13802 }; 13803 13804 handlers = params.handlers || {}; 13805 13806 this._makeRequest(contentBody, url, handlers); 13807 13808 return this; 13809 }, 13810 13811 /** 13812 * Getter for a MediaDialogs collection object that is associated with User on this Media. 13813 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 13814 * applicable when Object has not been previously created). 13815 * @returns {finesse.restservices.MediaDialogs} 13816 * A MediaDialogs collection object. 13817 */ 13818 getMediaDialogs: function (callbacks) { 13819 var options = callbacks || {}; 13820 options.parentObj = this._restObj; 13821 options.mediaObj = this; 13822 this.isLoaded(); 13823 13824 if (this._mdialogs === null) { 13825 this._mdialogs = new MediaDialogs(options); 13826 } 13827 13828 return this._mdialogs; 13829 }, 13830 13831 /** 13832 * Refresh the dialog collection associated with this media. 13833 */ 13834 refreshMediaDialogs: function() { 13835 if ( this._mdialogs ) { 13836 this._mdialogs.refresh(); 13837 } 13838 }, 13839 13840 /** 13841 * Set the maxDialogLimit, interruptAction, and dialogLogoutAction settings that the application will use for 13842 * this media. In the event of a failure, these options will be set on the new Finesse server. 13843 * 13844 * @param {Object} mediaOptions an object with the following properties:<ul> 13845 * <li><b>maxDialogLimit:</b> The id of the object being constructed</li> 13846 * <li><b>interruptAction:</b> Accept or ignore interrupts</li> 13847 * <li><b>dialogLogoutAction:</b> transfer or close the task at logout time</li></ul> 13848 */ 13849 setMediaOptions: function(mediaOptions) { 13850 if ( mediaOptions ) { 13851 this._mediaOptions = mediaOptions; 13852 if ( !this._optionsHelper ) { 13853 this._optionsHelper = new MediaOptionsHelper(this, this._mediaOptions); 13854 } 13855 } 13856 }, 13857 13858 /** 13859 * Refresh this media object and optionally refresh the list of media dialogs associated with this object. 13860 * 13861 * @param {Integer} retries the number of times to retry synchronizing this media object. 13862 */ 13863 refresh: function(retries) { 13864 retries = retries || 1; 13865 this._synchronize(retries); 13866 this.refreshMediaDialogs(); 13867 }, 13868 13869 /** 13870 * Is the user in work state on this Media. 13871 * @returns {boolean} returns true if the media is in work state; false otherwise 13872 */ 13873 isInWorkState: function() { 13874 return this.getState() === Media.States.WORK; 13875 }, 13876 13877 /** 13878 * Is the user in any state except LOGOUT on this Media. 13879 * @returns {boolean} returns true if the agent is in any state except LOGOUT in this media 13880 */ 13881 isLoggedIn: function() { 13882 return this.getState() !== Media.States.LOGOUT; 13883 } 13884 }); 13885 13886 Media.States = /** @lends finesse.restservices.Media.States.prototype */ { 13887 /** 13888 * User Login on a non-voice Media. Note that while this is an action, is not technically a state, since a 13889 * logged-in User will always be in a specific state (READY, NOT_READY, etc.). 13890 */ 13891 LOGIN: "LOGIN", 13892 /** 13893 * User is logged out of this Media. 13894 */ 13895 LOGOUT: "LOGOUT", 13896 /** 13897 * User is not ready on this Media. 13898 */ 13899 NOT_READY: "NOT_READY", 13900 /** 13901 * User is ready for activity on this Media. 13902 */ 13903 READY: "READY", 13904 /** 13905 * User has a dialog coming in, but has not accepted it. 13906 */ 13907 RESERVED: "RESERVED", 13908 /** 13909 * The dialogs in this media have been interrupted by a dialog in a non-interruptible media. 13910 */ 13911 INTERRUPTED: "INTERRUPTED", 13912 /** 13913 * User enters this state when failing over from one Finesse to the other or when failing over from one 13914 * PG side to the other. 13915 */ 13916 WORK: "WORK", 13917 /** 13918 * @class Possible Media state values. 13919 * @constructs 13920 */ 13921 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 13922 13923 }; 13924 13925 window.finesse = window.finesse || {}; 13926 window.finesse.restservices = window.finesse.restservices || {}; 13927 window.finesse.restservices.Media = Media; 13928 13929 return Media; 13930 }); 13931 /** 13932 * JavaScript representation of the Finesse Media collection 13933 * object which contains a list of Media objects. 13934 * 13935 * @requires finesse.clientservices.ClientServices 13936 * @requires Class 13937 * @requires finesse.FinesseBase 13938 * @requires finesse.restservices.RestBase 13939 * @requires finesse.restservices.Media 13940 */ 13941 /** @private */ 13942 define('restservices/MediaList',[ 13943 'restservices/RestCollectionBase', 13944 'restservices/Media', 13945 'restservices/RestBase', 13946 'utilities/Utilities' 13947 ], 13948 function (RestCollectionBase, Media, RestBase, Utilities) { 13949 var MediaList = RestCollectionBase.extend(/** @lends finesse.restservices.MediaList.prototype */{ 13950 13951 /** 13952 * @class 13953 * JavaScript representation of a MediaList collection object. 13954 * @augments finesse.restservices.RestCollectionBase 13955 * @constructs 13956 * @see finesse.restservices.Media 13957 * @example 13958 * mediaList = _user.getMediaList( { 13959 * onCollectionAdd : _handleMediaAdd, 13960 * onCollectionDelete : _handleMediaDelete, 13961 * onLoad : _handleMediaListLoaded 13962 * }); 13963 * 13964 * _mediaCollection = mediaList.getCollection(); 13965 * for (var mediaId in _mediaCollection) { 13966 * if (_mediaCollection.hasOwnProperty(mediaId)) { 13967 * media = _mediaCollection[mediaId]; 13968 * etc... 13969 * } 13970 * } 13971 */ 13972 _fakeConstuctor: function () { 13973 /* This is here to hide the real init constructor from the public docs */ 13974 }, 13975 13976 /** 13977 * @private 13978 * @param {Object} options 13979 * An object with the following properties:<ul> 13980 * <li><b>id:</b> The id of the object being constructed</li> 13981 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 13982 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 13983 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 13984 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 13985 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 13986 * <li><b>status:</b> {Number} The HTTP status code returned</li> 13987 * <li><b>content:</b> {String} Raw string of response</li> 13988 * <li><b>object:</b> {Object} Parsed object of response</li> 13989 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 13990 * <li><b>errorType:</b> {String} Type of error that was caught</li> 13991 * <li><b>errorMessage:</b> {String} Message associated with error</li> 13992 * </ul></li> 13993 * </ul></li> 13994 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 13995 **/ 13996 init: function (options) { 13997 this._super(options); 13998 }, 13999 14000 /** 14001 * @private 14002 * Gets the REST class for the current object - this is the MediaList class. 14003 */ 14004 getRestClass: function () { 14005 return MediaList; 14006 }, 14007 14008 /** 14009 * @private 14010 * Gets the REST class for the objects that make up the collection. - this 14011 * is the Media class. 14012 */ 14013 getRestItemClass: function () { 14014 return Media; 14015 }, 14016 14017 /** 14018 * @private 14019 * Gets the REST type for the current object - this is a "MediaList". 14020 */ 14021 getRestType: function () { 14022 return "MediaList"; 14023 }, 14024 14025 /** 14026 * @private 14027 * Gets the REST type for the objects that make up the collection - this is "Media". 14028 */ 14029 getRestItemType: function () { 14030 return "Media"; 14031 }, 14032 14033 /** 14034 * @private 14035 * Override default to indicates that the collection doesn't support making 14036 * requests. 14037 */ 14038 supportsRequests: true, 14039 14040 /** 14041 * @private 14042 * Override default to indicates that the collection subscribes to its objects. 14043 */ 14044 supportsRestItemSubscriptions: true, 14045 14046 /** 14047 * The REST URL in which this object can be referenced. 14048 * @return {String} 14049 * The REST URI for this object. 14050 * @private 14051 */ 14052 getRestUrl: function () { 14053 var 14054 restObj = this._restObj, 14055 restUrl = ""; 14056 14057 //Prepend the base REST object if one was provided. 14058 if (restObj instanceof RestBase) { 14059 restUrl += restObj.getRestUrl(); 14060 } 14061 //Otherwise prepend with the default webapp name. 14062 else { 14063 restUrl += "/finesse/api"; 14064 } 14065 14066 //Append the REST type. (Media not MediaList) 14067 restUrl += "/" + this.getRestItemType(); 14068 14069 //Append ID if it is not undefined, null, or empty. 14070 if (this._id) { 14071 restUrl += "/" + this._id; 14072 } 14073 14074 return restUrl; 14075 }, 14076 14077 /** 14078 * Getter for a Media from the MediaList collection. 14079 * * @param {Object} options 14080 * An object with the following properties:<ul> 14081 * <li><b>id:</b> The id of the media to fetch</li> 14082 * <li><b>onLoad(this): (optional)</b> callback handler for when the object is successfully loaded from the server</li> 14083 * <li><b>onChange(this): (optional)</b> callback handler for when an update notification of the object is received</li> 14084 * <li><b>onAdd(this): (optional)</b> callback handler for when a notification that the object is created is received</li> 14085 * <li><b>onDelete(this): (optional)</b> callback handler for when a notification that the object is deleted is received</li> 14086 * <li><b>onError(rsp): (optional)</b> callback handler for if loading of the object fails, invoked with the error response object:<ul> 14087 * <li><b>status:</b> {Number} The HTTP status code returned</li> 14088 * <li><b>content:</b> {String} Raw string of response</li> 14089 * <li><b>object:</b> {Object} Parsed object of response</li> 14090 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 14091 * <li><b>errorType:</b> {String} Type of error that was caught</li> 14092 * <li><b>errorMessage:</b> {String} Message associated with error</li> 14093 * </ul></li> 14094 * </ul></li> 14095 * <li><b>mediaOptions:</b> {Object} An object with the following properties:<ul> 14096 * <li><b>maxDialogLimit:</b> The id of the object being constructed</li> 14097 * <li><b>interruptAction:</b> Accept or ignore interrupts</li> 14098 * <li><b>dialogLogoutAction:</b> transfer or close the task at logout time</li></ul> 14099 * </li></ul> 14100 * 14101 * @returns {finesse.restservices.Media} 14102 * A Media object. 14103 */ 14104 getMedia: function (options) { 14105 this.isLoaded(); 14106 options = options || {}; 14107 var objectId = options.id, 14108 media = this._collection[objectId]; 14109 14110 //throw error if media not found 14111 if(!media) { 14112 throw new Error("No media found with id: " + objectId); 14113 } 14114 14115 media.addHandler('load', options.onLoad); 14116 media.addHandler('change', options.onChange); 14117 media.addHandler('add', options.onAdd); 14118 media.addHandler('delete', options.onDelete); 14119 media.addHandler('error', options.onError); 14120 14121 media.setMediaOptions(options.mediaOptions); 14122 14123 return media; 14124 }, 14125 14126 /** 14127 * Utility method to build the internal collection data structure (object) based on provided data 14128 * @param {Object} data 14129 * The data to build the internal collection from 14130 * @private 14131 */ 14132 _buildCollection: function (data) { 14133 //overriding this from RestBaseCollection because we need to pass in parentObj 14134 //because restUrl is finesse/api/User/<useri>/Media/<mediaId> 14135 //other objects like dialog have finesse/api/Dialog/<dialogId> 14136 14137 var i, object, objectId, dataArray; 14138 if (data && this.getProperty(data, this.getRestItemType()) !== null) { 14139 dataArray = Utilities.getArray(this.getProperty(data, this.getRestItemType())); 14140 for (i = 0; i < dataArray.length; i += 1) { 14141 14142 object = {}; 14143 object[this.getRestItemType()] = dataArray[i]; 14144 14145 //get id from object.id instead of uri, since uri is not there for some reason 14146 objectId = object[this.getRestItemType()].id;//this._extractId(object); 14147 14148 //create the Media Object 14149 this._collection[objectId] = new (this.getRestItemClass())({ 14150 id: objectId, 14151 data: object, 14152 parentObj: this._restObj 14153 }); 14154 this.length += 1; 14155 } 14156 } 14157 } 14158 }); 14159 14160 window.finesse = window.finesse || {}; 14161 window.finesse.restservices = window.finesse.restservices || {}; 14162 window.finesse.restservices.MediaList = MediaList; 14163 14164 return MediaList; 14165 }); 14166 14167 /** 14168 * JavaScript representation of the Finesse User object 14169 * 14170 * @requires finesse.clientservices.ClientServices 14171 * @requires Class 14172 * @requires finesse.FinesseBase 14173 * @requires finesse.restservices.RestBase 14174 */ 14175 14176 /** @private */ 14177 define('restservices/User',[ 14178 'restservices/RestBase', 14179 'restservices/Dialogs', 14180 'restservices/ClientLog', 14181 'restservices/Queues', 14182 'restservices/WrapUpReasons', 14183 'restservices/PhoneBooks', 14184 'restservices/Workflows', 14185 'restservices/UserMediaPropertiesLayout', 14186 'restservices/UserMediaPropertiesLayouts', 14187 'restservices/Media', 14188 'restservices/MediaList', 14189 'utilities/Utilities' 14190 ], 14191 function (RestBase, Dialogs, ClientLog, Queues, WrapUpReasons, PhoneBooks, Workflows, UserMediaPropertiesLayout, UserMediaPropertiesLayouts, Media, MediaList, Utilities) { 14192 14193 var User = RestBase.extend(/** @lends finesse.restservices.User.prototype */{ 14194 14195 _dialogs : null, 14196 _clientLogObj : null, 14197 _wrapUpReasons : null, 14198 _phoneBooks : null, 14199 _workflows : null, 14200 _mediaPropertiesLayout : null, 14201 _mediaPropertiesLayouts : null, 14202 _queues : null, 14203 media : null, 14204 mediaList : null, 14205 14206 /** 14207 * @class 14208 * The User represents a Finesse Agent or Supervisor. 14209 * 14210 * @param {Object} options 14211 * An object with the following properties:<ul> 14212 * <li><b>id:</b> The id of the object being constructed</li> 14213 * <li><b>onLoad(this): (optional)</b> callback handler for when the object is successfully loaded from the server</li> 14214 * <li><b>onChange(this): (optional)</b> callback handler for when an update notification of the object is received</li> 14215 * <li><b>onAdd(this): (optional)</b> callback handler for when a notification that the object is created is received</li> 14216 * <li><b>onDelete(this): (optional)</b> callback handler for when a notification that the object is deleted is received</li> 14217 * <li><b>onError(rsp): (optional)</b> callback handler for if loading of the object fails, invoked with the error response object:<ul> 14218 * <li><b>status:</b> {Number} The HTTP status code returned</li> 14219 * <li><b>content:</b> {String} Raw string of response</li> 14220 * <li><b>object:</b> {Object} Parsed object of response</li> 14221 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 14222 * <li><b>errorType:</b> {String} Type of error that was caught</li> 14223 * <li><b>errorMessage:</b> {String} Message associated with error</li> 14224 * </ul></li> 14225 * </ul></li> 14226 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 14227 * @augments finesse.restservices.RestBase 14228 * @constructs 14229 * @example 14230 * _user = new finesse.restservices.User({ 14231 * id: _id, 14232 * onLoad : _handleUserLoad, 14233 * onChange : _handleUserChange 14234 * }); 14235 **/ 14236 init: function (options) { 14237 this._super(options); 14238 }, 14239 14240 Callbacks: {}, 14241 14242 /** 14243 * @private 14244 * Gets the REST class for the current object - this is the User object. 14245 */ 14246 getRestClass: function () { 14247 return User; 14248 }, 14249 14250 /** 14251 * @private 14252 * Gets the REST type for the current object - this is a "User". 14253 */ 14254 getRestType: function () { 14255 return "User"; 14256 }, 14257 /** 14258 * @private 14259 * overloading this to return URI 14260 */ 14261 getXMPPNodePath: function () { 14262 return this.getRestUrl(); 14263 }, 14264 /** 14265 * @private 14266 * Returns whether this object supports subscriptions 14267 */ 14268 supportsSubscriptions: function () { 14269 return true; 14270 }, 14271 14272 /** 14273 * Getter for the firstName of this User. 14274 * @returns {String} 14275 * The firstName for this User 14276 */ 14277 getFirstName: function () { 14278 this.isLoaded(); 14279 return Utilities.convertNullToEmptyString(this.getData().firstName); 14280 }, 14281 14282 /** 14283 * Getter for the lastName of this User. 14284 * @returns {String} 14285 * The lastName for this User 14286 */ 14287 getLastName: function () { 14288 this.isLoaded(); 14289 return Utilities.convertNullToEmptyString(this.getData().lastName); 14290 }, 14291 14292 /** 14293 * Getter for the extension of this User. 14294 * @returns {String} 14295 * The extension, if any, of this User 14296 */ 14297 getExtension: function () { 14298 this.isLoaded(); 14299 return Utilities.convertNullToEmptyString(this.getData().extension); 14300 }, 14301 14302 /** 14303 * Getter for the id of the Team of this User 14304 * @returns {String} 14305 * The current (or last fetched) id of the Team of this User 14306 */ 14307 getTeamId: function () { 14308 this.isLoaded(); 14309 return this.getData().teamId; 14310 }, 14311 14312 /** 14313 * Getter for the name of the Team of this User 14314 * @returns {String} 14315 * The current (or last fetched) name of the Team of this User 14316 */ 14317 getTeamName: function () { 14318 this.isLoaded(); 14319 return this.getData().teamName; 14320 }, 14321 14322 /** 14323 * Is user an agent? 14324 * @returns {Boolean} True if user has role of agent, else false. 14325 */ 14326 hasAgentRole: function () { 14327 this.isLoaded(); 14328 return this.hasRole("Agent"); 14329 }, 14330 14331 /** 14332 * Is user a supervisor? 14333 * @returns {Boolean} True if user has role of supervisor, else false. 14334 */ 14335 hasSupervisorRole: function () { 14336 this.isLoaded(); 14337 return this.hasRole("Supervisor"); 14338 }, 14339 14340 /** 14341 * @private 14342 * Checks to see if user has "theRole" 14343 * @returns {Boolean} 14344 */ 14345 hasRole: function (theRole) { 14346 this.isLoaded(); 14347 var result = false, i, roles, len; 14348 14349 roles = this.getData().roles.role; 14350 len = roles.length; 14351 if (typeof roles === 'string') { 14352 if (roles === theRole) { 14353 result = true; 14354 } 14355 } else { 14356 for (i = 0; i < len ; i = i + 1) { 14357 if (roles[i] === theRole) { 14358 result = true; 14359 break; 14360 } 14361 } 14362 } 14363 14364 return result; 14365 }, 14366 14367 /** 14368 * Getter for the pending state of this User. 14369 * @returns {String} 14370 * The pending state of this User 14371 * @see finesse.restservices.User.States 14372 */ 14373 getPendingState: function () { 14374 this.isLoaded(); 14375 return Utilities.convertNullToEmptyString(this.getData().pendingState); 14376 }, 14377 14378 /** 14379 * Getter for the state of this User. 14380 * @returns {String} 14381 * The current (or last fetched) state of this User 14382 * @see finesse.restservices.User.States 14383 */ 14384 getState: function () { 14385 this.isLoaded(); 14386 return this.getData().state; 14387 }, 14388 14389 /** 14390 * Getter for the state change time of this User. 14391 * @returns {String} 14392 * The state change time of this User 14393 */ 14394 getStateChangeTime: function () { 14395 this.isLoaded(); 14396 return this.getData().stateChangeTime; 14397 }, 14398 14399 /** 14400 * Getter for the wrap-up mode of this User. 14401 * @see finesse.restservices.User.WrapUpMode 14402 * @returns {String} The wrap-up mode of this user that is a value of {@link finesse.restservices.User.WrapUpMode} 14403 */ 14404 getWrapUpOnIncoming: function () { 14405 this.isLoaded(); 14406 return this.getData().settings.wrapUpOnIncoming; 14407 }, 14408 14409 /** 14410 * Is User required to go into wrap-up? 14411 * @see finesse.restservices.User.WrapUpMode 14412 * @return {Boolean} 14413 * True if this agent is required to go into wrap-up. 14414 */ 14415 isWrapUpRequired: function () { 14416 return (this.getWrapUpOnIncoming() === User.WrapUpMode.REQUIRED || 14417 this.getWrapUpOnIncoming() === User.WrapUpMode.REQUIRED_WITH_WRAP_UP_DATA); 14418 }, 14419 14420 /** 14421 * Checks to see if the user is considered a mobile agent by checking for 14422 * the existence of the mobileAgent node. 14423 * @returns {Boolean} 14424 * True if this agent is a mobile agent. 14425 */ 14426 isMobileAgent: function () { 14427 this.isLoaded(); 14428 var ma = this.getData().mobileAgent; 14429 return ma !== null && typeof ma === "object"; 14430 }, 14431 14432 /** 14433 * Getter for the mobile agent work mode. 14434 * @returns {finesse.restservices.User.WorkMode} 14435 * If available, return the mobile agent work mode, otherwise null. 14436 */ 14437 getMobileAgentMode: function () { 14438 this.isLoaded(); 14439 if (this.isMobileAgent()) { 14440 return this.getData().mobileAgent.mode; 14441 } 14442 return null; 14443 }, 14444 14445 /** 14446 * Getter for the mobile agent dial number. 14447 * @returns {String} 14448 * If available, return the mobile agent dial number, otherwise null. 14449 */ 14450 getMobileAgentDialNumber: function () { 14451 this.isLoaded(); 14452 if (this.isMobileAgent()) { 14453 return this.getData().mobileAgent.dialNumber; 14454 } 14455 return null; 14456 }, 14457 14458 /** 14459 * Getter for a Dialogs collection object that is associated with User. 14460 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 14461 * applicable when Object has not been previously created). 14462 * @returns {finesse.restservices.Dialogs} 14463 * A Dialogs collection object. 14464 */ 14465 getDialogs: function (callbacks) { 14466 var options = callbacks || {}; 14467 options.parentObj = this; 14468 this.isLoaded(); 14469 14470 if (this._dialogs === null) { 14471 this._dialogs = new Dialogs(options); 14472 } 14473 14474 return this._dialogs; 14475 }, 14476 14477 /** 14478 * Getter for Media collection object that is associated with User. 14479 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 14480 * applicable when Object has not been previously created). 14481 * @returns {finesse.restservices.MediaList} 14482 * A Media Dialogs collection object. 14483 */ 14484 getMediaList: function (callbacks) { 14485 var options = callbacks || {}; 14486 options.parentObj = this; 14487 this.isLoaded(); 14488 14489 if (this.mediaList === null) { 14490 this.mediaList = new MediaList(options); 14491 } 14492 14493 return this.mediaList; 14494 }, 14495 14496 /** 14497 * @private 14498 * Getter for a ClientLog object that is associated with User. 14499 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 14500 * applicable when Object has not been previously created). 14501 * @returns {finesse.restservices.ClientLog} 14502 * A ClientLog collection object. 14503 */ 14504 getClientLog: function (callbacks) { 14505 var options = callbacks || {}; 14506 options.parentObj = this; 14507 this.isLoaded(); 14508 14509 if (this._clientLogObj === null) { 14510 this._clientLogObj = new ClientLog(options); 14511 } 14512 else { 14513 if(options.onLoad && typeof options.onLoad === "function") { 14514 options.onLoad(this._clientLogObj); 14515 } 14516 } 14517 return this._clientLogObj; 14518 }, 14519 14520 /** 14521 * Getter for a Queues collection object that is associated with User. 14522 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 14523 * applicable when Object has not been previously created). 14524 * @returns {finesse.restservices.Queues} 14525 * A Queues collection object. 14526 */ 14527 getQueues: function (callbacks) { 14528 var options = callbacks || {}; 14529 options.parentObj = this; 14530 this.isLoaded(); 14531 14532 if (this._queues === null) { 14533 this._queues = new Queues(options); 14534 } 14535 14536 return this._queues; 14537 }, 14538 14539 /** 14540 * Getter for a WrapUpReasons collection object that is associated with User. 14541 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 14542 * applicable when Object has not been previously created). 14543 * @returns {finesse.restservices.WrapUpReasons} 14544 * A WrapUpReasons collection object. 14545 */ 14546 getWrapUpReasons: function (callbacks) { 14547 var options = callbacks || {}; 14548 options.parentObj = this; 14549 this.isLoaded(); 14550 14551 if (this._wrapUpReasons === null) { 14552 this._wrapUpReasons = new WrapUpReasons(options); 14553 } 14554 14555 return this._wrapUpReasons; 14556 }, 14557 14558 /** 14559 * Getter for a PhoneBooks collection object that is associated with User. 14560 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 14561 * applicable when Object has not been previously created). 14562 * @returns {finesse.restservices.PhoneBooks} 14563 * A PhoneBooks collection object. 14564 */ 14565 getPhoneBooks: function (callbacks) { 14566 var options = callbacks || {}; 14567 options.parentObj = this; 14568 this.isLoaded(); 14569 14570 if (this._phoneBooks === null) { 14571 this._phoneBooks = new PhoneBooks(options); 14572 } 14573 14574 return this._phoneBooks; 14575 }, 14576 14577 /** 14578 * @private 14579 * Loads the Workflows collection object that is associated with User and 14580 * 'returns' them to the caller via the handlers. 14581 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 14582 * applicable when Object has not been previously created). 14583 * @see finesse.restservices.Workflow 14584 * @see finesse.restservices.Workflows 14585 * @see finesse.restservices.RestCollectionBase 14586 */ 14587 loadWorkflows: function (callbacks) { 14588 var options = callbacks || {}; 14589 options.parentObj = this; 14590 this.isLoaded(); 14591 14592 if (this._workflows === null) { 14593 this._workflows = new Workflows(options); 14594 } else { 14595 this._workflows.refresh(); 14596 } 14597 14598 }, 14599 14600 /** 14601 * Getter for a UserMediaPropertiesLayout object that is associated with User. 14602 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 14603 * applicable when Object has not been previously created). 14604 * @returns {finesse.restservices.UserMediaPropertiesLayout} 14605 * The UserMediaPropertiesLayout object associated with this user 14606 */ 14607 getMediaPropertiesLayout: function (callbacks) { 14608 var options = callbacks || {}; 14609 options.parentObj = this; 14610 options.id = this._id; 14611 14612 this.isLoaded(); 14613 if (this._mediaPropertiesLayout === null) { 14614 this._mediaPropertiesLayout = new UserMediaPropertiesLayout(options); 14615 } 14616 return this._mediaPropertiesLayout; 14617 }, 14618 14619 /** 14620 * Getter for a UserMediaPropertiesLayouts object that is associated with User. 14621 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers (only 14622 * applicable when Object has not been previously created). 14623 * @returns {finesse.restservices.UserMediaPropertiesLayout} 14624 * The UserMediaPropertiesLayout object associated with this user 14625 */ 14626 getMediaPropertiesLayouts: function (callbacks) { 14627 var options = callbacks || {}; 14628 options.parentObj = this; 14629 14630 this.isLoaded(); 14631 if (this._mediaPropertiesLayouts === null) { 14632 this._mediaPropertiesLayouts = new UserMediaPropertiesLayouts(options); 14633 } 14634 return this._mediaPropertiesLayouts; 14635 }, 14636 14637 /** 14638 * Getter for the supervised Teams this User (Supervisor) supervises, if any. 14639 * @see finesse.restservices.Team 14640 * @returns {Array} 14641 * An array of Teams supervised by this User (Supervisor) 14642 */ 14643 getSupervisedTeams: function () { 14644 this.isLoaded(); 14645 14646 try { 14647 return Utilities.getArray(this.getData().teams.Team); 14648 } catch (e) { 14649 return []; 14650 } 14651 14652 }, 14653 14654 /** 14655 * Perform an agent login for this user, associating him with the 14656 * specified extension. 14657 * @param {Object} params 14658 * An object containing properties for agent login. 14659 * @param {String} params.extension 14660 * The extension to associate with this user 14661 * @param {Object} [params.mobileAgent] 14662 * A mobile agent object containing the mode and dial number properties. 14663 * @param {finesse.interfaces.RequestHandlers} params.handlers 14664 * @see finesse.interfaces.RequestHandlers 14665 * @returns {finesse.restservices.User} 14666 * This User object, to allow cascading 14667 * @private 14668 */ 14669 _login: function (params) { 14670 var handlers, contentBody = {}, 14671 restType = this.getRestType(); 14672 14673 // Protect against null dereferencing. 14674 params = params || {}; 14675 14676 contentBody[restType] = { 14677 "state": User.States.LOGIN, 14678 "extension": params.extension 14679 }; 14680 14681 // Create mobile agent node if available. 14682 if (typeof params.mobileAgent === "object") { 14683 contentBody[restType].mobileAgent = { 14684 "mode": params.mobileAgent.mode, 14685 "dialNumber": params.mobileAgent.dialNumber 14686 }; 14687 } 14688 14689 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 14690 handlers = params.handlers || {}; 14691 14692 this.restRequest(this.getRestUrl(), { 14693 method: 'PUT', 14694 success: handlers.success, 14695 error: handlers.error, 14696 content: contentBody 14697 }); 14698 14699 return this; // Allow cascading 14700 }, 14701 14702 /** 14703 * Perform an agent login for this user, associating him with the 14704 * specified extension. 14705 * @param {String} extension 14706 * The extension to associate with this user 14707 * @param {finesse.interfaces.RequestHandlers} handlers 14708 * An object containing the handlers for the request 14709 * @returns {finesse.restservices.User} 14710 * This User object, to allow cascading 14711 */ 14712 login: function (extension, handlers) { 14713 this.isLoaded(); 14714 var params = { 14715 "extension": extension, 14716 "handlers": handlers 14717 }; 14718 return this._login(params); 14719 }, 14720 14721 /** 14722 * Perform an agent login for this user, associating him with the 14723 * specified extension. 14724 * @param {String} extension 14725 * The extension to associate with this user 14726 * @param {String} mode 14727 * The mobile agent work mode as defined in finesse.restservices.User.WorkMode. 14728 * @param {String} extension 14729 * The external dial number desired to be used by the mobile agent. 14730 * @param {finesse.interfaces.RequestHandlers} handlers 14731 * An object containing the handlers for the request 14732 * @returns {finesse.restservices.User} 14733 * This User object, to allow cascading 14734 */ 14735 loginMobileAgent: function (extension, mode, dialNumber, handlers) { 14736 this.isLoaded(); 14737 var params = { 14738 "extension": extension, 14739 "mobileAgent": { 14740 "mode": mode, 14741 "dialNumber": dialNumber 14742 }, 14743 "handlers": handlers 14744 }; 14745 return this._login(params); 14746 }, 14747 14748 /** 14749 * Perform an agent logout for this user. 14750 * @param {String} reasonCode 14751 * The reason this user is logging out. Pass null for no reason. 14752 * @param {finesse.interfaces.RequestHandlers} handlers 14753 * An object containing the handlers for the request 14754 * @returns {finesse.restservices.User} 14755 * This User object, to allow cascading 14756 */ 14757 logout: function (reasonCode, handlers) { 14758 return this.setState("LOGOUT", reasonCode, handlers); 14759 }, 14760 14761 /** 14762 * Set the state of the user. 14763 * @param {String} newState 14764 * The state you are setting 14765 * @param {ReasonCode} reasonCode 14766 * The reason this user is logging out. Pass null for no reason. 14767 * @param {finesse.interfaces.RequestHandlers} handlers 14768 * An object containing the handlers for the request 14769 * @see finesse.restservices.User.States 14770 * @returns {finesse.restservices.User} 14771 * This User object, to allow cascading 14772 */ 14773 setState: function (newState, reasonCode, handlers) { 14774 this.isLoaded(); 14775 14776 var options, contentBody = {}; 14777 14778 if (!reasonCode) { 14779 if(newState === "LOGOUT"){ 14780 contentBody[this.getRestType()] = { 14781 "state": newState, 14782 "logoutAllMedia": "true" 14783 }; 14784 } 14785 else{ 14786 contentBody[this.getRestType()] = { 14787 "state": newState 14788 }; 14789 } 14790 14791 } else { 14792 if(newState === "LOGOUT"){ 14793 contentBody[this.getRestType()] = { 14794 "state": newState, 14795 "reasonCodeId": reasonCode.id, 14796 "logoutAllMedia": "true" 14797 }; 14798 } 14799 else{ 14800 contentBody[this.getRestType()] = { 14801 "state": newState, 14802 "reasonCodeId": reasonCode.id 14803 }; 14804 } 14805 14806 } 14807 14808 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 14809 handlers = handlers || {}; 14810 14811 options = { 14812 method: 'PUT', 14813 success: handlers.success, 14814 error: handlers.error, 14815 content: contentBody 14816 }; 14817 14818 // After removing the selective 202 handling, we should be able to just use restRequest 14819 this.restRequest(this.getRestUrl(), options); 14820 14821 return this; // Allow cascading 14822 }, 14823 14824 /** 14825 * Make call to a particular phone number. 14826 * 14827 * @param {String} 14828 * The number to call 14829 * @param {finesse.interfaces.RequestHandlers} handlers 14830 * An object containing the handlers for the request 14831 * @returns {finesse.restservices.User} 14832 * This User object, to allow cascading 14833 */ 14834 makeCall: function (number, handlers) { 14835 this.isLoaded(); 14836 14837 this.getDialogs().createNewCallDialog(number, this.getExtension(), handlers); 14838 14839 return this; // Allow cascading 14840 }, 14841 14842 /** 14843 * Make a silent monitor call to a particular agent's phone number. 14844 * 14845 * @param {String} 14846 * The number to call 14847 * @param {finesse.interfaces.RequestHandlers} handlers 14848 * An object containing the handlers for the request 14849 * @returns {finesse.restservices.User} 14850 * This User object, to allow cascading 14851 */ 14852 makeSMCall: function (number, handlers) { 14853 this.isLoaded(); 14854 14855 var actionType = "SILENT_MONITOR"; 14856 14857 this.getDialogs().createNewSuperviseCallDialog(number, this.getExtension(), actionType, handlers); 14858 14859 return this; // Allow cascading 14860 }, 14861 14862 14863 /** 14864 * Make a silent monitor call to a particular agent's phone number. 14865 * 14866 * @param {String} 14867 * The number to call 14868 * @param {String} dialogUri 14869 * The associated dialog uri of SUPERVISOR_MONITOR call 14870 * @param {finesse.interfaces.RequestHandlers} handlers 14871 * An object containing the handlers for the request 14872 * @see finesse.restservices.dialog 14873 * @returns {finesse.restservices.User} 14874 * This User object, to allow cascading 14875 */ 14876 makeBargeCall:function (number, dialogURI, handlers) { 14877 this.isLoaded(); 14878 var actionType = "BARGE_CALL"; 14879 this.getDialogs().createNewBargeCall( this.getExtension(), number, actionType, dialogURI,handlers); 14880 14881 return this; // Allow cascading 14882 }, 14883 14884 /** 14885 * Returns true if the user's current state will result in a pending state change. A pending state 14886 * change is a request to change state that does not result in an immediate state change. For 14887 * example if an agent attempts to change to the NOT_READY state while in the TALKING state, the 14888 * agent will not change state until the call ends. 14889 * 14890 * The current set of states that result in pending state changes is as follows: 14891 * TALKING 14892 * HOLD 14893 * RESERVED_OUTBOUND_PREVIEW 14894 * @returns {Boolean} True if there is a pending state change. 14895 * @see finesse.restservices.User.States 14896 */ 14897 isPendingStateChange: function () { 14898 var state = this.getState(); 14899 return state && ((state === User.States.TALKING) || (state === User.States.HOLD) || (state === User.States.RESERVED_OUTBOUND_PREVIEW)); 14900 }, 14901 14902 /** 14903 * Returns true if the user's current state is WORK or WORK_READY. This is used so 14904 * that a pending state is not cleared when moving into wrap up (work) mode. 14905 * Note that we don't add this as a pending state, since changes while in wrap up 14906 * occur immediately (and we don't want any "pending state" to flash on screen. 14907 * 14908 * @see finesse.restservices.User.States 14909 * @returns {Boolean} True if user is in wrap-up mode. 14910 */ 14911 isWrapUp: function () { 14912 var state = this.getState(); 14913 return state && ((state === User.States.WORK) || (state === User.States.WORK_READY)); 14914 }, 14915 14916 /** 14917 * @private 14918 * Parses a uriString to retrieve the id portion 14919 * @param {String} uriString 14920 * @return {String} id 14921 */ 14922 _parseIdFromUriString : function (uriString) { 14923 return Utilities.getId(uriString); 14924 }, 14925 14926 /** 14927 * Gets the user's Reason Code label. 14928 * Works for both Not Ready and Logout reason codes 14929 * @return {String} the reason code label, or empty string if none 14930 */ 14931 getReasonCodeLabel : function () { 14932 this.isLoaded(); 14933 14934 if (this.getData().reasonCode) { 14935 return this.getData().reasonCode.label; 14936 } else { 14937 return ""; 14938 } 14939 }, 14940 14941 /** 14942 * Gets the user's Not Ready reason code. 14943 * @return {String} Reason Code Id, or undefined if not set or indeterminate 14944 */ 14945 getNotReadyReasonCodeId : function () { 14946 this.isLoaded(); 14947 14948 var reasoncodeIdResult, finesseServerReasonCodeId; 14949 finesseServerReasonCodeId = this.getData().reasonCodeId; 14950 14951 //FinesseServer will give "-l" => we will set to undefined (for convenience) 14952 if (finesseServerReasonCodeId !== "-1") { 14953 reasoncodeIdResult = finesseServerReasonCodeId; 14954 } 14955 14956 return reasoncodeIdResult; 14957 }, 14958 14959 /** 14960 * Performs a GET against the Finesse server looking up the reasonCodeId specified. 14961 * Note that there is no return value; use the success handler to process a 14962 * valid return. 14963 * @param {finesse.interfaces.RequestHandlers} handlers 14964 * An object containing the handlers for the request 14965 * @param {String} reasonCodeId The id for the reason code to lookup 14966 * 14967 */ 14968 getReasonCodeById : function (handlers, reasonCodeId) 14969 { 14970 var self = this, contentBody, reasonCode, url; 14971 contentBody = {}; 14972 14973 url = this.getRestUrl() + "/ReasonCode/" + reasonCodeId; 14974 this.restRequest(url, { 14975 method: 'GET', 14976 success: function (rsp) { 14977 reasonCode = { 14978 uri: rsp.object.ReasonCode.uri, 14979 label: rsp.object.ReasonCode.label, 14980 id: self._parseIdFromUriString(rsp.object.ReasonCode.uri) 14981 }; 14982 handlers.success(reasonCode); 14983 }, 14984 error: function (rsp) { 14985 handlers.error(rsp); 14986 }, 14987 content: contentBody 14988 }); 14989 }, 14990 14991 /** 14992 * Performs a GET against Finesse server retrieving all the specified type of reason codes. 14993 * @param {String} type (LOGOUT or NOT_READY) 14994 * @param {finesse.interfaces.RequestHandlers} handlers 14995 * An object containing the handlers for the request 14996 */ 14997 _getReasonCodesByType : function (type, handlers) 14998 { 14999 var self = this, contentBody = {}, url, reasonCodes, i, reasonCodeArray; 15000 15001 url = this.getRestUrl() + "/ReasonCodes?category=" + type; 15002 this.restRequest(url, { 15003 method: 'GET', 15004 success: function (rsp) { 15005 reasonCodes = []; 15006 15007 reasonCodeArray = rsp.object.ReasonCodes.ReasonCode; 15008 if (reasonCodeArray === undefined) { 15009 reasonCodes = undefined; 15010 } else if (reasonCodeArray[0] !== undefined) { 15011 for (i = 0; i < reasonCodeArray.length; i = i + 1) { 15012 reasonCodes[i] = { 15013 label: rsp.object.ReasonCodes.ReasonCode[i].label, 15014 id: self._parseIdFromUriString(rsp.object.ReasonCodes.ReasonCode[i].uri) 15015 }; 15016 } 15017 } else { 15018 reasonCodes[0] = { 15019 label: rsp.object.ReasonCodes.ReasonCode.label, 15020 id: self._parseIdFromUriString(rsp.object.ReasonCodes.ReasonCode.uri) 15021 }; 15022 } 15023 handlers.success(reasonCodes); 15024 }, 15025 error: function (rsp) { 15026 handlers.error(rsp); 15027 }, 15028 content: contentBody 15029 }); 15030 }, 15031 15032 /** 15033 * Performs a GET against Finesse server retrieving all the Signout reason codes. 15034 * Note that there is no return value; use the success handler to process a 15035 * valid return. 15036 * @param {finesse.interfaces.RequestHandlers} handlers 15037 * An object containing the handlers for the request 15038 */ 15039 getSignoutReasonCodes : function (handlers) 15040 { 15041 this._getReasonCodesByType("LOGOUT", handlers); 15042 }, 15043 15044 /** 15045 * Performs a GET against Finesse server retrieving all the Not Ready reason codes. 15046 * Note that there is no return value; use the success handler to process a 15047 * valid return. 15048 * @param {finesse.interfaces.RequestHandlers} handlers 15049 * An object containing the handlers for the request 15050 */ 15051 getNotReadyReasonCodes : function (handlers) 15052 { 15053 this._getReasonCodesByType("NOT_READY", handlers); 15054 } 15055 }); 15056 15057 User.States = /** @lends finesse.restservices.User.States.prototype */ { 15058 /** 15059 * User Login. Note that while this is an action, is not technically a state, since a 15060 * logged-in User will always be in a specific state (READY, NOT_READY, TALKING, etc.). 15061 */ 15062 LOGIN: "LOGIN", 15063 /** 15064 * User is logged out. 15065 */ 15066 LOGOUT: "LOGOUT", 15067 /** 15068 * User is not ready. Note that in UCCX implementations, the user is in this state while on a non-routed call. 15069 */ 15070 NOT_READY: "NOT_READY", 15071 /** 15072 * User is ready for calls. 15073 */ 15074 READY: "READY", 15075 /** 15076 * User has a call coming in, but has not answered it. 15077 */ 15078 RESERVED: "RESERVED", 15079 /** 15080 * User has an outbound call being made, but has not been connected to it. 15081 */ 15082 RESERVED_OUTBOUND: "RESERVED_OUTBOUND", 15083 /** 15084 * User has an outbound call's preview information being displayed, but has not acted on it. 15085 */ 15086 RESERVED_OUTBOUND_PREVIEW: "RESERVED_OUTBOUND_PREVIEW", 15087 /** 15088 * User is on a call. Note that in UCCX implementations, this is for routed calls only. 15089 */ 15090 TALKING: "TALKING", 15091 /** 15092 * User is on hold. Note that in UCCX implementations, the user remains in TALKING state while on hold. 15093 */ 15094 HOLD: "HOLD", 15095 /** 15096 * User is wrap-up/work mode. This mode is typically configured to time out, after which the user becomes NOT_READY. 15097 */ 15098 WORK: "WORK", 15099 /** 15100 * This is the same as WORK, except that after time out user becomes READY. 15101 */ 15102 WORK_READY: "WORK_READY", 15103 /** 15104 * @class Possible User state values. 15105 * @constructs 15106 */ 15107 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 15108 15109 }; 15110 15111 User.WorkMode = { /** @lends finesse.restservices.User.WorkMode.prototype */ 15112 /** 15113 * Mobile agent is connected (dialed) for each incoming call received. 15114 */ 15115 CALL_BY_CALL: "CALL_BY_CALL", 15116 /** 15117 * Mobile agent is connected (dialed) at login. 15118 */ 15119 NAILED_CONNECTION: "NAILED_CONNECTION", 15120 /** 15121 * @class Possible Mobile Agent Work Mode Types. 15122 * @constructs 15123 */ 15124 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 15125 15126 }; 15127 15128 User.WrapUpMode = { /** @lends finesse.restservices.User.WrapUpMode.prototype */ 15129 /** 15130 * Agent must go into wrap-up when call ends 15131 */ 15132 REQUIRED: "REQUIRED", 15133 /** 15134 * Agent must go into wrap-up when call ends and must enter wrap-up data 15135 */ 15136 REQUIRED_WITH_WRAP_UP_DATA: "REQUIRED_WITH_WRAP_UP_DATA", 15137 /** 15138 * Agent can choose to go into wrap-up on a call-by-call basis when the call ends 15139 */ 15140 OPTIONAL: "OPTIONAL", 15141 /** 15142 * Agent is not allowed to go into wrap-up when call ends. 15143 */ 15144 NOT_ALLOWED: "NOT_ALLOWED", 15145 /** 15146 * @class Possible Wrap-up Mode Types. 15147 * @constructs 15148 */ 15149 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 15150 15151 }; 15152 15153 window.finesse = window.finesse || {}; 15154 window.finesse.restservices = window.finesse.restservices || {}; 15155 window.finesse.restservices.User = User; 15156 15157 return User; 15158 }); 15159 15160 /** 15161 * JavaScript representation of the Finesse Users collection 15162 * object which contains a list of Users objects. 15163 * 15164 * @requires finesse.clientservices.ClientServices 15165 * @requires Class 15166 * @requires finesse.FinesseBase 15167 * @requires finesse.restservices.RestBase 15168 * @requires finesse.restservices.RestCollectionBase 15169 * @requires finesse.restservices.User 15170 */ 15171 15172 /** @private */ 15173 define('restservices/Users',[ 15174 'restservices/RestCollectionBase', 15175 'restservices/RestBase', 15176 'restservices/User' 15177 ], 15178 function (RestCollectionBase, RestBase, User) { 15179 15180 var Users = RestCollectionBase.extend(/** @lends finesse.restservices.Users.prototype */{ 15181 15182 /** 15183 * @class 15184 * JavaScript representation of a Users collection object. 15185 * While there is no method provided to retrieve all Users, this collection is 15186 * used to return the Users in a supervised Team. 15187 * @augments finesse.restservices.RestCollectionBase 15188 * @constructs 15189 * @see finesse.restservices.Team 15190 * @see finesse.restservices.User 15191 * @see finesse.restservices.User#getSupervisedTeams 15192 * @example 15193 * // Note: The following method gets an Array of Teams, not a Collection. 15194 * _teams = _user.getSupervisedTeams(); 15195 * if (_teams.length > 0) { 15196 * _team0Users = _teams[0].getUsers(); 15197 * } 15198 */ 15199 _fakeConstuctor: function () { 15200 /* This is here to hide the real init constructor from the public docs */ 15201 }, 15202 15203 /** 15204 * @private 15205 * JavaScript representation of the Finesse Users collection 15206 * object which contains a list of Users objects. 15207 * 15208 * @param {Object} options 15209 * An object with the following properties:<ul> 15210 * <li><b>id:</b> The id of the object being constructed</li> 15211 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 15212 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 15213 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 15214 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 15215 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 15216 * <li><b>status:</b> {Number} The HTTP status code returned</li> 15217 * <li><b>content:</b> {String} Raw string of response</li> 15218 * <li><b>object:</b> {Object} Parsed object of response</li> 15219 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 15220 * <li><b>errorType:</b> {String} Type of error that was caught</li> 15221 * <li><b>errorMessage:</b> {String} Message associated with error</li> 15222 * </ul></li> 15223 * </ul></li> 15224 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 15225 **/ 15226 init: function (options) { 15227 this._super(options); 15228 }, 15229 15230 /** 15231 * @private 15232 * Gets the REST class for the current object - this is the Users class. 15233 */ 15234 getRestClass: function () { 15235 return Users; 15236 }, 15237 15238 /** 15239 * @private 15240 * Gets the REST class for the objects that make up the collection. - this 15241 * is the User class. 15242 */ 15243 getRestItemClass: function () { 15244 return User; 15245 }, 15246 15247 /** 15248 * @private 15249 * Gets the REST type for the current object - this is a "Users". 15250 */ 15251 getRestType: function () { 15252 return "Users"; 15253 }, 15254 15255 /** 15256 * @private 15257 * Gets the REST type for the objects that make up the collection - this is "User". 15258 */ 15259 getRestItemType: function () { 15260 return "User"; 15261 }, 15262 15263 /** 15264 * @private 15265 * Gets the node path for the current object - this is the team Users node 15266 * @returns {String} The node path 15267 */ 15268 getXMPPNodePath: function () { 15269 return this.getRestUrl(); 15270 }, 15271 15272 /** 15273 * @private 15274 * Overloading _doGET to reroute the GET to /Team/id from /Team/id/Users 15275 * This needs to be done because the GET /Team/id/Users API is missing 15276 * @returns {Users} This Users (collection) object to allow cascading 15277 */ 15278 _doGET: function (handlers) { 15279 var _this = this; 15280 handlers = handlers || {}; 15281 // Only do this for /Team/id/Users 15282 if (this._restObj && this._restObj.getRestType() === "Team") { 15283 this._restObj._doGET({ 15284 success: function (rspObj) { 15285 // Making sure the response was a valid Team 15286 if (_this._restObj._validate(rspObj.object)) { 15287 // Shimmying the response to look like a Users collection by extracting it from the Team response 15288 rspObj.object[_this.getRestType()] = rspObj.object[_this._restObj.getRestType()][_this.getRestType().toLowerCase()]; 15289 handlers.success(rspObj); 15290 } else { 15291 handlers.error(rspObj); 15292 } 15293 }, 15294 error: handlers.error 15295 }); 15296 return this; // Allow cascading 15297 } else { 15298 return this._super(handlers); 15299 } 15300 }, 15301 15302 /** 15303 * @private 15304 * Override default to indicates that the collection doesn't support making 15305 * requests. 15306 */ 15307 supportsRequests: false, 15308 15309 /** 15310 * @private 15311 * Indicates that this collection handles the subscription for its items 15312 */ 15313 handlesItemSubscription: true, 15314 15315 /** 15316 * @private 15317 * Override default to indicate that we need to subscribe explicitly 15318 */ 15319 explicitSubscription: true 15320 15321 }); 15322 15323 window.finesse = window.finesse || {}; 15324 window.finesse.restservices = window.finesse.restservices || {}; 15325 window.finesse.restservices.Users = Users; 15326 15327 return Users; 15328 }); 15329 15330 /** 15331 * JavaScript representation of the Finesse Team Not Ready Reason Code Assignment object. 15332 * 15333 * @requires finesse.clientservices.ClientServices 15334 * @requires Class 15335 * @requires finesse.FinesseBase 15336 * @requires finesse.restservices.RestBase 15337 */ 15338 15339 /** @private */ 15340 define('restservices/TeamNotReadyReasonCode',['restservices/RestBase'], function (RestBase) { 15341 15342 var TeamNotReadyReasonCode = RestBase.extend( { 15343 15344 /** 15345 * @class 15346 * JavaScript representation of a Team Not Ready ReasonCode object. Also exposes 15347 * methods to operate on the object against the server. 15348 * 15349 * @param {Object} options 15350 * An object with the following properties:<ul> 15351 * <li><b>id:</b> The id of the object being constructed</li> 15352 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 15353 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 15354 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 15355 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 15356 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 15357 * <li><b>status:</b> {Number} The HTTP status code returned</li> 15358 * <li><b>content:</b> {String} Raw string of response</li> 15359 * <li><b>object:</b> {Object} Parsed object of response</li> 15360 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 15361 * <li><b>errorType:</b> {String} Type of error that was caught</li> 15362 * <li><b>errorMessage:</b> {String} Message associated with error</li> 15363 * </ul></li> 15364 * </ul></li> 15365 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 15366 * @constructs 15367 **/ 15368 init: function (options) { 15369 this._super(options); 15370 }, 15371 15372 /** 15373 * @private 15374 * Gets the REST class for the current object - this is the TeamNotReadyReasonCode class. 15375 * @returns {Object} The TeamNotReadyReasonCode class. 15376 */ 15377 getRestClass: function () { 15378 return TeamNotReadyReasonCode; 15379 }, 15380 15381 /** 15382 * @private 15383 * Gets the REST type for the current object - this is a "ReasonCode". 15384 * @returns {String} The ReasonCode string. 15385 */ 15386 getRestType: function () { 15387 return "ReasonCode"; 15388 }, 15389 15390 /** 15391 * @private 15392 * Override default to indicate that this object doesn't support making 15393 * requests. 15394 */ 15395 supportsRequests: false, 15396 15397 /** 15398 * @private 15399 * Override default to indicate that this object doesn't support subscriptions. 15400 */ 15401 supportsSubscriptions: false, 15402 15403 /** 15404 * Getter for the category. 15405 * @returns {String} The category. 15406 */ 15407 getCategory: function () { 15408 this.isLoaded(); 15409 return this.getData().category; 15410 }, 15411 15412 /** 15413 * Getter for the code. 15414 * @returns {String} The code. 15415 */ 15416 getCode: function () { 15417 this.isLoaded(); 15418 return this.getData().code; 15419 }, 15420 15421 /** 15422 * Getter for the label. 15423 * @returns {String} The label. 15424 */ 15425 getLabel: function () { 15426 this.isLoaded(); 15427 return this.getData().label; 15428 }, 15429 15430 /** 15431 * Getter for the forAll value. 15432 * @returns {String} The forAll. 15433 */ 15434 getForAll: function () { 15435 this.isLoaded(); 15436 return this.getData().forAll; 15437 }, 15438 15439 /** 15440 * Getter for the Uri value. 15441 * @returns {String} The Uri. 15442 */ 15443 getUri: function () { 15444 this.isLoaded(); 15445 return this.getData().uri; 15446 } 15447 15448 }); 15449 15450 window.finesse = window.finesse || {}; 15451 window.finesse.restservices = window.finesse.restservices || {}; 15452 window.finesse.restservices.TeamNotReadyReasonCode = TeamNotReadyReasonCode; 15453 15454 return TeamNotReadyReasonCode; 15455 }); 15456 15457 /** 15458 * JavaScript representation of the Finesse TeamNotReadyReasonCodes collection 15459 * object which contains a list of TeamNotReadyReasonCode objects. 15460 * 15461 * @requires finesse.clientservices.ClientServices 15462 * @requires Class 15463 * @requires finesse.FinesseBase 15464 * @requires finesse.restservices.RestBase 15465 * @requires finesse.restservices.Dialog 15466 * @requires finesse.restservices.RestCollectionBase 15467 */ 15468 15469 /** @private */ 15470 define('restservices/TeamNotReadyReasonCodes',[ 15471 'restservices/RestCollectionBase', 15472 'restservices/RestBase', 15473 'restservices/TeamNotReadyReasonCode' 15474 ], 15475 function (RestCollectionBase, RestBase, TeamNotReadyReasonCode) { 15476 15477 var TeamNotReadyReasonCodes = RestCollectionBase.extend( { 15478 15479 /** 15480 * @class 15481 * JavaScript representation of a TeamNotReadyReasonCodes collection object. Also exposes 15482 * methods to operate on the object against the server. 15483 * 15484 * @param {Object} options 15485 * An object with the following properties:<ul> 15486 * <li><b>id:</b> The id of the object being constructed</li> 15487 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 15488 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 15489 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 15490 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 15491 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 15492 * <li><b>status:</b> {Number} The HTTP status code returned</li> 15493 * <li><b>content:</b> {String} Raw string of response</li> 15494 * <li><b>object:</b> {Object} Parsed object of response</li> 15495 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 15496 * <li><b>errorType:</b> {String} Type of error that was caught</li> 15497 * <li><b>errorMessage:</b> {String} Message associated with error</li> 15498 * </ul></li> 15499 * </ul></li> 15500 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 15501 * @augments finesse.restservices.RestCollectionBase 15502 * @constructs 15503 **/ 15504 init: function (options) { 15505 this._super(options); 15506 }, 15507 15508 /** 15509 * @private 15510 * Gets the REST class for the current object - this is the TeamNotReadyReasonCodes class. 15511 */ 15512 getRestClass: function () { 15513 return TeamNotReadyReasonCodes; 15514 }, 15515 15516 /** 15517 * @private 15518 * Gets the REST class for the objects that make up the collection. - this 15519 * is the TeamNotReadyReasonCode class. 15520 */ 15521 getRestItemClass: function () { 15522 return TeamNotReadyReasonCode; 15523 }, 15524 15525 /** 15526 * @private 15527 * Gets the REST type for the current object - this is a "ReasonCodes". 15528 */ 15529 getRestType: function () { 15530 return "ReasonCodes"; 15531 }, 15532 15533 /** 15534 * @private 15535 * Overrides the parent class. Returns the url for the NotReadyReasonCodes resource 15536 */ 15537 getRestUrl: function () { 15538 // return ("/finesse/api/" + this.getRestType() + "?category=NOT_READY"); 15539 var restObj = this._restObj, 15540 restUrl = ""; 15541 //Prepend the base REST object if one was provided. 15542 //Otherwise prepend with the default webapp name. 15543 if (restObj instanceof RestBase) { 15544 restUrl += restObj.getRestUrl(); 15545 } 15546 else { 15547 restUrl += "/finesse/api"; 15548 } 15549 //Append the REST type. 15550 restUrl += "/ReasonCodes?category=NOT_READY"; 15551 //Append ID if it is not undefined, null, or empty. 15552 if (this._id) { 15553 restUrl += "/" + this._id; 15554 } 15555 return restUrl; 15556 }, 15557 15558 /** 15559 * @private 15560 * Gets the REST type for the objects that make up the collection - this is "ReasonCode". 15561 */ 15562 getRestItemType: function () { 15563 return "ReasonCode"; 15564 }, 15565 15566 /** 15567 * @private 15568 * Override default to indicates that the collection supports making 15569 * requests. 15570 */ 15571 supportsRequests: true, 15572 15573 /** 15574 * @private 15575 * Override default to indicate that this object doesn't support subscriptions. 15576 */ 15577 supportsRestItemSubscriptions: false, 15578 15579 /** 15580 * @private 15581 * Retrieve the Not Ready Reason Codes. 15582 * 15583 * @returns {TeamNotReadyReasonCodes} 15584 * This TeamNotReadyReasonCodes object to allow cascading. 15585 */ 15586 get: function () { 15587 // set loaded to false so it will rebuild the collection after the get 15588 this._loaded = false; 15589 // reset collection 15590 this._collection = {}; 15591 // perform get 15592 this._synchronize(); 15593 return this; 15594 }, 15595 15596 /** 15597 * @private 15598 * Set up the PutSuccessHandler for TeamNotReadyReasonCodes 15599 * @param {Object} reasonCodes 15600 * @param {String} contentBody 15601 * @param successHandler 15602 * @return {function} 15603 */ 15604 createPutSuccessHandler: function (reasonCodes, contentBody, successHandler) { 15605 return function (rsp) { 15606 // Update internal structure based on response. Here we 15607 // inject the contentBody from the PUT request into the 15608 // rsp.object element to mimic a GET as a way to take 15609 // advantage of the existing _processResponse method. 15610 rsp.object = contentBody; 15611 reasonCodes._processResponse(rsp); 15612 15613 //Remove the injected contentBody object before cascading response 15614 rsp.object = {}; 15615 15616 //cascade response back to consumer's response handler 15617 successHandler(rsp); 15618 }; 15619 }, 15620 15621 /** 15622 * @private 15623 * Perform the REST API PUT call to update the reason code assignments for the team 15624 * @param {string[]} newValues 15625 * @param handlers 15626 */ 15627 update: function (newValues, handlers) { 15628 this.isLoaded(); 15629 var contentBody = {}, contentBodyInner = [], i, innerObject = {}; 15630 15631 contentBody[this.getRestType()] = { 15632 }; 15633 15634 for (i in newValues) { 15635 if (newValues.hasOwnProperty(i)) { 15636 innerObject = { 15637 "uri": newValues[i] 15638 }; 15639 contentBodyInner.push(innerObject); 15640 } 15641 } 15642 15643 contentBody[this.getRestType()] = { 15644 "ReasonCode" : contentBodyInner 15645 }; 15646 15647 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 15648 handlers = handlers || {}; 15649 15650 this.restRequest(this.getRestUrl(), { 15651 method: 'PUT', 15652 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 15653 error: handlers.error, 15654 content: contentBody 15655 }); 15656 15657 return this; // Allow cascading 15658 } 15659 }); 15660 15661 window.finesse = window.finesse || {}; 15662 window.finesse.restservices = window.finesse.restservices || {}; 15663 window.finesse.restservices.TeamNotReadyReasonCodes = TeamNotReadyReasonCodes; 15664 15665 return TeamNotReadyReasonCodes; 15666 }); 15667 15668 /** 15669 * JavaScript representation of the Finesse Team Wrap Up Reason object. 15670 * 15671 * @requires finesse.clientservices.ClientServices 15672 * @requires Class 15673 * @requires finesse.FinesseBase 15674 * @requires finesse.restservices.RestBase 15675 */ 15676 /** @private */ 15677 define('restservices/TeamWrapUpReason',['restservices/RestBase'], function (RestBase) { 15678 15679 var TeamWrapUpReason = RestBase.extend({ 15680 15681 /** 15682 * @class 15683 * JavaScript representation of a TeamWrapUpReason object. Also exposes 15684 * methods to operate on the object against the server. 15685 * 15686 * @param {Object} options 15687 * An object with the following properties:<ul> 15688 * <li><b>id:</b> The id of the object being constructed</li> 15689 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 15690 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 15691 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 15692 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 15693 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 15694 * <li><b>status:</b> {Number} The HTTP status code returned</li> 15695 * <li><b>content:</b> {String} Raw string of response</li> 15696 * <li><b>object:</b> {Object} Parsed object of response</li> 15697 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 15698 * <li><b>errorType:</b> {String} Type of error that was caught</li> 15699 * <li><b>errorMessage:</b> {String} Message associated with error</li> 15700 * </ul></li> 15701 * </ul></li> 15702 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 15703 * @constructs 15704 **/ 15705 init: function (options) { 15706 this._super(options); 15707 }, 15708 15709 /** 15710 * @private 15711 * Gets the REST class for the current object - this is the TeamWrapUpReason class. 15712 * @returns {Object} The TeamWrapUpReason class. 15713 */ 15714 getRestClass: function () { 15715 return TeamWrapUpReason; 15716 }, 15717 15718 /** 15719 * @private 15720 * Gets the REST type for the current object - this is a "WrapUpReason". 15721 * @returns {String} The WrapUpReason string. 15722 */ 15723 getRestType: function () { 15724 return "WrapUpReason"; 15725 }, 15726 15727 /** 15728 * @private 15729 * Override default to indicate that this object doesn't support making 15730 * requests. 15731 */ 15732 supportsRequests: false, 15733 15734 /** 15735 * @private 15736 * Override default to indicate that this object doesn't support subscriptions. 15737 */ 15738 supportsSubscriptions: false, 15739 15740 /** 15741 * Getter for the label. 15742 * @returns {String} The label. 15743 */ 15744 getLabel: function () { 15745 this.isLoaded(); 15746 return this.getData().label; 15747 }, 15748 15749 /** 15750 * @private 15751 * Getter for the forAll value. 15752 * @returns {Boolean} True if global 15753 */ 15754 getForAll: function () { 15755 this.isLoaded(); 15756 return this.getData().forAll; 15757 }, 15758 15759 /** 15760 * @private 15761 * Getter for the Uri value. 15762 * @returns {String} The Uri. 15763 */ 15764 getUri: function () { 15765 this.isLoaded(); 15766 return this.getData().uri; 15767 } 15768 }); 15769 15770 window.finesse = window.finesse || {}; 15771 window.finesse.restservices = window.finesse.restservices || {}; 15772 window.finesse.restservices.TeamWrapUpReason = TeamWrapUpReason; 15773 15774 return TeamWrapUpReason; 15775 }); 15776 15777 /** 15778 * JavaScript representation of the Finesse Team Wrap-Up Reasons collection 15779 * object which contains a list of Wrap-Up Reasons objects. 15780 * 15781 * @requires finesse.clientservices.ClientServices 15782 * @requires Class 15783 * @requires finesse.FinesseBase 15784 * @requires finesse.restservices.RestBase 15785 * @requires finesse.restservices.Dialog 15786 * @requires finesse.restservices.RestCollectionBase 15787 */ 15788 /** @private */ 15789 define('restservices/TeamWrapUpReasons',[ 15790 'restservices/RestCollectionBase', 15791 'restservices/RestBase', 15792 'restservices/TeamWrapUpReason' 15793 ], 15794 function (RestCollectionBase, RestBase, TeamWrapUpReason) { 15795 15796 var TeamWrapUpReasons = RestCollectionBase.extend({ 15797 15798 /** 15799 * @class 15800 * JavaScript representation of a TeamWrapUpReasons collection object. Also exposes 15801 * methods to operate on the object against the server. 15802 * 15803 * @param {Object} options 15804 * An object with the following properties:<ul> 15805 * <li><b>id:</b> The id of the object being constructed</li> 15806 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 15807 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 15808 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 15809 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 15810 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 15811 * <li><b>status:</b> {Number} The HTTP status code returned</li> 15812 * <li><b>content:</b> {String} Raw string of response</li> 15813 * <li><b>object:</b> {Object} Parsed object of response</li> 15814 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 15815 * <li><b>errorType:</b> {String} Type of error that was caught</li> 15816 * <li><b>errorMessage:</b> {String} Message associated with error</li> 15817 * </ul></li> 15818 * </ul></li> 15819 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 15820 * @constructs 15821 **/ 15822 init: function (options) { 15823 this._super(options); 15824 }, 15825 15826 /** 15827 * @private 15828 * Gets the REST class for the current object - this is the TeamWrapUpReasons class. 15829 */ 15830 getRestClass: function () { 15831 return TeamWrapUpReasons; 15832 }, 15833 15834 /** 15835 * @private 15836 * Gets the REST class for the objects that make up the collection. - this 15837 * is the TeamWrapUpReason class. 15838 */ 15839 getRestItemClass: function () { 15840 return TeamWrapUpReason; 15841 }, 15842 15843 /** 15844 * @private 15845 * Gets the REST type for the current object - this is a "WrapUpReasons". 15846 */ 15847 getRestType: function () { 15848 return "WrapUpReasons"; 15849 }, 15850 15851 /** 15852 * @private 15853 * Gets the REST type for the objects that make up the collection - this is "WrapUpReason". 15854 */ 15855 getRestItemType: function () { 15856 return "WrapUpReason"; 15857 }, 15858 15859 /** 15860 * @private 15861 * Override default to indicates that the collection supports making 15862 * requests. 15863 */ 15864 supportsRequests: true, 15865 15866 /** 15867 * @private 15868 * Override default to indicate that this object doesn't support subscriptions. 15869 */ 15870 supportsRestItemSubscriptions: false, 15871 15872 /** 15873 * Retrieve the Team Wrap Up Reasons. 15874 * 15875 * @returns {finesse.restservices.TeamWrapUpReasons} 15876 * This TeamWrapUpReasons object to allow cascading. 15877 */ 15878 get: function () { 15879 // set loaded to false so it will rebuild the collection after the get 15880 this._loaded = false; 15881 // reset collection 15882 this._collection = {}; 15883 // perform get 15884 this._synchronize(); 15885 return this; 15886 }, 15887 15888 /** 15889 * Set up the PutSuccessHandler for TeamWrapUpReasons 15890 * @param {Object} wrapUpReasons 15891 * @param {Object} contentBody 15892 * @param successHandler 15893 * @returns response 15894 */ 15895 createPutSuccessHandler: function (wrapUpReasons, contentBody, successHandler) { 15896 return function (rsp) { 15897 // Update internal structure based on response. Here we 15898 // inject the contentBody from the PUT request into the 15899 // rsp.object element to mimic a GET as a way to take 15900 // advantage of the existing _processResponse method. 15901 rsp.object = contentBody; 15902 15903 wrapUpReasons._processResponse(rsp); 15904 15905 //Remove the injected contentBody object before cascading response 15906 rsp.object = {}; 15907 15908 //cascade response back to consumer's response handler 15909 successHandler(rsp); 15910 }; 15911 }, 15912 15913 /** 15914 * Perform the REST API PUT call to update the reason code assignments for the team 15915 * @param {String Array} newValues 15916 * @param handlers 15917 * @returns {Object} this 15918 */ 15919 update: function (newValues, handlers) { 15920 this.isLoaded(); 15921 var contentBody = {}, contentBodyInner = [], i, innerObject = {}; 15922 15923 contentBody[this.getRestType()] = { 15924 }; 15925 15926 for (i in newValues) { 15927 if (newValues.hasOwnProperty(i)) { 15928 innerObject = { 15929 "uri": newValues[i] 15930 }; 15931 contentBodyInner.push(innerObject); 15932 } 15933 } 15934 15935 contentBody[this.getRestType()] = { 15936 "WrapUpReason" : contentBodyInner 15937 }; 15938 15939 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 15940 handlers = handlers || {}; 15941 15942 this.restRequest(this.getRestUrl(), { 15943 method: 'PUT', 15944 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 15945 error: handlers.error, 15946 content: contentBody 15947 }); 15948 15949 return this; // Allow cascading 15950 } 15951 }); 15952 15953 window.finesse = window.finesse || {}; 15954 window.finesse.restservices = window.finesse.restservices || {}; 15955 window.finesse.restservices.TeamWrapUpReasons = TeamWrapUpReasons; 15956 15957 return TeamWrapUpReasons; 15958 }); 15959 15960 /** 15961 * JavaScript representation of a TeamSignOutReasonCode. 15962 * 15963 * @requires finesse.clientservices.ClientServices 15964 * @requires Class 15965 * @requires finesse.FinesseBase 15966 * @requires finesse.restservices.RestBase 15967 */ 15968 15969 /** @private */ 15970 define('restservices/TeamSignOutReasonCode',['restservices/RestBase'], function (RestBase) { 15971 var TeamSignOutReasonCode = RestBase.extend({ 15972 15973 /** 15974 * @class 15975 * JavaScript representation of a TeamSignOutReasonCode object. Also exposes 15976 * methods to operate on the object against the server. 15977 * 15978 * @param {Object} options 15979 * An object with the following properties:<ul> 15980 * <li><b>id:</b> The id of the object being constructed</li> 15981 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 15982 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 15983 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 15984 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 15985 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 15986 * <li><b>status:</b> {Number} The HTTP status code returned</li> 15987 * <li><b>content:</b> {String} Raw string of response</li> 15988 * <li><b>object:</b> {Object} Parsed object of response</li> 15989 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 15990 * <li><b>errorType:</b> {String} Type of error that was caught</li> 15991 * <li><b>errorMessage:</b> {String} Message associated with error</li> 15992 * </ul></li> 15993 * </ul></li> 15994 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 15995 * @constructs 15996 * @ignore 15997 **/ 15998 init: function (options) { 15999 this._super(options); 16000 }, 16001 16002 /** 16003 * @private 16004 * Gets the REST class for the current object - this is the TeamSignOutReasonCode class. 16005 * @returns {Object} The TeamSignOutReasonCode class. 16006 */ 16007 getRestClass: function () { 16008 return TeamSignOutReasonCode; 16009 }, 16010 16011 /** 16012 * @private 16013 * Gets the REST type for the current object - this is a "ReasonCode". 16014 * @returns {String} The ReasonCode string. 16015 */ 16016 getRestType: function () { 16017 return "ReasonCode"; 16018 }, 16019 16020 /** 16021 * @private 16022 * Override default to indicate that this object doesn't support making 16023 * requests. 16024 */ 16025 supportsRequests: false, 16026 16027 /** 16028 * @private 16029 * Override default to indicate that this object doesn't support subscriptions. 16030 */ 16031 supportsSubscriptions: false, 16032 16033 /** 16034 * Getter for the category. 16035 * @returns {String} The category. 16036 */ 16037 getCategory: function () { 16038 this.isLoaded(); 16039 return this.getData().category; 16040 }, 16041 16042 /** 16043 * Getter for the code. 16044 * @returns {String} The code. 16045 */ 16046 getCode: function () { 16047 this.isLoaded(); 16048 return this.getData().code; 16049 }, 16050 16051 /** 16052 * Getter for the label. 16053 * @returns {String} The label. 16054 */ 16055 getLabel: function () { 16056 this.isLoaded(); 16057 return this.getData().label; 16058 }, 16059 16060 /** 16061 * Getter for the forAll value. 16062 * @returns {String} The forAll. 16063 */ 16064 getForAll: function () { 16065 this.isLoaded(); 16066 return this.getData().forAll; 16067 }, 16068 16069 /** 16070 * Getter for the Uri value. 16071 * @returns {String} The Uri. 16072 */ 16073 getUri: function () { 16074 this.isLoaded(); 16075 return this.getData().uri; 16076 } 16077 16078 }); 16079 16080 window.finesse = window.finesse || {}; 16081 window.finesse.restservices = window.finesse.restservices || {}; 16082 window.finesse.restservices.TeamSignOutReasonCode = TeamSignOutReasonCode; 16083 16084 return TeamSignOutReasonCode; 16085 }); 16086 16087 /** 16088 * JavaScript representation of the TeamSignOutReasonCodes collection 16089 * object which contains a list of TeamSignOutReasonCode objects. 16090 * 16091 * @requires finesse.clientservices.ClientServices 16092 * @requires Class 16093 * @requires finesse.FinesseBase 16094 * @requires finesse.restservices.RestBase 16095 * @requires finesse.restservices.Dialog 16096 * @requires finesse.restservices.RestCollectionBase 16097 */ 16098 16099 /** @private */ 16100 define('restservices/TeamSignOutReasonCodes',[ 16101 'restservices/RestCollectionBase', 16102 'restservices/RestBase', 16103 'restservices/TeamSignOutReasonCode' 16104 ], 16105 function (RestCollectionBase, RestBase, TeamSignOutReasonCode) { 16106 16107 var TeamSignOutReasonCodes = RestCollectionBase.extend({ 16108 /** 16109 * @class 16110 * JavaScript representation of a TeamSignOutReasonCodes collection object. Also exposes 16111 * methods to operate on the object against the server. 16112 * 16113 * @param {Object} options 16114 * An object with the following properties:<ul> 16115 * <li><b>id:</b> The id of the object being constructed</li> 16116 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 16117 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 16118 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 16119 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 16120 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 16121 * <li><b>status:</b> {Number} The HTTP status code returned</li> 16122 * <li><b>content:</b> {String} Raw string of response</li> 16123 * <li><b>object:</b> {Object} Parsed object of response</li> 16124 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 16125 * <li><b>errorType:</b> {String} Type of error that was caught</li> 16126 * <li><b>errorMessage:</b> {String} Message associated with error</li> 16127 * </ul></li> 16128 * </ul></li> 16129 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 16130 * @constructs 16131 **/ 16132 init: function (options) { 16133 this._super(options); 16134 }, 16135 16136 /** 16137 * @private 16138 * Gets the REST class for the current object - this is the TeamSignOutReasonCodes class. 16139 */ 16140 getRestClass: function () { 16141 return TeamSignOutReasonCodes; 16142 }, 16143 16144 /** 16145 * @private 16146 * Gets the REST class for the objects that make up the collection. - this 16147 * is the TeamSignOutReasonCode class. 16148 */ 16149 getRestItemClass: function () { 16150 return TeamSignOutReasonCode; 16151 }, 16152 16153 /** 16154 * @private 16155 * Gets the REST type for the current object - this is a "ReasonCodes". 16156 */ 16157 getRestType: function () { 16158 return "ReasonCodes"; 16159 }, 16160 16161 /** 16162 * Overrides the parent class. Returns the url for the SignOutReasonCodes resource 16163 */ 16164 getRestUrl: function () { 16165 var restObj = this._restObj, restUrl = ""; 16166 16167 //Prepend the base REST object if one was provided. 16168 //Otherwise prepend with the default webapp name. 16169 if (restObj instanceof RestBase) { 16170 restUrl += restObj.getRestUrl(); 16171 } else { 16172 restUrl += "/finesse/api"; 16173 } 16174 //Append the REST type. 16175 restUrl += "/ReasonCodes?category=LOGOUT"; 16176 //Append ID if it is not undefined, null, or empty. 16177 if (this._id) { 16178 restUrl += "/" + this._id; 16179 } 16180 return restUrl; 16181 }, 16182 16183 /** 16184 * @private 16185 * Gets the REST type for the objects that make up the collection - this is "ReasonCode". 16186 */ 16187 getRestItemType: function () { 16188 return "ReasonCode"; 16189 }, 16190 16191 /** 16192 * @private 16193 * Override default to indicates that the collection supports making requests. 16194 */ 16195 supportsRequests: true, 16196 16197 /** 16198 * @private 16199 * Override default to indicates that the collection does not subscribe to its objects. 16200 */ 16201 supportsRestItemSubscriptions: false, 16202 16203 /** 16204 * Retrieve the Sign Out Reason Codes. 16205 * 16206 * @returns {finesse.restservices.TeamSignOutReasonCodes} 16207 * This TeamSignOutReasonCodes object to allow cascading. 16208 */ 16209 get: function () { 16210 // set loaded to false so it will rebuild the collection after the get 16211 this._loaded = false; 16212 // reset collection 16213 this._collection = {}; 16214 // perform get 16215 this._synchronize(); 16216 return this; 16217 }, 16218 16219 /* We only use PUT and GET on Reason Code team assignments 16220 * @param {Object} contact 16221 * @param {Object} contentBody 16222 * @param {Function} successHandler 16223 */ 16224 createPutSuccessHandler: function (contact, contentBody, successHandler) { 16225 return function (rsp) { 16226 // Update internal structure based on response. Here we 16227 // inject the contentBody from the PUT request into the 16228 // rsp.object element to mimic a GET as a way to take 16229 // advantage of the existing _processResponse method. 16230 rsp.object = contentBody; 16231 contact._processResponse(rsp); 16232 16233 //Remove the injected contentBody object before cascading response 16234 rsp.object = {}; 16235 16236 //cascade response back to consumer's response handler 16237 successHandler(rsp); 16238 }; 16239 }, 16240 16241 /** 16242 * Update - This should be all that is needed. 16243 * @param {Object} newValues 16244 * @param {Object} handlers 16245 * @returns {finesse.restservices.TeamSignOutReasonCodes} 16246 * This TeamSignOutReasonCodes object to allow cascading. 16247 */ 16248 update: function (newValues, handlers) { 16249 this.isLoaded(); 16250 var contentBody = {}, contentBodyInner = [], i, innerObject = {}; 16251 16252 contentBody[this.getRestType()] = { 16253 }; 16254 16255 for (i in newValues) { 16256 if (newValues.hasOwnProperty(i)) { 16257 innerObject = { 16258 "uri": newValues[i] 16259 }; 16260 contentBodyInner.push(innerObject); 16261 } 16262 } 16263 16264 contentBody[this.getRestType()] = { 16265 "ReasonCode" : contentBodyInner 16266 }; 16267 16268 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 16269 handlers = handlers || {}; 16270 16271 this.restRequest(this.getRestUrl(), { 16272 method: 'PUT', 16273 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 16274 error: handlers.error, 16275 content: contentBody 16276 }); 16277 16278 return this; // Allow cascading 16279 } 16280 16281 }); 16282 16283 window.finesse = window.finesse || {}; 16284 window.finesse.restservices = window.finesse.restservices || {}; 16285 window.finesse.restservices.TeamSignOutReasonCodes = TeamSignOutReasonCodes; 16286 16287 return TeamSignOutReasonCodes; 16288 }); 16289 16290 /** 16291 * JavaScript representation of the Finesse PhoneBook Assignment object. 16292 * 16293 * @requires finesse.clientservices.ClientServices 16294 * @requires Class 16295 * @requires finesse.FinesseBase 16296 * @requires finesse.restservices.RestBase 16297 */ 16298 16299 /** 16300 * The following comment prevents JSLint errors concerning undefined global variables. 16301 * It tells JSLint that these identifiers are defined elsewhere. 16302 */ 16303 /*jslint bitwise:true, browser:true, nomen:true, regexp:true, sloppy:true, white:true */ 16304 16305 /** The following comment is to prevent jslint errors about 16306 * using variables before they are defined. 16307 */ 16308 /*global $, jQuery, Handlebars, dojox, dojo, finesse */ 16309 16310 /** @private */ 16311 define('restservices/TeamPhoneBook',['restservices/RestBase'], function (RestBase) { 16312 var TeamPhoneBook = RestBase.extend({ 16313 16314 /** 16315 * @class 16316 * JavaScript representation of a PhoneBook object. Also exposes 16317 * methods to operate on the object against the server. 16318 * 16319 * @param {Object} options 16320 * An object with the following properties:<ul> 16321 * <li><b>id:</b> The id of the object being constructed</li> 16322 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 16323 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 16324 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 16325 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 16326 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 16327 * <li><b>status:</b> {Number} The HTTP status code returned</li> 16328 * <li><b>content:</b> {String} Raw string of response</li> 16329 * <li><b>object:</b> {Object} Parsed object of response</li> 16330 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 16331 * <li><b>errorType:</b> {String} Type of error that was caught</li> 16332 * <li><b>errorMessage:</b> {String} Message associated with error</li> 16333 * </ul></li> 16334 * </ul></li> 16335 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 16336 * @constructs 16337 **/ 16338 init: function (options) { 16339 this._super(options); 16340 }, 16341 16342 /** 16343 * @private 16344 * Gets the REST class for the current object - this is the PhoneBooks class. 16345 * @returns {Object} The PhoneBooks class. 16346 */ 16347 getRestClass: function () { 16348 return TeamPhoneBook; 16349 }, 16350 16351 /** 16352 * @private 16353 * Gets the REST type for the current object - this is a "PhoneBook". 16354 * @returns {String} The PhoneBook string. 16355 */ 16356 getRestType: function () { 16357 return "PhoneBook"; 16358 }, 16359 16360 /** 16361 * @private 16362 * Override default to indicate that this object doesn't support making 16363 * requests. 16364 */ 16365 supportsRequests: false, 16366 16367 /** 16368 * @private 16369 * Override default to indicate that this object doesn't support subscriptions. 16370 */ 16371 supportsSubscriptions: false, 16372 16373 /** 16374 * Getter for the name. 16375 * @returns {String} The name. 16376 */ 16377 getName: function () { 16378 this.isLoaded(); 16379 return this.getData().name; 16380 }, 16381 16382 /** 16383 * Getter for the Uri value. 16384 * @returns {String} The Uri. 16385 */ 16386 getUri: function () { 16387 this.isLoaded(); 16388 return this.getData().uri; 16389 } 16390 16391 }); 16392 16393 window.finesse = window.finesse || {}; 16394 window.finesse.restservices = window.finesse.restservices || {}; 16395 window.finesse.restservices.TeamPhoneBook = TeamPhoneBook; 16396 16397 return TeamPhoneBook; 16398 }); 16399 16400 /** 16401 * JavaScript representation of the Finesse PhoneBook Assignments collection 16402 * object which contains a list of Not Ready Reason Codes objects. 16403 * 16404 * @requires finesse.clientservices.ClientServices 16405 * @requires Class 16406 * @requires finesse.FinesseBase 16407 * @requires finesse.restservices.RestBase 16408 * @requires finesse.restservices.Dialog 16409 * @requires finesse.restservices.RestCollectionBase 16410 */ 16411 16412 /** 16413 * The following comment prevents JSLint errors concerning undefined global variables. 16414 * It tells JSLint that these identifiers are defined elsewhere. 16415 */ 16416 /*jslint bitwise:true, browser:true, nomen:true, regexp:true, sloppy:true, white:true */ 16417 16418 /** The following comment is to prevent jslint errors about 16419 * using variables before they are defined. 16420 */ 16421 /*global $, jQuery, Handlebars, dojox, dojo, finesse */ 16422 16423 /** @private */ 16424 define('restservices/TeamPhoneBooks',[ 16425 'restservices/RestCollectionBase', 16426 'restservices/RestBase', 16427 'restservices/TeamPhoneBook' 16428 ], 16429 function (RestCollectionBase, RestBase, TeamPhoneBook) { 16430 var TeamPhoneBooks = RestCollectionBase.extend({ 16431 16432 /** 16433 * @class 16434 * JavaScript representation of a TeamPhoneBooks collection object. Also exposes 16435 * methods to operate on the object against the server. 16436 * 16437 * @param {Object} options 16438 * An object with the following properties:<ul> 16439 * <li><b>id:</b> The id of the object being constructed</li> 16440 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 16441 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 16442 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 16443 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 16444 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 16445 * <li><b>status:</b> {Number} The HTTP status code returned</li> 16446 * <li><b>content:</b> {String} Raw string of response</li> 16447 * <li><b>object:</b> {Object} Parsed object of response</li> 16448 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 16449 * <li><b>errorType:</b> {String} Type of error that was caught</li> 16450 * <li><b>errorMessage:</b> {String} Message associated with error</li> 16451 * </ul></li> 16452 * </ul></li> 16453 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 16454 * @constructs 16455 **/ 16456 init: function (options) { 16457 this._super(options); 16458 }, 16459 16460 /** 16461 * @private 16462 * Gets the REST class for the current object - this is the TeamPhoneBooks class. 16463 */ 16464 getRestClass: function () { 16465 return TeamPhoneBooks; 16466 }, 16467 16468 /** 16469 * @private 16470 * Gets the REST class for the objects that make up the collection. - this 16471 * is the TeamPhoneBooks class. 16472 */ 16473 getRestItemClass: function () { 16474 return TeamPhoneBook; 16475 }, 16476 16477 /** 16478 * @private 16479 * Gets the REST type for the current object - this is a "ReasonCodes". 16480 */ 16481 getRestType: function () { 16482 return "PhoneBooks"; 16483 }, 16484 16485 /** 16486 * Overrides the parent class. Returns the url for the PhoneBooks resource 16487 */ 16488 getRestUrl: function () { 16489 // return ("/finesse/api/" + this.getRestType() + "?category=NOT_READY"); 16490 var restObj = this._restObj, 16491 restUrl = ""; 16492 //Prepend the base REST object if one was provided. 16493 if (restObj instanceof RestBase) { 16494 restUrl += restObj.getRestUrl(); 16495 } 16496 //Otherwise prepend with the default webapp name. 16497 else { 16498 restUrl += "/finesse/api"; 16499 } 16500 //Append the REST type. 16501 restUrl += "/PhoneBooks"; 16502 //Append ID if it is not undefined, null, or empty. 16503 if (this._id) { 16504 restUrl += "/" + this._id; 16505 } 16506 return restUrl; 16507 }, 16508 16509 /** 16510 * @private 16511 * Gets the REST type for the objects that make up the collection - this is "ReasonCode". 16512 */ 16513 getRestItemType: function () { 16514 return "PhoneBook"; 16515 }, 16516 16517 /** 16518 * @private 16519 * Override default to indicates that the collection supports making 16520 * requests. 16521 */ 16522 supportsRequests: true, 16523 16524 /** 16525 * @private 16526 * Override default to indicates that the collection subscribes to its objects. 16527 */ 16528 supportsRestItemSubscriptions: false, 16529 16530 /** 16531 * Retrieve the Not Ready Reason Codes. 16532 * 16533 * @returns {finesse.restservices.TeamPhoneBooks} 16534 * This TeamPhoneBooks object to allow cascading. 16535 */ 16536 get: function () { 16537 // set loaded to false so it will rebuild the collection after the get 16538 /** @private */ 16539 this._loaded = false; 16540 // reset collection 16541 /** @private */ 16542 this._collection = {}; 16543 // perform get 16544 this._synchronize(); 16545 return this; 16546 }, 16547 16548 /* We only use PUT and GET on Reason Code team assignments 16549 */ 16550 createPutSuccessHandler: function(contact, contentBody, successHandler){ 16551 return function (rsp) { 16552 // Update internal structure based on response. Here we 16553 // inject the contentBody from the PUT request into the 16554 // rsp.object element to mimic a GET as a way to take 16555 // advantage of the existing _processResponse method. 16556 rsp.object = contentBody; 16557 contact._processResponse(rsp); 16558 16559 //Remove the injected Contact object before cascading response 16560 rsp.object = {}; 16561 16562 //cascade response back to consumer's response handler 16563 successHandler(rsp); 16564 }; 16565 }, 16566 16567 /** 16568 * Update - This should be all that is needed. 16569 */ 16570 update: function (newValues, handlers) { 16571 this.isLoaded(); 16572 var contentBody = {}, contentBodyInner = [], i, innerObject; 16573 16574 contentBody[this.getRestType()] = { 16575 }; 16576 16577 for (i in newValues) { 16578 if (newValues.hasOwnProperty(i)) { 16579 innerObject = {}; 16580 innerObject = { 16581 "uri": newValues[i] 16582 }; 16583 contentBodyInner.push(innerObject); 16584 } 16585 } 16586 16587 contentBody[this.getRestType()] = { 16588 "PhoneBook" : contentBodyInner 16589 }; 16590 16591 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 16592 handlers = handlers || {}; 16593 16594 this.restRequest(this.getRestUrl(), { 16595 method: 'PUT', 16596 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 16597 error: handlers.error, 16598 content: contentBody 16599 }); 16600 16601 return this; // Allow cascading 16602 } 16603 16604 }); 16605 16606 window.finesse = window.finesse || {}; 16607 window.finesse.restservices = window.finesse.restservices || {}; 16608 window.finesse.restservices.TeamPhoneBooks = TeamPhoneBooks; 16609 16610 return TeamPhoneBooks; 16611 }); 16612 16613 /** 16614 * JavaScript representation of the Finesse LayoutConfig object 16615 * @requires ClientServices 16616 * @requires finesse.FinesseBase 16617 * @requires finesse.restservices.RestBase 16618 */ 16619 16620 /** @private */ 16621 define('restservices/LayoutConfig',['restservices/RestBase'], function (RestBase) { 16622 /** @private */ 16623 var LayoutConfig = RestBase.extend({ 16624 16625 /** 16626 * @class 16627 * JavaScript representation of a LayoutConfig object. Also exposes methods to operate 16628 * on the object against the server. 16629 * 16630 * @param {String} id 16631 * Not required... 16632 * @param {Object} callbacks 16633 * An object containing callbacks for instantiation and runtime 16634 * @param {Function} callbacks.onLoad(this) 16635 * Callback to invoke upon successful instantiation 16636 * @param {Function} callbacks.onLoadError(rsp) 16637 * Callback to invoke on instantiation REST request error 16638 * as passed by finesse.clientservices.ClientServices.ajax() 16639 * { 16640 * status: {Number} The HTTP status code returned 16641 * content: {String} Raw string of response 16642 * object: {Object} Parsed object of response 16643 * error: {Object} Wrapped exception that was caught 16644 * error.errorType: {String} Type of error that was caught 16645 * error.errorMessage: {String} Message associated with error 16646 * } 16647 * @param {Function} callbacks.onChange(this) 16648 * Callback to invoke upon successful update 16649 * @param {Function} callbacks.onError(rsp) 16650 * Callback to invoke on update error (refresh or event) 16651 * as passed by finesse.clientservices.ClientServices.ajax() 16652 * { 16653 * status: {Number} The HTTP status code returned 16654 * content: {String} Raw string of response 16655 * object: {Object} Parsed object of response 16656 * error: {Object} Wrapped exception that was caught 16657 * error.errorType: {String} Type of error that was caught 16658 * error.errorMessage: {String} Message associated with error 16659 * } 16660 * 16661 * @constructs 16662 */ 16663 init: function (callbacks) { 16664 this._super("", callbacks); 16665 //when post is performed and id is empty 16666 /*if (id === "") { 16667 this._loaded = true; 16668 }*/ 16669 this._layoutxml = {}; 16670 }, 16671 16672 /** 16673 * Returns REST class of LayoutConfig object 16674 */ 16675 getRestClass: function () { 16676 return LayoutConfig; 16677 }, 16678 16679 /** 16680 * The type of this REST object is LayoutConfig 16681 */ 16682 getRestType: function () { 16683 return "LayoutConfig"; 16684 }, 16685 16686 /** 16687 * Gets the REST URL of this object. 16688 * 16689 * If the parent has an id, the id is appended. 16690 * On occasions of POST, it will not have an id. 16691 */ 16692 getRestUrl: function () { 16693 var layoutUri = "/finesse/api/" + this.getRestType() + "/default"; 16694 /*if (this._id) { 16695 layoutUri = layoutUri + "/" + this._id; 16696 }*/ 16697 return layoutUri; 16698 }, 16699 16700 /** 16701 * This API does not support subscription 16702 */ 16703 supportsSubscriptions: false, 16704 16705 keepRestResponse: true, 16706 16707 16708 /** 16709 * Gets finesselayout.xml retrieved from the API call 16710 */ 16711 getLayoutxml: function () { 16712 this.isLoaded(); 16713 var layoutxml = this.getData().layoutxml; 16714 16715 // We need to unescape everything that is unallowed in xml so consumers don't have to deal with it (used in tandem with update()) 16716 layoutxml = layoutxml.replace(/&/g,"&"); 16717 16718 return layoutxml; 16719 }, 16720 16721 /** 16722 * Gets the type of this LayoutConfig object 16723 */ 16724 /* 16725 getType: function () { 16726 this.isLoaded(); 16727 return this.getData().type; 16728 },*/ 16729 16730 /** 16731 * Retrieve the LayoutConfig settings. 16732 * If the id is not provided the API call will fail. 16733 * @returns {LayoutConfig} 16734 * This LayoutConfig object to allow cascading. 16735 */ 16736 get: function () { 16737 this._synchronize(); 16738 return this; 16739 }, 16740 16741 /** 16742 * Closure handle updating of the internal data for the LayoutConfig object 16743 * upon a successful update (PUT) request before calling the intended 16744 * success handler provided by the consumer 16745 * 16746 * @param {Object} 16747 * layoutconfig Reference to this LayoutConfig object 16748 * @param {Object} 16749 * LayoutConfig Object that contains the settings to be 16750 * submitted in the api request 16751 * @param {Function} 16752 * successHandler The success handler specified by the consumer 16753 * of this object 16754 * @returns {LayoutConfig} This LayoutConfig object to allow cascading 16755 */ 16756 16757 createPutSuccessHandler: function (layoutconfig, contentBody, successHandler) { 16758 return function (rsp) { 16759 // Update internal structure based on response. Here we 16760 // inject the contentBody from the PUT request into the 16761 // rsp.object element to mimic a GET as a way to take 16762 // advantage of the existing _processResponse method. 16763 rsp.content = contentBody; 16764 rsp.object.LayoutConfig = {}; 16765 rsp.object.LayoutConfig.finesseLayout = contentBody; 16766 layoutconfig._processResponse(rsp); 16767 16768 //Remove the injected layoutConfig object before cascading response 16769 rsp.object.LayoutConfig = {}; 16770 16771 //cascade response back to consumer's response handler 16772 successHandler(rsp); 16773 }; 16774 }, 16775 16776 /** 16777 * Update LayoutConfig 16778 * @param {Object} finesselayout 16779 * The XML for FinesseLayout being stored 16780 * 16781 * @param {Object} handlers 16782 * An object containing callback handlers for the request. Optional. 16783 * @param {Function} options.success(rsp) 16784 * A callback function to be invoked for a successful request. 16785 * { 16786 * status: {Number} The HTTP status code returned 16787 * content: {String} Raw string of response 16788 * object: {Object} Parsed object of response 16789 * } 16790 * @param {Function} options.error(rsp) 16791 * A callback function to be invoked for an unsuccessful request. 16792 * { 16793 * status: {Number} The HTTP status code returned 16794 * content: {String} Raw string of response 16795 * object: {Object} Parsed object of response (HTTP errors) 16796 * error: {Object} Wrapped exception that was caught 16797 * error.errorType: {String} Type of error that was caught 16798 * error.errorMessage: {String} Message associated with error 16799 * } 16800 * @returns {finesse.restservices.LayoutConfig} 16801 * This LayoutConfig object to allow cascading 16802 */ 16803 16804 update: function (layoutxml, handlers) { 16805 this.isLoaded(); 16806 16807 16808 var contentBody = {}, 16809 //Created a regex (re) to scoop out just the gadget URL (without before and after whitespace characters) 16810 re = /<gadget>\s*(\S+)\s*<\/gadget>/g; 16811 16812 // We need to escape everything that is unallowed in xml so consumers don't have to deal with it (used in tandem with getLayoutxml()) 16813 layoutxml = layoutxml.replace(/&(?!amp;)/g, "&"); 16814 16815 //used the regex (re) to the update and save the layoutxml with the improved gadget URL (without before/after whitespace) 16816 layoutxml = layoutxml.replace(re, "<gadget>$1</gadget>"); 16817 16818 contentBody[this.getRestType()] = { 16819 "layoutxml": finesse.utilities.Utilities.translateHTMLEntities(layoutxml, true) 16820 }; 16821 16822 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 16823 handlers = handlers || {}; 16824 16825 this.restRequest(this.getRestUrl(), { 16826 method: 'PUT', 16827 success: this.createPutSuccessHandler(this, layoutxml, handlers.success), 16828 error: handlers.error, 16829 content: contentBody 16830 }); 16831 16832 return this; // Allow cascading 16833 } 16834 16835 /** 16836 *TODO createPostSuccessHandler needs to be debugged to make it working 16837 * Closure handle creating new LayoutConfig object 16838 * upon a successful create (POST) request before calling the intended 16839 * success handler provided by the consumer 16840 * 16841 * @param {Object} 16842 * layoutconfig Reference to this LayoutConfig object 16843 * @param {Object} 16844 * LayoutConfig Object that contains the settings to be 16845 * submitted in the api request 16846 * @param {Function} 16847 * successHandler The success handler specified by the consumer 16848 * of this object 16849 * @returns {finesse.restservices.LayoutConfig} This LayoutConfig object to allow cascading 16850 */ 16851 /* 16852 createPostSuccessHandler: function (layoutconfig, contentBody, successHandler) { 16853 return function (rsp) { 16854 16855 rsp.object = contentBody; 16856 layoutconfig._processResponse(rsp); 16857 16858 //Remove the injected layoutConfig object before cascading response 16859 rsp.object = {}; 16860 16861 //cascade response back to consumer's response handler 16862 successHandler(rsp); 16863 }; 16864 }, */ 16865 16866 /** 16867 * TODO Method needs to be debugged to make POST working 16868 * Add LayoutConfig 16869 * @param {Object} finesselayout 16870 * The XML for FinesseLayout being stored 16871 * 16872 * @param {Object} handlers 16873 * An object containing callback handlers for the request. Optional. 16874 * @param {Function} options.success(rsp) 16875 * A callback function to be invoked for a successful request. 16876 * { 16877 * status: {Number} The HTTP status code returned 16878 * content: {String} Raw string of response 16879 * object: {Object} Parsed object of response 16880 * } 16881 * @param {Function} options.error(rsp) 16882 * A callback function to be invoked for an unsuccessful request. 16883 * { 16884 * status: {Number} The HTTP status code returned 16885 * content: {String} Raw string of response 16886 * object: {Object} Parsed object of response (HTTP errors) 16887 * error: {Object} Wrapped exception that was caught 16888 * error.errorType: {String} Type of error that was caught 16889 * error.errorMessage: {String} Message associated with error 16890 * } 16891 * @returns {finesse.restservices.LayoutConfig} 16892 * This LayoutConfig object to allow cascading 16893 */ 16894 /* 16895 add: function (layoutxml, handlers) { 16896 this.isLoaded(); 16897 var contentBody = {}; 16898 16899 16900 contentBody[this.getRestType()] = { 16901 "layoutxml": layoutxml, 16902 "type": "current" 16903 }; 16904 16905 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 16906 handlers = handlers || {}; 16907 16908 this.restRequest(this.getRestUrl(), { 16909 method: 'POST', 16910 success: this.createPostSuccessHandler(this, contentBody, handlers.success), 16911 error: handlers.error, 16912 content: contentBody 16913 }); 16914 16915 return this; // Allow cascading 16916 } */ 16917 }); 16918 16919 window.finesse = window.finesse || {}; 16920 window.finesse.restservices = window.finesse.restservices || {}; 16921 window.finesse.restservices.LayoutConfig = LayoutConfig; 16922 16923 return LayoutConfig; 16924 16925 }); 16926 16927 /** 16928 * JavaScript representation of the Finesse LayoutConfig object for a Team. 16929 * 16930 * @requires finesse.clientservices.ClientServices 16931 * @requires Class 16932 * @requires finesse.FinesseBase 16933 * @requires finesse.restservices.RestBase 16934 * @requires finesse.utilities.Utilities 16935 * @requires finesse.restservices.LayoutConfig 16936 */ 16937 16938 /** The following comment is to prevent jslint errors about 16939 * using variables before they are defined. 16940 */ 16941 /*global Exception */ 16942 16943 /** @private */ 16944 define('restservices/TeamLayoutConfig',[ 16945 'restservices/RestBase', 16946 'utilities/Utilities', 16947 'restservices/LayoutConfig' 16948 ], 16949 function (RestBase, Utilities, LayoutConfig) { 16950 16951 var TeamLayoutConfig = RestBase.extend({ 16952 // Keep the restresponse so we can parse the layoutxml out of it in getLayoutXML() 16953 keepRestResponse: true, 16954 16955 /** 16956 * @class 16957 * JavaScript representation of a LayoutConfig object for a Team. Also exposes 16958 * methods to operate on the object against the server. 16959 * 16960 * @param {Object} options 16961 * An object with the following properties:<ul> 16962 * <li><b>id:</b> The id of the object being constructed</li> 16963 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 16964 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 16965 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 16966 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 16967 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 16968 * <li><b>status:</b> {Number} The HTTP status code returned</li> 16969 * <li><b>content:</b> {String} Raw string of response</li> 16970 * <li><b>object:</b> {Object} Parsed object of response</li> 16971 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 16972 * <li><b>errorType:</b> {String} Type of error that was caught</li> 16973 * <li><b>errorMessage:</b> {String} Message associated with error</li> 16974 * </ul></li> 16975 * </ul></li> 16976 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 16977 * @constructs 16978 **/ 16979 init: function (options) { 16980 this._super(options); 16981 }, 16982 16983 /** 16984 * @private 16985 * Gets the REST class for the current object - this is the LayoutConfigs class. 16986 * @returns {Object} The LayoutConfigs class. 16987 */ 16988 getRestClass: function () { 16989 return TeamLayoutConfig; 16990 }, 16991 16992 /** 16993 * @private 16994 * Gets the REST type for the current object - this is a "LayoutConfig". 16995 * @returns {String} The LayoutConfig string. 16996 */ 16997 getRestType: function () { 16998 return "TeamLayoutConfig"; 16999 }, 17000 17001 /** 17002 * @private 17003 * Override default to indicate that this object doesn't support making 17004 * requests. 17005 */ 17006 supportsRequests: false, 17007 17008 /** 17009 * @private 17010 * Override default to indicate that this object doesn't support subscriptions. 17011 */ 17012 supportsSubscriptions: false, 17013 17014 /** 17015 * Getter for the category. 17016 * @returns {String} The category. 17017 */ 17018 getLayoutXML: function () { 17019 this.isLoaded(); 17020 var layoutxml = this.getData().layoutxml; 17021 17022 // We need to unescape everything that is unallowed in xml so consumers don't have to deal with it (used in tandem with put()) 17023 layoutxml = layoutxml.replace(/&/g,"&"); 17024 17025 return layoutxml; 17026 }, 17027 17028 /** 17029 * Getter for the code. 17030 * @returns {String} The code. 17031 */ 17032 getUseDefault: function () { 17033 this.isLoaded(); 17034 return this.getData().useDefault; 17035 }, 17036 17037 /** 17038 * Retrieve the TeamLayoutConfig. 17039 * 17040 * @returns {finesse.restservices.TeamLayoutConfig} 17041 */ 17042 get: function () { 17043 // this._id is needed, but is not used in this object.. we're overriding getRestUrl anyway 17044 this._id = "0"; 17045 // set loaded to false so it will rebuild the collection after the get 17046 this._loaded = false; 17047 // reset collection 17048 this._collection = {}; 17049 // perform get 17050 this._synchronize(); 17051 return this; 17052 }, 17053 17054 createPutSuccessHandler: function(contact, contentBody, successHandler){ 17055 return function (rsp) { 17056 // Update internal structure based on response. Here we 17057 // inject the contentBody from the PUT request into the 17058 // rsp.object element to mimic a GET as a way to take 17059 // advantage of the existing _processResponse method. 17060 rsp.object = contentBody; 17061 contact._processResponse(rsp); 17062 17063 //Remove the injected Contact object before cascading response 17064 rsp.object = {}; 17065 17066 //cascade response back to consumer's response handler 17067 successHandler(rsp); 17068 }; 17069 }, 17070 17071 put: function (newValues, handlers) { 17072 // this._id is needed, but is not used in this object.. we're overriding getRestUrl anyway 17073 this._id = "0"; 17074 this.isLoaded(); 17075 17076 // We need to escape everything that is unallowed in xml so consumers don't have to deal with it (used in tandem with getLayoutxml()) 17077 var layoutxml = newValues.layoutXML.replace(/&(?!amp;)/g, "&"), 17078 contentBody = {}, 17079 //Created a regex (re) to scoop out just the gadget URL (without before and after whitespace characters) 17080 re = /<gadget>\s*(\S+)\s*<\/gadget>/g; 17081 17082 //used the regex (re) to the update and save the layoutxml with the improved gadget URL (without before/after whitespace) 17083 layoutxml = layoutxml.replace(re, "<gadget>$1</gadget>"); 17084 17085 contentBody[this.getRestType()] = { 17086 "useDefault": newValues.useDefault, 17087 // The LayoutConfig restservice javascript class only translates ampersands, so we'll do that also 17088 "layoutxml": finesse.utilities.Utilities.translateHTMLEntities(layoutxml, true) 17089 }; 17090 17091 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 17092 handlers = handlers || {}; 17093 17094 this.restRequest(this.getRestUrl(), { 17095 method: 'PUT', 17096 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 17097 error: handlers.error, 17098 content: contentBody 17099 }); 17100 17101 return this; // Allow cascading 17102 }, 17103 17104 getRestUrl: function(){ 17105 // return team's url + /LayoutConfig 17106 // eg: /api/Team/1/LayoutConfig 17107 if(this._restObj === undefined){ 17108 throw new Exception("TeamLayoutConfig instances must have a parent team object."); 17109 } 17110 return this._restObj.getRestUrl() + '/LayoutConfig'; 17111 } 17112 17113 }); 17114 17115 window.finesse = window.finesse || {}; 17116 window.finesse.restservices = window.finesse.restservices || {}; 17117 window.finesse.restservices.TeamLayoutConfig = TeamLayoutConfig; 17118 17119 return TeamLayoutConfig; 17120 }); 17121 17122 /** 17123 * JavaScript representation of a TeamWorkflow. 17124 * 17125 * @requires finesse.clientservices.ClientServices 17126 * @requires Class 17127 * @requires finesse.FinesseBase 17128 * @requires finesse.restservices.RestBase 17129 */ 17130 /** @private */ 17131 define('restservices/TeamWorkflow',['restservices/RestBase'], function (RestBase) { 17132 17133 var TeamWorkflow = RestBase.extend({ 17134 17135 /** 17136 * @class 17137 * JavaScript representation of a TeamWorkflow object. Also exposes 17138 * methods to operate on the object against the server. 17139 * 17140 * @param {Object} options 17141 * An object with the following properties:<ul> 17142 * <li><b>id:</b> The id of the object being constructed</li> 17143 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 17144 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 17145 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 17146 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 17147 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 17148 * <li><b>status:</b> {Number} The HTTP status description returned</li> 17149 * <li><b>content:</b> {String} Raw string of response</li> 17150 * <li><b>object:</b> {Object} Parsed object of response</li> 17151 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 17152 * <li><b>errorType:</b> {String} Type of error that was caught</li> 17153 * <li><b>errorMessage:</b> {String} Message associated with error</li> 17154 * </ul></li> 17155 * </ul></li> 17156 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 17157 * @constructs 17158 **/ 17159 init: function (options) { 17160 this._super(options); 17161 }, 17162 17163 /** 17164 * @private 17165 * Gets the REST class for the current object - this is the TeamWorkflow class. 17166 * @returns {Object} The TeamWorkflow class. 17167 */ 17168 getRestClass: function () { 17169 return TeamWorkflow; 17170 }, 17171 17172 /** 17173 * @private 17174 * Gets the REST type for the current object - this is a "Workflow". 17175 * @returns {String} The Workflow string. 17176 */ 17177 getRestType: function () { 17178 return "Workflow"; 17179 }, 17180 17181 /** 17182 * @private 17183 * Override default to indicate that this object doesn't support making 17184 * requests. 17185 */ 17186 supportsRequests: false, 17187 17188 /** 17189 * @private 17190 * Override default to indicate that this object doesn't support subscriptions. 17191 */ 17192 supportsSubscriptions: false, 17193 17194 /** 17195 * Getter for the name. 17196 * @returns {String} The name. 17197 */ 17198 getName: function () { 17199 this.isLoaded(); 17200 return this.getData().name; 17201 }, 17202 17203 /** 17204 * Getter for the description. 17205 * @returns {String} The description. 17206 */ 17207 getDescription: function () { 17208 this.isLoaded(); 17209 return this.getData().description; 17210 }, 17211 17212 /** 17213 * Getter for the Uri value. 17214 * @returns {String} The Uri. 17215 */ 17216 getUri: function () { 17217 this.isLoaded(); 17218 return this.getData().uri; 17219 } 17220 17221 }); 17222 17223 window.finesse = window.finesse || {}; 17224 window.finesse.restservices = window.finesse.restservices || {}; 17225 window.finesse.restservices.TeamWorkflow = TeamWorkflow; 17226 17227 return TeamWorkflow; 17228 }); 17229 17230 /** 17231 * JavaScript representation of the TeamWorkflows collection 17232 * object which contains a list of TeamWorkflow objects. 17233 * 17234 * @requires finesse.clientservices.ClientServices 17235 * @requires Class 17236 * @requires finesse.FinesseBase 17237 * @requires finesse.restservices.RestBase 17238 * @requires finesse.restservices.Dialog 17239 * @requires finesse.restservices.RestCollectionBase 17240 */ 17241 /** @private */ 17242 define('restservices/TeamWorkflows',[ 17243 'restservices/RestCollectionBase', 17244 'restservices/TeamWorkflow', 17245 'restservices/RestBase' 17246 ], 17247 function (RestCollectionBase, TeamWorkflow, RestBase) { 17248 17249 var TeamWorkflows = RestCollectionBase.extend({ 17250 17251 /** 17252 * @class 17253 * JavaScript representation of a TeamWorkflows collection object. Also exposes 17254 * methods to operate on the object against the server. 17255 * 17256 * @param {Object} options 17257 * An object with the following properties:<ul> 17258 * <li><b>id:</b> The id of the object being constructed</li> 17259 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 17260 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 17261 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 17262 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 17263 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 17264 * <li><b>status:</b> {Number} The HTTP status code returned</li> 17265 * <li><b>content:</b> {String} Raw string of response</li> 17266 * <li><b>object:</b> {Object} Parsed object of response</li> 17267 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 17268 * <li><b>errorType:</b> {String} Type of error that was caught</li> 17269 * <li><b>errorMessage:</b> {String} Message associated with error</li> 17270 * </ul></li> 17271 * </ul></li> 17272 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 17273 * @constructs 17274 **/ 17275 init: function (options) { 17276 this._super(options); 17277 }, 17278 17279 /** 17280 * @private 17281 * Gets the REST class for the current object - this is the TeamWorkflows class. 17282 */ 17283 getRestClass: function () { 17284 return TeamWorkflows; 17285 }, 17286 17287 /** 17288 * @private 17289 * Gets the REST class for the objects that make up the collection. - this 17290 * is the TeamWorkflow class. 17291 */ 17292 getRestItemClass: function () { 17293 return TeamWorkflow; 17294 }, 17295 17296 /** 17297 * @private 17298 * Gets the REST type for the current object - this is a "Workflows". 17299 */ 17300 getRestType: function () { 17301 return "Workflows"; 17302 }, 17303 17304 /** 17305 * Overrides the parent class. Returns the url for the Workflows resource 17306 */ 17307 getRestUrl: function () { 17308 var restObj = this._restObj, restUrl = ""; 17309 17310 //Prepend the base REST object if one was provided. 17311 //Otherwise prepend with the default webapp name. 17312 if (restObj instanceof RestBase) { 17313 restUrl += restObj.getRestUrl(); 17314 } else { 17315 restUrl += "/finesse/api/Team"; 17316 } 17317 //Append ID if it is not undefined, null, or empty. 17318 if (this._id) { 17319 restUrl += "/" + this._id; 17320 } 17321 //Append the REST type. 17322 restUrl += "/Workflows"; 17323 17324 return restUrl; 17325 }, 17326 17327 /** 17328 * @private 17329 * Gets the REST type for the objects that make up the collection - this is "Workflow". 17330 */ 17331 getRestItemType: function () { 17332 return "Workflow"; 17333 }, 17334 17335 /** 17336 * @private 17337 * Override default to indicates that the collection supports making requests. 17338 */ 17339 supportsRequests: true, 17340 17341 /** 17342 * @private 17343 * Override default to indicates that the collection does not subscribe to its objects. 17344 */ 17345 supportsRestItemSubscriptions: false, 17346 17347 /** 17348 * Retrieve the Sign Out Reason Codes. 17349 * 17350 * @returns {finesse.restservices.TeamWorkflows} 17351 * This TeamWorkflows object to allow cascading. 17352 */ 17353 get: function () { 17354 // set loaded to false so it will rebuild the collection after the get 17355 this._loaded = false; 17356 // reset collection 17357 this._collection = {}; 17358 // perform get 17359 this._synchronize(); 17360 return this; 17361 }, 17362 17363 /* We only use PUT and GET on Reason Code team assignments 17364 * @param {Object} contact 17365 * @param {Object} contentBody 17366 * @param {Function} successHandler 17367 */ 17368 createPutSuccessHandler: function (contact, contentBody, successHandler) { 17369 return function (rsp) { 17370 // Update internal structure based on response. Here we 17371 // inject the contentBody from the PUT request into the 17372 // rsp.object element to mimic a GET as a way to take 17373 // advantage of the existing _processResponse method. 17374 rsp.object = contentBody; 17375 contact._processResponse(rsp); 17376 17377 //Remove the injected contentBody object before cascading response 17378 rsp.object = {}; 17379 17380 //cascade response back to consumer's response handler 17381 successHandler(rsp); 17382 }; 17383 }, 17384 17385 /** 17386 * Update - This should be all that is needed. 17387 * @param {Object} newValues 17388 * @param {Object} handlers 17389 * @returns {finesse.restservices.TeamWorkflows} 17390 * This TeamWorkflows object to allow cascading. 17391 */ 17392 update: function (newValues, handlers) { 17393 this.isLoaded(); 17394 var contentBody = {}, contentBodyInner = [], i, innerObject = {}; 17395 17396 contentBody[this.getRestType()] = { 17397 }; 17398 17399 for (i in newValues) { 17400 if (newValues.hasOwnProperty(i)) { 17401 innerObject = { 17402 "uri": newValues[i] 17403 }; 17404 contentBodyInner.push(innerObject); 17405 } 17406 } 17407 17408 contentBody[this.getRestType()] = { 17409 "Workflow" : contentBodyInner 17410 }; 17411 17412 // Protect against null dereferencing of options allowing its (nonexistent) keys to be read as undefined 17413 handlers = handlers || {}; 17414 17415 this.restRequest(this.getRestUrl(), { 17416 method: 'PUT', 17417 success: this.createPutSuccessHandler(this, contentBody, handlers.success), 17418 error: handlers.error, 17419 content: contentBody 17420 }); 17421 17422 return this; // Allow cascading 17423 } 17424 17425 }); 17426 17427 window.finesse = window.finesse || {}; 17428 window.finesse.restservices = window.finesse.restservices || {}; 17429 window.finesse.restservices.TeamWorkflows = TeamWorkflows; 17430 17431 return TeamWorkflows; 17432 }); 17433 17434 /** 17435 * JavaScript representation of the Finesse Team REST object. 17436 * 17437 * @requires finesse.clientservices.ClientServices 17438 * @requires Class 17439 * @requires finesse.FinesseBase 17440 * @requires finesse.restservices.RestBase 17441 * @requires finesse.restservices.RestCollectionBase 17442 * @requires finesse.restservices.User 17443 * @requires finesse.restservices.Users 17444 */ 17445 17446 /** 17447 * The following comment prevents JSLint errors concerning undefined global variables. 17448 * It tells JSLint that these identifiers are defined elsewhere. 17449 */ 17450 /*jslint bitwise:true, browser:true, nomen:true, regexp:true, sloppy:true, white:true */ 17451 17452 /** The following comment is to prevent jslint errors about 17453 * using variables before they are defined. 17454 */ 17455 /*global $, jQuery, Handlebars, dojox, dojo, finesse */ 17456 17457 /** @private */ 17458 define('restservices/Team',[ 17459 'restservices/RestBase', 17460 'utilities/Utilities', 17461 'restservices/Users', 17462 'restservices/TeamNotReadyReasonCodes', 17463 'restservices/TeamWrapUpReasons', 17464 'restservices/TeamSignOutReasonCodes', 17465 'restservices/TeamPhoneBooks', 17466 'restservices/TeamLayoutConfig', 17467 'restservices/TeamWorkflows' 17468 ], 17469 function (RestBase, Utilities, Users, TeamNotReadyReasonCodes, TeamWrapUpReasons, TeamSignOutReasonCodes, TeamPhoneBooks, TeamLayoutConfig, TeamWorkflows) { 17470 var Team = RestBase.extend(/** @lends finesse.restservices.Team.prototype */{ 17471 17472 _teamLayoutConfig: null, 17473 17474 /** 17475 * @class 17476 * A Team is a set of Agent Users, typically supervised by one or more Supervisor Users. 17477 * 17478 * @augments finesse.restservices.RestBase 17479 * @see finesse.restservices.User#getSupervisedTeams 17480 * @see finesse.restservices.Users 17481 * @constructs 17482 */ 17483 _fakeConstuctor: function () { 17484 /* This is here to hide the real init constructor from the public docs */ 17485 }, 17486 17487 /** 17488 * @private 17489 * @class 17490 * JavaScript representation of a Team object. Also exposes methods to operate 17491 * on the object against the server. 17492 * 17493 * @param {Object} options 17494 * An object with the following properties:<ul> 17495 * <li><b>id:</b> The id of the object being constructed</li> 17496 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 17497 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 17498 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 17499 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 17500 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 17501 * <li><b>status:</b> {Number} The HTTP status code returned</li> 17502 * <li><b>content:</b> {String} Raw string of response</li> 17503 * <li><b>object:</b> {Object} Parsed object of response</li> 17504 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 17505 * <li><b>errorType:</b> {String} Type of error that was caught</li> 17506 * <li><b>errorMessage:</b> {String} Message associated with error</li> 17507 * </ul></li> 17508 * </ul></li> 17509 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 17510 **/ 17511 init: function (options) { 17512 this._super(options); 17513 }, 17514 17515 /** 17516 * @private 17517 * Gets the REST class for the current object - this is the Team class. 17518 * @returns {Object} The Team constructor. 17519 */ 17520 getRestClass: function () { 17521 return finesse.restesrvices.Team; 17522 }, 17523 17524 /** 17525 * @private 17526 * Gets the REST type for the current object - this is a "Team". 17527 * @returns {String} The Team string. 17528 */ 17529 getRestType: function () { 17530 return "Team"; 17531 }, 17532 17533 /** 17534 * @private 17535 * Override default to indicate that this object doesn't support making 17536 * requests. 17537 */ 17538 supportsSubscriptions: false, 17539 17540 /** 17541 * Getter for the team id. 17542 * @returns {String} The team id. 17543 */ 17544 getId: function () { 17545 this.isLoaded(); 17546 return this.getData().id; 17547 }, 17548 17549 /** 17550 * Getter for the team name. 17551 * @returns {String} The team name 17552 */ 17553 getName: function () { 17554 this.isLoaded(); 17555 return this.getData().name; 17556 }, 17557 17558 /** 17559 * @private 17560 * Getter for the team uri. 17561 * @returns {String} The team uri 17562 */ 17563 getUri: function () { 17564 this.isLoaded(); 17565 return this.getData().uri; 17566 }, 17567 17568 /** 17569 * Constructs and returns a collection of Users. 17570 * @param {finesse.interfaces.RestObjectHandlers} [handlers] Object that sets callback handlers. 17571 * @returns {finesse.restservices.Users} Users collection of User objects. 17572 */ 17573 getUsers: function (options) { 17574 this.isLoaded(); 17575 options = options || {}; 17576 17577 options.parentObj = this; 17578 // We are using getData() instead of getData.Users because the superclass (RestCollectionBase) 17579 // for Users needs the "Users" key to validate the provided payload matches the class type. 17580 options.data = this.getData(); 17581 17582 return new Users(options); 17583 }, 17584 17585 /** 17586 * @private 17587 * Getter for a teamNotReadyReasonCodes collection object that is associated with Team. 17588 * @param callbacks 17589 * @returns {teamNotReadyReasonCodes} 17590 * A teamNotReadyReasonCodes collection object. 17591 */ 17592 getTeamNotReadyReasonCodes: function (callbacks) { 17593 var options = callbacks || {}; 17594 options.parentObj = this; 17595 this.isLoaded(); 17596 17597 if (!this._teamNotReadyReasonCodes) { 17598 this._teamNotReadyReasonCodes = new TeamNotReadyReasonCodes(options); 17599 } 17600 17601 return this._teamNotReadyReasonCodes; 17602 }, 17603 17604 /** 17605 * @private 17606 * Getter for a teamWrapUpReasons collection object that is associated with Team. 17607 * @param callbacks 17608 * @returns {teamWrapUpReasons} 17609 * A teamWrapUpReasons collection object. 17610 */ 17611 getTeamWrapUpReasons: function (callbacks) { 17612 var options = callbacks || {}; 17613 options.parentObj = this; 17614 this.isLoaded(); 17615 17616 if (!this._teamWrapUpReasons) { 17617 this._teamWrapUpReasons = new TeamWrapUpReasons(options); 17618 } 17619 17620 return this._teamWrapUpReasons; 17621 }, 17622 17623 /** 17624 * @private 17625 * Getter for a teamSignOutReasonCodes collection object that is associated with Team. 17626 * @param callbacks 17627 * @returns {teamSignOutReasonCodes} 17628 * A teamSignOutReasonCodes collection object. 17629 */ 17630 17631 getTeamSignOutReasonCodes: function (callbacks) { 17632 var options = callbacks || {}; 17633 options.parentObj = this; 17634 this.isLoaded(); 17635 17636 if (!this._teamSignOutReasonCodes) { 17637 this._teamSignOutReasonCodes = new TeamSignOutReasonCodes(options); 17638 } 17639 17640 return this._teamSignOutReasonCodes; 17641 }, 17642 17643 /** 17644 * @private 17645 * Getter for a teamPhoneBooks collection object that is associated with Team. 17646 * @param callbacks 17647 * @returns {teamPhoneBooks} 17648 * A teamPhoneBooks collection object. 17649 */ 17650 getTeamPhoneBooks: function (callbacks) { 17651 var options = callbacks || {}; 17652 options.parentObj = this; 17653 this.isLoaded(); 17654 17655 if (!this._phonebooks) { 17656 this._phonebooks = new TeamPhoneBooks(options); 17657 } 17658 17659 return this._phonebooks; 17660 }, 17661 17662 /** 17663 * @private 17664 * Getter for a teamWorkflows collection object that is associated with Team. 17665 * @param callbacks 17666 * @returns {teamWorkflows} 17667 * A teamWorkflows collection object. 17668 */ 17669 getTeamWorkflows: function (callbacks) { 17670 var options = callbacks || {}; 17671 options.parentObj = this; 17672 this.isLoaded(); 17673 17674 if (!this._workflows) { 17675 this._workflows = new TeamWorkflows(options); 17676 } 17677 17678 return this._workflows; 17679 }, 17680 17681 /** 17682 * @private 17683 * Getter for a teamLayoutConfig object that is associated with Team. 17684 * @param callbacks 17685 * @returns {teamLayoutConfig} 17686 */ 17687 getTeamLayoutConfig: function (callbacks) { 17688 var options = callbacks || {}; 17689 options.parentObj = this; 17690 this.isLoaded(); 17691 17692 if (this._teamLayoutConfig === null) { 17693 this._teamLayoutConfig = new TeamLayoutConfig(options); 17694 } 17695 17696 return this._teamLayoutConfig; 17697 } 17698 17699 }); 17700 17701 window.finesse = window.finesse || {}; 17702 window.finesse.restservices = window.finesse.restservices || {}; 17703 window.finesse.restservices.Team = Team; 17704 17705 return Team; 17706 }); 17707 17708 /** 17709 * JavaScript representation of the Finesse Teams collection. 17710 * object which contains a list of Team objects 17711 * @requires finesse.clientservices.ClientServices 17712 * @requires Class 17713 * @requires finesse.FinesseBase 17714 * @requires finesse.restservices.RestBase 17715 * @requires finesse.restservices.RestCollectionBase 17716 */ 17717 17718 /** @private */ 17719 define('restservices/Teams',[ 17720 'restservices/RestCollectionBase', 17721 'restservices/Team' 17722 ], 17723 function (RestCollectionBase, Team) { 17724 /** @private */ 17725 var Teams = RestCollectionBase.extend({ 17726 17727 /** 17728 * @class 17729 * JavaScript representation of a Teams collection object. Also exposes methods to operate 17730 * on the object against the server. 17731 * 17732 * @param {Object} options 17733 * An object with the following properties:<ul> 17734 * <li><b>id:</b> The id of the object being constructed</li> 17735 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 17736 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 17737 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 17738 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 17739 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 17740 * <li><b>status:</b> {Number} The HTTP status code returned</li> 17741 * <li><b>content:</b> {String} Raw string of response</li> 17742 * <li><b>object:</b> {Object} Parsed object of response</li> 17743 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 17744 * <li><b>errorType:</b> {String} Type of error that was caught</li> 17745 * <li><b>errorMessage:</b> {String} Message associated with error</li> 17746 * </ul></li> 17747 * </ul></li> 17748 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 17749 * @constructs 17750 **/ 17751 init: function (options) { 17752 this._super(options); 17753 }, 17754 17755 /** 17756 * @private 17757 * Gets the REST class for the current object - this is the Teams class. 17758 * @returns {Object} The Teams constructor. 17759 */ 17760 getRestClass: function () { 17761 return Teams; 17762 }, 17763 17764 /** 17765 * @private 17766 * Gets the REST class for the objects that make up the collection. - this 17767 * is the Team class. 17768 */ 17769 getRestItemClass: function () { 17770 return Team; 17771 }, 17772 17773 /** 17774 * @private 17775 * Gets the REST type for the current object - this is a "Teams". 17776 * @returns {String} The Teams string. 17777 */ 17778 getRestType: function () { 17779 return "Teams"; 17780 }, 17781 17782 /** 17783 * @private 17784 * Gets the REST type for the objects that make up the collection - this is "Team". 17785 */ 17786 getRestItemType: function () { 17787 return "Team"; 17788 }, 17789 17790 /** 17791 * @private 17792 * Override default to indicates that the collection supports making 17793 * requests. 17794 */ 17795 supportsRequests: true, 17796 17797 /** 17798 * @private 17799 * Override default to indicate that this object doesn't support subscriptions. 17800 */ 17801 supportsRestItemSubscriptions: false, 17802 17803 /** 17804 * @private 17805 * Retrieve the Teams. This call will re-query the server and refresh the collection. 17806 * 17807 * @returns {finesse.restservices.Teams} 17808 * This Teams object to allow cascading. 17809 */ 17810 get: function () { 17811 // set loaded to false so it will rebuild the collection after the get 17812 this._loaded = false; 17813 // reset collection 17814 this._collection = {}; 17815 // perform get 17816 this._synchronize(); 17817 return this; 17818 } 17819 17820 }); 17821 17822 window.finesse = window.finesse || {}; 17823 window.finesse.restservices = window.finesse.restservices || {}; 17824 window.finesse.restservices.Teams = Teams; 17825 17826 return Teams; 17827 }); 17828 17829 /** 17830 * JavaScript representation of the Finesse SystemInfo object 17831 * 17832 * @requires finesse.clientservices.ClientServices 17833 * @requires Class 17834 * @requires finesse.FinesseBase 17835 * @requires finesse.restservices.RestBase 17836 */ 17837 17838 /** @private */ 17839 define('restservices/SystemInfo',['restservices/RestBase'], function (RestBase) { 17840 17841 var SystemInfo = RestBase.extend(/** @lends finesse.restservices.SystemInfo.prototype */{ 17842 /** 17843 * @private 17844 * Returns whether this object supports subscriptions 17845 */ 17846 supportsSubscriptions: false, 17847 17848 doNotRefresh: true, 17849 17850 /** 17851 * @class 17852 * JavaScript representation of a SystemInfo object. 17853 * 17854 * @augments finesse.restservices.RestBase 17855 * @see finesse.restservices.SystemInfo.Statuses 17856 * @constructs 17857 */ 17858 _fakeConstuctor: function () { 17859 /* This is here to hide the real init constructor from the public docs */ 17860 }, 17861 17862 /** 17863 * @private 17864 * JavaScript representation of a SystemInfo object. Also exposes methods to operate 17865 * on the object against the server. 17866 * 17867 * @param {Object} options 17868 * An object with the following properties:<ul> 17869 * <li><b>id:</b> The id of the object being constructed</li> 17870 * <li><b>onLoad(this): (optional)</b> when the object is successfully loaded from the server</li> 17871 * <li><b>onChange(this): (optional)</b> when an update notification of the object is received</li> 17872 * <li><b>onAdd(this): (optional)</b> when a notification that the object is created is received</li> 17873 * <li><b>onDelete(this): (optional)</b> when a notification that the object is deleted is received</li> 17874 * <li><b>onError(rsp): (optional)</b> if loading of the object fails, invoked with the error response object:<ul> 17875 * <li><b>status:</b> {Number} The HTTP status code returned</li> 17876 * <li><b>content:</b> {String} Raw string of response</li> 17877 * <li><b>object:</b> {Object} Parsed object of response</li> 17878 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 17879 * <li><b>errorType:</b> {String} Type of error that was caught</li> 17880 * <li><b>errorMessage:</b> {String} Message associated with error</li> 17881 * </ul></li> 17882 * </ul></li> 17883 * <li><b>parentObj: (optional)</b> The parent object</li></ul> 17884 **/ 17885 init: function (id, callbacks, restObj) 17886 { 17887 this._super(id, callbacks, restObj); 17888 }, 17889 17890 /** 17891 * @private 17892 * Gets the REST class for the current object - this is the SystemInfo object. 17893 */ 17894 getRestClass: function () { 17895 return SystemInfo; 17896 }, 17897 17898 /** 17899 * @private 17900 * Gets the REST type for the current object - this is a "SystemInfo". 17901 */ 17902 getRestType: function () 17903 { 17904 return "SystemInfo"; 17905 }, 17906 17907 _validate: function (obj) 17908 { 17909 return true; 17910 }, 17911 17912 /** 17913 * Returns the status of the Finesse system. 17914 * IN_SERVICE if the Finesse API reports that it is in service, 17915 * OUT_OF_SERVICE otherwise. 17916 * @returns {finesse.restservices.SystemInfo.Statuses} System Status 17917 */ 17918 getStatus: function () { 17919 this.isLoaded(); 17920 return this.getData().status; 17921 }, 17922 17923 /** 17924 * Returns the current timestamp from this SystemInfo object. 17925 * This is used to calculate time drift delta between server and client. 17926 * @returns {String} Time (GMT): yyyy-MM-dd'T'HH:mm:ss'Z' 17927 */ 17928 getCurrentTimestamp: function () { 17929 this.isLoaded(); 17930 return this.getData().currentTimestamp; 17931 }, 17932 17933 /** 17934 * Getter for the xmpp domain of the system. 17935 * @returns {String} The xmpp domain corresponding to this SystemInfo object. 17936 */ 17937 getXmppDomain: function () { 17938 this.isLoaded(); 17939 return this.getData().xmppDomain; 17940 }, 17941 17942 /** 17943 * Getter for the xmpp pubsub domain of the system. 17944 * @returns {String} The xmpp pubsub domain corresponding to this SystemInfo object. 17945 */ 17946 getXmppPubSubDomain: function () { 17947 this.isLoaded(); 17948 return this.getData().xmppPubSubDomain; 17949 }, 17950 17951 /** 17952 * Getter for the deployment type (UCCE or UCCX). 17953 * @returns {String} "UCCE" or "UCCX" 17954 */ 17955 getDeploymentType: function () { 17956 this.isLoaded(); 17957 return this.getData().deploymentType; 17958 }, 17959 17960 /** 17961 * Returns whether this is a single node deployment or not by checking for the existence of the secondary node in SystemInfo. 17962 * @returns {Boolean} True for single node deployments, false otherwise. 17963 */ 17964 isSingleNode: function () { 17965 var secondary = this.getData().secondaryNode; 17966 if (secondary && secondary.host) { 17967 return false; 17968 } 17969 return true; 17970 }, 17971 17972 /** 17973 * Checks all arguments against the primary and secondary hosts (FQDN) and returns the match. 17974 * This is useful for getting the FQDN of the current Finesse server. 17975 * @param {String} ...arguments[]... - any number of arguments to match against 17976 * @returns {String} FQDN (if properly configured) of the matched host of the primary or secondary node, or undefined if no match is found. 17977 */ 17978 getThisHost: function () { 17979 var i, 17980 primary = this.getData().primaryNode, 17981 secondary = this.getData().secondaryNode; 17982 17983 for (i = 0; (i < arguments.length); i = i + 1) { 17984 if (primary && arguments[i] === primary.host) { 17985 return primary.host; 17986 } else if (secondary && arguments[i] === secondary.host) { 17987 return secondary.host; 17988 } 17989 } 17990 }, 17991 17992 /** 17993 * Checks all arguments against the primary and secondary hosts (FQDN) and returns the other node. 17994 * This is useful for getting the FQDN of the other Finesse server, i.e. for failover purposes. 17995 * @param {String} arguments - any number of arguments to match against 17996 * @returns {String} FQDN (if properly configured) of the alternate node, defaults to primary if no match is found, undefined for single node deployments. 17997 */ 17998 getAlternateHost: function () { 17999 var i, 18000 isPrimary = false, 18001 primary = this.getData().primaryNode, 18002 secondary = this.getData().secondaryNode, 18003 xmppDomain = this.getData().xmppDomain, 18004 alternateHost; 18005 18006 if (primary && primary.host) { 18007 if (xmppDomain === primary.host) { 18008 isPrimary = true; 18009 } 18010 if (secondary && secondary.host) { 18011 if (isPrimary) { 18012 return secondary.host; 18013 } 18014 return primary.host; 18015 } 18016 } 18017 }, 18018 18019 /** 18020 * Gets the peripheral ID that Finesse is connected to. The peripheral 18021 * ID is the ID of the PG Routing Client (PIM). 18022 * 18023 * @returns {String} The peripheral Id if UCCE, or empty string otherwise. 18024 */ 18025 getPeripheralId : function () { 18026 this.isLoaded(); 18027 18028 var peripheralId = this.getData().peripheralId; 18029 if (peripheralId === null) { 18030 return ""; 18031 } else { 18032 return this.getData().peripheralId; 18033 } 18034 }, 18035 18036 /** 18037 * Gets the license. Only apply to UCCX. 18038 * 18039 * @returns {String} The license if UCCX, or empty string otherwise. 18040 */ 18041 getlicense : function () { 18042 this.isLoaded(); 18043 return this.getData().license || ""; 18044 }, 18045 18046 /** 18047 * Gets the systemAuthMode for the current deployment 18048 * 18049 * @returns {String} The System auth mode for current deployment 18050 */ 18051 getSystemAuthMode : function() { 18052 this.isLoaded(); 18053 return this.getData().systemAuthMode; 18054 } 18055 }); 18056 18057 SystemInfo.Statuses = /** @lends finesse.restservices.SystemInfo.Statuses.prototype */ { 18058 /** 18059 * Finesse is in service. 18060 */ 18061 IN_SERVICE: "IN_SERVICE", 18062 /** 18063 * Finesse is not in service. 18064 */ 18065 OUT_OF_SERVICE: "OUT_OF_SERVICE", 18066 /** 18067 * @class SystemInfo status values. 18068 * @constructs 18069 */ 18070 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 18071 18072 }; 18073 18074 window.finesse = window.finesse || {}; 18075 window.finesse.restservices = window.finesse.restservices || {}; 18076 window.finesse.restservices.SystemInfo = SystemInfo; 18077 18078 return SystemInfo; 18079 }); 18080 18081 define('restservices/DialogLogoutActions',[], function () 18082 { 18083 var DialogLogoutActions = /** @lends finesse.restservices.DialogLogoutActions.prototype */ { 18084 18085 /** 18086 * Set this action to close active dialogs when the agent logs out. 18087 */ 18088 CLOSE: "CLOSE", 18089 18090 /** 18091 * Set this action to transfer active dialogs when the agent logs out. 18092 */ 18093 TRANSFER: "TRANSFER", 18094 18095 /** 18096 * @class Actions used to handle tasks that are associated with a given media at logout time. 18097 * 18098 * @constructs 18099 */ 18100 _fakeConstructor: function () 18101 { 18102 }, // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 18103 18104 /** 18105 * Is the given action a valid dialog logout action. 18106 * 18107 * @param {String} action the action to evaluate 18108 * @returns {Boolean} true if the action is valid; false otherwise 18109 */ 18110 isValidAction: function(action) 18111 { 18112 if ( !action ) 18113 { 18114 return false; 18115 } 18116 18117 return DialogLogoutActions.hasOwnProperty(action.toUpperCase()); 18118 } 18119 }; 18120 18121 window.finesse = window.finesse || {}; 18122 window.finesse.restservices = window.finesse.restservices || {}; 18123 window.finesse.restservices.DialogLogoutActions = DialogLogoutActions; 18124 18125 return DialogLogoutActions; 18126 }); 18127 define('restservices/InterruptActions',[], function () 18128 { 18129 var InterruptActions = /** @lends finesse.restservices.InterruptActions.prototype */ 18130 { 18131 /** 18132 * The interrupt will be accepted and the agent will not work on dialogs in this media until the media is no longer interrupted. 18133 */ 18134 ACCEPT: "ACCEPT", 18135 18136 /** 18137 * the interrupt will be ignored and the agent is allowed to work on dialogs while the media is interrupted. 18138 */ 18139 IGNORE: "IGNORE", 18140 18141 /** 18142 * @class 18143 * 18144 * The action to be taken in the event this media is interrupted. The action will be one of the following:<ul> 18145 * <li><b>ACCEPT:</b> the interrupt will be accepted and the agent will not work on dialogs in this media 18146 * until the media is no longer interrupted.</li> 18147 * <li><b>IGNORE:</b> the interrupt will be ignored and the agent is allowed to work on dialogs while the 18148 * media is interrupted.</li></ul> 18149 * 18150 * @constructs 18151 */ 18152 _fakeConstructor: function () 18153 { 18154 }, // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 18155 18156 /** 18157 * Is the given action a valid dialog logout action. 18158 * 18159 * @param {String} action the action to evaluate 18160 * @returns {Boolean} true if the action is valid; false otherwise 18161 */ 18162 isValidAction: function(action) 18163 { 18164 if ( !action ) 18165 { 18166 return false; 18167 } 18168 18169 return InterruptActions.hasOwnProperty(action.toUpperCase()); 18170 } 18171 }; 18172 18173 window.finesse = window.finesse || {}; 18174 window.finesse.restservices = window.finesse.restservices || {}; 18175 window.finesse.restservices.InterruptActions = InterruptActions; 18176 18177 return InterruptActions; 18178 }); 18179 /** 18180 * Provides standard way resolve message keys with substitution 18181 * 18182 * @requires finesse.container.I18n or gadgets.Prefs 18183 */ 18184 18185 // Add Utilities to the finesse.utilities namespace 18186 define('utilities/I18n',[], function () { 18187 var I18n = (function () { 18188 18189 /** 18190 * Shortcut to finesse.container.I18n.getMsg or gadgets.Prefs.getMsg 18191 * @private 18192 */ 18193 var _getMsg; 18194 18195 return { 18196 /** 18197 * Provides a message resolver for this utility singleton. 18198 * @param {Function} getMsg 18199 * A function that returns a string given a message key. 18200 * If the key is not found, this function must return 18201 * something that tests false (i.e. undefined or ""). 18202 */ 18203 setGetter : function (getMsg) { 18204 _getMsg = getMsg; 18205 }, 18206 18207 /** 18208 * Resolves the given message key, also performing substitution. 18209 * This generic utility will use a custom function to resolve the key 18210 * provided by finesse.utilities.I18n.setGetter. Otherwise, it will 18211 * discover either finesse.container.I18n.getMsg or gadgets.Prefs.getMsg 18212 * upon the first invocation and store that reference for efficiency. 18213 * 18214 * Since this will construct a new gadgets.Prefs object, it is recommended 18215 * for gadgets to explicitly provide the setter to prevent duplicate 18216 * gadgets.Prefs objects. This does not apply if your gadget does not need 18217 * access to gadgets.Prefs other than getMsg. 18218 * 18219 * @param {String} key 18220 * The key to lookup 18221 * @param {String} arguments 18222 * Arguments for substitution 18223 * @returns {String/Function} 18224 * The resolved string if successful, otherwise a function that returns 18225 * a '???' string that can also be casted into a string. 18226 */ 18227 getString : function (key) { 18228 var prefs, i, retStr, noMsg, getFailed = ""; 18229 if (!_getMsg) { 18230 if (finesse.container && finesse.container.I18n) { 18231 _getMsg = finesse.container.I18n.getMsg; 18232 } else if (gadgets) { 18233 prefs = new gadgets.Prefs(); 18234 _getMsg = prefs.getMsg; 18235 } 18236 } 18237 18238 try { 18239 retStr = _getMsg(key); 18240 } catch (e) { 18241 getFailed = "finesse.utilities.I18n.getString(): invalid _getMsg"; 18242 } 18243 18244 if (retStr) { // Lookup was successful, perform substitution (if any) 18245 for (i = 1; i < arguments.length; i += 1) { 18246 retStr = retStr.replace(new RegExp("\\{" + (i - 1) + "\\}", "g"), arguments[i]); 18247 } 18248 //in order to fix French text with single quotes in it, we need to replace \' with ' 18249 return retStr.replace(/\\'/g, "'"); 18250 } 18251 // We want a function because jQuery.html() and jQuery.text() is smart enough to invoke it. 18252 /** @private */ 18253 noMsg = function () { 18254 return "???" + key + "???" + getFailed; 18255 }; 18256 // We overload the toString() of this "function" to allow JavaScript to cast it into a string 18257 // For example, var myMsg = "something " + finesse.utilities.I18n.getMsg("unresolvable.key"); 18258 /** @private */ 18259 noMsg.toString = function () { 18260 return "???" + key + "???" + getFailed; 18261 }; 18262 return noMsg; 18263 18264 } 18265 }; 18266 }()); 18267 18268 window.finesse = window.finesse || {}; 18269 window.finesse.utilities = window.finesse.utilities || {}; 18270 window.finesse.utilities.I18n = I18n; 18271 18272 return I18n; 18273 }); 18274 18275 /** 18276 * Logging.js: provides simple logging for clients to use and overrides synchronous native methods: alert(), confirm(), and prompt(). 18277 * 18278 * On Firefox, it will hook into console for logging. On IE, it will log to the status bar. 18279 */ 18280 // Add Utilities to the finesse.utilities namespace 18281 define('utilities/Logger',[], function () { 18282 var Logger = (function () { 18283 18284 var 18285 18286 /** @private **/ 18287 debugOn, 18288 18289 /** 18290 * Pads a single digit number for display purposes (e.g. '4' shows as '04') 18291 * @param num is the number to pad to 2 digits 18292 * @returns a two digit padded string 18293 * @private 18294 */ 18295 padTwoDigits = function (num) { 18296 return (num < 10) ? '0' + num : num; 18297 }, 18298 18299 /** 18300 * Checks to see if we have a console - this allows us to support Firefox or IE. 18301 * @returns {Boolean} True for Firefox, False for IE 18302 * @private 18303 */ 18304 hasConsole = function () { 18305 var retval = false; 18306 try 18307 { 18308 if (window.console !== undefined) 18309 { 18310 retval = true; 18311 } 18312 } 18313 catch (err) 18314 { 18315 retval = false; 18316 } 18317 18318 return retval; 18319 }, 18320 18321 /** 18322 * Gets a timestamp. 18323 * @returns {String} is a timestamp in the following format: HH:MM:SS 18324 * @private 18325 */ 18326 getTimeStamp = function () { 18327 var date = new Date(), timeStr; 18328 timeStr = padTwoDigits(date.getHours()) + ":" + padTwoDigits(date.getMinutes()) + ":" + padTwoDigits(date.getSeconds()); 18329 18330 return timeStr; 18331 }; 18332 18333 return { 18334 /** 18335 * Enable debug mode. Debug mode may impact performance on the UI. 18336 * 18337 * @param {Boolean} enable 18338 * True to enable debug logging. 18339 * @private 18340 */ 18341 setDebug : function (enable) { 18342 debugOn = enable; 18343 }, 18344 18345 /** 18346 * Logs a string as DEBUG. 18347 * 18348 * @param str is the string to log. 18349 * @private 18350 */ 18351 log : function (str) { 18352 var timeStr = getTimeStamp(); 18353 18354 if (debugOn) { 18355 if (hasConsole()) 18356 { 18357 window.console.log(timeStr + ": " + "DEBUG" + " - " + str); 18358 } 18359 } 18360 }, 18361 18362 /** 18363 * Logs a string as INFO. 18364 * 18365 * @param str is the string to log. 18366 * @private 18367 */ 18368 info : function (str) { 18369 var timeStr = getTimeStamp(); 18370 18371 if (hasConsole()) 18372 { 18373 window.console.info(timeStr + ": " + "INFO" + " - " + str); 18374 } 18375 }, 18376 18377 /** 18378 * Logs a string as WARN. 18379 * 18380 * @param str is the string to log. 18381 * @private 18382 */ 18383 warn : function (str) { 18384 var timeStr = getTimeStamp(); 18385 18386 if (hasConsole()) 18387 { 18388 window.console.warn(timeStr + ": " + "WARN" + " - " + str); 18389 } 18390 }, 18391 /** 18392 * Logs a string as ERROR. 18393 * 18394 * @param str is the string to log. 18395 * @private 18396 */ 18397 error : function (str) { 18398 var timeStr = getTimeStamp(); 18399 18400 if (hasConsole()) 18401 { 18402 window.console.error(timeStr + ": " + "ERROR" + " - " + str); 18403 } 18404 } 18405 }; 18406 }()); 18407 18408 return Logger; 18409 }); 18410 18411 /** 18412 * BackSpaceHandler.js: provides functionality to prevent the page from navigating back and hence losing the unsaved data when backspace is pressed. 18413 * 18414 */ 18415 define('utilities/BackSpaceHandler',[], function () { 18416 var eventCallback = function(event) { 18417 var doPrevent = false, d = event.srcElement || event.target; 18418 if (event.keyCode === 8) { 18419 if ((d.tagName.toUpperCase() === 'INPUT' && (d.type 18420 .toUpperCase() === 'TEXT' 18421 || d.type.toUpperCase() === 'PASSWORD' 18422 || d.type.toUpperCase() === 'FILE' 18423 || d.type.toUpperCase() === 'SEARCH' 18424 || d.type.toUpperCase() === 'EMAIL' 18425 || d.type.toUpperCase() === 'NUMBER' || d.type 18426 .toUpperCase() === 'DATE')) 18427 || d.tagName.toUpperCase() === 'TEXTAREA') { 18428 doPrevent = d.readOnly || d.disabled; 18429 } else { 18430 doPrevent = true; 18431 } 18432 } 18433 18434 if (doPrevent) { 18435 event.preventDefault(); 18436 } 18437 }; 18438 18439 if (window.addEventListener) { 18440 window.addEventListener('keydown', eventCallback); 18441 } else if (window.attachEvent) { 18442 window.attachEvent('onkeydown', eventCallback); 18443 } else { 18444 window.console.error("Unable to attach backspace handler event "); 18445 } 18446 }); 18447 /* using variables before they are defined. 18448 */ 18449 /*global navigator,unescape,sessionStorage,localStorage,_initSessionList,_initSessionListComplete */ 18450 18451 /** 18452 * Allows each gadget to communicate with the server to send logs. 18453 */ 18454 18455 /** 18456 * @class 18457 * @private 18458 * Allows each product to initialize its method of storage 18459 */ 18460 define('cslogger/FinesseLogger',["clientservices/ClientServices", "utilities/Utilities"], function (ClientServices, Utilities) { 18461 18462 var FinesseLogger = (function () { 18463 18464 var 18465 18466 /** 18467 * Array use to collect ongoing logs in memory 18468 * @private 18469 */ 18470 _logArray = [], 18471 18472 /** 18473 * The final data string sent to the server, =_logArray.join 18474 * @private 18475 */ 18476 _logStr = "", 18477 18478 /** 18479 * Keep track of size of log 18480 * @private 18481 */ 18482 _logSize = 0, 18483 18484 /** 18485 * Flag to keep track show/hide of send log link 18486 * @private 18487 */ 18488 _sendLogShown = false, 18489 18490 /** 18491 * Flag to keep track if local log initialized 18492 * @private 18493 */ 18494 _loggingInitialized = false, 18495 18496 18497 /** 18498 * local log size limit 18499 * @private 18500 */ 18501 _maxLocalStorageSize = 5000000, 18502 18503 /** 18504 * half local log size limit 18505 * @private 18506 */ 18507 _halfMaxLocalStorageSize = 0.5*_maxLocalStorageSize, 18508 18509 18510 /** 18511 * threshold for purge 18512 * @private 18513 */ 18514 _purgeStartPercent = 0.75, 18515 18516 /** 18517 * log item prefix 18518 * @private 18519 */ 18520 _linePrefix = null, 18521 18522 /** 18523 * locallog session 18524 * @private 18525 */ 18526 _session = null, 18527 18528 /** 18529 * Flag to keep track show/hide of send log link 18530 * @private 18531 */ 18532 _sessionKey = null, 18533 /** 18534 * Log session metadata 18535 * @private 18536 */ 18537 _logInfo = {}, 18538 18539 /** 18540 * Flag to find sessions 18541 * @private 18542 */ 18543 _findSessionsObj = null, 18544 18545 /** 18546 * Wrap up console.log esp. for IE9 18547 * @private 18548 */ 18549 _myConsoleLog = function (str) { 18550 if (window.console !== undefined) { 18551 window.console.log(str); 18552 } 18553 }, 18554 /** 18555 * Initialize the Local Logging 18556 * @private 18557 */ 18558 _initLogging = function () { 18559 if (_loggingInitialized) { 18560 return; 18561 } 18562 //Build a new store 18563 _session = sessionStorage.getItem("finSessKey"); 18564 //if the _session is null or empty, skip the init 18565 if (!_session) { 18566 return; 18567 } 18568 _sessionKey = "Fi"+_session; 18569 _linePrefix = _sessionKey + "_"; 18570 _logInfo = {}; 18571 _logInfo.name = _session; 18572 _logInfo.size = 0; 18573 _logInfo.head = 0; 18574 _logInfo.tail = 0; 18575 _logInfo.startTime = new Date().getTime(); 18576 _loggingInitialized = true; 18577 _initSessionList(); 18578 }, 18579 18580 /** 18581 * get total data size 18582 * 18583 * @return {Integer} which is the amount of data stored in local storage. 18584 * @private 18585 */ 18586 _getTotalData = function () 18587 { 18588 var sessName, sessLogInfoStr,sessLogInfoObj, sessionsInfoObj, totalData = 0, 18589 sessionsInfoStr = localStorage.getItem("FinesseSessionsInfo"); 18590 if (!sessionsInfoStr) { 18591 return 0; 18592 } 18593 sessionsInfoObj = JSON.parse(sessionsInfoStr); 18594 18595 for (sessName in sessionsInfoObj.sessions) 18596 { 18597 if (sessionsInfoObj.sessions.hasOwnProperty(sessName)) { 18598 sessLogInfoStr = localStorage.getItem("Fi" + sessName); 18599 if (!sessLogInfoStr) { 18600 _myConsoleLog("_getTotalData failed to get log info for "+sessName); 18601 } 18602 else { 18603 sessLogInfoObj = JSON.parse(sessLogInfoStr); 18604 totalData = totalData + sessLogInfoObj.size; 18605 } 18606 } 18607 } 18608 18609 return totalData; 18610 }, 18611 18612 /** 18613 * Remove lines from tail up until store size decreases to half of max size limit. 18614 * 18615 * @private 18616 */ 18617 _purgeCurrentSession = function() { 18618 var curStoreSize, purgedSize=0, line, tailKey, secLogInfoStr, logInfoStr, theLogInfo; 18619 curStoreSize = _getTotalData(); 18620 if (curStoreSize < _halfMaxLocalStorageSize) { 18621 return; 18622 } 18623 logInfoStr = localStorage.getItem(_sessionKey); 18624 if (!logInfoStr) { 18625 return; 18626 } 18627 theLogInfo = JSON.parse(logInfoStr); 18628 //_myConsoleLog("Starting _purgeCurrentSession() - currentStoreSize=" + curStoreSize); 18629 while(curStoreSize > _halfMaxLocalStorageSize) { 18630 try { 18631 tailKey = _sessionKey+"_"+theLogInfo.tail; 18632 line = localStorage.getItem(tailKey); 18633 if (line) { 18634 purgedSize = purgedSize +line.length; 18635 localStorage.removeItem(tailKey); 18636 curStoreSize = curStoreSize - line.length; 18637 theLogInfo.size = theLogInfo.size - line.length; 18638 } 18639 } 18640 catch (err) { 18641 _myConsoleLog("purgeCurrentSession encountered err="+err); 18642 } 18643 if (theLogInfo.tail < theLogInfo.head) { 18644 theLogInfo.tail = theLogInfo.tail + 1; 18645 } 18646 else { 18647 break; 18648 } 18649 } 18650 //purge stops here, we need to update session's meta data in storage 18651 secLogInfoStr = localStorage.getItem(_sessionKey); 18652 if (!secLogInfoStr) { 18653 //somebody cleared the localStorage 18654 return; 18655 } 18656 18657 //_myConsoleLog("In _purgeCurrentSession() - after purging current session, currentStoreSize=" + curStoreSize); 18658 //_myConsoleLog("In _purgeCurrentSession() - after purging purgedSize=" + purgedSize); 18659 //_myConsoleLog("In _purgeCurrentSession() - after purging logInfo.size=" + theLogInfo.size); 18660 //_myConsoleLog("In _purgeCurrentSession() - after purging logInfo.tail=" + theLogInfo.tail); 18661 localStorage.setItem(_sessionKey, JSON.stringify(theLogInfo)); 18662 _myConsoleLog("Done _purgeCurrentSession() - currentStoreSize=" + curStoreSize); 18663 }, 18664 18665 /** 18666 * Purge a session 18667 * 18668 * @param sessionName is the name of the session 18669 * @return {Integer} which is the current amount of data purged 18670 * @private 18671 */ 18672 _purgeSession = function (sessionName) { 18673 var theLogInfo, logInfoStr, sessionsInfoStr, sessionsInfoObj; 18674 //Get the session logInfo 18675 logInfoStr = localStorage.getItem("Fi" + sessionName); 18676 if (!logInfoStr) { 18677 _myConsoleLog("_purgeSession failed to get logInfo for "+sessionName); 18678 return 0; 18679 } 18680 theLogInfo = JSON.parse(logInfoStr); 18681 18682 //Note: This assumes that we don't crash in the middle of purging 18683 //=> if we do then it should get deleted next time 18684 //Purge tail->head 18685 while (theLogInfo.tail <= theLogInfo.head) 18686 { 18687 try { 18688 localStorage.removeItem("Fi" + sessionName + "_" + theLogInfo.tail); 18689 theLogInfo.tail = theLogInfo.tail + 1; 18690 } 18691 catch (err) { 18692 _myConsoleLog("In _purgeSession err="+err); 18693 break; 18694 } 18695 } 18696 18697 //Remove the entire session 18698 localStorage.removeItem("Fi" + sessionName); 18699 18700 //Update FinesseSessionsInfo 18701 sessionsInfoStr = localStorage.getItem("FinesseSessionsInfo"); 18702 if (!sessionsInfoStr) { 18703 _myConsoleLog("_purgeSession could not get sessions Info, it was cleared?"); 18704 return 0; 18705 } 18706 sessionsInfoObj = JSON.parse(sessionsInfoStr); 18707 if (sessionsInfoObj.sessions !== null) 18708 { 18709 delete sessionsInfoObj.sessions[sessionName]; 18710 18711 sessionsInfoObj.total = sessionsInfoObj.total - 1; 18712 sessionsInfoObj.lastWrittenBy = _session; 18713 localStorage.setItem("FinesseSessionsInfo", JSON.stringify(sessionsInfoObj)); 18714 } 18715 18716 return theLogInfo.size; 18717 }, 18718 18719 /** 18720 * purge old sessions 18721 * 18722 * @param storeSize 18723 * @return {Boolean} whether purging reaches its target 18724 * @private 18725 */ 18726 _purgeOldSessions = function (storeSize) { 18727 var sessionsInfoStr, purgedSize = 0, sessName, sessions, curStoreSize, activeSession, sessionsInfoObj; 18728 sessionsInfoStr = localStorage.getItem("FinesseSessionsInfo"); 18729 if (!sessionsInfoStr) { 18730 _myConsoleLog("Could not get FinesseSessionsInfo"); 18731 return true; 18732 } 18733 sessionsInfoObj = JSON.parse(sessionsInfoStr); 18734 curStoreSize = _getTotalData(); 18735 18736 activeSession = _session; 18737 sessions = sessionsInfoObj.sessions; 18738 for (sessName in sessions) { 18739 if (sessions.hasOwnProperty(sessName)) { 18740 if (sessName !== activeSession) { 18741 purgedSize = purgedSize + _purgeSession(sessName); 18742 if ((curStoreSize-purgedSize) < _halfMaxLocalStorageSize) { 18743 return true; 18744 } 18745 } 18746 } 18747 } 18748 //purge is not done, so return false 18749 return false; 18750 }, 18751 18752 /** 18753 * handle insert error 18754 * 18755 * @param error 18756 * @private 18757 */ 18758 _insertLineHandleError = function (error) { 18759 _myConsoleLog(error); 18760 }, 18761 18762 /** 18763 * check storage data size and if need purge 18764 * @private 18765 */ 18766 _checkSizeAndPurge = function () { 18767 var purgeIsDone=false, totalSize = _getTotalData(); 18768 if (totalSize > 0.75*_maxLocalStorageSize) { 18769 _myConsoleLog("in _checkSizeAndPurge, totalSize ("+totalSize+") exceeds limit"); 18770 purgeIsDone = _purgeOldSessions(totalSize); 18771 if (purgeIsDone) { 18772 _myConsoleLog("in _checkSizeAndPurge after purging old session, purge is done"); 18773 } 18774 else { 18775 //after all old sessions purged, still need purge 18776 totalSize = _getTotalData(); 18777 if (totalSize > 0.75*_maxLocalStorageSize) { 18778 _myConsoleLog("in _checkSizeAndPurge after purging old session,still needs purging, now storeSize ("+totalSize+")"); 18779 _purgeCurrentSession(); 18780 _myConsoleLog("in _checkSizeAndPurge done purging current session."); 18781 } 18782 } 18783 } 18784 }, 18785 18786 /** 18787 * check if the session is already in meta data 18788 * 18789 * @param metaData 18790 * @param sessionName 18791 * @return {Boolean} true if session has metaData (false otherwise) 18792 * @private 18793 */ 18794 _sessionsInfoContains = function (metaData, sessionName) { 18795 if (metaData && metaData.sessions && metaData.sessions.hasOwnProperty(sessionName)) { 18796 return true; 18797 } 18798 return false; 18799 }, 18800 18801 18802 /** 18803 * setup sessions in local storage 18804 * 18805 * @param logInfo 18806 * @private 18807 */ 18808 _getAndSetNumberOfSessions = function (logInfo) { 18809 var numOfSessionsPass1, numOfSessionsPass2, l; 18810 numOfSessionsPass1 = localStorage.getItem("FinesseSessionsInfo"); 18811 if (numOfSessionsPass1 === null) { 18812 //Init first time 18813 numOfSessionsPass1 = {}; 18814 numOfSessionsPass1.total = 1; 18815 numOfSessionsPass1.sessions = {}; 18816 numOfSessionsPass1.sessions[logInfo.name] = logInfo.startTime; 18817 numOfSessionsPass1.lastWrittenBy = logInfo.name; 18818 localStorage.setItem("FinesseSessionsInfo", JSON.stringify(numOfSessionsPass1)); 18819 } 18820 else { 18821 numOfSessionsPass1 = JSON.parse(numOfSessionsPass1); 18822 //check if the session is already in the FinesseSessionSInfo 18823 if (_sessionsInfoContains(numOfSessionsPass1, logInfo.name)) { 18824 return; 18825 } 18826 //Save numOfSessionsPass1 18827 numOfSessionsPass1.total = parseInt(numOfSessionsPass1.total, 10) + 1; 18828 numOfSessionsPass1.sessions[logInfo.name] = logInfo.startTime; 18829 numOfSessionsPass1.lastWrittenBy = logInfo.name; 18830 localStorage.setItem("FinesseSessionsInfo", JSON.stringify(numOfSessionsPass1)); 18831 numOfSessionsPass2 = localStorage.getItem("FinesseSessionsInfo"); 18832 if (!numOfSessionsPass2) { 18833 _myConsoleLog("Could not get FinesseSessionsInfo"); 18834 return; 18835 } 18836 numOfSessionsPass2 = JSON.parse(numOfSessionsPass2); 18837 //in future we need to confirm the numOfSessionsPass2 is the same as numOfSessionsPass1 18838 ////if (numOfSessionsPass1.lastWrittenBy !== numOfSessionsPass2.lastWrittenBy) { 18839 //// _myConsoleLog("Rebuild sessions"); 18840 //// _sessionTimerId = setTimeout(_initSessionList, 10000); 18841 ////} 18842 ////else { 18843 //// _sessionTimerId = null; 18844 ////callback(numOfSessionsPass2.sessions); 18845 ////} 18846 } 18847 if (!localStorage.getItem(_sessionKey)) { 18848 localStorage.setItem(_sessionKey, JSON.stringify(_logInfo)); 18849 } 18850 }, 18851 18852 18853 /** 18854 * init session list 18855 * @private 18856 */ 18857 _initSessionList = function () { 18858 _getAndSetNumberOfSessions(_logInfo); 18859 }, 18860 18861 /** 18862 * do the real store of log line 18863 * 18864 * @param line 18865 * @private 18866 */ 18867 _persistLine = function (line) { 18868 var key, logInfoStr; 18869 logInfoStr = localStorage.getItem(_sessionKey); 18870 if (logInfoStr === null) { 18871 return; 18872 } 18873 _logInfo = JSON.parse(logInfoStr); 18874 _logInfo.head = _logInfo.head + 1; 18875 key = _linePrefix + _logInfo.head; 18876 localStorage.setItem(key, line); 18877 //Save the size 18878 _logInfo.size = _logInfo.size + line.length; 18879 if (_logInfo.tail === 0) { 18880 _logInfo.tail = _logInfo.head; 18881 } 18882 18883 localStorage.setItem(_sessionKey, JSON.stringify(_logInfo)); 18884 _checkSizeAndPurge(); 18885 }, 18886 18887 /** 18888 * Insert a line into the localStorage. 18889 * 18890 * @param line line to be inserted 18891 * @private 18892 */ 18893 _insertLine = function (line) { 18894 //_myConsoleLog("_insertLine: [" + line + "]"); 18895 //Write the next line to localStorage 18896 try { 18897 //Persist the line 18898 _persistLine(line); 18899 } 18900 catch (err) { 18901 _myConsoleLog("error in _insertLine(), err="+err); 18902 //_insertLineHandleError(err); 18903 } 18904 }, 18905 18906 18907 /** 18908 * Clear the local storage 18909 * @private 18910 */ 18911 _clearLocalStorage = function() { 18912 localStorage.clear(); 18913 18914 }, 18915 18916 /** 18917 * Collect logs when onCollect called 18918 * 18919 * @param data 18920 * @private 18921 */ 18922 _collectMethod = function(data) { 18923 //Size of log should not exceed 1.5MB 18924 var info, maxLength = 1572864; 18925 18926 //add size buffer equal to the size of info to be added when publish 18927 info = Utilities.getSanitizedUserAgentString() + " "; 18928 info = escape(info); 18929 18930 //If log was empty previously, fade in buttons 18931 if (!_sendLogShown) { 18932 //call the fadeInSendLog() in Footer 18933 finesse.modules.Footer.sendLogAppear(); 18934 _sendLogShown = true; 18935 _logSize = info.length; 18936 } 18937 18938 //if local storage logging is enabled, then insert the log into local storage 18939 if (window.sessionStorage.getItem('enableLocalLog')==='true') { 18940 if (data) { 18941 if (data.length>0 && data.substring(0,1) === '\n') { 18942 _insertLine(data.substring(1)); 18943 } 18944 else { 18945 _insertLine(data); 18946 } 18947 } 18948 } 18949 18950 //escape all data to get accurate size (shindig will escape when it builds request) 18951 //escape 6 special chars for XML: &<>"'\n 18952 data = data.replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/>/g, ">").replace(/</g, "<").replace(/\n/g, " "); 18953 data = escape(data+"\n"); 18954 18955 if (data.length < maxLength){ 18956 //make room for new data if log is exceeding max length 18957 while (_logSize + data.length > maxLength) { 18958 _logSize -= (_logArray.shift()).length; 18959 } 18960 } 18961 18962 //Else push the log into memory, increment the log size 18963 _logArray.push(data); 18964 18965 //inc the size accordingly 18966 _logSize+=data.length; 18967 18968 }; 18969 18970 return { 18971 18972 /** 18973 * @private 18974 * Initiate FinesseLogger. 18975 */ 18976 init: function () { 18977 ClientServices.subscribe("finesse.clientLogging.*", _collectMethod); 18978 _initLogging(); 18979 }, 18980 18981 /** 18982 * @private 18983 * Clear all items stored in localStorage. 18984 */ 18985 clear : function () { 18986 _clearLocalStorage(); 18987 }, 18988 18989 /** 18990 * @private 18991 * Initialize the local storage logging. 18992 */ 18993 initLocalLog: function () { 18994 _initLogging(); 18995 }, 18996 18997 /** 18998 * @private 18999 * Inserts a line into the localStorage. 19000 * @param line to insert 19001 */ 19002 localLog : function (line) { 19003 _insertLine(line); 19004 }, 19005 19006 /** 19007 * @ignore 19008 * Publish logs to server and clear the memory 19009 * 19010 * @param userObj 19011 * @param options 19012 * @param callBack 19013 */ 19014 publish: function(userObj, options, callBack) { 19015 // Avoid null references. 19016 options = options || {}; 19017 callBack = callBack || {}; 19018 19019 if (callBack.sending === "function") { 19020 callBack.sending(); 19021 } 19022 19023 //logs the basic version and machine info and escaped new line 19024 _logStr = Utilities.getSanitizedUserAgentString() + " "; 19025 19026 //join the logs to correct string format 19027 _logStr += unescape(_logArray.join("")); 19028 19029 //turning log string to JSON obj 19030 var logObj = { 19031 ClientLog: { 19032 logData : _logStr //_logStr 19033 } 19034 }, 19035 tmpOnAdd = (options.onAdd && typeof options.onAdd === "function")? options.onAdd : function(){}; 19036 /** @private */ 19037 options.onAdd = function(){ 19038 tmpOnAdd(); 19039 _logArray.length = 0; _logSize =0; 19040 _sendLogShown = false; 19041 }; 19042 //adding onLoad to the callbacks, this is the subscribe success case for the first time user subscribe to the client log node 19043 /** @private */ 19044 options.onLoad = function (clientLogObj) { 19045 clientLogObj.sendLogs(logObj,{ 19046 error: callBack.error 19047 }); 19048 }; 19049 19050 userObj.getClientLog(options); 19051 } 19052 }; 19053 }()); 19054 19055 window.finesse = window.finesse || {}; 19056 window.finesse.cslogger = window.finesse.cslogger || {}; 19057 /** @private */ 19058 window.finesse.cslogger.FinesseLogger = FinesseLogger; 19059 19060 return FinesseLogger; 19061 }); 19062 19063 /** 19064 * Contains a list of topics used for containerservices pubsub. 19065 * 19066 */ 19067 19068 /** 19069 * @class 19070 * Contains a list of topics with some utility functions. 19071 */ 19072 /** @private */ 19073 define('containerservices/Topics',[], function () { 19074 19075 var Topics = (function () { 19076 19077 /** 19078 * The namespace prepended to all Finesse topics. 19079 */ 19080 this.namespace = "finesse.containerservices"; 19081 19082 /** 19083 * @private 19084 * Gets the full topic name with the ContainerServices namespace prepended. 19085 * @param {String} topic 19086 * The topic category. 19087 * @returns {String} 19088 * The full topic name with prepended namespace. 19089 */ 19090 var _getNSTopic = function (topic) { 19091 return this.namespace + "." + topic; 19092 }; 19093 19094 19095 19096 /** @scope finesse.containerservices.Topics */ 19097 return { 19098 /** 19099 * @private 19100 * request channel. */ 19101 REQUESTS: _getNSTopic("requests"), 19102 19103 /** 19104 * @private 19105 * reload gadget channel. */ 19106 RELOAD_GADGET: _getNSTopic("reloadGadget"), 19107 19108 /** 19109 * @private 19110 * Convert a Finesse REST URI to a OpenAjax compatible topic name. 19111 */ 19112 getTopic: function (restUri) { 19113 //The topic should not start with '/' else it will get replaced with 19114 //'.' which is invalid. 19115 //Thus, remove '/' if it is at the beginning of the string 19116 if (restUri.indexOf('/') === 0) { 19117 restUri = restUri.substr(1); 19118 } 19119 19120 //Replace every instance of "/" with ".". This is done to follow the 19121 //OpenAjaxHub topic name convention. 19122 return restUri.replace(/\//g, "."); 19123 } 19124 }; 19125 }()); 19126 19127 window.finesse = window.finesse || {}; 19128 window.finesse.containerservices = window.finesse.containerservices || {}; 19129 window.finesse.containerservices.Topics = Topics; 19130 19131 /** @namespace JavaScript class objects and methods to handle gadget container services.*/ 19132 finesse.containerservices = finesse.containerservices || {}; 19133 19134 return Topics; 19135 }); 19136 19137 /** The following comment is to prevent jslint errors about 19138 * using variables before they are defined. 19139 */ 19140 /*global finesse*/ 19141 19142 /** 19143 * Per containerservices request, publish to the OpenAjax gadget pubsub infrastructure. 19144 * 19145 * @requires OpenAjax, finesse.containerservices.Topics 19146 */ 19147 19148 /** @private */ 19149 define('containerservices/MasterPublisher',[ 19150 "utilities/Utilities", 19151 "containerservices/Topics" 19152 ], 19153 function (Utilities, Topics) { 19154 19155 var MasterPublisher = function () { 19156 19157 var 19158 19159 /** 19160 * Reference to the gadget pubsub Hub instance. 19161 * @private 19162 */ 19163 _hub = gadgets.Hub, 19164 19165 /** 19166 * Reference to the Topics class. 19167 * @private 19168 */ 19169 _topics = Topics, 19170 19171 /** 19172 * Reference to conversion utilities class. 19173 * @private 19174 */ 19175 _utils = Utilities, 19176 19177 /** 19178 * References to ClientServices logger methods 19179 * @private 19180 */ 19181 _logger = { 19182 log: finesse.clientservices.ClientServices.log 19183 }, 19184 19185 /** 19186 * The types of possible request types supported when listening to the 19187 * requests channel. Each request type could result in different operations. 19188 * @private 19189 */ 19190 _REQTYPES = { 19191 ACTIVETAB: "ActiveTabReq", 19192 SET_ACTIVETAB: "SetActiveTabReq", 19193 RELOAD_GADGET: "ReloadGadgetReq" 19194 }, 19195 19196 /** 19197 * Handles client requests made to the request topic. The type of the 19198 * request is described in the "type" property within the data payload. Each 19199 * type can result in a different operation. 19200 * @param {String} topic 19201 * The topic which data was published to. 19202 * @param {Object} data 19203 * The data containing requests information published by clients. 19204 * @param {String} data.type 19205 * The type of the request. Supported: "ActiveTabReq", "SetActiveTabReq", "ReloadGadgetReq" 19206 * @param {Object} data.data 19207 * May contain data relevant for the particular requests. 19208 * @param {String} [data.invokeID] 19209 * The ID used to identify the request with the response. The invoke ID 19210 * will be included in the data in the publish to the topic. It is the 19211 * responsibility of the client to correlate the published data to the 19212 * request made by using the invoke ID. 19213 * @private 19214 */ 19215 _clientRequestHandler = function (topic, data) { 19216 19217 //Ensure a valid data object with "type" and "data" properties. 19218 if (typeof data === "object" && 19219 typeof data.type === "string" && 19220 typeof data.data === "object") { 19221 switch (data.type) { 19222 case _REQTYPES.ACTIVETAB: 19223 _hub.publish("finesse.containerservices.activeTab", finesse.container.Tabs.getActiveTab()); 19224 break; 19225 case _REQTYPES.SET_ACTIVETAB: 19226 if (typeof data.data.id === "string") { 19227 _logger.log("Handling request to activate tab: " + data.data.id); 19228 if (!finesse.container.Tabs.activateTab(data.data.id)) { 19229 _logger.log("No tab found with id: " + data.data.id); 19230 } 19231 } 19232 break; 19233 case _REQTYPES.RELOAD_GADGET: 19234 _hub.publish("finesse.containerservices.reloadGadget", data.data); 19235 break; 19236 default: 19237 break; 19238 } 19239 } 19240 }; 19241 19242 (function () { 19243 19244 //Listen to a request channel to respond to any requests made by other 19245 //clients because the Master may have access to useful information. 19246 _hub.subscribe(_topics.REQUESTS, _clientRequestHandler); 19247 }()); 19248 19249 //BEGIN TEST CODE// 19250 /** 19251 * Test code added to expose private functions that are used by unit test 19252 * framework. This section of code is removed during the build process 19253 * before packaging production code. The [begin|end]TestSection are used 19254 * by the build to identify the section to strip. 19255 * @ignore 19256 */ 19257 this.beginTestSection = 0; 19258 19259 /** 19260 * @ignore 19261 */ 19262 this.getTestObject = function () { 19263 //Load mock dependencies. 19264 var _mock = new MockControl(); 19265 _hub = _mock.createMock(gadgets.Hub); 19266 19267 return { 19268 //Expose mock dependencies 19269 mock: _mock, 19270 hub: _hub, 19271 19272 //Expose internal private functions 19273 reqtypes: _REQTYPES, 19274 19275 clientRequestHandler: _clientRequestHandler 19276 19277 }; 19278 }; 19279 19280 19281 /** 19282 * @ignore 19283 */ 19284 this.endTestSection = 0; 19285 //END TEST CODE// 19286 }; 19287 19288 window.finesse = window.finesse || {}; 19289 window.finesse.containerservices = window.finesse.containerservices || {}; 19290 window.finesse.containerservices.MasterPublisher = MasterPublisher; 19291 19292 return MasterPublisher; 19293 }); 19294 19295 /** 19296 * JavaScript representation of the Finesse WorkflowActionEvent object. 19297 * 19298 * @requires finesse.FinesseBase 19299 */ 19300 19301 /** The following comment is to prevent jslint errors about 19302 * using variables before they are defined. 19303 */ 19304 /*global FinesseBase: true, publisher:true, define:true, finesse:true, window:true */ 19305 /** @private */ 19306 define('containerservices/WorkflowActionEvent', ["FinesseBase"], function (FinesseBase) { 19307 var WorkflowActionEvent = FinesseBase.extend(/** @lends finesse.containerservices.WorkflowActionEvent.prototype */{ 19308 /** 19309 * Reference to the WorkflowActionEvent name 19310 * This will be set by setWorkflowActionEvent 19311 * @private 19312 */ 19313 _name: null, 19314 19315 /** 19316 * Reference to the WorkflowActionEvent type 19317 * This will be set by setWorkflowActionEvent 19318 * @private 19319 */ 19320 _type: null, 19321 19322 /** 19323 * Reference to the WorkflowActionEvent handledBy value 19324 * This will be set by setWorkflowActionEvent 19325 * @private 19326 */ 19327 _handledBy: null, 19328 19329 /** 19330 * Reference to the WorkflowActionEvent params array 19331 * This will be set by setWorkflowActionEvent 19332 * @private 19333 */ 19334 _params: [], 19335 19336 /** 19337 * Reference to the WorkflowActionEvent actionVariables array 19338 * This will be set by setWorkflowActionEvent 19339 * @private 19340 */ 19341 _actionVariables: [], 19342 19343 /** 19344 * @class 19345 * JavaScript representation of a WorkflowActionEvent object. 19346 * The WorkflowActionEvent object is delivered as the payload of 19347 * a WorkflowAction callback. This can be subscribed to by using 19348 * {@link finesse.containerservices.ContainerServices#addHandler} with a 19349 * topic of {@link finesse.containerservices.ContainerServices.Topics#WORKFLOW_ACTION_EVENT}. 19350 * Gadgets should key on events with a handleBy value of "OTHER". 19351 * 19352 * @constructs 19353 **/ 19354 init: function () { 19355 this._super(); 19356 }, 19357 19358 /** 19359 * Validate that the passed in object is a WorkflowActionEvent object 19360 * and sets the variables if it is 19361 * @param maybeWorkflowActionEvent A possible WorkflowActionEvent object to be evaluated and set if 19362 * it validates successfully. 19363 * @returns {Boolean} Whether it is valid or not. 19364 * @private 19365 */ 19366 setWorkflowActionEvent: function(maybeWorkflowActionEvent) { 19367 var returnValue; 19368 19369 if (maybeWorkflowActionEvent.hasOwnProperty("name") === true && 19370 maybeWorkflowActionEvent.hasOwnProperty("type") === true && 19371 maybeWorkflowActionEvent.hasOwnProperty("handledBy") === true && 19372 maybeWorkflowActionEvent.hasOwnProperty("params") === true && 19373 maybeWorkflowActionEvent.hasOwnProperty("actionVariables") === true) { 19374 this._name = maybeWorkflowActionEvent.name; 19375 this._type = maybeWorkflowActionEvent.type; 19376 this._handledBy = maybeWorkflowActionEvent.handledBy; 19377 this._params = maybeWorkflowActionEvent.params; 19378 this._actionVariables = maybeWorkflowActionEvent.actionVariables; 19379 returnValue = true; 19380 } else { 19381 returnValue = false; 19382 } 19383 19384 return returnValue; 19385 }, 19386 19387 /** 19388 * Getter for the WorkflowActionEvent name. 19389 * @returns {String} The name of the WorkflowAction. 19390 */ 19391 getName: function () { 19392 // escape nulls to empty string 19393 return this._name || ""; 19394 }, 19395 19396 /** 19397 * Getter for the WorkflowActionEvent type. 19398 * @returns {String} The type of the WorkflowAction (BROWSER_POP, HTTP_REQUEST). 19399 */ 19400 getType: function () { 19401 // escape nulls to empty string 19402 return this._type || ""; 19403 }, 19404 19405 /** 19406 * Getter for the WorkflowActionEvent handledBy value. Gadgets should look for 19407 * events with a handleBy of "OTHER". 19408 * @see finesse.containerservices.WorkflowActionEvent.HandledBy 19409 * @returns {String} The handledBy value of the WorkflowAction that is a value of {@link finesse.containerservices.WorkflowActionEvent.HandledBy}. 19410 */ 19411 getHandledBy: function () { 19412 // escape nulls to empty string 19413 return this._handledBy || ""; 19414 }, 19415 19416 19417 /** 19418 * Getter for the WorkflowActionEvent Params map. 19419 * @returns {Object} key = param name, value = Object{name, value, expandedValue} 19420 * BROWSER_POP<ul> 19421 * <li>windowName : Name of window to pop into, or blank to always open new window. 19422 * <li>path : URL to open.</ul> 19423 * HTTP_REQUEST<ul> 19424 * <li>method : "PUT" or "POST". 19425 * <li>location : "FINESSE" or "OTHER". 19426 * <li>contentType : MIME type of request body, if applicable, e.g. "text/plain". 19427 * <li>path : Request URL. 19428 * <li>body : Request content for POST requests.</ul> 19429 */ 19430 getParams: function () { 19431 var map = {}, 19432 params = this._params, 19433 i, 19434 param; 19435 19436 if (params === null || params.length === 0) { 19437 return map; 19438 } 19439 19440 for (i = 0; i < params.length; i += 1) { 19441 param = params[i]; 19442 // escape nulls to empty string 19443 param.name = param.name || ""; 19444 param.value = param.value || ""; 19445 param.expandedValue = param.expandedValue || ""; 19446 map[param.name] = param; 19447 } 19448 19449 return map; 19450 }, 19451 19452 /** 19453 * Getter for the WorkflowActionEvent ActionVariables map 19454 * @returns {Object} key = action variable name, value = Object{name, type, node, testValue, actualValue} 19455 */ 19456 getActionVariables: function() { 19457 var map = {}, 19458 actionVariables = this._actionVariables, 19459 i, 19460 actionVariable; 19461 19462 if (actionVariables === null || actionVariables.length === 0) { 19463 return map; 19464 } 19465 19466 for (i = 0; i < actionVariables.length; i += 1) { 19467 actionVariable = actionVariables[i]; 19468 // escape nulls to empty string 19469 actionVariable.name = actionVariable.name || ""; 19470 actionVariable.type = actionVariable.type || ""; 19471 actionVariable.node = actionVariable.node || ""; 19472 actionVariable.testValue = actionVariable.testValue || ""; 19473 actionVariable.actualValue = actionVariable.actualValue || ""; 19474 map[actionVariable.name] = actionVariable; 19475 } 19476 19477 return map; 19478 } 19479 }); 19480 19481 19482 WorkflowActionEvent.HandledBy = /** @lends finesse.containerservices.WorkflowActionEvent.HandledBy.prototype */ { 19483 /** 19484 * This specifies that Finesse will handle this WorkflowActionEvent. A 3rd Party can do additional processing 19485 * with the action, but first and foremost Finesse will handle this WorkflowAction. 19486 */ 19487 FINESSE: "FINESSE", 19488 19489 /** 19490 * This specifies that a 3rd Party will handle this WorkflowActionEvent. Finesse's Workflow Engine Executor will 19491 * ignore this action and expects Gadget Developers to take action. 19492 */ 19493 OTHER: "OTHER", 19494 19495 /** 19496 * @class This is the set of possible HandledBy values used for WorkflowActionEvent from ContainerServices. This 19497 * is provided from the {@link finesse.containerservices.WorkflowActionEvent#getHandledBy} method. 19498 * @constructs 19499 */ 19500 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 19501 }; 19502 19503 window.finesse = window.finesse || {}; 19504 window.finesse.containerservices = window.finesse.containerservices || {}; 19505 window.finesse.containerservices.WorkflowActionEvent = WorkflowActionEvent; 19506 19507 return WorkflowActionEvent; 19508 }); 19509 19510 /** 19511 * JavaScript representation of the Finesse TimerTickEvent 19512 * 19513 * @requires finesse.FinesseBase 19514 */ 19515 19516 /** The following comment is to prevent jslint errors about 19517 * using variables before they are defined. 19518 */ 19519 /*global FinesseBase: true, publisher:true, define:true, finesse:true, window:true */ 19520 /** @private */ 19521 define('containerservices/TimerTickEvent',[ 19522 "FinesseBase" 19523 ], 19524 function (FinesseBase) { 19525 var TimerTickEvent = FinesseBase.extend(/** @lends finesse.containerservices.TimerTickEvent.prototype */{ 19526 /** 19527 * date the TimerTickEvent was queued 19528 * @private 19529 */ 19530 _dateQueued: null, 19531 19532 /** 19533 * the frequency of the timer tick (in miiliseconds) 19534 * @private 19535 */ 19536 _tickFrequency: 1000, 19537 19538 /** 19539 * @class 19540 * JavaScript representation of a TimerTickEvent object. 19541 * The TimerTickEvent object is delivered as the payload of 19542 * a TimerTickEvent callback. This can be subscribed to by using 19543 * {@link finesse.containerservices.ContainerServices#addHandler} with a 19544 * topic of {@link finesse.containerservices.ContainerServices.Topics#TIMER_TICK_EVENT}. 19545 * 19546 * @constructs 19547 **/ 19548 init: function (tickFrequency, dateQueued) { 19549 this._super(); 19550 19551 this._tickFrequency = tickFrequency; 19552 this._dateQueued = dateQueued; 19553 }, 19554 19555 /** 19556 * Get the "tickFrequency" field 19557 * @param {int} which is the "TickFrequency" field 19558 * @private 19559 */ 19560 getTickFrequency: function () { 19561 return this._tickFrequency; 19562 }, 19563 19564 /** 19565 * Getter for the TimerTickEvent "DateQueued" field. 19566 * @returns {Date} which is a Date object when the TimerTickEvent was queued 19567 */ 19568 getDateQueued: function () { 19569 return this._dateQueued; 19570 } 19571 19572 }); 19573 19574 window.finesse = window.finesse || {}; 19575 window.finesse.containerservices = window.finesse.containerservices || {}; 19576 window.finesse.containerservices.TimerTickEvent = TimerTickEvent; 19577 19578 return TimerTickEvent; 19579 }); 19580 19581 /** 19582 * JavaScript representation of the Finesse GadgetViewChangedEvent object. 19583 * 19584 * @requires finesse.FinesseBase 19585 */ 19586 19587 /** The following comment is to prevent jslint errors about 19588 * using variables before they are defined. 19589 */ 19590 /*global FinesseBase: true, publisher:true, define:true, finesse:true, window:true */ 19591 /** @private */ 19592 define('containerservices/GadgetViewChangedEvent',[ 19593 "FinesseBase" 19594 ], 19595 function (FinesseBase) { 19596 var GadgetViewChangedEvent = FinesseBase.extend(/** @lends finesse.containerservices.GadgetViewChangedEvent.prototype */{ 19597 /** 19598 * Reference to the gadget id 19599 * @private 19600 */ 19601 _gadgetId: null, 19602 19603 /** 19604 * Reference to the tab id 19605 * @private 19606 */ 19607 _tabId: null, 19608 19609 /** 19610 * Reference to the maxAvailableHeight 19611 * @private 19612 */ 19613 _maxAvailableHeight: null, 19614 19615 /** 19616 * Reference to the view 19617 * E.g. 'default' or 'canvas' 19618 * @private 19619 */ 19620 _view: null, 19621 19622 /** 19623 * @class 19624 * JavaScript representation of a GadgetViewChangedEvent object. 19625 * The GadgetViewChangedEvent object is delivered as the payload of 19626 * a GadgetViewChangedEvent callback. This can be subscribed to by using 19627 * {@link finesse.containerservices.ContainerServices#addHandler} with a 19628 * topic of {@link finesse.containerservices.ContainerServices.Topics#GADGET_VIEW_CHANGED_EVENT}. 19629 * 19630 * @constructs 19631 **/ 19632 init: function (gadgetId, tabId, maxAvailableHeight, view) { 19633 this._super(); 19634 19635 this._gadgetId = gadgetId; 19636 this._tabId = tabId; 19637 this._maxAvailableHeight = maxAvailableHeight; 19638 this._view = view; 19639 }, 19640 19641 /** 19642 * Getter for the gadget id. 19643 * @returns {String} The identifier for the gadget changing view. 19644 */ 19645 getGadgetId: function () { 19646 // escape nulls to empty string 19647 return this._gadgetId || ""; 19648 }, 19649 19650 /** 19651 * Getter for the maximum available height. 19652 * @returns {String} The maximum available height for the gadget's view. 19653 */ 19654 getMaxAvailableHeight: function () { 19655 // escape nulls to empty string 19656 return this._maxAvailableHeight || ""; 19657 }, 19658 19659 /** 19660 * Getter for the tab id. 19661 * @returns {String} The identifier for the tab where the gadget changing view resides. 19662 */ 19663 getTabId: function () { 19664 // escape nulls to empty string 19665 return this._tabId || ""; 19666 }, 19667 19668 /** 19669 * Getter for the view. 19670 * @returns {String} The view type the gadget is changing to. 19671 */ 19672 getView: function () { 19673 // escape nulls to empty string 19674 return this._view || ""; 19675 } 19676 }); 19677 19678 window.finesse = window.finesse || {}; 19679 window.finesse.containerservices = window.finesse.containerservices || {}; 19680 window.finesse.containerservices.GadgetViewChangedEvent = GadgetViewChangedEvent; 19681 19682 return GadgetViewChangedEvent; 19683 }); 19684 19685 /** 19686 * JavaScript representation of the Finesse MaxAvailableHeightChangedEvent object. 19687 * 19688 * @requires finesse.FinesseBase 19689 */ 19690 19691 /** The following comment is to prevent jslint errors about 19692 * using variables before they are defined. 19693 */ 19694 /*global FinesseBase: true, publisher:true, define:true, finesse:true, window:true */ 19695 /** @private */ 19696 define('containerservices/MaxAvailableHeightChangedEvent',[ 19697 "FinesseBase" 19698 ], 19699 function (FinesseBase) { 19700 var MaxAvailableHeightChangedEvent = FinesseBase.extend(/** @lends finesse.containerservices.MaxAvailableHeightChangedEvent.prototype */{ 19701 19702 /** 19703 * Reference to the maxAvailableHeight 19704 * @private 19705 */ 19706 _maxAvailableHeight: null, 19707 19708 /** 19709 * @class 19710 * JavaScript representation of a MaxAvailableHeightChangedEvent object. 19711 * The MaxAvailableHeightChangedEvent object is delivered as the payload of 19712 * a MaxAvailableHeightChangedEvent callback. This can be subscribed to by using 19713 * {@link finesse.containerservices.ContainerServices#addHandler} with a 19714 * topic of {@link finesse.containerservices.ContainerServices.Topics#MAX_AVAILABLE_HEIGHT_CHANGED_EVENT}. 19715 * 19716 * @constructs 19717 **/ 19718 init: function (maxAvailableHeight) { 19719 this._super(); 19720 19721 this._maxAvailableHeight = maxAvailableHeight; 19722 }, 19723 19724 /** 19725 * Getter for the maximum available height. 19726 * @returns {String} The maximum available height for a gadget in canvas view 19727 */ 19728 getMaxAvailableHeight: function () { 19729 // escape nulls to empty string 19730 return this._maxAvailableHeight || ""; 19731 } 19732 }); 19733 19734 window.finesse = window.finesse || {}; 19735 window.finesse.containerservices = window.finesse.containerservices || {}; 19736 window.finesse.containerservices.MaxAvailableHeightChangedEvent = MaxAvailableHeightChangedEvent; 19737 19738 return MaxAvailableHeightChangedEvent; 19739 }); 19740 19741 /** 19742 * Exposes a set of API wrappers that will hide the dirty work of 19743 * constructing Finesse API requests and consuming Finesse events. 19744 * 19745 * @requires OpenAjax, jQuery 1.5, finesse.utilities.Utilities 19746 */ 19747 19748 /** The following comment is to prevent jslint errors about using variables before they are defined. */ 19749 /*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 */ 19750 /*jslint nomen: true, unparam: true, sloppy: true, white: true */ 19751 /** @private */ 19752 define('containerservices/ContainerServices',[ 19753 "utilities/Utilities", 19754 "restservices/Notifier", 19755 "containerservices/Topics", 19756 "containerservices/MasterPublisher", 19757 "containerservices/WorkflowActionEvent", 19758 "containerservices/TimerTickEvent", 19759 "containerservices/GadgetViewChangedEvent", 19760 "containerservices/MaxAvailableHeightChangedEvent" 19761 ], 19762 function (Utilities, Notifier, Topics, MasterPublisher, WorkflowActionEvent) { 19763 19764 var ContainerServices = ( function () { /** @lends finesse.containerservices.ContainerServices.prototype */ 19765 19766 var 19767 19768 /** 19769 * Shortcut reference to the Utilities singleton 19770 * This will be set by init() 19771 * @private 19772 */ 19773 _util, 19774 19775 /** 19776 * Shortcut reference to the gadget pubsub Hub instance. 19777 * This will be set by init() 19778 * @private 19779 */ 19780 _hub, 19781 19782 /** 19783 * Boolean whether this instance is master or not 19784 * @private 19785 */ 19786 _master = false, 19787 19788 /** 19789 * Whether the Client Services have been initiated yet. 19790 * @private 19791 */ 19792 _inited = false, 19793 19794 /** 19795 * References to ClientServices logger methods 19796 * @private 19797 */ 19798 _logger = { 19799 log: finesse.clientservices.ClientServices.log 19800 }, 19801 19802 /** 19803 * Stores the list of subscription IDs for all subscriptions so that it 19804 * could be retrieve for unsubscriptions. 19805 * @private 19806 */ 19807 _subscriptionID = {}, 19808 19809 /** 19810 * Reference to the gadget's parent container 19811 * @private 19812 */ 19813 _container, 19814 19815 /** 19816 * Reference to the MasterPublisher 19817 * @private 19818 */ 19819 _publisher, 19820 19821 /** 19822 * Object that will contain the Notifiers 19823 * @private 19824 */ 19825 _notifiers = {}, 19826 19827 /** 19828 * Reference to the tabId that is associated with the gadget 19829 * @private 19830 */ 19831 _myTab = null, 19832 19833 /** 19834 * Reference to the visibility of current gadget 19835 * @private 19836 */ 19837 _visible = false, 19838 19839 /** 19840 * Reference for auth modes constants. 19841 * @private 19842 */ 19843 _authModes, 19844 19845 /** 19846 * Shortcut reference to the Topics class. 19847 * This will be set by init() 19848 * @private 19849 */ 19850 _topics, 19851 19852 /** 19853 * Associates a topic name with the private handler function. 19854 * Adding a new topic requires that you add this association here 19855 * in to keep addHandler generic. 19856 * @param {String} topic : Specifies the callback to retrieve 19857 * @return {Function} The callback function associated with the topic param. 19858 * @private 19859 */ 19860 _topicCallback = function (topic) { 19861 var callback, notifier; 19862 switch (topic) 19863 { 19864 case finesse.containerservices.ContainerServices.Topics.ACTIVE_TAB: 19865 callback = _tabTracker; 19866 break; 19867 case finesse.containerservices.ContainerServices.Topics.WORKFLOW_ACTION_EVENT: 19868 callback = _workflowActionEventTracker; 19869 break; 19870 case finesse.containerservices.ContainerServices.Topics.RELOAD_GADGET_EVENT: 19871 callback = _masterReloader; 19872 break; 19873 case finesse.containerservices.ContainerServices.Topics.GADGET_VIEW_CHANGED_EVENT: 19874 callback = _gadgetViewChanged; 19875 break; 19876 case finesse.containerservices.ContainerServices.Topics.MAX_AVAILABLE_HEIGHT_CHANGED_EVENT: 19877 callback = _maxAvailableHeightChanged; 19878 break; 19879 case finesse.containerservices.ContainerServices.Topics.ACCESS_TOKEN_REFRESHED_EVENT: 19880 callback = _accessTokenRefreshed; 19881 break; 19882 default: 19883 callback = function (param) { 19884 var data = null; 19885 19886 notifier = _getNotifierReference(topic); 19887 19888 if (arguments.length === 1) { 19889 data = param; 19890 } else { 19891 data = arguments; 19892 } 19893 notifier.notifyListeners(data); 19894 }; 19895 } 19896 return callback; 19897 }, 19898 19899 /** 19900 * Ensure that ClientServices have been inited. 19901 * @private 19902 */ 19903 _isInited = function () { 19904 if (!_inited) { 19905 throw new Error("ContainerServices needs to be inited."); 19906 } 19907 }, 19908 19909 /** 19910 * Retrieves a Notifier reference to a particular topic, and creates one if it doesn't exist. 19911 * @param {String} topic : Specifies the notifier to retrieve 19912 * @return {Notifier} The notifier object. 19913 * @private 19914 */ 19915 _getNotifierReference = function (topic) { 19916 if (!_notifiers.hasOwnProperty(topic)) 19917 { 19918 _notifiers[topic] = new Notifier(); 19919 } 19920 19921 return _notifiers[topic]; 19922 }, 19923 19924 /** 19925 * Utility function to make a subscription to a particular topic. Only one 19926 * callback function is registered to a particular topic at any time. 19927 * @param {String} topic 19928 * The full topic name. The topic name should follow the OpenAjax 19929 * convention using dot notation (ex: finesse.api.User.1000). 19930 * @param {Function} callback 19931 * The function that should be invoked with the data when an event 19932 * is delivered to the specific topic. 19933 * @returns {Boolean} 19934 * True if the subscription was made successfully and the callback was 19935 * been registered. False if the subscription already exist, the 19936 * callback was not overwritten. 19937 * @private 19938 */ 19939 _subscribe = function (topic, callback) { 19940 _isInited(); 19941 19942 //Ensure that the same subscription isn't made twice. 19943 if (!_subscriptionID[topic]) { 19944 //Store the subscription ID using the topic name as the key. 19945 _subscriptionID[topic] = _hub.subscribe(topic, 19946 //Invoke the callback just with the data object. 19947 function (topic, data) { 19948 callback(data); 19949 }); 19950 return true; 19951 } 19952 return false; 19953 }, 19954 19955 /** 19956 * Unsubscribe from a particular topic. 19957 * @param {String} topic : The full topic name. 19958 * @private 19959 */ 19960 _unsubscribe = function (topic) { 19961 _isInited(); 19962 19963 //Unsubscribe from the topic using the subscription ID recorded when 19964 //the subscription was made, then delete the ID from data structure. 19965 _hub.unsubscribe(_subscriptionID[topic]); 19966 delete _subscriptionID[topic]; 19967 }, 19968 19969 /** 19970 * Get my tab id. 19971 * @returns {String} tabid : The tabid of this container/gadget. 19972 * @private 19973 */ 19974 _getMyTab = function () { 19975 if (_myTab === null) 19976 { 19977 try { 19978 _myTab = $(frameElement).closest("div.tab-panel").attr("id").replace("panel_", ""); 19979 } catch (err) { 19980 _logger.log("Error accessing current tab: " + err.message); 19981 _myTab = null; 19982 } 19983 } 19984 return _myTab; 19985 }, 19986 19987 /** 19988 * Callback function that is called when an activeTab message is posted to the Hub. 19989 * Notifies listener functions if this tab is the one that was just made active. 19990 * @param {String} tabId : The tabId which was just made visible. 19991 * @private 19992 */ 19993 _tabTracker = function(tabId) { 19994 if (tabId === _getMyTab()) { 19995 if(!_visible) { 19996 _visible = true; 19997 _notifiers[finesse.containerservices.ContainerServices.Topics.ACTIVE_TAB].notifyListeners(this); 19998 } 19999 } else { 20000 _visible = false; 20001 } 20002 }, 20003 20004 /** 20005 * Make a request to set a particular tab active. This 20006 * method should be called after {@link finesse.containerservices.ContainerServices#addHandler} 20007 * to ensure the gadget gets properly initialized. 20008 * @param {String} tabId 20009 * The tabId (not the label text) of the tab to make active. If the id is invalid, no action will occur. 20010 * @private 20011 */ 20012 _activateTab = function ( tabId ) { 20013 _logger.log("Sending request to activate tab: " + tabId); 20014 if(_hub){ 20015 var data = { 20016 type: "SetActiveTabReq", 20017 data: { id: tabId }, 20018 invokeID: (new Date()).getTime() 20019 }; 20020 _hub.publish(_topics.REQUESTS, data); 20021 } else { 20022 throw new Error("Hub is not defined."); 20023 } 20024 20025 }, 20026 20027 /** 20028 * Callback function that is called when a gadget view changed message is posted to the Hub. 20029 * @private 20030 */ 20031 _gadgetViewChanged = function (data) { 20032 if (data) { 20033 var gadgetViewChangedEvent = new finesse.containerservices.GadgetViewChangedEvent( 20034 data.gadgetId, 20035 data.tabId, 20036 data.maxAvailableHeight, 20037 data.view); 20038 20039 _notifiers[finesse.containerservices.ContainerServices.Topics.GADGET_VIEW_CHANGED_EVENT].notifyListeners(gadgetViewChangedEvent); 20040 } 20041 }, 20042 20043 /** 20044 * Callback function that is called when a max available height changed message is posted to the Hub. 20045 * @private 20046 */ 20047 _maxAvailableHeightChanged = function (data) { 20048 if (data) { 20049 var maxAvailableHeightChangedEvent = new finesse.containerservices.MaxAvailableHeightChangedEvent( 20050 data.maxAvailableHeight); 20051 20052 _notifiers[finesse.containerservices.ContainerServices.Topics.MAX_AVAILABLE_HEIGHT_CHANGED_EVENT].notifyListeners(maxAvailableHeightChangedEvent); 20053 } 20054 }, 20055 20056 /** 20057 * Callback function that is called when a workflowActionEvent message is posted to the Hub. 20058 * Notifies listener functions if the posted object can be converted to a proper WorkflowActionEvent object. 20059 * @param {String} workflowActionEvent : The workflowActionEvent that was posted to the Hub 20060 * @private 20061 */ 20062 _workflowActionEventTracker = function(workflowActionEvent) { 20063 var vWorkflowActionEvent = new finesse.containerservices.WorkflowActionEvent(); 20064 20065 if (vWorkflowActionEvent.setWorkflowActionEvent(workflowActionEvent)) { 20066 _notifiers[finesse.containerservices.ContainerServices.Topics.WORKFLOW_ACTION_EVENT].notifyListeners(vWorkflowActionEvent); 20067 } 20068 // else 20069 // { 20070 //?console.log("Error in ContainerServices : _workflowActionEventTracker - could not map published HUB object to WorkflowActionEvent"); 20071 // } 20072 20073 }, 20074 20075 /** 20076 * Callback function that is called when a reloadGadget event message is posted to the Hub. 20077 * 20078 * Grabs the id of the gadget we want to reload from the data and reload it! 20079 * 20080 * @param {String} topic 20081 * which topic the event came on (unused) 20082 * @param {Object} data 20083 * the data published with the event 20084 * @private 20085 */ 20086 _masterReloader = function (topic, data) { 20087 var gadgetId = data.gadgetId; 20088 if (gadgetId) { 20089 _container.reloadGadget(gadgetId); 20090 } 20091 }, 20092 20093 /** 20094 * Callback function that is called when a refresh access token event message is posted to the Hub. 20095 * 20096 * Get access token from the data and update the finesse.gadget.Config object! 20097 * 20098 * @param {String} topic 20099 * which topic the event came on (unused) 20100 * @param {Object} data 20101 * the data published with the event 20102 * @private 20103 */ 20104 _accessTokenRefreshed = function(topic , data){ 20105 _logger.log("Access token refreshed: updating new token to finesse.gadget.Config"); 20106 20107 if(data.authToken){ 20108 finesse.gadget.Config.authToken = data.authToken; 20109 } 20110 20111 }, 20112 20113 /** 20114 * Pulls the gadget id from the url parameters 20115 * @return {String} id of the gadget 20116 * @private 20117 */ 20118 _findMyGadgetId = function () { 20119 if (gadgets && gadgets.util && gadgets.util.getUrlParameters()) { 20120 return gadgets.util.getUrlParameters().mid; 20121 } 20122 }; 20123 20124 return { 20125 /** 20126 * @class 20127 * This class provides container-level services for gadget developers, exposing container events by 20128 * calling a set of exposed functions. Gadgets can utilize the container dialogs and 20129 * event handling (add/remove). 20130 * @example 20131 * containerServices = finesse.containerservices.ContainerServices.init(); 20132 * containerServices.addHandler( 20133 * finesse.containerservices.ContainerServices.Topics.ACTIVE_TAB, 20134 * function() { 20135 * clientLogs.log("Gadget is now visible"); // log to Finesse logger 20136 * // automatically adjust the height of the gadget to show the html 20137 * gadgets.window.adjustHeight(); 20138 * }); 20139 * containerServices.makeActiveTabReq(); 20140 * 20141 * @constructs 20142 */ 20143 _fakeConstuctor: function () { 20144 /* This is here so we can document init() as a method rather than as a constructor. */ 20145 }, 20146 20147 /** 20148 * Initialize ContainerServices for use in gadget. 20149 * @param {Boolean} [master=false] Do not use this parameter from your gadget. 20150 * @returns ContainerServices instance. 20151 */ 20152 init: function (master) { 20153 if (!_inited) { 20154 _inited = true; 20155 // Set shortcuts 20156 _util = Utilities; 20157 _authModes = _util.getAuthModes(); 20158 20159 //init the hub only when it's available 20160 if(gadgets.Hub) { 20161 _hub = gadgets.Hub; 20162 } 20163 20164 if(Topics) { 20165 _topics = Topics; 20166 } 20167 20168 if (master) { 20169 _master = true; 20170 _container = finesse.container.Container; 20171 _publisher = new MasterPublisher(); 20172 20173 // subscribe for reloading gadget events 20174 // we only want the master ContainerServices handling these events 20175 _hub.subscribe(_topics.RELOAD_GADGET, _topicCallback(_topics.RELOAD_GADGET)); 20176 } else { 20177 _container = parent.finesse.container.Container; 20178 } 20179 } 20180 20181 this.makeActiveTabReq(); 20182 20183 //Every gadget should subscribe for access token refresh events in case of SSO deployment 20184 if( finesse.container.Config.systemAuthMode === _authModes.SSO){ 20185 _hub.subscribe(finesse.containerservices.ContainerServices.Topics.ACCESS_TOKEN_REFRESHED_EVENT, _topicCallback(finesse.containerservices.ContainerServices.Topics.ACCESS_TOKEN_REFRESHED_EVENT)); 20186 } 20187 20188 //Return the CS object for object chaining. 20189 return this; 20190 }, 20191 20192 /** 20193 * Shows the jQuery UI Dialog with the specified parameters. The following are the 20194 * default parameters: <ul> 20195 * <li> Title of "Cisco Finesse".</li> 20196 * <li>Message of "A generic error has occured".</li> 20197 * <li>The only button, "Ok", closes the dialog.</li> 20198 * <li>Modal (blocks other dialogs).</li> 20199 * <li>Not draggable.</li> 20200 * <li>Fixed size.</li></ul> 20201 * @param {Object} options 20202 * An object containing additional options for the dialog. 20203 * @param {String/Boolean} options.title 20204 * Title to use. undefined defaults to "Cisco Finesse". false to hide 20205 * @param {Function} options.close 20206 * A function to invoke when the dialog is closed. 20207 * @param {String} options.message 20208 * The message to display in the dialog. 20209 * Defaults to "A generic error has occurred." 20210 * @param {Boolean} options.isBlocking 20211 * Flag indicating whether this dialog will block other dialogs from being shown (Modal). 20212 * @returns {jQuery} JQuery wrapped object of the dialog DOM element. 20213 * @see finesse.containerservices.ContainerServices#hideDialog 20214 */ 20215 showDialog: function(options) { 20216 if ((_container.showDialog !== undefined) && (_container.showDialog !== this.showDialog)) { 20217 return _container.showDialog(options); 20218 } 20219 }, 20220 20221 /** 20222 * Hides the jQuery UI Dialog. 20223 * @returns {jQuery} jQuery wrapped object of the dialog DOM element 20224 * @see finesse.containerservices.ContainerServices#showDialog 20225 */ 20226 hideDialog: function() { 20227 if ((_container.hideDialog !== undefined) && (_container.hideDialog !== this.hideDialog)) { 20228 return _container.hideDialog(); 20229 } 20230 }, 20231 20232 /** 20233 * Reloads the current gadget. 20234 * For use from within a gadget only. 20235 */ 20236 reloadMyGadget: function () { 20237 var topic, gadgetId, data; 20238 20239 if (!_master) { 20240 // first unsubscribe this gadget from all topics on the hub 20241 for (topic in _notifiers) { 20242 if (_notifiers.hasOwnProperty(topic)) { 20243 _unsubscribe(topic); 20244 delete _notifiers[topic]; 20245 } 20246 } 20247 20248 // send an asynch request to the hub to tell the master container 20249 // services that we want to refresh this gadget 20250 gadgetId = _findMyGadgetId(); 20251 data = { 20252 type: "ReloadGadgetReq", 20253 data: {gadgetId: gadgetId}, 20254 invokeID: (new Date()).getTime() 20255 }; 20256 _hub.publish(_topics.REQUESTS, data); 20257 } 20258 }, 20259 20260 /** 20261 * Updates the url for this gadget and then reload it. 20262 * 20263 * This allows the gadget to be reloaded from a different location 20264 * than what is uploaded to the current server. For example, this 20265 * would be useful for 3rd party gadgets to implement their own failover 20266 * mechanisms. 20267 * 20268 * For use from within a gadget only. 20269 * 20270 * @param {String} url 20271 * url from which to reload gadget 20272 */ 20273 reloadMyGadgetFromUrl: function (url) { 20274 if (!_master) { 20275 var gadgetId = _findMyGadgetId(); 20276 20277 // update the url in the container 20278 _container.modifyGadgetUrl(gadgetId, url); 20279 20280 // reload it 20281 this.reloadMyGadget(); 20282 } 20283 }, 20284 20285 /** 20286 * Adds a handler for one of the supported topics provided by ContainerServices. The callbacks provided 20287 * will be invoked when that topic is notified. 20288 * @param {String} topic 20289 * The Hub topic to which we are listening. 20290 * @param {Function} callback 20291 * The callback function to invoke. 20292 * @see finesse.containerservices.ContainerServices.Topics 20293 * @see finesse.containerservices.ContainerServices#removeHandler 20294 */ 20295 addHandler: function (topic, callback) { 20296 _isInited(); 20297 var notifier = null; 20298 20299 try { 20300 // For backwards compatibility... 20301 if (topic === "tabVisible") { 20302 if (window.console && typeof window.console.log === "function") { 20303 window.console.log("WARNING - Using tabVisible as topic. This is deprecated. Use finesse.containerservices.ContainerServices.Topics.ACTIVE_TAB now!"); 20304 } 20305 20306 topic = finesse.containerservices.ContainerServices.Topics.ACTIVE_TAB; 20307 } 20308 20309 // Add the callback to the notifier. 20310 _util.validateHandler(callback); 20311 20312 notifier = _getNotifierReference(topic); 20313 20314 notifier.addListener(callback); 20315 20316 // Subscribe to the topic. _subscribe ensures that a topic is only subscribed to once, 20317 // so attempt to subscribe each time a handler is added. This ensures that a topic is subscribed 20318 // to only when necessary. 20319 _subscribe(topic, _topicCallback(topic)); 20320 20321 } catch (err) { 20322 throw new Error("addHandler(): " + err); 20323 } 20324 }, 20325 20326 /** 20327 * Removes a previously-added handler for one of the supported topics. 20328 * @param {String} topic 20329 * The Hub topic from which we are removing the callback. 20330 * @param {Function} callback 20331 * The name of the callback function to remove. 20332 * @see finesse.containerservices.ContainerServices.Topics 20333 * @see finesse.containerservices.ContainerServices#addHandler 20334 */ 20335 removeHandler: function(topic, callback) { 20336 var notifier = null; 20337 20338 try { 20339 _util.validateHandler(callback); 20340 20341 notifier = _getNotifierReference(topic); 20342 20343 notifier.removeListener(callback); 20344 } catch (err) { 20345 throw new Error("removeHandler(): " + err); 20346 } 20347 }, 20348 20349 /** 20350 * Returns the visibility of current gadget. Note that this 20351 * will not be set until after the initialization of the gadget. 20352 * @return {Boolean} The visibility of current gadget. 20353 */ 20354 tabVisible: function(){ 20355 return _visible; 20356 }, 20357 20358 /** 20359 * Make a request to check the current tab. The 20360 * activeTab event will be invoked if on the active tab. This 20361 * method should be called after {@link finesse.containerservices.ContainerServices#addHandler} 20362 * to ensure the gadget gets properly initialized. 20363 */ 20364 makeActiveTabReq : function () { 20365 if(_hub){ 20366 var data = { 20367 type: "ActiveTabReq", 20368 data: {}, 20369 invokeID: (new Date()).getTime() 20370 }; 20371 _hub.publish(_topics.REQUESTS, data); 20372 } else { 20373 throw new Error("Hub is not defined."); 20374 } 20375 20376 }, 20377 20378 /** 20379 * Make a request to set a particular tab active. This 20380 * method should be called after {@link finesse.containerservices.ContainerServices#addHandler} 20381 * to ensure the gadget gets properly initialized. 20382 * @param {String} tabId 20383 * The tabId (not the label text) of the tab to make active. If the id is invalid, no action will occur. 20384 */ 20385 activateTab : function (tabId) { 20386 _activateTab(tabId); 20387 }, 20388 20389 /** 20390 * Make a request to set this container's tab active. This 20391 * method should be called after {@link finesse.containerservices.ContainerServices#addHandler} 20392 * to ensure the gadget gets properly initialized. 20393 */ 20394 activateMyTab : function () { 20395 _activateTab( _getMyTab() ); 20396 }, 20397 20398 /** 20399 * Get the tabId of my container/gadget. 20400 * @returns {String} tabid : The tabid of this container/gadget. 20401 */ 20402 getMyTabId : function () { 20403 return _getMyTab(); 20404 }, 20405 20406 /** 20407 * Gets the id of the gadget. 20408 * @returns {number} the id of the gadget 20409 */ 20410 getMyGadgetId : function () { 20411 return _findMyGadgetId(); 20412 }, 20413 20414 //BEGIN TEST CODE// 20415 /** 20416 * Test code added to expose private functions that are used by unit test 20417 * framework. This section of code is removed during the build process 20418 * before packaging production code. The [begin|end]TestSection are used 20419 * by the build to identify the section to strip. 20420 * @ignore 20421 */ 20422 beginTestSection : 0, 20423 20424 /** 20425 * @ignore 20426 */ 20427 getTestObject: function () { 20428 //Load mock dependencies. 20429 var _mock = new MockControl(); 20430 _util = _mock.createMock(Utilities); 20431 _hub = _mock.createMock(gadgets.Hub); 20432 _inited = true; 20433 return { 20434 //Expose mock dependencies 20435 mock: _mock, 20436 hub: _hub, 20437 util: _util, 20438 addHandler: this.addHandler, 20439 removeHandler: this.removeHandler 20440 }; 20441 }, 20442 20443 /** 20444 * @ignore 20445 */ 20446 endTestSection: 0 20447 //END TEST CODE// 20448 }; 20449 }()); 20450 20451 ContainerServices.Topics = /** @lends finesse.containerservices.ContainerServices.Topics.prototype */ { 20452 /** 20453 * Topic for subscribing to be notified when the active tab changes. 20454 * The provided callback will be invoked when the tab that the gadget 20455 * that subscribes with this becomes active. To ensure code is called 20456 * when the gadget is already on the active tab use the 20457 * {@link finesse.containerservices.ContainerServices#makeActiveTabReq} 20458 * method. 20459 */ 20460 ACTIVE_TAB: "finesse.containerservices.activeTab", 20461 20462 /** 20463 * Topic for WorkflowAction events traffic. 20464 * The provided callback will be invoked when a WorkflowAction needs 20465 * to be handled. The callback will be passed a {@link finesse.containerservices.WorkflowActionEvent} 20466 * that can be used to interrogate the WorkflowAction and determine to use or not. 20467 */ 20468 WORKFLOW_ACTION_EVENT: "finesse.containerservices.workflowActionEvent", 20469 20470 /** 20471 * Topic for Timer Tick event. 20472 * The provided callback will be invoked when this event is fired. 20473 * The callback will be passed a {@link finesse.containerservices.TimerTickEvent}. 20474 */ 20475 TIMER_TICK_EVENT : "finesse.containerservices.timerTickEvent", 20476 20477 /** 20478 * Topic for Reload Gadget events traffic. 20479 * Only the master ContainerServices instance will handle this event. 20480 */ 20481 RELOAD_GADGET_EVENT: "finesse.containerservices.reloadGadget", 20482 20483 /** 20484 * Topic for listening to gadget view changed events. 20485 * The provided callback will be invoked when a gadget changes view. 20486 * The callback will be passed a {@link finesse.containerservices.GadgetViewChangedEvent}. 20487 */ 20488 GADGET_VIEW_CHANGED_EVENT: "finesse.containerservices.gadgetViewChangedEvent", 20489 20490 /** 20491 * Topic for listening to max available height changed events. 20492 * The provided callback will be invoked when the maximum height available to a maximized gadget changes. 20493 * This event is only meant for maximized gadgets and will not be published unless a maximized gadget exists. 20494 * The callback will be passed a {@link finesse.containerservices.MaxAvailableHeightChangedEvent}. 20495 */ 20496 MAX_AVAILABLE_HEIGHT_CHANGED_EVENT: "finesse.containerservices.maxAvailableHeightChangedEvent", 20497 20498 /** 20499 * Topic for listening to token refresh events. 20500 * The provided callback will be invoked when the access token is refreshed. 20501 * This event is only meant for updating the access token in finesse.gadget.Config object 20502 * The callback will be passed a {@link finesse.containerservices.accessTokenRefreshedEvent}. 20503 */ 20504 ACCESS_TOKEN_REFRESHED_EVENT: "finesse.containerservices.accessTokenRefreshedEvent", 20505 20506 /** 20507 * @class This is the set of Topics used for subscribing for events from ContainerServices. 20508 * Use {@link finesse.containerservices.ContainerServices#addHandler} to subscribe to the topic. 20509 * 20510 * @constructs 20511 */ 20512 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 20513 }; 20514 20515 window.finesse = window.finesse || {}; 20516 window.finesse.containerservices = window.finesse.containerservices || {}; 20517 window.finesse.containerservices.ContainerServices = ContainerServices; 20518 20519 return ContainerServices; 20520 }); 20521 20522 /** 20523 * FinesseToaster is a utility class to show toaster notification in Finesse. 20524 * FinesseToaster leverages HTML5 Notification API to display Toaster 20525 * Notification. 20526 * 20527 */ 20528 20529 define('containerservices/FinesseToaster',[],function() { 20530 20531 var FinesseToaster = (function() { /** @lends finesse.containerservices.FinesseToaster.prototype */ 20532 20533 var 20534 20535 /** How long the toaster will be displayed by default */ 20536 AUTO_CLOSE_TIME = -1, 20537 20538 /** PERMISSION_GRANTED constant for granted string */ 20539 PERMISSION_GRANTED = 'granted', 20540 20541 /** PERMISSION_DEFAULT constant for default string */ 20542 PERMISSION_DEFAULT = 'default', 20543 20544 /** PERMISSION_DENIED constant for denied string */ 20545 PERMISSION_DENIED = 'denied', 20546 20547 /** ICON_PATH constant for holding path icon images */ 20548 ICON_PATH = '/desktop/theme/finesse/images/modules/', 20549 20550 /** 20551 * Shortcut reference to finesse.cslogger.ClientLogger singleton This 20552 * will be set by init(), it should already be initialized by 20553 * PageServices 20554 * 20555 * @private 20556 */ 20557 _logger, 20558 20559 /** 20560 * _createNotification creates Notification instance 20561 * 20562 * @param {String} 20563 * title title string should be displayed in the Toaster 20564 * @param {Object} 20565 * options JSON object for notification options. 20566 */ 20567 _createNotification = function(title, options) { 20568 var notification = new window.Notification(title, options); 20569 return notification; 20570 }, 20571 20572 /** 20573 * _setAutoClose set the auto close time for toaster, it checks if 20574 * client code passed custom time out for the toaster, otherwise it set 20575 * the AUTO_CLOSE_TIME 20576 * 20577 * @param {Object} 20578 * notification window.Notification that creates Html5 20579 * Notification. 20580 * @param {String} 20581 * autoClose autoClose time of the Toaster 20582 * @return toasterTimeout Toaster Timeout 20583 */ 20584 _setAutoClose = function(notification, autoClose) { 20585 20586 // check if custom close time passed other wise set 20587 // DEFAULT_AUTO_CLOSE 20588 var autoCloseTime = (autoClose && !isNaN(autoClose)) ? autoClose 20589 : AUTO_CLOSE_TIME, 20590 // set the time out for notification toaster 20591 toasterTimer = setTimeout(function() { 20592 notification.close(); 20593 }, autoCloseTime); 20594 20595 return toasterTimer; 20596 20597 }, 20598 20599 /** This method will request permission to display Toaster. */ 20600 _requestPermission = function() { 20601 // If they are not denied (i.e. default) 20602 if (window.Notification 20603 && window.Notification.permission !== PERMISSION_DENIED) { 20604 // Request permission 20605 window.Notification 20606 .requestPermission(function(status) { 20607 20608 // Change based on user's decision 20609 if (window.Notification.permission !== status) { 20610 window.Notification.permission = status; 20611 } 20612 _logger 20613 .log("FinesseToaster.requestPermission(): request permission status " 20614 + status); 20615 20616 }); 20617 20618 } else { 20619 _logger 20620 .log("FinesseToaster.requestPermission(): Notification not supported or permission denied."); 20621 } 20622 20623 }, 20624 20625 /** 20626 * This method will add onclick and onerror listener to Notification. 20627 * 20628 * @param {Object} 20629 * notification window.Notification that creates Html5 20630 * Notification. 20631 * @param {Object} 20632 * options JSON object for notification options. 20633 */ 20634 _addToasterListeners = function(notification, options, toasterTimer) { 20635 // this is onlcik handler of toaster. this handler will be invoked 20636 // on click of toaster 20637 notification.onclick = function() { 20638 // in case of manually closed toaster, stop the notification 20639 // auto close method to be invoked 20640 clearTimeout(toasterTimer); 20641 // This will maximize/activate chrome browser on click of 20642 // toaster. this handling required only in case of Chrome 20643 if (window.chrome) { 20644 window.focus(); 20645 } 20646 20647 if (options && options.onclick) { 20648 options.onclick(); 20649 } 20650 20651 //close toaster upon click 20652 this.close(); 20653 20654 }; 20655 20656 // this is onerror handler of toaster, if there is any error while 20657 // loading toaster this hadnler will be invoked 20658 notification.onerror = function() { 20659 if (options !== undefined && options.onerror) { 20660 options.onerror(); 20661 } 20662 }; 20663 }; 20664 20665 return { 20666 20667 /** 20668 * @class 20669 * FinesseToaster is a utility class to show toaster 20670 * notification in Finesse. FinesseToaster leverages <a 20671 * href="https://www.w3.org/TR/notifications/">HTML5 20672 * Notification</a> API to display Toaster Notification. 20673 * <p> <a 20674 * href="https://developer.mozilla.org/en/docs/Web/API/notification#Browser_compatibility">For 20675 * HTML5 Notification API and browser compatibility, please click 20676 * here.</a></p> 20677 * 20678 * @constructs 20679 */ 20680 _fakeConstuctor: function () { 20681 20682 }, 20683 /** 20684 * TOASTER_DEFAULT_ICONS constants has list of predefined icons (e.g INCOMING_CALL_ICON). 20685 * <p><b>Constant list</b></p> 20686 * <ul> 20687 * <li>TOASTER_DEFAULT_ICONS.INCOMING_CALL_ICON</li> 20688 * </ul> 20689 */ 20690 TOASTER_DEFAULT_ICONS : { 20691 INCOMING_CALL_ICON : ICON_PATH + "incoming_call.png" 20692 }, 20693 20694 20695 /** 20696 * <b>showToaster </b>: shows Toaster Notification. 20697 * 20698 * @param {String} 20699 * <b>title</b> : title string should be displayed in the Toaster 20700 * @param {Object} 20701 * options is JSON object for notification options. 20702 * <ul> 20703 * <li><b>options</b> = { </li> 20704 * <li><b>body</b> : The body string of the notification as 20705 * specified in the options parameter of the constructor.</li> 20706 * <li><b>icon</b>: The URL of the image used as an icon of the 20707 * notification as specified in the options parameter of 20708 * the constructor.</li> 20709 * <li><b>autoClose</b> : custom auto close time of the toaster</li> 20710 * <li><b>showWhenVisible</b> : 'true' toaster shows up even when page is 20711 * visible,'false' toaster shows up only when page is invisible </li> 20712 * <li> }</li> 20713 * </ul> 20714 * 20715 */ 20716 showToaster : function(title, options) { 20717 20718 var notification, toasterTimer; 20719 20720 // If notifications are granted show the notification 20721 if (window.Notification 20722 && window.Notification.permission === PERMISSION_GRANTED) { 20723 20724 // document.hasFocus() used over document.hidden to keep the consistent behavior across mozilla/chrome 20725 if(document.hasFocus()===false || options.showWhenVisible) { 20726 if (_logger && AUTO_CLOSE_TIME > -1) { 20727 notification = _createNotification(title, options); 20728 20729 // set the auto close time out of the toaster 20730 toasterTimer = _setAutoClose(notification, 20731 options.autoClose); 20732 20733 // and Toaster Event listeners. eg. onclick , onerror. 20734 _addToasterListeners(notification, options, 20735 toasterTimer); 20736 } else { 20737 _logger 20738 .log("FinesseToaster is not initialised, call FineeseToaster.init() before showToaster"); 20739 } 20740 } else { 20741 _logger 20742 .log("FinesseToaster supressed : Page is visible and FineeseToaster.options.showWhenVisible is false"); 20743 } 20744 20745 } 20746 20747 return notification; 20748 }, 20749 20750 /** 20751 * initialize FininseToaster and inject dependencies. this method 20752 * will also request permission in browser from user to display 20753 * Toaster Notification. 20754 * 20755 * @param {Object} 20756 * finesse.cslogger.ClientLogger 20757 * @return finesse.containerservices.FinesseToaster 20758 */ 20759 init : function(logger) { 20760 // This is for injecting mocked logger. 20761 if (logger) { 20762 _logger = logger; 20763 } else { 20764 _logger = finesse.cslogger.ClientLogger; 20765 } 20766 20767 //set default toaster notification timeout 20768 if (finesse.container.Config.toasterNotificationDefaultTimeout) { 20769 AUTO_CLOSE_TIME = finesse.container.Config.toasterNotificationDefaultTimeout; 20770 } else { 20771 AUTO_CLOSE_TIME = 0; 20772 _logger 20773 .log("Finesse Toaster Notification Default Timeout not configured in desktop.properties"); 20774 } 20775 20776 // Request permission 20777 _requestPermission(); 20778 return finesse.containerservices.FinesseToaster; 20779 } 20780 }; 20781 20782 }()); 20783 20784 window.finesse = window.finesse || {}; 20785 window.finesse.containerservices = window.finesse.containerservices || {}; 20786 window.finesse.containerservices.FinesseToaster = FinesseToaster; 20787 20788 return FinesseToaster; 20789 }); 20790 20791 /** 20792 * This "interface" is just a way to easily jsdoc the Object callback handlers. 20793 * 20794 * @requires finesse.clientservices.ClientServices 20795 * @requires Class 20796 */ 20797 /** @private */ 20798 define('interfaces/RestObjectHandlers',[ 20799 "FinesseBase", 20800 "utilities/Utilities", 20801 "restservices/Notifier", 20802 "clientservices/ClientServices", 20803 "clientservices/Topics" 20804 ], 20805 function () { 20806 20807 var RestObjectHandlers = ( function () { /** @lends finesse.interfaces.RestObjectHandlers.prototype */ 20808 20809 return { 20810 20811 /** 20812 * @class 20813 * This "interface" defines REST Object callback handlers, passed as an argument to 20814 * Object getter methods in cases where the Object is going to be created. 20815 * 20816 * @param {Object} [handlers] 20817 * An object containing callback handlers for instantiation and runtime 20818 * Callback to invoke upon successful instantiation, passes in REST object. 20819 * @param {Function} [handlers.onLoad(this)] 20820 * Callback to invoke upon loading the data for the first time. 20821 * @param {Function} [handlers.onChange(this)] 20822 * Callback to invoke upon successful update object (PUT) 20823 * @param {Function} [handlers.onAdd(this)] 20824 * Callback to invoke upon successful update to add object (POST) 20825 * @param {Function} [handlers.onDelete(this)] 20826 * Callback to invoke upon successful update to delete object (DELETE) 20827 * @param {Function} [handlers.onError(rsp)] 20828 * Callback to invoke on update error (refresh or event) 20829 * as passed by finesse.restservices.RestBase.restRequest()<br> 20830 * {<br> 20831 * status: {Number} The HTTP status code returned<br> 20832 * content: {String} Raw string of response<br> 20833 * object: {Object} Parsed object of response<br> 20834 * error: {Object} Wrapped exception that was caught<br> 20835 * error.errorType: {String} Type of error that was caught<br> 20836 * error.errorMessage: {String} Message associated with error<br> 20837 * }<br> 20838 * <br> 20839 * Note that RestCollections have two additional callback handlers:<br> 20840 * <br> 20841 * @param {Function} [handlers.onCollectionAdd(this)]: when an object is added to this collection 20842 * @param {Function} [handlers.onCollectionDelete(this)]: when an object is removed from this collection 20843 20844 * @constructs 20845 */ 20846 _fakeConstuctor: function () { 20847 /* This is here to enable jsdoc to document this as a class. */ 20848 } 20849 }; 20850 }()); 20851 20852 window.finesse = window.finesse || {}; 20853 window.finesse.interfaces = window.finesse.interfaces || {}; 20854 window.finesse.interfaces.RestObjectHandlers = RestObjectHandlers; 20855 20856 return RestObjectHandlers; 20857 20858 }); 20859 20860 20861 /** 20862 * This "interface" is just a way to easily jsdoc the REST request handlers. 20863 * 20864 * @requires finesse.clientservices.ClientServices 20865 * @requires Class 20866 */ 20867 /** @private */ 20868 define('interfaces/RequestHandlers',[ 20869 "FinesseBase", 20870 "utilities/Utilities", 20871 "restservices/Notifier", 20872 "clientservices/ClientServices", 20873 "clientservices/Topics" 20874 ], 20875 function () { 20876 20877 var RequestHandlers = ( function () { /** @lends finesse.interfaces.RequestHandlers.prototype */ 20878 20879 return { 20880 20881 /** 20882 * @class 20883 * This "interface" defines REST Object callback handlers, passed as an argument to 20884 * Object getter methods in cases where the Object is going to be created. 20885 * 20886 * @param {Object} handlers 20887 * An object containing the following (optional) handlers for the request:<ul> 20888 * <li><b>success(rsp):</b> A callback function for a successful request to be invoked with the following 20889 * response object as its only parameter:<ul> 20890 * <li><b>status:</b> {Number} The HTTP status code returned</li> 20891 * <li><b>content:</b> {String} Raw string of response</li> 20892 * <li><b>object:</b> {Object} Parsed object of response</li></ul> 20893 * <li><b>error(rsp):</b> An error callback function for an unsuccessful request to be invoked with the 20894 * error response object as its only parameter:<ul> 20895 * <li><b>status:</b> {Number} The HTTP status code returned</li> 20896 * <li><b>content:</b> {String} Raw string of response</li> 20897 * <li><b>object:</b> {Object} Parsed object of response (HTTP errors)</li> 20898 * <li><b>error:</b> {Object} Wrapped exception that was caught:<ul> 20899 * <li><b>errorType:</b> {String} Type of error that was caught</li> 20900 * <li><b>errorMessage:</b> {String} Message associated with error</li> 20901 * </ul></li> 20902 * </ul> 20903 20904 * @constructs 20905 */ 20906 _fakeConstuctor: function () { 20907 /* This is here to enable jsdoc to document this as a class. */ 20908 } 20909 }; 20910 }()); 20911 20912 window.finesse = window.finesse || {}; 20913 window.finesse.interfaces = window.finesse.interfaces || {}; 20914 window.finesse.interfaces.RequestHandlers = RequestHandlers; 20915 20916 finesse = finesse || {}; 20917 /** @namespace These interfaces are just a convenience for documenting common parameter structures. */ 20918 finesse.interfaces = finesse.interfaces || {}; 20919 20920 return RequestHandlers; 20921 20922 }); 20923 20924 20925 20926 define('gadget/Config',[ 20927 "utilities/Utilities" 20928 ], function (Utilities) { 20929 var Config = (function () { /** @lends finesse.gadget.Config.prototype */ 20930 20931 if (gadgets && gadgets.Prefs) { 20932 20933 var _prefs = new gadgets.Prefs(); 20934 20935 return { 20936 /** 20937 * The base64 encoded "id:password" string used for authentication. 20938 */ 20939 authorization: Utilities.getUserAuthString(), 20940 20941 /** 20942 * The auth token string used for authentication in SSO deployments. 20943 */ 20944 authToken: Utilities.getToken(), 20945 20946 /** 20947 * The country code of the client (derived from locale). 20948 */ 20949 country: _prefs.getString("country"), 20950 20951 /** 20952 * The language code of the client (derived from locale). 20953 */ 20954 language: _prefs.getString("language"), 20955 20956 /** 20957 * The locale of the client. 20958 */ 20959 locale: _prefs.getString("locale"), 20960 20961 /** 20962 * The Finesse server IP/host as reachable from the browser. 20963 */ 20964 host: _prefs.getString("host"), 20965 20966 /** 20967 * The Finesse server host's port reachable from the browser. 20968 */ 20969 hostPort: _prefs.getString("hostPort"), 20970 20971 /** 20972 * The extension of the user. 20973 */ 20974 extension: _prefs.getString("extension"), 20975 20976 /** 20977 * One of the work modes found in {@link finesse.restservices.User.WorkMode}, or something false (undefined) for a normal login. 20978 */ 20979 mobileAgentMode: _prefs.getString("mobileAgentMode"), 20980 20981 /** 20982 * The dial number to use for mobile agent, or something false (undefined) for a normal login. 20983 */ 20984 mobileAgentDialNumber: _prefs.getString("mobileAgentDialNumber"), 20985 20986 /** 20987 * The domain of the XMPP server. 20988 */ 20989 xmppDomain: _prefs.getString("xmppDomain"), 20990 20991 /** 20992 * The pub sub domain where the pub sub service is running. 20993 */ 20994 pubsubDomain: _prefs.getString("pubsubDomain"), 20995 20996 /** 20997 * The Finesse API IP/host as reachable from the gadget container. 20998 */ 20999 restHost: _prefs.getString("restHost"), 21000 21001 /** 21002 * The type of HTTP protocol (http or https). 21003 */ 21004 scheme: _prefs.getString("scheme"), 21005 21006 /** 21007 * The localhost fully qualified domain name. 21008 */ 21009 localhostFQDN: _prefs.getString("localhostFQDN"), 21010 21011 /** 21012 * The localhost port. 21013 */ 21014 localhostPort: _prefs.getString("localhostPort"), 21015 21016 /** 21017 * The id of the team the user belongs to. 21018 */ 21019 teamId: _prefs.getString("teamId"), 21020 21021 /** 21022 * The name of the team the user belongs to. 21023 */ 21024 teamName: _prefs.getString("teamName"), 21025 21026 /** 21027 * The drift time between the client and the server in milliseconds. 21028 */ 21029 clientDriftInMillis: _prefs.getInt("clientDriftInMillis"), 21030 21031 /** 21032 * The client compatibility mode configuration (true if it is or false otherwise). 21033 */ 21034 compatibilityMode: _prefs.getString("compatibilityMode"), 21035 21036 /** 21037 * The peripheral Id that Finesse is connected to. 21038 */ 21039 peripheralId: _prefs.getString("peripheralId"), 21040 21041 /** 21042 * The auth mode of the finesse deployment. 21043 */ 21044 systemAuthMode: _prefs.getString("systemAuthMode"), 21045 21046 /** 21047 * @class 21048 * The Config object for gadgets within the Finesse desktop container which 21049 * contains configuration data provided by the container page. 21050 * @constructs 21051 */ 21052 _fakeConstructor : function () {} // For JS Doc to work need a constructor so that the lends/constructs build the doc properly 21053 21054 }; 21055 } else { 21056 return {}; 21057 } 21058 }()); 21059 21060 /** Assign to container and gadget namespace to have config available in both */ 21061 window.finesse = window.finesse || {}; 21062 window.finesse.container = window.finesse.container || {}; 21063 window.finesse.container.Config = window.finesse.container.Config || Config; 21064 21065 window.finesse.gadget = window.finesse.gadget || {}; 21066 window.finesse.gadget.Config = Config; 21067 21068 return Config; 21069 }); 21070 define('finesse',[ 21071 'restservices/Users', 21072 'restservices/Teams', 21073 'restservices/SystemInfo', 21074 'restservices/Media', 21075 'restservices/MediaDialogs', 21076 'restservices/DialogLogoutActions', 21077 'restservices/InterruptActions', 21078 'utilities/I18n', 21079 'utilities/Logger', 21080 'utilities/SaxParser', 21081 'utilities/BackSpaceHandler', 21082 'cslogger/ClientLogger', 21083 'cslogger/FinesseLogger', 21084 'containerservices/ContainerServices', 21085 'containerservices/FinesseToaster', 21086 'interfaces/RestObjectHandlers', 21087 'interfaces/RequestHandlers', 21088 'gadget/Config' 21089 ], 21090 function () { 21091 return window.finesse; 21092 }); 21093 21094 require(["finesse"]); 21095 return require('finesse'); })); 21096 21097 // Prevent other JS files from wiping out window.finesse from the namespace 21098 var finesse = window.finesse;